wasmer_argus/argus/tester/
cli_tester.rs1use crate::argus::{Argus, ArgusConfig, Backend};
2use indicatif::ProgressBar;
3use std::{fs::File, io::BufReader, path::Path, process::Command, sync::Arc};
4use tokio::time::{self, Instant};
5use tracing::*;
6use wasmer_backend_api::types::PackageVersionWithPackage;
7use webc::{Container, Version, v2::read::OwnedReader, v3::read::OwnedReader as OwnedReaderV3};
8
9use super::{TestReport, Tester};
10
11#[allow(unused)]
12pub struct CLIRunner<'a> {
13 test_id: u64,
14 config: Arc<ArgusConfig>,
15 p: &'a ProgressBar,
16 package: &'a PackageVersionWithPackage,
17}
18
19impl<'a> CLIRunner<'a> {
20 pub fn new(
21 test_id: u64,
22 config: Arc<ArgusConfig>,
23 p: &'a ProgressBar,
24 package: &'a PackageVersionWithPackage,
25 ) -> Self {
26 Self {
27 test_id,
28 config,
29 p,
30 package,
31 }
32 }
33
34 async fn test_atom(
35 &self,
36 cli_path: &String,
37 atom: &[u8],
38 dir_path: &Path,
39 atom_id: usize,
40 ) -> anyhow::Result<Result<(), String>> {
41 if let Err(e) = Command::new(cli_path).arg("-V").output() {
42 if let std::io::ErrorKind::NotFound = e.kind() {
43 anyhow::bail!("the command '{cli_path}' was not found");
44 }
45 }
46
47 let atom_path = dir_path.join(format!("atom_{atom_id}.wasm"));
48 let output_path = dir_path.join(format!("atom_{atom_id}.wasmu"));
49
50 tokio::fs::write(&atom_path, atom).await?;
51
52 let backend = match self.config.compiler_backend {
53 Backend::Llvm => "--llvm",
54 Backend::Singlepass => "--singlepass",
55 Backend::Cranelift => "--cranelift",
56 };
57
58 let res = std::panic::catch_unwind(move || {
59 let mut cmd = Command::new(cli_path);
60
61 let cmd = cmd.args([
62 "compile",
63 atom_path.to_str().unwrap(),
64 backend,
65 "-o",
66 output_path.to_str().unwrap(),
67 ]);
68
69 info!("running cmd: {:?}", cmd);
70
71 let out = cmd.output();
72
73 info!("run cmd that gave result: {:#?}", out);
74
75 out
76 });
77
78 Ok(match res {
79 Ok(r) => match r {
80 Ok(_) => Ok(()),
81 Err(e) => Err(e.to_string()),
82 },
83 Err(_) => Err(String::from("thread panicked")),
84 })
85 }
86
87 fn ok(&self, version: String, start_time: Instant) -> anyhow::Result<TestReport> {
88 Ok(TestReport::new(
89 self.package,
90 String::from("wasmer_cli"),
91 version,
92 self.config.compiler_backend.to_string(),
93 start_time - Instant::now(),
94 Ok(String::from("test passed")),
95 ))
96 }
97
98 fn err(
99 &self,
100 version: String,
101 start_time: Instant,
102 message: String,
103 ) -> anyhow::Result<TestReport> {
104 Ok(TestReport::new(
105 self.package,
106 String::from("wasmer_cli"),
107 version,
108 self.config.compiler_backend.to_string(),
109 start_time - Instant::now(),
110 Err(message),
111 ))
112 }
113
114 fn get_id(&self) -> String {
115 String::from("wasmer_cli")
116 }
117
118 async fn get_version(&self) -> anyhow::Result<String> {
119 let cli_path = match &self.config.cli_path {
120 Some(p) => p.clone(),
121 None => String::from("wasmer"),
122 };
123
124 let mut cmd = Command::new(&cli_path);
125 let cmd = cmd.arg("-V");
126
127 info!("running cmd: {:?}", cmd);
128
129 let out = cmd.output();
130
131 info!("run cmd that gave result: {:?}", out);
132
133 match out {
134 Ok(v) => Ok(String::from_utf8(v.stdout)
135 .unwrap()
136 .replace(' ', "")
137 .replace("wasmer", "")
138 .trim()
139 .to_string()),
140 Err(e) => anyhow::bail!("failed to launch cli program {cli_path}: {e}"),
141 }
142 }
143}
144
145#[async_trait::async_trait]
146impl Tester for CLIRunner<'_> {
147 async fn run_test(&self) -> anyhow::Result<TestReport> {
148 let start_time = time::Instant::now();
149 let version = self.get_version().await?;
150 let cli_path = match &self.config.cli_path {
151 Some(p) => p.clone(),
152 None => String::from("wasmer"),
153 };
154
155 info!("starting test using CLI at {cli_path}");
156 let dir_path = Argus::get_path(self.config.clone(), self.package).await;
157 let webc_v2_path = dir_path.join("package_v2.webc");
158
159 self.p
160 .set_message(format!("unpacking webc at {webc_v2_path:?}"));
161
162 let v2_bytes = std::fs::read(&webc_v2_path)?;
163
164 let webc_v2 = match webc::detect(v2_bytes.as_slice()) {
165 Ok(Version::V2) => Container::from(OwnedReader::parse(v2_bytes)?),
166 Ok(other) => {
167 return self.err(version, start_time, format!("Unsupported version, {other}"));
168 }
169 Err(e) => return self.err(version, start_time, format!("An error occurred: {e}")),
170 };
171
172 for (i, atom) in webc_v2.atoms().iter().enumerate() {
173 self.p.set_message(format!("testing atom #{i}"));
174 if let Err(e) = self
175 .test_atom(&cli_path, atom.1.as_slice(), &dir_path, i)
176 .await?
177 {
178 return self.err(version, start_time, e);
179 }
180 }
181
182 let webc_v3_path = dir_path.join("package_v3.webc");
183
184 self.p
185 .set_message(format!("unpacking webc at {webc_v3_path:?}"));
186
187 let v3_bytes = std::fs::read(&webc_v3_path)?;
188
189 let webc_v3 = match webc::detect(v3_bytes.as_slice()) {
190 Ok(Version::V3) => Container::from(OwnedReaderV3::parse(v3_bytes)?),
191 Ok(other) => {
192 return self.err(version, start_time, format!("Unsupported version, {other}"));
193 }
194 Err(e) => return self.err(version, start_time, format!("An error occurred: {e}")),
195 };
196
197 for (i, atom) in webc_v3.atoms().iter().enumerate() {
198 self.p.set_message(format!("testing atom #{i}"));
199 if let Err(e) = self
200 .test_atom(&cli_path, atom.1.as_slice(), &dir_path, i)
201 .await?
202 {
203 return self.err(version.clone(), start_time, e);
204 }
205 }
206
207 let v2_file = std::fs::File::open(&webc_v2_path)?;
208 let v3_file = std::fs::File::open(&webc_v3_path)?;
209 if let Err(e) = webc::migration::are_semantically_equivalent(
210 shared_buffer::OwnedBuffer::from_file(&v2_file)?,
211 shared_buffer::OwnedBuffer::from_file(&v3_file)?,
212 ) {
213 return self.err(version.clone(), start_time, e.to_string());
214 }
215
216 self.ok(version, start_time)
217 }
218
219 async fn is_to_test(&self) -> bool {
220 let pkg = self.package;
221 let version = match self.get_version().await {
222 Ok(version) => version,
223 Err(e) => {
224 error!("skipping test because of error while spawning wasmer CLI command: {e}");
225 return false;
226 }
227 };
228
229 let out_dir = Argus::get_path(self.config.clone(), self.package).await;
230 let test_results_path = out_dir.join(format!(
231 "result-{}-{}--{}-{}.json",
232 self.get_id(),
233 version,
234 std::env::consts::ARCH,
235 std::env::consts::OS,
236 ));
237
238 let file = match File::open(test_results_path) {
239 Ok(file) => file,
240 Err(e) => {
241 info!(
242 "re-running test for pkg {:?} as previous-run file failed to open: {e}",
243 pkg
244 );
245 return true;
246 }
247 };
248
249 let reader = BufReader::new(file);
250 let report: TestReport = match serde_json::from_reader(reader) {
251 Ok(p) => p,
252 Err(e) => {
253 info!(
254 "re-running test for pkg {:?} as previous-run file failed to be deserialized: {e}",
255 pkg
256 );
257 return true;
258 }
259 };
260
261 report.to_test(self.config.clone())
262 }
263}