Skip to content

Commit 1882b50

Browse files
committed
[libSyntax] Enhance incremental re-parsing testing to also check whether unexpected diagnostics were emitted
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.
1 parent 802af8c commit 1882b50

File tree

5 files changed

+89
-17
lines changed

5 files changed

+89
-17
lines changed

tools/swift-syntax-test/swift-syntax-test.cpp

Lines changed: 46 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,10 @@ static llvm::cl::opt<std::string>
175175
OutputFilename("output-filename",
176176
llvm::cl::desc("Path to the output file"));
177177

178+
static llvm::cl::opt<std::string>
179+
DiagsOutputFilename("diags-output-filename",
180+
llvm::cl::desc("Path to the output file for parser diagnostics text"));
181+
178182
static llvm::cl::opt<bool>
179183
PrintVisualReuseInfo("print-visual-reuse-info",
180184
llvm::cl::desc("Print a coloured output of which parts of "
@@ -556,12 +560,17 @@ bool verifyReusedRegions(ByteBasedSourceRangeSet ExpectedReparseRegions,
556560
return NoUnexpectedParse;
557561
}
558562

563+
struct ParseInfo {
564+
SourceFile *SF;
565+
SyntaxParsingCache *SyntaxCache;
566+
std::string Diags;
567+
};
568+
559569
/// Parse the given input file (incrementally if an old syntax tree was
560570
/// provided) and call the action specific callback with the new syntax tree
561571
int parseFile(
562572
const char *MainExecutablePath, const StringRef InputFileName,
563-
llvm::function_ref<int(SourceFile *, SyntaxParsingCache *SyntaxCache)>
564-
ActionSpecificCallback) {
573+
llvm::function_ref<int(ParseInfo)> ActionSpecificCallback) {
565574
// The cache needs to be a heap allocated pointer since we construct it inside
566575
// an if block but need to keep it alive until the end of the function.
567576
SyntaxParsingCache *SyntaxCache = nullptr;
@@ -608,7 +617,9 @@ int parseFile(
608617
Invocation.setMainFileSyntaxParsingCache(SyntaxCache);
609618
Invocation.setModuleName("Test");
610619

611-
PrintingDiagnosticConsumer DiagConsumer;
620+
std::string DiagsString;
621+
llvm::raw_string_ostream DiagOS(DiagsString);
622+
PrintingDiagnosticConsumer DiagConsumer(DiagOS);
612623
CompilerInstance Instance;
613624
Instance.addDiagnosticConsumer(&DiagConsumer);
614625
if (Instance.setup(Invocation)) {
@@ -658,7 +669,8 @@ int parseFile(
658669
}
659670
}
660671

661-
int ActionSpecificExitCode = ActionSpecificCallback(SF, SyntaxCache);
672+
int ActionSpecificExitCode =
673+
ActionSpecificCallback({SF, SyntaxCache, DiagOS.str()});
662674
if (ActionSpecificExitCode != EXIT_SUCCESS) {
663675
return ActionSpecificExitCode;
664676
} else {
@@ -713,16 +725,18 @@ int doDumpRawTokenSyntax(const StringRef InputFile) {
713725
int doFullParseRoundTrip(const char *MainExecutablePath,
714726
const StringRef InputFile) {
715727
return parseFile(MainExecutablePath, InputFile,
716-
[](SourceFile *SF, SyntaxParsingCache *SyntaxCache) -> int {
717-
SF->getSyntaxRoot().print(llvm::outs(), {});
728+
[](ParseInfo info) -> int {
729+
info.SF->getSyntaxRoot().print(llvm::outs(), {});
718730
return EXIT_SUCCESS;
719731
});
720732
}
721733

722734
int doSerializeRawTree(const char *MainExecutablePath,
723735
const StringRef InputFile) {
724736
return parseFile(MainExecutablePath, InputFile,
725-
[](SourceFile *SF, SyntaxParsingCache *SyntaxCache) -> int {
737+
[](ParseInfo info) -> int {
738+
auto SF = info.SF;
739+
auto SyntaxCache = info.SyntaxCache;
726740
auto Root = SF->getSyntaxRoot().getRaw();
727741
std::unordered_set<unsigned> ReusedNodeIds;
728742
if (options::IncrementalSerialization && SyntaxCache) {
@@ -780,6 +794,23 @@ int doSerializeRawTree(const char *MainExecutablePath,
780794
SerializeTree(llvm::outs(), Root, SyntaxCache);
781795
}
782796
}
797+
798+
if (!options::DiagsOutputFilename.empty()) {
799+
std::error_code errorCode;
800+
llvm::raw_fd_ostream os(options::DiagsOutputFilename, errorCode,
801+
llvm::sys::fs::F_None);
802+
if (errorCode) {
803+
llvm::errs() << "error opening file '" << options::DiagsOutputFilename
804+
<< "': " << errorCode.message() << '\n';
805+
return EXIT_FAILURE;
806+
}
807+
if (!info.Diags.empty())
808+
os << info.Diags << '\n';
809+
} else {
810+
if (!info.Diags.empty())
811+
llvm::errs() << info.Diags << '\n';
812+
}
813+
783814
return EXIT_SUCCESS;
784815
});
785816
}
@@ -800,27 +831,28 @@ int doDeserializeRawTree(const char *MainExecutablePath,
800831

801832
int doParseOnly(const char *MainExecutablePath, const StringRef InputFile) {
802833
return parseFile(MainExecutablePath, InputFile,
803-
[](SourceFile *SF, SyntaxParsingCache *SyntaxCache) {
804-
return SF ? EXIT_SUCCESS : EXIT_FAILURE;
834+
[](ParseInfo info) {
835+
return info.SF ? EXIT_SUCCESS : EXIT_FAILURE;
805836
});
806837
}
807838

808839
int dumpParserGen(const char *MainExecutablePath, const StringRef InputFile) {
809840
return parseFile(MainExecutablePath, InputFile,
810-
[](SourceFile *SF, SyntaxParsingCache *SyntaxCache) {
841+
[](ParseInfo info) {
811842
SyntaxPrintOptions Opts;
812843
Opts.PrintSyntaxKind = options::PrintNodeKind;
813844
Opts.Visual = options::Visual;
814845
Opts.PrintTrivialNodeKind = options::PrintTrivialNodeKind;
815-
SF->getSyntaxRoot().print(llvm::outs(), Opts);
846+
info.SF->getSyntaxRoot().print(llvm::outs(), Opts);
816847
return EXIT_SUCCESS;
817848
});
818849
}
819850

820851
int dumpEOFSourceLoc(const char *MainExecutablePath,
821852
const StringRef InputFile) {
822853
return parseFile(MainExecutablePath, InputFile,
823-
[](SourceFile *SF, SyntaxParsingCache *SyntaxCache) -> int {
854+
[](ParseInfo info) -> int {
855+
auto SF = info.SF;
824856
auto BufferId = *SF->getBufferID();
825857
auto Root = SF->getSyntaxRoot();
826858
auto AbPos = Root.getEOFToken().getAbsolutePosition();
@@ -841,8 +873,8 @@ int dumpEOFSourceLoc(const char *MainExecutablePath,
841873
}
842874
}// end of anonymous namespace
843875

844-
int invokeCommand(const char *MainExecutablePath,
845-
const StringRef InputSourceFilename) {
876+
static int invokeCommand(const char *MainExecutablePath,
877+
const StringRef InputSourceFilename) {
846878
int ExitCode = EXIT_SUCCESS;
847879

848880
switch (options::Action) {

utils/incrparse/incr_transfer_round_trip.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,7 @@ def main():
161161
serialization_format=serialization_format,
162162
omit_node_ids=False,
163163
output_file=pre_edit_tree_file,
164+
diags_output_file=None,
164165
temp_dir=temp_dir,
165166
swift_syntax_test=swift_syntax_test,
166167
print_visual_reuse_info=False)
@@ -172,6 +173,7 @@ def main():
172173
serialization_format=serialization_format,
173174
omit_node_ids=False,
174175
output_file=incremental_tree_file,
176+
diags_output_file=None,
175177
temp_dir=temp_dir,
176178
swift_syntax_test=swift_syntax_test,
177179
print_visual_reuse_info=False)

utils/incrparse/incr_transfer_tree.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ def main():
7272
serialization_format='json',
7373
omit_node_ids=False,
7474
output_file=incremental_serialized_file,
75+
diags_output_file=None,
7576
temp_dir=temp_dir + '/temp',
7677
swift_syntax_test=swift_syntax_test,
7778
print_visual_reuse_info=False)

utils/incrparse/test_util.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -133,8 +133,9 @@ def prepareForIncrParse(test_file, test_case, pre_edit_file, post_edit_file,
133133

134134
def serializeIncrParseMarkupFile(test_file, test_case, mode,
135135
serialization_mode, serialization_format,
136-
omit_node_ids, output_file, temp_dir,
137-
swift_syntax_test, print_visual_reuse_info):
136+
omit_node_ids, output_file, diags_output_file,
137+
temp_dir, swift_syntax_test,
138+
print_visual_reuse_info):
138139
test_file_name = os.path.basename(test_file)
139140
pre_edit_file = temp_dir + '/' + test_file_name + '.' + test_case + \
140141
'.pre.swift'
@@ -171,6 +172,9 @@ def serializeIncrParseMarkupFile(test_file, test_case, mode,
171172
'-output-filename', output_file
172173
]
173174

175+
if diags_output_file:
176+
command.extend(['-diags-output-filename', diags_output_file])
177+
174178
if omit_node_ids:
175179
command.extend(['-omit-node-ids'])
176180

@@ -320,6 +324,7 @@ def main():
320324
serialization_format=serialization_format,
321325
omit_node_ids=omit_node_ids,
322326
output_file=output_file,
327+
diags_output_file=None,
323328
temp_dir=temp_dir,
324329
swift_syntax_test=swift_syntax_test,
325330
print_visual_reuse_info=visual_reuse_info)

utils/incrparse/validate_parse.py

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,11 @@ def main():
6767
post_edit_serialized_file = temp_dir + '/' + test_file_name + '.' \
6868
+ test_case + '.post.json'
6969

70+
incremental_diags_file = temp_dir + '/' + test_file_name + '.' \
71+
+ test_case + '.diagsViaIncr.txt'
72+
post_edit_diags_file = temp_dir + '/' + test_file_name + '.' \
73+
+ test_case + '.post.diags.txt'
74+
7075
# Generate the syntax tree once incrementally and once from scratch
7176
try:
7277
serializeIncrParseMarkupFile(test_file=test_file,
@@ -76,6 +81,7 @@ def main():
7681
serialization_format='json',
7782
omit_node_ids=True,
7883
output_file=incremental_serialized_file,
84+
diags_output_file=incremental_diags_file,
7985
temp_dir=temp_dir + '/temp',
8086
swift_syntax_test=swift_syntax_test,
8187
print_visual_reuse_info=visual_reuse_info)
@@ -91,6 +97,7 @@ def main():
9197
serialization_format='json',
9298
omit_node_ids=True,
9399
output_file=post_edit_serialized_file,
100+
diags_output_file=post_edit_diags_file,
94101
temp_dir=temp_dir + '/temp',
95102
swift_syntax_test=swift_syntax_test,
96103
print_visual_reuse_info=visual_reuse_info)
@@ -104,7 +111,7 @@ def main():
104111
lines = difflib.unified_diff(open(incremental_serialized_file).readlines(),
105112
open(post_edit_serialized_file).readlines(),
106113
fromfile=incremental_serialized_file,
107-
tofile=incremental_serialized_file)
114+
tofile=post_edit_serialized_file)
108115
diff = '\n'.join(line for line in lines)
109116
if diff:
110117
print('Test case "%s" of %s FAILed' % (test_case, test_file),
@@ -114,6 +121,31 @@ def main():
114121
print(diff, file=sys.stderr)
115122
sys.exit(1)
116123

124+
# Verify that if the incremental parse resulted in parser diagnostics, those
125+
# diagnostics were also emitted during the full parse.
126+
# We can't just diff the outputs because the full parse includes diagnostics
127+
# from the whole file, while the incremental parse includes only a subset.
128+
# Each diagnostic is searched in the full parse diagnostics array but the
129+
# search for each diagnostic continues from where the previous search
130+
# stopped.
131+
incremental_diags = open(incremental_diags_file).readlines()
132+
post_edit_diags = open(post_edit_diags_file).readlines()
133+
full_idx = 0
134+
for diag in incremental_diags:
135+
while full_idx < len(post_edit_diags):
136+
if post_edit_diags[full_idx] == diag:
137+
break
138+
full_idx += 1
139+
if full_idx == len(post_edit_diags):
140+
print('Test case "%s" of %s FAILed' % (test_case, test_file),
141+
file=sys.stderr)
142+
print('Parser diagnostic of incremental parsing was not emitted '
143+
'during from-scratch parsing of post-edit file:',
144+
file=sys.stderr)
145+
print(diag, file=sys.stderr)
146+
sys.exit(1)
147+
full_idx += 1 # continue searching from the next diagnostic line.
148+
117149

118150
if __name__ == '__main__':
119151
main()

0 commit comments

Comments
 (0)