gdharness 1.3.10 → 1.3.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/cli.js +202 -42
- package/build/godot/tests/listed.gd +53 -0
- package/package.json +1 -1
package/build/cli.js
CHANGED
|
@@ -15127,6 +15127,13 @@ function funcOf(name) {
|
|
|
15127
15127
|
const colon = name.indexOf(":");
|
|
15128
15128
|
return colon < 0 ? name : name.slice(0, colon);
|
|
15129
15129
|
}
|
|
15130
|
+
function rowOf(name) {
|
|
15131
|
+
const row = /^[^:]+:(\d+)(?: |$)/.exec(name)?.[1];
|
|
15132
|
+
return row === undefined ? null : Number(row);
|
|
15133
|
+
}
|
|
15134
|
+
function rowRuns(share, row) {
|
|
15135
|
+
return "only" in share ? row !== null && share.only.includes(row) : row === null || !share.besides.includes(row);
|
|
15136
|
+
}
|
|
15130
15137
|
function readRun(printed) {
|
|
15131
15138
|
const lines = stripVTControlCharacters(printed).replaceAll(`\r
|
|
15132
15139
|
`, `
|
|
@@ -15146,7 +15153,15 @@ function readRun(printed) {
|
|
|
15146
15153
|
}
|
|
15147
15154
|
afterCase = [];
|
|
15148
15155
|
};
|
|
15156
|
+
const leftOut = new Map;
|
|
15149
15157
|
for (const [at, line] of lines.entries()) {
|
|
15158
|
+
const left = LEFT_OUT.exec(line);
|
|
15159
|
+
if (left !== null) {
|
|
15160
|
+
const key = `${left[1] ?? ""} > ${left[2] ?? ""}`;
|
|
15161
|
+
const row = Number(left[3]);
|
|
15162
|
+
leftOut.set(key, [...leftOut.get(key) ?? [], row < 0 ? null : row]);
|
|
15163
|
+
continue;
|
|
15164
|
+
}
|
|
15150
15165
|
const opened = SUITE_OPENED.exec(line);
|
|
15151
15166
|
if (opened?.[1] !== undefined) {
|
|
15152
15167
|
settleAfterCase("after");
|
|
@@ -15166,6 +15181,7 @@ function readRun(printed) {
|
|
|
15166
15181
|
open.cases.push({
|
|
15167
15182
|
name,
|
|
15168
15183
|
func: funcOf(name),
|
|
15184
|
+
row: rowOf(name),
|
|
15169
15185
|
verdict: caseLine[3] ?? "",
|
|
15170
15186
|
seconds: secondsOf(caseLine[4] ?? ""),
|
|
15171
15187
|
at
|
|
@@ -15225,7 +15241,7 @@ function readRun(printed) {
|
|
|
15225
15241
|
`);
|
|
15226
15242
|
const starved = STARVED.flatMap((pattern) => [...joined.matchAll(pattern)].map((found) => found[0]));
|
|
15227
15243
|
const unloadable = scriptErrorsPrinted(lines).map((error) => `${error.path}${error.line === null ? "" : `:${String(error.line)}`}: ${error.message}`);
|
|
15228
|
-
return { lines, suites, errors, leaks, starved, executed, unloadable, stoppedIn };
|
|
15244
|
+
return { lines, suites, errors, leaks, starved, executed, unloadable, stoppedIn, leftOut };
|
|
15229
15245
|
}
|
|
15230
15246
|
function withNestedErrors(printed, piped) {
|
|
15231
15247
|
const nested = piped.flatMap((entry, at) => {
|
|
@@ -15253,6 +15269,56 @@ function exitNamed(code) {
|
|
|
15253
15269
|
const gdunit = GDUNIT_EXITS.get(code);
|
|
15254
15270
|
return gdunit === undefined ? String(code) : `${String(code)} (${gdunit})`;
|
|
15255
15271
|
}
|
|
15272
|
+
function rowsAcross(engines) {
|
|
15273
|
+
const dealtOut = new Map;
|
|
15274
|
+
for (const { handed, printed } of engines) {
|
|
15275
|
+
for (const suite of handed) {
|
|
15276
|
+
for (const func of Object.keys(suite.rows ?? {})) {
|
|
15277
|
+
const key = `${suite.path} > ${func}`;
|
|
15278
|
+
const entry = dealtOut.get(key) ?? { path: suite.path, func, holders: [] };
|
|
15279
|
+
entry.holders.push(printed);
|
|
15280
|
+
dealtOut.set(key, entry);
|
|
15281
|
+
}
|
|
15282
|
+
}
|
|
15283
|
+
}
|
|
15284
|
+
const faults = [];
|
|
15285
|
+
for (const [key, { path, func, holders }] of dealtOut) {
|
|
15286
|
+
const suites = holders.map((printed) => printed.suites.get(path));
|
|
15287
|
+
if (suites.some((suite) => suite?.closed === null || suite?.closed === undefined)) {
|
|
15288
|
+
continue;
|
|
15289
|
+
}
|
|
15290
|
+
const counts = new Map;
|
|
15291
|
+
for (const ran of suites.flatMap((suite) => suite?.cases ?? []).filter((ran) => ran.func === func)) {
|
|
15292
|
+
counts.set(ran.row, (counts.get(ran.row) ?? 0) + 1);
|
|
15293
|
+
}
|
|
15294
|
+
const said = (row) => row === null ? "the case whole" : `row ${String(row)}`;
|
|
15295
|
+
if (counts.size === 0) {
|
|
15296
|
+
faults.push({
|
|
15297
|
+
suite: path,
|
|
15298
|
+
fault: `${key} ran in none of the ${String(holders.length)} engines its rows were dealt to`
|
|
15299
|
+
});
|
|
15300
|
+
continue;
|
|
15301
|
+
}
|
|
15302
|
+
const twice = [...counts].filter(([, times]) => times > 1).map(([row]) => said(row));
|
|
15303
|
+
if (twice.length > 0) {
|
|
15304
|
+
faults.push({ suite: path, fault: `${key} ran ${named(twice)} in more than one engine` });
|
|
15305
|
+
}
|
|
15306
|
+
const rows = [...counts.keys()].filter((row) => row !== null);
|
|
15307
|
+
if (rows.length > 0 && counts.has(null)) {
|
|
15308
|
+
faults.push({ suite: path, fault: `${key} ran both whole and by its rows` });
|
|
15309
|
+
}
|
|
15310
|
+
const left = holders.flatMap((printed) => printed.leftOut.get(key) ?? []).filter((row) => row !== null);
|
|
15311
|
+
const top = Math.max(-1, ...rows, ...left);
|
|
15312
|
+
const gaps = Array.from({ length: top + 1 }, (_, row) => row).filter((row) => !counts.has(row));
|
|
15313
|
+
if (gaps.length > 0) {
|
|
15314
|
+
faults.push({
|
|
15315
|
+
suite: path,
|
|
15316
|
+
fault: `${key} did not run ${named(gaps.map(said))} in any of the ${String(holders.length)} engines its rows were dealt to`
|
|
15317
|
+
});
|
|
15318
|
+
}
|
|
15319
|
+
}
|
|
15320
|
+
return faults;
|
|
15321
|
+
}
|
|
15256
15322
|
function named(items) {
|
|
15257
15323
|
const shown = items.slice(0, MOST_NAMED).join(", ");
|
|
15258
15324
|
return items.length > MOST_NAMED ? `${shown} and ${String(items.length - MOST_NAMED)} more` : shown;
|
|
@@ -15273,7 +15339,7 @@ function judgeEngine(printed, handed, end) {
|
|
|
15273
15339
|
if (printed.executed === null || printed.executed === 0) {
|
|
15274
15340
|
faults.push(printed.executed === null ? "the run never printed its summary" : "the run executed no test case at all, whatever it exited with");
|
|
15275
15341
|
}
|
|
15276
|
-
for (const { path, cases } of handed.suites) {
|
|
15342
|
+
for (const { path, cases, rows = {} } of handed.suites) {
|
|
15277
15343
|
const suite = printed.suites.get(path);
|
|
15278
15344
|
if (suite === undefined) {
|
|
15279
15345
|
faults.push(`${path} never started`);
|
|
@@ -15290,11 +15356,18 @@ function judgeEngine(printed, handed, end) {
|
|
|
15290
15356
|
failing.add(path);
|
|
15291
15357
|
}
|
|
15292
15358
|
const finished = new Set(suite.cases.map((ran) => ran.func));
|
|
15293
|
-
const missing = cases.filter((func) => !finished.has(func));
|
|
15359
|
+
const missing = cases.filter((func) => !finished.has(func) && rows[func] === undefined);
|
|
15294
15360
|
if (missing.length > 0 && suite.closed !== null) {
|
|
15295
15361
|
faults.push(`${path} closed without running ${named(missing)}`);
|
|
15296
15362
|
failing.add(path);
|
|
15297
15363
|
}
|
|
15364
|
+
for (const [func, share] of Object.entries(rows)) {
|
|
15365
|
+
const strays = suite.cases.filter((ran) => ran.func === func && !rowRuns(share, ran.row)).map((ran) => ran.row === null ? "the case whole" : `row ${String(ran.row)}`);
|
|
15366
|
+
if (strays.length > 0) {
|
|
15367
|
+
faults.push(`${path} > ${func} ran ${named(strays)}, which ${strays.length === 1 ? "was" : "were"} dealt to another engine`);
|
|
15368
|
+
failing.add(path);
|
|
15369
|
+
}
|
|
15370
|
+
}
|
|
15298
15371
|
const wanted = new Set(cases);
|
|
15299
15372
|
const unasked = [...finished].filter((func) => !wanted.has(func));
|
|
15300
15373
|
if (unasked.length > 0) {
|
|
@@ -15416,7 +15489,7 @@ ${String(error.warning)}`);
|
|
|
15416
15489
|
const neverPrinted = [...owed.values()].flatMap((debt) => debt.texts.map((text) => ({ suite: debt.suite, during: debt.during, text, warning: debt.warning })));
|
|
15417
15490
|
return { printed: unexpected, neverPrinted };
|
|
15418
15491
|
}
|
|
15419
|
-
var SUITE_OPENED, TIME, CASE_LINE, STATISTICS, ERROR_LINE, WARNING_LINE, EXECUTED, DRIVER_FALLBACK, ORPHANS_REPORTED, STARVED, NESTED_ERROR = "While attempting to print an error, another error was printed:", WINDOWS_EXITS, MEMORY_EXITS, GDUNIT_FAILED = 100, GDUNIT_EXITS, TAIL = 60, MOST_NAMED = 8;
|
|
15492
|
+
var SUITE_OPENED, TIME, CASE_LINE, STATISTICS, ERROR_LINE, WARNING_LINE, EXECUTED, LEFT_OUT, DRIVER_FALLBACK, ORPHANS_REPORTED, STARVED, NESTED_ERROR = "While attempting to print an error, another error was printed:", WINDOWS_EXITS, MEMORY_EXITS, GDUNIT_FAILED = 100, GDUNIT_EXITS, TAIL = 60, MOST_NAMED = 8;
|
|
15420
15493
|
var init_suite_judge = __esm(() => {
|
|
15421
15494
|
init_engine_leaks();
|
|
15422
15495
|
init_junit();
|
|
@@ -15427,6 +15500,7 @@ var init_suite_judge = __esm(() => {
|
|
|
15427
15500
|
ERROR_LINE = /^\s*(?:SCRIPT |USER )?ERROR: (.*?)\s*$/;
|
|
15428
15501
|
WARNING_LINE = /^\s*(?:SCRIPT |USER )?WARNING: (.*?)\s*$/;
|
|
15429
15502
|
EXECUTED = /^Executed test cases\s*:\s*\((\d+)\//;
|
|
15503
|
+
LEFT_OUT = /^gdharness left out (res:\/\/.+?\.gd) > ([^:\s]+):(-?\d+)\s*$/;
|
|
15430
15504
|
DRIVER_FALLBACK = /^\s*WARNING: Your video card drivers .*, switching to /;
|
|
15431
15505
|
ORPHANS_REPORTED = /^\s*WARNING: Detected \d+ (?:possible )?orphan nodes\.\s*$/;
|
|
15432
15506
|
STARVED = [
|
|
@@ -15587,7 +15661,7 @@ function writeJson(file, value) {
|
|
|
15587
15661
|
writeWhole(file, `${JSON.stringify(value, null, 1)}
|
|
15588
15662
|
`);
|
|
15589
15663
|
}
|
|
15590
|
-
function timesAfter(times, suites, runs, now) {
|
|
15664
|
+
function timesAfter(times, suites, runs, now, cutShort = new Set) {
|
|
15591
15665
|
const next = {};
|
|
15592
15666
|
for (const [path, time] of Object.entries(times)) {
|
|
15593
15667
|
const suite = now(path);
|
|
@@ -15596,7 +15670,12 @@ function timesAfter(times, suites, runs, now) {
|
|
|
15596
15670
|
}
|
|
15597
15671
|
const cases = Object.fromEntries(Object.entries(time.cases).filter(([func]) => suite.cases.includes(func)));
|
|
15598
15672
|
const dropped = Object.entries(time.cases).filter(([func]) => !(func in cases)).reduce((sum, [, seconds]) => sum + seconds, 0);
|
|
15599
|
-
|
|
15673
|
+
const rows = Object.fromEntries(Object.entries(time.rows ?? {}).filter(([func]) => suite.cases.includes(func)));
|
|
15674
|
+
next[path] = {
|
|
15675
|
+
seconds: round(Math.max(0, time.seconds - dropped)),
|
|
15676
|
+
cases,
|
|
15677
|
+
...Object.keys(rows).length > 0 ? { rows } : {}
|
|
15678
|
+
};
|
|
15600
15679
|
}
|
|
15601
15680
|
const declared = new Map(suites.map((suite) => [suite.path, suite]));
|
|
15602
15681
|
const measured = new Map;
|
|
@@ -15605,15 +15684,24 @@ function timesAfter(times, suites, runs, now) {
|
|
|
15605
15684
|
if (printed.closed === null || !declared.has(printed.path)) {
|
|
15606
15685
|
continue;
|
|
15607
15686
|
}
|
|
15608
|
-
const entry = measured.get(printed.path) ?? {
|
|
15687
|
+
const entry = measured.get(printed.path) ?? {
|
|
15688
|
+
cases: new Map,
|
|
15689
|
+
rows: new Map,
|
|
15690
|
+
around: 0
|
|
15691
|
+
};
|
|
15609
15692
|
const own = new Map;
|
|
15610
15693
|
for (const ran of printed.cases) {
|
|
15611
15694
|
own.set(ran.func, (own.get(ran.func) ?? 0) + ran.seconds);
|
|
15695
|
+
if (ran.row !== null) {
|
|
15696
|
+
const rows = entry.rows.get(ran.func) ?? new Map;
|
|
15697
|
+
rows.set(ran.row, ran.seconds);
|
|
15698
|
+
entry.rows.set(ran.func, rows);
|
|
15699
|
+
}
|
|
15612
15700
|
}
|
|
15613
15701
|
const inCases = [...own.values()].reduce((sum, seconds) => sum + seconds, 0);
|
|
15614
15702
|
entry.around = Math.max(entry.around, printed.closed.seconds - inCases);
|
|
15615
15703
|
for (const [func, seconds] of own) {
|
|
15616
|
-
entry.cases.set(func, seconds);
|
|
15704
|
+
entry.cases.set(func, (entry.cases.get(func) ?? 0) + seconds);
|
|
15617
15705
|
}
|
|
15618
15706
|
measured.set(printed.path, entry);
|
|
15619
15707
|
}
|
|
@@ -15621,17 +15709,33 @@ function timesAfter(times, suites, runs, now) {
|
|
|
15621
15709
|
for (const [path, entry] of measured) {
|
|
15622
15710
|
const suite = declared.get(path);
|
|
15623
15711
|
const cases = {};
|
|
15712
|
+
const rows = {};
|
|
15624
15713
|
for (const func of suite?.cases ?? []) {
|
|
15625
|
-
const
|
|
15714
|
+
const measuredRows = entry.rows.get(func);
|
|
15715
|
+
const keptRows = measuredRows === undefined ? times[path]?.rows?.[func] : rowsAfter(times[path]?.rows?.[func], measuredRows, cutShort.has(`${path}:${func}`));
|
|
15716
|
+
if (keptRows !== undefined) {
|
|
15717
|
+
rows[func] = keptRows.map(round);
|
|
15718
|
+
}
|
|
15719
|
+
const seconds = measuredRows === undefined || keptRows === undefined ? entry.cases.get(func) ?? times[path]?.cases[func] : keptRows.reduce((sum, row) => sum + row, 0);
|
|
15626
15720
|
if (seconds !== undefined) {
|
|
15627
15721
|
cases[func] = round(seconds);
|
|
15628
15722
|
}
|
|
15629
15723
|
}
|
|
15630
15724
|
const inCases = Object.values(cases).reduce((sum, seconds) => sum + seconds, 0);
|
|
15631
|
-
next[path] = {
|
|
15725
|
+
next[path] = {
|
|
15726
|
+
seconds: round(entry.around + inCases),
|
|
15727
|
+
cases,
|
|
15728
|
+
...Object.keys(rows).length > 0 ? { rows } : {}
|
|
15729
|
+
};
|
|
15632
15730
|
}
|
|
15633
15731
|
return next;
|
|
15634
15732
|
}
|
|
15733
|
+
function rowsAfter(before, measured, cutShort) {
|
|
15734
|
+
const count = Math.max(...measured.keys()) + 1;
|
|
15735
|
+
const whole = !cutShort && measured.size === count;
|
|
15736
|
+
const length = whole ? count : Math.max(count, before?.length ?? 0);
|
|
15737
|
+
return Array.from({ length }, (_, row) => measured.get(row) ?? before?.[row] ?? 0);
|
|
15738
|
+
}
|
|
15635
15739
|
function round(seconds) {
|
|
15636
15740
|
return Math.round(seconds * 1000) / 1000;
|
|
15637
15741
|
}
|
|
@@ -15652,24 +15756,39 @@ function unitsOf(suites, times, splitPast = SPLIT_PAST_SECONDS) {
|
|
|
15652
15756
|
const costOf = (func) => time?.cases[func] ?? caseCost;
|
|
15653
15757
|
const around = time === undefined ? aroundCost : Math.max(0, time.seconds - Object.values(time.cases).reduce((sum, s) => sum + s, 0));
|
|
15654
15758
|
const whole = around + suite.cases.reduce((sum, func) => sum + costOf(func), 0);
|
|
15655
|
-
|
|
15656
|
-
return [
|
|
15657
|
-
{
|
|
15658
|
-
suite: suite.path,
|
|
15659
|
-
cases: suite.cases,
|
|
15660
|
-
cost: whole,
|
|
15661
|
-
windowed: suite.windowed,
|
|
15662
|
-
headless: suite.headless
|
|
15663
|
-
}
|
|
15664
|
-
];
|
|
15665
|
-
}
|
|
15666
|
-
return suite.cases.map((func) => ({
|
|
15759
|
+
const wholly = {
|
|
15667
15760
|
suite: suite.path,
|
|
15668
|
-
cases:
|
|
15669
|
-
cost:
|
|
15670
|
-
windowed:
|
|
15761
|
+
cases: suite.cases,
|
|
15762
|
+
cost: whole,
|
|
15763
|
+
windowed: suite.windowed,
|
|
15671
15764
|
headless: suite.headless
|
|
15672
|
-
}
|
|
15765
|
+
};
|
|
15766
|
+
if (suite.windowed || whole <= splitPast) {
|
|
15767
|
+
return [wholly];
|
|
15768
|
+
}
|
|
15769
|
+
const units = suite.cases.flatMap((func) => {
|
|
15770
|
+
const rows = time?.rows?.[func] ?? [];
|
|
15771
|
+
if (rows.length < 2 || around + costOf(func) <= splitPast) {
|
|
15772
|
+
return [
|
|
15773
|
+
{
|
|
15774
|
+
suite: suite.path,
|
|
15775
|
+
cases: [func],
|
|
15776
|
+
cost: around + costOf(func),
|
|
15777
|
+
windowed: false,
|
|
15778
|
+
headless: suite.headless
|
|
15779
|
+
}
|
|
15780
|
+
];
|
|
15781
|
+
}
|
|
15782
|
+
return rows.map((seconds, index) => ({
|
|
15783
|
+
suite: suite.path,
|
|
15784
|
+
cases: [func],
|
|
15785
|
+
row: { index, rest: index === rows.length - 1 },
|
|
15786
|
+
cost: around + seconds,
|
|
15787
|
+
windowed: false,
|
|
15788
|
+
headless: suite.headless
|
|
15789
|
+
}));
|
|
15790
|
+
});
|
|
15791
|
+
return units.length < 2 ? [wholly] : units;
|
|
15673
15792
|
});
|
|
15674
15793
|
}
|
|
15675
15794
|
function dealt(units, declared, most) {
|
|
@@ -15677,7 +15796,7 @@ function dealt(units, declared, most) {
|
|
|
15677
15796
|
return [];
|
|
15678
15797
|
}
|
|
15679
15798
|
const windowed = units.filter((unit) => unit.windowed);
|
|
15680
|
-
const headless = units.filter((unit) => !unit.windowed).sort((a, b) => b.cost - a.cost || a.suite.localeCompare(b.suite) || (a.cases[0] ?? "").localeCompare(b.cases[0] ?? ""));
|
|
15799
|
+
const headless = units.filter((unit) => !unit.windowed).sort((a, b) => b.cost - a.cost || a.suite.localeCompare(b.suite) || (a.cases[0] ?? "").localeCompare(b.cases[0] ?? "") || (a.row?.index ?? 0) - (b.row?.index ?? 0));
|
|
15681
15800
|
const together = windowed.reduce((sum, unit) => sum + unit.cost, 0);
|
|
15682
15801
|
const restart = windowed.length > 0 ? ENGINE_START_SECONDS : 0;
|
|
15683
15802
|
const total = units.reduce((sum, unit) => sum + unit.cost, 0);
|
|
@@ -15691,8 +15810,8 @@ function dealt(units, declared, most) {
|
|
|
15691
15810
|
loads[lightest] = (loads[lightest] ?? 0) + unit.cost;
|
|
15692
15811
|
}
|
|
15693
15812
|
return [
|
|
15694
|
-
...windowed.length > 0 ? [planOf(windowed, declared, true, 0)] : [],
|
|
15695
|
-
...bins.flatMap((bin, lane) => bin.length === 0 ? [] : [planOf(bin, declared, false, lane)])
|
|
15813
|
+
...windowed.length > 0 ? [planOf(windowed, declared, true, 0, units)] : [],
|
|
15814
|
+
...bins.flatMap((bin, lane) => bin.length === 0 ? [] : [planOf(bin, declared, false, lane, units)])
|
|
15696
15815
|
];
|
|
15697
15816
|
}
|
|
15698
15817
|
function lanesOf(plans) {
|
|
@@ -15713,16 +15832,34 @@ function headlessInAWindow(plans, units) {
|
|
|
15713
15832
|
return plans.filter((plan) => plan.windowed).flatMap((plan) => plan.suites.map((suite) => suite.path)).filter((path) => said.has(path));
|
|
15714
15833
|
}
|
|
15715
15834
|
function inOneEngine(units, declared) {
|
|
15716
|
-
return planOf(units, declared, units.some((unit) => unit.windowed), 0);
|
|
15835
|
+
return planOf(units, declared, units.some((unit) => unit.windowed), 0, units);
|
|
15717
15836
|
}
|
|
15718
|
-
function planOf(bin, declared, windowed, lane) {
|
|
15837
|
+
function planOf(bin, declared, windowed, lane, all) {
|
|
15719
15838
|
const held = new Map;
|
|
15720
15839
|
for (const unit of bin) {
|
|
15721
|
-
|
|
15840
|
+
const cases = held.get(unit.suite) ?? [];
|
|
15841
|
+
held.set(unit.suite, [...cases, ...unit.cases.filter((func) => !cases.includes(func))]);
|
|
15722
15842
|
}
|
|
15723
15843
|
const skipping = [...held].flatMap(([path, cases]) => (declared.get(path) ?? []).filter((func) => !cases.includes(func)).map((func) => `${path}:${func}`));
|
|
15844
|
+
const rowsOf = (units, path, func) => units.flatMap((unit) => unit.suite === path && unit.cases[0] === func && unit.row !== undefined ? [unit.row.index] : []);
|
|
15845
|
+
const sharesOf = (path, cases) => {
|
|
15846
|
+
const shares = {};
|
|
15847
|
+
for (const func of cases) {
|
|
15848
|
+
const mine = rowsOf(bin, path, func);
|
|
15849
|
+
const dealtOut = rowsOf(all, path, func);
|
|
15850
|
+
if (mine.length === 0 || mine.length === dealtOut.length) {
|
|
15851
|
+
continue;
|
|
15852
|
+
}
|
|
15853
|
+
const rest = bin.some((unit) => unit.suite === path && unit.cases[0] === func && unit.row?.rest === true);
|
|
15854
|
+
shares[func] = rest ? { besides: dealtOut.filter((row) => !mine.includes(row)) } : { only: mine.sort((a, b) => a - b) };
|
|
15855
|
+
}
|
|
15856
|
+
return shares;
|
|
15857
|
+
};
|
|
15724
15858
|
return {
|
|
15725
|
-
suites: [...held].map(([path, cases]) =>
|
|
15859
|
+
suites: [...held].map(([path, cases]) => {
|
|
15860
|
+
const rows = sharesOf(path, cases);
|
|
15861
|
+
return { path, cases: [...cases].sort(), ...Object.keys(rows).length > 0 ? { rows } : {} };
|
|
15862
|
+
}),
|
|
15726
15863
|
skipping,
|
|
15727
15864
|
cost: bin.reduce((sum, unit) => sum + unit.cost, 0),
|
|
15728
15865
|
windowed,
|
|
@@ -57155,10 +57292,16 @@ ${existsSync19(path) ? readFileSync20(path, "latin1") : "missing"}`)
|
|
|
57155
57292
|
const one = windowedAnyway.length === 1;
|
|
57156
57293
|
console.log(`${named(windowedAnyway)} ${one ? "says it runs" : "say they run"} headless and ${one ? "runs" : "run"} with a window here, since an ordered run is one engine and a suite in it needs a window. Run ${one ? "it" : "them"} without the windowed suites to run ${one ? "it" : "them"} headless.`);
|
|
57157
57294
|
}
|
|
57158
|
-
const split = [...new Set(units.map((unit) => unit.suite))].filter((path) => units.filter((unit) => unit.suite === path).length > 1);
|
|
57295
|
+
const split = [...new Set(units.map((unit) => unit.suite))].filter((path) => units.filter((unit) => unit.suite === path && unit.row === undefined).length > 1);
|
|
57159
57296
|
if (plans.length > 1 && split.length > 0) {
|
|
57160
57297
|
console.log(`Dealt out case by case, since ${split.length === 1 ? "it" : "each"} took more than ${String(SPLIT_PAST_SECONDS)} s last time: ${named(split)}.`);
|
|
57161
57298
|
}
|
|
57299
|
+
const byRows = [
|
|
57300
|
+
...new Set(units.flatMap((unit) => unit.row === undefined ? [] : [`${unit.suite} > ${unit.cases[0] ?? ""}`]))
|
|
57301
|
+
];
|
|
57302
|
+
if (plans.length > 1 && byRows.length > 0) {
|
|
57303
|
+
console.log(`Dealt out row by row, since ${byRows.length === 1 ? "it" : "each"} took more than ${String(SPLIT_PAST_SECONDS)} s last time: ${named(byRows)}.`);
|
|
57304
|
+
}
|
|
57162
57305
|
const lanes = [...new Set(plans.map((plan) => plan.lane))].map((lane) => plans.flatMap((plan, index) => plan.lane === lane ? [{ plan, index }] : []));
|
|
57163
57306
|
let ran = (await inTurns(lanes, room.engines, (lane) => runLane(launch, lane, units))).flat().sort((a, b) => a.index - b.index);
|
|
57164
57307
|
if (ran.some((engine) => engine.verdict.starved) && ran.some((engine) => !engine.verdict.passed)) {
|
|
@@ -57169,9 +57312,10 @@ ${existsSync19(path) ? readFileSync20(path, "latin1") : "missing"}`)
|
|
|
57169
57312
|
}
|
|
57170
57313
|
ran = again;
|
|
57171
57314
|
}
|
|
57172
|
-
writeJson(timesFile, timesAfter(readTimes(timesFile), wholeSuites, ran.map((engine) => engine.printed), (path) => scripts.suite(path)));
|
|
57173
|
-
|
|
57174
|
-
|
|
57315
|
+
writeJson(timesFile, timesAfter(readTimes(timesFile), wholeSuites, ran.map((engine) => engine.printed), (path) => scripts.suite(path), new Set(ran.flatMap((engine) => engine.plan.suites.filter((suite) => (engine.printed.suites.get(suite.path)?.closed ?? null) === null).flatMap((suite) => suite.cases.map((func) => `${suite.path}:${func}`))))));
|
|
57316
|
+
const across = rowsAcross(ran.map((engine) => ({ handed: engine.plan.suites, printed: engine.printed })));
|
|
57317
|
+
recordPassed(passedFile, ran, printsOf, new Set(across.map((fault) => fault.suite)));
|
|
57318
|
+
return report(ran, across.map((fault) => fault.fault), startedAt, runDirectory, options.printOutput);
|
|
57175
57319
|
} finally {
|
|
57176
57320
|
process12.off("SIGINT", interrupted);
|
|
57177
57321
|
process12.off("SIGTERM", interrupted);
|
|
@@ -57207,9 +57351,9 @@ function readRecord(file) {
|
|
|
57207
57351
|
return {};
|
|
57208
57352
|
}
|
|
57209
57353
|
}
|
|
57210
|
-
function recordPassed(file, ran, printsOf) {
|
|
57354
|
+
function recordPassed(file, ran, printsOf, failedAcross) {
|
|
57211
57355
|
const passed = readRecord(file);
|
|
57212
|
-
const failedSomewhere = new Set;
|
|
57356
|
+
const failedSomewhere = new Set(failedAcross);
|
|
57213
57357
|
const passedSomewhere = new Set;
|
|
57214
57358
|
for (const engine of ran) {
|
|
57215
57359
|
const passing = new Set(engine.verdict.passedSuites);
|
|
@@ -57270,12 +57414,19 @@ async function runEngine2(launch, plan, index, units, again = "", slot = null) {
|
|
|
57270
57414
|
mkdirSync8(join22(directory, "user"), { recursive: true });
|
|
57271
57415
|
const suitesFile = join22(directory, "suites.txt");
|
|
57272
57416
|
const skippingFile = join22(directory, "skipping.txt");
|
|
57417
|
+
const rowsFile = join22(directory, "rows.txt");
|
|
57273
57418
|
const expectedFile = join22(directory, "expected.json");
|
|
57274
57419
|
writeFileSync10(suitesFile, `${plan.suites.map((suite) => suite.path).join(`
|
|
57275
57420
|
`)}
|
|
57276
57421
|
`);
|
|
57277
57422
|
writeFileSync10(skippingFile, `${plan.skipping.join(`
|
|
57278
57423
|
`)}
|
|
57424
|
+
`);
|
|
57425
|
+
writeFileSync10(rowsFile, `${plan.suites.flatMap((suite) => Object.entries(suite.rows ?? {}).map(([func, share]) => [
|
|
57426
|
+
`${suite.path}:${func}`,
|
|
57427
|
+
..."only" in share ? ["only", ...share.only] : ["besides", ...share.besides]
|
|
57428
|
+
].join("\t"))).join(`
|
|
57429
|
+
`)}
|
|
57279
57430
|
`);
|
|
57280
57431
|
const viewport = viewportOf(launch.projectPath);
|
|
57281
57432
|
const display = plan.windowed ? viewport === null ? [] : ["--resolution", `${String(viewport.width)}x${String(viewport.height)}`] : ["--headless"];
|
|
@@ -57298,6 +57449,8 @@ async function runEngine2(launch, plan, index, units, again = "", slot = null) {
|
|
|
57298
57449
|
suitesFile,
|
|
57299
57450
|
"--skipping-in",
|
|
57300
57451
|
skippingFile,
|
|
57452
|
+
"--rows-in",
|
|
57453
|
+
rowsFile,
|
|
57301
57454
|
"--html-report-off",
|
|
57302
57455
|
"--junit-report-off",
|
|
57303
57456
|
"--compile-together",
|
|
@@ -57443,7 +57596,7 @@ function endTree(child) {
|
|
|
57443
57596
|
process12.kill(-child.pid, "SIGKILL");
|
|
57444
57597
|
} catch {}
|
|
57445
57598
|
}
|
|
57446
|
-
function report(ran, startedAt, runDirectory, printOutput) {
|
|
57599
|
+
function report(ran, across, startedAt, runDirectory, printOutput) {
|
|
57447
57600
|
if (printOutput) {
|
|
57448
57601
|
for (const engine of ran) {
|
|
57449
57602
|
console.log("");
|
|
@@ -57466,18 +57619,25 @@ function report(ran, startedAt, runDirectory, printOutput) {
|
|
|
57466
57619
|
}
|
|
57467
57620
|
console.log(`everything it printed: ${engine.log}`);
|
|
57468
57621
|
}
|
|
57622
|
+
if (across.length > 0) {
|
|
57623
|
+
console.log("");
|
|
57624
|
+
console.log("--- the cases dealt by their rows, over every engine holding them:");
|
|
57625
|
+
for (const fault of across) {
|
|
57626
|
+
console.log(` ${fault}`);
|
|
57627
|
+
}
|
|
57628
|
+
}
|
|
57469
57629
|
const cases = ran.reduce((sum, engine) => sum + [...engine.printed.suites.values()].reduce((n, s) => n + s.cases.length, 0), 0);
|
|
57470
57630
|
const suites = new Set(ran.flatMap((engine) => engine.plan.suites.map((suite) => suite.path))).size;
|
|
57471
57631
|
const longest = Math.max(0, ...ran.map((engine) => engine.seconds));
|
|
57472
57632
|
const took = (Date.now() - startedAt) / 1000;
|
|
57473
57633
|
const totals = `${plural(cases, "case")} in ${plural(suites, "suite")} across ${plural(ran.length, "engine")}, ${took.toFixed(1)} s; the longest engine took ${longest.toFixed(1)} s.`;
|
|
57474
57634
|
console.log("");
|
|
57475
|
-
if (failed.length === 0) {
|
|
57635
|
+
if (failed.length === 0 && across.length === 0) {
|
|
57476
57636
|
rmSync10(runDirectory, { recursive: true, force: true });
|
|
57477
57637
|
console.log(`Passed: ${totals}`);
|
|
57478
57638
|
return 0;
|
|
57479
57639
|
}
|
|
57480
|
-
const problems = failed.reduce((sum, engine) => sum + engine.verdict.faults.length,
|
|
57640
|
+
const problems = failed.reduce((sum, engine) => sum + engine.verdict.faults.length, across.length);
|
|
57481
57641
|
console.log(`Failed: ${plural(problems, "problem")}, listed above. Ran ${totals}`);
|
|
57482
57642
|
console.log(`Every engine's output is kept under ${runDirectory} until the next run.`);
|
|
57483
57643
|
return 1;
|
|
@@ -15,6 +15,16 @@ extends SceneTree
|
|
|
15
15
|
|
|
16
16
|
const LISTED: String = "--suites-in"
|
|
17
17
|
const SKIPPING: String = "--skipping-in"
|
|
18
|
+
## The file of the parameterised cases dealt by their rows, one a line and tab-separated:
|
|
19
|
+
## `res://suite.gd:test_case`, then `only` and the rows this engine runs, or `besides` and the rows
|
|
20
|
+
## it leaves to the others, for the engine that takes every row dealt nowhere else. Apart from
|
|
21
|
+
## [constant SKIPPING], since gdUnit4 reads `suite:case:row` there as the case and leaves out every
|
|
22
|
+
## row of it.
|
|
23
|
+
const ROWS: String = "--rows-in"
|
|
24
|
+
## Printed before each row an engine leaves out, as `res://suite.gd > test_case:row`, -1 for a case
|
|
25
|
+
## that is not parameterised. gdUnit4 prints nothing for a case it skips, and the caller needs every
|
|
26
|
+
## row an engine found to tell one that no engine ran.
|
|
27
|
+
const LEFT_OUT: String = "gdharness left out"
|
|
18
28
|
## The flags turning off a report, with the gdUnit4 hook that writes it. A gate reads what a run
|
|
19
29
|
## prints, and each report finds every case it hears of in the session to write them all out as the
|
|
20
30
|
## run ends; project_test reads the JUnit one and turns off only the HTML one.
|
|
@@ -54,10 +64,23 @@ class GdhRunner:
|
|
|
54
64
|
extends GdUnitTestCIRunner
|
|
55
65
|
|
|
56
66
|
var own_path: String = ""
|
|
67
|
+
## The cases dealt by their rows, by `res://suite.gd:test_case`.
|
|
68
|
+
var row_shares: Dictionary[String, GdhRowShare] = {}
|
|
57
69
|
|
|
58
70
|
func get_cmdline_args() -> PackedStringArray:
|
|
59
71
|
return options_of(OS.get_cmdline_args(), own_path)
|
|
60
72
|
|
|
73
|
+
## gdUnit4 asks this of every case it finds, a parameterised case once a row, each row carrying
|
|
74
|
+
## its index.
|
|
75
|
+
func is_skipped(test: GdUnitTestCase) -> bool:
|
|
76
|
+
if super(test):
|
|
77
|
+
return true
|
|
78
|
+
var share: GdhRowShare = row_shares.get("%s:%s" % [test.suite_resource_path, test.test_name], null)
|
|
79
|
+
if share == null or not share.leaves_out(test.attribute_index):
|
|
80
|
+
return false
|
|
81
|
+
print("%s %s > %s:%d" % [LEFT_OUT, test.suite_resource_path, test.test_name, test.attribute_index])
|
|
82
|
+
return true
|
|
83
|
+
|
|
61
84
|
## The session gdUnit4 has just made, swapped for a [GdhIndexedSession] of the same cases as it
|
|
62
85
|
## is announced: everything that reads the session, the hooks, the cases and the console printing
|
|
63
86
|
## every case, is handed it after this.
|
|
@@ -87,6 +110,19 @@ class GdhRunner:
|
|
|
87
110
|
return forward.to_lower() if OS.get_name() == "Windows" else forward
|
|
88
111
|
|
|
89
112
|
|
|
113
|
+
## The rows of one parameterised case an engine runs: those named, or every row but those.
|
|
114
|
+
class GdhRowShare:
|
|
115
|
+
extends RefCounted
|
|
116
|
+
|
|
117
|
+
var only: bool = true
|
|
118
|
+
var rows: PackedInt32Array = []
|
|
119
|
+
|
|
120
|
+
## Whether the engine leaves out [param row], which is -1 for a case that is not parameterised
|
|
121
|
+
## and so runs only in the engine taking the rows besides.
|
|
122
|
+
func leaves_out(row: int) -> bool:
|
|
123
|
+
return rows.has(row) != only
|
|
124
|
+
|
|
125
|
+
|
|
90
126
|
## gdUnit4's session finding a case by its id in a dictionary, where its own walks every case of the
|
|
91
127
|
## session, `GdUnitTestSession.find_test_by_id`: the console asks it twice a case and each report
|
|
92
128
|
## twice more, so a run's time in it grew with the square of its cases (17.9 s of processor time
|
|
@@ -132,6 +168,7 @@ func _initialize() -> void:
|
|
|
132
168
|
_runner.own_path = get_script().resource_path
|
|
133
169
|
_runner.add_test_suites(suites)
|
|
134
170
|
_runner.skip_test_suites(listed(asked, SKIPPING))
|
|
171
|
+
_runner.row_shares = row_shares_of(listed(asked, ROWS))
|
|
135
172
|
root.add_child(_runner)
|
|
136
173
|
|
|
137
174
|
|
|
@@ -213,6 +250,22 @@ static func listed(args: PackedStringArray, flag: String) -> PackedStringArray:
|
|
|
213
250
|
return named
|
|
214
251
|
|
|
215
252
|
|
|
253
|
+
## The row shares [param lines] of a [constant ROWS] file give, by case.
|
|
254
|
+
static func row_shares_of(lines: PackedStringArray) -> Dictionary[String, GdhRowShare]:
|
|
255
|
+
var shares: Dictionary[String, GdhRowShare] = {}
|
|
256
|
+
for line: String in lines:
|
|
257
|
+
var parts: PackedStringArray = line.split("\t")
|
|
258
|
+
if parts.size() < 2:
|
|
259
|
+
continue
|
|
260
|
+
var share: GdhRowShare = GdhRowShare.new()
|
|
261
|
+
share.only = parts[1] == "only"
|
|
262
|
+
for row: String in parts.slice(2):
|
|
263
|
+
if row.is_valid_int():
|
|
264
|
+
var _added: bool = share.rows.append(row.to_int())
|
|
265
|
+
shares[parts[0]] = share
|
|
266
|
+
return shares
|
|
267
|
+
|
|
268
|
+
|
|
216
269
|
## The argument after [param flag] in [param args], or "" when there is none.
|
|
217
270
|
static func argument_after(args: PackedStringArray, flag: String) -> String:
|
|
218
271
|
var at: int = args.find(flag)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gdharness",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.11",
|
|
4
4
|
"mcpName": "io.github.Aureliolo/gdharness",
|
|
5
5
|
"description": "A harness for driving a Godot 4 project from an agent: editor addons, a runtime bridge into the running game, an MCP server in front of them, and a CLI that installs and diagnoses the Godot side.",
|
|
6
6
|
"type": "module",
|