Skip to main content

Mountain/Environment/
TestProvider.rs

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