#405 Fix "File name too long" error with deep paths in JUnit reports#426
#405 Fix "File name too long" error with deep paths in JUnit reports#426ascheman wants to merge 12 commits into
Conversation
There was a problem hiding this comment.
Pull Request Overview
This PR fixes issue #405 by resolving "File name too long" errors when generating JUnit XML reports for HTML files in deeply nested directory structures. The solution introduces a configurable output style system that maintains backward compatibility while providing a new hierarchical mode to solve filename length issues.
- Adds
JunitOutputStyleenum with FLAT (default) and HIERARCHICAL modes - Refactors
JUnitXmlReporterto support both output strategies - Updates all three interfaces (Gradle plugin, Maven plugin, CLI) to expose the new configuration option
Reviewed Changes
Copilot reviewed 9 out of 9 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| src/docs/development/design-discussions.adoc | Adds reference to new documentation for issue #405 |
| src/docs/development/_includes/issue-405.adoc | Comprehensive documentation explaining the problem, solution, and implementation |
| htmlSanityCheck-maven-plugin/src/main/java/org/aim42/htmlsanitycheck/maven/HtmlSanityCheckMojo.java | Adds junitOutputStyle parameter to Maven plugin |
| htmlSanityCheck-gradle-plugin/src/main/groovy/org/aim42/htmlsanitycheck/gradle/HtmlSanityCheckTask.groovy | Adds junitOutputStyle input property to Gradle plugin |
| htmlSanityCheck-core/src/test/groovy/org/aim42/htmlsanitycheck/report/JUnitXmlReporterTest.groovy | Adds comprehensive test coverage for both FLAT and HIERARCHICAL modes |
| htmlSanityCheck-core/src/main/java/org/aim42/htmlsanitycheck/report/JUnitXmlReporter.java | Core implementation supporting both output styles with strategy pattern |
| htmlSanityCheck-core/src/main/java/org/aim42/htmlsanitycheck/Configuration.java | Defines JunitOutputStyle enum and adds configuration field |
| htmlSanityCheck-core/src/main/java/org/aim42/htmlsanitycheck/AllChecksRunner.java | Updates to pass configuration to JUnitXmlReporter |
| htmlSanityCheck-cli/src/main/groovy/org/aim42/htmlsanitycheck/cli/HscCommand.groovy | Adds CLI option for junitOutputStyle |
Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.
|
|
||
| // Ensure the directory exists | ||
| if (!testOutputDir.exists() && !testOutputDir.mkdirs()) { | ||
| throw new RuntimeException("Cannot create directory " + testOutputDir); //NOSONAR(S112) |
There was a problem hiding this comment.
The error message should include more context about why directory creation failed. Consider including the original IOException details or system-specific error information to help with debugging.
| throw new RuntimeException("Cannot create directory " + testOutputDir); //NOSONAR(S112) | |
| StringBuilder errorMsg = new StringBuilder("Cannot create directory: ") | |
| .append(testOutputDir.getAbsolutePath()); | |
| errorMsg.append(" (exists: ").append(testOutputDir.exists()) | |
| .append(", canWrite: ").append(testOutputDir.getParentFile() != null ? testOutputDir.getParentFile().canWrite() : "unknown") | |
| .append(")"); | |
| throw new RuntimeException(errorMsg.toString()); //NOSONAR(S112) |
Test Results123 files ± 0 123 suites ±0 3m 23s ⏱️ -1s For more details on these failures, see this check. Results for commit 3ae9a12. ± Comparison against base commit 0c70c6f. This pull request removes 60 and adds 37 tests. Note that renamed tests count towards both.♻️ This comment has been updated with latest results. |
|
@copilot Please review the latest changes addressing your previous security and error handling suggestions. |
There was a problem hiding this comment.
Pull Request Overview
Copilot reviewed 9 out of 9 changed files in this pull request and generated 1 comment.
Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.
|
|
@ascheman I have tried the fix and its working with the new I tried the instructions in the description but haven't been able to get it to work (it seems like the build on jitpack fails?). Thank you for your work! |
Thanks for trying this, @strido. I experienced similar problems on another branch/PR (cf. #429 / #431) with Jitpack meanwhile. Sorry for the inconvenience. |
testcontainers 1.20.6 -> 1.21.4 — Docker Desktop detection. gradle-git-properties 2.5.3 -> 2.5.4 — Git worktree support.
Maven groupId and all artifactIds are now lowercase, aligning with
Maven conventions and unblocking publication to repositories that
enforce this (notably GitHub Packages).
Old -> New:
Group org.aim42.htmlSanityCheck -> org.aim42.htmlsanitycheck
Gradle plugin id 'org.aim42.htmlSanityCheck' -> id 'org.aim42.htmlsanitycheck'
All artifacts htmlSanityCheck-* -> htmlsanitycheck-*
Internal Gradle project directories (htmlSanityCheck-core/, etc.) and
DSL closure names (htmlSanityCheck { ... }) are unchanged for
readability; only the published coordinates differ.
Each subproject sets base.archivesName explicitly to its lowercase
artifactId; the root maven-publish config picks that up via
artifactId = project.base.archivesName.get().
CHANGELOG carries the migration table with links to the historical
CamelCase artifacts on Maven Central and the Gradle Plugin Portal.
Plugin READMEs gain a prominent [IMPORTANT] admonition pointing at
issue #432 and showing the historical URLs.
The lowercase coordinates introduced by #432 are not yet on the Gradle Plugin Portal or Maven Central — HSC's BrokenHttpLinksChecker would fail self-check and the integration tests on the new badge/canonical URLs in our READMEs and CHANGELOG. Exclude them with grep-able TODO(#432) markers so the cleanup after the first release under the lowercase coords is a pure revert: grep -rn 'TODO(#432)' integration-test self-check
Add a maven repository named 'GitHubPackages' under publishing
{ repositories { } } so all four artifacts (core, gradle-plugin,
gradle-plugin marker, maven-plugin) can be pushed to
https://maven.pkg.github.com/aim42/htmlSanityCheck.
The repo is only registered when GITHUB_USER and GITHUB_TOKEN are
both set in the env — that way running 'publish' tasks that target
other repositories (mavenLocal, the local integration repo) keeps
working without env vars, and a missing-PAT mistake yields a clear
"task not found" instead of Gradle's cryptic "property
'credentials.username' doesn't have a configured value".
Plugin READMEs gain a "Development versions" section that recommends
GitHub Packages and deprecates JitPack. publishing.adoc gains a
GitHub Packages credentials section and a 'Publish development
snapshot to GitHub Packages' how-to step.
bf8ce30 to
f6b7542
Compare
Feature and bugfix branches now publish under their own
branch-slug-SNAPSHOT (e.g. feature-432-add-gh-packages-SNAPSHOT)
so GitHub Packages can hold multiple in-flight branches in parallel
without colliding on a single 2.0.0-rcN coordinate.
Mechanism:
- Add me.qoomon.git-versioning 6.4.4. For refs matching
^(feature|bugfix)/.+ derive version = ${ref}-SNAPSHOT; for tags
v<version> derive a release version; otherwise fall back to
gradle.properties.
- Add a tiny root :printVersion task so shell callers (generate-
pages, CI) can capture the derived version without parsing build
output.
- Propagate -PhtmlSanityCheckVersion to the integration-test child
Gradle so the gradle-plugin integration test asks for the just-
published SNAPSHOT, not the static 2.0.0-rcN.
- generate-pages now captures printVersion and forwards it via -P
to self-check, so the CI doc-build picks up the SNAPSHOT plugin
instead of trying to resolve the static version that has not
been published yet.
SNAPSHOT-resolution plumbing for embedded/integration Maven:
Gradle's publishToRepository writes Maven layout but no
_remote.repositories markers, so Maven's
EnhancedLocalRepositoryManager refuses to resolve SNAPSHOTs that
simply appear in localRepository. Fix both Maven invocations
(htmlSanityCheck-maven-plugin build-time descriptor generation, and
integration-test/maven-plugin) by:
- declaring build/maven-repo as a 'remote' <repositories> /
<pluginRepositories> entry with a file:// URL, so Maven goes
through its download path and writes the origin markers
- pointing localRepository at a separate .gradle/maven-local-cache
so the cache lives outside build/ (and survives gradle clean,
avoiding re-downloading the Maven plugin toolchain on every
clean build)
The build-time pom.xml gets the absolute repo URL via a
mavenBuildRepoUrl expand binding in generatePom; the integration-
test pom.xml uses Maven's ${project.basedir} since it's loaded
directly.
Daily scheduled workflow that deletes htmlsanitycheck-* SNAPSHOT
versions on GitHub Packages whose branch no longer exists on origin
AND that are older than ORPHAN_RETENTION_DAYS (28).
Policy:
- Release versions (anything not ending in -SNAPSHOT) are NEVER
deleted.
- SNAPSHOTs whose branch still exists on origin are kept regardless
of age. So an active long-lived feature branch retains all its
snapshot history.
- SNAPSHOTs from removed branches stay in a 4-week grace window
before deletion — covers "I deleted the branch but a colleague
still consumes the artifact."
Implementation:
- Slugify active remote branches the same way git-versioning does
(slashes -> dashes) so 'feature/432-…' matches version
'feature-432-…-SNAPSHOT'.
- Iterate the 5 published artifacts (core, cli, gradle-plugin,
maven-plugin, gradle-plugin marker).
- Use the auto-injected secrets.GITHUB_TOKEN — no PAT required.
- workflow_dispatch with a 'dry-run' input for safe tuning before
enabling production deletes.
Replaces the simpler 'actions/delete-package-versions' approach with
branch-awareness: active branches never lose snapshot history, dead
branches age out cleanly.
Add a publish-gh-packages job to gradle-build.yml that runs after build-artifacts succeeds and pushes all four artifacts plus the plugin marker to GitHub Packages. Triggers only on push events to feature/* or bugfix/* branches — those are the refs git-versioning rewrites to <slug>-SNAPSHOT coordinates. develop and main fall through to gradle.properties' static 2.0.0-rcN and would conflict with already-published release versions, so we skip them here. The job uses the auto-injected secrets.GITHUB_TOKEN (no PAT needed for in-repo publishing) and github.actor as the username. The conditional credential block in build.gradle picks them up; without the env vars set (e.g. on PRs from forks) the GitHubPackages repo isn't registered and the job's gradle invocation would be a no-op, but the 'if:' gate prevents that path from running at all.
f6b7542 to
c0d5a67
Compare
SonarCloud's GitHub Actions rule S7637 flags `uses: …@<tag>` as a supply-chain risk: a compromised tag could rewrite the action's implementation on the next workflow run. Pin to the commit SHA the v4 tag points at today (gradle/actions v4.4.3, commit 48b5f213c81028ace310571dc5ec0fbbca0b2947) and keep a trailing `# v4.4.3` comment so the version stays human-readable. Without this, the bugfix/405 PR analysis hits sonar.qualitygate.wait on the unreviewed-new-hotspot metric and fails the build.
Implement hierarchical directory structure for JUnit XML reports to solve filename length issues when checking HTML files in deeply nested directories. Changes: - Modify JUnitXmlReporter to create directory hierarchies mirroring source file structure instead of encoding paths into flat filenames - Add path normalization and security checks to prevent directory traversal - Update JUnitXmlReporterTest with 7 comprehensive tests for hierarchical structure, including edge cases for long paths and relative references - Add helper method findFirstXmlFile() for recursive XML file discovery - Fix tearDown() to handle directories when traversing file tree - Add AsciiDoc documentation in issue-405.adoc with code includes via tags - Update CLAUDE.md with AsciiDoc and commit message conventions - Add source code tags to JUnitXmlReporter.java for documentation includes The solution keeps individual filenames under OS limits while preserving full path information through directory structure. All 379 existing tests continue to pass. Resolves #405 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
Add configuration option to choose between flat and hierarchical JUnit XML output structures for backwards compatibility. - Move JunitOutputStyle enum as nested class in Configuration - Add junitOutputStyle field to Configuration (default: FLAT) - Refactor JUnitXmlReporter with getFlatOutputFile() and getHierarchicalOutputFile() methods - Update Gradle plugin to expose junitOutputStyle property - Update Maven plugin to expose junitOutputStyle parameter - Update CLI to add --junitOutputStyle/-o option - Add 7 comprehensive tests for HIERARCHICAL mode - Update documentation with configuration examples FLAT mode (default) maintains backwards compatibility with existing behavior. HIERARCHICAL mode solves filename length issues for deeply nested directory structures. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
Add tests to improve code coverage for exception handling and both FLAT and HIERARCHICAL output modes. - testFlatModeCreatesEncodedFilename: Tests explicit FLAT mode - testFlatModeIsDefaultWhenNotSpecified: Tests default behavior - testHierarchicalModeFailsWhenCannotCreateDirectory: Tests exception when directory creation fails in HIERARCHICAL mode - Fix testInitReportWithNonWritableDirectory to properly test exception when output path cannot be created These tests address Sonar coverage gaps in: - Line 64: initReport() exception handling - Line 165: getHierarchicalOutputFile() directory creation error - getFlatOutputFile() method coverage Coverage improvements ensure both output modes and exception paths are properly tested. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
Address GitHub Copilot code review suggestions: 1. Enhanced path traversal security - Replace string-based startsWith() check with NIO Path API - Use Path.normalize() for more robust path validation - Prevent sophisticated path traversal attacks 2. Improved error diagnostics - Add detailed context to directory creation failures - Include directory existence status and parent permissions - Make debugging filesystem issues easier 3. Comprehensive test coverage - Add testPathTraversalAttackIsBlocked - Add testPathTraversalWithSymlinkStyleAttackIsBlocked - Add testEnhancedErrorMessageWhenDirectoryCreationFails - Add testEnhancedErrorMessageFormatIsCorrect - All 24 tests pass successfully 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
c0d5a67 to
3ae9a12
Compare
|



Summary
Fixes #405 - Resolves "File name too long" error when generating JUnit XML reports for HTML files in deeply nested directory structures.
The solution introduces a configurable output style for JUnit XML reports:
Changes
Core Implementation
JunitOutputStyleenum as nested class inConfiguration(FLAT, HIERARCHICAL)JUnitXmlReporterto support both output modes with strategy patterngetHierarchicalOutputFile()with path normalization and security checksAllChecksRunnerto pass configuration to JUnitXmlReporterSecurity & Quality Improvements (from GitHub Copilot review)
Plugin/CLI Updates
junitOutputStyleinput propertyjunitOutputStyleparameter--junitOutputStyle/-ocommand-line optionTesting
Documentation
src/docs/development/_includes/issue-405.adocTesting with JitPack
You can test this fix before it's released by using JitPack to build from this branch:
Gradle
```groovy
repositories {
maven { url 'https://jitpack.io' }
}
dependencies {
// For the Gradle plugin
classpath 'com.github.aim42.htmlSanityCheck:htmlSanityCheck-gradle-plugin:bugfix~405-enable-junit-results-per-dir-SNAPSHOT'
}
htmlSanityCheck {
junitOutputStyle = org.aim42.htmlsanitycheck.Configuration.JunitOutputStyle.HIERARCHICAL
}
```
Maven
```xml
com.github.aim42.htmlSanityCheck htmlSanityCheck-maven-plugin bugfix~405-enable-junit-results-per-dir-SNAPSHOT HIERARCHICAL \`\`\`jitpack.io
https://jitpack.io
Note: JitPack replaces
/with~in branch names, sobugfix/405-enable-junit-results-per-dirbecomesbugfix~405-enable-junit-results-per-dir-SNAPSHOTBackward Compatibility
✅ Fully backward compatible - The default value is
FLAT, maintaining existing behavior unless explicitly configured to useHIERARCHICALmode.CI Status
All workflows passing:
Related Commits