Skip to content

test(verify): run every operator both ways and report what it did - #8362

Draft
kz930 wants to merge 30 commits into
apache:mainfrom
kz930:feat/verify-run-every-operator
Draft

kz930 wants to merge 30 commits into
apache:mainfrom
kz930:feat/verify-run-every-operator

Conversation

@kz930

@kz930 kz930 commented Sep 2, 2026 •

Copy link
Copy Markdown
Contributor

What changes were proposed in this PR?

The runner discovers the operators that implement the trait, configures each
from its schema, runs it through the engine and through its generated script,
and compares. An operator that cannot be run is reported as a row with the
reason rather than passed over, since an operator missing from the report is
a fact the report has to carry.

OperatorBehaviorSpec is what turns that into tests: one per operator per
configuration its schema offers, named so a single operator can be run alone.

Every operator reads one canonical table, and it now also carries the values a type reaches only at its edges. That means a double at 1e7 and past it, and one under 1e-3, which Java writes in E notation. It means timestamps in 1600, 2262, 2500 and 9999, outside pandas' nanosecond range. And it means text of digits with leading zeros, which a reader must not take for a number. Without them, a difference that only shows at those values is never asked about.

Bullet and Gauge are compared by the figures their script writes, now that it writes every one rather than the first.

A whole workflow is compared too. WorkflowParitySpec, in amber's integration tests, runs each plan in the engine and through its translated script, and compares every leaf. Rows compare as a set unless the leaf's operator declares an order, since the engine runs most operators on several workers. The plans are the ones aimed at how fragments go wrong together: a chain, a holed integer column, a cast timestamp, a Union, both branches of a Split, a column named like a placeholder, and File Scan reading the paths its upstream names. TranslatedPlanRunSpec keeps the two the engine has nothing to compare with: two charts in one plan, and a chart that cannot be drawn.

amber's tests take the compiling service's and workflow-operator's classes and test resources, not the modules themselves. The compiling service brings dropwizard 4, which would evict the dropwizard 1 amber is built on.

Runs whose reason is that both paths raise are no longer skipped. They sit in their own list, variantsBothRefuse, run like any other, and pass only when the engine and the script both refuse, since a script that answers where the engine refuses is as wrong as a different answer. Only a run named there may pass on two errors, so a broken environment cannot turn the suite green. The one exception found is the dummy classifier on an empty table: it fits on nothing into a model that cannot predict, so it stays in variantsNotRun with that reason. File Scan reads the canonical table's own JSON, which both paths refuse as an archive and as a typed line, so those variants are listed there too. Its archives and typed lines are compared on real files, in the scenarios #8364 and #8363 add. A large binary is written to object storage, which the harness does not give the executor, so that variant is in variantsNotRun.

Any related issues, documentation, discussions?

Part of #8325, 23 of 27; that issue lists the set in order. The edge values pass only once #8341, #8704, #8357, #8709, #8710 and #8597 are in. Each of those fixes how one path reads or writes them. All of them come earlier in the set or merge before it. An empty table runs on every operator once #8766 is in, which hands a port that carried no rows its declared columns.

Closes #8425, the task this change is the whole of.

The workflow-level comparison answers the review that asked for plans run end to end. It calls runTranslated from #8358.

How was this PR tested?

The tests in this change cover it. The whole set is exercised together once the last piece lands: every operator run through the engine and through its generated script, and the two answers compared.

Was this PR authored or co-authored using generative AI tooling?

Generated-by: Claude Code (Claude Opus 5, Claude Opus 5.5)

The runner discovers the operators that implement the trait, configures each
from its schema, runs it through the engine and through its generated script,
and compares. An operator that cannot be run is reported as a row with the
reason rather than passed over, since an operator missing from the report is
a fact the report has to carry.

`OperatorBehaviorSpec` is what turns that into tests: one per operator per
configuration its schema offers, named so a single operator can be run alone.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@github-actions github-actions Bot added feature platform Non-amber Scala service paths labels Sep 2, 2026
@github-actions

github-actions Bot commented Sep 2, 2026 •

Copy link
Copy Markdown
Contributor

Automated Reviewer Suggestions

Based on the git blame history of the changed files, we recommend the following reviewers:

  • Contributors with relevant context: @aicam
    You can notify them by mentioning @aicam in a comment.

@codecov-commenter

codecov-commenter commented Sep 2, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 92.72%. Comparing base (d102e26) to head (40811f3).
⚠️ Report is 101 commits behind head on main.

Additional details and impacted files
@@             Coverage Diff              @@
##               main    #8362      +/-   ##
============================================
- Coverage     92.72%   92.72%   -0.01%     
- Complexity     4946     4950       +4     
============================================
  Files          1242     1243       +1     
  Lines         52671    52623      -48     
  Branches       6519     6506      -13     
============================================
- Hits          48838    48793      -45     
  Misses         2214     2214              
+ Partials       1619     1616       -3     
Flag Coverage Δ *Carryforward flag
access-control-service 77.38% <ø> (ø)
agent-service 99.16% <ø> (ø) Carriedforward from 62598dd
amber 88.47% <ø> (+0.01%) ⬆️ Carriedforward from 62598dd
computing-unit-managing-service 60.48% <ø> (+0.07%) ⬆️
config-service 87.37% <ø> (ø)
file-service 81.53% <ø> (ø) Carriedforward from 62598dd
frontend 96.52% <ø> (-0.01%) ⬇️ Carriedforward from 62598dd
notebook-migration-service 83.73% <ø> (ø)
pyamber 98.56% <ø> (ø) Carriedforward from 62598dd
workflow-compiling-service 74.09% <ø> (ø) Carriedforward from 62598dd

*This pull request uses carry forward flags. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

kz930 and others added 9 commits September 2, 2026 16:51
Three examples where one carries the point, and a cross-reference the
reader can follow without being told to.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…erator's issue

The row pointed at the issue for Substring Search and Unnest String, which
this operator was never part of. That issue closes with the change that
answers those two, and this one still raises.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The row said the platform raised on an empty cell. The operator answers one
now, so the variant runs like every other.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…re in

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Both had been withheld for drawing a different picture on every run, which
stopped being true once their placement was seeded. The registration is inert
until the operator implements the trait, so it can sit here rather than making
the change that gives them their generator depend on this one.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Which tier an operator lands in is the one thing the per-operator runs do not
check, so it is what this spec is for. Every visualization, the scorer, an
estimator and an advanced trainer each state where they route and why that is
not the tier they used to be in.

All of them land before this change does, so the assertions hold when it
merges rather than needing to be added back afterwards.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
One assertion per line, like every other test here. The loop it replaced was
the only one of its kind in the file.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
One said the coverage report prints the withheld runs together. It no longer
prints them, so what is left is the reason that does not depend on a reader:
one table means the next exemption has an obvious home.

The other pointed at `TransformHandler.unfillableVariants`, which does not
exist. What it meant is `nullsKeepFilled`, the one thing a handler still says
about its own table rather than about its operator.

`withheldRunsFor` was public for the report that read it. Its only caller now
is beside it, so it says so.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

@carloea2 carloea2 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The transform verification runner looks good.

@kz930
kz930 marked this pull request as draft September 4, 2026 17:12
The configuration generator produces it; this is the label a withholding would
have to name. A paragraph the one below it already carried goes with it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@kz930
kz930 force-pushed the feat/verify-run-every-operator branch from e450a54 to f1e3d14 Compare September 9, 2026 06:50

@carloea2 carloea2 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed alongside the related export and verification PRs. These findings are based on code inspection and focused Python checks, not a full Scala suite run.

@kz930
kz930 force-pushed the feat/verify-run-every-operator branch from a8a3653 to eb8b5aa Compare September 9, 2026 23:36
Every other verify spec runs one operator's fragment with the harness
binding its placeholders, so a fault that only appears once fragments
share a script and a directory has nowhere to show. Three of those are
pinned here: a column named after a placeholder, two charts writing one
file, and a branch that ends the process the other branches still need.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@kz930
kz930 force-pushed the feat/verify-run-every-operator branch from eb8b5aa to b052093 Compare September 9, 2026 23:46
No withheld row names the hostile column run, and no row builds its
label through the helper.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@kz930

kz930 commented Sep 11, 2026 •

Copy link
Copy Markdown
Contributor Author

@carloea2 the finding is fixed: a connected plan is now translated and run end to end. Would you take another look when you have a moment?

@carloea2 carloea2 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The translated chain and branch tests now run through the real translator. Looks good.

@carloea2 carloea2 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Visualization verification compares only one Plotly figure. Bullet and Gauge can produce one figure per input row, but standalone writes only the first figure. A wrong second figure can pass. Please compare every produced figure and add a multirow test.

@carloea2 carloea2 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The only full plan Union test gives both inputs the same file. Wiring the first input twice and dropping the second still passes. Please use distinct input data and assert a value unique to each port.

@carloea2 carloea2 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The full plan tests only create links from output port 0. A Split or If workflow can wire output port 1 incorrectly and every connected plan test still passes. Please add a connected plan that uses both output ports and verify each branch separately.

kz930 and others added 2 commits September 18, 2026 15:51
The union plan gave both of its inputs the same file, so a plan that wired
one upstream twice and dropped the other read the same rows and passed. Each
port now reads a file of its own, and the assertion is on the union's own
frame rather than on everything the run printed: a dropped upstream is still
a leaf of the plan, and printing that leaf puts its rows in stdout anyway.

Every plan here also read its upstream's port 0, so a translator that handed
each downstream the first frame its upstream produced would pass all of them.
The new plan wires both of a split's output ports into branches of their own
and reads each branch separately.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A bullet or gauge chart draws a figure per input row: both paths put all of
them on the page they render, but the exported script writes only the first
to its output.json, and that file was the side being compared. Nine of the
ten figures a run drew went unread, so a wrong one passed.

Those two operators are now compared against the page, which is the side
holding every figure. The ten agreed already, so this closes the hole rather
than a defect.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@kz930

kz930 commented Sep 18, 2026

Copy link
Copy Markdown
Contributor Author

All three fixed: each union port now reads its own file and the assertion is on the union's own frame, a new plan wires both of a split's output ports and reads each branch separately, and the Plotly comparison now reads every figure a run drew rather than the first. That last one lands in #8359 and #8509, where compare.py and the comparator live.

The epilogue writes a declared-integral column as an integer, the way the
engine's writer does, and the schemas to go by are the ones this run already
has in hand from the Texera path.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

@carloea2 carloea2 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The connected plan checks now cover the separate output routes and distinct source inputs. Looks good.

kz930 and others added 5 commits September 23, 2026 16:14
A table with no rows carries no columns in the engine, which is the
design apache#8488 settled on. Candlestick, Contour, Scatter
Matrix, Sort and Strip Chart read a column by name, so each raises on
the empty table. The generated script reads the same empty file into the
same columnless frame and raises the same error, which a forced run of
the script confirmed for all five. Each row states that reason, the way
the scorer's empty-table row already does.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…t keeps its columns

The script is handed an empty input with its columns now, as an exported
script is, so these operators no longer fail on both paths. The engine
still hands them a table of no columns and they fail on the columns they
read. The script handles the same empty table: Sort and the chart
operators return an empty result, an empty chart or their own error
page. The reason each row states says that instead.

Bar Chart, Filled Area Plot and Network Graph join the rows for the same
reason.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Sklearn Prediction and Sklearn Testing were withheld because a JSONL
fixture could not carry a fitted model. Both paths now unpickle a model
cell the way the worker does, and their curated fixtures carry one.

The model comparison predicts on the first port without a BINARY
column. It took port 0, which for these two is the model itself.
Testing's isRegression is not swept, since it has to match the model the
port carries; a regression scenario covers the other branch.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…very bullet and gauge figure

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@kz930

kz930 commented Sep 27, 2026

Copy link
Copy Markdown
Contributor Author

@carloea2 Since your approval the canonical table carries edge values (doubles Java writes in E notation, years outside pandas' range, digit strings), and Bullet and Gauge are compared by every figure they write.

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

@carloea2 carloea2 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Read the runner, routing rules and whole-plan tests. With the exact prerequisite implementations supplied locally, 41 tests pass: 29 canonical-fixture checks, all 8 translated plans, and 4 executions through the actual runner. Those exercise native and generated Limit and Union variants, plus real Python UDF and exported Pie and Contour variants, including nulls, empty input and error pages. Formatting passes.

sbt -batch 'WorkflowCompilingService/testOnly *CanonicalFixtureSpec *TranslatedPlanRunSpec *RunnerExecutionReviewSpec -- -oDF' 'scalafmtCheckAll'

No new runner-specific finding. The branch alone cannot compile yet, and the other routing assertions still need their operator export prerequisites. This is not merge ready until those and CI are cleared.

kz930 and others added 3 commits October 15, 2026 21:44
…ts DOUBLE columns

A specialDoubles run beside nulls and emptyTable. Withheld, with the reason, for
the 56 sklearn operators and the three charts whose library refuses a NaN or an
infinity on both paths; the two dummy classifiers never read a feature and run.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Every withheld run is ByDesign now; nothing reads PendingFix.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The six plans that the engine can also run move from TranslatedPlanRunSpec
to amber's WorkflowParitySpec, which runs each in the engine and compares
every leaf with the script's. File Scan, which needs an upstream to name its
files, joins them. The two plans only the script can answer stay where they
were.

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

kz930 commented Oct 21, 2026

Copy link
Copy Markdown
Contributor Author

@carloea2 the end-to-end layer now also runs each plan in the Texera engine and compares every leaf with the script's output (WorkflowParitySpec). Would you take another look when you have a moment?

kz930 and others added 2 commits October 21, 2026 03:51
A withheld run whose reason is that both paths raise now runs, and passes
only when each does: a script that answers where the engine refuses is as
wrong as a different answer. Every such row held, but one: the dummy
classifier fits on nothing, into a model that cannot predict, so its empty
table stays withheld with that reason.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
They run, so the list of runs not run was the wrong home for them. Each
list now says what it holds: variantsNotRun the runs that cannot be set
up, variantsBothRefuse the runs both paths must refuse.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions github-actions Bot added dependencies Pull requests that update a dependency file common amber-integration labels Oct 21, 2026
Read over the shared table's JSON, an archive or a typed line has no
answer, so both paths must refuse those. A large binary needs object
storage, which the harness does not give the executor, so it is not run.

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

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

amber-integration common dependencies Pull requests that update a dependency file feature platform Non-amber Scala service paths

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Run every operator both ways and report what it did

3 participants