blob: 99c9c5e3e741902695318cba4d8ea21c4e5a23c0 [file] [log] [blame]
// Copyright 2020 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//! This file contains "golden" tests, which compare the output of known sample
//! `Cargo.toml` files with known fixed reference output files.
//!
//! TODO(https://fxbug.dev/42178193) move these golden specs into GN
use {
anyhow::Context,
argh::FromArgs,
// Without this, the test diffs are impractical to debug.
pretty_assertions::assert_eq,
std::fmt::{Debug, Display},
std::path::{Path, PathBuf},
};
#[derive(FromArgs, Debug)]
/// Paths to use in test. All paths are relative to where this test is executed.
///
/// These paths have to be relative when passed to this test on infra bots, so they are mapped
/// correctly, otherwise they won't be available at test runtime. It is safe to convert these to
/// absolute paths later in the test.
struct Paths {
/// path to the directory where golden tests are placed.
#[argh(option)]
test_base_dir: String,
/// path to `rustc` binary to use in test.
#[argh(option)]
rustc_binary_path: String,
/// path to `gn` binary to use in test.
#[argh(option)]
gn_binary_path: String,
/// path to `cargo` binary to use in test.
#[argh(option)]
cargo_binary_path: String,
/// path to shared libraries directory to use in test.
#[argh(option)]
lib_path: String,
}
#[derive(PartialEq, Eq)]
struct DisplayAsDebug<T: Display>(T);
impl<T: Display> Debug for DisplayAsDebug<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
Display::fmt(&self.0, f)
}
}
fn main() {
let paths: Paths = argh::from_env();
eprintln!("paths: {:?}", &paths);
// Shared library setup for Linux and Mac. Systems will ignore the settings
// that don't apply to them.
//
// These values need to be absolute so they work regardless of the current working directory.
std::env::set_var("LD_LIBRARY_PATH", Path::new(&paths.lib_path).canonicalize().unwrap());
std::env::set_var("DYLD_LIBRARY_PATH", Path::new(&paths.lib_path).canonicalize().unwrap());
// Cargo internally invokes rustc; but we must tell it to use the one from
// our sandbox, and this is configured using the env variable "RUSTC".
//
// This value needs to be absolute so it works regardless of the current working directory.
//
// See:
// https://doc.rust-lang.org/cargo/reference/environment-variables.html
std::env::set_var("RUSTC", Path::new(&paths.rustc_binary_path).canonicalize().unwrap());
#[derive(Debug, Default)]
struct Options {
/// Fuchsia SDK metadata output path; relative to the base test directory.
sdk_metadata_path: Option<Vec<&'static str>>,
/// Fuchsia SDK metadata golden path; relative to the golden files directory.
sdk_metadata_golden_path: Option<Vec<&'static str>>,
/// Extra arguments to pass to gnaw.
extra_args: Vec<&'static str>,
}
#[derive(Debug)]
struct TestCase {
/// Manifest file path (`Cargo.toml`); relative to the base test directory.
manifest_path: Vec<&'static str>,
/// Expected file (`BUILD.gn`); relative to the base test directory.
golden_expected_filename: Vec<&'static str>,
/// Extra stuff not needed for most tests.
options: Options,
}
let tests = vec![
TestCase {
manifest_path: vec!["simple", "Cargo.toml"],
golden_expected_filename: vec!["simple", "BUILD.gn"],
options: Default::default(),
},
TestCase {
manifest_path: vec!["simple_deps", "Cargo.toml"],
golden_expected_filename: vec!["simple_deps", "BUILD.gn"],
options: Default::default(),
},
TestCase {
manifest_path: vec!["simple_deps", "Cargo.toml"],
golden_expected_filename: vec!["simple_deps", "BUILD_WITH_NO_ROOT.gn"],
options: Options { extra_args: vec!["--skip-root"], ..Default::default() },
},
TestCase {
manifest_path: vec!["platform_deps", "Cargo.toml"],
golden_expected_filename: vec!["platform_deps", "BUILD.gn"],
options: Options { extra_args: vec!["--skip-root"], ..Default::default() },
},
TestCase {
manifest_path: vec!["platform_features", "Cargo.toml"],
golden_expected_filename: vec!["platform_features", "BUILD.gn"],
options: Options { extra_args: vec!["--skip-root"], ..Default::default() },
},
TestCase {
manifest_path: vec!["binary", "Cargo.toml"],
golden_expected_filename: vec!["binary", "BUILD.gn"],
options: Default::default(),
},
TestCase {
manifest_path: vec!["binary_with_tests", "Cargo.toml"],
golden_expected_filename: vec!["binary_with_tests", "BUILD.gn"],
options: Default::default(),
},
TestCase {
manifest_path: vec!["multiple_crate_types", "Cargo.toml"],
golden_expected_filename: vec!["multiple_crate_types", "BUILD.gn"],
options: Default::default(),
},
TestCase {
manifest_path: vec!["feature_review", "Cargo.toml"],
golden_expected_filename: vec!["feature_review", "BUILD.gn"],
options: Default::default(),
},
TestCase {
manifest_path: vec!["cargo_features", "Cargo.toml"],
golden_expected_filename: vec!["cargo_features", "BUILD-default.gn"],
options: Default::default(),
},
TestCase {
manifest_path: vec!["cargo_features", "Cargo.toml"],
golden_expected_filename: vec!["cargo_features", "BUILD-all-features.gn"],
options: Options { extra_args: vec!["--all-features"], ..Default::default() },
},
TestCase {
manifest_path: vec!["cargo_features", "Cargo.toml"],
golden_expected_filename: vec!["cargo_features", "BUILD-no-default-features.gn"],
options: Options { extra_args: vec!["--no-default-features"], ..Default::default() },
},
TestCase {
manifest_path: vec!["cargo_features", "Cargo.toml"],
golden_expected_filename: vec!["cargo_features", "BUILD-featurefoo.gn"],
options: Options { extra_args: vec!["--features", "featurefoo"], ..Default::default() },
},
TestCase {
manifest_path: vec!["visibility", "Cargo.toml"],
golden_expected_filename: vec!["visibility", "BUILD.gn"],
options: Options { extra_args: vec!["--skip-root"], ..Default::default() },
},
TestCase {
manifest_path: vec!["target_renaming", "Cargo.toml"],
golden_expected_filename: vec!["target_renaming", "BUILD.gn"],
options: Options { extra_args: vec!["--skip-root"], ..Default::default() },
},
TestCase {
manifest_path: vec!["sdk_metadata", "Cargo.toml"],
golden_expected_filename: vec!["sdk_metadata", "BUILD.gn"],
options: Options {
sdk_metadata_path: Some(vec!["sdk_metas", "sdk_metadata.sdk.meta.json"]),
sdk_metadata_golden_path: Some(vec![
"sdk_metadata",
"sdk_metas",
"sdk_metadata.sdk.meta.json",
]),
..Default::default()
},
},
TestCase {
manifest_path: vec!["testonly", "Cargo.toml"],
golden_expected_filename: vec!["testonly", "BUILD.gn"],
options: Options { extra_args: vec!["--skip-root"], ..Default::default() },
},
];
let run_gnaw = |manifest_path: &[&str],
extra_args: &[&str],
sdk_metadata_path: Option<&[&str]>| {
let test_dir = tempfile::TempDir::new().unwrap();
let mut manifest_path: PathBuf =
test_dir.path().join(manifest_path.iter().collect::<PathBuf>());
let output = test_dir.path().join("BUILD.gn");
let output_sdk_metadata = sdk_metadata_path.map(|sdk_metadata_path| {
test_dir.path().join(sdk_metadata_path.iter().collect::<PathBuf>())
});
// we need the emitted file to be under the same path as the gn targets it references
let test_base_dir = PathBuf::from(&paths.test_base_dir);
copy_contents(&test_base_dir, test_dir.path());
if manifest_path.file_name().unwrap() != "Cargo.toml" {
// rename manifest so that `cargo metadata` is happy.
let manifest_dest_path =
manifest_path.parent().expect("getting Cargo.toml parent dir").join("Cargo.toml");
std::fs::copy(&manifest_path, &manifest_dest_path).expect("writing Cargo.toml");
manifest_path = manifest_dest_path;
}
let project_root = test_dir.path().to_str().unwrap().to_owned();
// Note: argh does not support "--flag=value" or "--bool-flag false".
let absolute_cargo_binary_path =
Path::new(&paths.cargo_binary_path).canonicalize().unwrap();
let mut args: Vec<&str> = vec![
// args[0] is not used in arg parsing, so this can be any string.
"fake_binary_name",
"--manifest-path",
manifest_path.to_str().unwrap(),
"--project-root",
&project_root,
"--output",
output.to_str().unwrap(),
"--gn-bin",
&paths.gn_binary_path,
"--cargo",
// Cargo is not executed in another working directory by gnaw_lib, so an absolute path
// is necessary here.
absolute_cargo_binary_path.to_str().unwrap(),
];
if let Some(output_sdk_metadata) = &output_sdk_metadata {
args.extend(&[
"--output-fuchsia-sdk-metadata",
output_sdk_metadata.parent().unwrap().to_str().unwrap(),
]);
}
args.extend(extra_args);
gnaw_lib::run(&args)
.with_context(|| format!("error running gnaw with args: {:?}\n\t", &args))?;
let output = std::fs::read_to_string(&output)
.with_context(|| format!("while reading tempfile: {}", output.display()))
.expect("tempfile read success");
let output_sdk_metadata = output_sdk_metadata
.as_ref()
.map(std::fs::read_to_string)
.transpose()
.with_context(|| {
format!("while reading sdk metadata: {}", output_sdk_metadata.unwrap().display())
})
.expect("sdk metadata read success");
Result::<_, anyhow::Error>::Ok((output, output_sdk_metadata))
};
for test in tests {
let (output, output_sdk_metadata) = run_gnaw(
&test.manifest_path,
&test.options.extra_args,
test.options.sdk_metadata_path.as_deref(),
)
.with_context(|| format!("\n\ttest was: {:?}", &test))
.expect("gnaw_lib::run should succeed");
let test_base_dir = PathBuf::from(&paths.test_base_dir);
let expected_path: PathBuf =
test_base_dir.join(test.golden_expected_filename.iter().collect::<PathBuf>());
let expected = std::fs::read_to_string(expected_path.to_string_lossy().to_string())
.with_context(|| {
format!("while reading expected: {:?}", &test.golden_expected_filename)
})
.expect("expected file read success");
assert_eq!(
DisplayAsDebug(&expected),
DisplayAsDebug(&output),
"left: expected; right: actual: {:?}\n\nGenerated content:\n----------\n{}\n----------\n",
&test,
&output
);
if let Some(output_sdk_metadata) = output_sdk_metadata {
let expected_sdk_metadata_path = test_base_dir.join(
test.options.sdk_metadata_golden_path.as_ref().unwrap().iter().collect::<PathBuf>(),
);
let expected_sdk_metadata = std::fs::read_to_string(&expected_sdk_metadata_path)
.with_context(|| {
format!("while reading sdk metadata: {}", expected_sdk_metadata_path.display())
})
.expect("sdk metadata read success");
assert_eq!(
DisplayAsDebug(&expected_sdk_metadata),
DisplayAsDebug(&output_sdk_metadata),
"left: expected; right: actual: {:?}\n\nGenerated content:\n----------\n{}\n----------\n",
&test,
&output_sdk_metadata,
);
}
}
#[derive(Debug)]
struct ExpectFailCase {
/// Manifest file path (`Cargo.toml`); relative to the base test directory.
manifest_path: Vec<&'static str>,
/// Expected string to search for in returned error.
expected_error_substring: &'static str,
/// Extra arguments to pass to gnaw.
extra_args: Vec<&'static str>,
}
let tests = vec![
ExpectFailCase {
manifest_path: vec!["feature_review", "Cargo_unreviewed_feature.toml"],
expected_error_substring:
"crate_with_features 0.1.0 is included with unreviewed features [\"feature1\"]",
extra_args: vec![],
},
ExpectFailCase {
manifest_path: vec!["feature_review", "Cargo_missing_review.toml"],
expected_error_substring:
"crate_with_features 0.1.0 requires feature review but reviewed features not found",
extra_args: vec![],
},
ExpectFailCase {
manifest_path: vec!["feature_review", "Cargo_extra_review.toml"],
expected_error_substring:
"crate_with_features 0.1.0 sets reviewed_features but crate_with_features was not found in require_feature_reviews",
extra_args: vec![],
},
];
for test in tests {
let result = run_gnaw(&test.manifest_path, &test.extra_args, None);
let error = match result {
Ok(_) => panic!("gnaw unexpectedly succeeded for {:?}", test),
Err(e) => e,
};
if error.chain().find(|e| e.to_string().contains(test.expected_error_substring)).is_none() {
panic!(
"expected error to contain {:?}, was: {:?}",
test.expected_error_substring, error
);
}
}
}
fn copy_contents(original_test_dir: &Path, test_dir_path: &Path) {
// copy the contents of original test dir to test_dir
for entry in walkdir::WalkDir::new(&original_test_dir) {
let entry = entry.expect("walking original test directory to copy files to /tmp");
if !entry.file_type().is_file() {
continue;
}
let to_copy = entry.path();
let destination = test_dir_path.join(to_copy.strip_prefix(&original_test_dir).unwrap());
std::fs::create_dir_all(destination.parent().unwrap())
.expect("making parent of file to copy");
std::fs::copy(to_copy, destination).expect("copying file");
}
println!("done copying files");
}