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;
29pub mod TestProviderState;
30pub mod TestResult;
31pub mod TestRun;
32pub mod TestRunStatus;
33
34use std::sync::Arc;
35
36use CommonLibrary::{
37	Environment::Requires::Requires,
38	Error::CommonError::CommonError,
39	IPC::{DTO::ProxyTarget::ProxyTarget, IPCProvider::IPCProvider, SkyEvent::SkyEvent},
40	Testing::TestController::TestController,
41};
42use async_trait::async_trait;
43use serde_json::{Value, json};
44use tauri::Emitter;
45use uuid::Uuid;
46
47use super::MountainEnvironment::MountainEnvironment;
48use crate::dev_log;
49
50#[async_trait]
51impl TestController for MountainEnvironment {
52	async fn RegisterTestController(&self, ControllerId:String, Label:String) -> Result<(), CommonError> {
53		dev_log!(
54			"extensions",
55			"[TestProvider] Registering test controller '{}' with label '{}'",
56			ControllerId,
57			Label
58		);
59
60		let SideCarIdentifier = Some("cocoon-main".to_string());
61
62		let ControllerState = TestControllerState::Struct {
63			ControllerIdentifier:ControllerId.clone(),
64			Label,
65			SideCarIdentifier,
66			IsActive:true,
67			SupportedTestTypes:vec!["unit".to_string(), "integration".to_string()],
68		};
69
70		let mut StateGuard = self.ApplicationState.TestProviderState.write().await;
71		StateGuard.Controllers.insert(ControllerId.clone(), ControllerState);
72		drop(StateGuard);
73
74		self.ApplicationHandle
75			.emit(
76				SkyEvent::TestRegistered.AsStr(),
77				json!({ "ControllerIdentifier": ControllerId }),
78			)
79			.map_err(|Error| {
80				CommonError::IPCError { Description:format!("Failed to emit test registration event: {}", Error) }
81			})?;
82
83		dev_log!(
84			"extensions",
85			"[TestProvider] Test controller '{}' registered successfully",
86			ControllerId
87		);
88		Ok(())
89	}
90
91	async fn RunTests(&self, ControllerIdentifier:String, TestRunRequest:Value) -> Result<(), CommonError> {
92		dev_log!(
93			"extensions",
94			"[TestProvider] Running tests for controller '{}': {:?}",
95			ControllerIdentifier,
96			TestRunRequest
97		);
98
99		let ControllerState = {
100			let StateGuard = self.ApplicationState.TestProviderState.read().await;
101			StateGuard.Controllers.get(&ControllerIdentifier).cloned().ok_or_else(|| {
102				CommonError::TestControllerNotFound { ControllerIdentifier:ControllerIdentifier.clone() }
103			})?
104		};
105
106		let RunIdentifier = Uuid::new_v4().to_string();
107		let TestRunRecord = TestRun::Struct {
108			RunIdentifier:RunIdentifier.clone(),
109			ControllerIdentifier:ControllerIdentifier.clone(),
110			Status:TestRunStatus::Enum::Queued,
111			StartedAt:std::time::Instant::now(),
112			Results:std::collections::HashMap::new(),
113		};
114
115		{
116			let mut StateGuard = self.ApplicationState.TestProviderState.write().await;
117			StateGuard.ActiveRuns.insert(RunIdentifier.clone(), TestRunRecord);
118		}
119
120		self.ApplicationHandle
121			.emit(
122				SkyEvent::TestRunStarted.AsStr(),
123				json!({ "RunIdentifier": RunIdentifier, "ControllerIdentifier": ControllerIdentifier }),
124			)
125			.map_err(|Error| {
126				CommonError::IPCError { Description:format!("Failed to emit test run started event: {}", Error) }
127			})?;
128
129		if let Some(SideCarIdentifier) = &ControllerState.SideCarIdentifier {
130			Self::RunProxiedTests(self, SideCarIdentifier, &RunIdentifier, TestRunRequest).await?;
131		} else {
132			dev_log!(
133				"extensions",
134				"warn: [TestProvider] Native test controllers not yet implemented for '{}'",
135				ControllerIdentifier
136			);
137			let _ = Self::UpdateRunStatus(self, &RunIdentifier, TestRunStatus::Enum::Skipped).await;
138		}
139
140		Ok(())
141	}
142}
143
144impl MountainEnvironment {
145	async fn RunProxiedTests(
146		&self,
147		SideCarIdentifier:&str,
148		RunIdentifier:&str,
149		TestRunRequest:Value,
150	) -> Result<(), CommonError> {
151		dev_log!(
152			"extensions",
153			"[TestProvider] Running proxied tests for run '{}' on sidecar '{}'",
154			RunIdentifier,
155			SideCarIdentifier
156		);
157
158		let _ = Self::UpdateRunStatus(self, RunIdentifier, TestRunStatus::Enum::Running).await;
159
160		let IPCProviderHandle:Arc<dyn IPCProvider> = self.Require();
161		let RPCMethod = format!("{}$runTests", ProxyTarget::ExtHostTesting.GetTargetPrefix());
162		let RPCParams = json!({ "RunIdentifier": RunIdentifier, "TestRunRequest": TestRunRequest });
163
164		match IPCProviderHandle
165			.SendRequestToSideCar(SideCarIdentifier.to_string(), RPCMethod, RPCParams, 300000)
166			.await
167		{
168			Ok(Response) => {
169				if let Ok(Results) = serde_json::from_value::<Vec<TestResult::Struct>>(Response) {
170					let _ = Self::StoreTestResults(self, RunIdentifier, Results).await;
171					let FinalStatus = Self::CalculateRunStatus(self, RunIdentifier).await;
172					let _ = Self::UpdateRunStatus(self, RunIdentifier, FinalStatus).await;
173					dev_log!(
174						"extensions",
175						"[TestProvider] Test run '{}' completed with status {:?}",
176						RunIdentifier,
177						FinalStatus
178					);
179				} else {
180					dev_log!(
181						"extensions",
182						"error: [TestProvider] Failed to parse test results for run '{}'",
183						RunIdentifier
184					);
185					let _ = Self::UpdateRunStatus(self, RunIdentifier, TestRunStatus::Enum::Errored).await;
186				}
187				Ok(())
188			},
189			Err(Error) => {
190				dev_log!("extensions", "error: [TestProvider] Failed to run tests: {}", Error);
191				let _ = Self::UpdateRunStatus(self, RunIdentifier, TestRunStatus::Enum::Errored).await;
192				Err(Error)
193			},
194		}
195	}
196
197	async fn UpdateRunStatus(&self, RunIdentifier:&str, Status:TestRunStatus::Enum) -> Result<(), CommonError> {
198		let mut StateGuard = self.ApplicationState.TestProviderState.write().await;
199		if let Some(TestRunRecord) = StateGuard.ActiveRuns.get_mut(RunIdentifier) {
200			TestRunRecord.Status = Status;
201			drop(StateGuard);
202			self.ApplicationHandle
203				.emit(
204					SkyEvent::TestRunStatusChanged.AsStr(),
205					json!({ "RunIdentifier": RunIdentifier, "Status": Status }),
206				)
207				.map_err(|Error| {
208					CommonError::IPCError { Description:format!("Failed to emit test status change event: {}", Error) }
209				})?;
210			Ok(())
211		} else {
212			Err(CommonError::TestRunNotFound { RunIdentifier:RunIdentifier.to_string() })
213		}
214	}
215
216	async fn StoreTestResults(&self, RunIdentifier:&str, Results:Vec<TestResult::Struct>) -> Result<(), CommonError> {
217		let mut StateGuard = self.ApplicationState.TestProviderState.write().await;
218		if let Some(TestRunRecord) = StateGuard.ActiveRuns.get_mut(RunIdentifier) {
219			for Result in Results {
220				TestRunRecord.Results.insert(Result.TestIdentifier.clone(), Result);
221			}
222			Ok(())
223		} else {
224			Err(CommonError::TestRunNotFound { RunIdentifier:RunIdentifier.to_string() })
225		}
226	}
227
228	async fn CalculateRunStatus(&self, RunIdentifier:&str) -> TestRunStatus::Enum {
229		let StateGuard = self.ApplicationState.TestProviderState.read().await;
230		if let Some(TestRunRecord) = StateGuard.ActiveRuns.get(RunIdentifier) {
231			if TestRunRecord.Results.is_empty() {
232				TestRunStatus::Enum::Passed
233			} else {
234				let HasFailed = TestRunRecord.Results.values().any(|R| R.Status == TestRunStatus::Enum::Failed);
235				let HasErrored = TestRunRecord.Results.values().any(|R| R.Status == TestRunStatus::Enum::Errored);
236				if HasErrored {
237					TestRunStatus::Enum::Errored
238				} else if HasFailed {
239					TestRunStatus::Enum::Failed
240				} else {
241					TestRunStatus::Enum::Passed
242				}
243			}
244		} else {
245			TestRunStatus::Enum::Errored
246		}
247	}
248}