| #!/usr/bin/env python |
| """Test that autoflake performs correctly on arbitrary Python files. |
| |
| This checks that autoflake never introduces incorrect syntax. This is |
| done by doing a syntax check after the autoflake run. The number of |
| Pyflakes warnings is also confirmed to always improve. |
| """ |
| import os |
| import shlex |
| import subprocess |
| import sys |
| |
| import autoflake |
| |
| |
| ROOT_PATH = os.path.abspath(os.path.dirname(__file__)) |
| AUTOFLAKE_BIN = "'{}' '{}'".format( |
| sys.executable, |
| os.path.join(ROOT_PATH, 'autoflake.py'), |
| ) |
| |
| if sys.stdout.isatty(): |
| YELLOW = '\x1b[33m' |
| END = '\x1b[0m' |
| else: |
| YELLOW = '' |
| END = '' |
| |
| |
| def colored(text, color): |
| """Return color coded text.""" |
| return color + text + END |
| |
| |
| def pyflakes_count(filename): |
| """Return pyflakes error count.""" |
| with autoflake.open_with_encoding( |
| filename, |
| encoding=autoflake.detect_encoding(filename), |
| ) as f: |
| return len(list(autoflake.check(f.read()))) |
| |
| |
| def readlines(filename): |
| """Return contents of file as a list of lines.""" |
| with autoflake.open_with_encoding( |
| filename, |
| encoding=autoflake.detect_encoding(filename), |
| ) as f: |
| return f.readlines() |
| |
| |
| def diff(before, after): |
| """Return diff of two files.""" |
| import difflib |
| return ''.join( |
| difflib.unified_diff( |
| readlines(before), |
| readlines(after), |
| before, |
| after, |
| ), |
| ) |
| |
| |
| def run(filename, command, verbose=False, options=None): |
| """Run autoflake on file at filename. |
| |
| Return True on success. |
| """ |
| if not options: |
| options = [] |
| |
| import test_autoflake |
| with test_autoflake.temporary_directory() as temp_directory: |
| temp_filename = os.path.join( |
| temp_directory, |
| os.path.basename(filename), |
| ) |
| import shutil |
| shutil.copyfile(filename, temp_filename) |
| |
| if 0 != subprocess.call( |
| shlex.split(command) + |
| ['--in-place', temp_filename] + |
| options, |
| ): |
| sys.stderr.write('autoflake crashed on ' + filename + '\n') |
| return False |
| |
| try: |
| file_diff = diff(filename, temp_filename) |
| if verbose: |
| sys.stderr.write(file_diff) |
| |
| if check_syntax(filename): |
| try: |
| check_syntax(temp_filename, raise_error=True) |
| except ( |
| SyntaxError, TypeError, |
| UnicodeDecodeError, ValueError, |
| ) as exception: |
| sys.stderr.write( |
| 'autoflake broke ' + filename + '\n' + |
| str(exception) + '\n', |
| ) |
| return False |
| |
| before_count = pyflakes_count(filename) |
| after_count = pyflakes_count(temp_filename) |
| |
| if verbose: |
| print('(before, after):', (before_count, after_count)) |
| |
| if file_diff and after_count > before_count: |
| sys.stderr.write('autoflake made ' + filename + ' worse\n') |
| return False |
| except OSError as exception: |
| sys.stderr.write(str(exception) + '\n') |
| |
| return True |
| |
| |
| def check_syntax(filename, raise_error=False): |
| """Return True if syntax is okay.""" |
| with autoflake.open_with_encoding( |
| filename, |
| encoding=autoflake.detect_encoding(filename), |
| ) as input_file: |
| try: |
| compile(input_file.read(), '<string>', 'exec', dont_inherit=True) |
| return True |
| except (SyntaxError, TypeError, UnicodeDecodeError, ValueError): |
| if raise_error: |
| raise |
| else: |
| return False |
| |
| |
| def process_args(): |
| """Return processed arguments (options and positional arguments).""" |
| import argparse |
| parser = argparse.ArgumentParser() |
| |
| parser.add_argument( |
| '--command', default=AUTOFLAKE_BIN, |
| help='autoflake command (default: %(default)s)', |
| ) |
| |
| parser.add_argument( |
| '--expand-star-imports', action='store_true', |
| help='expand wildcard star imports with undefined ' |
| 'names', |
| ) |
| |
| parser.add_argument( |
| '--imports', |
| help='pass to the autoflake "--imports" option', |
| ) |
| |
| parser.add_argument( |
| '--remove-all-unused-imports', action='store_true', |
| help='pass "--remove-all-unused-imports" option to ' |
| 'autoflake', |
| ) |
| |
| parser.add_argument( |
| '--remove-duplicate-keys', action='store_true', |
| help='pass "--remove-duplicate-keys" option to ' |
| 'autoflake', |
| ) |
| |
| parser.add_argument( |
| '--remove-unused-variables', action='store_true', |
| help='pass "--remove-unused-variables" option to ' |
| 'autoflake', |
| ) |
| |
| parser.add_argument( |
| '-v', '--verbose', action='store_true', |
| help='print verbose messages', |
| ) |
| |
| parser.add_argument('files', nargs='*', help='files to test against') |
| |
| return parser.parse_args() |
| |
| |
| def check(args): |
| """Run recursively run autoflake on directory of files. |
| |
| Return False if the fix results in broken syntax. |
| """ |
| if args.files: |
| dir_paths = args.files |
| else: |
| dir_paths = [ |
| path for path in sys.path |
| if os.path.isdir(path) |
| ] |
| |
| options = [] |
| if args.expand_star_imports: |
| options.append('--expand-star-imports') |
| |
| if args.imports: |
| options.append('--imports=' + args.imports) |
| |
| if args.remove_all_unused_imports: |
| options.append('--remove-all-unused-imports') |
| |
| if args.remove_duplicate_keys: |
| options.append('--remove-duplicate-keys') |
| |
| if args.remove_unused_variables: |
| options.append('--remove-unused-variables') |
| |
| filenames = dir_paths |
| completed_filenames = set() |
| |
| while filenames: |
| try: |
| name = os.path.realpath(filenames.pop(0)) |
| if not os.path.exists(name): |
| # Invalid symlink. |
| continue |
| |
| if name in completed_filenames: |
| sys.stderr.write( |
| colored( |
| '---> Skipping previously tested ' + name + '\n', |
| YELLOW, |
| ), |
| ) |
| continue |
| else: |
| completed_filenames.update(name) |
| |
| if os.path.isdir(name): |
| for root, directories, children in os.walk(name): |
| filenames += [ |
| os.path.join(root, f) for f in children |
| if f.endswith('.py') and |
| not f.startswith('.') |
| ] |
| |
| directories[:] = [ |
| d for d in directories |
| if not d.startswith('.') |
| ] |
| else: |
| verbose_message = '---> Testing with ' + name |
| sys.stderr.write(colored(verbose_message + '\n', YELLOW)) |
| |
| if not run( |
| os.path.join(name), |
| command=args.command, |
| verbose=args.verbose, |
| options=options, |
| ): |
| return False |
| except (UnicodeDecodeError, UnicodeEncodeError) as exception: |
| # Ignore annoying codec problems on Python 2. |
| print(exception, file=sys.stderr) |
| continue |
| |
| return True |
| |
| |
| def main(): |
| """Run main.""" |
| return 0 if check(process_args()) else 1 |
| |
| |
| if __name__ == '__main__': |
| try: |
| sys.exit(main()) |
| except KeyboardInterrupt: |
| sys.exit(1) |