File: runtest.sh

package info (click to toggle)
dtrace 2.0.5-1
  • links: PTS
  • area: main
  • in suites: sid
  • size: 24,408 kB
  • sloc: ansic: 61,247; sh: 17,997; asm: 1,717; lex: 947; awk: 754; yacc: 695; perl: 37; sed: 17; makefile: 15
file content (1564 lines) | stat: -rwxr-xr-x 55,896 bytes parent folder | download
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
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
#!/bin/bash
#
# runtest.sh -- A dtrace tester and regression tester.
#
#               Runs a bunch of .d scripts and tests their results.
#               If more than one dtrace build directory is available,
#               runs them each in turn, comparing their output
#               and generated intermediate representation.
#
# Oracle Linux DTrace.
# Copyright (c) 2011, 2025, Oracle and/or its affiliates. All rights reserved.
# Licensed under the Universal Permissive License v 1.0 as shown at
# http://oss.oracle.com/licenses/upl.

# Sanitize the shell and get configuration info.

shopt -s nullglob extglob
unset CDPATH
unset POSIXLY_CORRECT  # Interferes with 'wait'
export LC_COLLATE="C"
export dtrace="/usr/sbin/dtrace"

arch="$(uname -m)"

load_modules()
{
    # If running as root, pull in appropriate modules
    if [[ "x$(id -u)" = "x0" ]]; then
	cat test/modules | xargs -rn 1 modprobe 2>/dev/null
    fi
}

#
# Utility function to ensure a given provider is present.
# Uses the providers list written out at start time.
#
check_provider()
{
    grep -q "^$1\$" $tmpdir/providers
}

export -f check_provider

# get_dir_name
#
# Pick a unique temporary directory name to stick things in.
# Returns results via `echo'; must be called via substitution.
#
# Respect TMPDIR if set, as POSIX requires.

get_dir_name()
{
    local tmpname;                  # A subdir name being tested for uniqueness

    false;                            # Starting state - reset $?
    while [[ $? -ne 0 ]]; do
        tmpname=${TMPDIR:-/tmp}/runtest.${RANDOM}
        mkdir $tmpname                # Test-and-set it
    done

    echo $tmpname                     # Name is unique, return it
}

# find_next_numeric_dir DIR
#
# Create and return the first empty numerically-named directory under DIR.

find_next_numeric_dir()
{
    local i;

    mkdir -p $1
    for ((i=0; ; i++)); do
        if [[ ! -e $1/$i ]]; then
            mkdir -p $1/$i
            chattr +D $1/$i 2>/dev/null
            rm -f $1/current
            ln -s $i $1/current
            echo $1/$i
            return
        fi
    done
}

declare -a ZAPTHESE=

# run_with_timeout TIMEOUT CMD [ARGS ...]
#
# Run command CMD with argument ARGS, with the specified timeout.
# Return the exitcode of the command, or $TIMEOUTRET if a timeout occurred.
# Also send a KILL signal if the command is still running TIMEOUTKIL seconds
# after the initial signal is sent, but start with TIMEOUTSIG so that DTrace
# can perform an orderly shutdown, if possible.

TIMEOUTSIG=TERM
TIMEOUTRET=124
TIMEOUTKIL=5

run_with_timeout()
{
    log "Running timeout --signal=$TIMEOUTSIG $@ ${explicit_arg:+"$explicit_arg"}\n"
    timeout --signal=$TIMEOUTSIG --kill-after=$TIMEOUTKIL $@ ${explicit_arg:+"$explicit_arg"}
    local status=$?

    # short wait to allow any coredump to trickle out to disk.
    [[ $status -ne 0 ]] && sleep 1

    # status 128+9 indicates timeout via KILL(=9)
    if [[ $status -eq 137 ]]; then
        status=$TIMEOUTRET
    fi
    return $status
}

# exist_options NAME FILE
#
# Return 0 iff a set of runtest options named NAME exists in FILE,
# or in the test.options file in its directory or in any parent
# up to test/.
#
# The options have the form @@NAME.

exist_options()
{
    local retval=1
    local path=$2
    local file=$2

    while [[ $retval -ne 0 ]]; do
        if [[ -e "$file" ]]; then
            grep -Eq "@@"$1 $file
            retval=$?
        fi
        path="$(dirname $path)"
        file="$path/test.options"
        if [[ -e "$path/test.$arch.options" ]]; then
            file="$path/test.$arch.options"
        fi
        # Halt at top level.
        if [[ -e $path/Makecheck ]]; then
            break
        fi
    done

    return $retval
}

# extract_options NAME FILE EXPAND ACCUMULATE
#
# Extract the options named NAME from FILE, or in the test.options file in its
# directory or any parent up to test/, and return them on stdout.
#
# If EXPAND is set, do a round of shell evaluation on them first.
#
# If ACCUMULATE is set, return the concatenated values of all options named
# NAME up to test/.

extract_options()
{
    local retval=1
    local path=$2
    local file=$2
    local expand=$3
    local accumulate=$4
    local val=

    while :; do
        # This horrible sequence of patterns catches @@foo, @@foo: bar, and @@foo */,
        # while not catching @@foo-bar or @@foo-bar: baz. */
        if [[ -f $file ]] && grep -Eq -e "@@"$1' *:' -e "@@"$1" " -e "@@"$1'$' $file; then
            local val="$val$(grep -E -e "@@"$1' *:' -e "@@"$1" " -e "@@"$1'$' $file | sed 's,.*@@'$1' *: ,,; s,\*/$,,; s,  *$,,')"
            if [[ -n $accumulate ]]; then
                val="$val "
            fi
        fi
        path="$(dirname $path)"
        file="$path/test.options"
        if [[ -e "$path/test.$arch.options" ]]; then
            file="$path/test.$arch.options"
        fi
        # Halt when we have any tags if not accumulating, or at top level anyway.
        if { [[ -z $accumulate ]] && [[ -n $val ]]; } || [[ -e $path/Makecheck ]]; then
            # Force the $_pid to expand to itself.

            _pid='$_pid'

            # This trick is because printf with a straight eval ends up called
            # with more args than format string items and squashes out spaces,
            # echo -e -e foo prints 'foo', and bash's builtin echo -e -- "foo"
            # prints "-- foo".  coreutils echo does the right thing, but is more
            # expensive than the builtin printf, so use it only when needed.

            if [[ -n $val ]]; then
                if [[ -n $expand ]]; then
                    eval printf "%s" \""$val"\"
                else
                    printf "%s" ''"$val"
                fi
            fi
            return
        fi
    done
}

# is_interpreter_file FILE

is_interpreter_file()
{
    if [[ ! -e $1 ]]; then
        return 1
    fi
    if [[ $(gawk '{print $1; exit}' $1) != "#!dtrace" ]]; then
        return 1
    fi

    # At this point, perhaps we can also check that the file is executable.
    # But we will copy it anyhow.  So do not bother.

    return 0
}

DEFAULT_TIMEOUT=41

usage()
{
    cat <<EOF
$0 -- testsuite engine for DTrace

Usage: runtest options [TEST ...]

Options:
 --timeout=TIME: Time out test runs after TIME seconds (default
                 $DEFAULT_TIMEOUT). (May cause many test failures if lowered.)
 --skip-longer[=TIME]: Skip all tests with expected timeouts longer than
                       TIME (if not specified, $DEFAULT_TIMEOUT or whatever is
                       specified by --timeout, whichever is longer).
 --ignore-timeouts: Treat all timeouts as not being failures.  (For use when
                    testing under load.)
 --testsuites=SUITES: Which testsuites (directories under test/) to run,
                      as a comma-separated list.
 --[no-]execute: Execute probes with associated triggers.
 --[no-]comparison: (Do not) compare results against expected results.
                    If result comparison is suppressed, XPASSes are
                    silently converted to PASSes (catering for expected
                    failures which are shown only via a miscomparison).
 --[no-]capture-expected: Record results of tests that exit successfully
                          or with a timeout, but have no expected results,
                          as the expected results.  (Only useful if
                          --no-comparison is not specified.)
 --[no-]use-installed: Use an installed dtrace rather than a copy in the
                       source tree.
 --quiet: Only show unexpected output (FAILs and XPASSes).
 --verbose: The opposite of --quiet (and the default).
 --[no-]tag=TAG: Run only tests with[out] TAG.
                 Multiple such options are ANDed together.
 --help: This message.

If one or more TESTs is provided, they must be the name of .d files existing
under the test/ directory.  If none is provided, all available tests are run.
EOF
exit 0
}

# Parameters.

CAPTURE_EXPECTED=${DTRACE_TEST_CAPTURE_EXPECTED:+t}
OVERWRITE_RESULTS=
NOEXEC=${DTRACE_TEST_NO_EXECUTE:+t}
USE_INSTALLED=${DTRACE_TEST_USE_INSTALLED:+t}
VALGRIND=${DTRACE_TEST_VALGRIND:+t}
COMPARISON=t
SKIP_LONGER=

if [[ -n $DTRACE_TEST_TESTSUITES ]]; then
    TESTSUITES="${DTRACE_TEST_TESTSUITES}"
else
    TESTSUITES="unittest internals stress demo"
fi

ONLY_TESTS=
TESTS=
QUIET=
USER_TAGS=

if [[ -n $DTRACE_TEST_TIMEOUT ]]; then
    TIMEOUT="$DTRACE_TEST_TIMEOUT"
else
    TIMEOUT=$DEFAULT_TIMEOUT
fi
IGNORE_TIMEOUTS=${DTRACE_TEST_IGNORE_TIMEOUTS:+t}

ERRORS=

while [[ $# -gt 0 ]]; do
    case $1 in
        --capture-expected) CAPTURE_EXPECTED=t;;
        --no-capture-expected) CAPTURE_EXPECTED=;;
        --execute) NOEXEC=;;
        --no-execute) NOEXEC=t;;
        --comparison) COMPARISON=t;;
        --no-comparison) COMPARISON=;;
        --valgrind) VALGRIND=t;;
        --no-valgrind) VALGRIND=;;
        --use-installed) USE_INSTALLED=t;;
        --no-use-installed) USE_INSTALLED=;;
        --timeout=*) TIMEOUT="$(printf -- $1 | cut -d= -f2-)";;
        --ignore-timeouts) IGNORE_TIMEOUTS=t;;
	--skip-longer) SKIP_LONGER=$TIMEOUT;;
	--skip-longer=*) SKIP_LONGER="$(printf -- $1 | cut -d= -f2-)";;
        --testsuites=*) TESTSUITES="$(printf -- $1 | cut -d= -f2- | tr "," " ")";;
        --quiet) QUIET=t;;
        --verbose) QUIET=;;
        --tag=*) USER_TAGS="$USER_TAGS $(printf -- $1 | cut -d= -f2-)";;
        --no-tag=*) USER_TAGS="$USER_TAGS !$(printf -- $1 | cut -d= -f2-)";;
        --help|--*) usage;;
        *) ONLY_TESTS=t
           OVERWRITE_RESULTS=t
           if [[ -f $1 ]]; then
               TESTS="$TESTS $1"
           else
               echo "Test $1 not found, ignored." >&2
           fi;;
    esac
    shift
done

# Store test results in a directory with an incrementing name.
# We arbitrarily assume that we are being run from ~: since
# this is what 'make check' does, that's a reasonable
# assumption.

cd $(dirname $0)
logdir=$(find_next_numeric_dir test/log)

LOGFILE=$logdir/runtest.log
SUMFILE=$logdir/runtest.sum

# Set a locked-memory limit that should be big enough for the test suite.

ulimit -l $((256 * 1024 * 1024))

# If running as root, remember and turn off core_pattern, and set the
# coredumpsize to a biggish value.

orig_core_pattern=
orig_core_uses_pid=

if [[ "x$(id -u)" = "x0" ]]; then
    orig_core_pattern="$(cat /proc/sys/kernel/core_pattern)"
    orig_core_uses_pid="$(cat /proc/sys/kernel/core_uses_pid)"
    echo core > /proc/sys/kernel/core_pattern
    echo 0 > /proc/sys/kernel/core_uses_pid
    ulimit -c 1000000
fi

# Make a temporary directory for intermediate result storage.
# Make a binary directory within it, for wrapper scripts.

export tmpdir="$(get_dir_name)"
mkdir $tmpdir/bin
export PATH=$tmpdir/bin:$PATH

# Log and failure functions.

out()
{
    if [[ -z $QUIET ]]; then
        printf "%s" "$*" | sed 's,%,%%,g' | xargs -0n 1 printf
    fi
    sum "$@"
}

force_out_only()
{
    printf "%s" "$*" | sed 's,%,%%,g' | xargs -0n 1 printf
}

force_out()
{
    force_out_only "$@"
    sum "$@"
}

sum()
{
    printf "%s" "$*" | sed 's,%,%%,g' | xargs -0n 1 printf >> $SUMFILE
    log "$@"
}

log()
{
    printf "%s" "$*" | sed 's,%,%%,g' | xargs -0n 1 printf >> $LOGFILE
}

# At shutdown, delete this directory, kill requested processes, and restore
# core_pattern.  When this is specifically an interruption, report as much in
# the log.
closedown()
{
    for pid in ${ZAPTHESE[@]}; do
        kill -9 -- $pid
    done
    if [[ -z $orig_core_pattern ]]; then
        echo $orig_core_pattern > /proc/sys/kernel/core_pattern
        echo $orig_core_uses_pid > /proc/sys/kernel/core_uses_pid
    fi
    declare -ga ZAPTHESE=
}

trap 'rm -rf ${tmpdir}; closedown; exit' EXIT
trap 'force_out "Test interrupted.\n"; closedown' INT HUP QUIT TERM

# fail XFAIL XFAILMSG FAILMSG
#
# Report a test failure, whether expected or otherwise, with the given
# message.  If XFAIL is unset, XFAILMSG is ignored.  If FAILMSG
# would be printed but is unset, $testmsg is used instead.

fail()
{
    local xfail="$1"
    local xfailmsg="$2"

    # In quiet mode, we may need to print out the test name too.
    [[ -n $QUIET ]] && [[ -z $xfail ]] && force_out_only "$_test: "

    shift 2
    local failmsg="$(echo ''"$@" | sed 's,^ *,,; s, $,,;')"

    if [[ -z $failmsg ]]; then
        failmsg="$testmsg"
    fi

    if [[ -n "$xfail" ]] && [[ -n "$xfailmsg" ]]; then
        out "XFAIL: $@ ($xfailmsg).\n"
    elif [[ -n "$xfail" ]]; then
        out "XFAIL: $@.\n"
    elif [[ -n $failmsg ]]; then
        ERRORS=t
        force_out "FAIL: $failmsg.\n"
    else
        ERRORS=t
        force_out "FAIL.\n"
    fi
}

# pass XFAIL MSG
#
# Report a test pass, possibly unexpected.  The output format is subtly
# different from failures, to emphasise that the message is less important
# than the fact of passing.  If MSG would be printed but is unset, $testmsg
# is used instead.

pass()
{
    local xpass="$1"
    local xout=out

    # In quiet mode, we may need to print out the test name, and will
    # want to print out the PASS message if and only if this was an
    # XPASS.

    if [[ -n $xpass ]] && [[ -n $COMPARISON ]]; then
        [[ -n $QUIET ]] && force_out_only "$_test: "
        force_out "X"
        xout=force_out
    fi

    shift
    local msg="$(echo ''"$@" | sed 's,^ *,,; s, $,,; s,^# *,,')"

    if [[ -z $msg ]]; then
        msg="$testmsg"
    fi

    if [[ $# -gt 0 ]] && [[ -n "$msg" ]]; then
        $xout "PASS ($msg).\n"
    else
        $xout "PASS.\n"
    fi
}

# postprocess POSTPROCESSOR OUTPUT FINAL
#
# Run postprocessing over some test OUTPUT, yielding a file named
# FINAL.  Possibly run the POSTPROCESSOR first.

postprocess()
{
    local postprocessor=$1
    local output=$2
    local final=$3
    local retval=0

    cp -f $output $tmpdir/pp.out

    # Postprocess the output, if need be.
    if [[ -x $postprocessor ]]; then
        if $postprocessor < $output > $tmpdir/output.post; then
            mv -f $tmpdir/output.post $tmpdir/pp.out
        else
            retval=$?
            cp -f $output $tmpdir/pp.out
        fi
    fi

    # Transform any printed hex strings into a fixed string, excepting
    # only valgrind errors.
    # TODO: may need adjustment or making optional if scripts emit hex
    # values which are not continuously variable.

    sed -e '/^==[0-9][0-9]*== /!s,0x[0-9a-f][0-9a-f]*,{ptr},g' \
	-e 's,at BPF pc [1-9][0-9]*,at BPF pc NNN,' < $tmpdir/pp.out > $final

    return $retval
}

# Log results. (Purely to reduce duplication further down.)
# The single argument indicates whether to write to the sumfile:
# writes to the logfile due to reinvocations should not go to the sumfile,
# since that is just noise and throws off results counting.
resultslog()
{
    want_sum=$1
    # Always log the unpostprocessed test output.
    cat $testout >> $LOGFILE

    rm -f $testout

    if [[ -n $want_sum ]]; then
        if [[ -n $want_all_output ]]; then
            cat $tmpdir/test.out >> $SUMFILE

        elif [[ -n $want_expected_diff ]]; then
            log "Diff against expected:\n"

            diff -au $rfile $tmpdir/test.out | tee -a $LOGFILE >> $SUMFILE
        fi
    fi

    # Finally, write the debugging output, if any, to the logfile.
    if [[ -s $testdebug ]]; then
        echo "-- @@debug --" >> $LOGFILE
        cat $testdebug >> $LOGFILE
    fi
    rm -f $testdebug
}

# Flip a few env vars to tell dtrace to produce reproducible output.
export _DTRACE_TESTING=t
export LANGUAGE=C

if [[ -z $USE_INSTALLED ]]; then
    dtrace="$(pwd)/build*/dtrace"
    test_libdir="$(pwd)/build/dlibs"
    test_ldflags="-L$(pwd)/build"
    test_cppflags="-I$(pwd)/include -I$(pwd)/uts/common -I$(pwd)/build -I$(pwd)/libdtrace -DARCH_$arch"
    helper_device="dtrace/test-$$"
    # Pre-existing directories from earlier tests are just fine!
    # dtprobed will clean things up.
    mkdir -p $tmpdir/run/dtrace
    dtprobed_flags="-n $helper_device -s${tmpdir}/run/dtrace -F"
    export DTRACE_DOF_INIT_DEVNAME="/dev/$helper_device"
    export DTRACE_OPT_DOFSTASHPATH="${tmpdir}/run/dtrace"

    if [[ -z $(eval echo $dtrace) ]]; then
    	echo "No dtraces available." >&2
    	exit 1
    fi
    build/dtprobed $dtprobed_flags &
    export dtprobed_pid=$!
    ZAPTHESE+=($dtprobed_pid)
else
    # If we don't have a pkg-config path, try to point at a plausible
    # one given DTrace's default install paths.  This will fail if
    # the pkg-config path is changed as well: in that case, presumably
    # the person changing that path has pointed pkg-config at it anyway.
    if [[ -z $PKG_CONFIG_PATH ]]; then
        export PKG_CONFIG_PATH="$(pwd)/../../../share/pkgconfig"
    fi

    dtrace="$(pkg-config --variable=dtrace dtrace)"
    test_libdir="installed"
    test_ldflags=""
    test_cppflags="-DARCH_$arch $(pkg-config --cflags dtrace_sdt) $(pkg-config --cflags dtrace)"

    if [[ ! -x $dtrace ]]; then
        echo "$dtrace not available." >&2
        exit 1
    fi
fi
core_raw_dt_flags="$test_cppflags"
export test_cppflags="$test_cppflags -fno-lto"
export test_ldflags="$test_ldflags -fno-lto"
export test_libdir

# Figure out if the preprocessor supports -fno-diagnostics-show-option: if it
# does, add a bunch of options designed to make GCC output look like it used
# to.
# Regardless, turn off display of column numbers, if possible: they vary
# between GCC releases.

if ! /usr/bin/cpp -x c -fno-diagnostics-show-caret - /dev/null < /dev/null 2>&1 | \
	grep -q 'unrecognized command line option'; then
    export DTRACE_OPT_CPPARGS="-fno-diagnostics-show-caret -fdiagnostics-color=never -fno-diagnostics-show-option -fno-show-column"
elif ! /usr/bin/cpp -x c -fno-show-column - /dev/null < /dev/null 2>&1 | \
	grep -q 'unrecognized command line option'; then
    export DTRACE_OPT_CPPARGS="-fno-show-column"
fi

# More than one dtrace tree -> run tests for all dtraces, and verify identical
# intermediate code is produced by each dtrace.

regression=
first_dtest_dir=
if [[ $(echo $dtrace | wc -w) -gt 1 ]]; then
    regression=t
fi

# If not testing an installed dtrace, we must look for our system .d files
# in the right place.
if [[ "x$test_libdir" != "xinstalled" ]]; then
    export DTRACE_OPT_SYSLIBDIR="$test_libdir"
fi

# Determine the list of available providers
for dt in $dtrace; do
    # Look for our shared libraries in the right place.
    if [[ "x$test_libdir" != "xinstalled" ]]; then
        export LD_LIBRARY_PATH="$(dirname $dt)"
    fi

    # Write out a list of loaded providers.
    DTRACE_DEBUG= $dt -l | tail -n +2 | gawk '{print $2;}' | sort -u > $tmpdir/providers

    unset LD_LIBRARY_PATH
    break
done

# Initialize test coverage.

for name in build*; do
    if [[ -n "$(echo $name/*.gcno)" ]]; then
        rm -rf $logdir/coverage
        mkdir -p $logdir/coverage
        lcov --zerocounters --directory $name --quiet
        lcov --capture --base-directory . --directory $name --initial \
             --quiet -o $logdir/coverage/initial.lcov 2>/dev/null
    fi
done

load_modules

# Export some variables so triggers and .sh scripts can get at them.
export _test _pid dt_flags
export CC=${CC:-/usr/bin/gcc}
export NM=${NM:-/usr/bin/nm}
export OBJCOPY=${OBJCOPY:-/usr/bin/objcopy}
export OBJDUMP=${OBJDUMP:-/usr/bin/objdump}
export READELF=${READELF:-/usr/bin/readelf}

# Arrange to do (relatively expensive) mutex debugging.
export DTRACE_OPT_DEBUGASSERT="mutexes"

# If running DTrace inside valgrind, make sure valgrind works, and multiply
# the timeout by ten.
if [[ -n $VALGRIND ]]; then
    if ! valgrind -q /bin/true >/dev/null 2>&1; then
        echo "valgrind does not work: cannot run in --valgrind mode." >&2
        exit 1
    fi
    TIMEOUT=$((TIMEOUT * 10))
fi

# Close unexpectedly open FDs.
for fd in /proc/$$/fd/*; do
    fd="${fd##*/}"
    case $fd in
        0|1|2|255) continue;;
        *) exec {fd}>&-;;
    esac
done

# Mark the log and sumfiles as fsynced always.

touch $LOGFILE
touch $SUMFILE
chattr +S $LOGFILE $SUMFILE 2>/dev/null

# Form run and skip files from multiple sources of user-specified tags:
#   - default-tag file (test/tags.default)
#   - per-arch default-tag file (test/tags.$arch.default
#   - environment variable (TEST_TAGS)
#   - command-line options (--[no-]tag=)

USER_TAGS="$(cat test/tags.default test/tags.$arch.default 2>/dev/null) $TEST_TAGS $USER_TAGS"
rm -f $tmpdir/run.tags > /dev/null 2>&1
rm -f $tmpdir/skip.tags > /dev/null 2>&1
printf "%s\n" $USER_TAGS | grep -v '^!' | sort -u > $tmpdir/run.tags
printf "%s\n" $USER_TAGS | grep '^!' | sed 's/^!//' | sort -u > $tmpdir/skip.tags
# Free ourselves of the hassles of empty (but existing) files.
for name in $tmpdir/run.tags $tmpdir/skip.tags; do
    if [[ `cat $name | wc -w` -eq 0 ]]; then
        rm $name
    fi
done

# Loop over each test in turn, or the specified subset if test names were passed
# on the command line.

# Tests are .d files, representing D programs passed to dtrace, or .sh files,
# representing shell scripts which invoke dtrace and analyze the results.

for dt in $dtrace; do

    if [[ -n $regression ]]; then
        force_out "\nTest of $dt:\n"
    fi

    # Look for our shared libraries in the right place.
    if [[ "x$test_libdir" != "xinstalled" ]]; then
        export LD_LIBRARY_PATH="$(dirname $dt)"
    fi

    # Log versions.
    DTRACE_DEBUG= $dt -vV | tee -a $LOGFILE >> $SUMFILE
    uname -a | tee -a $LOGFILE >> $SUMFILE
    if [[ -f .git-version ]]; then
	sum "testsuite version-control ID: $(cat .git-version)\n\n"
    elif [[ -f .git-archive-version ]]; then
	sum "Testsuite version-control ID: $(cat .git-archive-version)\n\n"
    fi

    # If we are running valgrind, invoke dtrace via a wrapper.

    if [[ -n $VALGRIND ]]; then
	cat >> $tmpdir/bin/$(basename $dt) <<-EOT
	#!/bin/bash
	# Wrap DTrace calls inside valgrind.
	valgrind -q $dt "\$@"
	EOT
        dt="$tmpdir/bin/$(basename $dt)"
        vg="valgrind -q "
        chmod a+x $tmpdir/bin/$(basename $dt)
    else
        vg=""
    fi

    for _test in $(if [[ $ONLY_TESTS ]]; then
                      echo $TESTS | sed 's,\.r$,\.d,g; s,\.r ,.d ,g'
                   else
                      for name in $TESTSUITES; do
                          find test/$name \( -name "*.d" -o -name "*.sh" -o -name "*.c" \) | sort -u
                      done
                   fi); do

        base=${_test%.d}
        base=${base%.sh}
        base=${base%.c}
        testonly="$(basename $_test)"
        export timeout="$TIMEOUT"

        # Hidden files and editor backup files are not tests.

        if [[ $_test =~ /\. ]] || [[ $test =~ ~$ ]]; then
            continue
        fi

        if [[ ! -e $_test ]]; then
            force_out "$_test: Not found.\n"
            continue
        fi

        # Various options can be set in the test .d script, usually embedded in
        # comments.  In addition, any options listed in a file test.options in 
        # any directory from test/ on down will automatically be imposed on
        # any tests in that directory tree which do not themselves impose a
        # value for that option.  If a file named test.$(uname -m).options
        # exists at a given level, it is consulted instead of test.options at
        # that level.
        #
        # @@runtest-opts: A set of options to be added to the default set
        #                 (normally -S -e -s, where the -S and -e may not
        #                 necessarily be provided.) Subjected to shell
        #                 expansion.
        #
        # @@timeout: The timeout to use for this test.  Overrides the --timeout
        #            parameter, iff the @@timeout is greater.
        #
        # @@skip: If true, the test is skipped.
        #
        # @@tags: Tag test case with specified tags, separated by spaces.
        #         Tags named after all subdirectories below 'test' are
        #         automatically added. (e.g. all tests in test/unittest/proc
        #         will gain the tags test/unittest/proc and test/unittest.)
        #
        # @@xfail: A single line containing a reason for this test's expected
        #          failure.  (If the test passes unexpectedly, this message is
        #          not printed.) See '.x'.
        #
        # @@no-xfail: If present, this means that a test is *not* expected to
        #             fail, even though a test.options in a directory above
        #             this test says otherwise.
        #
        # @@timeout-success: If present, this means that a timeout is
        #                    considered a test pass, not a test failure.
        #
        # @@deadman-success: If present, this means that a deadman timer
        #                    firing is considered a test pass, not a test
        #                    failure.
        #
        # @@reinvoke-failure: If present, this is an integer indicating the
        #                     number of times to reinvoke failed tests: only
        #                     if they fail every time will they be considered
        #                     a FAIL rather than a PASS.
        #
        # @@trigger: A single line containing the name of a program in
        #            test/triggers which is executed after dtrace is started.
        #            When this program exits, dtrace will be killed.  If the
        #            timeout expires, both programs are killed.  Arguments may
        #            be provided as normal, and are subjected to shell
        #            expansion.  For the sake of reproducible testcases, only
        #            tests with a trigger, or that explicitly declare that they
        #            can produce reproducible output via /* @@trigger: none */
        #            are ever executed against the kernel.  Triggers should not
        #            emit anything to stdout or stderr, as their output is not
        #            currently trapped and it will spoil the screen display.
        #            See '.trigger'.
        #
        # @@trigger-timing: A single line containing 'before', 'after',
        #                   'synchro', or a number.  If 'synchro' (the
        #                   default), the trigger will be passed to dtrace via
        #                   -c and left for dtrace to invoke.  If 'after', the
        #                   trigger will be exec()ed (from a pre-existing PID)
        #                   after DTrace has started.  If 'before', the trigger
        #                   will already be running when DTrace starts.  If a
        #                   number, it is a count in seconds to delay trigger
        #                   execution (to wait for DTrace to start).
        #
        # @@link: A library to link .c programs against (see below).  -ldtrace
        #         by default.
        #
        # @@nosort: If present, this means do not sort before comparing results
        #           with a .r file.  Results are sorted by default, since many
        #           test results do not have a well-defined order -- notably,
        #           when generated on different CPUs.  Some tests, however,
        #           care about strict ordering.
        #
        # Certain filenames of test .d script are treated specially:
        #
        # tst.*.d: These are assumed to have /* @@trigger: none */ by default.
        #
        # err.*.d: As with tst.*.d, only these are additionally expected to
        #          return 1.  (This is not the same as an XFAIL, which suggests
        #          that this test is known to be not passing: this indicates
        #          that this test is marked PASS if it returns 1, which would
        #          normally indicate failure.)
        #
        # err.D_*.d: As with err.*.d, only run with -xerrtags.
        #
        # drp.*.d: As with tst.*.d, only run -xdroptags.

        # Various other files can exist with the same basename as the test.
        # Many of these serve the same purpose as embedded comments: both exist
        # to permit identical D scripts to be symlinked together while still
        # permitting e.g. different triggering programs to be used.
        #
        # The full list of suffxes is as follows:
        #
        # .d: The D program itself.
        #
        # .sh: If executable, a shell script that is run instead of dtrace with
        #      a single argument, the path to dtrace.  All other features are still
        #      provided, including timeouts.  The script is run as if with
        #      /* @@trigger: none */.  Arguments specified by @@runtest-opts, and
        #      a minimal set without which no tests will work, are passed in
        #      the environment variable dt_flags.  An exit code of 67 will cause
        #      the test to be considered an expected failure.
        #
        # .c: A C program that is linked against the libraries listed in @@link,
        #     or -ldtrace by default, then run.
        #
        # .r: Expected results, after postprocessing.  If not present,
        #     no expected-result comparison is done (the results are
        #     merely dumped into the logfile, and the test is deemed to
        #     pass as long as dtrace exits with a zero exitcode or
        #     times out).  If any output is expected on standard error, it
        #     should be appended to this file, after a single line reading
        #     only "-- @@stderr --".  Expected-result comparison can also
        #     be suppressed with the --no-expected command-line parameter.
        #
        #     Files suffixed $(uname -m).r serve to override the results for a
        #     particular architecture.
        #
        # .r.p: Expected-results postprocessor: a filter run before various
        #       hardwired pointer-value-replacement preprocessors which
        #       transform all 0x[a-f]* strings into the string {ptr} and edit
        #       out CPU and probe IDs.
        #       Receives one argument, the name of the testcase.
        #
        # .x: If executable, a program which when executed indicates whether
        #     the test is expected to succeed or not in this environment.
        #     0 indicates expected success, 1 expected failure, or 2 that the
        #     test is unreliable and should be skipped; in the latter two cases
        #     it can emit to standard output a one-line message describing the
        #     reason for failure or skipping.  This permits tests to inspect
        #     their environment and determine whether failure is expected, or
        #     whether they can safely run at all.  See '@@xfail' and '@@skip'.
        #     If both .x and @@xfail exist, only the .x is respected.
        #
        #     Files suffixed $(uname -m).x allow xfails or skips for a
        #     particular architecture.
        #
        #     As with test.options, executable files named test.x and
        #     test.$(uname -m).x can exist at any level in the hierarchy:
        #     the one that is 'most faily' wins (so any SKIP overrides any
        #     XFAIL, which overrides any PASS, no matter what level of the
        #     hierarchy it is found at).
        #
        # .t: If executable, serves the same purpose as the '@@trigger'
        #     option above.  If both .t and @@trigger exist, only the .t is
        #     respected.

        _exit="${NOEXEC:+-e}"

        # Optionally skip this test.

        if exist_options skip $_test; then
            sum "$_test: SKIP: $(extract_options skip $_test)\n"
            continue
        fi
        
        # Check if the user specified any tags.

        if [[ -n "$USER_TAGS" ]]; then

            # Extract tags from test, and add tags derived from the test's containing
            # directory.
            rm -f $tmpdir/test.tags >/dev/null 2>&1
            test_tags="$(extract_options tags $_test "" t)"
            test_tags="$test_tags $(shrinktest=$_test
                                    while :; do
                                        shrinktest="${shrinktest%/*}"
                                        [[ "$shrinktest" =~ / ]] || break
                                        printf "%s " "$shrinktest"
                                    done)"
            printf "%s\n" $test_tags | sort -u > $tmpdir/test.tags

            # If user specified run tags, check for them.
            if [[ -e $tmpdir/run.tags ]]; then
                if [[ -n "$(comm -23 $tmpdir/run.tags $tmpdir/test.tags)" ]]; then
                    # Some user-specified run tags are not in test.  Skip test.
                    tagdiff=$(comm -23 $tmpdir/run.tags $tmpdir/test.tags | xargs)
                    sum "$_test: SKIP: test lacks requested tags: $tagdiff\n"
                    continue
                fi
            fi

            # If user specified skip tags, check for them.
            if [[ -e $tmpdir/skip.tags && -n $test_tags ]]; then
                if [[ -n "$(comm -12 $tmpdir/skip.tags $tmpdir/test.tags)" ]]; then
                    # Some user-specified skip tags are in test.  Skip test.
                    tagdiff=$(comm -12 $tmpdir/skip.tags $tmpdir/test.tags | xargs)
                    sum "$_test: SKIP: test has excluded tags: $tagdiff\n"
                    continue
                fi
            fi
        fi

        # Per-test timeout.

        if exist_options timeout $_test; then
            if [[ "$(extract_options timeout $_test)" -gt $timeout ]]; then
                timeout="$(extract_options timeout $_test)"

                if [[ -n $VALGRIND ]]; then
                    timeout=$((timeout * 10))
                fi
            fi
        fi

        # Timeout-based skipping.

        if [[ -n $SKIP_LONGER ]] && [[ $timeout -gt $SKIP_LONGER ]]; then
            sum "$_test: SKIP: would take too long\n"
            continue
        fi

        # Note if this is expected to fail.
        xfail=0
        xfailpath="$_test"
        xbase=$base
        xfailmsg=

        if exist_options xfail $_test && ! exist_options no-xfail $_test; then
            xfail=1
            xfailmsg="$(extract_options xfail $_test | sed 's, *$,,')"
        fi

        # Scan for $test.x, $test.$arch.x, then test.x and test.arch.x
        # all the way up to the top level, much as for test.options.
        # Whichever .x file returns the highest value is used (SKIP >
        # XFAIL > PASS).
        while [[ $xfail -lt 2 ]]; do
            xfile=$xbase.$arch.x
            [[ -e $xfile ]] || xfile=$xbase.x
            if [[ -x $xfile ]]; then
                # xfail program.  Run, and capture its output, clamping the
                # exitcode at 2.  (The output of whichever program has the
                # highest exitcode is used.)
                xfailmsgthisfile="$($xfile)"
                xfailthisfile=$?
                if [[ $xfailthisfile -gt 2 ]]; then
                   echo "$xfile: Unexpected return value $xfailthisfile." >&2
                   xfailthisfile=2
                fi
                if [[ $xfailthisfile -gt $xfail ]]; then
                    xfail=$xfailthisfile
                    xfailmsg="$xfailmsgthisfile"
                fi
            elif [[ -e $xfile ]] && [[ $xfail -eq 0 ]]; then
                xfail=1
            fi
            xfailpath="$(dirname $xfailpath)"
            xbase="$xfailpath/test"
            if [[ -e "$xfailpath/test.$arch.x" ]]; then
                xbase="$xfailpath/test.$arch"
            fi
            # Halt at top level.
            if [[ -e $xfailpath/Makecheck ]]; then
                break
            fi
        done

        case $xfail in
           0) xfail="";; # no failure expected
           1) xfail=t;;  # failure expected
           2) sum "$_test: SKIP${xfailmsg:+: $xfailmsg}.\n" # skip
              continue;;
           *) xfail="";
              echo "$xfile: Unexpected return value $?." >&2;;
        esac

        # Failure reinvocation.
        reinvoke_failure=0
        reinvoked=0
        if exist_options reinvoke-failure $_test; then
            reinvoke_failure="$(extract_options reinvoke-failure $_test)"
        fi

        # Check for a trigger.

        trigger=

        # tst.*, err.* and drp.* default to running despite the absence of
        # a trigger.
        if [[ $testonly =~ ^(tst|err|drp)\. ]]; then
            trigger=none
        fi

        # Get a trigger from @@trigger, if possible.
        if exist_options trigger $_test; then
            trigger="$(extract_options trigger $_test t)"
        fi

        # Non-'none' @@triggers must exist in test/triggers.
        if [[ "x$trigger" = "xnone" ]] || [[ -z $trigger ]]; then
            :
        elif [[ ! -x test/triggers/$(printf '%s' "$trigger" | cut -d\  -f1) ]]; then
            out "$_test: "
            fail=t
            trigger=$(printf '%s' "$trigger" | cut -d\  -f1)
            fail "$xfail" "$xfailmsg" "trigger $trigger not found in test/triggers"
            continue
        else
            trigger="test/triggers/$trigger"
        fi

        # Finally, look for a .t file.
        if [[ -x $base.t ]]; then
            trigger=$base.t
        fi

        # Note the expected exitcode of this test.

        expected_exitcode=0
        if [[ $testonly =~ ^err\. ]]; then
            expected_exitcode=1
        fi

        # Default and substitute in flags.  The raw_dt_flags apply even to a
        # sh invocation.

        raw_dt_flags="$core_raw_dt_flags"

        expected_tag=
        if [[ $testonly =~ ^err\.D_ ]]; then
            raw_dt_flags="$raw_dt_flags -xerrtags"
            expected_tag="$(echo ''"$testonly" | sed 's,^err\.,,; s,\..*$,,')"
        elif [[ $testonly =~ ^drp\. ]]; then
            raw_dt_flags="$raw_dt_flags -xdroptags"
            expected_tag="$(echo ''"$testonly" | sed 's,^drp\.,,; s,\..*$,,')"
        fi

        dt_flags="$_exit $raw_dt_flags -s $_test"

        if exist_options runtest-opts $_test; then
             opts="$(extract_options runtest-opts $_test t)"

            # The flags go after the dt_flags above, and also land in
            # raw_dt_flags for the sake of shell-script runners.
            if [[ -n $opts ]] && [[ -z $override ]]; then
                dt_flags="$dt_flags $opts"
                raw_dt_flags="$raw_dt_flags $opts"
            fi
        fi

        # Handle an executable .sh.

        progtype=d
        run=$dt
        interpreter_file=""
        if is_interpreter_file $base.d; then
            interpreter_file=$tmpdir/test.interpreter.$RANDOM.d
            rm -f $interpreter_file
            sed 's:^#!dtrace :#!'$dt' :' $base.d > $interpreter_file
            chmod +x $interpreter_file
            progtype=shell
            run="$interpreter_file $raw_dt_flags"
        elif [[ -x $base.sh ]]; then
            progtype=shell
            run="$base.sh $dt"
            if [[ -z $trigger ]]; then
                trigger=none
            fi
            dt_flags="$raw_dt_flags"
        elif [[ -e $base.c ]]; then
            progtype=c
            run="$vg $tmpdir/$(basename $base)"
        fi

        # Erase any pre-existing coredump, then run dtrace, with a
        # timeout, without permitting execution, recording the output and
        # exitcode into temporary files.  (We use a different temporary file on
        # every invocation, so that hanging subprocesses emitting output into
        # these files do not mess up subsequent tests.  This won't strictly fix
        # the problem, but will drive its probability of occurrence way down,
        # even in the presence of a hanging test. stdout and stderr go into
        # different files, and any debugging output is split into a third.)

        rm -f core
        fail=
        this_noexec=$NOEXEC
        testmsg=
        testout=$tmpdir/test.out.$RANDOM
        testerr=$tmpdir/test.err.$RANDOM
        testdebug=$tmpdir/test.debug.$RANDOM
        out "$_test: "

        if [[ "$progtype" = "c" ]]; then
            if [[ -z "$(extract_options link $_test)" ]]; then
                link="-ldtrace"
            else
                link="$(extract_options link $_test)"
            fi

            CC="${CC:-gcc}"
            CCline="$CC -std=gnu99 -D_GNU_SOURCE -D_FILE_OFFSET_BITS=64"
            CCline="$CCline $test_cppflags $CFLAGS $test_ldflags $LDFLAGS"
            CCline="$CCline -o $tmpdir/$(basename $base) $_test $link"
            log "Compiling $CCline\n"
            if ! $CCline >/dev/null 2>$tmpdir/cc.err; then
                fail=t
                fail "$xfail" "$xfailmsg" "compilation failure"
                cat $tmpdir/cc.err >> $LOGFILE
                continue
            fi
        fi

        orig_dt_flags="$dt_flags"
        while [[ $reinvoke_failure -ge 0 ]]; do
            fail=
            tst=$base
            export tst
            if [[ -z $trigger ]] || [[ "$trigger" = "none" ]]; then
                # No trigger.
                case $progtype in
                    d) eflag=
                       if [[ -z $trigger ]]; then
                            eflag=-e
                            this_noexec=t
                       fi
                       run_with_timeout $timeout $run $dt_flags $eflag > $testout 2> $testerr;;
                    shell) run_with_timeout $timeout $run > $testout 2> $testerr;;
                    c) run_with_timeout $timeout $run > $testout 2> $testerr;;
                    *) out "$_test: Internal error: unknown program type $progtype";;
                esac
                exitcode=$?
            else
                # A trigger.  Run dtrace with timeout, and permit execution.  First,
                # run the trigger with a 1s delay before invocation, record
                # its PID, and note it in the dt_flags if $_pid is there.
                #
                # We have to run each of these in separate subprocesses to avoid
                # the SIGCHLD from the sleep 1's death leaking into run_with_timeout
                # and confusing it. (This happens even if disowned.)
                #
                # For .c tests, none of this is supported: we just run the trigger
                # first and kill it afterwards, as if @@trigger-timing: before,
                # passing the PID to the C program as its lone parameter.

                trigger_timing=synchro
                trigger_delay=
                needszap=
                if exist_options trigger-timing $_test &&
                   [[ "x$(extract_options trigger-timing $_test)" != "xsynchro" ]]; then
                    trigger_timing="$(extract_options trigger-timing $_test)"
                    case $trigger_timing in
                        after) trigger_delay=1;;
                        before|synchro) ;;
                        *) trigger_delay=$trigger_timing;;
                    esac
                fi

                explicit_arg=
                if [[ "$trigger_timing" == "synchro" ]] && [[ $progtype != "c" ]]; then
                    # This is an unbearably ugly hack to try to kludge around
                    # shell quoting difficulties.
                    dt_flags="$dt_flags -c "
                    explicit_arg="$trigger"
                    _pid=
                else
                    log "Running trigger $trigger${trigger_delay:+ with delay $trigger_delay}\n"
                    ( [[ -n $trigger_delay ]] && sleep $trigger_delay; exec $trigger; ) &
                    _pid=$!
                    disown %-
                    needszap=t
                    ZAPTHESE+=($_pid)

                    if [[ $dt_flags =~ \$_pid ]]; then
                        dt_flags="$(echo ''"$dt_flags" | sed 's,\$_pid[^a-zA-Z],'$_pid',g; s,\$_pid$,'$_pid',g')"
                    fi
                fi

                case $progtype in
                    d) ( run_with_timeout $timeout $run $dt_flags > $testout 2> $testerr
                         echo $? > $tmpdir/dtrace.exit; );;
                    shell) ( run_with_timeout $timeout $run > $testout 2> $testerr
                             echo $? > $tmpdir/dtrace.exit; );;
                    c) ( run_with_timeout $timeout $run $_pid > $testout 2> $testerr
                         echo $? > $tmpdir/dtrace.exit; );;
                    *) out "$_test: Internal error: unknown program type $progtype";;
                esac
                exitcode="$(cat $tmpdir/dtrace.exit)"
                explicit_arg=

                # If the trigger is still running, kill it, and wait for it, to
                # quiesce the background-process-kill noise the shell would
                # otherwise emit.
                if [[ -n $_pid ]] && [[ "$(ps -p $_pid -o ppid=)" -eq $BASHPID ]]; then
                    kill $_pid >/dev/null 2>&1
                fi
                if [[ -n $needszap ]]; then
                    unset "ZAPTHESE[$((${#ZAPTHESE[@]}-1))]"
                fi
                unset _pid
            fi

            # Translate $interpreter_file back into name of test file.
            if [[ -n $interpreter_file ]]; then
                sed -i 's:'$interpreter_file':'$base.d':' $testout
                sed -i 's:'$interpreter_file':'$base.d':' $testerr
            fi

            # Split debugging info out of the test output.
            grep -E '^[a-z_]+ DEBUG [0-9]+: ' $testerr > $testdebug
            grep -vE '^[a-z_]+ DEBUG [0-9]+: ' $testerr > $testerr.tmp

            # Account for an error message change in CTF
            sed -e 's/Invalid member name/Member name not found/' $testerr.tmp > $testerr
            rm -f $testerr.tmp

            # Note if dtrace mentions running out of memory at any point.
            # If it does, this test quietly becomes an expected failure
            # (without transforming an err.* test into an XPASS).
            if grep -q "Cannot allocate memory" $testout $testerr; then
                xfailmsg="out of memory"

                if [[ $expected_exitcode -eq 0 ]]; then
                    xfail=t
                fi
            fi

            # If deadman-success is on, then a deadman timer firing turns
            # a test into an XFAIL.
            if grep -q "Abort due to systemic unresponsiveness" $testerr &&
                [[ "x$(extract_options deadman-success $_test)" != "x" ]]; then
                testmsg="(expected) systemic unresponsiveness"
                if [[ $expected_exitcode -eq 0 ]]; then
                    xfail=t
                fi
            fi

            # If we are expecting a specific tag in the error output, make sure
            # it is present.  If found, we clear expected_tag to signal that
            # all is fine.
            if [[ -n $expected_tag ]] &&
                grep -q "\[$expected_tag\]" $testerr; then
                expected_tag=
            fi

            if [[ -n $this_noexec ]]; then
                testmsg="no execution"
            fi

            if [[ -s $testerr ]]; then
                echo "-- @@stderr --" >> $testout
                cat $testerr >> $testout
            fi

            if ! postprocess $base.r.p $testout $tmpdir/test.out; then
                testmsg="results postprocessor failed with exitcode $?"
                fail=t
            fi

            rm -f $testerr

            # Note if we will certainly capture results.

            capturing=
            if [[ -n $CAPTURE_EXPECTED ]] && [[ -n $OVERWRITE_RESULTS ]] &&
               [[ -n $COMPARISON ]]; then
                capturing="results captured"
            fi

            # Results processing is complicated by the existence of a
            # class-of-failure hierarchy (e.g. coredumps should be reported in
            # preference to comparison failures, but we still want comparison
            # failures to be logged) and by the need to print a message about each
            # failure at most once, and by our desire to print any expected-results
            # diff after the logged output (so it stands out).
            want_expected_diff=
            want_all_output=
            failmsg=

            # By default, sort before comparing since test results often
            # have indeterminate order (generated on different CPUs, etc.).
            if exist_options nosort $_test; then
                sortcmd=cat
            else
                sortcmd=sort
            fi

            # Compare results, if available, and log the diff.
            rfile=$base.$arch.r
            [[ -e $rfile ]] || rfile=$base.r

            if [[ -e $rfile ]] && [[ -n $COMPARISON ]] &&
               ! diff -u <($sortcmd $rfile) <($sortcmd $tmpdir/test.out) >/dev/null; then

                fail=t
                failmsg="expected results differ"
                want_expected_diff=t
            elif [[ ! -e $base.r ]]; then
                # No expected results?  Write all the output to the sumfile.
                # (It is also always written to the logfile: see below.)
                want_all_output=t
            fi

            if [[ -f core ]] || [[ "$(find $tmpdir -name core -print)" != "" ]] || [[ $exitcode = 139 ]]; then
                # A coredump in the current directory or under the tmpdir.
                # Preserve it in the logdir, if possible.

                mv core $logdir/$(echo $base | tr '/' '-').core 2>/dev/null || true
                find $tmpdir -name core -type f -print0 | xargs -0r -I'{}' mv --backup=numbered '{}' $logdir/$(echo $base | tr '/' '-').core
                fail=t
                failmsg="core dumped"

            # Expected tag not found.
            elif [[ -n $expected_tag ]]; then
                fail=t
                failmsg="expected tag $expected_tag"
                reinvoke_failure=-1

            # Detect a timeout.
            elif [[ $exitcode -eq $TIMEOUTRET ]] && [[ -z $IGNORE_TIMEOUTS ]] &&
                 [[ "x$(extract_options timeout-success $_test)" = "x" ]]; then
                fail=t
                failmsg="timed out"

            # Exitcode of 67 == XFAIL.
            elif [[ $exitcode -eq 67 ]]; then
                [[ $expected_exitcode -eq 1 ]] && fail=t
                xfail=t
                failmsg="requested by test"

            # Exitcodes are not useful if there's been a coredump, but otherwise...
            elif [[ $exitcode != $expected_exitcode ]] && [[ $exitcode -ne $TIMEOUTRET ]]; then

                # Some sort of exitcode error.  Assume that errors in the
                # range 129 -- 193 (a common value of SIGRTMAX) are signal
                # exits.

                fail=t
                if [[ $exitcode -lt 129 ]] || [[ $exitcode -gt 193 ]]; then
                    failmsg="erroneous exitcode ($exitcode)"
                else
                    failmsg="hit by signal $((exitcode - 128))"
                fi
            fi

            reinvoke_failure=$((reinvoke_failure-1))
            if [[ -z $fail ]]; then
                # No reinvocation if we didn't fail.
                reinvoke_failure=-1
            elif [[ -n $fail ]] && [[ $reinvoke_failure -ge 0 ]]; then
                reinvoked=$((reinvoked+1))
                testmsg="after $reinvoked reinvocations"

                resultslog
            fi
            dt_flags="$orig_dt_flags"
        done

        # If it xpasses but is unstable, just call it xfail.
        if [[ -z $fail ]] && [[ -n $xfail ]] && [[ "$(extract_options tags $_test)" == *unstable* ]]; then
            fail=t

            if [[ -z $xfailmsg ]]; then
                xfailmsg="xpassed but unstable"
            else
                xfailmsg="xpassed but unstable; $xfailmsg"
            fi
        fi

        if [[ -z $fail ]]; then

            # Success!
            # If results don't already exist and we are in capture mode, then
            # capture them even if forcible capturing is off.

            if [[ -n $CAPTURE_EXPECTED ]] && [[ -n $COMPARISON ]] &&
               [[ ! -e $base.r ]]; then
                capturing="results captured"
            fi

            pass "$xfail" "$xfailmsg" "$capturing"

        else

            # If it fails, is not xfail, and is unstable, just call it xfail.
            if [[ -z $xfail ]] && [[ "$(extract_options tags $_test)" == *unstable* ]]; then
                xfail=t

                if [[ -z $xfailmsg ]]; then
                    xfailmsg="unstable"
                else
                    xfailmsg="unstable; $xfailmsg"
                fi
            fi

            fail "$xfail" "$xfailmsg" "$failmsg${capturing:+, $capturing}"
        fi

        resultslog t

        # If capturing results is requested, capture them now.
        if [[ -n $capturing ]]; then
            cp -f $tmpdir/test.out $base.r
        fi

        test/utils/clean_probes.sh >> $LOGFILE

        log "\n"

	test/utils/get_remote.sh cleanup

        if [[ -n $regression ]]; then
            # If regtesting, we run a second time, with intermediate results
            # displayed, and output redirected to a per-test, per-dtrace
            # temporary file. We always ban execution during regtesting:
            # our real interest is whether intermediate results have changed.

            dtest_dir="$tmpdir/regtest/$(basename $(dirname $dt))"
            reglog="$dtest_dir/$(echo $_test | sed 's,/,-,g').log"
            mkdir -p $dtest_dir
            echo "$dt_flags -e" > $reglog
            echo >> $reglog
            DTRACE_DEBUG= $dt -S $dt_flags -e > $tmpdir/regtest.out 2>&1 
            postprocess $base.r.p $tmpdir/regtest.out $tmpdir/regtest.out
            cat $tmpdir/regtest.out >> $reglog

            if [[ -z $first_dtest_dir ]]; then
                first_dtest_dir=$dtest_dir
            fi
        fi
    done
done

if [[ -n $regression ]]; then
    # Regtest comparison.  Skip one directory and diff all the others
    # against it.

    force_out "\n\nRegression test comparison.\n"

    for name in $tmpdir/regtest/*; do
        [[ $name = $first_dtest_dir ]] && continue

        if ! diff -urN $first_dtest_dir $name | tee -a $LOGFILE $SUMFILE; then
            force_out "Regression test comparison failed."
        fi
    done
else
    # Test summary.

    gawk -f - $SUMFILE <<'EOF' | tee -a $LOGFILE $SUMFILE
{
	rc = 0;
}
match($0, /: X?(FAIL|PASS|SKIP)/) {
	rc = substr($0, RSTART + 2, RLENGTH - 2);
	count[rc]++;
	total++;
}
rc && match($0, /after [0-9]+ reinvocations/) {
	$0 = substr($0, RSTART);
	count["REINVOKES"] += int($2);
}
END {
        if (count["REINVOKES"] == 0)
	    printf("%d cases (%d PASS, %d FAIL, %d XPASS, %d XFAIL, %d SKIP)\n",
		    total, count["PASS"], count["FAIL"], count["XPASS"],
		           count["XFAIL"], count["SKIP"]);
	else
	    printf("%d cases (%d PASS (%d reinvocations), %d FAIL, %d XPASS, %d XFAIL, %d SKIP)\n",
		    total, count["PASS"], count["REINVOKES"], count["FAIL"],
		    count["XPASS"], count["XFAIL"], count["SKIP"]);
}
EOF

fi

# Test coverage.
for name in build*; do
    if [[ -n "$(echo $name/*.gcda)" ]]; then
        force_out "Coverage info for $(echo $name | sed 's,build,userspace,'):\n"
        lcov --capture --base-directory . --directory $name \
             --quiet -o $logdir/coverage/runtest.lcov
        lcov --add-tracefile $logdir/coverage/initial.lcov \
             --add-tracefile $logdir/coverage/runtest.lcov \
             --quiet -o $logdir/coverage/coverage.lcov
        genhtml --frames --show-details -o $logdir/coverage \
                --title "DTrace userspace coverage" \
                --highlight --legend $logdir/coverage/coverage.lcov | \
            gawk 'BEGIN { quiet=1; } { if (!quiet) { print ($0); } } /^Overall coverage rate:$/ { quiet=0; }' | \
            tee -a $LOGFILE $SUMFILE
    fi
done

if [[ -n $ERRORS ]]; then
    exit 1
fi

exit 0