From b128e9592b9b55788f9a91a71f6735604c14761b Mon Sep 17 00:00:00 2001 From: mariuszs Date: Thu, 16 Apr 2026 08:18:20 +0200 Subject: [PATCH] feat(mvn): add Maven filter module with Surefire/Failsafe XML enrichment Add Java ecosystem support with state-machine parsers for mvn test, compile, checkstyle:check, and dependency:tree (97-99%+ token savings). Enrich test failure output with structured details from Surefire/Failsafe XML reports: stack traces segmented on Caused-by with framework frames collapsed, root-cause preserved, captured stdout/stderr per failing test. Autodetect application package from pom.xml groupId for frame classification (override via RTK_MVN_APP_PACKAGE). Time-gated report reads skip stale files. Red-flag heuristic for zero-test runs. --- CHANGELOG.md | 7 + Cargo.lock | 68 +- Cargo.toml | 2 + README.md | 4 + src/cmds/java/README.md | 59 + src/cmds/java/mod.rs | 1 + src/cmds/java/mvn_cmd.rs | 2231 +++++++++++++++++ src/cmds/java/pom_groupid.rs | 170 ++ ...apshot_enriched_surefire_and_failsafe.snap | 48 + ...ests__snapshot_enriched_surefire_only.snap | 24 + ...md__tests__snapshot_red_flag_no_tests.snap | 5 + src/cmds/java/stack_trace.rs | 649 +++++ src/cmds/java/surefire_reports.rs | 573 +++++ src/cmds/mod.rs | 1 + src/core/toml_filter.rs | 11 +- src/discover/rules.rs | 6 +- src/filters/mvn-build.toml | 44 - src/main.rs | 45 + .../TEST-com.example.DbIntegrationIT.xml | 20 + .../TEST-com.example.PortConflictIT.xml | 9 + tests/fixtures/java/poms/child-pom.xml | 10 + tests/fixtures/java/poms/no-groupid-pom.xml | 6 + .../fixtures/java/poms/single-module-pom.xml | 7 + .../java/stack-traces/multi-caused-by.txt | 10 + .../TEST-com.example.ErrorTest.xml | 9 + .../TEST-com.example.FailingTest.xml | 16 + .../TEST-com.example.FailingTestWithLogs.xml | 24 + .../TEST-com.example.PassingTest.xml | 6 + .../TEST-com.example.SkippedTest.xml | 7 + tests/fixtures/mvn_checkstyle_clean.txt | 33 + .../fixtures/mvn_checkstyle_clean_native.txt | 37 + tests/fixtures/mvn_checkstyle_violations.txt | 44 + tests/fixtures/mvn_compile_auth.txt | 132 + tests/fixtures/mvn_dep_tree_beacon.txt | 652 +++++ tests/fixtures/mvn_dep_tree_conflicts.txt | 16 + tests/fixtures/mvn_dep_tree_large.txt | 142 ++ tests/fixtures/mvn_dep_tree_simple.txt | 22 + tests/fixtures/mvn_test_fail_auth.txt | 95 + tests/fixtures/mvn_test_large_suite.txt | 204 ++ tests/fixtures/mvn_test_many_failures.txt | 115 + tests/fixtures/mvn_test_multimodule.txt | 118 + tests/fixtures/mvn_test_pass_large_ansi.txt | 53 + tests/fixtures/mvn_test_pass_mavenmcp.txt | 35 + tests/fixtures/mvn_verify_auth.txt | 50 + 44 files changed, 5766 insertions(+), 54 deletions(-) create mode 100644 src/cmds/java/README.md create mode 100644 src/cmds/java/mod.rs create mode 100644 src/cmds/java/mvn_cmd.rs create mode 100644 src/cmds/java/pom_groupid.rs create mode 100644 src/cmds/java/snapshots/rtk__cmds__java__mvn_cmd__tests__snapshot_enriched_surefire_and_failsafe.snap create mode 100644 src/cmds/java/snapshots/rtk__cmds__java__mvn_cmd__tests__snapshot_enriched_surefire_only.snap create mode 100644 src/cmds/java/snapshots/rtk__cmds__java__mvn_cmd__tests__snapshot_red_flag_no_tests.snap create mode 100644 src/cmds/java/stack_trace.rs create mode 100644 src/cmds/java/surefire_reports.rs delete mode 100644 src/filters/mvn-build.toml create mode 100644 tests/fixtures/java/failsafe-reports/TEST-com.example.DbIntegrationIT.xml create mode 100644 tests/fixtures/java/failsafe-reports/TEST-com.example.PortConflictIT.xml create mode 100644 tests/fixtures/java/poms/child-pom.xml create mode 100644 tests/fixtures/java/poms/no-groupid-pom.xml create mode 100644 tests/fixtures/java/poms/single-module-pom.xml create mode 100644 tests/fixtures/java/stack-traces/multi-caused-by.txt create mode 100644 tests/fixtures/java/surefire-reports/TEST-com.example.ErrorTest.xml create mode 100644 tests/fixtures/java/surefire-reports/TEST-com.example.FailingTest.xml create mode 100644 tests/fixtures/java/surefire-reports/TEST-com.example.FailingTestWithLogs.xml create mode 100644 tests/fixtures/java/surefire-reports/TEST-com.example.PassingTest.xml create mode 100644 tests/fixtures/java/surefire-reports/TEST-com.example.SkippedTest.xml create mode 100644 tests/fixtures/mvn_checkstyle_clean.txt create mode 100644 tests/fixtures/mvn_checkstyle_clean_native.txt create mode 100644 tests/fixtures/mvn_checkstyle_violations.txt create mode 100644 tests/fixtures/mvn_compile_auth.txt create mode 100644 tests/fixtures/mvn_dep_tree_beacon.txt create mode 100644 tests/fixtures/mvn_dep_tree_conflicts.txt create mode 100644 tests/fixtures/mvn_dep_tree_large.txt create mode 100644 tests/fixtures/mvn_dep_tree_simple.txt create mode 100644 tests/fixtures/mvn_test_fail_auth.txt create mode 100644 tests/fixtures/mvn_test_large_suite.txt create mode 100644 tests/fixtures/mvn_test_many_failures.txt create mode 100644 tests/fixtures/mvn_test_multimodule.txt create mode 100644 tests/fixtures/mvn_test_pass_large_ansi.txt create mode 100644 tests/fixtures/mvn_test_pass_mavenmcp.txt create mode 100644 tests/fixtures/mvn_verify_auth.txt diff --git a/CHANGELOG.md b/CHANGELOG.md index a45309776..161233287 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Features + +* **mvn:** add Maven (Java) filter module — test, compile, checkstyle:check, dependency:tree ([#1089](https://github.com/rtk-ai/rtk/pull/1089)) +* **mvn:** enrich `mvn test` / `verify` / `integration-test` output with structured failure details read from `target/surefire-reports/TEST-*.xml` and `target/failsafe-reports/*.xml`. Stack traces are segmented on `Caused by:` with framework frames collapsed; the root-cause segment is always preserved. +* **mvn:** autodetect application package from `pom.xml` `` (with `/` fallback) for framework-frame classification. Override via `RTK_MVN_APP_PACKAGE`. +* **mvn:** red-flag heuristic — `no tests run` with no fresh XML reports emits a diagnostic pointing at surefire misconfiguration. + ### Bug Fixes * **git:** remove `-u` short alias from `--ultra-compact` to fix `git push -u` upstream tracking ([#1086](https://github.com/rtk-ai/rtk/issues/1086)) diff --git a/Cargo.lock b/Cargo.lock index 7f33886e0..b0ed806f5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -233,6 +233,17 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "console" +version = "0.16.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d64e8af5551369d19cf50138de61f1c42074ab970f74e99be916646777f8fc87" +dependencies = [ + "encode_unicode", + "libc", + "windows-sys 0.61.2", +] + [[package]] name = "core-foundation-sys" version = "0.8.7" @@ -334,6 +345,12 @@ dependencies = [ "syn", ] +[[package]] +name = "encode_unicode" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" + [[package]] name = "env_home" version = "0.1.0" @@ -374,6 +391,17 @@ version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" +[[package]] +name = "filetime" +version = "0.2.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f98844151eee8917efc50bd9e8318cb963ae8b297431495d3f758616ea5c57db" +dependencies = [ + "cfg-if", + "libc", + "libredox", +] + [[package]] name = "find-msvc-tools" version = "0.1.9" @@ -651,6 +679,18 @@ dependencies = [ "serde_core", ] +[[package]] +name = "insta" +version = "1.47.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b4a6248eb93a4401ed2f37dfe8ea592d3cf05b7cf4f8efa867b6895af7e094e" +dependencies = [ + "console", + "once_cell", + "similar", + "tempfile", +] + [[package]] name = "is_terminal_polyfill" version = "1.70.2" @@ -697,7 +737,10 @@ version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1744e39d1d6a9948f4f388969627434e31128196de472883b39f148769bfe30a" dependencies = [ + "bitflags", "libc", + "plain", + "redox_syscall", ] [[package]] @@ -784,6 +827,12 @@ version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" +[[package]] +name = "plain" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" + [[package]] name = "potential_utf" version = "0.1.4" @@ -836,6 +885,15 @@ version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" +[[package]] +name = "redox_syscall" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f450ad9c3b1da563fb6948a8e0fb0fb9269711c9c73d9ea1de5058c79c8d643a" +dependencies = [ + "bitflags", +] + [[package]] name = "redox_users" version = "0.4.6" @@ -892,7 +950,7 @@ dependencies = [ [[package]] name = "rtk" -version = "0.35.0" +version = "0.34.3" dependencies = [ "anyhow", "automod", @@ -900,9 +958,11 @@ dependencies = [ "clap", "colored", "dirs", + "filetime", "flate2", "getrandom 0.4.2", "ignore", + "insta", "lazy_static", "libc", "quick-xml", @@ -1077,6 +1137,12 @@ version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2" +[[package]] +name = "similar" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbbb5d9659141646ae647b42fe094daf6c6192d1620870b449d9557f748b2daa" + [[package]] name = "smallvec" version = "1.15.1" diff --git a/Cargo.toml b/Cargo.toml index ec55eea29..68942ff54 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -42,6 +42,8 @@ libc = "0.2" toml = "0.8" [dev-dependencies] +filetime = "0.2" +insta = "1" [profile.release] opt-level = 3 diff --git a/README.md b/README.md index 6228a689d..c0c6071e9 100644 --- a/README.md +++ b/README.md @@ -47,6 +47,7 @@ rtk filters and compresses command outputs before they reach your LLM context. S | `git log` | 5x | 2,500 | 500 | -80% | | `git add/commit/push` | 8x | 1,600 | 120 | -92% | | `cargo test` / `npm test` | 5x | 25,000 | 2,500 | -90% | +| `mvn test` | 3x | 30,000 | 300 | -99% | | `ruff check` | 3x | 3,000 | 600 | -80% | | `pytest` | 4x | 8,000 | 800 | -90% | | `go test` | 3x | 6,000 | 600 | -90% | @@ -179,6 +180,7 @@ rtk go test # Go tests (NDJSON, -90%) rtk cargo test # Cargo tests (-90%) rtk rake test # Ruby minitest (-90%) rtk rspec # RSpec tests (JSON, -60%+) +rtk mvn test # Maven tests (-99%) rtk err # Filter errors only from any command rtk test # Generic test wrapper - failures only (-90%) ``` @@ -195,6 +197,8 @@ rtk cargo clippy # Cargo clippy (-80%) rtk ruff check # Python linting (JSON, -80%) rtk golangci-lint run # Go linting (JSON, -85%) rtk rubocop # Ruby linting (JSON, -60%+) +rtk mvn build # Maven build (-90%) +rtk mvn dependency:tree # Maven dependency tree (-60%+) ``` ### Package Managers diff --git a/src/cmds/java/README.md b/src/cmds/java/README.md new file mode 100644 index 000000000..a52a6324e --- /dev/null +++ b/src/cmds/java/README.md @@ -0,0 +1,59 @@ +# Java Ecosystem + +> Part of [`src/cmds/`](../README.md) — see also [docs/contributing/TECHNICAL.md](../../../docs/contributing/TECHNICAL.md) + +## Specifics + +- **mvn_cmd.rs** handles Maven (`mvn`) and Maven Wrapper (`mvnw`) commands +- Auto-detects `mvnw` wrapper in project root; falls back to system `mvn` +- `mvn test` uses a state-machine parser (Preamble → Testing → Summary → Done) for 97-99%+ savings on real-world output +- `mvn compile` uses line filtering to strip `[INFO]` noise, download progress, JVM/native-access warnings, and plugin chatter (jOOQ codegen, Liquibase, npm/React builds, typescript-generator). Also routes `process-classes` and `test-compile` through the same filter (same noise profile) +- `mvn checkstyle:check` (aliased as `checkstyle`) compacts violation lines to `path:line:col [Rule] message`, strips mvn startup noise and Help-link boilerplate, keeps `N Checkstyle violations` summary and BUILD SUCCESS/FAILURE +- `mvn dependency:tree` strips "omitted for duplicate" lines, "version managed from" annotations, and collapses deep transitive branches +- Unknown goals stream via `cmd.status()` passthrough (safe for long-running goals like `spring-boot:run`); rare lifecycle phases (`package`, `install`, `verify`, `clean`, `deploy`) also passthrough — filtered only when the output shape matches compile +- Routing via Clap sub-enum with `#[command(external_subcommand)] Other` for unknown goals; compile-like and checkstyle goals received as `Other` are auto-re-dispatched by `route_goal` to the right filter + +## Output enrichment from Surefire/Failsafe XML reports + +When `mvn test` (or verify/integration-test) reports failures, rtk reads +`target/surefire-reports/TEST-*.xml` and `target/failsafe-reports/*.xml` +**after** the build finishes and appends a structured Failures section +with: + +- Full stack trace per failure, with framework frames collapsed and the + root-cause segment preserved (up to 50 lines per trace). +- Captured stdout + stderr from failing tests only, capped at 2000 chars + per test and 10000 chars total. +- File counters in the footer: `(reports: N surefire, M failsafe, K stale files skipped)`. + +### Application-package detection + +rtk classifies stack frames as *application* vs *framework* by comparing +frame class names against the Java `groupId` from `pom.xml`: + +1. `RTK_MVN_APP_PACKAGE` env var (if set, overrides everything). +2. `/` from the pom.xml in the current working directory. +3. Fallback: `//`. +4. Otherwise: no filtering — full stack traces are preserved. + +### Time-gated report reads + +Stale XML reports from previous runs are skipped: only files with +`mtime >= started_at` (captured just before `mvn` executes) are parsed. + +### Red-flag heuristic for "0 tests" + +If the summary says `no tests run` but surefire reports are empty or +absent, rtk emits a diagnostic instead of the silent summary: + +``` +mvn test: 0 tests executed — surefire detected no tests. Check pom.xml (surefire plugin configuration) or run: rtk proxy mvn test +``` + +### Bypass + +For the rare cases where you need the full raw Maven output: + +```bash +rtk proxy mvn test +``` diff --git a/src/cmds/java/mod.rs b/src/cmds/java/mod.rs new file mode 100644 index 000000000..4685e68f0 --- /dev/null +++ b/src/cmds/java/mod.rs @@ -0,0 +1 @@ +automod::dir!(pub "src/cmds/java"); diff --git a/src/cmds/java/mvn_cmd.rs b/src/cmds/java/mvn_cmd.rs new file mode 100644 index 000000000..b8e9274f9 --- /dev/null +++ b/src/cmds/java/mvn_cmd.rs @@ -0,0 +1,2231 @@ +//! Filters Maven (`mvn`) command output — test results, build errors. +//! +//! State machine parser for `mvn test` output with states: +//! Preamble -> Testing -> Summary -> Done. +//! Strips thousands of noise lines to compact failure reports (99%+ savings). + +use crate::cmds::java::surefire_reports::{self, FailureKind, SurefireResult, TestFailure, TestSummary}; +use crate::core::runner; +use crate::core::tracking; +use crate::core::utils::{exit_code_from_status, resolved_command, strip_ansi, truncate}; +use anyhow::{Context, Result}; +use lazy_static::lazy_static; +use regex::Regex; +use std::ffi::OsString; +use std::fmt::Write as _; +use std::path::Path; + +const INFO_TAG: &str = "[INFO]"; +const ERROR_TAG: &str = "[ERROR]"; +const WARNING_TAG: &str = "[WARNING]"; + +const MAX_FAILURES_PER_SOURCE: usize = 10; + +lazy_static! { + static ref TESTS_RUN_RE: Regex = + Regex::new(r"Tests run:\s*(\d+),\s*Failures:\s*(\d+),\s*Errors:\s*(\d+),\s*Skipped:\s*(\d+)") + .unwrap(); + static ref FAILURE_HEADER_RE: Regex = + Regex::new(r"^\[ERROR\]\s+(\S+\.\S+)\s+--\s+Time elapsed:.*<<<\s+(FAILURE|ERROR)!") + .unwrap(); + static ref TOTAL_TIME_RE: Regex = + Regex::new(r"Total time:\s+(.+)") + .unwrap(); + static ref VERSION_MANAGED_RE: Regex = + Regex::new(r"\s*\(version managed from [^)]+\)") + .unwrap(); + /// Code generator config params: `dialect : POSTGRES_15` + /// Also matches parens/hyphens in keys: `interfaces (immutable) : false` + static ref CODEGEN_CONFIG_RE: Regex = + Regex::new(r"^[\w][\w\s()\-]*\s{2,}:(\s|$)") + .unwrap(); + /// Frontend bundle size lines: `257.55 kB build/static/js/main.js` + static ref BUNDLE_SIZE_RE: Regex = + Regex::new(r"^\d[\d.]*\s+[kKMG]?B\s") + .unwrap(); + /// Checkstyle violation lines: + /// `[ERROR] :[[,]] () : ` + /// (also matches `[WARN]` severity for plugins configured with warn level). + static ref CHECKSTYLE_VIOLATION_RE: Regex = + Regex::new(r"^\[(?:ERROR|WARN)\] (.+?):\[(\d+)(?:,(\d+))?\] \(\w+\) (\w+): (.+)$") + .unwrap(); + /// mvnd / maven 3.9+ extension-loader noise: + /// `[INFO] Loaded 22539 auto-discovered prefixes for remote repository central (...)` + static ref PREFIX_LOAD_RE: Regex = + Regex::new(r"Loaded\s+\d+\s+auto-discovered prefixes").unwrap(); +} + +/// JVM warning lines emitted by Java 24+ (restricted methods, native access, +/// terminally-deprecated Unsafe). These have NO `[INFO]/[ERROR]/[WARNING]` +/// prefix — Maven wrappers surface them as bare text. They are always noise +/// for our purposes. +const JVM_WARNING_PREFIXES: &[&str] = &[ + "WARNING: A restricted method", + "WARNING: java.lang.System::", + "WARNING: sun.misc.Unsafe", + "WARNING: Use --enable-native-access", + "WARNING: Restricted methods will be blocked", + "WARNING: A terminally deprecated", + "WARNING: Please consider reporting", +]; + +/// Returns true for mvn startup / JVM / os-detection noise that is not +/// command-specific (applies to compile, checkstyle, and most goals). +/// Expects a raw (non-trimmed) line or a trimmed line — both work. +fn is_mvn_startup_noise(line: &str) -> bool { + let t = line.trim_start(); + + // mvnd / maven 3.9+ extension-loader progress + if PREFIX_LOAD_RE.is_match(t) { + return true; + } + + // JVM restricted-method / native-access warnings (no Maven prefix) + for p in JVM_WARNING_PREFIXES { + if t.starts_with(p) { + return true; + } + } + + // os-maven-plugin detection output: `[INFO] os.detected.name: linux` etc. + if t.starts_with("[INFO] os.detected") { + return true; + } + + false +} + +/// Auto-detect mvnw wrapper; fall back to system `mvn`. +fn mvn_command() -> std::process::Command { + if Path::new("mvnw").exists() { + resolved_command("./mvnw") + } else { + resolved_command("mvn") + } +} + +/// Run `mvn test` with state-machine filtered output. +pub fn run_test(args: &[String], verbose: u8) -> Result { + let mut cmd = mvn_command(); + cmd.arg("test"); + + for arg in args { + cmd.arg(arg); + } + + if verbose > 0 { + eprintln!("Running: mvn test {}", args.join(" ")); + } + + let started_at = std::time::SystemTime::now(); + let cwd = std::env::current_dir().unwrap_or_else(|_| std::path::PathBuf::from(".")); + let app_pkg = crate::cmds::java::pom_groupid::detect(&cwd); + + let cwd_for_filter = cwd.clone(); + let app_pkg_for_filter = app_pkg.clone(); + + runner::run_filtered( + cmd, + "mvn test", + &args.join(" "), + move |raw: &str| { + let filtered = filter_mvn_test(raw); + enrich_with_reports( + &filtered, + &cwd_for_filter, + started_at, + app_pkg_for_filter.as_deref(), + ) + }, + runner::RunOptions::with_tee("mvn_test"), + ) +} + +/// Run `mvn compile` with line-filtered output. +/// +/// `compile` is itself a Maven lifecycle phase (not a goal name we invented), +/// so no implicit default is added when `args` is empty — `mvn compile` runs +/// the compile phase directly. +pub fn run_compile(args: &[String], verbose: u8) -> Result { + run_compile_like("compile", args, verbose) +} + +/// Shared implementation for compile-phase-like goals: runs `mvn ` +/// through `filter_mvn_compile`. Used directly by `run_compile` and reused by +/// `run_other` to route `process-classes` / `test-compile` through the same +/// filter while preserving the original goal name in the invocation and in +/// the tracking label. +fn run_compile_like(goal: &str, args: &[String], verbose: u8) -> Result { + let mut cmd = mvn_command(); + cmd.arg(goal); + for arg in args { + cmd.arg(arg); + } + + if verbose > 0 { + eprintln!("Running: mvn {} {}", goal, args.join(" ")); + } + + let (tool_name, tee_label) = compile_like_labels(goal); + + runner::run_filtered( + cmd, + tool_name, + &args.join(" "), + filter_mvn_compile, + runner::RunOptions::with_tee(tee_label), + ) +} + +/// Run `mvn checkstyle:check` with compact output — strips mvn/JVM startup +/// noise, keeps violations and BUILD SUCCESS/FAILURE summary. +pub fn run_checkstyle(args: &[String], verbose: u8) -> Result { + let mut cmd = mvn_command(); + cmd.arg("checkstyle:check"); + for arg in args { + cmd.arg(arg); + } + + if verbose > 0 { + eprintln!("Running: mvn checkstyle:check {}", args.join(" ")); + } + + runner::run_filtered( + cmd, + "mvn checkstyle:check", + &args.join(" "), + filter_mvn_checkstyle, + runner::RunOptions::with_tee("mvn_checkstyle"), + ) +} + +/// Run `mvn dependency:tree` with filtered output — strips duplicates and boilerplate. +pub fn run_dep_tree(args: &[String], verbose: u8) -> Result { + let mut cmd = mvn_command(); + cmd.arg("dependency:tree"); + + for arg in args { + cmd.arg(arg); + } + + if verbose > 0 { + eprintln!("Running: mvn dependency:tree {}", args.join(" ")); + } + + runner::run_filtered( + cmd, + "mvn dependency:tree", + &args.join(" "), + filter_mvn_dep_tree, + runner::RunOptions::with_tee("mvn_dep_tree"), + ) +} + +/// Goals whose output looks like `mvn compile` (same noise profile: plugin +/// codegen, npm lifecycle, Liquibase, Docker). Tuples are +/// `(goal, tool_name, tee_label)` — single source of truth for routing, +/// tracking labels, and tee filenames. +const COMPILE_LIKE_GOALS: &[(&str, &str, &str)] = &[ + ("compile", "mvn compile", "mvn_compile"), + ("process-classes", "mvn process-classes", "mvn_process_classes"), + ("test-compile", "mvn test-compile", "mvn_test_compile"), +]; + +/// Look up the `(tool_name, tee_label)` pair for a compile-like goal. Callers +/// are gated on `route_goal` / `COMPILE_LIKE_GOALS`, so the fallback is only +/// reached if that invariant is violated. +fn compile_like_labels(goal: &str) -> (&'static str, &'static str) { + for &(g, tool, tee) in COMPILE_LIKE_GOALS { + if g == goal { + return (tool, tee); + } + } + ("mvn compile", "mvn_compile") +} + +/// Routing decision for a raw mvn subcommand seen on `run_other` — i.e. the +/// first positional arg after `rtk mvn`. Pure function, easy to unit-test. +#[derive(Debug, PartialEq, Eq)] +enum GoalRouting { + /// Re-dispatch to `run_compile` (filter_mvn_compile). + Compile, + /// Re-dispatch to `run_checkstyle` (filter_mvn_checkstyle). + Checkstyle, + /// Stream unchanged via `status()`; tracked for metrics only. + Passthrough, +} + +fn route_goal(subcommand: &str) -> GoalRouting { + if COMPILE_LIKE_GOALS.iter().any(|(g, _, _)| *g == subcommand) { + return GoalRouting::Compile; + } + if subcommand == "checkstyle:check" || subcommand == "checkstyle" { + return GoalRouting::Checkstyle; + } + GoalRouting::Passthrough +} + +/// Convert `args[1..]` into `Vec`, lossy-decoding any non-UTF-8 bytes. +/// The subcommand (args[0]) is stripped so callers can re-dispatch to a +/// `run_*` function that prepends its own goal name. +fn trailing_args(args: &[OsString]) -> Vec { + args.iter() + .skip(1) + .map(|a| a.to_string_lossy().into_owned()) + .collect() +} + +/// Handles mvn subcommands not matched by dedicated Clap variants. +/// Compile-like goals go through `filter_mvn_compile`; `checkstyle` and +/// `checkstyle:check` go through `filter_mvn_checkstyle`; everything else +/// streams directly via `status()` (safe for long-running goals like +/// `spring-boot:run`, and metric-only for rare ones like `package`). +pub fn run_other(args: &[OsString], verbose: u8) -> Result { + if args.is_empty() { + anyhow::bail!("mvn: no subcommand specified"); + } + + let subcommand = args[0].to_string_lossy(); + + if verbose > 0 { + eprintln!("Running: mvn {} ...", subcommand); + } + + match route_goal(&subcommand) { + GoalRouting::Compile => { + return run_compile_like(&subcommand, &trailing_args(args), verbose); + } + GoalRouting::Checkstyle => { + return run_checkstyle(&trailing_args(args), verbose); + } + GoalRouting::Passthrough => {} + } + + // Everything else: passthrough with streaming (safe for spring-boot:run etc.) + let timer = tracking::TimedExecution::start(); + + let mut cmd = mvn_command(); + for arg in args { + cmd.arg(arg); + } + + let status = cmd + .status() + .with_context(|| format!("Failed to run mvn {}", subcommand))?; + + let args_str = tracking::args_display(args); + timer.track_passthrough( + &format!("mvn {}", args_str), + &format!("rtk mvn {} (passthrough)", args_str), + ); + + Ok(exit_code_from_status(&status, "mvn")) +} + +// --------------------------------------------------------------------------- +// State machine parser for mvn test output +// --------------------------------------------------------------------------- + +const MAX_DETAIL_LINES: usize = 3; +const MAX_FAILURES_SHOWN: usize = 10; +const MAX_LINE_LENGTH: usize = 200; + +#[derive(Debug, PartialEq)] +enum TestParseState { + Preamble, + Testing, + Summary, + Done, +} + + +struct FailureEntry { + name: String, + details: Vec, +} + +/// Parse the four count fields from a `TESTS_RUN_RE` captures. The regex +/// guarantees four numeric groups so defaulting to 0 is only a safety net. +fn parse_counts(caps: ®ex::Captures) -> TestSummary { + TestSummary { + run: caps.get(1).map_or(0, |m| m.as_str().parse().unwrap_or(0)), + failures: caps.get(2).map_or(0, |m| m.as_str().parse().unwrap_or(0)), + errors: caps.get(3).map_or(0, |m| m.as_str().parse().unwrap_or(0)), + skipped: caps.get(4).map_or(0, |m| m.as_str().parse().unwrap_or(0)), + } +} + +/// Wrap the text-filter summary with structured failure details sourced from +/// `target/surefire-reports/` and `target/failsafe-reports/` XML files. +pub(crate) fn enrich_with_reports( + text_summary: &str, + cwd: &std::path::Path, + since: std::time::SystemTime, + app_package: Option<&str>, +) -> String { + if !text_summary.starts_with("mvn ") { + return text_summary.to_string(); + } + + let zero_tests = text_summary == "mvn test: no tests run" + || text_summary.contains(": 0 passed"); + let has_failures = + text_summary.contains("failed") || text_summary.contains("BUILD FAILURE"); + let looks_clean = text_summary.contains("passed (") + && !text_summary.contains("failed") + && !text_summary.contains("BUILD FAILURE"); + + if looks_clean && !zero_tests { + return text_summary.to_string(); + } + + let sf = surefire_reports::parse_dir( + &cwd.join("target/surefire-reports"), + Some(since), + app_package, + ); + let fs = surefire_reports::parse_dir( + &cwd.join("target/failsafe-reports"), + Some(since), + app_package, + ); + + match (zero_tests, has_failures, &sf, &fs) { + (true, _, None, None) => { + "mvn test: 0 tests executed — surefire detected no tests. \ + Check pom.xml (surefire plugin configuration) or run: \ + rtk proxy mvn test" + .to_string() + } + (_, true, None, None) => format!( + "{text_summary}\n(no XML reports found — check target/surefire-reports/ \ + or run: rtk proxy mvn test)" + ), + _ => render_enriched(text_summary, sf.as_ref(), fs.as_ref()), + } +} + +fn render_enriched( + text_summary: &str, + surefire: Option<&SurefireResult>, + failsafe: Option<&SurefireResult>, +) -> String { + let mut out = String::from(text_summary); + + if let Some(sf) = surefire { + if !sf.failures.is_empty() { + out.push_str("\n\nFailures (from surefire-reports/):\n"); + render_failure_block(&mut out, &sf.failures); + } + } + + if let Some(fs) = failsafe { + if !fs.failures.is_empty() { + out.push_str("\n\nIntegration failures (from failsafe-reports/):\n"); + render_failure_block(&mut out, &fs.failures); + } + } + + let footer = render_footer(surefire, failsafe); + if !footer.is_empty() { + out.push_str("\n\n"); + out.push_str(&footer); + } + + out +} + +fn render_failure_block(out: &mut String, failures: &[TestFailure]) { + let shown = failures.iter().take(MAX_FAILURES_PER_SOURCE); + for (i, f) in shown.enumerate() { + writeln!(out, "{}. {}.{}", i + 1, f.test_class, f.test_method).ok(); + if let Some(kind_label) = failure_kind_label(f) { + writeln!(out, " {kind_label}").ok(); + } + if let Some(trace) = &f.stack_trace { + for line in trace.lines() { + writeln!(out, " {line}").ok(); + } + } + if let Some(output) = f.test_output.as_deref().filter(|s| !s.is_empty()) { + writeln!(out, " captured output:").ok(); + for line in output.lines() { + writeln!(out, " {line}").ok(); + } + } + out.push('\n'); + } + if failures.len() > MAX_FAILURES_PER_SOURCE { + writeln!( + out, + "... +{} more failures", + failures.len() - MAX_FAILURES_PER_SOURCE + ) + .ok(); + } +} + +fn failure_kind_label(f: &TestFailure) -> Option { + let msg = f.message.as_deref().unwrap_or("").trim(); + let ty = f + .failure_type + .as_deref() + .and_then(|t| t.rsplit('.').next()) + .unwrap_or(""); + match (ty.is_empty(), msg.is_empty()) { + (true, true) => None, + (true, false) => Some(msg.to_string()), + (false, true) => Some(ty.to_string()), + (false, false) => Some(format!("{ty}: {msg}")), + } + .map(|s| match f.kind { + FailureKind::Error => format!("[error] {s}"), + FailureKind::Failure => s, + }) +} + +fn render_footer( + surefire: Option<&SurefireResult>, + failsafe: Option<&SurefireResult>, +) -> String { + let mut parts: Vec = Vec::new(); + let (sf_read, sf_stale, sf_bad) = counts(surefire); + let (fs_read, fs_stale, fs_bad) = counts(failsafe); + + if sf_read > 0 { + parts.push(format!("{sf_read} surefire")); + } + if fs_read > 0 { + parts.push(format!("{fs_read} failsafe")); + } + let stale = sf_stale + fs_stale; + if stale > 0 { + parts.push(format!("{stale} stale files skipped")); + } + let malformed = sf_bad + fs_bad; + if malformed > 0 { + parts.push(format!("{malformed} malformed")); + } + if parts.is_empty() { + return String::new(); + } + format!("(reports: {})", parts.join(", ")) +} + +fn counts(r: Option<&SurefireResult>) -> (usize, usize, usize) { + r.map(|x| (x.files_read, x.files_skipped_stale, x.files_malformed)) + .unwrap_or((0, 0, 0)) +} + +/// Filter `mvn test` output using a state machine parser. +/// +/// States: Preamble -> Testing -> Summary -> Done +/// - Preamble: skip everything before "T E S T S" marker +/// - Testing: collect failure details from [ERROR] headers and assertion lines +/// - Summary: parse final "Tests run:" line, BUILD SUCCESS/FAILURE, Total time +/// - Done: stop at Help boilerplate +fn filter_mvn_test(output: &str) -> String { + let clean = strip_ansi(output); + let mut state = TestParseState::Preamble; + + let mut failures: Vec = Vec::with_capacity(MAX_FAILURES_SHOWN); + let mut current_failure: Option = None; + + let mut cumulative = TestSummary::default(); + let mut section: Option = None; + let mut total_time: Option = None; + let mut total_failures_seen: usize = 0; + + for line in clean.lines() { + let trimmed = line.trim(); + let stripped = strip_maven_prefix(trimmed); + + // Global transition: T E S T S marker resets to Testing from any state + // (multi-module builds emit this marker per module) + if stripped.contains("T E S T S") { + if let Some(s) = section.take() { + cumulative.add(&s); + } + state = TestParseState::Testing; + continue; + } + + match state { + TestParseState::Preamble => {} + TestParseState::Testing => { + if stripped == "Results:" { + if let Some(f) = current_failure.take() { + total_failures_seen += 1; + if failures.len() < MAX_FAILURES_SHOWN { + failures.push(f); + } + } + state = TestParseState::Summary; + continue; + } + + if let Some(caps) = FAILURE_HEADER_RE.captures(trimmed) { + if let Some(f) = current_failure.take() { + total_failures_seen += 1; + if failures.len() < MAX_FAILURES_SHOWN { + failures.push(f); + } + } + let test_name = caps.get(1).map_or("", |m| m.as_str()).to_string(); + current_failure = Some(FailureEntry { + name: test_name, + details: Vec::new(), + }); + continue; + } + + // Per-plugin summary line inside the Testing block: + // "Tests run: N, Failures: N, Errors: N, Skipped: N" with no + // "-- in " suffix. Priority over any later Summary-state + // match so that the reactor aggregate (which appears after the + // LAST module's Summary block in multi-module builds) does not + // overwrite the real per-module total. + if !trimmed.contains("-- in") { + if let Some(caps) = TESTS_RUN_RE.captures(stripped) { + section = Some(parse_counts(&caps)); + continue; + } + } + + if let Some(ref mut f) = current_failure { + if f.details.len() >= MAX_DETAIL_LINES { + continue; + } + if is_framework_frame(stripped) + || is_maven_boilerplate(trimmed) + || stripped.is_empty() + || (trimmed.starts_with(ERROR_TAG) && stripped.contains("<<<")) + { + continue; + } + f.details.push(stripped.to_string()); + } + } + TestParseState::Summary => { + if is_maven_boilerplate(trimmed) || stripped.starts_with("Failures:") { + continue; + } + + if section.is_none() { + if let Some(caps) = TESTS_RUN_RE.captures(stripped) { + section = Some(parse_counts(&caps)); + } + } + + if let Some(caps) = TOTAL_TIME_RE.captures(stripped) { + total_time = Some(caps.get(1).map_or("", |m| m.as_str()).trim().to_string()); + state = TestParseState::Done; + } + } + TestParseState::Done => break, + } + } + + if let Some(s) = section.take() { + cumulative.add(&s); + } + + if state == TestParseState::Preamble { + return "mvn test: no tests run".to_string(); + } + + let counts = cumulative; + let time_str = total_time.as_deref().unwrap_or("?"); + let has_failures = counts.failures > 0 || counts.errors > 0; + + if !has_failures { + let passed = counts.run.saturating_sub(counts.skipped); + let mut summary = format!("mvn test: {} passed", passed); + if counts.skipped > 0 { + summary.push_str(&format!(", {} skipped", counts.skipped)); + } + summary.push_str(&format!(" ({})", time_str)); + return summary; + } + + let failed_count = counts.failures + counts.errors; + let mut result = format!("mvn test: {} run, {} failed", counts.run, failed_count); + if counts.skipped > 0 { + result.push_str(&format!(", {} skipped", counts.skipped)); + } + result.push_str(&format!(" ({})\n", time_str)); + + result.push_str("BUILD FAILURE\n"); + + if !failures.is_empty() { + result.push_str("\nFailures:\n"); + } + for (i, failure) in failures.iter().enumerate() { + writeln!(result, "{}. {}", i + 1, failure.name).unwrap(); + for detail in &failure.details { + writeln!(result, " {}", truncate(detail, MAX_LINE_LENGTH)).unwrap(); + } + } + if total_failures_seen > MAX_FAILURES_SHOWN { + writeln!( + result, + "\n... +{} more failures", + total_failures_seen - MAX_FAILURES_SHOWN + ) + .unwrap(); + } + + result.trim().to_string() +} + +/// Strip [INFO], [ERROR], [WARNING] prefixes from Maven output lines. +/// Expects pre-trimmed input from callers. +fn strip_maven_prefix(line: &str) -> &str { + for tag in [INFO_TAG, ERROR_TAG, WARNING_TAG] { + if let Some(rest) = line.strip_prefix(tag) { + return rest.trim_start(); + } + } + line +} + +/// Returns true for Java framework stack frames that should be stripped. +/// Expects pre-trimmed input (callers pass `stripped` or `trimmed`). +fn is_framework_frame(line: &str) -> bool { + let check = line.strip_prefix("at ").unwrap_or(line); + + const FRAMEWORK_PREFIXES: &[&str] = &[ + "org.apache.maven.", + "org.junit.platform.", + "org.junit.jupiter.", + "org.codehaus.plexus.", + "java.base/", + "sun.reflect.", + "jdk.internal.", + ]; + + for prefix in FRAMEWORK_PREFIXES { + if check.starts_with(prefix) { + return true; + } + } + + // "... N more" truncation markers + line.starts_with("...") && line.contains("more") +} + +/// Returns true for Maven boilerplate lines that should be stripped. +/// Expects pre-trimmed input from callers. +fn is_maven_boilerplate(line: &str) -> bool { + // Empty [ERROR] or [INFO] lines + if line == ERROR_TAG || line == INFO_TAG || line == WARNING_TAG { + return true; + } + + let stripped = strip_maven_prefix(line); + + // Separator lines (dashes) + if stripped.starts_with("---") && stripped.chars().all(|c| c == '-' || c.is_whitespace()) { + return true; + } + + const BOILERPLATE_PATTERNS: &[&str] = &[ + "-> [Help", + "http://cwiki.apache.org", + "https://cwiki.apache.org", + "surefire-reports", + "Re-run Maven", + "re-run Maven", + "full stack trace", + "enable verbose output", + "See dump files", + "Failed to execute goal", + "There are test failures", + ]; + + for pattern in BOILERPLATE_PATTERNS { + if stripped.contains(pattern) { + return true; + } + } + + false +} + +// --------------------------------------------------------------------------- +// Line filter for mvn compile output +// --------------------------------------------------------------------------- + +/// Filter `mvn compile` (and compile-like goals such as `process-classes`, +/// `test-compile`) output — strip [INFO] noise, keep errors and summary. +fn filter_mvn_compile(output: &str) -> String { + let clean = strip_ansi(output); + let result_lines: Vec<&str> = clean + .lines() + .map(str::trim) + .filter(|line| should_keep_compile_line(line)) + .collect(); + + if result_lines.is_empty() { + return "mvn: ok".to_string(); + } + + result_lines.join("\n") +} + +const INFO_NOISE_PATTERNS: &[&str] = &[ + "---", + "===", + "Building ", + "Downloading ", + "Downloaded ", + "Scanning ", + "Compiling ", + "Recompiling ", + "Nothing to compile", + "Using auto detected", + "Loaded ", + "Finished at:", + "from pom.xml", + "Copying ", + "argLine set to", + "Migration completed", + "Inferring ", + "No bool { + if line.is_empty() { + return false; + } + + let stripped = strip_maven_prefix(line); + + if line.starts_with(ERROR_TAG) { + return !is_maven_boilerplate(line); + } + + if stripped.contains("BUILD SUCCESS") || stripped.contains("BUILD FAILURE") { + return true; + } + + if TOTAL_TIME_RE.is_match(stripped) { + return true; + } + + // Strip [INFO] noise + if line.starts_with(INFO_TAG) { + if stripped.is_empty() { + return false; + } + + if stripped.starts_with("[stdout]") || stripped.starts_with("[stderr]") { + return false; + } + + // npm lifecycle script lines: "> my-app@1.0.0 build" + if stripped.starts_with("> ") { + return false; + } + + for pattern in INFO_NOISE_PATTERNS { + if stripped.contains(pattern) { + return false; + } + } + + if stripped.contains("deprecated") || stripped.contains("WARNING") { + return false; + } + + // Code generator config params and bundle size lines (regex — slower, run last) + if CODEGEN_CONFIG_RE.is_match(stripped) || BUNDLE_SIZE_RE.is_match(stripped) { + return false; + } + + return true; + } + + if line.starts_with(WARNING_TAG) { + return false; + } + + for pattern in BARE_TEXT_NOISE { + if line.contains(pattern) { + return false; + } + } + + // Keep anything else (compilation errors without prefix, etc.) + true +} + +// --------------------------------------------------------------------------- +// Line filter for mvn checkstyle:check output +// --------------------------------------------------------------------------- + +/// Maven "Help" footer emitted on BUILD FAILURE. These come prefixed with +/// `[ERROR]` but are not actionable for the user — just pointers to wiki +/// pages. They are distinct from real `[ERROR]` violations, so we match by +/// substring after stripping the prefix. +const CHECKSTYLE_HELP_BOILERPLATE: &[&str] = &[ + "Failed to execute goal", + "To see the full stack trace", + "Re-run Maven using", + "For more information about the errors", + "[Help 1]", + "[Help 2]", + "MojoFailureException", + "cwiki.apache.org", +]; + +/// Filter `mvn checkstyle:check` output: +/// - strip ANSI codes, mvn/JVM/os-detection startup noise +/// - strip Maven model problem WARNING block (10 stock lines) +/// - strip `[INFO] Scanning / Building / ---…---` separators +/// - keep violation lines, rewritten compactly: +/// ` path:line:col [RuleName] message` +/// - keep `There are N errors reported by Checkstyle` and +/// `You have N Checkstyle violations` summaries +/// - keep `BUILD SUCCESS` / `BUILD FAILURE` and `Total time` +/// - strip trailing Help-link boilerplate +fn filter_mvn_checkstyle(output: &str) -> String { + let clean = strip_ansi(output); + let mut result: Vec = Vec::new(); + + for raw in clean.lines() { + // Drop cross-cutting startup noise first + if is_mvn_startup_noise(raw) { + continue; + } + + let line = raw.trim(); + if line.is_empty() { + continue; + } + + // Violations: rewrite compactly + if let Some(caps) = CHECKSTYLE_VIOLATION_RE.captures(line) { + let path = &caps[1]; + let lineno = &caps[2]; + let col = caps.get(3).map(|m| m.as_str()).unwrap_or(""); + let rule = &caps[4]; + let msg = &caps[5]; + let compact = if col.is_empty() { + format!(" {}:{} [{}] {}", path, lineno, rule, msg) + } else { + format!(" {}:{}:{} [{}] {}", path, lineno, col, rule, msg) + }; + result.push(compact); + continue; + } + + let stripped = strip_maven_prefix(line); + + // Drop Help-link boilerplate emitted after BUILD FAILURE + if line.starts_with(ERROR_TAG) + && CHECKSTYLE_HELP_BOILERPLATE + .iter() + .any(|p| stripped.contains(p)) + { + continue; + } + + // Keep [INFO] summary & result lines + if line.starts_with(INFO_TAG) { + if stripped.is_empty() { + continue; + } + + // Keep: N-errors / N-violations / BUILD SUCCESS|FAILURE / Total time + if stripped.contains("Checkstyle violations") + || stripped.contains("reported by Checkstyle") + || stripped.contains("BUILD SUCCESS") + || stripped.contains("BUILD FAILURE") + || TOTAL_TIME_RE.is_match(stripped) + { + result.push(stripped.to_string()); + continue; + } + + // Drop everything else: Scanning, Building, separators, plugin + // banners, `from pom.xml`, `Finished at:`, etc. These match + // `is_maven_boilerplate` or known noise words. + continue; + } + + // Strip Maven model WARNING block (empty and boilerplate WARNINGs) + if line.starts_with(WARNING_TAG) { + continue; + } + + // Bare `[ERROR]` continuation (e.g., blank separator between help blocks) + if line == ERROR_TAG { + continue; + } + + // Anything else (e.g., unexpected bare errors not matching the rule + // regex) — keep, in the spirit of the fallback principle. + result.push(line.to_string()); + } + + if result.is_empty() { + return "mvn checkstyle: ok".to_string(); + } + + result.join("\n") +} + +// --------------------------------------------------------------------------- +// Line filter for mvn dependency:tree output +// --------------------------------------------------------------------------- + +/// Filter `mvn dependency:tree` — strip Maven boilerplate, omitted duplicates, +/// and "version managed" annotations. Keep tree structure and conflicts. +/// Returns the tree depth of a dependency line (0 = root, 1 = direct dep, 2+ = transitive). +/// Counts tree-drawing segments: each `| `, `+- `, `\- `, or ` ` at the start adds one level. +fn dep_tree_depth(line: &str) -> usize { + let mut depth = 0; + let bytes = line.as_bytes(); + let mut i = 0; + while i + 2 < bytes.len() { + match (bytes[i], bytes[i + 1], bytes[i + 2]) { + (b'|', b' ', b' ') | (b'+', b'-', b' ') | (b'\\', b'-', b' ') | (b' ', b' ', b' ') => { + depth += 1; + i += 3; + } + _ => break, + } + } + depth +} + +fn filter_mvn_dep_tree(output: &str) -> String { + let clean = strip_ansi(output); + + // First pass: collect clean tree lines + let mut tree_lines: Vec = Vec::new(); + for line in clean.lines() { + let trimmed = line.trim(); + + if trimmed.is_empty() || is_maven_boilerplate(trimmed) { + continue; + } + + let stripped = strip_maven_prefix(trimmed); + + if trimmed.starts_with(WARNING_TAG) { + continue; + } + if trimmed.starts_with(INFO_TAG) + && (stripped.is_empty() + || stripped.starts_with("Scanning ") + || stripped.starts_with("Building ") + || stripped.starts_with("Loaded ") + || stripped.contains("from pom.xml") + || stripped.contains("BUILD SUCCESS") + || stripped.contains("BUILD FAILURE") + || stripped.starts_with("Total time:") + || stripped.starts_with("Finished at:")) + { + continue; + } + + if stripped.contains("omitted for duplicate") { + continue; + } + + let cleaned = if stripped.contains("version managed from") { + VERSION_MANAGED_RE.replace_all(stripped, "").into_owned() + } else { + stripped.to_string() + }; + + tree_lines.push(cleaned); + } + + if tree_lines.is_empty() { + return "mvn dependency:tree: no output".to_string(); + } + + // Second pass: collapse transitive deps (depth 2+) into counts on their parent + let mut result_lines: Vec = Vec::new(); + let mut i = 0; + while i < tree_lines.len() { + let depth = dep_tree_depth(&tree_lines[i]); + + if depth <= 1 { + // Root or direct dep — count transitive children + let mut transitive_count = 0; + let mut j = i + 1; + while j < tree_lines.len() { + let child_depth = dep_tree_depth(&tree_lines[j]); + if child_depth <= depth { + break; + } + if child_depth >= depth + 2 { + transitive_count += 1; + } + j += 1; + } + + if depth == 1 && transitive_count > 0 { + result_lines.push(format!( + "{} ({} transitive)", + tree_lines[i], transitive_count + )); + } else { + result_lines.push(tree_lines[i].clone()); + } + } + // depth 2+ lines are skipped (counted above) + i += 1; + } + + result_lines.join("\n") +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::core::utils::count_tokens; + + #[test] + fn test_test_counts_add() { + let mut a = TestSummary { + run: 10, + failures: 1, + errors: 2, + skipped: 3, + }; + let b = TestSummary { + run: 100, + failures: 20, + errors: 30, + skipped: 40, + }; + a.add(&b); + assert_eq!(a.run, 110); + assert_eq!(a.failures, 21); + assert_eq!(a.errors, 32); + assert_eq!(a.skipped, 43); + } + + #[test] + fn test_filter_pass_output() { + let input = include_str!("../../../tests/fixtures/mvn_test_pass_mavenmcp.txt"); + let output = filter_mvn_test(input); + assert!( + output.contains("mvn test:"), + "should contain summary prefix" + ); + assert!(output.contains("183 passed"), "should show 183 passed"); + assert!(output.contains("4.748 s"), "should contain total time"); + assert!( + !output.contains("[INFO]"), + "should not contain raw [INFO] prefix" + ); + } + + #[test] + fn test_filter_fail_output() { + let input = include_str!("../../../tests/fixtures/mvn_test_fail_auth.txt"); + let output = filter_mvn_test(input); + assert!( + output.contains("5 run, 2 failed"), + "should show run/failed counts, got: {}", + output + ); + assert!(output.contains("23.819 s"), "should contain total time"); + assert!( + output.contains("EmailParserTest.should_extract_domain_from_email"), + "should list first failure" + ); + assert!( + output.contains("ScoreTypeTest.shouldMapToRole"), + "should list second failure" + ); + assert!( + output.contains("broken.example.com"), + "should include assertion details" + ); + assert!( + !output.contains("surefire-reports"), + "should strip boilerplate" + ); + assert!( + !output.contains("cwiki.apache.org"), + "should strip help links" + ); + } + + #[test] + fn test_pass_savings() { + let input = include_str!("../../../tests/fixtures/mvn_test_pass_mavenmcp.txt"); + let output = filter_mvn_test(input); + + let input_tokens = count_tokens(input); + let output_tokens = count_tokens(&output); + + let savings = 100.0 - (output_tokens as f64 / input_tokens as f64 * 100.0); + + assert!( + savings >= 90.0, + "mvn test pass: expected >=90% savings, got {:.1}% ({} -> {} tokens)", + savings, + input_tokens, + output_tokens, + ); + } + + #[test] + fn test_fail_savings() { + let input = include_str!("../../../tests/fixtures/mvn_test_fail_auth.txt"); + let output = filter_mvn_test(input); + + let input_tokens = count_tokens(input); + let output_tokens = count_tokens(&output); + + let savings = 100.0 - (output_tokens as f64 / input_tokens as f64 * 100.0); + + assert!( + savings >= 60.0, + "mvn test fail: expected >=60% savings, got {:.1}% ({} -> {} tokens)", + savings, + input_tokens, + output_tokens, + ); + } + + #[test] + fn test_filter_large_suite() { + let input = include_str!("../../../tests/fixtures/mvn_test_large_suite.txt"); + let output = filter_mvn_test(input); + assert!( + output.contains("3262 run, 23 failed"), + "should show run/failed counts, got: {}", + output + ); + assert!( + output.contains("+13 more failures"), + "should cap at 10 and show remaining" + ); + assert!( + output.contains("SearchReadModelTest"), + "should list assertion failures" + ); + assert!( + output.contains("PatchableFieldTest"), + "should list compilation errors" + ); + } + + #[test] + fn test_large_suite_savings() { + let input = include_str!("../../../tests/fixtures/mvn_test_large_suite.txt"); + let output = filter_mvn_test(input); + + let input_tokens = count_tokens(input); + let output_tokens = count_tokens(&output); + let savings = 100.0 - (output_tokens as f64 / input_tokens as f64 * 100.0); + + assert!( + savings >= 60.0, + "mvn test large suite: expected >=60% savings, got {:.1}% ({} -> {} tokens)", + savings, + input_tokens, + output_tokens, + ); + } + + #[test] + fn test_empty_input() { + let output = filter_mvn_test(""); + assert_eq!(output, "mvn test: no tests run"); + } + + #[test] + fn test_filter_many_failures_output() { + let input = include_str!("../../../tests/fixtures/mvn_test_many_failures.txt"); + let output = filter_mvn_test(input); + assert!( + output.contains("28 run, 28 failed"), + "should show total run/failed counts, got: {}", + output + ); + assert!( + output.contains("+4 more failures"), + "should cap at 10 and show remaining count" + ); + } + + #[test] + fn test_many_failures_savings() { + let input = include_str!("../../../tests/fixtures/mvn_test_many_failures.txt"); + let output = filter_mvn_test(input); + + let input_tokens = count_tokens(input); + let output_tokens = count_tokens(&output); + let savings = 100.0 - (output_tokens as f64 / input_tokens as f64 * 100.0); + + assert!( + savings >= 60.0, + "mvn test many failures: expected >=60% savings, got {:.1}% ({} -> {} tokens)", + savings, + input_tokens, + output_tokens, + ); + } + + #[test] + fn test_filter_multimodule_output() { + let input = include_str!("../../../tests/fixtures/mvn_test_multimodule.txt"); + let output = filter_mvn_test(input); + assert!( + output.contains("860 run, 4 failed"), + "should show total run/failed across modules, got: {}", + output + ); + assert!( + output.contains("GitDiffReaderTest.shouldBuildDiff"), + "should list failure from services module" + ); + assert!( + output.contains("ServiceUnavailableException"), + "should include error details" + ); + assert!( + output.contains("01:31 min"), + "should contain total time" + ); + } + + #[test] + fn test_multimodule_savings() { + let input = include_str!("../../../tests/fixtures/mvn_test_multimodule.txt"); + let output = filter_mvn_test(input); + + let input_tokens = count_tokens(input); + let output_tokens = count_tokens(&output); + let savings = 100.0 - (output_tokens as f64 / input_tokens as f64 * 100.0); + + assert!( + savings >= 60.0, + "mvn test multimodule: expected >=60% savings, got {:.1}% ({} -> {} tokens)", + savings, + input_tokens, + output_tokens, + ); + } + + #[test] + fn test_filter_pass_large_ansi() { + let input = include_str!("../../../tests/fixtures/mvn_test_pass_large_ansi.txt"); + let output = filter_mvn_test(input); + assert!( + output.contains("950 passed"), + "should show 950 passed (959-9 skipped), got: {}", + output + ); + assert!( + output.contains("9 skipped"), + "should show 9 skipped" + ); + assert!( + output.contains("01:32 min"), + "should contain total time" + ); + assert!( + !output.contains("PortUnreachableException"), + "should strip app log noise" + ); + assert!( + !output.contains("[stdout]"), + "should strip [stdout] lines" + ); + assert!( + !output.contains("liquibase"), + "should strip liquibase stderr" + ); + } + + #[test] + fn test_pass_large_ansi_savings() { + let input = include_str!("../../../tests/fixtures/mvn_test_pass_large_ansi.txt"); + let output = filter_mvn_test(input); + + let input_tokens = count_tokens(input); + let output_tokens = count_tokens(&output); + let savings = 100.0 - (output_tokens as f64 / input_tokens as f64 * 100.0); + + assert!( + savings >= 95.0, + "mvn test large ANSI pass: expected >=95% savings, got {:.1}% ({} -> {} tokens)", + savings, + input_tokens, + output_tokens, + ); + } + + #[test] + fn test_no_test_section() { + let input = "[INFO] Building my-project 1.0\n[INFO] BUILD SUCCESS\n"; + let output = filter_mvn_test(input); + assert_eq!(output, "mvn test: no tests run"); + } + + // --- dependency:tree tests --- + + #[test] + fn test_dep_tree_simple() { + let input = include_str!("../../../tests/fixtures/mvn_dep_tree_simple.txt"); + let output = filter_mvn_dep_tree(input); + assert!( + output.contains("com.example:my-app:jar:1.0.0"), + "should contain root artifact, got: {}", + output + ); + assert!( + output.contains("slf4j-api"), + "should contain direct dep" + ); + assert!( + output.contains("guava"), + "should contain guava" + ); + assert!( + !output.contains("[INFO]"), + "should strip [INFO] prefix" + ); + assert!( + !output.contains("BUILD SUCCESS"), + "should strip boilerplate" + ); + assert!( + !output.contains("Scanning"), + "should strip preamble" + ); + } + + #[test] + fn test_dep_tree_conflicts() { + let input = include_str!("../../../tests/fixtures/mvn_dep_tree_conflicts.txt"); + let output = filter_mvn_dep_tree(input); + assert!( + output.contains("omitted for conflict with 2.18.3"), + "should keep conflict info, got: {}", + output + ); + assert!( + !output.contains("BUILD SUCCESS"), + "should strip boilerplate" + ); + } + + #[test] + fn test_dep_tree_beacon_strips_duplicates() { + let input = include_str!("../../../tests/fixtures/mvn_dep_tree_beacon.txt"); + let output = filter_mvn_dep_tree(input); + assert!( + !output.contains("omitted for duplicate"), + "should strip all 'omitted for duplicate' lines" + ); + assert!( + output.contains("com.skillpanel:beacon"), + "should contain root artifact" + ); + assert!( + output.contains("spring-boot-starter-web"), + "should contain direct deps" + ); + } + + #[test] + fn test_dep_tree_beacon_cleans_version_managed() { + let input = include_str!("../../../tests/fixtures/mvn_dep_tree_beacon.txt"); + let output = filter_mvn_dep_tree(input); + assert!( + !output.contains("version managed from"), + "should strip 'version managed' annotations" + ); + } + + #[test] + fn test_dep_tree_beacon_savings() { + let input = include_str!("../../../tests/fixtures/mvn_dep_tree_beacon.txt"); + let output = filter_mvn_dep_tree(input); + + let input_tokens = count_tokens(input); + let output_tokens = count_tokens(&output); + let savings = 100.0 - (output_tokens as f64 / input_tokens as f64 * 100.0); + + assert!( + savings >= 60.0, + "mvn dep tree beacon: expected >=60% savings, got {:.1}% ({} -> {} tokens)", + savings, + input_tokens, + output_tokens, + ); + } + + #[test] + fn test_dep_tree_simple_savings() { + let input = include_str!("../../../tests/fixtures/mvn_dep_tree_simple.txt"); + let output = filter_mvn_dep_tree(input); + + let input_tokens = count_tokens(input); + let output_tokens = count_tokens(&output); + let savings = 100.0 - (output_tokens as f64 / input_tokens as f64 * 100.0); + + assert!( + savings >= 30.0, + "mvn dep tree simple: expected >=30% savings, got {:.1}% ({} -> {} tokens)", + savings, + input_tokens, + output_tokens, + ); + } + + #[test] + fn test_dep_tree_empty() { + let output = filter_mvn_dep_tree(""); + assert_eq!(output, "mvn dependency:tree: no output"); + } + + #[test] + fn test_dep_tree_ansi_codes_stripped() { + let input = "\x1b[34;1m[INFO]\x1b[0m com.example:app:jar:1.0\n\ + \x1b[34;1m[INFO]\x1b[0m +- org.junit:junit:jar:5.0:test\n\ + \x1b[34;1m[INFO]\x1b[0m | \\- org.hamcrest:hamcrest:jar:2.0:test\n\ + \x1b[34;1m[INFO]\x1b[0m \\- com.google:guava:jar:33.0:compile"; + let output = filter_mvn_dep_tree(input); + assert!( + !output.contains("\x1b["), + "output should not contain ANSI escape codes" + ); + assert!( + output.contains("com.example:app"), + "should contain root artifact" + ); + assert!( + output.contains("junit"), + "should contain direct dep" + ); + assert!( + !output.contains("hamcrest"), + "should collapse transitive dep" + ); + } + + #[test] + fn test_dep_tree_large_collapses_transitive() { + let input = include_str!("../../../tests/fixtures/mvn_dep_tree_large.txt"); + let output = filter_mvn_dep_tree(input); + + // Should show root artifact + assert!( + output.contains("com.example.demo:webapp"), + "should contain root artifact" + ); + + // Direct deps should be listed + assert!( + output.contains("spring-boot-starter-actuator"), + "should contain direct dep" + ); + + // Transitive deps (depth 2+) should NOT appear as separate lines + assert!( + !output.contains("logback-classic"), + "should not show transitive dep logback-classic" + ); + assert!( + !output.contains("logback-core"), + "should not show transitive dep logback-core" + ); + + // Direct deps with children should show transitive count + assert!( + output.contains("transitive"), + "should show transitive count for deps with children" + ); + + // Output should be dramatically smaller + let output_lines = output.lines().count(); + assert!( + output_lines < 40, + "collapsed tree should be under 40 lines, got {}", + output_lines + ); + } + + #[test] + fn test_dep_tree_large_savings_above_80() { + let input = include_str!("../../../tests/fixtures/mvn_dep_tree_large.txt"); + let output = filter_mvn_dep_tree(input); + + let input_tokens = count_tokens(input); + let output_tokens = count_tokens(&output); + let savings = 100.0 - (output_tokens as f64 / input_tokens as f64 * 100.0); + + assert!( + savings >= 80.0, + "mvn dep tree large: expected >=80% savings, got {:.1}% ({} -> {} tokens)", + savings, input_tokens, output_tokens, + ); + } + + // --- compile filter tests (auth project: jOOQ + typescript-generator + React) --- + + #[test] + fn test_filter_compile_auth() { + let input = include_str!("../../../tests/fixtures/mvn_compile_auth.txt"); + let output = filter_mvn_compile(input); + + // Must preserve critical lines + assert!( + output.contains("BUILD SUCCESS"), + "should keep BUILD SUCCESS, got: {}", + output + ); + assert!( + output.contains("Total time:"), + "should keep Total time" + ); + + // Must strip plugin noise + assert!( + !output.contains("[stdout]"), + "should strip [stdout] lines" + ); + assert!( + !output.contains("Generating table"), + "should strip jOOQ codegen" + ); + assert!( + !output.contains("Generating record"), + "should strip jOOQ record gen" + ); + assert!( + !output.contains("Generating routine"), + "should strip jOOQ routine gen" + ); + assert!( + !output.contains("Missing name"), + "should strip jOOQ warnings" + ); + assert!( + !output.contains("kB build/static"), + "should strip bundle sizes" + ); + assert!( + !output.contains("The project was built"), + "should strip CRA messages" + ); + assert!( + !output.contains("npm fund"), + "should strip npm messages" + ); + assert!( + !output.contains("Server Version:"), + "should strip Docker bare text" + ); + assert!( + !output.contains("Parsing"), + "should strip typescript-generator parsing lines" + ); + assert!( + !output.contains("Loading class"), + "should strip typescript-generator loading lines" + ); + } + + #[test] + fn test_compile_auth_savings() { + let input = include_str!("../../../tests/fixtures/mvn_compile_auth.txt"); + let output = filter_mvn_compile(input); + + let input_tokens = count_tokens(input); + let output_tokens = count_tokens(&output); + let savings = 100.0 - (output_tokens as f64 / input_tokens as f64 * 100.0); + + assert!( + savings >= 90.0, + "mvn compile auth: expected >=90% savings, got {:.1}% ({} -> {} tokens)\nOutput:\n{}", + savings, + input_tokens, + output_tokens, + output, + ); + } + + #[test] + fn test_compile_success_only() { + let input = "[INFO] BUILD SUCCESS\n[INFO] Total time: 2.5 s\n"; + let output = filter_mvn_compile(input); + assert!(output.contains("BUILD SUCCESS")); + assert!(output.contains("Total time:")); + } + + #[test] + fn test_compile_strips_stdout_lines() { + let input = "[INFO] [stdout] Parsing 'com.example.Foo'\n\ + [INFO] [stdout] Loading class java.lang.String\n\ + [INFO] [stdout] Writing declarations to: /tmp/out.d.ts\n\ + [INFO] BUILD SUCCESS\n\ + [INFO] Total time: 1.0 s\n"; + let output = filter_mvn_compile(input); + assert!(!output.contains("[stdout]"), "should strip all [stdout] lines"); + assert!(output.contains("BUILD SUCCESS")); + } + + #[test] + fn test_compile_strips_codegen_config() { + let input = "[INFO] dialect : POSTGRES_15\n\ + [INFO] generated : false\n\ + [INFO] JPA : false\n\ + [INFO] BUILD SUCCESS\n\ + [INFO] Total time: 1.0 s\n"; + let output = filter_mvn_compile(input); + assert!(!output.contains("dialect"), "should strip codegen config"); + assert!(!output.contains("JPA"), "should strip codegen config"); + assert!(output.contains("BUILD SUCCESS")); + } + + #[test] + fn test_compile_strips_bundle_sizes() { + let input = "[INFO] 257.55 kB build/static/js/main.js\n\ + [INFO] 40.41 kB build/static/js/962.chunk.js\n\ + [INFO] 918 B build/static/js/636.chunk.js\n\ + [INFO] BUILD SUCCESS\n\ + [INFO] Total time: 1.0 s\n"; + let output = filter_mvn_compile(input); + assert!(!output.contains("kB"), "should strip bundle sizes"); + assert!(!output.contains("918 B"), "should strip small bundle sizes"); + assert!(output.contains("BUILD SUCCESS")); + } + + #[test] + fn test_compile_preserves_errors() { + let input = "[INFO] Compiling 42 source files\n\ + [ERROR] /src/Foo.java:[10,5] cannot find symbol\n\ + [INFO] BUILD FAILURE\n\ + [INFO] Total time: 1.0 s\n"; + let output = filter_mvn_compile(input); + assert!( + output.contains("[ERROR]"), + "should preserve [ERROR] lines, got: {}", + output + ); + assert!(output.contains("cannot find symbol")); + assert!(output.contains("BUILD FAILURE")); + } + + // --- run_other routing --- + + #[test] + fn test_route_goal() { + // Compile-family → compile filter + assert_eq!(route_goal("compile"), GoalRouting::Compile); + assert_eq!(route_goal("process-classes"), GoalRouting::Compile); + assert_eq!(route_goal("test-compile"), GoalRouting::Compile); + + // Checkstyle (both canonical and short form) + assert_eq!(route_goal("checkstyle:check"), GoalRouting::Checkstyle); + assert_eq!(route_goal("checkstyle"), GoalRouting::Checkstyle); + + // Rare lifecycle phases → passthrough (rare in real usage) + assert_eq!(route_goal("package"), GoalRouting::Passthrough); + assert_eq!(route_goal("install"), GoalRouting::Passthrough); + assert_eq!(route_goal("verify"), GoalRouting::Passthrough); + assert_eq!(route_goal("clean"), GoalRouting::Passthrough); + assert_eq!(route_goal("deploy"), GoalRouting::Passthrough); + + // Long-running / interactive goals must always passthrough + assert_eq!(route_goal("spring-boot:run"), GoalRouting::Passthrough); + assert_eq!(route_goal("quarkus:dev"), GoalRouting::Passthrough); + + // Unknown / typo: passthrough (safer default) + assert_eq!(route_goal("compilee"), GoalRouting::Passthrough); + assert_eq!(route_goal(""), GoalRouting::Passthrough); + } + + // --- checkstyle filter tests --- + + #[test] + fn test_filter_checkstyle_clean() { + let input = include_str!("../../../tests/fixtures/mvn_checkstyle_clean.txt"); + let output = filter_mvn_checkstyle(input); + + // Keep success summary + assert!( + output.contains("0 Checkstyle violations"), + "should keep violation-count summary, got: {}", + output + ); + assert!(output.contains("BUILD SUCCESS"), "should keep BUILD SUCCESS"); + assert!(output.contains("Total time"), "should keep Total time"); + + // Strip ANSI escapes (fixture has them) + assert!( + !output.contains('\x1b'), + "should strip ANSI escape codes" + ); + + // Strip mvnd/maven 3.9+ startup noise + assert!( + !output.contains("auto-discovered prefixes"), + "should strip 'Loaded N auto-discovered prefixes' lines" + ); + assert!( + !output.contains("Scanning for projects"), + "should strip 'Scanning for projects'" + ); + + // Savings ≥60% + let input_tokens = count_tokens(input); + let output_tokens = count_tokens(&output); + let savings = 100.0 - (output_tokens as f64 / input_tokens as f64 * 100.0); + assert!( + savings >= 60.0, + "mvn checkstyle clean: expected >=60% savings, got {:.1}% ({} -> {})\nOutput:\n{}", + savings, + input_tokens, + output_tokens, + output, + ); + } + + #[test] + fn test_filter_checkstyle_clean_native_warnings() { + let input = + include_str!("../../../tests/fixtures/mvn_checkstyle_clean_native.txt"); + let output = filter_mvn_checkstyle(input); + + assert!(output.contains("0 Checkstyle violations")); + assert!(output.contains("BUILD SUCCESS")); + + // Strip JVM restricted-method / native-access warnings (non-prefixed WARNING:) + assert!( + !output.contains("sun.misc.Unsafe"), + "should strip JVM native-access warnings" + ); + assert!( + !output.contains("native-access"), + "should strip --enable-native-access hints" + ); + + // Strip os-maven-plugin detection lines + assert!( + !output.contains("os.detected"), + "should strip [INFO] os.detected.* lines" + ); + + let savings = 100.0 + - (count_tokens(&output) as f64 / count_tokens(input) as f64 * 100.0); + assert!( + savings >= 60.0, + "mvn checkstyle clean (native): expected >=60% savings, got {:.1}%", + savings + ); + } + + #[test] + fn test_filter_checkstyle_violations() { + let input = + include_str!("../../../tests/fixtures/mvn_checkstyle_violations.txt"); + let output = filter_mvn_checkstyle(input); + + // Keep: error-count summary + assert!( + output.contains("4 errors reported by Checkstyle"), + "should keep '4 errors reported' summary, got:\n{}", + output + ); + + // Keep: final result + assert!(output.contains("BUILD FAILURE")); + assert!(output.contains("Total time")); + + // Keep: each of 4 violations (rule name must survive the rewrite) + for rule in &[ + "UnusedImports", + "MethodName", + "LineLength", + "LocalVariableName", + ] { + assert!( + output.contains(rule), + "should keep violation rule {}, got:\n{}", + rule, + output + ); + } + + // Strip: maven Help-link boilerplate + assert!( + !output.contains("To see the full stack trace"), + "should strip 'To see the full stack trace' boilerplate" + ); + assert!( + !output.contains("MojoFailureException"), + "should strip Help-link MojoFailureException reference" + ); + assert!( + !output.contains("Failed to execute goal org.apache.maven.plugins"), + "should strip 'Failed to execute goal …' [ERROR] line" + ); + + // Exactly 4 rewritten violation lines (one per rule above). + // Our compact format is ` :: [] `. + let violation_count = output + .lines() + .filter(|l| l.contains("ExternalAppId.java") && l.contains('[')) + .count(); + assert_eq!( + violation_count, 4, + "expected exactly 4 violation lines, got {}:\n{}", + violation_count, output + ); + + // Strip: mvn startup noise (fixture has 7 `auto-discovered prefixes` lines) + assert!(!output.contains("auto-discovered prefixes")); + + // Savings ≥60% + let savings = 100.0 + - (count_tokens(&output) as f64 / count_tokens(input) as f64 * 100.0); + assert!( + savings >= 60.0, + "mvn checkstyle violations: expected >=60% savings, got {:.1}%\nOutput:\n{}", + savings, + output + ); + } + + #[test] + fn test_filter_verify_auth_counts() { + let input = include_str!("../../../tests/fixtures/mvn_verify_auth.txt"); + let output = filter_mvn_test(input); + assert!( + output.contains("941 passed"), + "should accumulate surefire+failsafe (688+262)=950 run, minus 9 skipped = 941 passed, got: {}", + output + ); + assert!( + output.contains("9 skipped"), + "should accumulate skipped (8 surefire + 1 failsafe), got: {}", + output + ); + assert!( + output.contains("02:11 min"), + "should preserve total time, got: {}", + output + ); + assert!( + !output.contains("BUILD FAILURE"), + "passing verify run should not say FAILURE, got: {}", + output + ); + } + + #[test] + fn test_filter_verify_auth_savings() { + let input = include_str!("../../../tests/fixtures/mvn_verify_auth.txt"); + let output = filter_mvn_test(input); + + let input_tokens = count_tokens(input); + let output_tokens = count_tokens(&output); + let savings = 100.0 - (output_tokens as f64 / input_tokens as f64 * 100.0); + + assert!( + savings >= 90.0, + "mvn verify auth: expected >=90% savings, got {:.1}% ({} -> {} tokens)", + savings, + input_tokens, + output_tokens, + ); + } + + #[test] + fn enrich_happy_path_passes_through_without_io() { + let tmp = tempfile::tempdir().unwrap(); + // No target/ directory exists under tmp — ensures no I/O fallback would succeed. + let text = "mvn test: 42 passed (1.234 s)"; + let out = super::enrich_with_reports( + text, + tmp.path(), + std::time::SystemTime::now(), + Some("com.example"), + ); + assert_eq!(out, text); + } + + #[test] + fn enrich_no_tests_with_no_reports_emits_red_flag() { + let tmp = tempfile::tempdir().unwrap(); + let text = "mvn test: no tests run"; + let out = super::enrich_with_reports( + text, + tmp.path(), + std::time::SystemTime::now(), + Some("com.example"), + ); + assert!(out.contains("0 tests executed")); + assert!(out.contains("rtk proxy mvn test") || out.contains("surefire")); + } + + #[test] + fn enrich_with_surefire_fixture_appends_failures_section() { + let tmp = tempfile::tempdir().unwrap(); + let reports_dir = tmp.path().join("target/surefire-reports"); + std::fs::create_dir_all(&reports_dir).unwrap(); + std::fs::copy( + "tests/fixtures/java/surefire-reports/TEST-com.example.FailingTest.xml", + reports_dir.join("TEST-com.example.FailingTest.xml"), + ) + .unwrap(); + + let since = std::time::SystemTime::now() - std::time::Duration::from_secs(60); + let text = "mvn test: 4 run, 2 failed (01:02 min)\nBUILD FAILURE"; + let out = super::enrich_with_reports(text, tmp.path(), since, Some("com.example")); + + assert!(out.contains("Failures (from surefire-reports/)")); + assert!(out.contains("com.example.FailingTest.shouldReturnUser")); + assert!(out.contains("reports:")); + } + + #[test] + fn enrich_with_both_report_dirs_appends_both_sections() { + let tmp = tempfile::tempdir().unwrap(); + let sf = tmp.path().join("target/surefire-reports"); + let fs = tmp.path().join("target/failsafe-reports"); + std::fs::create_dir_all(&sf).unwrap(); + std::fs::create_dir_all(&fs).unwrap(); + std::fs::copy( + "tests/fixtures/java/surefire-reports/TEST-com.example.FailingTest.xml", + sf.join("TEST-com.example.FailingTest.xml"), + ) + .unwrap(); + std::fs::copy( + "tests/fixtures/java/failsafe-reports/TEST-com.example.DbIntegrationIT.xml", + fs.join("TEST-com.example.DbIntegrationIT.xml"), + ) + .unwrap(); + + let since = std::time::SystemTime::now() - std::time::Duration::from_secs(60); + let text = "mvn verify: 10 run, 3 failed (03:30 min)\nBUILD FAILURE"; + let out = super::enrich_with_reports(text, tmp.path(), since, Some("com.example")); + assert!(out.contains("Failures (from surefire-reports/)")); + assert!(out.contains("Integration failures (from failsafe-reports/)")); + assert!(out.contains("Caused by: org.hibernate.HibernateException")); + } + + #[test] + fn enrich_failures_without_xml_appends_hint() { + let tmp = tempfile::tempdir().unwrap(); + let text = "mvn test: 5 run, 2 failed (0.500 s)\nBUILD FAILURE"; + let out = super::enrich_with_reports( + text, + tmp.path(), + std::time::SystemTime::now(), + Some("com.example"), + ); + assert!(out.contains("no XML reports")); + assert!(out.contains("rtk proxy mvn test")); + } + + #[test] + fn enrich_happy_path_with_10_passed_is_short_circuited() { + // Regression: "10 passed" must not trigger zero_tests via substring of "0 passed". + let tmp = tempfile::tempdir().unwrap(); + let text = "mvn test: 10 passed (0.500 s)"; + let out = super::enrich_with_reports( + text, + tmp.path(), + std::time::SystemTime::now(), + Some("com.example"), + ); + assert_eq!(out, text, "10 passed must short-circuit without enrichment"); + } + + #[test] + fn snapshot_enriched_surefire_only() { + let tmp = tempfile::tempdir().unwrap(); + let reports = tmp.path().join("target/surefire-reports"); + std::fs::create_dir_all(&reports).unwrap(); + for name in [ + "TEST-com.example.FailingTest.xml", + "TEST-com.example.PassingTest.xml", + ] { + std::fs::copy( + format!("tests/fixtures/java/surefire-reports/{name}"), + reports.join(name), + ) + .unwrap(); + } + + let since = std::time::SystemTime::now() - std::time::Duration::from_secs(60); + let text = "mvn test: 7 run, 2 failed (00:10 min)\nBUILD FAILURE"; + let out = super::enrich_with_reports(text, tmp.path(), since, Some("com.example")); + insta::assert_snapshot!(out); + } + + #[test] + fn snapshot_enriched_surefire_and_failsafe() { + let tmp = tempfile::tempdir().unwrap(); + let sf = tmp.path().join("target/surefire-reports"); + let fs = tmp.path().join("target/failsafe-reports"); + std::fs::create_dir_all(&sf).unwrap(); + std::fs::create_dir_all(&fs).unwrap(); + std::fs::copy( + "tests/fixtures/java/surefire-reports/TEST-com.example.FailingTest.xml", + sf.join("TEST-com.example.FailingTest.xml"), + ) + .unwrap(); + std::fs::copy( + "tests/fixtures/java/failsafe-reports/TEST-com.example.DbIntegrationIT.xml", + fs.join("TEST-com.example.DbIntegrationIT.xml"), + ) + .unwrap(); + std::fs::copy( + "tests/fixtures/java/failsafe-reports/TEST-com.example.PortConflictIT.xml", + fs.join("TEST-com.example.PortConflictIT.xml"), + ) + .unwrap(); + + let since = std::time::SystemTime::now() - std::time::Duration::from_secs(60); + let text = "mvn verify: 12 run, 4 failed (05:42 min)\nBUILD FAILURE"; + let out = super::enrich_with_reports(text, tmp.path(), since, Some("com.example")); + insta::assert_snapshot!(out); + } + + #[test] + fn snapshot_red_flag_no_tests() { + let tmp = tempfile::tempdir().unwrap(); + let out = super::enrich_with_reports( + "mvn test: no tests run", + tmp.path(), + std::time::SystemTime::now(), + Some("com.example"), + ); + insta::assert_snapshot!(out); + } + + #[test] + fn savings_happy_path_unchanged_by_enrichment() { + // Happy path short-circuits without I/O; savings must match pre-enrichment. + let text = "mvn test: 859 passed, 4 skipped (02:11 min)"; + let tmp = tempfile::tempdir().unwrap(); + let out = super::enrich_with_reports( + text, + tmp.path(), + std::time::SystemTime::now(), + Some("com.example"), + ); + assert_eq!(out, text, "happy path must not allocate or append"); + } + + #[test] + fn savings_enriched_failures_stays_under_15_percent() { + // Simulate a ~2000-line build log whose text filter produced a short + // summary, plus one big failsafe XML with system-err and a 3-segment + // Caused-by chain. Total enriched output must be ≥85% smaller than raw. + let raw_log: String = std::iter::repeat_n( + "[INFO] Running com.example.some.Heavy.Test — lots of noisy build output\n", + 2000, + ) + .collect::(); + + let tmp = tempfile::tempdir().unwrap(); + let fs = tmp.path().join("target/failsafe-reports"); + std::fs::create_dir_all(&fs).unwrap(); + std::fs::copy( + "tests/fixtures/java/failsafe-reports/TEST-com.example.DbIntegrationIT.xml", + fs.join("TEST-com.example.DbIntegrationIT.xml"), + ) + .unwrap(); + + let since = std::time::SystemTime::now() - std::time::Duration::from_secs(60); + let text_summary = "mvn verify: 4 run, 1 failed (01:23 min)\nBUILD FAILURE"; + let enriched = super::enrich_with_reports(text_summary, tmp.path(), since, Some("com.example")); + + let raw_tokens = count_tokens(&raw_log); + let enriched_tokens = count_tokens(&enriched); + let savings = 100.0 - (enriched_tokens as f64 / raw_tokens as f64 * 100.0); + assert!( + savings >= 85.0, + "expected ≥85% savings on enriched failure path, got {savings:.1}% \ + (raw={raw_tokens}, enriched={enriched_tokens})" + ); + } +} diff --git a/src/cmds/java/pom_groupid.rs b/src/cmds/java/pom_groupid.rs new file mode 100644 index 000000000..f2785df91 --- /dev/null +++ b/src/cmds/java/pom_groupid.rs @@ -0,0 +1,170 @@ +//! Autodetects the application Java package from `pom.xml `. +//! Used by `surefire_reports` / `stack_trace` to classify application frames. +//! Can be overridden by `RTK_MVN_APP_PACKAGE` env var. + +use quick_xml::events::Event; +use quick_xml::Reader; +use std::path::Path; + +const OVERRIDE_ENV: &str = "RTK_MVN_APP_PACKAGE"; + +/// Detect the Maven groupId of `cwd`'s `pom.xml`. +/// +/// Resolution order: +/// 1. If env var `RTK_MVN_APP_PACKAGE` is set and non-empty, return it. +/// 2. Read `cwd/pom.xml` and extract top-level `/`. +/// 3. Fall back to `//`. +/// 4. Otherwise `None`. +pub fn detect(cwd: &Path) -> Option { + if let Ok(value) = std::env::var(OVERRIDE_ENV) { + let trimmed = value.trim(); + if !trimmed.is_empty() { + return Some(trimmed.to_string()); + } + } + + let pom_path = cwd.join("pom.xml"); + let content = std::fs::read_to_string(&pom_path).ok()?; + extract_groupid(&content) +} + +pub(crate) fn extract_groupid(xml: &str) -> Option { + let mut reader = Reader::from_str(xml); + reader.config_mut().trim_text(true); + let mut buf = Vec::new(); + + // Tag stack tracked as simple Vec of local names. + let mut stack: Vec = Vec::new(); + let mut top_level_groupid: Option = None; + let mut parent_groupid: Option = None; + let mut capturing = false; + + loop { + match reader.read_event_into(&mut buf) { + Ok(Event::Start(e)) => { + let name = std::str::from_utf8(e.name().as_ref()) + .ok() + .and_then(|s| s.rsplit(':').next()) + .unwrap_or("") + .to_string(); + stack.push(name); + + if is_top_level_groupid(&stack) || is_parent_groupid(&stack) { + capturing = true; + } + } + Ok(Event::Text(t)) => { + if capturing { + if let Ok(text) = t.unescape() { + let text = text.trim(); + if !text.is_empty() { + if is_top_level_groupid(&stack) && top_level_groupid.is_none() { + top_level_groupid = Some(text.to_string()); + } else if is_parent_groupid(&stack) && parent_groupid.is_none() { + parent_groupid = Some(text.to_string()); + } + } + } + } + } + Ok(Event::End(_)) => { + stack.pop(); + capturing = false; + if top_level_groupid.is_some() { + break; + } + } + Ok(Event::Eof) => break, + Err(_) => return None, + _ => {} + } + buf.clear(); + } + + top_level_groupid.or(parent_groupid) +} + +fn is_top_level_groupid(stack: &[String]) -> bool { + stack.len() == 2 && stack[0] == "project" && stack[1] == "groupId" +} + +fn is_parent_groupid(stack: &[String]) -> bool { + stack.len() == 3 + && stack[0] == "project" + && stack[1] == "parent" + && stack[2] == "groupId" +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn extract_single_module_groupid() { + let xml = include_str!("../../../tests/fixtures/java/poms/single-module-pom.xml"); + assert_eq!(extract_groupid(xml).as_deref(), Some("com.example.app")); + } + + #[test] + fn extract_falls_back_to_parent_groupid() { + let xml = include_str!("../../../tests/fixtures/java/poms/child-pom.xml"); + assert_eq!(extract_groupid(xml).as_deref(), Some("com.example.parent")); + } + + #[test] + fn extract_no_groupid_returns_none() { + let xml = include_str!("../../../tests/fixtures/java/poms/no-groupid-pom.xml"); + assert!(extract_groupid(xml).is_none()); + } + + #[test] + fn extract_malformed_returns_none() { + assert!(extract_groupid(">>>").is_none()); + } + + #[test] + fn detect_missing_pom_returns_none() { + let tmp = tempfile::tempdir().unwrap(); + assert!(detect(tmp.path()).is_none()); + } + + #[test] + fn detect_env_override_wins() { + let tmp = tempfile::tempdir().unwrap(); + std::fs::copy( + "tests/fixtures/java/poms/single-module-pom.xml", + tmp.path().join("pom.xml"), + ) + .unwrap(); + + // Serial to avoid concurrent env mutation with other tests — this is + // tested in isolation; we restore the var on exit. + let guard = EnvGuard::set(OVERRIDE_ENV, "com.override"); + assert_eq!(detect(tmp.path()).as_deref(), Some("com.override")); + drop(guard); + } + + struct EnvGuard { + key: &'static str, + original: Option, + } + + impl EnvGuard { + fn set(key: &'static str, value: &str) -> Self { + let original = std::env::var(key).ok(); + // SAFETY: single-threaded test; no other thread reads this env var. + unsafe { std::env::set_var(key, value) }; + Self { key, original } + } + } + + impl Drop for EnvGuard { + fn drop(&mut self) { + // SAFETY: single-threaded test; restoring env var on drop. + match &self.original { + Some(v) => unsafe { std::env::set_var(self.key, v) }, + None => unsafe { std::env::remove_var(self.key) }, + } + } + } +} diff --git a/src/cmds/java/snapshots/rtk__cmds__java__mvn_cmd__tests__snapshot_enriched_surefire_and_failsafe.snap b/src/cmds/java/snapshots/rtk__cmds__java__mvn_cmd__tests__snapshot_enriched_surefire_and_failsafe.snap new file mode 100644 index 000000000..f631b2f83 --- /dev/null +++ b/src/cmds/java/snapshots/rtk__cmds__java__mvn_cmd__tests__snapshot_enriched_surefire_and_failsafe.snap @@ -0,0 +1,48 @@ +--- +source: src/cmds/java/mvn_cmd.rs +expression: out +--- +mvn verify: 12 run, 4 failed (05:42 min) +BUILD FAILURE + +Failures (from surefire-reports/): +1. com.example.FailingTest.shouldReturnUser + AssertionFailedError: expected:<200> but was:<404> + org.opentest4j.AssertionFailedError: expected:<200> but was:<404> + ... 1 framework frames omitted + at com.example.FailingTest.shouldReturnUser(FailingTest.java:42) + ... 1 framework frames omitted + +2. com.example.FailingTest.shouldHandleNull + AssertionError: Unexpected exception: NullPointerException + java.lang.AssertionError: Unexpected exception: NullPointerException + at com.example.FailingTest.shouldHandleNull(FailingTest.java:55) + ... 1 framework frames omitted + + + +Integration failures (from failsafe-reports/): +1. com.example.PortConflictIT.shouldStartServer + [error] BindException: Address already in use + java.net.BindException: Address already in use + ... 2 framework frames omitted + at com.example.PortConflictIT.shouldStartServer(PortConflictIT.java:42) + +2. com.example.DbIntegrationIT.shouldConnect + [error] IllegalStateException: Failed to load ApplicationContext + java.lang.IllegalStateException: Failed to load ApplicationContext + ... 3 framework frames omitted + Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'dataSource' + ... 3 framework frames omitted + Caused by: org.hibernate.HibernateException: Unable to acquire JDBC Connection; nested exception is java.sql.SQLTransientConnectionException: HikariPool-1 - Connection is not available, request timed ... + ... 1 framework frames omitted + at com.example.DbIntegrationIT.shouldConnect(DbIntegrationIT.java:88) + ... 1 framework frames omitted + captured output: + [STDERR] + 2026-04-15 10:42:17 ERROR HikariDataSource - HikariPool-1 - Connection is not available + Connection refused (Connection refused) + + + +(reports: 1 surefire, 2 failsafe) diff --git a/src/cmds/java/snapshots/rtk__cmds__java__mvn_cmd__tests__snapshot_enriched_surefire_only.snap b/src/cmds/java/snapshots/rtk__cmds__java__mvn_cmd__tests__snapshot_enriched_surefire_only.snap new file mode 100644 index 000000000..fed4a62b5 --- /dev/null +++ b/src/cmds/java/snapshots/rtk__cmds__java__mvn_cmd__tests__snapshot_enriched_surefire_only.snap @@ -0,0 +1,24 @@ +--- +source: src/cmds/java/mvn_cmd.rs +expression: out +--- +mvn test: 7 run, 2 failed (00:10 min) +BUILD FAILURE + +Failures (from surefire-reports/): +1. com.example.FailingTest.shouldReturnUser + AssertionFailedError: expected:<200> but was:<404> + org.opentest4j.AssertionFailedError: expected:<200> but was:<404> + ... 1 framework frames omitted + at com.example.FailingTest.shouldReturnUser(FailingTest.java:42) + ... 1 framework frames omitted + +2. com.example.FailingTest.shouldHandleNull + AssertionError: Unexpected exception: NullPointerException + java.lang.AssertionError: Unexpected exception: NullPointerException + at com.example.FailingTest.shouldHandleNull(FailingTest.java:55) + ... 1 framework frames omitted + + + +(reports: 2 surefire) diff --git a/src/cmds/java/snapshots/rtk__cmds__java__mvn_cmd__tests__snapshot_red_flag_no_tests.snap b/src/cmds/java/snapshots/rtk__cmds__java__mvn_cmd__tests__snapshot_red_flag_no_tests.snap new file mode 100644 index 000000000..dfa55773d --- /dev/null +++ b/src/cmds/java/snapshots/rtk__cmds__java__mvn_cmd__tests__snapshot_red_flag_no_tests.snap @@ -0,0 +1,5 @@ +--- +source: src/cmds/java/mvn_cmd.rs +expression: out +--- +mvn test: 0 tests executed — surefire detected no tests. Check pom.xml (surefire plugin configuration) or run: rtk proxy mvn test diff --git a/src/cmds/java/stack_trace.rs b/src/cmds/java/stack_trace.rs new file mode 100644 index 000000000..ca3c7d042 --- /dev/null +++ b/src/cmds/java/stack_trace.rs @@ -0,0 +1,649 @@ +//! Port of maven-mcp's StackTraceProcessor. +//! +//! Parses Java stack traces into segments (top-level exception + Caused by +//! chains), classifies frames as application or framework by package prefix, +//! collapses framework noise, and preserves root-cause frames. + +const MAX_HEADER_LENGTH: usize = 200; +const DEFAULT_ROOT_CAUSE_APP_FRAMES: usize = 10; + +#[derive(Debug, PartialEq)] +struct Segment { + header: String, + frames: Vec, +} + +/// Split a stack trace into segments. +/// +/// The first non-empty line becomes the header of segment 0. Each subsequent +/// line starting with the literal `"Caused by:"` (no leading whitespace) closes +/// the current segment and opens a new one. All other lines append to the +/// current segment's frames. +/// +/// Indented `"\tCaused by:"` inside Suppressed blocks stays as a frame and +/// does NOT split segments — `is_structural_line` preserves it during frame +/// collapsing. +fn parse_segments(trace: &str) -> Vec { + let trace = trace.trim(); + if trace.is_empty() { + return Vec::new(); + } + + let mut segments = Vec::new(); + let mut current_header: Option = None; + let mut current_frames: Vec = Vec::new(); + + for line in trace.lines() { + if current_header.is_none() { + current_header = Some(line.to_string()); + } else if line.starts_with("Caused by:") { + segments.push(Segment { + header: current_header.take().unwrap(), + frames: std::mem::take(&mut current_frames), + }); + current_header = Some(line.to_string()); + } else { + current_frames.push(line.to_string()); + } + } + + if let Some(header) = current_header { + segments.push(Segment { + header, + frames: current_frames, + }); + } + + segments +} + +/// Truncate a header to `MAX_HEADER_LENGTH` **Unicode characters** (not bytes), +/// appending "..." if truncated. +pub(crate) fn truncate_header(header: &str) -> String { + let char_count = header.chars().count(); + if char_count <= MAX_HEADER_LENGTH { + return header.to_string(); + } + let truncated: String = header.chars().take(MAX_HEADER_LENGTH).collect(); + format!("{truncated}...") +} + +/// A stack frame belongs to the application if, after stripping whitespace and +/// the leading `"at "` marker, the remainder starts with `app_package`. +/// +/// When `app_package` is `None` or empty, every frame is considered an app frame +/// (framework collapsing disabled). Summary lines like `"\t... 42 more"` are +/// always framework artifacts. +fn is_application_frame(frame: &str, app_package: Option<&str>) -> bool { + let Some(pkg) = app_package.filter(|p| !p.is_empty()) else { + return true; + }; + let trimmed = frame.trim_start(); + let Some(after_at) = trimmed.strip_prefix("at ") else { + return false; + }; + after_at.starts_with(pkg) +} + +/// Structural lines must always be preserved even while collapsing framework +/// frames: Suppressed block headers and **indented** Caused-by lines (which +/// appear inside Suppressed blocks; top-level Caused-by is already a segment +/// boundary, not a frame). +fn is_structural_line(line: &str) -> bool { + if line.is_empty() { + return false; + } + let trimmed = line.trim_start(); + if trimmed.starts_with("Suppressed:") { + return true; + } + if trimmed.starts_with("Caused by:") { + // Only structural when indented (nested in suppressed). Top-level + // Caused by: is handled by parse_segments, not here. + return line + .chars() + .next() + .is_some_and(char::is_whitespace); + } + false +} + +/// Push frames to `output`, collapsing runs of consecutive framework frames +/// into a single `"\t... N framework frames omitted"` marker. +/// +/// When `app_package` is `None`, all frames are considered app frames and no +/// collapsing occurs — pass-through mode. +/// +/// When `max_app_frames` is `Some(n)`, at most `n` non-structural application +/// frames are kept (root-cause mode). Structural lines bypass the cap. +fn add_frames( + output: &mut Vec, + frames: &[String], + app_package: Option<&str>, + max_app_frames: Option, +) { + let filter = app_package.is_some_and(|p| !p.is_empty()); + if !filter { + for frame in frames { + output.push(frame.clone()); + } + return; + } + + let mut app_count: usize = 0; + let mut framework_count: usize = 0; + for frame in frames { + let structural = is_structural_line(frame); + if structural || is_application_frame(frame, app_package) { + if framework_count > 0 { + output.push(format!("\t... {framework_count} framework frames omitted")); + framework_count = 0; + } + if structural { + output.push(truncate_header(frame)); + } else if max_app_frames.is_none_or(|cap| app_count < cap) { + output.push(frame.clone()); + app_count += 1; + } + } else { + framework_count += 1; + } + } + if framework_count > 0 { + output.push(format!("\t... {framework_count} framework frames omitted")); + } +} + +/// Process a Java stack trace: +/// - Top-level header preserved (truncated to 200 chars). +/// - Non-root segments: header + collapsed frames. +/// - Root (last) segment: header + capped root-cause frames. +/// - If `max_lines > 0` and the collapsed output exceeds the cap, +/// `apply_hard_cap` is called to truncate while preserving the root cause. +/// +/// Returns `None` iff `raw` is empty or whitespace-only. +pub(crate) fn process(raw: &str, app_package: Option<&str>, max_lines: usize) -> Option { + let trimmed = raw.trim(); + if trimmed.is_empty() { + return None; + } + + let segments = parse_segments(trimmed); + if segments.is_empty() { + return Some(trimmed.to_string()); + } + + let mut out: Vec = Vec::new(); + out.push(truncate_header(&segments[0].header)); + + if segments.len() == 1 { + add_frames(&mut out, &segments[0].frames, app_package, None); + } else { + add_frames(&mut out, &segments[0].frames, app_package, None); + for seg in &segments[1..segments.len() - 1] { + out.push(truncate_header(&seg.header)); + add_frames(&mut out, &seg.frames, app_package, None); + } + let root = segments.last().unwrap(); + out.push(truncate_header(&root.header)); + add_frames( + &mut out, + &root.frames, + app_package, + Some(DEFAULT_ROOT_CAUSE_APP_FRAMES), + ); + } + + if max_lines > 0 && out.len() > max_lines { + out = apply_hard_cap(out, &segments, max_lines); + } + + Some(out.join("\n")) +} + +/// Apply a hard cap while preserving the root cause. +/// +/// - For a single segment: straight truncate to `max_lines`. +/// - For multiple segments: +/// - If the root-cause header's index in `out` is already beyond the cap, +/// build a synthetic output: `[top_header, "... (intermediate frames +/// truncated)", root_header, root frames until cap]`. +/// - Otherwise (root-cause header within the cap): straight truncate. +fn apply_hard_cap(out: Vec, segments: &[Segment], max_lines: usize) -> Vec { + if segments.len() <= 1 { + let mut out = out; + out.truncate(max_lines); + return out; + } + + let root = segments.last().unwrap(); + let truncated_root_header = truncate_header(&root.header); + let root_idx = out + .iter() + .rposition(|line| line == &truncated_root_header); + + let Some(idx) = root_idx else { + let mut out = out; + out.truncate(max_lines); + return out; + }; + + if idx < max_lines.saturating_sub(1) { + let mut out = out; + out.truncate(max_lines); + return out; + } + + // Root cause beyond the cap — build synthetic layout. + let mut result: Vec = Vec::with_capacity(max_lines); + if let Some(top) = out.first() { + result.push(top.clone()); + } + if max_lines >= 3 { + result.push("\t... (intermediate frames truncated)".to_string()); + } + result.push(truncated_root_header.clone()); + + let mut remaining = max_lines.saturating_sub(result.len()); + for line in &out[(idx + 1)..] { + if remaining == 0 { + break; + } + result.push(line.clone()); + remaining -= 1; + } + result +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn parse_segments_empty_input_returns_empty() { + assert!(parse_segments("").is_empty()); + } + + #[test] + fn parse_segments_single_header_no_frames() { + let trace = "java.lang.RuntimeException: boom"; + let segs = parse_segments(trace); + assert_eq!(segs.len(), 1); + assert_eq!(segs[0].header, "java.lang.RuntimeException: boom"); + assert!(segs[0].frames.is_empty()); + } + + #[test] + fn parse_segments_single_segment_with_frames() { + let trace = "java.lang.RuntimeException: boom\n\ + \tat com.example.A.foo(A.java:1)\n\ + \tat com.example.B.bar(B.java:2)"; + let segs = parse_segments(trace); + assert_eq!(segs.len(), 1); + assert_eq!(segs[0].frames.len(), 2); + } + + #[test] + fn parse_segments_caused_by_starts_new_segment() { + let trace = "java.lang.RuntimeException: outer\n\ + \tat com.example.A.foo(A.java:1)\n\ + Caused by: java.io.IOException: inner\n\ + \tat com.example.B.bar(B.java:2)"; + let segs = parse_segments(trace); + assert_eq!(segs.len(), 2); + assert_eq!(segs[0].header, "java.lang.RuntimeException: outer"); + assert_eq!(segs[0].frames, vec!["\tat com.example.A.foo(A.java:1)"]); + assert_eq!(segs[1].header, "Caused by: java.io.IOException: inner"); + assert_eq!(segs[1].frames, vec!["\tat com.example.B.bar(B.java:2)"]); + } + + #[test] + fn parse_segments_indented_caused_by_stays_as_frame() { + // Inside a Suppressed block, the "Caused by:" is indented and must NOT + // split segments — it stays as a frame so structural handling keeps it. + let trace = "java.lang.RuntimeException: outer\n\ + \tSuppressed: java.io.IOException: suppressed\n\ + \t\tat com.example.A.foo(A.java:1)\n\ + \t\tCaused by: java.lang.Error: nested\n\ + Caused by: java.io.IOException: real cause"; + let segs = parse_segments(trace); + assert_eq!(segs.len(), 2, "indented Caused by must not split segments"); + assert_eq!(segs[0].frames.len(), 3, "Suppressed block stays in outer"); + assert_eq!(segs[1].header, "Caused by: java.io.IOException: real cause"); + } + + #[test] + fn truncate_header_short_passes_through() { + assert_eq!(truncate_header("short"), "short"); + } + + #[test] + fn truncate_header_exact_200_chars_passes() { + let s = "a".repeat(200); + assert_eq!(truncate_header(&s), s); + } + + #[test] + fn truncate_header_over_200_chars_truncates_with_ellipsis() { + let s = "a".repeat(250); + let out = truncate_header(&s); + assert_eq!(out.chars().count(), 203); // 200 + "..." + assert!(out.ends_with("...")); + } + + #[test] + fn truncate_header_utf8_multibyte_safe() { + // 100 4-byte chars = 400 bytes but 100 chars — must not panic + let s = "日".repeat(100); + assert_eq!(truncate_header(&s), s); + let s = "日".repeat(250); + let out = truncate_header(&s); + assert_eq!(out.chars().count(), 203); + assert!(out.ends_with("...")); + } + + #[test] + fn is_app_frame_no_filter_accepts_everything() { + assert!(is_application_frame("\tat com.example.A.foo(A.java:1)", None)); + assert!(is_application_frame("\tat org.springframework.boot.Run(Run.java:1)", None)); + assert!(is_application_frame("\t... 42 more", None)); + } + + #[test] + fn is_app_frame_with_package_accepts_matching() { + assert!(is_application_frame( + "\tat com.example.A.foo(A.java:1)", + Some("com.example"), + )); + assert!(!is_application_frame( + "\tat org.springframework.boot.Run(Run.java:1)", + Some("com.example"), + )); + } + + #[test] + fn is_app_frame_rejects_summary_dots() { + // "\t... 42 more" is a framework artifact, never app + assert!(!is_application_frame("\t... 42 more", Some("com.example"))); + } + + #[test] + fn is_app_frame_rejects_empty_or_whitespace() { + assert!(!is_application_frame("", Some("com.example"))); + assert!(!is_application_frame(" ", Some("com.example"))); + } + + #[test] + fn is_structural_suppressed_top_level() { + assert!(is_structural_line("\tSuppressed: java.io.IOException")); + assert!(is_structural_line("Suppressed: foo")); + } + + #[test] + fn is_structural_indented_caused_by_only() { + // Top-level "Caused by:" is a segment boundary, not structural + assert!(!is_structural_line("Caused by: java.io.IOException")); + // Indented "Caused by:" inside suppressed is structural + assert!(is_structural_line("\tCaused by: java.io.IOException")); + assert!(is_structural_line(" Caused by: nested")); + } + + #[test] + fn is_structural_regular_frame_no() { + assert!(!is_structural_line("\tat com.example.A.foo(A.java:1)")); + assert!(!is_structural_line("")); + } + + fn collect_root_cause(frames: &[&str], app_package: Option<&str>) -> Vec { + let frames: Vec = frames.iter().map(|s| s.to_string()).collect(); + let mut out = Vec::new(); + add_frames( + &mut out, + &frames, + app_package, + Some(DEFAULT_ROOT_CAUSE_APP_FRAMES), + ); + out + } + + #[test] + fn root_cause_caps_app_frames_at_ten() { + let mut frames = Vec::new(); + for i in 0..15 { + frames.push(format!("\tat com.example.A.m{i}(A.java:{i})")); + } + let frame_refs: Vec<&str> = frames.iter().map(|s| s.as_str()).collect(); + let out = collect_root_cause(&frame_refs, Some("com.example")); + // 10 kept, 5 dropped silently (no "framework" marker because these are app frames) + assert_eq!(out.len(), 10); + } + + #[test] + fn root_cause_no_filter_keeps_all_frames() { + let mut frames = Vec::new(); + for i in 0..15 { + frames.push(format!("\tat com.example.A.m{i}(A.java:{i})")); + } + let frame_refs: Vec<&str> = frames.iter().map(|s| s.as_str()).collect(); + let out = collect_root_cause(&frame_refs, None); + assert_eq!(out.len(), 15); + } + + #[test] + fn root_cause_structural_bypasses_cap() { + // Structural lines are always preserved, even if we already hit the 10-app cap. + let mut frames = Vec::new(); + for i in 0..10 { + frames.push(format!("\tat com.example.A.m{i}(A.java:{i})")); + } + frames.push("\tSuppressed: x".to_string()); + frames.push("\tat com.example.Z.zzz(Z.java:99)".to_string()); // 11th app — dropped + let frame_refs: Vec<&str> = frames.iter().map(|s| s.as_str()).collect(); + let out = collect_root_cause(&frame_refs, Some("com.example")); + assert_eq!(out.len(), 11, "10 app frames + 1 structural, 11th app dropped"); + assert!(out.contains(&"\tSuppressed: x".to_string())); + } + + #[test] + fn root_cause_collapses_framework_as_before() { + let frames = [ + "\tat com.example.A.foo(A.java:1)", + "\tat org.framework.X(X.java:1)", + "\tat org.framework.Y(Y.java:2)", + "\tat com.example.B.bar(B.java:2)", + ]; + let out = collect_root_cause(&frames, Some("com.example")); + assert_eq!( + out, + vec![ + "\tat com.example.A.foo(A.java:1)", + "\t... 2 framework frames omitted", + "\tat com.example.B.bar(B.java:2)", + ] + ); + } + + #[test] + fn process_empty_returns_none() { + assert!(process("", Some("com.example"), 0).is_none()); + assert!(process(" \n ", Some("com.example"), 0).is_none()); + } + + #[test] + fn process_single_segment_no_filter_returns_verbatim() { + let trace = "java.lang.RuntimeException: boom\n\tat com.example.A.foo(A.java:1)"; + let out = process(trace, None, 0).unwrap(); + assert_eq!(out, trace); + } + + #[test] + fn process_single_segment_collapses_framework() { + let trace = "java.lang.AssertionError: fail\n\ + \tat com.example.Test.t(Test.java:5)\n\ + \tat org.junit.runner.Run(Run.java:1)\n\ + \tat org.junit.runner.Run(Run.java:2)"; + let out = process(trace, Some("com.example"), 0).unwrap(); + assert_eq!( + out, + "java.lang.AssertionError: fail\n\ + \tat com.example.Test.t(Test.java:5)\n\ + \t... 2 framework frames omitted" + ); + } + + #[test] + fn process_multi_segment_preserves_root_cause() { + let trace = "java.lang.RuntimeException: outer\n\ + \tat org.spring.Foo(Foo.java:1)\n\ + Caused by: java.io.IOException: middle\n\ + \tat org.hibernate.Bar(Bar.java:2)\n\ + Caused by: java.net.ConnectException: inner\n\ + \tat com.example.DbService.connect(DbService.java:42)"; + let out = process(trace, Some("com.example"), 0).unwrap(); + assert!(out.contains("java.lang.RuntimeException: outer")); + assert!(out.contains("Caused by: java.io.IOException: middle")); + assert!(out.contains("Caused by: java.net.ConnectException: inner")); + assert!(out.contains("\tat com.example.DbService.connect(DbService.java:42)")); + assert!(out.contains("framework frames omitted")); + } + + fn collect_collapsed(frames: &[&str], app_package: Option<&str>) -> Vec { + let frames: Vec = frames.iter().map(|s| s.to_string()).collect(); + let mut out = Vec::new(); + add_frames(&mut out, &frames, app_package, None); + out + } + + #[test] + fn collapse_no_filter_keeps_everything() { + let frames = [ + "\tat org.framework.Foo(Foo.java:1)", + "\tat com.example.A.foo(A.java:1)", + "\tat org.framework.Bar(Bar.java:2)", + ]; + let out = collect_collapsed(&frames, None); + assert_eq!(out.len(), 3); + } + + #[test] + fn collapse_all_framework_yields_single_summary() { + let frames = [ + "\tat org.framework.Foo(Foo.java:1)", + "\tat org.framework.Bar(Bar.java:2)", + "\tat org.framework.Baz(Baz.java:3)", + ]; + let out = collect_collapsed(&frames, Some("com.example")); + assert_eq!(out, vec!["\t... 3 framework frames omitted"]); + } + + #[test] + fn collapse_alternating_produces_multiple_summaries() { + let frames = [ + "\tat org.framework.Foo(Foo.java:1)", + "\tat com.example.A.one(A.java:10)", + "\tat org.framework.Bar(Bar.java:2)", + "\tat org.framework.Baz(Baz.java:3)", + "\tat com.example.B.two(B.java:20)", + ]; + let out = collect_collapsed(&frames, Some("com.example")); + assert_eq!( + out, + vec![ + "\t... 1 framework frames omitted", + "\tat com.example.A.one(A.java:10)", + "\t... 2 framework frames omitted", + "\tat com.example.B.two(B.java:20)", + ] + ); + } + + #[test] + fn collapse_preserves_structural_inline() { + let frames = [ + "\tat org.framework.Foo(Foo.java:1)", + "\tSuppressed: java.io.IOException", + "\t\tat org.framework.Bar(Bar.java:2)", + "\t\tCaused by: java.lang.Error: nested", + ]; + let out = collect_collapsed(&frames, Some("com.example")); + assert_eq!( + out, + vec![ + "\t... 1 framework frames omitted", + "\tSuppressed: java.io.IOException", + "\t... 1 framework frames omitted", + "\t\tCaused by: java.lang.Error: nested", + ] + ); + } + + #[test] + fn hard_cap_single_segment_simple_truncate() { + // Non-app frames collapse to one marker, so a 21-line input produces + // a 2-line out. Verify cap-not-triggered behavior first. + let mut trace = String::from("java.lang.RuntimeException: boom"); + for i in 0..20 { + trace.push_str(&format!("\n\tat com.example.A.m{i}(A.java:{i})")); + } + // With app_package=Some("com.example"), all frames are app frames. + // out = [header, 20 app frames]. With cap=5, out.len()=21 > 5 → + // straight-truncate to 5. + let out = process(&trace, Some("com.example"), 5).unwrap(); + assert_eq!(out.lines().count(), 5); + } + + #[test] + fn hard_cap_multi_segment_preserves_root_cause() { + // Two segments, each with enough app frames that even after + // framework-frame collapsing, the total pushed lines exceeds the cap + // AND the root-cause header sits at/beyond (max_lines - 1), forcing + // the synthetic "... (intermediate frames truncated)" layout. + let trace = "java.lang.RuntimeException: outer\n\ + \tat com.example.A.foo(A.java:1)\n\ + \tat com.example.A.bar(A.java:2)\n\ + \tat com.example.A.baz(A.java:3)\n\ + \tat com.example.A.qux(A.java:4)\n\ + Caused by: java.io.IOException: real cause\n\ + \tat com.example.DbService.connect(Db.java:88)\n\ + \tat com.example.DbService.prepare(Db.java:91)\n\ + \tat com.example.DbService.execute(Db.java:94)"; + // out from process(): [top_header, 4 app frames, root_header, 3 app frames] + // out.len() = 9. With max_lines = 5, root_idx = 5, 5 >= 5-1=4 → synthetic. + let out = process(trace, Some("com.example"), 5).unwrap(); + let lines: Vec<&str> = out.lines().collect(); + assert_eq!(lines.len(), 5, "must fit exactly in max_lines=5, got: {out}"); + assert_eq!(lines[0], "java.lang.RuntimeException: outer"); + assert_eq!(lines[1], "\t... (intermediate frames truncated)"); + assert_eq!(lines[2], "Caused by: java.io.IOException: real cause"); + assert!( + lines[3].contains("com.example.DbService"), + "first root frame must survive; got line 3: {:?}", + lines[3] + ); + } + + #[test] + fn hard_cap_multi_segment_root_within_limit_straight_truncate() { + // Root cause header at line 3 of output, cap at 10 → straight truncate. + let trace = "java.lang.RuntimeException: outer\n\ + \tat com.example.A.foo(A.java:1)\n\ + Caused by: java.io.IOException: inner\n\ + \tat com.example.B.bar(B.java:1)\n\ + \tat com.example.B.baz(B.java:2)\n\ + \tat com.example.B.qux(B.java:3)\n\ + \tat com.example.B.quux(B.java:4)\n\ + \tat com.example.B.corge(B.java:5)"; + let out = process(trace, Some("com.example"), 6).unwrap(); + assert_eq!(out.lines().count(), 6); + } + + #[test] + fn process_real_world_spring_fixture() { + let trace = include_str!("../../../tests/fixtures/java/stack-traces/multi-caused-by.txt"); + let out = process(trace, Some("com.example"), 50).unwrap(); + assert!(out.contains("Caused by: org.springframework.beans.factory.BeanCreationException")); + assert!(out.contains("Caused by: org.hibernate.HibernateException")); + assert!(out.contains("com.example.DbIntegrationIT.shouldConnect")); + assert!(out.contains("framework frames omitted")); + } +} diff --git a/src/cmds/java/surefire_reports.rs b/src/cmds/java/surefire_reports.rs new file mode 100644 index 000000000..15e5190be --- /dev/null +++ b/src/cmds/java/surefire_reports.rs @@ -0,0 +1,573 @@ +//! Parses Maven Surefire/Failsafe XML test reports from +//! `target/surefire-reports/TEST-*.xml` and `target/failsafe-reports/*.xml`. +//! Uses quick-xml streaming parser. Time-gated by `started_at` to skip stale +//! reports from previous runs. + +use crate::cmds::java::stack_trace; +use quick_xml::events::{BytesStart, Event}; +use quick_xml::Reader; +use std::path::Path; +use std::time::SystemTime; + +pub const DEFAULT_STACK_TRACE_LINES: usize = 50; +pub const DEFAULT_PER_TEST_OUTPUT_LIMIT: usize = 2000; +const DEFAULT_TOTAL_OUTPUT_LIMIT: usize = 10_000; + +#[derive(Debug, Default, PartialEq)] +pub struct TestSummary { + pub run: u32, + pub failures: u32, + pub errors: u32, + pub skipped: u32, +} + +impl TestSummary { + pub(crate) fn add(&mut self, other: &Self) { + self.run += other.run; + self.failures += other.failures; + self.errors += other.errors; + self.skipped += other.skipped; + } +} + +#[derive(Debug, PartialEq, Clone, Copy)] +pub enum FailureKind { + Failure, + Error, +} + +#[derive(Debug, PartialEq)] +pub struct TestFailure { + pub test_class: String, + pub test_method: String, + pub kind: FailureKind, + pub message: Option, + pub failure_type: Option, + pub stack_trace: Option, + pub test_output: Option, +} + +#[derive(Debug, Default, PartialEq)] +pub struct SurefireResult { + pub summary: TestSummary, + pub failures: Vec, + pub files_read: usize, + pub files_skipped_stale: usize, + pub files_malformed: usize, +} + +fn local_name(name: &[u8]) -> &[u8] { + name.rsplit(|b| *b == b':').next().unwrap_or(name) +} + +fn extract_attr( + reader: &Reader<&[u8]>, + start: &BytesStart<'_>, + key: &[u8], +) -> Option { + for attr in start.attributes().flatten() { + if local_name(attr.key.as_ref()) != key { + continue; + } + if let Ok(value) = attr.decode_and_unescape_value(reader.decoder()) { + return Some(value.into_owned()); + } + } + None +} + +fn parse_u32_attr(reader: &Reader<&[u8]>, start: &BytesStart<'_>, key: &[u8]) -> u32 { + extract_attr(reader, start, key) + .and_then(|v| v.parse::().ok()) + .unwrap_or(0) +} + +/// Parse a single Surefire XML testsuite string into a partial result. +/// `app_package` is passed to `stack_trace::process` for frame classification. +/// +/// Returns `None` only if the XML is completely malformed; otherwise a +/// best-effort result is returned. +pub(crate) fn parse_content(xml: &str, app_package: Option<&str>) -> Option { + #[derive(Clone, Copy, PartialEq)] + enum CaptureField { + StackTrace, + SystemOut, + SystemErr, + } + + let mut reader = Reader::from_str(xml); + reader.config_mut().trim_text(false); + let mut buf = Vec::new(); + + let mut result = SurefireResult::default(); + let mut saw_testsuite = false; + let mut current_class: Option = None; + let mut current_method: Option = None; + let mut current_has_failure = false; + + let mut pending_message: Option = None; + let mut pending_type: Option = None; + let mut pending_kind: Option = None; + let mut stack_buf = String::new(); + let mut stdout_buf = String::new(); + let mut stderr_buf = String::new(); + let mut capture: Option = None; + + loop { + match reader.read_event_into(&mut buf) { + Ok(Event::Start(e)) | Ok(Event::Empty(e)) => { + match local_name(e.name().as_ref()) { + b"testsuite" => { + saw_testsuite = true; + let file_summary = TestSummary { + run: parse_u32_attr(&reader, &e, b"tests"), + failures: parse_u32_attr(&reader, &e, b"failures"), + errors: parse_u32_attr(&reader, &e, b"errors"), + skipped: parse_u32_attr(&reader, &e, b"skipped"), + }; + result.summary.add(&file_summary); + } + b"testcase" => { + current_class = extract_attr(&reader, &e, b"classname"); + current_method = extract_attr(&reader, &e, b"name"); + current_has_failure = false; + } + b"failure" | b"error" => { + let kind = if local_name(e.name().as_ref()) == b"failure" { + FailureKind::Failure + } else { + FailureKind::Error + }; + pending_message = extract_attr(&reader, &e, b"message"); + pending_type = extract_attr(&reader, &e, b"type"); + pending_kind = Some(kind); + stack_buf.clear(); + capture = Some(CaptureField::StackTrace); + current_has_failure = true; + } + b"system-out" if current_has_failure => { + stdout_buf.clear(); + capture = Some(CaptureField::SystemOut); + } + b"system-err" if current_has_failure => { + stderr_buf.clear(); + capture = Some(CaptureField::SystemErr); + } + _ => {} + } + } + Ok(Event::Text(t)) => { + if let Some(field) = capture { + if let Ok(text) = t.unescape() { + match field { + CaptureField::StackTrace => stack_buf.push_str(&text), + CaptureField::SystemOut => stdout_buf.push_str(&text), + CaptureField::SystemErr => stderr_buf.push_str(&text), + } + } + } + } + Ok(Event::End(e)) => { + match local_name(e.name().as_ref()) { + b"failure" | b"error" => { + let processed = stack_trace::process( + stack_buf.trim(), + app_package, + DEFAULT_STACK_TRACE_LINES, + ); + result.failures.push(TestFailure { + test_class: current_class.clone().unwrap_or_default(), + test_method: current_method.clone().unwrap_or_default(), + kind: pending_kind.take().unwrap_or(FailureKind::Failure), + message: pending_message + .take() + .filter(|s| !s.is_empty()) + .map(|s| stack_trace::truncate_header(&s)), + failure_type: pending_type.take().filter(|s| !s.is_empty()), + stack_trace: processed, + test_output: None, + }); + capture = None; + } + b"system-out" | b"system-err" => { + capture = None; + } + b"testcase" => { + let combined = combine_test_output( + &stdout_buf, + &stderr_buf, + DEFAULT_PER_TEST_OUTPUT_LIMIT, + ); + stdout_buf.clear(); + stderr_buf.clear(); + if let Some(combined) = combined { + if let Some(last) = result.failures.last_mut() { + if last.test_class == current_class.clone().unwrap_or_default() + && last.test_method + == current_method.clone().unwrap_or_default() + { + last.test_output = Some(combined); + } + } + } + current_class = None; + current_method = None; + current_has_failure = false; + } + _ => {} + } + } + Ok(Event::Eof) => break, + Err(_) => return None, + _ => {} + } + buf.clear(); + } + + if !saw_testsuite { + return None; + } + + Some(result) +} + +fn combine_test_output(stdout: &str, stderr: &str, per_test_limit: usize) -> Option { + let stdout = stdout.trim(); + let stderr = stderr.trim(); + if stdout.is_empty() && stderr.is_empty() { + return None; + } + let mut combined = String::new(); + if !stdout.is_empty() { + combined.push_str(stdout); + } + if !stderr.is_empty() { + if !combined.is_empty() { + combined.push_str("\n[STDERR]\n"); + } else { + combined.push_str("[STDERR]\n"); + } + combined.push_str(stderr); + } + Some(truncate_test_output(&combined, per_test_limit)) +} + +fn truncate_test_output(output: &str, max_chars: usize) -> String { + let char_count = output.chars().count(); + if char_count <= max_chars { + return output.to_string(); + } + let skip = char_count - max_chars; + let tail: String = output.chars().skip(skip).collect(); + format!("... ({skip} chars truncated)\n{tail}") +} + +/// Scan a directory for `TEST-*.xml` files and merge their parsed results. +/// +/// - Files whose `mtime < since` are skipped and counted in `files_skipped_stale`. +/// - Files that parse to `None` (malformed) count in `files_malformed`. +/// - Returns `None` only if the directory does not exist or is empty. +pub fn parse_dir( + dir: &Path, + since: Option, + app_package: Option<&str>, +) -> Option { + if !dir.exists() || !dir.is_dir() { + return None; + } + + let entries = std::fs::read_dir(dir).ok()?; + let mut aggregate = SurefireResult::default(); + let mut any_candidate = false; + + for entry in entries.flatten() { + let path = entry.path(); + let Some(name) = path.file_name().and_then(|s| s.to_str()) else { + continue; + }; + if !name.starts_with("TEST-") || !name.ends_with(".xml") { + continue; + } + any_candidate = true; + + if let Some(since) = since { + let modified = entry.metadata().ok().and_then(|m| m.modified().ok()); + match modified { + Some(m) if m >= since => {} + Some(_) => { + aggregate.files_skipped_stale += 1; + continue; + } + None => { + aggregate.files_skipped_stale += 1; + continue; + } + } + } + + let Ok(content) = std::fs::read_to_string(&path) else { + aggregate.files_malformed += 1; + eprintln!("rtk mvn: skipping unreadable {}", name); + continue; + }; + + match parse_content(&content, app_package) { + Some(file_result) => { + aggregate.files_read += 1; + aggregate.summary.add(&file_result.summary); + aggregate.failures.extend(file_result.failures); + } + None => { + aggregate.files_malformed += 1; + eprintln!("rtk mvn: skipping malformed {}", name); + } + } + } + + if !any_candidate { + return None; + } + + apply_total_output_limit(&mut aggregate.failures, DEFAULT_TOTAL_OUTPUT_LIMIT); + Some(aggregate) +} + +fn apply_total_output_limit(failures: &mut [TestFailure], total_limit: usize) { + let mut budget = total_limit; + let mut exhausted = false; + for failure in failures.iter_mut() { + if exhausted { + failure.test_output = None; + continue; + } + if let Some(out) = &failure.test_output { + let len = out.chars().count(); + if len > budget { + failure.test_output = None; + exhausted = true; + } else { + budget -= len; + } + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use std::time::{Duration, SystemTime}; + + fn copy_fixture( + tmp: &tempfile::TempDir, + fixture_name: &str, + mtime: Option, + ) -> std::path::PathBuf { + let src = std::path::Path::new("tests/fixtures/java/surefire-reports").join(fixture_name); + let dst = tmp.path().join(fixture_name); + std::fs::copy(&src, &dst).expect("copy fixture"); + if let Some(mtime) = mtime { + filetime::set_file_mtime(&dst, filetime::FileTime::from_system_time(mtime)) + .expect("set mtime"); + } + dst + } + + #[test] + fn parse_dir_missing_returns_none() { + assert!(super::parse_dir( + std::path::Path::new("/definitely/does/not/exist/rtk-test"), + None, + None + ) + .is_none()); + } + + #[test] + fn parse_dir_empty_returns_none() { + let tmp = tempfile::tempdir().unwrap(); + assert!(super::parse_dir(tmp.path(), None, None).is_none()); + } + + #[test] + fn parse_dir_ignores_non_test_prefix_files() { + let tmp = tempfile::tempdir().unwrap(); + copy_fixture(&tmp, "TEST-com.example.PassingTest.xml", None); + std::fs::write(tmp.path().join("summary.xml"), "").unwrap(); + std::fs::write(tmp.path().join("other.txt"), "hi").unwrap(); + + let result = super::parse_dir(tmp.path(), None, None).expect("parses"); + assert_eq!(result.files_read, 1); + } + + #[test] + fn parse_dir_aggregates_multi_file_counts() { + let tmp = tempfile::tempdir().unwrap(); + copy_fixture(&tmp, "TEST-com.example.PassingTest.xml", None); + copy_fixture(&tmp, "TEST-com.example.FailingTest.xml", None); + copy_fixture(&tmp, "TEST-com.example.SkippedTest.xml", None); + + let result = super::parse_dir(tmp.path(), None, None).expect("parses"); + assert_eq!(result.files_read, 3); + assert!(result.summary.run >= 3); + assert!(result.summary.failures >= 2); + assert!(result.summary.skipped >= 1); + } + + #[test] + fn parse_dir_time_gate_skips_stale_files() { + let tmp = tempfile::tempdir().unwrap(); + let now = SystemTime::now(); + let stale = now - Duration::from_secs(60 * 60); // 1h ago + let fresh = now + Duration::from_millis(50); + + copy_fixture(&tmp, "TEST-com.example.PassingTest.xml", Some(stale)); + copy_fixture(&tmp, "TEST-com.example.FailingTest.xml", Some(fresh)); + + let since = now; + let result = super::parse_dir(tmp.path(), Some(since), None).expect("parses"); + assert_eq!(result.files_read, 1, "only the fresh file counts"); + assert_eq!(result.files_skipped_stale, 1); + assert_eq!(result.summary.failures, 2, "from FailingTest only"); + } + + #[test] + fn parse_dir_malformed_counts_but_continues() { + let tmp = tempfile::tempdir().unwrap(); + copy_fixture(&tmp, "TEST-com.example.PassingTest.xml", None); + std::fs::write( + tmp.path().join("TEST-com.example.Broken.xml"), + ">>>", + ) + .unwrap(); + + let result = super::parse_dir(tmp.path(), None, None).expect("parses"); + assert_eq!(result.files_read, 1); + assert_eq!(result.files_malformed, 1); + } + + #[test] + fn parse_content_single_passing() { + let xml = include_str!( + "../../../tests/fixtures/java/surefire-reports/TEST-com.example.PassingTest.xml" + ); + let result = parse_content(xml, None).expect("passing testsuite parses"); + assert!(result.summary.run >= 1); + assert_eq!(result.summary.failures, 0); + assert_eq!(result.summary.errors, 0); + assert!(result.failures.is_empty()); + } + + #[test] + fn parse_content_single_failing_extracts_details() { + let xml = include_str!( + "../../../tests/fixtures/java/surefire-reports/TEST-com.example.FailingTest.xml" + ); + let result = parse_content(xml, None).expect("failing testsuite parses"); + assert_eq!(result.summary.failures, 2); + assert_eq!(result.failures.len(), 2); + let first = &result.failures[0]; + assert_eq!(first.test_class, "com.example.FailingTest"); + assert!(first.message.as_deref().unwrap_or("").contains("expected")); + assert!(first.stack_trace.is_some()); + assert_eq!(first.kind, FailureKind::Failure); + } + + #[test] + fn parse_content_captures_system_out_err_only_for_failed_tests() { + let xml = include_str!( + "../../../tests/fixtures/java/surefire-reports/TEST-com.example.FailingTestWithLogs.xml" + ); + let result = parse_content(xml, None).expect("parses"); + assert_eq!(result.failures.len(), 2); + let with_both_streams = result + .failures + .iter() + .find(|f| f.test_method == "shouldConnectToDb") + .expect("shouldConnectToDb present"); + let output = with_both_streams + .test_output + .as_deref() + .expect("test_output captured"); + assert!(output.contains("Initializing connection pool")); + assert!(output.contains("[STDERR]")); + assert!(output.contains("Connection refused")); + + let with_stdout_only = result + .failures + .iter() + .find(|f| f.test_method == "shouldProcessData") + .expect("shouldProcessData present"); + let output = with_stdout_only.test_output.as_deref().unwrap_or(""); + assert!(output.contains("Processing batch")); + assert!(!output.contains("[STDERR]")); + + // Passing test's must NOT be captured + let passing_system_out_text = "This output belongs to a passing test"; + for failure in &result.failures { + if let Some(out) = &failure.test_output { + assert!( + !out.contains(passing_system_out_text), + "passing-test stdout must not leak into a failure's test_output" + ); + } + } + } + + #[test] + fn parse_content_error_testsuite_marks_failure_kind_error() { + let xml = include_str!( + "../../../tests/fixtures/java/surefire-reports/TEST-com.example.ErrorTest.xml" + ); + let result = parse_content(xml, None).expect("parses"); + assert!(result.failures.iter().any(|f| f.kind == FailureKind::Error)); + } + + #[test] + fn parse_content_skipped_testsuite_counts_skipped() { + let xml = include_str!( + "../../../tests/fixtures/java/surefire-reports/TEST-com.example.SkippedTest.xml" + ); + let result = parse_content(xml, None).expect("parses"); + assert!(result.summary.skipped > 0); + } + + #[test] + fn apply_total_output_limit_nulls_out_excess() { + let mut failures = vec![ + TestFailure { + test_class: "A".into(), + test_method: "m1".into(), + kind: FailureKind::Failure, + message: None, + failure_type: None, + stack_trace: None, + test_output: Some("a".repeat(4000)), + }, + TestFailure { + test_class: "A".into(), + test_method: "m2".into(), + kind: FailureKind::Failure, + message: None, + failure_type: None, + stack_trace: None, + test_output: Some("b".repeat(4000)), + }, + TestFailure { + test_class: "A".into(), + test_method: "m3".into(), + kind: FailureKind::Failure, + message: None, + failure_type: None, + stack_trace: None, + test_output: Some("c".repeat(4000)), + }, + ]; + super::apply_total_output_limit(&mut failures, 10_000); + assert!(failures[0].test_output.is_some()); + assert!(failures[1].test_output.is_some()); + assert!( + failures[2].test_output.is_none(), + "third should exceed 10k cumulative" + ); + } +} diff --git a/src/cmds/mod.rs b/src/cmds/mod.rs index 1eca0b84c..0f25bd8de 100644 --- a/src/cmds/mod.rs +++ b/src/cmds/mod.rs @@ -4,6 +4,7 @@ pub mod cloud; pub mod dotnet; pub mod git; pub mod go; +pub mod java; pub mod js; pub mod python; pub mod ruby; diff --git a/src/core/toml_filter.rs b/src/core/toml_filter.rs index 06060d22d..74b61026d 100644 --- a/src/core/toml_filter.rs +++ b/src/core/toml_filter.rs @@ -1582,7 +1582,6 @@ match_command = "^make\\b" "markdownlint", "mix-compile", "mix-format", - "mvn-build", "ping", "pio-run", "poetry-install", @@ -1621,8 +1620,8 @@ match_command = "^make\\b" let filters = make_filters(BUILTIN_TOML); assert_eq!( filters.len(), - 59, - "Expected exactly 59 built-in filters, got {}. \ + 58, + "Expected exactly 58 built-in filters, got {}. \ Update this count when adding/removing filters in src/filters/.", filters.len() ); @@ -1679,11 +1678,11 @@ expected = "output line 1\noutput line 2" let combined = format!("{}\n\n{}", BUILTIN_TOML, new_filter); let filters = make_filters(&combined); - // All 59 existing filters still present + 1 new = 60 + // All 58 existing filters still present + 1 new = 59 assert_eq!( filters.len(), - 60, - "Expected 60 filters after concat (59 built-in + 1 new)" + 59, + "Expected 59 filters after concat (58 built-in + 1 new)" ); // New filter is discoverable diff --git a/src/discover/rules.rs b/src/discover/rules.rs index 11359496c..a6642269d 100644 --- a/src/discover/rules.rs +++ b/src/discover/rules.rs @@ -672,11 +672,11 @@ pub const RULES: &[RtkRule] = &[ subcmd_status: &[], }, RtkRule { - pattern: r"^mvn\s+(compile|package|clean|install)\b", + pattern: r"^(\.\/?)?mvnw?\s+(test|compile|package|clean|install|dependency:tree)\b", rtk_cmd: "rtk mvn", - rewrite_prefixes: &["mvn"], + rewrite_prefixes: &["mvn", "mvnw", "./mvnw"], category: "Build", - savings_pct: 70.0, + savings_pct: 90.0, subcmd_savings: &[], subcmd_status: &[], }, diff --git a/src/filters/mvn-build.toml b/src/filters/mvn-build.toml deleted file mode 100644 index 430a7f0cd..000000000 --- a/src/filters/mvn-build.toml +++ /dev/null @@ -1,44 +0,0 @@ -[filters.mvn-build] -description = "Compact Maven build output" -match_command = "^mvn\\s+(compile|package|clean|install)\\b" -strip_ansi = true -strip_lines_matching = [ - "^\\[INFO\\] ---", - "^\\[INFO\\] Building\\s", - "^\\[INFO\\] Downloading\\s", - "^\\[INFO\\] Downloaded\\s", - "^\\[INFO\\]\\s*$", - "^\\s*$", - "^Downloading:", - "^Downloaded:", - "^Progress", -] -max_lines = 50 -on_empty = "mvn: ok" - -[[tests.mvn-build]] -name = "strips INFO noise, preserves errors and summary" -input = """ -[INFO] --- -[INFO] Building myapp 1.0-SNAPSHOT -[INFO] Downloading org.apache.maven.plugins:maven-compiler-plugin:3.11.0 -[INFO] Downloaded org.apache.maven.plugins:maven-compiler-plugin:3.11.0 -[INFO] -[ERROR] /src/main/java/Main.java:[10,5] cannot find symbol - symbol: method foo() -[INFO] BUILD FAILURE -[INFO] Total time: 2.543 s -""" -expected = "[ERROR] /src/main/java/Main.java:[10,5] cannot find symbol\n symbol: method foo()\n[INFO] BUILD FAILURE\n[INFO] Total time: 2.543 s" - -[[tests.mvn-build]] -name = "successful build keeps BUILD SUCCESS line" -input = """ -[INFO] --- -[INFO] Building myapp 1.0-SNAPSHOT -[INFO] -[INFO] BUILD SUCCESS -[INFO] Total time: 4.123 s -[INFO] Finished at: 2024-01-15T10:30:00Z -""" -expected = "[INFO] BUILD SUCCESS\n[INFO] Total time: 4.123 s\n[INFO] Finished at: 2024-01-15T10:30:00Z" diff --git a/src/main.rs b/src/main.rs index 1d139d958..15e40b2d7 100644 --- a/src/main.rs +++ b/src/main.rs @@ -11,6 +11,7 @@ use cmds::cloud::{aws_cmd, container, curl_cmd, psql_cmd, wget_cmd}; use cmds::dotnet::{binlog, dotnet_cmd, dotnet_format_report, dotnet_trx}; use cmds::git::{diff_cmd, gh_cmd, git, gt_cmd}; use cmds::go::{go_cmd, golangci_cmd}; +use cmds::java::mvn_cmd; use cmds::js::{ lint_cmd, next_cmd, npm_cmd, playwright_cmd, pnpm_cmd, prettier_cmd, prisma_cmd, tsc_cmd, vitest_cmd, @@ -685,6 +686,12 @@ enum Commands { command: GoCommands, }, + /// Maven commands with compact output + Mvn { + #[command(subcommand)] + command: MvnCommands, + }, + /// Graphite (gt) stacked PR commands with compact output Gt { #[command(subcommand)] @@ -1073,6 +1080,35 @@ enum GoCommands { Other(Vec), } +#[derive(Debug, Subcommand)] +enum MvnCommands { + /// Run tests with compact output (state machine parser, 99%+ token reduction) + Test { + #[arg(trailing_var_arg = true, allow_hyphen_values = true)] + args: Vec, + }, + /// Compile with compact output (strip [INFO] noise, keep errors and summary) + Compile { + #[arg(trailing_var_arg = true, allow_hyphen_values = true)] + args: Vec, + }, + /// Run `mvn checkstyle:check` with grouped violations and stripped boilerplate + #[command(name = "checkstyle:check", alias = "checkstyle")] + Checkstyle { + #[arg(trailing_var_arg = true, allow_hyphen_values = true)] + args: Vec, + }, + /// Dependency tree with compact output (strip duplicates and boilerplate) + #[command(name = "dependency:tree")] + DepTree { + #[arg(trailing_var_arg = true, allow_hyphen_values = true)] + args: Vec, + }, + /// Passthrough: runs any unsupported mvn subcommand directly + #[command(external_subcommand)] + Other(Vec), +} + /// RTK-only subcommands that should never fall back to raw execution. /// If Clap fails to parse these, show the Clap error directly. const RTK_META_COMMANDS: &[&str] = &[ @@ -2039,6 +2075,14 @@ fn run_cli() -> Result { GoCommands::Other(args) => go_cmd::run_other(&args, cli.verbose)?, }, + Commands::Mvn { command } => match command { + MvnCommands::Test { args } => mvn_cmd::run_test(&args, cli.verbose)?, + MvnCommands::Compile { args } => mvn_cmd::run_compile(&args, cli.verbose)?, + MvnCommands::Checkstyle { args } => mvn_cmd::run_checkstyle(&args, cli.verbose)?, + MvnCommands::DepTree { args } => mvn_cmd::run_dep_tree(&args, cli.verbose)?, + MvnCommands::Other(args) => mvn_cmd::run_other(&args, cli.verbose)?, + }, + Commands::Gt { command } => match command { GtCommands::Log { args } => gt_cmd::run_log(&args, cli.verbose)?, GtCommands::Submit { args } => gt_cmd::run_submit(&args, cli.verbose)?, @@ -2381,6 +2425,7 @@ fn is_operational_command(cmd: &Commands) -> bool { | Commands::Rspec { .. } | Commands::Pip { .. } | Commands::Go { .. } + | Commands::Mvn { .. } | Commands::GolangciLint { .. } | Commands::Gt { .. } ) diff --git a/tests/fixtures/java/failsafe-reports/TEST-com.example.DbIntegrationIT.xml b/tests/fixtures/java/failsafe-reports/TEST-com.example.DbIntegrationIT.xml new file mode 100644 index 000000000..ae4b796d9 --- /dev/null +++ b/tests/fixtures/java/failsafe-reports/TEST-com.example.DbIntegrationIT.xml @@ -0,0 +1,20 @@ + + + + + java.lang.IllegalStateException: Failed to load ApplicationContext + at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:180) + at org.springframework.test.context.support.DefaultTestContext.getApplicationContext(DefaultTestContext.java:124) + at org.springframework.test.context.support.DependencyInjectionTestExecutionListener.injectDependencies(DependencyInjectionTestExecutionListener.java:118) +Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'dataSource' + at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:628) + at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:542) + at org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration.dataSource(DataSourceAutoConfiguration.java:114) +Caused by: org.hibernate.HibernateException: Unable to acquire JDBC Connection; nested exception is java.sql.SQLTransientConnectionException: HikariPool-1 - Connection is not available, request timed out after 30000ms. + at org.hibernate.internal.SessionFactoryImpl.createEntityManagerFactory(SessionFactoryImpl.java:512) + at com.example.DbIntegrationIT.shouldConnect(DbIntegrationIT.java:88) + at java.base/java.lang.reflect.Method.invoke(Method.java:580) + 2026-04-15 10:42:17 ERROR HikariDataSource - HikariPool-1 - Connection is not available +Connection refused (Connection refused) + + diff --git a/tests/fixtures/java/failsafe-reports/TEST-com.example.PortConflictIT.xml b/tests/fixtures/java/failsafe-reports/TEST-com.example.PortConflictIT.xml new file mode 100644 index 000000000..f96d0bf1a --- /dev/null +++ b/tests/fixtures/java/failsafe-reports/TEST-com.example.PortConflictIT.xml @@ -0,0 +1,9 @@ + + + + java.net.BindException: Address already in use + at java.base/sun.nio.ch.Net.bind0(Native Method) + at java.base/sun.nio.ch.Net.bind(Net.java:555) + at com.example.PortConflictIT.shouldStartServer(PortConflictIT.java:42) + + diff --git a/tests/fixtures/java/poms/child-pom.xml b/tests/fixtures/java/poms/child-pom.xml new file mode 100644 index 000000000..050da017f --- /dev/null +++ b/tests/fixtures/java/poms/child-pom.xml @@ -0,0 +1,10 @@ + + + 4.0.0 + + com.example.parent + parent + 1.0.0 + + child + diff --git a/tests/fixtures/java/poms/no-groupid-pom.xml b/tests/fixtures/java/poms/no-groupid-pom.xml new file mode 100644 index 000000000..c6ff96f69 --- /dev/null +++ b/tests/fixtures/java/poms/no-groupid-pom.xml @@ -0,0 +1,6 @@ + + + 4.0.0 + orphan + 1.0.0 + diff --git a/tests/fixtures/java/poms/single-module-pom.xml b/tests/fixtures/java/poms/single-module-pom.xml new file mode 100644 index 000000000..d6226df58 --- /dev/null +++ b/tests/fixtures/java/poms/single-module-pom.xml @@ -0,0 +1,7 @@ + + + 4.0.0 + com.example.app + single + 1.0.0 + diff --git a/tests/fixtures/java/stack-traces/multi-caused-by.txt b/tests/fixtures/java/stack-traces/multi-caused-by.txt new file mode 100644 index 000000000..b4bab03d1 --- /dev/null +++ b/tests/fixtures/java/stack-traces/multi-caused-by.txt @@ -0,0 +1,10 @@ +java.lang.IllegalStateException: Failed to load ApplicationContext + at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:180) + at org.springframework.test.context.support.DefaultTestContext.getApplicationContext(DefaultTestContext.java:124) +Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'dataSource' + at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:628) + at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:542) +Caused by: org.hibernate.HibernateException: Unable to acquire JDBC Connection + at org.hibernate.internal.SessionFactoryImpl.createEntityManagerFactory(SessionFactoryImpl.java:512) + at com.example.DbIntegrationIT.shouldConnect(DbIntegrationIT.java:88) + at java.base/java.lang.reflect.Method.invoke(Method.java:580) diff --git a/tests/fixtures/java/surefire-reports/TEST-com.example.ErrorTest.xml b/tests/fixtures/java/surefire-reports/TEST-com.example.ErrorTest.xml new file mode 100644 index 000000000..5fe9307fd --- /dev/null +++ b/tests/fixtures/java/surefire-reports/TEST-com.example.ErrorTest.xml @@ -0,0 +1,9 @@ + + + + + java.net.ConnectException: Connection refused + at java.base/sun.nio.ch.Net.connect(Net.java:579) + at com.example.ErrorTest.shouldNotThrow(ErrorTest.java:30) + + diff --git a/tests/fixtures/java/surefire-reports/TEST-com.example.FailingTest.xml b/tests/fixtures/java/surefire-reports/TEST-com.example.FailingTest.xml new file mode 100644 index 000000000..fb28cae26 --- /dev/null +++ b/tests/fixtures/java/surefire-reports/TEST-com.example.FailingTest.xml @@ -0,0 +1,16 @@ + + + + + org.opentest4j.AssertionFailedError: expected:<200> but was:<404> + at org.junit.jupiter.api.AssertEquals.assertEquals(AssertEquals.java:150) + at com.example.FailingTest.shouldReturnUser(FailingTest.java:42) + at java.base/java.lang.reflect.Method.invoke(Method.java:580) + + + java.lang.AssertionError: Unexpected exception: NullPointerException + at com.example.FailingTest.shouldHandleNull(FailingTest.java:55) + at java.base/java.lang.reflect.Method.invoke(Method.java:580) + + + diff --git a/tests/fixtures/java/surefire-reports/TEST-com.example.FailingTestWithLogs.xml b/tests/fixtures/java/surefire-reports/TEST-com.example.FailingTestWithLogs.xml new file mode 100644 index 000000000..c0a843c1a --- /dev/null +++ b/tests/fixtures/java/surefire-reports/TEST-com.example.FailingTestWithLogs.xml @@ -0,0 +1,24 @@ + + + + + org.opentest4j.AssertionFailedError: expected connection + at org.junit.jupiter.api.AssertEquals.assertEquals(AssertEquals.java:150) + at com.example.FailingTestWithLogs.shouldConnectToDb(FailingTestWithLogs.java:25) + DEBUG: Initializing connection pool +INFO: Attempting connection to localhost:5432 +WARN: Connection timeout after 5000ms +ERROR: Failed to establish connection + SLF4J: Failed to load class org.slf4j.impl.StaticLoggerBinder +STDERR: Connection refused + + + org.opentest4j.AssertionFailedError: data mismatch + at com.example.FailingTestWithLogs.shouldProcessData(FailingTestWithLogs.java:42) + INFO: Processing batch of 100 records +DEBUG: Record 50 processed successfully + + + This output belongs to a passing test and should NOT be extracted + + diff --git a/tests/fixtures/java/surefire-reports/TEST-com.example.PassingTest.xml b/tests/fixtures/java/surefire-reports/TEST-com.example.PassingTest.xml new file mode 100644 index 000000000..6b2087d37 --- /dev/null +++ b/tests/fixtures/java/surefire-reports/TEST-com.example.PassingTest.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/tests/fixtures/java/surefire-reports/TEST-com.example.SkippedTest.xml b/tests/fixtures/java/surefire-reports/TEST-com.example.SkippedTest.xml new file mode 100644 index 000000000..fc60faddc --- /dev/null +++ b/tests/fixtures/java/surefire-reports/TEST-com.example.SkippedTest.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/tests/fixtures/mvn_checkstyle_clean.txt b/tests/fixtures/mvn_checkstyle_clean.txt new file mode 100644 index 000000000..a381da719 --- /dev/null +++ b/tests/fixtures/mvn_checkstyle_clean.txt @@ -0,0 +1,33 @@ +[INFO] Loaded 22539 auto-discovered prefixes for remote repository central (prefixes-central.txt) +[INFO] Loaded 74 auto-discovered prefixes for remote repository apache.snapshots (prefixes-apache.snapshots-c1d4c55f9308e5ac18a4069bed41dca64d85c515.txt) +[INFO] Scanning for projects... +[INFO] Loaded 22456 auto-discovered prefixes for remote repository google-maven-central-copy (prefixes-google-maven-central-copy-c4cd290f3f22839f5b0a2afe35defbe31dfa3a63.txt) +[INFO] Loaded 22539 auto-discovered prefixes for remote repository central (prefixes-central.txt) +[INFO] Loaded 22539 auto-discovered prefixes for remote repository maven-central (prefixes-maven-central-de66ab544dabb92499170649134bc3bd0ea8afac.txt) +[INFO] Loaded 22456 auto-discovered prefixes for remote repository google-maven-central-copy (prefixes-google-maven-central-copy-618f292136d6a9d006041d40d67cd906e4edc388.txt) +[WARNING] +[WARNING] 1 problem was encountered while building the effective model for 'com.devskiller:auth:jar:1.3-SNAPSHOT' (use -e to see details) +[WARNING] +[WARNING] Total model problems reported: 1 +[WARNING] +[WARNING] It is highly recommended to fix these problems because they threaten the stability of your build. +[WARNING] +[WARNING] For this reason, future Maven versions might no longer support building such malformed projects. +[WARNING] +[INFO] Loaded 22539 auto-discovered prefixes for remote repository central (prefixes-central.txt) +[INFO] Loaded 74 auto-discovered prefixes for remote repository apache.snapshots (prefixes-apache.snapshots-c1d4c55f9308e5ac18a4069bed41dca64d85c515.txt) +[INFO] +[INFO] -------------------------------------------------< com.devskiller:auth >-------------------------------------------------- +[INFO] Building auth 1.3-SNAPSHOT +[INFO] from pom.xml +[INFO] ---------------------------------------------------------[ jar ]---------------------------------------------------------- +[INFO] +[INFO] --- checkstyle:3.6.0:check (default-cli) @ auth --- +[INFO] Loaded 22459 auto-discovered prefixes for remote repository jvnet-nexus-releases (prefixes-jvnet-nexus-releases-a5d540dd75d8efce5a7e6a5ccab82bab11416135.txt) +[INFO] You have 0 Checkstyle violations. +[INFO] -------------------------------------------------------------------------------------------------------------------------- +[INFO] BUILD SUCCESS +[INFO] -------------------------------------------------------------------------------------------------------------------------- +[INFO] Total time: 0.905 s +[INFO] Finished at: 2026-04-14T17:16:37+02:00 +[INFO] -------------------------------------------------------------------------------------------------------------------------- diff --git a/tests/fixtures/mvn_checkstyle_clean_native.txt b/tests/fixtures/mvn_checkstyle_clean_native.txt new file mode 100644 index 000000000..6bb982ccf --- /dev/null +++ b/tests/fixtures/mvn_checkstyle_clean_native.txt @@ -0,0 +1,37 @@ +WARNING: A restricted method in java.lang.System has been called +WARNING: java.lang.System::load has been called by org.fusesource.jansi.internal.JansiLoader in an unnamed module (file:/home/mariusz/.m2/wrapper/dists/apache-maven-3.9.9/3477a4f1/lib/jansi-2.4.1.jar) +WARNING: Use --enable-native-access=ALL-UNNAMED to avoid a warning for callers in this module +WARNING: Restricted methods will be blocked in a future release unless native access is enabled + +WARNING: A terminally deprecated method in sun.misc.Unsafe has been called +WARNING: sun.misc.Unsafe::objectFieldOffset has been called by com.google.common.util.concurrent.AbstractFuture$UnsafeAtomicHelper (file:/home/mariusz/.m2/wrapper/dists/apache-maven-3.9.9/3477a4f1/lib/guava-33.2.1-jre.jar) +WARNING: Please consider reporting this to the maintainers of class com.google.common.util.concurrent.AbstractFuture$UnsafeAtomicHelper +WARNING: sun.misc.Unsafe::objectFieldOffset will be removed in a future release +[INFO] Scanning for projects... +[INFO] ------------------------------------------------------------------------ +[INFO] Detecting the operating system and CPU architecture +[INFO] ------------------------------------------------------------------------ +[INFO] os.detected.name: linux +[INFO] os.detected.arch: x86_64 +[INFO] os.detected.bitness: 64 +[INFO] os.detected.version: 6.19 +[INFO] os.detected.version.major: 6 +[INFO] os.detected.version.minor: 19 +[INFO] os.detected.release: fedora +[INFO] os.detected.release.version: 43 +[INFO] os.detected.release.like.fedora: true +[INFO] os.detected.classifier: linux-x86_64 +[INFO] +[INFO] -------------------------< com.devskiller:map >------------------------- +[INFO] Building map 1.0-SNAPSHOT +[INFO] from pom.xml +[INFO] --------------------------------[ jar ]--------------------------------- +[INFO] +[INFO] --- checkstyle:3.6.0:check (default-cli) @ map --- +[INFO] You have 0 Checkstyle violations. +[INFO] ------------------------------------------------------------------------ +[INFO] BUILD SUCCESS +[INFO] ------------------------------------------------------------------------ +[INFO] Total time: 5.567 s +[INFO] Finished at: 2026-04-14T17:16:35+02:00 +[INFO] ------------------------------------------------------------------------ diff --git a/tests/fixtures/mvn_checkstyle_violations.txt b/tests/fixtures/mvn_checkstyle_violations.txt new file mode 100644 index 000000000..dbb997597 --- /dev/null +++ b/tests/fixtures/mvn_checkstyle_violations.txt @@ -0,0 +1,44 @@ +[INFO] Loaded 22539 auto-discovered prefixes for remote repository central (prefixes-central.txt) +[INFO] Loaded 74 auto-discovered prefixes for remote repository apache.snapshots (prefixes-apache.snapshots-c1d4c55f9308e5ac18a4069bed41dca64d85c515.txt) +[INFO] Scanning for projects... +[INFO] Loaded 22456 auto-discovered prefixes for remote repository google-maven-central-copy (prefixes-google-maven-central-copy-c4cd290f3f22839f5b0a2afe35defbe31dfa3a63.txt) +[INFO] Loaded 22539 auto-discovered prefixes for remote repository central (prefixes-central.txt) +[INFO] Loaded 22539 auto-discovered prefixes for remote repository maven-central (prefixes-maven-central-de66ab544dabb92499170649134bc3bd0ea8afac.txt) +[INFO] Loaded 22456 auto-discovered prefixes for remote repository google-maven-central-copy (prefixes-google-maven-central-copy-618f292136d6a9d006041d40d67cd906e4edc388.txt) +[WARNING] +[WARNING] 1 problem was encountered while building the effective model for 'com.devskiller:auth:jar:1.3-SNAPSHOT' (use -e to see details) +[WARNING] +[WARNING] Total model problems reported: 1 +[WARNING] +[WARNING] It is highly recommended to fix these problems because they threaten the stability of your build. +[WARNING] +[WARNING] For this reason, future Maven versions might no longer support building such malformed projects. +[WARNING] +[INFO] Loaded 22539 auto-discovered prefixes for remote repository central (prefixes-central.txt) +[INFO] Loaded 74 auto-discovered prefixes for remote repository apache.snapshots (prefixes-apache.snapshots-c1d4c55f9308e5ac18a4069bed41dca64d85c515.txt) +[INFO] +[INFO] ------------------------< com.devskiller:auth >------------------------- +[INFO] Building auth 1.3-SNAPSHOT +[INFO] from pom.xml +[INFO] --------------------------------[ jar ]--------------------------------- +[INFO] +[INFO] --- checkstyle:3.6.0:check (default-cli) @ auth --- +[INFO] Loaded 22459 auto-discovered prefixes for remote repository jvnet-nexus-releases (prefixes-jvnet-nexus-releases-a5d540dd75d8efce5a7e6a5ccab82bab11416135.txt) +[INFO] There are 4 errors reported by Checkstyle 13.3.0 with checkstyle.xml ruleset. +[ERROR] src/main/java/com/devskiller/auth/app/ExternalAppId.java:[3,8] (imports) UnusedImports: Unused import - java.util.List. +[ERROR] src/main/java/com/devskiller/auth/app/ExternalAppId.java:[9,19] (naming) MethodName: Name 'BadMethodName' must match pattern '^[a-z][a-zA-Z0-9]*$'. +[ERROR] src/main/java/com/devskiller/auth/app/ExternalAppId.java:[10] (sizes) LineLength: Line is longer than 200 characters (found 241). +[ERROR] src/main/java/com/devskiller/auth/app/ExternalAppId.java:[10,16] (naming) LocalVariableName: Name 'BadVariable' must match pattern '^([a-z][a-zA-Z0-9]*|_)$'. +[INFO] ------------------------------------------------------------------------ +[INFO] BUILD FAILURE +[INFO] ------------------------------------------------------------------------ +[INFO] Total time: 0.907 s +[INFO] Finished at: 2026-04-14T17:19:19+02:00 +[INFO] ------------------------------------------------------------------------ +[ERROR] Failed to execute goal org.apache.maven.plugins:maven-checkstyle-plugin:3.6.0:check (default-cli) on project auth: You have 4 Checkstyle violations. -> [Help 1] +[ERROR] +[ERROR] To see the full stack trace of the errors, re-run Maven with the '-e' switch +[ERROR] Re-run Maven using the '-X' switch to enable verbose output +[ERROR] +[ERROR] For more information about the errors and possible solutions, please read the following articles: +[ERROR] [Help 1] http://cwiki.apache.org/confluence/display/MAVEN/MojoFailureException diff --git a/tests/fixtures/mvn_compile_auth.txt b/tests/fixtures/mvn_compile_auth.txt new file mode 100644 index 000000000..d14fcef56 --- /dev/null +++ b/tests/fixtures/mvn_compile_auth.txt @@ -0,0 +1,132 @@ +[INFO] argLine set to -javaagent:/home/user/.m2/repository/org/jacoco/org.jacoco.agent/0.8.14/org.jacoco.agent-0.8.14-runtime.jar=destfile=/home/user/project/target/jacoco.exec +Server Version: 28.1.1 +API Version: 1.49 +Operating System: Fedora Linux 43 (Workstation Edition) +Total Memory: 61869 MB +[INFO] [stdout] +[INFO] [stdout] UPDATE SUMMARY +[INFO] [stdout] Run: 352 +[INFO] [stdout] Previously run: 0 +[INFO] [stdout] Filtered out: 0 +[INFO] [stdout] Total change sets: 352 +[INFO] [stdout] +[INFO] [stdout] Liquibase: Update has been successful. +[INFO] Migration completed +[INFO] Database : Inferring driver org.postgresql.Driver from URL jdbc:postgresql://localhost:32811/test?loggerLevel=OFF +[INFO] Database : Inferring database org.jooq.meta.postgres.PostgresDatabase from URL jdbc:postgresql://localhost:32811/test?loggerLevel=OFF +[INFO] No was provided. Generating ALL available catalogs instead. +[INFO] License parameters +[INFO] Thank you for using jOOQ and jOOQ's code generator +[INFO] Database parameters +[INFO] dialect : POSTGRES_15 +[INFO] URL : jdbc:postgresql://localhost:32811/test?loggerLevel=OFF +[INFO] target dir : /home/user/project/target/generated-sources/jooq +[INFO] target package : com.example.webapp.jooq +[INFO] includes : [.*] +[INFO] JavaGenerator parameters +[INFO] annotations +[INFO] generated : false +[INFO] JPA : false +[INFO] JPA version : +[INFO] comments +[INFO] comments : true +[INFO] sources +[INFO] sources : true +[INFO] global references +[INFO] global references : true +[INFO] object types +[INFO] interfaces (immutable) : false +[INFO] table-valued functions : true +[INFO] other +[INFO] Generation remarks +[INFO] Generating catalogs : Total: 1 +[INFO] Version : Database version is supported by dialect POSTGRES_15: 17.5 (Debian 17.5-1.pgdg120+1) +[INFO] ARRAYs fetched : 0 (0 included, 0 excluded) +[INFO] Tables fetched : 42 (40 included, 2 excluded) +[INFO] Routines fetched : 35 (35 included, 0 excluded) +[INFO] No schema version is applied for catalog . Regenerating. +[INFO] Generating catalog : DefaultCatalog.java +[INFO] ========================================================== +[INFO] Generating schemata : Total: 1 +[INFO] Generating schema : Public.java +[INFO] Generating tables +[INFO] Indexes fetched : 61 (61 included, 0 excluded) +[INFO] Generating table : Company.java [input=company, pk=company_pkey] +[INFO] Generating table : User.java [input=user, pk=user_pkey] +[INFO] Generating table : Role.java [input=role, pk=role_pkey] +- https://www.jooq.org/doc/latest/manual/code-generation/codegen-generatorstrategy/ +- https://www.jooq.org/doc/latest/manual/code-generation/codegen-matcherstrategy/ +[INFO] Tables generated : Total: 421.766ms +[INFO] Generating table records +[INFO] Generating record : CompanyRecord.java +[INFO] Generating record : UserRecord.java +[INFO] Table records generated : Total: 449.399ms, +27.632ms +[INFO] Generating routines and table-valued functions +[INFO] Generating routine : Encrypt.java +[INFO] Missing name : Routine public.encrypt holds a parameter without a name at position 1 +[INFO] Missing name : Routine public.encrypt holds a parameter without a name at position 2 +[INFO] Routines generated : Total: 509.982ms, +52.111ms +[INFO] Generation finished: public: Total: 510.052ms, +0.07ms +[INFO] Affected files: 121 +[INFO] Modified files: 0 +[INFO] No modified files : This code generation run has not produced any file modifications. +This means, the schema has not changed, and no other parameters (jOOQ version, driver version, database version, +and any configuration elements) have changed either. +In automated builds, it is recommended to prevent unnecessary code generation runs. This run took: 500.067ms +Possible means to prevent this: +- Use manual code generation and check in generated sources: https://www.jooq.org/doc/latest/manual/code-generation/codegen-version-control/ +- Use schema version providers: https://www.jooq.org/doc/latest/manual/code-generation/codegen-advanced/codegen-config-database/codegen-database-version-providers/ +- Use gradle tasks and inputs: https://docs.gradle.org/current/userguide/incremental_build.html +[INFO] Removing excess files +[INFO] Source directory: /home/user/project/target/generated-sources/jooq added. +[INFO] Node v20.18.1 is already installed. +[INFO] > my-webapp-frontend@0.1.0 postinstall +[INFO] > ./scripts/git-settings.sh +[INFO] added 1667 packages, and audited 1668 packages in 3s +[INFO] 354 packages are looking for funding +[INFO] run `npm fund` for details +[INFO] 30 vulnerabilities (9 low, 5 moderate, 15 high, 1 critical) +[INFO] To address issues that do not require attention, run: +[INFO] npm audit fix +[INFO] To address all issues (including breaking changes), run: +[INFO] npm audit fix --force +[INFO] Run `npm audit` for details. +[INFO] > my-webapp-frontend@0.1.0 prebuild +[INFO] > node ./scripts/prebuild.js +[INFO] > my-webapp-frontend@0.1.0 build +[INFO] > npm run build:stage && npm run build:prod +[INFO] > my-webapp-frontend@0.1.0 build:stage +[INFO] > env-cmd -f .env.stage react-app-rewired build && rm -rf dist-stage && mv build dist-stage +[INFO] (node:542456) ExperimentalWarning: Importing JSON modules is an experimental feature and might change at any time +[INFO] (Use `node --trace-warnings ...` to show where the warning was created) +[INFO] Creating an optimized production build... +[INFO] Compiled successfully. +[INFO] File sizes after gzip: +[INFO] 257.55 kB build/static/js/main.js +[INFO] 40.41 kB build/static/js/962.f23c413d.chunk.js +[INFO] 8.43 kB build/static/css/main.d7534f51.css +[INFO] 918 B build/static/js/636.3320b849.chunk.js +[INFO] The project was built assuming it is hosted at /. +[INFO] You can control this with the homepage field in your package.json. +[INFO] The build folder is ready to be deployed. +[INFO] You may serve it with a static server: +[INFO] npm install -g serve +[INFO] serve -s build +[INFO] Find out more about deployment here: +[INFO] https://cra.link/deployment +[INFO] Copying 2 resources from src/main/resources to target/classes +[INFO] Copying 145 resources from frontend/dist-prod to target/classes/static/dist-prod +[INFO] [stdout] Loading class org.jspecify.annotations.Nullable +[INFO] [stdout] Loading class com.example.webapp.shared.TeamId +[INFO] [stdout] Loading class java.time.Instant +[INFO] [stdout] Suggestion: annotation 'org.jspecify.annotations.Nullable' supports 'TYPE_PARAMETER' or 'TYPE_USE' target. +[INFO] [stdout] Parsing 'com.example.webapp.GlobalControllerExceptionHandler$VndErrors' +[INFO] [stdout] Parsing Spring component: com.example.webapp.user.api.UserApiController +[INFO] [stdout] Parsing Spring component: com.example.webapp.user.api.CompanyApiController +[INFO] [stdout] Parsing Spring component: com.example.webapp.sync.api.UserImportApiController +[INFO] [stdout] Parsing 'com.example.webapp.user.api.UserDetailsDto' used in 'UserApiController.getUserDetails' +[INFO] [stdout] Parsing 'com.example.webapp.user.api.AuthContextDto' used in 'SelfController.self' +[INFO] [stdout] Parsing 'com.example.webapp.user.CompanyResource' used in 'CompanyApiController.getCompany' +[INFO] [stdout] Writing declarations to: /home/user/project/frontend/src/types/app.d.ts +[INFO] BUILD SUCCESS +[INFO] Total time: 36.332 s diff --git a/tests/fixtures/mvn_dep_tree_beacon.txt b/tests/fixtures/mvn_dep_tree_beacon.txt new file mode 100644 index 000000000..6d3f6d58b --- /dev/null +++ b/tests/fixtures/mvn_dep_tree_beacon.txt @@ -0,0 +1,652 @@ +[INFO] Scanning for projects... +[INFO] +[INFO] -----------------------< com.skillpanel:beacon >------------------------ +[INFO] Building beacon 1.0-SNAPSHOT +[INFO] from pom.xml +[INFO] --------------------------------[ jar ]--------------------------------- +[INFO] +[INFO] --- dependency:3.9.0:tree (default-cli) @ beacon --- +[INFO] com.skillpanel:beacon:jar:1.0-SNAPSHOT +[INFO] +- org.springframework.boot:spring-boot-starter-web:jar:4.0.3:compile +[INFO] | +- org.springframework.boot:spring-boot-starter-jackson:jar:4.0.3:compile (version managed from 4.0.3; scope not updated to compile) +[INFO] | | +- (org.springframework.boot:spring-boot-starter:jar:4.0.3:compile - version managed from 4.0.3; omitted for duplicate) +[INFO] | | \- org.springframework.boot:spring-boot-jackson:jar:4.0.3:compile (version managed from 4.0.3) +[INFO] | | +- (org.springframework.boot:spring-boot:jar:4.0.3:compile - version managed from 4.0.3; omitted for duplicate) +[INFO] | | \- (tools.jackson.core:jackson-databind:jar:3.0.4:compile - version managed from 3.0.4; omitted for duplicate) +[INFO] | +- org.springframework.boot:spring-boot-starter-tomcat:jar:4.0.3:compile (version managed from 4.0.3; scope not updated to compile) +[INFO] | | +- (org.springframework.boot:spring-boot-starter:jar:4.0.3:compile - version managed from 4.0.3; omitted for duplicate) +[INFO] | | +- org.springframework.boot:spring-boot-starter-tomcat-runtime:jar:4.0.3:compile (version managed from 4.0.3) +[INFO] | | | +- (org.springframework.boot:spring-boot-tomcat:jar:4.0.3:compile - version managed from 4.0.3; omitted for duplicate) +[INFO] | | | +- (org.springframework.boot:spring-boot-web-server:jar:4.0.3:compile - version managed from 4.0.3; omitted for duplicate) +[INFO] | | | +- (jakarta.annotation:jakarta.annotation-api:jar:3.0.0:compile - version managed from 3.0.0; omitted for duplicate) +[INFO] | | | +- org.apache.tomcat.embed:tomcat-embed-core:jar:11.0.18:compile (version managed from 11.0.18) +[INFO] | | | +- (org.apache.tomcat.embed:tomcat-embed-el:jar:11.0.18:compile - version managed from 11.0.18; omitted for duplicate) +[INFO] | | | \- org.apache.tomcat.embed:tomcat-embed-websocket:jar:11.0.18:compile (version managed from 11.0.18) +[INFO] | | | \- (org.apache.tomcat.embed:tomcat-embed-core:jar:11.0.18:compile - version managed from 11.0.18; omitted for duplicate) +[INFO] | | \- org.springframework.boot:spring-boot-tomcat:jar:4.0.3:compile (version managed from 4.0.3) +[INFO] | | +- (org.springframework.boot:spring-boot-web-server:jar:4.0.3:compile - version managed from 4.0.3; omitted for duplicate) +[INFO] | | +- (org.apache.tomcat.embed:tomcat-embed-core:jar:11.0.18:compile - version managed from 11.0.18; omitted for duplicate) +[INFO] | | \- (jakarta.annotation:jakarta.annotation-api:jar:3.0.0:runtime - version managed from 3.0.0; omitted for duplicate) +[INFO] | +- org.springframework.boot:spring-boot-http-converter:jar:4.0.3:compile (version managed from 4.0.3; scope not updated to compile) +[INFO] | | +- org.springframework.boot:spring-boot:jar:4.0.3:compile (version managed from 4.0.3; scope not updated to compile) +[INFO] | | | +- (org.springframework:spring-core:jar:7.0.5:compile - version managed from 7.0.5; omitted for duplicate) +[INFO] | | | \- (org.springframework:spring-context:jar:7.0.5:compile - version managed from 7.0.5; omitted for duplicate) +[INFO] | | \- org.springframework:spring-web:jar:7.0.5:compile (version managed from 7.0.5; scope not updated to compile) +[INFO] | | +- (org.springframework:spring-beans:jar:7.0.5:compile - version managed from 7.0.5; omitted for duplicate) +[INFO] | | +- (org.springframework:spring-core:jar:7.0.5:compile - version managed from 7.0.5; omitted for duplicate) +[INFO] | | \- (io.micrometer:micrometer-observation:jar:1.16.3:compile - version managed from 1.16.3; omitted for duplicate) +[INFO] | \- org.springframework.boot:spring-boot-webmvc:jar:4.0.3:compile (version managed from 4.0.3; scope not updated to compile) +[INFO] | +- org.springframework.boot:spring-boot-servlet:jar:4.0.3:compile (version managed from 4.0.3) +[INFO] | | +- (org.springframework.boot:spring-boot:jar:4.0.3:compile - version managed from 4.0.3; omitted for duplicate) +[INFO] | | \- (org.springframework:spring-web:jar:7.0.5:compile - version managed from 7.0.5; omitted for duplicate) +[INFO] | +- (org.springframework:spring-web:jar:7.0.5:compile - version managed from 7.0.5; omitted for duplicate) +[INFO] | +- org.springframework:spring-webmvc:jar:7.0.5:compile (version managed from 7.0.5) +[INFO] | | +- (org.springframework:spring-aop:jar:7.0.5:compile - version managed from 7.0.5; omitted for duplicate) +[INFO] | | +- (org.springframework:spring-beans:jar:7.0.5:compile - version managed from 7.0.5; omitted for duplicate) +[INFO] | | +- (org.springframework:spring-context:jar:7.0.5:compile - version managed from 7.0.5; omitted for duplicate) +[INFO] | | +- (org.springframework:spring-core:jar:7.0.5:compile - version managed from 7.0.5; omitted for duplicate) +[INFO] | | +- (org.springframework:spring-expression:jar:7.0.5:compile - version managed from 7.0.5; omitted for duplicate) +[INFO] | | \- (org.springframework:spring-web:jar:7.0.5:compile - version managed from 7.0.5; omitted for duplicate) +[INFO] | \- (org.springframework.boot:spring-boot-http-converter:jar:4.0.3:runtime - version managed from 4.0.3; omitted for duplicate) +[INFO] +- org.springframework.boot:spring-boot-starter-webflux:jar:4.0.3:compile +[INFO] | +- org.springframework.boot:spring-boot-starter:jar:4.0.3:compile (version managed from 4.0.3; scope not updated to compile) +[INFO] | | +- org.springframework.boot:spring-boot-starter-logging:jar:4.0.3:compile (version managed from 4.0.3) +[INFO] | | | +- ch.qos.logback:logback-classic:jar:1.5.32:compile (version managed from 1.5.32) +[INFO] | | | | +- ch.qos.logback:logback-core:jar:1.5.32:compile (version managed from 1.5.32) +[INFO] | | | | \- (org.slf4j:slf4j-api:jar:2.0.17:compile - version managed from 2.0.17; omitted for duplicate) +[INFO] | | | +- org.apache.logging.log4j:log4j-to-slf4j:jar:2.25.3:compile (version managed from 2.25.3) +[INFO] | | | | +- org.apache.logging.log4j:log4j-api:jar:2.25.3:compile (version managed from 2.25.3) +[INFO] | | | | \- (org.slf4j:slf4j-api:jar:2.0.17:compile - version managed from 2.0.17; omitted for duplicate) +[INFO] | | | \- org.slf4j:jul-to-slf4j:jar:2.0.17:compile (version managed from 2.0.17) +[INFO] | | | \- (org.slf4j:slf4j-api:jar:2.0.17:compile - version managed from 2.0.17; omitted for duplicate) +[INFO] | | +- (org.springframework.boot:spring-boot-autoconfigure:jar:4.0.3:compile - version managed from 4.0.3; omitted for duplicate) +[INFO] | | +- jakarta.annotation:jakarta.annotation-api:jar:3.0.0:compile (version managed from 3.0.0; scope not updated to compile) +[INFO] | | \- (org.yaml:snakeyaml:jar:2.5:compile - version managed from 2.5; omitted for duplicate) +[INFO] | +- (org.springframework.boot:spring-boot-starter-jackson:jar:4.0.3:compile - version managed from 4.0.3; omitted for duplicate) +[INFO] | +- org.springframework.boot:spring-boot-starter-reactor-netty:jar:4.0.3:compile (version managed from 4.0.3) +[INFO] | | +- (org.springframework.boot:spring-boot-starter:jar:4.0.3:compile - version managed from 4.0.3; omitted for duplicate) +[INFO] | | +- org.springframework.boot:spring-boot-reactor:jar:4.0.3:compile (version managed from 4.0.3) +[INFO] | | | +- (org.springframework.boot:spring-boot:jar:4.0.3:compile - version managed from 4.0.3; omitted for duplicate) +[INFO] | | | \- (io.projectreactor:reactor-core:jar:3.8.3:compile - version managed from 3.8.3; omitted for duplicate) +[INFO] | | \- org.springframework.boot:spring-boot-reactor-netty:jar:4.0.3:compile (version managed from 4.0.3) +[INFO] | | +- (org.springframework.boot:spring-boot-web-server:jar:4.0.3:compile - version managed from 4.0.3; omitted for duplicate) +[INFO] | | +- io.projectreactor.netty:reactor-netty-http:jar:1.3.3:compile (version managed from 1.3.3) +[INFO] | | | +- io.netty:netty-codec-http:jar:4.2.10.Final:compile (version managed from 4.2.10.Final) +[INFO] | | | | +- (io.netty:netty-common:jar:4.2.10.Final:compile - version managed from 4.2.10.Final; omitted for duplicate) +[INFO] | | | | +- (io.netty:netty-buffer:jar:4.2.10.Final:compile - version managed from 4.2.10.Final; omitted for duplicate) +[INFO] | | | | +- (io.netty:netty-transport:jar:4.2.10.Final:compile - version managed from 4.2.10.Final; omitted for duplicate) +[INFO] | | | | +- (io.netty:netty-codec-base:jar:4.2.10.Final:compile - version managed from 4.2.10.Final; omitted for duplicate) +[INFO] | | | | +- (io.netty:netty-codec-compression:jar:4.2.10.Final:compile - version managed from 4.2.10.Final; omitted for duplicate) +[INFO] | | | | \- (io.netty:netty-handler:jar:4.2.10.Final:compile - version managed from 4.2.10.Final; omitted for duplicate) +[INFO] | | | +- io.netty:netty-codec-http2:jar:4.2.10.Final:compile (version managed from 4.2.10.Final) +[INFO] | | | | +- (io.netty:netty-common:jar:4.2.10.Final:compile - version managed from 4.2.10.Final; omitted for duplicate) +[INFO] | | | | +- (io.netty:netty-buffer:jar:4.2.10.Final:compile - version managed from 4.2.10.Final; omitted for duplicate) +[INFO] | | | | +- (io.netty:netty-transport:jar:4.2.10.Final:compile - version managed from 4.2.10.Final; omitted for duplicate) +[INFO] | | | | +- (io.netty:netty-codec-base:jar:4.2.10.Final:compile - version managed from 4.2.10.Final; omitted for duplicate) +[INFO] | | | | +- (io.netty:netty-handler:jar:4.2.10.Final:compile - version managed from 4.2.10.Final; omitted for duplicate) +[INFO] | | | | \- (io.netty:netty-codec-http:jar:4.2.10.Final:compile - version managed from 4.2.10.Final; omitted for duplicate) +[INFO] | | | +- io.netty:netty-codec-http3:jar:4.2.10.Final:compile (version managed from 4.2.10.Final) +[INFO] | | | | +- (io.netty:netty-common:jar:4.2.10.Final:compile - version managed from 4.2.10.Final; omitted for duplicate) +[INFO] | | | | +- (io.netty:netty-buffer:jar:4.2.10.Final:compile - version managed from 4.2.10.Final; omitted for duplicate) +[INFO] | | | | +- (io.netty:netty-codec-base:jar:4.2.10.Final:compile - version managed from 4.2.10.Final; omitted for duplicate) +[INFO] | | | | +- (io.netty:netty-codec-http:jar:4.2.10.Final:compile - version managed from 4.2.10.Final; omitted for duplicate) +[INFO] | | | | +- (io.netty:netty-codec-compression:jar:4.2.10.Final:compile - version managed from 4.2.10.Final; omitted for duplicate) +[INFO] | | | | +- (io.netty:netty-handler:jar:4.2.10.Final:compile - version managed from 4.2.10.Final; omitted for duplicate) +[INFO] | | | | +- (io.netty:netty-transport-native-unix-common:jar:4.2.10.Final:compile - version managed from 4.2.10.Final; omitted for duplicate) +[INFO] | | | | +- (io.netty:netty-transport:jar:4.2.10.Final:compile - version managed from 4.2.10.Final; omitted for duplicate) +[INFO] | | | | +- (io.netty:netty-resolver:jar:4.2.10.Final:compile - version managed from 4.2.10.Final; omitted for duplicate) +[INFO] | | | | +- io.netty:netty-codec-classes-quic:jar:4.2.10.Final:compile (version managed from 4.2.10.Final; scope not updated to compile) +[INFO] | | | | +- io.netty:netty-codec-native-quic:jar:linux-x86_64:4.2.10.Final:runtime (version managed from 4.2.10.Final; scope managed from runtime) +[INFO] | | | | | \- (io.netty:netty-codec-classes-quic:jar:4.2.10.Final:runtime - version managed from 4.2.10.Final; omitted for duplicate) +[INFO] | | | | +- io.netty:netty-codec-native-quic:jar:linux-aarch_64:4.2.10.Final:runtime (version managed from 4.2.10.Final; scope managed from runtime) +[INFO] | | | | | \- (io.netty:netty-codec-classes-quic:jar:4.2.10.Final:runtime - version managed from 4.2.10.Final; omitted for duplicate) +[INFO] | | | | +- io.netty:netty-codec-native-quic:jar:osx-x86_64:4.2.10.Final:runtime (version managed from 4.2.10.Final; scope managed from runtime) +[INFO] | | | | | \- (io.netty:netty-codec-classes-quic:jar:4.2.10.Final:runtime - version managed from 4.2.10.Final; omitted for duplicate) +[INFO] | | | | +- io.netty:netty-codec-native-quic:jar:osx-aarch_64:4.2.10.Final:runtime (version managed from 4.2.10.Final; scope managed from runtime) +[INFO] | | | | | \- (io.netty:netty-codec-classes-quic:jar:4.2.10.Final:runtime - version managed from 4.2.10.Final; omitted for duplicate) +[INFO] | | | | \- io.netty:netty-codec-native-quic:jar:windows-x86_64:4.2.10.Final:runtime (version managed from 4.2.10.Final; scope managed from runtime) +[INFO] | | | | \- (io.netty:netty-codec-classes-quic:jar:4.2.10.Final:runtime - version managed from 4.2.10.Final; omitted for duplicate) +[INFO] | | | +- (io.netty:netty-resolver-dns:jar:4.2.10.Final:compile - version managed from 4.2.10.Final; omitted for duplicate) +[INFO] | | | +- io.netty:netty-resolver-dns-native-macos:jar:osx-x86_64:4.2.10.Final:runtime (version managed from 4.2.10.Final; scope managed from compile) +[INFO] | | | | \- io.netty:netty-resolver-dns-classes-macos:jar:4.2.10.Final:runtime (version managed from 4.2.10.Final) +[INFO] | | | | +- (io.netty:netty-common:jar:4.2.10.Final:runtime - version managed from 4.2.10.Final; omitted for duplicate) +[INFO] | | | | +- (io.netty:netty-resolver-dns:jar:4.2.10.Final:runtime - version managed from 4.2.10.Final; omitted for duplicate) +[INFO] | | | | \- (io.netty:netty-transport-native-unix-common:jar:4.2.10.Final:runtime - version managed from 4.2.10.Final; omitted for duplicate) +[INFO] | | | +- io.netty:netty-transport-native-epoll:jar:linux-x86_64:4.2.10.Final:runtime (version managed from 4.2.10.Final; scope managed from compile) +[INFO] | | | | +- (io.netty:netty-common:jar:4.2.10.Final:runtime - version managed from 4.2.10.Final; omitted for duplicate) +[INFO] | | | | +- (io.netty:netty-buffer:jar:4.2.10.Final:runtime - version managed from 4.2.10.Final; omitted for duplicate) +[INFO] | | | | +- (io.netty:netty-transport:jar:4.2.10.Final:runtime - version managed from 4.2.10.Final; omitted for duplicate) +[INFO] | | | | +- (io.netty:netty-transport-native-unix-common:jar:4.2.10.Final:runtime - version managed from 4.2.10.Final; omitted for duplicate) +[INFO] | | | | \- io.netty:netty-transport-classes-epoll:jar:4.2.10.Final:runtime (version managed from 4.2.10.Final) +[INFO] | | | | +- (io.netty:netty-common:jar:4.2.10.Final:runtime - version managed from 4.2.10.Final; omitted for duplicate) +[INFO] | | | | +- (io.netty:netty-buffer:jar:4.2.10.Final:runtime - version managed from 4.2.10.Final; omitted for duplicate) +[INFO] | | | | +- (io.netty:netty-transport:jar:4.2.10.Final:runtime - version managed from 4.2.10.Final; omitted for duplicate) +[INFO] | | | | \- (io.netty:netty-transport-native-unix-common:jar:4.2.10.Final:runtime - version managed from 4.2.10.Final; omitted for duplicate) +[INFO] | | | +- io.projectreactor.netty:reactor-netty-core:jar:1.3.3:compile (version managed from 1.3.3) +[INFO] | | | | +- (io.netty:netty-handler:jar:4.2.10.Final:compile - version managed from 4.2.10.Final; omitted for duplicate) +[INFO] | | | | +- io.netty:netty-handler-proxy:jar:4.2.10.Final:compile (version managed from 4.2.10.Final) +[INFO] | | | | | +- (io.netty:netty-common:jar:4.2.10.Final:compile - version managed from 4.2.10.Final; omitted for duplicate) +[INFO] | | | | | +- (io.netty:netty-buffer:jar:4.2.10.Final:compile - version managed from 4.2.10.Final; omitted for duplicate) +[INFO] | | | | | +- (io.netty:netty-transport:jar:4.2.10.Final:compile - version managed from 4.2.10.Final; omitted for duplicate) +[INFO] | | | | | +- (io.netty:netty-codec-base:jar:4.2.10.Final:compile - version managed from 4.2.10.Final; omitted for duplicate) +[INFO] | | | | | +- io.netty:netty-codec-socks:jar:4.2.10.Final:compile (version managed from 4.2.10.Final) +[INFO] | | | | | | +- (io.netty:netty-common:jar:4.2.10.Final:compile - version managed from 4.2.10.Final; omitted for duplicate) +[INFO] | | | | | | +- (io.netty:netty-buffer:jar:4.2.10.Final:compile - version managed from 4.2.10.Final; omitted for duplicate) +[INFO] | | | | | | +- (io.netty:netty-transport:jar:4.2.10.Final:compile - version managed from 4.2.10.Final; omitted for duplicate) +[INFO] | | | | | | \- (io.netty:netty-codec-base:jar:4.2.10.Final:compile - version managed from 4.2.10.Final; omitted for duplicate) +[INFO] | | | | | +- (io.netty:netty-codec-http:jar:4.2.10.Final:compile - version managed from 4.2.10.Final; omitted for duplicate) +[INFO] | | | | | \- (io.netty:netty-handler:jar:4.2.10.Final:compile - version managed from 4.2.10.Final; omitted for duplicate) +[INFO] | | | | +- (io.netty:netty-resolver-dns:jar:4.2.10.Final:compile - version managed from 4.2.10.Final; omitted for duplicate) +[INFO] | | | | +- (io.netty:netty-resolver-dns-native-macos:jar:osx-x86_64:4.2.10.Final:runtime - version managed from 4.2.10.Final; scope managed from compile; omitted for duplicate) +[INFO] | | | | +- (io.netty:netty-transport-native-epoll:jar:linux-x86_64:4.2.10.Final:runtime - version managed from 4.2.10.Final; scope managed from compile; omitted for duplicate) +[INFO] | | | | +- (io.projectreactor:reactor-core:jar:3.8.3:compile - version managed from 3.8.3; omitted for duplicate) +[INFO] | | | | \- (org.jspecify:jspecify:jar:1.0.0:compile - version managed from 1.0.0; omitted for duplicate) +[INFO] | | | +- (io.projectreactor:reactor-core:jar:3.8.3:compile - version managed from 3.8.3; omitted for duplicate) +[INFO] | | | \- (org.jspecify:jspecify:jar:1.0.0:compile - version managed from 1.0.0; omitted for duplicate) +[INFO] | | +- (org.springframework:spring-web:jar:7.0.5:compile - version managed from 7.0.5; omitted for duplicate) +[INFO] | | \- (org.springframework.boot:spring-boot-netty:jar:4.0.3:runtime - version managed from 4.0.3; omitted for duplicate) +[INFO] | \- org.springframework.boot:spring-boot-webflux:jar:4.0.3:compile (version managed from 4.0.3) +[INFO] | +- (org.springframework.boot:spring-boot:jar:4.0.3:compile - version managed from 4.0.3; omitted for duplicate) +[INFO] | +- org.springframework.boot:spring-boot-http-codec:jar:4.0.3:compile (version managed from 4.0.3) +[INFO] | | +- (org.springframework.boot:spring-boot:jar:4.0.3:compile - version managed from 4.0.3; omitted for duplicate) +[INFO] | | \- (org.springframework:spring-web:jar:7.0.5:compile - version managed from 7.0.5; omitted for duplicate) +[INFO] | +- org.springframework:spring-webflux:jar:7.0.5:compile (version managed from 7.0.5) +[INFO] | | +- (org.springframework:spring-beans:jar:7.0.5:compile - version managed from 7.0.5; omitted for duplicate) +[INFO] | | +- (org.springframework:spring-core:jar:7.0.5:compile - version managed from 7.0.5; omitted for duplicate) +[INFO] | | +- (org.springframework:spring-web:jar:7.0.5:compile - version managed from 7.0.5; omitted for duplicate) +[INFO] | | \- (io.projectreactor:reactor-core:jar:3.8.3:compile - version managed from 3.8.3; omitted for duplicate) +[INFO] | \- org.springframework.boot:spring-boot-web-server:jar:4.0.3:compile (version managed from 4.0.3; scope not updated to compile) +[INFO] | +- (org.springframework.boot:spring-boot:jar:4.0.3:compile - version managed from 4.0.3; omitted for duplicate) +[INFO] | \- (org.springframework:spring-web:jar:7.0.5:compile - version managed from 7.0.5; omitted for duplicate) +[INFO] +- org.springframework.boot:spring-boot-starter-data-jpa:jar:4.0.3:compile +[INFO] | +- (org.springframework.boot:spring-boot-starter:jar:4.0.3:compile - version managed from 4.0.3; omitted for duplicate) +[INFO] | +- org.springframework.boot:spring-boot-starter-jdbc:jar:4.0.3:compile (version managed from 4.0.3) +[INFO] | | +- (org.springframework.boot:spring-boot-starter:jar:4.0.3:compile - version managed from 4.0.3; omitted for duplicate) +[INFO] | | +- (org.springframework.boot:spring-boot-jdbc:jar:4.0.3:compile - version managed from 4.0.3; omitted for duplicate) +[INFO] | | \- com.zaxxer:HikariCP:jar:7.0.2:compile (version managed from 7.0.2) +[INFO] | | \- (org.slf4j:slf4j-api:jar:2.0.17:compile - version managed from 2.0.17; omitted for duplicate) +[INFO] | +- org.springframework.boot:spring-boot-data-jpa:jar:4.0.3:compile (version managed from 4.0.3) +[INFO] | | +- org.springframework.boot:spring-boot-data-commons:jar:4.0.3:compile (version managed from 4.0.3) +[INFO] | | | +- (org.springframework.boot:spring-boot:jar:4.0.3:compile - version managed from 4.0.3; omitted for duplicate) +[INFO] | | | +- org.springframework.boot:spring-boot-persistence:jar:4.0.3:compile (version managed from 4.0.3) +[INFO] | | | | +- (org.springframework.boot:spring-boot:jar:4.0.3:compile - version managed from 4.0.3; omitted for duplicate) +[INFO] | | | | \- (org.springframework:spring-tx:jar:7.0.5:compile - version managed from 7.0.5; omitted for duplicate) +[INFO] | | | \- org.springframework.data:spring-data-commons:jar:4.0.3:compile (version managed from 4.0.3) +[INFO] | | | +- (org.springframework:spring-core:jar:7.0.5:compile - version managed from 7.0.3; omitted for duplicate) +[INFO] | | | +- (org.springframework:spring-beans:jar:7.0.5:compile - version managed from 7.0.3; omitted for duplicate) +[INFO] | | | \- (org.slf4j:slf4j-api:jar:2.0.17:compile - version managed from 2.0.17; omitted for duplicate) +[INFO] | | +- org.springframework.boot:spring-boot-hibernate:jar:4.0.3:compile (version managed from 4.0.3) +[INFO] | | | +- org.springframework.boot:spring-boot-jpa:jar:4.0.3:compile (version managed from 4.0.3) +[INFO] | | | | +- (org.springframework.boot:spring-boot-jdbc:jar:4.0.3:compile - version managed from 4.0.3; omitted for duplicate) +[INFO] | | | | +- (org.springframework.boot:spring-boot-transaction:jar:4.0.3:compile - version managed from 4.0.3; omitted for duplicate) +[INFO] | | | | +- jakarta.persistence:jakarta.persistence-api:jar:3.2.0:compile (version managed from 3.2.0) +[INFO] | | | | \- (org.springframework:spring-orm:jar:7.0.5:compile - version managed from 7.0.5; omitted for duplicate) +[INFO] | | | +- org.hibernate.orm:hibernate-core:jar:7.2.4.Final:compile (version managed from 7.2.4.Final) +[INFO] | | | | +- (jakarta.persistence:jakarta.persistence-api:jar:3.2.0:compile - version managed from 3.2.0; omitted for duplicate) +[INFO] | | | | +- jakarta.transaction:jakarta.transaction-api:jar:2.0.1:compile (version managed from 2.0.1) +[INFO] | | | | +- (org.jboss.logging:jboss-logging:jar:3.6.2.Final:runtime - version managed from 3.6.1.Final; omitted for duplicate) +[INFO] | | | | +- org.hibernate.models:hibernate-models:jar:1.0.1:runtime +[INFO] | | | | | \- (org.jboss.logging:jboss-logging:jar:3.6.2.Final:runtime - version managed from 3.5.0.Final; omitted for duplicate) +[INFO] | | | | +- (com.fasterxml:classmate:jar:1.7.3:runtime - version managed from 1.7.1; omitted for duplicate) +[INFO] | | | | +- (net.bytebuddy:byte-buddy:jar:1.17.8:runtime - version managed from 1.17.8; omitted for duplicate) +[INFO] | | | | +- (jakarta.xml.bind:jakarta.xml.bind-api:jar:4.0.4:runtime - version managed from 4.0.4; omitted for duplicate) +[INFO] | | | | +- org.glassfish.jaxb:jaxb-runtime:jar:4.0.6:runtime (version managed from 4.0.6) +[INFO] | | | | | \- org.glassfish.jaxb:jaxb-core:jar:4.0.6:runtime (version managed from 4.0.6) +[INFO] | | | | | +- (jakarta.xml.bind:jakarta.xml.bind-api:jar:4.0.4:runtime - version managed from 4.0.4; omitted for duplicate) +[INFO] | | | | | +- (jakarta.activation:jakarta.activation-api:jar:2.1.4:runtime - version managed from 2.1.4; omitted for duplicate) +[INFO] | | | | | +- org.eclipse.angus:angus-activation:jar:2.0.3:runtime +[INFO] | | | | | | \- (jakarta.activation:jakarta.activation-api:jar:2.1.4:runtime - version managed from 2.1.4; omitted for duplicate) +[INFO] | | | | | +- org.glassfish.jaxb:txw2:jar:4.0.6:runtime (version managed from 4.0.6) +[INFO] | | | | | \- com.sun.istack:istack-commons-runtime:jar:4.1.2:runtime +[INFO] | | | | +- jakarta.inject:jakarta.inject-api:jar:2.0.1:runtime (version managed from 2.0.1) +[INFO] | | | | \- (org.antlr:antlr4-runtime:jar:4.13.2:runtime - omitted for duplicate) +[INFO] | | | \- org.springframework:spring-orm:jar:7.0.5:compile (version managed from 7.0.5) +[INFO] | | | +- (org.springframework:spring-beans:jar:7.0.5:compile - version managed from 7.0.5; omitted for duplicate) +[INFO] | | | +- (org.springframework:spring-core:jar:7.0.5:compile - version managed from 7.0.5; omitted for duplicate) +[INFO] | | | +- (org.springframework:spring-jdbc:jar:7.0.5:compile - version managed from 7.0.5; omitted for duplicate) +[INFO] | | | \- (org.springframework:spring-tx:jar:7.0.5:compile - version managed from 7.0.5; omitted for duplicate) +[INFO] | | +- org.springframework.data:spring-data-jpa:jar:4.0.3:compile (version managed from 4.0.3) +[INFO] | | | +- (org.springframework.data:spring-data-commons:jar:4.0.3:compile - version managed from 4.0.3; omitted for duplicate) +[INFO] | | | +- (org.springframework:spring-orm:jar:7.0.5:compile - version managed from 7.0.3; omitted for duplicate) +[INFO] | | | +- (org.springframework:spring-context:jar:7.0.5:compile - version managed from 7.0.3; omitted for duplicate) +[INFO] | | | +- (org.springframework:spring-aop:jar:7.0.5:compile - version managed from 7.0.3; omitted for duplicate) +[INFO] | | | +- (org.springframework:spring-tx:jar:7.0.5:compile - version managed from 7.0.3; omitted for duplicate) +[INFO] | | | +- (org.springframework:spring-beans:jar:7.0.5:compile - version managed from 7.0.3; omitted for duplicate) +[INFO] | | | +- (org.springframework:spring-core:jar:7.0.5:compile - version managed from 7.0.3; omitted for duplicate) +[INFO] | | | +- org.antlr:antlr4-runtime:jar:4.13.2:compile +[INFO] | | | +- (jakarta.annotation:jakarta.annotation-api:jar:3.0.0:compile - version managed from 2.0.0; omitted for duplicate) +[INFO] | | | \- (org.slf4j:slf4j-api:jar:2.0.17:compile - version managed from 2.0.17; omitted for duplicate) +[INFO] | | \- org.springframework:spring-aspects:jar:7.0.5:compile (version managed from 7.0.5) +[INFO] | | \- org.aspectj:aspectjweaver:jar:1.9.25.1:compile (version managed from 1.9.25) +[INFO] | \- org.springframework.boot:spring-boot-jdbc:jar:4.0.3:compile (version managed from 4.0.3) +[INFO] | +- (org.springframework.boot:spring-boot:jar:4.0.3:compile - version managed from 4.0.3; omitted for duplicate) +[INFO] | +- org.springframework.boot:spring-boot-sql:jar:4.0.3:compile (version managed from 4.0.3) +[INFO] | | \- (org.springframework.boot:spring-boot:jar:4.0.3:compile - version managed from 4.0.3; omitted for duplicate) +[INFO] | +- org.springframework.boot:spring-boot-transaction:jar:4.0.3:compile (version managed from 4.0.3; scope not updated to compile) +[INFO] | | +- (org.springframework.boot:spring-boot:jar:4.0.3:compile - version managed from 4.0.3; omitted for duplicate) +[INFO] | | +- (org.springframework.boot:spring-boot-persistence:jar:4.0.3:compile - version managed from 4.0.3; omitted for duplicate) +[INFO] | | \- (org.springframework:spring-tx:jar:7.0.5:compile - version managed from 7.0.5; omitted for duplicate) +[INFO] | \- org.springframework:spring-jdbc:jar:7.0.5:compile (version managed from 7.0.5) +[INFO] | +- (org.springframework:spring-beans:jar:7.0.5:compile - version managed from 7.0.5; omitted for duplicate) +[INFO] | +- (org.springframework:spring-core:jar:7.0.5:compile - version managed from 7.0.5; omitted for duplicate) +[INFO] | \- (org.springframework:spring-tx:jar:7.0.5:compile - version managed from 7.0.5; omitted for duplicate) +[INFO] +- org.springframework.boot:spring-boot-starter-data-redis:jar:4.0.3:compile +[INFO] | +- (org.springframework.boot:spring-boot-starter:jar:4.0.3:compile - version managed from 4.0.3; omitted for duplicate) +[INFO] | \- org.springframework.boot:spring-boot-data-redis:jar:4.0.3:compile (version managed from 4.0.3) +[INFO] | +- (org.springframework.boot:spring-boot:jar:4.0.3:compile - version managed from 4.0.3; omitted for duplicate) +[INFO] | +- (org.springframework.boot:spring-boot-data-commons:jar:4.0.3:compile - version managed from 4.0.3; omitted for duplicate) +[INFO] | +- (org.springframework.boot:spring-boot-transaction:jar:4.0.3:compile - version managed from 4.0.3; omitted for duplicate) +[INFO] | +- io.lettuce:lettuce-core:jar:6.8.2.RELEASE:compile (version managed from 6.8.2.RELEASE) +[INFO] | | +- redis.clients.authentication:redis-authx-core:jar:0.1.1-beta2:compile +[INFO] | | | \- (org.slf4j:slf4j-api:jar:2.0.17:compile - version managed from 1.7.36; omitted for duplicate) +[INFO] | | +- io.netty:netty-common:jar:4.2.10.Final:compile (version managed from 4.1.125.Final) +[INFO] | | +- io.netty:netty-handler:jar:4.2.10.Final:compile (version managed from 4.1.125.Final) +[INFO] | | | +- (io.netty:netty-common:jar:4.2.10.Final:compile - version managed from 4.2.10.Final; omitted for duplicate) +[INFO] | | | +- io.netty:netty-resolver:jar:4.2.10.Final:compile (version managed from 4.2.10.Final) +[INFO] | | | | \- (io.netty:netty-common:jar:4.2.10.Final:compile - version managed from 4.2.10.Final; omitted for duplicate) +[INFO] | | | +- io.netty:netty-buffer:jar:4.2.10.Final:compile (version managed from 4.2.10.Final) +[INFO] | | | | \- (io.netty:netty-common:jar:4.2.10.Final:compile - version managed from 4.2.10.Final; omitted for duplicate) +[INFO] | | | +- (io.netty:netty-transport:jar:4.2.10.Final:compile - version managed from 4.2.10.Final; omitted for duplicate) +[INFO] | | | +- io.netty:netty-transport-native-unix-common:jar:4.2.10.Final:compile (version managed from 4.2.10.Final; scope not updated to compile) +[INFO] | | | | +- (io.netty:netty-common:jar:4.2.10.Final:compile - version managed from 4.2.10.Final; omitted for duplicate) +[INFO] | | | | +- (io.netty:netty-buffer:jar:4.2.10.Final:compile - version managed from 4.2.10.Final; omitted for duplicate) +[INFO] | | | | \- (io.netty:netty-transport:jar:4.2.10.Final:compile - version managed from 4.2.10.Final; omitted for duplicate) +[INFO] | | | \- io.netty:netty-codec-base:jar:4.2.10.Final:compile (version managed from 4.2.10.Final) +[INFO] | | | +- (io.netty:netty-common:jar:4.2.10.Final:compile - version managed from 4.2.10.Final; omitted for duplicate) +[INFO] | | | +- (io.netty:netty-buffer:jar:4.2.10.Final:compile - version managed from 4.2.10.Final; omitted for duplicate) +[INFO] | | | \- (io.netty:netty-transport:jar:4.2.10.Final:compile - version managed from 4.2.10.Final; omitted for duplicate) +[INFO] | | +- io.netty:netty-transport:jar:4.2.10.Final:compile (version managed from 4.1.125.Final) +[INFO] | | | +- (io.netty:netty-common:jar:4.2.10.Final:compile - version managed from 4.2.10.Final; omitted for duplicate) +[INFO] | | | +- (io.netty:netty-buffer:jar:4.2.10.Final:compile - version managed from 4.2.10.Final; omitted for duplicate) +[INFO] | | | \- (io.netty:netty-resolver:jar:4.2.10.Final:compile - version managed from 4.2.10.Final; omitted for duplicate) +[INFO] | | +- (io.projectreactor:reactor-core:jar:3.8.3:compile - version managed from 3.6.6; omitted for duplicate) +[INFO] | | \- io.netty:netty-resolver-dns:jar:4.2.10.Final:compile (version managed from 4.1.125.Final; scope not updated to compile) +[INFO] | | +- (io.netty:netty-common:jar:4.2.10.Final:compile - version managed from 4.2.10.Final; omitted for duplicate) +[INFO] | | +- (io.netty:netty-buffer:jar:4.2.10.Final:compile - version managed from 4.2.10.Final; omitted for duplicate) +[INFO] | | +- (io.netty:netty-resolver:jar:4.2.10.Final:compile - version managed from 4.2.10.Final; omitted for duplicate) +[INFO] | | +- (io.netty:netty-transport:jar:4.2.10.Final:compile - version managed from 4.2.10.Final; omitted for duplicate) +[INFO] | | +- (io.netty:netty-codec-base:jar:4.2.10.Final:compile - version managed from 4.2.10.Final; omitted for duplicate) +[INFO] | | +- io.netty:netty-codec-dns:jar:4.2.10.Final:compile (version managed from 4.2.10.Final) +[INFO] | | | +- (io.netty:netty-common:jar:4.2.10.Final:compile - version managed from 4.2.10.Final; omitted for duplicate) +[INFO] | | | +- (io.netty:netty-buffer:jar:4.2.10.Final:compile - version managed from 4.2.10.Final; omitted for duplicate) +[INFO] | | | +- (io.netty:netty-transport:jar:4.2.10.Final:compile - version managed from 4.2.10.Final; omitted for duplicate) +[INFO] | | | \- (io.netty:netty-codec-base:jar:4.2.10.Final:compile - version managed from 4.2.10.Final; omitted for duplicate) +[INFO] | | \- (io.netty:netty-handler:jar:4.2.10.Final:compile - version managed from 4.2.10.Final; omitted for duplicate) +[INFO] | +- org.springframework.data:spring-data-redis:jar:4.0.3:compile (version managed from 4.0.3) +[INFO] | | +- org.springframework.data:spring-data-keyvalue:jar:4.0.3:compile (version managed from 4.0.3) +[INFO] | | | +- (org.springframework.data:spring-data-commons:jar:4.0.3:compile - version managed from 4.0.3; omitted for duplicate) +[INFO] | | | +- (org.springframework:spring-context:jar:7.0.5:compile - version managed from 7.0.3; omitted for duplicate) +[INFO] | | | +- (org.springframework:spring-tx:jar:7.0.5:compile - version managed from 7.0.3; omitted for duplicate) +[INFO] | | | \- (org.slf4j:slf4j-api:jar:2.0.17:compile - version managed from 2.0.17; omitted for duplicate) +[INFO] | | +- (org.springframework:spring-tx:jar:7.0.5:compile - version managed from 7.0.3; omitted for duplicate) +[INFO] | | +- org.springframework:spring-oxm:jar:7.0.5:compile (version managed from 7.0.3) +[INFO] | | | +- (org.springframework:spring-beans:jar:7.0.5:compile - version managed from 7.0.5; omitted for duplicate) +[INFO] | | | +- (org.springframework:spring-core:jar:7.0.5:compile - version managed from 7.0.5; omitted for duplicate) +[INFO] | | | \- (jakarta.xml.bind:jakarta.xml.bind-api:jar:4.0.4:runtime - version managed from 3.0.1; omitted for duplicate) +[INFO] | | +- (org.springframework:spring-aop:jar:7.0.5:compile - version managed from 7.0.3; omitted for duplicate) +[INFO] | | +- org.springframework:spring-context-support:jar:7.0.5:compile (version managed from 7.0.3) +[INFO] | | | +- (org.springframework:spring-beans:jar:7.0.5:compile - version managed from 7.0.5; omitted for duplicate) +[INFO] | | | +- (org.springframework:spring-context:jar:7.0.5:compile - version managed from 7.0.5; omitted for duplicate) +[INFO] | | | \- (org.springframework:spring-core:jar:7.0.5:compile - version managed from 7.0.5; omitted for duplicate) +[INFO] | | \- (org.slf4j:slf4j-api:jar:2.0.17:compile - version managed from 2.0.17; omitted for duplicate) +[INFO] | \- org.springframework.boot:spring-boot-netty:jar:4.0.3:runtime (version managed from 4.0.3) +[INFO] | +- (org.springframework.boot:spring-boot:jar:4.0.3:runtime - version managed from 4.0.3; omitted for duplicate) +[INFO] | \- (io.netty:netty-common:jar:4.2.10.Final:runtime - version managed from 4.2.10.Final; omitted for duplicate) +[INFO] +- org.springframework.boot:spring-boot-starter-validation:jar:4.0.3:compile +[INFO] | +- (org.springframework.boot:spring-boot-starter:jar:4.0.3:compile - version managed from 4.0.3; omitted for duplicate) +[INFO] | \- org.springframework.boot:spring-boot-validation:jar:4.0.3:compile (version managed from 4.0.3) +[INFO] | +- (org.springframework.boot:spring-boot:jar:4.0.3:compile - version managed from 4.0.3; omitted for duplicate) +[INFO] | +- org.apache.tomcat.embed:tomcat-embed-el:jar:11.0.18:compile (version managed from 11.0.18) +[INFO] | \- org.hibernate.validator:hibernate-validator:jar:9.0.1.Final:compile (version managed from 9.0.1.Final) +[INFO] | +- jakarta.validation:jakarta.validation-api:jar:3.1.1:compile (version managed from 3.1.1) +[INFO] | +- org.jboss.logging:jboss-logging:jar:3.6.2.Final:compile (version managed from 3.6.1.Final; scope not updated to compile) +[INFO] | \- com.fasterxml:classmate:jar:1.7.3:compile (version managed from 1.7.0; scope not updated to compile) +[INFO] +- org.springframework.boot:spring-boot-starter-actuator:jar:4.0.3:compile +[INFO] | +- (org.springframework.boot:spring-boot-starter:jar:4.0.3:compile - version managed from 4.0.3; omitted for duplicate) +[INFO] | +- org.springframework.boot:spring-boot-starter-micrometer-metrics:jar:4.0.3:compile (version managed from 4.0.3) +[INFO] | | +- (org.springframework.boot:spring-boot-starter:jar:4.0.3:compile - version managed from 4.0.3; omitted for duplicate) +[INFO] | | \- org.springframework.boot:spring-boot-micrometer-metrics:jar:4.0.3:compile (version managed from 4.0.3) +[INFO] | | +- (org.springframework.boot:spring-boot:jar:4.0.3:compile - version managed from 4.0.3; omitted for duplicate) +[INFO] | | +- org.springframework.boot:spring-boot-micrometer-observation:jar:4.0.3:compile (version managed from 4.0.3) +[INFO] | | | +- (org.springframework.boot:spring-boot:jar:4.0.3:compile - version managed from 4.0.3; omitted for duplicate) +[INFO] | | | \- (io.micrometer:micrometer-observation:jar:1.16.3:compile - version managed from 1.16.3; omitted for duplicate) +[INFO] | | \- (io.micrometer:micrometer-core:jar:1.16.3:compile - version managed from 1.16.3; omitted for duplicate) +[INFO] | +- org.springframework.boot:spring-boot-actuator-autoconfigure:jar:4.0.3:compile (version managed from 4.0.3) +[INFO] | | +- (org.springframework.boot:spring-boot-autoconfigure:jar:4.0.3:compile - version managed from 4.0.3; omitted for duplicate) +[INFO] | | \- org.springframework.boot:spring-boot-actuator:jar:4.0.3:compile (version managed from 4.0.3) +[INFO] | | \- (org.springframework.boot:spring-boot:jar:4.0.3:compile - version managed from 4.0.3; omitted for duplicate) +[INFO] | +- org.springframework.boot:spring-boot-health:jar:4.0.3:compile (version managed from 4.0.3) +[INFO] | | \- (org.springframework.boot:spring-boot:jar:4.0.3:compile - version managed from 4.0.3; omitted for duplicate) +[INFO] | +- io.micrometer:micrometer-observation:jar:1.16.3:compile (version managed from 1.16.3) +[INFO] | | +- (org.jspecify:jspecify:jar:1.0.0:compile - version managed from 1.0.0; omitted for duplicate) +[INFO] | | \- io.micrometer:micrometer-commons:jar:1.16.3:compile (version managed from 1.16.3) +[INFO] | | \- (org.jspecify:jspecify:jar:1.0.0:compile - version managed from 1.0.0; omitted for duplicate) +[INFO] | \- io.micrometer:micrometer-jakarta9:jar:1.16.3:compile (version managed from 1.16.3) +[INFO] | +- (org.jspecify:jspecify:jar:1.0.0:compile - version managed from 1.0.0; omitted for duplicate) +[INFO] | +- io.micrometer:micrometer-core:jar:1.16.3:compile (version managed from 1.16.3) +[INFO] | | +- (org.jspecify:jspecify:jar:1.0.0:compile - version managed from 1.0.0; omitted for duplicate) +[INFO] | | +- (io.micrometer:micrometer-commons:jar:1.16.3:compile - version managed from 1.16.3; omitted for duplicate) +[INFO] | | +- (io.micrometer:micrometer-observation:jar:1.16.3:compile - version managed from 1.16.3; omitted for duplicate) +[INFO] | | +- org.hdrhistogram:HdrHistogram:jar:2.2.2:runtime +[INFO] | | \- org.latencyutils:LatencyUtils:jar:2.0.3:runtime +[INFO] | +- (io.micrometer:micrometer-commons:jar:1.16.3:compile - version managed from 1.16.3; omitted for duplicate) +[INFO] | \- (io.micrometer:micrometer-observation:jar:1.16.3:compile - version managed from 1.16.3; omitted for duplicate) +[INFO] +- org.springframework.boot:spring-boot-starter-security:jar:4.0.3:compile +[INFO] | +- (org.springframework.boot:spring-boot-starter:jar:4.0.3:compile - version managed from 4.0.3; omitted for duplicate) +[INFO] | +- org.springframework.boot:spring-boot-security:jar:4.0.3:compile (version managed from 4.0.3) +[INFO] | | +- (org.springframework.boot:spring-boot:jar:4.0.3:compile - version managed from 4.0.3; omitted for duplicate) +[INFO] | | +- org.springframework.security:spring-security-config:jar:7.0.3:compile (version managed from 7.0.3) +[INFO] | | | +- (org.springframework.security:spring-security-core:jar:7.0.3:compile - version managed from 7.0.3; omitted for duplicate) +[INFO] | | | +- (org.springframework:spring-aop:jar:7.0.5:compile - version managed from 7.0.4; omitted for duplicate) +[INFO] | | | +- (org.springframework:spring-beans:jar:7.0.5:compile - version managed from 7.0.4; omitted for duplicate) +[INFO] | | | +- (org.springframework:spring-context:jar:7.0.5:compile - version managed from 7.0.4; omitted for duplicate) +[INFO] | | | \- (org.springframework:spring-core:jar:7.0.5:compile - version managed from 7.0.4; omitted for duplicate) +[INFO] | | \- (org.springframework.security:spring-security-web:jar:7.0.3:compile - version managed from 7.0.3; omitted for duplicate) +[INFO] | \- org.springframework:spring-aop:jar:7.0.5:compile (version managed from 7.0.5) +[INFO] | +- org.springframework:spring-beans:jar:7.0.5:compile (version managed from 7.0.5) +[INFO] | | \- (org.springframework:spring-core:jar:7.0.5:compile - version managed from 7.0.5; omitted for duplicate) +[INFO] | \- (org.springframework:spring-core:jar:7.0.5:compile - version managed from 7.0.5; omitted for duplicate) +[INFO] +- com.devskiller:toolkit:jar:1.3-38:compile +[INFO] | +- org.slf4j:slf4j-api:jar:2.0.17:compile (version managed from 2.0.7) +[INFO] | +- (com.nimbusds:nimbus-jose-jwt:jar:10.5:compile - omitted for conflict with 9.37) +[INFO] | +- org.bouncycastle:bcprov-jdk18on:jar:1.82:compile +[INFO] | +- org.bouncycastle:bcpkix-jdk18on:jar:1.82:compile +[INFO] | | \- org.bouncycastle:bcutil-jdk18on:jar:1.82:compile +[INFO] | | \- (org.bouncycastle:bcprov-jdk18on:jar:1.82:compile - omitted for duplicate) +[INFO] | +- com.courier:courier-java:jar:3.3.0:compile +[INFO] | | +- (com.squareup.okhttp3:okhttp:jar:4.12.0:compile - omitted for conflict with 5.0.0-alpha.14) +[INFO] | | +- (com.fasterxml.jackson.core:jackson-databind:jar:2.20.2:compile - version managed from 2.13.0; omitted for duplicate) +[INFO] | | +- com.fasterxml.jackson.datatype:jackson-datatype-jdk8:jar:2.20.2:compile (version managed from 2.12.3) +[INFO] | | | +- (com.fasterxml.jackson.core:jackson-core:jar:2.20.2:compile - version managed from 2.20.2; omitted for duplicate) +[INFO] | | | \- (com.fasterxml.jackson.core:jackson-databind:jar:2.20.2:compile - version managed from 2.20.2; omitted for duplicate) +[INFO] | | \- (com.fasterxml.jackson.datatype:jackson-datatype-jsr310:jar:2.20.2:compile - version managed from 2.12.3; omitted for duplicate) +[INFO] | +- (com.devskiller.friendly-id:friendly-id:jar:1.1.0:compile - omitted for conflict with 2.0.0-SNAPSHOT) +[INFO] | +- org.apache.commons:commons-lang3:jar:3.19.0:compile (version managed from 3.13.0; scope not updated to compile) +[INFO] | +- jakarta.servlet:jakarta.servlet-api:jar:6.1.0:compile (version managed from 6.0.0) +[INFO] | +- com.github.ulisesbocchio:jasypt-spring-boot-starter:jar:3.0.5:compile +[INFO] | | \- com.github.ulisesbocchio:jasypt-spring-boot:jar:3.0.5:compile +[INFO] | | \- org.jasypt:jasypt:jar:1.9.3:compile +[INFO] | +- io.github.resilience4j:resilience4j-retry:jar:2.0.2:compile +[INFO] | | +- io.github.resilience4j:resilience4j-core:jar:2.0.2:compile +[INFO] | | | \- (org.slf4j:slf4j-api:jar:2.0.17:runtime - version managed from 1.7.30; omitted for duplicate) +[INFO] | | \- (org.slf4j:slf4j-api:jar:2.0.17:runtime - version managed from 1.7.30; omitted for duplicate) +[INFO] | \- io.vavr:vavr:jar:0.10.4:compile +[INFO] | \- io.vavr:vavr-match:jar:0.10.4:compile +[INFO] +- com.devskiller.friendly-id:friendly-id:jar:2.0.0-SNAPSHOT:compile (scope not updated to compile) +[INFO] | \- org.jspecify:jspecify:jar:1.0.0:compile (version managed from 1.0.0; scope not updated to compile) +[INFO] +- com.devskiller.friendly-id:friendly-id-jackson2-datatype:jar:2.0.0-SNAPSHOT:compile +[INFO] | +- (com.devskiller.friendly-id:friendly-id:jar:2.0.0-SNAPSHOT:compile - omitted for duplicate) +[INFO] | +- com.fasterxml.jackson.core:jackson-annotations:jar:2.20:compile (version managed from 2.18.2) +[INFO] | +- com.fasterxml.jackson.core:jackson-core:jar:2.20.2:compile (version managed from 2.18.2) +[INFO] | +- com.fasterxml.jackson.core:jackson-databind:jar:2.20.2:compile (version managed from 2.18.2) +[INFO] | | +- (com.fasterxml.jackson.core:jackson-annotations:jar:2.20:compile - version managed from 2.20; omitted for duplicate) +[INFO] | | \- (com.fasterxml.jackson.core:jackson-core:jar:2.20.2:compile - version managed from 2.20.2; omitted for duplicate) +[INFO] | \- com.fasterxml.jackson.module:jackson-module-parameter-names:jar:2.20.2:compile (version managed from 2.18.2) +[INFO] | +- (com.fasterxml.jackson.core:jackson-core:jar:2.20.2:compile - version managed from 2.20.2; omitted for duplicate) +[INFO] | \- (com.fasterxml.jackson.core:jackson-databind:jar:2.20.2:compile - version managed from 2.20.2; omitted for duplicate) +[INFO] +- com.devskiller.friendly-id:friendly-id-jackson-datatype:jar:2.0.0-SNAPSHOT:compile +[INFO] | +- (com.devskiller.friendly-id:friendly-id:jar:2.0.0-SNAPSHOT:compile - omitted for duplicate) +[INFO] | +- (com.fasterxml.jackson.core:jackson-annotations:jar:2.20:compile - version managed from 2.20; omitted for duplicate) +[INFO] | +- tools.jackson.core:jackson-core:jar:3.0.4:compile (version managed from 3.0.3) +[INFO] | \- tools.jackson.core:jackson-databind:jar:3.0.4:compile (version managed from 3.0.3) +[INFO] | +- (com.fasterxml.jackson.core:jackson-annotations:jar:2.20:compile - version managed from 2.20; omitted for duplicate) +[INFO] | \- (tools.jackson.core:jackson-core:jar:3.0.4:compile - version managed from 3.0.4; omitted for duplicate) +[INFO] +- org.springframework.boot:spring-boot-starter-amqp:jar:4.0.3:compile +[INFO] | +- (org.springframework.boot:spring-boot-starter:jar:4.0.3:compile - version managed from 4.0.3; omitted for duplicate) +[INFO] | \- org.springframework.boot:spring-boot-amqp:jar:4.0.3:compile (version managed from 4.0.3) +[INFO] | +- (org.springframework.boot:spring-boot:jar:4.0.3:compile - version managed from 4.0.3; omitted for duplicate) +[INFO] | +- org.springframework:spring-messaging:jar:7.0.5:compile (version managed from 7.0.5) +[INFO] | | +- (org.springframework:spring-beans:jar:7.0.5:compile - version managed from 7.0.5; omitted for duplicate) +[INFO] | | \- (org.springframework:spring-core:jar:7.0.5:compile - version managed from 7.0.5; omitted for duplicate) +[INFO] | +- (org.springframework.amqp:spring-rabbit:jar:4.0.2:compile - version managed from 4.0.2; omitted for duplicate) +[INFO] | \- (org.springframework.boot:spring-boot-transaction:jar:4.0.3:runtime - version managed from 4.0.3; omitted for duplicate) +[INFO] +- nl.big-o:liqp:jar:0.9.2.3:compile +[INFO] | +- (com.fasterxml.jackson.core:jackson-annotations:jar:2.20:compile - version managed from 2.13.2; omitted for duplicate) +[INFO] | +- (com.fasterxml.jackson.core:jackson-core:jar:2.20.2:compile - version managed from 2.13.2; omitted for duplicate) +[INFO] | +- (com.fasterxml.jackson.core:jackson-databind:jar:2.20.2:compile - version managed from 2.13.4.2; omitted for duplicate) +[INFO] | +- com.fasterxml.jackson.datatype:jackson-datatype-jsr310:jar:2.20.2:compile (version managed from 2.13.2) +[INFO] | | +- (com.fasterxml.jackson.core:jackson-annotations:jar:2.20:compile - version managed from 2.20; omitted for duplicate) +[INFO] | | +- (com.fasterxml.jackson.core:jackson-core:jar:2.20.2:compile - version managed from 2.20.2; omitted for duplicate) +[INFO] | | \- (com.fasterxml.jackson.core:jackson-databind:jar:2.20.2:compile - version managed from 2.20.2; omitted for duplicate) +[INFO] | \- ua.co.k:strftime4j:jar:1.0.6:compile +[INFO] +- com.ibm.icu:icu4j:jar:78.2:compile +[INFO] +- com.mailjet:mailjet-client:jar:6.0.1:compile +[INFO] | +- com.squareup.okhttp3:okhttp:jar:5.0.0-alpha.14:compile +[INFO] | | +- com.squareup.okio:okio-jvm:jar:3.9.0:compile +[INFO] | | | \- (org.jetbrains.kotlin:kotlin-stdlib:jar:2.2.21:compile - version managed from 1.9.21; omitted for duplicate) +[INFO] | | \- org.jetbrains.kotlin:kotlin-stdlib:jar:2.2.21:compile (version managed from 1.9.23) +[INFO] | | \- org.jetbrains:annotations:jar:13.0:compile (scope not updated to compile) +[INFO] | +- org.json:json:jar:20231013:compile +[INFO] | \- com.google.code.gson:gson:jar:2.13.2:compile (version managed from 2.9.0) +[INFO] | \- com.google.errorprone:error_prone_annotations:jar:2.41.0:compile +[INFO] +- com.slack.api:slack-api-client:jar:1.47.0:compile +[INFO] | +- com.slack.api:slack-api-model:jar:1.47.0:compile +[INFO] | | \- (com.google.code.gson:gson:jar:2.13.2:compile - version managed from 2.12.1; omitted for duplicate) +[INFO] | +- (com.squareup.okhttp3:okhttp:jar:4.12.0:compile - omitted for conflict with 5.0.0-alpha.14) +[INFO] | +- (com.google.code.gson:gson:jar:2.13.2:compile - version managed from 2.12.1; omitted for duplicate) +[INFO] | \- (org.slf4j:slf4j-api:jar:2.0.17:compile - version managed from 1.7.36; omitted for duplicate) +[INFO] +- com.nimbusds:nimbus-jose-jwt:jar:9.37:compile (scope not updated to compile) +[INFO] | \- com.github.stephenc.jcip:jcip-annotations:jar:1.0-1:compile +[INFO] +- org.jsoup:jsoup:jar:1.22.1:compile +[INFO] +- commons-validator:commons-validator:jar:1.10.1:compile +[INFO] | +- commons-beanutils:commons-beanutils:jar:1.11.0:compile +[INFO] | | +- (commons-logging:commons-logging:jar:1.3.5:compile - version managed from 1.3.5; omitted for duplicate) +[INFO] | | \- (commons-collections:commons-collections:jar:3.2.2:compile - omitted for duplicate) +[INFO] | +- commons-digester:commons-digester:jar:2.1:compile +[INFO] | +- commons-logging:commons-logging:jar:1.3.5:compile (version managed from 1.3.5) +[INFO] | \- commons-collections:commons-collections:jar:3.2.2:compile +[INFO] +- org.postgresql:postgresql:jar:42.7.10:runtime +[INFO] | \- org.checkerframework:checker-qual:jar:3.52.0:runtime +[INFO] +- org.liquibase:liquibase-core:jar:5.0.1:compile +[INFO] | +- com.opencsv:opencsv:jar:5.12.0:compile +[INFO] | | +- (org.apache.commons:commons-lang3:jar:3.19.0:compile - version managed from 3.18.0; omitted for duplicate) +[INFO] | | +- (org.apache.commons:commons-text:jar:1.13.1:compile - omitted for conflict with 1.14.0) +[INFO] | | \- (org.apache.commons:commons-collections4:jar:4.5.0:compile - omitted for duplicate) +[INFO] | +- org.yaml:snakeyaml:jar:2.5:compile (version managed from 2.5) +[INFO] | +- javax.xml.bind:jaxb-api:jar:2.3.1:compile +[INFO] | +- org.apache.commons:commons-collections4:jar:4.5.0:compile +[INFO] | +- org.apache.commons:commons-text:jar:1.14.0:compile +[INFO] | | \- (org.apache.commons:commons-lang3:jar:3.19.0:compile - version managed from 3.18.0; omitted for duplicate) +[INFO] | +- (org.apache.commons:commons-lang3:jar:3.19.0:compile - version managed from 3.19.0; omitted for duplicate) +[INFO] | \- commons-io:commons-io:jar:2.20.0:compile (scope not updated to compile) +[INFO] +- org.springframework.boot:spring-boot-starter-jooq:jar:4.0.3:compile +[INFO] | +- (org.springframework.boot:spring-boot-starter:jar:4.0.3:compile - version managed from 4.0.3; omitted for duplicate) +[INFO] | +- (org.springframework.boot:spring-boot-starter-jdbc:jar:4.0.3:compile - version managed from 4.0.3; omitted for duplicate) +[INFO] | +- org.springframework.boot:spring-boot-jooq:jar:4.0.3:compile (version managed from 4.0.3) +[INFO] | | +- (org.springframework.boot:spring-boot-jdbc:jar:4.0.3:compile - version managed from 4.0.3; omitted for duplicate) +[INFO] | | \- (org.springframework.boot:spring-boot-transaction:jar:4.0.3:compile - version managed from 4.0.3; omitted for duplicate) +[INFO] | \- (org.springframework.boot:spring-boot-jdbc:jar:4.0.3:compile - version managed from 4.0.3; omitted for duplicate) +[INFO] +- org.jooq.pro:jooq:jar:3.20.8:compile +[INFO] | \- io.r2dbc:r2dbc-spi:jar:1.0.0.RELEASE:compile (version managed from 1.0.0.RELEASE) +[INFO] | \- org.reactivestreams:reactive-streams:jar:1.0.4:compile (version managed from 1.0.3) +[INFO] +- org.projectlombok:lombok:jar:1.18.42:compile +[INFO] +- net.logstash.logback:logstash-logback-encoder:jar:8.0:compile +[INFO] | \- (com.fasterxml.jackson.core:jackson-databind:jar:2.20.2:compile - version managed from 2.17.2; omitted for duplicate) +[INFO] +- com.datadoghq:dd-trace-api:jar:1.60.1:compile +[INFO] | \- (org.slf4j:slf4j-api:jar:2.0.17:compile - version managed from 1.7.30; omitted for duplicate) +[INFO] +- org.springframework.boot:spring-boot-starter-test:jar:4.0.3:test +[INFO] | +- (org.springframework.boot:spring-boot-starter:jar:4.0.3:test - version managed from 4.0.3; omitted for duplicate) +[INFO] | +- org.springframework.boot:spring-boot-test:jar:4.0.3:test (version managed from 4.0.3) +[INFO] | | +- (org.springframework.boot:spring-boot:jar:4.0.3:test - version managed from 4.0.3; omitted for duplicate) +[INFO] | | \- (org.springframework:spring-test:jar:7.0.5:test - version managed from 7.0.5; omitted for duplicate) +[INFO] | +- org.springframework.boot:spring-boot-test-autoconfigure:jar:4.0.3:test (version managed from 4.0.3) +[INFO] | | \- (org.springframework.boot:spring-boot-test:jar:4.0.3:test - version managed from 4.0.3; omitted for duplicate) +[INFO] | +- com.jayway.jsonpath:json-path:jar:2.10.0:test (version managed from 2.10.0) +[INFO] | | +- (net.minidev:json-smart:jar:2.6.0:test - version managed from 2.6.0; omitted for duplicate) +[INFO] | | \- (org.slf4j:slf4j-api:jar:2.0.17:test - version managed from 2.0.17; omitted for duplicate) +[INFO] | +- jakarta.xml.bind:jakarta.xml.bind-api:jar:4.0.4:runtime (version managed from 4.0.4; scope not updated to runtime) +[INFO] | | \- jakarta.activation:jakarta.activation-api:jar:2.1.4:runtime (version managed from 2.1.4) +[INFO] | +- net.minidev:json-smart:jar:2.6.0:test (version managed from 2.6.0) +[INFO] | | \- net.minidev:accessors-smart:jar:2.6.0:test +[INFO] | | \- org.ow2.asm:asm:jar:9.7.1:test +[INFO] | +- org.assertj:assertj-core:jar:3.27.7:test (version managed from 3.27.7) +[INFO] | | \- net.bytebuddy:byte-buddy:jar:1.17.8:runtime (version managed from 1.18.3; scope not updated to runtime) +[INFO] | +- (org.awaitility:awaitility:jar:4.3.0:test - version managed from 4.3.0; omitted for duplicate) +[INFO] | +- org.hamcrest:hamcrest:jar:3.0:test (version managed from 3.0) +[INFO] | +- org.junit.jupiter:junit-jupiter:jar:6.0.3:test (version managed from 6.0.3) +[INFO] | | +- org.junit.jupiter:junit-jupiter-api:jar:6.0.3:test (version managed from 6.0.3) +[INFO] | | | +- org.opentest4j:opentest4j:jar:1.3.0:test +[INFO] | | | +- org.junit.platform:junit-platform-commons:jar:6.0.3:test (version managed from 6.0.3) +[INFO] | | | | +- (org.apiguardian:apiguardian-api:jar:1.1.2:test - omitted for duplicate) +[INFO] | | | | \- (org.jspecify:jspecify:jar:1.0.0:test - version managed from 1.0.0; omitted for duplicate) +[INFO] | | | +- org.apiguardian:apiguardian-api:jar:1.1.2:test +[INFO] | | | \- (org.jspecify:jspecify:jar:1.0.0:test - version managed from 1.0.0; omitted for duplicate) +[INFO] | | +- org.junit.jupiter:junit-jupiter-params:jar:6.0.3:test (version managed from 6.0.3) +[INFO] | | | +- (org.junit.jupiter:junit-jupiter-api:jar:6.0.3:test - version managed from 6.0.3; omitted for duplicate) +[INFO] | | | +- (org.apiguardian:apiguardian-api:jar:1.1.2:test - omitted for duplicate) +[INFO] | | | \- (org.jspecify:jspecify:jar:1.0.0:test - version managed from 1.0.0; omitted for duplicate) +[INFO] | | \- org.junit.jupiter:junit-jupiter-engine:jar:6.0.3:test (version managed from 6.0.3) +[INFO] | | +- org.junit.platform:junit-platform-engine:jar:6.0.3:test (version managed from 6.0.3) +[INFO] | | | +- (org.opentest4j:opentest4j:jar:1.3.0:test - omitted for duplicate) +[INFO] | | | +- (org.junit.platform:junit-platform-commons:jar:6.0.3:test - version managed from 6.0.3; omitted for duplicate) +[INFO] | | | +- (org.apiguardian:apiguardian-api:jar:1.1.2:test - omitted for duplicate) +[INFO] | | | \- (org.jspecify:jspecify:jar:1.0.0:test - version managed from 1.0.0; omitted for duplicate) +[INFO] | | +- (org.junit.jupiter:junit-jupiter-api:jar:6.0.3:test - version managed from 6.0.3; omitted for duplicate) +[INFO] | | +- (org.apiguardian:apiguardian-api:jar:1.1.2:test - omitted for duplicate) +[INFO] | | \- (org.jspecify:jspecify:jar:1.0.0:test - version managed from 1.0.0; omitted for duplicate) +[INFO] | +- org.mockito:mockito-core:jar:5.20.0:test (version managed from 5.20.0) +[INFO] | | +- (net.bytebuddy:byte-buddy:jar:1.17.8:test - version managed from 1.17.7; omitted for duplicate) +[INFO] | | +- net.bytebuddy:byte-buddy-agent:jar:1.17.8:test (version managed from 1.17.7) +[INFO] | | \- org.objenesis:objenesis:jar:3.3:test +[INFO] | +- org.mockito:mockito-junit-jupiter:jar:5.20.0:test (version managed from 5.20.0) +[INFO] | | +- (org.mockito:mockito-core:jar:5.20.0:test - version managed from 5.20.0; omitted for duplicate) +[INFO] | | \- (org.junit.jupiter:junit-jupiter-api:jar:6.0.3:test - version managed from 5.13.4; omitted for duplicate) +[INFO] | +- org.skyscreamer:jsonassert:jar:1.5.3:test (version managed from 1.5.3) +[INFO] | | \- com.vaadin.external.google:android-json:jar:0.0.20131108.vaadin1:test +[INFO] | +- org.springframework:spring-core:jar:7.0.5:compile (version managed from 7.0.5; scope not updated to compile) +[INFO] | | +- (commons-logging:commons-logging:jar:1.3.5:compile - version managed from 1.3.5; omitted for duplicate) +[INFO] | | \- (org.jspecify:jspecify:jar:1.0.0:compile - version managed from 1.0.0; omitted for duplicate) +[INFO] | +- org.springframework:spring-test:jar:7.0.5:test (version managed from 7.0.5) +[INFO] | | \- (org.springframework:spring-core:jar:7.0.5:test - version managed from 7.0.5; omitted for duplicate) +[INFO] | \- org.xmlunit:xmlunit-core:jar:2.10.4:test (version managed from 2.10.4) +[INFO] | \- (jakarta.xml.bind:jakarta.xml.bind-api:jar:4.0.4:test - version managed from 2.3.3; omitted for duplicate) +[INFO] +- org.springframework.boot:spring-boot-starter-webmvc-test:jar:4.0.3:test +[INFO] | +- org.springframework.boot:spring-boot-starter-jackson-test:jar:4.0.3:test (version managed from 4.0.3) +[INFO] | | +- (org.springframework.boot:spring-boot-starter-jackson:jar:4.0.3:test - version managed from 4.0.3; omitted for duplicate) +[INFO] | | \- (org.springframework.boot:spring-boot-starter-test:jar:4.0.3:test - version managed from 4.0.3; omitted for duplicate) +[INFO] | +- (org.springframework.boot:spring-boot-starter-test:jar:4.0.3:test - version managed from 4.0.3; omitted for duplicate) +[INFO] | +- org.springframework.boot:spring-boot-starter-webmvc:jar:4.0.3:test (version managed from 4.0.3) +[INFO] | | +- (org.springframework.boot:spring-boot-starter:jar:4.0.3:test - version managed from 4.0.3; omitted for duplicate) +[INFO] | | +- (org.springframework.boot:spring-boot-starter-jackson:jar:4.0.3:test - version managed from 4.0.3; omitted for duplicate) +[INFO] | | +- (org.springframework.boot:spring-boot-starter-tomcat:jar:4.0.3:test - version managed from 4.0.3; omitted for duplicate) +[INFO] | | +- (org.springframework.boot:spring-boot-http-converter:jar:4.0.3:test - version managed from 4.0.3; omitted for duplicate) +[INFO] | | \- (org.springframework.boot:spring-boot-webmvc:jar:4.0.3:test - version managed from 4.0.3; omitted for duplicate) +[INFO] | +- org.springframework.boot:spring-boot-webmvc-test:jar:4.0.3:test (version managed from 4.0.3) +[INFO] | | +- (org.springframework.boot:spring-boot-test-autoconfigure:jar:4.0.3:test - version managed from 4.0.3; omitted for duplicate) +[INFO] | | +- (org.springframework.boot:spring-boot-webmvc:jar:4.0.3:test - version managed from 4.0.3; omitted for duplicate) +[INFO] | | +- (org.springframework.boot:spring-boot-http-converter:jar:4.0.3:test - version managed from 4.0.3; omitted for duplicate) +[INFO] | | \- (org.springframework.boot:spring-boot-web-server:jar:4.0.3:test - version managed from 4.0.3; omitted for duplicate) +[INFO] | \- org.springframework.boot:spring-boot-resttestclient:jar:4.0.3:test (version managed from 4.0.3) +[INFO] | +- (org.springframework.boot:spring-boot-test:jar:4.0.3:test - version managed from 4.0.3; omitted for duplicate) +[INFO] | +- (org.springframework.boot:spring-boot-http-converter:jar:4.0.3:test - version managed from 4.0.3; omitted for duplicate) +[INFO] | \- (org.springframework:spring-web:jar:7.0.5:test - version managed from 7.0.5; omitted for duplicate) +[INFO] +- org.springframework.security:spring-security-test:jar:7.0.3:test +[INFO] | +- org.springframework.security:spring-security-core:jar:7.0.3:compile (version managed from 7.0.3; scope not updated to compile) +[INFO] | | +- org.springframework.security:spring-security-crypto:jar:7.0.3:compile (version managed from 7.0.3) +[INFO] | | +- (org.springframework:spring-aop:jar:7.0.5:compile - version managed from 7.0.4; omitted for duplicate) +[INFO] | | +- (org.springframework:spring-beans:jar:7.0.5:compile - version managed from 7.0.4; omitted for duplicate) +[INFO] | | +- org.springframework:spring-context:jar:7.0.5:compile (version managed from 7.0.4) +[INFO] | | | +- (org.springframework:spring-aop:jar:7.0.5:compile - version managed from 7.0.5; omitted for duplicate) +[INFO] | | | +- (org.springframework:spring-beans:jar:7.0.5:compile - version managed from 7.0.5; omitted for duplicate) +[INFO] | | | +- (org.springframework:spring-core:jar:7.0.5:compile - version managed from 7.0.5; omitted for duplicate) +[INFO] | | | +- (org.springframework:spring-expression:jar:7.0.5:compile - version managed from 7.0.5; omitted for duplicate) +[INFO] | | | \- (io.micrometer:micrometer-observation:jar:1.16.3:compile - version managed from 1.16.3; omitted for duplicate) +[INFO] | | +- (org.springframework:spring-core:jar:7.0.5:compile - version managed from 7.0.4; omitted for duplicate) +[INFO] | | +- org.springframework:spring-expression:jar:7.0.5:compile (version managed from 7.0.4) +[INFO] | | | \- (org.springframework:spring-core:jar:7.0.5:compile - version managed from 7.0.5; omitted for duplicate) +[INFO] | | \- (io.micrometer:micrometer-observation:jar:1.16.3:compile - version managed from 1.16.3; omitted for duplicate) +[INFO] | +- org.springframework.security:spring-security-web:jar:7.0.3:compile (version managed from 7.0.3; scope not updated to compile) +[INFO] | | +- (org.springframework.security:spring-security-core:jar:7.0.3:compile - version managed from 7.0.3; omitted for duplicate) +[INFO] | | +- (org.springframework:spring-core:jar:7.0.5:compile - version managed from 7.0.4; omitted for duplicate) +[INFO] | | +- (org.springframework:spring-aop:jar:7.0.5:compile - version managed from 7.0.4; omitted for duplicate) +[INFO] | | +- (org.springframework:spring-beans:jar:7.0.5:compile - version managed from 7.0.4; omitted for duplicate) +[INFO] | | +- (org.springframework:spring-context:jar:7.0.5:compile - version managed from 7.0.4; omitted for duplicate) +[INFO] | | +- (org.springframework:spring-expression:jar:7.0.5:compile - version managed from 7.0.4; omitted for duplicate) +[INFO] | | \- (org.springframework:spring-web:jar:7.0.5:compile - version managed from 7.0.4; omitted for duplicate) +[INFO] | +- (org.springframework:spring-core:jar:7.0.5:test - version managed from 7.0.4; omitted for duplicate) +[INFO] | \- (org.springframework:spring-test:jar:7.0.5:test - version managed from 7.0.4; omitted for duplicate) +[INFO] +- org.springframework.boot:spring-boot-testcontainers:jar:4.0.3:test +[INFO] | +- org.springframework.boot:spring-boot-autoconfigure:jar:4.0.3:compile (version managed from 4.0.3; scope not updated to compile) +[INFO] | | \- (org.springframework.boot:spring-boot:jar:4.0.3:compile - version managed from 4.0.3; omitted for duplicate) +[INFO] | \- org.testcontainers:testcontainers:jar:2.0.3:test (version managed from 2.0.3) +[INFO] | +- (org.slf4j:slf4j-api:jar:2.0.17:test - version managed from 1.7.36; omitted for duplicate) +[INFO] | +- org.apache.commons:commons-compress:jar:1.28.0:test +[INFO] | | +- commons-codec:commons-codec:jar:1.19.0:test (version managed from 1.19.0) +[INFO] | | +- (commons-io:commons-io:jar:2.20.0:test - omitted for duplicate) +[INFO] | | \- (org.apache.commons:commons-lang3:jar:3.19.0:test - version managed from 3.18.0; omitted for duplicate) +[INFO] | +- org.rnorth.duct-tape:duct-tape:jar:1.0.8:test +[INFO] | | \- (org.jetbrains:annotations:jar:17.0.0:test - omitted for conflict with 13.0) +[INFO] | +- com.github.docker-java:docker-java-api:jar:3.7.0:test +[INFO] | | +- (com.fasterxml.jackson.core:jackson-annotations:jar:2.20:test - version managed from 2.20; omitted for duplicate) +[INFO] | | \- (org.slf4j:slf4j-api:jar:2.0.17:test - version managed from 1.7.30; omitted for duplicate) +[INFO] | \- com.github.docker-java:docker-java-transport-zerodep:jar:3.7.0:test +[INFO] | +- com.github.docker-java:docker-java-transport:jar:3.7.0:test +[INFO] | +- (org.slf4j:slf4j-api:jar:2.0.17:test - version managed from 1.7.36; omitted for duplicate) +[INFO] | \- net.java.dev.jna:jna:jar:5.18.1:test +[INFO] +- org.springframework.amqp:spring-rabbit-test:jar:4.0.2:test +[INFO] | +- org.springframework.amqp:spring-rabbit:jar:4.0.2:compile (version managed from 4.0.2; scope not updated to compile) +[INFO] | | +- org.springframework.amqp:spring-amqp:jar:4.0.2:compile (version managed from 4.0.2) +[INFO] | | | \- (org.springframework:spring-core:jar:7.0.5:compile - version managed from 7.0.3; omitted for duplicate) +[INFO] | | +- com.rabbitmq:amqp-client:jar:5.27.1:compile (version managed from 5.27.1; scope not updated to compile) +[INFO] | | | +- (org.slf4j:slf4j-api:jar:2.0.17:compile - version managed from 1.7.36; omitted for duplicate) +[INFO] | | | +- (io.netty:netty-transport:jar:4.2.10.Final:compile - version managed from 4.2.7.Final; omitted for duplicate) +[INFO] | | | +- io.netty:netty-codec:jar:4.2.10.Final:compile (version managed from 4.2.7.Final) +[INFO] | | | | +- (io.netty:netty-common:jar:4.2.10.Final:compile - version managed from 4.2.10.Final; omitted for duplicate) +[INFO] | | | | +- (io.netty:netty-buffer:jar:4.2.10.Final:compile - version managed from 4.2.10.Final; omitted for duplicate) +[INFO] | | | | +- (io.netty:netty-transport:jar:4.2.10.Final:compile - version managed from 4.2.10.Final; omitted for duplicate) +[INFO] | | | | +- (io.netty:netty-codec-base:jar:4.2.10.Final:compile - version managed from 4.2.10.Final; omitted for duplicate) +[INFO] | | | | +- io.netty:netty-codec-compression:jar:4.2.10.Final:compile (version managed from 4.2.10.Final) +[INFO] | | | | | +- (io.netty:netty-common:jar:4.2.10.Final:compile - version managed from 4.2.10.Final; omitted for duplicate) +[INFO] | | | | | +- (io.netty:netty-buffer:jar:4.2.10.Final:compile - version managed from 4.2.10.Final; omitted for duplicate) +[INFO] | | | | | +- (io.netty:netty-transport:jar:4.2.10.Final:compile - version managed from 4.2.10.Final; omitted for duplicate) +[INFO] | | | | | \- (io.netty:netty-codec-base:jar:4.2.10.Final:compile - version managed from 4.2.10.Final; omitted for duplicate) +[INFO] | | | | +- io.netty:netty-codec-protobuf:jar:4.2.10.Final:compile (version managed from 4.2.10.Final) +[INFO] | | | | | +- (io.netty:netty-common:jar:4.2.10.Final:compile - version managed from 4.2.10.Final; omitted for duplicate) +[INFO] | | | | | +- (io.netty:netty-buffer:jar:4.2.10.Final:compile - version managed from 4.2.10.Final; omitted for duplicate) +[INFO] | | | | | +- (io.netty:netty-transport:jar:4.2.10.Final:compile - version managed from 4.2.10.Final; omitted for duplicate) +[INFO] | | | | | \- (io.netty:netty-codec-base:jar:4.2.10.Final:compile - version managed from 4.2.10.Final; omitted for duplicate) +[INFO] | | | | \- io.netty:netty-codec-marshalling:jar:4.2.10.Final:compile (version managed from 4.2.10.Final) +[INFO] | | | | +- (io.netty:netty-common:jar:4.2.10.Final:compile - version managed from 4.2.10.Final; omitted for duplicate) +[INFO] | | | | +- (io.netty:netty-buffer:jar:4.2.10.Final:compile - version managed from 4.2.10.Final; omitted for duplicate) +[INFO] | | | | +- (io.netty:netty-transport:jar:4.2.10.Final:compile - version managed from 4.2.10.Final; omitted for duplicate) +[INFO] | | | | \- (io.netty:netty-codec-base:jar:4.2.10.Final:compile - version managed from 4.2.10.Final; omitted for duplicate) +[INFO] | | | \- (io.netty:netty-handler:jar:4.2.10.Final:compile - version managed from 4.2.7.Final; omitted for duplicate) +[INFO] | | +- (org.springframework:spring-context:jar:7.0.5:compile - version managed from 7.0.3; omitted for duplicate) +[INFO] | | +- (org.springframework:spring-messaging:jar:7.0.5:compile - version managed from 7.0.3; omitted for duplicate) +[INFO] | | +- org.springframework:spring-tx:jar:7.0.5:compile (version managed from 7.0.3) +[INFO] | | | +- (org.springframework:spring-beans:jar:7.0.5:compile - version managed from 7.0.5; omitted for duplicate) +[INFO] | | | \- (org.springframework:spring-core:jar:7.0.5:compile - version managed from 7.0.5; omitted for duplicate) +[INFO] | | \- (io.micrometer:micrometer-observation:jar:1.16.3:compile - version managed from 1.16.2; omitted for duplicate) +[INFO] | +- org.springframework.amqp:spring-rabbit-junit:jar:4.0.2:test (version managed from 4.0.2) +[INFO] | | +- (org.springframework:spring-core:jar:7.0.5:test - version managed from 7.0.3; omitted for duplicate) +[INFO] | | +- (org.springframework:spring-test:jar:7.0.5:test - version managed from 7.0.3; omitted for duplicate) +[INFO] | | +- (com.rabbitmq:amqp-client:jar:5.27.1:test - version managed from 5.27.1; omitted for duplicate) +[INFO] | | +- (org.springframework:spring-web:jar:7.0.5:test - version managed from 7.0.3; omitted for duplicate) +[INFO] | | +- (org.junit.jupiter:junit-jupiter-api:jar:6.0.3:test - version managed from 6.0.2; omitted for duplicate) +[INFO] | | \- (org.assertj:assertj-core:jar:3.27.7:test - version managed from 3.27.6; omitted for duplicate) +[INFO] | +- org.hamcrest:hamcrest-library:jar:3.0:test (version managed from 3.0) +[INFO] | | \- (org.hamcrest:hamcrest-core:jar:3.0:test - version managed from 3.0; omitted for duplicate) +[INFO] | +- org.hamcrest:hamcrest-core:jar:3.0:test (version managed from 3.0) +[INFO] | | \- (org.hamcrest:hamcrest:jar:3.0:test - version managed from 3.0; omitted for duplicate) +[INFO] | \- (org.mockito:mockito-core:jar:5.20.0:test - version managed from 5.20.0; omitted for duplicate) +[INFO] +- io.projectreactor:reactor-test:jar:3.8.3:test +[INFO] | +- io.projectreactor:reactor-core:jar:3.8.3:compile (version managed from 3.8.3; scope not updated to compile) +[INFO] | | +- (org.reactivestreams:reactive-streams:jar:1.0.4:compile - version managed from 1.0.4; omitted for duplicate) +[INFO] | | \- (org.jspecify:jspecify:jar:1.0.0:compile - version managed from 1.0.0; omitted for duplicate) +[INFO] | \- (org.jspecify:jspecify:jar:1.0.0:test - version managed from 1.0.0; omitted for duplicate) +[INFO] +- org.awaitility:awaitility:jar:4.3.0:test (scope not updated to test) +[INFO] | \- (org.hamcrest:hamcrest:jar:3.0:test - version managed from 2.1; omitted for duplicate) +[INFO] +- org.testcontainers:testcontainers-junit-jupiter:jar:2.0.3:test +[INFO] | \- (org.testcontainers:testcontainers:jar:2.0.3:test - version managed from 2.0.3; omitted for duplicate) +[INFO] +- org.testcontainers:testcontainers-postgresql:jar:2.0.3:test +[INFO] | \- org.testcontainers:testcontainers-jdbc:jar:2.0.3:test (version managed from 2.0.3) +[INFO] | \- org.testcontainers:testcontainers-database-commons:jar:2.0.3:test (version managed from 2.0.3) +[INFO] | \- (org.testcontainers:testcontainers:jar:2.0.3:test - version managed from 2.0.3; omitted for duplicate) +[INFO] \- org.testcontainers:testcontainers-rabbitmq:jar:2.0.3:test +[INFO] \- (org.testcontainers:testcontainers:jar:2.0.3:test - version managed from 2.0.3; omitted for duplicate) +[INFO] ------------------------------------------------------------------------ +[INFO] BUILD SUCCESS +[INFO] ------------------------------------------------------------------------ +[INFO] Total time: 0.561 s +[INFO] Finished at: 2026-03-09T20:37:27+01:00 +[INFO] ------------------------------------------------------------------------ diff --git a/tests/fixtures/mvn_dep_tree_conflicts.txt b/tests/fixtures/mvn_dep_tree_conflicts.txt new file mode 100644 index 000000000..72e8bd9ac --- /dev/null +++ b/tests/fixtures/mvn_dep_tree_conflicts.txt @@ -0,0 +1,16 @@ +[INFO] Scanning for projects... +[INFO] +[INFO] --- dependency:3.7.0:tree (default-cli) @ my-app --- +[INFO] com.example:my-app:jar:1.0.0 +[INFO] +- com.example:lib-a:jar:1.0.0:compile +[INFO] | +- com.fasterxml.jackson.core:jackson-databind:jar:2.18.3:compile +[INFO] | | +- com.fasterxml.jackson.core:jackson-core:jar:2.18.3:compile +[INFO] | | \- com.fasterxml.jackson.core:jackson-annotations:jar:2.18.3:compile +[INFO] | \- org.slf4j:slf4j-api:jar:2.0.9:compile +[INFO] +- com.example:lib-b:jar:2.0.0:compile +[INFO] | +- (com.fasterxml.jackson.core:jackson-databind:jar:2.17.0:compile - omitted for conflict with 2.18.3) +[INFO] | \- (org.slf4j:slf4j-api:jar:2.0.7:compile - omitted for conflict with 2.0.9) +[INFO] \- com.example:lib-c:jar:3.0.0:compile +[INFO] \- (com.fasterxml.jackson.core:jackson-databind:jar:2.19.0:compile - omitted for conflict with 2.18.3) +[INFO] ------------------------------------------------------------------------ +[INFO] BUILD SUCCESS diff --git a/tests/fixtures/mvn_dep_tree_large.txt b/tests/fixtures/mvn_dep_tree_large.txt new file mode 100644 index 000000000..203d81500 --- /dev/null +++ b/tests/fixtures/mvn_dep_tree_large.txt @@ -0,0 +1,142 @@ +[INFO] Loaded 22524 auto-discovered prefixes for remote repository central (prefixes-central.txt) +[INFO] Loaded 74 auto-discovered prefixes for remote repository apache.snapshots (prefixes-apache.snapshots.txt) +[INFO] Scanning for projects... +[WARNING] Could not transfer metadata /.meta/prefixes.txt from/to repo-releases (https://repo.example.com/releases/): Checksum validation failed +[INFO] Loaded 22524 auto-discovered prefixes for remote repository central (prefixes-central.txt) +[WARNING] +[WARNING] 1 problem was encountered while building the effective model for 'com.example.demo:webapp:jar:2.0-SNAPSHOT' (use -e to see details) +[WARNING] +[WARNING] It is highly recommended to fix these problems because they threaten the stability of your build. +[WARNING] +[INFO] +[INFO] -------------------------------------------------< com.example.demo:webapp >-------------------------------------------------- +[INFO] Building webapp 2.0-SNAPSHOT +[INFO] from pom.xml +[INFO] ---------------------------------------------------------[ jar ]---------------------------------------------------------- +[WARNING] 130 problems were encountered while building the effective model for 'org.redisson:redisson-spring-boot-starter:jar:4.0.0' during dependency collection step for project (use -X to see details) +[WARNING] 76 problems were encountered while building the effective model for 'com.playtika.testcontainers:embedded-redis:jar:3.1.11' during dependency collection step for project (use -X to see details) +[INFO] +[INFO] --- dependency:3.9.0:tree (default-cli) @ webapp --- +[INFO] com.example.demo:webapp:jar:2.0-SNAPSHOT +[INFO] +- org.springframework.boot:spring-boot-starter-actuator:jar:4.0.5:compile +[INFO] | +- org.springframework.boot:spring-boot-starter:jar:4.0.5:compile +[INFO] | | +- org.springframework.boot:spring-boot-starter-logging:jar:4.0.5:compile +[INFO] | | | +- ch.qos.logback:logback-classic:jar:1.5.32:compile +[INFO] | | | | \- ch.qos.logback:logback-core:jar:1.5.32:compile +[INFO] | | | +- org.apache.logging.log4j:log4j-to-slf4j:jar:2.25.3:compile +[INFO] | | | | \- org.apache.logging.log4j:log4j-api:jar:2.25.3:compile +[INFO] | | | \- org.slf4j:jul-to-slf4j:jar:2.0.17:compile +[INFO] | | \- org.springframework.boot:spring-boot-autoconfigure:jar:4.0.5:compile +[INFO] | +- org.springframework.boot:spring-boot-actuator-autoconfigure:jar:4.0.5:compile +[INFO] | | \- org.springframework.boot:spring-boot-actuator:jar:4.0.5:compile +[INFO] | +- io.micrometer:micrometer-observation:jar:1.16.4:compile +[INFO] | | \- io.micrometer:micrometer-commons:jar:1.16.4:compile +[INFO] | \- io.micrometer:micrometer-jakarta9:jar:1.16.4:compile +[INFO] +- org.springframework.cloud:spring-cloud-starter-circuitbreaker-resilience4j:jar:5.0.1:compile +[INFO] | +- org.springframework.cloud:spring-cloud-starter:jar:5.0.1:compile +[INFO] | | \- org.springframework.cloud:spring-cloud-context:jar:5.0.1:compile +[INFO] | +- org.springframework.cloud:spring-cloud-circuitbreaker-resilience4j:jar:5.0.1:compile +[INFO] | | +- com.fasterxml.jackson.core:jackson-core:jar:2.21.2:compile +[INFO] | | \- io.github.resilience4j:resilience4j-spring-boot3:jar:2.3.0:compile +[INFO] | | +- io.github.resilience4j:resilience4j-spring6:jar:2.3.0:compile +[INFO] | | | +- io.github.resilience4j:resilience4j-annotations:jar:2.3.0:compile +[INFO] | | | +- io.github.resilience4j:resilience4j-consumer:jar:2.3.0:compile +[INFO] | | | | \- io.github.resilience4j:resilience4j-circularbuffer:jar:2.3.0:runtime +[INFO] | | | \- io.github.resilience4j:resilience4j-framework-common:jar:2.3.0:compile +[INFO] | | \- io.github.resilience4j:resilience4j-micrometer:jar:2.3.0:compile +[INFO] | +- io.github.resilience4j:resilience4j-circuitbreaker:jar:2.3.0:compile +[INFO] | | \- io.github.resilience4j:resilience4j-core:jar:2.3.0:compile +[INFO] | \- io.github.resilience4j:resilience4j-timelimiter:jar:2.3.0:compile +[INFO] +- org.springframework.boot:spring-boot-starter-web:jar:4.0.5:compile +[INFO] | +- org.springframework.boot:spring-boot-starter-jackson:jar:4.0.5:compile +[INFO] | | \- org.springframework.boot:spring-boot-jackson:jar:4.0.5:compile +[INFO] | +- org.springframework.boot:spring-boot-starter-tomcat:jar:4.0.5:compile +[INFO] | | +- org.apache.tomcat.embed:tomcat-embed-core:jar:11.0.20:compile +[INFO] | | \- org.apache.tomcat.embed:tomcat-embed-websocket:jar:11.0.20:compile +[INFO] | +- org.springframework.boot:spring-boot-http-converter:jar:4.0.5:compile +[INFO] | \- org.springframework.boot:spring-boot-webmvc:jar:4.0.5:compile +[INFO] | \- org.springframework.boot:spring-boot-servlet:jar:4.0.5:compile +[INFO] +- org.springframework.boot:spring-boot-starter-security:jar:4.0.5:compile +[INFO] | +- org.springframework.boot:spring-boot-security:jar:4.0.5:compile +[INFO] | | \- org.springframework.security:spring-security-config:jar:7.0.4:compile +[INFO] | \- org.springframework:spring-aop:jar:7.0.6:compile +[INFO] +- org.springframework.boot:spring-boot-starter-jdbc:jar:4.0.5:compile +[INFO] | \- com.zaxxer:HikariCP:jar:7.0.2:compile +[INFO] +- org.springframework.retry:spring-retry:jar:2.0.12:compile +[INFO] +- org.springframework.cloud:spring-cloud-stream:jar:5.0.1:compile +[INFO] | +- org.springframework:spring-messaging:jar:7.0.6:compile +[INFO] | +- org.springframework.integration:spring-integration-core:jar:7.0.4:compile +[INFO] | | +- org.springframework:spring-context:jar:7.0.6:compile +[INFO] | | +- org.springframework:spring-tx:jar:7.0.6:compile +[INFO] | | \- io.projectreactor:reactor-core:jar:3.8.4:compile +[INFO] | +- org.springframework.cloud:spring-cloud-function-context:jar:5.0.1:compile +[INFO] | | +- org.springframework.boot:spring-boot-restclient:jar:4.0.5:compile +[INFO] | | | \- org.springframework.boot:spring-boot-http-client:jar:4.0.5:compile +[INFO] | | +- org.springframework.cloud:spring-cloud-function-core:jar:5.0.1:compile +[INFO] | | \- org.springframework.boot:spring-boot-web-server:jar:4.0.5:compile +[INFO] | +- org.springframework.cloud:spring-cloud-function-grpc:jar:5.0.1:compile +[INFO] | | \- io.grpc:grpc-netty-shaded:jar:1.71.0:compile +[INFO] | \- org.springframework.integration:spring-integration-jmx:jar:7.0.4:compile +[INFO] +- io.micrometer:micrometer-registry-statsd:jar:1.16.4:compile +[INFO] | +- io.micrometer:micrometer-core:jar:1.16.4:compile +[INFO] | | \- org.latencyutils:LatencyUtils:jar:2.0.3:runtime +[INFO] | \- io.netty:netty-transport-native-epoll:jar:4.2.1.Final:compile (version managed from 4.1.100.Final) +[INFO] | +- io.netty:netty-common:jar:4.2.1.Final:compile +[INFO] | +- io.netty:netty-buffer:jar:4.2.1.Final:compile +[INFO] | +- io.netty:netty-transport:jar:4.2.1.Final:compile +[INFO] | \- io.netty:netty-transport-native-unix-common:jar:4.2.1.Final:compile +[INFO] +- org.projectlombok:lombok:jar:1.18.44:compile (optional) +[INFO] +- org.jspecify:jspecify:jar:1.0.0:compile +[INFO] +- org.postgresql:postgresql:jar:42.7.10:runtime +[INFO] +- org.apache.commons:commons-lang3:jar:3.19.0:compile +[INFO] +- com.google.guava:guava:jar:33.4.8-jre:compile +[INFO] +- org.springframework.boot:spring-boot-starter-data-jpa:jar:4.0.5:compile +[INFO] | +- org.springframework.boot:spring-boot-starter-aop:jar:4.0.5:compile +[INFO] | +- org.springframework.boot:spring-boot-data-jpa:jar:4.0.5:compile +[INFO] | | +- org.hibernate.orm:hibernate-core:jar:7.2.7.Final:compile +[INFO] | | | +- jakarta.persistence:jakarta.persistence-api:jar:3.2.0:compile +[INFO] | | | +- jakarta.transaction:jakarta.transaction-api:jar:2.0.1:compile +[INFO] | | | \- org.jboss.logging:jboss-logging:jar:3.6.1.Final:compile +[INFO] | | \- org.springframework.data:spring-data-jpa:jar:4.0.4:compile +[INFO] | | \- org.springframework.data:spring-data-commons:jar:4.0.4:compile +[INFO] | \- org.springframework:spring-aspects:jar:7.0.6:compile +[INFO] +- com.example.demo:common-utils:jar:2.0-42:compile +[INFO] | +- com.example.demo:common-model:jar:2.0-42:compile +[INFO] | | +- com.fasterxml.jackson.core:jackson-annotations:jar:2.21.2:compile +[INFO] | | \- com.fasterxml.jackson.core:jackson-databind:jar:2.21.2:compile +[INFO] | \- com.example.demo:common-config:jar:2.0-42:compile +[INFO] +- net.logstash.logback:logstash-logback-encoder:jar:8.0:compile +[INFO] +- org.springframework.boot:spring-boot-starter-test:jar:4.0.5:test +[INFO] | +- org.springframework.boot:spring-boot-test:jar:4.0.5:test +[INFO] | +- org.springframework.boot:spring-boot-test-autoconfigure:jar:4.0.5:test +[INFO] | +- com.jayway.jsonpath:json-path:jar:2.9.0:test +[INFO] | +- org.assertj:assertj-core:jar:3.27.3:test +[INFO] | +- org.hamcrest:hamcrest:jar:3.0:test +[INFO] | +- org.junit.jupiter:junit-jupiter:jar:5.12.2:test +[INFO] | | +- org.junit.jupiter:junit-jupiter-api:jar:5.12.2:test +[INFO] | | +- org.junit.jupiter:junit-jupiter-params:jar:5.12.2:test +[INFO] | | \- org.junit.jupiter:junit-jupiter-engine:jar:5.12.2:test +[INFO] | +- org.mockito:mockito-core:jar:5.18.0:test +[INFO] | +- org.mockito:mockito-junit-jupiter:jar:5.18.0:test +[INFO] | \- org.skyscreamer:jsonassert:jar:2.0-rc1:test +[INFO] +- org.testcontainers:testcontainers:jar:2.0.4:test +[INFO] | +- com.github.docker-java:docker-java-api:jar:4.0.0:test +[INFO] | \- com.github.docker-java:docker-java-transport-zerodep:jar:4.0.0:test +[INFO] +- org.testcontainers:testcontainers-junit-jupiter:jar:2.0.4:test +[INFO] +- org.testcontainers:testcontainers-postgresql:jar:2.0.4:test (omitted for duplicate) +[INFO] +- org.springframework.security:spring-security-test:jar:7.0.4:test +[INFO] +- org.liquibase:liquibase-core:jar:5.0.2:test +[INFO] +- org.hibernate.orm:hibernate-processor:jar:7.2.7.Final:provided (optional) +[INFO] | +- io.smallrye:jandex:jar:3.3.2:provided (optional) +[INFO] | +- jakarta.validation:jakarta.validation-api:jar:3.1.1:compile +[INFO] | +- org.antlr:antlr4-runtime:jar:4.13.2:compile +[INFO] | \- net.bytebuddy:byte-buddy:jar:1.17.8:compile +[INFO] \- io.rest-assured:spring-mock-mvc:jar:6.0.0:test +[INFO] +- io.rest-assured:spring-commons:jar:6.0.0:test +[INFO] \- org.springframework:spring-webmvc:jar:7.0.6:compile +[INFO] -------------------------------------------------------------------------------------------------------------------------- +[INFO] BUILD SUCCESS +[INFO] -------------------------------------------------------------------------------------------------------------------------- +[INFO] Total time: 4.406 s +[INFO] Finished at: 2026-04-10T17:56:14+02:00 +[INFO] -------------------------------------------------------------------------------------------------------------------------- diff --git a/tests/fixtures/mvn_dep_tree_simple.txt b/tests/fixtures/mvn_dep_tree_simple.txt new file mode 100644 index 000000000..92a436d85 --- /dev/null +++ b/tests/fixtures/mvn_dep_tree_simple.txt @@ -0,0 +1,22 @@ +[INFO] Scanning for projects... +[INFO] +[INFO] ------------------< com.example:my-app >------------------- +[INFO] Building my-app 1.0.0 +[INFO] from pom.xml +[INFO] --------------------------------[ jar ]--------------------------------- +[INFO] +[INFO] --- dependency:3.7.0:tree (default-cli) @ my-app --- +[INFO] com.example:my-app:jar:1.0.0 +[INFO] +- org.slf4j:slf4j-api:jar:2.0.17:compile +[INFO] +- com.google.guava:guava:jar:33.0.0-jre:compile +[INFO] | +- com.google.guava:failureaccess:jar:1.0.2:compile +[INFO] | \- com.google.guava:listenablefuture:jar:9999.0-empty-to-avoid-conflict-with-guava:compile +[INFO] +- com.fasterxml.jackson.core:jackson-databind:jar:2.19.2:compile +[INFO] | +- com.fasterxml.jackson.core:jackson-annotations:jar:2.19.2:compile +[INFO] | \- com.fasterxml.jackson.core:jackson-core:jar:2.19.2:compile +[INFO] \- org.junit.jupiter:junit-jupiter:jar:5.11.4:test +[INFO] +- org.junit.jupiter:junit-jupiter-api:jar:5.11.4:test +[INFO] \- org.junit.jupiter:junit-jupiter-engine:jar:5.11.4:test +[INFO] ------------------------------------------------------------------------ +[INFO] BUILD SUCCESS +[INFO] ------------------------------------------------------------------------ diff --git a/tests/fixtures/mvn_test_fail_auth.txt b/tests/fixtures/mvn_test_fail_auth.txt new file mode 100644 index 000000000..018dad29e --- /dev/null +++ b/tests/fixtures/mvn_test_fail_auth.txt @@ -0,0 +1,95 @@ +[INFO] Scanning for projects... +[WARNING] Could not transfer metadata from/to central: Checksum validation failed +[WARNING] +[WARNING] 1 problem was encountered while building the effective model for 'com.example:webapp:jar:1.0-SNAPSHOT' +[WARNING] +[WARNING] Total model problems reported: 1 +[WARNING] +[INFO] +[INFO] -------------------------------------------------< com.example:webapp >-------------------------------------------------- +[INFO] Building webapp 1.0-SNAPSHOT +[INFO] from pom.xml +[INFO] ---------------------------------------------------------[ jar ]---------------------------------------------------------- +[WARNING] 176 problems were encountered while building the effective model for 'com.example:dep:jar:3.2.0' during n/a +[WARNING] 130 problems were encountered while building the effective model for 'org.redisson:redisson-spring-boot-starter:jar:4.0.0' +[WARNING] 76 problems were encountered while building the effective model for 'com.playtika.testcontainers:embedded-redis:jar:3.1.11' +[INFO] +[INFO] --- clean:3.5.0:clean (default-clean) @ webapp --- +[INFO] Deleting /home/user/project/target +[INFO] +[INFO] --- jacoco:0.8.14:prepare-agent (prepare-agent) @ webapp --- +[INFO] argLine set to -javaagent:/home/user/.m2/repository/org/jacoco/org.jacoco.agent/0.8.14/org.jacoco.agent-0.8.14-runtime.jar +[INFO] +[INFO] --- testcontainers-jooq-codegen:0.0.4:generate (generate-jooq-sources) @ webapp --- +[WARNING] Could not transfer metadata from/to flyway-repo: Connection refused +[INFO] Image pull policy will be performed by: DefaultPullPolicy() +[WARNING] [stderr] Apr 08, 2026 5:45:45 PM liquibase.changelog +[WARNING] [stderr] INFO: Reading resource: db/changelog/2025/db.changelog-main.yaml +[WARNING] [stderr] INFO: Reading resource: db/changelog/2025/db.changelog-001.yaml +[WARNING] [stderr] INFO: Reading resource: db/changelog/2025/db.changelog-002.yaml +[INFO] +[INFO] --- maven-compiler-plugin:3.14.0:compile (default-compile) @ webapp --- +[INFO] Compiling 42 source files with javac [debug target 25] to target/classes +[INFO] +[INFO] --- maven-compiler-plugin:3.14.0:testCompile (default-testCompile) @ webapp --- +[INFO] Compiling 18 source files with javac [debug target 25] to target/test-classes +[WARNING] /home/user/project/src/test/java/com/example/TestFactory.java:[44,27] deprecated +[WARNING] /home/user/project/src/test/java/com/example/TestFactory.java:[48,45] deprecated +[WARNING] /home/user/project/src/test/java/com/example/SpecificationsTest.java:[60,44] deprecated +[INFO] /home/user/project/src/test/java/com/example/ProcessorTest.java: uses unchecked or unsafe operations. +[INFO] +[INFO] --- surefire:3.5.5:test (default-test) @ webapp --- +[INFO] Using auto detected provider org.apache.maven.surefire.junitplatform.JUnitPlatformProvider +[WARNING] The system property java.util.logging.config.file is configured twice! +[INFO] +[INFO] ------------------------------------------------------- +[INFO] T E S T S +[INFO] ------------------------------------------------------- +[INFO] Running com.example.score.ScoreTypeTest +[INFO] Running com.example.company.EmailParserTest +[ERROR] Tests run: 2, Failures: 1, Errors: 0, Skipped: 0, Time elapsed: 0.050 s <<< FAILURE! -- in com.example.company.EmailParserTest +[ERROR] Tests run: 3, Failures: 1, Errors: 0, Skipped: 0, Time elapsed: 0.058 s <<< FAILURE! -- in com.example.score.ScoreTypeTest +[ERROR] com.example.company.EmailParserTest.should_extract_domain_from_email -- Time elapsed: 0.009 s <<< FAILURE! +org.opentest4j.AssertionFailedError: + +expected: "broken.example.com" + but was: "user.example.com" + at com.example.company.EmailParserTest.should_extract_domain_from_email(EmailParserTest.java:14) + +[ERROR] com.example.score.ScoreTypeTest.shouldMapToRole -- Time elapsed: 0.008 s <<< FAILURE! +org.opentest4j.AssertionFailedError: + +expected: "app:BROKEN" + but was: "app:all" + at com.example.score.ScoreTypeTest.shouldMapToRole(ScoreTypeTest.java:24) + +[INFO] +[INFO] Results: +[INFO] +[ERROR] Failures: +[ERROR] EmailParserTest.should_extract_domain_from_email:14 +expected: "broken.example.com" + but was: "user.example.com" +[ERROR] ScoreTypeTest.shouldMapToRole:24 +expected: "app:BROKEN" + but was: "app:all" +[INFO] +[ERROR] Tests run: 5, Failures: 2, Errors: 0, Skipped: 0 +[INFO] +[INFO] -------------------------------------------------------------------------------------------------------------------------- +[INFO] BUILD FAILURE +[INFO] -------------------------------------------------------------------------------------------------------------------------- +[INFO] Total time: 23.819 s +[INFO] Finished at: 2026-04-08T17:45:57+02:00 +[INFO] -------------------------------------------------------------------------------------------------------------------------- +[ERROR] Failed to execute goal org.apache.maven.plugins:maven-surefire-plugin:3.5.5:test (default-test) on project webapp: There are test failures. +[ERROR] +[ERROR] See /home/user/project/target/surefire-reports for the individual test results. +[ERROR] See dump files (if any exist) [date].dump, [date]-jvmRun[N].dump and [date].dumpstream. +[ERROR] -> [Help 1] +[ERROR] +[ERROR] To see the full stack trace of the errors, re-run Maven with the '-e' switch +[ERROR] Re-run Maven using the '-X' switch to enable verbose output +[ERROR] +[ERROR] For more information about the errors and possible solutions, please read the following articles: +[ERROR] [Help 1] http://cwiki.apache.org/confluence/display/MAVEN/MojoFailureException diff --git a/tests/fixtures/mvn_test_large_suite.txt b/tests/fixtures/mvn_test_large_suite.txt new file mode 100644 index 000000000..5ab6abb71 --- /dev/null +++ b/tests/fixtures/mvn_test_large_suite.txt @@ -0,0 +1,204 @@ +[INFO] Scanning for projects... +[WARNING] Could not transfer metadata from/to central: Checksum validation failed +[INFO] +[INFO] -----------------------< com.example:platform >------------------------ +[INFO] Building platform 2.0-SNAPSHOT +[INFO] from pom.xml +[INFO] --------------------------------[ jar ]--------------------------------- +[WARNING] 42 problems were encountered while building the effective model +[INFO] +[INFO] --- maven-compiler-plugin:3.14.0:compile (default-compile) @ platform --- +[INFO] Compiling 120 source files with javac +[INFO] +[INFO] --- maven-compiler-plugin:3.14.0:testCompile (default-testCompile) @ platform --- +[INFO] Compiling 85 source files with javac +[WARNING] /home/user/project/src/test/java/com/example/DeprecatedTest.java:[10,5] deprecated +[WARNING] /home/user/project/src/test/java/com/example/OtherTest.java:[20,8] deprecated +[INFO] +[INFO] --- surefire:3.5.4:test (default-test) @ platform --- +[INFO] Using auto detected provider org.apache.maven.surefire.junitplatform.JUnitPlatformProvider +[INFO] +[INFO] ------------------------------------------------------- +[INFO] T E S T S +[INFO] ------------------------------------------------------- +[INFO] Running com.example.registry.SearchModelTest +[INFO] Tests run: 50, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 5.123 s -- in com.example.registry.SearchModelTest +[INFO] Running com.example.registry.IndexServiceTest +[INFO] Tests run: 30, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 2.456 s -- in com.example.registry.IndexServiceTest +[INFO] Running com.example.organization.OrgServiceTest +[INFO] Tests run: 25, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 1.234 s -- in com.example.organization.OrgServiceTest +[INFO] Running com.example.organization.PatchableFieldTest +[ERROR] Tests run: 6, Failures: 0, Errors: 6, Skipped: 0, Time elapsed: 0.010 s <<< FAILURE! -- in com.example.organization.PatchableFieldTest +[ERROR] com.example.organization.PatchableFieldTest.fromStrings_returns_empty -- Time elapsed: 0 s <<< ERROR! +java.lang.Error: +Unresolved compilation problem: + log cannot be resolved + + at com.example.organization.PatchableFieldTest.fromStrings_returns_empty(PatchableFieldTest.java:12) + +[ERROR] com.example.organization.PatchableFieldTest.fromStrings_converts_names -- Time elapsed: 0 s <<< ERROR! +java.lang.Error: +Unresolved compilation problem: + log cannot be resolved + + at com.example.organization.PatchableFieldTest.fromStrings_converts_names(PatchableFieldTest.java:20) + +[ERROR] com.example.organization.PatchableFieldTest.shouldUpdate_false_when_empty -- Time elapsed: 0 s <<< ERROR! +java.lang.ClassCastException: class com.example.organization.PatchableField not an enum + at com.example.organization.PatchableFieldTest.shouldUpdate_false_when_empty(PatchableFieldTest.java:36) + +[ERROR] com.example.organization.PatchableFieldTest.shouldUpdate_true_when_null -- Time elapsed: 0 s <<< ERROR! +java.lang.Error: +Unresolved compilation problem: + + at com.example.organization.PatchableField.shouldUpdate(PatchableField.java:29) + +[ERROR] com.example.organization.PatchableFieldTest.shouldUpdateAny_true -- Time elapsed: 0 s <<< ERROR! +java.lang.NullPointerException: Cannot invoke "java.lang.Enum.getDeclaringClass()" because "e1" is null + at com.example.organization.PatchableFieldTest.shouldUpdateAny_true(PatchableFieldTest.java:49) + +[ERROR] com.example.organization.PatchableFieldTest.fromStrings_ignores_unknown -- Time elapsed: 0 s <<< ERROR! +java.lang.Error: +Unresolved compilation problem: + log cannot be resolved + + at com.example.organization.PatchableFieldTest.fromStrings_ignores_unknown(PatchableFieldTest.java:55) + +[INFO] Running com.example.search.SearchReadModelTest +[ERROR] Tests run: 3, Failures: 3, Errors: 0, Skipped: 0, Time elapsed: 4.567 s <<< FAILURE! -- in com.example.search.SearchReadModelTest +[ERROR] com.example.search.SearchReadModelTest.should_get_person_ratings -- Time elapsed: 1.234 s <<< FAILURE! +java.lang.AssertionError: +Expecting actual: + [("Group1", {SELF=50, SUPERVISOR=35}, 40, null, 3), + ("Group2", {SELF=80}, 80, null, 1)] +to have size: + <1> +but had size: + <2> + + at com.example.search.SearchReadModelTest.should_get_person_ratings(SearchReadModelTest.java:85) + +[ERROR] com.example.search.SearchReadModelTest.should_get_company_ratings -- Time elapsed: 0.987 s <<< FAILURE! +org.assertj.core.error.AssertJMultipleFailuresError: +[List check single element] (1 failure) +-- failure 1 -- +expected: 42 + but was: 38 + + at com.example.search.SearchReadModelTest.should_get_company_ratings(SearchReadModelTest.java:120) + +[ERROR] com.example.search.SearchReadModelTest.should_not_include_unknown_skills -- Time elapsed: 0.456 s <<< FAILURE! +org.assertj.core.error.AssertJMultipleFailuresError: +[List check single element] (1 failure) +-- failure 1 -- +expected size: 0 but was: 1 + + at com.example.search.SearchReadModelTest.should_not_include_unknown_skills(SearchReadModelTest.java:150) + +[INFO] Running com.example.payment.PaymentServiceTest +[INFO] Tests run: 100, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 8.901 s -- in com.example.payment.PaymentServiceTest +[INFO] Running com.example.report.ReportGeneratorTest +[INFO] Tests run: 45, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 3.210 s -- in com.example.report.ReportGeneratorTest +[INFO] Running com.example.integration.ApiIntegrationTest +[ERROR] Tests run: 14, Failures: 0, Errors: 14, Skipped: 0, Time elapsed: 0.015 s <<< FAILURE! -- in com.example.integration.ApiIntegrationTest +[ERROR] com.example.integration.ApiIntegrationTest.shouldCreateUser -- Time elapsed: 0 s <<< ERROR! +java.lang.IllegalStateException: ApplicationContext failure threshold (1) exceeded + at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:157) + +[ERROR] com.example.integration.ApiIntegrationTest.shouldDeleteUser -- Time elapsed: 0 s <<< ERROR! +java.lang.IllegalStateException: ApplicationContext failure threshold (1) exceeded + at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:157) + +[ERROR] com.example.integration.ApiIntegrationTest.shouldUpdateUser -- Time elapsed: 0 s <<< ERROR! +java.lang.IllegalStateException: ApplicationContext failure threshold (1) exceeded + at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:157) + +[ERROR] com.example.integration.ApiIntegrationTest.shouldListUsers -- Time elapsed: 0 s <<< ERROR! +java.lang.IllegalStateException: ApplicationContext failure threshold (1) exceeded + at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:157) + +[ERROR] com.example.integration.ApiIntegrationTest.shouldGetUser -- Time elapsed: 0 s <<< ERROR! +java.lang.IllegalStateException: ApplicationContext failure threshold (1) exceeded + at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:157) + +[ERROR] com.example.integration.ApiIntegrationTest.shouldSearchUsers -- Time elapsed: 0 s <<< ERROR! +java.lang.IllegalStateException: ApplicationContext failure threshold (1) exceeded + at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:157) + +[ERROR] com.example.integration.ApiIntegrationTest.shouldPaginateUsers -- Time elapsed: 0 s <<< ERROR! +java.lang.IllegalStateException: ApplicationContext failure threshold (1) exceeded + at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:157) + +[ERROR] com.example.integration.ApiIntegrationTest.shouldFilterUsers -- Time elapsed: 0 s <<< ERROR! +java.lang.IllegalStateException: ApplicationContext failure threshold (1) exceeded + at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:157) + +[ERROR] com.example.integration.ApiIntegrationTest.shouldSortUsers -- Time elapsed: 0 s <<< ERROR! +java.lang.IllegalStateException: ApplicationContext failure threshold (1) exceeded + at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:157) + +[ERROR] com.example.integration.ApiIntegrationTest.shouldExportUsers -- Time elapsed: 0 s <<< ERROR! +java.lang.IllegalStateException: ApplicationContext failure threshold (1) exceeded + at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:157) + +[ERROR] com.example.integration.ApiIntegrationTest.shouldImportUsers -- Time elapsed: 0 s <<< ERROR! +java.lang.IllegalStateException: ApplicationContext failure threshold (1) exceeded + at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:157) + +[ERROR] com.example.integration.ApiIntegrationTest.shouldValidateInput -- Time elapsed: 0 s <<< ERROR! +java.lang.IllegalStateException: ApplicationContext failure threshold (1) exceeded + at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:157) + +[ERROR] com.example.integration.ApiIntegrationTest.shouldHandleConcurrency -- Time elapsed: 0 s <<< ERROR! +java.lang.IllegalStateException: ApplicationContext failure threshold (1) exceeded + at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:157) + +[ERROR] com.example.integration.ApiIntegrationTest.shouldRetryOnFailure -- Time elapsed: 0 s <<< ERROR! +java.lang.IllegalStateException: ApplicationContext failure threshold (1) exceeded + at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:157) + +[INFO] +[INFO] Results: +[INFO] +[ERROR] Failures: +[ERROR] SearchReadModelTest.should_get_person_ratings:85 expected size: 1 but was: 2 +[ERROR] SearchReadModelTest.should_get_company_ratings:120 expected: 42 but was: 38 +[ERROR] SearchReadModelTest.should_not_include_unknown_skills:150 expected size: 0 but was: 1 +[ERROR] Errors: +[ERROR] PatchableFieldTest.fromStrings_returns_empty >> Error Unresolved compilation problem: log cannot be resolved +[ERROR] PatchableFieldTest.fromStrings_converts_names >> Error Unresolved compilation problem: log cannot be resolved +[ERROR] PatchableFieldTest.shouldUpdate_false_when_empty >> ClassCast not an enum +[ERROR] PatchableFieldTest.shouldUpdate_true_when_null >> Error Unresolved compilation problem +[ERROR] PatchableFieldTest.shouldUpdateAny_true >> NullPointer Cannot invoke "java.lang.Enum.getDeclaringClass()" +[ERROR] PatchableFieldTest.fromStrings_ignores_unknown >> Error Unresolved compilation problem: log cannot be resolved +[ERROR] ApiIntegrationTest.shouldCreateUser >> IllegalState ApplicationContext failure threshold (1) exceeded +[ERROR] ApiIntegrationTest.shouldDeleteUser >> IllegalState ApplicationContext failure threshold (1) exceeded +[ERROR] ApiIntegrationTest.shouldUpdateUser >> IllegalState ApplicationContext failure threshold (1) exceeded +[ERROR] ApiIntegrationTest.shouldListUsers >> IllegalState ApplicationContext failure threshold (1) exceeded +[ERROR] ApiIntegrationTest.shouldGetUser >> IllegalState ApplicationContext failure threshold (1) exceeded +[ERROR] ApiIntegrationTest.shouldSearchUsers >> IllegalState ApplicationContext failure threshold (1) exceeded +[ERROR] ApiIntegrationTest.shouldPaginateUsers >> IllegalState ApplicationContext failure threshold (1) exceeded +[ERROR] ApiIntegrationTest.shouldFilterUsers >> IllegalState ApplicationContext failure threshold (1) exceeded +[ERROR] ApiIntegrationTest.shouldSortUsers >> IllegalState ApplicationContext failure threshold (1) exceeded +[ERROR] ApiIntegrationTest.shouldExportUsers >> IllegalState ApplicationContext failure threshold (1) exceeded +[ERROR] ApiIntegrationTest.shouldImportUsers >> IllegalState ApplicationContext failure threshold (1) exceeded +[ERROR] ApiIntegrationTest.shouldValidateInput >> IllegalState ApplicationContext failure threshold (1) exceeded +[ERROR] ApiIntegrationTest.shouldHandleConcurrency >> IllegalState ApplicationContext failure threshold (1) exceeded +[ERROR] ApiIntegrationTest.shouldRetryOnFailure >> IllegalState ApplicationContext failure threshold (1) exceeded +[INFO] +[ERROR] Tests run: 3262, Failures: 3, Errors: 20, Skipped: 4 +[INFO] +[INFO] -------------------------------------------------------------------------------------------------------------------------- +[INFO] BUILD FAILURE +[INFO] -------------------------------------------------------------------------------------------------------------------------- +[INFO] Total time: 03:25 min +[INFO] Finished at: 2026-04-08T21:07:54+02:00 +[INFO] -------------------------------------------------------------------------------------------------------------------------- +[ERROR] Failed to execute goal org.apache.maven.plugins:maven-surefire-plugin:3.5.4:test (default-test) on project platform: There are test failures. +[ERROR] +[ERROR] See /home/user/project/target/surefire-reports for the individual test results. +[ERROR] -> [Help 1] +[ERROR] +[ERROR] Re-run Maven using the '-X' switch to enable full debug logging. +[ERROR] +[ERROR] [Help 1] http://cwiki.apache.org/confluence/display/MAVEN/MojoFailureException diff --git a/tests/fixtures/mvn_test_many_failures.txt b/tests/fixtures/mvn_test_many_failures.txt new file mode 100644 index 000000000..a3466fff9 --- /dev/null +++ b/tests/fixtures/mvn_test_many_failures.txt @@ -0,0 +1,115 @@ +[INFO] Scanning for projects... +[WARNING] The requested profile "unit-tests" could not be activated because it does not exist. +[INFO] +[INFO] -----------------------< com.example:myapp >------------------------ +[INFO] Building myapp 0.0.1-SNAPSHOT +[INFO] from pom.xml +[INFO] --------------------------------[ jar ]--------------------------------- +[INFO] +[INFO] --- resources:3.3.1:resources (default-resources) @ myapp --- +[INFO] Copying 2 resources from src/main/resources to target/classes +[INFO] +[INFO] --- surefire:3.5.5:test (default-test) @ myapp --- +[INFO] +[INFO] ------------------------------------------------------- +[INFO] T E S T S +[INFO] ------------------------------------------------------- +[INFO] Running com.example.controller.ItemControllerTest +[ERROR] Tests run: 4, Failures: 0, Errors: 4, Skipped: 0, Time elapsed: 8.737 s <<< FAILURE! -- in com.example.controller.ItemControllerTest +[ERROR] com.example.controller.ItemControllerTest.shouldReturn404ForNonExistent -- Time elapsed: 0.003 s <<< ERROR! +java.lang.IllegalStateException: Failed to load ApplicationContext for [WebMergedContextConfiguration@7b05dab testClass = com.example.controller.ItemControllerTest, locations = [], classes = [com.example.MyApplication], contextInitializerClasses = [], activeProfiles = [], propertySourceDescriptors = [], propertySourceProperties = ["org.springframework.boot.test.context.SpringBootTestContextBootstrapper=true"]] + at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.lambda$loadContext$0(DefaultCacheAwareContextLoaderDelegate.java:195) + at org.springframework.test.context.cache.DefaultContextCache.put(DefaultContextCache.java:214) + at java.base/java.lang.reflect.Method.invoke(Method.java:565) + +[ERROR] com.example.controller.ItemControllerTest.shouldCreate -- Time elapsed: 0 s <<< ERROR! +java.lang.IllegalStateException: ApplicationContext failure threshold (1) exceeded: skipping repeated attempt to load context + at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:157) + +[ERROR] com.example.controller.ItemControllerTest.shouldUpdate -- Time elapsed: 0 s <<< ERROR! +java.lang.IllegalStateException: ApplicationContext failure threshold (1) exceeded: skipping repeated attempt to load context + at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:157) + +[ERROR] com.example.controller.ItemControllerTest.shouldDelete -- Time elapsed: 0 s <<< ERROR! +java.lang.IllegalStateException: ApplicationContext failure threshold (1) exceeded: skipping repeated attempt to load context + at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:157) + +[INFO] Running com.example.controller.OrderControllerTest +[ERROR] Tests run: 7, Failures: 0, Errors: 7, Skipped: 0, Time elapsed: 0.012 s <<< FAILURE! -- in com.example.controller.OrderControllerTest +[ERROR] com.example.controller.OrderControllerTest.shouldReturn404 -- Time elapsed: 0 s <<< ERROR! +java.lang.IllegalStateException: ApplicationContext failure threshold (1) exceeded: skipping repeated attempt to load context + at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:157) + +[ERROR] com.example.controller.OrderControllerTest.shouldUpdate -- Time elapsed: 0 s <<< ERROR! +java.lang.IllegalStateException: ApplicationContext failure threshold (1) exceeded: skipping repeated attempt to load context + at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:157) + +[ERROR] com.example.controller.OrderControllerTest.shouldCreate -- Time elapsed: 0 s <<< ERROR! +java.lang.IllegalStateException: ApplicationContext failure threshold (1) exceeded: skipping repeated attempt to load context + at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:157) + +[ERROR] com.example.controller.OrderControllerTest.shouldDelete -- Time elapsed: 0 s <<< ERROR! +java.lang.IllegalStateException: ApplicationContext failure threshold (1) exceeded: skipping repeated attempt to load context + at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:157) + +[ERROR] com.example.controller.OrderControllerTest.shouldList -- Time elapsed: 0 s <<< ERROR! +java.lang.IllegalStateException: ApplicationContext failure threshold (1) exceeded: skipping repeated attempt to load context + at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:157) + +[ERROR] com.example.controller.OrderControllerTest.shouldRejectDuplicate -- Time elapsed: 0 s <<< ERROR! +java.lang.IllegalStateException: ApplicationContext failure threshold (1) exceeded: skipping repeated attempt to load context + at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:157) + +[ERROR] com.example.controller.OrderControllerTest.shouldGetById -- Time elapsed: 0.001 s <<< ERROR! +java.lang.IllegalStateException: ApplicationContext failure threshold (1) exceeded: skipping repeated attempt to load context + at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:157) + +[INFO] Running com.example.controller.LeaseControllerTest +[ERROR] Tests run: 14, Failures: 0, Errors: 14, Skipped: 0, Time elapsed: 0.019 s <<< FAILURE! -- in com.example.controller.LeaseControllerTest +[ERROR] com.example.controller.LeaseControllerTest.shouldReturn400 -- Time elapsed: 0 s <<< ERROR! +java.lang.IllegalStateException: ApplicationContext failure threshold (1) exceeded + at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:157) + +[ERROR] com.example.controller.LeaseControllerTest.shouldList -- Time elapsed: 0 s <<< ERROR! +java.lang.IllegalStateException: ApplicationContext failure threshold (1) exceeded + at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:157) + +[ERROR] com.example.controller.LeaseControllerTest.shouldGetById -- Time elapsed: 0 s <<< ERROR! +java.lang.IllegalStateException: ApplicationContext failure threshold (1) exceeded + at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:157) + +[INFO] +[INFO] Results: +[INFO] +[ERROR] Errors: +[ERROR] ItemControllerTest.shouldReturn404ForNonExistent >> IllegalState Failed to load ApplicationContext +[ERROR] ItemControllerTest.shouldCreate >> IllegalState ApplicationContext failure threshold (1) exceeded +[ERROR] ItemControllerTest.shouldUpdate >> IllegalState ApplicationContext failure threshold (1) exceeded +[ERROR] ItemControllerTest.shouldDelete >> IllegalState ApplicationContext failure threshold (1) exceeded +[ERROR] OrderControllerTest.shouldReturn404 >> IllegalState ApplicationContext failure threshold (1) exceeded +[ERROR] OrderControllerTest.shouldUpdate >> IllegalState ApplicationContext failure threshold (1) exceeded +[ERROR] OrderControllerTest.shouldCreate >> IllegalState ApplicationContext failure threshold (1) exceeded +[ERROR] OrderControllerTest.shouldDelete >> IllegalState ApplicationContext failure threshold (1) exceeded +[ERROR] OrderControllerTest.shouldList >> IllegalState ApplicationContext failure threshold (1) exceeded +[ERROR] OrderControllerTest.shouldRejectDuplicate >> IllegalState ApplicationContext failure threshold (1) exceeded +[ERROR] OrderControllerTest.shouldGetById >> IllegalState ApplicationContext failure threshold (1) exceeded +[ERROR] LeaseControllerTest.shouldReturn400 >> IllegalState ApplicationContext failure threshold (1) exceeded +[ERROR] LeaseControllerTest.shouldList >> IllegalState ApplicationContext failure threshold (1) exceeded +[ERROR] LeaseControllerTest.shouldGetById >> IllegalState ApplicationContext failure threshold (1) exceeded +[INFO] +[ERROR] Tests run: 28, Failures: 0, Errors: 28, Skipped: 0 +[INFO] +[INFO] ------------------------------------------------------------------------ +[INFO] BUILD FAILURE +[INFO] ------------------------------------------------------------------------ +[INFO] Total time: 22.379 s +[INFO] Finished at: 2026-04-08T20:58:01+02:00 +[INFO] ------------------------------------------------------------------------ +[ERROR] Failed to execute goal org.apache.maven.plugins:maven-surefire-plugin:3.5.5:test (default-test) on project myapp: There are test failures. +[ERROR] +[ERROR] See /home/user/project/target/surefire-reports for the individual test results. +[ERROR] -> [Help 1] +[ERROR] +[ERROR] Re-run Maven using the '-X' switch to enable full debug logging. +[ERROR] +[ERROR] [Help 1] http://cwiki.apache.org/confluence/display/MAVEN/MojoFailureException diff --git a/tests/fixtures/mvn_test_multimodule.txt b/tests/fixtures/mvn_test_multimodule.txt new file mode 100644 index 000000000..ab3ea3404 --- /dev/null +++ b/tests/fixtures/mvn_test_multimodule.txt @@ -0,0 +1,118 @@ +[INFO] Scanning for projects... +[INFO] Building parent 1.0-SNAPSHOT +[INFO] --------------------------------[ pom ]--------------------------------- +[INFO] +[INFO] --- maven-surefire-plugin:3.1.2:test (default-test) @ common --- +[INFO] +[INFO] ------------------------------------------------------- +[INFO] T E S T S +[INFO] ------------------------------------------------------- +[INFO] Running com.example.common.PathUtilsTest +[INFO] Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.056 s -- in com.example.common.PathUtilsTest +[INFO] Tests run: 10, Failures: 0, Errors: 0, Skipped: 0 +[INFO] +[INFO] Results: +[INFO] +[INFO] Tests run: 10, Failures: 0, Errors: 0, Skipped: 0 +[INFO] +[INFO] --- maven-surefire-plugin:3.1.2:test (default-test) @ data --- +[INFO] +[INFO] ------------------------------------------------------- +[INFO] T E S T S +[INFO] ------------------------------------------------------- +[INFO] Running com.example.data.RepositoryTest +[INFO] Tests run: 34, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.036 s -- in com.example.data.RepositoryTest +[INFO] Running com.example.data.ModelTest +[INFO] Tests run: 25, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.370 s -- in com.example.data.ModelTest +[INFO] Tests run: 194, Failures: 0, Errors: 0, Skipped: 0 +[INFO] +[INFO] Results: +[INFO] +[INFO] Tests run: 194, Failures: 0, Errors: 0, Skipped: 0 +[INFO] +[INFO] --- maven-surefire-plugin:3.1.2:test (default-test) @ ml --- +[INFO] +[INFO] ------------------------------------------------------- +[INFO] T E S T S +[INFO] ------------------------------------------------------- +[INFO] Running com.example.ml.BenchmarkTest +[INFO] Tests run: 5, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.632 s -- in com.example.ml.BenchmarkTest +[INFO] Tests run: 5, Failures: 0, Errors: 0, Skipped: 0 +[INFO] +[INFO] Results: +[INFO] +[INFO] Tests run: 5, Failures: 0, Errors: 0, Skipped: 0 +[INFO] +[INFO] --- maven-surefire-plugin:3.1.2:test (default-test) @ services --- +[INFO] +[INFO] ------------------------------------------------------- +[INFO] T E S T S +[INFO] ------------------------------------------------------- +[INFO] Running com.example.services.FileHasherTest +[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0 s -- in com.example.services.FileHasherTest +[INFO] Running com.example.services.GitDiffReaderTest +[ERROR] Tests run: 4, Failures: 0, Errors: 4, Skipped: 0, Time elapsed: 3.167 s <<< FAILURE! -- in com.example.services.GitDiffReaderTest +[ERROR] com.example.services.GitDiffReaderTest.shouldBuildDiffWithFiltering -- Time elapsed: 3.109 s <<< ERROR! +org.eclipse.jgit.api.errors.ServiceUnavailableException: Signing service is not available + at org.eclipse.jgit.api.CommitCommand.sign(CommitCommand.java:328) + at org.eclipse.jgit.api.CommitCommand.call(CommitCommand.java:283) + at com.example.services.GitDiffReaderTest.shouldBuildDiffWithFiltering(GitDiffReaderTest.java:116) + at java.base/java.lang.reflect.Method.invoke(Method.java:565) + +[ERROR] com.example.services.GitDiffReaderTest.shouldReturnEmptyDiff -- Time elapsed: 0.013 s <<< ERROR! +org.eclipse.jgit.api.errors.ServiceUnavailableException: Signing service is not available + at org.eclipse.jgit.api.CommitCommand.sign(CommitCommand.java:328) + at org.eclipse.jgit.api.CommitCommand.call(CommitCommand.java:283) + at com.example.services.GitDiffReaderTest.shouldReturnEmptyDiff(GitDiffReaderTest.java:67) + at java.base/java.lang.reflect.Method.invoke(Method.java:565) + +[ERROR] com.example.services.GitDiffReaderTest.shouldBuildDiff -- Time elapsed: 0.012 s <<< ERROR! +org.eclipse.jgit.api.errors.ServiceUnavailableException: Signing service is not available + at org.eclipse.jgit.api.CommitCommand.sign(CommitCommand.java:328) + at com.example.services.GitDiffReaderTest.shouldBuildDiff(GitDiffReaderTest.java:35) + +[ERROR] com.example.services.GitDiffReaderTest.shouldBuildDiffForDevops -- Time elapsed: 0.011 s <<< ERROR! +org.eclipse.jgit.api.errors.ServiceUnavailableException: Signing service is not available + at org.eclipse.jgit.api.CommitCommand.sign(CommitCommand.java:328) + at com.example.services.GitDiffReaderTest.shouldBuildDiffForDevops(GitDiffReaderTest.java:88) + +[INFO] Running com.example.services.IoHelperTest +[INFO] Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0 s -- in com.example.services.IoHelperTest +[INFO] Tests run: 651, Failures: 0, Errors: 4, Skipped: 4 +[INFO] +[INFO] Results: +[INFO] +[ERROR] Errors: +[ERROR] GitDiffReaderTest.shouldBuildDiff:35 >> ServiceUnavailable Signing service is not available +[ERROR] GitDiffReaderTest.shouldBuildDiffForDevops:88 >> ServiceUnavailable Signing service is not available +[ERROR] GitDiffReaderTest.shouldBuildDiffWithFiltering:116 >> ServiceUnavailable Signing service is not available +[ERROR] GitDiffReaderTest.shouldReturnEmptyDiff:67 >> ServiceUnavailable Signing service is not available +[INFO] +[ERROR] Tests run: 860, Failures: 0, Errors: 4, Skipped: 4 +[INFO] +[INFO] -------------------------------------------------------------------------------------------------------------------------- +[INFO] Reactor Summary for parent 1.0-SNAPSHOT: +[INFO] +[INFO] parent ............................................................................................... SUCCESS [ 0.189 s] +[INFO] common ............................................................................................... SUCCESS [ 0.959 s] +[INFO] data ................................................................................................. SUCCESS [ 28.127 s] +[INFO] ml ................................................................................................... SUCCESS [ 2.214 s] +[INFO] services ............................................................................................. FAILURE [ 58.575 s] +[INFO] integrations ......................................................................................... SKIPPED +[INFO] webapp ............................................................................................... SKIPPED +[INFO] -------------------------------------------------------------------------------------------------------------------------- +[INFO] BUILD FAILURE +[INFO] -------------------------------------------------------------------------------------------------------------------------- +[INFO] Total time: 01:31 min +[INFO] Finished at: 2026-04-08T20:45:10+02:00 +[INFO] -------------------------------------------------------------------------------------------------------------------------- +[ERROR] Failed to execute goal org.apache.maven.plugins:maven-surefire-plugin:3.1.2:test (default-test) on project services: There are test failures. +[ERROR] +[ERROR] See /home/user/project/services/target/surefire-reports for the individual test results. +[ERROR] -> [Help 1] +[ERROR] +[ERROR] To see the full stack trace of the errors, re-run Maven with the '-e' switch +[ERROR] Re-run Maven using the '-X' switch to enable verbose output +[ERROR] +[ERROR] For more information about the errors and possible solutions, please read the following articles: +[ERROR] [Help 1] http://cwiki.apache.org/confluence/display/MAVEN/MojoFailureException diff --git a/tests/fixtures/mvn_test_pass_large_ansi.txt b/tests/fixtures/mvn_test_pass_large_ansi.txt new file mode 100644 index 000000000..93e3ac76e --- /dev/null +++ b/tests/fixtures/mvn_test_pass_large_ansi.txt @@ -0,0 +1,53 @@ +[INFO] Loaded 22505 auto-discovered prefixes for remote repository central (prefixes-central.txt) +[INFO] Scanning for projects... +[WARNING] Could not transfer metadata /.meta/prefixes.txt from/to shibboleth-releases-5d99c0486312ae43e4bc39d107c6522d62203c00 (https://build.shibboleth.net/nexus/content/repositories/releases/): Checksum validation failed, no checksums available +[INFO] -------------------------------------------------< com.example:auth >-------------------------------------------------- +[INFO] Building auth 1.3-SNAPSHOT +[INFO] from pom.xml +[INFO] ---------------------------------------------------------[ jar ]---------------------------------------------------------- +[INFO] Loaded 22505 auto-discovered prefixes for remote repository central (prefixes-central.txt) +[INFO] Image name substitution will be performed by: DefaultImageNameSubstitutor (composite of 'ConfigurationFileImageNameSubstitutor' and 'PrefixingImageNameSubstitutor') +[INFO] Found Docker environment with local Unix socket (unix:///var/run/docker.sock) +[INFO] Docker host IP address is localhost +[INFO] Creating container for image: testcontainers/ryuk:0.14.0 +[INFO] Container testcontainers/ryuk:0.14.0 started in PT0.44146219S +[INFO] ✔︎ Docker server version should be at least 1.6.0 +[INFO] Container postgres:17 started in PT5.716666928S +[WARNING] [stderr] Apr 08, 2026 9:25:10 PM liquibase.database +[WARNING] [stderr] Apr 08, 2026 9:25:10 PM liquibase.changelog +[WARNING] [stderr] INFO: Reading resource: db/changelog/2024/db.changelog.2024-1719408042.sql +[WARNING] [stderr] Apr 08, 2026 9:25:10 PM liquibase.changelog +[INFO] ------------------------------------------------------- +[INFO] T E S T S +[INFO] ------------------------------------------------------- +[INFO] Running com.example.webapp.user.PermissionCompareTest +[INFO] Running com.example.webapp.user.ModifyTeamTest +[INFO] Tests run: 4, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.044 s -- in com.example.webapp.user.PermissionCompareTest +[INFO] Tests run: 8, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.065 s -- in com.example.webapp.user.PermissionGrouperTest +[INFO] Running com.example.webapp.user.PermissionProcessorTest +[INFO] Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.004 s -- in com.example.webapp.user.PermissionProcessorTest +[INFO] Running com.example.webapp.user.ProductHelperTest +[INFO] Tests run: 3, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.003 s -- in com.example.webapp.user.ProductHelperTest +[INFO] Running com.example.webapp.user.RelayStateUtilTest +[INFO] Tests run: 5, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.015 s -- in com.example.webapp.user.RelayStateUtilTest +[INFO] [stdout] Running Changeset: db/changelog/2024/db.changelog.2024-1719408042.sql::dev2:1719408042-1::dev2 +[INFO] [stdout] Running Changeset: db/changelog/2024/db.changelog.2024-1719478245.yaml::1719478245-1::dev1 +[INFO] [stdout] Running Changeset: db/changelog/2024/db.changelog.2024-1719571809.yaml::1719571809-1::dev1 +[INFO] [stdout] Running Changeset: db/changelog/2024/db.changelog.2024-1721123826.yaml::1721123826-1::dev1 +[INFO] [stdout] Running Changeset: db/changelog/2024/db.changelog.2024-1726125249.yaml::1726125249-1::dev1 +[INFO] [stdout] Running Changeset: db/changelog/2024/db.changelog.2024-1726133977.yaml::1726133977-1::dev2 +[INFO] [stdout] Running Changeset: db/changelog/2024/db.changelog.2024-1726647573.yaml::1726647573-1::dev1 +[INFO] [stdout] Running Changeset: db/changelog/2024/db.changelog.2024-1727864000.sql::1727864000-1::dev3 +[INFO] [stdout] Running Changeset: db/changelog/2024/db.changelog.2024-1727939482.yaml::1727939482-1::dev1 +[INFO] [stdout] Running Changeset: db/changelog/2024/db.changelog.2024-1728049660.yaml::1728049660-1::dev1 +[INFO] Tests run: 61, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 18.44 s -- in com.example.webapp.scim.ScimUserControllerIntegrationTest +[INFO] Results: +[INFO] +[INFO] Tests run: 959, Failures: 0, Errors: 0, Skipped: 9 +[INFO] +[INFO] -------------------------------------------------------------------------------------------------------------------------- +[INFO] BUILD SUCCESS +[INFO] -------------------------------------------------------------------------------------------------------------------------- +[INFO] Total time: 01:32 min +[INFO] Finished at: 2026-04-08T21:26:30+02:00 +[INFO] -------------------------------------------------------------------------------------------------------------------------- diff --git a/tests/fixtures/mvn_test_pass_mavenmcp.txt b/tests/fixtures/mvn_test_pass_mavenmcp.txt new file mode 100644 index 000000000..beb535770 --- /dev/null +++ b/tests/fixtures/mvn_test_pass_mavenmcp.txt @@ -0,0 +1,35 @@ +[INFO] Scanning for projects... +[INFO] +[INFO] -------------------< com.example:my-app >-------------------- +[INFO] Building my-app 1.0-SNAPSHOT +[INFO] from pom.xml +[INFO] --------------------------------[ jar ]--------------------------------- +[INFO] +[INFO] --- maven-compiler-plugin:3.13.0:compile (default-compile) --- +[INFO] Nothing to compile - all classes are up to date. +[INFO] +[INFO] --- maven-surefire-plugin:3.5.4:test (default-test) --- +[INFO] Using auto detected provider org.apache.maven.surefire.junitplatform.JUnitPlatformProvider +[INFO] +[INFO] ------------------------------------------------------- +[INFO] T E S T S +[INFO] ------------------------------------------------------- +[INFO] Running com.example.config.AppConfigTest +[INFO] Tests run: 3, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.047 s -- in com.example.config.AppConfigTest +[INFO] Running com.example.service.UserServiceTest +[INFO] Tests run: 12, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.009 s -- in com.example.service.UserServiceTest +[INFO] Running com.example.controller.ApiControllerTest +[INFO] Tests run: 8, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 1.325 s -- in com.example.controller.ApiControllerTest +[INFO] Running com.example.repository.ItemRepositoryTest +[INFO] Tests run: 5, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 2.060 s -- in com.example.repository.ItemRepositoryTest +[INFO] +[INFO] Results: +[INFO] +[INFO] Tests run: 183, Failures: 0, Errors: 0, Skipped: 0 +[INFO] +[INFO] ------------------------------------------------------------------------ +[INFO] BUILD SUCCESS +[INFO] ------------------------------------------------------------------------ +[INFO] Total time: 4.748 s +[INFO] Finished at: 2026-04-08T18:26:55+02:00 +[INFO] ------------------------------------------------------------------------ diff --git a/tests/fixtures/mvn_verify_auth.txt b/tests/fixtures/mvn_verify_auth.txt new file mode 100644 index 000000000..5d44b1539 --- /dev/null +++ b/tests/fixtures/mvn_verify_auth.txt @@ -0,0 +1,50 @@ +[INFO] Scanning for projects... +[INFO] +[INFO] --- surefire:3.5.5:test (default-test) @ app --- +[INFO] +[INFO] ------------------------------------------------------- +[INFO] T E S T S +[INFO] ------------------------------------------------------- +[INFO] Running com.example.app.AlphaTest +[INFO] Tests run: 4, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.051 s -- in com.example.app.AlphaTest +[INFO] Running com.example.app.BetaTest +[INFO] Tests run: 12, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.020 s -- in com.example.app.BetaTest +[INFO] Running com.example.app.GammaTest +[INFO] Tests run: 8, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.595 s -- in com.example.app.GammaTest +[WARNING] Tests run: 8, Failures: 0, Errors: 0, Skipped: 8, Time elapsed: 0 s -- in com.example.app.SkippedSuiteTest +[INFO] Running com.example.app.DeltaTest +[INFO] Tests run: 656, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.221 s -- in com.example.app.DeltaTest +[INFO] +[INFO] Results: +[INFO] +[INFO] Tests run: 688, Failures: 0, Errors: 0, Skipped: 8 +[INFO] +[INFO] --- spring-boot:4.0.5:repackage (repackage) @ app --- +[INFO] Replacing main artifact /build/app.jar with repackaged archive +[INFO] +[INFO] --- failsafe:3.5.5:integration-test (default) @ app --- +[INFO] Using auto detected provider org.apache.maven.surefire.junitplatform.JUnitPlatformProvider +[INFO] +[INFO] ------------------------------------------------------- +[INFO] T E S T S +[INFO] ------------------------------------------------------- +[INFO] Running com.example.app.AuthenticationTest +[INFO] Tests run: 9, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 17.19 s -- in com.example.app.AuthenticationTest +[INFO] Running com.example.app.ProvisioningTest +[INFO] Tests run: 13, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 4.23 s -- in com.example.app.ProvisioningTest +[INFO] Running com.example.app.ScimTest +[WARNING] Tests run: 1, Failures: 0, Errors: 0, Skipped: 1, Time elapsed: 0 s -- in com.example.app.ScimTest +[INFO] Running com.example.app.E2EFlowTest +[INFO] Tests run: 239, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 31.44 s -- in com.example.app.E2EFlowTest +[INFO] +[INFO] Results: +[INFO] +[INFO] Tests run: 262, Failures: 0, Errors: 0, Skipped: 1 +[INFO] +[INFO] --- failsafe:3.5.5:verify (default) @ app --- +[INFO] -------------------------------------------------------------------------------------------------------------------------- +[INFO] BUILD SUCCESS +[INFO] -------------------------------------------------------------------------------------------------------------------------- +[INFO] Total time: 02:11 min +[INFO] Finished at: 2026-04-14T18:32:59+02:00 +[INFO] --------------------------------------------------------------------------------------------------------------------------