@@ -9,6 +9,7 @@ use crate::storage::Storage;
99use super :: OriginalPosition ;
1010use super :: s3_key;
1111
12+ const PROGUARD_DIR : & str = "proguard" ;
1213const PROGUARD_FILE_NAME : & str = "proguard/mapping.txt" ;
1314
1415/// A parsed proguard mapping file.
@@ -99,6 +100,17 @@ impl ProguardMapping {
99100 Ok ( ProguardMapping { classes } )
100101 }
101102
103+ fn parse_many < ' a > ( inputs : impl IntoIterator < Item = & ' a str > ) -> Result < Self , AppError > {
104+ let mut classes = HashMap :: new ( ) ;
105+
106+ for input in inputs {
107+ let mapping = Self :: parse ( input) ?;
108+ classes. extend ( mapping. classes ) ;
109+ }
110+
111+ Ok ( ProguardMapping { classes } )
112+ }
113+
102114 fn retrace ( & self , stacktrace : & str ) -> String {
103115 let mut output = String :: with_capacity ( stacktrace. len ( ) ) ;
104116 for ( i, line) in stacktrace. lines ( ) . enumerate ( ) {
@@ -328,21 +340,44 @@ pub async fn ingest(
328340 build_id : & str ,
329341 mapping : & str ,
330342) -> Result < ( ) , AppError > {
331- let key = s3_key ( project_id, build_id, PROGUARD_FILE_NAME ) ;
343+ let key = proguard_s3_key ( project_id, build_id) ;
332344 storage. put ( & key, mapping. as_bytes ( ) ) . await
333345}
334346
335- pub fn retrace_stacktrace ( data : & [ u8 ] , stacktrace : & str ) -> Result < String , AppError > {
336- let content = std:: str:: from_utf8 ( data)
337- . map_err ( |e| AppError :: BadRequest ( format ! ( "invalid proguard mapping: {e}" ) ) ) ?;
338- let mapping = ProguardMapping :: parse ( content) ?;
347+ pub fn retrace_stacktrace < ' a > (
348+ mapping_parts : impl IntoIterator < Item = & ' a [ u8 ] > ,
349+ stacktrace : & str ,
350+ ) -> Result < String , AppError > {
351+ let mut contents = Vec :: new ( ) ;
352+
353+ for data in mapping_parts {
354+ let content = std:: str:: from_utf8 ( data)
355+ . map_err ( |e| AppError :: BadRequest ( format ! ( "invalid proguard mapping: {e}" ) ) ) ?;
356+ contents. push ( content) ;
357+ }
358+
359+ let mapping = ProguardMapping :: parse_many ( contents) ?;
339360 Ok ( mapping. retrace ( stacktrace) )
340361}
341362
363+ pub fn proguard_s3_prefix ( project_id : Uuid , build_id : & str ) -> String {
364+ s3_key ( project_id, build_id, PROGUARD_DIR )
365+ }
366+
342367pub fn proguard_s3_key ( project_id : Uuid , build_id : & str ) -> String {
343368 s3_key ( project_id, build_id, PROGUARD_FILE_NAME )
344369}
345370
371+ #[ cfg( test) ]
372+ fn parse_test_mapping ( input : & str ) -> ProguardMapping {
373+ ProguardMapping :: parse ( input) . unwrap ( )
374+ }
375+
376+ #[ cfg( test) ]
377+ fn parse_test_mappings < ' a > ( inputs : impl IntoIterator < Item = & ' a str > ) -> ProguardMapping {
378+ ProguardMapping :: parse_many ( inputs) . unwrap ( )
379+ }
380+
346381#[ cfg( test) ]
347382mod tests {
348383 use super :: * ;
@@ -368,7 +403,7 @@ core.file.Validatable -> a.a.b:
368403
369404 #[ test]
370405 fn parse_class_mappings ( ) {
371- let mapping = ProguardMapping :: parse ( SAMPLE_MAPPING ) . unwrap ( ) ;
406+ let mapping = parse_test_mapping ( SAMPLE_MAPPING ) ;
372407 assert ! ( mapping. classes. contains_key( "a.a.a" ) ) ;
373408 assert ! ( mapping. classes. contains_key( "a.a.b" ) ) ;
374409
@@ -379,7 +414,7 @@ core.file.Validatable -> a.a.b:
379414
380415 #[ test]
381416 fn parse_field_mappings ( ) {
382- let mapping = ProguardMapping :: parse ( SAMPLE_MAPPING ) . unwrap ( ) ;
417+ let mapping = parse_test_mapping ( SAMPLE_MAPPING ) ;
383418 let file_io = & mapping. classes [ "a.a.a" ] ;
384419 assert_eq ! ( file_io. fields. get( "a" ) , Some ( & "file" . to_string( ) ) ) ;
385420 assert_eq ! ( file_io. fields. get( "b" ) , Some ( & "charset" . to_string( ) ) ) ;
@@ -388,7 +423,7 @@ core.file.Validatable -> a.a.b:
388423
389424 #[ test]
390425 fn parse_method_mappings ( ) {
391- let mapping = ProguardMapping :: parse ( SAMPLE_MAPPING ) . unwrap ( ) ;
426+ let mapping = parse_test_mapping ( SAMPLE_MAPPING ) ;
392427 let file_io = & mapping. classes [ "a.a.a" ] ;
393428
394429 let init_methods: Vec < _ > = file_io
@@ -405,22 +440,22 @@ core.file.Validatable -> a.a.b:
405440
406441 #[ test]
407442 fn resolve_class ( ) {
408- let mapping = ProguardMapping :: parse ( SAMPLE_MAPPING ) . unwrap ( ) ;
443+ let mapping = parse_test_mapping ( SAMPLE_MAPPING ) ;
409444 let result = mapping. resolve ( "a.a.a" , None , None ) . unwrap ( ) ;
410445 assert_eq ! ( result. source, "core.file.FileIO" ) ;
411446 }
412447
413448 #[ test]
414449 fn resolve_method_with_line ( ) {
415- let mapping = ProguardMapping :: parse ( SAMPLE_MAPPING ) . unwrap ( ) ;
450+ let mapping = parse_test_mapping ( SAMPLE_MAPPING ) ;
416451 let result = mapping. resolve ( "a.a.a" , Some ( "c" ) , Some ( 92 ) ) . unwrap ( ) ;
417452 assert_eq ! ( result. source, "core.file.FileIO" ) ;
418453 assert_eq ! ( result. name. as_deref( ) , Some ( "reload" ) ) ;
419454 }
420455
421456 #[ test]
422457 fn resolve_method_without_line ( ) {
423- let mapping = ProguardMapping :: parse ( SAMPLE_MAPPING ) . unwrap ( ) ;
458+ let mapping = parse_test_mapping ( SAMPLE_MAPPING ) ;
424459 let result = mapping. resolve ( "a.a.a" , Some ( "a" ) , None ) . unwrap ( ) ;
425460 assert_eq ! ( result. source, "core.file.FileIO" ) ;
426461 // Should find one of the methods named "a" (setRoot or getRoot)
@@ -429,14 +464,14 @@ core.file.Validatable -> a.a.b:
429464
430465 #[ test]
431466 fn resolve_unknown_class ( ) {
432- let mapping = ProguardMapping :: parse ( SAMPLE_MAPPING ) . unwrap ( ) ;
467+ let mapping = parse_test_mapping ( SAMPLE_MAPPING ) ;
433468 let result = mapping. resolve ( "z.z.z" , None , None ) ;
434469 assert ! ( result. is_err( ) ) ;
435470 }
436471
437472 #[ test]
438473 fn retrace_stacktrace_full ( ) {
439- let mapping = ProguardMapping :: parse ( SAMPLE_MAPPING ) . unwrap ( ) ;
474+ let mapping = parse_test_mapping ( SAMPLE_MAPPING ) ;
440475 let input = "\
441476 java.lang.NullPointerException: something broke
442477\t at a.a.a.c(SourceFile:92)
@@ -456,7 +491,7 @@ java.lang.NullPointerException: something broke
456491
457492 #[ test]
458493 fn retrace_preserves_unknown_lines ( ) {
459- let mapping = ProguardMapping :: parse ( SAMPLE_MAPPING ) . unwrap ( ) ;
494+ let mapping = parse_test_mapping ( SAMPLE_MAPPING ) ;
460495 let input = "\
461496 java.lang.RuntimeException: oops
462497\t at a.a.a.c(SourceFile:92)
@@ -476,17 +511,62 @@ java.lang.RuntimeException: oops
476511
477512 #[ test]
478513 fn retrace_caused_by ( ) {
479- let mapping = ProguardMapping :: parse ( SAMPLE_MAPPING ) . unwrap ( ) ;
514+ let mapping = parse_test_mapping ( SAMPLE_MAPPING ) ;
480515 let input = "Caused by: a.a.a: some message" ;
481516 let output = mapping. retrace ( input) ;
482517 assert_eq ! ( output, "Caused by: core.file.FileIO: some message" ) ;
483518 }
484519
485520 #[ test]
486521 fn retrace_unknown_source ( ) {
487- let mapping = ProguardMapping :: parse ( SAMPLE_MAPPING ) . unwrap ( ) ;
522+ let mapping = parse_test_mapping ( SAMPLE_MAPPING ) ;
488523 let input = "\t at a.a.a.c(Unknown Source)" ;
489524 let output = mapping. retrace ( input) ;
490525 assert_eq ! ( output, "\t at core.file.FileIO.reload(FileIO.java)" ) ;
491526 }
527+
528+ #[ test]
529+ fn parse_many_combines_split_mapping_files ( ) {
530+ const PART_ONE : & str = r#"core.file.FileIO -> a.a.a:
531+ # {"fileName":"FileIO.java","id":"sourceFile"}
532+ 92:92:core.file.FileIO reload() -> c
533+ "# ;
534+ const PART_TWO : & str = r#"core.file.Validatable -> a.a.b:
535+ # {"fileName":"Validatable.java","id":"sourceFile"}
536+ 26:26:core.file.FileIO validate() -> a_
537+ "# ;
538+
539+ let mapping = parse_test_mappings ( [ PART_ONE , PART_TWO ] ) ;
540+
541+ assert_eq ! ( mapping. classes[ "a.a.a" ] . original_name, "core.file.FileIO" ) ;
542+ assert_eq ! (
543+ mapping. classes[ "a.a.b" ] . file_name. as_deref( ) ,
544+ Some ( "Validatable.java" )
545+ ) ;
546+ }
547+
548+ #[ test]
549+ fn retrace_stacktrace_uses_all_mapping_parts ( ) {
550+ const PART_ONE : & str = r#"core.file.FileIO -> a.a.a:
551+ # {"fileName":"FileIO.java","id":"sourceFile"}
552+ 92:92:core.file.FileIO reload() -> c
553+ "# ;
554+ const PART_TWO : & str = r#"core.file.Validatable -> a.a.b:
555+ # {"fileName":"Validatable.java","id":"sourceFile"}
556+ 26:26:core.file.FileIO validate() -> a_
557+ "# ;
558+
559+ let input = "\
560+ \t at a.a.a.c(SourceFile:92)
561+ \t at a.a.b.a_(SourceFile:26)";
562+
563+ let output = retrace_stacktrace ( [ PART_ONE . as_bytes ( ) , PART_TWO . as_bytes ( ) ] , input) . unwrap ( ) ;
564+
565+ assert_eq ! (
566+ output,
567+ "\
568+ \t at core.file.FileIO.reload(FileIO.java:92)
569+ \t at core.file.Validatable.validate(Validatable.java:26)"
570+ ) ;
571+ }
492572}
0 commit comments