4747 Dict ,
4848 List ,
4949 Union ,
50+ Optional ,
5051)
52+ import warnings
5153
5254import click
5355import libcst as cst
@@ -177,6 +179,13 @@ def __init__(self, pytest_args: list[str]) -> None:
177179 super ().__init__ (msg )
178180
179181
182+ class InvalidGeneratedSyntaxException (Exception ):
183+ def __init__ (self , file : Union [Path , str ]) -> None :
184+ super ().__init__ (f'Mutmut generated invalid python syntax for { file } . '
185+ 'If the original file has valid python syntax, please file an issue '
186+ 'with a minimal reproducible example file.' )
187+
188+
180189def copy_src_dir ():
181190 for path in mutmut .config .paths_to_mutate :
182191 output_path : Path = Path ('mutants' ) / path
@@ -186,21 +195,33 @@ def copy_src_dir():
186195 output_path .parent .mkdir (exist_ok = True , parents = True )
187196 shutil .copyfile (path , output_path )
188197
198+ @dataclass
199+ class FileMutationResult :
200+ """Dataclass to transfer warnings and errors from child processes to the parent"""
201+ warnings : list [Warning ]
202+ error : Optional [Exception ] = None
189203
190204def create_mutants (max_children : int ):
191205 with Pool (processes = max_children ) as p :
192- p .map (create_file_mutants , walk_source_files ())
206+ for result in p .imap_unordered (create_file_mutants , walk_source_files ()):
207+ for warning in result .warnings :
208+ warnings .warn (warning )
209+ if result .error :
210+ raise result .error
193211
212+ def create_file_mutants (path : Path ) -> FileMutationResult :
213+ try :
214+ print (path )
215+ output_path = Path ('mutants' ) / path
216+ makedirs (output_path .parent , exist_ok = True )
194217
195- def create_file_mutants (path : Path ):
196- print (path )
197- output_path = Path ('mutants' ) / path
198- makedirs (output_path .parent , exist_ok = True )
199-
200- if mutmut .config .should_ignore_for_mutation (path ):
201- shutil .copy (path , output_path )
202- else :
203- create_mutants_for_file (path , output_path )
218+ if mutmut .config .should_ignore_for_mutation (path ):
219+ shutil .copy (path , output_path )
220+ return FileMutationResult (warnings = [])
221+ else :
222+ return create_mutants_for_file (path , output_path )
223+ except Exception as e :
224+ return FileMutationResult (warnings = [], error = e )
204225
205226
206227def copy_also_copy_files ():
@@ -216,23 +237,30 @@ def copy_also_copy_files():
216237 else :
217238 shutil .copytree (path , destination , dirs_exist_ok = True )
218239
219-
220- def create_mutants_for_file (filename , output_path ):
240+ def create_mutants_for_file (filename , output_path ) -> FileMutationResult :
221241 input_stat = os .stat (filename )
242+ warnings : list [Warning ] = []
222243
223244 with open (filename ) as f :
224245 source = f .read ()
225246
226247 with open (output_path , 'w' ) as out :
227- mutant_names , hash_by_function_name = write_all_mutants_to_file (out = out , source = source , filename = filename )
248+ try :
249+ mutant_names , hash_by_function_name = write_all_mutants_to_file (out = out , source = source , filename = filename )
250+ except cst .ParserSyntaxError as e :
251+ # if libcst cannot parse it, then copy the source without any mutations
252+ warnings .append (SyntaxWarning (f'Unsupported syntax in { filename } ({ str (e )} ), skipping' ))
253+ out .write (source )
254+ mutant_names , hash_by_function_name = [], {}
228255
229256 # validate no syntax errors of mutants
230257 with open (output_path ) as f :
231258 try :
232259 ast .parse (f .read ())
233260 except (IndentationError , SyntaxError ) as e :
234- print (output_path , 'has invalid syntax: ' , e )
235- exit (1 )
261+ invalid_syntax_error = InvalidGeneratedSyntaxException (output_path )
262+ invalid_syntax_error .__cause__ = e
263+ return FileMutationResult (warnings = warnings , error = invalid_syntax_error )
236264
237265 source_file_mutation_data = SourceFileMutationData (path = filename )
238266 module_name = strip_prefix (str (filename )[:- len (filename .suffix )].replace (os .sep , '.' ), prefix = 'src.' )
@@ -246,6 +274,7 @@ def create_mutants_for_file(filename, output_path):
246274 source_file_mutation_data .save ()
247275
248276 os .utime (output_path , (input_stat .st_atime , input_stat .st_mtime ))
277+ return FileMutationResult (warnings = warnings )
249278
250279
251280def write_all_mutants_to_file (* , out , source , filename ):
0 commit comments