1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.apache.commons.rng.examples.stress;
18
19 import org.apache.commons.rng.simple.RandomSource;
20
21 import picocli.CommandLine.Command;
22 import picocli.CommandLine.Mixin;
23 import picocli.CommandLine.Option;
24 import picocli.CommandLine.Parameters;
25
26 import java.io.BufferedReader;
27 import java.io.BufferedWriter;
28 import java.io.File;
29 import java.io.FilterOutputStream;
30 import java.io.IOException;
31 import java.io.OutputStream;
32 import java.io.OutputStreamWriter;
33 import java.nio.charset.StandardCharsets;
34 import java.nio.file.Files;
35 import java.util.ArrayList;
36 import java.util.Collections;
37 import java.util.EnumSet;
38 import java.util.Formatter;
39 import java.util.HashMap;
40 import java.util.HashSet;
41 import java.util.Iterator;
42 import java.util.LinkedHashSet;
43 import java.util.List;
44 import java.util.ListIterator;
45 import java.util.Map.Entry;
46 import java.util.Set;
47 import java.util.concurrent.Callable;
48 import java.util.function.Function;
49 import java.util.function.Supplier;
50 import java.util.regex.Matcher;
51 import java.util.regex.Pattern;
52 import java.util.stream.Collectors;
53
54
55
56
57
58
59
60
61
62
63
64 @Command(name = "results",
65 description = {"Collate results from stress test applications."})
66 class ResultsCommand implements Callable<Void> {
67
68 private static final Pattern RANDOM_SOURCE_PATTERN = Pattern.compile("^# RandomSource: (.*)");
69
70 private static final Pattern RNG_PATTERN = Pattern.compile("^# RNG: (.*)");
71
72 private static final Pattern TEST_EXIT_PATTERN = Pattern.compile("^# Exit value: (\\d+)");
73
74 private static final Pattern DIEHARDER_PATTERN = Pattern.compile("^# *dieharder version");
75
76 private static final Pattern DIEHARDER_FAILED_PATTERN = Pattern.compile("FAILED *$");
77
78 private static final Pattern TESTU01_PATTERN = Pattern.compile("^ *Version: TestU01");
79
80 private static final Pattern TESTU01_SUMMARY_PATTERN = Pattern.compile("^========= Summary results of (\\S*) ");
81
82 private static final Pattern TESTU01_TEST_RESULT_PATTERN = Pattern.compile("^ ?(\\d+ .*) ");
83
84 private static final Pattern TESTU01_STARTING_PATTERN = Pattern.compile("^ *Starting (\\S*)");
85
86 private static final Pattern PRACTRAND_PATTERN = Pattern.compile("PractRand version");
87
88 private static final Pattern PRACTRAND_OUTPUT_SIZE_PATTERN = Pattern.compile("\\(2\\^(\\d+) bytes\\)");
89
90 private static final Pattern PRACTRAND_FAILED_PATTERN = Pattern.compile("FAIL *!* *$");
91
92 private static final String DIEHARDER_SUMS = "diehard_sums";
93
94 private static final String BIT_REVERSED = "Bit-reversed";
95
96 private static final char FORWARD_SLASH = '\\';
97
98 private static final char BACK_SLASH = '\\';
99
100 private static final char PIPE = '|';
101
102 private static final String COLUMN_RNG = "RNG";
103
104 private static final String[] BINARY_UNITS = {" B", " KiB", " MiB", " GiB", " TiB", " PiB", " EiB"};
105
106
107 @Mixin
108 private StandardOptions reusableOptions;
109
110
111 @Parameters(arity = "1..*",
112 description = "The results files.",
113 paramLabel = "<file>")
114 private List<File> resultsFiles = new ArrayList<>();
115
116
117 @Option(names = {"-o", "--out"},
118 description = "The output file (default: stdout).")
119 private File fileOutput;
120
121
122 @Option(names = {"-f", "--format"},
123 description = {"Output format (default: ${DEFAULT-VALUE}).",
124 "Valid values: ${COMPLETION-CANDIDATES}."})
125 private ResultsCommand.OutputFormat outputFormat = OutputFormat.TXT;
126
127
128 @Option(names = {"--failed"},
129 description = {"Output failed tests (support varies by format).",
130 "CSV: List individual test failures.",
131 "APT: Count of systematic test failures."})
132 private boolean showFailedTests;
133
134
135 @Option(names = {"--include-sums"},
136 description = "Include Dieharder sums test.")
137 private boolean includeDiehardSums;
138
139
140 @Option(names = {"--path-prefix"},
141 description = {"Common path prefix.",
142 "If specified this will replace the common prefix from all " +
143 "files when the path is output, e.g. for the APT report."})
144 private String pathPrefix = "";
145
146
147 @Option(names = {"-i", "--ignore"},
148 description = "Ignore partial results.")
149 private boolean ignorePartialResults;
150
151
152 @Option(names = {"--delete"},
153 description = {"Delete partial results files.",
154 "This is not reversible!"})
155 private boolean deletePartialResults;
156
157
158
159
160 enum OutputFormat {
161
162 CSV,
163
164 APT,
165
166 TXT,
167
168 FAILURES,
169 }
170
171
172
173
174 enum TestFormat {
175
176 DIEHARDER,
177
178 TESTU01,
179
180 PRACTRAND,
181 }
182
183
184
185
186 private static class TestResult {
187
188 private final File resultFile;
189
190 private final RandomSource randomSource;
191
192 private final boolean bitReversed;
193
194 private final TestFormat testFormat;
195
196 private final List<String> failedTests = new ArrayList<>();
197
198 private String testApplicationName;
199
200
201
202
203
204 private int exitCode = Integer.MIN_VALUE;
205
206
207
208
209
210
211
212 TestResult(File resultFile,
213 RandomSource randomSource,
214 boolean bitReversed,
215 TestFormat testFormat) {
216 this.resultFile = resultFile;
217 this.randomSource = randomSource;
218 this.bitReversed = bitReversed;
219 this.testFormat = testFormat;
220 }
221
222
223
224
225
226
227 void addFailedTest(String testId) {
228 failedTests.add(testId);
229 }
230
231
232
233
234
235
236 File getResultFile() {
237 return resultFile;
238 }
239
240
241
242
243
244
245 RandomSource getRandomSource() {
246 return randomSource;
247 }
248
249
250
251
252
253
254 boolean isBitReversed() {
255 return bitReversed;
256 }
257
258
259
260
261
262
263 TestFormat getTestFormat() {
264 return testFormat;
265 }
266
267
268
269
270
271
272 List<String> getFailedTests() {
273 return failedTests;
274 }
275
276
277
278
279
280
281 int getFailureCount() {
282 return failedTests.size();
283 }
284
285
286
287
288
289
290
291
292
293 String getFailureSummaryString() {
294 return isComplete() ? Integer.toString(failedTests.size()) : "-" + failedTests.size();
295 }
296
297
298
299
300
301
302 void setTestApplicationName(String testApplicationName) {
303 this.testApplicationName = testApplicationName;
304 }
305
306
307
308
309
310
311 String getTestApplicationName() {
312 return testApplicationName == null ? String.valueOf(getTestFormat()) : testApplicationName;
313 }
314
315
316
317
318
319
320
321 boolean isComplete() {
322 return exitCode == 0;
323 }
324
325
326
327
328
329
330 void setExitCode(int exitCode) {
331 this.exitCode = exitCode;
332 }
333 }
334
335
336
337
338
339 private static class PractRandTestResult extends TestResult {
340
341 private int lengthExponent;
342
343
344
345
346
347
348
349 PractRandTestResult(File resultFile, RandomSource randomSource, boolean bitReversed, TestFormat testFormat) {
350 super(resultFile, randomSource, bitReversed, testFormat);
351 }
352
353
354
355
356
357
358
359 int getLengthExponent() {
360 return lengthExponent;
361 }
362
363
364
365
366
367
368 void setLengthExponent(int lengthExponent) {
369 this.lengthExponent = lengthExponent;
370 }
371
372
373
374
375
376
377
378 @Override
379 String getFailureSummaryString() {
380 return lengthExponent == 0 ? "-" : Integer.toString(lengthExponent);
381 }
382 }
383
384
385
386
387 @Override
388 public Void call() {
389 LogUtils.setLogLevel(reusableOptions.logLevel);
390
391
392 final List<TestResult> results = readResults();
393
394 if (deletePartialResults) {
395 deleteIfIncomplete(results);
396 return null;
397 }
398
399 try (OutputStream out = createOutputStream()) {
400 switch (outputFormat) {
401 case CSV:
402 writeCSVData(out, results);
403 break;
404 case APT:
405 writeAPT(out, results);
406 break;
407 case TXT:
408 writeTXT(out, results);
409 break;
410 case FAILURES:
411 writeFailures(out, results);
412 break;
413 default:
414 throw new ApplicationException("Unknown output format: " + outputFormat);
415 }
416 } catch (final IOException ex) {
417 throw new ApplicationException("IO error: " + ex.getMessage(), ex);
418 }
419 return null;
420 }
421
422
423
424
425
426
427 private List<TestResult> readResults() {
428 final ArrayList<TestResult> results = new ArrayList<>();
429 for (final File resultFile : resultsFiles) {
430 readResults(results, resultFile);
431 }
432 return results;
433 }
434
435
436
437
438
439
440
441
442 private void readResults(List<TestResult> results,
443 File resultFile) {
444 final List<String> contents = readFileContents(resultFile);
445
446 final List<List<String>> outputs = splitContents(contents);
447 if (outputs.isEmpty()) {
448 LogUtils.error("No test output in file: %s", resultFile);
449 } else {
450 for (final List<String> testOutput : outputs) {
451 final TestResult result = readResult(resultFile, testOutput);
452 if (!result.isComplete()) {
453 LogUtils.info("Partial results in file: %s", resultFile);
454 if (ignorePartialResults) {
455 continue;
456 }
457 }
458 results.add(result);
459 }
460 }
461 }
462
463
464
465
466
467
468
469
470 private static List<String> readFileContents(File resultFile) {
471 final ArrayList<String> contents = new ArrayList<>();
472 try (BufferedReader reader = Files.newBufferedReader(resultFile.toPath())) {
473 for (String line = reader.readLine(); line != null; line = reader.readLine()) {
474 contents.add(line);
475 }
476 } catch (final IOException ex) {
477 throw new ApplicationException("Failed to read file contents: " + resultFile, ex);
478 }
479 return contents;
480 }
481
482
483
484
485
486
487
488
489 private static List<List<String>> splitContents(List<String> contents) {
490 final ArrayList<List<String>> testOutputs = new ArrayList<>();
491
492
493 int begin = -1;
494 for (int i = 0; i < contents.size(); i++) {
495 if (RANDOM_SOURCE_PATTERN.matcher(contents.get(i)).matches()) {
496 if (begin >= 0) {
497 testOutputs.add(contents.subList(begin, i));
498 }
499 begin = i;
500 }
501 }
502 if (begin >= 0) {
503 testOutputs.add(contents.subList(begin, contents.size()));
504 }
505 return testOutputs;
506 }
507
508
509
510
511
512
513
514
515
516 private TestResult readResult(File resultFile,
517 List<String> testOutput) {
518
519 final ListIterator<String> iter = testOutput.listIterator();
520
521
522 final RandomSource randomSource = getRandomSource(resultFile, iter);
523 final boolean bitReversed = getBitReversed(resultFile, iter);
524
525
526 final TestFormat testFormat = getTestFormat(resultFile, iter);
527
528
529 final TestResult testResult = createTestResult(resultFile, randomSource, bitReversed, testFormat);
530 if (testFormat == TestFormat.DIEHARDER) {
531 readDieharder(iter, testResult);
532 } else if (testFormat == TestFormat.TESTU01) {
533 readTestU01(resultFile, iter, testResult);
534 } else {
535 readPractRand(iter, (PractRandTestResult) testResult);
536 }
537 return testResult;
538 }
539
540
541
542
543
544
545
546
547
548
549 private static TestResult createTestResult(File resultFile, RandomSource randomSource,
550 boolean bitReversed, TestFormat testFormat) {
551 return testFormat == TestFormat.PRACTRAND ?
552 new PractRandTestResult(resultFile, randomSource, bitReversed, testFormat) :
553 new TestResult(resultFile, randomSource, bitReversed, testFormat);
554 }
555
556
557
558
559
560
561
562
563
564 private static RandomSource getRandomSource(File resultFile, Iterator<String> iter) {
565 while (iter.hasNext()) {
566 final Matcher matcher = RANDOM_SOURCE_PATTERN.matcher(iter.next());
567 if (matcher.matches()) {
568 return RandomSource.valueOf(matcher.group(1));
569 }
570 }
571 throw new ApplicationException("Failed to find RandomSource header line: " + resultFile);
572 }
573
574
575
576
577
578
579
580
581
582 private static boolean getBitReversed(File resultFile, Iterator<String> iter) {
583 while (iter.hasNext()) {
584 final Matcher matcher = RNG_PATTERN.matcher(iter.next());
585 if (matcher.matches()) {
586 return matcher.group(1).contains(BIT_REVERSED);
587 }
588 }
589 throw new ApplicationException("Failed to find RNG header line: " + resultFile);
590 }
591
592
593
594
595
596
597
598
599
600
601
602 private TestFormat getTestFormat(File resultFile, Iterator<String> iter) {
603 while (iter.hasNext()) {
604 final String line = iter.next();
605 if (DIEHARDER_PATTERN.matcher(line).find()) {
606 return TestFormat.DIEHARDER;
607 }
608 if (TESTU01_PATTERN.matcher(line).find()) {
609 return TestFormat.TESTU01;
610 }
611 if (PRACTRAND_PATTERN.matcher(line).find()) {
612 return TestFormat.PRACTRAND;
613 }
614 }
615 if (!ignorePartialResults) {
616 throw new ApplicationException("Failed to identify the test application format: " + resultFile);
617 }
618 LogUtils.error("Failed to identify the test application format: %s", resultFile);
619 return null;
620 }
621
622
623
624
625
626
627
628 private void readDieharder(Iterator<String> iter,
629 TestResult testResult) {
630
631
632
633
634
635
636
637
638 testResult.setTestApplicationName("Dieharder");
639
640
641
642 while (iter.hasNext()) {
643 final String line = iter.next();
644 if (DIEHARDER_FAILED_PATTERN.matcher(line).find()) {
645
646 if (!includeDiehardSums && line.contains(DIEHARDER_SUMS)) {
647 continue;
648 }
649 final int index1 = line.indexOf('|');
650 final int index2 = line.indexOf('|', index1 + 1);
651 testResult.addFailedTest(line.substring(0, index1).trim() + ":" +
652 line.substring(index1 + 1, index2).trim());
653 } else if (findExitCode(testResult, line)) {
654 return;
655 }
656 }
657 }
658
659
660
661
662
663
664
665
666 private static boolean findExitCode(TestResult testResult, String line) {
667 final Matcher matcher = TEST_EXIT_PATTERN.matcher(line);
668 if (matcher.find()) {
669 testResult.setExitCode(Integer.parseInt(matcher.group(1)));
670 return true;
671 }
672 return false;
673 }
674
675
676
677
678
679
680
681
682
683
684
685
686 private void readTestU01(File resultFile,
687 ListIterator<String> iter,
688 TestResult testResult) {
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708 final String testSuiteName = skipToTestU01Summary(resultFile, iter);
709
710
711 if (testSuiteName == null) {
712
713 while (iter.hasPrevious()) {
714 iter.previous();
715 }
716 updateTestU01ApplicationName(iter, testResult);
717 return;
718 }
719
720 setTestU01ApplicationName(testResult, testSuiteName);
721
722
723
724
725 while (iter.hasNext()) {
726 final String line = iter.next();
727 final Matcher matcher = TESTU01_TEST_RESULT_PATTERN.matcher(line);
728 if (matcher.find()) {
729 testResult.addFailedTest(matcher.group(1).trim());
730 } else if (findExitCode(testResult, line)) {
731 return;
732 }
733 }
734 }
735
736
737
738
739
740
741
742 private static void setTestU01ApplicationName(TestResult testResult, String testSuiteName) {
743 testResult.setTestApplicationName("TestU01 (" + testSuiteName + ")");
744 }
745
746
747
748
749
750
751
752
753
754
755
756
757 private String skipToTestU01Summary(File resultFile, Iterator<String> iter) {
758 final String testSuiteName = findMatcherGroup1(iter, TESTU01_SUMMARY_PATTERN);
759
760 if (testSuiteName != null || ignorePartialResults) {
761 return testSuiteName;
762 }
763 throw new ApplicationException("Failed to identify the Test U01 result summary: " + resultFile);
764 }
765
766
767
768
769
770
771
772
773
774 private static void updateTestU01ApplicationName(Iterator<String> iter,
775 TestResult testResult) {
776 final String testSuiteName = findMatcherGroup1(iter, TESTU01_STARTING_PATTERN);
777 if (testSuiteName != null) {
778 setTestU01ApplicationName(testResult, testSuiteName);
779 }
780 }
781
782
783
784
785
786
787
788
789
790 private static String findMatcherGroup1(Iterator<String> iter,
791 Pattern pattern) {
792 while (iter.hasNext()) {
793 final String line = iter.next();
794 final Matcher matcher = pattern.matcher(line);
795 if (matcher.find()) {
796 return matcher.group(1);
797 }
798 }
799 return null;
800 }
801
802
803
804
805
806
807
808 private static void readPractRand(Iterator<String> iter,
809 PractRandTestResult testResult) {
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828 testResult.setTestApplicationName("PractRand");
829
830
831 int exp = 0;
832
833
834
835 while (iter.hasNext()) {
836 String line = iter.next();
837 final Matcher matcher = PRACTRAND_OUTPUT_SIZE_PATTERN.matcher(line);
838 if (matcher.find()) {
839
840 exp = Integer.parseInt(matcher.group(1));
841 } else if (PRACTRAND_FAILED_PATTERN.matcher(line).find()) {
842
843 testResult.setLengthExponent(exp);
844
845
846
847 line = line.trim();
848 final int index = line.indexOf(' ');
849 testResult.addFailedTest(line.substring(0, index));
850 } else if (findExitCode(testResult, line)) {
851 return;
852 }
853 }
854 }
855
856
857
858
859
860
861
862 private static void deleteIfIncomplete(List<TestResult> results) {
863 results.forEach(ResultsCommand::deleteIfIncomplete);
864 }
865
866
867
868
869
870
871
872 private static void deleteIfIncomplete(TestResult result) {
873 if (!result.isComplete()) {
874 try {
875 Files.delete(result.getResultFile().toPath());
876 LogUtils.info("Deleted file: %s", result.getResultFile());
877 } catch (IOException ex) {
878 throw new ApplicationException("Failed to delete file: " + result.getResultFile(), ex);
879 }
880 }
881 }
882
883
884
885
886
887
888 private OutputStream createOutputStream() {
889 if (fileOutput != null) {
890 try {
891 return Files.newOutputStream(fileOutput.toPath());
892 } catch (final IOException ex) {
893 throw new ApplicationException("Failed to create output: " + fileOutput, ex);
894 }
895 }
896 return new FilterOutputStream(System.out) {
897 @Override
898 public void close() {
899
900 }
901 };
902 }
903
904
905
906
907
908
909
910
911 private void writeCSVData(OutputStream out,
912 List<TestResult> results) throws IOException {
913
914 Collections.sort(results, (o1, o2) -> {
915 int result = Integer.compare(o1.getRandomSource().ordinal(), o2.getRandomSource().ordinal());
916 if (result != 0) {
917 return result;
918 }
919 result = Boolean.compare(o1.isBitReversed(), o2.isBitReversed());
920 if (result != 0) {
921 return result;
922 }
923 result = o1.getTestApplicationName().compareTo(o2.getTestApplicationName());
924 if (result != 0) {
925 return result;
926 }
927 return Integer.compare(o1.getFailureCount(), o2.getFailureCount());
928 });
929
930 try (BufferedWriter output = new BufferedWriter(new OutputStreamWriter(out, StandardCharsets.UTF_8))) {
931 output.write("RandomSource,Bit-reversed,Test,Failures");
932 if (showFailedTests) {
933 output.write(",Failed");
934 }
935 output.newLine();
936 for (final TestResult result : results) {
937 output.write(result.getRandomSource().toString());
938 output.write(',');
939 output.write(Boolean.toString(result.isBitReversed()));
940 output.write(',');
941 output.write(result.getTestApplicationName());
942 output.write(',');
943 output.write(result.getFailureSummaryString());
944
945 if (showFailedTests) {
946 output.write(',');
947 output.write(result.getFailedTests().stream().collect(Collectors.joining("|")));
948 }
949 output.newLine();
950 }
951 }
952 }
953
954
955
956
957
958
959
960
961
962 private void writeAPT(OutputStream out,
963 List<TestResult> results) throws IOException {
964
965
966 final List<RandomSource> randomSources = getRandomSources(results);
967 final List<Boolean> bitReversed = getBitReversed(results);
968 final List<String> testNames = getTestNames(results);
969
970
971 final int prefixLength = (pathPrefix.isEmpty()) ? 0 : findCommonPathPrefixLength(results);
972
973
974
975 final Function<TestResult, String> toAPTString = result -> {
976 String path = result.getResultFile().getPath();
977
978 path = path.substring(prefixLength);
979
980 final StringBuilder sb = new StringBuilder()
981 .append("{{{").append(pathPrefix).append(path).append('}')
982 .append(result.getFailureSummaryString()).append("}}");
983
984 for (int i = 0; i < sb.length(); i++) {
985 if (sb.charAt(i) == BACK_SLASH) {
986 sb.setCharAt(i, FORWARD_SLASH);
987 }
988 }
989 return sb.toString();
990 };
991
992
993
994 final boolean showBitReversedColumn = bitReversed.contains(Boolean.TRUE);
995
996 final String header = createAPTHeader(showBitReversedColumn, testNames);
997 final String separator = createAPTSeparator(header);
998
999
1000 try (BufferedWriter output = new BufferedWriter(new OutputStreamWriter(out, StandardCharsets.UTF_8))) {
1001
1002 output.write(separator.replace('+', '*'));
1003 output.write(header);
1004 output.newLine();
1005 output.write(separator);
1006
1007 final StringBuilder sb = new StringBuilder();
1008
1009
1010 for (final RandomSource randomSource : randomSources) {
1011 for (final boolean reversed : bitReversed) {
1012
1013 boolean highlight = true;
1014
1015
1016 sb.setLength(0);
1017
1018 if (showBitReversedColumn) {
1019 writeAPTColumn(sb, Boolean.toString(reversed), false);
1020 }
1021 for (final String testName : testNames) {
1022 final List<TestResult> testResults = getTestResults(results, randomSource, reversed, testName);
1023 String text = testResults.stream()
1024 .map(toAPTString)
1025 .collect(Collectors.joining(", "));
1026
1027 final String summary = getFailuresSummary(testResults);
1028 if (!summary.isEmpty()) {
1029
1030 highlight = false;
1031 if (showFailedTests) {
1032
1033 text += " (" + summary + ")";
1034 }
1035 }
1036 writeAPTColumn(sb, text, false);
1037 }
1038
1039 output.write('|');
1040 writeAPTColumn(output, randomSource.toString(), highlight);
1041 output.write(sb.toString());
1042 output.newLine();
1043 output.write(separator);
1044 }
1045 }
1046 }
1047 }
1048
1049
1050
1051
1052
1053
1054
1055 private static List<RandomSource> getRandomSources(List<TestResult> results) {
1056 final EnumSet<RandomSource> set = EnumSet.noneOf(RandomSource.class);
1057 for (final TestResult result : results) {
1058 set.add(result.getRandomSource());
1059 }
1060 final ArrayList<RandomSource> list = new ArrayList<>(set);
1061 Collections.sort(list);
1062 return list;
1063 }
1064
1065
1066
1067
1068
1069
1070
1071 private static List<Boolean> getBitReversed(List<TestResult> results) {
1072 final ArrayList<Boolean> list = new ArrayList<>(2);
1073 if (results.isEmpty()) {
1074
1075 list.add(Boolean.FALSE);
1076 } else {
1077 final boolean first = results.get(0).isBitReversed();
1078 list.add(first);
1079 for (final TestResult result : results) {
1080 if (first != result.isBitReversed()) {
1081 list.add(!first);
1082 break;
1083 }
1084 }
1085 }
1086 Collections.sort(list);
1087 return list;
1088 }
1089
1090
1091
1092
1093
1094
1095
1096 private static List<String> getTestNames(List<TestResult> results) {
1097
1098 final Set<String> set = new LinkedHashSet<>();
1099 for (final TestResult result : results) {
1100 set.add(result.getTestApplicationName());
1101 }
1102 return new ArrayList<>(set);
1103 }
1104
1105
1106
1107
1108
1109
1110
1111
1112 private static int findCommonPathPrefixLength(List<TestResult> results) {
1113 if (results.isEmpty()) {
1114 return 0;
1115 }
1116
1117 final String prefix1 = getPathPrefix(results.get(0));
1118 int length = prefix1.length();
1119 for (int i = 1; i < results.size() && length != 0; i++) {
1120 final String prefix2 = getPathPrefix(results.get(i));
1121
1122 final int size = Math.min(prefix2.length(), length);
1123 length = 0;
1124 while (length < size && prefix1.charAt(length) == prefix2.charAt(length)) {
1125 length++;
1126 }
1127 }
1128 return length;
1129 }
1130
1131
1132
1133
1134
1135
1136
1137 private static String getPathPrefix(TestResult testResult) {
1138 final String parent = testResult.getResultFile().getParent();
1139 return parent == null ? "" : parent;
1140 }
1141
1142
1143
1144
1145
1146
1147
1148
1149 private static String createAPTHeader(boolean showBitReversedColumn,
1150 List<String> testNames) {
1151 final StringBuilder sb = new StringBuilder(100).append("|| RNG identifier ||");
1152 if (showBitReversedColumn) {
1153 sb.append(" Bit-reversed ||");
1154 }
1155 for (final String name : testNames) {
1156 sb.append(' ').append(name).append(" ||");
1157 }
1158 return sb.toString();
1159 }
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169 private static String createAPTSeparator(String header) {
1170
1171
1172 final StringBuilder sb = new StringBuilder(header);
1173 for (int i = 0; i < header.length(); i++) {
1174 if (sb.charAt(i) == PIPE) {
1175 sb.setCharAt(i, i == 0 ? '*' : '+');
1176 sb.setCharAt(i + 1, '-');
1177 } else {
1178 sb.setCharAt(i, '-');
1179 }
1180 }
1181
1182 sb.setCharAt(header.length() - 2, '-');
1183 sb.setCharAt(header.length() - 1, '+');
1184 sb.append(System.lineSeparator());
1185 return sb.toString();
1186 }
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196 private static void writeAPTColumn(Appendable output,
1197 String text,
1198 boolean highlight) throws IOException {
1199 output.append(' ');
1200 if (highlight) {
1201 output.append("<<");
1202 }
1203 output.append(text);
1204 if (highlight) {
1205 output.append(">>");
1206 }
1207 output.append(" |");
1208 }
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219 private static List<TestResult> getTestResults(List<TestResult> results,
1220 RandomSource randomSource,
1221 boolean bitReversed,
1222 String testName) {
1223 final ArrayList<TestResult> list = new ArrayList<>();
1224 for (final TestResult result : results) {
1225 if (result.getRandomSource() == randomSource &&
1226 result.bitReversed == bitReversed &&
1227 result.getTestApplicationName().equals(testName)) {
1228 list.add(result);
1229 }
1230 }
1231 return list;
1232 }
1233
1234
1235
1236
1237
1238
1239
1240 private static List<String> getSystematicFailures(List<TestResult> results) {
1241 final HashMap<String, Integer> map = new HashMap<>();
1242 for (final TestResult result : results) {
1243
1244 if (!result.isComplete()) {
1245 continue;
1246 }
1247
1248
1249
1250
1251
1252
1253 final HashSet<String> unique = new HashSet<>(result.getFailedTests());
1254 for (final String test : unique) {
1255 map.merge(test, 1, (i, j) -> i + j);
1256 }
1257 }
1258 final int completeCount = (int) results.stream().filter(TestResult::isComplete).count();
1259 final List<String> list = map.entrySet().stream()
1260 .filter(e -> e.getValue() == completeCount)
1261 .map(Entry::getKey)
1262 .collect(Collectors.toCollection(
1263 (Supplier<List<String>>) ArrayList::new));
1264
1265
1266
1267
1268 final int max = getMaxLengthExponent(results);
1269 if (max != 0) {
1270 list.add(bytesToString(max));
1271 }
1272 return list;
1273 }
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286 private static int getMaxLengthExponent(List<TestResult> results) {
1287 if (results.isEmpty()) {
1288 return 0;
1289 }
1290
1291
1292 final int[] data = new int[2];
1293 results.stream()
1294 .filter(TestResult::isComplete)
1295 .filter(r -> r instanceof PractRandTestResult)
1296 .mapToInt(r -> ((PractRandTestResult) r).getLengthExponent())
1297 .forEach(i -> {
1298 if (i == 0) {
1299
1300 data[0]++;
1301 } else {
1302
1303 data[1] = Math.max(i, data[1]);
1304 }
1305 });
1306
1307 return data[0] == 0 ? data[1] : 0;
1308 }
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323 private static String getFailuresSummary(List<TestResult> results) {
1324 if (results.isEmpty()) {
1325 return "";
1326 }
1327 if (results.get(0).getTestFormat() == TestFormat.PRACTRAND) {
1328 final int max = getMaxLengthExponent(results);
1329 return max == 0 ? "" : bytesToString(max);
1330 }
1331 final int count = getSystematicFailures(results).size();
1332 return count == 0 ? "" : Integer.toString(count);
1333 }
1334
1335
1336
1337
1338
1339
1340
1341
1342 private static void writeTXT(OutputStream out,
1343 List<TestResult> results) throws IOException {
1344
1345
1346 final List<RandomSource> randomSources = getRandomSources(results);
1347 final List<Boolean> bitReversed = getBitReversed(results);
1348 final List<String> testNames = getTestNames(results);
1349
1350
1351
1352 final boolean showBitReversedColumn = bitReversed.contains(Boolean.TRUE);
1353
1354 final List<List<String>> columns = createTXTColumns(testNames, showBitReversedColumn);
1355
1356
1357
1358 for (final RandomSource randomSource : randomSources) {
1359 for (final boolean reversed : bitReversed) {
1360 int i = 0;
1361 columns.get(i++).add(randomSource.toString());
1362 if (showBitReversedColumn) {
1363 columns.get(i++).add(Boolean.toString(reversed));
1364 }
1365 for (final String testName : testNames) {
1366 final List<TestResult> testResults = getTestResults(results, randomSource,
1367 reversed, testName);
1368 columns.get(i++).add(testResults.stream()
1369 .map(TestResult::getFailureSummaryString)
1370 .collect(Collectors.joining(",")));
1371 columns.get(i++).add(getFailuresSummary(testResults));
1372 }
1373 }
1374 }
1375
1376 writeColumns(out, columns);
1377 }
1378
1379
1380
1381
1382
1383
1384
1385
1386 private static List<List<String>> createTXTColumns(final List<String> testNames,
1387 final boolean showBitReversedColumn) {
1388 final ArrayList<List<String>> columns = new ArrayList<>();
1389 columns.add(createColumn(COLUMN_RNG));
1390 if (showBitReversedColumn) {
1391 columns.add(createColumn(BIT_REVERSED));
1392 }
1393 for (final String testName : testNames) {
1394 columns.add(createColumn(testName));
1395 columns.add(createColumn("∩"));
1396 }
1397 return columns;
1398 }
1399
1400
1401
1402
1403
1404
1405
1406 private static List<String> createColumn(String columnName) {
1407 final ArrayList<String> list = new ArrayList<>();
1408 list.add(columnName);
1409 return list;
1410 }
1411
1412
1413
1414
1415
1416
1417
1418
1419 private static String createTextFormatFromColumnWidths(final List<List<String>> columns) {
1420 final StringBuilder sb = new StringBuilder();
1421 try (Formatter formatter = new Formatter(sb)) {
1422 for (int i = 0; i < columns.size(); i++) {
1423 if (i != 0) {
1424 sb.append('\t');
1425 }
1426 formatter.format("%%-%ds", getColumnWidth(columns.get(i)));
1427 }
1428 }
1429 sb.append(System.lineSeparator());
1430 return sb.toString();
1431 }
1432
1433
1434
1435
1436
1437
1438
1439 private static int getColumnWidth(List<String> column) {
1440 int width = 0;
1441 for (final String text : column) {
1442 width = Math.max(width, text.length());
1443 }
1444 return width;
1445 }
1446
1447
1448
1449
1450
1451
1452
1453
1454 private static void writeColumns(OutputStream out,
1455 List<List<String>> columns) throws IOException {
1456
1457 final String format = createTextFormatFromColumnWidths(columns);
1458
1459
1460 try (BufferedWriter output = new BufferedWriter(new OutputStreamWriter(out, StandardCharsets.UTF_8));
1461 Formatter formatter = new Formatter(output)) {
1462 final int rows = columns.get(0).size();
1463 final Object[] args = new Object[columns.size()];
1464 for (int row = 0; row < rows; row++) {
1465 for (int i = 0; i < args.length; i++) {
1466 args[i] = columns.get(i).get(row);
1467 }
1468 formatter.format(format, args);
1469 }
1470 }
1471 }
1472
1473
1474
1475
1476
1477
1478
1479
1480 private static void writeFailures(OutputStream out,
1481 List<TestResult> results) throws IOException {
1482
1483
1484 final List<RandomSource> randomSources = getRandomSources(results);
1485 final List<Boolean> bitReversed = getBitReversed(results);
1486 final List<String> testNames = getTestNames(results);
1487
1488
1489
1490 final boolean showBitReversedColumn = bitReversed.contains(Boolean.TRUE);
1491
1492 final List<List<String>> columns = createFailuresColumns(testNames, showBitReversedColumn);
1493
1494 final AlphaNumericComparator cmp = new AlphaNumericComparator();
1495
1496
1497 for (final RandomSource randomSource : randomSources) {
1498 for (final boolean reversed : bitReversed) {
1499 for (final String testName : testNames) {
1500 final List<TestResult> testResults = getTestResults(results, randomSource,
1501 reversed, testName);
1502 final List<String> failures = getSystematicFailures(testResults);
1503 if (failures.isEmpty()) {
1504 continue;
1505 }
1506 Collections.sort(failures, cmp);
1507 for (final String failed : failures) {
1508 int i = 0;
1509 columns.get(i++).add(randomSource.toString());
1510 if (showBitReversedColumn) {
1511 columns.get(i++).add(Boolean.toString(reversed));
1512 }
1513 columns.get(i++).add(testName);
1514 columns.get(i).add(failed);
1515 }
1516 }
1517 }
1518 }
1519
1520 writeColumns(out, columns);
1521 }
1522
1523
1524
1525
1526
1527
1528
1529
1530 private static List<List<String>> createFailuresColumns(final List<String> testNames,
1531 final boolean showBitReversedColumn) {
1532 final ArrayList<List<String>> columns = new ArrayList<>();
1533 columns.add(createColumn(COLUMN_RNG));
1534 if (showBitReversedColumn) {
1535 columns.add(createColumn(BIT_REVERSED));
1536 }
1537 columns.add(createColumn("Test Suite"));
1538 columns.add(createColumn("Test"));
1539 return columns;
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
1565 static String bytesToString(int exponent) {
1566 final int unit = exponent / 10;
1567 final int size = 1 << (exponent - 10 * unit);
1568 return size + BINARY_UNITS[unit];
1569 }
1570 }