@@ -84,7 +84,7 @@ func TestNoPrebuilds(t *testing.T) {
84
84
preset (true ,0 ,current ),
85
85
}
86
86
87
- snapshot := prebuilds .NewGlobalSnapshot (presets ,nil ,nil ,nil ,nil ,nil )
87
+ snapshot := prebuilds .NewGlobalSnapshot (presets ,nil ,nil ,nil ,nil ,nil , quartz . NewMock ( t ) )
88
88
ps ,err := snapshot .FilterByPreset (current .presetID )
89
89
require .NoError (t ,err )
90
90
@@ -106,7 +106,7 @@ func TestNetNew(t *testing.T) {
106
106
preset (true ,1 ,current ),
107
107
}
108
108
109
- snapshot := prebuilds .NewGlobalSnapshot (presets ,nil ,nil ,nil ,nil ,nil )
109
+ snapshot := prebuilds .NewGlobalSnapshot (presets ,nil ,nil ,nil ,nil ,nil , quartz . NewMock ( t ) )
110
110
ps ,err := snapshot .FilterByPreset (current .presetID )
111
111
require .NoError (t ,err )
112
112
@@ -148,7 +148,7 @@ func TestOutdatedPrebuilds(t *testing.T) {
148
148
var inProgress []database.CountInProgressPrebuildsRow
149
149
150
150
// WHEN: calculating the outdated preset's state.
151
- snapshot := prebuilds .NewGlobalSnapshot (presets ,nil ,running ,inProgress ,nil ,nil )
151
+ snapshot := prebuilds .NewGlobalSnapshot (presets ,nil ,running ,inProgress ,nil ,nil , quartz . NewMock ( t ) )
152
152
ps ,err := snapshot .FilterByPreset (outdated .presetID )
153
153
require .NoError (t ,err )
154
154
@@ -214,7 +214,7 @@ func TestDeleteOutdatedPrebuilds(t *testing.T) {
214
214
}
215
215
216
216
// WHEN: calculating the outdated preset's state.
217
- snapshot := prebuilds .NewGlobalSnapshot (presets ,nil ,running ,inProgress ,nil ,nil )
217
+ snapshot := prebuilds .NewGlobalSnapshot (presets ,nil ,running ,inProgress ,nil ,nil , quartz . NewMock ( t ) )
218
218
ps ,err := snapshot .FilterByPreset (outdated .presetID )
219
219
require .NoError (t ,err )
220
220
@@ -459,7 +459,7 @@ func TestInProgressActions(t *testing.T) {
459
459
}
460
460
461
461
// WHEN: calculating the current preset's state.
462
- snapshot := prebuilds .NewGlobalSnapshot (presets ,nil ,running ,inProgress ,nil ,nil )
462
+ snapshot := prebuilds .NewGlobalSnapshot (presets ,nil ,running ,inProgress ,nil ,nil , quartz . NewMock ( t ) )
463
463
ps ,err := snapshot .FilterByPreset (current .presetID )
464
464
require .NoError (t ,err )
465
465
@@ -502,7 +502,7 @@ func TestExtraneous(t *testing.T) {
502
502
var inProgress []database.CountInProgressPrebuildsRow
503
503
504
504
// WHEN: calculating the current preset's state.
505
- snapshot := prebuilds .NewGlobalSnapshot (presets ,nil ,running ,inProgress ,nil ,nil )
505
+ snapshot := prebuilds .NewGlobalSnapshot (presets ,nil ,running ,inProgress ,nil ,nil , quartz . NewMock ( t ) )
506
506
ps ,err := snapshot .FilterByPreset (current .presetID )
507
507
require .NoError (t ,err )
508
508
@@ -683,7 +683,7 @@ func TestExpiredPrebuilds(t *testing.T) {
683
683
}
684
684
685
685
// WHEN: calculating the current preset's state.
686
- snapshot := prebuilds .NewGlobalSnapshot (presets ,nil ,running ,nil ,nil ,nil )
686
+ snapshot := prebuilds .NewGlobalSnapshot (presets ,nil ,running ,nil ,nil ,nil , quartz . NewMock ( t ) )
687
687
ps ,err := snapshot .FilterByPreset (current .presetID )
688
688
require .NoError (t ,err )
689
689
@@ -719,7 +719,7 @@ func TestDeprecated(t *testing.T) {
719
719
var inProgress []database.CountInProgressPrebuildsRow
720
720
721
721
// WHEN: calculating the current preset's state.
722
- snapshot := prebuilds .NewGlobalSnapshot (presets ,nil ,running ,inProgress ,nil ,nil )
722
+ snapshot := prebuilds .NewGlobalSnapshot (presets ,nil ,running ,inProgress ,nil ,nil , quartz . NewMock ( t ) )
723
723
ps ,err := snapshot .FilterByPreset (current .presetID )
724
724
require .NoError (t ,err )
725
725
@@ -772,7 +772,7 @@ func TestLatestBuildFailed(t *testing.T) {
772
772
}
773
773
774
774
// WHEN: calculating the current preset's state.
775
- snapshot := prebuilds .NewGlobalSnapshot (presets ,nil ,running ,inProgress ,backoffs ,nil )
775
+ snapshot := prebuilds .NewGlobalSnapshot (presets ,nil ,running ,inProgress ,backoffs ,nil , quartz . NewMock ( t ) )
776
776
psCurrent ,err := snapshot .FilterByPreset (current .presetID )
777
777
require .NoError (t ,err )
778
778
@@ -865,7 +865,7 @@ func TestMultiplePresetsPerTemplateVersion(t *testing.T) {
865
865
},
866
866
}
867
867
868
- snapshot := prebuilds .NewGlobalSnapshot (presets ,nil ,nil ,inProgress ,nil ,nil )
868
+ snapshot := prebuilds .NewGlobalSnapshot (presets ,nil ,nil ,inProgress ,nil ,nil , quartz . NewMock ( t ) )
869
869
870
870
// Nothing has to be created for preset 1.
871
871
{
@@ -905,6 +905,129 @@ func TestMultiplePresetsPerTemplateVersion(t *testing.T) {
905
905
}
906
906
}
907
907
908
+ func TestMultiplePresetsPerTemplateVersionV2 (t * testing.T ) {
909
+ t .Parallel ()
910
+
911
+ testCases := []struct {
912
+ name string
913
+ at time.Time
914
+ expectedInstances []int32
915
+ }{
916
+ {
917
+ name :"Before the 1st schedule" ,
918
+ at :mustParseTime (t ,time .RFC1123 ,"Mon, 02 Jun 2025 01:00:00 UTC" ),
919
+ expectedInstances : []int32 {1 ,1 },
920
+ },
921
+ {
922
+ name :"1st schedule" ,
923
+ at :mustParseTime (t ,time .RFC1123 ,"Mon, 02 Jun 2025 03:00:00 UTC" ),
924
+ expectedInstances : []int32 {2 ,1 },
925
+ },
926
+ {
927
+ name :"2nd schedule" ,
928
+ at :mustParseTime (t ,time .RFC1123 ,"Mon, 02 Jun 2025 07:00:00 UTC" ),
929
+ expectedInstances : []int32 {3 ,1 },
930
+ },
931
+ {
932
+ name :"3rd schedule" ,
933
+ at :mustParseTime (t ,time .RFC1123 ,"Mon, 02 Jun 2025 11:00:00 UTC" ),
934
+ expectedInstances : []int32 {1 ,4 },
935
+ },
936
+ {
937
+ name :"4th schedule" ,
938
+ at :mustParseTime (t ,time .RFC1123 ,"Mon, 02 Jun 2025 15:00:00 UTC" ),
939
+ expectedInstances : []int32 {1 ,5 },
940
+ },
941
+ }
942
+
943
+ for _ ,tc := range testCases {
944
+ t .Run (tc .name ,func (t * testing.T ) {
945
+ t .Parallel ()
946
+
947
+ templateID := uuid .New ()
948
+ templateVersionID := uuid .New ()
949
+ presetOpts1 := options {
950
+ templateID :templateID ,
951
+ templateVersionID :templateVersionID ,
952
+ presetID :uuid .New (),
953
+ presetName :"my-preset-1" ,
954
+ prebuiltWorkspaceID :uuid .New (),
955
+ workspaceName :"prebuilds1" ,
956
+ }
957
+ presetOpts2 := options {
958
+ templateID :templateID ,
959
+ templateVersionID :templateVersionID ,
960
+ presetID :uuid .New (),
961
+ presetName :"my-preset-2" ,
962
+ prebuiltWorkspaceID :uuid .New (),
963
+ workspaceName :"prebuilds2" ,
964
+ }
965
+
966
+ clock := quartz .NewMock (t )
967
+ clock .Set (tc .at )
968
+ enableAutoscaling := func (preset database.GetTemplatePresetsWithPrebuildsRow ) database.GetTemplatePresetsWithPrebuildsRow {
969
+ preset .AutoscalingEnabled = true
970
+ preset .AutoscalingTimezone = "UTC"
971
+ return preset
972
+ }
973
+ presets := []database.GetTemplatePresetsWithPrebuildsRow {
974
+ preset (true ,1 ,presetOpts1 ,enableAutoscaling ),
975
+ preset (true ,1 ,presetOpts2 ,enableAutoscaling ),
976
+ }
977
+ schedules := []database.TemplateVersionPresetPrebuildSchedule {
978
+ schedule (presets [0 ].ID ,"* 2-4 * * 1-5" ,2 ),
979
+ schedule (presets [0 ].ID ,"* 6-8 * * 1-5" ,3 ),
980
+ schedule (presets [1 ].ID ,"* 10-12 * * 1-5" ,4 ),
981
+ schedule (presets [1 ].ID ,"* 14-16 * * 1-5" ,5 ),
982
+ }
983
+
984
+ snapshot := prebuilds .NewGlobalSnapshot (presets ,schedules ,nil ,nil ,nil ,nil ,clock )
985
+
986
+ // Check 1st preset.
987
+ {
988
+ ps ,err := snapshot .FilterByPreset (presetOpts1 .presetID )
989
+ require .NoError (t ,err )
990
+
991
+ state := ps .CalculateState ()
992
+ actions ,err := ps .CalculateActions (clock ,backoffInterval )
993
+ require .NoError (t ,err )
994
+
995
+ validateState (t , prebuilds.ReconciliationState {
996
+ Starting :0 ,
997
+ Desired :tc .expectedInstances [0 ],
998
+ },* state )
999
+ validateActions (t , []* prebuilds.ReconciliationActions {
1000
+ {
1001
+ ActionType :prebuilds .ActionTypeCreate ,
1002
+ Create :tc .expectedInstances [0 ],
1003
+ },
1004
+ },actions )
1005
+ }
1006
+
1007
+ // Check 2nd preset.
1008
+ {
1009
+ ps ,err := snapshot .FilterByPreset (presetOpts2 .presetID )
1010
+ require .NoError (t ,err )
1011
+
1012
+ state := ps .CalculateState ()
1013
+ actions ,err := ps .CalculateActions (clock ,backoffInterval )
1014
+ require .NoError (t ,err )
1015
+
1016
+ validateState (t , prebuilds.ReconciliationState {
1017
+ Starting :0 ,
1018
+ Desired :tc .expectedInstances [1 ],
1019
+ },* state )
1020
+ validateActions (t , []* prebuilds.ReconciliationActions {
1021
+ {
1022
+ ActionType :prebuilds .ActionTypeCreate ,
1023
+ Create :tc .expectedInstances [1 ],
1024
+ },
1025
+ },actions )
1026
+ }
1027
+ })
1028
+ }
1029
+ }
1030
+
908
1031
func TestMatchesCron (t * testing.T ) {
909
1032
t .Parallel ()
910
1033
testCases := []struct {
@@ -1294,6 +1417,15 @@ func preset(active bool, instances int32, opts options, muts ...func(row databas
1294
1417
return entry
1295
1418
}
1296
1419
1420
+ func schedule (presetID uuid.UUID ,cronExpr string ,instances int32 ) database.TemplateVersionPresetPrebuildSchedule {
1421
+ return database.TemplateVersionPresetPrebuildSchedule {
1422
+ ID :uuid .New (),
1423
+ PresetID :presetID ,
1424
+ CronExpression :cronExpr ,
1425
+ Instances :instances ,
1426
+ }
1427
+ }
1428
+
1297
1429
func prebuiltWorkspace (
1298
1430
opts options ,
1299
1431
clock quartz.Clock ,