test(workflow-compiler): Extend WorkflowCompilerSpec to cover the Python code-generation error path - #7649
Open
eugenegujing wants to merge 1 commit into
Open
Conversation
… 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.
Contributor
Automated Reviewer SuggestionsBased on the
|
Contributor
|
| 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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What changes were proposed in this PR?
WorkflowCompilerscans every Python-based physical operator's generated code for the#EXCEPTION DURING CODE GENERATION:marker thatPythonOperatorDescriptorembeds when an operator'sgeneratePythonCodethrows. On a hit it either appends aRuntimeException("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-sidePythonOperatorDescriptorSpec, 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:
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).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.should trim the marker's message and report it as a plain RuntimeExceptionshould 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.should not subject non-Python operators to the code-generation checkStrict path:
in strict mode should throw immediately when a Python operator's code generation failedin strict mode should not throw for a well-formed Python operatorFive of the seven drive the real shipped
SortOpDesc, whosegeneratePythonCodeopens withrequire(attributes.nonEmpty, ...)and a per-keyrequire(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 onPythonOperatorDescriptorSpec'sThrowingPyOp): no shipped operator raises a whitespace-padded message, and without padding the.trimis unobservable. It is also the spec's only source-operator Python case.Two notes:
SortOpDesc's exactrequirewording, so rewording those messages inworkflow-operatorwill fail theseworkflow-compilertests. That cross-module coupling is deliberate — it pins the end-to-end string a user actually sees — but it is worth knowing.java.lang.RuntimeException:. That prefix comes fromerr.toStringatWorkflowCompiler.scala:66and 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/scalafmtCheckandWorkflowCompiler/Test/scalafmtCheckare 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:
WorkflowCompiler.scala.trimon the captured message&& !isSourceOperator)case None => ())errorListarms (Some→ throw,None→ skip)group(1)→group(0)(.*)(matches every operator)RuntimeException→IllegalArgumentExceptionTwo 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
isPythonBasedguard entirely does not fail test 5 — it aborts the whole suite, becausePhysicalOp.getCodethrows anIllegalAccessError(aLinkageError, which is notNonFatal) 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)