Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

test: compile testdata/ only once #6305

Merged
merged 2 commits into from
Nov 14, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 1 addition & 4 deletions crates/forge/tests/cli/test_cmd.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
//! Contains various tests for checking `forge test`
use foundry_config::Config;
use foundry_test_utils::util::{template_lock, OutputExt, OTHER_SOLC_VERSION, SOLC_VERSION};
use foundry_test_utils::util::{OutputExt, OTHER_SOLC_VERSION, SOLC_VERSION};
use foundry_utils::rpc;
use std::{path::PathBuf, process::Command, str::FromStr};

Expand Down Expand Up @@ -265,8 +265,6 @@ forgetest_init!(
#[serial_test::serial]
can_test_forge_std,
|prj, cmd| {
let mut lock = template_lock();
let write = lock.write().unwrap();
let forge_std_dir = prj.root().join("lib/forge-std");
let status = Command::new("git")
.current_dir(&forge_std_dir)
Expand All @@ -276,7 +274,6 @@ forgetest_init!(
if !status.success() {
panic!("failed to update forge-std");
}
drop(write);

// execute in subdir
cmd.cmd().current_dir(forge_std_dir);
Expand Down
8 changes: 4 additions & 4 deletions crates/forge/tests/it/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,15 +31,15 @@ impl TestConfig {
Self::with_filter(runner, Filter::matches_all())
}

pub async fn filter(filter: Filter) -> Self {
Self::with_filter(runner().await, filter)
}

pub fn with_filter(runner: MultiContractRunner, filter: Filter) -> Self {
init_tracing();
Self { runner, should_fail: false, filter, opts: test_opts() }
}

pub async fn filter(filter: Filter) -> Self {
Self::with_filter(runner().await, filter)
}

pub fn evm_spec(mut self, spec: SpecId) -> Self {
self.runner.evm_spec = spec;
self
Expand Down
28 changes: 26 additions & 2 deletions crates/forge/tests/it/test_helpers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,13 @@ use foundry_evm::{
opts::{Env, EvmOpts},
revm::db::DatabaseRef,
};
use foundry_test_utils::fd_lock;
use once_cell::sync::Lazy;
use std::{env, io::Write};

pub const RE_PATH_SEPARATOR: &str = "/";

const TESTDATA: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/../../testdata");
// const TESTDATA_LOCK: Lazy<PathBuf> = Lazy::new(|| {});

pub static PROJECT: Lazy<Project> = Lazy::new(|| {
let paths = ProjectPathsConfig::builder().root(TESTDATA).sources(TESTDATA).build().unwrap();
Expand All @@ -29,7 +30,30 @@ pub static PROJECT: Lazy<Project> = Lazy::new(|| {
});

pub static COMPILED: Lazy<ProjectCompileOutput> = Lazy::new(|| {
let out = PROJECT.compile().unwrap();
const LOCK: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/../../testdata/.lock");

let project = &*PROJECT;
assert!(project.cached);

// Compile only once per test run.
// We need to use a file lock because `cargo-nextest` runs tests in different processes.
// This is similar to [`foundry_test_utils::util::initialize`], see its comments for more
// details.
let mut lock = fd_lock::new_lock(LOCK);
let read = lock.read().unwrap();
let out;
if project.cache_path().exists() && std::fs::read(LOCK).unwrap() == b"1" {
out = project.compile();
drop(read);
} else {
drop(read);
let mut write = lock.write().unwrap();
write.write_all(b"1").unwrap();
out = project.compile();
drop(write);
};

let out = out.unwrap();
if out.has_compiler_errors() {
panic!("Compiled with errors:\n{out}");
}
Expand Down
21 changes: 21 additions & 0 deletions crates/test-utils/src/fd_lock.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
//! File locking utilities.

use crate::util::pretty_err;
use std::{
fs::{File, OpenOptions},
path::Path,
};

pub use fd_lock::*;

/// Creates a new lock file at the given path.
pub fn new_lock(lock_path: impl AsRef<Path>) -> RwLock<File> {
fn new_lock(lock_path: &Path) -> RwLock<File> {
let lock_file = pretty_err(
lock_path,
OpenOptions::new().read(true).write(true).create(true).open(lock_path),
);
RwLock::new(lock_file)
}
new_lock(lock_path.as_ref())
}
3 changes: 2 additions & 1 deletion crates/test-utils/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ extern crate tracing;
// Macros useful for testing.
mod macros;

pub mod fd_lock;

mod filter;
pub use filter::Filter;

Expand All @@ -17,7 +19,6 @@ mod script;
pub use script::{ScriptOutcome, ScriptTester};

// re-exports for convenience
pub use fd_lock;
pub use foundry_compilers;

/// Initializes tracing for tests.
Expand Down
17 changes: 3 additions & 14 deletions crates/test-utils/src/util.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
use crate::init_tracing;
use eyre::{Result, WrapErr};
use fd_lock::RwLock;
use foundry_compilers::{
cache::SolFilesCache,
error::Result as SolcResult,
Expand Down Expand Up @@ -51,16 +50,6 @@ pub const SOLC_VERSION: &str = "0.8.23";
/// versions.
pub const OTHER_SOLC_VERSION: &str = "0.8.22";

/// Creates a file lock to the global template dir.
pub fn template_lock() -> RwLock<File> {
let lock_path = &*TEMPLATE_LOCK;
let lock_file = pretty_err(
lock_path,
fs::OpenOptions::new().read(true).write(true).create(true).open(lock_path),
);
RwLock::new(lock_file)
}

/// Initializes a project with `forge init` at the given path.
///
/// This should be called after an empty project is created like in
Expand All @@ -81,9 +70,9 @@ pub fn initialize(target: &Path) {
pretty_err(tpath, fs::create_dir_all(tpath));

// Initialize the global template if necessary.
let mut lock = template_lock();
let mut lock = crate::fd_lock::new_lock(TEMPLATE_LOCK.as_path());
let mut _read = Some(lock.read().unwrap());
if fs::read_to_string(&*TEMPLATE_LOCK).unwrap() != "1" {
if fs::read(&*TEMPLATE_LOCK).unwrap() != b"1" {
// We are the first to acquire the lock:
// - initialize a new empty temp project;
// - run `forge init`;
Expand Down Expand Up @@ -571,7 +560,7 @@ fn config_paths_exist(paths: &ProjectPathsConfig, cached: bool) {
pub fn pretty_err<T, E: std::error::Error>(path: impl AsRef<Path>, res: Result<T, E>) -> T {
match res {
Ok(t) => t,
Err(err) => panic!("{}: {err:?}", path.as_ref().display()),
Err(err) => panic!("{}: {err}", path.as_ref().display()),
}
}

Expand Down
3 changes: 2 additions & 1 deletion testdata/.gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
# Compiler files
cache/
out/
out/
.lock