-
Notifications
You must be signed in to change notification settings - Fork 274
/
Copy pathcheck-examples
executable file
·499 lines (480 loc) · 20.8 KB
/
check-examples
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
#! /usr/bin/env bash
## This bash script checks that example code in markdown files runs as expected.
## -- the script expects to be located in dafny/docs
## and finds other parts of the dafny working tree
## relative to that location
## -- it is intended to be run within a development or CI environment. That is, it finds
## an executable dafny presuming such an environment. (The variable $dafny in the
## script says what executable to use.)
## -- There is only one command-line option, '-c', which
## if present must be first. It sets the coverage mode
## described below.
##
## What is checked?
## In each input file in turn, each code block delineated by
## markdown ``` pairs is checked, as described below.
## -- Each block is expected to have a language tag. Often this
## tag is "dafny" (as in ```dafny) but may be other tags as well.
## A reasonable tag to disable checking is "text".
## If there is no tag a default tag is used (see below).
## The script warns if there is no tag and no default.
## -- No whitespace is permitted between ``` and the tag.
## -- Markdown itself does not allow any other text on this delimiter line.
## -- The script is not robust against mismatched ``` pairs.
##
## Operational modes -- there are three modes of operation.
## -- checking the code against an expected output file (the default behavior)
## -- checking the code for a given error message (if %useHeadings has been set)
## -- producing lit files for coverage checking (if the -c command-line option is present)
##
## What command is used to check?
## Instructions for checking each code block are given in a single-line HTML
## comment (which is also a markdown comment), namely, keywords
## enclosed in <!-- and -->, with the first character of the enclosed text being a '%'.
## The text inside any such comment is interpreted as potential command information.
##
## Global defaults.
## If the command information line contains '%default', then the
## contents of the line are used to set defaults that apply for the
## remainder of the file (until a subsequent default setting command).
## Possible settings are:
## - if '%useHeadings' is present then the checks are against error messages, as described below
## - if '%check-ids' is present, the error-ids are checked as described below
## - if '%lang=' is present, then the alpha-numeric word after the = sign is now the default language
## - if '%exit=n' is present, then the default exit code is the value of n
## - if '%options' is present, then everything that follows is the default string of command options
## - the first word of a default line is taken as the default command, if that word is not %default
## e.g. <!-- %check-resolve %default --> sets the default command to %check-resolve
##
## Each code block is checked according to command information set just before the code block.
## If there is no such line, the defaults above are used. The information just before a code block
## applies only to that code block.
## For example:
## <!-- %check-resolve -->
## ```dafny
## ... code...
## ```
## The command information has the format <!-- command [ expected-output ] [ other options ] -->
## - The command has one of the forms
## %no-check -- meaning to skip any checking of this block
## %check-cli -- meaning to execute the code as bash shell commands. The language must be 'bash'
## %check-legacy -- meaning to check with dafny's legacy cli
## %check-verb -- where 'verb' is one of dafny's new-cli verbs (e.g. resolve or verify or test)
## The %check-verb form can have '-warn' appended to indicate that only warnings, not errors are expected
## - If the expected-output is present it is a file name (of a file in the same folder as the markdown file
## being tested). If the name is present, then the output of the command is checked against the content of the file
## (and is expected to match exactly).
## - The 'other options' can be any one of these:
## %save <file> -- save the code block (including anything appended from %use) in the given temporary file
## %use <file> -- append to the code block the content of the given file (often a temporary saved by a previous %save)
## %nomsg -- forget the error pattern from the heading (in the case of subsequent working examples in the explanation)
## %first -- only look at the first error message produced (usually because the error recovery is poor)
## %err -- check stderr output instead of stdout
## %exit <n> -- set the expected exit code of the command.
## If not set, the default set in the %default command is used.
## If that is not set, The default exit code is
## -- 0 if there is no output file and %iuseHeadings is not being used
## -- 0 if the command includes 'warn'
## -- if there is an output file and no 'warn', then the default exit code depends on the verb
## %options ... -- must be last; everything that follows is the list of options to use. If not present, then the default
## option list is used. If that is not present a built-in default is used.
##
## Mode: Compare against output file
## In this mode, the command is assembled as stated above and run against the code block.
## Note that the code block (together with anything included via %use) must be a full dafny
## program. There is no support for testing just simple expressions.
## The exit code is compared against the expected exit code.
## The actual output is compared aginst the stated output file, if any, and otherwise ignored.
##
## Mode: Compare against error message
## This mode is for the special case of checking the error catalog (the HowToFAQ/Errors-*.md files).
## These files consist of sections, each of which has a heading stating an error message and errorid,
## a ```-delineated code snippet that is an example of the error message, and then more explanation of the error.
## The heading line (which starts with '## **Error:' or '## **Warning:') is parsed to
## determine the errorid and a regular expression that matches the expected error messages.
## The test consists of running the command against the code snippet, extracting the error message and errorid
## from the output and checking those against the expected values.
##
## The error message as given in the header has italicized words (markdown: _word_) for variable text in the error
## message. The script turns these headings into regular expressions, taking into account the presence of
## characters special to regex processing.
##
## Mode: Generate lit
## The -c option to the script causes lit files to be generated and written to Test/docexamples (see $outdir in the script below)
## If these files are generated during CI prior to running integration tests, then they are both executed
## as part of the integration tests and included in the coverage metrics. Importantly, all the error
## catalog tests are then part of the coverage metrics.
##
## In this mode, the code is not checked, just the lit files generated.
##
## Also, currently, the generated lit commands just run dafny against the given dafny code and check the exit code.
## They do not check the results other than the exit code.
## Perhaps someday the full test will be made into a lit test and we won't need this script to do the testing.
dir=$(dirname "${BASH_SOURCE[0]}")
dir=`cd "$dir" && pwd`
dafnydir=$dir/../Scripts
dafny="$dafnydir/dafny"
outdir="$dir/../Test/docexamples"
coverage=0
permOptions="--show-snippets:false "
defOptions="--function-syntax:4 --use-basename-for-filename --type-system-refresh=true --general-traits=datatype --general-newtypes=true"
legacyOptions="-functionSyntax:4 -useBaseNameForFileName -showSnippets:0 /typeSystemRefresh /generalTraits:datatype /generalNewtypes"
while getopts 'c' opt; do
case "$opt" in
"c") coverage=1; rm -rf $outdir; mkdir -p $outdir ;;
esac
done
shift $(($OPTIND - 1))
## Tracks if there have been any failures
ANYFAIL=0
## Loop over all remaining command-line arguments
for file in $@ ; do
dir=$(dirname "$file")
dir=`cd "$dir" && pwd`
defaultCommand=
defaultExit=
defaultLang=
useHeadings=0
checkIds=0
## Temporary files
text="$dir/text.dfy"
actualOut="$dir/actual_out.tmp"
actualMsgs="$dir/actual_msgs.tmp"
actualError="$dir/actual_error.tmp"
tocheck="$actualOut"
## Whether this file fails
FAIL=0
## line number
n=0
## if inblock==1 we are in a ```-delimited block
inblock=0
msg=
use=
expectedExit=
command=
expectFile=
options=( )
first=0
checkerr=0
default=0
globalDefaultExit=
## Read and process each line of the file. The file is input on stdin
## by redirection after the 'done' corresponding to this while-do
while IFS= read -r line
do
let n++
## For header lines, extract the error message from the header text
## (everything but the initial ##[ ]*, the enclosing ** and the appended {#_errorid_})
## The sed commands turn this into a pattern matching regex to match against the output of applying dafny to the example in the text:
## remove the trailing error id if present; remove initial ## **; remove trailing '**' and errorid; turn any italicized text, which marks some
## hole in the template to a '.*' matcher. Also, escape any regex-special characters.
( echo "$line" | grep -E -e '[#][#] ' > /dev/null ) \
&& msg=`echo "$line" | sed -E -e 's/ [ ]*\{#.*\}[ ]*$//' -e 's/^##[ ]*[*]*//' -e 's/[*]*$//' -e 's/\\*/\\\\*/' -e 's/\[/\\\[/g' -e 's/\]/\\\]/g' -e 's/_[A-Za-z]+_/.*/g'`\
&& expid=`echo $line | sed -e 's/^.*[\{][\#]//' -e 's/[\}]$//'`
if [ "$checkIds" == "0" ]; then expid= ; fi
## msg - the error message turned into a regex
## expid -- the expected errorId if any
## Check for the test command information
echo "$line" | grep '^[ ]*<!-- [ ]*%' > /dev/null
if [ "$?" == "0" ]; then
##echo COMMAND "$line"
contents=( `echo "$line" | sed -e 's/[^<]*<!--//' -e 's/-->//'` )
echo "$line" | grep -e '%default' > /dev/null
if [ "$?" == "0" ]; then
default=1;
echo "$line" | grep -e '%useHeadings' > /dev/null
if [ "$?" == "0" ]; then useHeadings=1; fi
echo "$line" | grep -e '%lang=' > /dev/null
if [ "$?" == "0" ]; then defaultLang=`echo "$line" | sed -e 's/^.*%lang=//' | sed -e 's/ .*$//'` ; fi
echo "$line" | grep -e '%exit=' > /dev/null
if [ "$?" == "0" ]; then globalDefaultExit=`echo "$line" | sed -e 's/^.*%exit=//' | sed -e 's/ .*$//'` ; fi
echo "$line" | grep -e '%check-ids' > /dev/null
if [ "$?" == "0" ]; then checkIds=1; fi
defaultCommand=${contents[0]}
continue
fi
command=${contents[0]}
contents=( ${contents[@]:1} )
while [ ${#contents[@]} != "0" ]; do
if [ "${contents[0]}" == "%use" ]; then
use=${contents[1]}
contents=( ${contents[@]:2} )
elif [ "${contents[0]}" == "%save" ]; then
save=${contents[1]}
contents=( ${contents[@]:2} )
elif [ "${contents[0]}" == "%exit" ]; then
expectedExit=${contents[1]}
contents=( ${contents[@]:2} )
elif [ "${contents[0]}" == "%err" ]; then
tocheck="$actualError"
contents=( ${contents[@]:1} )
elif [ "${contents[0]}" == "%first" ]; then
first=1
contents=( ${contents[@]:1} )
elif [ "${contents[0]}" == "%nomsg" ]; then
msg=
contents=( ${contents[@]:1} )
elif [ "${contents[0]}" == "%options" ]; then
contents=${contents[@]:1}
options=${contents[@]}
contents=()
elif [ -z "${contents[0]}" ]; then
contents=()
else
expect=${contents[0]}
contents=( ${contents[@]:1} )
fi
done
fi
## Check for the marker
echo "$line" | grep -e '^[ ]*[`][`][`]' > /dev/null
if [ "$?" != "0" ]; then
if [ "$inblock" == "1" ]; then
## If in a backtick block, save the text to the temporary file - erase any ... which are just placeholders in some examples
echo "$line" | sed -e 's/^[ ]*\.\.\./ /' >> $text
fi
elif [ "$inblock" == "0" ]; then
## Start of backtick block
## get language
lang=`echo "$line" | sed -e 's/^[ ]*\`\`\`[\`]*//' -e 's/[ ]*$//'`
if [ -z "$lang" ]; then
lang=$defaultLang
if [ -z "$lang" ]; then
echo NO LANGUAGE LABEL $n "$line"
FAIL=1
fi
fi
inblock=1
rm -f "$text"
touch "$text"
else
## End of backtick block, so check the text
if [ -z "$command" -a "$lang" == "dafny" ]; then
command="$defaultCommand"
if [ -z "$command" ]; then
echo "NO COMMAND GIVEN $n $line"
FAIL=1
fi
fi
inblock=0
if [ -n "$use" ]; then
cat "$use" >> $text
fi
if [ -n "$save" ]; then
cp $text $save
save=
fi
if [ "$lang" == "dafny" -o "$lang" == "cli" -o "$lang" == "bash" ]; then
## This script can take some time, so this reports progress
if [ "$coverage" == "1" ]; then
echo "GENERATING $n $file $command $expect $expid"
else
echo "TESTING $n $file $command $expect $expid"
fi
fi
ischeck=0
echo $command | grep '%check-' >> /dev/null
if [ "$?" == "0" ]; then ischeck=1; fi
iswarn=0
echo $command | grep 'warn' >> /dev/null
if [ "$?" == "0" ]; then iswarn=1; fi
verb=`echo $command | sed -e 's/%check-//' -e 's/-warn//'`
if [ "$command" == "%no-check" ]; then
echo -n ""
elif [ "$verb" == "cli" ]; then
## These are command-line error tests, so the expected exit is typically 1
if [ -z "$expectedExit" ]; then expectedExit=1 ; fi
if [ "$lang" != "bash" ]; then
echo EXPECTED A bash BLOCK, NOT $lang
FAIL=1
elif [ "$coverage" == "1" ]; then
## The lit tests currently expect the text of the test to start with 'dafny' and become '%baredafny'
f="$outdir/$(basename $file)_${n}.dfy"
echo "// RUN: %exits-with" $expectedExit "%bare`cat $text`" " > /dev/null 2> /dev/null" > "$f"
else
isverbose=
if [ "$checkIds" == "1" ]; then isverbose="--verbose" ; fi
PATH="$dafnydir:$PATH" . $text $isverbose > "$actualOut" 2> "$actualError"
actualExit=$?
if [ "$actualExit" != "$expectedExit" ]; then
if [ "$actualExit" == "127" -a "$expectedExit" == "1" ]; then
echo EXIT CODE is $actualExit
which dafny
else
echo EXPECTED EXIT CODE OF $expectedExit, GOT $actualExit
FAIL=1
fi
fi
if [ "$first" == "1" ]; then
act=`cat "$tocheck" | head -1 | sed -e 's/^.*Error/Error/'`
else
act=`cat "$tocheck" | sed -e 's/^.*Error/Error/'`
fi
dif=`echo "$act" | tr "\n" " " | sed -e 's/[ ]*$//' -e "s@$msg@@"`
if [ -n "$dif" ]; then
echo ACTUAL OUTPUT DOES NOT MATCH EXPECTED: "$msg" VS "$act"
FAIL=1
elif [ -z "$act" -a "$useHeadings" == "1" ]; then
echo "NO OUTPUT TO COMPARE"
FAIL=1
fi
fi
elif [ "$ischeck" == "1" ]; then ## Not cli though
dOptions=$defOptions
if [ -z "$expect" -a "$useHeadings" == "0" ]; then
defaultExit=0
elif [ "$iswarn" == "1" ]; then
dOptions="$dOptions --allow-warnings"
defaultExit=0
elif [ "$verb" == "verify" ]; then
defaultExit=4
elif [ "$verb" == "resolve" ]; then
defaultExit=2
elif [ "$verb" == "translate" ]; then
defaultExit=3
elif [ "$verb" == "build" ]; then
defaultExit=0
elif [ "$verb" == "run" ]; then
defaultExit=0
if [ "$useHeadings" == "1" ]; then defaultExit=3; fi
elif [ "$verb" == "test" ]; then
defaultExit=2
elif [ "$verb" == "legacy" ]; then
defaultExit=0
if [ "$useHeadings" == "1" ]; then defaultExit=3; fi
if [ "$defaultCommand" == "%check-resolve" ]; then defaultExit=2 ; fi
fi
if [ "$verb" == "legacy" ]; then
dOptions=$legacyOptions
verb=
fi
if [ -z "$expectedExit" ]; then expectedExit=$defaultExit ; fi
if [ "$coverage" == "1" ] ; then
f=$outdir/$(basename $file)_${n}.dfy
if [ "$command" == "%check-legacy" ]; then
echo "// RUN: %exits-with" $expectedExit "%baredafny" "/showSnippets:0" $verb $dOptions ${options[@]} '"%s" > "%t"' > "$f"
echo " " >> $f
cat $text >> $f
elif [ "$command" == "%check-cli" ]; then
echo "// RUN: %exits-with $expectedExit %bare`cat $text` > /dev/null 2> /dev/null" > $f
else
echo "// RUN: %exits-with $expectedExit %baredafny $verb $dOptions ${options[@]}" '"%s" > "%t"' > "$f"
echo " " >> "$f"
cat "$text" >> "$f"
fi
else
if [ "$command" == "%check-legacy" ]; then
isverbose=-compileVerbose:$checkIds
(cd "$dir"; "$dafny" /showSnippets:0 $dOptions $isverbose ${options[@]} $text ) > "$actualOut" 2> "$actualError"
else
isverbose=
if [ "$checkIds" == "1" ]; then isverbose="--verbose" ; fi
( cd "$dir" ; "$dafny" $verb $isverbose $permOptions $dOptions ${options[@]} $text ) > "$actualOut" 2> "$actualError"
fi
actualExit=$?
if [ "$useHeadings" == "1" ]; then
if [ "$first" == "1" ]; then
cat "$actualOut" | grep -E '(Error|Warning):' | head -1 > "$actualMsgs"
else
cat "$actualOut" | grep -E '(Error|Warning):' > "$actualMsgs"
fi
actualid=
grep 'ID:' "$actualMsgs" > /dev/null
if [ "$?" == "0" -a "$checkIds" == "1" ]; then
num=`cat "$actualMsgs" | sed -e 's/^.*[\(]ID: //' -e 's/[\)]$//' | sort -u | wc -l | sed -e 's/[ \t]*//'`
if [ "$num" != "1" ]; then
echo "UNEXPECTED NUMBER OF DIFFERENT IDS: $num"
FAIL=1
fi
actualid=`cat "$actualMsgs" | sed -e 's/^.*[\(]ID: //' -e 's/[\)]$//' | sort -u`
fi
act=`cat "$actualMsgs" | sed -e 's/^[^:]*: //' -e 's/ [ ]*[\(]ID: .*$//' `
if [ "$checkIds" == "1" -a "$actualid" != "$expid" -a -n "$msg" ] ; then
echo ERROR ID MISMATCH "$actualid : $expid"
cat "$actualMsgs"
fi
if [ -n "$msg" ]; then
dif=`echo "$act" | sed -e "s@$msg@@g" | sed -e 's/[ ]*//'`
if [ -z "$act" -a "$expectedExit" != "0" ] ; then
echo NO ERROR MESSAGE FOUND
FAIL=1
fi
if [ -n "$dif" ] ; then
echo NO MATCH
echo PAT "$msg"
echo ACT "$act"
echo DIF "$dif"
FAIL=1
fi
if [ "$actualExit" != "$expectedExit" ]; then
echo EXPECTED EXIT CODE $expectedExit, got $actualExit
FAIL=1
fi
else
if [ -n "$act" ]; then
echo "EXPECTED NO ERROR, got " $act
FAIL=1
fi
if [ "$actualExit" != "0" ]; then
echo EXPECTED EXIT CODE 0, got $actualec
FAIL=1
fi
fi
elif [ -z "$expect" ]; then
# No output file -- so no error expected
if [ "$actualExit" != "0" ]; then
echo "TEST FAILED" $file line $n $command $expect
cat "$text"
cat "$actualOut"
FAIL=1
fi
if [ `cat "$actualOut" | wc -l ` != "2" ]; then
echo ACTUAL ERROR OUTPUT BUT NONE EXPECTED
cat "$actualOut"
FAIL=1
fi
else
if [ "$actualExit" != "$expectedExit" ]; then
echo EXPECTED EXIT CODE $expectedExit, got $actualExit
FAIL=1
fi
if [ -e "$dir/$expect" ]; then
diff -b -B "$actualOut" "$dir/$expect"
if [ "$?" != "0" ]; then
FAIL=1
echo Actual output differs from expected
echo "TEST FAILED" $file line $n $command $expect
cat "$text"
cat "$actualOut"
fi
else
cat "$actualOut"
FAIL=1
fi
fi
fi
elif [ "$lang" == "dafny" ]; then
echo UNKNOWN TEST COMMAND $command
FAIL=1
fi
use=
command=
expect=
expectedExit=
options=( )
first=0
tocheck="$actualOut"
fi
done < "$file"
rm -rf *.tmp $text.*
if [ "$inblock" == "1" ]; then
echo UNCLOSED BACKTICK BLOCK
FAIL=1
fi
if [ "$FAIL" == "1" ] ; then
echo "Test Failure: $file"
ANYFAIL=1
fi
rm -f $text *.tmp
done
exit $ANYFAIL