From 7bdd8b1db144d530ffcd5290f58eaad8a247d7c5 Mon Sep 17 00:00:00 2001 From: Teakowa <27560638+Teakowa@users.noreply.github.com> Date: Wed, 28 Oct 2026 10:19:12 +0800 Subject: [PATCH 1/3] test: prune redundant and implementation-mirroring tests Remove tests that repeat coverage held at another layer (agent/CLI surface parity, driver service, unit tests), lock help text or removed-flag history, hard-code fixture-derived counts, or need test-only production state. Terminal snapshots keep one 100-column no-color variant per command; width and color behavior stay covered by their dedicated tests. Probe-vs-capture comparisons run once, in the driver suite. --- crates/wright-analyzer/src/service.rs | 6 - crates/wright-cli/src/completion.rs | 19 - crates/wright-cli/src/present.rs | 135 +- crates/wright-cli/tests/cli.rs | 1756 ++--------------- crates/wright-cli/tests/error_wording.rs | 47 - .../wright-cli/tests/selector_vocabulary.rs | 115 -- crates/wright-cli/tests/serve.rs | 440 ----- crates/wright-cli/tests/serve_mcp.rs | 27 - crates/wright-cli/tests/serve_simulate.rs | 324 +-- ...never.snap => terminal__analyze_warn.snap} | 0 .../terminal__analyze_warn_w100_always.snap | 43 - .../terminal__analyze_warn_w80_always.snap | 43 - .../terminal__analyze_warn_w80_never.snap | 43 - ..._never.snap => terminal__check_error.snap} | 0 .../terminal__check_error_w100_always.snap | 29 - .../terminal__check_error_w80_always.snap | 29 - .../terminal__check_error_w80_never.snap | 29 - ...0_never.snap => terminal__check_pass.snap} | 0 .../terminal__check_pass_w100_always.snap | 17 - .../terminal__check_pass_w80_always.snap | 17 - .../terminal__check_pass_w80_never.snap | 17 - ...w100_never.snap => terminal__compile.snap} | 0 .../terminal__compile_w100_always.snap | 11 - .../terminal__compile_w80_always.snap | 11 - .../terminal__compile_w80_never.snap | 11 - ...100_always.snap => terminal__convert.snap} | 0 .../terminal__convert_w100_never.snap | 22 - .../terminal__convert_w80_always.snap | 22 - .../terminal__convert_w80_never.snap | 22 - ..._never.snap => terminal__inspect_cfg.snap} | 0 .../terminal__inspect_cfg_w100_always.snap | 22 - .../terminal__inspect_cfg_w80_always.snap | 22 - .../terminal__inspect_cfg_w80_never.snap | 22 - ...never.snap => terminal__inspect_cost.snap} | 0 .../terminal__inspect_cost_w100_always.snap | 24 - .../terminal__inspect_cost_w80_always.snap | 24 - .../terminal__inspect_cost_w80_never.snap | 24 - ...snap => terminal__inspect_refs_error.snap} | 0 ...minal__inspect_refs_error_w100_always.snap | 12 - ...rminal__inspect_refs_error_w80_always.snap | 12 - ...erminal__inspect_refs_error_w80_never.snap | 12 - ...er.snap => terminal__inspect_summary.snap} | 0 ...terminal__inspect_summary_w100_always.snap | 24 - .../terminal__inspect_summary_w80_always.snap | 24 - .../terminal__inspect_summary_w80_never.snap | 24 - ...er.snap => terminal__lint_gated_fail.snap} | 0 ...terminal__lint_gated_fail_w100_always.snap | 22 - .../terminal__lint_gated_fail_w80_always.snap | 22 - .../terminal__lint_gated_fail_w80_never.snap | 22 - ...00_never.snap => terminal__lint_warn.snap} | 0 .../terminal__lint_warn_w100_always.snap | 22 - .../terminal__lint_warn_w80_always.snap | 22 - .../terminal__lint_warn_w80_never.snap | 22 - ..._w100_never.snap => terminal__lookup.snap} | 0 .../terminal__lookup_w100_always.snap | 11 - .../terminal__lookup_w80_always.snap | 11 - .../snapshots/terminal__lookup_w80_never.snap | 11 - ..._never.snap => terminal__update_pass.snap} | 0 .../terminal__update_pass_w100_always.snap | 10 - .../terminal__update_pass_w80_always.snap | 10 - .../terminal__update_pass_w80_never.snap | 10 - ...snap => terminal__update_unsupported.snap} | 0 ...minal__update_unsupported_w100_always.snap | 9 - ...rminal__update_unsupported_w80_always.snap | 9 - ...erminal__update_unsupported_w80_never.snap | 9 - ...e_w100_never.snap => terminal__usage.snap} | 0 .../terminal__usage_w100_always.snap | 9 - .../snapshots/terminal__usage_w80_always.snap | 9 - .../snapshots/terminal__usage_w80_never.snap | 9 - crates/wright-cli/tests/terminal.rs | 47 +- crates/wright-cli/tests/update/main.rs | 51 - crates/wright-consumer/tests/consumer.rs | 6 - crates/wright-driver/src/diag.rs | 34 - crates/wright-driver/src/service/refusal.rs | 32 - crates/wright-driver/tests/driver.rs | 140 -- crates/wright-driver/tests/service.rs | 87 - crates/wright-language/src/service.rs | 50 - 77 files changed, 186 insertions(+), 3991 deletions(-) rename crates/wright-cli/tests/snapshots/{terminal__analyze_warn_w100_never.snap => terminal__analyze_warn.snap} (100%) delete mode 100644 crates/wright-cli/tests/snapshots/terminal__analyze_warn_w100_always.snap delete mode 100644 crates/wright-cli/tests/snapshots/terminal__analyze_warn_w80_always.snap delete mode 100644 crates/wright-cli/tests/snapshots/terminal__analyze_warn_w80_never.snap rename crates/wright-cli/tests/snapshots/{terminal__check_error_w100_never.snap => terminal__check_error.snap} (100%) delete mode 100644 crates/wright-cli/tests/snapshots/terminal__check_error_w100_always.snap delete mode 100644 crates/wright-cli/tests/snapshots/terminal__check_error_w80_always.snap delete mode 100644 crates/wright-cli/tests/snapshots/terminal__check_error_w80_never.snap rename crates/wright-cli/tests/snapshots/{terminal__check_pass_w100_never.snap => terminal__check_pass.snap} (100%) delete mode 100644 crates/wright-cli/tests/snapshots/terminal__check_pass_w100_always.snap delete mode 100644 crates/wright-cli/tests/snapshots/terminal__check_pass_w80_always.snap delete mode 100644 crates/wright-cli/tests/snapshots/terminal__check_pass_w80_never.snap rename crates/wright-cli/tests/snapshots/{terminal__compile_w100_never.snap => terminal__compile.snap} (100%) delete mode 100644 crates/wright-cli/tests/snapshots/terminal__compile_w100_always.snap delete mode 100644 crates/wright-cli/tests/snapshots/terminal__compile_w80_always.snap delete mode 100644 crates/wright-cli/tests/snapshots/terminal__compile_w80_never.snap rename crates/wright-cli/tests/snapshots/{terminal__convert_w100_always.snap => terminal__convert.snap} (100%) delete mode 100644 crates/wright-cli/tests/snapshots/terminal__convert_w100_never.snap delete mode 100644 crates/wright-cli/tests/snapshots/terminal__convert_w80_always.snap delete mode 100644 crates/wright-cli/tests/snapshots/terminal__convert_w80_never.snap rename crates/wright-cli/tests/snapshots/{terminal__inspect_cfg_w100_never.snap => terminal__inspect_cfg.snap} (100%) delete mode 100644 crates/wright-cli/tests/snapshots/terminal__inspect_cfg_w100_always.snap delete mode 100644 crates/wright-cli/tests/snapshots/terminal__inspect_cfg_w80_always.snap delete mode 100644 crates/wright-cli/tests/snapshots/terminal__inspect_cfg_w80_never.snap rename crates/wright-cli/tests/snapshots/{terminal__inspect_cost_w100_never.snap => terminal__inspect_cost.snap} (100%) delete mode 100644 crates/wright-cli/tests/snapshots/terminal__inspect_cost_w100_always.snap delete mode 100644 crates/wright-cli/tests/snapshots/terminal__inspect_cost_w80_always.snap delete mode 100644 crates/wright-cli/tests/snapshots/terminal__inspect_cost_w80_never.snap rename crates/wright-cli/tests/snapshots/{terminal__inspect_refs_error_w100_never.snap => terminal__inspect_refs_error.snap} (100%) delete mode 100644 crates/wright-cli/tests/snapshots/terminal__inspect_refs_error_w100_always.snap delete mode 100644 crates/wright-cli/tests/snapshots/terminal__inspect_refs_error_w80_always.snap delete mode 100644 crates/wright-cli/tests/snapshots/terminal__inspect_refs_error_w80_never.snap rename crates/wright-cli/tests/snapshots/{terminal__inspect_summary_w100_never.snap => terminal__inspect_summary.snap} (100%) delete mode 100644 crates/wright-cli/tests/snapshots/terminal__inspect_summary_w100_always.snap delete mode 100644 crates/wright-cli/tests/snapshots/terminal__inspect_summary_w80_always.snap delete mode 100644 crates/wright-cli/tests/snapshots/terminal__inspect_summary_w80_never.snap rename crates/wright-cli/tests/snapshots/{terminal__lint_gated_fail_w100_never.snap => terminal__lint_gated_fail.snap} (100%) delete mode 100644 crates/wright-cli/tests/snapshots/terminal__lint_gated_fail_w100_always.snap delete mode 100644 crates/wright-cli/tests/snapshots/terminal__lint_gated_fail_w80_always.snap delete mode 100644 crates/wright-cli/tests/snapshots/terminal__lint_gated_fail_w80_never.snap rename crates/wright-cli/tests/snapshots/{terminal__lint_warn_w100_never.snap => terminal__lint_warn.snap} (100%) delete mode 100644 crates/wright-cli/tests/snapshots/terminal__lint_warn_w100_always.snap delete mode 100644 crates/wright-cli/tests/snapshots/terminal__lint_warn_w80_always.snap delete mode 100644 crates/wright-cli/tests/snapshots/terminal__lint_warn_w80_never.snap rename crates/wright-cli/tests/snapshots/{terminal__lookup_w100_never.snap => terminal__lookup.snap} (100%) delete mode 100644 crates/wright-cli/tests/snapshots/terminal__lookup_w100_always.snap delete mode 100644 crates/wright-cli/tests/snapshots/terminal__lookup_w80_always.snap delete mode 100644 crates/wright-cli/tests/snapshots/terminal__lookup_w80_never.snap rename crates/wright-cli/tests/snapshots/{terminal__update_pass_w100_never.snap => terminal__update_pass.snap} (100%) delete mode 100644 crates/wright-cli/tests/snapshots/terminal__update_pass_w100_always.snap delete mode 100644 crates/wright-cli/tests/snapshots/terminal__update_pass_w80_always.snap delete mode 100644 crates/wright-cli/tests/snapshots/terminal__update_pass_w80_never.snap rename crates/wright-cli/tests/snapshots/{terminal__update_unsupported_w100_never.snap => terminal__update_unsupported.snap} (100%) delete mode 100644 crates/wright-cli/tests/snapshots/terminal__update_unsupported_w100_always.snap delete mode 100644 crates/wright-cli/tests/snapshots/terminal__update_unsupported_w80_always.snap delete mode 100644 crates/wright-cli/tests/snapshots/terminal__update_unsupported_w80_never.snap rename crates/wright-cli/tests/snapshots/{terminal__usage_w100_never.snap => terminal__usage.snap} (100%) delete mode 100644 crates/wright-cli/tests/snapshots/terminal__usage_w100_always.snap delete mode 100644 crates/wright-cli/tests/snapshots/terminal__usage_w80_always.snap delete mode 100644 crates/wright-cli/tests/snapshots/terminal__usage_w80_never.snap diff --git a/crates/wright-analyzer/src/service.rs b/crates/wright-analyzer/src/service.rs index 5bd74c39..eabcf52a 100644 --- a/crates/wright-analyzer/src/service.rs +++ b/crates/wright-analyzer/src/service.rs @@ -210,12 +210,6 @@ mod tests { let _ = ErrorInfo::new("unknown-enum member", "message"); } - #[test] - #[should_panic(expected = "violates the code grammar")] - fn error_info_new_rejects_an_uppercase_code() { - let _ = ErrorInfo::new("Unknown-Value", "message"); - } - #[test] fn error_info_carried_keeps_foreign_vocabulary() { for code in [ diff --git a/crates/wright-cli/src/completion.rs b/crates/wright-cli/src/completion.rs index 6df4a528..195c8b2d 100644 --- a/crates/wright-cli/src/completion.rs +++ b/crates/wright-cli/src/completion.rs @@ -447,23 +447,4 @@ mod tests { assert_eq!(filename_for(ShellArg::Fish), "wright.fish"); assert_eq!(filename_for(ShellArg::PowerShell), "_wright.ps1"); } - - #[test] - fn generate_script_produces_non_empty_content_for_all_shells() { - for shell in [ - ShellArg::Bash, - ShellArg::Zsh, - ShellArg::Fish, - ShellArg::PowerShell, - ] { - let script = generate_script(shell); - assert!(!script.is_empty(), "{:?} script must not be empty", shell); - let text = String::from_utf8_lossy(&script); - assert!( - text.contains("wright"), - "{:?} script contains wright", - shell - ); - } - } } diff --git a/crates/wright-cli/src/present.rs b/crates/wright-cli/src/present.rs index 7526fd85..58632780 100644 --- a/crates/wright-cli/src/present.rs +++ b/crates/wright-cli/src/present.rs @@ -1,7 +1,5 @@ use std::io::{IsTerminal, Write}; use std::path::Path; -#[cfg(test)] -use std::sync::atomic::AtomicUsize; use std::sync::{ Arc, Mutex, atomic::{AtomicBool, Ordering}, @@ -60,8 +58,6 @@ pub(crate) struct Activity { visible: Arc, status: Arc>>, output: Arc>, - #[cfg(test)] - frame: Arc, handle: Option>, } @@ -72,8 +68,6 @@ impl Activity { visible: Arc::new(AtomicBool::new(false)), status: Arc::new(Mutex::new(None)), output: Arc::new(Mutex::new(())), - #[cfg(test)] - frame: Arc::new(AtomicUsize::new(0)), handle: None, } } @@ -83,15 +77,11 @@ impl Activity { let visible = Arc::new(AtomicBool::new(false)); let status = Arc::new(Mutex::new(None)); let output = Arc::new(Mutex::new(())); - #[cfg(test)] - let frame = Arc::new(AtomicUsize::new(0)); write_activity_line(&output, &done, None, None); visible.store(true, Ordering::Release); let t_done = Arc::clone(&done); let t_status = Arc::clone(&status); let t_output = Arc::clone(&output); - #[cfg(test)] - let t_frame = Arc::clone(&frame); let handle = thread::spawn(move || { thread::sleep(Duration::from_millis(60)); let mut f = 0; @@ -99,8 +89,6 @@ impl Activity { let event = *t_status.lock().expect("lock"); write_activity_line(&t_output, &t_done, event, Some(SPINNER[f])); f = (f + 1) % SPINNER.len(); - #[cfg(test)] - t_frame.store(f, Ordering::Release); thread::sleep(Duration::from_millis(80)); } }); @@ -109,8 +97,6 @@ impl Activity { visible, status, output, - #[cfg(test)] - frame, handle: Some(handle), } } @@ -1360,47 +1346,6 @@ mod tests { assert!(dumb.activity().handle.is_none()); } - #[test] - fn activity_is_visible_immediately_and_accepts_phase_updates() { - let terminal = Presentation::resolve( - OutputFormat::Text, - RendererArg::Terminal, - ColorArg::Never, - environment(), - ); - let activity = terminal.activity(); - assert!(activity.visible.load(Ordering::Acquire)); - assert_eq!(*activity.status.lock().unwrap(), None); - activity.on_progress(ProgressEvent::with_count( - ProgressPhase::Linting, - 12, - ProgressUnit::Rules, - )); - assert_eq!( - *activity.status.lock().unwrap(), - Some(ProgressEvent::with_count( - ProgressPhase::Linting, - 12, - ProgressUnit::Rules, - )) - ); - thread::sleep(Duration::from_millis(150)); - assert!(activity.frame.load(Ordering::Acquire) > 0); - } - - #[test] - fn activity_cleanup_clears_the_line_and_terminates_it() { - let mut output = Vec::new(); - clear_activity_line(&mut output); - assert_eq!(output, b"\r\x1b[2K\r\n"); - } - - #[test] - fn metadata_is_dimmed_only_for_terminal_color_output() { - assert_eq!(dim("2 symbols", false), "2 symbols"); - assert_eq!(dim("2 symbols", true), "\x1b[2m2 symbols\x1b[0m"); - } - #[test] fn workflow_command_escaping_is_split_by_context() { assert_eq!(escape_workflow_property("a,b:c%\n"), "a%2Cb%3Ac%25%0A"); @@ -1457,40 +1402,27 @@ mod tests { } #[test] - fn summary_info_only_is_pass() { - let envelope = summary_envelope( - true, - vec![diagnostic(Severity::Info)], - serde_json::json!([ - {"severity": "info"}, - {"severity": "notice"} - ]), - ); - assert_eq!(summary_status(&envelope), "PASS"); - } - - #[test] - fn summary_warning_over_info_is_warn() { - let envelope = summary_envelope( - true, - vec![diagnostic(Severity::Warning)], - serde_json::json!([{"severity": "info"}]), - ); - assert_eq!(summary_status(&envelope), "WARN"); - } - - #[test] - fn summary_error_over_warning_is_error() { - let envelope = summary_envelope( - true, - vec![diagnostic(Severity::Error)], - serde_json::json!([ - {"severity": "warning"}, - {"severity": "error"}, - {"severity": "info"} - ]), - ); - assert_eq!(summary_status(&envelope), "ERROR"); + fn summary_status_is_the_highest_severity_and_info_passes() { + for (diagnostic_severity, findings, expected) in [ + ( + Severity::Info, + serde_json::json!([{"severity": "info"}, {"severity": "notice"}]), + "PASS", + ), + ( + Severity::Warning, + serde_json::json!([{"severity": "info"}]), + "WARN", + ), + ( + Severity::Error, + serde_json::json!([{"severity": "warning"}, {"severity": "error"}]), + "ERROR", + ), + ] { + let envelope = summary_envelope(true, vec![diagnostic(diagnostic_severity)], findings); + assert_eq!(summary_status(&envelope), expected); + } } #[test] @@ -1519,15 +1451,6 @@ mod tests { assert_eq!(summary_status(&envelope), "FAIL"); } - #[test] - fn gate_counts_info_severity_for_fail_on_info() { - let mut envelope = - summary_envelope(true, Vec::new(), serde_json::json!([{"severity": "info"}])); - gate(&mut envelope, Severity::Info); - assert_eq!(envelope.exit, 1); - assert_eq!(summary_status(&envelope), "FAIL"); - } - #[test] fn gate_judges_the_full_set_not_the_selected_remainder() { let mut envelope = check_envelope( @@ -1585,15 +1508,6 @@ mod tests { assert_eq!(summary_status(&envelope), "WARN"); } - #[test] - fn gate_leaves_a_clean_result_passing() { - let mut envelope = summary_envelope(true, Vec::new(), serde_json::json!([])); - gate(&mut envelope, Severity::Info); - assert!(!envelope.gated); - assert_eq!(envelope.exit, 0); - assert_eq!(summary_status(&envelope), "PASS"); - } - #[test] fn gate_reads_the_essential_projection_severity() { let mut envelope = Envelope { @@ -1693,13 +1607,6 @@ mod tests { assert_eq!(result.metadata().unwrap(), "7 finding(s) across 2 rule(s)"); } - #[test] - fn severity_rank_orders_errors_first() { - assert_eq!(severity_rank(Severity::Error), 0); - assert_eq!(severity_rank(Severity::Warning), 1); - assert_eq!(severity_rank(Severity::Info), 2); - } - #[test] fn finding_location_resolves_reported_paths_against_the_source_base() { let root = std::env::temp_dir().join(format!("wright-finding-{}", std::process::id())); diff --git a/crates/wright-cli/tests/cli.rs b/crates/wright-cli/tests/cli.rs index a266c670..02b8950a 100644 --- a/crates/wright-cli/tests/cli.rs +++ b/crates/wright-cli/tests/cli.rs @@ -129,18 +129,6 @@ fn command_result(output: &std::process::Output) -> String { ) } -#[test] -fn compile_over_workshop_file_emits_correct_text() { - let path = temp_file("basic.txt", &corpus_workshop("synthetic/basic-rule")); - let output = run(&["compile", path.to_str().unwrap(), "-o", "-"]); - assert!(output.status.success()); - assert!(output.stderr.is_empty(), "stderr clean on success"); - let stdout = String::from_utf8_lossy(&output.stdout); - assert!(stdout.contains("Disable Inspector Recording"), "{stdout}"); - assert!(stdout.contains("Ongoing - Global")); - let _ = std::fs::remove_dir_all(path.parent().unwrap()); -} - #[test] fn compile_over_element_limit_warns_and_still_emits() { let array = (0..17_000).map(|_| "1").collect::>().join(", "); @@ -434,40 +422,6 @@ fn compile_to_rejects_unknown_and_unmapped_target_locales() { let _ = std::fs::remove_dir_all(&dir); } -#[test] -fn check_over_clean_input_exits_zero() { - let path = temp_file("basic.txt", &corpus_workshop("synthetic/basic-rule")); - let output = run(&["check", path.to_str().unwrap()]); - assert_eq!(output.status.code(), Some(0)); - assert!(String::from_utf8_lossy(&output.stdout).contains("PASS check")); - let _ = std::fs::remove_dir_all(path.parent().unwrap()); -} - -#[test] -fn terminal_renderer_uses_command_specific_hierarchy() { - let path = temp_file("flow.txt", &corpus_workshop("synthetic/control-flow")); - for (command, heading, detail) in [ - ("check", " PASS check", "0 diagnostics"), - ("lint", " WARN lint", "Lint findings"), - ("analyze", " WARN analyze", "Program overview"), - ("inspect", " INSPECT", "Rules"), - ] { - let output = run(&[ - command, - path.to_str().unwrap(), - "--renderer", - "terminal", - "--color", - "never", - ]); - assert!(output.status.code().is_some(), "{command} exited by signal"); - let stdout = String::from_utf8_lossy(&output.stdout); - assert!(stdout.contains(heading), "{command}: {stdout}"); - assert!(stdout.contains(detail), "{command}: {stdout}"); - } - let _ = std::fs::remove_dir_all(path.parent().unwrap()); -} - #[test] fn analyze_real_project_report_is_bounded_and_ranked() { let path = temp_file( @@ -500,34 +454,6 @@ fn analyze_real_project_report_is_bounded_and_ranked() { let _ = std::fs::remove_dir_all(path.parent().unwrap()); } -#[test] -fn analyze_low_cost_program_reports_cost_without_risks() { - let path = temp_file("basic.txt", &corpus_workshop("synthetic/basic-rule")); - let output = run(&[ - "analyze", - path.to_str().unwrap(), - "--renderer", - "terminal", - "--color", - "never", - ]); - assert!(output.status.success()); - let stdout = String::from_utf8_lossy(&output.stdout); - assert!(stdout.contains("Workshop cost"), "{stdout}"); - assert!(stdout.contains("[exact]"), "{stdout}"); - assert!( - stdout.contains("Performance and stability risks"), - "{stdout}" - ); - let risks = stdout - .split("Performance and stability risks") - .nth(1) - .expect("risks section"); - let risks = risks.split("State and coupling").next().unwrap(); - assert!(risks.contains("none"), "{stdout}"); - let _ = std::fs::remove_dir_all(path.parent().unwrap()); -} - #[test] fn check_over_malformed_input_exits_one_with_structured_diagnostics() { let path = temp_file( @@ -581,534 +507,124 @@ rule ("x") { "#; #[test] -fn terminal_renderer_marks_independent_diagnostics() { - let path = temp_file("mixed.txt", MIXED_CHECK_SOURCE); - let output = run(&[ - "check", - path.to_str().unwrap(), - "--renderer", - "terminal", - "--color", - "never", - ]); +fn check_unmapped_locations_are_not_fabricated() { + let output = run_with_stdin( + &["check", "-", "--renderer", "plain", "--color", "never"], + "rule (\"x\") { event { Ongoing - Global; } actions { If(True); }", + ); assert_eq!(output.status.code(), Some(1)); let stdout = String::from_utf8_lossy(&output.stdout); - assert!(stdout.contains("error["), "{stdout}"); - assert!(stdout.contains("warning["), "{stdout}"); - assert!( - stdout.contains(" --> ") && stdout.contains(" = "), - "locations and secondary metadata stay under the diagnostic: {stdout}" - ); - let error_at = stdout.find("error[").expect("an error diagnostic"); - let warning_at = stdout.find("warning[").expect("a warning diagnostic"); - assert!( - error_at < warning_at, - "blocking errors precede warnings: {stdout}" - ); - assert!( - stdout.contains("Set Global Variable(B, FutureValueThing(2));"), - "the source context stays with the diagnostic: {stdout}" - ); - assert!(output.stderr.is_empty(), "{}", command_result(&output)); - let stderr = String::from_utf8_lossy(&output.stderr); - assert!( - stdout.starts_with(" ERROR check\n"), - "the verdict tag leads: {stdout}" - ); - assert!( - stdout.contains(" 1 error, 2 warnings\n"), - "counts carry no (s) suffix: {stdout}" - ); assert!( - !stdout.contains(['╭', '├', '╰', '│']) && !stderr.contains(['╭', '├', '╰', '│']), - "no rail frame anywhere: {stdout}{stderr}" + stdout.contains("at=:1:63"), + "the unmapped position is one verbatim token: {stdout}" ); - let _ = std::fs::remove_dir_all(path.parent().unwrap()); + assert!(!stdout.contains("-->"), "{stdout}"); } #[test] -fn plain_renderer_keeps_the_undecorated_diagnostic_layout() { +fn check_json_prioritizes_blocking_diagnostics_like_text() { let path = temp_file("mixed.txt", MIXED_CHECK_SOURCE); - let output = run(&[ - "check", - path.to_str().unwrap(), - "--renderer", - "plain", - "--color", - "never", - ]); + let output = run(&["check", path.to_str().unwrap(), "-f", "json"]); assert_eq!(output.status.code(), Some(1)); - let stdout = String::from_utf8_lossy(&output.stdout); - assert!( - !stdout.contains('├') && !stdout.contains('╰') && !stdout.contains(" --> "), - "plain stays undecorated: {stdout}" - ); - assert!(stdout.contains("diagnostic: error "), "{stdout}"); - assert!( - stdout.contains("at=") && stdout.contains("mixed.txt:"), - "{stdout}" - ); - assert!(output.stderr.is_empty(), "{}", command_result(&output)); + let envelope = parse_json(&output.stdout); + let severities: Vec<&str> = envelope["diagnostics"] + .as_array() + .unwrap() + .iter() + .map(|diagnostic| diagnostic["severity"].as_str().unwrap()) + .collect(); + assert_eq!(severities, ["error", "warning", "warning"]); let _ = std::fs::remove_dir_all(path.parent().unwrap()); } -#[test] -fn terminal_renderer_marks_lint_findings_and_keeps_compact_results_flat() { - let path = temp_file("flow.txt", &corpus_workshop("synthetic/control-flow")); - let output = run(&[ - "lint", - path.to_str().unwrap(), - "--renderer", - "terminal", - "--color", - "never", - ]); - assert!(output.status.success()); - let stdout = String::from_utf8_lossy(&output.stdout); - assert!( - stdout.starts_with(" WARN lint\n"), - "the verdict tag leads: {stdout}" - ); - assert!( - stdout.contains("Lint findings (evidence: static-indicator)"), - "uniform evidence is stated once in the section heading: {stdout}" - ); - assert!( - stdout.contains(" warning[min-wait-loop]:"), - "the finding indents under its section: {stdout}" - ); - assert!( - stdout.contains(" --> "), - "the location indents under the finding: {stdout}" - ); - assert!( - !stdout.contains("(s)"), - "counts carry no (s) suffix: {stdout}" - ); - let _ = std::fs::remove_dir_all(path.parent().unwrap()); - - let path = temp_file("basic.txt", &corpus_workshop("synthetic/basic-rule")); - let output = run(&[ - "check", - path.to_str().unwrap(), - "--renderer", - "terminal", - "--color", - "never", - ]); - assert!(output.status.success()); - let stdout = String::from_utf8_lossy(&output.stdout); - assert_eq!( - stdout, - " PASS check\n 0 diagnostics\n\nProgram overview\n 1 file, 1 rule, \ - 0 global variables, 0 player variables, 0 subroutines\n\nNext\n \ - wright lint\n wright analyze\n wright inspect\n", - "{stdout}" - ); - let _ = std::fs::remove_dir_all(path.parent().unwrap()); +const FIXABLE_WORKSHOP: &str = r#"variables { + global: + 0: index + 1: other } - -#[test] -fn only_the_terminal_renderer_draws_the_verdict_tag() { - let path = temp_file("flow.txt", &corpus_workshop("synthetic/control-flow")); - let path = path.to_str().unwrap(); - let terminal = run(&["lint", path, "--renderer", "terminal", "--color", "never"]); - let terminal = String::from_utf8_lossy(&terminal.stdout).into_owned(); - assert!(terminal.starts_with(" WARN lint\n"), "{terminal}"); - - type Env<'a> = &'a [(&'a str, &'a str)]; - let undecorated: [(&[&str], Env); 5] = [ - (&["--renderer", "plain"], &[]), - (&[], &[]), - (&[], &[("CI", "true")]), - (&[], &[("GITHUB_ACTIONS", "true")]), - (&["--format", "json"], &[]), - ]; - for (flags, env) in undecorated { - let mut args = vec!["lint", path]; - args.extend_from_slice(flags); - let output = run_with_env(&args, env); - let text = format!( - "{}{}", - String::from_utf8_lossy(&output.stdout), - String::from_utf8_lossy(&output.stderr) - ); - assert!( - !text.contains(['╭', '├', '╰', '│', '\u{1}', '\u{2}', '\u{3}']), - "{flags:?} {env:?} is decorated: {text}" - ); - assert!( - !text.contains(" WARN lint"), - "{flags:?} {env:?} draws the tag: {text}" - ); +rule ("duplicates") { + event { + Ongoing - Global; + } + actions { + If(Compare(Global.index, ==, 0)); + Set Global Variable(other, 1); + Else If(Compare(Global.index, ==, 0)); + Set Global Variable(other, 2); + End; + While(Compare(Global.index, <, 3)); + Set Global Variable(other, Distance Between(Position Of(Event Player), Vector(0, 0, 0))); + Set Global Variable(index, Distance Between(Position Of(Event Player), Vector(0, 0, 0))); + Wait(0.016, Ignore Condition); + End; } - let _ = std::fs::remove_dir_all(std::path::Path::new(path).parent().unwrap()); } +"#; #[test] -fn terminal_renderer_never_leaks_frame_markers() { - let path = temp_file("flow.txt", &corpus_workshop("synthetic/control-flow")); +fn lint_fix_previews_and_writes_validated_fixes() { + let path = temp_file("fixable.ws", FIXABLE_WORKSHOP); let path = path.to_str().unwrap(); - let invocations: [&[&str]; 4] = [ - &["convert", "--target", "opy", path], - &["check", path], - &["lint", path], - &["analyze", path], - ]; - for args in invocations { - let mut args = args.to_vec(); - args.extend(["--renderer", "terminal", "--color", "never"]); - let output = run(&args); - let text = format!( - "{}{}", - String::from_utf8_lossy(&output.stdout), - String::from_utf8_lossy(&output.stderr) - ); - assert!( - !text.contains(['\u{1}', '\u{2}', '\u{3}']), - "{args:?} leaked a control marker: {text:?}" - ); + + let output = run(&["lint", path, "--fix", "-f", "json"]); + assert!(output.status.success(), "{}", command_result(&output)); + let findings = parse_json(&output.stdout)["result"]["findings"] + .as_array() + .unwrap() + .clone(); + for code in ["duplicate-condition", "repeated-value"] { + let fix = findings + .iter() + .find(|finding| finding["code"] == code) + .and_then(|finding| finding.get("fix")) + .unwrap_or_else(|| panic!("{code} carries a fix")); assert!( - !text.contains(['╭', '├', '╰', '│']), - "{args:?} drew the retired rail: {text:?}" + fix["transaction"]["edits"] + .as_array() + .is_some_and(|edits| !edits.is_empty()), + "the fix carries a source-edit transaction: {fix}" ); } - let _ = std::fs::remove_dir_all(std::path::Path::new(path).parent().unwrap()); -} -#[test] -fn terminal_renderer_marks_analyze_risks_and_cfg_blocks() { - let path = temp_file("flow.txt", &corpus_workshop("synthetic/control-flow")); - let output = run(&[ - "analyze", - path.to_str().unwrap(), - "--renderer", - "terminal", - "--color", - "never", - ]); - assert!(output.status.success()); + let output = run(&["lint", path, "--fix"]); + assert!(output.status.success(), "{}", command_result(&output)); let stdout = String::from_utf8_lossy(&output.stdout); assert!( - stdout.contains("Performance and stability risks (evidence: static-indicator)"), - "the risk section states its evidence once: {stdout}" + stdout.contains("fix: preview duplicate-condition"), + "{stdout}" ); - assert!( - stdout.contains("warning[min-wait-loop]"), - "the risk item sits inside its section: {stdout}" + assert!(stdout.contains("fix: preview repeated-value"), "{stdout}"); + assert_eq!( + std::fs::read_to_string(path).unwrap(), + FIXABLE_WORKSHOP, + "preview writes nothing" ); - let output = run(&[ - "inspect", - "cfg", - "bounded while", - path.to_str().unwrap(), - "--renderer", - "terminal", - "--color", - "never", - ]); - assert!(output.status.success()); + let output = run(&["lint", path, "--fix", "--write"]); + assert!(output.status.success(), "{}", command_result(&output)); let stdout = String::from_utf8_lossy(&output.stdout); assert!( - stdout.starts_with(" CFG\n"), - "a query command's tag is its own name: {stdout}" + stdout.contains("fix: applied duplicate-condition"), + "{stdout}" ); - assert!(stdout.contains(" block "), "{stdout}"); + assert!(stdout.contains("fix: applied repeated-value"), "{stdout}"); + let applied = std::fs::read_to_string(path).unwrap(); assert!( - stdout.contains(" -> "), - "successors indent under their block: {stdout}" + !applied.contains("Else If(Compare(Global.index, ==, 0));"), + "the dead branch is gone: {applied}" ); - let _ = std::fs::remove_dir_all(path.parent().unwrap()); -} - -#[test] -fn check_unmapped_locations_are_not_fabricated() { - let output = run_with_stdin( - &["check", "-", "--renderer", "plain", "--color", "never"], - "rule (\"x\") { event { Ongoing - Global; } actions { If(True); }", + assert_eq!( + applied + .matches("Evaluate Once(Distance Between(Position Of(Event Player), Vector(0, 0, 0)))") + .count(), + 2, + "each duplicated occurrence wraps: {applied}" ); - assert_eq!(output.status.code(), Some(1)); - let stdout = String::from_utf8_lossy(&output.stdout); + let output = run(&["check", path]); assert!( - stdout.contains("at=:1:63"), - "the unmapped position is one verbatim token: {stdout}" - ); - assert!(!stdout.contains("-->"), "{stdout}"); -} - -#[test] -fn check_success_renders_a_compact_deterministic_summary() { - let path = temp_file("basic.txt", &corpus_workshop("synthetic/basic-rule")); - let output = run(&[ - "check", - path.to_str().unwrap(), - "--renderer", - "plain", - "--color", - "never", - ]); - assert_eq!(output.status.code(), Some(0)); - assert!(output.stderr.is_empty()); - let stdout = String::from_utf8_lossy(&output.stdout); - let mut lines = stdout.lines(); - assert!( - lines - .next() - .unwrap_or_default() - .starts_with("PASS check root="), - "{stdout}" - ); - assert_eq!( - lines.next(), - Some("summary: errors=0 warnings=0 notes=0"), - "{stdout}" - ); - assert_eq!( - lines.next(), - Some("program: files=1 rules=1 globalVariables=0 playerVariables=0 subroutines=0"), - "{stdout}" - ); - for expected in [ - "next: wright lint", - "next: wright analyze", - "next: wright inspect", - ] { - assert_eq!(lines.next(), Some(expected), "{stdout}"); - } - assert!(lines.next().is_none(), "{stdout}"); - let _ = std::fs::remove_dir_all(path.parent().unwrap()); -} - -#[test] -fn check_json_prioritizes_blocking_diagnostics_like_text() { - let path = temp_file("mixed.txt", MIXED_CHECK_SOURCE); - let output = run(&["check", path.to_str().unwrap(), "-f", "json"]); - assert_eq!(output.status.code(), Some(1)); - let envelope = parse_json(&output.stdout); - let severities: Vec<&str> = envelope["diagnostics"] - .as_array() - .unwrap() - .iter() - .map(|diagnostic| diagnostic["severity"].as_str().unwrap()) - .collect(); - assert_eq!(severities, ["error", "warning", "warning"]); - let _ = std::fs::remove_dir_all(path.parent().unwrap()); -} - -#[cfg(any(target_os = "linux", target_os = "macos"))] -#[test] -fn check_tty_clears_progress_then_renders_verdict_and_elapsed() { - let path = temp_file("flow.txt", &corpus_workshop("synthetic/control-flow")); - let output = run_in_tty(&[ - "check", - path.to_str().unwrap(), - "--renderer", - "terminal", - "--color", - "never", - ]); - assert!(output.status.success()); - let mut transcript = output.stdout; - transcript.extend_from_slice(&output.stderr); - let transcript = String::from_utf8_lossy(&transcript); - let verdict = transcript - .find(" PASS check") - .expect("the final verdict renders"); - assert!( - transcript[..verdict].contains("\x1b[2K"), - "transient progress is cleared before the verdict: {transcript}" - ); - assert!( - !transcript[verdict..].contains("Resolving input"), - "progress labels never compete with the verdict: {transcript}" - ); - assert!( - transcript[verdict..].contains(" ms"), - "the interactive footer carries elapsed time: {transcript}" - ); - let _ = std::fs::remove_dir_all(path.parent().unwrap()); -} - -#[test] -fn analyze_over_workshop_input_reports_semantic_facts() { - let path = temp_file("flow.txt", &corpus_workshop("synthetic/control-flow")); - let output = run(&["analyze", path.to_str().unwrap(), "-f", "json"]); - assert!(output.status.success()); - let envelope = parse_json(&output.stdout); - assert!(envelope["result"]["program"]["findings"].is_null()); - assert!( - !envelope["result"]["variables"] - .as_array() - .unwrap() - .is_empty() - ); - assert!( - !envelope["result"]["hotspots"] - .as_array() - .unwrap() - .is_empty() - ); - assert!( - envelope["result"]["persistentObjects"].is_object(), - "analyze reports persistent-object facts (#429)" - ); - let cost = &envelope["result"]["cost"]; - assert!( - cost["elementCount"].as_u64().is_some_and(|total| total > 0), - "analyze reports the canonical element count (#445): {cost}" - ); - assert!( - cost["counts"]["actions"].as_u64().is_some(), - "analyze reports structural counts (#445): {cost}" - ); - let rule = &envelope["result"]["hotspots"][0]; - assert!( - rule["elements"].as_u64().is_some(), - "analyze attributes element cost to rules (#445): {rule}" - ); - assert!( - envelope["result"]["risks"].is_array(), - "analyze reports performance/stability risk facts (#445)" - ); - let _ = std::fs::remove_dir_all(path.parent().unwrap()); -} - -#[test] -fn inspect_over_workshop_input_lists_rules_and_symbols() { - let path = temp_file("decl.txt", &corpus_workshop("synthetic/declarations-rules")); - let output = run(&["inspect", path.to_str().unwrap(), "-f", "json"]); - assert!(output.status.success()); - let envelope = parse_json(&output.stdout); - assert!(!envelope["result"]["rules"].as_array().unwrap().is_empty()); - assert!(envelope["result"]["symbols"].is_null()); - assert!(envelope["result"]["counts"]["symbols"].as_u64().unwrap() > 0); - let _ = std::fs::remove_dir_all(path.parent().unwrap()); -} - -#[test] -fn lint_over_workshop_input_reports_findings_in_text_and_json() { - let path = temp_file("flow.txt", &corpus_workshop("synthetic/control-flow")); - let output = run(&["lint", path.to_str().unwrap()]); - assert!( - output.status.success(), - "{}", - String::from_utf8_lossy(&output.stderr) - ); - let stdout = String::from_utf8_lossy(&output.stdout); - assert!(stdout.contains("WARN lint"), "summary line: {stdout}"); - assert!(stdout.contains("min-wait-loop"), "findings: {stdout}"); - assert!( - stdout.contains("evidence="), - "text mode exposes the evidence class: {stdout}" - ); - let output = run(&["lint", path.to_str().unwrap(), "-f", "json"]); - assert!(output.status.success()); - let envelope = parse_json(&output.stdout); - assert_eq!(envelope["command"], "lint"); - assert_eq!(envelope["ok"], true); - let findings = envelope["result"]["findings"].as_array().unwrap(); - assert!(!findings.is_empty(), "control-flow produces findings"); - for finding in findings { - assert!(finding["evidence"].is_string(), "findings carry evidence"); - assert!( - finding["location"].is_string(), - "finding spans carry the resolved path" - ); - } - assert!(envelope["result"]["rules"].is_null()); - assert!(envelope["result"]["config"].is_null()); - let _ = std::fs::remove_dir_all(path.parent().unwrap()); -} - -const FIXABLE_WORKSHOP: &str = r#"variables { - global: - 0: index - 1: other -} -rule ("duplicates") { - event { - Ongoing - Global; - } - actions { - If(Compare(Global.index, ==, 0)); - Set Global Variable(other, 1); - Else If(Compare(Global.index, ==, 0)); - Set Global Variable(other, 2); - End; - While(Compare(Global.index, <, 3)); - Set Global Variable(other, Distance Between(Position Of(Event Player), Vector(0, 0, 0))); - Set Global Variable(index, Distance Between(Position Of(Event Player), Vector(0, 0, 0))); - Wait(0.016, Ignore Condition); - End; - } -} -"#; - -#[test] -fn lint_fix_previews_and_writes_validated_fixes() { - let path = temp_file("fixable.ws", FIXABLE_WORKSHOP); - let path = path.to_str().unwrap(); - - let output = run(&["lint", path, "--fix", "-f", "json"]); - assert!(output.status.success(), "{}", command_result(&output)); - let findings = parse_json(&output.stdout)["result"]["findings"] - .as_array() - .unwrap() - .clone(); - for code in ["duplicate-condition", "repeated-value"] { - let fix = findings - .iter() - .find(|finding| finding["code"] == code) - .and_then(|finding| finding.get("fix")) - .unwrap_or_else(|| panic!("{code} carries a fix")); - assert!( - fix["transaction"]["edits"] - .as_array() - .is_some_and(|edits| !edits.is_empty()), - "the fix carries a source-edit transaction: {fix}" - ); - } - - let output = run(&["lint", path, "--fix"]); - assert!(output.status.success(), "{}", command_result(&output)); - let stdout = String::from_utf8_lossy(&output.stdout); - assert!( - stdout.contains("fix: preview duplicate-condition"), - "{stdout}" - ); - assert!(stdout.contains("fix: preview repeated-value"), "{stdout}"); - assert_eq!( - std::fs::read_to_string(path).unwrap(), - FIXABLE_WORKSHOP, - "preview writes nothing" - ); - - let output = run(&["lint", path, "--fix", "--write"]); - assert!(output.status.success(), "{}", command_result(&output)); - let stdout = String::from_utf8_lossy(&output.stdout); - assert!( - stdout.contains("fix: applied duplicate-condition"), - "{stdout}" - ); - assert!(stdout.contains("fix: applied repeated-value"), "{stdout}"); - let applied = std::fs::read_to_string(path).unwrap(); - assert!( - !applied.contains("Else If(Compare(Global.index, ==, 0));"), - "the dead branch is gone: {applied}" - ); - assert_eq!( - applied - .matches("Evaluate Once(Distance Between(Position Of(Event Player), Vector(0, 0, 0)))") - .count(), - 2, - "each duplicated occurrence wraps: {applied}" - ); - let output = run(&["check", path]); - assert!( - output.status.success(), - "check passes after the fixes: {}", - command_result(&output) + output.status.success(), + "check passes after the fixes: {}", + command_result(&output) ); let output = run(&["lint", path, "-f", "json"]); let remaining = parse_json(&output.stdout)["result"]["findings"] @@ -1356,18 +872,6 @@ fn lint_selection_filters_findings_and_reports_withheld() { let _ = std::fs::remove_dir_all(Path::new(path).parent().unwrap()); } -#[test] -fn omitted_selection_reproduces_the_unfiltered_envelope() { - let path = temp_file("cake.txt", &corpus_workshop("real-world/overpy-cake")); - let output = run(&["lint", path.to_str().unwrap(), "-f", "json"]); - assert!(output.status.success()); - let envelope = parse_json(&output.stdout); - assert_eq!(envelope["result"]["findings"].as_array().unwrap().len(), 10); - assert!(envelope["result"].get("selection").is_none()); - assert!(envelope.get("selection").is_none()); - let _ = std::fs::remove_dir_all(path.parent().unwrap()); -} - #[test] fn selection_never_changes_the_verdict_or_exit_code() { let broken = temp_file( @@ -1534,17 +1038,6 @@ fn fail_on_can_only_raise_a_verdict() { let _ = std::fs::remove_dir_all(cake.parent().unwrap()); } -#[test] -fn lint_max_reports_the_withheld_count_in_text_and_json() { - let path = temp_file("cake.txt", &corpus_workshop("real-world/overpy-cake")); - let output = run(&["lint", path.to_str().unwrap(), "--max", "3"]); - assert!(output.status.success()); - let stdout = String::from_utf8_lossy(&output.stdout); - assert!(stdout.contains("withheld=7"), "{stdout}"); - assert!(stdout.contains("findings=10"), "{stdout}"); - let _ = std::fs::remove_dir_all(path.parent().unwrap()); -} - #[test] fn essential_reports_bound_lists_and_expose_expansion() { let rules = (0..21).map(|i|format!("rule (\"Rule{i}\") {{ event {{ Ongoing - Global; }} actions {{ Wait(1, Ignore Condition); }} }}")).collect::>().join("\n"); @@ -1643,219 +1136,12 @@ const WAITLESS_LOOP: &str = "rule (\"r\") { "; #[test] -fn analyze_default_and_all_report_the_same_verdict() { - let path = temp_file("waitless.txt", WAITLESS_LOOP); - for args in [ - vec!["analyze", path.to_str().unwrap()], - vec!["analyze", path.to_str().unwrap(), "--all"], - ] { - let output = run(&args); - assert!( - output.status.success(), - "{args:?}: {}", - String::from_utf8_lossy(&output.stderr) - ); - let stdout = String::from_utf8_lossy(&output.stdout); - assert!(stdout.contains("WARN analyze"), "{args:?}: {stdout}"); - } - - let lint = run(&["lint", path.to_str().unwrap(), "--severity", "error"]); - let lint_stdout = String::from_utf8_lossy(&lint.stdout); - assert!(lint_stdout.contains("WARN lint"), "{lint_stdout}"); - let expanded = run(&[ - "lint", - path.to_str().unwrap(), - "--severity", - "error", - "--all", - ]); - let expanded_stdout = String::from_utf8_lossy(&expanded.stdout); - assert!(expanded_stdout.contains("WARN lint"), "{expanded_stdout}"); - - let _ = std::fs::remove_dir_all(path.parent().unwrap()); -} - -#[test] -fn analyze_selection_narrows_facts_risks() { - let path = temp_file("cake.txt", &corpus_workshop("real-world/overpy-cake")); - - let output = run(&[ - "analyze", - path.to_str().unwrap(), - "--rule-id", - "repeated-value", - "--max", - "2", - "-f", - "json", - ]); - assert!(output.status.success()); - let envelope = parse_json(&output.stdout); - let risks = envelope["result"]["risks"].as_array().unwrap(); - assert_eq!(risks.len(), 2); - assert!(risks.iter().all(|risk| risk["code"] == "repeated-value")); - assert_eq!(envelope["result"]["selection"]["total"], 10); - assert_eq!(envelope["result"]["selection"]["withheld"], 7); - - let output = run(&[ - "analyze", - path.to_str().unwrap(), - "--severity", - "error", - "-f", - "json", - ]); - let envelope = parse_json(&output.stdout); - assert!(envelope["result"]["risks"].as_array().unwrap().is_empty()); - assert_eq!(envelope["result"]["selection"]["total"], 10); - - let output = run(&["analyze", path.to_str().unwrap(), "--severity", "error"]); - let stdout = String::from_utf8_lossy(&output.stdout); - assert!(stdout.contains("WARN analyze"), "{stdout}"); - let output = run(&["analyze", path.to_str().unwrap(), "--max", "2"]); - let stdout = String::from_utf8_lossy(&output.stdout); - assert!(stdout.contains("withheld=8"), "{stdout}"); - - let _ = std::fs::remove_dir_all(path.parent().unwrap()); -} - -#[test] -fn inspect_json_resolves_span_paths() { - let path = temp_file("cake.txt", &corpus_workshop("real-world/overpy-cake")); - let output = run(&["inspect", "rules", path.to_str().unwrap(), "-f", "json"]); - assert!(output.status.success()); - let result = parse_json(&output.stdout)["result"].clone(); - - fn collect(value: &serde_json::Value, spans: &mut Vec) { - match value { - serde_json::Value::Object(object) => { - if let Some(span) = object.get("span") - && span.is_object() - { - spans.push(span.clone()); - } - object.values().for_each(|value| collect(value, spans)); - } - serde_json::Value::Array(items) => { - items.iter().for_each(|value| collect(value, spans)); - } - _ => {} - } - } - let mut spans = Vec::new(); - collect(&result, &mut spans); - assert!(!spans.is_empty(), "the fixture carries spans"); - assert!( - spans - .iter() - .all(|span| span["path"].as_str().is_some_and(|p| !p.is_empty())), - "every span carries a resolved path: {spans:?}" - ); - - let _ = std::fs::remove_dir_all(path.parent().unwrap()); -} - -#[test] -fn inspect_cost_verdict_and_file_selection() { - let path = temp_file("waitless.txt", WAITLESS_LOOP); - let output = run(&["inspect", "cost", path.to_str().unwrap()]); - assert!(output.status.success()); - let stdout = String::from_utf8_lossy(&output.stdout); - assert!(stdout.contains("WARN cost"), "{stdout}"); - - let output = run(&[ - "inspect", - "cost", - path.to_str().unwrap(), - "--file", - "waitless.txt", - "-f", - "json", - ]); - assert_eq!(output.status.code(), Some(0)); - let envelope = parse_json(&output.stdout); - assert_eq!(envelope["result"]["findings"].as_array().unwrap().len(), 1); - assert_eq!(envelope["result"]["selection"]["total"], 1); - - let output = run(&[ - "inspect", - "cost", - path.to_str().unwrap(), - "--file", - "other-file.txt", - "-f", - "json", - ]); - assert_eq!(output.status.code(), Some(2)); - let envelope = parse_json(&output.stdout); - assert_eq!(envelope["diagnostics"][0]["code"], "invalid-selection"); - assert!( - envelope["diagnostics"][0]["message"] - .as_str() - .unwrap() - .contains("other-file.txt"), - "the diagnostic names the unmatched file: {}", - envelope["diagnostics"][0]["message"] - ); - - let output = run(&[ - "inspect", - "cost", - path.to_str().unwrap(), - "--severity", - "error", - "-f", - "json", - ]); - let envelope = parse_json(&output.stdout); - assert!( - envelope["result"]["findings"] - .as_array() - .unwrap() - .is_empty() - ); - assert_eq!(envelope["result"]["selection"]["total"], 1); - let output = run(&[ - "inspect", - "cost", - path.to_str().unwrap(), - "--severity", - "error", - ]); - let stdout = String::from_utf8_lossy(&output.stdout); - assert!(stdout.contains("WARN cost"), "{stdout}"); - - let _ = std::fs::remove_dir_all(path.parent().unwrap()); -} - -#[test] -fn analyze_github_renderer_emits_risk_annotations() { - let path = temp_file("waitless.txt", WAITLESS_LOOP); - let output = run_with_env( - &[ - "analyze", - path.to_str().unwrap(), - "--renderer", - "github-actions", - ], - &[("GITHUB_ACTIONS", "true")], - ); - assert!(output.status.success()); - let stderr = String::from_utf8_lossy(&output.stderr); - assert!(stderr.contains("::warning"), "{stderr}"); - assert!(stderr.contains("title=while-without-wait"), "{stderr}"); - assert!(stderr.contains("WARN analyze"), "{stderr}"); - - let _ = std::fs::remove_dir_all(path.parent().unwrap()); -} - -#[test] -fn lint_text_collapses_identical_findings_into_one_entry() { - let path = temp_file( - "collapse.txt", - "rule (\"r\") { - event { - Ongoing - Global; +fn lint_text_collapses_identical_findings_into_one_entry() { + let path = temp_file( + "collapse.txt", + "rule (\"r\") { + event { + Ongoing - Global; } actions { While(True); @@ -1922,37 +1208,6 @@ fn lint_text_collapses_identical_findings_into_one_entry() { let _ = std::fs::remove_dir_all(path.parent().unwrap()); } -#[test] -fn lint_text_leads_with_the_verdict_and_orders_by_action_priority() { - let path = temp_file("cake.txt", &corpus_workshop("real-world/overpy-cake")); - let output = run(&[ - "lint", - path.to_str().unwrap(), - "--rule-severity", - "min-wait-loop:error", - "--renderer", - "terminal", - "--color", - "never", - ]); - assert_eq!(output.status.code(), Some(1)); - let stdout = String::from_utf8_lossy(&output.stdout); - let verdict = stdout.find(" ERROR lint").expect("verdict: {stdout}"); - let counts = stdout - .find("1 error, 9 warnings") - .expect("severity counts: {stdout}"); - let findings = stdout.find("Lint findings").expect("section: {stdout}"); - let error = stdout - .find("error[min-wait-loop]") - .expect("error entry: {stdout}"); - let warning = stdout - .find("warning[repeated-value]") - .expect("warning entry: {stdout}"); - assert!(verdict < counts && counts < findings, "{stdout}"); - assert!(findings < error && error < warning, "{stdout}"); - let _ = std::fs::remove_dir_all(path.parent().unwrap()); -} - #[test] fn lint_text_orders_all_severity_bands_by_action_priority() { let path = temp_file( @@ -2015,24 +1270,6 @@ fn lint_text_orders_all_severity_bands_by_action_priority() { let _ = std::fs::remove_dir_all(path.parent().unwrap()); } -#[test] -fn lint_text_presents_a_finding_with_location_and_secondary_metadata() { - let path = temp_file("flow.txt", &corpus_workshop("synthetic/control-flow")); - let output = run(&["lint", path.to_str().unwrap()]); - assert!(output.status.success()); - let stdout = String::from_utf8_lossy(&output.stdout); - assert!(stdout.contains("WARN lint"), "{stdout}"); - assert!(stdout.contains("warnings=1"), "{stdout}"); - assert!( - stdout.contains( - "finding: warning min-wait-loop at=flow.txt:11:9 evidence=static-indicator message=\"loop body waits at the workshop minimum rate; the loop runs at maximum frequency\"" - ), - "{stdout}" - ); - assert!(stdout.contains("affected=1"), "{stdout}"); - let _ = std::fs::remove_dir_all(path.parent().unwrap()); -} - #[test] fn lint_text_resolves_locations_under_a_subdirectory_input_root() { let dir = temp_dir(); @@ -2065,21 +1302,6 @@ fn lint_text_resolves_locations_under_a_subdirectory_input_root() { let _ = std::fs::remove_dir_all(&dir); } -#[test] -fn lint_text_clean_input_reports_a_pass_verdict_and_no_findings() { - let path = temp_file("basic.txt", &corpus_workshop("synthetic/basic-rule")); - let output = run(&["lint", path.to_str().unwrap()]); - assert!(output.status.success()); - let stdout = String::from_utf8_lossy(&output.stdout); - assert!(stdout.contains("PASS lint"), "{stdout}"); - assert!(stdout.contains("findings=0"), "{stdout}"); - assert!( - !stdout.contains("finding:"), - "a clean result lists no records: {stdout}" - ); - let _ = std::fs::remove_dir_all(path.parent().unwrap()); -} - #[test] fn an_unknown_rule_id_in_a_selection_is_a_usage_error() { let path = temp_file("cake.txt", &corpus_workshop("real-world/overpy-cake")); @@ -2352,15 +1574,37 @@ fn json_output_is_deterministic_across_runs() { let _ = std::fs::remove_dir_all(path.parent().unwrap()); } +#[cfg(any(target_os = "linux", target_os = "macos"))] #[test] -fn stdout_stderr_separation_holds_in_both_modes() { - let path = temp_file("basic.txt", &corpus_workshop("synthetic/basic-rule")); - let output = run(&["check", path.to_str().unwrap()]); - assert!(output.stderr.is_empty()); - assert!(String::from_utf8_lossy(&output.stdout).contains("PASS check")); - let output = run(&["check", path.to_str().unwrap(), "-f", "json"]); - assert!(output.stderr.is_empty()); - parse_json(&output.stdout); +fn check_tty_clears_progress_then_renders_verdict_and_elapsed() { + let path = temp_file("flow.txt", &corpus_workshop("synthetic/control-flow")); + let output = run_in_tty(&[ + "check", + path.to_str().unwrap(), + "--renderer", + "terminal", + "--color", + "never", + ]); + assert!(output.status.success()); + let mut transcript = output.stdout; + transcript.extend_from_slice(&output.stderr); + let transcript = String::from_utf8_lossy(&transcript); + let verdict = transcript + .find(" PASS check") + .expect("the final verdict renders"); + assert!( + transcript[..verdict].contains("\x1b[2K"), + "transient progress is cleared before the verdict: {transcript}" + ); + assert!( + !transcript[verdict..].contains("Resolving input"), + "progress labels never compete with the verdict: {transcript}" + ); + assert!( + transcript[verdict..].contains(" ms"), + "the interactive footer carries elapsed time: {transcript}" + ); let _ = std::fs::remove_dir_all(path.parent().unwrap()); } @@ -2455,67 +1699,12 @@ fn non_interactive_renderers_have_no_progress_artifacts() { } #[test] -fn explicit_locale_override_is_accepted() { - let path = temp_file("basic.txt", &corpus_workshop("synthetic/basic-rule")); - let output = run(&["check", path.to_str().unwrap(), "--locale", "en-US"]); - assert_eq!(output.status.code(), Some(0)); - let _ = std::fs::remove_dir_all(path.parent().unwrap()); -} - -#[test] -fn version_and_help_are_documented_contract_surfaces() { +fn version_reports_the_implementation_version() { let output = run(&["--version"]); assert!(output.status.success()); let banner = String::from_utf8_lossy(&output.stdout); assert!(banner.starts_with("wright "), "{banner}"); - assert!( - banner.contains(env!("CARGO_PKG_VERSION")), - "banner does not report the implementation version: {banner}" - ); - assert!(banner.contains("wright-driver"), "{banner}"); - - let output = run(&["--help"]); - assert!(output.status.success()); - let help = String::from_utf8_lossy(&output.stdout); - for command in [ - "compile", "convert", "check", "analyze", "lint", "inspect", "agent", "lookup", - ] { - assert!(help.contains(command), "help documents {command}"); - } - - let output = run(&["inspect", "--help"]); - assert!(output.status.success()); - let inspect_help = String::from_utf8_lossy(&output.stdout); - for subcommand in ["symbols", "refs", "cfg", "callgraph", "cost"] { - assert!( - inspect_help.contains(subcommand), - "inspect help documents {subcommand}" - ); - } - for option in [ - "--kind", - "--target", - "--locale", - "--root", - "--profile", - "--format", - "--renderer", - "--color", - "--disable-rule", - "--rule-severity", - ] { - assert!(help.contains(option), "top-level help documents {option}"); - } - assert!(help.contains("EXIT CODES")); - - for removed in ["version", "help", "provider"] { - let output = run(&[removed]); - assert_eq!( - output.status.code(), - Some(2), - "{removed} is a usage error now" - ); - } + assert!(banner.contains(env!("CARGO_PKG_VERSION")), "{banner}"); } #[test] @@ -2637,83 +1826,6 @@ fn completion_shell_positional_before_install_selects_that_shell() { let _ = std::fs::remove_dir_all(&dir); } -#[test] -fn agent_install_writes_the_guide_and_refreshes_it() { - let dir = temp_dir(); - let target = dir.join("wright"); - let install = |extra: &[&str]| { - let mut argv = vec!["agent", "install", "--dest", dir.to_str().unwrap()]; - argv.extend_from_slice(extra); - run(&argv) - }; - - let output = install(&[]); - assert!( - output.status.success(), - "{}", - String::from_utf8_lossy(&output.stderr) - ); - assert!(String::from_utf8_lossy(&output.stdout).contains("installed")); - assert!(target.join("SKILL.md").is_file()); - assert!(target.join("references/language-notes.md").is_file()); - let record: serde_json::Value = - serde_json::from_str(&std::fs::read_to_string(target.join("BUILD.json")).unwrap()).unwrap(); - assert_eq!(record["installedBy"], "wright agent install"); - - let output = install(&[]); - assert!(output.status.success()); - assert!(String::from_utf8_lossy(&output.stdout).contains("up to date")); - - std::fs::write(target.join("BUILD.json"), "{}").unwrap(); - let output = install(&[]); - assert_eq!(output.status.code(), Some(1)); - assert!(String::from_utf8_lossy(&output.stderr).contains("--force")); - assert!(install(&["--force"]).status.success()); - - let _ = std::fs::remove_dir_all(&dir); - let output = install(&["--dry-run"]); - assert!(output.status.success()); - assert!(String::from_utf8_lossy(&output.stdout).contains("would install")); - assert!(!target.exists()); - - let _ = std::fs::remove_dir_all(&dir); -} - -#[test] -fn agent_tools_emits_messages_and_json_schema_forms_deterministically() { - let messages = run(&["agent", "tools"]); - assert!( - messages.status.success(), - "{}", - String::from_utf8_lossy(&messages.stderr) - ); - let doc: serde_json::Value = serde_json::from_slice(&messages.stdout).unwrap(); - assert_eq!(doc["contract"], "wright-agent/v1"); - let tools = doc["tools"].as_array().unwrap(); - assert!(!tools.is_empty()); - for tool in tools { - assert!(tool["name"].as_str().unwrap().starts_with("wright_")); - assert!(tool["description"].as_str().unwrap().contains("Result:")); - assert_eq!(tool["input_schema"]["type"], "object"); - assert_eq!( - tool["allowed_callers"], - serde_json::json!(["code_execution_20260120"]) - ); - } - assert_eq!(run(&["agent", "tools"]).stdout, messages.stdout); - - let plain = run(&["agent", "tools", "--format", "json-schema"]); - assert!(plain.status.success()); - let doc: serde_json::Value = serde_json::from_slice(&plain.stdout).unwrap(); - assert_eq!(doc["contract"], "wright-agent/v1"); - let schemas = doc["schemas"].as_object().unwrap(); - assert_eq!(schemas.len(), tools.len()); - for (op, schema) in schemas { - assert_eq!(schema["type"], "object", "{op}"); - assert!(schema["description"].is_string(), "{op}"); - } -} - #[test] fn completion_install_dry_run() { let dir = temp_dir(); @@ -2756,36 +1868,6 @@ fn completion_install_detection_via_env() { let _ = std::fs::remove_dir_all(&dir); } -#[test] -fn completion_install_all_flag() { - let dir = temp_dir(); - let output = run(&[ - "completion", - "install", - "--all", - "--dir", - dir.to_str().unwrap(), - ]); - assert!( - output.status.success(), - "{}", - String::from_utf8_lossy(&output.stderr) - ); - let stdout = String::from_utf8_lossy(&output.stdout); - assert!(stdout.contains("installed bash completion"), "{stdout}"); - assert!(stdout.contains("installed zsh completion"), "{stdout}"); - assert!(stdout.contains("installed fish completion"), "{stdout}"); - assert!( - stdout.contains("installed powershell completion"), - "{stdout}" - ); - assert!(dir.join("wright").is_file()); - assert!(dir.join("_wright").is_file()); - assert!(dir.join("wright.fish").is_file()); - assert!(dir.join("_wright.ps1").is_file()); - let _ = std::fs::remove_dir_all(&dir); -} - #[test] fn completion_install_undetected_shell_reports_user_error() { let output = run_with_env( @@ -2817,48 +1899,6 @@ fn completion_install_undetected_shell_reports_user_error() { } } -#[test] -fn explicit_renderer_and_color_overrides_are_respected() { - let path = temp_file("broken.txt", "rule (\"x\") { event { Ongoing - Global; }"); - - let github = run_with_env( - &[ - "check", - path.to_str().unwrap(), - "--renderer", - "github-actions", - ], - &[("GITHUB_ACTIONS", "true")], - ); - assert_eq!(github.status.code(), Some(1)); - assert!(String::from_utf8_lossy(&github.stderr).contains("::error")); - assert!(String::from_utf8_lossy(&github.stderr).contains("::group::")); - - let plain = run_with_env( - &["check", path.to_str().unwrap(), "--renderer", "plain"], - &[("GITHUB_ACTIONS", "true")], - ); - assert_eq!(plain.status.code(), Some(1)); - assert!(!String::from_utf8_lossy(&plain.stdout).contains("::error")); - assert!(!String::from_utf8_lossy(&plain.stdout).contains("\x1b[")); - assert!(plain.stderr.is_empty()); - - let color = run_with_env( - &[ - "check", - path.to_str().unwrap(), - "--renderer", - "terminal", - "--color", - "always", - ], - &[("GITHUB_ACTIONS", "true"), ("NO_COLOR", "1")], - ); - assert_eq!(color.status.code(), Some(1)); - assert!(String::from_utf8_lossy(&color.stdout).contains("\x1b[")); - let _ = std::fs::remove_dir_all(path.parent().unwrap()); -} - #[test] fn json_and_source_stdout_stay_pure_in_github_actions() { let path = temp_file("broken.txt", "rule (\"x\") { event { Ongoing - Global; }"); @@ -2919,193 +1959,45 @@ fn source_artifacts_are_byte_exact_in_plain_and_github_renderers() { .success() ); let expected = std::fs::read(&written).unwrap(); - for renderer in ["plain", "github-actions"] { - let output = run_with_env( - &[ - "compile", - path.to_str().unwrap(), - "-o", - "-", - "--renderer", - renderer, - ], - &[("GITHUB_ACTIONS", "true")], - ); - assert!(output.status.success(), "{renderer}"); - assert_eq!(output.stdout, expected, "{renderer} must preserve bytes"); - } - - let _ = std::fs::remove_dir_all(path.parent().unwrap()); -} - -#[test] -fn github_summary_uses_step_summary_file_when_available() { - let path = temp_file("broken.txt", "rule (\"x\") { event { Ongoing - Global; }"); - let summary = temp_file("summary.md", ""); - let output = run_with_env( - &[ - "check", - path.to_str().unwrap(), - "--renderer", - "github-actions", - ], - &[ - ("GITHUB_ACTIONS", "true"), - ("GITHUB_STEP_SUMMARY", summary.to_str().unwrap()), - ], - ); - assert_eq!(output.status.code(), Some(1)); - assert!(!std::fs::read_to_string(&summary).unwrap().is_empty()); - let _ = std::fs::remove_dir_all(path.parent().unwrap()); -} - -#[test] -fn opy_file_does_not_fall_back_to_native_frontend() { - let source = - std::fs::read_to_string(workspace_root().join("tests/fixtures/opy/basic-rule.opy")) - .unwrap(); - let path = temp_file("basic-rule.opy", &source); - let missing_provider = path.parent().unwrap().join("missing-opy-provider"); - let output = run(&[ - "compile", - path.to_str().unwrap(), - "--opy-provider", - missing_provider.to_str().unwrap(), - "-f", - "json", - ]); - assert_eq!(output.status.code(), Some(4)); - let envelope = parse_json(&output.stdout); - assert_eq!(envelope["ok"], false); - assert_eq!(envelope["diagnostics"][0]["code"], "provider-missing"); - let _ = std::fs::remove_dir_all(path.parent().unwrap()); -} - -#[test] -fn provider_backed_opy_analyze_surfaces_provider_resolution_failures() { - let path = temp_file("main.opy", "rule \"r\":\n @Event global\n"); - let missing_provider = path.parent().unwrap().join("missing-opy-provider"); - let output = run(&[ - "analyze", - path.to_str().unwrap(), - "--opy-provider", - missing_provider.to_str().unwrap(), - "-f", - "json", - ]); - assert_eq!(output.status.code(), Some(4)); - let envelope = parse_json(&output.stdout); - assert_eq!(envelope["ok"], false); - assert_eq!(envelope["diagnostics"][0]["code"], "provider-missing"); - assert!(envelope["result"]["program"].is_null()); - let _ = std::fs::remove_dir_all(path.parent().unwrap()); -} - -#[cfg(unix)] -#[test] -fn provider_backed_opy_accepts_inspect_and_its_query_subcommands() { - use std::os::unix::fs::PermissionsExt; - - const PROVIDER: &str = r#"#!/usr/bin/env python3 -import json, os, sys -artifact = open(os.path.join(os.path.dirname(os.path.abspath(__file__)), "artifact.ws")).read() -initialized = False -def reply(id, result=None, error=None): - message = {"jsonrpc": "2.0", "id": id} - message.update({"error": error} if error else {"result": result}) - print(json.dumps(message), flush=True) -def lpp_error(id, kind, details, text): - reply(id, error={"code": -32000, "message": text, "data": {"lpp": {"kind": kind, "details": details}}}) -for line in sys.stdin: - request = json.loads(line) - id, method = request["id"], request["method"] - if method == "lpp/initialize": - first = not initialized - initialized = True - if not first: - lpp_error(id, "alreadyInitialized", {}, "already initialized") - elif request["params"]["protocolVersion"] != "1.1": - lpp_error(id, "protocolVersionMismatch", {"supportedProtocolVersions": ["1.1"]}, "unsupported") - else: - reply(id, {"protocolVersion": "1.1", "serverInfo": {"name": "fake", "version": "0"}, - "languages": [{"id": "opy", "extensions": ["opy"]}], - "capabilities": {"check": True, "compile": True, "reconstruct": False, "symbols": False, "definition": False, "references": False, "rename": False, "editValidation": False, "projectLoading": True}}) - elif method == "lpp/compile": - reply(id, {"diagnostics": [], "artifact": {"format": "workshop-rs/text-v1", "content": artifact}}) - else: - reply(id, {}) -"#; - - let dir = temp_dir(); - let script = dir.join("fake-provider"); - std::fs::write(&script, PROVIDER).unwrap(); - std::fs::set_permissions(&script, std::fs::Permissions::from_mode(0o755)).unwrap(); - std::fs::write( - dir.join("artifact.ws"), - corpus_workshop("synthetic/basic-rule"), - ) - .unwrap(); - let opy = std::fs::read_to_string(workspace_root().join("tests/fixtures/opy/basic-rule.opy")) - .unwrap(); - let input = dir.join("main.opy"); - std::fs::write(&input, &opy).unwrap(); - let provider = script.to_str().unwrap().to_string(); - let input = input.to_str().unwrap().to_string(); - let run_inspect = |args: &[&str]| { - let output = run(args); - assert!( - output.status.success(), - "{args:?}: {}", - command_result(&output) + for renderer in ["plain", "github-actions"] { + let output = run_with_env( + &[ + "compile", + path.to_str().unwrap(), + "-o", + "-", + "--renderer", + renderer, + ], + &[("GITHUB_ACTIONS", "true")], ); - parse_json(&output.stdout) - }; - - let envelope = run_inspect(&["inspect", &input, "--opy-provider", &provider, "-f", "json"]); - assert_eq!(envelope["command"], "inspect"); - assert_eq!(envelope["ok"], true); - let rules = envelope["result"]["rules"].as_array().unwrap(); - assert_eq!(rules.len(), 1, "{envelope}"); - assert_eq!(rules[0]["name"], "setup"); + assert!(output.status.success(), "{renderer}"); + assert_eq!(output.stdout, expected, "{renderer} must preserve bytes"); + } - let envelope = run_inspect(&[ - "inspect", - "rules", - "--content", - &input, - "--opy-provider", - &provider, - "-f", - "json", - ]); - assert_eq!(envelope["command"], "rules"); - assert_eq!(envelope["ok"], true); - let rules = envelope["result"]["rules"].as_array().unwrap(); - assert_eq!(rules.len(), 1, "{envelope}"); - assert_eq!( - rules[0]["content"]["actions"].as_array().unwrap().len(), - 1, - "{envelope}" - ); + let _ = std::fs::remove_dir_all(path.parent().unwrap()); +} - let envelope = run_inspect(&[ - "inspect", - "symbols", - &input, +#[test] +fn opy_file_does_not_fall_back_to_native_frontend() { + let source = + std::fs::read_to_string(workspace_root().join("tests/fixtures/opy/basic-rule.opy")) + .unwrap(); + let path = temp_file("basic-rule.opy", &source); + let missing_provider = path.parent().unwrap().join("missing-opy-provider"); + let output = run(&[ + "compile", + path.to_str().unwrap(), "--opy-provider", - &provider, + missing_provider.to_str().unwrap(), "-f", "json", ]); - assert_eq!(envelope["command"], "symbols"); - assert_eq!(envelope["ok"], true); - let symbols = envelope["result"]["symbols"].as_array().unwrap(); - assert!( - symbols.iter().any(|symbol| symbol["name"] == "setup"), - "{envelope}" - ); - - let _ = std::fs::remove_dir_all(&dir); + assert_eq!(output.status.code(), Some(4)); + let envelope = parse_json(&output.stdout); + assert_eq!(envelope["ok"], false); + assert_eq!(envelope["diagnostics"][0]["code"], "provider-missing"); + let _ = std::fs::remove_dir_all(path.parent().unwrap()); } #[cfg(unix)] @@ -3468,56 +2360,6 @@ fn cfg_reports_the_named_rules_control_flow_graph() { let _ = std::fs::remove_dir_all(Path::new(path).parent().unwrap()); } -#[test] -fn callgraph_reports_subroutine_call_edges() { - let path = temp_file("decl.txt", &corpus_workshop("synthetic/declarations-rules")); - let path = path.to_str().unwrap(); - - let output = run(&["inspect", "callgraph", path, "-f", "json"]); - assert!(output.status.success(), "{}", command_result(&output)); - let envelope = parse_json(&output.stdout); - assert_eq!(envelope["command"], "callgraph"); - assert_eq!( - envelope["result"], - serde_json::json!([ - { "caller": "player starts", "callerAddress": "player starts", "callee": "showStatus" } - ]) - ); - - let output = run(&["inspect", "callgraph", path]); - assert!(output.status.success(), "{}", command_result(&output)); - let stdout = String::from_utf8_lossy(&output.stdout); - assert!(stdout.contains("CALLGRAPH callgraph"), "{stdout}"); - assert!( - stdout.contains("edge: caller=\"player starts\" callee=showStatus"), - "{stdout}" - ); - let _ = std::fs::remove_dir_all(Path::new(path).parent().unwrap()); -} - -#[test] -fn callgraph_ranks_fan_in_and_fan_out_before_the_edge_list() { - let path = temp_file("subs.txt", &corpus_workshop("synthetic/subroutines")); - let path = path.to_str().unwrap(); - - let output = run(&["inspect", "callgraph", path]); - assert!(output.status.success(), "{}", command_result(&output)); - let stdout = String::from_utf8_lossy(&output.stdout); - assert!( - stdout.contains("summary: edges=3 callers=2 callees=2"), - "{stdout}" - ); - assert!( - stdout.contains("fan-in: subroutine=shared callers=2"), - "{stdout}" - ); - assert!(stdout.contains("fan-out: rule=first calls=2"), "{stdout}"); - let fan_in = stdout.find("fan-in:").unwrap(); - let edges = stdout.find("edge:").unwrap(); - assert!(fan_in < edges, "ranking before edge list: {stdout}"); - let _ = std::fs::remove_dir_all(Path::new(path).parent().unwrap()); -} - #[test] fn callgraph_highlight_sections_fold_beyond_the_bound() { let mut source = String::from("subroutines {\n"); @@ -3664,34 +2506,6 @@ fn cost_run_cache_is_shared_by_compile_and_inspect() { let _ = std::fs::remove_dir_all(&dir); } -#[test] -fn cost_run_cache_names_added_and_grown_rules() { - let dir = temp_dir(); - let path = dir.join("t.ws"); - let base = "rule (\"a\") {\n event {\n Ongoing - Global;\n }\n actions {\n Wait(0.016);\n }\n}\n"; - std::fs::write(&path, base).unwrap(); - let path_str = path.to_str().unwrap(); - assert!(run(&["inspect", "cost", path_str]).status.success()); - - let grown = "rule (\"a\") {\n event {\n Ongoing - Global;\n }\n actions {\n Wait(0.016);\n Wait(0.016);\n }\n}\n\nrule (\"b\") {\n event {\n Ongoing - Global;\n }\n actions {\n Wait(0.016);\n }\n}\n"; - std::fs::write(&path, grown).unwrap(); - let second = run(&["inspect", "cost", path_str]); - let stdout = String::from_utf8_lossy(&second.stdout); - assert!( - stdout.contains("comparison: reference=previous"), - "{stdout}" - ); - assert!( - stdout.contains("change: rule=a"), - "grown rule a is named: {stdout}" - ); - assert!( - stdout.contains("change: rule=b added"), - "added rule b is named: {stdout}" - ); - let _ = std::fs::remove_dir_all(&dir); -} - #[test] fn cost_baseline_degrades_silently_under_ci() { let dir = temp_dir(); @@ -3764,91 +2578,6 @@ fn cost_baseline_degrades_silently_under_ci() { let _ = std::fs::remove_dir_all(&dir); } -#[test] -fn cost_reports_exact_counts_and_selected_findings() { - let path = temp_file("cake.txt", &corpus_workshop("real-world/overpy-cake")); - let path = path.to_str().unwrap(); - - let output = run(&["inspect", "cost", path]); - assert!(output.status.success(), "{}", command_result(&output)); - let stdout = String::from_utf8_lossy(&output.stdout); - assert!(stdout.contains("WARN cost"), "{stdout}"); - assert!(stdout.contains("emittedBytes=3960"), "{stdout}"); - assert!(stdout.contains("actions=29"), "{stdout}"); - assert_eq!( - stdout.matches("finding: warning repeated-value").count(), - 3, - "{stdout}" - ); - assert!(stdout.contains("truncated: findings=3/10"), "{stdout}"); - let full = run(&["inspect", "cost", path, "--all"]); - assert!(full.status.success(), "{}", command_result(&full)); - let full_stdout = String::from_utf8_lossy(&full.stdout); - assert_eq!( - full_stdout - .matches("finding: warning repeated-value") - .count(), - 9, - "{full_stdout}" - ); - assert!( - full_stdout.contains("`add(valueInArray(cakePos, 7), vector(0, i2, 0))` is evaluated"), - "{full_stdout}" - ); - let identical = temp_file( - "identical.txt", - "rule (\"r\") { - event { - Ongoing - Global; - } - actions { - While(True); - Wait(0.016, Ignore Condition); - End; - While(True); - Wait(0.016, Ignore Condition); - End; - } -} -", - ); - let output = run(&["inspect", "cost", identical.to_str().unwrap()]); - assert!(output.status.success(), "{}", command_result(&output)); - let stdout = String::from_utf8_lossy(&output.stdout); - assert_eq!( - stdout.matches("finding: warning min-wait-loop").count(), - 2, - "each identical finding is its own record: {stdout}" - ); - - let output = run(&["inspect", "cost", path, "-f", "json"]); - assert!(output.status.success(), "{}", command_result(&output)); - let envelope = parse_json(&output.stdout); - assert_eq!(envelope["command"], "cost"); - let exact = &envelope["result"]["exact"]; - assert_eq!(exact["emittedBytes"], 3960); - assert_eq!(exact["programActions"], 29); - assert_eq!(exact["programRules"], 2); - assert_eq!(exact["waitActions"], 1); - assert_eq!(envelope["result"]["truncation"]["findings"]["total"], 10); - let output = run(&["inspect", "cost", path, "--all", "-f", "json"]); - assert!(output.status.success(), "{}", command_result(&output)); - let envelope = parse_json(&output.stdout); - assert_eq!( - envelope["result"]["findings"].as_array().unwrap().len(), - 10, - "cost reports the same findings set as `findings`/`lint`" - ); - - let output = run(&["inspect", "cost", path, "--max", "2", "-f", "json"]); - let envelope = parse_json(&output.stdout); - assert_eq!(envelope["result"]["findings"].as_array().unwrap().len(), 2); - assert_eq!(envelope["result"]["selection"]["total"], 10); - assert_eq!(envelope["result"]["selection"]["withheld"], 8); - let _ = std::fs::remove_dir_all(Path::new(path).parent().unwrap()); - let _ = std::fs::remove_dir_all(identical.parent().unwrap()); -} - #[test] fn cost_text_reports_the_element_budget_and_rule_shares() { let path = temp_file("cake.txt", &corpus_workshop("real-world/overpy-cake")); @@ -4189,31 +2918,6 @@ fn analyze_lint_and_cost_report_the_same_rule_and_finding_totals() { let _ = std::fs::remove_dir_all(Path::new(path).parent().unwrap()); } -#[test] -fn inspect_names_the_detail_query_commands() { - let path = temp_file("cake.txt", &corpus_workshop("real-world/overpy-cake")); - let output = run(&["inspect", path.to_str().unwrap()]); - assert!(output.status.success(), "{}", command_result(&output)); - let stdout = String::from_utf8_lossy(&output.stdout); - for pointer in [ - "wright inspect symbols", - "wright inspect refs ", - "wright inspect cfg ", - "wright inspect callgraph", - "wright inspect cost", - ] { - assert!( - stdout.contains(&format!("next: {pointer}")), - "{pointer}: {stdout}" - ); - } - assert!( - stdout.find("summary:").unwrap() < stdout.find("next:").unwrap(), - "{stdout}" - ); - let _ = std::fs::remove_dir_all(path.parent().unwrap()); -} - #[test] fn inspect_names_unnamed_rules_by_index() { let path = temp_file( @@ -4227,15 +2931,6 @@ fn inspect_names_unnamed_rules_by_index() { let _ = std::fs::remove_dir_all(path.parent().unwrap()); } -#[test] -fn rename_is_an_ordinary_unknown_subcommand() { - let output = run(&["rename", "score", "total"]); - assert_eq!(output.status.code(), Some(2)); - assert!(output.stdout.is_empty(), "usage errors write stderr only"); - let stderr = String::from_utf8_lossy(&output.stderr); - assert!(stderr.contains("unrecognized subcommand"), "{stderr}"); -} - #[test] fn inspect_overview_and_cfg_bound_large_detail() { let mut source = String::new(); @@ -4375,20 +3070,6 @@ fn agent_install_mcp_configures_a_project_whose_server_answers_mcp() { let _ = std::fs::remove_dir_all(&elsewhere); } -#[test] -fn lookup_help_carries_the_agent_guidance() { - let output = run(&["lookup", "--help"]); - assert!(output.status.success()); - let help = String::from_utf8_lossy(&output.stdout); - assert!( - help.contains("unfamiliar language") && help.contains("when a name is rejected"), - "help tells agents when to reach for lookup: {help}" - ); - for option in ["--language", "--only", "--within", "--locale", "--limit"] { - assert!(help.contains(option), "lookup help documents {option}"); - } -} - #[test] fn lookup_resolves_a_display_name_to_a_rendered_signature() { let output = run(&["lookup", "create hud text"]); @@ -4402,49 +3083,6 @@ fn lookup_resolves_a_display_name_to_a_rendered_signature() { ); } -#[test] -fn lookup_json_matches_the_agent_result() { - let output = run(&["lookup", "wait", "-f", "json"]); - assert_eq!(output.status.code(), Some(0), "{output:?}"); - assert!(output.stderr.is_empty(), "JSON mode: no stderr"); - let result = parse_json(&output.stdout); - assert_eq!(result["language"], "workshop"); - assert_eq!(result["owner"], "workshop-rs"); - let entries = result["entries"].as_array().unwrap(); - assert!( - entries - .iter() - .any(|entry| entry["signature"] == "Wait(time: Any, waitBehavior=Ignore Condition)"), - "the Wait signature renders from catalog facts: {result}" - ); -} - -#[test] -fn lookup_json_carries_callable_returns_and_variadic() { - let output = run(&["lookup", "distance", "-f", "json"]); - assert_eq!(output.status.code(), Some(0), "{output:?}"); - let result = parse_json(&output.stdout); - let distance = result["entries"] - .as_array() - .unwrap() - .iter() - .find(|entry| entry["identity"] == "distance") - .expect("the distance value"); - assert_eq!(distance["callable"]["returns"], "Number"); - assert!(distance["callable"].get("variadic").is_none()); - - let output = run(&["lookup", "array", "-f", "json"]); - assert_eq!(output.status.code(), Some(0), "{output:?}"); - let result = parse_json(&output.stdout); - let array = result["entries"] - .as_array() - .unwrap() - .iter() - .find(|entry| entry["identity"] == "array") - .expect("the array value"); - assert_eq!(array["callable"]["variadic"], true); -} - #[test] fn lookup_within_lists_an_enums_members() { let output = run(&["lookup", "--within", "Team", "--only", "enum-member"]); @@ -4454,26 +3092,6 @@ fn lookup_within_lists_an_enums_members() { assert!(stdout.contains("name=\"Team 1\""), "{stdout}"); } -#[test] -fn lookup_within_rejects_an_unknown_scope() { - let output = run(&["lookup", "--within", "nope"]); - assert_eq!(output.status.code(), Some(1)); - let stderr = String::from_utf8_lossy(&output.stderr); - assert!(stderr.contains("lookup.unknownWithin"), "{stderr}"); -} - -#[test] -fn lookup_rejects_an_out_of_range_limit() { - for limit in ["0", "11"] { - let output = run(&["lookup", "wait", "--limit", limit, "-f", "json"]); - assert_eq!(output.status.code(), Some(1), "--limit {limit}: {output:?}"); - let result = parse_json(&output.stdout); - assert_eq!(result["error"]["code"], "malformed-request", "{result}"); - } - let output = run(&["lookup", "wait", "--limit", "10"]); - assert_eq!(output.status.code(), Some(0), "{output:?}"); -} - #[test] fn lookup_never_reads_stdin() { let mut child = Command::new(wright()) @@ -4519,72 +3137,6 @@ fn lookup_names_opy_rs_when_the_provider_cannot_answer() { assert_eq!(result["unavailable"]["capability"], "lookup"); } -#[test] -fn check_text_points_unknown_names_at_lookup() { - let path = temp_file("lookup-hint.txt", MIXED_CHECK_SOURCE); - let output = run(&[ - "check", - path.to_str().unwrap(), - "--renderer", - "plain", - "--color", - "never", - ]); - assert_eq!(output.status.code(), Some(1)); - let stdout = String::from_utf8_lossy(&output.stdout); - assert!( - stdout.contains("next: wright lookup\n"), - "the unknown-value diagnostic carries the pointer: {stdout}" - ); - assert!( - !stdout.contains("--language"), - "a Workshop source keeps the default-vocabulary hint: {stdout}" - ); - let _ = std::fs::remove_dir_all(path.parent().unwrap()); -} - -#[test] -fn check_unknown_kind_code_is_kebab_case_on_every_surface() { - const SOURCE: &str = "rule (\"r\")\n{\n event\n {\n Ongoing - Global;\n }\n actions\n {\n Set Global Variable(A, Map(Not A Real Map));\n }\n}\n"; - let path = temp_file("bad-enum.ws", SOURCE); - let file = path.to_str().unwrap(); - - let text = run(&["check", file, "--renderer", "plain", "--color", "never"]); - assert_eq!(text.status.code(), Some(1)); - let stdout = String::from_utf8_lossy(&text.stdout); - assert!( - stdout.contains("diagnostic: error unknown-enum-member "), - "the record carries one kebab-case token: {stdout}" - ); - assert!( - !stdout.contains("unknown-enum member"), - "no code carries a space: {stdout}" - ); - - let json = run(&["check", file, "--format", "json"]); - assert_eq!(json.status.code(), Some(1)); - let envelope = parse_json(&json.stdout); - assert!( - envelope["diagnostics"] - .as_array() - .unwrap() - .iter() - .any(|d| d["code"] == "unknown-enum-member"), - "the JSON code is unknown-enum-member: {}", - envelope["diagnostics"] - ); - - let github = run(&["check", file, "--renderer", "github-actions"]); - assert_eq!(github.status.code(), Some(1)); - let annotations = String::from_utf8_lossy(&github.stderr); - assert!( - annotations.contains("title=unknown-enum-member"), - "the annotation title is one token: {annotations}" - ); - - let _ = std::fs::remove_dir_all(path.parent().unwrap()); -} - #[test] fn check_text_locates_unknown_setting_value_and_names_the_locale() { let path = temp_file( diff --git a/crates/wright-cli/tests/error_wording.rs b/crates/wright-cli/tests/error_wording.rs index 90e49a69..727db883 100644 --- a/crates/wright-cli/tests/error_wording.rs +++ b/crates/wright-cli/tests/error_wording.rs @@ -245,53 +245,6 @@ fn serve_names_the_operation_not_the_command() { let _ = std::fs::remove_dir_all(path.parent().unwrap()); } -#[test] -fn help_shows_inputless_narrowed_examples() { - for args in [vec!["inspect", "cost", "--help"], vec!["inspect", "--help"]] { - let output = run(&args); - assert!(output.status.success(), "{args:?}"); - let stdout = String::from_utf8_lossy(&output.stdout); - let example = stdout - .lines() - .find(|line| line.contains("wright inspect") && line.contains("--rule")) - .unwrap_or_else(|| panic!("{args:?} shows a narrowed example: {stdout}")); - assert!( - !example.contains("INPUT") && !example.ends_with(".ws"), - "the narrowed example omits INPUT: {example}" - ); - } -} - -#[test] -fn ambiguous_rule_lists_candidates_with_locations() { - let path = temp_file("dup.ws", DUP_RULES); - let output = run(&[ - "inspect", - "cost", - path.to_str().unwrap(), - "--rule", - "tick", - "-f", - "json", - ]); - assert_eq!(output.status.code(), Some(2)); - let message = parse_json(&output.stdout)["diagnostics"][0]["message"] - .as_str() - .unwrap() - .to_string(); - for candidate in ["tick#1 at dup.ws:", "tick#2 at dup.ws:"] { - assert!( - message.contains(candidate), - "the candidate carries path:line:column: {message}" - ); - } - assert!( - !message.contains("{file:") && !message.contains(" at 0:"), - "no placeholder or bare file index: {message}" - ); - let _ = std::fs::remove_dir_all(path.parent().unwrap()); -} - #[test] fn unmatched_file_lists_project_sources() { let path = temp_file("main.ws", DUP_RULES); diff --git a/crates/wright-cli/tests/selector_vocabulary.rs b/crates/wright-cli/tests/selector_vocabulary.rs index 4c7ad0a3..f417b75b 100644 --- a/crates/wright-cli/tests/selector_vocabulary.rs +++ b/crates/wright-cli/tests/selector_vocabulary.rs @@ -2,8 +2,6 @@ use std::io::{BufRead, BufReader, Write}; use std::path::{Path, PathBuf}; use std::process::{Command, Stdio}; -use serde_json::json; - fn wright() -> &'static str { env!("CARGO_BIN_EXE_wright") } @@ -63,57 +61,6 @@ fn parse_json(output: &[u8]) -> serde_json::Value { serde_json::from_slice(output).expect("stdout is one JSON envelope") } -fn run_lines(transport: &str, input: &Path, lines: &[&str]) -> Vec { - static NEXT: std::sync::atomic::AtomicUsize = std::sync::atomic::AtomicUsize::new(0); - let cwd = Path::new(env!("CARGO_TARGET_TMPDIR")).join(format!( - "selector-serve-{}-{}", - std::process::id(), - NEXT.fetch_add(1, std::sync::atomic::Ordering::SeqCst) - )); - std::fs::create_dir_all(&cwd).unwrap(); - let mut child = Command::new(wright()) - .args(["serve", "--transport", transport]) - .arg(input) - .current_dir(&cwd) - .stdin(Stdio::piped()) - .stdout(Stdio::piped()) - .stderr(Stdio::piped()) - .spawn() - .expect("wright serve spawns"); - let mut stdin = child.stdin.take().unwrap(); - for line in lines { - writeln!(stdin, "{line}").unwrap(); - } - drop(stdin); - let output = child.wait_with_output().unwrap(); - let _ = std::fs::remove_dir_all(&cwd); - assert!( - output.status.success(), - "wright serve failed: {}", - String::from_utf8_lossy(&output.stderr) - ); - String::from_utf8(output.stdout) - .unwrap() - .lines() - .map(|line| serde_json::from_str(line).expect("JSON response")) - .collect() -} - -fn mcp_call(id: u64, name: &str, arguments: serde_json::Value) -> String { - json!({ - "jsonrpc": "2.0", - "id": id, - "method": "tools/call", - "params": { "name": name, "arguments": arguments }, - }) - .to_string() -} - -fn mcp_payload(response: &serde_json::Value) -> serde_json::Value { - serde_json::from_str(response["result"]["content"][0]["text"].as_str().unwrap()) - .expect("tool content is JSON") -} - fn serve_with(args: &[&str], input: &Path) -> (std::process::Child, Vec) { let mut child = Command::new(wright()) .arg("serve") @@ -326,27 +273,6 @@ fn serve_applies_the_cli_lint_policy_to_the_session() { let _ = std::fs::remove_dir_all(&dir); } -#[test] -fn removed_selector_spellings_are_usage_errors() { - let input = corpus_workshop("synthetic/basic-rule"); - let input = input.to_str().unwrap(); - for args in [ - &["lint", "--rule", "rules.yaml", input][..], - &["serve", "--rule", "rules.yaml", input][..], - &["inspect", "rules", "--only", "player starts", input][..], - &["lookup", "--kind", "action"][..], - ] { - let output = run(args); - assert_eq!( - output.status.code(), - Some(2), - "{args:?} must be a usage error: {}", - command_result(&output) - ); - assert!(output.stdout.is_empty(), "usage errors write stderr only"); - } -} - #[test] fn lookup_only_filters_entries_by_kind() { let output = run(&["lookup", "wait", "--only", "action", "-f", "json"]); @@ -382,44 +308,3 @@ fn inspect_refs_and_cfg_keep_only_as_the_category_filter() { let _ = std::fs::remove_dir_all(Path::new(path).parent().unwrap()); } - -#[test] -fn a_rules_request_carrying_name_is_malformed_on_every_transport() { - let input = corpus_workshop("synthetic/basic-rule"); - let stdio = run_lines( - "stdio", - &input, - &[ - r#"{"op":"rules","name":"player starts"}"#, - r#"{"op":"rules"}"#, - ], - ); - assert_eq!(stdio[0]["error"]["code"], "malformed-request"); - assert!( - stdio[1].get("error").is_none() && stdio[1].get("result").is_some(), - "bare rules still answers: {}", - stdio[1] - ); - - let jsonrpc = run_lines( - "jsonrpc", - &input, - &[ - r#"{"jsonrpc":"2.0","id":1,"method":"request","params":{"op":"rules","name":"player starts"}}"#, - ], - ); - assert_eq!(jsonrpc[0]["result"]["error"]["code"], "malformed-request"); - assert!(jsonrpc[0].get("error").is_none()); - - let mcp = run_lines( - "mcp", - &input, - &[&mcp_call( - 1, - "wright_rules", - json!({"name": "player starts"}), - )], - ); - assert_eq!(mcp[0]["result"]["isError"], true, "{}", mcp[0]); - assert_eq!(mcp_payload(&mcp[0])["code"], "malformed-request"); -} diff --git a/crates/wright-cli/tests/serve.rs b/crates/wright-cli/tests/serve.rs index 53314c3c..595966f7 100644 --- a/crates/wright-cli/tests/serve.rs +++ b/crates/wright-cli/tests/serve.rs @@ -68,68 +68,6 @@ fn run_lines_with( .collect() } -#[test] -fn stdio_transport_serves_structured_queries() { - let responses = run_lines( - "stdio", - &corpus_workshop("synthetic/control-flow"), - &[ - r#"{"op":"capabilities"}"#, - r#"{"op":"project"}"#, - r#"{"op":"callGraph"}"#, - r#"{"op":"costEstimate"}"#, - ], - ); - assert_eq!(responses.len(), 4); - assert_eq!(responses[0]["result"]["contract"], "wright-result/v1"); - assert_eq!(responses[0]["result"]["agent_contract"], "wright-agent/v1"); - assert_eq!(responses[1]["result"]["origin"]["kind"], "workshop"); - assert!(responses[2]["result"].as_array().unwrap().is_empty()); - assert!( - responses[3]["result"]["exact"]["emittedBytes"] - .as_u64() - .unwrap() - > 0 - ); -} - -#[test] -fn jsonrpc_transport_serves_requests_and_workflows() { - let responses = run_lines( - "jsonrpc", - &corpus_workshop("synthetic/control-flow"), - &[ - r#"{"jsonrpc":"2.0","id":1,"method":"request","params":{"op":"rules"}}"#, - r#"{"jsonrpc":"2.0","id":2,"method":"request","params":{"op":"costEstimate"}}"#, - r#"{"jsonrpc":"2.0","id":3,"method":"compile"}"#, - r#"{"jsonrpc":"2.0","id":4,"method":"check"}"#, - r#"{"jsonrpc":"2.0","id":5,"method":"analyze"}"#, - r#"{"jsonrpc":"2.0","id":6,"method":"inspect"}"#, - ], - ); - assert_eq!(responses.len(), 6); - assert_eq!(responses[0]["id"], 1); - assert_eq!(responses[0]["result"]["rules"].as_array().unwrap().len(), 2); - assert!( - responses[0]["result"]["snapshot"].is_u64(), - "the rules listing reports the generation that issued the indexes" - ); - assert_eq!(responses[1]["id"], 2); - assert_eq!(responses[2]["id"], 3); - assert_eq!(responses[2]["result"]["command"], "compile"); - assert_eq!(responses[3]["id"], 4); - assert_eq!(responses[3]["result"]["command"], "check"); - assert_eq!(responses[4]["id"], 5); - assert_eq!(responses[4]["result"]["command"], "analyze"); - assert_eq!(responses[5]["id"], 6); - assert_eq!(responses[5]["result"]["command"], "inspect"); - for response in responses { - assert_eq!(response["jsonrpc"], "2.0"); - assert!(response.get("result").is_some()); - assert!(response.get("error").is_none()); - } -} - #[test] fn workflows_have_equivalent_results_through_agent_requests_and_legacy_methods() { let input = corpus_workshop("synthetic/control-flow"); @@ -197,49 +135,6 @@ fn serve_reserves_stdin_for_session_requests() { assert!(String::from_utf8_lossy(&output.stderr).contains("reserves stdin for requests")); } -#[test] -fn stdio_transport_applies_finding_selection() { - let input = corpus_workshop("real-world/overpy-cake"); - let responses = run_lines( - "stdio", - &input, - &[ - r#"{"op":"findings","max":3}"#, - r#"{"op":"lint","severity":"error"}"#, - r#"{"op":"lint","rule":"not-a-rule"}"#, - ], - ); - assert_eq!( - responses[0]["result"]["findings"].as_array().unwrap().len(), - 3 - ); - assert_eq!(responses[0]["result"]["selection"]["total"], 10); - assert_eq!(responses[0]["result"]["selection"]["withheld"], 7); - assert!( - responses[1]["result"]["findings"] - .as_array() - .unwrap() - .is_empty() - ); - assert_eq!(responses[1]["result"]["selection"]["total"], 10); - assert_eq!(responses[2]["error"]["code"], "invalid-selection"); -} - -#[test] -fn transports_match_in_process_semantics() { - let stdio = run_lines( - "stdio", - &corpus_workshop("synthetic/control-flow"), - &[r#"{"op":"findings"}"#], - ); - let jsonrpc = run_lines( - "jsonrpc", - &corpus_workshop("synthetic/control-flow"), - &[r#"{"jsonrpc":"2.0","id":1,"method":"request","params":{"op":"findings"}}"#], - ); - assert_eq!(stdio[0]["result"], jsonrpc[0]["result"]); -} - #[test] fn jsonrpc_transport_preserves_application_errors_and_protocol_errors() { let responses = run_lines( @@ -488,30 +383,6 @@ fn compile_request_carries_the_output_locale() { assert_eq!(rejected["diagnostics"][0]["code"], "invalid-locale"); } -#[test] -fn capability_negotiation_is_preserved() { - let responses = run_lines( - "stdio", - &corpus_workshop("synthetic/basic-rule"), - &[r#"{"op":"capabilities"}"#], - ); - let capabilities = &responses[0]["result"]; - assert!( - capabilities["operations"] - .as_array() - .unwrap() - .iter() - .any(|op| op == "compile") - ); - assert!( - capabilities["contract"] - .as_str() - .unwrap() - .starts_with("wright-result/") - ); - assert_eq!(capabilities["agent_contract"], "wright-agent/v1"); -} - #[test] fn stdio_and_jsonrpc_transports_serve_over_an_unloadable_project() { let dir = std::env::temp_dir().join(format!("wright-serve-512-{}", std::process::id())); @@ -604,122 +475,6 @@ fn lookup_answers_over_every_transport_and_a_broken_project() { let _ = std::fs::remove_dir_all(&dir); } -#[test] -fn lookup_callable_carries_returns_and_variadic_on_every_transport() { - let input = corpus_workshop("synthetic/basic-rule"); - let stdio = run_lines( - "stdio", - &input, - &[ - r#"{"op":"lookup","language":"workshop","query":"distance","kind":"value"}"#, - r#"{"op":"lookup","language":"workshop","query":"array","kind":"value"}"#, - ], - ); - let jsonrpc = run_lines( - "jsonrpc", - &input, - &[ - r#"{"jsonrpc":"2.0","id":1,"method":"request","params":{"op":"lookup","language":"workshop","query":"distance","kind":"value"}}"#, - r#"{"jsonrpc":"2.0","id":2,"method":"request","params":{"op":"lookup","language":"workshop","query":"array","kind":"value"}}"#, - ], - ); - let mcp = run_lines( - "mcp", - &input, - &[ - &mcp_call( - 1, - "wright_lookup", - json!({"language": "workshop", "query": "distance", "kind": "value"}), - ), - &mcp_call( - 2, - "wright_lookup", - json!({"language": "workshop", "query": "array", "kind": "value"}), - ), - ], - ); - let entry = |result: &serde_json::Value, identity: &str| { - result["entries"] - .as_array() - .unwrap() - .iter() - .find(|entry| entry["identity"] == identity) - .unwrap_or_else(|| panic!("no '{identity}' entry: {result}")) - .clone() - }; - assert_eq!( - entry(&stdio[0]["result"], "distance")["callable"]["returns"], - "Number" - ); - let array = entry(&stdio[1]["result"], "array"); - assert_eq!(array["callable"]["variadic"], true); - assert!( - array["callable"].get("returns").is_some(), - "array is a value and declares a return type: {array}" - ); - assert_eq!(stdio[0]["result"], jsonrpc[0]["result"]); - assert_eq!(stdio[1]["result"], jsonrpc[1]["result"]); - assert_eq!(stdio[0]["result"], mcp_payload(&mcp[0])); - assert_eq!(stdio[1]["result"], mcp_payload(&mcp[1])); -} - -#[test] -fn lookup_rejects_an_out_of_range_limit_on_every_transport() { - let input = corpus_workshop("synthetic/basic-rule"); - let stdio = run_lines( - "stdio", - &input, - &[ - r#"{"op":"lookup","language":"workshop","query":"wait","limit":0}"#, - r#"{"op":"lookup","language":"workshop","query":"wait","limit":11}"#, - r#"{"op":"lookup","language":"workshop","query":"wait","limit":10}"#, - ], - ); - assert_eq!(stdio[0]["error"]["code"], "malformed-request"); - assert_eq!(stdio[1]["error"]["code"], "malformed-request"); - assert!( - stdio[2]["result"]["entries"] - .as_array() - .is_some_and(|entries| !entries.is_empty()), - "limit 10 still answers: {}", - stdio[2] - ); - - let jsonrpc = run_lines( - "jsonrpc", - &input, - &[ - r#"{"jsonrpc":"2.0","id":1,"method":"request","params":{"op":"lookup","language":"workshop","query":"wait","limit":0}}"#, - r#"{"jsonrpc":"2.0","id":2,"method":"request","params":{"op":"lookup","language":"workshop","query":"wait","limit":11}}"#, - ], - ); - assert_eq!(jsonrpc[0]["result"]["error"]["code"], "malformed-request"); - assert_eq!(jsonrpc[1]["result"]["error"]["code"], "malformed-request"); - assert!(jsonrpc[0].get("error").is_none() && jsonrpc[1].get("error").is_none()); - - let mcp = run_lines( - "mcp", - &input, - &[ - &mcp_call( - 1, - "wright_lookup", - json!({"language": "workshop", "query": "wait", "limit": 0}), - ), - &mcp_call( - 2, - "wright_lookup", - json!({"language": "workshop", "query": "wait", "limit": 11}), - ), - ], - ); - for response in &mcp { - assert_eq!(response["result"]["isError"], true, "{response}"); - assert_eq!(mcp_payload(response)["code"], "malformed-request"); - } -} - #[test] fn stdio_transport_recovers_in_the_same_process() { use std::io::{BufRead, BufReader}; @@ -775,201 +530,6 @@ fn stdio_transport_recovers_in_the_same_process() { let _ = std::fs::remove_dir_all(&dir); } -#[test] -fn stdio_transport_serves_mutation_operations() { - let input = corpus_workshop("synthetic/control-flow"); - let source = std::fs::read_to_string(&input).unwrap(); - let identity = wright_driver::input_identity(&source); - let request = serde_json::json!({ - "op": "validateEditTransaction", - "sources": { input.to_string_lossy().into_owned(): source.clone() }, - "transaction": { - "edits": [{ - "kind": "edit", - "source": input.to_string_lossy().into_owned(), - "source_identity": identity, - "range": { - "start_line": 6, "start_col": 8, - "end_line": 6, "end_col": 21, - }, - "new_text": "bounded loop" - }] - } - }); - let responses = run_lines( - "stdio", - &input, - &[&serde_json::to_string(&request).unwrap()], - ); - assert_eq!(responses[0]["result"]["ok"], true, "{responses:?}"); - assert!( - responses[0]["result"]["preview"][0]["new_text"] - .as_str() - .unwrap() - .contains("\"bounded loop\""), - "the preview carries the edited source: {responses:?}" - ); -} - -#[test] -fn a_long_lived_session_observes_disk_changes_and_enforces_fresh_ids() { - use std::io::{BufRead, BufReader}; - let dir = std::env::temp_dir().join(format!("wright-serve-471-{}", std::process::id())); - std::fs::create_dir_all(&dir).unwrap(); - let input = dir.join("session.ws"); - let v1 = r#" -variables { - global: - 0: score -} -rule ("setup") { - event { - Ongoing - Global; - } - actions { - Set Global Variable(score, 1); - } -} -"#; - let v2 = r#" -variables { - global: - 0: points -} -rule ("setup") { - event { - Ongoing - Global; - } - actions { - Set Global Variable(points, sqrt(4)); - } -} -rule ("extra") { - event { - Ongoing - Global; - } - actions { - While(Compare(Global.points, <, 3)); - Modify Global Variable(points, Add, 1); - Wait(0.016, Ignore Condition); - End; - } -} -"#; - std::fs::write(&input, v1).unwrap(); - - let mut child = Command::new(wright()) - .args(["serve", "--transport", "stdio"]) - .arg(&input) - .stdin(Stdio::piped()) - .stdout(Stdio::piped()) - .stderr(Stdio::piped()) - .spawn() - .expect("wright serve spawns"); - let mut stdin = child.stdin.take().unwrap(); - let mut stdout = BufReader::new(child.stdout.take().unwrap()); - let mut exchange = |request: &str| -> serde_json::Value { - writeln!(stdin, "{request}").unwrap(); - stdin.flush().unwrap(); - let mut line = String::new(); - assert_ne!( - stdout.read_line(&mut line).unwrap(), - 0, - "the session answered {request}" - ); - serde_json::from_str(&line).expect("JSON response") - }; - - let names = |response: &serde_json::Value| { - response["result"]["symbols"] - .as_array() - .unwrap() - .iter() - .map(|symbol| symbol["name"].as_str().unwrap().to_string()) - .collect::>() - }; - let listed = exchange(r#"{"op":"symbols"}"#); - assert_eq!(names(&listed), ["score", "setup"]); - let snapshot = listed["result"]["snapshot"].as_u64().unwrap(); - - let empty = exchange(r#"{"op":"symbols","max":0}"#); - assert_eq!(empty["result"]["snapshot"], snapshot, "{empty}"); - assert_eq!( - exchange(r#"{"op":"references","symbol":0}"#)["error"]["code"], - "stale-id" - ); - assert_eq!( - exchange(r#"{"op":"usage","symbol":0}"#)["error"]["code"], - "stale-id" - ); - let live = format!(r#"{{"op":"references","symbol":0,"snapshot":{snapshot}}}"#); - assert!(exchange(&live).get("result").is_some(), "{}", live); - - std::fs::write(&input, v2).unwrap(); - - let stale = exchange(&live); - assert_eq!(stale["error"]["code"], "stale-id", "{stale}"); - let by_name = exchange(r#"{"op":"references","symbol":"points"}"#); - assert!(by_name.get("result").is_some(), "{by_name}"); - let name_stale = format!(r#"{{"op":"references","symbol":"points","snapshot":{snapshot}}}"#); - assert!( - exchange(&name_stale).get("result").is_some(), - "a name ignores the stale generation: {name_stale}" - ); - assert_eq!( - exchange(r#"{"op":"cfg","rule":1}"#)["error"]["code"], - "stale-id" - ); - - let listed = exchange(r#"{"op":"symbols"}"#); - assert_eq!(names(&listed), ["points", "setup", "extra"]); - let snapshot = listed["result"]["snapshot"].as_u64().unwrap(); - assert!( - exchange(&format!( - r#"{{"op":"references","symbol":0,"snapshot":{snapshot}}}"# - )) - .get("result") - .is_some() - ); - let rules = exchange(r#"{"op":"rules"}"#); - let rule_generation = rules["result"]["snapshot"].as_u64().unwrap(); - assert_eq!(rule_generation, snapshot, "{rules}"); - assert!( - exchange(&format!(r#"{{"op":"cfg","rule":1,"snapshot":{snapshot}}}"#)) - .get("result") - .is_some() - ); - - let check = exchange(r#"{"op":"check"}"#); - assert_eq!(check["result"]["ok"], false, "{check}"); - assert!( - check["result"]["diagnostics"] - .as_array() - .unwrap() - .iter() - .any(|d| d["code"].as_str().unwrap().contains("sqrt")), - "{check}" - ); - let lint = exchange(r#"{"op":"lint"}"#); - assert!( - lint["result"]["findings"] - .as_array() - .unwrap() - .iter() - .any(|finding| finding["code"] == "min-wait-loop"), - "{lint}" - ); - - drop(stdin); - let output = child.wait_with_output().unwrap(); - assert!( - output.status.success(), - "serve exited: {}", - String::from_utf8_lossy(&output.stderr) - ); - let _ = std::fs::remove_dir_all(&dir); -} - #[test] fn transports_are_equivalent_for_mutation_operations() { let input = corpus_workshop("synthetic/control-flow"); diff --git a/crates/wright-cli/tests/serve_mcp.rs b/crates/wright-cli/tests/serve_mcp.rs index ada17d0a..2fea0901 100644 --- a/crates/wright-cli/tests/serve_mcp.rs +++ b/crates/wright-cli/tests/serve_mcp.rs @@ -92,33 +92,6 @@ fn mcp_transport_lists_the_initial_tool_set_within_capabilities() { assert!(responses[2]["result"].is_object()); let tools = responses[1]["result"]["tools"].as_array().unwrap(); - let names: Vec<&str> = tools.iter().map(|t| t["name"].as_str().unwrap()).collect(); - assert_eq!( - names, - [ - "wright_project", - "wright_rules", - "wright_symbols", - "wright_references", - "wright_usage", - "wright_cfg", - "wright_findings", - "wright_persistent_objects", - "wright_lint", - "wright_lint_rules", - "wright_call_graph", - "wright_cost_estimate", - "wright_target_metadata", - "wright_check", - "wright_analyze", - "wright_inspect", - "wright_validate_edit_transaction", - "wright_provider_validate_edit", - "wright_edit_statements", - "wright_lookup", - "wright_simulate", - ] - ); let caps = run_lines("stdio", &input, &[r#"{"op":"capabilities"}"#]); let operations: Vec = caps[0]["result"]["operations"] .as_array() diff --git a/crates/wright-cli/tests/serve_simulate.rs b/crates/wright-cli/tests/serve_simulate.rs index 1d0ba39e..f134d5e5 100644 --- a/crates/wright-cli/tests/serve_simulate.rs +++ b/crates/wright-cli/tests/serve_simulate.rs @@ -74,21 +74,6 @@ fn trace_of(response: &Value) -> &Value { &response["result"]["result"]["trace"] } -#[test] -fn stdio_serves_simulate_on_the_real_practice_range() { - let input = fixture("athena/practice-range.ow"); - let response = simulate("stdio", &input, json!({"seconds": 1.0})); - let envelope = &response["result"]; - assert_eq!(envelope["command"], json!("simulate")); - let trace = trace_of(&response); - assert_eq!(trace["outcome"]["status"], json!("complete")); - assert_eq!(trace["globals"]["score"]["value"], json!(0.0)); - let [hud] = trace["hud"].as_array().unwrap().as_slice() else { - panic!("expected the init rule's HUD element: {}", trace["hud"]) - }; - assert_eq!(hud["header"]["value"], json!("Score: 0.00")); -} - #[test] fn stdio_serves_simulate_scenario_and_missing_input() { let input = fixture("athena/practice-range.ow"); @@ -130,310 +115,11 @@ fn jsonrpc_serves_simulate_and_reports_the_athena_version() { assert_eq!(trace["globals"]["score"]["value"], json!(2.0)); let caps = run_lines("stdio", &input, &[r#"{"op":"capabilities"}"#]); - assert_eq!(caps[0]["result"]["athena_version"], json!("0.11.0")); -} - -#[test] -fn stdio_simulate_stale_identity_refuses_before_running() { - let input = fixture("athena/practice-range.ow"); - let responses = run_lines( - "stdio", - &input, - &[r#"{"op":"simulate","inputIdentity":"stale","seconds":1.0}"#], - ); - assert_eq!(responses[0]["error"]["code"], json!("edit-stale-source")); -} - -// `athena`'s fixture tolerances and comparison, ported over the wire trace -// `wright serve` returns (the driver suite runs the identical comparison -// in-process). - -const LOG_TOLERANCE: f64 = 0.0051; -const VARIABLE_TOLERANCE: f64 = 0.0006; -const CAPTURES: [&str; 3] = ["capture.txt", "capture-2.txt", "capture-3.txt"]; -const RUN_SECONDS: f64 = 10.0; - -struct Capture { - variables: Vec<(String, String)>, - logs: Vec, -} - -fn parse_capture(text: &str) -> Capture { - let mut variables = Vec::new(); - let mut logs = Vec::new(); - for line in text.lines() { - let line = line.trim(); - if let Some(rest) = line.strip_prefix("全局.") { - let (name, value) = rest.trim_end_matches(';').split_once(" = ").unwrap(); - variables.push((name.trim().to_string(), value.trim().to_string())); - } else if line.starts_with("[00:") { - let (_, message) = line.split_once("] ").unwrap(); - logs.push(message.to_string()); - } - } - Capture { variables, logs } -} - -#[derive(Debug, PartialEq)] -enum Token { - Text(String), - Number(f64), -} - -fn tokens(text: &str) -> Vec { - let chars: Vec = text.chars().collect(); - let mut out = Vec::new(); - let mut text_run = String::new(); - let mut i = 0; - while i < chars.len() { - if chars[i].is_ascii_digit() { - let start = i; - while i < chars.len() && chars[i].is_ascii_digit() { - i += 1; - } - if i + 1 < chars.len() && chars[i] == '.' && chars[i + 1].is_ascii_digit() { - i += 1; - while i < chars.len() && chars[i].is_ascii_digit() { - i += 1; - } - } - if !text_run.is_empty() { - out.push(Token::Text(std::mem::take(&mut text_run))); - } - let number: String = chars[start..i].iter().collect(); - out.push(Token::Number(number.parse().unwrap())); - } else { - text_run.push(chars[i]); - i += 1; - } - } - if !text_run.is_empty() { - out.push(Token::Text(text_run)); - } - out -} - -fn same_log(simulated: &str, captured: &str) -> bool { - let (a, b) = (tokens(simulated), tokens(captured)); - a.len() == b.len() - && a.iter().zip(&b).all(|pair| match pair { - (Token::Text(x), Token::Text(y)) => x == y, - (Token::Number(x), Token::Number(y)) => (x - y).abs() <= LOG_TOLERANCE, - _ => false, - }) -} - -/// `athena`'s zero-like rule: the client omits variables whose value is -/// zero, so a trace global absent from the export must be null, false, or 0. -fn is_zero_like(value: &Value) -> bool { - match value["type"].as_str() { - Some("null") => true, - Some("bool") => value["value"].as_bool() == Some(false), - Some("number") => value["value"].as_f64() == Some(0.0), - _ => false, - } -} - -fn compare_variables(trace: &Value, captured: &Capture) -> Result<(), String> { - let globals = trace["globals"].as_object().unwrap(); - for (name, expected) in &captured.variables { - let Some(actual) = globals.get(name) else { - return Err(format!("variable {name} missing from simulation")); - }; - let ok = match (expected.as_str(), actual["type"].as_str()) { - ("真", Some("bool")) => actual["value"].as_bool() == Some(true), - ("假", Some("bool")) => actual["value"].as_bool() == Some(false), - ("假", _) => is_zero_like(actual), - (number, Some("number")) => number - .parse::() - .is_ok_and(|n| (n - actual["value"].as_f64().unwrap()).abs() <= VARIABLE_TOLERANCE), - _ => false, - }; - if !ok { - return Err(format!( - "variable {name}: capture {expected}, simulated {actual}" - )); - } - } - for (name, value) in globals { - let exported = captured.variables.iter().any(|(n, _)| n == name); - if !exported && !is_zero_like(value) { - return Err(format!( - "variable {name} = {value} is absent from the capture" - )); - } - } - Ok(()) -} - -fn compare_logs(trace: &Value, captured: &Capture) -> Result<(), String> { - let simulated: Vec<&str> = trace["logs"] - .as_array() - .unwrap() - .iter() - .map(|entry| entry["text"].as_str().unwrap()) - .collect(); - if simulated.len() != captured.logs.len() { - return Err(format!( - "{} simulated log lines, {} captured", - simulated.len(), - captured.logs.len() - )); - } - for (index, (sim, cap)) in simulated.iter().zip(&captured.logs).enumerate() { - if !same_log(sim, cap) { - return Err(format!( - "log line {index}: simulated {sim:?}, captured {cap:?}" - )); - } - } - Ok(()) -} - -fn check_probe(id: &str) { - let dir = fixture(&format!("athena/probes/{id}")); - let program = dir.join("probe.workshop"); - let response = simulate("stdio", &program, json!({"seconds": RUN_SECONDS})); - let trace = trace_of(&response); - assert_eq!(trace["outcome"]["status"], json!("complete"), "{id}"); - for file in CAPTURES { - let captured = parse_capture(&std::fs::read_to_string(dir.join(file)).unwrap()); - if let Err(message) = - compare_logs(trace, &captured).and(compare_variables(trace, &captured)) - { - panic!("{id} vs {file}: {message}"); - } - } -} - -#[test] -fn serve_probe_p0_matches_recorded_captures() { - check_probe("p0-timer-resolution"); -} - -#[test] -fn serve_probe_p1_matches_recorded_captures() { - check_probe("p1-wait-rounding"); -} - -#[test] -fn serve_probe_p2_matches_recorded_captures() { - check_probe("p2-edge-trigger-and-wait-block"); -} - -#[test] -fn serve_probe_p3_matches_recorded_captures() { - check_probe("p3-same-tick-rule-order"); -} - -#[test] -fn serve_probe_p3_driver_last_matches_recorded_captures() { - check_probe("p3-same-tick-rule-order-driver-last"); -} - -#[cfg(unix)] -const PROVIDER: &str = r#"#!/usr/bin/env python3 -import json, os, sys -artifact = open(os.path.join(os.path.dirname(os.path.abspath(__file__)), "artifact.ws")).read() -initialized = False -def reply(id, result=None, error=None): - message = {"jsonrpc": "2.0", "id": id} - message.update({"error": error} if error else {"result": result}) - print(json.dumps(message), flush=True) -def lpp_error(id, kind, details, text): - reply(id, error={"code": -32000, "message": text, "data": {"lpp": {"kind": kind, "details": details}}}) -for line in sys.stdin: - request = json.loads(line) - id, method = request["id"], request["method"] - if method == "lpp/initialize": - first = not initialized - initialized = True - if not first: - lpp_error(id, "alreadyInitialized", {}, "already initialized") - elif request["params"]["protocolVersion"] != "1.1": - lpp_error(id, "protocolVersionMismatch", {"supportedProtocolVersions": ["1.1"]}, "unsupported") - else: - reply(id, {"protocolVersion": "1.1", "serverInfo": {"name": "fake", "version": "0"}, - "languages": [{"id": "opy", "extensions": ["opy"]}], - "capabilities": {"check": True, "compile": True, "reconstruct": False, "symbols": False, "definition": False, "references": False, "rename": False, "editValidation": False, "projectLoading": True}}) - elif method == "lpp/compile": - reply(id, {"diagnostics": [], "artifact": {"format": "workshop-rs/text-v1", "content": artifact}}) - else: - reply(id, {}) -"#; - -#[cfg(unix)] -#[test] -fn stdio_simulates_a_provider_loaded_opy_artifact() { - use std::io::{BufRead, BufReader}; - use std::os::unix::fs::PermissionsExt; - - let target = match (std::env::consts::OS, std::env::consts::ARCH) { - ("linux", "x86_64") => "x86_64-unknown-linux-gnu", - ("macos", "x86_64") => "x86_64-apple-darwin", - ("macos", "aarch64") => "aarch64-apple-darwin", - ("windows", "x86_64") => "x86_64-pc-windows-msvc", - (os, arch) => panic!("unsupported OPY provider target {os}/{arch}"), - }; - let dir = std::env::temp_dir().join(format!("wright-serve-sim-opy-{}", std::process::id())); - let provider_dir = dir.join("providers/opy/9.9.9").join(target); - std::fs::create_dir_all(&provider_dir).unwrap(); - let script = provider_dir.join("opy-provider"); - std::fs::write(&script, PROVIDER).unwrap(); - std::fs::set_permissions(&script, std::fs::Permissions::from_mode(0o755)).unwrap(); - std::fs::write( - provider_dir.join("artifact.ws"), - std::fs::read_to_string(fixture( - "athena/probes/p5-value-refresh-rate/probe.workshop", - )) - .unwrap(), - ) - .unwrap(); - std::fs::write(dir.join("providers/opy/active"), "9.9.9").unwrap(); - let input = dir.join("main.opy"); - std::fs::write(&input, "# fake OPY entry; the staged provider ignores it").unwrap(); - - let mut child = Command::new(env!("CARGO_BIN_EXE_wright")) - .args(["serve", "--transport", "stdio"]) - .arg(&input) - .env("WRIGHT_PROVIDER_DATA_DIR", &dir) - .stdin(Stdio::piped()) - .stdout(Stdio::piped()) - .stderr(Stdio::piped()) - .spawn() - .expect("wright serve spawns"); - let mut stdin = child.stdin.take().unwrap(); - let mut stdout = BufReader::new(child.stdout.take().unwrap()); - let mut exchange = |request: &str| -> Value { - writeln!(stdin, "{request}").unwrap(); - stdin.flush().unwrap(); - let mut line = String::new(); - assert_ne!( - stdout.read_line(&mut line).unwrap(), - 0, - "the session answered {request}" - ); - serde_json::from_str(&line).expect("JSON response") - }; - - let project = exchange(r#"{"op":"project"}"#); - let identity = project["result"]["inputIdentity"].as_str().unwrap(); - let simulate = exchange( - &json!({"op": "simulate", "inputIdentity": identity, "seconds": RUN_SECONDS}).to_string(), - ); - let trace = &simulate["result"]["result"]["trace"]; - assert_eq!(trace["outcome"]["status"], "not_modeled", "{simulate}"); - assert!( - trace["outcome"]["construct"].is_string(), - "not_modeled names the construct: {simulate}" - ); - - drop(stdin); - let output = child.wait_with_output().unwrap(); - let _ = std::fs::remove_dir_all(&dir); assert!( - output.status.success(), - "serve exited: {}", - String::from_utf8_lossy(&output.stderr) + caps[0]["result"]["athena_version"] + .as_str() + .is_some_and(|version| !version.is_empty()), + "{}", + caps[0] ); } diff --git a/crates/wright-cli/tests/snapshots/terminal__analyze_warn_w100_never.snap b/crates/wright-cli/tests/snapshots/terminal__analyze_warn.snap similarity index 100% rename from crates/wright-cli/tests/snapshots/terminal__analyze_warn_w100_never.snap rename to crates/wright-cli/tests/snapshots/terminal__analyze_warn.snap diff --git a/crates/wright-cli/tests/snapshots/terminal__analyze_warn_w100_always.snap b/crates/wright-cli/tests/snapshots/terminal__analyze_warn_w100_always.snap deleted file mode 100644 index 577fe146..00000000 --- a/crates/wright-cli/tests/snapshots/terminal__analyze_warn_w100_always.snap +++ /dev/null @@ -1,43 +0,0 @@ ---- -source: crates/wright-cli/tests/terminal.rs -expression: transcript(&out) ---- -exit: 0 ---- stdout --- -[43;30m WARN [0m analyze - [2m1 warning, 0 persistent objects[0m - -[1mProgram overview[0m - 1 file, 2 rules, 1 global variable, 0 player variables, 0 subroutines, 0 persistent objects - [2mevidence: [static] parsed program inventory[0m - -[1mWorkshop cost[0m - 21 elements [exact] — canonical element count; structural size, not runtime cost - 7 actions, 0 conditions, 1 wait [static] - -[1mHotspots[0m - [2mranked by Workshop element count [exact counts; the ranking itself is a heuristic][0m - "bounded while": 12 elements (57.1%), 5 blocks, 5 edges, 1 risk at flow.ws:6:1 - "branch": 9 elements (42.9%), 5 blocks, 5 edges, 0 risks at flow.ws:18:1 - -[1mComplexity[0m - 10 blocks, 10 edges, 1 loop block, 1 wait block [static] - -[1mPerformance and stability risks (evidence: static-indicator)[0m - [2mheuristic/static indicators — not measured runtime cost or behavior[0m - [33mwarning[0m[min-wait-loop]: loop body waits at the workshop minimum rate; the loop runs at maximum frequency - --> flow.ws:11:9 - -[1mState and coupling[0m - [2mTop variables (heuristic ranking: rules touched, then reads + writes)[0m - globalVariable index: 2 rules, 3 reads, 2 writes [static] - [2m--all; inspect symbols, refs, cfg, callgraph, cost for detail[0m - -[1mNext[0m - wright lint - wright inspect symbols - wright inspect refs - wright inspect cfg - wright inspect callgraph - wright inspect cost ---- stderr --- diff --git a/crates/wright-cli/tests/snapshots/terminal__analyze_warn_w80_always.snap b/crates/wright-cli/tests/snapshots/terminal__analyze_warn_w80_always.snap deleted file mode 100644 index 1b57efc9..00000000 --- a/crates/wright-cli/tests/snapshots/terminal__analyze_warn_w80_always.snap +++ /dev/null @@ -1,43 +0,0 @@ ---- -source: crates/wright-cli/tests/terminal.rs -expression: transcript(&out) ---- -exit: 0 ---- stdout --- -[43;30m WARN [0m analyze - [2m1 warning, 0 persistent objects[0m - -[1mProgram overview[0m - 1 file, 2 rules, 1 global variable, 0 player variables, 0 subroutines, 0 persistent objects - [2mevidence: [static] parsed program inventory[0m - -[1mWorkshop cost[0m - 21 elements [exact] — canonical element count; structural size, not runtime cost - 7 actions, 0 conditions, 1 wait [static] - -[1mHotspots[0m - [2mranked by Workshop element count [exact counts; the ranking itself is a heuristic][0m - "bounded while": 12 elements (57.1%), 5 blocks, 5 edges, 1 risk at flow.ws:6:1 - "branch": 9 elements (42.9%), 5 blocks, 5 edges, 0 risks at flow.ws:18:1 - -[1mComplexity[0m - 10 blocks, 10 edges, 1 loop block, 1 wait block [static] - -[1mPerformance and stability risks (evidence: static-indicator)[0m - [2mheuristic/static indicators — not measured runtime cost or behavior[0m - [33mwarning[0m[min-wait-loop]: loop body waits at the workshop minimum rate; the loop runs at maximum frequency - --> flow.ws:11:9 - -[1mState and coupling[0m - [2mTop variables (heuristic ranking: rules touched, then reads + writes)[0m - globalVariable index: 2 rules, 3 reads, 2 writes [static] - [2m--all; inspect symbols, refs, cfg, callgraph, cost for detail[0m - -[1mNext[0m - wright lint - wright inspect symbols - wright inspect refs - wright inspect cfg - wright inspect callgraph - wright inspect cost ---- stderr --- diff --git a/crates/wright-cli/tests/snapshots/terminal__analyze_warn_w80_never.snap b/crates/wright-cli/tests/snapshots/terminal__analyze_warn_w80_never.snap deleted file mode 100644 index deacbbb9..00000000 --- a/crates/wright-cli/tests/snapshots/terminal__analyze_warn_w80_never.snap +++ /dev/null @@ -1,43 +0,0 @@ ---- -source: crates/wright-cli/tests/terminal.rs -expression: transcript(&out) ---- -exit: 0 ---- stdout --- - WARN analyze - 1 warning, 0 persistent objects - -Program overview - 1 file, 2 rules, 1 global variable, 0 player variables, 0 subroutines, 0 persistent objects - evidence: [static] parsed program inventory - -Workshop cost - 21 elements [exact] — canonical element count; structural size, not runtime cost - 7 actions, 0 conditions, 1 wait [static] - -Hotspots - ranked by Workshop element count [exact counts; the ranking itself is a heuristic] - "bounded while": 12 elements (57.1%), 5 blocks, 5 edges, 1 risk at flow.ws:6:1 - "branch": 9 elements (42.9%), 5 blocks, 5 edges, 0 risks at flow.ws:18:1 - -Complexity - 10 blocks, 10 edges, 1 loop block, 1 wait block [static] - -Performance and stability risks (evidence: static-indicator) - heuristic/static indicators — not measured runtime cost or behavior - warning[min-wait-loop]: loop body waits at the workshop minimum rate; the loop runs at maximum frequency - --> flow.ws:11:9 - -State and coupling - Top variables (heuristic ranking: rules touched, then reads + writes) - globalVariable index: 2 rules, 3 reads, 2 writes [static] - --all; inspect symbols, refs, cfg, callgraph, cost for detail - -Next - wright lint - wright inspect symbols - wright inspect refs - wright inspect cfg - wright inspect callgraph - wright inspect cost ---- stderr --- diff --git a/crates/wright-cli/tests/snapshots/terminal__check_error_w100_never.snap b/crates/wright-cli/tests/snapshots/terminal__check_error.snap similarity index 100% rename from crates/wright-cli/tests/snapshots/terminal__check_error_w100_never.snap rename to crates/wright-cli/tests/snapshots/terminal__check_error.snap diff --git a/crates/wright-cli/tests/snapshots/terminal__check_error_w100_always.snap b/crates/wright-cli/tests/snapshots/terminal__check_error_w100_always.snap deleted file mode 100644 index 8535bbf9..00000000 --- a/crates/wright-cli/tests/snapshots/terminal__check_error_w100_always.snap +++ /dev/null @@ -1,29 +0,0 @@ ---- -source: crates/wright-cli/tests/terminal.rs -assertion_line: 97 -expression: transcript(&out) ---- -exit: 1 ---- stdout --- -[41;37m ERROR [0m check - [2m1 error, 2 warnings[0m -[31merror[0m[workshop.unknown-value.futurevaluething]: Workshop construct 'FutureValueThing' is unsupported (truly-unresolved-identifier) - --> mixed.ws:13:26 - | 13 | Set Global Variable(B, FutureValueThing(2)); - | | ^ - [2m= analysis[0m - [2mhint: run 'wright lookup' with the rejected name to find its spelling and signature[0m - -[33mwarning[0m[workshop.raw-setting.mysetting]: Workshop construct 'mySetting' is partially supported (project-defined-construct) - --> mixed.ws:3:3 - | 3 | mySetting: 1 - | | ^ - [2m= analysis[0m - -[33mwarning[0m[workshop.raw-setting.othersetting]: Workshop construct 'otherSetting' is partially supported (project-defined-construct) - --> mixed.ws:4:3 - | 4 | otherSetting: 2 - | | ^ - [2m= analysis[0m - [2m1 file affected[0m ---- stderr --- diff --git a/crates/wright-cli/tests/snapshots/terminal__check_error_w80_always.snap b/crates/wright-cli/tests/snapshots/terminal__check_error_w80_always.snap deleted file mode 100644 index 8535bbf9..00000000 --- a/crates/wright-cli/tests/snapshots/terminal__check_error_w80_always.snap +++ /dev/null @@ -1,29 +0,0 @@ ---- -source: crates/wright-cli/tests/terminal.rs -assertion_line: 97 -expression: transcript(&out) ---- -exit: 1 ---- stdout --- -[41;37m ERROR [0m check - [2m1 error, 2 warnings[0m -[31merror[0m[workshop.unknown-value.futurevaluething]: Workshop construct 'FutureValueThing' is unsupported (truly-unresolved-identifier) - --> mixed.ws:13:26 - | 13 | Set Global Variable(B, FutureValueThing(2)); - | | ^ - [2m= analysis[0m - [2mhint: run 'wright lookup' with the rejected name to find its spelling and signature[0m - -[33mwarning[0m[workshop.raw-setting.mysetting]: Workshop construct 'mySetting' is partially supported (project-defined-construct) - --> mixed.ws:3:3 - | 3 | mySetting: 1 - | | ^ - [2m= analysis[0m - -[33mwarning[0m[workshop.raw-setting.othersetting]: Workshop construct 'otherSetting' is partially supported (project-defined-construct) - --> mixed.ws:4:3 - | 4 | otherSetting: 2 - | | ^ - [2m= analysis[0m - [2m1 file affected[0m ---- stderr --- diff --git a/crates/wright-cli/tests/snapshots/terminal__check_error_w80_never.snap b/crates/wright-cli/tests/snapshots/terminal__check_error_w80_never.snap deleted file mode 100644 index 5e41f3cd..00000000 --- a/crates/wright-cli/tests/snapshots/terminal__check_error_w80_never.snap +++ /dev/null @@ -1,29 +0,0 @@ ---- -source: crates/wright-cli/tests/terminal.rs -assertion_line: 97 -expression: transcript(&out) ---- -exit: 1 ---- stdout --- - ERROR check - 1 error, 2 warnings -error[workshop.unknown-value.futurevaluething]: Workshop construct 'FutureValueThing' is unsupported (truly-unresolved-identifier) - --> mixed.ws:13:26 - | 13 | Set Global Variable(B, FutureValueThing(2)); - | | ^ - = analysis - hint: run 'wright lookup' with the rejected name to find its spelling and signature - -warning[workshop.raw-setting.mysetting]: Workshop construct 'mySetting' is partially supported (project-defined-construct) - --> mixed.ws:3:3 - | 3 | mySetting: 1 - | | ^ - = analysis - -warning[workshop.raw-setting.othersetting]: Workshop construct 'otherSetting' is partially supported (project-defined-construct) - --> mixed.ws:4:3 - | 4 | otherSetting: 2 - | | ^ - = analysis - 1 file affected ---- stderr --- diff --git a/crates/wright-cli/tests/snapshots/terminal__check_pass_w100_never.snap b/crates/wright-cli/tests/snapshots/terminal__check_pass.snap similarity index 100% rename from crates/wright-cli/tests/snapshots/terminal__check_pass_w100_never.snap rename to crates/wright-cli/tests/snapshots/terminal__check_pass.snap diff --git a/crates/wright-cli/tests/snapshots/terminal__check_pass_w100_always.snap b/crates/wright-cli/tests/snapshots/terminal__check_pass_w100_always.snap deleted file mode 100644 index cec1efe5..00000000 --- a/crates/wright-cli/tests/snapshots/terminal__check_pass_w100_always.snap +++ /dev/null @@ -1,17 +0,0 @@ ---- -source: crates/wright-cli/tests/terminal.rs -expression: transcript(&out) ---- -exit: 0 ---- stdout --- -[42;30m PASS [0m check - [2m0 diagnostics[0m - -[1mProgram overview[0m - 1 file, 1 rule, 0 global variables, 0 player variables, 0 subroutines - -[1mNext[0m - wright lint - wright analyze - wright inspect ---- stderr --- diff --git a/crates/wright-cli/tests/snapshots/terminal__check_pass_w80_always.snap b/crates/wright-cli/tests/snapshots/terminal__check_pass_w80_always.snap deleted file mode 100644 index cec1efe5..00000000 --- a/crates/wright-cli/tests/snapshots/terminal__check_pass_w80_always.snap +++ /dev/null @@ -1,17 +0,0 @@ ---- -source: crates/wright-cli/tests/terminal.rs -expression: transcript(&out) ---- -exit: 0 ---- stdout --- -[42;30m PASS [0m check - [2m0 diagnostics[0m - -[1mProgram overview[0m - 1 file, 1 rule, 0 global variables, 0 player variables, 0 subroutines - -[1mNext[0m - wright lint - wright analyze - wright inspect ---- stderr --- diff --git a/crates/wright-cli/tests/snapshots/terminal__check_pass_w80_never.snap b/crates/wright-cli/tests/snapshots/terminal__check_pass_w80_never.snap deleted file mode 100644 index fa59a3bd..00000000 --- a/crates/wright-cli/tests/snapshots/terminal__check_pass_w80_never.snap +++ /dev/null @@ -1,17 +0,0 @@ ---- -source: crates/wright-cli/tests/terminal.rs -expression: transcript(&out) ---- -exit: 0 ---- stdout --- - PASS check - 0 diagnostics - -Program overview - 1 file, 1 rule, 0 global variables, 0 player variables, 0 subroutines - -Next - wright lint - wright analyze - wright inspect ---- stderr --- diff --git a/crates/wright-cli/tests/snapshots/terminal__compile_w100_never.snap b/crates/wright-cli/tests/snapshots/terminal__compile.snap similarity index 100% rename from crates/wright-cli/tests/snapshots/terminal__compile_w100_never.snap rename to crates/wright-cli/tests/snapshots/terminal__compile.snap diff --git a/crates/wright-cli/tests/snapshots/terminal__compile_w100_always.snap b/crates/wright-cli/tests/snapshots/terminal__compile_w100_always.snap deleted file mode 100644 index 67bcd41c..00000000 --- a/crates/wright-cli/tests/snapshots/terminal__compile_w100_always.snap +++ /dev/null @@ -1,11 +0,0 @@ ---- -source: crates/wright-cli/tests/terminal.rs -expression: transcript(&out) ---- -exit: 2 ---- stdout --- -[41;37m USAGE [0m compile - [2m1 error[0m ---- stderr --- -[31merror[0m[output-exists]: 'out.ow' already exists; pass --force (agent request: force) to overwrite it, or choose another path with -o - [2m= discovery[0m diff --git a/crates/wright-cli/tests/snapshots/terminal__compile_w80_always.snap b/crates/wright-cli/tests/snapshots/terminal__compile_w80_always.snap deleted file mode 100644 index 67bcd41c..00000000 --- a/crates/wright-cli/tests/snapshots/terminal__compile_w80_always.snap +++ /dev/null @@ -1,11 +0,0 @@ ---- -source: crates/wright-cli/tests/terminal.rs -expression: transcript(&out) ---- -exit: 2 ---- stdout --- -[41;37m USAGE [0m compile - [2m1 error[0m ---- stderr --- -[31merror[0m[output-exists]: 'out.ow' already exists; pass --force (agent request: force) to overwrite it, or choose another path with -o - [2m= discovery[0m diff --git a/crates/wright-cli/tests/snapshots/terminal__compile_w80_never.snap b/crates/wright-cli/tests/snapshots/terminal__compile_w80_never.snap deleted file mode 100644 index 26e81265..00000000 --- a/crates/wright-cli/tests/snapshots/terminal__compile_w80_never.snap +++ /dev/null @@ -1,11 +0,0 @@ ---- -source: crates/wright-cli/tests/terminal.rs -expression: transcript(&out) ---- -exit: 2 ---- stdout --- - USAGE compile - 1 error ---- stderr --- -error[output-exists]: 'out.ow' already exists; pass --force (agent request: force) to overwrite it, or choose another path with -o - = discovery diff --git a/crates/wright-cli/tests/snapshots/terminal__convert_w100_always.snap b/crates/wright-cli/tests/snapshots/terminal__convert.snap similarity index 100% rename from crates/wright-cli/tests/snapshots/terminal__convert_w100_always.snap rename to crates/wright-cli/tests/snapshots/terminal__convert.snap diff --git a/crates/wright-cli/tests/snapshots/terminal__convert_w100_never.snap b/crates/wright-cli/tests/snapshots/terminal__convert_w100_never.snap deleted file mode 100644 index f747f712..00000000 --- a/crates/wright-cli/tests/snapshots/terminal__convert_w100_never.snap +++ /dev/null @@ -1,22 +0,0 @@ ---- -source: crates/wright-cli/tests/terminal.rs -expression: transcript(&out) ---- -exit: 0 ---- stdout --- -#!optimizeStrict - -globalvar index 0 - -rule "bounded while": - @Event global - while (index < 3): - index = index + 1 - wait(0.016, Wait.IGNORE_CONDITION) - -rule "branch": - @Event global - if (index == 0): - index = 1 - ---- stderr --- diff --git a/crates/wright-cli/tests/snapshots/terminal__convert_w80_always.snap b/crates/wright-cli/tests/snapshots/terminal__convert_w80_always.snap deleted file mode 100644 index f747f712..00000000 --- a/crates/wright-cli/tests/snapshots/terminal__convert_w80_always.snap +++ /dev/null @@ -1,22 +0,0 @@ ---- -source: crates/wright-cli/tests/terminal.rs -expression: transcript(&out) ---- -exit: 0 ---- stdout --- -#!optimizeStrict - -globalvar index 0 - -rule "bounded while": - @Event global - while (index < 3): - index = index + 1 - wait(0.016, Wait.IGNORE_CONDITION) - -rule "branch": - @Event global - if (index == 0): - index = 1 - ---- stderr --- diff --git a/crates/wright-cli/tests/snapshots/terminal__convert_w80_never.snap b/crates/wright-cli/tests/snapshots/terminal__convert_w80_never.snap deleted file mode 100644 index f747f712..00000000 --- a/crates/wright-cli/tests/snapshots/terminal__convert_w80_never.snap +++ /dev/null @@ -1,22 +0,0 @@ ---- -source: crates/wright-cli/tests/terminal.rs -expression: transcript(&out) ---- -exit: 0 ---- stdout --- -#!optimizeStrict - -globalvar index 0 - -rule "bounded while": - @Event global - while (index < 3): - index = index + 1 - wait(0.016, Wait.IGNORE_CONDITION) - -rule "branch": - @Event global - if (index == 0): - index = 1 - ---- stderr --- diff --git a/crates/wright-cli/tests/snapshots/terminal__inspect_cfg_w100_never.snap b/crates/wright-cli/tests/snapshots/terminal__inspect_cfg.snap similarity index 100% rename from crates/wright-cli/tests/snapshots/terminal__inspect_cfg_w100_never.snap rename to crates/wright-cli/tests/snapshots/terminal__inspect_cfg.snap diff --git a/crates/wright-cli/tests/snapshots/terminal__inspect_cfg_w100_always.snap b/crates/wright-cli/tests/snapshots/terminal__inspect_cfg_w100_always.snap deleted file mode 100644 index f41f7308..00000000 --- a/crates/wright-cli/tests/snapshots/terminal__inspect_cfg_w100_always.snap +++ /dev/null @@ -1,22 +0,0 @@ ---- -source: crates/wright-cli/tests/terminal.rs -expression: transcript(&out) ---- -exit: 0 ---- stdout --- -[46;30m CFG [0m - [2m5 blocks, 5 edges[0m - -[1mControl-flow graph of rule "bounded while"[0m - 5 blocks, 5 edges: 1 loop header, 1 wait block, 0 call sites - entry: block 0, exit: block 4 - block 0 (entry): 0 actions - -> 1 (fallthrough) - block 1 (while): 1 action - -> 2 (true), 3 (loop-exit) - block 2 (block): 2 actions [waits] - -> 1 (back) - block 3 (block): 0 actions - -> 4 (fallthrough) - block 4 (exit): 0 actions ---- stderr --- diff --git a/crates/wright-cli/tests/snapshots/terminal__inspect_cfg_w80_always.snap b/crates/wright-cli/tests/snapshots/terminal__inspect_cfg_w80_always.snap deleted file mode 100644 index 4a4ba445..00000000 --- a/crates/wright-cli/tests/snapshots/terminal__inspect_cfg_w80_always.snap +++ /dev/null @@ -1,22 +0,0 @@ ---- -source: crates/wright-cli/tests/terminal.rs -expression: transcript(&out) ---- -exit: 0 ---- stdout --- -[46;30m CFG [0m - [2m5 blocks, 5 edges[0m - -[1mControl-flow graph of rule "bounded while"[0m - 5 blocks, 5 edges: 1 loop header, 1 wait block, 0 call sites - entry: block 0, exit: block 4 - block 0 (entry): 0 actions - -> 1 (fallthrough) - block 1 (while): 1 action - -> 2 (true), 3 (loop-exit) - block 2 (block): 2 actions [waits] - -> 1 (back) - block 3 (block): 0 actions - -> 4 (fallthrough) - block 4 (exit): 0 actions ---- stderr --- diff --git a/crates/wright-cli/tests/snapshots/terminal__inspect_cfg_w80_never.snap b/crates/wright-cli/tests/snapshots/terminal__inspect_cfg_w80_never.snap deleted file mode 100644 index e7b1b6e5..00000000 --- a/crates/wright-cli/tests/snapshots/terminal__inspect_cfg_w80_never.snap +++ /dev/null @@ -1,22 +0,0 @@ ---- -source: crates/wright-cli/tests/terminal.rs -expression: transcript(&out) ---- -exit: 0 ---- stdout --- - CFG - 5 blocks, 5 edges - -Control-flow graph of rule "bounded while" - 5 blocks, 5 edges: 1 loop header, 1 wait block, 0 call sites - entry: block 0, exit: block 4 - block 0 (entry): 0 actions - -> 1 (fallthrough) - block 1 (while): 1 action - -> 2 (true), 3 (loop-exit) - block 2 (block): 2 actions [waits] - -> 1 (back) - block 3 (block): 0 actions - -> 4 (fallthrough) - block 4 (exit): 0 actions ---- stderr --- diff --git a/crates/wright-cli/tests/snapshots/terminal__inspect_cost_w100_never.snap b/crates/wright-cli/tests/snapshots/terminal__inspect_cost.snap similarity index 100% rename from crates/wright-cli/tests/snapshots/terminal__inspect_cost_w100_never.snap rename to crates/wright-cli/tests/snapshots/terminal__inspect_cost.snap diff --git a/crates/wright-cli/tests/snapshots/terminal__inspect_cost_w100_always.snap b/crates/wright-cli/tests/snapshots/terminal__inspect_cost_w100_always.snap deleted file mode 100644 index 96eab106..00000000 --- a/crates/wright-cli/tests/snapshots/terminal__inspect_cost_w100_always.snap +++ /dev/null @@ -1,24 +0,0 @@ ---- -source: crates/wright-cli/tests/terminal.rs -expression: transcript(&out) ---- -exit: 0 ---- stdout --- -[46;30m COST [0m - [2m457 emitted bytes, 7 actions, 2 rules, 1 wait[0m - -[1mGenerated resources (exact)[0m - emitted bytes: 457 - actions: 7 - rules: 2 - waits: 1 - 21 elements of the 32768-element budget [exact] — canonical structure, not a statement of client importability - 12 elements bounded while flow.ws:6:1 - 9 elements branch flow.ws:18:1 - -[1mStatic findings (evidence: static-indicator)[0m - [33mwarning[0m[min-wait-loop]: loop body waits at the workshop minimum rate; the loop runs at maximum frequency - --> flow.ws:11:9 - | 11 | While(Compare(Global.index, <, 3)); - | | ^ ---- stderr --- diff --git a/crates/wright-cli/tests/snapshots/terminal__inspect_cost_w80_always.snap b/crates/wright-cli/tests/snapshots/terminal__inspect_cost_w80_always.snap deleted file mode 100644 index 710cf50b..00000000 --- a/crates/wright-cli/tests/snapshots/terminal__inspect_cost_w80_always.snap +++ /dev/null @@ -1,24 +0,0 @@ ---- -source: crates/wright-cli/tests/terminal.rs -expression: transcript(&out) ---- -exit: 0 ---- stdout --- -[46;30m COST [0m - [2m457 emitted bytes, 7 actions, 2 rules, 1 wait[0m - -[1mGenerated resources (exact)[0m - emitted bytes: 457 - actions: 7 - rules: 2 - waits: 1 - 21 elements of the 32768-element budget [exact] — canonical structure, not a statement of client importability - 12 elements bounded while flow.ws:6:1 - 9 elements branch flow.ws:18:1 - -[1mStatic findings (evidence: static-indicator)[0m - [33mwarning[0m[min-wait-loop]: loop body waits at the workshop minimum rate; the loop runs at maximum frequency - --> flow.ws:11:9 - | 11 | While(Compare(Global.index, <, 3)); - | | ^ ---- stderr --- diff --git a/crates/wright-cli/tests/snapshots/terminal__inspect_cost_w80_never.snap b/crates/wright-cli/tests/snapshots/terminal__inspect_cost_w80_never.snap deleted file mode 100644 index 5c0fdb6c..00000000 --- a/crates/wright-cli/tests/snapshots/terminal__inspect_cost_w80_never.snap +++ /dev/null @@ -1,24 +0,0 @@ ---- -source: crates/wright-cli/tests/terminal.rs -expression: transcript(&out) ---- -exit: 0 ---- stdout --- - COST - 457 emitted bytes, 7 actions, 2 rules, 1 wait - -Generated resources (exact) - emitted bytes: 457 - actions: 7 - rules: 2 - waits: 1 - 21 elements of the 32768-element budget [exact] — canonical structure, not a statement of client importability - 12 elements bounded while flow.ws:6:1 - 9 elements branch flow.ws:18:1 - -Static findings (evidence: static-indicator) - warning[min-wait-loop]: loop body waits at the workshop minimum rate; the loop runs at maximum frequency - --> flow.ws:11:9 - | 11 | While(Compare(Global.index, <, 3)); - | | ^ ---- stderr --- diff --git a/crates/wright-cli/tests/snapshots/terminal__inspect_refs_error_w100_never.snap b/crates/wright-cli/tests/snapshots/terminal__inspect_refs_error.snap similarity index 100% rename from crates/wright-cli/tests/snapshots/terminal__inspect_refs_error_w100_never.snap rename to crates/wright-cli/tests/snapshots/terminal__inspect_refs_error.snap diff --git a/crates/wright-cli/tests/snapshots/terminal__inspect_refs_error_w100_always.snap b/crates/wright-cli/tests/snapshots/terminal__inspect_refs_error_w100_always.snap deleted file mode 100644 index 6e6c0c87..00000000 --- a/crates/wright-cli/tests/snapshots/terminal__inspect_refs_error_w100_always.snap +++ /dev/null @@ -1,12 +0,0 @@ ---- -source: crates/wright-cli/tests/terminal.rs -assertion_line: 97 -expression: transcript(&out) ---- -exit: 1 ---- stdout --- -[41;37m ERROR [0m refs - [2m1 error[0m -[31merror[0m[unknown-symbol]: unknown symbol 'Missing.symbol'; no declared symbol is near; declared symbol names (1 total): 'index'; wright inspect symbols lists them - [2m= analysis[0m ---- stderr --- diff --git a/crates/wright-cli/tests/snapshots/terminal__inspect_refs_error_w80_always.snap b/crates/wright-cli/tests/snapshots/terminal__inspect_refs_error_w80_always.snap deleted file mode 100644 index 6e6c0c87..00000000 --- a/crates/wright-cli/tests/snapshots/terminal__inspect_refs_error_w80_always.snap +++ /dev/null @@ -1,12 +0,0 @@ ---- -source: crates/wright-cli/tests/terminal.rs -assertion_line: 97 -expression: transcript(&out) ---- -exit: 1 ---- stdout --- -[41;37m ERROR [0m refs - [2m1 error[0m -[31merror[0m[unknown-symbol]: unknown symbol 'Missing.symbol'; no declared symbol is near; declared symbol names (1 total): 'index'; wright inspect symbols lists them - [2m= analysis[0m ---- stderr --- diff --git a/crates/wright-cli/tests/snapshots/terminal__inspect_refs_error_w80_never.snap b/crates/wright-cli/tests/snapshots/terminal__inspect_refs_error_w80_never.snap deleted file mode 100644 index 95c469f9..00000000 --- a/crates/wright-cli/tests/snapshots/terminal__inspect_refs_error_w80_never.snap +++ /dev/null @@ -1,12 +0,0 @@ ---- -source: crates/wright-cli/tests/terminal.rs -assertion_line: 97 -expression: transcript(&out) ---- -exit: 1 ---- stdout --- - ERROR refs - 1 error -error[unknown-symbol]: unknown symbol 'Missing.symbol'; no declared symbol is near; declared symbol names (1 total): 'index'; wright inspect symbols lists them - = analysis ---- stderr --- diff --git a/crates/wright-cli/tests/snapshots/terminal__inspect_summary_w100_never.snap b/crates/wright-cli/tests/snapshots/terminal__inspect_summary.snap similarity index 100% rename from crates/wright-cli/tests/snapshots/terminal__inspect_summary_w100_never.snap rename to crates/wright-cli/tests/snapshots/terminal__inspect_summary.snap diff --git a/crates/wright-cli/tests/snapshots/terminal__inspect_summary_w100_always.snap b/crates/wright-cli/tests/snapshots/terminal__inspect_summary_w100_always.snap deleted file mode 100644 index dc63e2a9..00000000 --- a/crates/wright-cli/tests/snapshots/terminal__inspect_summary_w100_always.snap +++ /dev/null @@ -1,24 +0,0 @@ ---- -source: crates/wright-cli/tests/terminal.rs -expression: transcript(&out) ---- -exit: 0 ---- stdout --- -[46;30m INSPECT [0m - [2m2 rules, 3 symbols, 8 references[0m - -[1mProgram overview[0m - 1 file, 2 rules, 1 global variable, 0 player variables, 0 subroutines - -[1mRules[0m - bounded while - branch - [2minspect symbols, refs, cfg, callgraph, cost; --all for all rule names[0m - -[1mNext[0m - wright inspect symbols - wright inspect refs - wright inspect cfg - wright inspect callgraph - wright inspect cost ---- stderr --- diff --git a/crates/wright-cli/tests/snapshots/terminal__inspect_summary_w80_always.snap b/crates/wright-cli/tests/snapshots/terminal__inspect_summary_w80_always.snap deleted file mode 100644 index dc63e2a9..00000000 --- a/crates/wright-cli/tests/snapshots/terminal__inspect_summary_w80_always.snap +++ /dev/null @@ -1,24 +0,0 @@ ---- -source: crates/wright-cli/tests/terminal.rs -expression: transcript(&out) ---- -exit: 0 ---- stdout --- -[46;30m INSPECT [0m - [2m2 rules, 3 symbols, 8 references[0m - -[1mProgram overview[0m - 1 file, 2 rules, 1 global variable, 0 player variables, 0 subroutines - -[1mRules[0m - bounded while - branch - [2minspect symbols, refs, cfg, callgraph, cost; --all for all rule names[0m - -[1mNext[0m - wright inspect symbols - wright inspect refs - wright inspect cfg - wright inspect callgraph - wright inspect cost ---- stderr --- diff --git a/crates/wright-cli/tests/snapshots/terminal__inspect_summary_w80_never.snap b/crates/wright-cli/tests/snapshots/terminal__inspect_summary_w80_never.snap deleted file mode 100644 index 2c534234..00000000 --- a/crates/wright-cli/tests/snapshots/terminal__inspect_summary_w80_never.snap +++ /dev/null @@ -1,24 +0,0 @@ ---- -source: crates/wright-cli/tests/terminal.rs -expression: transcript(&out) ---- -exit: 0 ---- stdout --- - INSPECT - 2 rules, 3 symbols, 8 references - -Program overview - 1 file, 2 rules, 1 global variable, 0 player variables, 0 subroutines - -Rules - bounded while - branch - inspect symbols, refs, cfg, callgraph, cost; --all for all rule names - -Next - wright inspect symbols - wright inspect refs - wright inspect cfg - wright inspect callgraph - wright inspect cost ---- stderr --- diff --git a/crates/wright-cli/tests/snapshots/terminal__lint_gated_fail_w100_never.snap b/crates/wright-cli/tests/snapshots/terminal__lint_gated_fail.snap similarity index 100% rename from crates/wright-cli/tests/snapshots/terminal__lint_gated_fail_w100_never.snap rename to crates/wright-cli/tests/snapshots/terminal__lint_gated_fail.snap diff --git a/crates/wright-cli/tests/snapshots/terminal__lint_gated_fail_w100_always.snap b/crates/wright-cli/tests/snapshots/terminal__lint_gated_fail_w100_always.snap deleted file mode 100644 index 3732c897..00000000 --- a/crates/wright-cli/tests/snapshots/terminal__lint_gated_fail_w100_always.snap +++ /dev/null @@ -1,22 +0,0 @@ ---- -source: crates/wright-cli/tests/terminal.rs -expression: transcript(&out) ---- -exit: 1 ---- stdout --- -[41;37m FAIL [0m lint - [2m1 warning[0m - -[1mLint findings (evidence: static-indicator)[0m - [33mwarning[0m[min-wait-loop]: loop body waits at the workshop minimum rate; the loop runs at maximum frequency - --> flow.ws:11:9 - | 11 | While(Compare(Global.index, <, 3)); - | | ^ - [2m1 file affected[0m - [2mlint --all; lintRules for effective configuration and rule metadata[0m - -[1mNext[0m - wright lint --fix - wright analyze - wright inspect cost ---- stderr --- diff --git a/crates/wright-cli/tests/snapshots/terminal__lint_gated_fail_w80_always.snap b/crates/wright-cli/tests/snapshots/terminal__lint_gated_fail_w80_always.snap deleted file mode 100644 index ca2d8b33..00000000 --- a/crates/wright-cli/tests/snapshots/terminal__lint_gated_fail_w80_always.snap +++ /dev/null @@ -1,22 +0,0 @@ ---- -source: crates/wright-cli/tests/terminal.rs -expression: transcript(&out) ---- -exit: 1 ---- stdout --- -[41;37m FAIL [0m lint - [2m1 warning[0m - -[1mLint findings (evidence: static-indicator)[0m - [33mwarning[0m[min-wait-loop]: loop body waits at the workshop minimum rate; the loop runs at maximum frequency - --> flow.ws:11:9 - | 11 | While(Compare(Global.index, <, 3)); - | | ^ - [2m1 file affected[0m - [2mlint --all; lintRules for effective configuration and rule metadata[0m - -[1mNext[0m - wright lint --fix - wright analyze - wright inspect cost ---- stderr --- diff --git a/crates/wright-cli/tests/snapshots/terminal__lint_gated_fail_w80_never.snap b/crates/wright-cli/tests/snapshots/terminal__lint_gated_fail_w80_never.snap deleted file mode 100644 index 62aeaf51..00000000 --- a/crates/wright-cli/tests/snapshots/terminal__lint_gated_fail_w80_never.snap +++ /dev/null @@ -1,22 +0,0 @@ ---- -source: crates/wright-cli/tests/terminal.rs -expression: transcript(&out) ---- -exit: 1 ---- stdout --- - FAIL lint - 1 warning - -Lint findings (evidence: static-indicator) - warning[min-wait-loop]: loop body waits at the workshop minimum rate; the loop runs at maximum frequency - --> flow.ws:11:9 - | 11 | While(Compare(Global.index, <, 3)); - | | ^ - 1 file affected - lint --all; lintRules for effective configuration and rule metadata - -Next - wright lint --fix - wright analyze - wright inspect cost ---- stderr --- diff --git a/crates/wright-cli/tests/snapshots/terminal__lint_warn_w100_never.snap b/crates/wright-cli/tests/snapshots/terminal__lint_warn.snap similarity index 100% rename from crates/wright-cli/tests/snapshots/terminal__lint_warn_w100_never.snap rename to crates/wright-cli/tests/snapshots/terminal__lint_warn.snap diff --git a/crates/wright-cli/tests/snapshots/terminal__lint_warn_w100_always.snap b/crates/wright-cli/tests/snapshots/terminal__lint_warn_w100_always.snap deleted file mode 100644 index f9f1792e..00000000 --- a/crates/wright-cli/tests/snapshots/terminal__lint_warn_w100_always.snap +++ /dev/null @@ -1,22 +0,0 @@ ---- -source: crates/wright-cli/tests/terminal.rs -expression: transcript(&out) ---- -exit: 0 ---- stdout --- -[43;30m WARN [0m lint - [2m1 warning[0m - -[1mLint findings (evidence: static-indicator)[0m - [33mwarning[0m[min-wait-loop]: loop body waits at the workshop minimum rate; the loop runs at maximum frequency - --> flow.ws:11:9 - | 11 | While(Compare(Global.index, <, 3)); - | | ^ - [2m1 file affected[0m - [2mlint --all; lintRules for effective configuration and rule metadata[0m - -[1mNext[0m - wright lint --fix - wright analyze - wright inspect cost ---- stderr --- diff --git a/crates/wright-cli/tests/snapshots/terminal__lint_warn_w80_always.snap b/crates/wright-cli/tests/snapshots/terminal__lint_warn_w80_always.snap deleted file mode 100644 index 72e21e41..00000000 --- a/crates/wright-cli/tests/snapshots/terminal__lint_warn_w80_always.snap +++ /dev/null @@ -1,22 +0,0 @@ ---- -source: crates/wright-cli/tests/terminal.rs -expression: transcript(&out) ---- -exit: 0 ---- stdout --- -[43;30m WARN [0m lint - [2m1 warning[0m - -[1mLint findings (evidence: static-indicator)[0m - [33mwarning[0m[min-wait-loop]: loop body waits at the workshop minimum rate; the loop runs at maximum frequency - --> flow.ws:11:9 - | 11 | While(Compare(Global.index, <, 3)); - | | ^ - [2m1 file affected[0m - [2mlint --all; lintRules for effective configuration and rule metadata[0m - -[1mNext[0m - wright lint --fix - wright analyze - wright inspect cost ---- stderr --- diff --git a/crates/wright-cli/tests/snapshots/terminal__lint_warn_w80_never.snap b/crates/wright-cli/tests/snapshots/terminal__lint_warn_w80_never.snap deleted file mode 100644 index a5fd27d2..00000000 --- a/crates/wright-cli/tests/snapshots/terminal__lint_warn_w80_never.snap +++ /dev/null @@ -1,22 +0,0 @@ ---- -source: crates/wright-cli/tests/terminal.rs -expression: transcript(&out) ---- -exit: 0 ---- stdout --- - WARN lint - 1 warning - -Lint findings (evidence: static-indicator) - warning[min-wait-loop]: loop body waits at the workshop minimum rate; the loop runs at maximum frequency - --> flow.ws:11:9 - | 11 | While(Compare(Global.index, <, 3)); - | | ^ - 1 file affected - lint --all; lintRules for effective configuration and rule metadata - -Next - wright lint --fix - wright analyze - wright inspect cost ---- stderr --- diff --git a/crates/wright-cli/tests/snapshots/terminal__lookup_w100_never.snap b/crates/wright-cli/tests/snapshots/terminal__lookup.snap similarity index 100% rename from crates/wright-cli/tests/snapshots/terminal__lookup_w100_never.snap rename to crates/wright-cli/tests/snapshots/terminal__lookup.snap diff --git a/crates/wright-cli/tests/snapshots/terminal__lookup_w100_always.snap b/crates/wright-cli/tests/snapshots/terminal__lookup_w100_always.snap deleted file mode 100644 index b09250dc..00000000 --- a/crates/wright-cli/tests/snapshots/terminal__lookup_w100_always.snap +++ /dev/null @@ -1,11 +0,0 @@ ---- -source: crates/wright-cli/tests/terminal.rs -expression: transcript(&out) ---- -exit: 0 ---- stdout --- -[46;30m LOOKUP [0m - 2 entries - Stop All Damage Over Time(player: Player|Array) [action] stopAllDamageOverTime - Stop All Damage Modifications() [action] stopAllDamageModifications ---- stderr --- diff --git a/crates/wright-cli/tests/snapshots/terminal__lookup_w80_always.snap b/crates/wright-cli/tests/snapshots/terminal__lookup_w80_always.snap deleted file mode 100644 index b09250dc..00000000 --- a/crates/wright-cli/tests/snapshots/terminal__lookup_w80_always.snap +++ /dev/null @@ -1,11 +0,0 @@ ---- -source: crates/wright-cli/tests/terminal.rs -expression: transcript(&out) ---- -exit: 0 ---- stdout --- -[46;30m LOOKUP [0m - 2 entries - Stop All Damage Over Time(player: Player|Array) [action] stopAllDamageOverTime - Stop All Damage Modifications() [action] stopAllDamageModifications ---- stderr --- diff --git a/crates/wright-cli/tests/snapshots/terminal__lookup_w80_never.snap b/crates/wright-cli/tests/snapshots/terminal__lookup_w80_never.snap deleted file mode 100644 index 513c5ea7..00000000 --- a/crates/wright-cli/tests/snapshots/terminal__lookup_w80_never.snap +++ /dev/null @@ -1,11 +0,0 @@ ---- -source: crates/wright-cli/tests/terminal.rs -expression: transcript(&out) ---- -exit: 0 ---- stdout --- - LOOKUP - 2 entries - Stop All Damage Over Time(player: Player|Array) [action] stopAllDamageOverTime - Stop All Damage Modifications() [action] stopAllDamageModifications ---- stderr --- diff --git a/crates/wright-cli/tests/snapshots/terminal__update_pass_w100_never.snap b/crates/wright-cli/tests/snapshots/terminal__update_pass.snap similarity index 100% rename from crates/wright-cli/tests/snapshots/terminal__update_pass_w100_never.snap rename to crates/wright-cli/tests/snapshots/terminal__update_pass.snap diff --git a/crates/wright-cli/tests/snapshots/terminal__update_pass_w100_always.snap b/crates/wright-cli/tests/snapshots/terminal__update_pass_w100_always.snap deleted file mode 100644 index 19c61637..00000000 --- a/crates/wright-cli/tests/snapshots/terminal__update_pass_w100_always.snap +++ /dev/null @@ -1,10 +0,0 @@ ---- -source: crates/wright-cli/tests/terminal.rs -expression: transcript(&out) ---- -exit: 0 ---- stdout --- -skipped wright self-update: unsupported platform plan9/x86_64 (supported: linux/x86_64, darwin/x86_64, darwin/aarch64, windows/x86_64); use the official installer (curl -fsSL https://install.wrightkit.dev/wright/install.sh | bash) or a package manager -opy provider is not installed; skipping -[42;30m PASS [0m update ---- stderr --- diff --git a/crates/wright-cli/tests/snapshots/terminal__update_pass_w80_always.snap b/crates/wright-cli/tests/snapshots/terminal__update_pass_w80_always.snap deleted file mode 100644 index 19c61637..00000000 --- a/crates/wright-cli/tests/snapshots/terminal__update_pass_w80_always.snap +++ /dev/null @@ -1,10 +0,0 @@ ---- -source: crates/wright-cli/tests/terminal.rs -expression: transcript(&out) ---- -exit: 0 ---- stdout --- -skipped wright self-update: unsupported platform plan9/x86_64 (supported: linux/x86_64, darwin/x86_64, darwin/aarch64, windows/x86_64); use the official installer (curl -fsSL https://install.wrightkit.dev/wright/install.sh | bash) or a package manager -opy provider is not installed; skipping -[42;30m PASS [0m update ---- stderr --- diff --git a/crates/wright-cli/tests/snapshots/terminal__update_pass_w80_never.snap b/crates/wright-cli/tests/snapshots/terminal__update_pass_w80_never.snap deleted file mode 100644 index a6127bdc..00000000 --- a/crates/wright-cli/tests/snapshots/terminal__update_pass_w80_never.snap +++ /dev/null @@ -1,10 +0,0 @@ ---- -source: crates/wright-cli/tests/terminal.rs -expression: transcript(&out) ---- -exit: 0 ---- stdout --- -skipped wright self-update: unsupported platform plan9/x86_64 (supported: linux/x86_64, darwin/x86_64, darwin/aarch64, windows/x86_64); use the official installer (curl -fsSL https://install.wrightkit.dev/wright/install.sh | bash) or a package manager -opy provider is not installed; skipping - PASS update ---- stderr --- diff --git a/crates/wright-cli/tests/snapshots/terminal__update_unsupported_w100_never.snap b/crates/wright-cli/tests/snapshots/terminal__update_unsupported.snap similarity index 100% rename from crates/wright-cli/tests/snapshots/terminal__update_unsupported_w100_never.snap rename to crates/wright-cli/tests/snapshots/terminal__update_unsupported.snap diff --git a/crates/wright-cli/tests/snapshots/terminal__update_unsupported_w100_always.snap b/crates/wright-cli/tests/snapshots/terminal__update_unsupported_w100_always.snap deleted file mode 100644 index fdaaa72f..00000000 --- a/crates/wright-cli/tests/snapshots/terminal__update_unsupported_w100_always.snap +++ /dev/null @@ -1,9 +0,0 @@ ---- -source: crates/wright-cli/tests/terminal.rs -expression: transcript(&out) ---- -exit: 3 ---- stdout --- ---- stderr --- -[46;30m UNSUPPORTED [0m update - unsupported platform plan9/x86_64 (supported: linux/x86_64, darwin/x86_64, darwin/aarch64, windows/x86_64); use the official installer (curl -fsSL https://install.wrightkit.dev/wright/install.sh | bash) or a package manager diff --git a/crates/wright-cli/tests/snapshots/terminal__update_unsupported_w80_always.snap b/crates/wright-cli/tests/snapshots/terminal__update_unsupported_w80_always.snap deleted file mode 100644 index fdaaa72f..00000000 --- a/crates/wright-cli/tests/snapshots/terminal__update_unsupported_w80_always.snap +++ /dev/null @@ -1,9 +0,0 @@ ---- -source: crates/wright-cli/tests/terminal.rs -expression: transcript(&out) ---- -exit: 3 ---- stdout --- ---- stderr --- -[46;30m UNSUPPORTED [0m update - unsupported platform plan9/x86_64 (supported: linux/x86_64, darwin/x86_64, darwin/aarch64, windows/x86_64); use the official installer (curl -fsSL https://install.wrightkit.dev/wright/install.sh | bash) or a package manager diff --git a/crates/wright-cli/tests/snapshots/terminal__update_unsupported_w80_never.snap b/crates/wright-cli/tests/snapshots/terminal__update_unsupported_w80_never.snap deleted file mode 100644 index 9b6c8c0f..00000000 --- a/crates/wright-cli/tests/snapshots/terminal__update_unsupported_w80_never.snap +++ /dev/null @@ -1,9 +0,0 @@ ---- -source: crates/wright-cli/tests/terminal.rs -expression: transcript(&out) ---- -exit: 3 ---- stdout --- ---- stderr --- - UNSUPPORTED update - unsupported platform plan9/x86_64 (supported: linux/x86_64, darwin/x86_64, darwin/aarch64, windows/x86_64); use the official installer (curl -fsSL https://install.wrightkit.dev/wright/install.sh | bash) or a package manager diff --git a/crates/wright-cli/tests/snapshots/terminal__usage_w100_never.snap b/crates/wright-cli/tests/snapshots/terminal__usage.snap similarity index 100% rename from crates/wright-cli/tests/snapshots/terminal__usage_w100_never.snap rename to crates/wright-cli/tests/snapshots/terminal__usage.snap diff --git a/crates/wright-cli/tests/snapshots/terminal__usage_w100_always.snap b/crates/wright-cli/tests/snapshots/terminal__usage_w100_always.snap deleted file mode 100644 index 8133e7e9..00000000 --- a/crates/wright-cli/tests/snapshots/terminal__usage_w100_always.snap +++ /dev/null @@ -1,9 +0,0 @@ ---- -source: crates/wright-cli/tests/terminal.rs -expression: transcript(&out) ---- -exit: 2 ---- stdout --- ---- stderr --- -[41;37m USAGE [0m lint - --rule-severity expects : (got 'bogus') diff --git a/crates/wright-cli/tests/snapshots/terminal__usage_w80_always.snap b/crates/wright-cli/tests/snapshots/terminal__usage_w80_always.snap deleted file mode 100644 index 8133e7e9..00000000 --- a/crates/wright-cli/tests/snapshots/terminal__usage_w80_always.snap +++ /dev/null @@ -1,9 +0,0 @@ ---- -source: crates/wright-cli/tests/terminal.rs -expression: transcript(&out) ---- -exit: 2 ---- stdout --- ---- stderr --- -[41;37m USAGE [0m lint - --rule-severity expects : (got 'bogus') diff --git a/crates/wright-cli/tests/snapshots/terminal__usage_w80_never.snap b/crates/wright-cli/tests/snapshots/terminal__usage_w80_never.snap deleted file mode 100644 index 846307e6..00000000 --- a/crates/wright-cli/tests/snapshots/terminal__usage_w80_never.snap +++ /dev/null @@ -1,9 +0,0 @@ ---- -source: crates/wright-cli/tests/terminal.rs -expression: transcript(&out) ---- -exit: 2 ---- stdout --- ---- stderr --- - USAGE lint - --rule-severity expects : (got 'bogus') diff --git a/crates/wright-cli/tests/terminal.rs b/crates/wright-cli/tests/terminal.rs index 1cc85ac5..ab1be862 100644 --- a/crates/wright-cli/tests/terminal.rs +++ b/crates/wright-cli/tests/terminal.rs @@ -68,19 +68,12 @@ fn transcript(out: &Outcome) -> String { } fn snap(name: &str, dir: &Path, args: &[&str], env: &[(&str, String)]) { - for (columns, color) in [ - (100u16, "never"), - (80, "never"), - (100, "always"), - (80, "always"), - ] { - let mut full_args: Vec<&str> = args.to_vec(); - full_args.extend(["--renderer", "terminal", "--color", color]); - let mut env = env.to_vec(); - env.push(("COLUMNS", columns.to_string())); - let out = run(dir, &full_args, &env); - insta::assert_snapshot!(format!("{name}_w{columns}_{color}"), transcript(&out)); - } + let mut full_args: Vec<&str> = args.to_vec(); + full_args.extend(["--renderer", "terminal", "--color", "never"]); + let mut env = env.to_vec(); + env.push(("COLUMNS", "100".to_string())); + let out = run(dir, &full_args, &env); + insta::assert_snapshot!(name, transcript(&out)); } #[test] @@ -342,34 +335,6 @@ fn no_color_and_color_flag_both_apply() { let _ = std::fs::remove_dir_all(&dir); } -#[test] -fn status_survives_without_colour() { - let dir = work_dir( - "no-color", - &[("flow.ws", &workshop("synthetic/control-flow"))], - ); - let args = [ - "lint", - "flow.ws", - "--renderer", - "terminal", - "--color", - "never", - ]; - let out = run(&dir, &args, &[("COLUMNS", "100".to_string())]); - assert!( - out.stdout.starts_with(" WARN lint\n"), - "the word carries the status: {}", - out.stdout - ); - assert!( - !out.stdout.contains(""), - "no escape reaches the no-colour report: {}", - out.stdout - ); - let _ = std::fs::remove_dir_all(&dir); -} - #[test] fn verdict_exit_and_counts_match_plain_and_json() { let dir = work_dir( diff --git a/crates/wright-cli/tests/update/main.rs b/crates/wright-cli/tests/update/main.rs index 6984e8f4..5e871d7d 100644 --- a/crates/wright-cli/tests/update/main.rs +++ b/crates/wright-cli/tests/update/main.rs @@ -444,32 +444,6 @@ fn standalone_install_in_a_scooped_directory_still_self_updates() { let _ = std::fs::remove_dir_all(&dir); } -#[test] -fn unknown_update_flag_is_a_usage_error() { - let dir = install_dir("update-usage"); - let output = run_update(&dir, &["update", "--frobnicate"], &[]); - assert_eq!(output.status.code(), Some(2)); - assert!(output.stdout.is_empty(), "usage errors write stderr only"); - assert!(!output.stderr.is_empty()); - let _ = std::fs::remove_dir_all(&dir); -} - -#[test] -fn help_documents_the_update_command() { - let output = Command::new(env!("CARGO_BIN_EXE_wright")) - .arg("--help") - .stdin(Stdio::null()) - .output() - .unwrap(); - let help = String::from_utf8_lossy(&output.stdout); - assert!(help.contains("update"), "help documents update: {help}"); - assert!(help.contains("--check"), "help documents --check"); - assert!( - help.contains("--version "), - "help documents --version" - ); -} - #[test] fn terminal_update_closes_with_the_verdict_tag() { let dir = install_dir("terminal-close"); @@ -534,31 +508,6 @@ fn terminal_update_reports_failures_under_the_tag() { let _ = std::fs::remove_dir_all(&dir); } -#[test] -fn terminal_update_reports_unsupported_platforms() { - let dir = install_dir("terminal-unsupported"); - let output = run_update( - &dir, - &[ - "update", - "self", - "--renderer", - "terminal", - "--color", - "never", - ], - &[("WRIGHT_INSTALL_OS", "freebsd".to_string())], - ); - assert_eq!(output.status.code(), Some(3)); - let stderr = String::from_utf8_lossy(&output.stderr); - assert!( - stderr.starts_with(" UNSUPPORTED update\n"), - "the unsupported tag leads: {stderr}" - ); - assert!(stderr.contains("unsupported platform freebsd"), "{stderr}"); - let _ = std::fs::remove_dir_all(&dir); -} - #[test] fn windows_zip_release_replaces_both_exe_binaries() { const WINDOWS_TRIPLE: &str = "x86_64-pc-windows-msvc"; diff --git a/crates/wright-consumer/tests/consumer.rs b/crates/wright-consumer/tests/consumer.rs index 32404c13..05679af2 100644 --- a/crates/wright-consumer/tests/consumer.rs +++ b/crates/wright-consumer/tests/consumer.rs @@ -23,9 +23,3 @@ fn consumer_runs_all_public_api_workflows_on_representative_inputs() { .unwrap_or_else(|message| panic!("{id}: {message}")); } } - -#[test] -fn consumer_accepts_workshop_inputs() { - let path = workshop_fixture("synthetic/control-flow"); - wright_consumer::run_consumer(path.to_str().unwrap()).expect("workshop consumer works"); -} diff --git a/crates/wright-driver/src/diag.rs b/crates/wright-driver/src/diag.rs index 949c9309..ab0c6c5d 100644 --- a/crates/wright-driver/src/diag.rs +++ b/crates/wright-driver/src/diag.rs @@ -289,40 +289,6 @@ mod tests { use crate::input::{InputTarget, ResolvedInput}; use std::path::PathBuf; - #[test] - fn error_accepts_a_kebab_case_code() { - let diagnostic = Diagnostic::error("unknown-enum-member", Stage::Frontend, "message"); - assert_eq!(diagnostic.code, "unknown-enum-member"); - } - - #[test] - #[should_panic(expected = "violates the code grammar")] - fn error_rejects_a_spaced_code() { - let _ = Diagnostic::error("unknown-enum member", Stage::Frontend, "message"); - } - - #[test] - #[should_panic(expected = "violates the code grammar")] - fn error_rejects_an_uppercase_code() { - let _ = Diagnostic::error("Unknown-Value", Stage::Frontend, "message"); - } - - #[test] - fn carried_error_keeps_foreign_vocabulary() { - let diagnostic = Diagnostic::carried_error( - "provider-refusal-compile.tooLarge", - Stage::Frontend, - "message", - ); - assert_eq!(diagnostic.code, "provider-refusal-compile.tooLarge"); - } - - #[test] - #[should_panic(expected = "empty or contains whitespace")] - fn carried_error_rejects_whitespace() { - let _ = Diagnostic::carried_error("bad code", Stage::Frontend, "message"); - } - #[test] fn workshop_diag_codes_match_the_grammar_for_every_owner_kind() { const OWNER_KINDS: &[&str] = &[ diff --git a/crates/wright-driver/src/service/refusal.rs b/crates/wright-driver/src/service/refusal.rs index 8cae86bc..c5b2e8c1 100644 --- a/crates/wright-driver/src/service/refusal.rs +++ b/crates/wright-driver/src/service/refusal.rs @@ -259,35 +259,3 @@ fn compose_payload( _ => {} } } - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - #[should_panic(expected = "violates the code grammar")] - fn refusal_rejects_a_spaced_code() { - let _ = refusal("invalid language", "message"); - } - - #[test] - #[should_panic(expected = "violates the code grammar")] - fn refusal_rejects_an_uppercase_code() { - let _ = refusal("Invalid-Language", "message"); - } - - #[test] - fn carried_refusal_keeps_the_lpp_refusal_code() { - let response = carried_refusal(crate::lookup::UNKNOWN_WITHIN, "message"); - let ToolResponse::Error { error } = response else { - panic!("a refusal is always an error response"); - }; - assert_eq!(error.code, "lookup.unknownWithin"); - } - - #[test] - #[should_panic(expected = "empty or contains whitespace")] - fn carried_refusal_rejects_whitespace() { - let _ = carried_refusal("bad code", "message"); - } -} diff --git a/crates/wright-driver/tests/driver.rs b/crates/wright-driver/tests/driver.rs index aad000ea..306057c6 100644 --- a/crates/wright-driver/tests/driver.rs +++ b/crates/wright-driver/tests/driver.rs @@ -259,38 +259,6 @@ fn workshop_check_and_compile_reject_violations_beside_residuals() { let _ = std::fs::remove_dir_all(&directory); } -#[test] -fn workshop_unknown_kind_codes_are_kebab_case_tokens() { - let mut session = CompilerSession::new(SessionConfig { - input: InputSpec::Text { - text: "rule (\"r\")\n{\n event\n {\n Ongoing - Global;\n }\n actions\n {\n Set Global Variable(A, Map(Not A Real Map));\n }\n}\n".to_string(), - path: None, - }, - kind: SourceKind::Workshop, - ..SessionConfig::default() - }) - .expect("session creates"); - let check = session.check(); - let compile = session.compile(); - for (name, diagnostics) in [ - ("check", check.diagnostics), - ("compile", compile.diagnostics), - ] { - let diagnostic = diagnostics - .iter() - .find(|d| d.code == "unknown-enum-member") - .unwrap_or_else(|| panic!("{name}: the code is unknown-enum-member: {diagnostics:?}")); - assert_eq!(diagnostic.severity, wright_driver::Severity::Error); - assert_eq!(diagnostic.stage, wright_driver::Stage::Frontend); - assert!( - diagnostics - .iter() - .all(|d| !d.code.contains(char::is_whitespace)), - "{name}: every code is a single token: {diagnostics:?}" - ); - } -} - #[test] fn workshop_check_accepts_optional_action_defaults() { let directory = workspace_root() @@ -387,88 +355,6 @@ fn workshop_compile_is_deterministic_and_idempotent() { ); } -#[test] -fn compile_warns_over_client_element_limit_but_still_emits() { - let elements = (0..17_000).map(|_| "1").collect::>().join(", "); - let source = format!( - "variables {{\n global:\n 0: values\n}}\n\nrule (\"fill\") {{\n event {{\n Ongoing - Global;\n }}\n actions {{\n Set Global Variable(values, Array({elements}));\n }}\n}}\n" - ); - let catalog = workshop_rs::catalog::Catalog::builtin_ref().expect("catalog"); - let locale = workshop_rs::catalog::Locale::new("en-US"); - let expected_total = - workshop_rs::parser::parse_with_context(&source, catalog, &locale, catalog) - .expect("generated program parses") - .element_count(catalog) - .expect("generated program counts") - .total; - assert!(expected_total > 32768, "fixture is over the client limit"); - - let directory = workspace_root() - .join("target") - .join(format!("wright-driver-{}", std::process::id())); - std::fs::create_dir_all(&directory).expect("fixture directory creates"); - let path = directory.join("over-limit.ws"); - std::fs::write(&path, source).expect("over-limit fixture writes"); - - let mut session = CompilerSession::new(SessionConfig { - input: InputSpec::Path(path.clone()), - kind: SourceKind::Workshop, - ..SessionConfig::default() - }) - .expect("session creates"); - - let result = session.compile(); - assert!( - result.ok, - "over-limit compile still succeeds: {:?}", - result.diagnostics - ); - assert_eq!(result.exit, 0); - let output = result - .result - .output - .as_ref() - .expect("the artifact is still emitted"); - assert!(output.text.contains("Set Global Variable")); - let warning = result - .diagnostics - .iter() - .find(|d| d.code == "target-element-limit") - .expect("the element-limit warning is reported"); - assert_eq!(warning.severity, wright_driver::Severity::Warning); - assert_eq!(warning.stage, wright_driver::Stage::Emission); - assert!( - warning.message.contains(&expected_total.to_string()), - "the warning reports the canonical observed count: {warning:?}" - ); - assert!(warning.message.contains("32768"), "{warning:?}"); - assert!( - warning.message.contains("still emitted"), - "the warning must not read as a compilation failure: {warning:?}" - ); - assert!( - warning.message.contains("\"fill\""), - "the warning names the largest contributing rule: {warning:?}" - ); - let span = warning - .span - .as_ref() - .expect("the largest contributing rule is located"); - assert!(span.path.ends_with("over-limit.ws"), "{span:?}"); - - let check = session.check(); - assert!( - check - .diagnostics - .iter() - .all(|d| d.code != "target-element-limit"), - "check does not evaluate client import limits: {:?}", - check.diagnostics - ); - - let _ = std::fs::remove_dir_all(&directory); -} - #[test] fn legacy_protocol_input_is_refused_without_hir_lowering() { let path = workspace_root() @@ -761,29 +647,3 @@ fn compile_to_asks_before_replacing_and_never_replaces_the_input() { assert_eq!(std::fs::read_to_string(&input).unwrap(), source); let _ = std::fs::remove_dir_all(&dir); } - -#[test] -fn failed_compile_writes_no_file() { - use wright_driver::{CompileWrite, OutputDest, Overwrite}; - - let dir = PathBuf::from(env!("CARGO_TARGET_TMPDIR")).join("failed-compile"); - std::fs::create_dir_all(&dir).unwrap(); - let input = dir.join("broken.ws"); - std::fs::write(&input, "rule (\"x\") { event { Ongoing - Global; }").unwrap(); - let explicit = dir.join("explicit.ow"); - let envelope = CompilerSession::new(SessionConfig { - input: InputSpec::Path(input), - kind: SourceKind::Workshop, - ..SessionConfig::default() - }) - .unwrap() - .compile_to(CompileWrite { - dest: OutputDest::Path(explicit.clone()), - overwrite: Overwrite::Force, - to: None, - }); - assert!(!envelope.ok); - assert!(envelope.result.output.is_none()); - assert!(!explicit.exists()); - let _ = std::fs::remove_dir_all(&dir); -} diff --git a/crates/wright-driver/tests/service.rs b/crates/wright-driver/tests/service.rs index b3bd97a9..f49f2886 100644 --- a/crates/wright-driver/tests/service.rs +++ b/crates/wright-driver/tests/service.rs @@ -51,25 +51,6 @@ fn tool_service_queries_canonical_workshop() { } } -#[test] -fn tool_service_inspect_matches_session_inspect() { - let mut session = CompilerSession::new(SessionConfig { - input: InputSpec::Path(workshop_path()), - kind: SourceKind::Workshop, - ..SessionConfig::default() - }) - .unwrap(); - let expected = session.inspect(); - let mut service = ToolService::new(&mut session).unwrap(); - let actual = service.inspect(); - - assert_eq!(actual.ok, expected.ok); - assert_eq!( - serde_json::to_value(actual.result).unwrap(), - serde_json::to_value(expected.result).unwrap() - ); -} - #[test] fn tool_service_lint_queries_keep_the_session_configuration() { let mut lint = wright_driver::config::LintConfig::default(); @@ -666,54 +647,6 @@ fn cost_estimate_reports_the_canonical_element_count() { ); } -#[test] -fn cost_estimate_reports_unavailable_count_with_budget_and_reason() { - let path = temp_workshop( - "opaque.ws", - "variables {\n global:\n 0: x\n}\nrule (\"test\") {\n event {\n Ongoing - Global;\n }\n actions {\n Wait(1);\n End;\n }\n}\n", - ); - let mut session = CompilerSession::new(SessionConfig { - input: InputSpec::Path(path.clone()), - kind: SourceKind::Workshop, - ..SessionConfig::default() - }) - .unwrap(); - let mut service = ToolService::new(&mut session).unwrap(); - let cost = result_of( - &mut service, - &ToolRequest::CostEstimate { - selection: FindingSelection::default(), - rule: None, - all: true, - }, - ); - assert_eq!(cost["elementCount"]["budget"], 32768); - assert!( - cost["elementCount"].get("total").is_none(), - "the count is absent, not zero: {cost}" - ); - assert!( - cost["elementCount"].get("rules").is_none(), - "no fabricated shares list: {cost}" - ); - assert!( - cost["elementCountUnavailable"] - .as_str() - .unwrap() - .contains("rawWorkshopAction"), - "{cost}" - ); - assert!(cost["exact"]["emittedBytes"].is_null()); - assert!( - cost["exact"]["emittedBytesUnavailable"] - .as_str() - .unwrap() - .contains("rawWorkshopAction"), - "{cost}" - ); - let _ = std::fs::remove_dir_all(path.parent().unwrap()); -} - #[test] fn session_cost_keeps_the_selection_outcome_for_the_verdict() { let mut session = CompilerSession::new(SessionConfig { @@ -3795,26 +3728,6 @@ fn session_lint_keeps_the_effective_configuration_over_the_shared_index() { assert_eq!(session.semantic_build_count(), 1); } -#[test] -fn tool_service_analyze_matches_session_analyze() { - let mut session = CompilerSession::new(SessionConfig { - input: InputSpec::Path(workshop_path()), - kind: SourceKind::Workshop, - ..SessionConfig::default() - }) - .unwrap(); - let expected = session.analyze(); - let mut service = ToolService::new(&mut session).unwrap(); - let actual = service.analyze(); - - assert_eq!(actual.ok, expected.ok); - assert_eq!( - serde_json::to_value(actual.result).unwrap(), - serde_json::to_value(expected.result).unwrap() - ); - assert_eq!(service.semantic_build_count(), 1); -} - #[test] fn a_reload_rebuilds_the_semantic_service_once() { let dir = freshness_dir("semantic"); diff --git a/crates/wright-language/src/service.rs b/crates/wright-language/src/service.rs index 134a0d53..0c75b7fd 100644 --- a/crates/wright-language/src/service.rs +++ b/crates/wright-language/src/service.rs @@ -713,10 +713,6 @@ mod tests { use super::*; use crate::document::Document; - fn service() -> LanguageService { - LanguageService::new(PathBuf::from("/project")) - } - fn open(service: &mut LanguageService, name: &str, text: &str) -> String { let uri = format!("file:///project/{name}"); service.store.open(Document::with_version( @@ -735,52 +731,6 @@ mod tests { } } - #[test] - fn rename_on_an_unopen_document_refuses_without_a_provider() { - let mut service = service(); - let (code, message) = refused(service.rename( - "file:///project/main.opy", - Position { - line: 0, - character: 0, - }, - "renamed", - )); - assert_eq!(code, "rename-unknown-document"); - assert!(message.contains("not open"), "{message}"); - } - - #[test] - fn rename_on_a_non_source_document_refuses_without_a_provider() { - let mut service = service(); - let uri = open(&mut service, "notes.txt", "hello\n"); - let (code, _) = refused(service.rename( - &uri, - Position { - line: 0, - character: 0, - }, - "renamed", - )); - assert_eq!(code, "rename-unsupported-document"); - } - - #[test] - fn rename_on_a_source_document_without_a_provider_refuses() { - let mut service = service(); - let uri = open(&mut service, "main.del", "x\n"); - let (code, message) = refused(service.rename( - &uri, - Position { - line: 0, - character: 0, - }, - "renamed", - )); - assert_eq!(code, "provider-not-configured"); - assert!(message.contains("del"), "{message}"); - } - #[test] fn rename_on_opy_with_an_unresolvable_provider_refuses() { let config = SessionConfig { From f72979bcabbd5642f5277ce474604e6f871d281f Mon Sep 17 00:00:00 2001 From: Teakowa <27560638+Teakowa@users.noreply.github.com> Date: Wed, 28 Oct 2026 10:45:44 +0800 Subject: [PATCH 2/3] test: drop driver, analyzer, and LSP tests duplicated at another layer Second pass over the driver, analyzer, and LSP suites. Remove service tests already covered by agent/CLI surface parity, smoke tests that only assert Ok, unit tests repeated by an integration test of the same claim, and CLI copies of driver lint-package and lookup behavior. Fold seven pass-through provider-failure tests into one table-driven test. --- .../tests/workshop_integration.rs | 48 -- crates/wright-cli/tests/address_grammar.rs | 27 -- crates/wright-cli/tests/cli.rs | 103 ----- crates/wright-cli/tests/error_wording.rs | 23 - crates/wright-cli/tests/lint_package.rs | 194 -------- .../tests/lint_package_lifecycle.rs | 72 --- crates/wright-driver/src/cache.rs | 25 - crates/wright-driver/src/edit.rs | 84 ---- crates/wright-driver/src/input.rs | 22 - crates/wright-driver/tests/service.rs | 426 ------------------ crates/wright-driver/tests/source_provider.rs | 308 +++---------- crates/wright-lsp/src/main.rs | 39 -- 12 files changed, 58 insertions(+), 1313 deletions(-) diff --git a/crates/wright-analyzer/tests/workshop_integration.rs b/crates/wright-analyzer/tests/workshop_integration.rs index 81b22b19..d2ecbac9 100644 --- a/crates/wright-analyzer/tests/workshop_integration.rs +++ b/crates/wright-analyzer/tests/workshop_integration.rs @@ -101,34 +101,6 @@ fn span_text(source: &str, span: &Value) -> String { .collect() } -#[test] -fn workshop_input_runs_all_semantic_queries() { - let service = workshop_service("synthetic/control-flow"); - let rule = id_for( - &service, - serde_json::json!({"op": "listRules"}), - "bounded while", - ); - let symbol = id_for(&service, serde_json::json!({"op": "listSymbols"}), "index"); - let requests = [ - serde_json::json!({"op": "program"}), - serde_json::json!({"op": "listRules"}), - serde_json::json!({"op": "getRule", "rule": rule}), - serde_json::json!({"op": "findReferences", "symbol": symbol}), - serde_json::json!({"op": "getUsage", "symbol": symbol}), - serde_json::json!({"op": "getCfg", "rule": rule}), - serde_json::json!({"op": "getFindings"}), - ]; - for request in requests { - query(&service, request); - } - - let program_response: Value = - serde_json::from_str(&service.handle_json(r#"{"op":"program"}"#)).unwrap(); - assert_eq!(program_response["result"]["origin"]["kind"], "workshop"); - assert_eq!(program_response["result"]["origin"]["locale"], "en-us"); -} - #[test] fn player_event_queries_keep_catalog_event_ids() { let mut program = Program::new(); @@ -145,26 +117,6 @@ fn player_event_queries_keep_catalog_event_ids() { assert_eq!(rule["event"], "playerDealtDamage"); } -#[test] -fn workshop_input_analysis_findings_are_available() { - let service = workshop_service("synthetic/control-flow"); - let response: Value = - serde_json::from_str(&service.handle_json(r#"{"op":"getFindings"}"#)).unwrap(); - let findings = response["result"].as_array().unwrap(); - assert!( - findings - .iter() - .any(|finding| finding["code"] == "min-wait-loop"), - "the Workshop-origin bounded-while is a hot loop" - ); - for finding in findings { - assert!( - finding["span"].is_object(), - "findings must carry Workshop source spans: {finding}" - ); - } -} - #[test] fn workshop_analysis_preserves_lint_semantics() { let source = r#" diff --git a/crates/wright-cli/tests/address_grammar.rs b/crates/wright-cli/tests/address_grammar.rs index 32ce2c13..c740da13 100644 --- a/crates/wright-cli/tests/address_grammar.rs +++ b/crates/wright-cli/tests/address_grammar.rs @@ -480,33 +480,6 @@ fn unmatched_files_refuse_on_cli_and_serve() { let _ = std::fs::remove_dir_all(Path::new(clean).parent().unwrap()); } -#[test] -fn name_addresses_ignore_snapshot() { - let path = temp_file("snapshot.ws", DUP_CALLERS); - - let responses = serve( - &path, - &[ - json!({"op":"cfg","rule":"tick#1","snapshot":999}).to_string(), - json!({"op":"cfg","rule":0,"snapshot":999}).to_string(), - json!({"op":"costEstimate","rule":"tick#1","snapshot":999}).to_string(), - ], - ); - assert!( - responses[0].get("error").is_none() && responses[0]["result"].is_object(), - "a name address ignores snapshot: {}", - responses[0] - ); - assert_eq!(responses[1]["error"]["code"], "stale-id"); - assert!( - responses[2].get("error").is_none() && responses[2]["result"].is_object(), - "costEstimate's name filter ignores snapshot: {}", - responses[2] - ); - - let _ = std::fs::remove_dir_all(path.parent().unwrap()); -} - #[test] fn cost_estimate_rule_uses_the_same_grammar() { let path = temp_file("cost.ws", DUP_CALLERS); diff --git a/crates/wright-cli/tests/cli.rs b/crates/wright-cli/tests/cli.rs index 02b8950a..619785a3 100644 --- a/crates/wright-cli/tests/cli.rs +++ b/crates/wright-cli/tests/cli.rs @@ -1397,36 +1397,6 @@ fn stdin_workshop_works_and_legacy_protocol_is_refused() { ); } -#[test] -fn bom_prefixed_workshop_behaves_like_the_plain_file() { - let bom = format!("\u{FEFF}{}", corpus_workshop("synthetic/basic-rule")); - let plain = temp_file("plain.ws", &corpus_workshop("synthetic/basic-rule")); - let bommed = temp_file("bom.ws", &bom); - - let output = run(&["check", bommed.to_str().unwrap()]); - assert!(output.status.success(), "{}", command_result(&output)); - let stdout = String::from_utf8_lossy(&output.stdout); - assert!(stdout.contains("PASS check"), "{stdout}"); - - let bommed_out = run(&["compile", bommed.to_str().unwrap(), "-o", "-"]); - let plain_out = run(&["compile", plain.to_str().unwrap(), "-o", "-"]); - assert!(bommed_out.status.success() && plain_out.status.success()); - assert_eq!(bommed_out.stdout, plain_out.stdout); - - let output = run_with_stdin(&["check", "-"], &bom); - assert_eq!(output.status.code(), Some(0)); - let output = run_with_stdin(&["check", "-"], "\u{FEFF}{\"protocol\":{}}"); - assert_eq!(output.status.code(), Some(1)); - assert!( - String::from_utf8_lossy(&output.stdout).contains("input-kind-unsupported"), - "{}", - command_result(&output) - ); - - let _ = std::fs::remove_dir_all(bommed.parent().unwrap()); - let _ = std::fs::remove_dir_all(plain.parent().unwrap()); -} - #[test] fn stdin_opy_requires_an_entry_for_provider_workflows() { let source = @@ -2235,57 +2205,6 @@ fn convert_rejects_non_workshop_input() { let _ = std::fs::remove_dir_all(path.parent().unwrap()); } -#[test] -fn symbols_lists_program_symbols_and_filters_by_kind() { - let path = temp_file("cake.txt", &corpus_workshop("real-world/overpy-cake")); - let path = path.to_str().unwrap(); - - let output = run(&["inspect", "symbols", path]); - assert!(output.status.success(), "{}", command_result(&output)); - let stdout = String::from_utf8_lossy(&output.stdout); - assert!(stdout.contains("SYMBOLS symbols"), "{stdout}"); - assert!(stdout.contains("summary: symbols=5"), "{stdout}"); - assert!( - stdout.contains("symbol: globalVariable name=cakePos at=cake.txt:3:14"), - "{stdout}" - ); - assert!( - stdout.contains("symbol: rule name=cake at=cake.txt:8:1"), - "{stdout}" - ); - - let output = run(&["inspect", "symbols", path, "-f", "json"]); - assert!(output.status.success(), "{}", command_result(&output)); - let envelope = parse_json(&output.stdout); - assert_eq!(envelope["command"], "symbols"); - assert_eq!(envelope["wright"]["contract"], "wright-result/v1"); - let symbols = envelope["result"]["symbols"].as_array().unwrap(); - assert_eq!(symbols.len(), 5); - let rule = symbols - .iter() - .find(|symbol| symbol["name"] == "cake") - .expect("the cake rule symbol"); - assert_eq!(rule["kind"], "rule"); - assert_eq!(rule["span"]["path"], "cake.txt"); - - let output = run(&["inspect", "symbols", path, "--only", "rule", "-f", "json"]); - let envelope = parse_json(&output.stdout); - let symbols = envelope["result"]["symbols"].as_array().unwrap(); - assert_eq!(symbols.len(), 2); - assert!(symbols.iter().all(|symbol| symbol["kind"] == "rule")); - - let output = run(&[ - "inspect", "symbols", path, "--only", "rule", "--max", "1", "-f", "json", - ]); - let envelope = parse_json(&output.stdout); - let symbols = envelope["result"]["symbols"].as_array().unwrap(); - assert_eq!(symbols.len(), 1); - assert!(symbols.iter().all(|symbol| symbol["kind"] == "rule")); - assert_eq!(envelope["result"]["selection"]["total"], 5); - - let _ = std::fs::remove_dir_all(Path::new(path).parent().unwrap()); -} - #[test] fn refs_reports_references_and_usage_for_a_named_symbol() { let path = temp_file("cake.txt", &corpus_workshop("real-world/overpy-cake")); @@ -3070,28 +2989,6 @@ fn agent_install_mcp_configures_a_project_whose_server_answers_mcp() { let _ = std::fs::remove_dir_all(&elsewhere); } -#[test] -fn lookup_resolves_a_display_name_to_a_rendered_signature() { - let output = run(&["lookup", "create hud text"]); - assert_eq!(output.status.code(), Some(0), "{output:?}"); - let stdout = String::from_utf8_lossy(&output.stdout); - assert!(stdout.contains("entry: action"), "{stdout}"); - assert!(stdout.contains("identity=createHudText"), "{stdout}"); - assert!( - stdout.contains("signature=\"Create HUD Text("), - "a callable renders its signature: {stdout}" - ); -} - -#[test] -fn lookup_within_lists_an_enums_members() { - let output = run(&["lookup", "--within", "Team", "--only", "enum-member"]); - assert_eq!(output.status.code(), Some(0), "{output:?}"); - let stdout = String::from_utf8_lossy(&output.stdout); - assert!(stdout.contains("name=\"All Teams\""), "{stdout}"); - assert!(stdout.contains("name=\"Team 1\""), "{stdout}"); -} - #[test] fn lookup_never_reads_stdin() { let mut child = Command::new(wright()) diff --git a/crates/wright-cli/tests/error_wording.rs b/crates/wright-cli/tests/error_wording.rs index 727db883..8944e335 100644 --- a/crates/wright-cli/tests/error_wording.rs +++ b/crates/wright-cli/tests/error_wording.rs @@ -245,29 +245,6 @@ fn serve_names_the_operation_not_the_command() { let _ = std::fs::remove_dir_all(path.parent().unwrap()); } -#[test] -fn unmatched_file_lists_project_sources() { - let path = temp_file("main.ws", DUP_RULES); - let output = run(&[ - "inspect", - "symbols", - path.to_str().unwrap(), - "--file", - "nope.ws", - "-f", - "json", - ]); - assert_eq!(output.status.code(), Some(2)); - let envelope = parse_json(&output.stdout); - assert_eq!(envelope["diagnostics"][0]["code"], "invalid-selection"); - let message = envelope["diagnostics"][0]["message"].as_str().unwrap(); - assert!( - message.contains("nope.ws") && message.contains("main.ws"), - "the refusal lists the program's source files: {message}" - ); - let _ = std::fs::remove_dir_all(path.parent().unwrap()); -} - #[test] fn file_selector_has_no_suffix_matching() { let dir = std::env::temp_dir().join(format!("wright-wording-project-{}", std::process::id())); diff --git a/crates/wright-cli/tests/lint_package.rs b/crates/wright-cli/tests/lint_package.rs index 2e1e0791..5a96303f 100644 --- a/crates/wright-cli/tests/lint_package.rs +++ b/crates/wright-cli/tests/lint_package.rs @@ -47,50 +47,6 @@ fn search_lists_the_topic_packages_with_stable_records() { ); } -#[test] -fn search_terms_narrow_the_query_and_search_errors_report_their_code() { - let dirs = TestDirs::new("search-fail"); - let server = TestServer::serve(|request| { - if request.path.starts_with("/search/repositories?") { - Response::json(&search_page(&[("community/rules", None)], 1)) - } else { - Response::not_found() - } - }); - let output = run( - &dirs, - Some(&server), - &["lint", "package", "search", "wait", "--renderer", "plain"], - ); - assert_eq!(output.status.code(), Some(0)); - assert!( - server.paths()[0].contains("fork:false%20wait&per_page=100"), - "terms append to the topic query: {}", - server.paths()[0] - ); - - let limited = TestServer::serve(|_| Response { - status: "403 Forbidden", - headers: vec![ - ("x-ratelimit-remaining".to_string(), "0".to_string()), - ("x-ratelimit-reset".to_string(), "1893456000".to_string()), - ], - body: b"API rate limit exceeded".to_vec(), - }); - let output = run( - &dirs, - Some(&limited), - &["lint", "package", "search", "--renderer", "plain"], - ); - let text = transcript(&dirs, &output); - assert!( - text.contains("ERROR package-search") - && text.contains("diagnostic: error lint-package-rate-limited"), - "{text}" - ); - assert_eq!(output.status.code(), Some(4)); -} - #[test] fn install_fetches_into_the_store_and_never_activates() { let dirs = TestDirs::new("install"); @@ -159,156 +115,6 @@ fn install_fetches_into_the_store_and_never_activates() { ); } -#[test] -fn install_accepts_ref_spellings_and_a_full_sha() { - let dirs = TestDirs::new("install-refs"); - let server = package_server("community", "rules", SHA_A, package_tarball(RULE)); - - let output = run( - &dirs, - Some(&server), - &[ - "lint", - "package", - "install", - "community/rules@v1", - "--renderer", - "plain", - ], - ); - assert_eq!( - output.status.code(), - Some(0), - "{}", - transcript(&dirs, &output) - ); - assert!( - server - .paths() - .contains(&"/repos/community/rules/commits/v1".to_string()) - ); - - let dirs = TestDirs::new("install-refs-sha"); - let server = package_server("community", "rules", SHA_B, package_tarball(RULE)); - let output = run( - &dirs, - Some(&server), - &[ - "lint", - "package", - "install", - &format!("community/rules@{SHA_B}"), - "--renderer", - "plain", - ], - ); - assert_eq!( - output.status.code(), - Some(0), - "{}", - transcript(&dirs, &output) - ); - assert!( - !server.paths().iter().any(|p| p.starts_with("/repos/")), - "a full SHA skips the commits API: {:?}", - server.paths() - ); - - let dirs = TestDirs::new("install-refs-url"); - let server = package_server("community", "rules", SHA_A, package_tarball(RULE)); - let output = run( - &dirs, - Some(&server), - &[ - "lint", - "package", - "install", - "https://github.com/community/rules", - "--ref", - "v1", - "--renderer", - "plain", - ], - ); - assert_eq!( - output.status.code(), - Some(0), - "{}", - transcript(&dirs, &output) - ); -} - -#[test] -fn install_failures_report_stable_error_codes() { - let dirs = TestDirs::new("install-errors"); - let gone = TestServer::serve(|_| Response::not_found()); - let output = run( - &dirs, - Some(&gone), - &[ - "lint", - "package", - "install", - "community/rules", - "--renderer", - "plain", - ], - ); - let text = transcript(&dirs, &output); - assert!( - text.contains("ERROR package-install") - && text.contains("diagnostic: error lint-package-not-found"), - "{text}" - ); - assert_eq!(output.status.code(), Some(4)); - - let malformed = tarball(&[("o-r-bad/rules/bad.yaml", b"id: bad\nmetadata: []\n")]); - let server = package_server("community", "rules", SHA_A, malformed); - let output = run( - &dirs, - Some(&server), - &[ - "lint", - "package", - "install", - "community/rules", - "--renderer", - "plain", - ], - ); - let text = transcript(&dirs, &output); - assert!( - text.contains("ERROR package-install") - && text.contains("diagnostic: error lint-package-invalid"), - "{text}" - ); - assert_eq!(output.status.code(), Some(1)); - assert!( - !entry(&dirs.root, "community", "rules", SHA_A).exists(), - "a refused package leaves no partial store entry" - ); - - let output = run( - &dirs, - Some(&server), - &[ - "lint", - "package", - "install", - "gitlab.com/community/rules", - "--renderer", - "plain", - ], - ); - let text = transcript(&dirs, &output); - assert!( - text.contains("USAGE package-install") - && text.contains("diagnostic: error lint-package-source"), - "{text}" - ); - assert_eq!(output.status.code(), Some(2)); -} - #[test] fn list_reports_installed_and_damaged_entries() { let dirs = TestDirs::new("list"); diff --git a/crates/wright-cli/tests/lint_package_lifecycle.rs b/crates/wright-cli/tests/lint_package_lifecycle.rs index 3021fc00..5a3bea04 100644 --- a/crates/wright-cli/tests/lint_package_lifecycle.rs +++ b/crates/wright-cli/tests/lint_package_lifecycle.rs @@ -206,66 +206,6 @@ fn update_fails_on_row_errors_and_refuses_damaged_entries() { ); } -#[test] -fn remove_deletes_managed_content_and_keeps_user_files() { - let dirs = TestDirs::new("remove"); - write_entry(&dirs.root, "community", "rules", SHA_A, RULE); - let pruned = write_entry(&dirs.root, "community", "rules", SHA_B, RULE); - std::fs::write(pruned.join("notes.txt"), b"mine").unwrap(); - let output = run( - &dirs, - None, - &[ - "lint", - "package", - "remove", - "community/rules", - "--renderer", - "plain", - ], - ); - let text = transcript(&dirs, &output); - assert!(text.contains("PASS package-remove"), "{text}"); - assert!( - text.contains(&format!( - "package: source=community/rules status=removed sha={SHA_A}" - )), - "{text}" - ); - assert!( - text.contains(&format!( - "package: source=community/rules status=pruned sha={SHA_B}" - )), - "{text}" - ); - assert!(!entry(&dirs.root, "community", "rules", SHA_A).exists()); - assert!(!pruned.join("rules").exists()); - assert_eq!( - std::fs::read_to_string(pruned.join("notes.txt")).unwrap(), - "mine" - ); - - let output = run( - &dirs, - None, - &[ - "lint", - "package", - "remove", - "community/rules", - "--renderer", - "plain", - ], - ); - let text = transcript(&dirs, &output); - assert!( - text.contains("ERROR package-remove") - && text.contains("diagnostic: error lint-package-not-found"), - "{text}" - ); - assert_eq!(output.status.code(), Some(4)); -} - #[test] fn remove_at_ref_removes_only_the_matching_entry() { let dirs = TestDirs::new("remove-ref"); @@ -325,15 +265,3 @@ fn the_json_envelope_is_machine_readable() { let text = String::from_utf8_lossy(&output.stdout); assert!(!text.contains("WRIGHT_LINT_PACKAGE_API_BASE")); } - -#[test] -fn a_bare_lint_package_is_a_usage_error() { - let dirs = TestDirs::new("bare"); - let output = run(&dirs, None, &["lint", "package"]); - assert_eq!(output.status.code(), Some(2)); - let stderr = String::from_utf8_lossy(&output.stderr); - assert!( - stderr.contains("search") || stderr.contains("subcommand"), - "{stderr}" - ); -} diff --git a/crates/wright-driver/src/cache.rs b/crates/wright-driver/src/cache.rs index 85c19b85..5fb4b2d1 100644 --- a/crates/wright-driver/src/cache.rs +++ b/crates/wright-driver/src/cache.rs @@ -398,18 +398,6 @@ mod tests { } } - #[test] - fn same_key_second_run_reports_zero_delta() { - let dir = std::env::temp_dir().join(format!("wright-cache-{}", std::process::id())); - let run = cost_run(&dir); - let snapshot = snap(100, &[("a", 60), ("b", 40)]); - assert!(compare_and_record_in(&run, &snapshot, LOCAL_CACHE).is_none()); - let cmp = compare_and_record_in(&run, &snapshot, LOCAL_CACHE).expect("second run compares"); - assert_eq!(cmp.total_delta, 0); - assert!(cmp.changed.is_empty() && cmp.added.is_empty() && cmp.removed.is_empty()); - let _ = fs::remove_dir_all(&dir); - } - #[test] fn added_and_grown_rules_are_named() { let dir = std::env::temp_dir().join(format!("wright-cache2-{}", std::process::id())); @@ -463,19 +451,6 @@ mod tests { let _ = fs::remove_dir_all(&dir); } - #[test] - fn ci_run_without_baseline_flags_touches_nothing() { - let dir = std::env::temp_dir().join(format!("wright-cache-ci1-{}", std::process::id())); - let run = cost_run(&dir); - compare_and_record_in(&run, &snap(100, &[("a", 100)]), LOCAL_CACHE); - assert!(dir.join(".wright").is_dir()); - assert!( - compare_and_record_in(&run, &snap(120, &[("a", 100)]), CI_CACHE).is_none(), - "no implicit previous-run comparison under CI" - ); - let _ = fs::remove_dir_all(&dir); - } - #[test] fn ci_save_and_compare_round_trips_the_named_baseline() { let dir = std::env::temp_dir().join(format!("wright-cache-ci2-{}", std::process::id())); diff --git a/crates/wright-driver/src/edit.rs b/crates/wright-driver/src/edit.rs index 1a541d4f..b0a22c89 100644 --- a/crates/wright-driver/src/edit.rs +++ b/crates/wright-driver/src/edit.rs @@ -616,35 +616,6 @@ mod tests { assert_eq!(error.code, "edit-overlap"); } - #[test] - fn workshop_edits_validate_by_reparsing_the_source() { - let (dir, mut session) = workshop_session(WORKSHOP); - let loaded = session.load().expect("loaded"); - let edit = SourceEdit { - edit_kind: "edit".to_string(), - source: loaded.input.display.clone(), - source_identity: crate::input_identity(&loaded.input.text), - range: EditRange { - start_line: 3, - start_col: 12, - end_line: 3, - end_col: 17, - }, - new_text: "total".to_string(), - }; - let sources = BTreeMap::from([(loaded.input.display.clone(), loaded.input.text.clone())]); - let validation = validate_transaction( - &session.config, - session.catalog(), - Some(&sources), - &EditTransaction::new(vec![edit]).unwrap(), - ); - assert!(validation.ok, "{:?}", validation.diagnostics); - let preview = validation.preview.expect("preview"); - assert!(preview[0].new_text.contains("0: total")); - let _ = std::fs::remove_dir_all(dir); - } - #[test] fn workshop_edits_refuse_with_the_reparse_diagnostics() { let (dir, mut session) = workshop_session(WORKSHOP); @@ -890,61 +861,6 @@ mod tests { ); } - #[test] - fn opy_input_routes_to_the_provider_operation() { - let dir = std::env::temp_dir().join(format!( - "wright-edit-opy-{}-{}", - std::process::id(), - COUNTER.fetch_add(1, Ordering::SeqCst) - )); - std::fs::create_dir_all(&dir).unwrap(); - let path = dir.join("input.opy"); - std::fs::write(&path, "rule \"r\":\n pass\n").unwrap(); - let session = crate::CompilerSession::new(SessionConfig { - input: crate::InputSpec::Path(path.clone()), - kind: SourceKind::Opy, - ..SessionConfig::default() - }) - .expect("session"); - let sources = BTreeMap::from([( - path.to_string_lossy().into_owned(), - "rule \"r\":\n pass\n".to_string(), - )]); - let edit = SourceEdit { - edit_kind: "edit".to_string(), - source: path.to_string_lossy().into_owned(), - source_identity: crate::input_identity("rule \"r\":\n pass\n"), - range: EditRange { - start_line: 1, - start_col: 1, - end_line: 1, - end_col: 2, - }, - new_text: "x".to_string(), - }; - let validation = validate_transaction( - &session.config, - session.catalog(), - Some(&sources), - &EditTransaction::new(vec![edit]).unwrap(), - ); - assert!(!validation.ok); - assert_eq!(validation.diagnostics[0].code, "edit-requires-provider"); - assert!( - validation.diagnostics[0] - .message - .contains("providerValidateEdit"), - "the refusal names the provider operation" - ); - let _ = std::fs::remove_dir_all(dir); - } - - #[test] - fn empty_transaction_is_rejected() { - let error = EditTransaction::new(Vec::new()).unwrap_err(); - assert_eq!(error.code, "edit-empty-transaction"); - } - #[test] fn transaction_orders_edits_deterministically() { let source = "rule \"r\":\n a\n b\n"; diff --git a/crates/wright-driver/src/input.rs b/crates/wright-driver/src/input.rs index 8b666f42..52f15a68 100644 --- a/crates/wright-driver/src/input.rs +++ b/crates/wright-driver/src/input.rs @@ -645,26 +645,4 @@ mod tests { std::fs::remove_dir_all(directory).expect("remove test directory"); } - - #[test] - fn directory_detection_ignores_a_stray_txt() { - static COUNTER: AtomicUsize = AtomicUsize::new(0); - let directory = std::env::temp_dir().join(format!( - "wright-input-stray-txt-{}-{}", - std::process::id(), - COUNTER.fetch_add(1, Ordering::SeqCst) - )); - std::fs::create_dir_all(&directory).expect("create test directory"); - std::fs::write(directory.join("version.txt"), "0.11.0\n").expect("write text file"); - - let config = SessionConfig { - input: InputSpec::Path(directory.clone()), - kind: SourceKind::Auto, - ..SessionConfig::default() - }; - let error = resolve(&config).expect_err("a lone .txt must not claim the directory"); - assert_eq!(error.code, "input-kind-unknown"); - - std::fs::remove_dir_all(directory).expect("remove test directory"); - } } diff --git a/crates/wright-driver/tests/service.rs b/crates/wright-driver/tests/service.rs index f49f2886..e8101add 100644 --- a/crates/wright-driver/tests/service.rs +++ b/crates/wright-driver/tests/service.rs @@ -16,41 +16,6 @@ fn workshop_path() -> PathBuf { workspace_root().join("tests/fixtures/workshop/synthetic/control-flow.ws") } -#[test] -fn tool_service_queries_canonical_workshop() { - let mut session = CompilerSession::new(SessionConfig { - input: InputSpec::Path(workshop_path()), - kind: SourceKind::Workshop, - ..SessionConfig::default() - }) - .unwrap(); - let mut service = ToolService::new(&mut session).unwrap(); - for request in [ - ToolRequest::Capabilities, - ToolRequest::Project, - ToolRequest::Rules { - snapshot: None, - name: None, - file: None, - max: None, - rule: None, - content: false, - all: true, - }, - ToolRequest::Findings { - selection: FindingSelection::default(), - all: true, - }, - ToolRequest::CostEstimate { - selection: FindingSelection::default(), - rule: None, - all: true, - }, - ] { - assert!(matches!(service.handle(&request), ToolResponse::Ok { .. })); - } -} - #[test] fn tool_service_lint_queries_keep_the_session_configuration() { let mut lint = wright_driver::config::LintConfig::default(); @@ -121,99 +86,6 @@ fn compile_request(name: &str) -> ToolRequest { } } -#[test] -fn tool_service_routes_workflows_through_the_agent_request_contract() { - let mut session = CompilerSession::new(SessionConfig { - input: InputSpec::Path(workshop_path()), - kind: SourceKind::Workshop, - ..SessionConfig::default() - }) - .unwrap(); - let mut service = ToolService::new(&mut session).unwrap(); - - let capabilities = service.capabilities(); - assert_eq!(capabilities.agent_contract, "wright-agent/v1"); - for (request, command) in [ - (compile_request("routes.ow"), "compile"), - (ToolRequest::Check { all: true }, "check"), - ( - ToolRequest::Analyze { - selection: Default::default(), - all: true, - }, - "analyze", - ), - (ToolRequest::Inspect { all: true }, "inspect"), - ] { - let ToolResponse::Ok { result } = service.handle(&request) else { - panic!("{command} returns its structured envelope"); - }; - assert_eq!(result["command"], command); - assert_eq!(result["wright"]["contract"], "wright-result/v1"); - } -} - -#[test] -fn agent_finding_selection_filters_and_truncates_without_touching_defaults() { - let input = workspace_root().join("tests/fixtures/workshop/real-world/overpy-cake.ws"); - let mut session = CompilerSession::new(SessionConfig { - input: InputSpec::Path(input), - kind: SourceKind::Workshop, - ..SessionConfig::default() - }) - .unwrap(); - let mut service = ToolService::new(&mut session).unwrap(); - let mut result_of = |request| match service.handle(&request) { - ToolResponse::Ok { result } => result, - ToolResponse::Error { error } => panic!("{error:?}"), - }; - - let plain = result_of(ToolRequest::Findings { - selection: FindingSelection::default(), - all: true, - }); - assert_eq!(plain["snapshot"], 1, "{plain}"); - assert_eq!(plain["findings"].as_array().unwrap().len(), 10); - - let errors = result_of(ToolRequest::Findings { - selection: FindingSelection { - severity: Some(Severity::Error), - ..FindingSelection::default() - }, - all: true, - }); - assert!(errors["findings"].as_array().unwrap().is_empty()); - assert_eq!( - errors["selection"], - serde_json::json!({"total": 10, "withheld": 0}) - ); - - let lint = result_of(ToolRequest::Lint { - selection: FindingSelection { - rule: Some("repeated-value".to_string()), - max: Some(2), - ..FindingSelection::default() - }, - all: true, - }); - assert_eq!(lint["findings"].as_array().unwrap().len(), 2); - assert_eq!( - lint["selection"], - serde_json::json!({"total": 10, "withheld": 7, "byCode": {"repeated-value": 9}}) - ); - - let cost = result_of(ToolRequest::CostEstimate { - selection: FindingSelection { - max: Some(1), - ..FindingSelection::default() - }, - rule: None, - all: true, - }); - assert_eq!(cost["findings"].as_array().unwrap().len(), 1); - assert!(cost["selection"]["withheld"].as_u64().unwrap() > 0); -} - #[test] fn an_unknown_rule_id_is_a_service_error_not_an_empty_result() { let mut session = CompilerSession::new(SessionConfig { @@ -323,108 +195,6 @@ fn an_unknown_rule_id_in_the_lint_policy_is_a_usage_error() { let _ = std::fs::remove_dir_all(&dir); } -#[test] -fn analyze_selection_narrows_risks_and_reports_the_outcome() { - let input = workspace_root().join("tests/fixtures/workshop/real-world/overpy-cake.ws"); - let mut session = CompilerSession::new(SessionConfig { - input: InputSpec::Path(input.clone()), - kind: SourceKind::Workshop, - ..SessionConfig::default() - }) - .unwrap(); - let mut service = ToolService::new(&mut session).unwrap(); - let mut analyze = |selection| { - result_of( - &mut service, - &ToolRequest::Analyze { - selection, - all: true, - }, - ) - }; - - let plain = analyze(FindingSelection::default()); - assert_eq!(plain["result"]["risks"].as_array().unwrap().len(), 10); - assert!(plain["result"].get("selection").is_none()); - - let errors = analyze(FindingSelection { - severity: Some(Severity::Error), - ..FindingSelection::default() - }); - assert!(errors["result"]["risks"].as_array().unwrap().is_empty()); - assert_eq!( - errors["result"]["selection"], - serde_json::json!({"total": 10, "withheld": 0}) - ); - - let bounded = analyze(FindingSelection { - rule: Some("repeated-value".to_string()), - max: Some(2), - ..FindingSelection::default() - }); - let risks = bounded["result"]["risks"].as_array().unwrap(); - assert_eq!(risks.len(), 2); - assert!(risks.iter().all(|risk| risk["code"] == "repeated-value")); - assert_eq!( - bounded["result"]["selection"], - serde_json::json!({"total": 10, "withheld": 7, "byCode": {"repeated-value": 9}}) - ); - - let here = analyze(FindingSelection { - file: Some(input.to_str().unwrap().to_string()), - ..FindingSelection::default() - }); - assert_eq!(here["result"]["risks"].as_array().unwrap().len(), 10); - let code = refusal_code( - &mut service, - &ToolRequest::Analyze { - selection: FindingSelection { - file: Some("elsewhere.ws".to_string()), - ..FindingSelection::default() - }, - all: true, - }, - ); - assert_eq!(code, "invalid-selection"); - - let essential = result_of( - &mut service, - &ToolRequest::Analyze { - selection: FindingSelection { - rule: Some("repeated-value".to_string()), - ..FindingSelection::default() - }, - all: false, - }, - ); - assert_eq!(essential["result"]["counts"]["risks"]["total"], 9); - assert_eq!( - essential["result"]["selection"], - serde_json::json!({"total": 10, "withheld": 0, "byCode": {"repeated-value": 9}}) - ); - - let mut selected_session = CompilerSession::new(SessionConfig { - selection: FindingSelection { - severity: Some(Severity::Error), - ..FindingSelection::default() - }, - input: InputSpec::Path(input), - kind: SourceKind::Workshop, - ..SessionConfig::default() - }) - .unwrap(); - let envelope = selected_session.analyze(); - assert!( - envelope.result.facts["risks"] - .as_array() - .unwrap() - .is_empty() - ); - let outcome = envelope.result.selection.expect("selection is recorded"); - assert_eq!(outcome.total, 10); - assert_eq!(outcome.max_severity, Some(Severity::Warning)); -} - #[test] fn inspect_results_resolve_span_paths() { let mut session = CompilerSession::new(SessionConfig { @@ -775,22 +545,6 @@ fn essential_lint_applies_selection_before_summarizing() { assert_eq!(lint["findings"].as_array().unwrap().len(), 1); } -#[test] -fn tool_service_keeps_provider_refusals_structured() { - let path = workspace_root().join("tests/fixtures/opy/basic-rule.opy"); - let mut session = CompilerSession::new(SessionConfig { - input: InputSpec::Path(path), - kind: SourceKind::Opy, - ..SessionConfig::default() - }) - .unwrap(); - let diagnostic = match session.load() { - Ok(_) => panic!("static OPY fallback must not load"), - Err(diagnostic) => diagnostic, - }; - assert_eq!(diagnostic.code, "source-provider-unavailable"); -} - fn declarations_path() -> PathBuf { workspace_root().join("tests/fixtures/workshop/synthetic/declarations-rules.ws") } @@ -1559,130 +1313,6 @@ fn semantic_query_selection_refuses_unknown_filter_values() { } } -#[test] -fn session_query_workflows_return_the_agent_operation_payloads() { - let input = || SessionConfig { - input: InputSpec::Path(declarations_path()), - kind: SourceKind::Workshop, - ..SessionConfig::default() - }; - - let mut agent = CompilerSession::new(input()).unwrap(); - let mut service = ToolService::new(&mut agent).unwrap(); - let agent_symbols = result_of( - &mut service, - &ToolRequest::Symbols { - kind: None, - file: None, - max: None, - all: true, - }, - ); - let agent_references = result_of( - &mut service, - &ToolRequest::References { - snapshot: None, - symbol: "score".into(), - kind: None, - rule: None, - file: None, - max: None, - all: true, - }, - ); - let agent_usage = result_of( - &mut service, - &ToolRequest::Usage { - snapshot: None, - symbol: "score".into(), - }, - ); - let agent_cfg = result_of( - &mut service, - &ToolRequest::Cfg { - snapshot: None, - rule: "player starts".into(), - kind: None, - max: None, - all: true, - }, - ); - let agent_callgraph = result_of( - &mut service, - &ToolRequest::CallGraph { - caller: None, - callee: None, - max: None, - all: true, - }, - ); - let agent_cost = result_of( - &mut service, - &ToolRequest::CostEstimate { - selection: FindingSelection::default(), - rule: None, - all: true, - }, - ); - drop(service); - - let mut cli = CompilerSession::new(input()).unwrap(); - assert_eq!( - serde_json::to_value(cli.symbols(None, None, None).result).unwrap(), - agent_symbols - ); - - let refs = serde_json::to_value(cli.refs("score", None, None, None, None).result).unwrap(); - assert_eq!(refs, agent_references); - for (key, value) in agent_usage.as_object().unwrap() { - assert_eq!(&refs[key], value); - } - - assert_eq!( - serde_json::to_value(cli.cfg("player starts", None, None).result).unwrap(), - agent_cfg - ); - assert_eq!( - serde_json::to_value(cli.callgraph(None, None, None).result).unwrap(), - agent_callgraph - ); - assert_eq!( - serde_json::to_value(cli.cost(None).result).unwrap(), - agent_cost - ); -} - -#[test] -fn analyze_reports_persistent_object_facts() { - let source = r#" -rule ("effect") { - event { - Ongoing - Global; - } - actions { - Create Effect(All Players(All Teams), Orb, Red, Vector(0, 0, 0), 1, None); - } -} -"#; - let path = temp_workshop("effect.ws", source); - let mut session = CompilerSession::new(SessionConfig { - input: InputSpec::Path(path.clone()), - kind: SourceKind::Workshop, - ..SessionConfig::default() - }) - .unwrap(); - let envelope = session.analyze(); - assert!(envelope.ok); - let objects = envelope.result.facts["persistentObjects"] - .as_array() - .expect("analyze reports facts.persistentObjects"); - assert_eq!(objects.len(), 1); - assert_eq!(objects[0]["kind"], "effect"); - assert_eq!(objects[0]["executionScope"], "global"); - assert_eq!(objects[0]["visibility"], "all-players"); - let _ = std::fs::remove_dir_all(path.parent().unwrap()); -} - #[test] fn analyze_reports_cost_complexity_and_risk_facts() { let source = r#" @@ -3646,34 +3276,6 @@ fn unsourced_transaction_unifies_spelling_variants_of_the_input() { let _ = std::fs::remove_dir_all(path.parent().unwrap()); } -#[test] -fn repeated_analyze_requests_do_not_rebuild_semantic_state() { - let mut session = CompilerSession::new(SessionConfig { - input: InputSpec::Path(declarations_path()), - kind: SourceKind::Workshop, - ..SessionConfig::default() - }) - .unwrap(); - let mut service = ToolService::new(&mut session).unwrap(); - assert_eq!(service.semantic_build_count(), 1); - - for _ in 0..3 { - let analyze = result_of( - &mut service, - &ToolRequest::Analyze { - selection: Default::default(), - all: true, - }, - ); - assert_eq!(analyze["command"], "analyze"); - assert!(analyze["ok"].as_bool().unwrap_or(false), "{analyze}"); - } - assert_eq!(service.semantic_build_count(), 1); - - assert!(service.analyze().ok); - assert_eq!(service.semantic_build_count(), 1); -} - #[test] fn repeated_embedding_workflows_share_one_semantic_build() { let mut session = CompilerSession::new(SessionConfig { @@ -3700,34 +3302,6 @@ fn repeated_embedding_workflows_share_one_semantic_build() { assert_eq!(session.semantic_build_count(), 1); } -#[test] -fn session_lint_keeps_the_effective_configuration_over_the_shared_index() { - let mut lint = wright_driver::config::LintConfig::default(); - assert!(lint.set_severity_by_name("min-wait-loop", "error")); - let mut session = CompilerSession::new(SessionConfig { - input: InputSpec::Path(workshop_path()), - kind: SourceKind::Workshop, - lint, - ..SessionConfig::default() - }) - .unwrap(); - - let lint = session.lint(); - let finding = lint - .result - .findings - .as_array() - .unwrap() - .iter() - .find(|f| f["code"] == "min-wait-loop") - .expect("the fixture triggers min-wait-loop"); - assert_eq!(finding["severity"], "error"); - - assert!(!session.lint().ok); - assert!(session.analyze().ok); - assert_eq!(session.semantic_build_count(), 1); -} - #[test] fn a_reload_rebuilds_the_semantic_service_once() { let dir = freshness_dir("semantic"); diff --git a/crates/wright-driver/tests/source_provider.rs b/crates/wright-driver/tests/source_provider.rs index 720c034c..d3b9a425 100644 --- a/crates/wright-driver/tests/source_provider.rs +++ b/crates/wright-driver/tests/source_provider.rs @@ -1230,88 +1230,28 @@ fn service_compile_establishes_the_snapshot_and_recovers_after_reload() { } #[test] -fn service_check_and_compile_match_the_direct_workflow_methods() { - for via_request in [true, false] { - let (dir, entry) = temp_entry(); - let operations = Arc::new(Mutex::new(Vec::new())); - let provider = ReloadableProvider { - operations: Arc::clone(&operations), - workshop_text: workshop_fixture("synthetic/basic-rule"), - }; +fn provider_backend_rejects_unpathed_input_without_fabricating_an_entry() { + for input in [ + InputSpec::Stdin, + InputSpec::Text { + text: "globalvar score = 0\n".to_string(), + path: Some(PathBuf::from("entry.opy")), + }, + ] { let config = SessionConfig { - input: InputSpec::Path(entry.clone()), + input, kind: SourceKind::Opy, + source_backend: SourceBackend::Provider, ..SessionConfig::default() }; - let mut session = CompilerSession::with_source_provider(config, Box::new(provider)) - .expect("provider session"); - let mut service = ToolService::new(&mut session).expect("service constructs"); - - let check = if via_request { - let ToolResponse::Ok { result } = service.handle(&ToolRequest::Check { all: true }) - else { - panic!("check must return the workflow envelope") - }; - result - } else { - serde_json::to_value(service.check()).expect("envelope serializes") - }; - assert_eq!(check["command"], "check"); - assert_eq!(check["ok"], true, "{check}"); - assert_eq!(*operations.lock().expect("ops"), vec!["compile"]); - - let compile = if via_request { - let ToolResponse::Ok { result } = service.handle(&ToolRequest::Compile { - output: Some(dir.join("program.ow")), - force: true, - to: None, - }) else { - panic!("compile must return the workflow envelope") - }; - result - } else { - serde_json::to_value(service.compile()).expect("envelope serializes") - }; - assert_eq!(compile["command"], "compile"); - assert_eq!(compile["ok"], true, "{compile}"); - assert_eq!(*operations.lock().expect("ops"), vec!["compile"]); - cleanup(dir); + let mut session = CompilerSession::new(config).expect("session"); + let result = session.check(); + assert!(!result.ok); + assert_eq!(result.exit, 3); + assert_eq!(result.diagnostics[0].code, "source-provider-unsupported"); } } -#[test] -fn provider_backend_rejects_stdin_without_fabricating_an_entry() { - let config = SessionConfig { - input: InputSpec::Stdin, - kind: SourceKind::Opy, - source_backend: SourceBackend::Provider, - ..SessionConfig::default() - }; - let mut session = CompilerSession::new(config).expect("session"); - let result = session.check(); - assert!(!result.ok); - assert_eq!(result.exit, 3); - assert_eq!(result.diagnostics[0].code, "source-provider-unsupported"); -} - -#[test] -fn provider_backend_rejects_text_input_without_fabricating_an_entry() { - let config = SessionConfig { - input: InputSpec::Text { - text: "globalvar score = 0\n".to_string(), - path: Some(PathBuf::from("entry.opy")), - }, - kind: SourceKind::Opy, - source_backend: SourceBackend::Provider, - ..SessionConfig::default() - }; - let mut session = CompilerSession::new(config).expect("session"); - let result = session.check(); - assert!(!result.ok); - assert_eq!(result.exit, 3); - assert_eq!(result.diagnostics[0].code, "source-provider-unsupported"); -} - #[test] fn unmapped_provider_artifact_errors_do_not_claim_the_opy_entry() { let (dir, entry) = temp_entry(); @@ -1354,36 +1294,6 @@ fn unmapped_provider_artifact_errors_do_not_claim_the_opy_entry() { cleanup(dir); } -#[test] -fn provider_backend_does_not_fall_back_when_the_provider_fails() { - let (dir, entry) = temp_entry(); - let provider = RecordingProvider { - target: Arc::new(Mutex::new(None)), - operations: Arc::new(Mutex::new(Vec::new())), - check_compilation: None, - compilation: None, - check_failure: None, - failure: Some(failed( - "provider-exited", - Stage::Internal, - wright_driver::ExitClass::Internal, - "provider exited before compiling the entry", - )), - }; - let config = SessionConfig { - input: InputSpec::Path(entry), - kind: SourceKind::Opy, - ..SessionConfig::default() - }; - let mut session = CompilerSession::with_source_provider(config, Box::new(provider)) - .expect("provider session"); - let result = session.check(); - assert!(!result.ok); - assert_eq!(result.exit, 4); - assert_eq!(result.diagnostics[0].code, "provider-exited"); - cleanup(dir); -} - #[test] fn provider_backend_provider_resolution_failure_is_explicit() { let (dir, entry) = temp_entry(); @@ -1405,127 +1315,56 @@ fn provider_backend_provider_resolution_failure_is_explicit() { } #[test] -fn provider_refusal_is_a_frontend_failure_with_its_refusal_code() { - let (dir, entry) = temp_entry(); - let provider = RecordingProvider { - target: Arc::new(Mutex::new(None)), - operations: Arc::new(Mutex::new(Vec::new())), - check_compilation: None, - compilation: None, - check_failure: None, - failure: Some(failed( +fn provider_failures_keep_their_code_stage_and_exit_class() { + use wright_driver::ExitClass; + for (code, stage, class, exit) in [ + ( "provider-refusal-compile.tooLarge", Stage::Frontend, - wright_driver::ExitClass::SourceError, - "the compile request failed: the provider refused (compile.tooLarge)", - )), - }; - let config = SessionConfig { - input: InputSpec::Path(entry), - kind: SourceKind::Opy, - ..SessionConfig::default() - }; - let mut session = CompilerSession::with_source_provider(config, Box::new(provider)) - .expect("provider session"); - let result = session.check(); - assert!(!result.ok); - assert_eq!(result.exit, 1); - assert_eq!( - result.diagnostics[0].code, - "provider-refusal-compile.tooLarge" - ); - assert_eq!(result.diagnostics[0].stage, Stage::Frontend); - cleanup(dir); -} - -#[test] -fn provider_capability_unavailable_exits_as_unsupported() { - let (dir, entry) = temp_entry(); - let provider = RecordingProvider { - target: Arc::new(Mutex::new(None)), - operations: Arc::new(Mutex::new(Vec::new())), - check_compilation: Some(SourceCompilation { - workshop_text: None, - locale: None, - provenance: wright_driver::SourceProvenance::Unmapped, - diagnostics: Vec::new(), - source_identity: None, - }), - compilation: None, - check_failure: Some(failed( - "capability-unavailable", + ExitClass::SourceError, + 1, + ), + ( + "project-load-failed", Stage::Frontend, - wright_driver::ExitClass::Unsupported, - "the check request failed: capability 'projectLoading' was not negotiated", - )), - failure: Some(failed( + ExitClass::SourceError, + 1, + ), + ( + "session-config-changed", + Stage::Discovery, + ExitClass::SourceError, + 1, + ), + ( "capability-unavailable", Stage::Frontend, - wright_driver::ExitClass::Unsupported, - "the check request failed: capability 'projectLoading' was not negotiated", - )), - }; - let config = SessionConfig { - input: InputSpec::Path(entry), - kind: SourceKind::Opy, - ..SessionConfig::default() - }; - let mut session = CompilerSession::with_source_provider(config, Box::new(provider)) - .expect("provider session"); - let result = session.check(); - assert!(!result.ok); - assert_eq!(result.exit, 3); - assert_eq!(result.diagnostics[0].code, "capability-unavailable"); - assert_eq!(result.diagnostics[0].stage, Stage::Frontend); - cleanup(dir); -} - -#[test] -fn provider_project_load_failure_is_a_source_side_failure() { - let (dir, entry) = temp_entry(); - let provider = RecordingProvider { - target: Arc::new(Mutex::new(None)), - operations: Arc::new(Mutex::new(Vec::new())), - check_compilation: None, - compilation: None, - check_failure: None, - failure: Some(failed( - "project-load-failed", - Stage::Frontend, - wright_driver::ExitClass::SourceError, - "the check request failed: a required source file could not be loaded", - )), - }; - let config = SessionConfig { - input: InputSpec::Path(entry), - kind: SourceKind::Opy, - ..SessionConfig::default() - }; - let mut session = CompilerSession::with_source_provider(config, Box::new(provider)) - .expect("provider session"); - let result = session.check(); - assert!(!result.ok); - assert_eq!(result.exit, 1); - assert_eq!(result.diagnostics[0].code, "project-load-failed"); - cleanup(dir); -} - -#[test] -fn provider_transport_failures_stay_internal() { - for code in ["provider-timeout", "provider-exited", "provider-malformed"] { + ExitClass::Unsupported, + 3, + ), + ("provider-timeout", Stage::Internal, ExitClass::Internal, 4), + ("provider-exited", Stage::Internal, ExitClass::Internal, 4), + ( + "provider-malformed", + Stage::Internal, + ExitClass::Internal, + 4, + ), + ] { let (dir, entry) = temp_entry(); let provider = RecordingProvider { target: Arc::new(Mutex::new(None)), operations: Arc::new(Mutex::new(Vec::new())), - check_compilation: None, + check_compilation: Some(SourceCompilation { + workshop_text: None, + locale: None, + provenance: wright_driver::SourceProvenance::Unmapped, + diagnostics: Vec::new(), + source_identity: None, + }), compilation: None, - check_failure: None, - failure: Some(failed( - code, - Stage::Internal, - wright_driver::ExitClass::Internal, - format!("the compile request failed: {code}"), - )), + check_failure: Some(failed(code, stage, class, format!("failed: {code}"))), + failure: Some(failed(code, stage, class, format!("failed: {code}"))), }; let config = SessionConfig { input: InputSpec::Path(entry), @@ -1534,46 +1373,15 @@ fn provider_transport_failures_stay_internal() { }; let mut session = CompilerSession::with_source_provider(config, Box::new(provider)) .expect("provider session"); - let result = session.compile(); + let result = session.check(); assert!(!result.ok, "{code}"); - assert_eq!(result.exit, 4, "{code}"); + assert_eq!(result.exit, exit, "{code}"); assert_eq!(result.diagnostics[0].code, code); - assert_eq!(result.diagnostics[0].stage, Stage::Internal); + assert_eq!(result.diagnostics[0].stage, stage, "{code}"); cleanup(dir); } } -#[test] -fn wright_originated_refusal_keeps_its_verbatim_code() { - let (dir, entry) = temp_entry(); - let provider = RecordingProvider { - target: Arc::new(Mutex::new(None)), - operations: Arc::new(Mutex::new(Vec::new())), - check_compilation: None, - compilation: None, - check_failure: None, - failure: Some(failed( - "session-config-changed", - Stage::Discovery, - wright_driver::ExitClass::SourceError, - "the check request failed: session configuration is fixed", - )), - }; - let config = SessionConfig { - input: InputSpec::Path(entry), - kind: SourceKind::Opy, - ..SessionConfig::default() - }; - let mut session = CompilerSession::with_source_provider(config, Box::new(provider)) - .expect("provider session"); - let result = session.check(); - assert!(!result.ok); - assert_eq!(result.exit, 1); - assert_eq!(result.diagnostics[0].code, "session-config-changed"); - assert_eq!(result.diagnostics[0].stage, Stage::Discovery); - cleanup(dir); -} - #[test] fn provider_backend_inspect_and_queries_run_the_compile_load() { let (dir, entry) = temp_entry(); diff --git a/crates/wright-lsp/src/main.rs b/crates/wright-lsp/src/main.rs index 9a61e208..95f1587f 100644 --- a/crates/wright-lsp/src/main.rs +++ b/crates/wright-lsp/src/main.rs @@ -844,43 +844,4 @@ mod tests { ]; assert_eq!(versioned_workspace_edit(edits).unwrap_err(), "not a uri"); } - - #[test] - fn rename_is_advertised_only_with_versioned_workspace_edits() { - let opy = OpyProviderSupport::default(); - assert!( - initialize_result(true, opy) - .capabilities - .rename_provider - .is_some() - ); - assert!( - initialize_result(false, opy) - .capabilities - .rename_provider - .is_none() - ); - } - - #[test] - fn document_symbols_are_advertised_only_with_provider_backing() { - let backed = OpyProviderSupport { - symbols: true, - ..OpyProviderSupport::default() - }; - assert!( - initialize_result(true, backed) - .capabilities - .document_symbol_provider - .is_some(), - "a provider that serves lpp/symbols backs the advertisement" - ); - assert!( - initialize_result(true, OpyProviderSupport::default()) - .capabilities - .document_symbol_provider - .is_none(), - "without a negotiated provider the capability stays unadvertised" - ); - } } From f357161b833c23b4b418c08c97dc95e01bc9f50e Mon Sep 17 00:00:00 2001 From: Teakowa <27560638+Teakowa@users.noreply.github.com> Date: Wed, 28 Oct 2026 15:07:50 +0800 Subject: [PATCH 3/3] test: restore CLI coverage for lint package exit classes and lookup --limit Pin code and exit for each LintPackageError variant on the CLI surface in one table test, and restore one wright lookup --limit range case next to the lookup --only test. --- crates/wright-cli/tests/lint_package.rs | 64 +++++++++++++++++++ .../wright-cli/tests/selector_vocabulary.rs | 8 +++ 2 files changed, 72 insertions(+) diff --git a/crates/wright-cli/tests/lint_package.rs b/crates/wright-cli/tests/lint_package.rs index 5a96303f..6aaf4110 100644 --- a/crates/wright-cli/tests/lint_package.rs +++ b/crates/wright-cli/tests/lint_package.rs @@ -115,6 +115,70 @@ fn install_fetches_into_the_store_and_never_activates() { ); } +#[test] +fn package_failures_report_their_code_and_exit_class() { + let gone = TestServer::serve(|_| Response::not_found()); + let malformed = package_server( + "community", + "rules", + SHA_A, + tarball(&[("o-r-bad/rules/bad.yaml", b"id: bad\nmetadata: []\n")]), + ); + let limited = TestServer::serve(|_| Response { + status: "403 Forbidden", + headers: vec![ + ("x-ratelimit-remaining".to_string(), "0".to_string()), + ("x-ratelimit-reset".to_string(), "1893456000".to_string()), + ], + body: b"API rate limit exceeded".to_vec(), + }); + let cases: [(&TestServer, &[&str], &str, &str, i32); 4] = [ + ( + &gone, + &["install", "gitlab.com/community/rules"], + "USAGE package-install", + "lint-package-source", + 2, + ), + ( + &malformed, + &["install", "community/rules"], + "ERROR package-install", + "lint-package-invalid", + 1, + ), + ( + &gone, + &["install", "community/rules"], + "ERROR package-install", + "lint-package-not-found", + 4, + ), + ( + &limited, + &["search"], + "ERROR package-search", + "lint-package-rate-limited", + 4, + ), + ]; + for (server, command, header, code, exit) in cases { + let dirs = TestDirs::new(code); + let args: Vec<&str> = ["lint", "package"] + .into_iter() + .chain(command.iter().copied()) + .chain(["--renderer", "plain"]) + .collect(); + let output = run(&dirs, Some(server), &args); + let text = transcript(&dirs, &output); + assert!( + text.contains(header) && text.contains(&format!("diagnostic: error {code}")), + "{code}: {text}" + ); + assert_eq!(output.status.code(), Some(exit), "{code}: {text}"); + } +} + #[test] fn list_reports_installed_and_damaged_entries() { let dirs = TestDirs::new("list"); diff --git a/crates/wright-cli/tests/selector_vocabulary.rs b/crates/wright-cli/tests/selector_vocabulary.rs index f417b75b..6ba7705b 100644 --- a/crates/wright-cli/tests/selector_vocabulary.rs +++ b/crates/wright-cli/tests/selector_vocabulary.rs @@ -283,6 +283,14 @@ fn lookup_only_filters_entries_by_kind() { assert!(entries.iter().all(|entry| entry["kind"] == "action")); } +#[test] +fn lookup_rejects_an_out_of_range_limit() { + let output = run(&["lookup", "wait", "--limit", "0", "-f", "json"]); + assert_eq!(output.status.code(), Some(1), "{}", command_result(&output)); + let result = parse_json(&output.stdout); + assert_eq!(result["error"]["code"], "malformed-request", "{result}"); +} + #[test] fn inspect_refs_and_cfg_keep_only_as_the_category_filter() { let path = temp_file("cake.txt", &corpus_text("real-world/overpy-cake"));