Fixes-issue-1411: Extract a reusable test harness with Driver and CodeGenerator abstractions#1424
Open
zyf265600 wants to merge 16 commits intoeisop:masterfrom
Open
Fixes-issue-1411: Extract a reusable test harness with Driver and CodeGenerator abstractions#1424zyf265600 wants to merge 16 commits intoeisop:masterfrom
zyf265600 wants to merge 16 commits intoeisop:masterfrom
Conversation
Cache NewClassTree processing in NullnessNoInitAnnotatedTypeFactory to prevent duplicate type annotation calculations.
…rn if type already has @nonnull.
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.
Summary
Adds a reusable test harness for A/B performance testing of annotation processors.
Provides pluggable
DriverandCodeGeneratorinterfaces with three execution modes (in-process, external-process, jtreg), two test protocols (SINGLE, CROSS), and automatic Markdown reports.Motivation
The Checker Framework's type-checking is performance-sensitive. Optimizations can significantly impact analysis time, but lacked systematic benchmarking infrastructure.
Previous limitations
NewClassPerf.java) duplicated timing logic and couldn't produce comparable reportsWhat this enables
Developers can now validate optimizations with
./gradlew :harness-driver-cli:run --args="..."instead of writing custom test harnesses.Components
Core APIs
CodeGenerator- interface: Writes test source files. IncludesNewAndArrayGeneratorfor nullness checking tests (deterministic output via seed).Driver- interface: Runs one test cycle (generate sources → compile → collect results). Three implementations:InProcessJavacDriver: Calls javac viaToolProvider(fast, shared JVM)ExternalProcessJavacDriver: Runs javac as separate process (clean isolation)JtregDriver: Wraps existing jtreg testsCLI Tool
Main: Single command-line entry point with flexible flag handling:Protocols:
Flag system: Isolate the exact change being tested
--baseline-flags: Options for baseline configuration--update-flags: Options for update configurationExample: Testing a fast-path optimization in the nullness checker
Baseline code (always adds annotation):
Optimized code (skip if already present):
Benchmark command:
The harness compiles identical sources under both configs and reports speedup.
Flag forwarding (handles three execution contexts):
Annotation processors read runtime configuration from
System.getProperty("cf.*"). Each engine forwards flags differently based on its JVM boundary:In-process (
InProcessJavacDriver):-Dcf.skipNonnullFastPath=false→System.setProperty("cf.skipNonnullFastPath", "false")-Dand-J-Dforms (strips-Jprefix)External (
ExternalProcessJavacDriver):-J-Dcf.skipNonnullFastPath=false→ passed as JVM argument toProcessBuilder-Jprefix tells javac to forward the flag to its internal JVMJtreg (
JtregDriver):-Dcf.skipNonnullFastPath=false→ forwarded via jtreg's-vmoptionflagJtregPerfHarnessreads it and constructs javac argsAuto-warmup: Each variant runs once (untimed) before measurements to stabilize JIT
How to Use
Run from the repository root. Build artifacts first (needed by --processor-path):
Run these whenever code under
checker/orchecker-qual/changes (or after a clean clone) to keep artifacts up to date.1) Navigate to the harness subproject
The harness is a standalone Gradle subproject with its own
settings.gradle. You must run commands from within its directory:cd checker/harnessAll subsequent commands assume you are in
checker/harness/. The harness uses the root project'sgradlewwrapper via../../gradlew.2) Run harness commands
In-process (fast dev loop):
External process (clean isolation):
Note: Use
-J-Dprefix for system properties with external engine.JTReg (Compatibility with Existing Tests)
Before running, verify that JTReg is available under
checker/harness:If JTReg is not installed, install it using one of the following methods (run from
checker/harness):Automated
Verification Steps (run from
checker/harness)Once JTReg is installed, run the following command from
checker/harness:Note: Jtreg requires
-Dharness.releaseto match--release, and uses-D(not-J-D).Output:
checker/harness/result/report.md(config, env, diagnostics, timing stats, comparison, reproduction commands).3) What each flag means (quick reference)
--generator <Name>NewAndArray.--sampleCount N,--seed S--processor,--processor-pathchecker/dist/checker.jarANDchecker-qual/build/libs/checker-qual-*.jar.--release <8|11|17|21>--protocol SINGLE|CROSSAᵢ,Bᵢ.--runs NA_iand oneB_i. First occurrence of A and B is auto‑warmed (not timed).--engine inproc|external|jtregjavacprocess (clean isolation), or via jtreg (compatible with existing jtreg setups).--baseline-flags ...-A...options.--update-flags ...Extra generator knobs:
--extra.groupsPerFile <N>: increases per‑file work (default 400). More groups → longer compile → less noise.Engine‑specific system property forwarding (for
cf.*and other-Dkeys)inproc: pass
-Dkey=value(also accepts-J-Dkey=value; we strip-Jautomatically).System.getProperty("key")(orBoolean.getBoolean("key")for booleans).true/false(lowercase recommended).123.-Dfoo="a b".=splits key/value; everything after it is the value.-Dto set multiple properties.external: pass
-J-Dkey=value(because-Jforwards to the forked JVM that hostsjavac).javac’s JVM.jtreg: pass
-Dkey=value(we forward via-vmoptionto the test JVM).-Dharness.release=<ver>so the jtreg test uses the same languagelevel as
--release.Examples
-Dcf.skipNonnullFastPath=false-Dcf.skipNonnullFastPath=true-Dharness.release=17 -Dcf.skipNonnullFastPath=true3) Pick the right engine
Reporting
report.mdincluding:For jtreg engine, samples count always equals to 1 is correct because Main.java executes only one driver.runOnce() call per variant, while the --runs x (x > 1) parameter is handled internally by JtregPerfHarness which performs multiple compilations and returns aggregated statistics.
Future Work
Fixes #1411