11use crate :: config:: Log ;
2+ use crate :: db:: Db ;
23use crate :: parser:: { BacktickOffsets , EmbeddedFileSourceMap } ;
34use camino:: Utf8Path ;
45use colored:: Colorize ;
@@ -292,11 +293,29 @@ fn run_test(
292293 // all diagnostics. Otherwise it remains empty.
293294 let mut snapshot_diagnostics = vec ! [ ] ;
294295
295- let failures: Failures = test_files
296- . into_iter ( )
296+ let mut any_pull_types_failures = false ;
297+
298+ let mut failures: Failures = test_files
299+ . iter ( )
297300 . filter_map ( |test_file| {
298- if !KNOWN_PULL_TYPES_FAILURES . contains ( & & * relative_fixture_path. as_str ( ) . replace ( '\\' , "/" ) ) {
299- pull_types ( db, test_file. file ) ;
301+ let pull_types_result = attempt_test (
302+ db,
303+ pull_types,
304+ test_file,
305+ "\" pull types\" " ,
306+ Some (
307+ "Note: either fix the panic or add the `<!-- pull-types:skip -->` \
308+ directive to this test",
309+ ) ,
310+ ) ;
311+ match pull_types_result {
312+ Ok ( ( ) ) => { }
313+ Err ( failures) => {
314+ any_pull_types_failures = true ;
315+ if !test. should_skip_pulling_types ( ) {
316+ return Some ( failures) ;
317+ }
318+ }
300319 }
301320
302321 let parsed = parsed_module ( db, test_file. file ) . load ( db) ;
@@ -314,55 +333,22 @@ fn run_test(
314333 . map ( |error| create_unsupported_syntax_diagnostic ( test_file. file , error) ) ,
315334 ) ;
316335
317- let type_diagnostics = match catch_unwind ( || check_types ( db, test_file. file ) ) {
318- Ok ( type_diagnostics) => type_diagnostics,
319- Err ( info) => {
320- let mut by_line = matcher:: FailuresByLine :: default ( ) ;
321- let mut messages = vec ! [ ] ;
322- match info. location {
323- Some ( location) => messages. push ( format ! ( "panicked at {location}" ) ) ,
324- None => messages. push ( "panicked at unknown location" . to_string ( ) ) ,
325- }
326- match info. payload . as_str ( ) {
327- Some ( message) => messages. push ( message. to_string ( ) ) ,
328- // Mimic the default panic hook's rendering of the panic payload if it's
329- // not a string.
330- None => messages. push ( "Box<dyn Any>" . to_string ( ) ) ,
331- }
332- if let Some ( backtrace) = info. backtrace {
333- match backtrace. status ( ) {
334- BacktraceStatus :: Disabled => {
335- let msg = "run with `RUST_BACKTRACE=1` environment variable to display a backtrace" ;
336- messages. push ( msg. to_string ( ) ) ;
337- }
338- BacktraceStatus :: Captured => {
339- messages. extend ( backtrace. to_string ( ) . split ( '\n' ) . map ( String :: from) ) ;
340- }
341- _ => { }
342- }
343- }
344-
345- if let Some ( backtrace) = info. salsa_backtrace {
346- salsa:: attach ( db, || {
347- messages. extend ( format ! ( "{backtrace:#}" ) . split ( '\n' ) . map ( String :: from) ) ;
348- } ) ;
349- }
350-
351- by_line. push ( OneIndexed :: from_zero_indexed ( 0 ) , messages) ;
352- return Some ( FileFailures {
353- backtick_offsets : test_file. backtick_offsets ,
354- by_line,
355- } ) ;
356- }
336+ let mdtest_result = attempt_test ( db, check_types, test_file, "run mdtest" , None ) ;
337+ let type_diagnostics = match mdtest_result {
338+ Ok ( diagnostics) => diagnostics,
339+ Err ( failures) => return Some ( failures) ,
357340 } ;
358341
359342 diagnostics. extend ( type_diagnostics. into_iter ( ) . cloned ( ) ) ;
360- diagnostics. sort_by ( |left, right|left. rendering_sort_key ( db) . cmp ( & right. rendering_sort_key ( db) ) ) ;
343+ diagnostics. sort_by ( |left, right| {
344+ left. rendering_sort_key ( db)
345+ . cmp ( & right. rendering_sort_key ( db) )
346+ } ) ;
361347
362348 let failure = match matcher:: match_file ( db, test_file. file , & diagnostics) {
363349 Ok ( ( ) ) => None ,
364350 Err ( line_failures) => Some ( FileFailures {
365- backtick_offsets : test_file. backtick_offsets ,
351+ backtick_offsets : test_file. backtick_offsets . clone ( ) ,
366352 by_line : line_failures,
367353 } ) ,
368354 } ;
@@ -374,6 +360,23 @@ fn run_test(
374360 } )
375361 . collect ( ) ;
376362
363+ if test. should_skip_pulling_types ( ) && !any_pull_types_failures {
364+ let mut by_line = matcher:: FailuresByLine :: default ( ) ;
365+ by_line. push (
366+ OneIndexed :: from_zero_indexed ( 0 ) ,
367+ vec ! [
368+ "Remove the `<!-- pull-types:skip -->` directive from this test: pulling types \
369+ succeeded for all files in the test."
370+ . to_string( ) ,
371+ ] ,
372+ ) ;
373+ let failure = FileFailures {
374+ backtick_offsets : test_files[ 0 ] . backtick_offsets . clone ( ) ,
375+ by_line,
376+ } ;
377+ failures. push ( failure) ;
378+ }
379+
377380 if snapshot_diagnostics. is_empty ( ) && test. should_snapshot_diagnostics ( ) {
378381 panic ! (
379382 "Test `{}` requested snapshotting diagnostics but it didn't produce any." ,
@@ -470,10 +473,70 @@ fn create_diagnostic_snapshot(
470473 snapshot
471474}
472475
473- const KNOWN_PULL_TYPES_FAILURES : & [ & str ] = & [
474- "crates/ty_python_semantic/resources/mdtest/annotations/any.md" ,
475- "crates/ty_python_semantic/resources/mdtest/annotations/callable.md" ,
476- "crates/ty_python_semantic/resources/mdtest/type_api.md" ,
477- "crates/ty_python_semantic/resources/mdtest/type_qualifiers/classvar.md" ,
478- "crates/ty_python_semantic/resources/mdtest/type_qualifiers/final.md" ,
479- ] ;
476+ /// Run a function over an embedded test file, catching any panics that occur in the process.
477+ ///
478+ /// If no panic occurs, the result of the function is returned as an `Ok()` variant.
479+ ///
480+ /// If a panic occurs, a nicely formatted [`FileFailures`] is returned as an `Err()` variant.
481+ /// This will be formatted into a diagnostic message by `ty_test`.
482+ fn attempt_test < ' db , T , F > (
483+ db : & ' db Db ,
484+ test_fn : F ,
485+ test_file : & TestFile ,
486+ action : & str ,
487+ clarification : Option < & str > ,
488+ ) -> Result < T , FileFailures >
489+ where
490+ F : FnOnce ( & ' db dyn ty_python_semantic:: Db , File ) -> T + std:: panic:: UnwindSafe ,
491+ {
492+ catch_unwind ( || test_fn ( db, test_file. file ) ) . map_err ( |info| {
493+ let mut by_line = matcher:: FailuresByLine :: default ( ) ;
494+ let mut messages = vec ! [ ] ;
495+ match info. location {
496+ Some ( location) => messages. push ( format ! (
497+ "Attempting to {action} caused a panic at {location}"
498+ ) ) ,
499+ None => messages. push ( format ! (
500+ "Attempting to {action} caused a panic at an unknown location" ,
501+ ) ) ,
502+ }
503+ if let Some ( clarification) = clarification {
504+ messages. push ( clarification. to_string ( ) ) ;
505+ }
506+ messages. push ( String :: new ( ) ) ;
507+ match info. payload . as_str ( ) {
508+ Some ( message) => messages. push ( message. to_string ( ) ) ,
509+ // Mimic the default panic hook's rendering of the panic payload if it's
510+ // not a string.
511+ None => messages. push ( "Box<dyn Any>" . to_string ( ) ) ,
512+ }
513+ messages. push ( String :: new ( ) ) ;
514+
515+ if let Some ( backtrace) = info. backtrace {
516+ match backtrace. status ( ) {
517+ BacktraceStatus :: Disabled => {
518+ let msg =
519+ "run with `RUST_BACKTRACE=1` environment variable to display a backtrace" ;
520+ messages. push ( msg. to_string ( ) ) ;
521+ }
522+ BacktraceStatus :: Captured => {
523+ messages. extend ( backtrace. to_string ( ) . split ( '\n' ) . map ( String :: from) ) ;
524+ }
525+ _ => { }
526+ }
527+ }
528+
529+ if let Some ( backtrace) = info. salsa_backtrace {
530+ salsa:: attach ( db, || {
531+ messages. extend ( format ! ( "{backtrace:#}" ) . split ( '\n' ) . map ( String :: from) ) ;
532+ } ) ;
533+ }
534+
535+ by_line. push ( OneIndexed :: from_zero_indexed ( 0 ) , messages) ;
536+
537+ FileFailures {
538+ backtick_offsets : test_file. backtick_offsets . clone ( ) ,
539+ by_line,
540+ }
541+ } )
542+ }
0 commit comments