@polyengine/ct-runner 0.6.2 → 0.6.4

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/esm/run-suite.js CHANGED
@@ -49,9 +49,10 @@ function describeThrow(e) {
49
49
  * emit the complete results-JSONL stream (envelope, one line per case,
50
50
  * terminator) through `opts.emit`. Throws `MissingImportsError` up front
51
51
  * (contracts/embedder-api.md's `requiredImports`) if the caller's imports
52
- * cannot satisfy the suite, and a plain `Error` if the census is empty (an
53
- * empty selection is a run error, per component-test-results/src/lib.rs's
54
- * `fold_jsonl` and harness.mjs's `runSuiteJsonl` — both refuse it).
52
+ * cannot satisfy the suite, and a plain `Error` if the census is empty or
53
+ * an unsharded `only` selects nothing (an empty selection is a run error,
54
+ * per component-test-results/src/lib.rs's `fold_jsonl` and harness.mjs's
55
+ * `runSuiteJsonl`/`runCases` — all refuse it).
55
56
  */
56
57
  export async function runSuite(artifacts, opts) {
57
58
  const provided = opts.imports ?? {};
@@ -116,7 +117,15 @@ export async function runSuite(artifacts, opts) {
116
117
  // `runSuiteJsonl` ("suite enumerated zero cases").
117
118
  throw new Error("suite enumerated zero cases (empty selection is a run error)");
118
119
  }
119
- const counts = { passed: 0, failed: 0, skipped: 0, na: 0, total: 0 };
120
+ const counts = {
121
+ passed: 0,
122
+ failed: 0,
123
+ skipped: 0,
124
+ na: 0,
125
+ deselected: 0,
126
+ selected: 0,
127
+ total: 0,
128
+ };
120
129
  for (const [i, testCase] of census.entries()) {
121
130
  // Stripe membership (issue #110) is decided on the census index `i`,
122
131
  // BEFORE `only`/tag filtering — a case outside this shard's stripe is
@@ -126,13 +135,17 @@ export async function runSuite(artifacts, opts) {
126
135
  continue;
127
136
  const name = String(await testCase.name());
128
137
  counts.total++;
129
- // js/viewer/harness.mjs `runCases`: "if (only && !name.includes(only))
130
- // continue" — a filtered-out case is skipped entirely, no emit.
131
- if (opts.only && !name.includes(opts.only))
132
- continue;
133
- // harness.mjs `runCases` mark scheduling, in its exact order: `only`
134
- // first (above), then drift, then applicability. The N/A row's shape is
135
- // the embed runner's (expected/verify-pipeline-fixture.jsonl):
138
+ // harness.mjs `runCases`: `isSelected` is computed up front (before tag
139
+ // gating) and counted in `selected` regardless of applicability — a
140
+ // case can be both selected and N/A.
141
+ const isSelected = !opts.only || name.includes(opts.only);
142
+ if (isSelected)
143
+ counts.selected++;
144
+ // harness.mjs `runCases` mark scheduling, in its exact order:
145
+ // applicability first, THEN selection — "capability wins over
146
+ // selection" (docs/runner-policy.md "Selection is not capability"): a
147
+ // tags-excluded case is N/A regardless of `only`. The N/A row's shape
148
+ // is the embed runner's (expected/verify-pipeline-fixture.jsonl):
136
149
  // status, first excluding mark as detail, diagnostics-complete true.
137
150
  if (inventory !== null) {
138
151
  const tags = tagsOf(inventory, name);
@@ -151,6 +164,22 @@ export async function runSuite(artifacts, opts) {
151
164
  continue;
152
165
  }
153
166
  }
167
+ // A case that applies but sits outside `only`: reported `deselected`
168
+ // (never executed) rather than omitted, so subset runs keep full
169
+ // coverage with the subsetting visible as selection policy
170
+ // (harness.mjs `runCases`; docs/runner-policy.md "Selection is not
171
+ // capability"). Exact row shape (harness.mjs:197): case, status,
172
+ // `only <filter>` detail — no other fields.
173
+ if (!isSelected) {
174
+ counts.deselected++;
175
+ opts.emit(JSON.stringify({
176
+ case: name,
177
+ status: "deselected",
178
+ detail: `only ${opts.only}`,
179
+ }), i);
180
+ opts.log?.(`${name} … deselected`);
181
+ continue;
182
+ }
154
183
  // js/viewer/harness.mjs `runCases`' `freshCases` branch: re-enumerate
155
184
  // from a fresh instance and run the matching case; a vanished case is
156
185
  // inventory drift, not a failing case, and throws.
@@ -278,6 +307,14 @@ export async function runSuite(artifacts, opts) {
278
307
  opts.emit(JSON.stringify(event), i);
279
308
  opts.log?.(`${name} … ${event.status}`);
280
309
  }
310
+ // The reference runner's empty-selection rule (a typo'd filter must not
311
+ // exit green with the whole census deselected), applied where the whole
312
+ // census is visible — unsharded (harness.mjs `runCases`: "sharded stripes
313
+ // may legitimately match nothing; their coordinator guards over merged
314
+ // counts").
315
+ if (opts.only && opts.shard === undefined && counts.selected === 0) {
316
+ throw new Error(`only \`${opts.only}\` matches no cases (empty selection is a run error)`);
317
+ }
281
318
  opts.emit('{"segment-end":true}');
282
319
  return counts;
283
320
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@polyengine/ct-runner",
3
- "version": "0.6.2",
3
+ "version": "0.6.4",
4
4
  "description": "The polyengine execution runner for component-test-results (L1) conformance suites.",
5
5
  "homepage": "https://github.com/polymorph-components/polyengine#readme",
6
6
  "repository": {
@@ -32,7 +32,7 @@
32
32
  },
33
33
  "dependencies": {
34
34
  "@polyengine/protocol": "^0.3.1",
35
- "@polyengine/runtime": "0.6.2"
35
+ "@polyengine/runtime": "0.6.4"
36
36
  },
37
37
  "_generatedBy": "dnt@0.43.2"
38
38
  }
@@ -25,8 +25,15 @@ export interface RunSuiteOptions {
25
25
  * does the same normalization (`replaceAll("-", "_")`).
26
26
  */
27
27
  suiteName: string;
28
- /** Substring filter: non-matching cases are skipped entirely (no emit),
29
- * per js/viewer/harness.mjs `runCases`'s `only` handling. */
28
+ /** Substring selection: census cases outside it are reported `deselected`
29
+ * (never executed) rather than omitted, so subset runs keep full
30
+ * coverage with the subsetting visible as selection policy
31
+ * (js/viewer/harness.mjs `runCases`'s `only` doc comment;
32
+ * docs/runner-policy.md "Selection is not capability"). Capability wins:
33
+ * a tags-excluded case stays `not-applicable` even outside the
34
+ * selection. A filter matching no census case throws (empty selection is
35
+ * a run error) when the loop sees the whole census — unsharded; pooled
36
+ * coordinators apply the same guard over merged counts. */
30
37
  only?: string;
31
38
  /**
32
39
  * Feature-tag scheduling (issue #25): the features this target LACKS —
@@ -71,7 +78,9 @@ export interface RunSuiteOptions {
71
78
  * nor emitted, exactly as if it never existed for this shard) — this is
72
79
  * the interpretation that keeps the invariant "the union of every shard's
73
80
  * rows, in suite order, equals the unsharded run's rows" (pinned by
74
- * shard_test.ts's partition-identity test).
81
+ * shard_test.ts's partition-identity test). Cases IN the stripe that are
82
+ * then filtered out by `only` still get their `deselected` row, same as
83
+ * an unsharded run.
75
84
  *
76
85
  * Sharded envelope/terminator contract: a sharded call still emits its
77
86
  * own envelope line and its own `{"segment-end":true}` terminator —
@@ -108,6 +117,12 @@ export interface RunCounts {
108
117
  skipped: number;
109
118
  /** Cases scheduled out as `not-applicable` (tag gating; harness.mjs `na`). */
110
119
  na: number;
120
+ /** Cases outside `only` (harness.mjs `deselected`): never executed, but
121
+ * emitted as a `deselected` row (capability outranks selection). */
122
+ deselected: number;
123
+ /** Census cases matching the selection (all of them without `only`),
124
+ * regardless of applicability (harness.mjs `runCases` doc comment). */
125
+ selected: number;
111
126
  total: number;
112
127
  }
113
128
  /**
@@ -115,8 +130,9 @@ export interface RunCounts {
115
130
  * emit the complete results-JSONL stream (envelope, one line per case,
116
131
  * terminator) through `opts.emit`. Throws `MissingImportsError` up front
117
132
  * (contracts/embedder-api.md's `requiredImports`) if the caller's imports
118
- * cannot satisfy the suite, and a plain `Error` if the census is empty (an
119
- * empty selection is a run error, per component-test-results/src/lib.rs's
120
- * `fold_jsonl` and harness.mjs's `runSuiteJsonl` — both refuse it).
133
+ * cannot satisfy the suite, and a plain `Error` if the census is empty or
134
+ * an unsharded `only` selects nothing (an empty selection is a run error,
135
+ * per component-test-results/src/lib.rs's `fold_jsonl` and harness.mjs's
136
+ * `runSuiteJsonl`/`runCases` — all refuse it).
121
137
  */
122
138
  export declare function runSuite(artifacts: ComponentArtifacts, opts: RunSuiteOptions): Promise<RunCounts>;