trident_client/commander/
mod.rs1use anyhow::Context;
2use fehler::throw;
3use fehler::throws;
4use std::env;
5use std::io;
6use std::path::Path;
7use std::path::PathBuf;
8use std::process::Stdio;
9use std::string::FromUtf8Error;
10use thiserror::Error;
11use tokio::io::AsyncWriteExt;
12use tokio::process::Child;
13use tokio::process::Command;
14use tokio::signal;
15
16use crate::constants::TESTS_WORKSPACE_DIRECTORY;
17
18mod fuzz;
19
20#[derive(Error, Debug)]
21pub enum Error {
22 #[error("{0:?}")]
23 Io(#[from] io::Error),
24 #[error("{0:?}")]
25 Utf8(#[from] FromUtf8Error),
26 #[error("build programs failed")]
27 BuildProgramsFailed,
28 #[error("fuzzing failed")]
29 FuzzingFailed,
30 #[error("Fuzzing found failing invariants or unhandled panics")]
31 FuzzingFailedInvariantOrPanic,
32 #[error("Coverage error: {0}")]
33 Coverage(#[from] crate::coverage::CoverageError),
34 #[error("Cannot find the trident-tests directory in the current workspace")]
35 BadWorkspace,
36 #[error("{0:?}")]
37 Anyhow(#[from] anyhow::Error),
38}
39
40#[derive(Default)]
43pub struct Commander {
44 root: PathBuf,
45}
46
47impl Commander {
48 pub fn new(root: &str) -> Self {
49 Self {
50 root: Path::new(&root).to_path_buf(),
51 }
52 }
53
54 #[throws]
55 pub async fn build_anchor_project(root: &Path, program_name: Option<String>) {
56 let mut cmd = Command::new("anchor");
57 cmd.arg("build");
58 cmd.current_dir(root);
59
60 if let Some(name) = program_name {
61 cmd.args(["-p", name.as_str()]);
62 }
63
64 let success = cmd.spawn()?.wait().await?.success();
65 if !success {
66 throw!(Error::BuildProgramsFailed);
67 }
68 }
69
70 #[throws]
72 pub async fn format_program_code(code: &str) -> String {
73 let mut rustfmt = Command::new("rustfmt")
74 .args(["--edition", "2018"])
75 .kill_on_drop(true)
76 .stdin(Stdio::piped())
77 .stdout(Stdio::piped())
78 .spawn()?;
79 if let Some(stdio) = &mut rustfmt.stdin {
80 stdio.write_all(code.as_bytes()).await?;
81 }
82 let output = rustfmt.wait_with_output().await?;
83 String::from_utf8(output.stdout)?
84 }
85
86 #[throws]
88 pub async fn format_program_code_nightly(code: &str) -> String {
89 let mut rustfmt = Command::new("rustfmt")
90 .arg("+nightly")
91 .arg("--config")
92 .arg(
93 "\
94 edition=2021,\
95 wrap_comments=true,\
96 normalize_doc_attributes=true",
97 )
98 .kill_on_drop(true)
99 .stdin(Stdio::piped())
100 .stdout(Stdio::piped())
101 .spawn()?;
102 if let Some(stdio) = &mut rustfmt.stdin {
103 stdio.write_all(code.as_bytes()).await?;
104 }
105 let output = rustfmt.wait_with_output().await?;
106 String::from_utf8(output.stdout)?
107 }
108
109 #[throws]
119 async fn handle_child(child: &mut Child, with_exit_code: bool) {
120 tokio::select! {
121 res = child.wait() =>
122 match res {
123 Ok(status) => match status.code() {
124 Some(code) => {
125 match (code, with_exit_code) {
126 (0, _) => {} (99, true) => throw!(Error::FuzzingFailedInvariantOrPanic), (99, false) => {} (_, _) => throw!(Error::FuzzingFailed), }
131 }
132 None => throw!(Error::FuzzingFailed),
133 },
134 Err(e) => throw!(e),
135 },
136 _ = signal::ctrl_c() => {
137 let _res = child.wait().await?;
138
139 tokio::time::sleep(tokio::time::Duration::from_millis(1000)).await;
140 },
141 }
142 }
143 #[throws]
144 pub async fn clean_target(&self) {
145 self.clean_anchor_target().await?;
146 self.clean_fuzz_target().await?;
147 }
148
149 #[throws]
150 async fn clean_anchor_target(&self) {
151 Command::new("anchor").arg("clean").spawn()?.wait().await?;
152 }
153
154 #[throws]
155 #[allow(dead_code)]
156 async fn clean_fuzz_target(&self) {
157 let trident_tests_dir = self.root.join(TESTS_WORKSPACE_DIRECTORY);
158 Command::new("cargo")
159 .arg("clean")
160 .current_dir(trident_tests_dir)
161 .spawn()?
162 .wait()
163 .await?;
164 }
165
166 pub fn get_target_dir(&self) -> Result<String, Error> {
167 let current_dir = env::current_dir()?;
168 let mut dir = Some(current_dir.as_path());
169 while let Some(cwd) = dir {
170 for file in std::fs::read_dir(cwd).with_context(|| {
171 format!("Error reading the directory with path: {}", cwd.display())
172 })? {
173 let path = file
174 .with_context(|| {
175 format!("Error reading the directory with path: {}", cwd.display())
176 })?
177 .path();
178 if let Some(filename) = path.file_name() {
179 if filename.to_str() == Some(TESTS_WORKSPACE_DIRECTORY) {
180 return Ok(path.join("target").to_str().unwrap().to_string());
181 }
182 }
183 }
184 dir = cwd.parent();
185 }
186 throw!(Error::BadWorkspace);
187 }
188}