@@ -312,33 +312,35 @@ def __init__(self, *, path):
312312 self .estimated_time_of_tests_by_mutant = {}
313313 self .path = path
314314 self .meta_path = Path ('mutants' ) / (str (path ) + '.meta' )
315- self .meta = None
316315 self .key_by_pid = {}
317316 self .exit_code_by_key = {}
317+ self .durations_by_key = {}
318318 self .hash_by_function_name = {}
319319 self .start_time_by_pid = {}
320- self .estimated_time_of_tests_by_pid = {}
321320
322321 def load (self ):
323322 try :
324323 with open (self .meta_path ) as f :
325- self . meta = json .load (f )
324+ meta = json .load (f )
326325 except FileNotFoundError :
327326 return
328327
329- self .exit_code_by_key = self .meta .pop ('exit_code_by_key' )
330- self .hash_by_function_name = self .meta .pop ('hash_by_function_name' )
331- assert not self .meta , self .meta # We should read all the data!
328+ self .exit_code_by_key = meta .pop ('exit_code_by_key' )
329+ self .hash_by_function_name = meta .pop ('hash_by_function_name' )
330+ self .durations_by_key = meta .pop ('durations_by_key' )
331+ self .estimated_time_of_tests_by_mutant = meta .pop ('estimated_durations_by_key' )
332+ assert not meta , f'Meta file { self .meta_path } constains unexpected keys: { set (meta .keys ())} '
332333
333- def register_pid (self , * , pid , key , estimated_time_of_tests ):
334+ def register_pid (self , * , pid , key ):
334335 self .key_by_pid [pid ] = key
335336 with START_TIMES_BY_PID_LOCK :
336337 self .start_time_by_pid [pid ] = datetime .now ()
337- self .estimated_time_of_tests_by_pid [pid ] = estimated_time_of_tests
338338
339339 def register_result (self , * , pid , exit_code ):
340340 assert self .key_by_pid [pid ] in self .exit_code_by_key
341- self .exit_code_by_key [self .key_by_pid [pid ]] = exit_code
341+ key = self .key_by_pid [pid ]
342+ self .exit_code_by_key [key ] = exit_code
343+ self .durations_by_key [key ] = (datetime .now () - self .start_time_by_pid [pid ]).total_seconds ()
342344 # TODO: maybe rate limit this? Saving on each result can slow down mutation testing a lot if the test run is fast.
343345 del self .key_by_pid [pid ]
344346 with START_TIMES_BY_PID_LOCK :
@@ -354,6 +356,8 @@ def save(self):
354356 json .dump (dict (
355357 exit_code_by_key = self .exit_code_by_key ,
356358 hash_by_function_name = self .hash_by_function_name ,
359+ durations_by_key = self .durations_by_key ,
360+ estimated_durations_by_key = self .estimated_time_of_tests_by_mutant ,
357361 ), f , indent = 4 )
358362
359363
@@ -1110,7 +1114,7 @@ def read_one_child_exit_status():
11101114 else :
11111115 # in the parent
11121116 source_file_mutation_data_by_pid [pid ] = m
1113- m .register_pid (pid = pid , key = mutant_name , estimated_time_of_tests = estimated_time_of_tests )
1117+ m .register_pid (pid = pid , key = mutant_name )
11141118 running_children += 1
11151119
11161120 if running_children >= max_children :
@@ -1297,8 +1301,6 @@ def apply_mutant(mutant_name):
12971301 f .write (new_module .code )
12981302
12991303
1300- # TODO: junitxml, html commands
1301-
13021304@cli .command ()
13031305@click .option ("--show-killed" , is_flag = True , default = False , help = "Display killed mutants." )
13041306def browse (show_killed ):
@@ -1331,13 +1333,14 @@ class ResultBrowser(App):
13311333 ]
13321334
13331335 cursor_type = 'row'
1334- source_file_mutation_data_and_stat_by_path = None
1336+ source_file_mutation_data_and_stat_by_path : dict [ str , tuple [ SourceFileMutationData , Stat ]] = {}
13351337
13361338 def compose (self ):
13371339 with Container (classes = 'container' ):
13381340 yield DataTable (id = 'files' )
13391341 yield DataTable (id = 'mutants' )
13401342 with Widget (id = "diff_view_widget" ):
1343+ yield Static (id = 'description' )
13411344 yield Static (id = 'diff_view' )
13421345 yield Footer ()
13431346
@@ -1405,25 +1408,36 @@ def on_data_table_row_highlighted(self, event):
14051408 else :
14061409 assert event .data_table .id == 'mutants'
14071410 # noinspection PyTypeChecker
1408- diff_view : Static = self .query_one ('#diff_view' )
1409- if event .row_key .value is None :
1410- diff_view .update ('' )
1411+ description : Static = self .query_one ('#description' )
1412+ mutant_name = event .row_key .value
1413+ self .loading_id = mutant_name
1414+ path = self .path_by_name .get (mutant_name )
1415+ source_file_mutation_data , stat = self .source_file_mutation_data_and_stat_by_path [str (path )]
1416+
1417+ exit_code = source_file_mutation_data .exit_code_by_key [mutant_name ]
1418+ status = status_by_exit_code [exit_code ]
1419+ estimated_duration = source_file_mutation_data .estimated_time_of_tests_by_mutant .get (mutant_name , '?' )
1420+ duration = source_file_mutation_data .durations_by_key .get (mutant_name , '?' )
1421+
1422+ if status == 'timeout' :
1423+ description .update (f'Timed out because tests did not finish within { duration :.3f} seconds. Tests without mutation took { estimated_duration :.3f} seconds.\n ' )
14111424 else :
1412- diff_view .update ('<loading...>' )
1413- self .loading_id = event .row_key .value
1414- path = self .path_by_name .get (event .row_key .value )
1425+ description .update ('' )
14151426
1416- def load_thread ():
1417- ensure_config_loaded ()
1418- try :
1419- d = get_diff_for_mutant (event .row_key .value , path = path )
1420- if event .row_key .value == self .loading_id :
1421- diff_view .update (Syntax (d , "diff" ))
1422- except Exception as e :
1423- diff_view .update (f"<{ type (e )} { e } >" )
1424-
1425- t = Thread (target = load_thread )
1426- t .start ()
1427+ diff_view : Static = self .query_one ('#diff_view' )
1428+ diff_view .update ('<loading code diff...>' )
1429+
1430+ def load_thread ():
1431+ ensure_config_loaded ()
1432+ try :
1433+ d = get_diff_for_mutant (event .row_key .value , path = path )
1434+ if event .row_key .value == self .loading_id :
1435+ diff_view .update (Syntax (d , "diff" ))
1436+ except Exception as e :
1437+ diff_view .update (f"<{ type (e )} { e } >" )
1438+
1439+ t = Thread (target = load_thread )
1440+ t .start ()
14271441
14281442 def retest (self , pattern ):
14291443 with self .suspend ():
0 commit comments