From 565fbd3407f4427778fd1a19f5f260ad4114229f Mon Sep 17 00:00:00 2001 From: Michael Gattozzi Date: Mon, 31 Jul 2023 14:09:12 +0000 Subject: [PATCH] Add testing apparatus to freight with freight test We finally have tests that aren't just "Run the cli to make sure it runs!" With this we can start adding more and more tests to make sure that freight is well tested and catch things that might break a bit more easily than we could before. We make quite a few changes here that are important to cover: - Expanded the derive traits for Edition and CrateType so that we can test them better - Added support to parse from a string to CrateType, just like Edition - Added tests to make sure that string parsing works as expected for them - Moved the bin_compile and lib_compile function out to be fn() instead of a closure - Added a test_compile function to compile libs and bins as tests - Added a function to just build the tests - Added a function to just run the tests - Update CI to build and to run tests - Update pre-commit hook to build and to run tests This captures most of it. With these changes we can be more confident that things work and can more thoroughly test our code as we go along. --- .github/workflows/ci.yaml | 4 + hooks/pre-commit.sh | 1 + justfile | 5 +- src/lib.rs | 231 ++++++++++++++++++++++++++++++++------ src/main.rs | 5 + 5 files changed, 211 insertions(+), 35 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 01a24c5..c44fd51 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -12,3 +12,7 @@ jobs: - name: "Build and run Freight" run: "just run" shell: bash + + - name: "Build and test Freight" + run: "just test" + shell: bash diff --git a/hooks/pre-commit.sh b/hooks/pre-commit.sh index bdd181c..f2da918 100755 --- a/hooks/pre-commit.sh +++ b/hooks/pre-commit.sh @@ -1,2 +1,3 @@ #!/bin/sh just run +just test diff --git a/justfile b/justfile index d1eee71..6623f76 100644 --- a/justfile +++ b/justfile @@ -1,5 +1,4 @@ run: build - ./target/bootstrap/freight build ./target/debug/freight help build: rm -rf target @@ -10,3 +9,7 @@ build: # Create the executable rustc src/main.rs --edition 2021 --crate-type=bin --crate-name=freight \ --out-dir=target/bootstrap -L target/bootstrap --extern freight + ./target/bootstrap/freight build +test: build + mkdir -p target/test + ./target/debug/freight test diff --git a/src/lib.rs b/src/lib.rs index d218c8d..30229d5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -8,6 +8,7 @@ use std::error::Error; use std::fmt; use std::fmt::Display; use std::fs; +use std::path::Path; use std::path::PathBuf; use std::process::Command; use std::str::FromStr; @@ -15,6 +16,78 @@ use std::str::FromStr; pub type Result = std::result::Result; pub type BoxError = Box; +fn lib_compile( + logger: &mut Logger, + manifest: &Manifest, + lib_path: &Path, + out_dir: &Path, +) -> Result<()> { + logger.compiling_crate(&manifest.crate_name); + Rustc::builder() + .edition(manifest.edition) + .crate_type(CrateType::Lib) + .crate_name(&manifest.crate_name) + .out_dir(out_dir.clone()) + .lib_dir(out_dir.clone()) + .done() + .run(lib_path.to_str().unwrap())?; + logger.done_compiling(); + Ok(()) +} + +fn bin_compile( + logger: &mut Logger, + manifest: &Manifest, + bin_path: &Path, + out_dir: &Path, + externs: &[&str], +) -> Result<()> { + logger.compiling_bin(&manifest.crate_name); + let mut builder = Rustc::builder() + .edition(manifest.edition) + .crate_type(CrateType::Bin) + .crate_name(&manifest.crate_name) + .out_dir(out_dir.clone()) + .lib_dir(out_dir.clone()); + + for ex in externs { + builder = builder.externs(*ex); + } + + builder.done().run(bin_path.to_str().unwrap())?; + logger.done_compiling(); + Ok(()) +} + +fn test_compile( + logger: &mut Logger, + manifest: &Manifest, + bin_path: &Path, + out_dir: &Path, + externs: &[&str], +) -> Result<()> { + logger.compiling_bin(&manifest.crate_name); + let mut builder = Rustc::builder() + .edition(manifest.edition) + .crate_type(CrateType::Bin) + .crate_name(format!( + "test_{}_{}", + &manifest.crate_name, + bin_path.file_stem().unwrap().to_str().unwrap() + )) + .out_dir(out_dir.clone()) + .lib_dir(out_dir.clone()) + .test(true); + + for ex in externs { + builder = builder.externs(*ex); + } + + builder.done().run(bin_path.to_str().unwrap())?; + logger.done_compiling(); + Ok(()) +} + pub fn build() -> Result<()> { let mut logger = Logger::new(); let root_dir = root_dir()?; @@ -26,48 +99,57 @@ pub fn build() -> Result<()> { let target_debug = target.join("debug"); fs::create_dir_all(&target_debug)?; - let lib_compile = |logger: &mut Logger| -> Result<()> { - logger.compiling_crate(&manifest.crate_name); - Rustc::builder() - .edition(manifest.edition) - .crate_type(CrateType::Lib) - .crate_name(&manifest.crate_name) - .out_dir(target_debug.clone()) - .lib_dir(target_debug.clone()) - .done() - .run(lib_rs.to_str().unwrap())?; - logger.done_compiling(); - Ok(()) - }; - - let bin_compile = |logger: &mut Logger, externs: Vec<&str>| -> Result<()> { - logger.compiling_bin(&manifest.crate_name); - let mut builder = Rustc::builder() - .edition(manifest.edition) - .crate_type(CrateType::Bin) - .crate_name(&manifest.crate_name) - .out_dir(target_debug.clone()) - .lib_dir(target_debug.clone()); - - for ex in externs { - builder = builder.externs(ex); + match (lib_rs.exists(), main_rs.exists()) { + (true, true) => { + lib_compile(&mut logger, &manifest, &lib_rs, &target_debug)?; + bin_compile( + &mut logger, + &manifest, + &main_rs, + &target_debug, + &[&manifest.crate_name], + )?; + } + (true, false) => { + lib_compile(&mut logger, &manifest, &lib_rs, &target_debug)?; + } + (false, true) => { + bin_compile(&mut logger, &manifest, &main_rs, &target_debug, &[])?; } + (false, false) => return Err("There is nothing to compile".into()), + } - builder.done().run(main_rs.to_str().unwrap())?; - logger.done_compiling(); - Ok(()) - }; + Ok(()) +} + +pub fn build_tests() -> Result<()> { + let mut logger = Logger::new(); + let root_dir = root_dir()?; + let manifest = Manifest::parse_from_file(root_dir.join("Freight.toml"))?; + + let lib_rs = root_dir.join("src").join("lib.rs"); + let main_rs = root_dir.join("src").join("main.rs"); + let target = root_dir.join("target"); + let target_tests = target.join("debug").join("tests"); + fs::create_dir_all(&target_tests)?; match (lib_rs.exists(), main_rs.exists()) { (true, true) => { - lib_compile(&mut logger)?; - bin_compile(&mut logger, vec![&manifest.crate_name])?; + test_compile(&mut logger, &manifest, &lib_rs, &target_tests, &[])?; + lib_compile(&mut logger, &manifest, &lib_rs, &target_tests)?; + test_compile( + &mut logger, + &manifest, + &main_rs, + &target_tests, + &[&manifest.crate_name], + )?; } (true, false) => { - lib_compile(&mut logger)?; + test_compile(&mut logger, &manifest, &lib_rs, &target_tests, &[])?; } (false, true) => { - bin_compile(&mut logger, vec![])?; + test_compile(&mut logger, &manifest, &main_rs, &target_tests, &[])?; } (false, false) => return Err("There is nothing to compile".into()), } @@ -75,6 +157,23 @@ pub fn build() -> Result<()> { Ok(()) } +pub fn run_tests() -> Result<()> { + for item in root_dir()? + .join("target") + .join("debug") + .join("tests") + .read_dir()? + { + let item = item?; + let path = item.path(); + let is_test = path.extension().is_none(); + if is_test { + Command::new(path).spawn()?.wait()?; + } + } + Ok(()) +} + fn root_dir() -> Result { let current_dir = env::current_dir()?; for ancestor in current_dir.ancestors() { @@ -93,6 +192,7 @@ pub struct Rustc { lib_dir: PathBuf, cfg: Vec, externs: Vec, + test: bool, } impl Rustc { @@ -115,6 +215,7 @@ impl Rustc { .arg(self.out_dir) .arg("-L") .arg(self.lib_dir) + .args(if self.test { vec!["--test"] } else { vec![] }) .args( self.externs .into_iter() @@ -143,6 +244,7 @@ pub struct RustcBuilder { lib_dir: Option, cfg: Vec, externs: Vec, + test: bool, } impl RustcBuilder { @@ -175,6 +277,11 @@ impl RustcBuilder { self } + pub fn test(mut self, test: bool) -> Self { + self.test = test; + self + } + pub fn done(self) -> Rustc { Rustc { edition: self.edition.unwrap_or(Edition::E2015), @@ -184,11 +291,12 @@ impl RustcBuilder { lib_dir: self.lib_dir.expect("Lib dir given"), cfg: self.cfg, externs: self.externs, + test: self.test, } } } -#[derive(Clone, Copy)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum Edition { E2015, E2018, @@ -218,6 +326,7 @@ impl FromStr for Edition { } } +#[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum CrateType { Bin, Lib, @@ -242,3 +351,57 @@ impl Display for CrateType { write!(f, "{crate_type}") } } + +impl FromStr for CrateType { + type Err = BoxError; + fn from_str(input: &str) -> Result { + match input { + "bin" => Ok(Self::Bin), + "lib" => Ok(Self::Lib), + "rlib" => Ok(Self::RLib), + "dylib" => Ok(Self::DyLib), + "cdylib" => Ok(Self::CDyLib), + "staticlib" => Ok(Self::StaticLib), + "proc-macro" => Ok(Self::ProcMacro), + crate_type => Err(format!("Crate Type {crate_type} is not supported").into()), + } + } +} + +#[test] +fn edition_from_str() -> Result<()> { + let e2015 = Edition::from_str("2015")?; + assert_eq!(e2015, Edition::E2015); + let e2018 = Edition::from_str("2018")?; + assert_eq!(e2018, Edition::E2018); + let e2021 = Edition::from_str("2021")?; + assert_eq!(e2021, Edition::E2021); + if !Edition::from_str("\"2015\"").is_err() { + panic!("bad string parsed correctly"); + } + + Ok(()) +} + +#[test] +fn crate_type_from_str() -> Result<()> { + let bin = CrateType::from_str("bin")?; + assert_eq!(bin, CrateType::Bin); + let lib = CrateType::from_str("lib")?; + assert_eq!(lib, CrateType::Lib); + let rlib = CrateType::from_str("rlib")?; + assert_eq!(rlib, CrateType::RLib); + let dylib = CrateType::from_str("dylib")?; + assert_eq!(dylib, CrateType::DyLib); + let cdylib = CrateType::from_str("cdylib")?; + assert_eq!(cdylib, CrateType::CDyLib); + let staticlib = CrateType::from_str("staticlib")?; + assert_eq!(staticlib, CrateType::StaticLib); + let proc_macro = CrateType::from_str("proc-macro")?; + assert_eq!(proc_macro, CrateType::ProcMacro); + if !CrateType::from_str("proc-marco").is_err() { + panic!("bad string parsed correctly"); + } + + Ok(()) +} diff --git a/src/main.rs b/src/main.rs index b3ae623..1fdb667 100644 --- a/src/main.rs +++ b/src/main.rs @@ -8,12 +8,17 @@ fn main() -> Result<(), Box> { Usage: freight [COMMAND] [OPTIONS]\n\n\ Commands:\n \ build Build a Freight or Cargo project\n \ + test Test a Freight or Cargo project\n \ help Print out this message "; let mut args = env::args().skip(1); match args.next().as_ref().map(String::as_str) { Some("build") => freight::build()?, + Some("test") => { + freight::build_tests()?; + freight::run_tests()? + } Some("help") => println!("{HELP}"), _ => { println!("Unsupported command");