1use std::{collections::HashMap, sync::Arc};
83
84use CommonLibrary::{
85 Environment::Requires::Requires,
86 Error::CommonError::CommonError,
87 IPC::{DTO::ProxyTarget::ProxyTarget, IPCProvider::IPCProvider},
88 Testing::TestController::TestController,
89};
90use async_trait::async_trait;
91use serde::{Deserialize, Serialize};
92use serde_json::{Value, json};
93use tauri::Emitter;
94use uuid::Uuid;
95
96use super::MountainEnvironment::MountainEnvironment;
97use crate::dev_log;
98
99#[derive(Debug, Clone, Serialize, Deserialize)]
101struct TestControllerState {
102 pub ControllerIdentifier:String,
103
104 pub Label:String,
105
106 pub SideCarIdentifier:Option<String>,
107
108 pub IsActive:bool,
109
110 pub SupportedTestTypes:Vec<String>,
111}
112
113#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
115enum TestRunStatus {
116 Queued,
117
118 Running,
119
120 Passed,
121
122 Failed,
123
124 Skipped,
125
126 Errored,
127}
128
129#[derive(Debug, Clone, Serialize, Deserialize)]
131struct TestResult {
132 pub TestIdentifier:String,
133
134 pub FullName:String,
135
136 pub Status:TestRunStatus,
137
138 pub DurationMs:Option<u64>,
139
140 pub ErrorMessage:Option<String>,
141
142 pub StackTrace:Option<String>,
143}
144
145#[derive(Debug, Clone)]
147struct TestRun {
148 pub RunIdentifier:String,
149
150 pub ControllerIdentifier:String,
151
152 pub Status:TestRunStatus,
153
154 pub StartedAt:std::time::Instant,
155
156 pub Results:HashMap<String, TestResult>,
157}
158
159#[derive(Debug)]
161pub struct TestProviderState {
162 pub Controllers:HashMap<String, TestControllerState>,
163
164 pub ActiveRuns:HashMap<String, TestRun>,
165}
166
167impl TestProviderState {
168 pub fn new() -> Self { Self { Controllers:HashMap::new(), ActiveRuns:HashMap::new() } }
169}
170
171#[async_trait]
172impl TestController for MountainEnvironment {
173 async fn RegisterTestController(&self, ControllerId:String, Label:String) -> Result<(), CommonError> {
178 dev_log!(
179 "extensions",
180 "[TestProvider] Registering test controller '{}' with label '{}'",
181 ControllerId,
182 Label
183 );
184
185 let SideCarIdentifier = Some("cocoon-main".to_string());
187
188 let ControllerState = TestControllerState {
189 ControllerIdentifier:ControllerId.clone(),
190
191 Label,
192
193 SideCarIdentifier,
194
195 IsActive:true,
196
197 SupportedTestTypes:vec!["unit".to_string(), "integration".to_string()],
198 };
199
200 let mut StateGuard = self.ApplicationState.TestProviderState.write().await;
202
203 StateGuard.Controllers.insert(ControllerId.clone(), ControllerState);
204
205 drop(StateGuard);
206
207 self.ApplicationHandle
209 .emit("sky://test/registered", json!({ "ControllerIdentifier": ControllerId }))
210 .map_err(|Error| {
211 CommonError::IPCError { Description:format!("Failed to emit test registration event: {}", Error) }
212 })?;
213
214 dev_log!(
215 "extensions",
216 "[TestProvider] Test controller '{}' registered successfully",
217 ControllerId
218 );
219
220 Ok(())
221 }
222
223 async fn RunTests(&self, ControllerIdentifier:String, TestRunRequest:Value) -> Result<(), CommonError> {
229 dev_log!(
230 "extensions",
231 "[TestProvider] Running tests for controller '{}': {:?}",
232 ControllerIdentifier,
233 TestRunRequest
234 );
235
236 let ControllerState = {
238 let StateGuard = self.ApplicationState.TestProviderState.read().await;
239
240 StateGuard.Controllers.get(&ControllerIdentifier).cloned().ok_or_else(|| {
241 CommonError::TestControllerNotFound { ControllerIdentifier:ControllerIdentifier.clone() }
242 })?
243 };
244
245 let RunIdentifier = Uuid::new_v4().to_string();
247
248 let TestRun = TestRun {
249 RunIdentifier:RunIdentifier.clone(),
250
251 ControllerIdentifier:ControllerIdentifier.clone(),
252
253 Status:TestRunStatus::Queued,
254
255 StartedAt:std::time::Instant::now(),
256
257 Results:HashMap::new(),
258 };
259
260 {
261 let mut StateGuard = self.ApplicationState.TestProviderState.write().await;
262
263 StateGuard.ActiveRuns.insert(RunIdentifier.clone(), TestRun);
264 }
265
266 self.ApplicationHandle
268 .emit(
269 "sky://test/run-started",
270 json!({ "RunIdentifier": RunIdentifier, "ControllerIdentifier": ControllerIdentifier }),
271 )
272 .map_err(|Error| {
273 CommonError::IPCError { Description:format!("Failed to emit test run started event: {}", Error) }
274 })?;
275
276 if let Some(SideCarIdentifier) = &ControllerState.SideCarIdentifier {
278 Self::RunProxiedTests(self, SideCarIdentifier, &RunIdentifier, TestRunRequest).await?;
280 } else {
281 dev_log!(
283 "extensions",
284 "warn: [TestProvider] Native test controllers not yet implemented for '{}'",
285 ControllerIdentifier
286 );
287
288 Self::UpdateRunStatus(self, &RunIdentifier, TestRunStatus::Skipped).await;
289 }
290
291 Ok(())
292 }
293}
294
295impl MountainEnvironment {
300 async fn RunProxiedTests(
302 &self,
303
304 SideCarIdentifier:&str,
305
306 RunIdentifier:&str,
307
308 TestRunRequest:Value,
309 ) -> Result<(), CommonError> {
310 dev_log!(
311 "extensions",
312 "[TestProvider] Running proxied tests for run '{}' on sidecar '{}'",
313 RunIdentifier,
314 SideCarIdentifier
315 );
316
317 Self::UpdateRunStatus(self, RunIdentifier, TestRunStatus::Running).await;
319
320 let IPCProvider:Arc<dyn IPCProvider> = self.Require();
321
322 let RPCMethod = format!("{}$runTests", ProxyTarget::ExtHostTesting.GetTargetPrefix());
323
324 let RPCParams = json!({
325 "RunIdentifier": RunIdentifier,
326
327 "TestRunRequest": TestRunRequest,
328
329 });
330
331 match IPCProvider
332 .SendRequestToSideCar(SideCarIdentifier.to_string(), RPCMethod, RPCParams, 300000)
333 .await
334 {
335 Ok(Response) => {
336 if let Ok(Results) = serde_json::from_value::<Vec<TestResult>>(Response) {
338 Self::StoreTestResults(self, RunIdentifier, Results).await;
339
340 let FinalStatus = Self::CalculateRunStatus(self, RunIdentifier).await;
342
343 Self::UpdateRunStatus(self, RunIdentifier, FinalStatus).await;
344
345 dev_log!(
346 "extensions",
347 "[TestProvider] Test run '{}' completed with status {:?}",
348 RunIdentifier,
349 FinalStatus
350 );
351 } else {
352 dev_log!(
353 "extensions",
354 "error: [TestProvider] Failed to parse test results for run '{}'",
355 RunIdentifier
356 );
357
358 Self::UpdateRunStatus(self, RunIdentifier, TestRunStatus::Errored).await;
359 }
360 Ok(())
361 },
362
363 Err(Error) => {
364 dev_log!("extensions", "error: [TestProvider] Failed to run tests: {}", Error);
365
366 let _ = Self::UpdateRunStatus(self, RunIdentifier, TestRunStatus::Errored).await;
367
368 Err(Error)
369 },
370 }
371 }
372
373 async fn UpdateRunStatus(&self, RunIdentifier:&str, Status:TestRunStatus) -> Result<(), CommonError> {
375 let mut StateGuard = self.ApplicationState.TestProviderState.write().await;
376
377 if let Some(TestRun) = StateGuard.ActiveRuns.get_mut(RunIdentifier) {
378 TestRun.Status = Status;
379
380 drop(StateGuard);
381
382 self.ApplicationHandle
384 .emit(
385 "sky://test/run-status-changed",
386 json!({
387 "RunIdentifier": RunIdentifier,
388
389 "Status": Status,
390
391 }),
392 )
393 .map_err(|Error| {
394 CommonError::IPCError { Description:format!("Failed to emit test status change event: {}", Error) }
395 })?;
396
397 Ok(())
398 } else {
399 Err(CommonError::TestRunNotFound { RunIdentifier:RunIdentifier.to_string() })
400 }
401 }
402
403 async fn StoreTestResults(&self, RunIdentifier:&str, Results:Vec<TestResult>) -> Result<(), CommonError> {
405 let mut StateGuard = self.ApplicationState.TestProviderState.write().await;
406
407 if let Some(TestRun) = StateGuard.ActiveRuns.get_mut(RunIdentifier) {
408 for Result in Results {
409 TestRun.Results.insert(Result.TestIdentifier.clone(), Result);
410 }
411 Ok(())
412 } else {
413 Err(CommonError::TestRunNotFound { RunIdentifier:RunIdentifier.to_string() })
414 }
415 }
416
417 async fn CalculateRunStatus(&self, RunIdentifier:&str) -> TestRunStatus {
419 let StateGuard = self.ApplicationState.TestProviderState.read().await;
420
421 if let Some(TestRun) = StateGuard.ActiveRuns.get(RunIdentifier) {
422 if TestRun.Results.is_empty() {
423 TestRunStatus::Passed } else {
425 let HasFailed = TestRun.Results.values().any(|r| r.Status == TestRunStatus::Failed);
426
427 let HasErrored = TestRun.Results.values().any(|r| r.Status == TestRunStatus::Errored);
428
429 if HasErrored {
430 TestRunStatus::Errored
431 } else if HasFailed {
432 TestRunStatus::Failed
433 } else {
434 TestRunStatus::Passed
435 }
436 }
437 } else {
438 TestRunStatus::Errored
439 }
440 }
441}