detached 736f11e

build(expr): pin the runner commit and runtime of gen/runner, require RunnerSrc (GO-005)

env.mjs asserts the clone's HEAD (runner-commit.txt), that src/Sdk is clean and
that a 9.0.x runtime is installed; TestRunnerCommit checks that the recorded
data, runner-commit.txt and PORTING.md agree. No global.json: only SDK 10 is
installed here and 'latestPatch' on 9.0.x cannot build, so the runtime is
asserted instead.
commit
736f11edddc5fbe930923e104415b18030134e2e
author
brice · · 2026-10-12 18:32 UTC
committer
brice · · 2026-10-18 14:59 UTC
parent
cf6b697
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
11 files · +118 -34
M expr/gen/runner/README.md +20 -2
@@ -8,8 +8,26 @@ the stubs in `Stubs.cs`. `Program.cs` reads
88 per case: `{"k": kind, "s": string form, "j": toJSON of the result}`, or
99 `{"err": {"t": "parse"|"eval", "m": message}}`.
1010
11- dotnet build -c Release expr/gen/runner -p:RunnerSrc=/path/to/runner/src/Sdk
12- dotnet expr/gen/runner/bin/Release/net9.0/runner.dll input.json
11+Requirements (checked, not assumed):
12+
13+- a clone of https://github.com/actions/runner at the commit in
14+ `runner-commit.txt` (`5f0b3ddcc6ee9fe9441e00d55e85b94ea4aad798`), clean in
15+ `src/Sdk`. `RunnerSrc` has no default: the build fails without it.
16+- the .NET SDK (the project targets `net9.0`) and a **9.0.x runtime**
17+ (`dotnet --list-runtimes`): number formatting and case folding come from the
18+ runtime, and `env.mjs` refuses to run on another line. No `global.json` pins
19+ the SDK because any SDK that can build `net9.0` works (the author's machine
20+ has SDK 10 with the 9.0 runtime); the runtime is what matters and is asserted.
21+
22+Build and run:
23+
24+ dotnet build -c Release expr/gen/runner -p:RunnerSrc=$RUNNER_CLONE/src/Sdk
25+ RUNNER_CLONE=<clone> node expr/gen/runner/answers.mjs | gzip -9 -n > expr/testdata/runner.json.gz
26+
27+The other scripts (`goldens.mjs`, `ordinal_cases.mjs`, `number_cases.mjs`,
28+`fromjson_cases.mjs`) use the same environment (`env.mjs`); `runner.dll
29+input.json` can also be run by hand. `TestRunnerCommit` checks that the
30+recorded data, `runner-commit.txt` and `PORTING.md` name the same commit.
1331
1432 It is how the C# column of `expr/PORTING.md` was established: the 34 765
1533 expressions of `testdata/oracle.json.gz` were run through it and compared with
@@ -8,8 +8,26 @@ the stubs in `Stubs.cs`. `Program.cs` reads
8 per case: `{"k": kind, "s": string form, "j": toJSON of the result}`, or8 per case: `{"k": kind, "s": string form, "j": toJSON of the result}`, or
9 `{"err": {"t": "parse"|"eval", "m": message}}`.9 `{"err": {"t": "parse"|"eval", "m": message}}`.
10 10
11- dotnet build -c Release expr/gen/runner -p:RunnerSrc=/path/to/runner/src/Sdk11+Requirements (checked, not assumed):
12- dotnet expr/gen/runner/bin/Release/net9.0/runner.dll input.json12+
13+- a clone of https://github.com/actions/runner at the commit in
14+ `runner-commit.txt` (`5f0b3ddcc6ee9fe9441e00d55e85b94ea4aad798`), clean in
15+ `src/Sdk`. `RunnerSrc` has no default: the build fails without it.
16+- the .NET SDK (the project targets `net9.0`) and a **9.0.x runtime**
17+ (`dotnet --list-runtimes`): number formatting and case folding come from the
18+ runtime, and `env.mjs` refuses to run on another line. No `global.json` pins
19+ the SDK because any SDK that can build `net9.0` works (the author's machine
20+ has SDK 10 with the 9.0 runtime); the runtime is what matters and is asserted.
21+
22+Build and run:
23+
24+ dotnet build -c Release expr/gen/runner -p:RunnerSrc=$RUNNER_CLONE/src/Sdk
25+ RUNNER_CLONE=<clone> node expr/gen/runner/answers.mjs | gzip -9 -n > expr/testdata/runner.json.gz
26+
27+The other scripts (`goldens.mjs`, `ordinal_cases.mjs`, `number_cases.mjs`,
28+`fromjson_cases.mjs`) use the same environment (`env.mjs`); `runner.dll
29+input.json` can also be run by hand. `TestRunnerCommit` checks that the
30+recorded data, `runner-commit.txt` and `PORTING.md` name the same commit.
13 31
14 It is how the C# column of `expr/PORTING.md` was established: the 34 76532 It is how the C# column of `expr/PORTING.md` was established: the 34 765
15 expressions of `testdata/oracle.json.gz` were run through it and compared with33 expressions of `testdata/oracle.json.gz` were run through it and compared with
M expr/gen/runner/answers.mjs +6 -11
@@ -5,32 +5,27 @@
55 // testdata/oracle.json.gz. Needs the .NET SDK and a clone of actions/runner:
66 //
77 // dotnet build -c Release expr/gen/runner -p:RunnerSrc=<runner>/src/Sdk
8-// node expr/gen/runner/answers.mjs <runner clone> | gzip -9 -n > expr/testdata/runner.json.gz
8+// RUNNER_CLONE=<runner clone> node expr/gen/runner/answers.mjs | gzip -9 -n > expr/testdata/runner.json.gz
99
10-import { execFileSync } from "node:child_process";
1110 import { readFileSync, writeFileSync, mkdtempSync } from "node:fs";
1211 import { tmpdir } from "node:os";
13-import { join, dirname } from "node:path";
14-import { fileURLToPath } from "node:url";
12+import { join } from "node:path";
13+import { runDriver, here, checkEnvironment } from "./env.mjs";
1514 import { gunzipSync } from "node:zlib";
1615
17-const here = dirname(fileURLToPath(import.meta.url));
18-const runnerClone = process.argv[2];
19-if (!runnerClone) throw new Error("usage: answers.mjs <actions/runner clone>");
16+const env = checkEnvironment(process.argv[2]);
2017
2118 const oracle = JSON.parse(gunzipSync(readFileSync(join(here, "../../testdata/oracle.json.gz"))));
2219 const dir = mkdtempSync(join(tmpdir(), "runner-answers-"));
2320 const input = join(dir, "in.json");
2421 writeFileSync(input, JSON.stringify({ contexts: oracle.contexts, cases: oracle.cases.map(c => ({ e: c.e })) }));
25-const out = execFileSync("dotnet", [join(here, "bin/Release/net9.0/runner.dll"), input], { maxBuffer: 1 << 30 }).toString("utf8").split("\n").filter(Boolean);
22+const out = runDriver([input]).split("\n").filter(Boolean);
2623 if (out.length !== oracle.cases.length) throw new Error(`got ${out.length} answers for ${oracle.cases.length} cases`);
27-const git = (...a) => execFileSync("git", ["-C", runnerClone, ...a]).toString().trim();
28-const dotnet = execFileSync("dotnet", ["--list-runtimes"]).toString().split("\n").find(l => l.includes("Microsoft.NETCore.App 9")) ?? "";
2924 const cases = out.map((l, i) => {
3025 const r = JSON.parse(l);
3126 return { e: oracle.cases[i].e, ...r };
3227 });
3328 process.stdout.write(JSON.stringify({
34- reference: { source: "actions/runner src/Sdk/DTExpressions2 (Expressions2)", commit: git("rev-parse", "HEAD"), runtime: dotnet.trim() },
29+ reference: { source: "actions/runner src/Sdk/DTExpressions2 (Expressions2)", commit: env.commit, runtime: env.runtime },
3530 cases,
3631 }));
@@ -5,32 +5,27 @@
5 // testdata/oracle.json.gz. Needs the .NET SDK and a clone of actions/runner:5 // testdata/oracle.json.gz. Needs the .NET SDK and a clone of actions/runner:
6 //6 //
7 // dotnet build -c Release expr/gen/runner -p:RunnerSrc=<runner>/src/Sdk7 // dotnet build -c Release expr/gen/runner -p:RunnerSrc=<runner>/src/Sdk
8-// node expr/gen/runner/answers.mjs <runner clone> | gzip -9 -n > expr/testdata/runner.json.gz8+// RUNNER_CLONE=<runner clone> node expr/gen/runner/answers.mjs | gzip -9 -n > expr/testdata/runner.json.gz
9 9
10-import { execFileSync } from "node:child_process";
11 import { readFileSync, writeFileSync, mkdtempSync } from "node:fs";10 import { readFileSync, writeFileSync, mkdtempSync } from "node:fs";
12 import { tmpdir } from "node:os";11 import { tmpdir } from "node:os";
13-import { join, dirname } from "node:path";12+import { join } from "node:path";
14-import { fileURLToPath } from "node:url";13+import { runDriver, here, checkEnvironment } from "./env.mjs";
15 import { gunzipSync } from "node:zlib";14 import { gunzipSync } from "node:zlib";
16 15
17-const here = dirname(fileURLToPath(import.meta.url));16+const env = checkEnvironment(process.argv[2]);
18-const runnerClone = process.argv[2];
19-if (!runnerClone) throw new Error("usage: answers.mjs <actions/runner clone>");
20 17
21 const oracle = JSON.parse(gunzipSync(readFileSync(join(here, "../../testdata/oracle.json.gz"))));18 const oracle = JSON.parse(gunzipSync(readFileSync(join(here, "../../testdata/oracle.json.gz"))));
22 const dir = mkdtempSync(join(tmpdir(), "runner-answers-"));19 const dir = mkdtempSync(join(tmpdir(), "runner-answers-"));
23 const input = join(dir, "in.json");20 const input = join(dir, "in.json");
24 writeFileSync(input, JSON.stringify({ contexts: oracle.contexts, cases: oracle.cases.map(c => ({ e: c.e })) }));21 writeFileSync(input, JSON.stringify({ contexts: oracle.contexts, cases: oracle.cases.map(c => ({ e: c.e })) }));
25-const out = execFileSync("dotnet", [join(here, "bin/Release/net9.0/runner.dll"), input], { maxBuffer: 1 << 30 }).toString("utf8").split("\n").filter(Boolean);22+const out = runDriver([input]).split("\n").filter(Boolean);
26 if (out.length !== oracle.cases.length) throw new Error(`got ${out.length} answers for ${oracle.cases.length} cases`);23 if (out.length !== oracle.cases.length) throw new Error(`got ${out.length} answers for ${oracle.cases.length} cases`);
27-const git = (...a) => execFileSync("git", ["-C", runnerClone, ...a]).toString().trim();
28-const dotnet = execFileSync("dotnet", ["--list-runtimes"]).toString().split("\n").find(l => l.includes("Microsoft.NETCore.App 9")) ?? "";
29 const cases = out.map((l, i) => {24 const cases = out.map((l, i) => {
30 const r = JSON.parse(l);25 const r = JSON.parse(l);
31 return { e: oracle.cases[i].e, ...r };26 return { e: oracle.cases[i].e, ...r };
32 });27 });
33 process.stdout.write(JSON.stringify({28 process.stdout.write(JSON.stringify({
34- reference: { source: "actions/runner src/Sdk/DTExpressions2 (Expressions2)", commit: git("rev-parse", "HEAD"), runtime: dotnet.trim() },29+ reference: { source: "actions/runner src/Sdk/DTExpressions2 (Expressions2)", commit: env.commit, runtime: env.runtime },
35 cases,30 cases,
36 }));31 }));
A expr/gen/runner/env.mjs +45 -0
@@ -0,0 +1,45 @@
1+// SPDX-License-Identifier: MIT
2+//
3+// Shared by the scripts of this directory: finds the runner driver, and checks
4+// that the clone it was built from is the one the recorded data is about and
5+// that the .NET runtime is the 9.0 line (number formatting and case folding come
6+// from the runtime, not from the SDK).
7+//
8+// RUNNER_CLONE=/path/to/actions/runner node expr/gen/runner/<script>.mjs
9+
10+import { execFileSync } from "node:child_process";
11+import { readFileSync } from "node:fs";
12+import { join, dirname } from "node:path";
13+import { fileURLToPath } from "node:url";
14+
15+export const here = dirname(fileURLToPath(import.meta.url));
16+
17+/** The commit of actions/runner the data in testdata/ is about (runner-commit.txt). */
18+export const runnerCommit = readFileSync(join(here, "runner-commit.txt"), "utf8").trim();
19+
20+/** The driver built by `dotnet build -c Release expr/gen/runner -p:RunnerSrc=<clone>/src/Sdk`. */
21+export const driver = join(here, "bin/Release/net9.0/runner.dll");
22+
23+/**
24+ * Checks the environment and returns { clone, commit, runtime }. The clone is
25+ * $RUNNER_CLONE (or the first argument); its HEAD must be runner-commit.txt and
26+ * its tree clean in src/Sdk, and `dotnet --list-runtimes` must list a
27+ * Microsoft.NETCore.App 9.0.x.
28+ */
29+export function checkEnvironment(arg) {
30+ const clone = process.env.RUNNER_CLONE || arg;
31+ if (!clone) throw new Error("set RUNNER_CLONE to a clone of https://github.com/actions/runner");
32+ const git = (...a) => execFileSync("git", ["-C", clone, ...a]).toString().trim();
33+ const commit = git("rev-parse", "HEAD");
34+ if (commit !== runnerCommit) throw new Error(`the clone is at ${commit}, the data is about ${runnerCommit} (runner-commit.txt)`);
35+ if (git("status", "--porcelain", "--", "src/Sdk") !== "") throw new Error("the clone has local changes in src/Sdk");
36+ const rt = execFileSync("dotnet", ["--list-runtimes"]).toString().split("\n").filter(l => /^Microsoft\.NETCore\.App 9\.0\.\d+ /.test(l));
37+ if (rt.length === 0) throw new Error("no Microsoft.NETCore.App 9.0.x runtime installed (dotnet --list-runtimes)");
38+ return { clone, commit, runtime: rt[rt.length - 1].replace(/ \[.*$/, "") };
39+}
40+
41+/** Runs the driver with args; the environment is checked first. */
42+export function runDriver(args, opts = {}) {
43+ checkEnvironment();
44+ return execFileSync("dotnet", [driver, ...args], { maxBuffer: 1 << 30, ...opts }).toString("utf8");
45+}
@@ -0,0 +1,45 @@
1+// SPDX-License-Identifier: MIT
2+//
3+// Shared by the scripts of this directory: finds the runner driver, and checks
4+// that the clone it was built from is the one the recorded data is about and
5+// that the .NET runtime is the 9.0 line (number formatting and case folding come
6+// from the runtime, not from the SDK).
7+//
8+// RUNNER_CLONE=/path/to/actions/runner node expr/gen/runner/<script>.mjs
9+
10+import { execFileSync } from "node:child_process";
11+import { readFileSync } from "node:fs";
12+import { join, dirname } from "node:path";
13+import { fileURLToPath } from "node:url";
14+
15+export const here = dirname(fileURLToPath(import.meta.url));
16+
17+/** The commit of actions/runner the data in testdata/ is about (runner-commit.txt). */
18+export const runnerCommit = readFileSync(join(here, "runner-commit.txt"), "utf8").trim();
19+
20+/** The driver built by `dotnet build -c Release expr/gen/runner -p:RunnerSrc=<clone>/src/Sdk`. */
21+export const driver = join(here, "bin/Release/net9.0/runner.dll");
22+
23+/**
24+ * Checks the environment and returns { clone, commit, runtime }. The clone is
25+ * $RUNNER_CLONE (or the first argument); its HEAD must be runner-commit.txt and
26+ * its tree clean in src/Sdk, and `dotnet --list-runtimes` must list a
27+ * Microsoft.NETCore.App 9.0.x.
28+ */
29+export function checkEnvironment(arg) {
30+ const clone = process.env.RUNNER_CLONE || arg;
31+ if (!clone) throw new Error("set RUNNER_CLONE to a clone of https://github.com/actions/runner");
32+ const git = (...a) => execFileSync("git", ["-C", clone, ...a]).toString().trim();
33+ const commit = git("rev-parse", "HEAD");
34+ if (commit !== runnerCommit) throw new Error(`the clone is at ${commit}, the data is about ${runnerCommit} (runner-commit.txt)`);
35+ if (git("status", "--porcelain", "--", "src/Sdk") !== "") throw new Error("the clone has local changes in src/Sdk");
36+ const rt = execFileSync("dotnet", ["--list-runtimes"]).toString().split("\n").filter(l => /^Microsoft\.NETCore\.App 9\.0\.\d+ /.test(l));
37+ if (rt.length === 0) throw new Error("no Microsoft.NETCore.App 9.0.x runtime installed (dotnet --list-runtimes)");
38+ return { clone, commit, runtime: rt[rt.length - 1].replace(/ \[.*$/, "") };
39+}
40+
41+/** Runs the driver with args; the environment is checked first. */
42+export function runDriver(args, opts = {}) {
43+ checkEnvironment();
44+ return execFileSync("dotnet", [driver, ...args], { maxBuffer: 1 << 30, ...opts }).toString("utf8");
45+}
M expr/gen/runner/fromjson_cases.mjs +3 -5
@@ -9,13 +9,11 @@
99 //
1010 // Needs the built driver, see README.md.
1111
12-import { execFileSync } from "node:child_process";
1312 import { writeFileSync, mkdtempSync, rmSync } from "node:fs";
1413 import { tmpdir } from "node:os";
15-import { join, dirname } from "node:path";
16-import { fileURLToPath } from "node:url";
14+import { join } from "node:path";
15+import { runDriver, here, checkEnvironment } from "./env.mjs";
1716
18-const here = dirname(fileURLToPath(import.meta.url));
1917 const seeds = [
2018 "null", "true", "false", "NaN", "Infinity", "-Infinity", "undefined", "0", "-0", "010", "08", "0x1F", "1.5e3", ".5", "5.", "12345678901234567890",
2119 '"a"', "'a'", '"\\u00e9\\ud83d\\ude00"', '"\\ud800"', "[]", "{}", "[1,2,3]", "[1,]", "[,1]", "[1,,2]", '{"a":1}', '{"a":1,"A":2}', "{a:1}", "{'a':'b'}",
@@ -44,7 +42,7 @@ const lit = s => "'" + s.replaceAll("'", "''") + "'";
4442 const cases = inputs.map(s => ({ e: `toJSON(fromJSON(${lit(s)}))` }));
4543 const tmp = mkdtempSync(join(tmpdir(), "fj-"));
4644 writeFileSync(join(tmp, "in.json"), JSON.stringify({ contexts: {}, cases }));
47-const out = execFileSync("dotnet", [join(here, "bin/Release/net9.0/runner.dll"), join(tmp, "in.json")], { maxBuffer: 1 << 28 }).toString("utf8").split("\n").filter(Boolean);
45+const out = runDriver([join(tmp, "in.json")]).split("\n").filter(Boolean);
4846 rmSync(tmp, { recursive: true });
4947 if (out.length !== inputs.length) throw new Error("answer count");
5048 const res = inputs.map((s, i) => {
@@ -9,13 +9,11 @@
9 //9 //
10 // Needs the built driver, see README.md.10 // Needs the built driver, see README.md.
11 11
12-import { execFileSync } from "node:child_process";
13 import { writeFileSync, mkdtempSync, rmSync } from "node:fs";12 import { writeFileSync, mkdtempSync, rmSync } from "node:fs";
14 import { tmpdir } from "node:os";13 import { tmpdir } from "node:os";
15-import { join, dirname } from "node:path";14+import { join } from "node:path";
16-import { fileURLToPath } from "node:url";15+import { runDriver, here, checkEnvironment } from "./env.mjs";
17 16
18-const here = dirname(fileURLToPath(import.meta.url));
19 const seeds = [17 const seeds = [
20 "null", "true", "false", "NaN", "Infinity", "-Infinity", "undefined", "0", "-0", "010", "08", "0x1F", "1.5e3", ".5", "5.", "12345678901234567890",18 "null", "true", "false", "NaN", "Infinity", "-Infinity", "undefined", "0", "-0", "010", "08", "0x1F", "1.5e3", ".5", "5.", "12345678901234567890",
21 '"a"', "'a'", '"\\u00e9\\ud83d\\ude00"', '"\\ud800"', "[]", "{}", "[1,2,3]", "[1,]", "[,1]", "[1,,2]", '{"a":1}', '{"a":1,"A":2}', "{a:1}", "{'a':'b'}",19 '"a"', "'a'", '"\\u00e9\\ud83d\\ude00"', '"\\ud800"', "[]", "{}", "[1,2,3]", "[1,]", "[,1]", "[1,,2]", '{"a":1}', '{"a":1,"A":2}', "{a:1}", "{'a':'b'}",
@@ -44,7 +42,7 @@ const lit = s => "'" + s.replaceAll("'", "''") + "'";
44 const cases = inputs.map(s => ({ e: `toJSON(fromJSON(${lit(s)}))` }));42 const cases = inputs.map(s => ({ e: `toJSON(fromJSON(${lit(s)}))` }));
45 const tmp = mkdtempSync(join(tmpdir(), "fj-"));43 const tmp = mkdtempSync(join(tmpdir(), "fj-"));
46 writeFileSync(join(tmp, "in.json"), JSON.stringify({ contexts: {}, cases }));44 writeFileSync(join(tmp, "in.json"), JSON.stringify({ contexts: {}, cases }));
47-const out = execFileSync("dotnet", [join(here, "bin/Release/net9.0/runner.dll"), join(tmp, "in.json")], { maxBuffer: 1 << 28 }).toString("utf8").split("\n").filter(Boolean);45+const out = runDriver([join(tmp, "in.json")]).split("\n").filter(Boolean);
48 rmSync(tmp, { recursive: true });46 rmSync(tmp, { recursive: true });
49 if (out.length !== inputs.length) throw new Error("answer count");47 if (out.length !== inputs.length) throw new Error("answer count");
50 const res = inputs.map((s, i) => {48 const res = inputs.map((s, i) => {
M expr/gen/runner/goldens.mjs +3 -5
@@ -7,19 +7,17 @@
77 // dotnet build -c Release expr/gen/runner -p:RunnerSrc=<runner>/src/Sdk
88 // node expr/gen/runner/goldens.mjs
99
10-import { execFileSync } from "node:child_process";
1110 import { readFileSync, readdirSync, writeFileSync, mkdtempSync, rmSync } from "node:fs";
1211 import { tmpdir } from "node:os";
13-import { join, dirname } from "node:path";
14-import { fileURLToPath } from "node:url";
12+import { join } from "node:path";
13+import { runDriver, here, checkEnvironment } from "./env.mjs";
1514
16-const here = dirname(fileURLToPath(import.meta.url));
1715 const dir = join(here, "../../testdata/tojson");
1816 const names = readdirSync(dir).filter(f => f.endsWith(".json")).map(f => f.slice(0, -5)).sort();
1917 const cases = names.map(n => ({ e: `toJSON(fromJSON('${readFileSync(join(dir, n + ".json"), "utf8").replaceAll("'", "''")}'))` }));
2018 const tmp = mkdtempSync(join(tmpdir(), "goldens-"));
2119 writeFileSync(join(tmp, "in.json"), JSON.stringify({ contexts: {}, cases }));
22-const out = execFileSync("dotnet", [join(here, "bin/Release/net9.0/runner.dll"), join(tmp, "in.json")], { maxBuffer: 1 << 28 }).toString("utf8").split("\n").filter(Boolean);
20+const out = runDriver([join(tmp, "in.json")]).split("\n").filter(Boolean);
2321 rmSync(tmp, { recursive: true });
2422 names.forEach((n, i) => {
2523 const r = JSON.parse(out[i]);
@@ -7,19 +7,17 @@
7 // dotnet build -c Release expr/gen/runner -p:RunnerSrc=<runner>/src/Sdk7 // dotnet build -c Release expr/gen/runner -p:RunnerSrc=<runner>/src/Sdk
8 // node expr/gen/runner/goldens.mjs8 // node expr/gen/runner/goldens.mjs
9 9
10-import { execFileSync } from "node:child_process";
11 import { readFileSync, readdirSync, writeFileSync, mkdtempSync, rmSync } from "node:fs";10 import { readFileSync, readdirSync, writeFileSync, mkdtempSync, rmSync } from "node:fs";
12 import { tmpdir } from "node:os";11 import { tmpdir } from "node:os";
13-import { join, dirname } from "node:path";12+import { join } from "node:path";
14-import { fileURLToPath } from "node:url";13+import { runDriver, here, checkEnvironment } from "./env.mjs";
15 14
16-const here = dirname(fileURLToPath(import.meta.url));
17 const dir = join(here, "../../testdata/tojson");15 const dir = join(here, "../../testdata/tojson");
18 const names = readdirSync(dir).filter(f => f.endsWith(".json")).map(f => f.slice(0, -5)).sort();16 const names = readdirSync(dir).filter(f => f.endsWith(".json")).map(f => f.slice(0, -5)).sort();
19 const cases = names.map(n => ({ e: `toJSON(fromJSON('${readFileSync(join(dir, n + ".json"), "utf8").replaceAll("'", "''")}'))` }));17 const cases = names.map(n => ({ e: `toJSON(fromJSON('${readFileSync(join(dir, n + ".json"), "utf8").replaceAll("'", "''")}'))` }));
20 const tmp = mkdtempSync(join(tmpdir(), "goldens-"));18 const tmp = mkdtempSync(join(tmpdir(), "goldens-"));
21 writeFileSync(join(tmp, "in.json"), JSON.stringify({ contexts: {}, cases }));19 writeFileSync(join(tmp, "in.json"), JSON.stringify({ contexts: {}, cases }));
22-const out = execFileSync("dotnet", [join(here, "bin/Release/net9.0/runner.dll"), join(tmp, "in.json")], { maxBuffer: 1 << 28 }).toString("utf8").split("\n").filter(Boolean);20+const out = runDriver([join(tmp, "in.json")]).split("\n").filter(Boolean);
23 rmSync(tmp, { recursive: true });21 rmSync(tmp, { recursive: true });
24 names.forEach((n, i) => {22 names.forEach((n, i) => {
25 const r = JSON.parse(out[i]);23 const r = JSON.parse(out[i]);
M expr/gen/runner/number_cases.mjs +3 -5
@@ -8,13 +8,11 @@
88 // dotnet build -c Release expr/gen/runner -p:RunnerSrc=<runner>/src/Sdk
99 // node expr/gen/runner/number_cases.mjs > expr/testdata/number_cases.txt
1010
11-import { execFileSync } from "node:child_process";
1211 import { writeFileSync, mkdtempSync, rmSync } from "node:fs";
1312 import { tmpdir } from "node:os";
14-import { join, dirname } from "node:path";
15-import { fileURLToPath } from "node:url";
13+import { join } from "node:path";
14+import { runDriver, here, checkEnvironment } from "./env.mjs";
1615
17-const here = dirname(fileURLToPath(import.meta.url));
1816 const set = new Set();
1917 const add = s => set.add(s);
2018 const base = ["", " ", "0", "00", "-0", "+0", "1", "-1", "+1", "12", "007", "1.5", "-1.5", "1.", ".5", "-.5", "+.5", ".", "-.", "+.", "..", "1..2", "1.2.3",
@@ -36,7 +34,7 @@ const hex = s => s.length ? [...s].flatMap(c => { const n = c.codePointAt(0); re
3634 const all = [...set];
3735 const tmp = mkdtempSync(join(tmpdir(), "num-"));
3836 writeFileSync(join(tmp, "in.txt"), all.map(hex).join("\n") + "\n");
39-const out = execFileSync("dotnet", [join(here, "bin/Release/net9.0/runner.dll"), "--num", join(tmp, "in.txt")], { maxBuffer: 1 << 28 }).toString().split("\n").filter(Boolean);
37+const out = runDriver(["--num", join(tmp, "in.txt")]).split("\n").filter(Boolean);
4038 rmSync(tmp, { recursive: true });
4139 if (out.length !== all.length) throw new Error("answer count");
4240 process.stdout.write(all.map((s, i) => `${hex(s)} ${out[i]}`).join("\n") + "\n");
@@ -8,13 +8,11 @@
8 // dotnet build -c Release expr/gen/runner -p:RunnerSrc=<runner>/src/Sdk8 // dotnet build -c Release expr/gen/runner -p:RunnerSrc=<runner>/src/Sdk
9 // node expr/gen/runner/number_cases.mjs > expr/testdata/number_cases.txt9 // node expr/gen/runner/number_cases.mjs > expr/testdata/number_cases.txt
10 10
11-import { execFileSync } from "node:child_process";
12 import { writeFileSync, mkdtempSync, rmSync } from "node:fs";11 import { writeFileSync, mkdtempSync, rmSync } from "node:fs";
13 import { tmpdir } from "node:os";12 import { tmpdir } from "node:os";
14-import { join, dirname } from "node:path";13+import { join } from "node:path";
15-import { fileURLToPath } from "node:url";14+import { runDriver, here, checkEnvironment } from "./env.mjs";
16 15
17-const here = dirname(fileURLToPath(import.meta.url));
18 const set = new Set();16 const set = new Set();
19 const add = s => set.add(s);17 const add = s => set.add(s);
20 const base = ["", " ", "0", "00", "-0", "+0", "1", "-1", "+1", "12", "007", "1.5", "-1.5", "1.", ".5", "-.5", "+.5", ".", "-.", "+.", "..", "1..2", "1.2.3",18 const base = ["", " ", "0", "00", "-0", "+0", "1", "-1", "+1", "12", "007", "1.5", "-1.5", "1.", ".5", "-.5", "+.5", ".", "-.", "+.", "..", "1..2", "1.2.3",
@@ -36,7 +34,7 @@ const hex = s => s.length ? [...s].flatMap(c => { const n = c.codePointAt(0); re
36 const all = [...set];34 const all = [...set];
37 const tmp = mkdtempSync(join(tmpdir(), "num-"));35 const tmp = mkdtempSync(join(tmpdir(), "num-"));
38 writeFileSync(join(tmp, "in.txt"), all.map(hex).join("\n") + "\n");36 writeFileSync(join(tmp, "in.txt"), all.map(hex).join("\n") + "\n");
39-const out = execFileSync("dotnet", [join(here, "bin/Release/net9.0/runner.dll"), "--num", join(tmp, "in.txt")], { maxBuffer: 1 << 28 }).toString().split("\n").filter(Boolean);37+const out = runDriver(["--num", join(tmp, "in.txt")]).split("\n").filter(Boolean);
40 rmSync(tmp, { recursive: true });38 rmSync(tmp, { recursive: true });
41 if (out.length !== all.length) throw new Error("answer count");39 if (out.length !== all.length) throw new Error("answer count");
42 process.stdout.write(all.map((s, i) => `${hex(s)} ${out[i]}`).join("\n") + "\n");40 process.stdout.write(all.map((s, i) => `${hex(s)} ${out[i]}`).join("\n") + "\n");
M expr/gen/runner/ordinal_cases.mjs +3 -5
@@ -8,13 +8,11 @@
88 // dotnet build -c Release expr/gen/runner -p:RunnerSrc=<runner>/src/Sdk
99 // node expr/gen/runner/ordinal_cases.mjs > expr/testdata/ordinal_cases.txt
1010
11-import { execFileSync } from "node:child_process";
1211 import { writeFileSync, mkdtempSync, rmSync } from "node:fs";
1312 import { tmpdir } from "node:os";
14-import { join, dirname } from "node:path";
15-import { fileURLToPath } from "node:url";
13+import { join } from "node:path";
14+import { runDriver, here, checkEnvironment } from "./env.mjs";
1615
17-const here = dirname(fileURLToPath(import.meta.url));
1816 const words = [
1917 "", "a", "A", "ab", "AB", "abc", "ABC", "Hello", "hELLO", "hello world", "true", "TRUE", "null",
2018 "ß", "SS", "ss", "ẞ", "ʼn", "ʼN", "ffi", "FFI", "ffi", "ı", "I", "i", "İ", "ſ", "s", "S",
@@ -29,7 +27,7 @@ const lines = [];
2927 for (const a of words) for (const b of words) lines.push(`${hex(a)} ${hex(b)}`);
3028 const tmp = mkdtempSync(join(tmpdir(), "ordinal-"));
3129 writeFileSync(join(tmp, "in.txt"), lines.join("\n") + "\n");
32-const out = execFileSync("dotnet", [join(here, "bin/Release/net9.0/runner.dll"), "--cmp", join(tmp, "in.txt")], { maxBuffer: 1 << 28 }).toString().split("\n").filter(Boolean);
30+const out = runDriver(["--cmp", join(tmp, "in.txt")]).split("\n").filter(Boolean);
3331 rmSync(tmp, { recursive: true });
3432 if (out.length !== lines.length) throw new Error("answer count");
3533 // Keep every pair that is interesting (some predicate true) and a sample of the rest, to bound the file.
@@ -8,13 +8,11 @@
8 // dotnet build -c Release expr/gen/runner -p:RunnerSrc=<runner>/src/Sdk8 // dotnet build -c Release expr/gen/runner -p:RunnerSrc=<runner>/src/Sdk
9 // node expr/gen/runner/ordinal_cases.mjs > expr/testdata/ordinal_cases.txt9 // node expr/gen/runner/ordinal_cases.mjs > expr/testdata/ordinal_cases.txt
10 10
11-import { execFileSync } from "node:child_process";
12 import { writeFileSync, mkdtempSync, rmSync } from "node:fs";11 import { writeFileSync, mkdtempSync, rmSync } from "node:fs";
13 import { tmpdir } from "node:os";12 import { tmpdir } from "node:os";
14-import { join, dirname } from "node:path";13+import { join } from "node:path";
15-import { fileURLToPath } from "node:url";14+import { runDriver, here, checkEnvironment } from "./env.mjs";
16 15
17-const here = dirname(fileURLToPath(import.meta.url));
18 const words = [16 const words = [
19 "", "a", "A", "ab", "AB", "abc", "ABC", "Hello", "hELLO", "hello world", "true", "TRUE", "null",17 "", "a", "A", "ab", "AB", "abc", "ABC", "Hello", "hELLO", "hello world", "true", "TRUE", "null",
20 "ß", "SS", "ss", "ẞ", "ʼn", "ʼN", "ffi", "FFI", "ffi", "ı", "I", "i", "İ", "ſ", "s", "S",18 "ß", "SS", "ss", "ẞ", "ʼn", "ʼN", "ffi", "FFI", "ffi", "ı", "I", "i", "İ", "ſ", "s", "S",
@@ -29,7 +27,7 @@ const lines = [];
29 for (const a of words) for (const b of words) lines.push(`${hex(a)} ${hex(b)}`);27 for (const a of words) for (const b of words) lines.push(`${hex(a)} ${hex(b)}`);
30 const tmp = mkdtempSync(join(tmpdir(), "ordinal-"));28 const tmp = mkdtempSync(join(tmpdir(), "ordinal-"));
31 writeFileSync(join(tmp, "in.txt"), lines.join("\n") + "\n");29 writeFileSync(join(tmp, "in.txt"), lines.join("\n") + "\n");
32-const out = execFileSync("dotnet", [join(here, "bin/Release/net9.0/runner.dll"), "--cmp", join(tmp, "in.txt")], { maxBuffer: 1 << 28 }).toString().split("\n").filter(Boolean);30+const out = runDriver(["--cmp", join(tmp, "in.txt")]).split("\n").filter(Boolean);
33 rmSync(tmp, { recursive: true });31 rmSync(tmp, { recursive: true });
34 if (out.length !== lines.length) throw new Error("answer count");32 if (out.length !== lines.length) throw new Error("answer count");
35 // Keep every pair that is interesting (some predicate true) and a sample of the rest, to bound the file.33 // Keep every pair that is interesting (some predicate true) and a sample of the rest, to bound the file.
A expr/gen/runner/runner-commit.txt +1 -0
@@ -0,0 +1,1 @@
1+5f0b3ddcc6ee9fe9441e00d55e85b94ea4aad798
@@ -0,0 +1,1 @@
1+5f0b3ddcc6ee9fe9441e00d55e85b94ea4aad798
M expr/gen/runner/runner.csproj +4 -1
@@ -10,8 +10,11 @@
1010 <NoWarn>NU1701;CS8632;CS0108;CS0114;CS1591;CS0618;SYSLIB0050</NoWarn>
1111 <EnableDefaultCompileItems>false</EnableDefaultCompileItems>
1212 <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
13- <RunnerSrc Condition="'$(RunnerSrc)' == ''">$(HOME)/dev/clones/runner/src/Sdk</RunnerSrc>
1413 </PropertyGroup>
14+ <Target Name="RequireRunnerSrc" BeforeTargets="BeforeBuild;CoreCompile">
15+ <Error Condition="'$(RunnerSrc)' == '' Or !Exists('$(RunnerSrc)/DTExpressions2')" Text="Set RunnerSrc to the src/Sdk directory of a clone of actions/runner at the commit in runner-commit.txt: dotnet build -c Release expr/gen/runner -p:RunnerSrc=&lt;clone&gt;/src/Sdk" />
16+ </Target>
17+
1518 <ItemGroup>
1619 <Compile Include="$(RunnerSrc)/DTExpressions2/**/*.cs" />
1720 <Compile Include="$(RunnerSrc)/DTLogging/**/*.cs" />
@@ -10,8 +10,11 @@
10 <NoWarn>NU1701;CS8632;CS0108;CS0114;CS1591;CS0618;SYSLIB0050</NoWarn>10 <NoWarn>NU1701;CS8632;CS0108;CS0114;CS1591;CS0618;SYSLIB0050</NoWarn>
11 <EnableDefaultCompileItems>false</EnableDefaultCompileItems>11 <EnableDefaultCompileItems>false</EnableDefaultCompileItems>
12 <AllowUnsafeBlocks>true</AllowUnsafeBlocks>12 <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
13- <RunnerSrc Condition="'$(RunnerSrc)' == ''">$(HOME)/dev/clones/runner/src/Sdk</RunnerSrc>
14 </PropertyGroup>13 </PropertyGroup>
14+ <Target Name="RequireRunnerSrc" BeforeTargets="BeforeBuild;CoreCompile">
15+ <Error Condition="'$(RunnerSrc)' == '' Or !Exists('$(RunnerSrc)/DTExpressions2')" Text="Set RunnerSrc to the src/Sdk directory of a clone of actions/runner at the commit in runner-commit.txt: dotnet build -c Release expr/gen/runner -p:RunnerSrc=&lt;clone&gt;/src/Sdk" />
16+ </Target>
17+
15 <ItemGroup>18 <ItemGroup>
16 <Compile Include="$(RunnerSrc)/DTExpressions2/**/*.cs" />19 <Compile Include="$(RunnerSrc)/DTExpressions2/**/*.cs" />
17 <Compile Include="$(RunnerSrc)/DTLogging/**/*.cs" />20 <Compile Include="$(RunnerSrc)/DTLogging/**/*.cs" />
M expr/runner_oracle_test.go +30 -0
@@ -20,6 +20,12 @@ import (
2020 // and this package follows it (see PORTING.md). TestOracle excuses a mismatch
2121 // with the JavaScript only if this package gives the runner's answer there.
2222
23+// runnerCommit is the commit of actions/runner that every file recorded from the
24+// runner is about. gen/runner/runner-commit.txt holds the same value and the
25+// generator scripts refuse a clone at another commit; TestRunnerCommit checks
26+// that the two agree and that runner.json.gz was made from it.
27+const runnerCommit = "5f0b3ddcc6ee9fe9441e00d55e85b94ea4aad798"
28+
2329 type runnerFile struct {
2430 Reference struct {
2531 Source string `json:"source"`
@@ -179,3 +185,27 @@ func TestSameAnswerFromJSON(t *testing.T) {
179185 }
180186 }
181187 }
188+
189+func TestRunnerCommit(t *testing.T) {
190+ txt, err := os.ReadFile("gen/runner/runner-commit.txt")
191+ if err != nil {
192+ t.Fatal(err)
193+ }
194+ if got := strings.TrimSpace(string(txt)); got != runnerCommit {
195+ t.Errorf("gen/runner/runner-commit.txt = %q, runnerCommit = %q", got, runnerCommit)
196+ }
197+ rf := loadRunner(t)
198+ if rf.Reference.Commit != runnerCommit {
199+ t.Errorf("testdata/runner.json.gz was recorded at %s, want %s", rf.Reference.Commit, runnerCommit)
200+ }
201+ if !strings.HasPrefix(rf.Reference.Runtime, "Microsoft.NETCore.App 9.0.") {
202+ t.Errorf("runtime %q, want Microsoft.NETCore.App 9.0.x", rf.Reference.Runtime)
203+ }
204+ doc, err := os.ReadFile("PORTING.md")
205+ if err != nil {
206+ t.Fatal(err)
207+ }
208+ if !strings.Contains(string(doc), runnerCommit) {
209+ t.Errorf("PORTING.md does not mention the runner commit %s", runnerCommit)
210+ }
211+}
@@ -20,6 +20,12 @@ import (
20 // and this package follows it (see PORTING.md). TestOracle excuses a mismatch20 // and this package follows it (see PORTING.md). TestOracle excuses a mismatch
21 // with the JavaScript only if this package gives the runner's answer there.21 // with the JavaScript only if this package gives the runner's answer there.
22 22
23+// runnerCommit is the commit of actions/runner that every file recorded from the
24+// runner is about. gen/runner/runner-commit.txt holds the same value and the
25+// generator scripts refuse a clone at another commit; TestRunnerCommit checks
26+// that the two agree and that runner.json.gz was made from it.
27+const runnerCommit = "5f0b3ddcc6ee9fe9441e00d55e85b94ea4aad798"
28+
23 type runnerFile struct {29 type runnerFile struct {
24 Reference struct {30 Reference struct {
25 Source string `json:"source"`31 Source string `json:"source"`
@@ -179,3 +185,27 @@ func TestSameAnswerFromJSON(t *testing.T) {
179 }185 }
180 }186 }
181 }187 }
188+
189+func TestRunnerCommit(t *testing.T) {
190+ txt, err := os.ReadFile("gen/runner/runner-commit.txt")
191+ if err != nil {
192+ t.Fatal(err)
193+ }
194+ if got := strings.TrimSpace(string(txt)); got != runnerCommit {
195+ t.Errorf("gen/runner/runner-commit.txt = %q, runnerCommit = %q", got, runnerCommit)
196+ }
197+ rf := loadRunner(t)
198+ if rf.Reference.Commit != runnerCommit {
199+ t.Errorf("testdata/runner.json.gz was recorded at %s, want %s", rf.Reference.Commit, runnerCommit)
200+ }
201+ if !strings.HasPrefix(rf.Reference.Runtime, "Microsoft.NETCore.App 9.0.") {
202+ t.Errorf("runtime %q, want Microsoft.NETCore.App 9.0.x", rf.Reference.Runtime)
203+ }
204+ doc, err := os.ReadFile("PORTING.md")
205+ if err != nil {
206+ t.Fatal(err)
207+ }
208+ if !strings.Contains(string(doc), runnerCommit) {
209+ t.Errorf("PORTING.md does not mention the runner commit %s", runnerCommit)
210+ }
211+}
M expr/testdata/runner.json.gz +0 -0 binary file: no text diff

binary file: no text diff

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 1

Tags 3

↑↓ moveenter openesc close