// Copyright 2025 The Jujutsu Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // https://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. use std::env; use std::fs; use std::path::PathBuf; use std::process::exit; use clap::Parser; use itertools::Itertools as _; #[cfg(unix)] use nix::sys::signal; /// A fake diff-editor, useful for testing #[derive(Parser, Debug)] #[clap()] struct Args { /// Fail if the given file doesn't exist #[arg(long)] require_file: Option, } fn main() { let args: Args = Args::parse(); let edit_script_path = PathBuf::from(env::var_os("BISECTION_SCRIPT").unwrap()); let commit_to_test = env::var_os("JJ_BISECT_TARGET") .unwrap() .to_str() .unwrap() .to_owned(); println!("fake-bisector testing commit {commit_to_test}"); let edit_script = fs::read_to_string(&edit_script_path).unwrap(); if let Some(path) = args.require_file && !std::fs::exists(&path).unwrap() { exit(1) } let mut instructions = edit_script.split('\0').collect_vec(); if let Some(pos) = instructions.iter().position(|&i| i == "next invocation\n") { // Overwrite the edit script. The next time `fake-bisector` is called, it will // only see the part after the `next invocation` command. fs::write(&edit_script_path, instructions[pos + 1..].join("\0")).unwrap(); instructions.truncate(pos); } for instruction in instructions { let (command, payload) = instruction.split_once('\n').unwrap_or((instruction, "")); let parts = command.split(' ').collect_vec(); match parts.as_slice() { [""] => {} ["crash"] => { // Coredump generation varies by UNIX and is irrelevant to tests // Prefer raising SIGTERM to crash without dumping core #[cfg(unix)] let _ = signal::raise(signal::Signal::SIGTERM); std::process::abort() } ["abort"] => exit(127), ["skip"] => exit(125), ["fail"] => exit(1), ["fail-if-target-is", bad_target_commit] => { if commit_to_test == *bad_target_commit { exit(1) } } ["write", path] => { fs::write(path, payload).unwrap_or_else(|_| panic!("Failed to write file {path}")); } ["jj", args @ ..] => { let jj_executable_path = PathBuf::from(env::var_os("JJ_EXECUTABLE_PATH").unwrap()); let status = std::process::Command::new(&jj_executable_path) .args(args) .status() .unwrap(); if !status.success() { eprintln!("fake-bisector: failed to run jj: {status:?}"); exit(1) } } _ => { eprintln!("fake-bisector: unexpected command: {command}"); exit(1) } } } }