mirror of
https://github.com/rizsotto/Bear.git
synced 2026-05-28 00:20:45 +02:00
9033e598fb
The Event wrapper carried a pid field that no production code ever read.
The captured pid was the shim/wrapper's process id (not the compiler's),
so it was semantically misleading and only invited future misuse.
The TCP wire and on-disk JSON Lines format now carry bare Execution
objects rather than {"pid": N, "execution": {...}}. The trim invariant
(strip env vars irrelevant for compilation database generation) moved
from the deleted Event::new into ReporterOnTcp::report, so the
boundary that emits to the wire is the single place that enforces it.
The on-disk format break is hard: prior .events files will be skipped
on read with a warning per line. The output/intercept.rs module
already documents the format as not stable.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
243 lines
8.2 KiB
Rust
243 lines
8.2 KiB
Rust
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
|
|
use crate::fixtures::constants::*;
|
|
use crate::fixtures::infrastructure::TestEnvironment;
|
|
use anyhow::Result;
|
|
#[cfg(has_executable_sleep)]
|
|
use std::process::Stdio;
|
|
#[cfg(has_executable_sleep)]
|
|
use std::time::Instant;
|
|
|
|
#[test]
|
|
fn exit_code_for_empty_arguments() -> Result<()> {
|
|
// Executing Bear with no arguments should return a non-zero exit code,
|
|
// and print usage information.
|
|
let env = TestEnvironment::new("exit_code_for_empty_arguments")?;
|
|
|
|
let result = env.run_bear(&[])?;
|
|
result.assert_failure()?;
|
|
assert!(result.stderr().contains("Usage: bear"));
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn exit_code_for_help() -> Result<()> {
|
|
// Executing help and subcommand help should always has zero exit code,
|
|
// and print out usage information
|
|
let env = TestEnvironment::new("exit_code_for_help")?;
|
|
|
|
// Test main help
|
|
let result = env.run_bear(&["--help"])?;
|
|
result.assert_success()?;
|
|
assert!(result.stdout().contains("Usage: bear"));
|
|
|
|
// Test intercept help
|
|
let result = env.run_bear(&["intercept", "--help"])?;
|
|
result.assert_success()?;
|
|
assert!(result.stdout().contains("Usage: bear"));
|
|
|
|
// Test semantic help
|
|
let result = env.run_bear(&["semantic", "--help"])?;
|
|
result.assert_success()?;
|
|
assert!(result.stdout().contains("Usage: bear"));
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn exit_code_for_invalid_argument() -> Result<()> {
|
|
// Executing Bear with an invalid argument should always has non-zero exit code,
|
|
// and print relevant information about the reason about the failure.
|
|
let env = TestEnvironment::new("exit_code_for_invalid_argument")?;
|
|
|
|
let result = env.run_bear(&["invalid_argument"])?;
|
|
result.assert_failure()?;
|
|
assert!(result.stderr().contains("error: unexpected argument"));
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn exit_code_for_non_existing_command() -> Result<()> {
|
|
// Executing a non-existing command should always has non-zero exit code,
|
|
// and print relevant information about the reason about the failure.
|
|
let env = TestEnvironment::new("exit_code_for_non_existing_command")?;
|
|
|
|
let result = env.run_bear(&["--", "invalid_command"])?;
|
|
result.assert_failure()?;
|
|
assert!(result.stderr().contains("Build execution failed: Failed to execute"));
|
|
Ok(())
|
|
}
|
|
|
|
// Requirements: interception-signal-forwarding
|
|
#[test]
|
|
#[cfg(has_executable_true)]
|
|
fn exit_code_for_true() -> Result<()> {
|
|
// When the executed command returns successfully, Bear exit code should be zero.
|
|
let env = TestEnvironment::new("exit_code_for_true")?;
|
|
|
|
let result = env.run_bear(&["--", TRUE_PATH])?;
|
|
result.assert_success()?;
|
|
Ok(())
|
|
}
|
|
|
|
// Requirements: interception-signal-forwarding
|
|
#[test]
|
|
#[cfg(has_executable_false)]
|
|
fn exit_code_for_false() -> Result<()> {
|
|
// When the executed command returns unsuccessfully, Bear exit code should be non-zero.
|
|
let env = TestEnvironment::new("exit_code_for_false")?;
|
|
|
|
let result = env.run_bear(&["--", FALSE_PATH])?;
|
|
result.assert_failure()?;
|
|
Ok(())
|
|
}
|
|
|
|
// Requirements: interception-signal-forwarding
|
|
#[test]
|
|
#[cfg(has_executable_sleep)]
|
|
fn exit_code_when_signaled() -> Result<()> {
|
|
// When the bear process is signaled, Bear exit code should be non-zero.
|
|
// And should terminate the child process and return immediately.
|
|
let env = TestEnvironment::new("exit_code_when_signaled")?;
|
|
|
|
let mut cmd = env.command_bear();
|
|
cmd.current_dir(env.test_dir())
|
|
.arg("--")
|
|
.arg(SLEEP_PATH)
|
|
.arg("10")
|
|
.env("RUST_LOG", "debug")
|
|
.env("RUST_BACKTRACE", "1")
|
|
.stdout(Stdio::null())
|
|
.stderr(Stdio::null());
|
|
|
|
let mut child = cmd.spawn().expect("Failed to spawn command");
|
|
|
|
// Wait 200ms to ensure that the sleep command was also executed
|
|
std::thread::sleep(std::time::Duration::from_millis(200));
|
|
|
|
let kill_time = Instant::now();
|
|
child.kill().expect("Failed to signal the process");
|
|
let status = child.wait().expect("Failed to wait for command");
|
|
let wait_end = Instant::now();
|
|
|
|
assert!(!status.success());
|
|
assert!(wait_end.duration_since(kill_time).as_secs() < 1, "Process took too long to terminate.",);
|
|
Ok(())
|
|
}
|
|
|
|
// Intercept mode exit code tests
|
|
|
|
/// Test that intercept command returns 0 for successful interception
|
|
// Requirements: interception-signal-forwarding
|
|
#[test]
|
|
#[cfg(has_executable_true)]
|
|
fn intercept_exit_code_for_success() -> Result<()> {
|
|
let env = TestEnvironment::new("intercept_exit_code_for_success")?;
|
|
|
|
let result = env.run_bear(&["intercept", "--output", "events.json", "--", TRUE_PATH])?;
|
|
result.assert_success()?;
|
|
Ok(())
|
|
}
|
|
|
|
/// Test that intercept command propagates command failure exit codes
|
|
// Requirements: interception-signal-forwarding
|
|
#[test]
|
|
#[cfg(has_executable_false)]
|
|
fn intercept_exit_code_for_failure() -> Result<()> {
|
|
let env = TestEnvironment::new("intercept_exit_code_for_failure")?;
|
|
|
|
let result = env.run_bear(&["intercept", "--output", "events.json", "--", FALSE_PATH])?;
|
|
result.assert_failure()?;
|
|
Ok(())
|
|
}
|
|
|
|
/// A compiler that is blocked reading from a FIFO with no writer is in the
|
|
/// mid-compile state. Signaling Bear with SIGTERM must stop both Bear and
|
|
/// the compiler quickly, with Bear reporting non-success.
|
|
// Requirements: interception-signal-forwarding
|
|
#[test]
|
|
#[cfg(target_family = "unix")]
|
|
#[cfg(all(has_executable_compiler_c, has_executable_shell))]
|
|
fn exit_code_when_compiler_is_interrupted_mid_compile() -> Result<()> {
|
|
let env = TestEnvironment::new("exit_code_mid_compile_signal")?;
|
|
|
|
// Named pipe as the compiler's input. With no writer, the compiler
|
|
// blocks in read() and stays mid-compile until a signal arrives.
|
|
let fifo = env.test_dir().join("source.c");
|
|
let mkfifo_status = std::process::Command::new("mkfifo").arg(&fifo).status()?;
|
|
assert!(mkfifo_status.success(), "mkfifo failed -- this test needs a POSIX environment");
|
|
|
|
let mut cmd = env.command_bear();
|
|
cmd.current_dir(env.test_dir())
|
|
.args([
|
|
"--output",
|
|
"compile_commands.json",
|
|
"--",
|
|
COMPILER_C_PATH,
|
|
"-x",
|
|
"c",
|
|
"-c",
|
|
fifo.to_str().unwrap(),
|
|
"-o",
|
|
"out.o",
|
|
])
|
|
.stdout(Stdio::null())
|
|
.stderr(Stdio::null());
|
|
let mut child = cmd.spawn().expect("failed to spawn bear");
|
|
|
|
// Give the compiler time to start and block on opening/reading the FIFO.
|
|
std::thread::sleep(std::time::Duration::from_millis(500));
|
|
|
|
// Send SIGTERM so Bear's signal forwarding path is exercised (unlike
|
|
// Child::kill() which sends SIGKILL and bypasses handlers).
|
|
let signal_time = Instant::now();
|
|
let pid = child.id().to_string();
|
|
let kill_status = std::process::Command::new("kill")
|
|
.arg("-TERM")
|
|
.arg(&pid)
|
|
.status()
|
|
.expect("kill -TERM command failed to run");
|
|
assert!(kill_status.success(), "kill -TERM reported failure");
|
|
|
|
let status = child.wait().expect("failed to wait for bear");
|
|
let elapsed = signal_time.elapsed();
|
|
|
|
assert!(!status.success(), "bear must report non-success after signal");
|
|
assert!(
|
|
elapsed.as_secs() < 2,
|
|
"bear must exit within ~1s of signal while the compiler was mid-compile, took {:?}",
|
|
elapsed
|
|
);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
// Semantic mode exit code tests (note: this is now called 'semantic' not 'citnames')
|
|
|
|
/// Test that semantic command returns 0 for valid input
|
|
#[test]
|
|
fn semantic_exit_code_for_success() -> Result<()> {
|
|
let env = TestEnvironment::new("semantic_exit_code_for_success")?;
|
|
|
|
// Create a sample events file
|
|
let events_content =
|
|
r#"{"executable":"/usr/bin/gcc","arguments":["-c","test.c"],"working_dir":"/tmp","environment":{}}"#;
|
|
env.create_source_files(&[("events.json", events_content)])?;
|
|
|
|
let result =
|
|
env.run_bear(&["semantic", "--input", "events.json", "--output", "compile_commands.json"])?;
|
|
result.assert_success()?;
|
|
Ok(())
|
|
}
|
|
|
|
/// Test that semantic command with missing input file returns non-zero
|
|
#[test]
|
|
fn semantic_exit_code_for_missing_input() -> Result<()> {
|
|
let env = TestEnvironment::new("semantic_exit_code_for_missing_input")?;
|
|
|
|
let result =
|
|
env.run_bear(&["semantic", "--input", "nonexistent.json", "--output", "compile_commands.json"])?;
|
|
result.assert_failure()?;
|
|
Ok(())
|
|
}
|