Skip to content

test(workflow-compiler): Extend WorkflowCompilerSpec to cover the Python code-generation error path - #7649

Open
eugenegujing wants to merge 1 commit into
apache:mainfrom
eugenegujing:test/workflow-compiler-codegen-error
Open

test(workflow-compiler): Extend WorkflowCompilerSpec to cover the Python code-generation error path#7649
eugenegujing wants to merge 1 commit into
apache:mainfrom
eugenegujing:test/workflow-compiler-codegen-error

Conversation

@eugenegujing

Copy link
Copy Markdown
Contributor

What changes were proposed in this PR?

WorkflowCompiler scans every Python-based physical operator's generated code for the #EXCEPTION DURING CODE GENERATION: marker that PythonOperatorDescriptor embeds when an operator's generatePythonCode throws. On a hit it either appends a RuntimeException("Operator is not configured properly: ...") to the caller's error list (the editing-time, lenient path) or throws it immediately when no error list was given (the pre-execution, strict path). WorkflowCompilerSpec's 15 tests never exercised either branch; the only existing test of the marker is the producer-side PythonOperatorDescriptorSpec, which asserts the marker is written but not that the compiler reacts to it.

This adds 7 tests to WorkflowCompilerSpec (15 → 22). Test-only: no production file is touched, and none of the 15 existing tests is modified — the diff removes exactly three lines, all of them import lines being widened.

Lenient path:

  1. should accumulate a per-operator error when a Python operator's code generation fails — the error is attributed to the right logical operator, carries the expected message, and compilation continues (the rest of the plan still lands in the physical plan and the storage set).
  2. should attribute each Python code-generation failure to its own logical operator — two failing Python operators with two distinct messages, each keyed to its own id.
  3. should trim the marker's message and report it as a plain RuntimeException
  4. should report no code-generation error for a well-formed Python operator — asserts the operator really is still Python-based, so it cannot pass by quietly ceasing to be one.
  5. should not subject non-Python operators to the code-generation check

Strict path:

  1. in strict mode should throw immediately when a Python operator's code generation failed
  2. in strict mode should not throw for a well-formed Python operator

Five of the seven drive the real shipped SortOpDesc, whose generatePythonCode opens with require(attributes.nonEmpty, ...) and a per-key require (SortOpDesc.scala:34-38). A Sort dropped on the canvas and left unconfigured is therefore a genuine, user-reachable route into the marker state, and it conveniently yields two distinct messages — which is what makes test 2's per-operator attribution meaningful. A configured Sort gives the negative control in tests 4 and 7: same operator, same code path, codegen simply succeeds.

Test 3 needs a small test-only fixture (PaddedFailurePyOp, ~15 lines, modelled on PythonOperatorDescriptorSpec's ThrowingPyOp): no shipped operator raises a whitespace-padded message, and without padding the .trim is unobservable. It is also the spec's only source-operator Python case.

Two notes:

  • Four assertions hard-code SortOpDesc's exact require wording, so rewording those messages in workflow-operator will fail these workflow-compiler tests. That cross-module coupling is deliberate — it pins the end-to-end string a user actually sees — but it is worth knowing.
  • Test 3 also asserts the reported message starts with java.lang.RuntimeException: . That prefix comes from err.toString at WorkflowCompiler.scala:66 and is part of what the UI renders today; the assertion carries a comment saying so, so if that pre-existing wart is ever fixed there is one self-explaining test to update.

Any related issues, documentation, discussions?

Closes #7647

Builds on the specs added by #5019 / #5022 and the module unification in #6143.

How was this PR tested?

sbt "WorkflowCompiler/testOnly *WorkflowCompilerSpec" — 22 tests, all passing (15 pre-existing + 7 new). Running the whole module (WorkflowCompiler/test) is green too: 3 suites / 57 tests / 0 failures. WorkflowCompiler/scalafmtCheck and WorkflowCompiler/Test/scalafmtCheck are clean.

Every new test was mutation-checked, twice and independently: the production check was temporarily broken, the suite re-run, and the file reverted (verified byte-identical afterwards). Highlights, with the tests that are the sole killer of a mutant:

Mutant in WorkflowCompiler.scala Caught by
lenient arm appends and then throws (abort instead of continue) 1 only
record only the first codegen failure per compile 2 only
drop .trim on the captured message 3 only
skip the check for source operators (&& !isSourceOperator) 3 only
strict arm swallows instead of throwing (case None => ()) 6 only
flip the errorList arms (Some → throw, None → skip) 1, 6
change the message prefix 1, 2, 3, 6
group(1)group(0) 1, 2, 3, 6
marker regex changed so it never matches 1, 2, 3, 6
regex relaxed to (.*) (matches every operator) 1, 2, 3, 4, 6, 7
attribute the error to the wrong logical operator id 1, 2, 3
build the error but never record it 1, 2, 3, 6
RuntimeExceptionIllegalArgumentException 3

Two mutants survive, both semantically equivalent rather than gaps: reporting the last marker match instead of the first (a codegen-failure body is exactly the one-line marker, so first == last), and anchoring the regex with ^ — the latter is in fact the fix for a bug found along the way (below), and all 22 tests stay green under it, so the suite does not over-fit to the current unanchored check.

One more mutant is worth calling out honestly: removing the isPythonBased guard entirely does not fail test 5 — it aborts the whole suite, because PhysicalOp.getCode throws an IllegalAccessError (a LinkageError, which is not NonFatal) for non-code operators. Test 5 therefore documents the guard's intent rather than detecting its removal. It is kept for that reason, with a self-check that stops it silently becoming a no-op.

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

Generated-by: Claude Code (Claude Opus 5)

… error path

Extend WorkflowCompilerSpec from 15 to 22 tests to cover the #EXCEPTION DURING CODE GENERATION marker check. The lenient path now asserts that the error is attributed to the failing logical operator, that two failing operators keep their own messages, that the marker's message is trimmed and reported as a plain RuntimeException, and that a well-formed Python operator and a non-Python operator produce no such error. The strict path asserts that the same marker throws immediately and that a well-formed Python operator does not.

Five of the new tests drive the real SortOpDesc, whose code generation rejects an unconfigured operator; only the trim test needs a test-only fixture, since no shipped operator raises a whitespace-padded message. No production code is changed and no existing test is modified.
@github-actions

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: @Yicong-Huang
    You can notify them by mentioning @Yicong-Huang in a comment.

@github-actions

Copy link
Copy Markdown
Contributor

⚠️ Benchmark changes need a look

🟢 0 better · 🔴 6 worse · ⚪ 9 noise (<±5%) · 0 without baseline

Compared against main 12169c2 benchmarked on this same runner, so the delta is largely free of cross-runner hardware noise. The "7d avg" column still reflects the gh-pages dashboard. Treat <±5% as noise unless repeated.

Dashboard · Run

config throughput MB/s latency max Δ latest / 7d
🔴 bs=10 sw=10 sl=64 399 0.243 24,573/33,854/33,854 us 🔴 +9.1% / 🔴 +107.1%
🔴 bs=100 sw=10 sl=64 751 0.458 128,448/166,717/166,717 us 🔴 -8.8% / 🔴 +54.9%
bs=1000 sw=10 sl=64 918 0.56 1,092,061/1,142,894/1,142,894 us ⚪ within ±5% / 🔴 -11.0%
Baseline details

Latest main 12169c2 from same runner

config metric PR latest main 7d avg Δ latest Δ 7d
bs=10 sw=10 sl=64 throughput 399 tuples/sec 421 tuples/sec 774.89 tuples/sec -5.2% -48.5%
bs=10 sw=10 sl=64 MB/s 0.243 MB/s 0.257 MB/s 0.473 MB/s -5.4% -48.6%
bs=10 sw=10 sl=64 p50 24,573 us 22,520 us 12,738 us +9.1% +92.9%
bs=10 sw=10 sl=64 p95 33,854 us 32,803 us 16,348 us +3.2% +107.1%
bs=10 sw=10 sl=64 p99 33,854 us 32,803 us 18,848 us +3.2% +79.6%
bs=100 sw=10 sl=64 throughput 751 tuples/sec 822 tuples/sec 1,005 tuples/sec -8.6% -25.2%
bs=100 sw=10 sl=64 MB/s 0.458 MB/s 0.502 MB/s 0.613 MB/s -8.8% -25.3%
bs=100 sw=10 sl=64 p50 128,448 us 118,379 us 100,970 us +8.5% +27.2%
bs=100 sw=10 sl=64 p95 166,717 us 159,553 us 107,605 us +4.5% +54.9%
bs=100 sw=10 sl=64 p99 166,717 us 159,553 us 116,429 us +4.5% +43.2%
bs=1000 sw=10 sl=64 throughput 918 tuples/sec 915 tuples/sec 1,030 tuples/sec +0.3% -10.9%
bs=1000 sw=10 sl=64 MB/s 0.56 MB/s 0.558 MB/s 0.629 MB/s +0.4% -11.0%
bs=1000 sw=10 sl=64 p50 1,092,061 us 1,100,911 us 991,433 us -0.8% +10.1%
bs=1000 sw=10 sl=64 p95 1,142,894 us 1,145,303 us 1,036,668 us -0.2% +10.2%
bs=1000 sw=10 sl=64 p99 1,142,894 us 1,145,303 us 1,070,470 us -0.2% +6.8%
Raw CSV
config_idx,batch_size,schema_width,string_len,num_batches,total_ms,total_tuples,total_bytes,tuples_per_sec,mb_per_sec,lat_p50_us,lat_p95_us,lat_p99_us
0,10,10,64,20,501.41,200,128000,399,0.243,24572.83,33854.18,33854.18
1,100,10,64,20,2664.87,2000,1280000,751,0.458,128447.94,166716.57,166716.57
2,1000,10,64,20,21798.20,20000,12800000,918,0.560,1092061.23,1142894.49,1142894.49

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

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

extend WorkflowCompilerSpec to cover the Python code-generation error path

1 participant