1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 package org.newsclub.net.unix.selftest;
19
20 import java.io.File;
21 import java.io.FileInputStream;
22 import java.io.IOException;
23 import java.io.InputStream;
24 import java.io.InputStreamReader;
25 import java.io.OutputStreamWriter;
26 import java.io.PipedInputStream;
27 import java.io.PipedOutputStream;
28 import java.io.PrintStream;
29 import java.io.PrintWriter;
30 import java.io.StringWriter;
31 import java.io.Writer;
32 import java.nio.charset.Charset;
33 import java.nio.charset.StandardCharsets;
34 import java.nio.file.Files;
35 import java.nio.file.Path;
36 import java.time.Duration;
37 import java.util.ArrayList;
38 import java.util.Arrays;
39 import java.util.Collections;
40 import java.util.HashSet;
41 import java.util.LinkedHashMap;
42 import java.util.LinkedHashSet;
43 import java.util.List;
44 import java.util.Locale;
45 import java.util.Map;
46 import java.util.Map.Entry;
47 import java.util.Objects;
48 import java.util.Optional;
49 import java.util.Properties;
50 import java.util.Set;
51 import java.util.TreeMap;
52 import java.util.TreeSet;
53 import java.util.function.Supplier;
54
55 import org.junit.jupiter.engine.JupiterTestEngine;
56 import org.junit.jupiter.engine.discovery.DiscoverySelectorResolver;
57 import org.junit.platform.engine.TestExecutionResult;
58 import org.junit.platform.engine.support.descriptor.EngineDescriptor;
59 import org.junit.platform.engine.support.hierarchical.HierarchicalTestEngine;
60 import org.junit.platform.launcher.TestIdentifier;
61 import org.junit.platform.launcher.listeners.TestExecutionSummary;
62 import org.newsclub.net.unix.AFSocket;
63 import org.newsclub.net.unix.AFSocketCapability;
64 import org.newsclub.net.unix.AFUNIXSocket;
65
66 import com.kohlschutter.annotations.compiletime.SuppressFBWarnings;
67 import com.kohlschutter.testutil.TestAbortedNotAnIssueException;
68 import com.kohlschutter.testutil.TestAbortedWithImportantMessageException;
69 import com.kohlschutter.testutil.TestAbortedWithImportantMessageException.MessageType;
70 import com.kohlschutter.util.ConsolePrintStream;
71 import com.kohlschutter.util.ProcessUtil;
72 import com.kohlschutter.util.SystemPropertyUtil;
73
74
75
76
77
78
79
80
81
82
83
84 @SuppressWarnings({
85 "PMD.CyclomaticComplexity", "PMD.CognitiveComplexity", "PMD.CouplingBetweenObjects",
86 "PMD.ExcessiveImports"})
87 @SuppressFBWarnings({"PATH_TRAVERSAL_IN", "INFORMATION_EXPOSURE_THROUGH_AN_ERROR_MESSAGE"})
88 public class Selftest {
89 private final Class<?> diagnosticsHelperClass = resolveOptionalClass(
90 "org.newsclub.net.unix.SelftestDiagnosticsHelper");
91 private final ConsolePrintStream out;
92 private final Map<String, ModuleResult> results = new LinkedHashMap<>();
93 private final List<AFSocketCapability> supportedCapabilites = new ArrayList<>();
94 private final List<AFSocketCapability> unsupportedCapabilites = new ArrayList<>();
95 private boolean withIssues = false;
96 private boolean fail = false;
97 private boolean modified = false;
98 private boolean isSupportedAFUNIX = false;
99 private final Set<String> important = new LinkedHashSet<>();
100 private boolean inconclusive = false;
101 private final SelftestProvider sp;
102
103 private enum Result {
104 AUTOSKIP, SKIP, PASS, DONE, NONE, FAIL
105 }
106
107 private enum SkipMode {
108 UNDECLARED(false), KEEP(false), SKIP(true), SKIP_FORCE(true), SKIP_AUTO(true);
109
110 final boolean skip;
111
112 SkipMode(boolean skip) {
113 this.skip = skip;
114 }
115
116 boolean isSkip() {
117 return skip;
118 }
119
120 boolean isDeclared() {
121 return this != UNDECLARED;
122 }
123
124 boolean isForce() {
125 return this == SKIP_FORCE || this == SKIP_AUTO;
126 }
127
128 public static SkipMode parse(String skipMode) {
129 if (skipMode == null || skipMode.isEmpty()) {
130 return SkipMode.UNDECLARED;
131 } else if ("force".equalsIgnoreCase(skipMode)) {
132 return SkipMode.SKIP_FORCE;
133 } else if ("force_auto".equalsIgnoreCase(skipMode)) {
134 return SkipMode.SKIP_AUTO;
135 } else {
136 return Boolean.parseBoolean(skipMode) ? SkipMode.SKIP : SkipMode.KEEP;
137 }
138 }
139
140 }
141
142
143
144
145
146
147
148 @SuppressFBWarnings("UUF_UNUSED_FIELD")
149 static final class MinimizeJarDependencies {
150 JupiterTestEngine jte;
151 HierarchicalTestEngine<?> hte;
152 EngineDescriptor ed;
153 DiscoverySelectorResolver dsr;
154 org.newsclub.lib.junixsocket.common.NarMetadata nmCommon;
155 org.newsclub.lib.junixsocket.custom.NarMetadata nmCustom;
156 }
157
158 public Selftest(PrintStream out, SelftestProvider sp) {
159 System.setProperty("com.kohlschutter.selftest", getClass().getName());
160
161 this.out = ConsolePrintStream.wrapPrintStream(out);
162 this.sp = sp;
163
164 checkSystemProperties();
165 }
166
167 private void checkSystemProperties() {
168 String tmpDir = System.getProperty("java.io.tmpdir", "");
169 if (System.getProperty("java.home", "").isEmpty()) {
170 System.setProperty("java.home", tmpDir);
171 out.println("Setting java.home to temporary directory: " + tmpDir);
172 }
173 }
174
175 public void checkVM() {
176 boolean isSubstrateVM = "Substrate VM".equals(System.getProperty("java.vm.name"));
177
178 if (isSubstrateVM) {
179 important.add("Substrate VM detected: Support for native-images is work in progress");
180
181 String vendorVersion = System.getProperty("java.vendor.version", "");
182 if (vendorVersion.contains("GraalVM 20") || vendorVersion.contains("GraalVM 19")) {
183 if (!getSkipModeForModule("junixsocket-rmi").isDeclared()) {
184 important.add("Auto-skipping junixsocket-rmi tests due to old Substrate VM");
185 System.setProperty("selftest.skip.junixsocket-rmi", "force_auto");
186 withIssues = true;
187 }
188
189 if (!getSkipModeForClass("org.newsclub.net.unix.FileDescriptorCastTest").isDeclared()) {
190 important.add("Auto-skipping FileDescriptorCastTest tests due to Substrate VM");
191 System.setProperty("selftest.skip.FileDescriptorCastTest", "force_auto");
192 withIssues = true;
193 }
194 }
195 } else {
196 if (!getSkipModeForModule("junixsocket-rmi").isDeclared()) {
197 try {
198 Class.forName("java.rmi.Remote");
199 } catch (ClassNotFoundException e) {
200 important.add("Auto-skipping junixsocket-rmi tests due to java.rmi.Remote class missing");
201 System.setProperty("selftest.skip.junixsocket-rmi", "force_auto");
202 withIssues = true;
203 }
204
205 if (!AFSocket.supports(AFSocketCapability.CAPABILITY_LARGE_PORTS)) {
206 important.add(
207 "Auto-skipping junixsocket-rmi tests due to missing CAPABILITY_LARGE_PORTS");
208 System.setProperty("selftest.skip.junixsocket-rmi", "force_auto");
209 withIssues = true;
210 }
211 }
212 }
213 }
214
215
216
217
218
219
220
221
222
223 public static void main(String[] args) throws Exception {
224 int delay = SystemPropertyUtil.getIntSystemProperty("selftest.delay.at-start", 0);
225 if (delay > 0) {
226 System.out.println("Delaying execution of selftest by " + delay + " seconds");
227 Thread.sleep(Duration.ofSeconds(delay).toMillis());
228 }
229
230 int rc = runSelftest();
231
232 if (SystemPropertyUtil.getBooleanSystemProperty("selftest.wait.at-end", false)) {
233 System.gc();
234 System.out.print("Press any key to end test. ");
235 System.out.flush();
236 System.in.read();
237 System.out.println("RC=" + rc);
238 }
239 System.out.flush();
240
241 System.exit(rc);
242 }
243
244
245
246
247
248
249
250
251 public static int runSelftest() throws Exception {
252 return runSelftest(System.out);
253 }
254
255 private static void printStackTrace(Throwable t) {
256 t.printStackTrace();
257 }
258
259 public static int runSelftest(Writer out) throws Exception {
260 PipedInputStream pis = new PipedInputStream();
261 @SuppressWarnings("all")
262 PipedOutputStream pos = new PipedOutputStream(pis);
263 @SuppressWarnings("all")
264 PrintStream ps = new PrintStream(pos, false, Charset.defaultCharset().name());
265
266 InputStreamReader isr = new InputStreamReader(pis, Charset.defaultCharset());
267
268 Thread t = new Thread(new Runnable() {
269 @Override
270 public void run() {
271 char[] buf = new char[4096];
272 int read;
273 try {
274 while ((read = isr.read(buf)) >= 0) {
275 out.write(buf, 0, read);
276 out.flush();
277 }
278 } catch (IOException e) {
279 printStackTrace(e);
280 }
281 }
282 });
283 t.start();
284
285 return runSelftest0(ps, () -> {
286 ps.close();
287 try {
288 t.join();
289 } catch (InterruptedException e) {
290 printStackTrace(e);
291 }
292 });
293 }
294
295 public static int runSelftest(PrintStream out) throws Exception {
296 return runSelftest0(out, null);
297 }
298
299 private static int runSelftest0(PrintStream out, Runnable whenDone) throws Exception {
300 int rc;
301 PrintStream origSystemOut = System.out;
302 System.setOut(out);
303 try {
304 rc = runSelftest0(out);
305 } finally {
306 out.flush();
307 System.setOut(origSystemOut);
308 if (whenDone != null) {
309 whenDone.run();
310 }
311 }
312 return rc;
313 }
314
315 private static int runSelftest0(PrintStream out) throws Exception {
316 SelftestProvider sp = new SelftestProvider();
317 Selftest st = new Selftest(out, sp);
318
319 st.checkVM();
320 st.printExplanation();
321 st.dumpAdditionalProperties();
322 st.dumpSystemProperties();
323 st.dumpOSReleaseFiles();
324 st.dumpPid();
325 st.checkSupported();
326 st.checkCapabilities();
327
328 Set<String> disabledModules = sp.modulesDisabledByDefault();
329 List<String> messagesAtEnd = new ArrayList<>();
330
331 boolean skipModules = false;
332 String only = System.getProperty("selftest.only", "");
333 if (!only.isEmpty()) {
334 if ("mini".equals(only)) {
335 skipModules = true;
336 st.important.add("Selftest was modified, only a mini-selftest was run");
337 st.inconclusive = true;
338 AFUNIXSocket.main(new String[0]);
339 }
340 }
341
342 if (!skipModules) {
343 for (Entry<String, Class<?>[]> en : sp.tests().entrySet()) {
344 String module = en.getKey();
345 if (disabledModules.contains(module)) {
346 if (SystemPropertyUtil.getBooleanSystemProperty("selftest.enable-module." + module,
347 false)) {
348 out.println("Enabling optional module: " + module
349 + " (consult documentation for errors)");
350 st.modified = true;
351 } else {
352 messagesAtEnd.add("Skipping optional module: " + module
353 + "; enable by launching with -Dselftest.enable-module." + module
354 + "=true (consult documentation first)");
355 continue;
356 }
357 } else if (SystemPropertyUtil.getBooleanSystemProperty("selftest.disable-module." + module,
358 false)) {
359 messagesAtEnd.add("Skipping required module: " + module + "; this taints the test");
360 st.withIssues = true;
361 st.results.put(module, new ModuleResult(Result.SKIP, null, null));
362 continue;
363 }
364 try {
365 st.runTests(module, en.getValue());
366 } catch (Error | RuntimeException t) {
367 messagesAtEnd.add("INTERNAL INCONSISTENCY: Unexpected error while running tests for "
368 + module + ": " + t);
369 t.printStackTrace();
370 st.fail = true;
371 }
372 }
373 }
374
375 if (!messagesAtEnd.isEmpty()) {
376 for (String m : messagesAtEnd) {
377 out.println(m);
378 }
379 }
380
381 st.checkInitError();
382 st.dumpResults();
383
384 int rc = st.isFail() ? 1 : 0;
385
386 out.flush();
387 return rc;
388 }
389
390 private void dumpPid() {
391 String pid;
392 try {
393 pid = Long.toString(ProcessUtil.getPid());
394 } catch (Exception e) {
395 pid = "(unknown)";
396 }
397 out.println("Selftest process PID: " + pid);
398 String javaCmd = ProcessUtil.getJavaCommand();
399 String[] javaArgs = ProcessUtil.getJavaCommandArguments();
400 if (javaCmd != null && javaArgs != null) {
401 out.println("Selftest process command: " + javaCmd + " " + Arrays.toString(javaArgs));
402 }
403 out.println();
404 }
405
406 private void dumpAdditionalProperties() {
407 PrintWriter pw = new PrintWriter(new OutputStreamWriter(out, Charset.defaultCharset()));
408 sp.printAdditionalProperties(pw);
409 pw.flush();
410 out.println();
411 }
412
413 public void printExplanation() throws IOException {
414 out.println(
415 "This program determines whether junixsocket is supported on the current platform.");
416 out.println("The final line should say whether the selftest passed or failed.");
417 out.println();
418 out.println(
419 "If the selftest failed, please visit https://github.com/kohlschutter/junixsocket/issues");
420 out.println("and file a new bug report with the output below.");
421 out.println();
422 out.println("junixsocket selftest version " + AFUNIXSocket.getVersion());
423
424 Map<String, String> buildProperties = new LinkedHashMap<>(retrieveBuildProperties());
425 try (InputStream in = getClass().getResourceAsStream(
426 "/META-INF/maven/com.kohlschutter.junixsocket/junixsocket-selftest/git.properties")) {
427 if (in != null) {
428 Properties props = new Properties();
429 props.load(in);
430 for (String key : new TreeSet<>(props.stringPropertyNames())) {
431 buildProperties.put(key, props.getProperty(key));
432 }
433 }
434 }
435 out.println();
436 out.println("Build properties:");
437 for (Map.Entry<String, String> en : buildProperties.entrySet()) {
438 out.println(en.getKey() + ": " + en.getValue());
439 }
440 out.println();
441 }
442
443 public void dumpSystemProperties() {
444 Map<Object, Object> map = new TreeMap<>(System.getProperties());
445
446
447
448
449
450
451
452 for (String expectedKey : new String[] {
453 "android.icu.library.version",
454 "android.icu.unicode.version",
455 "android.icu.cldr.version",
456 "ICUDebug",
457 "android.icu.text.DecimalFormat.SkipExtendedSeparatorParsing",
458 "android.icu.text.MessagePattern.ApostropheMode",
459 "sun.io.useCanonCaches",
460 "sun.io.useCanonPrefixCache",
461 "sun.stdout.encoding",
462 "sun.stderr.encoding",
463 "http.keepAlive",
464 "http.keepAliveDuration",
465 "http.maxConnections",
466 "javax.net.debug",
467 "com.sun.security.preserveOldDCEncoding",
468 "java.util.logging.manager",
469
470 "file.encoding",
471 "file.separator",
472 "line.separator",
473 "path.separator",
474 "java.boot.class.path",
475 "java.class.path",
476 "java.class.version",
477 "java.compiler",
478 "java.ext.dirs",
479 "java.home",
480 "java.io.tmpdir",
481 "java.library.path",
482 "java.vendor",
483 "java.vendor.url",
484 "java.version",
485 "java.net.preferIPv6Addresses",
486 "java.specification.version",
487 "java.specification.vendor",
488 "java.specification.name",
489 "java.vm.version",
490 "java.vm.vendor",
491 "java.vm.vendor.url",
492 "java.vm.name",
493 "java.vm.specification.version",
494 "java.vm.specification.vendor",
495 "java.vm.specification.name",
496 "os.arch",
497 "os.name",
498 "os.version",
499 "user.dir",
500 "user.home",
501 "user.language",
502 "user.region",
503 "user.variant",
504 "user.name"
505 }) {
506 if (!map.containsKey(expectedKey)) {
507 String value = System.getProperty(expectedKey);
508 if (value != null) {
509 map.put(expectedKey, value);
510 }
511 }
512 }
513
514 out.println("System properties:");
515 out.println();
516 for (Map.Entry<Object, Object> en : map.entrySet()) {
517 String key = String.valueOf(en.getKey());
518 String value = String.valueOf(en.getValue());
519 StringBuilder sb = new StringBuilder();
520 for (int i = 0; i < value.length(); i++) {
521 char c = value.charAt(i);
522 switch (c) {
523 case '\n':
524 sb.append("\\n");
525 break;
526 case '\r':
527 sb.append("\\r");
528 break;
529 case '\t':
530 sb.append("\\r");
531 break;
532 default:
533 if (c < 32 || c >= 127) {
534 sb.append(String.format(Locale.ENGLISH, "\\u%04x", (int) c));
535 }
536 sb.append(c);
537 break;
538 }
539 }
540 out.println(key + ": " + sb.toString());
541 }
542 out.println();
543 }
544
545 public void checkSupported() {
546 out.print("AFSocket.isSupported: ");
547 out.flush();
548
549 boolean isSupported = AFSocket.isSupported();
550 out.println(isSupported);
551 out.println();
552 out.flush();
553
554 if (!isSupported) {
555 out.println("FAIL: junixsocket is not supported on this platform");
556 out.println();
557 fail = true;
558 }
559
560 out.print("AFUNIXSocket.isSupported: ");
561 out.flush();
562
563 isSupportedAFUNIX = AFUNIXSocket.isSupported();
564 out.println(isSupportedAFUNIX);
565 out.println();
566 out.flush();
567
568 if (!isSupportedAFUNIX) {
569 out.println("WARNING: AF_UNIX sockets are not supported on this platform");
570 out.println();
571 withIssues = true;
572 }
573 }
574
575 public void checkCapabilities() {
576 for (AFSocketCapability cap : AFSocketCapability.values()) {
577 boolean supported = AFSocket.supports(cap);
578 (supported ? supportedCapabilites : unsupportedCapabilites).add(cap);
579 }
580 }
581
582
583
584
585
586
587 public boolean isFail() {
588 return fail;
589 }
590
591 private void checkInitError() {
592 Throwable t = retrieveInitError();
593 if (t == null) {
594 return;
595 }
596
597 important.add("The native library failed to load.");
598
599 StringWriter sw = new StringWriter();
600 PrintWriter pw = new PrintWriter(sw);
601 t.printStackTrace(pw);
602 pw.flush();
603 String ts = sw.toString();
604 String tsLower = ts.toLowerCase(Locale.ENGLISH);
605
606 if (tsLower.contains("not permitted") || ts.contains("permission")) {
607 important.add("It looks like there were some permission errors.");
608 }
609
610 if (tsLower.contains("failed to map segment")) {
611 important.add("Your temporary directory is probably mounted with \"noexec\", "
612 + "which prevents the native library from loading.");
613 important.add("see: https://github.com/kohlschutter/junixsocket/issues/99");
614 Object tmpDir = retrieveTempDir();
615 if (tmpDir == null) {
616 tmpDir = System.getProperty("java.io.tmpdir");
617 }
618 if (tmpDir != null) {
619 important.add("Temp dir: " + tmpDir);
620 }
621 important.add(
622 "You can specify a different directory using -Dorg.newsclub.net.unix.library.tmpdir=");
623 }
624 }
625
626
627
628
629
630 public void dumpResults() {
631 if (modified) {
632 important.add("Selftest was modified, for example to exclude/include certain tests.");
633 inconclusive = true;
634 }
635 if (!isSupportedAFUNIX) {
636 important.add(
637 "Environment does not support UNIX sockets, which is an important part of junixsocket.");
638
639 }
640 if (inconclusive) {
641 important.add("Selftest results may be inconclusive.");
642 }
643
644 if (withIssues) {
645 important.add("\"With issues\": "
646 + "Please carefully check the output above; the software may not be able to do what you want.");
647 }
648
649 out.println();
650 out.println("Selftest results:");
651
652 for (Map.Entry<String, ModuleResult> en : results.entrySet()) {
653 ModuleResult res = en.getValue();
654
655 String result = res == null ? null : res.result.name();
656 String extra;
657 if (res == null || ((res.result == Result.SKIP || res.result == Result.AUTOSKIP)
658 && res.throwable == null)) {
659 result = "SKIP";
660 if (res != null && res.result == Result.AUTOSKIP) {
661 extra = "(skipped automatically)";
662 } else {
663 extra = "(skipped by user request)";
664 }
665 } else if (res.summary == null) {
666 extra = res.throwable == null ? "(unknown error)" : res.throwable.toString();
667 fail = true;
668 } else {
669 TestExecutionSummary summary = res.summary;
670
671 long nSucceeded = (summary.getTestsSucceededCount() + res.getNumAbortedNonIssues());
672 extra = nSucceeded + "/" + summary.getTestsFoundCount();
673 long nSkipped = summary.getTestsSkippedCount();
674 if (nSkipped > 0) {
675 extra += " (" + nSkipped + " skipped)";
676 }
677 long failures = summary.getTestsFailedCount();
678 if (failures > 0) {
679 extra += " failures: " + failures + "/" + summary.getTestsStartedCount();
680 }
681 }
682
683 out.println(result + "\t" + en.getKey() + "\t" + extra);
684 }
685 out.println();
686
687 if (!important.isEmpty()) {
688 for (String l : important) {
689 out.println("IMPORTANT: " + l);
690 }
691 out.println();
692 }
693
694 out.println("Supported capabilities: " + supportedCapabilites);
695 out.println("Unsupported capabilities: " + unsupportedCapabilites);
696 out.println();
697
698 if (fail) {
699 out.println("Selftest FAILED");
700 } else if (inconclusive || modified) {
701 out.println("Selftest INCONCLUSIVE");
702 } else if (withIssues) {
703 out.println("Selftest PASSED WITH ISSUES");
704 } else {
705 out.println("Selftest PASSED");
706 }
707 }
708
709 private SkipMode getSkipModeForModule(String moduleName) {
710 return SkipMode.parse(System.getProperty("selftest.skip." + moduleName));
711 }
712
713 private SkipMode getSkipModeForClass(String className) {
714 SkipMode skipMode = SkipMode.parse(System.getProperty("selftest.skip." + className));
715 if (skipMode.isDeclared()) {
716 return skipMode;
717 }
718 int i = className.lastIndexOf('.');
719 if (i < 0) {
720 return SkipMode.UNDECLARED;
721 }
722
723 className = className.substring(i + 1);
724 return SkipMode.parse(System.getProperty("selftest.skip." + className));
725 }
726
727
728
729
730
731
732
733 @SuppressWarnings({"PMD.ExcessiveMethodLength", "PMD.NcssCount", "PMD.NPathComplexity"})
734 public void runTests(String module, Class<?>[] testClasses) {
735 String prefix = "Testing \"" + module + "\"... ";
736 out.markPosition();
737 out.update(prefix);
738 out.flush();
739
740 String only = System.getProperty("selftest.only", "");
741 if (!only.isEmpty()) {
742 modified = true;
743 }
744
745 final ModuleResult moduleResult;
746
747 SkipMode skipMode;
748
749 if ((skipMode = getSkipModeForModule(module)).isSkip()) {
750 boolean autoSkip = skipMode == SkipMode.SKIP_AUTO;
751
752 out.println("Skipping module " + module + "; skipped " + (autoSkip ? "automatically"
753 : "by user request" + (skipMode.isForce() ? " (force)" : "")));
754 if (!skipMode.isForce()) {
755 withIssues = true;
756 modified = true;
757 }
758 moduleResult = new ModuleResult(autoSkip ? Result.AUTOSKIP : Result.SKIP, null, null);
759 } else {
760 List<Class<?>> list = new ArrayList<>(testClasses.length);
761 for (Class<?> testClass : testClasses) {
762 if (testClass == null) {
763
764 continue;
765 }
766 String className = testClass.getName();
767 String simpleName = testClass.getSimpleName();
768
769 if (!only.isEmpty() && !only.equals(className) && !only.equals(simpleName)) {
770 continue;
771 }
772
773 if ((skipMode = getSkipModeForClass(className)).isSkip()) {
774 out.println("Skipping test class " + className + "; skipped by request" + (skipMode
775 .isForce() ? " (force)" : ""));
776 if (!skipMode.isForce()) {
777 modified = true;
778 withIssues = true;
779 }
780 } else {
781 list.add(testClass);
782 }
783 }
784
785 TestExecutionSummary summary = null;
786 Exception exception = null;
787 long numAbortedNonIssues = 0;
788 try {
789 SelftestExecutor ex = new SelftestExecutor(list, prefix);
790 summary = ex.execute(out);
791
792 for (Map.Entry<TestIdentifier, TestExecutionResult> en : ex.getTestsWithWarnings()
793 .entrySet()) {
794 TestIdentifier tid = en.getKey();
795 TestExecutionResult res = en.getValue();
796 Optional<Throwable> t = res.getThrowable();
797 if (!t.isPresent()) {
798 continue;
799 }
800 Throwable throwable = t.get();
801 if (throwable instanceof TestAbortedWithImportantMessageException) {
802 String key = module + ": " + ex.getTestIdentifier(tid.getParentId().get())
803 .getDisplayName() + "." + tid.getDisplayName();
804 TestAbortedWithImportantMessageException ime =
805 (TestAbortedWithImportantMessageException) t.get();
806
807 MessageType messageType = ime.messageType();
808 if (messageType.isIncludeTestInfo()) {
809 important.add(ime.getSummaryMessage() + "; " + key);
810 } else {
811 String msg = ime.getSummaryMessage();
812 if (!msg.isEmpty()) {
813 important.add(msg);
814 }
815 }
816 if (!messageType.isWithIssues()) {
817 numAbortedNonIssues++;
818 }
819 } else if (throwable instanceof TestAbortedNotAnIssueException) {
820 numAbortedNonIssues++;
821 }
822 }
823 } catch (Exception e) {
824 e.printStackTrace(out);
825 exception = e;
826 }
827
828 if (exception != null || summary == null) {
829 moduleResult = new ModuleResult(Result.FAIL, null, exception);
830 fail = true;
831 } else {
832 final Result result;
833 if (summary.getTestsFailedCount() > 0) {
834 result = Result.FAIL;
835 fail = true;
836 } else if (summary.getTestsFoundCount() == 0) {
837 result = Result.NONE;
838 } else if ((summary.getTestsSucceededCount() + summary.getTestsSkippedCount()
839 + numAbortedNonIssues) == summary.getTestsFoundCount()) {
840 result = Result.PASS;
841 } else if (summary.getTestsAbortedCount() > 0) {
842 result = Result.DONE;
843 withIssues = true;
844 } else {
845 result = Result.DONE;
846 }
847
848 moduleResult = new ModuleResult(result, summary, null);
849 moduleResult.numAbortedNonIssues = numAbortedNonIssues;
850 }
851 }
852 results.put(module, moduleResult);
853 }
854
855 private void dumpContentsOfSystemConfigFile(File file) {
856 if (!file.exists()) {
857 return;
858 }
859 String p = file.getAbsolutePath();
860 out.println("BEGIN contents of file: " + p);
861
862 final int maxToRead = 4096;
863 char[] buf = new char[4096];
864 int numRead = 0;
865 try (InputStreamReader isr = new InputStreamReader(new FileInputStream(file),
866 StandardCharsets.UTF_8);) {
867
868 OutputStreamWriter outWriter = new OutputStreamWriter(out, Charset.defaultCharset());
869 int read = -1;
870 boolean lastWasNewline = false;
871 while (numRead < maxToRead && (read = isr.read(buf)) != -1) {
872 numRead += read;
873 outWriter.write(buf, 0, read);
874 outWriter.flush();
875 lastWasNewline = (read > 0 && buf[read - 1] == '\n');
876 }
877 if (!lastWasNewline) {
878 out.println();
879 }
880 if (read != -1) {
881 out.println("[...]");
882 }
883 } catch (Exception e) {
884 out.println("ERROR while reading contents of file: " + p + ": " + e);
885 }
886 out.println("=END= contents of file: " + p);
887 out.println();
888 }
889
890 public void dumpOSReleaseFiles() throws IOException {
891 Set<Path> canonicalPaths = new HashSet<>();
892 for (String f : new String[] {
893 "/etc/os-release", "/etc/lsb-release", "/etc/lsb_release", "/etc/system-release",
894 "/etc/system-release-cpe",
895
896 "/etc/debian_version", "/etc/fedora-release", "/etc/redhat-release", "/etc/centos-release",
897 "/etc/centos-release-upstream", "/etc/SuSE-release", "/etc/arch-release",
898 "/etc/gentoo-release", "/etc/ubuntu-release",}) {
899
900 File file = new File(f);
901 if (!file.exists() || file.isDirectory()) {
902 continue;
903 }
904 Path p = file.toPath().toAbsolutePath();
905 for (int i = 0; i < 2; i++) {
906 if (Files.isSymbolicLink(p)) {
907 Path p2 = Files.readSymbolicLink(p);
908 if (!p2.isAbsolute()) {
909 p = new File(p.toFile().getParentFile(), p2.toString()).toPath().toAbsolutePath();
910 }
911 }
912 }
913
914 if (!canonicalPaths.add(p)) {
915 continue;
916 }
917
918 dumpContentsOfSystemConfigFile(file);
919 }
920 }
921
922 private Throwable retrieveInitError() {
923 return callStaticMethod(diagnosticsHelperClass, "initError", null);
924 }
925
926 private File retrieveTempDir() {
927 return callStaticMethod(diagnosticsHelperClass, "tempDir", null);
928 }
929
930 private Map<String, String> retrieveBuildProperties() {
931 return callStaticMethod(diagnosticsHelperClass, "buildProperties", Collections::emptyMap);
932 }
933
934 private static Class<?> resolveOptionalClass(String name) {
935 try {
936 return Class.forName(name);
937 } catch (Exception e) {
938 return null;
939 }
940 }
941
942 @SuppressWarnings({"unchecked", "null"})
943 private static <T> T callStaticMethod(Class<?> clazz, String methodName,
944 Supplier<T> defaultSupplier) {
945 try {
946 return (T) clazz.getMethod(methodName).invoke(null);
947 } catch (Exception e) {
948 return defaultSupplier == null ? (T) null : defaultSupplier.get();
949 }
950 }
951
952 private static final class ModuleResult {
953 private final Result result;
954 private final TestExecutionSummary summary;
955 private final Throwable throwable;
956 private long numAbortedNonIssues = 0;
957
958 ModuleResult(Result result, TestExecutionSummary summary, Throwable t) {
959 Objects.requireNonNull(result);
960 this.result = result;
961 this.summary = summary;
962 this.throwable = t;
963 }
964
965 long getNumAbortedNonIssues() {
966 return numAbortedNonIssues;
967 }
968 }
969 }