wasmer_argus/argus/tester/
cli_tester.rs

1use 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}