1use std::{collections::HashMap, sync::Arc};
84
85use CommonLibrary::{
86 Environment::Requires::Requires,
87 Error::CommonError::CommonError,
88 IPC::{DTO::ProxyTarget::ProxyTarget, IPCProvider::IPCProvider},
89 Testing::TestController::TestController,
90};
91use async_trait::async_trait;
92use log::{debug, error, info, warn};
93use serde::{Deserialize, Serialize};
94use serde_json::{Value, json};
95use tauri::Emitter;
96use tokio::sync::RwLock;
97use uuid::Uuid;
98
99use super::{MountainEnvironment::MountainEnvironment, Utility};
100
101#[derive(Debug, Clone, Serialize, Deserialize)]
103struct TestControllerState {
104 pub ControllerIdentifier:String,
105
106 pub Label:String,
107
108 pub SideCarIdentifier:Option<String>,
109
110 pub IsActive:bool,
111
112 pub SupportedTestTypes:Vec<String>,
113}
114
115#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
117enum TestRunStatus {
118 Queued,
119
120 Running,
121
122 Passed,
123
124 Failed,
125
126 Skipped,
127
128 Errored,
129}
130
131#[derive(Debug, Clone, Serialize, Deserialize)]
133struct TestResult {
134 pub TestIdentifier:String,
135
136 pub FullName:String,
137
138 pub Status:TestRunStatus,
139
140 pub DurationMs:Option<u64>,
141
142 pub ErrorMessage:Option<String>,
143
144 pub StackTrace:Option<String>,
145}
146
147#[derive(Debug, Clone)]
149struct TestRun {
150 pub RunIdentifier:String,
151
152 pub ControllerIdentifier:String,
153
154 pub Status:TestRunStatus,
155
156 pub StartedAt:std::time::Instant,
157
158 pub Results:HashMap<String, TestResult>,
159}
160
161#[derive(Debug)]
163pub struct TestProviderState {
164 pub Controllers:HashMap<String, TestControllerState>,
165
166 pub ActiveRuns:HashMap<String, TestRun>,
167}
168
169impl TestProviderState {
170 pub fn new() -> Self { Self { Controllers:HashMap::new(), ActiveRuns:HashMap::new() } }
171}
172
173#[async_trait]
174impl TestController for MountainEnvironment {
175 async fn RegisterTestController(&self, ControllerId:String, Label:String) -> Result<(), CommonError> {
180 info!(
181 "[TestProvider] Registering test controller '{}' with label '{}'",
182 ControllerId, 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 debug!("[TestProvider] Test controller '{}' registered successfully", ControllerId);
215
216 Ok(())
217 }
218
219 async fn RunTests(&self, ControllerIdentifier:String, TestRunRequest:Value) -> Result<(), CommonError> {
225 info!(
226 "[TestProvider] Running tests for controller '{}': {:?}",
227 ControllerIdentifier, TestRunRequest
228 );
229
230 let ControllerState = {
232 let StateGuard = self.ApplicationState.TestProviderState.read().await;
233
234 StateGuard.Controllers.get(&ControllerIdentifier).cloned().ok_or_else(|| {
235 CommonError::TestControllerNotFound { ControllerIdentifier:ControllerIdentifier.clone() }
236 })?
237 };
238
239 let RunIdentifier = Uuid::new_v4().to_string();
241
242 let TestRun = TestRun {
243 RunIdentifier:RunIdentifier.clone(),
244
245 ControllerIdentifier:ControllerIdentifier.clone(),
246
247 Status:TestRunStatus::Queued,
248
249 StartedAt:std::time::Instant::now(),
250
251 Results:HashMap::new(),
252 };
253
254 {
255 let mut StateGuard = self.ApplicationState.TestProviderState.write().await;
256
257 StateGuard.ActiveRuns.insert(RunIdentifier.clone(), TestRun);
258 }
259
260 self.ApplicationHandle
262 .emit(
263 "sky://test/run-started",
264 json!({ "RunIdentifier": RunIdentifier, "ControllerIdentifier": ControllerIdentifier }),
265 )
266 .map_err(|Error| {
267 CommonError::IPCError { Description:format!("Failed to emit test run started event: {}", Error) }
268 })?;
269
270 if let Some(SideCarIdentifier) = &ControllerState.SideCarIdentifier {
272 Self::RunProxiedTests(self, SideCarIdentifier, &RunIdentifier, TestRunRequest).await?;
274 } else {
275 warn!(
277 "[TestProvider] Native test controllers not yet implemented for '{}'",
278 ControllerIdentifier
279 );
280
281 Self::UpdateRunStatus(self, &RunIdentifier, TestRunStatus::Skipped).await;
282 }
283
284 Ok(())
285 }
286}
287
288impl MountainEnvironment {
293 async fn RunProxiedTests(
295 &self,
296
297 SideCarIdentifier:&str,
298
299 RunIdentifier:&str,
300
301 TestRunRequest:Value,
302 ) -> Result<(), CommonError> {
303 info!(
304 "[TestProvider] Running proxied tests for run '{}' on sidecar '{}'",
305 RunIdentifier, SideCarIdentifier
306 );
307
308 Self::UpdateRunStatus(self, RunIdentifier, TestRunStatus::Running).await;
310
311 let IPCProvider:Arc<dyn IPCProvider> = self.Require();
312
313 let RPCMethod = format!("{}$runTests", ProxyTarget::ExtHostTesting.GetTargetPrefix());
314
315 let RPCParams = json!({
316 "RunIdentifier": RunIdentifier,
317
318 "TestRunRequest": TestRunRequest,
319
320 });
321
322 match IPCProvider
323 .SendRequestToSideCar(SideCarIdentifier.to_string(), RPCMethod, RPCParams, 300000)
324 .await
325 {
326 Ok(Response) => {
327 if let Ok(Results) = serde_json::from_value::<Vec<TestResult>>(Response) {
329 Self::StoreTestResults(self, RunIdentifier, Results).await;
330
331 let FinalStatus = Self::CalculateRunStatus(self, RunIdentifier).await;
333
334 Self::UpdateRunStatus(self, RunIdentifier, FinalStatus).await;
335
336 info!(
337 "[TestProvider] Test run '{}' completed with status {:?}",
338 RunIdentifier, FinalStatus
339 );
340 } else {
341 error!("[TestProvider] Failed to parse test results for run '{}'", RunIdentifier);
342
343 Self::UpdateRunStatus(self, RunIdentifier, TestRunStatus::Errored).await;
344 }
345 Ok(())
346 },
347
348 Err(Error) => {
349 error!("[TestProvider] Failed to run tests: {}", Error);
350
351 Self::UpdateRunStatus(self, RunIdentifier, TestRunStatus::Errored).await;
352
353 Err(Error)
354 },
355 }
356 }
357
358 async fn UpdateRunStatus(&self, RunIdentifier:&str, Status:TestRunStatus) -> Result<(), CommonError> {
360 let mut StateGuard = self.ApplicationState.TestProviderState.write().await;
361
362 if let Some(TestRun) = StateGuard.ActiveRuns.get_mut(RunIdentifier) {
363 TestRun.Status = Status;
364
365 drop(StateGuard);
366
367 self.ApplicationHandle
369 .emit(
370 "sky://test/run-status-changed",
371 json!({
372 "RunIdentifier": RunIdentifier,
373
374 "Status": Status,
375
376 }),
377 )
378 .map_err(|Error| {
379 CommonError::IPCError { Description:format!("Failed to emit test status change event: {}", Error) }
380 })?;
381
382 Ok(())
383 } else {
384 Err(CommonError::TestRunNotFound { RunIdentifier:RunIdentifier.to_string() })
385 }
386 }
387
388 async fn StoreTestResults(&self, RunIdentifier:&str, Results:Vec<TestResult>) -> Result<(), CommonError> {
390 let mut StateGuard = self.ApplicationState.TestProviderState.write().await;
391
392 if let Some(TestRun) = StateGuard.ActiveRuns.get_mut(RunIdentifier) {
393 for Result in Results {
394 TestRun.Results.insert(Result.TestIdentifier.clone(), Result);
395 }
396 Ok(())
397 } else {
398 Err(CommonError::TestRunNotFound { RunIdentifier:RunIdentifier.to_string() })
399 }
400 }
401
402 async fn CalculateRunStatus(&self, RunIdentifier:&str) -> TestRunStatus {
404 let StateGuard = self.ApplicationState.TestProviderState.read().await;
405
406 if let Some(TestRun) = StateGuard.ActiveRuns.get(RunIdentifier) {
407 if TestRun.Results.is_empty() {
408 TestRunStatus::Passed } else {
410 let HasFailed = TestRun.Results.values().any(|r| r.Status == TestRunStatus::Failed);
411
412 let HasErrored = TestRun.Results.values().any(|r| r.Status == TestRunStatus::Errored);
413
414 if HasErrored {
415 TestRunStatus::Errored
416 } else if HasFailed {
417 TestRunStatus::Failed
418 } else {
419 TestRunStatus::Passed
420 }
421 }
422 } else {
423 TestRunStatus::Errored
424 }
425 }
426}