Skip to content

SONARJAVA-7136 Add opt-in source-only Java analysis comparison - #6307

Draft
matthew-elliott-sonarsource wants to merge 8 commits into
alban/SemanticReportfrom
hackathon/source-only-analysis-comparison
Draft

matthew-elliott-sonarsource wants to merge 8 commits into
alban/SemanticReportfrom
hackathon/source-only-analysis-comparison

Conversation

@matthew-elliott-sonarsource

@matthew-elliott-sonarsource matthew-elliott-sonarsource commented Oct 6, 2026 •

Copy link
Copy Markdown

Add an opt-in harness for comparing current and future SonarJava analysis without compiling the target project or supplying dependency JARs. It is rebased onto alban/SemanticReport and now uses the existing Orchestrator MavenBuild runner.

What it does

  • Places the comparison, helpers, tests, and saved results in its/plugin/tests beside UnitTestsTest.
  • Runs TestUtils.createMavenBuild() through orchestrator.executeBuild(build), passing the semantic JSON path with .setProperty("sonar.java.internal.semantic.report", path).
  • Analyzes separate source copies of sonar-xml using minimal POMs without project dependencies. Runs only sonar:sonar, with empty binaries/libraries overrides, the same JDK, Java 21 level, and Sonar way profile. The candidate remains a placeholder until its feature flag exists.
  • Uses a temporary Community SonarQube server for the comparison. The scanner tests require comparison.project and stay out of normal pipeline runs; the helper unit tests remain enabled.
  • Compares global and per-file total/known/unknown identifiers, unknown percentages, files with no unknown identifiers, file progress, net changes, and the top contributors. Also compares findings by rule, file, and line while preserving duplicates.
  • Saves only numbered Markdown reports beside the test. The latest Maven-runner report is results/run-007/report.md; earlier reports are preserved.
  • Adds a small opt-in UnitTestsTest.semantic_report_without_compilation beside the original example, using its existing Orchestrator and an existing main-source fixture. Also supplies the missing property value in tests_with_report_name_suffix.

Run the comparison

Build/install the Java plugin and custom-rule example, configure artifact access, and ensure Maven is on PATH (or set maven.binary):

mvn -f its/plugin/tests/pom.xml -Pit-plugin test \
  -Dtest=NoCompilationComparisonTest,SourceOnlyComparisonTest,SemanticReportTest \
  -Dcomparison.project="$HOME/Work/Code/sonar-xml" \
  -Dsonar.java.internal.semantic.report=/tmp/report.json

The output property is optional. When supplied, raw JSON is written to /tmp/report-current.json and /tmp/report-candidate.json; otherwise it stays temporary. It selects report output, not the candidate analyzer mode.

Small test in the original class

mvn -f its/plugin/tests/pom.xml -Pit-plugin test \
  '-Dtest=UnitTestsTest#semantic_report_without_compilation' \
  -Dsonar.java.internal.semantic.report=/tmp/report.json

This uses the existing Enterprise lightweight suite and requires valid test-license GitHub access. Its Maven goal is fully qualified to avoid plugin-prefix configuration dependencies. The semantic reporter counts main sources; the original surefire-only fixture can therefore produce an empty report.

Validation

  • Full Maven-backed comparison: all 20 tests passed. Both scans processed 69 files and counted 8,007 identifiers: 4,171 known and 3,836 unknown (47.908%), with the same S6204 finding. Identical results are expected while candidate settings match.
  • Direct test in UnitTestsTest: passed, producing JSON for 3 main-source files with 8 identifiers and 0 unknowns. Validation used existing GitHub access in a private temporary Orchestrator config, removed afterward.
  • Without comparison.project: 18 unit tests passed and the two comparison scanner tests skipped without starting a server or creating a report.
  • Orchestrator invocations were validated serially; separate JVMs share default installation directories under target.

@hashicorp-vault-sonar-prod hashicorp-vault-sonar-prod Bot changed the title Add opt-in source-only Java analysis comparison SONARJAVA-7136 Add opt-in source-only Java analysis comparison Oct 6, 2026
@hashicorp-vault-sonar-prod

hashicorp-vault-sonar-prod Bot commented Oct 6, 2026 •

Copy link
Copy Markdown
Contributor

SONARJAVA-7136

@matthew-elliott-sonarsource
matthew-elliott-sonarsource changed the base branch from master to alban/SemanticReport October 6, 2026 11:18
Comment on lines +47 to +49
Previous results are preserved, including across Maven clean builds. Maven and
IntelliJ use the same location, and the selected directory is printed when the
class starts. Keep saved run directories in Git so results can be reviewed

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Quality: README still says results dir is printed when the class starts

This commit removes the @BeforeAll createResultsDirectory() hook. The run directory is now created and printed inside compare_current_and_candidate_source_only_analysis, after the sources are copied and the rules are loaded. The README still says "the selected directory is printed when the class starts". That is wrong now: running only the smoke test prints nothing, and in the comparison test the path is printed partway through the test. Change the sentence to say the directory is printed when the comparison test runs.

Update the README to match where the directory is now created and printed:

Previous results are preserved, including across Maven clean builds. Maven and
IntelliJ use the same location, and the comparison test prints the selected
directory when it creates it. Keep saved run directories in Git so results can be reviewed
alongside the test.
  • Apply fix

Check the box to apply the fix or reply for a change | Was this helpful? React with 👍 / 👎

@matthew-elliott-sonarsource
matthew-elliott-sonarsource force-pushed the hackathon/source-only-analysis-comparison branch from 72eafcf to 3f58a20 Compare October 6, 2026 13:04
Comment on lines +190 to +199
boolean complete = files.equals(expectedFiles);
SemanticReport semantics = SemanticReport.read(semanticReportPath, expectedFiles);
return new SourceOnlyComparison.Run(label, complete, scanMillis, files, findings, telemetry,
semantics,
complete ? null : "Indexed files differ from the intended production Java files. Expected: " + expectedFiles + "; actual: " + files);
} catch (IOException | RuntimeException e) {
var stacktrace = new StringWriter();
e.printStackTrace(new PrintWriter(stacktrace));
return failed(label, TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start), stacktrace.toString());
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Quality: Semantic-report failure discards scan results and hides file mismatch

In scan(), SemanticReport.read(semanticReportPath, expectedFiles) runs after complete is computed. Its IOException is caught by the outer catch, which returns failed(...). When the indexed files differ from expectedFiles, the semantic report almost always fails the same coverage check. The run is then recorded as a scanner FAILURE with 0 files and 0 findings, and the error text is a semantic-report stack trace instead of the specific "Indexed files differ..." message. A semantic-report problem after a scan that actually succeeded likewise shows as Scan status | FAILED and drops the files, findings and telemetry from the report. It also makes the semantics() == null branch in SourceOnlyComparison.compare unreachable for real runs. Fix: read the semantic report in its own try block. Keep the scan results, set semantics to null or record the semantic error separately, and keep the indexed-files message when complete is false.

Read semantics separately so a successful scan keeps its data and the file-mismatch message is kept:

boolean complete = files.equals(expectedFiles);
SemanticReport semantics = null;
String semanticError = null;
try {
  semantics = SemanticReport.read(semanticReportPath, expectedFiles);
} catch (IOException e) {
  semanticError = e.getMessage();
}
String error = !complete ? "Indexed files differ from the intended production Java files. Expected: " + expectedFiles + "; actual: " + files
  : semanticError;
return new SourceOnlyComparison.Run(label, complete && semanticError == null, scanMillis, files, findings, telemetry, semantics, error);
  • Apply fix

Check the box to apply the fix or reply for a change | Was this helpful? React with 👍 / 👎

Comment on lines +19 to +25
Run the comparison, its scanner smoke test, and the comparison and semantic-report tests:

```sh
mvn -f its/scanner-integration-tests/pom.xml test \
-Dtest=NoCompilationComparisonTest,SourceOnlyComparisonTest,SemanticReportTest \
-Dcomparison.project="$HOME/Work/Code/sonar-xml"
```

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Quality: PR description test counts and run command are stale

The description says "All 9 Maven tests passed" and "the 7 unit tests passed", and its run command lists only NoCompilationComparisonTest,SourceOnlyComparisonTest. It also says the regenerated run-001/report.md is checked in. This commit adds SemanticReportTest (6 tests) and expands SourceOnlyComparisonTest to 10 tests, giving 18 in total. The README command now includes SemanticReportTest, and the new results are in run-002/report.md. Readers following the description will skip the semantic-report tests and look at the wrong report. Fix: update the description's counts, command and report path.

Was this helpful? React with 👍 / 👎

@gitar-bot

gitar-bot Bot commented Oct 7, 2026 •

Copy link
Copy Markdown
Code Review 👍 Approved with suggestions 1 closed / 4 findings

🟡 Medium risk · Adds opt-in source-only scans and semantic-report checks to integration tests.

Adds an opt-in harness to compare SonarJava analysis with and without compilation, using the scanner integration tester and generating a Markdown report of findings by rule and location. The comparison runs without project binaries or dependency libraries, and results are saved in numbered run directories.

Three minor suggestions before merge: update the README to reflect that the results directory is now printed when the comparison test runs rather than at class start; separate semantic-report error handling from scan-completion logic to preserve scan results and show specific file-mismatch messages instead of stack traces; and refresh the PR description's test counts (now 18 total), run command to include SemanticReportTest, and report path to point to run-002/report.md.

💡 Quality: README still says results dir is printed when the class starts

📄 its/scanner-integration-tests/README.md:47-49 📄 its/scanner-integration-tests/src/test/java/org/sonar/java/it/NoCompilationComparisonTest.java:109-111

This commit removes the @BeforeAll createResultsDirectory() hook. The run directory is now created and printed inside compare_current_and_candidate_source_only_analysis, after the sources are copied and the rules are loaded. The README still says "the selected directory is printed when the class starts". That is wrong now: running only the smoke test prints nothing, and in the comparison test the path is printed partway through the test. Change the sentence to say the directory is printed when the comparison test runs.

Update the README to match where the directory is now created and printed
Previous results are preserved, including across Maven clean builds. Maven and
IntelliJ use the same location, and the comparison test prints the selected
directory when it creates it. Keep saved run directories in Git so results can be reviewed
alongside the test.
💡 Quality: Semantic-report failure discards scan results and hides file mismatch

📄 its/scanner-integration-tests/src/test/java/org/sonar/java/it/NoCompilationComparisonTest.java:190-199 📄 its/scanner-integration-tests/src/test/java/org/sonar/java/it/SourceOnlyComparison.java:84-86

In scan(), SemanticReport.read(semanticReportPath, expectedFiles) runs after complete is computed. Its IOException is caught by the outer catch, which returns failed(...). When the indexed files differ from expectedFiles, the semantic report almost always fails the same coverage check. The run is then recorded as a scanner FAILURE with 0 files and 0 findings, and the error text is a semantic-report stack trace instead of the specific "Indexed files differ..." message. A semantic-report problem after a scan that actually succeeded likewise shows as Scan status | FAILED and drops the files, findings and telemetry from the report. It also makes the semantics() == null branch in SourceOnlyComparison.compare unreachable for real runs. Fix: read the semantic report in its own try block. Keep the scan results, set semantics to null or record the semantic error separately, and keep the indexed-files message when complete is false.

Read semantics separately so a successful scan keeps its data and the file-mismatch message is kept
boolean complete = files.equals(expectedFiles);
SemanticReport semantics = null;
String semanticError = null;
try {
  semantics = SemanticReport.read(semanticReportPath, expectedFiles);
} catch (IOException e) {
  semanticError = e.getMessage();
}
String error = !complete ? "Indexed files differ from the intended production Java files. Expected: " + expectedFiles + "; actual: " + files
  : semanticError;
return new SourceOnlyComparison.Run(label, complete && semanticError == null, scanMillis, files, findings, telemetry, semantics, error);
💡 Quality: PR description test counts and run command are stale

📄 its/scanner-integration-tests/README.md:19-25 📄 its/scanner-integration-tests/src/test/java/org/sonar/java/it/SemanticReportTest.java:34-48 📄 its/scanner-integration-tests/src/test/java/org/sonar/java/it/results/run-002/report.md:1

The description says "All 9 Maven tests passed" and "the 7 unit tests passed", and its run command lists only NoCompilationComparisonTest,SourceOnlyComparisonTest. It also says the regenerated run-001/report.md is checked in. This commit adds SemanticReportTest (6 tests) and expands SourceOnlyComparisonTest to 10 tests, giving 18 in total. The README command now includes SemanticReportTest, and the new results are in run-002/report.md. Readers following the description will skip the semantic-report tests and look at the wrong report. Fix: update the description's counts, command and report path.

✅ 1 closed
✅ Quality: Committed run logs contain the author's local absolute paths

📄 its/scanner-integration-tests/src/test/java/org/sonar/java/it/results/run-001/candidate.log:1 📄 its/scanner-integration-tests/src/test/java/org/sonar/java/it/NoCompilationComparisonTest.java:153 📄 its/scanner-integration-tests/src/test/java/org/sonar/java/it/NoCompilationComparisonTest.java:164
scan() writes the full scanner property map to the first line of each log, and that map includes sonar.java.jdkHome=System.getProperty("java.home"). The committed run-001/candidate.log (and the matching current.log and smoke.log) therefore contain /Users/matthew.elliott/Library/Java/JavaVirtualMachines/..., which puts a developer's username and machine layout into the repo. Every future run that is committed the way the README asks ('Keep saved run directories in Git') will do the same. Fix: redact the JDK home when writing logs, or scrub run-* logs before committing them.

🤖 Prompt for agents
Code Review: Adds an opt-in harness to compare SonarJava analysis with and without compilation, using the scanner integration tester and generating a Markdown report of findings by rule and location. The comparison runs without project binaries or dependency libraries, and results are saved in numbered run directories.
  
  Three minor suggestions before merge: update the README to reflect that the results directory is now printed when the comparison test runs rather than at class start; separate semantic-report error handling from scan-completion logic to preserve scan results and show specific file-mismatch messages instead of stack traces; and refresh the PR description's test counts (now 18 total), run command to include `SemanticReportTest`, and report path to point to `run-002/report.md`.

1. 💡 Quality: README still says results dir is printed when the class starts
   Files: its/scanner-integration-tests/README.md:47-49, its/scanner-integration-tests/src/test/java/org/sonar/java/it/NoCompilationComparisonTest.java:109-111

   This commit removes the `@BeforeAll createResultsDirectory()` hook. The run directory is now created and printed inside `compare_current_and_candidate_source_only_analysis`, after the sources are copied and the rules are loaded. The README still says "the selected directory is printed when the class starts". That is wrong now: running only the smoke test prints nothing, and in the comparison test the path is printed partway through the test. Change the sentence to say the directory is printed when the comparison test runs.

   Fix (Update the README to match where the directory is now created and printed):
   Previous results are preserved, including across Maven clean builds. Maven and
   IntelliJ use the same location, and the comparison test prints the selected
   directory when it creates it. Keep saved run directories in Git so results can be reviewed
   alongside the test.

2. 💡 Quality: Semantic-report failure discards scan results and hides file mismatch
   Files: its/scanner-integration-tests/src/test/java/org/sonar/java/it/NoCompilationComparisonTest.java:190-199, its/scanner-integration-tests/src/test/java/org/sonar/java/it/SourceOnlyComparison.java:84-86

   In `scan()`, `SemanticReport.read(semanticReportPath, expectedFiles)` runs after `complete` is computed. Its `IOException` is caught by the outer catch, which returns `failed(...)`. When the indexed files differ from `expectedFiles`, the semantic report almost always fails the same coverage check. The run is then recorded as a scanner FAILURE with 0 files and 0 findings, and the error text is a semantic-report stack trace instead of the specific "Indexed files differ..." message. A semantic-report problem after a scan that actually succeeded likewise shows as `Scan status | FAILED` and drops the files, findings and telemetry from the report. It also makes the `semantics() == null` branch in `SourceOnlyComparison.compare` unreachable for real runs. Fix: read the semantic report in its own try block. Keep the scan results, set `semantics` to null or record the semantic error separately, and keep the indexed-files message when `complete` is false.

   Fix (Read semantics separately so a successful scan keeps its data and the file-mismatch message is kept):
   boolean complete = files.equals(expectedFiles);
   SemanticReport semantics = null;
   String semanticError = null;
   try {
     semantics = SemanticReport.read(semanticReportPath, expectedFiles);
   } catch (IOException e) {
     semanticError = e.getMessage();
   }
   String error = !complete ? "Indexed files differ from the intended production Java files. Expected: " + expectedFiles + "; actual: " + files
     : semanticError;
   return new SourceOnlyComparison.Run(label, complete && semanticError == null, scanMillis, files, findings, telemetry, semantics, error);

3. 💡 Quality: PR description test counts and run command are stale
   Files: its/scanner-integration-tests/README.md:19-25, its/scanner-integration-tests/src/test/java/org/sonar/java/it/SemanticReportTest.java:34-48, its/scanner-integration-tests/src/test/java/org/sonar/java/it/results/run-002/report.md:1

   The description says "All 9 Maven tests passed" and "the 7 unit tests passed", and its run command lists only `NoCompilationComparisonTest,SourceOnlyComparisonTest`. It also says the regenerated `run-001/report.md` is checked in. This commit adds `SemanticReportTest` (6 tests) and expands `SourceOnlyComparisonTest` to 10 tests, giving 18 in total. The README command now includes `SemanticReportTest`, and the new results are in `run-002/report.md`. Readers following the description will skip the semantic-report tests and look at the wrong report. Fix: update the description's counts, command and report path.

Review coverage

🧪 Functional validation No results

📋 Rules No rules evaluated

🤖 Auto-approval Not enabled · Set up

Options

Auto-apply is off → Gitar will not commit updates to this branch.
Display: compact → Counting what did not apply, without listing it.

Comment with these commands to change the behavior for this request:

Auto-apply Compact
gitar auto-apply:on         
gitar display:verbose         

Was this helpful? React with 👍 / 👎 | Gitar

@sonarqube-next

sonarqube-next Bot commented Oct 7, 2026

Copy link
Copy Markdown
Contributor

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

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant