Skip to content

refactor(conformance): the Python runner is codeupipe pipelines - #16

Merged
JoshuaWink merged 2 commits into
judge-as-pipelinefrom
runner-as-pipeline
Oct 13, 2026
Merged

JoshuaWink merged 2 commits into
judge-as-pipelinefrom
runner-as-pipeline

Conversation

@JoshuaWink

Copy link
Copy Markdown
Member

What

The Python conformance runner is now two codeupipe pipelines. It was 15 plain functions in runner.py.

This PR is on top of #15, which is on top of #14. Merge them in that order.

The pipelines (conformance/runners/python/case_runner/)

RunCasePipeline plays one case on one target:

Step What it does
ParseTargetFilter What the name of the target means: compiled or not, async or not, the layout, with State or not
WritePredicatesFilter The Predicates of the case, as real classes in a module on disk
BuildPipelineFilter The pipeline of the case, and the name that the case gives each step label
AttachRecordHookFilter The Hook that records the lifecycle, for a case that asks for it
Valve, compiled: ExportFilter Compiles the pipeline and imports the package from disk
Valve, not compiled: RunDynamicFilter Runs it with run or run_async
Valve, compiled: RunCompiledFilter Runs the compiled function
NameStepsFilter Gives each step in the result the name of the case
FinishResultFilter The id of the case and what its Taps recorded

BenchScenarioPipeline times one scenario. It uses the first three steps and ExportFilter again, then MakeRunFunctionFilter and MeasureFilter.

RunEachCaseFilter and BenchEachScenarioFilter go through the cases. A case that the target cannot express is reported, and the next case still runs. runner.py is the command line: 12 lines.

How it was checked against the old runner

  • On each of the 5 Python targets, both runners got all 30 cases and 7 more that were made to fail (a word that the runner does not know, an empty nested pipeline, Hooks with a failure). The reports are the same, byte for byte.
  • A name that is not a target gives the same message and the same exit code.
  • In the timing mode, the outputs and the refusals are the same on each target. The times are the same within the noise of a run: 6 more runs of one target, in both orders, gave ratios from 0.95 to 1.03.

The old runner is not in this PR. It was used for this comparison only.

Two things that the linter found in the new code

Writing the runner as primitives put it under the rules for primitives, and two of them fired on my first version:

  • CUP-I002: two steps changed the result dict that they got from the Payload, in place. They now make a new dict.
  • CUP-I003: RunDynamicFilter reads pipeline.state. Here that is correct: it is the State of the pipeline of the case, which the step ran, and that State is what the step reports. It has one allow-state-read comment that says so.

What it gains

  • 15 allow-plain comments are gone. The pinned number in the repository self-check goes from 75 to 60.
  • Each step has tests (tests/conformance/test_case_runner.py, 19 tests).
  • scorecard/ used two functions of the old runner file. It now uses the same steps as the runner.

Checks

  • Python tests: 554 pass, on Python 3.12 and 3.14.
  • Conformance: 30 of 30 on each of the 7 targets.
  • Both linters report nothing, in strict mode.
  • Speed check: within its band.

Not in this change

  • scorecard/ (29 comments) and conformance/bench.py (5 comments) are still plain code.
  • The TypeScript runner.

🤖 Generated with Claude Code

JoshuaWink and others added 2 commits October 13, 2026 12:05
The runner was 15 plain functions in runner.py. It is now two pipelines
in conformance/runners/python/case_runner: one plays a case on a target
and one times a scenario. They share the steps that read the target,
write the Predicates, build the pipeline and compile it. Each step is a
primitive in its own file with tests of its own. runner.py is the
command line.

The new runner was run beside the old one on each Python target, with
all 30 cases and 7 more that were made to fail. The reports are the
same byte for byte. In the timing mode the outputs and the refusals are
the same, and the times are the same within the noise of a run.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The differential test tool used two functions of the old runner file.
It now uses the same steps that the runner uses.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@JoshuaWink
JoshuaWink merged commit c27cb15 into judge-as-pipeline Oct 13, 2026
9 of 10 checks passed
JoshuaWink added a commit that referenced this pull request Oct 13, 2026
* refactor(conformance): the judge is a codeupipe pipeline

The judge was eight plain functions in run.py. It is now a pipeline in
conformance/judging: read the cases, choose the targets, run each
target, compare, count, format, and write the results. Each step is a
primitive in its own file and has tests that start no process.

run.py keeps its command line and the function names that the tests and
bench.py use. Each one calls a step of the pipeline, so there is one
judge.

The new judge was run beside the old one. Their reports are the same
byte for byte on all 7 targets, and they gave the same verdict for each
of 6550 results, 2700 of them wrong on purpose.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* refactor(conformance): the Python runner is codeupipe pipelines (#16)

* refactor(conformance): the Python runner is codeupipe pipelines

The runner was 15 plain functions in runner.py. It is now two pipelines
in conformance/runners/python/case_runner: one plays a case on a target
and one times a scenario. They share the steps that read the target,
write the Predicates, build the pipeline and compile it. Each step is a
primitive in its own file with tests of its own. runner.py is the
command line.

The new runner was run beside the old one on each Python target, with
all 30 cases and 7 more that were made to fail. The reports are the
same byte for byte. In the timing mode the outputs and the refusals are
the same, and the times are the same within the noise of a run.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(scorecard): use the steps of the new runner

The differential test tool used two functions of the old runner file.
It now uses the same steps that the runner uses.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant