Skip to main content

DevelopmentNodeEnvironment_MicrosoftVSCodeDependency_22NodeVersion_Bundle_Clean_Debug_ElectronProfile_EsbuildCompiler_Mountain/Environment/
TestProvider.rs

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