mirror of
https://github.com/apple/swift.git
synced 2025-12-14 20:36:38 +01:00
Enhances `swift-syntax-test` to output the parser diagnostics so we can verify that if the incremental parse resulted in parser diagnostics, those diagnostics were also emitted during the full parse.
152 lines
6.5 KiB
Python
Executable File
152 lines
6.5 KiB
Python
Executable File
#!/usr/bin/env python
|
|
|
|
from __future__ import print_function
|
|
|
|
import argparse
|
|
import difflib
|
|
import os
|
|
import sys
|
|
|
|
from test_util import TestFailedError, serializeIncrParseMarkupFile
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(
|
|
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
description='Utility for testing incremental syntax parsing',
|
|
epilog='''
|
|
Based of a single template the utility generates a pre-edit and a post-edit
|
|
file. It then verifies that incrementally parsing the post-edit file base
|
|
on the pre-edit file results in the same syntax tree as reparsing the
|
|
post-edit file from scratch.
|
|
|
|
To generate the pre-edit and the post-edit file from the template, it
|
|
operates on markers of the form:
|
|
|
|
<<test_case<pre|||post>>>
|
|
|
|
These placeholders are replaced by:
|
|
- 'pre' if a different test case than 'test_case' is run
|
|
- 'pre' for the pre-edit version of 'test_case'
|
|
- 'post' for the post-edit version of 'test_case'
|
|
''')
|
|
parser.add_argument(
|
|
'file', type=argparse.FileType(),
|
|
help='The template file to test')
|
|
parser.add_argument(
|
|
'--test-case', default='',
|
|
help='The test case to execute. If no test case is specified all \
|
|
unnamed substitutions are applied')
|
|
parser.add_argument(
|
|
'--temp-dir', required=True,
|
|
help='A temporary directory where pre-edit and post-edit files can be \
|
|
saved')
|
|
parser.add_argument(
|
|
'--swift-syntax-test', required=True,
|
|
help='The path to swift-syntax-test')
|
|
parser.add_argument(
|
|
'--print-visual-reuse-info', default=False, action='store_true',
|
|
help='Print visual reuse information about the incremental parse \
|
|
instead of diffing the syntax trees. This option is intended \
|
|
for debug purposes only.')
|
|
|
|
args = parser.parse_args(sys.argv[1:])
|
|
|
|
test_file = args.file.name
|
|
test_file_name = os.path.basename(test_file)
|
|
test_case = args.test_case
|
|
temp_dir = args.temp_dir
|
|
swift_syntax_test = args.swift_syntax_test
|
|
visual_reuse_info = args.print_visual_reuse_info
|
|
|
|
if not os.path.exists(temp_dir):
|
|
os.makedirs(temp_dir)
|
|
|
|
incremental_serialized_file = temp_dir + '/' + test_file_name + '.' \
|
|
+ test_case + '.postViaIncr.json'
|
|
post_edit_serialized_file = temp_dir + '/' + test_file_name + '.' \
|
|
+ test_case + '.post.json'
|
|
|
|
incremental_diags_file = temp_dir + '/' + test_file_name + '.' \
|
|
+ test_case + '.diagsViaIncr.txt'
|
|
post_edit_diags_file = temp_dir + '/' + test_file_name + '.' \
|
|
+ test_case + '.post.diags.txt'
|
|
|
|
# Generate the syntax tree once incrementally and once from scratch
|
|
try:
|
|
serializeIncrParseMarkupFile(test_file=test_file,
|
|
test_case=test_case,
|
|
mode='incremental',
|
|
serialization_mode='full',
|
|
serialization_format='json',
|
|
omit_node_ids=True,
|
|
output_file=incremental_serialized_file,
|
|
diags_output_file=incremental_diags_file,
|
|
temp_dir=temp_dir + '/temp',
|
|
swift_syntax_test=swift_syntax_test,
|
|
print_visual_reuse_info=visual_reuse_info)
|
|
if visual_reuse_info:
|
|
# If we just want the reuse info, we don't need to parse the file
|
|
# from scratch or validate it
|
|
sys.exit(0)
|
|
|
|
serializeIncrParseMarkupFile(test_file=test_file,
|
|
test_case=test_case,
|
|
mode='post-edit',
|
|
serialization_mode='full',
|
|
serialization_format='json',
|
|
omit_node_ids=True,
|
|
output_file=post_edit_serialized_file,
|
|
diags_output_file=post_edit_diags_file,
|
|
temp_dir=temp_dir + '/temp',
|
|
swift_syntax_test=swift_syntax_test,
|
|
print_visual_reuse_info=visual_reuse_info)
|
|
except TestFailedError as e:
|
|
print('Test case "%s" of %s FAILed' % (test_case, test_file),
|
|
file=sys.stderr)
|
|
print(e.message, file=sys.stderr)
|
|
sys.exit(1)
|
|
|
|
# Check if the two syntax trees are the same
|
|
lines = difflib.unified_diff(open(incremental_serialized_file).readlines(),
|
|
open(post_edit_serialized_file).readlines(),
|
|
fromfile=incremental_serialized_file,
|
|
tofile=post_edit_serialized_file)
|
|
diff = '\n'.join(line for line in lines)
|
|
if diff:
|
|
print('Test case "%s" of %s FAILed' % (test_case, test_file),
|
|
file=sys.stderr)
|
|
print('Syntax tree of incremental parsing does not match '
|
|
'from-scratch parsing of post-edit file:\n\n', file=sys.stderr)
|
|
print(diff, file=sys.stderr)
|
|
sys.exit(1)
|
|
|
|
# Verify that if the incremental parse resulted in parser diagnostics, those
|
|
# diagnostics were also emitted during the full parse.
|
|
# We can't just diff the outputs because the full parse includes diagnostics
|
|
# from the whole file, while the incremental parse includes only a subset.
|
|
# Each diagnostic is searched in the full parse diagnostics array but the
|
|
# search for each diagnostic continues from where the previous search
|
|
# stopped.
|
|
incremental_diags = open(incremental_diags_file).readlines()
|
|
post_edit_diags = open(post_edit_diags_file).readlines()
|
|
full_idx = 0
|
|
for diag in incremental_diags:
|
|
while full_idx < len(post_edit_diags):
|
|
if post_edit_diags[full_idx] == diag:
|
|
break
|
|
full_idx += 1
|
|
if full_idx == len(post_edit_diags):
|
|
print('Test case "%s" of %s FAILed' % (test_case, test_file),
|
|
file=sys.stderr)
|
|
print('Parser diagnostic of incremental parsing was not emitted '
|
|
'during from-scratch parsing of post-edit file:',
|
|
file=sys.stderr)
|
|
print(diag, file=sys.stderr)
|
|
sys.exit(1)
|
|
full_idx += 1 # continue searching from the next diagnostic line.
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|