ic_testkit/artifacts/
workspace.rs1use ic_host_process::tool::{ExecutionFailure, OutputLimit, OutputLimits, capture_command};
2use std::{
3 io,
4 path::{Path, PathBuf},
5 process::Command,
6};
7
8pub fn workspace_root_for(crate_manifest_dir: impl AsRef<Path>) -> io::Result<PathBuf> {
20 let manifest = crate_manifest_dir
21 .as_ref()
22 .canonicalize()?
23 .join("Cargo.toml");
24 let cargo = std::env::var_os("CARGO").unwrap_or_else(|| "cargo".into());
25 let mut command = Command::new(cargo);
26 command
27 .env("RUSTUP_AUTO_INSTALL", "0")
28 .args([
29 "--offline",
30 "locate-project",
31 "--workspace",
32 "--message-format=json",
33 ])
34 .arg("--manifest-path")
35 .arg(manifest);
36 locate_workspace(&mut command)
37}
38
39fn locate_workspace(command: &mut Command) -> io::Result<PathBuf> {
40 let output = capture_command(
41 command,
42 OutputLimits {
43 stdout: OutputLimit::Terminate(64 * 1024),
44 stderr: OutputLimit::Terminate(64 * 1024),
45 timeout: None,
46 },
47 )
48 .and_then(ic_host_process::tool::CompletedExecution::require_complete)
49 .map_err(|error| {
50 let kind = match error.execution_error().map(|execution| &execution.failure) {
51 Some(ExecutionFailure::Io { source, .. }) => source.kind(),
52 _ => io::ErrorKind::Other,
53 };
54 io::Error::new(kind, error)
55 })?;
56 if !output.status.success() {
57 return Err(io::Error::new(
58 io::ErrorKind::InvalidInput,
59 format!(
60 "Cargo workspace discovery failed ({}): {}",
61 output.status,
62 String::from_utf8_lossy(&output.stderr).trim()
63 ),
64 ));
65 }
66 let response: serde_json::Value = serde_json::from_slice(&output.stdout)
67 .map_err(|error| io::Error::new(io::ErrorKind::InvalidData, error))?;
68 let manifest = response
69 .get("root")
70 .and_then(serde_json::Value::as_str)
71 .map(Path::new)
72 .filter(|path| {
73 path.is_absolute() && path.file_name() == Some(std::ffi::OsStr::new("Cargo.toml"))
74 })
75 .ok_or_else(|| {
76 io::Error::new(
77 io::ErrorKind::InvalidData,
78 "Cargo returned no absolute workspace manifest",
79 )
80 })?;
81 manifest.parent().map(Path::to_owned).ok_or_else(|| {
82 io::Error::new(
83 io::ErrorKind::InvalidData,
84 "Cargo workspace manifest has no parent",
85 )
86 })
87}
88
89#[must_use]
91pub fn test_target_dir(workspace_root: &Path, name: &str) -> PathBuf {
92 workspace_root.join("target").join(name)
93}
94
95#[cfg(all(test, unix))]
96mod tests {
97 use super::*;
98 use ic_host_process::tool::ToolError;
99 use std::fs;
100
101 #[test]
102 fn discovery_refuses_oversized_complete_json_and_preserves_invocation_errors() {
103 let root = crate::artifacts::test_support::unique_temp_directory("discovery-capture");
104 let mut response = br#"{"root":"/tmp/Cargo.toml"}"#.to_vec();
105 response.resize(64 * 1024, b' ');
106 fs::write(root.join("response"), &response).unwrap();
107 let mut command = Command::new("/bin/sh");
108 command.current_dir(&root).args(["-c", "cat response"]);
109 assert_eq!(locate_workspace(&mut command).unwrap(), Path::new("/tmp"));
110 response.push(b' ');
111 fs::write(root.join("response"), &response).unwrap();
112 let error = locate_workspace(&mut command).unwrap_err();
113 assert!(
114 error
115 .get_ref()
116 .unwrap()
117 .downcast_ref::<ToolError>()
118 .is_some()
119 );
120 let mut failed = Command::new("/bin/sh");
121 failed.args(["-c", "printf refused >&2; exit 23"]);
122 let error = locate_workspace(&mut failed).unwrap_err();
123 assert_eq!(error.kind(), io::ErrorKind::InvalidInput);
124 assert!(error.to_string().contains("refused"));
125 let mut missing = Command::new(root.join("absent"));
126 let error = locate_workspace(&mut missing).unwrap_err();
127 assert_eq!(error.kind(), io::ErrorKind::NotFound);
128 assert!(
129 error
130 .get_ref()
131 .unwrap()
132 .downcast_ref::<ToolError>()
133 .is_some()
134 );
135 fs::remove_dir_all(root).unwrap();
136 }
137}