blob: fc0916521148869fdaae9e3486dc67cb7f41579e [file] [log] [blame]
#!/usr/bin/env python
from __future__ import print_function, unicode_literals
import argparse
import difflib
import logging
import os
import subprocess
import sys
import tempfile
logging.basicConfig(format='%(message)s', level=logging.INFO)
class RoundTripTask(object):
def __init__(self, input_filename, action, swift_syntax_test,
skip_bad_syntax):
assert action == '-round-trip-parse' or action == '-round-trip-lex'
assert type(input_filename) == unicode
assert type(swift_syntax_test) == str
assert os.path.isfile(input_filename), \
"Input file {} is not accessible!".format(input_filename)
assert os.path.isfile(swift_syntax_test), \
"{} tool is not accessible!".format(swift_syntax_test)
self.input_filename = input_filename
self.action = action
self.swift_syntax_test = swift_syntax_test
self.skip_bad_syntax = skip_bad_syntax
self.returncode = None
self.stdout = None
self.stderr = None
@property
def test_command(self):
return [self.swift_syntax_test, self.action,
'-input-source-filename', self.input_filename]
def run(self):
command = self.test_command
logging.debug(' '.join(command))
self.output_file = tempfile.NamedTemporaryFile('w', delete=False)
self.stderr_file = tempfile.NamedTemporaryFile('w', delete=False)
process = subprocess.Popen(command, stdout=self.output_file,
stderr=self.stderr_file)
process.wait()
self.returncode = process.returncode
self.output_file.close()
self.stderr_file.close()
with open(self.output_file.name, 'r') as stdout_in:
self.stdout = stdout_in.read()
with open(self.stderr_file.name, 'r') as stderr_in:
self.stderr = stderr_in.read()
os.remove(self.output_file.name)
os.remove(self.stderr_file.name)
if self.returncode != 0:
if self.skip_bad_syntax:
logging.warning('---===WARNING===--- Lex/parse had error'
' diagnostics, so not diffing. Skipping'
' this file due to -skip-bad-syntax.')
logging.error(' '.join(command))
return None
else:
logging.error('---===ERROR===--- Lex/parse had error'
' diagnostics, so not diffing.')
logging.error(' '.join(command))
logging.error(self.stdout)
logging.error(self.stderr)
raise RuntimeError()
contents = ''.join(map(lambda l: l.decode('utf-8', errors='replace'),
open(self.input_filename).readlines()))
stdout_contents = self.stdout.decode('utf-8', errors='replace')
if contents == stdout_contents:
return None
lines = difflib.unified_diff(contents, stdout_contents,
fromfile=self.input_filename,
tofile='-')
diff = '\n'.join(line for line in lines)
return diff if diff else None
def swift_files_in_dir(d):
swift_files = []
for root, dirs, files in os.walk(d):
for basename in files:
if not basename.decode('utf-8').endswith('.swift'):
continue
abs_file = os.path.abspath(os.path.join(root, basename))
swift_files.append(abs_file)
return swift_files
def run_task(task):
try:
diff = task.run()
if diff is not None:
logging.error('---===ERROR===--- Diff failed!')
logging.error(' '.join(task.test_command))
logging.error(diff)
logging.error('')
return True
except RuntimeError as e:
logging.error(e.message)
return True
return False
def main():
tool_description = '''
Checks for round-trip lex/parse/print compatibility.
Swift's syntax representation should be "full-fidelity", meaning that there is
a perfect representation of what is in the source. When printing a syntax tree
to a file, that file should be identical to the file that was
originally parsed.
This driver invokes swift-syntax-test using -round-trip-lex and
-round-trip-parse on .swift files and .swift files in directories.
'''
parser = argparse.ArgumentParser(description=tool_description)
parser.add_argument('--directory', '-d', action='append',
dest='input_directories', default=[],
help='Add a directory, searching for .swift files'
' within')
parser.add_argument('--file', '-f', action='append',
dest='individual_input_files', default=[],
help='Add an individual file to test')
parser.add_argument('--swift-syntax-test', '-t', required=True,
dest='tool_path',
help='Absolute path to the swift-syntax-test tool')
parser.add_argument('--skip-bad-syntax',
action='store_true',
default=False,
help="Skip files that caused lex or parse diagnostics"
" to be emitted")
args = parser.parse_args()
dir_listings = [swift_files_in_dir(d) for d in args.input_directories]
all_input_files = [filename for dir_listing in dir_listings
for filename in dir_listing]
all_input_files += args.individual_input_files
all_input_files = [f.decode('utf-8') for f in all_input_files]
if len(all_input_files) == 0:
logging.error('No input files!')
sys.exit(1)
if not os.path.isfile(args.tool_path):
raise RuntimeError("Couldn't find swift-syntax-test at {}"
.format(args.tool_path))
lex_tasks = [RoundTripTask(filename, '-round-trip-lex', args.tool_path,
args.skip_bad_syntax)
for filename in all_input_files]
parse_tasks = [RoundTripTask(filename, '-round-trip-parse', args.tool_path,
args.skip_bad_syntax)
for filename in all_input_files]
failed = reduce(lambda a, b: a or b,
map(run_task, lex_tasks + parse_tasks))
sys.exit(1 if failed else 0)
if __name__ == '__main__':
main()