diff --git a/CHANGELOG.md b/CHANGELOG.md index 438b2859..88ad3dde 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,21 @@ All notable changes to rtk (Rust Token Killer) will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [Unreleased] + +### Bug Fixes + +* **hook:** respect Claude Code deny/ask permission rules on rewrite — hook now checks settings.json before rewriting commands, preventing bypass of user-configured deny/ask permissions +* **git:** replace symbol prefixes (`* branch`, `+ Staged:`, `~ Modified:`, `? Untracked:`) with plain lowercase labels (`branch:`, `staged:`, `modified:`, `untracked:`) in git status output + +### Features + +* **gradle**: add Gradle command support with sub-enum routing (test/build/other) — 75-90% token savings +* **maven**: add Maven command support with Surefire test parser (test/compile/package/other) — 70-90% token savings +* **gradle**: auto-detect ./gradlew wrapper with fallback to gradle on PATH +* **maven**: intelligent Surefire output parsing — failures only with compact error messages +* **discover**: add Gradle/Maven rewrite rules for transparent hook integration + ## [0.33.0-rc.54](https://github.com/rtk-ai/rtk/compare/v0.32.0-rc.54...v0.33.0-rc.54) (2026-03-24) @@ -38,33 +53,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * **ruby:** use rails test for positional file args in rtk rake ([138e914](https://github.com/rtk-ai/rtk/commit/138e91411b4802e445a97429056cca73282d09e1)) * update Discord invite link ([#711](https://github.com/rtk-ai/rtk/issues/711)) ([#786](https://github.com/rtk-ai/rtk/issues/786)) ([af56573](https://github.com/rtk-ai/rtk/commit/af56573ae2b234123e4685fd945980e644f40fa3)) -## [Unreleased] - -### Bug Fixes - -* **hook:** respect Claude Code deny/ask permission rules on rewrite — hook now checks settings.json before rewriting commands, preventing bypass of user-configured deny/ask permissions -* **git:** replace symbol prefixes (`* branch`, `+ Staged:`, `~ Modified:`, `? Untracked:`) with plain lowercase labels (`branch:`, `staged:`, `modified:`, `untracked:`) in git status output -* **ruby:** use `rails test` instead of `rake test` when positional file args are passed — `rake test` ignores positional files and only supports `TEST=path` - -### Features - -* **ruby:** add RSpec test runner filter with JSON parsing and text fallback (60%+ reduction) -* **ruby:** add RuboCop linter filter with JSON parsing, grouped by cop/severity (60%+ reduction) -* **ruby:** add Minitest filter for `rake test` / `rails test` with state machine parser (85-90% reduction) -* **ruby:** add TOML filter for `bundle install/update` — strip `Using` lines (90%+ reduction) -* **ruby:** add `ruby_exec()` shared utility for auto-detecting `bundle exec` when Gemfile exists -* **ruby:** add discover/rewrite rules for rake, rails, rspec, rubocop, and bundle commands - -### Bug Fixes - -* **cargo:** preserve compile diagnostics when `cargo test` fails before any test suites run -## [0.31.0](https://github.com/rtk-ai/rtk/compare/v0.30.1...v0.31.0) (2026-03-19) - - -### Features - -* 9-tool AI agent support + emoji removal ([#704](https://github.com/rtk-ai/rtk/issues/704)) ([737dada](https://github.com/rtk-ai/rtk/commit/737dada4a56c0d7a482cc438e7280340d634f75d)) - ## [0.30.1](https://github.com/rtk-ai/rtk/compare/v0.30.0...v0.30.1) (2026-03-18) diff --git a/src/discover/rules.rs b/src/discover/rules.rs index 44f19d60..8c421713 100644 --- a/src/discover/rules.rs +++ b/src/discover/rules.rs @@ -68,7 +68,8 @@ pub const PATTERNS: &[&str] = &[ r"^make\b", r"^markdownlint\b", r"^mix\s+(compile|format)(\s|$)", - r"^mvn\s+(compile|package|clean|install)\b", + r"^mvn\s+(compile|package|clean|install|test|verify)\b", + r"^(gradle|gradlew|\./gradlew)\s+", r"^ping\b", r"^pio\s+run", r"^poetry\s+(install|lock|update)\b", @@ -511,8 +512,16 @@ pub const RULES: &[RtkRule] = &[ rtk_cmd: "rtk mvn", rewrite_prefixes: &["mvn"], category: "Build", - savings_pct: 70.0, - subcmd_savings: &[], + savings_pct: 75.0, + subcmd_savings: &[("test", 88.0), ("compile", 75.0), ("package", 75.0)], + subcmd_status: &[], + }, + RtkRule { + rtk_cmd: "rtk gradle", + rewrite_prefixes: &["gradle", "./gradlew", "gradlew"], + category: "Build", + savings_pct: 80.0, + subcmd_savings: &[("test", 88.0), ("build", 80.0)], subcmd_status: &[], }, RtkRule { diff --git a/src/gradle_cmd.rs b/src/gradle_cmd.rs new file mode 100644 index 00000000..102a6e69 --- /dev/null +++ b/src/gradle_cmd.rs @@ -0,0 +1,576 @@ +use crate::tracking; +use crate::utils::{resolved_command, truncate}; +use anyhow::{Context, Result}; +use lazy_static::lazy_static; +use regex::Regex; +use std::ffi::OsString; + +lazy_static! { + // Lines to strip (noise) + static ref NOISE_PATTERNS: Vec = vec![ + Regex::new(r"^Downloading https://").unwrap(), + Regex::new(r"^Download https://").unwrap(), + Regex::new(r"^\.+\d+%").unwrap(), + Regex::new(r"^Welcome to Gradle").unwrap(), + Regex::new(r"^Here are the highlights").unwrap(), + Regex::new(r"^ - ").unwrap(), + Regex::new(r"^For more details see https://docs\.gradle\.org").unwrap(), + Regex::new(r"^Starting a Gradle Daemon").unwrap(), + Regex::new(r"^Daemon will be stopped").unwrap(), + Regex::new(r"^> Configure project").unwrap(), + Regex::new(r"^> Resolving dependencies").unwrap(), + Regex::new(r"^> Transform ").unwrap(), + Regex::new(r"^> Task :\S+ UP-TO-DATE$").unwrap(), + Regex::new(r"^> Task :\S+ NO-SOURCE$").unwrap(), + Regex::new(r"^> Task :\S+ FROM-CACHE$").unwrap(), + Regex::new(r"^\s*<-+>\s*$").unwrap(), + Regex::new(r"^Note: .*(deprecated|Recompile with)").unwrap(), + ]; + + // Gradle test result line: "ClassName > methodName PASSED/FAILED" + static ref TEST_RESULT_RE: Regex = + Regex::new(r"^(\S+)\s+>\s+(\S+)\s+(PASSED|FAILED)\s*$").unwrap(); + + // Gradle test summary: "N tests completed, M failed" + static ref TEST_SUMMARY_RE: Regex = + Regex::new(r"^(\d+)\s+tests?\s+completed,\s+(\d+)\s+failed").unwrap(); + + // Stack trace line (indented, starts with "at ") + static ref STACK_TRACE_RE: Regex = + Regex::new(r"^\s+at\s+").unwrap(); + + // Assertion/error line + static ref ERROR_LINE_RE: Regex = + Regex::new(r"(?i)(error|exception|assert|expected|but was)").unwrap(); + + // Compiler error line (file:line: error:) + static ref COMPILER_ERROR_RE: Regex = + Regex::new(r"^.+\.java:\d+:.*error:").unwrap(); +} + +/// Detect whether to use ./gradlew or gradle +fn gradle_command() -> std::process::Command { + // Prefer ./gradlew wrapper if it exists in current directory + let gradlew = if cfg!(windows) { + "gradlew.bat" + } else { + "./gradlew" + }; + + if std::path::Path::new(gradlew).exists() { + resolved_command(gradlew) + } else { + resolved_command("gradle") + } +} + +pub fn run_build(args: &[String], verbose: u8) -> Result<()> { + let timer = tracking::TimedExecution::start(); + + let mut cmd = gradle_command(); + cmd.arg("build"); + + for arg in args { + cmd.arg(arg); + } + + if verbose > 0 { + eprintln!("Running: gradle build {}", args.join(" ")); + } + + let output = cmd + .output() + .context("Failed to run gradle. Is Gradle installed? Check ./gradlew or gradle on PATH")?; + + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + let raw = format!("{}\n{}", stdout, stderr); + + let exit_code = output + .status + .code() + .unwrap_or(if output.status.success() { 0 } else { 1 }); + let filtered = filter_gradle_build(&raw); + + if let Some(hint) = crate::tee::tee_and_hint(&raw, "gradle_build", exit_code) { + println!("{}\n{}", filtered, hint); + } else { + println!("{}", filtered); + } + + timer.track( + &format!("gradle build {}", args.join(" ")), + &format!("rtk gradle build {}", args.join(" ")), + &raw, + &filtered, + ); + + if !output.status.success() { + std::process::exit(exit_code); + } + + Ok(()) +} + +pub fn run_test(args: &[String], verbose: u8) -> Result<()> { + let timer = tracking::TimedExecution::start(); + + let mut cmd = gradle_command(); + cmd.arg("test"); + + for arg in args { + cmd.arg(arg); + } + + if verbose > 0 { + eprintln!("Running: gradle test {}", args.join(" ")); + } + + let output = cmd + .output() + .context("Failed to run gradle test. Is Gradle installed?")?; + + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + let raw = format!("{}\n{}", stdout, stderr); + + let exit_code = output + .status + .code() + .unwrap_or(if output.status.success() { 0 } else { 1 }); + let filtered = filter_gradle_test(&raw); + + if let Some(hint) = crate::tee::tee_and_hint(&raw, "gradle_test", exit_code) { + println!("{}\n{}", filtered, hint); + } else { + println!("{}", filtered); + } + + timer.track( + &format!("gradle test {}", args.join(" ")), + &format!("rtk gradle test {}", args.join(" ")), + &raw, + &filtered, + ); + + if !output.status.success() { + std::process::exit(exit_code); + } + + Ok(()) +} + +pub fn run_other(args: &[OsString], verbose: u8) -> Result<()> { + if args.is_empty() { + anyhow::bail!("gradle: no subcommand specified"); + } + + let timer = tracking::TimedExecution::start(); + + let subcommand = args[0].to_string_lossy(); + let mut cmd = gradle_command(); + cmd.arg(&*subcommand); + + for arg in &args[1..] { + cmd.arg(arg); + } + + if verbose > 0 { + eprintln!("Running: gradle {} ...", subcommand); + } + + let output = cmd + .output() + .with_context(|| format!("Failed to run gradle {}", subcommand))?; + + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + let raw = format!("{}\n{}", stdout, stderr); + + // Apply basic noise stripping for any subcommand + let filtered = filter_gradle_build(&raw); + println!("{}", filtered); + + timer.track( + &format!("gradle {}", subcommand), + &format!("rtk gradle {}", subcommand), + &raw, + &filtered, + ); + + if !output.status.success() { + std::process::exit(output.status.code().unwrap_or(1)); + } + + Ok(()) +} + +/// Filter Gradle build output: strip noise, keep errors and summary +fn filter_gradle_build(output: &str) -> String { + let mut result_lines: Vec = Vec::new(); + + for line in output.lines() { + let trimmed = line.trim(); + + // Skip empty lines + if trimmed.is_empty() { + continue; + } + + // Skip noise patterns + if is_noise_line(trimmed) { + continue; + } + + // Keep task lines that actually ran (not UP-TO-DATE/NO-SOURCE/FROM-CACHE) + // Keep error lines, build status, task counts + result_lines.push(truncate(trimmed, 150).to_string()); + } + + if result_lines.is_empty() { + return "Gradle build: ok".to_string(); + } + + result_lines.join("\n") +} + +/// Filter Gradle test output: show only failures and summary +fn filter_gradle_test(output: &str) -> String { + let mut passed: usize = 0; + let mut failed: usize = 0; + let mut failures: Vec = Vec::new(); + let mut current_failure: Option = None; + let mut in_failure_block = false; + let mut has_build_failure = false; + let mut build_errors: Vec = Vec::new(); + let mut time_str = String::new(); + + for line in output.lines() { + let trimmed = line.trim(); + + // Extract timing from BUILD line + if trimmed.starts_with("BUILD SUCCESSFUL in") || trimmed.starts_with("BUILD FAILED in") { + if let Some(pos) = trimmed.rfind(" in ") { + time_str = trimmed[pos + 4..].to_string(); + } + if trimmed.contains("FAILED") { + has_build_failure = true; + } + continue; + } + + // Capture test summary line + if let Some(caps) = TEST_SUMMARY_RE.captures(trimmed) { + let total: usize = caps[1].parse().unwrap_or(0); + let fail_count: usize = caps[2].parse().unwrap_or(0); + passed = total.saturating_sub(fail_count); + failed = fail_count; + continue; + } + + // Capture individual test results + if let Some(caps) = TEST_RESULT_RE.captures(trimmed) { + let class = caps[1].to_string(); + let method = caps[2].to_string(); + let status = &caps[3]; + + if status == "FAILED" { + // Save previous failure if any + if let Some(f) = current_failure.take() { + failures.push(f); + } + current_failure = Some(TestFailure { + class: compact_class_name(&class), + method, + message: String::new(), + location: String::new(), + }); + in_failure_block = true; + } else { + in_failure_block = false; + } + continue; + } + + // Inside a failure block, capture error details + if in_failure_block { + if let Some(ref mut f) = current_failure { + if ERROR_LINE_RE.is_match(trimmed) && f.message.is_empty() { + f.message = truncate(trimmed, 120).to_string(); + } else if STACK_TRACE_RE.is_match(trimmed) && f.location.is_empty() { + // Extract just the location (first non-framework stack line) + if !trimmed.contains("org.junit.") + && !trimmed.contains("java.base/") + && !trimmed.contains("jdk.internal") + { + f.location = trimmed.trim_start_matches("at ").trim().to_string(); + } + } + } + } + + // Capture compiler errors (build failures, not test failures) + if COMPILER_ERROR_RE.is_match(trimmed) { + build_errors.push(truncate(trimmed, 120).to_string()); + } + + // Capture FAILURE explanation blocks + if trimmed.starts_with("* What went wrong:") + || trimmed.starts_with("Execution failed for task") + { + has_build_failure = true; + } + } + + // Save last failure + if let Some(f) = current_failure.take() { + failures.push(f); + } + + // Build output + let total = passed + failed; + + // If no tests were found but there were build errors + if total == 0 && !build_errors.is_empty() { + let mut result = format!("Gradle: {} build errors\n", build_errors.len()); + result.push_str("=======================================\n"); + for error in build_errors.iter().take(10) { + result.push_str(&format!(" {}\n", error)); + } + if build_errors.len() > 10 { + result.push_str(&format!(" ... +{} more errors\n", build_errors.len() - 10)); + } + return result.trim().to_string(); + } + + // All passed + if failed == 0 && !has_build_failure { + let time_info = if time_str.is_empty() { + String::new() + } else { + format!(" ({})", time_str) + }; + return format!("Gradle test: {} passed{}", total, time_info); + } + + // Has failures + let time_info = if time_str.is_empty() { + String::new() + } else { + format!(" ({})", time_str) + }; + + let mut result = format!("FAILED: {}/{} tests{}\n", failed, total, time_info); + result.push_str("=======================================\n"); + + for f in &failures { + result.push_str(&format!(" {} > {}() FAILED\n", f.class, f.method)); + if !f.message.is_empty() { + result.push_str(&format!(" {}\n", f.message)); + } + if !f.location.is_empty() { + result.push_str(&format!(" at {}\n", truncate(&f.location, 100))); + } + } + + if has_build_failure { + result.push_str("\nBUILD FAILED"); + } + + result.trim().to_string() +} + +struct TestFailure { + class: String, + method: String, + message: String, + location: String, +} + +/// Check if a line matches any noise pattern +fn is_noise_line(line: &str) -> bool { + NOISE_PATTERNS.iter().any(|re| re.is_match(line)) +} + +/// Compact class name: "com.edeal.frontline.UserServiceTest" -> "UserServiceTest" +fn compact_class_name(class: &str) -> String { + if let Some(pos) = class.rfind('.') { + class[pos + 1..].to_string() + } else { + class.to_string() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + fn count_tokens(text: &str) -> usize { + text.split_whitespace().count() + } + + // ============================================================ + // Build filter tests + // ============================================================ + + #[test] + fn test_filter_gradle_build_success() { + let input = include_str!("../tests/fixtures/gradle_build_success_raw.txt"); + let output = filter_gradle_build(input); + + // Should strip noise (downloads, UP-TO-DATE, Welcome, Daemon) + assert!(!output.contains("Downloading https://")); + assert!(!output.contains("Welcome to Gradle")); + assert!(!output.contains("Starting a Gradle Daemon")); + assert!(!output.contains("UP-TO-DATE")); + assert!(!output.contains("NO-SOURCE")); + assert!(!output.contains("Configure project")); + + // Should keep build result + assert!(output.contains("BUILD SUCCESSFUL")); + assert!(output.contains("14 actionable tasks")); + } + + #[test] + fn test_filter_gradle_build_fail() { + let input = include_str!("../tests/fixtures/gradle_build_fail_raw.txt"); + let output = filter_gradle_build(input); + + // Should keep error details + assert!(output.contains("FAILED")); + assert!(output.contains("cannot find symbol")); + assert!(output.contains("error")); + + // Should strip noise + assert!(!output.contains("Starting a Gradle Daemon")); + assert!(!output.contains("UP-TO-DATE")); + } + + #[test] + fn test_filter_gradle_build_empty() { + let output = filter_gradle_build(""); + assert_eq!(output, "Gradle build: ok"); + } + + #[test] + fn test_filter_gradle_build_savings() { + let input = include_str!("../tests/fixtures/gradle_build_success_raw.txt"); + let output = filter_gradle_build(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, + "Gradle build filter: expected >=60% savings, got {:.1}% ({} -> {} tokens)", + savings, + input_tokens, + output_tokens + ); + } + + // ============================================================ + // Test filter tests + // ============================================================ + + #[test] + fn test_filter_gradle_test_all_pass() { + let input = include_str!("../tests/fixtures/gradle_test_pass_raw.txt"); + let output = filter_gradle_test(input); + + assert!(output.contains("Gradle test:")); + assert!(output.contains("20 passed")); + assert!(!output.contains("FAILED")); + } + + #[test] + fn test_filter_gradle_test_with_failures() { + let input = include_str!("../tests/fixtures/gradle_test_fail_raw.txt"); + let output = filter_gradle_test(input); + + assert!(output.contains("FAILED: 2/20")); + assert!(output.contains("testUpdateUserProfile")); + assert!(output.contains("testAuthRequired")); + // Should contain error messages + assert!( + output.contains("Expected user name") + || output.contains("AssertionError") + || output.contains("expected") + ); + } + + #[test] + fn test_filter_gradle_test_savings_pass() { + let input = include_str!("../tests/fixtures/gradle_test_pass_raw.txt"); + let output = filter_gradle_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 >= 85.0, + "Gradle test (pass) filter: expected >=85% savings, got {:.1}% ({} -> {} tokens)", + savings, + input_tokens, + output_tokens + ); + } + + #[test] + fn test_filter_gradle_test_savings_fail() { + let input = include_str!("../tests/fixtures/gradle_test_fail_raw.txt"); + let output = filter_gradle_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 >= 70.0, + "Gradle test (fail) filter: expected >=70% savings, got {:.1}% ({} -> {} tokens)", + savings, + input_tokens, + output_tokens + ); + } + + #[test] + fn test_filter_gradle_test_empty() { + let output = filter_gradle_test(""); + // No tests found, no build errors => all-pass with 0 total + assert!(output.contains("Gradle test: 0 passed")); + } + + // ============================================================ + // Utility tests + // ============================================================ + + #[test] + fn test_compact_class_name() { + assert_eq!( + compact_class_name("com.edeal.frontline.UserServiceTest"), + "UserServiceTest" + ); + assert_eq!(compact_class_name("SimpleTest"), "SimpleTest"); + } + + #[test] + fn test_is_noise_line() { + assert!(is_noise_line( + "Downloading https://services.gradle.org/dist" + )); + assert!(is_noise_line("Welcome to Gradle 9.1.0!")); + assert!(is_noise_line( + "Starting a Gradle Daemon (subsequent builds will be faster)" + )); + assert!(is_noise_line("> Task :app:compileJava UP-TO-DATE")); + assert!(is_noise_line("> Task :app:processResources NO-SOURCE")); + assert!(is_noise_line("> Configure project :app")); + + assert!(!is_noise_line("BUILD SUCCESSFUL in 28s")); + assert!(!is_noise_line("> Task :app:compileJava FAILED")); + assert!(!is_noise_line("3 errors")); + } +} diff --git a/src/main.rs b/src/main.rs index 654a2676..a9819635 100644 --- a/src/main.rs +++ b/src/main.rs @@ -22,6 +22,7 @@ mod gh_cmd; mod git; mod go_cmd; mod golangci_cmd; +mod gradle_cmd; mod grep_cmd; mod gt_cmd; mod hook_audit_cmd; @@ -35,6 +36,7 @@ mod lint_cmd; mod local_llm; mod log_cmd; mod ls; +mod maven_cmd; mod mypy_cmd; mod next_cmd; mod npm_cmd; @@ -682,6 +684,18 @@ enum Commands { command: GoCommands, }, + /// Gradle commands with compact output (75-90% token reduction) + Gradle { + #[command(subcommand)] + command: GradleCommands, + }, + + /// Maven commands with compact output (70-90% token reduction) + Mvn { + #[command(subcommand)] + command: MavenCommands, + }, + /// Graphite (gt) stacked PR commands with compact output Gt { #[command(subcommand)] @@ -1073,6 +1087,50 @@ enum GoCommands { Other(Vec), } +#[derive(Subcommand)] +enum GradleCommands { + /// Run tests with compact output (85-90% token reduction) + Test { + /// Additional gradle test arguments + #[arg(trailing_var_arg = true, allow_hyphen_values = true)] + args: Vec, + }, + /// Build with compact output (75-85% token reduction) + Build { + /// Additional gradle build arguments + #[arg(trailing_var_arg = true, allow_hyphen_values = true)] + args: Vec, + }, + /// Passthrough: runs any unsupported gradle subcommand directly + #[command(external_subcommand)] + Other(Vec), +} + +#[derive(Subcommand)] +enum MavenCommands { + /// Run tests with compact Surefire output (85-90% token reduction) + Test { + /// Additional mvn test arguments + #[arg(trailing_var_arg = true, allow_hyphen_values = true)] + args: Vec, + }, + /// Compile with compact output (70-80% token reduction) + Compile { + /// Additional mvn compile arguments + #[arg(trailing_var_arg = true, allow_hyphen_values = true)] + args: Vec, + }, + /// Package with compact output (70-80% token reduction) + Package { + /// Additional mvn package arguments + #[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] = &[ @@ -2056,6 +2114,33 @@ fn main() -> Result<()> { } }, + Commands::Gradle { command } => match command { + GradleCommands::Test { args } => { + gradle_cmd::run_test(&args, cli.verbose)?; + } + GradleCommands::Build { args } => { + gradle_cmd::run_build(&args, cli.verbose)?; + } + GradleCommands::Other(args) => { + gradle_cmd::run_other(&args, cli.verbose)?; + } + }, + + Commands::Mvn { command } => match command { + MavenCommands::Test { args } => { + maven_cmd::run_test(&args, cli.verbose)?; + } + MavenCommands::Compile { args } => { + maven_cmd::run_compile(&args, cli.verbose)?; + } + MavenCommands::Package { args } => { + maven_cmd::run_package(&args, cli.verbose)?; + } + MavenCommands::Other(args) => { + maven_cmd::run_other(&args, cli.verbose)?; + } + }, + Commands::Gt { command } => match command { GtCommands::Log { args } => { gt_cmd::run_log(&args, cli.verbose)?; @@ -2301,6 +2386,8 @@ fn is_operational_command(cmd: &Commands) -> bool { | Commands::Rspec { .. } | Commands::Pip { .. } | Commands::Go { .. } + | Commands::Gradle { .. } + | Commands::Mvn { .. } | Commands::GolangciLint { .. } | Commands::Gt { .. } ) diff --git a/src/maven_cmd.rs b/src/maven_cmd.rs new file mode 100644 index 00000000..745b3752 --- /dev/null +++ b/src/maven_cmd.rs @@ -0,0 +1,744 @@ +use crate::tracking; +use crate::utils::{resolved_command, truncate}; +use anyhow::{Context, Result}; +use lazy_static::lazy_static; +use regex::Regex; +use std::ffi::OsString; + +lazy_static! { + // Maven noise patterns to strip + static ref NOISE_PATTERNS: Vec = vec![ + Regex::new(r"^\[INFO\]\s*$").unwrap(), + Regex::new(r"^\[INFO\] Scanning for projects").unwrap(), + Regex::new(r"^\[INFO\] -+(<|$)").unwrap(), + Regex::new(r"^\[INFO\] ={5,}").unwrap(), + Regex::new(r"^\[INFO\] Downloading\s").unwrap(), + Regex::new(r"^\[INFO\] Downloaded\s").unwrap(), + Regex::new(r"^Downloading:").unwrap(), + Regex::new(r"^Downloaded:").unwrap(), + Regex::new(r"^Progress").unwrap(), + Regex::new(r"^\[INFO\] --- maven-").unwrap(), + Regex::new(r"^\[INFO\] Using encoding").unwrap(), + Regex::new(r"^\[INFO\] skip non existing").unwrap(), + Regex::new(r"^\[INFO\] Nothing to compile").unwrap(), + Regex::new(r"^\[INFO\] Copying \d+ resources?").unwrap(), + Regex::new(r"^\[INFO\] Changes detected").unwrap(), + Regex::new(r"^\[INFO\] Finished at:").unwrap(), + Regex::new(r"^\[INFO\]\s+from\s+\S+/pom\.xml").unwrap(), + Regex::new(r"^\[INFO\] Using auto detected provider").unwrap(), + Regex::new(r"^\s*$").unwrap(), + ]; + + // Surefire test result line: "Tests run: N, Failures: M, Errors: E, Skipped: S" + static ref TEST_RESULT_RE: Regex = + Regex::new(r"Tests run:\s*(\d+),\s*Failures:\s*(\d+),\s*Errors:\s*(\d+),\s*Skipped:\s*(\d+)").unwrap(); + + // Surefire test class header: "Running com.example.FooTest" + static ref RUNNING_TEST_RE: Regex = + Regex::new(r"^\[INFO\] Running\s+(\S+)").unwrap(); + + // Surefire failure line: "ClassName.methodName:line message" + static ref FAILURE_SUMMARY_RE: Regex = + Regex::new(r"^\[ERROR\]\s+(\S+\.\S+):(\d+)\s+(.+)").unwrap(); + + // Surefire failure detail: "com.example.Test.method -- Time elapsed..." + static ref FAILURE_DETAIL_RE: Regex = + Regex::new(r"^\[ERROR\]\s+(\S+)\s+--\s+Time elapsed").unwrap(); + + // Reactor summary line: "module ... SUCCESS/FAILURE [time]" + static ref REACTOR_LINE_RE: Regex = + Regex::new(r"^\[INFO\]\s+\S.*\.\.\s+(SUCCESS|FAILURE)\s+\[").unwrap(); + + // Stack trace line + static ref STACK_TRACE_RE: Regex = + Regex::new(r"^\s+at\s+").unwrap(); + + // Exception/assertion line + static ref EXCEPTION_RE: Regex = + Regex::new(r"^(java\.\S+Exception|java\.\S+Error|org\.junit\.\S+Error)").unwrap(); + + // Maven BUILD SUCCESS/FAILURE + static ref BUILD_STATUS_RE: Regex = + Regex::new(r"^\[INFO\] BUILD (SUCCESS|FAILURE)").unwrap(); + + // Total time line + static ref TOTAL_TIME_RE: Regex = + Regex::new(r"^\[INFO\] Total time:\s+(.+)").unwrap(); + + // Compilation error + static ref COMPILE_ERROR_RE: Regex = + Regex::new(r"^\[ERROR\]\s+/").unwrap(); + + // Reactor Summary header + static ref REACTOR_SUMMARY_RE: Regex = + Regex::new(r"^\[INFO\] Reactor Summary").unwrap(); + + // Building module header + static ref BUILDING_MODULE_RE: Regex = + Regex::new(r"^\[INFO\] Building\s+\S").unwrap(); + + // Compiling N source files + static ref COMPILING_RE: Regex = + Regex::new(r"^\[INFO\] Compiling \d+ source files").unwrap(); +} + +pub fn run_test(args: &[String], verbose: u8) -> Result<()> { + let timer = tracking::TimedExecution::start(); + + let mut cmd = resolved_command("mvn"); + cmd.arg("test"); + + for arg in args { + cmd.arg(arg); + } + + if verbose > 0 { + eprintln!("Running: mvn test {}", args.join(" ")); + } + + let output = cmd + .output() + .context("Failed to run mvn test. Is Maven installed?")?; + + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + let raw = format!("{}\n{}", stdout, stderr); + + let exit_code = output + .status + .code() + .unwrap_or(if output.status.success() { 0 } else { 1 }); + let filtered = filter_mvn_test(&raw); + + if let Some(hint) = crate::tee::tee_and_hint(&raw, "mvn_test", exit_code) { + println!("{}\n{}", filtered, hint); + } else { + println!("{}", filtered); + } + + timer.track( + &format!("mvn test {}", args.join(" ")), + &format!("rtk mvn test {}", args.join(" ")), + &raw, + &filtered, + ); + + if !output.status.success() { + std::process::exit(exit_code); + } + + Ok(()) +} + +pub fn run_compile(args: &[String], verbose: u8) -> Result<()> { + run_build_phase("compile", args, verbose) +} + +pub fn run_package(args: &[String], verbose: u8) -> Result<()> { + run_build_phase("package", args, verbose) +} + +/// Generic build phase runner for compile/package/install/clean +fn run_build_phase(phase: &str, args: &[String], verbose: u8) -> Result<()> { + let timer = tracking::TimedExecution::start(); + + let mut cmd = resolved_command("mvn"); + cmd.arg(phase); + + for arg in args { + cmd.arg(arg); + } + + if verbose > 0 { + eprintln!("Running: mvn {} {}", phase, args.join(" ")); + } + + let output = cmd + .output() + .with_context(|| format!("Failed to run mvn {}. Is Maven installed?", phase))?; + + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + let raw = format!("{}\n{}", stdout, stderr); + + let exit_code = output + .status + .code() + .unwrap_or(if output.status.success() { 0 } else { 1 }); + let filtered = filter_mvn_build(&raw); + + if let Some(hint) = crate::tee::tee_and_hint(&raw, &format!("mvn_{}", phase), exit_code) { + println!("{}\n{}", filtered, hint); + } else { + println!("{}", filtered); + } + + timer.track( + &format!("mvn {} {}", phase, args.join(" ")), + &format!("rtk mvn {} {}", phase, args.join(" ")), + &raw, + &filtered, + ); + + if !output.status.success() { + std::process::exit(exit_code); + } + + Ok(()) +} + +pub fn run_other(args: &[OsString], verbose: u8) -> Result<()> { + if args.is_empty() { + anyhow::bail!("mvn: no subcommand specified"); + } + + let timer = tracking::TimedExecution::start(); + + let subcommand = args[0].to_string_lossy(); + let mut cmd = resolved_command("mvn"); + cmd.arg(&*subcommand); + + for arg in &args[1..] { + cmd.arg(arg); + } + + if verbose > 0 { + eprintln!("Running: mvn {} ...", subcommand); + } + + let output = cmd + .output() + .with_context(|| format!("Failed to run mvn {}", subcommand))?; + + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + let raw = format!("{}\n{}", stdout, stderr); + + // Apply basic noise stripping + let filtered = filter_mvn_build(&raw); + println!("{}", filtered); + + timer.track( + &format!("mvn {}", subcommand), + &format!("rtk mvn {}", subcommand), + &raw, + &filtered, + ); + + if !output.status.success() { + std::process::exit(output.status.code().unwrap_or(1)); + } + + Ok(()) +} + +/// Filter Maven build output (compile/package/install): strip noise, keep errors and summary +fn filter_mvn_build(output: &str) -> String { + let mut result_lines: Vec = Vec::new(); + let mut in_reactor_summary = false; + let mut build_status = String::new(); + let mut total_time = String::new(); + + for line in output.lines() { + let trimmed = line.trim(); + + // Track reactor summary section + if REACTOR_SUMMARY_RE.is_match(trimmed) { + in_reactor_summary = true; + continue; + } + + // Capture build status + if let Some(caps) = BUILD_STATUS_RE.captures(trimmed) { + build_status = caps[1].to_string(); + continue; + } + + // Capture total time + if let Some(caps) = TOTAL_TIME_RE.captures(trimmed) { + total_time = caps[1].to_string(); + continue; + } + + // In reactor summary: keep module status lines + if in_reactor_summary { + if REACTOR_LINE_RE.is_match(trimmed) { + result_lines.push(trimmed.to_string()); + continue; + } + // Empty [INFO] line ends reactor summary + if trimmed == "[INFO]" || trimmed.starts_with("[INFO] ---") { + in_reactor_summary = false; + continue; + } + } + + // Skip noise + if is_noise_line(trimmed) { + continue; + } + + // Keep ERROR lines + if trimmed.starts_with("[ERROR]") { + result_lines.push(truncate(trimmed, 150).to_string()); + continue; + } + + // Keep WARNING lines + if trimmed.starts_with("[WARNING]") { + result_lines.push(truncate(trimmed, 150).to_string()); + continue; + } + + // Keep compilation info + if COMPILING_RE.is_match(trimmed) { + result_lines.push(trimmed.to_string()); + continue; + } + + // Keep stack trace context for errors + if !result_lines.is_empty() + && result_lines + .last() + .map_or(false, |l| l.starts_with("[ERROR]")) + { + if trimmed.starts_with("symbol:") + || trimmed.starts_with("location:") + || trimmed.starts_with("required:") + || trimmed.starts_with("found:") + || trimmed.starts_with("reason:") + { + result_lines.push(format!(" {}", trimmed)); + continue; + } + } + } + + // Build summary footer + if !build_status.is_empty() { + let time_info = if total_time.is_empty() { + String::new() + } else { + format!(" ({})", total_time) + }; + result_lines.push(format!("BUILD {}{}", build_status, time_info)); + } + + if result_lines.is_empty() { + return "mvn: ok".to_string(); + } + + result_lines.join("\n") +} + +/// Filter Maven test output (Surefire): show failures and summary +fn filter_mvn_test(output: &str) -> String { + let mut total_run: usize = 0; + let mut total_failures: usize = 0; + let mut total_errors: usize = 0; + let mut total_skipped: usize = 0; + let mut failures: Vec = Vec::new(); + let mut current_failure: Option = None; + let mut in_failure_output = false; + let mut stack_lines_collected: usize = 0; + let mut build_status = String::new(); + let mut total_time = String::new(); + let mut in_failures_section = false; + + for line in output.lines() { + let trimmed = line.trim(); + + // Capture build status + if let Some(caps) = BUILD_STATUS_RE.captures(trimmed) { + build_status = caps[1].to_string(); + continue; + } + + // Capture total time + if let Some(caps) = TOTAL_TIME_RE.captures(trimmed) { + total_time = caps[1].to_string(); + continue; + } + + // Detect [ERROR] Failures: section (Surefire summary) + if trimmed == "[ERROR] Failures:" { + in_failures_section = true; + continue; + } + + // In failures section: capture compact failure summaries + if in_failures_section { + if let Some(caps) = FAILURE_SUMMARY_RE.captures(trimmed) { + let method_path = caps[1].to_string(); + let line_num = caps[2].to_string(); + let message = caps[3].to_string(); + + failures.push(MavenTestFailure { + test_name: compact_test_name(&method_path), + message: truncate(&message, 120).to_string(), + location: format!("{}:{}", method_path, line_num), + stack_lines: Vec::new(), + }); + continue; + } + // End of failures section + if trimmed.is_empty() + || trimmed.starts_with("[INFO]") + || trimmed.starts_with("[ERROR] Tests run:") + { + in_failures_section = false; + // Fall through to process the line normally + } + } + + // Capture test result summary (last one wins — it's the global summary) + if let Some(caps) = TEST_RESULT_RE.captures(trimmed) { + let run: usize = caps[1].parse().unwrap_or(0); + let fail: usize = caps[2].parse().unwrap_or(0); + let err: usize = caps[3].parse().unwrap_or(0); + let skip: usize = caps[4].parse().unwrap_or(0); + + // Only use the global summary (from [ERROR] or final [INFO] Results section) + if trimmed.starts_with("[ERROR]") { + total_run = run; + total_failures = fail; + total_errors = err; + total_skipped = skip; + } else if total_run == 0 { + // Accumulate from per-module summaries if no global yet + total_run += run; + total_failures += fail; + total_errors += err; + total_skipped += skip; + } + continue; + } + + // Capture failure detail line + if FAILURE_DETAIL_RE.is_match(trimmed) { + // Save previous failure + if let Some(f) = current_failure.take() { + if failures + .iter() + .all(|existing| existing.test_name != f.test_name) + { + failures.push(f); + } + } + + let test_name = trimmed + .trim_start_matches("[ERROR] ") + .split(" -- ") + .next() + .unwrap_or("") + .to_string(); + current_failure = Some(MavenTestFailure { + test_name: compact_test_name(&test_name), + message: String::new(), + location: String::new(), + stack_lines: Vec::new(), + }); + in_failure_output = true; + stack_lines_collected = 0; + continue; + } + + // Inside failure output: capture exception and stack + if in_failure_output { + if let Some(ref mut f) = current_failure { + if EXCEPTION_RE.is_match(trimmed) || trimmed.starts_with("java.lang.Assertion") { + // Extract message from "ExceptionType: message" + if let Some(pos) = trimmed.find(": ") { + f.message = truncate(&trimmed[pos + 2..], 120).to_string(); + } else { + f.message = truncate(trimmed, 120).to_string(); + } + } else if STACK_TRACE_RE.is_match(trimmed) && stack_lines_collected < 3 { + // Keep first few relevant stack lines (skip framework) + if !trimmed.contains("org.junit.") + && !trimmed.contains("java.base/") + && !trimmed.contains("jdk.internal") + && !trimmed.contains("sun.reflect") + { + f.stack_lines + .push(truncate(trimmed.trim(), 120).to_string()); + stack_lines_collected += 1; + } + } else if trimmed.is_empty() + || trimmed.starts_with("[INFO]") + || trimmed.starts_with("[ERROR]") + { + in_failure_output = false; + } + } + } + } + + // Save last failure + if let Some(f) = current_failure.take() { + if failures + .iter() + .all(|existing| existing.test_name != f.test_name) + { + failures.push(f); + } + } + + let total_failed = total_failures + total_errors; + let total_passed = total_run.saturating_sub(total_failed + total_skipped); + + // No tests found + if total_run == 0 { + let time_info = if total_time.is_empty() { + String::new() + } else { + format!(" ({})", total_time) + }; + if build_status == "FAILURE" { + return format!("mvn test: BUILD FAILURE{}", time_info); + } + return format!("mvn test: no tests found{}", time_info); + } + + // All passed + if total_failed == 0 { + let time_info = if total_time.is_empty() { + String::new() + } else { + format!(" ({})", total_time) + }; + let skip_info = if total_skipped > 0 { + format!(", {} skipped", total_skipped) + } else { + String::new() + }; + return format!("mvn test: {} passed{}{}", total_run, skip_info, time_info); + } + + // Has failures + let time_info = if total_time.is_empty() { + String::new() + } else { + format!(" ({})", total_time) + }; + + let mut result = format!( + "FAILED: {}/{} tests{}\n", + total_failed, total_run, time_info + ); + result.push_str("=======================================\n"); + + for f in &failures { + result.push_str(&format!(" {} FAILED\n", f.test_name)); + if !f.message.is_empty() { + result.push_str(&format!(" {}\n", f.message)); + } + if !f.location.is_empty() { + result.push_str(&format!(" at {}\n", truncate(&f.location, 100))); + } + for stack_line in &f.stack_lines { + result.push_str(&format!(" {}\n", stack_line)); + } + } + + if build_status == "FAILURE" { + result.push_str("\nBUILD FAILURE"); + } + + result.trim().to_string() +} + +struct MavenTestFailure { + test_name: String, + message: String, + location: String, + stack_lines: Vec, +} + +/// Check if a line matches any noise pattern +fn is_noise_line(line: &str) -> bool { + NOISE_PATTERNS.iter().any(|re| re.is_match(line)) +} + +/// Compact test name: "com.edeal.frontline.UserServiceTest.testFoo" -> "UserServiceTest.testFoo" +fn compact_test_name(name: &str) -> String { + let parts: Vec<&str> = name.rsplitn(3, '.').collect(); + if parts.len() >= 2 { + format!("{}.{}", parts[1], parts[0]) + } else { + name.to_string() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + fn count_tokens(text: &str) -> usize { + text.split_whitespace().count() + } + + // ============================================================ + // Build filter tests + // ============================================================ + + #[test] + fn test_filter_mvn_build_success() { + let input = include_str!("../tests/fixtures/mvn_compile_success_raw.txt"); + let output = filter_mvn_build(input); + + // Should strip noise + assert!(!output.contains("Scanning for projects")); + assert!(!output.contains("Downloading from central")); + assert!(!output.contains("Downloaded from central")); + assert!(!output.contains("maven-resources-plugin")); + assert!(!output.contains("maven-compiler-plugin")); + assert!(!output.contains("Nothing to compile")); + + // Should keep build result + assert!(output.contains("BUILD SUCCESS")); + assert!(output.contains("18.234")); + } + + #[test] + fn test_filter_mvn_build_failure() { + let input = include_str!("../tests/fixtures/mvn_compile_fail_raw.txt"); + let output = filter_mvn_build(input); + + // Should keep errors + assert!(output.contains("[ERROR]")); + assert!(output.contains("cannot find symbol")); + assert!(output.contains("BUILD FAILURE")); + + // Should strip noise + assert!(!output.contains("Scanning for projects")); + assert!(!output.contains("maven-resources-plugin")); + } + + #[test] + fn test_filter_mvn_build_empty() { + let output = filter_mvn_build(""); + assert_eq!(output, "mvn: ok"); + } + + #[test] + fn test_filter_mvn_build_savings() { + let input = include_str!("../tests/fixtures/mvn_compile_success_raw.txt"); + let output = filter_mvn_build(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, + "Maven build filter: expected >=60% savings, got {:.1}% ({} -> {} tokens)", + savings, + input_tokens, + output_tokens + ); + } + + // ============================================================ + // Test filter tests + // ============================================================ + + #[test] + fn test_filter_mvn_test_all_pass() { + let input = include_str!("../tests/fixtures/mvn_test_pass_raw.txt"); + let output = filter_mvn_test(input); + + assert!(output.contains("mvn test:")); + assert!(output.contains("passed")); + assert!(!output.contains("FAILED")); + } + + #[test] + fn test_filter_mvn_test_with_failures() { + let input = include_str!("../tests/fixtures/mvn_test_fail_raw.txt"); + let output = filter_mvn_test(input); + + assert!(output.contains("FAILED")); + assert!(output.contains("2/14") || output.contains("2 ")); + // Should contain failure information + assert!(output.contains("testUpdateUserProfile") || output.contains("UserServiceTest")); + } + + #[test] + fn test_filter_mvn_test_savings_pass() { + let input = include_str!("../tests/fixtures/mvn_test_pass_raw.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 >= 85.0, + "Maven test (pass) filter: expected >=85% savings, got {:.1}% ({} -> {} tokens)", + savings, + input_tokens, + output_tokens + ); + } + + #[test] + fn test_filter_mvn_test_savings_fail() { + let input = include_str!("../tests/fixtures/mvn_test_fail_raw.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 >= 70.0, + "Maven test (fail) filter: expected >=70% savings, got {:.1}% ({} -> {} tokens)", + savings, + input_tokens, + output_tokens + ); + } + + #[test] + fn test_filter_mvn_test_empty() { + let output = filter_mvn_test(""); + assert!(output.contains("mvn test:")); + } + + // ============================================================ + // Utility tests + // ============================================================ + + #[test] + fn test_compact_test_name() { + assert_eq!( + compact_test_name("com.edeal.frontline.UserServiceTest.testFoo"), + "UserServiceTest.testFoo" + ); + assert_eq!( + compact_test_name("SimpleTest.testBar"), + "SimpleTest.testBar" + ); + } + + #[test] + fn test_is_noise_line() { + assert!(is_noise_line("[INFO] Scanning for projects...")); + assert!(is_noise_line("[INFO] Downloading org.apache:foo:1.0")); + assert!(is_noise_line("[INFO] Downloaded org.apache:foo:1.0")); + assert!(is_noise_line( + "[INFO] --- maven-compiler-plugin:3.11.0:compile ---" + )); + assert!(is_noise_line( + "[INFO] Nothing to compile - all classes are up to date." + )); + assert!(is_noise_line("[INFO] ")); + assert!(is_noise_line("[INFO] ---")); + assert!(is_noise_line("[INFO] Using encoding: UTF-8")); + + assert!(!is_noise_line("[ERROR] Compilation failed")); + assert!(!is_noise_line("[INFO] BUILD SUCCESS")); + assert!(!is_noise_line("[WARNING] Using deprecated API")); + } +} diff --git a/tests/fixtures/gradle_build_fail_raw.txt b/tests/fixtures/gradle_build_fail_raw.txt new file mode 100644 index 00000000..23becaf8 --- /dev/null +++ b/tests/fixtures/gradle_build_fail_raw.txt @@ -0,0 +1,42 @@ +Starting a Gradle Daemon (subsequent builds will be faster) + +> Configure project : +> Configure project :edeal-common + +> Task :edeal-common:compileJava UP-TO-DATE +> Task :edeal-common:processResources UP-TO-DATE +> Task :edeal-common:classes UP-TO-DATE +> Task :edeal-common:jar UP-TO-DATE +> Task :edeal-webapp:compileJava FAILED + +FAILURE: Build failed with an exception. + +* What went wrong: +Execution failed for task ':edeal-webapp:compileJava'. +> Compilation failed; see the compiler error output for details. + +* Try: +> Run with --stacktrace option to get the stack trace. +> Run with --info or --debug option to get more log output. +> Run with --scan to generate a Build Scan (Powered by Develocity). +> Get more help at https://help.gradle.org. + +BUILD FAILED in 12s +5 actionable tasks: 1 executed, 4 up-to-date + +> Task :edeal-webapp:compileJava +/home/dev/e-deal/edeal-webapp/src/main/java/com/edeal/frontline/UserService.java:42: error: cannot find symbol + return userRepository.findByEmail(email); + ^ + symbol: method findByEmail(String) + location: variable userRepository of type UserRepository +/home/dev/e-deal/edeal-webapp/src/main/java/com/edeal/frontline/UserService.java:58: error: method login in class AuthManager cannot be applied to given types; + authManager.login(user, password, rememberMe); + ^ + required: User, String + found: User, String, boolean + reason: actual and formal argument lists differ in length +/home/dev/e-deal/edeal-webapp/src/main/java/com/edeal/frontline/api/RestController.java:23: error: package javax.ws.rs does not exist +import javax.ws.rs.GET; + ^ +3 errors diff --git a/tests/fixtures/gradle_build_success_raw.txt b/tests/fixtures/gradle_build_success_raw.txt new file mode 100644 index 00000000..86dc7537 --- /dev/null +++ b/tests/fixtures/gradle_build_success_raw.txt @@ -0,0 +1,38 @@ +Downloading https://services.gradle.org/distributions/gradle-9.1.0-bin.zip +............10%.............20%.............30%.............40%.............50%............60%.............70%.............80%.............90%.............100% + +Welcome to Gradle 9.1.0! + +Here are the highlights of this release: + - Full Java 25 support + - Native task graph visualization + - Enhanced console output + +For more details see https://docs.gradle.org/9.1.0/release-notes.html + +Starting a Gradle Daemon (subsequent builds will be faster) + +> Configure project : +> Configure project :edeal-common +> Configure project :edeal-webapp + +> Task :edeal-common:compileJava UP-TO-DATE +> Task :edeal-common:processResources UP-TO-DATE +> Task :edeal-common:classes UP-TO-DATE +> Task :edeal-common:jar UP-TO-DATE +> Task :edeal-webapp:compileJava +Note: Some input files use or override a deprecated API. +Note: Recompile with -Xlint:deprecation for details. +> Task :edeal-webapp:processResources UP-TO-DATE +> Task :edeal-webapp:classes +> Task :edeal-webapp:war +> Task :edeal-webapp:assemble +> Task :edeal-common:compileTestJava NO-SOURCE +> Task :edeal-webapp:compileTestJava UP-TO-DATE +> Task :edeal-webapp:processTestResources NO-SOURCE +> Task :edeal-webapp:testClasses UP-TO-DATE +> Task :edeal-webapp:test UP-TO-DATE +> Task :edeal-webapp:check UP-TO-DATE + +BUILD SUCCESSFUL in 28s +14 actionable tasks: 3 executed, 11 up-to-date diff --git a/tests/fixtures/gradle_test_fail_raw.txt b/tests/fixtures/gradle_test_fail_raw.txt new file mode 100644 index 00000000..022d52a0 --- /dev/null +++ b/tests/fixtures/gradle_test_fail_raw.txt @@ -0,0 +1,73 @@ +Starting a Gradle Daemon (subsequent builds will be faster) + +> Configure project : +> Configure project :edeal-common +> Configure project :edeal-webapp + +> Task :edeal-common:compileJava UP-TO-DATE +> Task :edeal-common:processResources UP-TO-DATE +> Task :edeal-common:classes UP-TO-DATE +> Task :edeal-common:jar UP-TO-DATE +> Task :edeal-webapp:compileJava UP-TO-DATE +> Task :edeal-webapp:processResources UP-TO-DATE +> Task :edeal-webapp:classes UP-TO-DATE +> Task :edeal-webapp:compileTestJava UP-TO-DATE +> Task :edeal-webapp:processTestResources NO-SOURCE +> Task :edeal-webapp:testClasses UP-TO-DATE +> Task :edeal-webapp:test + +com.edeal.frontline.UserServiceTest > testGetUserByEmail PASSED +com.edeal.frontline.UserServiceTest > testCreateUser PASSED +com.edeal.frontline.UserServiceTest > testDeleteUser PASSED +com.edeal.frontline.UserServiceTest > testUpdateUserProfile FAILED + + com.edeal.frontline.UserServiceTest > testUpdateUserProfile FAILED + java.lang.AssertionError: Expected user name to be "John Updated" but was "John" + at org.junit.Assert.assertEquals(Assert.java:117) + at com.edeal.frontline.UserServiceTest.testUpdateUserProfile(UserServiceTest.java:89) + at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) + at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77) + at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) + +com.edeal.frontline.api.RestControllerTest > testGetEndpoint PASSED +com.edeal.frontline.api.RestControllerTest > testPostEndpoint PASSED +com.edeal.frontline.api.RestControllerTest > testAuthRequired FAILED + + com.edeal.frontline.api.RestControllerTest > testAuthRequired FAILED + java.lang.AssertionError: Expected status code 401 but was 200 + at org.junit.Assert.assertEquals(Assert.java:117) + at com.edeal.frontline.api.RestControllerTest.testAuthRequired(RestControllerTest.java:67) + at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) + +com.edeal.frontline.api.RestControllerTest > testPagination PASSED +com.edeal.frontline.repository.UserRepositoryTest > testFindById PASSED +com.edeal.frontline.repository.UserRepositoryTest > testFindByEmail PASSED +com.edeal.frontline.repository.UserRepositoryTest > testSaveAndFlush PASSED +com.edeal.frontline.service.EmailServiceTest > testSendEmail PASSED +com.edeal.frontline.service.EmailServiceTest > testSendBulkEmail PASSED +com.edeal.frontline.service.EmailServiceTest > testTemplateRendering PASSED +com.edeal.frontline.util.DateUtilsTest > testFormatDate PASSED +com.edeal.frontline.util.DateUtilsTest > testParseDate PASSED +com.edeal.frontline.util.DateUtilsTest > testTimezoneConversion PASSED +com.edeal.frontline.util.StringUtilsTest > testTruncate PASSED +com.edeal.frontline.util.StringUtilsTest > testSanitizeHtml PASSED +com.edeal.frontline.util.StringUtilsTest > testNormalize PASSED + +20 tests completed, 2 failed + +> Task :edeal-webapp:test FAILED + +FAILURE: Build failed with an exception. + +* What went wrong: +Execution failed for task ':edeal-webapp:test'. +> There were failing tests. See the report at: file:///home/dev/e-deal/edeal-webapp/build/reports/tests/test/index.html + +* Try: +> Run with --stacktrace option to get the stack trace. +> Run with --info or --debug option to get more log output. +> Run with --scan to generate a Build Scan (Powered by Develocity). +> Get more help at https://help.gradle.org. + +BUILD FAILED in 42s +14 actionable tasks: 1 executed, 13 up-to-date diff --git a/tests/fixtures/gradle_test_pass_raw.txt b/tests/fixtures/gradle_test_pass_raw.txt new file mode 100644 index 00000000..10d6abe7 --- /dev/null +++ b/tests/fixtures/gradle_test_pass_raw.txt @@ -0,0 +1,45 @@ +Starting a Gradle Daemon (subsequent builds will be faster) + +> Configure project : +> Configure project :edeal-common +> Configure project :edeal-webapp + +> Task :edeal-common:compileJava UP-TO-DATE +> Task :edeal-common:processResources UP-TO-DATE +> Task :edeal-common:classes UP-TO-DATE +> Task :edeal-common:jar UP-TO-DATE +> Task :edeal-webapp:compileJava UP-TO-DATE +> Task :edeal-webapp:processResources UP-TO-DATE +> Task :edeal-webapp:classes UP-TO-DATE +> Task :edeal-webapp:compileTestJava UP-TO-DATE +> Task :edeal-webapp:processTestResources NO-SOURCE +> Task :edeal-webapp:testClasses UP-TO-DATE +> Task :edeal-webapp:test + +com.edeal.frontline.UserServiceTest > testGetUserByEmail PASSED +com.edeal.frontline.UserServiceTest > testCreateUser PASSED +com.edeal.frontline.UserServiceTest > testDeleteUser PASSED +com.edeal.frontline.UserServiceTest > testUpdateUserProfile PASSED +com.edeal.frontline.api.RestControllerTest > testGetEndpoint PASSED +com.edeal.frontline.api.RestControllerTest > testPostEndpoint PASSED +com.edeal.frontline.api.RestControllerTest > testAuthRequired PASSED +com.edeal.frontline.api.RestControllerTest > testPagination PASSED +com.edeal.frontline.repository.UserRepositoryTest > testFindById PASSED +com.edeal.frontline.repository.UserRepositoryTest > testFindByEmail PASSED +com.edeal.frontline.repository.UserRepositoryTest > testSaveAndFlush PASSED +com.edeal.frontline.service.EmailServiceTest > testSendEmail PASSED +com.edeal.frontline.service.EmailServiceTest > testSendBulkEmail PASSED +com.edeal.frontline.service.EmailServiceTest > testTemplateRendering PASSED +com.edeal.frontline.util.DateUtilsTest > testFormatDate PASSED +com.edeal.frontline.util.DateUtilsTest > testParseDate PASSED +com.edeal.frontline.util.DateUtilsTest > testTimezoneConversion PASSED +com.edeal.frontline.util.StringUtilsTest > testTruncate PASSED +com.edeal.frontline.util.StringUtilsTest > testSanitizeHtml PASSED +com.edeal.frontline.util.StringUtilsTest > testNormalize PASSED + +20 tests completed, 0 failed + +> Task :edeal-webapp:check UP-TO-DATE + +BUILD SUCCESSFUL in 45s +14 actionable tasks: 1 executed, 13 up-to-date diff --git a/tests/fixtures/mvn_compile_fail_raw.txt b/tests/fixtures/mvn_compile_fail_raw.txt new file mode 100644 index 00000000..4e166990 --- /dev/null +++ b/tests/fixtures/mvn_compile_fail_raw.txt @@ -0,0 +1,71 @@ +[INFO] Scanning for projects... +[INFO] ------------------------------------------------------------------------ +[INFO] Reactor Build Order: +[INFO] +[INFO] E-DEAL Parent [pom] +[INFO] edeal-common [jar] +[INFO] edeal-webapp [war] +[INFO] +[INFO] ----------------< com.edeal.frontline:edeal-parent >---------------- +[INFO] Building E-DEAL Parent 2027-000-SNAPSHOT [1/3] +[INFO] from pom.xml +[INFO] --------------------------------[ pom ]--------------------------------- +[INFO] +[INFO] ----------------< com.edeal.frontline:edeal-common >---------------- +[INFO] Building edeal-common 2027-000-SNAPSHOT [2/3] +[INFO] from edeal-common/pom.xml +[INFO] --------------------------------[ jar ]--------------------------------- +[INFO] +[INFO] --- maven-resources-plugin:3.3.1:resources (default-resources) @ edeal-common --- +[INFO] Copying 12 resources from src/main/resources to target/classes +[INFO] +[INFO] --- maven-compiler-plugin:3.11.0:compile (default-compile) @ edeal-common --- +[INFO] Nothing to compile - all classes are up to date. +[INFO] +[INFO] ----------------< com.edeal.frontline:edeal-webapp >---------------- +[INFO] Building edeal-webapp 2027-000-SNAPSHOT [3/3] +[INFO] from edeal-webapp/pom.xml +[INFO] --------------------------------[ war ]--------------------------------- +[INFO] +[INFO] --- maven-compiler-plugin:3.11.0:compile (default-compile) @ edeal-webapp --- +[INFO] Compiling 342 source files to /home/dev/e-deal/edeal-webapp/target/classes +[ERROR] COMPILATION ERROR : +[INFO] ------------------------------------------------------------- +[ERROR] /home/dev/e-deal/edeal-webapp/src/main/java/com/edeal/frontline/UserService.java:[42,30] cannot find symbol + symbol: method findByEmail(java.lang.String) + location: variable userRepository of type com.edeal.frontline.repository.UserRepository +[ERROR] /home/dev/e-deal/edeal-webapp/src/main/java/com/edeal/frontline/UserService.java:[58,20] method login in class com.edeal.frontline.AuthManager cannot be applied to given types; + required: com.edeal.frontline.User,java.lang.String + found: com.edeal.frontline.User,java.lang.String,boolean + reason: actual and formal argument lists differ in length +[ERROR] /home/dev/e-deal/edeal-webapp/src/main/java/com/edeal/frontline/api/RestController.java:[23,19] package javax.ws.rs does not exist +[INFO] 3 errors +[INFO] ------------------------------------------------------------- +[INFO] ------------------------------------------------------------------------ +[INFO] Reactor Summary for E-DEAL Parent 2027-000-SNAPSHOT: +[INFO] +[INFO] E-DEAL Parent ...................................... SUCCESS [ 0.234 s] +[INFO] edeal-common ....................................... SUCCESS [ 1.123 s] +[INFO] edeal-webapp ....................................... FAILURE [ 12.345 s] +[INFO] ------------------------------------------------------------------------ +[INFO] BUILD FAILURE +[INFO] ------------------------------------------------------------------------ +[INFO] Total time: 15.234 s +[INFO] Finished at: 2026-03-21T10:30:00+01:00 +[INFO] ------------------------------------------------------------------------ +[ERROR] Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.11.0:compile (default-compile) on project edeal-webapp: Compilation failure: Compilation failure: +[ERROR] /home/dev/e-deal/edeal-webapp/src/main/java/com/edeal/frontline/UserService.java:[42,30] cannot find symbol +[ERROR] symbol: method findByEmail(java.lang.String) +[ERROR] location: variable userRepository of type com.edeal.frontline.repository.UserRepository +[ERROR] /home/dev/e-deal/edeal-webapp/src/main/java/com/edeal/frontline/UserService.java:[58,20] method login in class com.edeal.frontline.AuthManager cannot be applied to given types; +[ERROR] required: com.edeal.frontline.User,java.lang.String +[ERROR] found: com.edeal.frontline.User,java.lang.String,boolean +[ERROR] reason: actual and formal argument lists differ in length +[ERROR] /home/dev/e-deal/edeal-webapp/src/main/java/com/edeal/frontline/api/RestController.java:[23,19] package javax.ws.rs does not exist +[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 full debug logging. +[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_success_raw.txt b/tests/fixtures/mvn_compile_success_raw.txt new file mode 100644 index 00000000..9ac07798 --- /dev/null +++ b/tests/fixtures/mvn_compile_success_raw.txt @@ -0,0 +1,81 @@ +[INFO] Scanning for projects... +[INFO] ------------------------------------------------------------------------ +[INFO] Reactor Build Order: +[INFO] +[INFO] E-DEAL Parent [pom] +[INFO] edeal-common [jar] +[INFO] edeal-concurrent [jar] +[INFO] edeal-rules-engine [jar] +[INFO] edeal-webapp [war] +[INFO] edeal-rest-api-core [jar] +[INFO] +[INFO] ----------------< com.edeal.frontline:edeal-parent >---------------- +[INFO] Building E-DEAL Parent 2027-000-SNAPSHOT [1/6] +[INFO] from pom.xml +[INFO] --------------------------------[ pom ]--------------------------------- +[INFO] +[INFO] ----------------< com.edeal.frontline:edeal-common >---------------- +[INFO] Building edeal-common 2027-000-SNAPSHOT [2/6] +[INFO] from edeal-common/pom.xml +[INFO] --------------------------------[ jar ]--------------------------------- +[INFO] +[INFO] --- maven-resources-plugin:3.3.1:resources (default-resources) @ edeal-common --- +[INFO] Copying 12 resources from src/main/resources to target/classes +[INFO] +[INFO] --- maven-compiler-plugin:3.11.0:compile (default-compile) @ edeal-common --- +[INFO] Nothing to compile - all classes are up to date. +[INFO] +[INFO] ----------------< com.edeal.frontline:edeal-concurrent >---------------- +[INFO] Building edeal-concurrent 2027-000-SNAPSHOT [3/6] +[INFO] from edeal-concurrent/pom.xml +[INFO] --------------------------------[ jar ]--------------------------------- +[INFO] +[INFO] --- maven-resources-plugin:3.3.1:resources (default-resources) @ edeal-concurrent --- +[INFO] Copying 3 resources from src/main/resources to target/classes +[INFO] +[INFO] --- maven-compiler-plugin:3.11.0:compile (default-compile) @ edeal-concurrent --- +[INFO] Nothing to compile - all classes are up to date. +[INFO] +[INFO] ----------------< com.edeal.frontline:edeal-webapp >---------------- +[INFO] Building edeal-webapp 2027-000-SNAPSHOT [4/6] +[INFO] from edeal-webapp/pom.xml +[INFO] --------------------------------[ war ]--------------------------------- +[INFO] +[INFO] Downloading from central: https://repo.maven.apache.org/maven2/org/apache/commons/commons-lang3/3.14.0/commons-lang3-3.14.0.pom +[INFO] Downloaded from central: https://repo.maven.apache.org/maven2/org/apache/commons/commons-lang3/3.14.0/commons-lang3-3.14.0.pom (3.2 kB at 12 kB/s) +[INFO] Downloading from central: https://repo.maven.apache.org/maven2/org/apache/commons/commons-lang3/3.14.0/commons-lang3-3.14.0.jar +[INFO] Downloaded from central: https://repo.maven.apache.org/maven2/org/apache/commons/commons-lang3/3.14.0/commons-lang3-3.14.0.jar (658 kB at 2.1 MB/s) +[INFO] +[INFO] --- maven-resources-plugin:3.3.1:resources (default-resources) @ edeal-webapp --- +[INFO] Copying 45 resources from src/main/resources to target/classes +[INFO] +[INFO] --- maven-compiler-plugin:3.11.0:compile (default-compile) @ edeal-webapp --- +[INFO] Compiling 342 source files to /home/dev/e-deal/edeal-webapp/target/classes +[INFO] /home/dev/e-deal/edeal-webapp/src/main/java/com/edeal/frontline/Utils.java: Some input files use or override a deprecated API. +[INFO] /home/dev/e-deal/edeal-webapp/src/main/java/com/edeal/frontline/Utils.java: Recompile with -Xlint:deprecation for details. +[INFO] +[INFO] ----------------< com.edeal.frontline:edeal-rest-api-core >---------------- +[INFO] Building edeal-rest-api-core 2027-000-SNAPSHOT [5/6] +[INFO] from edeal-rest-api-core/pom.xml +[INFO] --------------------------------[ jar ]--------------------------------- +[INFO] +[INFO] --- maven-resources-plugin:3.3.1:resources (default-resources) @ edeal-rest-api-core --- +[INFO] Copying 8 resources from src/main/resources to target/classes +[INFO] +[INFO] --- maven-compiler-plugin:3.11.0:compile (default-compile) @ edeal-rest-api-core --- +[INFO] Nothing to compile - all classes are up to date. +[INFO] +[INFO] ------------------------------------------------------------------------ +[INFO] Reactor Summary for E-DEAL Parent 2027-000-SNAPSHOT: +[INFO] +[INFO] E-DEAL Parent ...................................... SUCCESS [ 0.234 s] +[INFO] edeal-common ....................................... SUCCESS [ 1.123 s] +[INFO] edeal-concurrent ................................... SUCCESS [ 0.456 s] +[INFO] edeal-webapp ....................................... SUCCESS [ 12.345 s] +[INFO] edeal-rest-api-core ................................ SUCCESS [ 0.789 s] +[INFO] ------------------------------------------------------------------------ +[INFO] BUILD SUCCESS +[INFO] ------------------------------------------------------------------------ +[INFO] Total time: 18.234 s +[INFO] Finished at: 2026-03-21T10:30:00+01:00 +[INFO] ------------------------------------------------------------------------ diff --git a/tests/fixtures/mvn_test_fail_raw.txt b/tests/fixtures/mvn_test_fail_raw.txt new file mode 100644 index 00000000..0e3bdebe --- /dev/null +++ b/tests/fixtures/mvn_test_fail_raw.txt @@ -0,0 +1,123 @@ +[INFO] Scanning for projects... +[INFO] ------------------------------------------------------------------------ +[INFO] Reactor Build Order: +[INFO] +[INFO] E-DEAL Parent [pom] +[INFO] edeal-common [jar] +[INFO] edeal-webapp [war] +[INFO] +[INFO] ----------------< com.edeal.frontline:edeal-parent >---------------- +[INFO] Building E-DEAL Parent 2027-000-SNAPSHOT [1/3] +[INFO] from pom.xml +[INFO] --------------------------------[ pom ]--------------------------------- +[INFO] +[INFO] ----------------< com.edeal.frontline:edeal-common >---------------- +[INFO] Building edeal-common 2027-000-SNAPSHOT [2/3] +[INFO] from edeal-common/pom.xml +[INFO] --------------------------------[ jar ]--------------------------------- +[INFO] +[INFO] --- maven-resources-plugin:3.3.1:resources (default-resources) @ edeal-common --- +[INFO] Copying 12 resources from src/main/resources to target/classes +[INFO] +[INFO] --- maven-compiler-plugin:3.11.0:compile (default-compile) @ edeal-common --- +[INFO] Nothing to compile - all classes are up to date. +[INFO] +[INFO] --- maven-resources-plugin:3.3.1:testResources (default-testResources) @ edeal-common --- +[INFO] Copying 3 resources from src/test/resources to target/test-classes +[INFO] +[INFO] --- maven-compiler-plugin:3.11.0:testCompile (default-testCompile) @ edeal-common --- +[INFO] Nothing to compile - all classes are up to date. +[INFO] +[INFO] --- maven-surefire-plugin:3.2.5:test (default-test) @ edeal-common --- +[INFO] Using auto detected provider org.apache.maven.surefire.junitplatform.JUnitPlatformProvider +[INFO] +[INFO] ------------------------------------------------------- +[INFO] T E S T S +[INFO] ------------------------------------------------------- +[INFO] Running com.edeal.frontline.util.DateUtilsTest +[INFO] Tests run: 3, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.234 s -- in com.edeal.frontline.util.DateUtilsTest +[INFO] Running com.edeal.frontline.util.StringUtilsTest +[INFO] Tests run: 3, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.123 s -- in com.edeal.frontline.util.StringUtilsTest +[INFO] +[INFO] Results: +[INFO] +[INFO] Tests run: 6, Failures: 0, Errors: 0, Skipped: 0 +[INFO] +[INFO] +[INFO] ----------------< com.edeal.frontline:edeal-webapp >---------------- +[INFO] Building edeal-webapp 2027-000-SNAPSHOT [3/3] +[INFO] from edeal-webapp/pom.xml +[INFO] --------------------------------[ war ]--------------------------------- +[INFO] +[INFO] --- maven-resources-plugin:3.3.1:resources (default-resources) @ edeal-webapp --- +[INFO] Copying 45 resources from src/main/resources to target/classes +[INFO] +[INFO] --- maven-compiler-plugin:3.11.0:compile (default-compile) @ edeal-webapp --- +[INFO] Nothing to compile - all classes are up to date. +[INFO] +[INFO] --- maven-resources-plugin:3.3.1:testResources (default-testResources) @ edeal-webapp --- +[INFO] Copying 5 resources from src/test/resources to target/test-classes +[INFO] +[INFO] --- maven-compiler-plugin:3.11.0:testCompile (default-testCompile) @ edeal-webapp --- +[INFO] Nothing to compile - all classes are up to date. +[INFO] +[INFO] --- maven-surefire-plugin:3.2.5:test (default-test) @ edeal-webapp --- +[INFO] Using auto detected provider org.apache.maven.surefire.junitplatform.JUnitPlatformProvider +[INFO] +[INFO] ------------------------------------------------------- +[INFO] T E S T S +[INFO] ------------------------------------------------------- +[INFO] Running com.edeal.frontline.UserServiceTest +[ERROR] Tests run: 4, Failures: 1, Errors: 0, Skipped: 0, Time elapsed: 1.234 s <<< FAILURE! -- in com.edeal.frontline.UserServiceTest +[ERROR] com.edeal.frontline.UserServiceTest.testUpdateUserProfile -- Time elapsed: 0.089 s <<< FAILURE! +java.lang.AssertionError: Expected user name to be "John Updated" but was "John" + at org.junit.Assert.assertEquals(Assert.java:117) + at com.edeal.frontline.UserServiceTest.testUpdateUserProfile(UserServiceTest.java:89) + at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) + at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77) + at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) + +[INFO] Running com.edeal.frontline.api.RestControllerTest +[ERROR] Tests run: 4, Failures: 1, Errors: 0, Skipped: 0, Time elapsed: 2.345 s <<< FAILURE! -- in com.edeal.frontline.api.RestControllerTest +[ERROR] com.edeal.frontline.api.RestControllerTest.testAuthRequired -- Time elapsed: 0.045 s <<< FAILURE! +java.lang.AssertionError: Expected status code 401 but was 200 + at org.junit.Assert.assertEquals(Assert.java:117) + at com.edeal.frontline.api.RestControllerTest.testAuthRequired(RestControllerTest.java:67) + at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) + +[INFO] Running com.edeal.frontline.repository.UserRepositoryTest +[INFO] Tests run: 3, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.567 s -- in com.edeal.frontline.repository.UserRepositoryTest +[INFO] Running com.edeal.frontline.service.EmailServiceTest +[INFO] Tests run: 3, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.345 s -- in com.edeal.frontline.service.EmailServiceTest +[INFO] +[INFO] Results: +[INFO] +[ERROR] Failures: +[ERROR] UserServiceTest.testUpdateUserProfile:89 Expected user name to be "John Updated" but was "John" +[ERROR] RestControllerTest.testAuthRequired:67 Expected status code 401 but was 200 +[INFO] +[ERROR] Tests run: 14, Failures: 2, Errors: 0, Skipped: 0 +[INFO] +[INFO] ------------------------------------------------------------------------ +[INFO] Reactor Summary for E-DEAL Parent 2027-000-SNAPSHOT: +[INFO] +[INFO] E-DEAL Parent ...................................... SUCCESS [ 0.234 s] +[INFO] edeal-common ....................................... SUCCESS [ 3.456 s] +[INFO] edeal-webapp ....................................... FAILURE [ 15.678 s] +[INFO] ------------------------------------------------------------------------ +[INFO] BUILD FAILURE +[INFO] ------------------------------------------------------------------------ +[INFO] Total time: 22.345 s +[INFO] Finished at: 2026-03-21T10:30:00+01:00 +[INFO] ------------------------------------------------------------------------ +[ERROR] Failed to execute goal org.apache.maven.plugins:maven-surefire-plugin:3.2.5:test (default-test) on project edeal-webapp: There are test failures. +[ERROR] +[ERROR] Please refer to /home/dev/e-deal/edeal-webapp/target/surefire-reports for the individual test results. +[ERROR] Please refer to 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 full debug logging. +[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_raw.txt b/tests/fixtures/mvn_test_pass_raw.txt new file mode 100644 index 00000000..d46dfc87 --- /dev/null +++ b/tests/fixtures/mvn_test_pass_raw.txt @@ -0,0 +1,94 @@ +[INFO] Scanning for projects... +[INFO] ------------------------------------------------------------------------ +[INFO] Reactor Build Order: +[INFO] +[INFO] E-DEAL Parent [pom] +[INFO] edeal-common [jar] +[INFO] edeal-webapp [war] +[INFO] +[INFO] ----------------< com.edeal.frontline:edeal-parent >---------------- +[INFO] Building E-DEAL Parent 2027-000-SNAPSHOT [1/3] +[INFO] from pom.xml +[INFO] --------------------------------[ pom ]--------------------------------- +[INFO] +[INFO] ----------------< com.edeal.frontline:edeal-common >---------------- +[INFO] Building edeal-common 2027-000-SNAPSHOT [2/3] +[INFO] from edeal-common/pom.xml +[INFO] --------------------------------[ jar ]--------------------------------- +[INFO] +[INFO] --- maven-resources-plugin:3.3.1:resources (default-resources) @ edeal-common --- +[INFO] Copying 12 resources from src/main/resources to target/classes +[INFO] +[INFO] --- maven-compiler-plugin:3.11.0:compile (default-compile) @ edeal-common --- +[INFO] Nothing to compile - all classes are up to date. +[INFO] +[INFO] --- maven-resources-plugin:3.3.1:testResources (default-testResources) @ edeal-common --- +[INFO] Copying 3 resources from src/test/resources to target/test-classes +[INFO] +[INFO] --- maven-compiler-plugin:3.11.0:testCompile (default-testCompile) @ edeal-common --- +[INFO] Nothing to compile - all classes are up to date. +[INFO] +[INFO] --- maven-surefire-plugin:3.2.5:test (default-test) @ edeal-common --- +[INFO] Using auto detected provider org.apache.maven.surefire.junitplatform.JUnitPlatformProvider +[INFO] +[INFO] ------------------------------------------------------- +[INFO] T E S T S +[INFO] ------------------------------------------------------- +[INFO] Running com.edeal.frontline.util.DateUtilsTest +[INFO] Tests run: 3, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.234 s -- in com.edeal.frontline.util.DateUtilsTest +[INFO] Running com.edeal.frontline.util.StringUtilsTest +[INFO] Tests run: 3, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.123 s -- in com.edeal.frontline.util.StringUtilsTest +[INFO] +[INFO] Results: +[INFO] +[INFO] Tests run: 6, Failures: 0, Errors: 0, Skipped: 0 +[INFO] +[INFO] +[INFO] ----------------< com.edeal.frontline:edeal-webapp >---------------- +[INFO] Building edeal-webapp 2027-000-SNAPSHOT [3/3] +[INFO] from edeal-webapp/pom.xml +[INFO] --------------------------------[ war ]--------------------------------- +[INFO] +[INFO] --- maven-resources-plugin:3.3.1:resources (default-resources) @ edeal-webapp --- +[INFO] Copying 45 resources from src/main/resources to target/classes +[INFO] +[INFO] --- maven-compiler-plugin:3.11.0:compile (default-compile) @ edeal-webapp --- +[INFO] Nothing to compile - all classes are up to date. +[INFO] +[INFO] --- maven-resources-plugin:3.3.1:testResources (default-testResources) @ edeal-webapp --- +[INFO] Copying 5 resources from src/test/resources to target/test-classes +[INFO] +[INFO] --- maven-compiler-plugin:3.11.0:testCompile (default-testCompile) @ edeal-webapp --- +[INFO] Nothing to compile - all classes are up to date. +[INFO] +[INFO] --- maven-surefire-plugin:3.2.5:test (default-test) @ edeal-webapp --- +[INFO] Using auto detected provider org.apache.maven.surefire.junitplatform.JUnitPlatformProvider +[INFO] +[INFO] ------------------------------------------------------- +[INFO] T E S T S +[INFO] ------------------------------------------------------- +[INFO] Running com.edeal.frontline.UserServiceTest +[INFO] Tests run: 4, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 1.234 s -- in com.edeal.frontline.UserServiceTest +[INFO] Running com.edeal.frontline.api.RestControllerTest +[INFO] Tests run: 4, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 2.345 s -- in com.edeal.frontline.api.RestControllerTest +[INFO] Running com.edeal.frontline.repository.UserRepositoryTest +[INFO] Tests run: 3, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.567 s -- in com.edeal.frontline.repository.UserRepositoryTest +[INFO] Running com.edeal.frontline.service.EmailServiceTest +[INFO] Tests run: 3, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.345 s -- in com.edeal.frontline.service.EmailServiceTest +[INFO] +[INFO] Results: +[INFO] +[INFO] Tests run: 14, Failures: 0, Errors: 0, Skipped: 0 +[INFO] +[INFO] ------------------------------------------------------------------------ +[INFO] Reactor Summary for E-DEAL Parent 2027-000-SNAPSHOT: +[INFO] +[INFO] E-DEAL Parent ...................................... SUCCESS [ 0.234 s] +[INFO] edeal-common ....................................... SUCCESS [ 3.456 s] +[INFO] edeal-webapp ....................................... SUCCESS [ 15.678 s] +[INFO] ------------------------------------------------------------------------ +[INFO] BUILD SUCCESS +[INFO] ------------------------------------------------------------------------ +[INFO] Total time: 22.345 s +[INFO] Finished at: 2026-03-21T10:30:00+01:00 +[INFO] ------------------------------------------------------------------------