#!/usr/bin/env python from __future__ import print_function, unicode_literals import argparse 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] @property def diff_command(self): return ['/usr/bin/diff', '-u', self.input_filename, '-'] def diff(self): logging.debug(' '.join(self.diff_command)) diff = subprocess.Popen(self.diff_command, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) stdout, stderr = diff.communicate(self.stdout) if diff.returncode != 0: return stdout assert stdout == '' assert stderr == '' return None def run(self): command = self.test_command logging.debug(' '.join(command)) self.output_file = tempfile.NamedTemporaryFile('w') self.stderr_file = tempfile.NamedTemporaryFile('w') process = subprocess.Popen(command, stdout=self.output_file, stderr=self.stderr_file) process.wait() self.returncode = process.returncode 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() self.output_file.flush() self.stderr_file.flush() try: 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() finally: self.output_file.close() self.stderr_file.close() diff = self.diff() return diff 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()