foundry_test_utils/
util.rs1use foundry_compilers::{
2 Project, ProjectCompileOutput, Vyper, project_util::copy_dir, utils::RuntimeOrHandle,
3};
4use foundry_config::Config;
5use std::{
6 env,
7 fs::{self, File},
8 io::{IsTerminal, Read, Seek, Write},
9 path::{Path, PathBuf},
10 process::Command,
11 sync::LazyLock,
12};
13
14pub use crate::{ext::*, prj::*};
15
16pub const FORGE_STD_REVISION: &str = include_str!("../../../testdata/forge-std-rev");
18
19pub static IS_TTY: LazyLock<bool> = LazyLock::new(|| std::io::stdout().is_terminal());
21
22static TEMPLATE_PATH: LazyLock<PathBuf> =
25 LazyLock::new(|| env::temp_dir().join("foundry-forge-test-template"));
26
27static TEMPLATE_LOCK: LazyLock<PathBuf> =
30 LazyLock::new(|| env::temp_dir().join("foundry-forge-test-template.lock"));
31
32pub const SOLC_VERSION: &str = "0.8.30";
34
35pub const OTHER_SOLC_VERSION: &str = "0.8.26";
39
40pub fn initialize(target: &Path) {
56 test_debug!("initializing {}", target.display());
57
58 let tpath = TEMPLATE_PATH.as_path();
59 pretty_err(tpath, fs::create_dir_all(tpath));
60
61 let mut lock = crate::fd_lock::new_lock(TEMPLATE_LOCK.as_path());
63 let mut _read = lock.read().unwrap();
64 if !crate::fd_lock::lock_exists(TEMPLATE_LOCK.as_path()) {
65 drop(_read);
75 let mut write = lock.write().unwrap();
76
77 let mut data = Vec::new();
78 write.read_to_end(&mut data).unwrap();
79 if data != crate::fd_lock::LOCK_TOKEN {
80 let (prj, mut cmd) = setup_forge("template", foundry_compilers::PathStyle::Dapptools);
82 test_debug!("- initializing template dir in {}", prj.root().display());
83
84 cmd.args(["init", "--force", "--empty"]).assert_success();
85 prj.write_config(Config {
86 solc: Some(foundry_config::SolcReq::Version(SOLC_VERSION.parse().unwrap())),
87 ..Default::default()
88 });
89
90 let output = Command::new("git")
92 .current_dir(prj.root().join("lib/forge-std"))
93 .args(["checkout", FORGE_STD_REVISION])
94 .output()
95 .expect("failed to checkout forge-std");
96 assert!(output.status.success(), "{output:#?}");
97
98 cmd.forge_fuse().arg("build").assert_success();
100
101 let _ = fs::remove_dir_all(tpath);
103
104 pretty_err(tpath, copy_dir(prj.root(), tpath));
106
107 write.set_len(0).unwrap();
109 write.seek(std::io::SeekFrom::Start(0)).unwrap();
110 write.write_all(crate::fd_lock::LOCK_TOKEN).unwrap();
111 }
112
113 drop(write);
115 _read = lock.read().unwrap();
116 }
117
118 test_debug!("- copying template dir from {}", tpath.display());
119 pretty_err(target, fs::create_dir_all(target));
120 pretty_err(target, copy_dir(tpath, target));
121}
122
123pub fn get_compiled(project: &mut Project) -> ProjectCompileOutput {
125 let lock_file_path = project.sources_path().join(".lock");
126 let mut lock = crate::fd_lock::new_lock(&lock_file_path);
129 let read = lock.read().unwrap();
130 let out;
131
132 let mut write = None;
133 if !project.cache_path().exists() || !crate::fd_lock::lock_exists(&lock_file_path) {
134 drop(read);
135 write = Some(lock.write().unwrap());
136 test_debug!("cache miss for {}", lock_file_path.display());
137 } else {
138 test_debug!("cache hit for {}", lock_file_path.display());
139 }
140
141 if project.compiler.vyper.is_none() {
142 project.compiler.vyper = Some(get_vyper());
143 }
144
145 test_debug!("compiling {}", lock_file_path.display());
146 out = project.compile().unwrap();
147 test_debug!("compiled {}", lock_file_path.display());
148
149 if out.has_compiler_errors() {
150 panic!("Compiled with errors:\n{out}");
151 }
152
153 if let Some(write) = &mut write {
154 write.write_all(crate::fd_lock::LOCK_TOKEN).unwrap();
155 }
156
157 out
158}
159
160pub fn get_vyper() -> Vyper {
162 static VYPER: LazyLock<PathBuf> = LazyLock::new(|| std::env::temp_dir().join("vyper"));
163
164 if let Ok(vyper) = Vyper::new("vyper") {
165 return vyper;
166 }
167 if let Ok(vyper) = Vyper::new(&*VYPER) {
168 return vyper;
169 }
170 return RuntimeOrHandle::new().block_on(install());
171
172 async fn install() -> Vyper {
173 #[cfg(target_family = "unix")]
174 use std::{fs::Permissions, os::unix::fs::PermissionsExt};
175
176 let path = VYPER.as_path();
177 let mut file = File::create(path).unwrap();
178 if let Err(e) = file.try_lock() {
179 if let fs::TryLockError::WouldBlock = e {
180 file.lock().unwrap();
181 assert!(path.exists());
182 return Vyper::new(path).unwrap();
183 }
184 file.lock().unwrap();
185 }
186
187 let suffix = match svm::platform() {
188 svm::Platform::MacOsAarch64 => "darwin",
189 svm::Platform::LinuxAmd64 => "linux",
190 svm::Platform::WindowsAmd64 => "windows.exe",
191 platform => panic!(
192 "unsupported platform {platform:?} for installing vyper, \
193 install it manually and add it to $PATH"
194 ),
195 };
196 let url = format!(
197 "https://github.com/vyperlang/vyper/releases/download/v0.4.3/vyper.0.4.3+commit.bff19ea2.{suffix}"
198 );
199
200 test_debug!("downloading vyper from {url}");
201 let res = reqwest::Client::builder().build().unwrap().get(url).send().await.unwrap();
202
203 assert!(res.status().is_success());
204
205 let bytes = res.bytes().await.unwrap();
206
207 file.write_all(&bytes).unwrap();
208
209 #[cfg(target_family = "unix")]
210 file.set_permissions(Permissions::from_mode(0o755)).unwrap();
211
212 Vyper::new(path).unwrap()
213 }
214}
215
216#[track_caller]
217pub fn pretty_err<T, E: std::error::Error>(path: impl AsRef<Path>, res: Result<T, E>) -> T {
218 match res {
219 Ok(t) => t,
220 Err(err) => panic!("{}: {err}", path.as_ref().display()),
221 }
222}
223
224pub fn read_string(path: impl AsRef<Path>) -> String {
225 let path = path.as_ref();
226 pretty_err(path, std::fs::read_to_string(path))
227}