//! The config file and `spectral init`, driven through the built binary. //! //! Precedence is asserted at the command line rather than in a unit test, //! because `std::env::set_var` is `unsafe` under edition 2024 and this crate //! allows no `unsafe`: the process under test is the one that sets the //! variables. mod common; use std::path::Path; use common::Fixture; fn stdout_of(output: &std::process::Output) -> String { String::from_utf8_lossy(&output.stdout).into_owned() } fn stderr_of(output: &std::process::Output) -> String { String::from_utf8_lossy(&output.stderr).into_owned() } /// A second kernel tree, so a test can tell two answers apart. fn other_tree(fixture: &Fixture) -> std::path::PathBuf { let tree = fixture.join("other-tree"); std::fs::create_dir_all(tree.join("scripts")).expect("create the other tree"); std::fs::write(tree.join("scripts/checkpatch.pl"), "#!/bin/sh\n").expect("write checkpatch"); std::fs::write(tree.join("scripts/get_maintainer.pl"), "#!/bin/sh\n") .expect("write the script"); std::fs::write(tree.join("MAINTAINERS"), "OTHER DRIVER\n").expect("write MAINTAINERS"); tree } #[test] fn a_config_file_supplies_the_tree_when_the_environment_does_not() { let fixture = Fixture::kernel_tree(); fixture.write_config(&format!( "kernel_tree = \"{}\"\npatch_dir = \"{}\"\n", fixture.tree().display(), fixture.home().join("patches").display() )); let output = fixture.cli_without_the_tree_variable(&["doctor"], &[]); assert!( output.status.success(), "{}{}", stdout_of(&output), stderr_of(&output) ); let stdout = stdout_of(&output); assert!(stdout.contains("ok kernel tree"), "{stdout}"); assert!( stdout.contains("from the config file"), "the report says the file answered: {stdout}" ); assert!( stdout.contains("ok config file"), "the file is a check of its own: {stdout}" ); } #[test] fn the_environment_beats_the_config_file() { let fixture = Fixture::kernel_tree(); fixture.write_config(&format!( "kernel_tree = \"{}\"\n", other_tree(&fixture).display() )); let output = fixture.cli_without_the_tree_variable( &["doctor"], &[( "SPECTRAL_KERNEL", fixture.tree().to_str().expect("the tree"), )], ); assert!(output.status.success(), "{}", stdout_of(&output)); let stdout = stdout_of(&output); assert!(stdout.contains("from $SPECTRAL_KERNEL"), "{stdout}"); assert!( stdout.contains(fixture.tree().to_str().expect("the tree")), "{stdout}" ); } #[test] fn the_file_beats_the_built_in_default() { let fixture = Fixture::kernel_tree(); let other = other_tree(&fixture); fixture.write_config(&format!("kernel_tree = \"{}\"\n", other.display())); let output = fixture.cli_without_the_tree_variable(&["doctor"], &[]); assert!(output.status.success(), "{}", stdout_of(&output)); let stdout = stdout_of(&output); assert!( stdout.contains(other.to_str().expect("the other tree")), "the file's tree is the one doctor looked at: {stdout}" ); assert!( !stdout.contains(".spectral/linux"), "the default was not used: {stdout}" ); } #[test] fn with_no_file_and_no_variable_the_default_is_used() { let fixture = Fixture::kernel_tree(); let output = fixture.cli_without_the_tree_variable(&["doctor"], &[]); assert_eq!(output.status.code(), Some(1)); let stdout = stdout_of(&output); assert!( stdout.contains(".spectral/linux"), "the default is $HOME/.spectral/linux: {stdout}" ); assert!(stdout.contains("the built-in default"), "{stdout}"); } #[test] fn malformed_config_is_a_named_error_rather_than_a_panic() { let fixture = Fixture::kernel_tree(); let path = fixture.write_config("kernel_tree = 42\n"); let output = fixture.cli_without_the_tree_variable(&["doctor"], &[]); assert_eq!( output.status.code(), Some(1), "a bad config is not a panic: {}{}", stdout_of(&output), stderr_of(&output) ); let stderr = stderr_of(&output); assert!(stderr.contains("is not a readable config file"), "{stderr}"); assert!(stderr.contains(&path.display().to_string()), "{stderr}"); } #[test] fn an_unknown_key_is_refused_rather_than_ignored() { let fixture = Fixture::kernel_tree(); fixture.write_config(&format!( "kernel_tree = \"{}\"\ntree = \"a typo\"\n", fixture.tree().display() )); let output = fixture.cli_without_the_tree_variable(&["doctor"], &[]); assert_eq!(output.status.code(), Some(1)); assert!( stderr_of(&output).contains("is not a readable config file"), "{}", stderr_of(&output) ); } #[test] fn a_relative_path_in_the_config_is_refused() { let fixture = Fixture::kernel_tree(); fixture.write_config("kernel_tree = \"linux\"\n"); let output = fixture.cli_without_the_tree_variable(&["doctor"], &[]); assert_eq!(output.status.code(), Some(1)); let stderr = stderr_of(&output); assert!(stderr.contains("not an absolute path"), "{stderr}"); } #[test] fn init_writes_the_config_and_a_second_run_changes_nothing() { let fixture = Fixture::kernel_tree(); let first = fixture.cli_without_the_tree_variable( &["init", "--tree", fixture.tree().to_str().expect("the tree")], &[], ); assert!( first.status.success(), "{}{}", stdout_of(&first), stderr_of(&first) ); assert!(stdout_of(&first).contains("wrote"), "{}", stdout_of(&first)); let config = fixture.home().join("config/spectral/config.toml"); let written = std::fs::read(&config).expect("the config file"); assert!( String::from_utf8_lossy(&written).contains(fixture.tree().to_str().expect("the tree")), "{}", String::from_utf8_lossy(&written) ); let second = fixture.cli_without_the_tree_variable( &["init", "--tree", fixture.tree().to_str().expect("the tree")], &[], ); assert!(second.status.success(), "{}", stderr_of(&second)); assert!( stdout_of(&second).contains("already says this"), "{}", stdout_of(&second) ); assert_eq!( std::fs::read(&config).expect("the config file"), written, "the second run rewrote the file" ); } #[test] fn init_refuses_to_clobber_a_config_that_says_something_else() { let fixture = Fixture::kernel_tree(); let other = other_tree(&fixture); fixture.write_config(&format!("kernel_tree = \"{}\"\n", other.display())); let refused = fixture.cli_without_the_tree_variable( &["init", "--tree", fixture.tree().to_str().expect("the tree")], &[], ); assert_eq!(refused.status.code(), Some(1)); let stderr = stderr_of(&refused); assert!(stderr.contains("already points somewhere else"), "{stderr}"); assert!(stderr.contains("--force"), "{stderr}"); let forced = fixture.cli_without_the_tree_variable( &[ "init", "--tree", fixture.tree().to_str().expect("the tree"), "--force", ], &[], ); assert!( forced.status.success(), "{}{}", stdout_of(&forced), stderr_of(&forced) ); let config = std::fs::read_to_string(fixture.home().join("config/spectral/config.toml")) .expect("the config file"); assert!( config.contains(fixture.tree().to_str().expect("the tree")), "{config}" ); } #[test] fn init_verifies_the_tree_it_is_pointed_at() { let fixture = Fixture::kernel_tree(); let not_a_tree = fixture.join("not-a-tree"); std::fs::create_dir_all(¬_a_tree).expect("create the directory"); let output = fixture.cli_without_the_tree_variable( &[ "init", "--tree", not_a_tree.to_str().expect("the directory"), ], &[], ); assert_eq!(output.status.code(), Some(1)); assert!( stderr_of(&output).contains("not a kernel source tree"), "{}", stderr_of(&output) ); assert!( !Path::new(&fixture.home().join("config/spectral/config.toml")).exists(), "a tree that did not verify must not be written into the config" ); } #[test] fn init_refuses_before_it_clones_anything() { // The refusal is about the config file, so it has to happen before a // multi-minute kernel clone rather than after one has already landed. let fixture = Fixture::kernel_tree(); let other = other_tree(&fixture); fixture.write_config(&format!("kernel_tree = \"{}\"\n", other.display())); let into = fixture.home().join("should-not-exist"); let output = fixture.cli_without_the_tree_variable( &[ "init", "--clone", fixture.tree().to_str().expect("the tree"), "--tree", into.to_str().expect("the destination"), ], &[], ); assert_eq!(output.status.code(), Some(1)); assert!( stderr_of(&output).contains("already points somewhere else"), "{}", stderr_of(&output) ); assert!( !into.exists(), "the clone ran before the refusal was printed" ); } #[test] fn init_clones_a_tree_it_was_given_a_url_for() { // A local path is a url git can clone, so this exercises the clone branch // without a network. let fixture = Fixture::kernel_tree(); let into = fixture.home().join("cloned"); let output = fixture.cli_without_the_tree_variable( &[ "init", "--clone", fixture.tree().to_str().expect("the tree"), "--tree", into.to_str().expect("the destination"), ], &[], ); assert!( output.status.success(), "{}{}", stdout_of(&output), stderr_of(&output) ); assert!(into.join("scripts/checkpatch.pl").is_file()); assert!(into.join("MAINTAINERS").is_file()); let config = std::fs::read_to_string(fixture.home().join("config/spectral/config.toml")) .expect("the config file"); assert!(config.contains("cloned"), "{config}"); } #[test] fn the_config_file_carries_always_cc_and_an_identity_into_submit() { let fixture = Fixture::kernel_tree(); fixture.work_on_a_branch(); fixture.write("drivers/foo/bar.c", common::EDITED); fixture.commit_all("foo: return a + 1"); assert!( fixture .cli(&["patch", "create", "000-foo"]) .status .success() ); fixture.write_config(&format!( "kernel_tree = \"{}\"\nalways_cc = [\"watcher@example.com\"]\n\n[send_email]\nfrom = \"Someone \"\n", fixture.tree().display() )); let output = fixture .cli_without_the_tree_variable(&["patch", "submit", "000-foo.patch", "--dry-run"], &[]); assert!( output.status.success(), "{}{}", stdout_of(&output), stderr_of(&output) ); let stdout = stdout_of(&output); assert!(stdout.contains("Cc: watcher@example.com"), "{stdout}"); assert!( stdout.contains("--from 'Someone '"), "{stdout}" ); assert!(stdout.contains("Dry-OK"), "{stdout}"); }