test(output): cover canonical, relative, and fallback path formats

The existing path_format_config test only exercised the 'absolute'
strategy. Add three tests for the remaining acceptance criteria:

- canonical_path_format_resolves_symlinks: compiles via a symlinked
  directory (src/ -> real/) and asserts the canonical file field
  resolves through the symlink to the real path.
- relative_file_format_is_relative_to_directory: invokes the compiler
  with an absolute source path and asserts 'file: relative' rewrites
  it relative to the formatted directory (which is absolute).
- canonical_file_format_falls_back_for_missing_source: feeds the
  semantic subcommand an event referencing a source that does not
  exist on disk; asserts the entry is kept (not dropped) and file
  falls back to the unformatted path.

The Windows \\?\ prefix stripping scenario is Windows-only and remains
uncovered.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Laszlo Nagy
2026-04-21 11:57:56 +00:00
parent 7fc6c097ae
commit e89dd8a6f7
+184
View File
@@ -360,6 +360,190 @@ sources:
Ok(())
}
/// With `file: canonical`, symlinked source paths are written as the resolved
/// real path (symlinks followed).
// Requirements: output-path-format
#[test]
#[cfg(target_family = "unix")]
#[cfg(has_preload_library)]
#[cfg(all(has_executable_compiler_c, has_executable_shell))]
fn canonical_path_format_resolves_symlinks() -> Result<()> {
use std::os::unix::fs::symlink;
let env = TestEnvironment::new("canonical_symlinks")?;
// Real source under real/; src/ is a symlink pointing at real/. Compiling
// via src/main.c records "src/main.c" in the event, which canonical must
// resolve back to .../real/main.c.
env.create_source_files(&[("real/main.c", "int main() { return 0; }")])?;
symlink(env.test_dir().join("real"), env.test_dir().join("src"))?;
let build = format!("{} -c src/main.c -o src/main.o", COMPILER_C_PATH);
let script = env.create_shell_script("build.sh", &build)?;
let config = format!(
r#"
schema: "4.1"
intercept:
mode: preload
path: "{preload}"
format:
paths:
directory: canonical
file: canonical
"#,
preload = PRELOAD_LIBRARY_PATH
);
let config_path = env.test_dir().join("config.yaml");
std::fs::write(&config_path, config)?;
env.run_bear_success(&[
"--output",
"compile_commands.json",
"--config",
config_path.to_str().unwrap(),
"--",
SHELL_PATH,
script.to_str().unwrap(),
])?;
let db = env.load_compilation_database("compile_commands.json")?;
let file = db
.entries()
.first()
.and_then(|e| e.get("file").and_then(|v| v.as_str()))
.expect("expected at least one entry")
.to_string();
assert!(
file.ends_with("/real/main.c"),
"canonical file field must resolve symlinks; got {file}, expected path ending with /real/main.c"
);
assert!(
!file.contains("/src/"),
"canonical file field must not contain the symlink segment /src/; got {file}"
);
Ok(())
}
/// With `directory: absolute` and `file: relative`, an absolute source path
/// observed at interception is rewritten relative to the (formatted) directory.
// Requirements: output-path-format
#[test]
#[cfg(target_family = "unix")]
#[cfg(has_preload_library)]
#[cfg(all(has_executable_compiler_c, has_executable_shell))]
fn relative_file_format_is_relative_to_directory() -> Result<()> {
let env = TestEnvironment::new("relative_file_format")?;
env.create_source_files(&[("src/main.c", "int main() { return 0; }")])?;
// Compile using an absolute source path so the intercepted event records
// the absolute form; `file: relative` must then rewrite it to src/main.c.
let build = format!(r#"{} -c "$(pwd)/src/main.c" -o src/main.o"#, COMPILER_C_PATH);
let script = env.create_shell_script("build.sh", &build)?;
let config = format!(
r#"
schema: "4.1"
intercept:
mode: preload
path: "{preload}"
format:
paths:
directory: absolute
file: relative
"#,
preload = PRELOAD_LIBRARY_PATH
);
let config_path = env.test_dir().join("config.yaml");
std::fs::write(&config_path, config)?;
env.run_bear_success(&[
"--output",
"compile_commands.json",
"--config",
config_path.to_str().unwrap(),
"--",
SHELL_PATH,
script.to_str().unwrap(),
])?;
let db = env.load_compilation_database("compile_commands.json")?;
let entry = db.entries().first().expect("expected at least one entry");
let file = entry.get("file").and_then(|v| v.as_str()).unwrap_or("");
let directory = entry.get("directory").and_then(|v| v.as_str()).unwrap_or("");
assert_eq!(file, "src/main.c", "file must be relative to directory, got {file:?}");
assert!(std::path::Path::new(directory).is_absolute(), "directory must be absolute, got {directory:?}");
Ok(())
}
/// With `file: canonical` and a source that does not exist at output-write
/// time, Bear must not drop the entry: it falls back to the unformatted path
/// (and logs a warning). Exercised via the `semantic` subcommand so we can
/// feed a hand-crafted event referencing a ghost source.
// Requirements: output-path-format
#[test]
#[cfg(target_family = "unix")]
fn canonical_file_format_falls_back_for_missing_source() -> Result<()> {
use serde_json::json;
let env = TestEnvironment::new("canonical_missing_fallback")?;
// Working dir exists (so directory canonicalization succeeds and the entry
// is kept); the source file does not exist, so file canonicalization must
// fall back to the unformatted path.
let temp_dir = env.test_dir().to_str().unwrap().to_string();
let event = json!({
"pid": 12345,
"execution": {
"executable": COMPILER_C_PATH,
"arguments": [COMPILER_C_PATH, "-c", "ghost.c"],
"working_dir": temp_dir,
"environment": {}
}
});
env.create_source_files(&[("events.json", &event.to_string())])?;
let config = r#"
schema: "4.1"
format:
paths:
directory: canonical
file: canonical
"#;
let config_path = env.test_dir().join("config.yaml");
std::fs::write(&config_path, config)?;
env.run_bear_success(&[
"--config",
config_path.to_str().unwrap(),
"semantic",
"--input",
"events.json",
"--output",
"compile_commands.json",
])?;
let db = env.load_compilation_database("compile_commands.json")?;
let file = db
.entries()
.first()
.and_then(|e| e.get("file").and_then(|v| v.as_str()))
.expect("entry must survive the canonical-file fallback");
assert_eq!(file, "ghost.c", "file field must fall back to the unformatted path; got {file:?}");
Ok(())
}
/// Test invalid configuration handling
/// Verifies Bear handles invalid config gracefully
#[test]