Skip to content

Commit

Permalink
Add testing apparatus to freight with freight test
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
mgattozzi committed Jul 31, 2023
1 parent 2c1a862 commit 565fbd3
Show file tree
Hide file tree
Showing 5 changed files with 211 additions and 35 deletions.
4 changes: 4 additions & 0 deletions .github/workflows/ci.yaml
Expand Up @@ -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
1 change: 1 addition & 0 deletions hooks/pre-commit.sh
@@ -1,2 +1,3 @@
#!/bin/sh
just run
just test
5 changes: 4 additions & 1 deletion justfile
@@ -1,5 +1,4 @@
run: build
./target/bootstrap/freight build
./target/debug/freight help
build:
rm -rf target
Expand All @@ -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
231 changes: 197 additions & 34 deletions src/lib.rs
Expand Up @@ -8,13 +8,86 @@ 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;

pub type Result<T> = std::result::Result<T, BoxError>;
pub type BoxError = Box<dyn Error>;

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()?;
Expand All @@ -26,55 +99,81 @@ 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()),
}

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<PathBuf> {
let current_dir = env::current_dir()?;
for ancestor in current_dir.ancestors() {
Expand All @@ -93,6 +192,7 @@ pub struct Rustc {
lib_dir: PathBuf,
cfg: Vec<String>,
externs: Vec<String>,
test: bool,
}

impl Rustc {
Expand All @@ -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()
Expand Down Expand Up @@ -143,6 +244,7 @@ pub struct RustcBuilder {
lib_dir: Option<PathBuf>,
cfg: Vec<String>,
externs: Vec<String>,
test: bool,
}

impl RustcBuilder {
Expand Down Expand Up @@ -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),
Expand All @@ -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,
Expand Down Expand Up @@ -218,6 +326,7 @@ impl FromStr for Edition {
}
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CrateType {
Bin,
Lib,
Expand All @@ -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<Self> {
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(())
}
5 changes: 5 additions & 0 deletions src/main.rs
Expand Up @@ -8,12 +8,17 @@ fn main() -> Result<(), Box<dyn Error>> {
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");
Expand Down

0 comments on commit 565fbd3

Please sign in to comment.