Skip to main content

Mountain/Environment/
TestProvider.rs

1
2//! # TestProvider (Environment)
3//!
4//! `TestController` impl for `MountainEnvironment`. Hosts the
5//! controller registry and routes test runs through proxied sidecars
6//! (extension-provided test frameworks). Native Rust controllers are
7//! not yet supported - they short-circuit to `Skipped`.
8//!
9//! Layout (one export per file, file name = identity):
10//! - `TestControllerState::Struct` - per-controller registration.
11//! - `TestRunStatus::Enum` - Queued / Running / Passed / Failed / Skipped /
12//!   Errored.
13//! - `TestResult::Struct` - per-test outcome.
14//! - `TestRun::Struct` - active test run record.
15//! - `TestProviderState::Struct` - aggregate controller + active-runs map, held
16//!   inside `ApplicationState` behind a `RwLock`.
17//!
18//! The trait impl `TestController for MountainEnvironment` and its
19//! private helpers stay in this parent file; they are dispatched via
20//! the trait, not directly addressable, so they don't need atomic
21//! split for navigability.
22//!
23//! VS Code reference:
24//! - `vs/workbench/contrib/testing/common/testService.ts`,
25//! - `vs/workbench/contrib/testing/common/testTypes.ts`.
26
27pub mod TestControllerState;
28
29pub mod TestProviderState;
30
31pub mod TestResult;
32
33pub mod TestRun;
34
35pub mod TestRunStatus;
36
37use std::sync::Arc;
38
39use CommonLibrary::{
40	Environment::Requires::Requires,
41	Error::CommonError::CommonError,
42	IPC::{DTO::ProxyTarget::ProxyTarget, IPCProvider::IPCProvider, SkyEvent::SkyEvent},
43	Testing::TestController::TestController,
44};
45use async_trait::async_trait;
46use serde_json::{Value, json};
47use tauri::Emitter;
48use uuid::Uuid;
49
50use super::MountainEnvironment::MountainEnvironment;
51use crate::dev_log;
52
53#[async_trait]
54impl TestController for MountainEnvironment {
55	async fn RegisterTestController(&self, ControllerId:String, Label:String) -> Result<(), CommonError> {
56		dev_log!(
57			"extensions",
58			"[TestProvider] Registering test controller '{}' with label '{}'",
59			ControllerId,
60			Label
61		);
62
63		let SideCarIdentifier = Some("cocoon-main".to_string());
64
65		let ControllerState = TestControllerState::Struct {
66			ControllerIdentifier:ControllerId.clone(),
67
68			Label,
69
70			SideCarIdentifier,
71
72			IsActive:true,
73
74			SupportedTestTypes:vec!["unit".to_string(), "integration".to_string()],
75		};
76
77		let mut StateGuard = self.ApplicationState.TestProviderState.write().await;
78
79		StateGuard.Controllers.insert(ControllerId.clone(), ControllerState);
80
81		drop(StateGuard);
82
83		self.ApplicationHandle
84			.emit(
85				SkyEvent::TestRegistered.AsStr(),
86				json!({ "ControllerIdentifier": ControllerId }),
87			)
88			.map_err(|Error| {
89				CommonError::IPCError { Description:format!("Failed to emit test registration event: {}", Error) }
90			})?;
91
92		dev_log!(
93			"extensions",
94			"[TestProvider] Test controller '{}' registered successfully",
95			ControllerId
96		);
97
98		Ok(())
99	}
100
101	async fn RunTests(&self, ControllerIdentifier:String, TestRunRequest:Value) -> Result<(), CommonError> {
102		dev_log!(
103			"extensions",
104			"[TestProvider] Running tests for controller '{}': {:?}",
105			ControllerIdentifier,
106			TestRunRequest
107		);
108
109		let ControllerState = {
110			let StateGuard = self.ApplicationState.TestProviderState.read().await;
111
112			StateGuard.Controllers.get(&ControllerIdentifier).cloned().ok_or_else(|| {
113				CommonError::TestControllerNotFound { ControllerIdentifier:ControllerIdentifier.clone() }
114			})?
115		};
116
117		let RunIdentifier = Uuid::new_v4().to_string();
118
119		let TestRunRecord = TestRun::Struct {
120			RunIdentifier:RunIdentifier.clone(),
121
122			ControllerIdentifier:ControllerIdentifier.clone(),
123
124			Status:TestRunStatus::Enum::Queued,
125
126			StartedAt:std::time::Instant::now(),
127
128			Results:std::collections::HashMap::new(),
129		};
130
131		{
132			let mut StateGuard = self.ApplicationState.TestProviderState.write().await;
133
134			StateGuard.ActiveRuns.insert(RunIdentifier.clone(), TestRunRecord);
135		}
136
137		self.ApplicationHandle
138			.emit(
139				SkyEvent::TestRunStarted.AsStr(),
140				json!({ "RunIdentifier": RunIdentifier, "ControllerIdentifier": ControllerIdentifier }),
141			)
142			.map_err(|Error| {
143				CommonError::IPCError { Description:format!("Failed to emit test run started event: {}", Error) }
144			})?;
145
146		if let Some(SideCarIdentifier) = &ControllerState.SideCarIdentifier {
147			Self::RunProxiedTests(self, SideCarIdentifier, &RunIdentifier, TestRunRequest).await?;
148		} else {
149			dev_log!(
150				"extensions",
151				"warn: [TestProvider] Native test controllers not yet implemented for '{}'",
152				ControllerIdentifier
153			);
154
155			let _ = Self::UpdateRunStatus(self, &RunIdentifier, TestRunStatus::Enum::Skipped).await;
156		}
157
158		Ok(())
159	}
160}
161
162impl MountainEnvironment {
163	async fn RunProxiedTests(
164		&self,
165
166		SideCarIdentifier:&str,
167
168		RunIdentifier:&str,
169
170		TestRunRequest:Value,
171	) -> Result<(), CommonError> {
172		dev_log!(
173			"extensions",
174			"[TestProvider] Running proxied tests for run '{}' on sidecar '{}'",
175			RunIdentifier,
176			SideCarIdentifier
177		);
178
179		let _ = Self::UpdateRunStatus(self, RunIdentifier, TestRunStatus::Enum::Running).await;
180
181		let IPCProviderHandle:Arc<dyn IPCProvider> = self.Require();
182
183		let RPCMethod = format!("{}$runTests", ProxyTarget::ExtHostTesting.GetTargetPrefix());
184
185		let RPCParams = json!({ "RunIdentifier": RunIdentifier, "TestRunRequest": TestRunRequest });
186
187		match IPCProviderHandle
188			.SendRequestToSideCar(SideCarIdentifier.to_string(), RPCMethod, RPCParams, 300000)
189			.await
190		{
191			Ok(Response) => {
192				if let Ok(Results) = serde_json::from_value::<Vec<TestResult::Struct>>(Response) {
193					let _ = Self::StoreTestResults(self, RunIdentifier, Results).await;
194
195					let FinalStatus = Self::CalculateRunStatus(self, RunIdentifier).await;
196
197					let _ = Self::UpdateRunStatus(self, RunIdentifier, FinalStatus).await;
198
199					dev_log!(
200						"extensions",
201						"[TestProvider] Test run '{}' completed with status {:?}",
202						RunIdentifier,
203						FinalStatus
204					);
205				} else {
206					dev_log!(
207						"extensions",
208						"error: [TestProvider] Failed to parse test results for run '{}'",
209						RunIdentifier
210					);
211
212					let _ = Self::UpdateRunStatus(self, RunIdentifier, TestRunStatus::Enum::Errored).await;
213				}
214
215				Ok(())
216			},
217
218			Err(Error) => {
219				dev_log!("extensions", "error: [TestProvider] Failed to run tests: {}", Error);
220
221				let _ = Self::UpdateRunStatus(self, RunIdentifier, TestRunStatus::Enum::Errored).await;
222
223				Err(Error)
224			},
225		}
226	}
227
228	async fn UpdateRunStatus(&self, RunIdentifier:&str, Status:TestRunStatus::Enum) -> Result<(), CommonError> {
229		let mut StateGuard = self.ApplicationState.TestProviderState.write().await;
230
231		if let Some(TestRunRecord) = StateGuard.ActiveRuns.get_mut(RunIdentifier) {
232			TestRunRecord.Status = Status;
233
234			drop(StateGuard);
235
236			self.ApplicationHandle
237				.emit(
238					SkyEvent::TestRunStatusChanged.AsStr(),
239					json!({ "RunIdentifier": RunIdentifier, "Status": Status }),
240				)
241				.map_err(|Error| {
242					CommonError::IPCError { Description:format!("Failed to emit test status change event: {}", Error) }
243				})?;
244
245			Ok(())
246		} else {
247			Err(CommonError::TestRunNotFound { RunIdentifier:RunIdentifier.to_string() })
248		}
249	}
250
251	async fn StoreTestResults(&self, RunIdentifier:&str, Results:Vec<TestResult::Struct>) -> Result<(), CommonError> {
252		let mut StateGuard = self.ApplicationState.TestProviderState.write().await;
253
254		if let Some(TestRunRecord) = StateGuard.ActiveRuns.get_mut(RunIdentifier) {
255			for Result in Results {
256				TestRunRecord.Results.insert(Result.TestIdentifier.clone(), Result);
257			}
258
259			Ok(())
260		} else {
261			Err(CommonError::TestRunNotFound { RunIdentifier:RunIdentifier.to_string() })
262		}
263	}
264
265	async fn CalculateRunStatus(&self, RunIdentifier:&str) -> TestRunStatus::Enum {
266		let StateGuard = self.ApplicationState.TestProviderState.read().await;
267
268		if let Some(TestRunRecord) = StateGuard.ActiveRuns.get(RunIdentifier) {
269			if TestRunRecord.Results.is_empty() {
270				TestRunStatus::Enum::Passed
271			} else {
272				let HasFailed = TestRunRecord.Results.values().any(|R| R.Status == TestRunStatus::Enum::Failed);
273
274				let HasErrored = TestRunRecord.Results.values().any(|R| R.Status == TestRunStatus::Enum::Errored);
275
276				if HasErrored {
277					TestRunStatus::Enum::Errored
278				} else if HasFailed {
279					TestRunStatus::Enum::Failed
280				} else {
281					TestRunStatus::Enum::Passed
282				}
283			}
284		} else {
285			TestRunStatus::Enum::Errored
286		}
287	}
288}