@@ -3,7 +3,7 @@ use std::{
33 collections:: HashMap ,
44 env, fs,
55 path:: { Path , PathBuf } ,
6- time:: SystemTime ,
6+ time:: { Duration , SystemTime } ,
77} ;
88
99use api:: { client:: ApiClient , message} ;
@@ -19,18 +19,27 @@ use proto::test_context::test_run::{
1919 CodeOwner , TestCaseRun , TestCaseRunStatus , TestReport as TestReportProto , TestResult ,
2020 UploaderMetadata ,
2121} ;
22+ use serde:: { Deserialize , Serialize } ;
2223use third_party:: sentry;
2324use tracing_subscriber:: { filter:: FilterFn , prelude:: * } ;
2425use trunk_analytics_cli:: { context:: gather_initial_test_context, upload_command:: run_upload} ;
26+ use uuid:: Uuid ;
2527#[ cfg( feature = "wasm" ) ]
2628use wasm_bindgen:: prelude:: wasm_bindgen;
2729
30+ #[ derive( Debug , Clone , Serialize , Deserialize ) ]
31+ struct QuarantinedTestsDiskCacheEntry {
32+ quarantined_tests : HashMap < String , bool > ,
33+ cached_at_secs : u64 ,
34+ }
35+
2836#[ derive( Debug , Clone , PartialEq ) ]
2937pub struct TestReport {
3038 test_report : TestReportProto ,
3139 command : String ,
3240 started_at : SystemTime ,
3341 quarantined_tests : Option < HashMap < String , bool > > ,
42+ quarantined_tests_disk_cache_ttl : Duration ,
3443 codeowners : Option < CodeOwners > ,
3544 variant : Option < String > ,
3645 repo : Option < BundleRepo > ,
@@ -143,14 +152,21 @@ impl MutTestReport {
143152 . as_deref ( ) ,
144153 )
145154 } ) ;
155+ let quarantined_tests_disk_cache_ttl = Duration :: from_secs (
156+ env:: var ( constants:: TRUNK_QUARANTINED_TESTS_DISK_CACHE_TTL_SECS_ENV )
157+ . ok ( )
158+ . and_then ( |v| v. parse ( ) . ok ( ) )
159+ . unwrap_or ( constants:: DEFAULT_QUARANTINED_TESTS_DISK_CACHE_TTL_SECS ) ,
160+ ) ;
146161 Self ( RefCell :: new ( TestReport {
147162 test_report,
148163 command,
149164 started_at,
150165 quarantined_tests : None ,
166+ quarantined_tests_disk_cache_ttl,
151167 codeowners,
152168 repo,
153- variant : variant . clone ( ) ,
169+ variant,
154170 } ) )
155171 }
156172
@@ -298,22 +314,127 @@ impl MutTestReport {
298314 }
299315 }
300316
317+ fn get_quarantined_tests_cache_file_path ( & self , org_url_slug : & str , repo_url : & str ) -> PathBuf {
318+ let cache_key = Uuid :: new_v5 (
319+ & Uuid :: NAMESPACE_URL ,
320+ format ! ( "{org_url_slug}#{repo_url}" ) . as_bytes ( ) ,
321+ )
322+ . to_string ( ) ;
323+ let quarantined_tests_cache_file_name = format ! ( "quarantined_tests_{cache_key}.json" ) ;
324+
325+ env:: temp_dir ( )
326+ . join ( constants:: CACHE_DIR )
327+ . join ( quarantined_tests_cache_file_name)
328+ }
329+
330+ fn load_quarantined_tests_from_disk_cache (
331+ & self ,
332+ org_url_slug : & str ,
333+ repo_url : & str ,
334+ ) -> Option < HashMap < String , bool > > {
335+ let cache_path = self . get_quarantined_tests_cache_file_path ( org_url_slug, repo_url) ;
336+
337+ let cache_data = match fs:: read_to_string ( & cache_path) {
338+ Ok ( data) => data,
339+ Err ( err) => {
340+ tracing:: warn!( "Failed to read quarantined tests cache file: {:?}" , err) ;
341+ return None ;
342+ }
343+ } ;
344+
345+ let cache_entry: QuarantinedTestsDiskCacheEntry = match serde_json:: from_str ( & cache_data) {
346+ Ok ( entry) => entry,
347+ Err ( err) => {
348+ tracing:: warn!( "Failed to parse quarantined tests cache file: {:?}" , err) ;
349+ return None ;
350+ }
351+ } ;
352+
353+ let now = match SystemTime :: now ( ) . duration_since ( SystemTime :: UNIX_EPOCH ) {
354+ Ok ( duration) => duration. as_secs ( ) ,
355+ Err ( _) => {
356+ tracing:: warn!( "Failed to get current time" ) ;
357+ return None ;
358+ }
359+ } ;
360+ let cache_age = now. saturating_sub ( cache_entry. cached_at_secs ) ;
361+
362+ if cache_age <= self . 0 . borrow ( ) . quarantined_tests_disk_cache_ttl . as_secs ( ) {
363+ Some ( cache_entry. quarantined_tests )
364+ } else {
365+ let _ = fs:: remove_file ( & cache_path) ;
366+ None
367+ }
368+ }
369+
370+ fn save_quarantined_tests_to_disk_cache (
371+ & self ,
372+ org_url_slug : & str ,
373+ repo_url : & str ,
374+ quarantined_tests : & HashMap < String , bool > ,
375+ ) {
376+ let cache_path = self . get_quarantined_tests_cache_file_path ( org_url_slug, repo_url) ;
377+
378+ let now = match SystemTime :: now ( ) . duration_since ( SystemTime :: UNIX_EPOCH ) {
379+ Ok ( duration) => duration. as_secs ( ) ,
380+ Err ( _) => {
381+ tracing:: warn!( "Failed to get current time" ) ;
382+ return ;
383+ }
384+ } ;
385+
386+ let cache_entry = QuarantinedTestsDiskCacheEntry {
387+ quarantined_tests : quarantined_tests. clone ( ) ,
388+ cached_at_secs : now,
389+ } ;
390+
391+ // create cache directory if it doesn't exist
392+ let cache_dir = match cache_path. parent ( ) {
393+ Some ( dir) => dir,
394+ None => {
395+ tracing:: warn!( "Failed to get cache directory" ) ;
396+ return ;
397+ }
398+ } ;
399+ if let Err ( err) = fs:: create_dir_all ( cache_dir) {
400+ tracing:: warn!( "Failed to create cache directory: {:?}" , err) ;
401+ return ;
402+ }
403+
404+ if let Ok ( json) = serde_json:: to_string ( & cache_entry) {
405+ if let Err ( err) = fs:: write ( & cache_path, json) {
406+ tracing:: warn!( "Failed to write quarantined tests cache file: {:?}" , err) ;
407+ }
408+ }
409+ }
410+
301411 fn populate_quarantined_tests (
302412 & self ,
303413 api_client : & ApiClient ,
304414 repo : & RepoUrlParts ,
305415 repo_url : String ,
306416 org_url_slug : String ,
307417 ) {
418+ // first check in-memory cache
308419 if self . 0 . borrow ( ) . quarantined_tests . as_ref ( ) . is_some ( ) {
309- // already fetched
310420 return ;
311421 }
422+
423+ // then check disk cache
424+ if let Some ( quarantined_tests) =
425+ self . load_quarantined_tests_from_disk_cache ( & org_url_slug, & repo_url)
426+ {
427+ // update in-memory cache
428+ self . 0 . borrow_mut ( ) . quarantined_tests = Some ( quarantined_tests) ;
429+ return ;
430+ }
431+
432+ // cache miss - make API call
312433 let mut quarantined_tests = HashMap :: new ( ) ;
313434 let request = message:: GetQuarantineConfigRequest {
314- org_url_slug,
435+ org_url_slug : org_url_slug . clone ( ) ,
315436 test_identifiers : vec ! [ ] ,
316- remote_urls : vec ! [ repo_url] ,
437+ remote_urls : vec ! [ repo_url. clone ( ) ] ,
317438 repo : repo. clone ( ) ,
318439 } ;
319440 let response = tokio:: runtime:: Builder :: new_multi_thread ( )
@@ -336,7 +457,12 @@ impl MutTestReport {
336457 ) ;
337458 }
338459 }
339- self . 0 . borrow_mut ( ) . quarantined_tests = Some ( quarantined_tests) ;
460+
461+ println ! ( "quarantined_tests: {quarantined_tests:?}" ) ;
462+
463+ // update both in-memory and disk cache
464+ self . 0 . borrow_mut ( ) . quarantined_tests = Some ( quarantined_tests. clone ( ) ) ;
465+ self . save_quarantined_tests_to_disk_cache ( & org_url_slug, & repo_url, & quarantined_tests) ;
340466 }
341467
342468 fn get_org_url_slug ( & self ) -> String {
0 commit comments