mirror of https://github.com/tracel-ai/burn.git
[xtask] New commands: dependencies and vulnerabilities (#1181)
* [xtask] Add support for vulnerabilities check in xtask cargo +nightly xtask vulnerability --help * [xtask] Add support for dependencies check in xtask cargo xtask dependencies --help * Make sure all vulnerabilities checks are called with cargo +nightly * Fix clippy errors * Use automatic links in docstrings * Move run function to the top of the file * Skip dependencies documenation * pub -> pub(crate) * Change return type of Sanitizer::flags to &str * Move the run functions as an impl function of check types enums * Remove Type suffix in check type enums * Fix wrong variable name * cargo_commande -> cargo_crate * Improve robustness of is_target_supported and add tests * Reorganize utils module into a directory
This commit is contained in:
parent
b9bd42959b
commit
3814c4c9fb
|
@ -12,3 +12,6 @@ clap = { version = "4.4.8", features = ["derive"] }
|
|||
env_logger = "0.10.0"
|
||||
log = "0.4.17"
|
||||
serde_json = { version = "1" }
|
||||
|
||||
[dev-dependencies]
|
||||
rstest.workspace = true
|
||||
|
|
|
@ -0,0 +1,106 @@
|
|||
use std::{collections::HashMap, time::Instant};
|
||||
|
||||
use crate::{
|
||||
endgroup, group,
|
||||
logging::init_logger,
|
||||
utils::{
|
||||
cargo::{ensure_cargo_crate_is_installed, run_cargo},
|
||||
rustup::is_current_toolchain_nightly,
|
||||
time::format_duration,
|
||||
Params,
|
||||
},
|
||||
};
|
||||
|
||||
#[derive(clap::ValueEnum, Default, Copy, Clone, PartialEq, Eq)]
|
||||
pub(crate) enum DependencyCheck {
|
||||
/// Run all dependency checks.
|
||||
#[default]
|
||||
All,
|
||||
/// Perform an audit of all dependencies using the cargo-audit crate `<https://crates.io/crates/cargo-audit>`
|
||||
Audit,
|
||||
/// Run cargo-deny check `<https://crates.io/crates/cargo-deny>`
|
||||
Deny,
|
||||
/// Run cargo-udeps to find unused dependencies `<https://crates.io/crates/cargo-udeps>`
|
||||
Unused,
|
||||
}
|
||||
|
||||
impl DependencyCheck {
|
||||
pub(crate) fn run(&self) -> anyhow::Result<()> {
|
||||
// Setup logger
|
||||
init_logger().init();
|
||||
// Start time measurement
|
||||
let start = Instant::now();
|
||||
match self {
|
||||
Self::Audit => cargo_audit(),
|
||||
Self::Deny => cargo_deny(),
|
||||
Self::Unused => cargo_udeps(),
|
||||
Self::All => {
|
||||
cargo_audit();
|
||||
cargo_deny();
|
||||
cargo_udeps();
|
||||
}
|
||||
}
|
||||
|
||||
// Stop time measurement
|
||||
//
|
||||
// Compute runtime duration
|
||||
let duration = start.elapsed();
|
||||
|
||||
// Print duration
|
||||
info!(
|
||||
"\x1B[32;1mTime elapsed for the current execution: {}\x1B[0m",
|
||||
format_duration(&duration)
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Run cargo-audit
|
||||
fn cargo_audit() {
|
||||
ensure_cargo_crate_is_installed("cargo-audit");
|
||||
// Run cargo audit
|
||||
group!("Cargo: run audit checks");
|
||||
run_cargo(
|
||||
"audit",
|
||||
Params::from([]),
|
||||
HashMap::new(),
|
||||
"Cargo audit should be installed and it should correctly run",
|
||||
);
|
||||
endgroup!();
|
||||
}
|
||||
|
||||
/// Run cargo-deny
|
||||
fn cargo_deny() {
|
||||
ensure_cargo_crate_is_installed("cargo-deny");
|
||||
// Run cargo deny
|
||||
group!("Cargo: run deny checks");
|
||||
run_cargo(
|
||||
"deny",
|
||||
Params::from(["check"]),
|
||||
HashMap::new(),
|
||||
"Cargo deny should be installed and it should correctly run",
|
||||
);
|
||||
endgroup!();
|
||||
}
|
||||
|
||||
/// Run cargo-udeps
|
||||
fn cargo_udeps() {
|
||||
if is_current_toolchain_nightly() {
|
||||
ensure_cargo_crate_is_installed("cargo-udeps");
|
||||
// Run cargo udeps
|
||||
group!("Cargo: run unused dependencies checks");
|
||||
run_cargo(
|
||||
"udeps",
|
||||
Params::from([]),
|
||||
HashMap::new(),
|
||||
"Cargo udeps should be installed and it should correctly run",
|
||||
);
|
||||
endgroup!();
|
||||
} else {
|
||||
error!(
|
||||
"You must use 'cargo +nightly' to check for unused dependencies.
|
||||
Install a nightly toolchain with 'rustup toolchain install nightly'."
|
||||
)
|
||||
}
|
||||
}
|
|
@ -1,9 +1,11 @@
|
|||
use clap::{Parser, Subcommand};
|
||||
|
||||
mod dependencies;
|
||||
mod logging;
|
||||
mod publish;
|
||||
mod runchecks;
|
||||
mod utils;
|
||||
mod vulnerabilities;
|
||||
|
||||
#[macro_use]
|
||||
extern crate log;
|
||||
|
@ -17,6 +19,11 @@ struct Args {
|
|||
|
||||
#[derive(Subcommand)]
|
||||
enum Command {
|
||||
/// Run the specified dependencies check locally
|
||||
Dependencies {
|
||||
/// The dependency check to run
|
||||
dependency_check: dependencies::DependencyCheck,
|
||||
},
|
||||
/// Publish a crate to crates.io
|
||||
Publish {
|
||||
/// The name of the crate to publish on crates.io
|
||||
|
@ -27,13 +34,23 @@ enum Command {
|
|||
/// The environment to run checks against
|
||||
env: runchecks::CheckType,
|
||||
},
|
||||
/// Run the specified vulnerability check locally. These commands must be called with 'cargo +nightly'.
|
||||
Vulnerabilities {
|
||||
/// The vulnerability check to run.
|
||||
/// For the reference visit the page `<https://doc.rust-lang.org/beta/unstable-book/compiler-flags/sanitizer.html>`
|
||||
vulnerability_check: vulnerabilities::VulnerabilityCheck,
|
||||
},
|
||||
}
|
||||
|
||||
fn main() -> anyhow::Result<()> {
|
||||
let args = Args::parse();
|
||||
|
||||
match args.command {
|
||||
Command::RunChecks { env } => runchecks::run(env),
|
||||
Command::Dependencies { dependency_check } => dependency_check.run(),
|
||||
Command::Publish { name } => publish::run(name),
|
||||
Command::RunChecks { env } => env.run(),
|
||||
Command::Vulnerabilities {
|
||||
vulnerability_check,
|
||||
} => vulnerability_check.run(),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,11 +5,16 @@
|
|||
//! It is also used to check that the code is formatted correctly and passes clippy.
|
||||
|
||||
use crate::logging::init_logger;
|
||||
use crate::utils::{format_duration, get_workspaces, WorkspaceMemberType};
|
||||
use crate::utils::cargo::{run_cargo, run_cargo_with_path};
|
||||
use crate::utils::process::{handle_child_process, run_command};
|
||||
use crate::utils::rustup::{rustup_add_component, rustup_add_target};
|
||||
use crate::utils::time::format_duration;
|
||||
use crate::utils::workspace::{get_workspaces, WorkspaceMemberType};
|
||||
use crate::utils::Params;
|
||||
use crate::{endgroup, group};
|
||||
use std::collections::HashMap;
|
||||
use std::env;
|
||||
use std::path::Path;
|
||||
use std::process::{Child, Command, Stdio};
|
||||
use std::process::{Command, Stdio};
|
||||
use std::str;
|
||||
use std::time::Instant;
|
||||
|
||||
|
@ -17,124 +22,110 @@ use std::time::Instant;
|
|||
const WASM32_TARGET: &str = "wasm32-unknown-unknown";
|
||||
const ARM_TARGET: &str = "thumbv7m-none-eabi";
|
||||
|
||||
// Handle child process
|
||||
fn handle_child_process(mut child: Child, error: &str) {
|
||||
// Wait for the child process to finish
|
||||
let status = child.wait().expect(error);
|
||||
#[derive(clap::ValueEnum, Default, Copy, Clone, PartialEq, Eq)]
|
||||
pub(crate) enum CheckType {
|
||||
/// Run all checks.
|
||||
#[default]
|
||||
All,
|
||||
/// Run `std` environment checks
|
||||
Std,
|
||||
/// Run `no-std` environment checks
|
||||
NoStd,
|
||||
/// Check for typos
|
||||
Typos,
|
||||
/// Test the examples
|
||||
Examples,
|
||||
}
|
||||
|
||||
// If exit status is not a success, terminate the process with an error
|
||||
if !status.success() {
|
||||
// Use the exit code associated to a command to terminate the process,
|
||||
// if any exit code had been found, use the default value 1
|
||||
std::process::exit(status.code().unwrap_or(1));
|
||||
impl CheckType {
|
||||
pub(crate) fn run(&self) -> anyhow::Result<()> {
|
||||
// Setup logger
|
||||
init_logger().init();
|
||||
|
||||
// Start time measurement
|
||||
let start = Instant::now();
|
||||
|
||||
// The environment can assume ONLY "std", "no_std", "typos", "examples"
|
||||
//
|
||||
// Depending on the input argument, the respective environment checks
|
||||
// are run.
|
||||
//
|
||||
// If no environment has been passed, run all checks.
|
||||
match self {
|
||||
Self::Std => std_checks(),
|
||||
Self::NoStd => no_std_checks(),
|
||||
Self::Typos => check_typos(),
|
||||
Self::Examples => check_examples(),
|
||||
Self::All => {
|
||||
/* Run all checks */
|
||||
check_typos();
|
||||
std_checks();
|
||||
no_std_checks();
|
||||
check_examples();
|
||||
}
|
||||
}
|
||||
|
||||
// Stop time measurement
|
||||
//
|
||||
// Compute runtime duration
|
||||
let duration = start.elapsed();
|
||||
|
||||
// Print duration
|
||||
info!(
|
||||
"\x1B[32;1mTime elapsed for the current execution: {}\x1B[0m",
|
||||
format_duration(&duration)
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
// Run a command
|
||||
fn run_command(command: &str, args: &[&str], command_error: &str, child_error: &str) {
|
||||
// Format command
|
||||
info!("{command} {}\n\n", args.join(" "));
|
||||
|
||||
// Run command as child process
|
||||
let command = Command::new(command)
|
||||
.args(args)
|
||||
.stdout(Stdio::inherit()) // Send stdout directly to terminal
|
||||
.stderr(Stdio::inherit()) // Send stderr directly to terminal
|
||||
.spawn()
|
||||
.expect(command_error);
|
||||
|
||||
// Handle command child process
|
||||
handle_child_process(command, child_error);
|
||||
}
|
||||
|
||||
// Define and run rustup command
|
||||
fn rustup(command: &str, target: &str) {
|
||||
group!("Rustup: {} add {}", command, target);
|
||||
run_command(
|
||||
"rustup",
|
||||
&[command, "add", target],
|
||||
"Failed to run rustup",
|
||||
"Failed to wait for rustup child process",
|
||||
);
|
||||
endgroup!();
|
||||
}
|
||||
|
||||
// Define and run a cargo command
|
||||
fn run_cargo(command: &str, params: Params, error: &str) {
|
||||
run_cargo_with_path::<String>(command, params, None, error)
|
||||
}
|
||||
|
||||
// Define and run a cargo command with curr dir
|
||||
fn run_cargo_with_path<P: AsRef<Path>>(
|
||||
command: &str,
|
||||
params: Params,
|
||||
path: Option<P>,
|
||||
error: &str,
|
||||
) {
|
||||
// Print cargo command
|
||||
info!("cargo {} {}\n", command, params);
|
||||
|
||||
// Run cargo
|
||||
let mut cargo = Command::new("cargo");
|
||||
cargo
|
||||
.env("CARGO_INCREMENTAL", "0")
|
||||
.arg(command)
|
||||
.args(params.params)
|
||||
.stdout(Stdio::inherit()) // Send stdout directly to terminal
|
||||
.stderr(Stdio::inherit()); // Send stderr directly to terminal
|
||||
|
||||
if let Some(path) = path {
|
||||
cargo.current_dir(path);
|
||||
}
|
||||
|
||||
let cargo_process = cargo.spawn().expect(error);
|
||||
|
||||
// Handle cargo child process
|
||||
handle_child_process(cargo_process, "Failed to wait for cargo child process");
|
||||
}
|
||||
|
||||
// Run cargo build command
|
||||
/// Run cargo build command
|
||||
fn cargo_build(params: Params) {
|
||||
// Run cargo build
|
||||
run_cargo(
|
||||
"build",
|
||||
params + "--color=always",
|
||||
HashMap::new(),
|
||||
"Failed to run cargo build",
|
||||
);
|
||||
}
|
||||
|
||||
// Run cargo install command
|
||||
/// Run cargo install command
|
||||
fn cargo_install(params: Params) {
|
||||
// Run cargo install
|
||||
run_cargo(
|
||||
"install",
|
||||
params + "--color=always",
|
||||
HashMap::new(),
|
||||
"Failed to run cargo install",
|
||||
);
|
||||
}
|
||||
|
||||
// Run cargo test command
|
||||
/// Run cargo test command
|
||||
fn cargo_test(params: Params) {
|
||||
// Run cargo test
|
||||
run_cargo(
|
||||
"test",
|
||||
params + "--color=always" + "--" + "--color=always",
|
||||
HashMap::new(),
|
||||
"Failed to run cargo test",
|
||||
);
|
||||
}
|
||||
|
||||
// Run cargo fmt command
|
||||
/// Run cargo fmt command
|
||||
fn cargo_fmt() {
|
||||
group!("Cargo: fmt");
|
||||
run_cargo(
|
||||
"fmt",
|
||||
["--check", "--all", "--", "--color=always"].into(),
|
||||
HashMap::new(),
|
||||
"Failed to run cargo fmt",
|
||||
);
|
||||
endgroup!();
|
||||
}
|
||||
|
||||
// Run cargo clippy command
|
||||
/// Run cargo clippy command
|
||||
fn cargo_clippy() {
|
||||
if std::env::var("CI").is_ok() {
|
||||
return;
|
||||
|
@ -143,14 +134,20 @@ fn cargo_clippy() {
|
|||
run_cargo(
|
||||
"clippy",
|
||||
["--color=always", "--all-targets", "--", "-D", "warnings"].into(),
|
||||
HashMap::new(),
|
||||
"Failed to run cargo clippy",
|
||||
);
|
||||
}
|
||||
|
||||
// Run cargo doc command
|
||||
/// Run cargo doc command
|
||||
fn cargo_doc(params: Params) {
|
||||
// Run cargo doc
|
||||
run_cargo("doc", params + "--color=always", "Failed to run cargo doc");
|
||||
run_cargo(
|
||||
"doc",
|
||||
params + "--color=always",
|
||||
HashMap::new(),
|
||||
"Failed to run cargo doc",
|
||||
);
|
||||
}
|
||||
|
||||
// Build and test a crate in a no_std environment
|
||||
|
@ -191,7 +188,7 @@ fn build_and_test_no_std<const N: usize>(crate_name: &str, extra_args: [&str; N]
|
|||
// Setup code coverage
|
||||
fn setup_coverage() {
|
||||
// Install llvm-tools-preview
|
||||
rustup("component", "llvm-tools-preview");
|
||||
rustup_add_component("llvm-tools-preview");
|
||||
|
||||
// Set coverage environment variables
|
||||
env::set_var("RUSTFLAGS", "-Cinstrument-coverage");
|
||||
|
@ -226,10 +223,10 @@ fn run_grcov() {
|
|||
// Run no_std checks
|
||||
fn no_std_checks() {
|
||||
// Install wasm32 target
|
||||
rustup("target", WASM32_TARGET);
|
||||
rustup_add_target(WASM32_TARGET);
|
||||
|
||||
// Install ARM target
|
||||
rustup("target", ARM_TARGET);
|
||||
rustup_add_target(ARM_TARGET);
|
||||
|
||||
// Run checks for the following crates
|
||||
build_and_test_no_std("burn", []);
|
||||
|
@ -279,7 +276,7 @@ fn burn_dataset_features_std() {
|
|||
cargo_test(["-p", "burn-dataset", "--all-features"].into());
|
||||
|
||||
// Run cargo doc --all-features
|
||||
cargo_doc(["-p", "burn-dataset", "--all-features"].into());
|
||||
cargo_doc(["-p", "burn-dataset", "--all-features", "--no-deps"].into());
|
||||
|
||||
endgroup!();
|
||||
}
|
||||
|
@ -315,7 +312,7 @@ fn std_checks() {
|
|||
|
||||
// Produce documentation for each workspace
|
||||
group!("Docs: workspaces");
|
||||
cargo_doc(["--workspace"].into());
|
||||
cargo_doc(["--workspace", "--no-deps"].into());
|
||||
endgroup!();
|
||||
|
||||
// Setup code coverage
|
||||
|
@ -394,102 +391,10 @@ fn check_examples() {
|
|||
run_cargo_with_path(
|
||||
"check",
|
||||
["--examples"].into(),
|
||||
HashMap::new(),
|
||||
Some(workspace.path),
|
||||
"Failed to check example",
|
||||
);
|
||||
endgroup!();
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(clap::ValueEnum, Default, Copy, Clone, PartialEq, Eq)]
|
||||
pub enum CheckType {
|
||||
/// Run all checks.
|
||||
#[default]
|
||||
All,
|
||||
/// Run `std` environment checks
|
||||
Std,
|
||||
/// Run `no-std` environment checks
|
||||
NoStd,
|
||||
/// Check for typos
|
||||
Typos,
|
||||
/// Test the examples
|
||||
Examples,
|
||||
}
|
||||
|
||||
pub fn run(env: CheckType) -> anyhow::Result<()> {
|
||||
// Setup logger
|
||||
init_logger().init();
|
||||
|
||||
// Start time measurement
|
||||
let start = Instant::now();
|
||||
|
||||
// The environment can assume ONLY "std", "no_std", "typos", "examples"
|
||||
// as values.
|
||||
//
|
||||
// Depending on the input argument, the respective environment checks
|
||||
// are run.
|
||||
//
|
||||
// If no environment has been passed, run all checks.
|
||||
match env {
|
||||
CheckType::Std => std_checks(),
|
||||
CheckType::NoStd => no_std_checks(),
|
||||
CheckType::Typos => check_typos(),
|
||||
CheckType::Examples => check_examples(),
|
||||
CheckType::All => {
|
||||
/* Run all checks */
|
||||
check_typos();
|
||||
std_checks();
|
||||
no_std_checks();
|
||||
check_examples();
|
||||
}
|
||||
}
|
||||
|
||||
// Stop time measurement
|
||||
//
|
||||
// Compute runtime duration
|
||||
let duration = start.elapsed();
|
||||
|
||||
// Print duration
|
||||
info!(
|
||||
"\x1B[32;1mTime elapsed for the current execution: {}\x1B[0m",
|
||||
format_duration(&duration)
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
struct Params {
|
||||
params: Vec<String>,
|
||||
}
|
||||
|
||||
impl<const N: usize> From<[&str; N]> for Params {
|
||||
fn from(value: [&str; N]) -> Self {
|
||||
Self {
|
||||
params: value.iter().map(|v| v.to_string()).collect(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&str> for Params {
|
||||
fn from(value: &str) -> Self {
|
||||
Self {
|
||||
params: vec![value.to_string()],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for Params {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.write_str(self.params.join(" ").as_str())
|
||||
}
|
||||
}
|
||||
|
||||
impl<Rhs: Into<Params>> std::ops::Add<Rhs> for Params {
|
||||
type Output = Params;
|
||||
|
||||
fn add(mut self, rhs: Rhs) -> Self::Output {
|
||||
let rhs: Params = rhs.into();
|
||||
self.params.extend(rhs.params);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,66 @@
|
|||
use std::{
|
||||
collections::HashMap,
|
||||
path::Path,
|
||||
process::{Command, Stdio},
|
||||
};
|
||||
|
||||
use crate::{endgroup, group, utils::process::handle_child_process};
|
||||
|
||||
use super::Params;
|
||||
|
||||
/// Run a cargo command
|
||||
pub(crate) fn run_cargo(command: &str, params: Params, envs: HashMap<&str, String>, error: &str) {
|
||||
run_cargo_with_path::<String>(command, params, envs, None, error)
|
||||
}
|
||||
|
||||
/// Run acargo command with the passed directory as the current directory
|
||||
pub(crate) fn run_cargo_with_path<P: AsRef<Path>>(
|
||||
command: &str,
|
||||
params: Params,
|
||||
envs: HashMap<&str, String>,
|
||||
path: Option<P>,
|
||||
error: &str,
|
||||
) {
|
||||
info!("cargo {} {}\n", command, params.params.join(" "));
|
||||
let mut cargo = Command::new("cargo");
|
||||
cargo
|
||||
.env("CARGO_INCREMENTAL", "0")
|
||||
.envs(&envs)
|
||||
.arg(command)
|
||||
.args(¶ms.params)
|
||||
.stdout(Stdio::inherit()) // Send stdout directly to terminal
|
||||
.stderr(Stdio::inherit()); // Send stderr directly to terminal
|
||||
|
||||
if let Some(path) = path {
|
||||
cargo.current_dir(path);
|
||||
}
|
||||
|
||||
// Handle cargo child process
|
||||
let cargo_process = cargo.spawn().expect(error);
|
||||
handle_child_process(cargo_process, "Cargo process should run flawlessly");
|
||||
}
|
||||
|
||||
/// Ensure that a cargo crate is installed
|
||||
pub(crate) fn ensure_cargo_crate_is_installed(crate_name: &str) {
|
||||
if !is_cargo_crate_installed(crate_name) {
|
||||
group!("Cargo: install {} crate_name", crate_name);
|
||||
run_cargo(
|
||||
"install",
|
||||
[crate_name].into(),
|
||||
HashMap::new(),
|
||||
&format!("{} should be installed", crate_name),
|
||||
);
|
||||
endgroup!();
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns true if the passed cargo crate is installed locally
|
||||
fn is_cargo_crate_installed(crate_name: &str) -> bool {
|
||||
let output = Command::new("cargo")
|
||||
.arg("install")
|
||||
.arg("--list")
|
||||
.output()
|
||||
.expect("Should get the list of installed cargo commands");
|
||||
let output_str = String::from_utf8_lossy(&output.stdout);
|
||||
output_str.lines().any(|line| line.contains(crate_name))
|
||||
}
|
|
@ -0,0 +1,49 @@
|
|||
pub(crate) mod cargo;
|
||||
pub(crate) mod process;
|
||||
pub(crate) mod rustup;
|
||||
pub(crate) mod time;
|
||||
pub(crate) mod workspace;
|
||||
|
||||
pub(crate) struct Params {
|
||||
params: Vec<String>,
|
||||
}
|
||||
|
||||
impl<const N: usize> From<[&str; N]> for Params {
|
||||
fn from(value: [&str; N]) -> Self {
|
||||
Self {
|
||||
params: value.iter().map(|v| v.to_string()).collect(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&str> for Params {
|
||||
fn from(value: &str) -> Self {
|
||||
Self {
|
||||
params: vec![value.to_string()],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Vec<&str>> for Params {
|
||||
fn from(value: Vec<&str>) -> Self {
|
||||
Self {
|
||||
params: value.iter().map(|s| s.to_string()).collect(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for Params {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.write_str(self.params.join(" ").as_str())
|
||||
}
|
||||
}
|
||||
|
||||
impl<Rhs: Into<Params>> std::ops::Add<Rhs> for Params {
|
||||
type Output = Params;
|
||||
|
||||
fn add(mut self, rhs: Rhs) -> Self::Output {
|
||||
let rhs: Params = rhs.into();
|
||||
self.params.extend(rhs.params);
|
||||
self
|
||||
}
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
use std::process::{Child, Command, Stdio};
|
||||
|
||||
// Handle child process
|
||||
pub(crate) fn handle_child_process(mut child: Child, error: &str) {
|
||||
// Wait for the child process to finish
|
||||
let status = child.wait().expect(error);
|
||||
|
||||
// If exit status is not a success, terminate the process with an error
|
||||
if !status.success() {
|
||||
// Use the exit code associated to a command to terminate the process,
|
||||
// if any exit code had been found, use the default value 1
|
||||
std::process::exit(status.code().unwrap_or(1));
|
||||
}
|
||||
}
|
||||
|
||||
// Run a command
|
||||
pub(crate) fn run_command(command: &str, args: &[&str], command_error: &str, child_error: &str) {
|
||||
// Format command
|
||||
info!("{command} {}\n\n", args.join(" "));
|
||||
|
||||
// Run command as child process
|
||||
let command = Command::new(command)
|
||||
.args(args)
|
||||
.stdout(Stdio::inherit()) // Send stdout directly to terminal
|
||||
.stderr(Stdio::inherit()) // Send stderr directly to terminal
|
||||
.spawn()
|
||||
.expect(command_error);
|
||||
|
||||
// Handle command child process
|
||||
handle_child_process(command, child_error);
|
||||
}
|
|
@ -0,0 +1,68 @@
|
|||
use std::process::{Command, Stdio};
|
||||
|
||||
use crate::{endgroup, group, utils::process::handle_child_process};
|
||||
|
||||
use super::Params;
|
||||
|
||||
/// Run rustup command
|
||||
pub(crate) fn rustup(command: &str, params: Params, expected: &str) {
|
||||
info!("rustup {} {}\n", command, params);
|
||||
// Run rustup
|
||||
let mut rustup = Command::new("rustup");
|
||||
rustup
|
||||
.arg(command)
|
||||
.args(params.params)
|
||||
.stdout(Stdio::inherit()) // Send stdout directly to terminal
|
||||
.stderr(Stdio::inherit()); // Send stderr directly to terminal
|
||||
let cargo_process = rustup.spawn().expect(expected);
|
||||
handle_child_process(cargo_process, "Failed to wait for rustup child process");
|
||||
}
|
||||
|
||||
/// Add a Rust target
|
||||
pub(crate) fn rustup_add_target(target: &str) {
|
||||
group!("Rustup: add target {}", target);
|
||||
rustup(
|
||||
"target",
|
||||
Params::from(["add", target]),
|
||||
"Target should be added",
|
||||
);
|
||||
endgroup!();
|
||||
}
|
||||
|
||||
/// Add a Rust component
|
||||
pub(crate) fn rustup_add_component(component: &str) {
|
||||
group!("Rustup: add component {}", component);
|
||||
rustup(
|
||||
"component",
|
||||
Params::from(["add", component]),
|
||||
"Component should be added",
|
||||
);
|
||||
endgroup!();
|
||||
}
|
||||
|
||||
// Returns the output of the rustup command to get the installed targets
|
||||
pub(crate) fn rustup_get_installed_targets() -> String {
|
||||
let output = Command::new("rustup")
|
||||
.args(["target", "list", "--installed"])
|
||||
.stdout(Stdio::piped())
|
||||
.output()
|
||||
.expect("Rustup command should execute successfully");
|
||||
String::from_utf8(output.stdout).expect("Output should be valid UTF-8")
|
||||
}
|
||||
|
||||
/// Returns true if the current toolchain is the nightly
|
||||
pub(crate) fn is_current_toolchain_nightly() -> bool {
|
||||
let output = Command::new("rustup")
|
||||
.arg("show")
|
||||
.output()
|
||||
.expect("Should get the list of installed Rust toolchains");
|
||||
let output_str = String::from_utf8_lossy(&output.stdout);
|
||||
for line in output_str.lines() {
|
||||
// look for the "rustc.*-nightly" line
|
||||
if line.contains("rustc") && line.contains("-nightly") {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
// assume we are using a stable toolchain if we did not find the nightly compiler
|
||||
false
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
use std::time::Duration;
|
||||
|
||||
/// Print duration as HH:MM:SS format
|
||||
pub(crate) fn format_duration(duration: &Duration) -> String {
|
||||
let seconds = duration.as_secs();
|
||||
let minutes = seconds / 60;
|
||||
let hours = minutes / 60;
|
||||
let remaining_minutes = minutes % 60;
|
||||
let remaining_seconds = seconds % 60;
|
||||
|
||||
format!(
|
||||
"{:02}:{:02}:{:02}",
|
||||
hours, remaining_minutes, remaining_seconds
|
||||
)
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
use std::process::Command;
|
||||
|
||||
use serde_json::Value;
|
||||
use std::{process::Command, time::Duration};
|
||||
|
||||
pub(crate) enum WorkspaceMemberType {
|
||||
Crate,
|
||||
|
@ -70,17 +71,3 @@ pub(crate) fn get_workspaces(w_type: WorkspaceMemberType) -> Vec<WorkspaceMember
|
|||
|
||||
workspaces
|
||||
}
|
||||
|
||||
/// Print duration as HH:MM:SS format
|
||||
pub(crate) fn format_duration(duration: &Duration) -> String {
|
||||
let seconds = duration.as_secs();
|
||||
let minutes = seconds / 60;
|
||||
let hours = minutes / 60;
|
||||
let remaining_minutes = minutes % 60;
|
||||
let remaining_seconds = seconds % 60;
|
||||
|
||||
format!(
|
||||
"{:02}:{:02}:{:02}",
|
||||
hours, remaining_minutes, remaining_seconds
|
||||
)
|
||||
}
|
|
@ -0,0 +1,396 @@
|
|||
use std::collections::HashMap;
|
||||
use std::time::Instant;
|
||||
|
||||
use crate::logging::init_logger;
|
||||
use crate::utils::cargo::{ensure_cargo_crate_is_installed, run_cargo};
|
||||
use crate::utils::rustup::{
|
||||
is_current_toolchain_nightly, rustup_add_component, rustup_get_installed_targets,
|
||||
};
|
||||
use crate::utils::time::format_duration;
|
||||
use crate::utils::Params;
|
||||
use crate::{endgroup, group};
|
||||
use std::fmt;
|
||||
|
||||
#[derive(clap::ValueEnum, Default, Copy, Clone, PartialEq, Eq)]
|
||||
pub(crate) enum VulnerabilityCheck {
|
||||
/// Run all most useful vulnerability checks.
|
||||
#[default]
|
||||
All,
|
||||
/// Run Address sanitizer (memory error detector)
|
||||
AddressSanitizer,
|
||||
/// Run LLVM Control Flow Integrity (CFI) (provides forward-edge control flow protection)
|
||||
ControlFlowIntegrity,
|
||||
/// Run newer variant of Address sanitizer (memory error detector similar to AddressSanitizer, but based on partial hardware assistance)
|
||||
HWAddressSanitizer,
|
||||
/// Run Kernel LLVM Control Flow Integrity (KCFI) (provides forward-edge control flow protection for operating systems kernels)
|
||||
KernelControlFlowIntegrity,
|
||||
/// Run Leak sanitizer (run-time memory leak detector)
|
||||
LeakSanitizer,
|
||||
/// Run memory sanitizer (detector of uninitialized reads)
|
||||
MemorySanitizer,
|
||||
/// Run another address sanitizer (like AddressSanitizer and HardwareAddressSanitizer but with lower overhead suitable for use as hardening for production binaries)
|
||||
MemTagSanitizer,
|
||||
/// Run nightly-only checks through cargo-careful `<https://crates.io/crates/cargo-careful>`
|
||||
NightlyChecks,
|
||||
/// Run SafeStack check (provides backward-edge control flow protection by separating
|
||||
/// stack into safe and unsafe regions)
|
||||
SafeStack,
|
||||
/// Run ShadowCall check (provides backward-edge control flow protection - aarch64 only)
|
||||
ShadowCallStack,
|
||||
/// Run Thread sanitizer (data race detector)
|
||||
ThreadSanitizer,
|
||||
}
|
||||
|
||||
impl VulnerabilityCheck {
|
||||
pub(crate) fn run(&self) -> anyhow::Result<()> {
|
||||
// Setup logger
|
||||
init_logger().init();
|
||||
// Start time measurement
|
||||
let start = Instant::now();
|
||||
match self {
|
||||
Self::NightlyChecks => cargo_careful(),
|
||||
Self::AddressSanitizer => Sanitizer::Address.run_tests(),
|
||||
Self::ControlFlowIntegrity => Sanitizer::CFI.run_tests(),
|
||||
Self::HWAddressSanitizer => Sanitizer::HWAddress.run_tests(),
|
||||
Self::KernelControlFlowIntegrity => Sanitizer::KCFI.run_tests(),
|
||||
Self::LeakSanitizer => Sanitizer::Leak.run_tests(),
|
||||
Self::MemorySanitizer => Sanitizer::Memory.run_tests(),
|
||||
Self::MemTagSanitizer => Sanitizer::MemTag.run_tests(),
|
||||
Self::SafeStack => Sanitizer::SafeStack.run_tests(),
|
||||
Self::ShadowCallStack => Sanitizer::ShadowCallStack.run_tests(),
|
||||
Self::ThreadSanitizer => Sanitizer::Thread.run_tests(),
|
||||
Self::All => {
|
||||
cargo_careful();
|
||||
Sanitizer::Address.run_tests();
|
||||
Sanitizer::Leak.run_tests();
|
||||
Sanitizer::Memory.run_tests();
|
||||
Sanitizer::SafeStack.run_tests();
|
||||
Sanitizer::Thread.run_tests();
|
||||
}
|
||||
}
|
||||
|
||||
// Stop time measurement
|
||||
//
|
||||
// Compute runtime duration
|
||||
let duration = start.elapsed();
|
||||
|
||||
// Print duration
|
||||
info!(
|
||||
"\x1B[32;1mTime elapsed for the current execution: {}\x1B[0m",
|
||||
format_duration(&duration)
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Run cargo-careful
|
||||
fn cargo_careful() {
|
||||
if is_current_toolchain_nightly() {
|
||||
ensure_cargo_crate_is_installed("cargo-careful");
|
||||
rustup_add_component("rust-src");
|
||||
// prepare careful sysroot
|
||||
group!("Cargo: careful setup");
|
||||
run_cargo(
|
||||
"careful",
|
||||
Params::from(["setup"]),
|
||||
HashMap::new(),
|
||||
"Cargo sysroot should be available",
|
||||
);
|
||||
endgroup!();
|
||||
// Run cargo careful
|
||||
group!("Cargo: run careful checks");
|
||||
run_cargo(
|
||||
"careful",
|
||||
Params::from(["test"]),
|
||||
HashMap::new(),
|
||||
"Cargo careful should be installed and it should correctly run",
|
||||
);
|
||||
endgroup!();
|
||||
} else {
|
||||
error!(
|
||||
"You must use 'cargo +nightly' to run nightly checks.
|
||||
Install a nightly toolchain with 'rustup toolchain install nightly'."
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Represents the various sanitizer available in nightly compiler
|
||||
// source: https://doc.rust-lang.org/beta/unstable-book/compiler-flags/sanitizer.html
|
||||
#[allow(clippy::upper_case_acronyms)]
|
||||
enum Sanitizer {
|
||||
Address,
|
||||
CFI,
|
||||
HWAddress,
|
||||
KCFI,
|
||||
Leak,
|
||||
Memory,
|
||||
MemTag,
|
||||
SafeStack,
|
||||
ShadowCallStack,
|
||||
Thread,
|
||||
}
|
||||
|
||||
impl fmt::Display for Sanitizer {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match self {
|
||||
Sanitizer::Address => write!(f, "AddressSanitizer"),
|
||||
Sanitizer::CFI => write!(f, "ControlFlowIntegrity"),
|
||||
Sanitizer::HWAddress => write!(f, "HWAddressSanitizer"),
|
||||
Sanitizer::KCFI => write!(f, "KernelControlFlowIntegrity"),
|
||||
Sanitizer::Leak => write!(f, "LeakSanitizer"),
|
||||
Sanitizer::Memory => write!(f, "MemorySanitizer"),
|
||||
Sanitizer::MemTag => write!(f, "MemTagSanitizer"),
|
||||
Sanitizer::SafeStack => write!(f, "SafeStack"),
|
||||
Sanitizer::ShadowCallStack => write!(f, "ShadowCallStack"),
|
||||
Sanitizer::Thread => write!(f, "ThreadSanitizer"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Sanitizer {
|
||||
const DEFAULT_RUSTFLAGS: &'static str = "-Copt-level=3";
|
||||
|
||||
fn run_tests(&self) {
|
||||
if is_current_toolchain_nightly() {
|
||||
group!("Sanitizer: {}", self.to_string());
|
||||
let retriever = RustupTargetRetriever;
|
||||
if self.is_target_supported(&retriever) {
|
||||
let envs = vec![
|
||||
(
|
||||
"RUSTFLAGS",
|
||||
format!("{} {}", self.flags(), Sanitizer::DEFAULT_RUSTFLAGS),
|
||||
),
|
||||
("RUSTDOCFLAGS", self.flags().to_string()),
|
||||
];
|
||||
|
||||
let features = self.cargo_features();
|
||||
let mut args = vec!["--", "--color=always", "--no-capture"];
|
||||
args.extend(features);
|
||||
|
||||
run_cargo(
|
||||
"test",
|
||||
args.into(),
|
||||
envs.into_iter().collect(),
|
||||
"Failed to run cargo test",
|
||||
);
|
||||
} else {
|
||||
info!("No supported target found for this sanitizer.");
|
||||
}
|
||||
endgroup!();
|
||||
} else {
|
||||
error!(
|
||||
"You must use 'cargo +nightly' to run this check.
|
||||
Install a nightly toolchain with 'rustup toolchain install nightly'."
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fn flags(&self) -> &'static str {
|
||||
match self {
|
||||
Sanitizer::Address => "-Zsanitizer=address",
|
||||
Sanitizer::CFI => "-Zsanitizer=cfi -Clto",
|
||||
Sanitizer::HWAddress => "-Zsanitizer=hwaddress -Ctarget-feature=+tagged-globals",
|
||||
Sanitizer::KCFI => "-Zsanitizer=kcfi",
|
||||
Sanitizer::Leak => "-Zsanitizer=leak",
|
||||
Sanitizer::Memory => "-Zsanitizer=memory -Zsanitizer-memory-track-origins",
|
||||
Sanitizer::MemTag => "--Zsanitizer=memtag -Ctarget-feature=\"+mte\"",
|
||||
Sanitizer::SafeStack => "-Zsanitizer=safestack",
|
||||
Sanitizer::ShadowCallStack => "-Zsanitizer=shadow-call-stack",
|
||||
Sanitizer::Thread => "-Zsanitizer=thread",
|
||||
}
|
||||
}
|
||||
|
||||
fn cargo_features(&self) -> Vec<&str> {
|
||||
match self {
|
||||
Sanitizer::CFI => vec!["-Zbuild-std", "--target x86_64-unknown-linux-gnu"],
|
||||
_ => vec![],
|
||||
}
|
||||
}
|
||||
|
||||
fn supported_targets(&self) -> Vec<Target> {
|
||||
match self {
|
||||
Sanitizer::Address => vec![
|
||||
Target::Aarch64AppleDarwin,
|
||||
Target::Aarch64UnknownFuchsia,
|
||||
Target::Aarch64UnknownLinuxGnu,
|
||||
Target::X8664AppleDarwin,
|
||||
Target::X8664UnknownFuchsia,
|
||||
Target::X8664UnknownFreebsd,
|
||||
Target::X8664UnknownLinuxGnu,
|
||||
],
|
||||
Sanitizer::CFI => vec![Target::X8664UnknownLinuxGnu],
|
||||
Sanitizer::HWAddress => {
|
||||
vec![Target::Aarch64LinuxAndroid, Target::Aarch64UnknownLinuxGnu]
|
||||
}
|
||||
Sanitizer::KCFI => vec![
|
||||
Target::Aarch64LinuxAndroid,
|
||||
Target::Aarch64UnknownLinuxGnu,
|
||||
Target::X8664LinuxAndroid,
|
||||
Target::X8664UnknownLinuxGnu,
|
||||
],
|
||||
Sanitizer::Leak => vec![
|
||||
Target::Aarch64AppleDarwin,
|
||||
Target::Aarch64UnknownLinuxGnu,
|
||||
Target::X8664AppleDarwin,
|
||||
Target::X8664UnknownLinuxGnu,
|
||||
],
|
||||
Sanitizer::Memory => vec![
|
||||
Target::Aarch64UnknownLinuxGnu,
|
||||
Target::X8664UnknownFreebsd,
|
||||
Target::X8664UnknownLinuxGnu,
|
||||
],
|
||||
Sanitizer::MemTag => vec![Target::Aarch64LinuxAndroid, Target::Aarch64UnknownLinuxGnu],
|
||||
Sanitizer::SafeStack => vec![Target::X8664UnknownLinuxGnu],
|
||||
Sanitizer::ShadowCallStack => vec![Target::Aarch64LinuxAndroid],
|
||||
Sanitizer::Thread => vec![
|
||||
Target::Aarch64AppleDarwin,
|
||||
Target::Aarch64UnknownLinuxGnu,
|
||||
Target::X8664AppleDarwin,
|
||||
Target::X8664UnknownFreebsd,
|
||||
Target::X8664UnknownLinuxGnu,
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
// Returns true if the sanitizer is supported by the currently installed targets
|
||||
fn is_target_supported<T: TargetRetriever>(&self, retriever: &T) -> bool {
|
||||
let installed_targets = retriever.get_installed_targets();
|
||||
let supported = self.supported_targets();
|
||||
installed_targets.iter().any(|installed| {
|
||||
let installed_target = Target::from_str(installed.trim()).unwrap_or(Target::Unknown);
|
||||
supported.iter().any(|target| target == &installed_target)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Constants for target names
|
||||
const AARCH64_APPLE_DARWIN: &str = "aarch64-apple-darwin";
|
||||
const AARCH64_LINUX_ANDROID: &str = "aarch64-linux-android";
|
||||
const AARCH64_UNKNOWN_FUCHSIA: &str = "aarch64-unknown-fuchsia";
|
||||
const AARCH64_UNKNOWN_LINUX_GNU: &str = "aarch64-unknown-linux-gnu";
|
||||
const X8664_APPLE_DARWIN: &str = "x86_64-apple-darwin";
|
||||
const X8664_LINUX_ANDROID: &str = "x86_64-linux-android";
|
||||
const X8664_UNKNOWN_FUCHSIA: &str = "x86_64-unknown-fuchsia";
|
||||
const X8664_UNKNOWN_FREEBSD: &str = "x86_64-unknown-freebsd";
|
||||
const X8664_UNKNOWN_LINUX_GNU: &str = "x86_64-unknown-linux-gnu";
|
||||
|
||||
trait TargetRetriever {
|
||||
fn get_installed_targets(&self) -> Vec<String>;
|
||||
}
|
||||
|
||||
struct RustupTargetRetriever;
|
||||
|
||||
impl TargetRetriever for RustupTargetRetriever {
|
||||
fn get_installed_targets(&self) -> Vec<String> {
|
||||
rustup_get_installed_targets()
|
||||
.lines()
|
||||
.map(|s| s.to_string())
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
// Represents Rust targets
|
||||
// Remark: we list only the targets that are supported by sanitizers
|
||||
#[derive(Debug, PartialEq)]
|
||||
enum Target {
|
||||
Aarch64AppleDarwin,
|
||||
Aarch64LinuxAndroid,
|
||||
Aarch64UnknownFuchsia,
|
||||
Aarch64UnknownLinuxGnu,
|
||||
X8664AppleDarwin,
|
||||
X8664LinuxAndroid,
|
||||
X8664UnknownFuchsia,
|
||||
X8664UnknownFreebsd,
|
||||
X8664UnknownLinuxGnu,
|
||||
Unknown,
|
||||
}
|
||||
|
||||
impl Target {
|
||||
fn from_str(s: &str) -> Option<Self> {
|
||||
match s {
|
||||
AARCH64_APPLE_DARWIN => Some(Self::Aarch64AppleDarwin),
|
||||
AARCH64_LINUX_ANDROID => Some(Self::Aarch64LinuxAndroid),
|
||||
AARCH64_UNKNOWN_FUCHSIA => Some(Self::Aarch64UnknownFuchsia),
|
||||
AARCH64_UNKNOWN_LINUX_GNU => Some(Self::Aarch64UnknownLinuxGnu),
|
||||
X8664_APPLE_DARWIN => Some(Self::X8664AppleDarwin),
|
||||
X8664_LINUX_ANDROID => Some(Self::X8664LinuxAndroid),
|
||||
X8664_UNKNOWN_FUCHSIA => Some(Self::X8664UnknownFuchsia),
|
||||
X8664_UNKNOWN_FREEBSD => Some(Self::X8664UnknownFreebsd),
|
||||
X8664_UNKNOWN_LINUX_GNU => Some(Self::X8664UnknownLinuxGnu),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Target {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
let target_str = match self {
|
||||
Target::Aarch64AppleDarwin => AARCH64_APPLE_DARWIN,
|
||||
Target::Aarch64LinuxAndroid => AARCH64_LINUX_ANDROID,
|
||||
Target::Aarch64UnknownFuchsia => AARCH64_UNKNOWN_FUCHSIA,
|
||||
Target::Aarch64UnknownLinuxGnu => AARCH64_UNKNOWN_LINUX_GNU,
|
||||
Target::X8664AppleDarwin => X8664_APPLE_DARWIN,
|
||||
Target::X8664LinuxAndroid => X8664_LINUX_ANDROID,
|
||||
Target::X8664UnknownFuchsia => X8664_UNKNOWN_FUCHSIA,
|
||||
Target::X8664UnknownFreebsd => X8664_UNKNOWN_FREEBSD,
|
||||
Target::X8664UnknownLinuxGnu => X8664_UNKNOWN_LINUX_GNU,
|
||||
Target::Unknown => "",
|
||||
};
|
||||
write!(f, "{}", target_str)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use rstest::rstest;
|
||||
|
||||
struct MockTargetRetriever {
|
||||
mock_data: Vec<String>,
|
||||
}
|
||||
|
||||
impl MockTargetRetriever {
|
||||
fn new(mock_data: Vec<String>) -> Self {
|
||||
Self { mock_data }
|
||||
}
|
||||
}
|
||||
|
||||
impl TargetRetriever for MockTargetRetriever {
|
||||
fn get_installed_targets(&self) -> Vec<String> {
|
||||
self.mock_data.clone()
|
||||
}
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
#[case(vec!["".to_string()], false)] // empty string
|
||||
#[case(vec!["x86_64-pc-windows-msvc".to_string()], false)] // not supported target
|
||||
#[case(vec!["x86_64-pc-windows-msvc".to_string(), "".to_string()], false)] // not supported target and empty string
|
||||
#[case(vec!["x86_64-unknown-linux-gnu".to_string()], true)] // one supported target
|
||||
#[case(vec!["aarch64-apple-darwin".to_string(), "x86_64-unknown-linux-gnu".to_string()], true)] // one unsupported target and one supported
|
||||
fn test_is_target_supported(#[case] installed_targets: Vec<String>, #[case] expected: bool) {
|
||||
let mock_retriever = MockTargetRetriever::new(installed_targets);
|
||||
let sanitizer = Sanitizer::Memory;
|
||||
assert_eq!(sanitizer.is_target_supported(&mock_retriever), expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_consistency_of_fmt_and_from_str_strings() {
|
||||
let variants = vec![
|
||||
Target::Aarch64AppleDarwin,
|
||||
Target::Aarch64LinuxAndroid,
|
||||
Target::Aarch64UnknownFuchsia,
|
||||
Target::Aarch64UnknownLinuxGnu,
|
||||
Target::X8664AppleDarwin,
|
||||
Target::X8664LinuxAndroid,
|
||||
Target::X8664UnknownFuchsia,
|
||||
Target::X8664UnknownFreebsd,
|
||||
Target::X8664UnknownLinuxGnu,
|
||||
];
|
||||
for variant in variants {
|
||||
let variant_str = format!("{}", variant);
|
||||
let parsed_variant = Target::from_str(&variant_str);
|
||||
assert_eq!(Some(variant), parsed_variant);
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue