Skip to content

Commit 73c65a7

Browse files
committed
Add additional unit tests
1 parent 08c13d5 commit 73c65a7

File tree

1 file changed

+179
-11
lines changed

1 file changed

+179
-11
lines changed

ruby/test/ci/queue/strategy/suite_bin_packing_test.rb

Lines changed: 179 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@
55
class SuiteBinPackingTest < Minitest::Test
66
def setup
77
@config = CI::Queue::Configuration.new(
8-
suite_max_duration: 120_000,
8+
minimum_max_chunk_duration: 120_000,
9+
maximum_max_chunk_duration: 300_000,
910
suite_buffer_percent: 10,
1011
timing_fallback_duration: 100.0
1112
)
@@ -67,7 +68,8 @@ def test_splits_suite_when_over_max_duration
6768
end
6869

6970
def test_applies_buffer_when_splitting
70-
@config.suite_max_duration = 100_000
71+
@config.minimum_max_chunk_duration = 100_000
72+
@config.maximum_max_chunk_duration = 100_000
7173
@config.suite_buffer_percent = 10
7274

7375
tests = create_mock_tests(['TestSuite#test_1', 'TestSuite#test_2'])
@@ -186,7 +188,8 @@ def test_chunk_id_format
186188
end
187189

188190
def test_suite_exactly_at_max_duration_gets_split
189-
@config.suite_max_duration = 100_000
191+
@config.minimum_max_chunk_duration = 100_000
192+
@config.maximum_max_chunk_duration = 100_000
190193
@config.suite_buffer_percent = 10
191194

192195
tests = create_mock_tests(['TestSuite#test_1', 'TestSuite#test_2'])
@@ -204,7 +207,8 @@ def test_suite_exactly_at_max_duration_gets_split
204207
end
205208

206209
def test_suite_exactly_at_effective_max_fits_in_one_chunk
207-
@config.suite_max_duration = 100_000
210+
@config.minimum_max_chunk_duration = 100_000
211+
@config.maximum_max_chunk_duration = 100_000
208212
@config.suite_buffer_percent = 10
209213

210214
tests = create_mock_tests(['TestSuite#test_1', 'TestSuite#test_2'])
@@ -282,7 +286,8 @@ def test_test_count_is_set_correctly
282286
end
283287

284288
def test_large_suite_creates_multiple_chunks
285-
@config.suite_max_duration = 100_000
289+
@config.minimum_max_chunk_duration = 100_000
290+
@config.maximum_max_chunk_duration = 100_000
286291
@config.suite_buffer_percent = 10
287292

288293
# Create a suite that will need many chunks
@@ -462,7 +467,7 @@ def test_dynamic_max_duration_calculation
462467
# Total duration = 40,000ms
463468
# Parallel jobs = 4
464469
# Calculated base_max_duration = 40,000 / 4 = 10,000ms
465-
# However, 10,000ms < configured @max_duration (120,000ms), so floor logic applies
470+
# However, 10,000ms < configured minimum_max_chunk_duration (120,000ms), so floor logic applies
466471
# Actual max_duration used = max(10,000, 120,000) = 120,000ms
467472
# With 10% buffer, effective_max = 120,000 * 0.9 = 108,000ms
468473
# All 4 tests fit in one chunk: 4 * 10,000 = 40,000ms < 108,000ms
@@ -482,7 +487,7 @@ def test_dynamic_max_duration_falls_back_to_configured_when_no_env_var
482487

483488
chunks = order_with_timing(tests, timing_data)
484489

485-
# Should use configured max_duration (120,000ms default)
490+
# Should use configured minimum_max_chunk_duration (120,000ms default)
486491
# So test should fit in one chunk
487492
assert_equal 1, chunks.size
488493
end
@@ -497,7 +502,7 @@ def test_dynamic_max_duration_uses_floor_when_calculated_value_too_small
497502
chunks = order_with_timing(tests, timing_data)
498503

499504
# Calculated max = 1000 / 100 = 10ms (too small)
500-
# Should use configured max_duration (120,000ms) as floor
505+
# Should use configured minimum_max_chunk_duration (120,000ms) as floor
501506
# So test should fit in one chunk
502507
assert_equal 1, chunks.size
503508
ensure
@@ -518,7 +523,7 @@ def test_dynamic_max_duration_with_large_parallelism
518523
# Total duration = 20 * 5000 = 100,000ms
519524
# Parallel jobs = 10
520525
# Calculated base_max_duration = 100,000 / 10 = 10,000ms
521-
# However, 10,000ms < configured @max_duration (120,000ms), so floor logic applies
526+
# However, 10,000ms < configured minimum_max_chunk_duration (120,000ms), so floor logic applies
522527
# Actual max_duration used = max(10,000, 120,000) = 120,000ms
523528
# With 10% buffer, effective_max = 120,000 * 0.9 = 108,000ms
524529
# All 20 tests fit in one chunk: 20 * 5,000 = 100,000ms < 108,000ms
@@ -530,7 +535,7 @@ def test_dynamic_max_duration_with_large_parallelism
530535
end
531536

532537
def test_dynamic_max_duration_exceeds_default_max_causes_splits
533-
# Set up scenario where computed chunk size > default max_duration
538+
# Set up scenario where computed chunk size > default minimum_max_chunk_duration
534539
ENV['BUILDKITE_PARALLEL_JOB_COUNT'] = '2'
535540

536541
# Create tests with large total duration
@@ -544,7 +549,7 @@ def test_dynamic_max_duration_exceeds_default_max_causes_splits
544549
# Total duration = 10 * 30,000 = 300,000ms
545550
# Parallel jobs = 2
546551
# Calculated base_max_duration = 300,000 / 2 = 150,000ms
547-
# 150,000ms > configured @max_duration (120,000ms), so use calculated value
552+
# 150,000ms > configured minimum_max_chunk_duration (120,000ms), so use calculated value
548553
# Actual max_duration used = max(150,000, 120,000) = 150,000ms
549554
# With 10% buffer, effective_max = 150,000 * 0.9 = 135,000ms
550555
# Each test is 30,000ms, so we can fit 4 per chunk (4 * 30,000 = 120,000 < 135,000)
@@ -560,6 +565,169 @@ def test_dynamic_max_duration_exceeds_default_max_causes_splits
560565
ENV.delete('BUILDKITE_PARALLEL_JOB_COUNT')
561566
end
562567

568+
def test_dynamic_max_duration_capped_at_maximum
569+
# Set up scenario where computed chunk size would exceed maximum_max_chunk_duration
570+
ENV['BUILDKITE_PARALLEL_JOB_COUNT'] = '1'
571+
572+
# Create tests with very large total duration
573+
tests = create_mock_tests((1..10).map { |i| "TestSuite#test_#{i}" })
574+
timing_data = (1..10).each_with_object({}) do |i, hash|
575+
hash["TestSuite#test_#{i}"] = 80_000.0 # Each test is 80 seconds
576+
end
577+
578+
chunks = order_with_timing(tests, timing_data)
579+
580+
# Total duration = 10 * 80,000 = 800,000ms
581+
# Parallel jobs = 1
582+
# Calculated base_max_duration = 800,000 / 1 = 800,000ms
583+
# 800,000ms > configured maximum_max_chunk_duration (300,000ms), so cap it
584+
# Actual max_duration used = min(800,000, 300,000) = 300,000ms
585+
# With 10% buffer, effective_max = 300,000 * 0.9 = 270,000ms
586+
# Each test is 80,000ms, so we can fit 3 per chunk (3 * 80,000 = 240,000 < 270,000)
587+
# With 10 tests, we'll get 4 chunks (3+3+3+1)
588+
test_suite_chunks = chunks.select { |c| c.suite_name == 'TestSuite' }
589+
assert_equal 4, test_suite_chunks.size, 'Should cap at maximum_max_chunk_duration'
590+
591+
# Verify chunks respect the capped max_duration
592+
test_suite_chunks[0..2].each do |chunk|
593+
assert_equal 3, chunk.test_ids.size, 'First 3 chunks should have 3 tests each'
594+
assert_equal 240_000.0, chunk.estimated_duration
595+
end
596+
assert_equal 1, test_suite_chunks[3].test_ids.size, 'Last chunk should have 1 test'
597+
assert_equal 80_000.0, test_suite_chunks[3].estimated_duration
598+
ensure
599+
ENV.delete('BUILDKITE_PARALLEL_JOB_COUNT')
600+
end
601+
602+
def test_dynamic_max_duration_within_range
603+
# Set up scenario where computed chunk size falls within the configured range
604+
ENV['BUILDKITE_PARALLEL_JOB_COUNT'] = '5'
605+
606+
tests = create_mock_tests((1..10).map { |i| "TestSuite#test_#{i}" })
607+
timing_data = (1..10).each_with_object({}) do |i, hash|
608+
hash["TestSuite#test_#{i}"] = 20_000.0 # Each test is 20 seconds
609+
end
610+
611+
chunks = order_with_timing(tests, timing_data)
612+
613+
# Total duration = 10 * 20,000 = 200,000ms
614+
# Parallel jobs = 5
615+
# Calculated base_max_duration = 200,000 / 5 = 40,000ms
616+
# 40,000ms < minimum_max_chunk_duration (120,000ms), so use minimum
617+
# Actual max_duration used = max(40,000, 120,000) = 120,000ms
618+
# With 10% buffer, effective_max = 120,000 * 0.9 = 108,000ms
619+
# Each test is 20,000ms, so we can fit 5 per chunk (5 * 20,000 = 100,000 < 108,000)
620+
# With 10 tests, we'll get 2 chunks (5+5)
621+
test_suite_chunks = chunks.select { |c| c.suite_name == 'TestSuite' }
622+
assert_equal 2, test_suite_chunks.size, 'Should create 2 chunks with minimum floor applied'
623+
624+
test_suite_chunks.each do |chunk|
625+
assert_equal 5, chunk.test_ids.size, 'Each chunk should have 5 tests'
626+
assert_equal 100_000.0, chunk.estimated_duration
627+
end
628+
ensure
629+
ENV.delete('BUILDKITE_PARALLEL_JOB_COUNT')
630+
end
631+
632+
def test_range_based_max_duration_with_custom_values
633+
# Test with custom minimum and maximum values
634+
@config.minimum_max_chunk_duration = 60_000
635+
@config.maximum_max_chunk_duration = 180_000
636+
637+
ENV['BUILDKITE_PARALLEL_JOB_COUNT'] = '2'
638+
639+
tests = create_mock_tests((1..10).map { |i| "TestSuite#test_#{i}" })
640+
timing_data = (1..10).each_with_object({}) do |i, hash|
641+
hash["TestSuite#test_#{i}"] = 20_000.0 # Each test is 20 seconds
642+
end
643+
644+
chunks = order_with_timing(tests, timing_data)
645+
646+
# Total duration = 10 * 20,000 = 200,000ms
647+
# Parallel jobs = 2
648+
# Calculated base_max_duration = 200,000 / 2 = 100,000ms
649+
# 100,000ms is within range [60,000, 180,000], so use it
650+
# With 10% buffer, effective_max = 100,000 * 0.9 = 90,000ms
651+
# Each test is 20,000ms, so we can fit 4 per chunk (4 * 20,000 = 80,000 < 90,000)
652+
# With 10 tests, we'll get 3 chunks (4+4+2)
653+
test_suite_chunks = chunks.select { |c| c.suite_name == 'TestSuite' }
654+
assert_equal 3, test_suite_chunks.size, 'Should create 3 chunks with calculated max_duration'
655+
656+
assert_equal 4, test_suite_chunks[0].test_ids.size, 'First chunk should have 4 tests'
657+
assert_equal 80_000.0, test_suite_chunks[0].estimated_duration
658+
assert_equal 4, test_suite_chunks[1].test_ids.size, 'Second chunk should have 4 tests'
659+
assert_equal 80_000.0, test_suite_chunks[1].estimated_duration
660+
assert_equal 2, test_suite_chunks[2].test_ids.size, 'Third chunk should have 2 tests'
661+
assert_equal 40_000.0, test_suite_chunks[2].estimated_duration
662+
ensure
663+
ENV.delete('BUILDKITE_PARALLEL_JOB_COUNT')
664+
end
665+
666+
def test_maximum_max_duration_prevents_oversized_chunks
667+
# Test that maximum prevents chunks from being too large
668+
@config.minimum_max_chunk_duration = 50_000
669+
@config.maximum_max_chunk_duration = 100_000
670+
671+
ENV['BUILDKITE_PARALLEL_JOB_COUNT'] = '1'
672+
673+
tests = create_mock_tests((1..5).map { |i| "TestSuite#test_#{i}" })
674+
timing_data = (1..5).each_with_object({}) do |i, hash|
675+
hash["TestSuite#test_#{i}"] = 40_000.0 # Each test is 40 seconds
676+
end
677+
678+
chunks = order_with_timing(tests, timing_data)
679+
680+
# Total duration = 5 * 40,000 = 200,000ms
681+
# Parallel jobs = 1
682+
# Calculated base_max_duration = 200,000 / 1 = 200,000ms
683+
# 200,000ms > maximum_max_chunk_duration (100,000ms), so cap it
684+
# Actual max_duration used = min(200,000, 100,000) = 100,000ms
685+
# With 10% buffer, effective_max = 100,000 * 0.9 = 90,000ms
686+
# Each test is 40,000ms, so we can fit 2 per chunk (2 * 40,000 = 80,000 < 90,000)
687+
# With 5 tests, we'll get 3 chunks (2+2+1)
688+
test_suite_chunks = chunks.select { |c| c.suite_name == 'TestSuite' }
689+
assert_equal 3, test_suite_chunks.size, 'Should cap at maximum and create smaller chunks'
690+
691+
assert_equal 2, test_suite_chunks[0].test_ids.size
692+
assert_equal 2, test_suite_chunks[1].test_ids.size
693+
assert_equal 1, test_suite_chunks[2].test_ids.size
694+
ensure
695+
ENV.delete('BUILDKITE_PARALLEL_JOB_COUNT')
696+
end
697+
698+
def test_minimum_max_duration_prevents_undersized_chunks
699+
# Test that minimum prevents chunks from being too small
700+
@config.minimum_max_chunk_duration = 200_000
701+
@config.maximum_max_chunk_duration = 500_000
702+
703+
ENV['BUILDKITE_PARALLEL_JOB_COUNT'] = '20'
704+
705+
tests = create_mock_tests((1..10).map { |i| "TestSuite#test_#{i}" })
706+
timing_data = (1..10).each_with_object({}) do |i, hash|
707+
hash["TestSuite#test_#{i}"] = 20_000.0 # Each test is 20 seconds
708+
end
709+
710+
chunks = order_with_timing(tests, timing_data)
711+
712+
# Total duration = 10 * 20,000 = 200,000ms
713+
# Parallel jobs = 20
714+
# Calculated base_max_duration = 200,000 / 20 = 10,000ms
715+
# 10,000ms < minimum_max_chunk_duration (200,000ms), so use minimum
716+
# Actual max_duration used = max(10,000, 200,000) = 200,000ms
717+
# With 10% buffer, effective_max = 200,000 * 0.9 = 180,000ms
718+
# Each test is 20,000ms, so we can fit 9 per chunk (9 * 20,000 = 180,000 = 180,000)
719+
# With 10 tests, we'll get 2 chunks (9+1)
720+
test_suite_chunks = chunks.select { |c| c.suite_name == 'TestSuite' }
721+
assert_equal 2, test_suite_chunks.size, 'Should use minimum and create larger chunks'
722+
723+
assert_equal 9, test_suite_chunks[0].test_ids.size, 'First chunk should have 9 tests'
724+
assert_equal 180_000.0, test_suite_chunks[0].estimated_duration
725+
assert_equal 1, test_suite_chunks[1].test_ids.size, 'Second chunk should have 1 test'
726+
assert_equal 20_000.0, test_suite_chunks[1].estimated_duration
727+
ensure
728+
ENV.delete('BUILDKITE_PARALLEL_JOB_COUNT')
729+
end
730+
563731
private
564732

565733
def create_mock_tests(test_ids)

0 commit comments

Comments
 (0)