From 903e962dc01ecca8651e5251a63814ddc65727a5 Mon Sep 17 00:00:00 2001 From: SirOlaf <34164198+SirOlaf@users.noreply.github.com> Date: Wed, 9 Sep 2026 14:26:56 +0200 Subject: [PATCH] Attempt at speeding up CI (#26084) Co-authored-by: Andreas Rumpf --- testament/categories.nim | 9 ++++++++- testament/specs.nim | 6 +++++- testament/testament.nim | 29 ++++++++++++++++++++++++++++- 3 files changed, 41 insertions(+), 3 deletions(-) diff --git a/testament/categories.nim b/testament/categories.nim index 6e60d2595c..980a3f529c 100644 --- a/testament/categories.nim +++ b/testament/categories.nim @@ -592,10 +592,17 @@ proc changedModuleCount(changed: seq[string]): int = proc runMetamorphicIcTest(r: var TResults; file: string; cat: Category; options: string) = var test = TTest(cat: cat, name: file, options: options, - spec: initSpec(file), startTime: epochTime()) + spec: parseSpec(file), startTime: epochTime()) test.spec.targets = {targetC} inc r.total + # Metamorphic tests bypass `testSpec`, so honour Testament's batch selection + # here before creating their build directory or invoking `nim ic`. + if not test.spec.inCurrentBatch: + finishTest(r, test, targetC, "", "", "", reDisabled) + inc r.skipped + return + # Absolute paths: `nim ic` runs with `workingDir = buildDir`, so a relative # `--nimcache` would resolve against the build dir, not where we read it back. let buildDir = (file.changeFileExt("") & "_mm").absolutePath diff --git a/testament/specs.nim b/testament/specs.nim index 11b21c2a64..3c44a2fa54 100644 --- a/testament/specs.nim +++ b/testament/specs.nim @@ -314,7 +314,11 @@ proc initSpec*(filename: string): TSpec = proc isCurrentBatch*(testamentData: TestamentData; filename: string): bool = if testamentData.testamentNumBatch != 0: - hash(filename) mod testamentData.testamentNumBatch == testamentData.testamentBatch + let count = testamentData.testamentNumBatch + # String hashes can be negative. Normalize the remainder so every test maps + # to one of the non-negative batch indexes instead of silently mapping to + # no batch at all. + ((hash(filename) mod count) + count) mod count == testamentData.testamentBatch else: true diff --git a/testament/testament.nim b/testament/testament.nim index bf2bb87c49..1b02101d01 100644 --- a/testament/testament.nim +++ b/testament/testament.nim @@ -803,6 +803,30 @@ else: include categories +const slowCategoriesFirst = [ + # `execProcesses` schedules commands in order. Starting the longest categories + # first avoids leaving a large category as the tail after the other workers + # have gone idle. This approximate order comes from representative CI timings; + # exact runtimes vary by platform and batch. + "stdlib", "ic", "gc", "async", "misc", "lib", "arc", "js", "system", + "errmsgs", "ccgbugs", "megatest", "destructor", "vm", "threads", + "parallel", "cpp", "generics", "compilerapi", "iter", "exception", + "macros", "niminaction", "compiler", "effects", "types", "views", "tools", + "dll", "parser", "yrc", "template", "pragmas" +] + +proc categoryScheduleRank(category: string): int = + let normalized = category.normalize + result = slowCategoriesFirst.len + for i, candidate in slowCategoriesFirst: + if normalized == candidate: + return i + +proc cmpCategorySchedule(a, b: string): int = + result = cmp(categoryScheduleRank(a), categoryScheduleRank(b)) + if result == 0: + result = cmp(a, b) + proc loadSkipFrom(name: string): seq[string] = result = @[] if name.len == 0: return @@ -922,6 +946,7 @@ proc main() = for cat in AdditionalCategories: if cat notin cats: cats.add cat if useMegatest: cats.add MegaTestCat + cats.sort(cmpCategorySchedule) var cmds: seq[string] = @[] for cat in cats: @@ -938,7 +963,9 @@ proc main() = processCategory(r, Category(cati), p.cmdLineRest, testsDir, runJoinableTests = false) else: addExitProc azure.finalize - quit osproc.execProcesses(cmds, {poEchoCmd, poStdErrToStdOut, poUsePath, poParentStreams}, beforeRunEvent = progressStatus) + quit osproc.execProcesses(cmds, + {poEchoCmd, poStdErrToStdOut, poUsePath, poParentStreams}, + beforeRunEvent = progressStatus) of "c", "cat", "category": skips = loadSkipFrom(skipFrom) var cat = Category(p.key)