1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.maven.plugin.surefire.report;
20
21 import java.io.ByteArrayOutputStream;
22 import java.io.File;
23 import java.io.FileInputStream;
24 import java.io.IOException;
25 import java.io.InputStreamReader;
26 import java.io.RandomAccessFile;
27 import java.nio.Buffer;
28 import java.nio.ByteBuffer;
29 import java.nio.file.Path;
30 import java.util.Deque;
31 import java.util.HashMap;
32 import java.util.concurrent.ConcurrentHashMap;
33 import java.util.concurrent.atomic.AtomicInteger;
34
35 import junit.framework.TestCase;
36 import org.apache.maven.plugin.surefire.booterclient.output.DeserializedStacktraceWriter;
37 import org.apache.maven.surefire.api.report.ReportEntry;
38 import org.apache.maven.surefire.api.report.SimpleReportEntry;
39 import org.apache.maven.surefire.api.report.StackTraceWriter;
40 import org.apache.maven.surefire.shared.utils.xml.Xpp3Dom;
41 import org.apache.maven.surefire.shared.utils.xml.Xpp3DomBuilder;
42
43 import static java.nio.charset.StandardCharsets.UTF_8;
44 import static java.nio.file.Files.readAllLines;
45 import static org.apache.maven.plugin.surefire.report.ReportEntryType.ERROR;
46 import static org.apache.maven.plugin.surefire.report.ReportEntryType.SKIPPED;
47 import static org.apache.maven.plugin.surefire.report.ReportEntryType.SUCCESS;
48 import static org.apache.maven.surefire.api.report.RunMode.NORMAL_RUN;
49 import static org.apache.maven.surefire.api.report.RunMode.RERUN_TEST_AFTER_FAILURE;
50 import static org.apache.maven.surefire.api.util.internal.ObjectUtils.systemProps;
51 import static org.apache.maven.surefire.api.util.internal.StringUtils.NL;
52 import static org.apache.maven.surefire.shared.utils.StringUtils.isEmpty;
53 import static org.assertj.core.api.Assertions.assertThat;
54 import static org.mockito.Mockito.doThrow;
55 import static org.mockito.Mockito.mock;
56 import static org.mockito.Mockito.times;
57 import static org.mockito.Mockito.verify;
58 import static org.mockito.Mockito.when;
59 import static org.powermock.reflect.Whitebox.getInternalState;
60 import static org.powermock.reflect.Whitebox.invokeMethod;
61 import static org.powermock.reflect.Whitebox.setInternalState;
62
63
64
65
66 @SuppressWarnings({"ResultOfMethodCallIgnored", "checkstyle:magicnumber"})
67 public class StatelessXmlReporterTest extends TestCase {
68 private static final String XSD =
69 "https://maven.apache.org/surefire/maven-surefire-plugin/xsd/surefire-test-report-3.0.xsd";
70 private static final String TEST_ONE = "aTestMethod";
71 private static final String TEST_TWO = "bTestMethod";
72 private static final String TEST_THREE = "cTestMethod";
73 private static final AtomicInteger FOLDER_POSTFIX = new AtomicInteger();
74
75 private TestSetStats stats;
76 private TestSetStats rerunStats;
77 private File expectedReportFile;
78 private File reportDir;
79
80 @Override
81 protected void setUp() throws Exception {
82 stats = new TestSetStats(false, true);
83 rerunStats = new TestSetStats(false, true);
84
85 File basedir = new File(".");
86 File target = new File(basedir.getCanonicalFile(), "target");
87 target.mkdir();
88 String reportRelDir = getClass().getSimpleName() + "-" + FOLDER_POSTFIX.incrementAndGet();
89 reportDir = new File(target, reportRelDir);
90 reportDir.mkdir();
91 }
92
93 @Override
94 protected void tearDown() {
95 if (expectedReportFile != null) {
96 expectedReportFile.delete();
97 }
98 }
99
100 public void testFileNameWithoutSuffix() {
101 StatelessXmlReporter reporter = new StatelessXmlReporter(
102 reportDir,
103 null,
104 false,
105 0,
106 new ConcurrentHashMap<String, Deque<WrappedReportEntry>>(),
107 XSD,
108 "3.0",
109 false,
110 false,
111 false,
112 false);
113 reporter.cleanTestHistoryMap();
114
115 ReportEntry reportEntry = new SimpleReportEntry(
116 NORMAL_RUN, 0L, getClass().getName(), null, getClass().getName(), null, 12);
117 WrappedReportEntry testSetReportEntry =
118 new WrappedReportEntry(reportEntry, ReportEntryType.SUCCESS, 12, null, null, systemProps());
119 stats.testSucceeded(testSetReportEntry);
120 reporter.testSetCompleted(testSetReportEntry, stats);
121
122 expectedReportFile = new File(reportDir, "TEST-" + getClass().getName() + ".xml");
123 assertTrue(
124 "Report file (" + expectedReportFile.getAbsolutePath() + ") doesn't exist",
125 expectedReportFile.exists());
126 }
127
128 public void testAllFieldsSerialized() throws IOException {
129 ReportEntry reportEntry =
130 new SimpleReportEntry(NORMAL_RUN, 0L, getClass().getName(), null, TEST_ONE, null, 12);
131 WrappedReportEntry testSetReportEntry =
132 new WrappedReportEntry(reportEntry, SUCCESS, 12, null, null, systemProps());
133 expectedReportFile = new File(reportDir, "TEST-" + getClass().getName() + ".xml");
134
135 stats.testSucceeded(testSetReportEntry);
136 StackTraceWriter stackTraceWriter = new DeserializedStacktraceWriter("A fud msg", "trimmed", "fail at foo");
137 Utf8RecodingDeferredFileOutputStream stdOut = new Utf8RecodingDeferredFileOutputStream("fds");
138 String stdOutPrefix;
139 String stdErrPrefix;
140 if (defaultCharsetSupportsSpecialChar()) {
141 stdErrPrefix = "std-\u0115rr";
142 stdOutPrefix = "st]]>d-o\u00DCt";
143 } else {
144 stdErrPrefix = "std-err";
145 stdOutPrefix = "st]]>d-out";
146 }
147
148 stdOut.write(stdOutPrefix + "<null>!\u0020\u0000\u001F", false);
149
150 Utf8RecodingDeferredFileOutputStream stdErr = new Utf8RecodingDeferredFileOutputStream("fds");
151
152 stdErr.write(stdErrPrefix + "?&-&£\u0020\u0000\u001F", false);
153 WrappedReportEntry t2 = new WrappedReportEntry(
154 new SimpleReportEntry(NORMAL_RUN, 0L, getClass().getName(), null, TEST_TWO, null, stackTraceWriter, 13),
155 ReportEntryType.ERROR,
156 13,
157 stdOut,
158 stdErr);
159
160 stats.testSucceeded(t2);
161 StatelessXmlReporter reporter = new StatelessXmlReporter(
162 reportDir,
163 null,
164 false,
165 0,
166 new ConcurrentHashMap<String, Deque<WrappedReportEntry>>(),
167 XSD,
168 "3.0",
169 false,
170 false,
171 false,
172 false);
173 reporter.testSetCompleted(testSetReportEntry, stats);
174
175 FileInputStream fileInputStream = new FileInputStream(expectedReportFile);
176
177 Xpp3Dom testSuite = Xpp3DomBuilder.build(new InputStreamReader(fileInputStream, UTF_8));
178 assertEquals("testsuite", testSuite.getName());
179 Xpp3Dom properties = testSuite.getChild("properties");
180 assertEquals(System.getProperties().size(), properties.getChildCount());
181 Xpp3Dom child = properties.getChild(1);
182 assertFalse(isEmpty(child.getAttribute("value")));
183 assertFalse(isEmpty(child.getAttribute("name")));
184
185 Xpp3Dom[] testcase = testSuite.getChildren("testcase");
186 Xpp3Dom tca = testcase[0];
187 assertEquals(TEST_ONE, tca.getAttribute("name"));
188 assertEquals("0.012", tca.getAttribute("time"));
189 assertEquals(getClass().getName(), tca.getAttribute("classname"));
190
191 Xpp3Dom tcb = testcase[1];
192 assertEquals(TEST_TWO, tcb.getAttribute("name"));
193 assertEquals("0.013", tcb.getAttribute("time"));
194 assertEquals(getClass().getName(), tcb.getAttribute("classname"));
195 Xpp3Dom errorNode = tcb.getChild("error");
196 assertNotNull(errorNode);
197 assertEquals("A fud msg", errorNode.getAttribute("message"));
198 assertEquals("fail at foo", errorNode.getAttribute("type"));
199 assertEquals(
200 stdOutPrefix + "<null>! &#0;&#31;",
201 tcb.getChild("system-out").getValue());
202
203 assertEquals(
204 stdErrPrefix + "?&-&£ &#0;&#31;",
205 tcb.getChild("system-err").getValue());
206 }
207
208 public void testOutputRerunFlakyFailure() throws IOException {
209 WrappedReportEntry testSetReportEntry = new WrappedReportEntry(
210 new SimpleReportEntry(NORMAL_RUN, 0L, getClass().getName(), null, TEST_ONE, null, 12),
211 ReportEntryType.SUCCESS,
212 12,
213 null,
214 null,
215 systemProps());
216 expectedReportFile = new File(reportDir, "TEST-" + getClass().getName() + ".xml");
217
218 stats.testSucceeded(testSetReportEntry);
219 StackTraceWriter stackTraceWriterOne = new DeserializedStacktraceWriter("A fud msg", "trimmed", "fail at foo");
220 StackTraceWriter stackTraceWriterTwo =
221 new DeserializedStacktraceWriter("A fud msg two", "trimmed two", "fail at foo two");
222
223 String firstRunOut = "first run out";
224 String firstRunErr = "first run err";
225 String secondRunOut = "second run out";
226 String secondRunErr = "second run err";
227
228 String cls = getClass().getName();
229 WrappedReportEntry testTwoFirstError = new WrappedReportEntry(
230 new SimpleReportEntry(NORMAL_RUN, 0L, cls, null, TEST_TWO, null, stackTraceWriterOne, 5),
231 ReportEntryType.ERROR,
232 5,
233 createStdOutput(firstRunOut),
234 createStdOutput(firstRunErr));
235
236 WrappedReportEntry testTwoSecondError = new WrappedReportEntry(
237 new SimpleReportEntry(RERUN_TEST_AFTER_FAILURE, 1L, cls, null, TEST_TWO, null, stackTraceWriterTwo, 13),
238 ReportEntryType.ERROR,
239 13,
240 createStdOutput(secondRunOut),
241 createStdOutput(secondRunErr));
242
243 WrappedReportEntry testThreeFirstRun = new WrappedReportEntry(
244 new SimpleReportEntry(NORMAL_RUN, 2L, cls, null, TEST_THREE, null, stackTraceWriterOne, 13),
245 ReportEntryType.FAILURE,
246 13,
247 createStdOutput(firstRunOut),
248 createStdOutput(firstRunErr));
249
250 WrappedReportEntry testThreeSecondRun = new WrappedReportEntry(
251 new SimpleReportEntry(
252 RERUN_TEST_AFTER_FAILURE, 3L, cls, null, TEST_THREE, null, stackTraceWriterTwo, 2),
253 ReportEntryType.SUCCESS,
254 2,
255 createStdOutput(secondRunOut),
256 createStdOutput(secondRunErr));
257
258 stats.testSucceeded(testTwoFirstError);
259 stats.testSucceeded(testThreeFirstRun);
260 rerunStats.testSucceeded(testTwoSecondError);
261 rerunStats.testSucceeded(testThreeSecondRun);
262
263 StatelessXmlReporter reporter = new StatelessXmlReporter(
264 reportDir,
265 null,
266 false,
267 1,
268 new HashMap<String, Deque<WrappedReportEntry>>(),
269 XSD,
270 "3.0",
271 false,
272 false,
273 false,
274 false);
275
276 reporter.testSetCompleted(testSetReportEntry, stats);
277 reporter.testSetCompleted(testSetReportEntry, rerunStats);
278
279 FileInputStream fileInputStream = new FileInputStream(expectedReportFile);
280
281 Xpp3Dom testSuite = Xpp3DomBuilder.build(new InputStreamReader(fileInputStream, UTF_8));
282 assertEquals("testsuite", testSuite.getName());
283 assertEquals("0.012", testSuite.getAttribute("time"));
284 Xpp3Dom properties = testSuite.getChild("properties");
285 assertEquals(System.getProperties().size(), properties.getChildCount());
286 Xpp3Dom child = properties.getChild(1);
287 assertFalse(isEmpty(child.getAttribute("value")));
288 assertFalse(isEmpty(child.getAttribute("name")));
289
290 Xpp3Dom[] testcase = testSuite.getChildren("testcase");
291 Xpp3Dom testCaseOne = testcase[0];
292 assertEquals(TEST_ONE, testCaseOne.getAttribute("name"));
293 assertEquals("0.012", testCaseOne.getAttribute("time"));
294 assertEquals(getClass().getName(), testCaseOne.getAttribute("classname"));
295
296 Xpp3Dom testCaseTwo = testcase[1];
297 assertEquals(TEST_TWO, testCaseTwo.getAttribute("name"));
298
299 assertEquals("0.005", testCaseTwo.getAttribute("time"));
300 assertEquals(getClass().getName(), testCaseTwo.getAttribute("classname"));
301 Xpp3Dom errorNode = testCaseTwo.getChild("error");
302 Xpp3Dom rerunErrorNode = testCaseTwo.getChild("rerunError");
303 assertNotNull(errorNode);
304 assertNotNull(rerunErrorNode);
305
306 assertEquals("A fud msg", errorNode.getAttribute("message"));
307 assertEquals("fail at foo", errorNode.getAttribute("type"));
308
309
310 assertEquals(firstRunOut, testCaseTwo.getChild("system-out").getValue());
311 assertEquals(firstRunErr, testCaseTwo.getChild("system-err").getValue());
312 assertEquals(secondRunOut, rerunErrorNode.getChild("system-out").getValue());
313 assertEquals(secondRunErr, rerunErrorNode.getChild("system-err").getValue());
314 assertEquals("A fud msg two", rerunErrorNode.getAttribute("message"));
315 assertEquals("fail at foo two", rerunErrorNode.getAttribute("type"));
316
317
318 Xpp3Dom testCaseThree = testcase[2];
319 assertEquals(TEST_THREE, testCaseThree.getAttribute("name"));
320
321 assertEquals("0.002", testCaseThree.getAttribute("time"));
322 assertEquals(getClass().getName(), testCaseThree.getAttribute("classname"));
323 Xpp3Dom flakyFailureNode = testCaseThree.getChild("flakyFailure");
324 assertNotNull(flakyFailureNode);
325 assertEquals(firstRunOut, flakyFailureNode.getChild("system-out").getValue());
326 assertEquals(firstRunErr, flakyFailureNode.getChild("system-err").getValue());
327
328 assertNull(testCaseThree.getChild("system-out"));
329 assertNull(testCaseThree.getChild("system-err"));
330 }
331
332 public void testOutputRerunFlakyAssumption() throws IOException {
333 expectedReportFile = new File(reportDir, "TEST-" + getClass().getName() + ".xml");
334
335 StackTraceWriter stackTraceWriterOne = new DeserializedStacktraceWriter("A fud msg", "trimmed", "fail at foo");
336
337 StackTraceWriter stackTraceWriterTwo =
338 new DeserializedStacktraceWriter("A fud msg two", "trimmed two", "fail at foo two");
339
340 String firstRunOut = "first run out";
341 String firstRunErr = "first run err";
342 String secondRunOut = "second run out";
343 String secondRunErr = "second run err";
344
345 WrappedReportEntry testTwoFirstError = new WrappedReportEntry(
346 new SimpleReportEntry(
347 NORMAL_RUN, 1L, getClass().getName(), null, TEST_TWO, null, stackTraceWriterOne, 5),
348 ERROR,
349 5,
350 createStdOutput(firstRunOut),
351 createStdOutput(firstRunErr));
352
353 stats.testSucceeded(testTwoFirstError);
354
355 WrappedReportEntry testTwoSecondError = new WrappedReportEntry(
356 new SimpleReportEntry(
357 RERUN_TEST_AFTER_FAILURE,
358 1L,
359 getClass().getName(),
360 null,
361 TEST_TWO,
362 null,
363 stackTraceWriterTwo,
364 13),
365 SKIPPED,
366 13,
367 createStdOutput(secondRunOut),
368 createStdOutput(secondRunErr));
369
370 rerunStats.testSucceeded(testTwoSecondError);
371
372 StatelessXmlReporter reporter = new StatelessXmlReporter(
373 reportDir, null, false, 1, new HashMap<>(), XSD, "3.0", false, false, false, false);
374
375 WrappedReportEntry testSetReportEntry = new WrappedReportEntry(
376 new SimpleReportEntry(
377 RERUN_TEST_AFTER_FAILURE, 1L, getClass().getName(), null, null, null, stackTraceWriterOne, 5),
378 ERROR,
379 20,
380 createStdOutput(firstRunOut),
381 createStdOutput(firstRunErr));
382
383 reporter.testSetCompleted(testSetReportEntry, stats);
384 reporter.testSetCompleted(testSetReportEntry, rerunStats);
385
386 FileInputStream fileInputStream = new FileInputStream(expectedReportFile);
387
388 Xpp3Dom testSuite = Xpp3DomBuilder.build(new InputStreamReader(fileInputStream, UTF_8));
389 assertEquals("testsuite", testSuite.getName());
390 assertEquals("0.02", testSuite.getAttribute("time"));
391
392 Xpp3Dom[] testcase = testSuite.getChildren("testcase");
393 assertEquals(1, testcase.length);
394 Xpp3Dom testCaseOne = testcase[0];
395 assertEquals(getClass().getName(), testCaseOne.getAttribute("classname"));
396 assertEquals(TEST_TWO, testCaseOne.getAttribute("name"));
397 assertEquals("0.005", testCaseOne.getAttribute("time"));
398
399 Xpp3Dom[] testCaseElements = testCaseOne.getChildren();
400 assertEquals(3, testCaseElements.length);
401 assertEquals("error", testCaseElements[0].getName());
402 assertEquals("system-out", testCaseElements[1].getName());
403 assertEquals("system-err", testCaseElements[2].getName());
404 long linesWithComments = readAllLines(expectedReportFile.toPath(), UTF_8).stream()
405 .filter(line -> line.contains("<!-- a skipped test execution in re-run phase -->"))
406 .count();
407 assertEquals(1, linesWithComments);
408 }
409
410 public void testNoWritesOnDeferredFile() throws Exception {
411 Utf8RecodingDeferredFileOutputStream out = new Utf8RecodingDeferredFileOutputStream("test");
412 out.free();
413 out.write("a", false);
414 assertThat((boolean) getInternalState(out, "isDirty")).isFalse();
415 }
416
417 public void testLengthOnDeferredFile() throws Exception {
418 Utf8RecodingDeferredFileOutputStream out = new Utf8RecodingDeferredFileOutputStream("test");
419
420 assertThat(out.getByteCount()).isZero();
421
422 File f = File.createTempFile("test", "tmp");
423 RandomAccessFile storage = new RandomAccessFile(f, "rw");
424 setInternalState(out, "storage", storage);
425 setInternalState(out, "file", f.toPath());
426 storage.writeByte(0);
427 storage.getFD().sync();
428 assertThat(out.getByteCount()).isEqualTo(1);
429
430 storage.close();
431 assertThat(f.delete()).isTrue();
432 assertThat(out.getByteCount()).isZero();
433 out.free();
434 }
435
436 @SuppressWarnings("checkstyle:magicnumber")
437 public void testWritesOnDeferredFile() throws Exception {
438 Utf8RecodingDeferredFileOutputStream out = new Utf8RecodingDeferredFileOutputStream("test");
439 for (int i = 0; i < 33_000; i++) {
440 out.write("A", false);
441 out.write("B", true);
442 }
443 out.write(null, false);
444 out.write(null, true);
445
446 assertThat(out.getByteCount()).isEqualTo(33_000 * (1 + 1 + NL.length()) + 4 + 4 + NL.length());
447
448 StringBuilder expectedContent = new StringBuilder(150_000);
449 for (int i = 0; i < 33_000; i++) {
450 expectedContent.append('A').append('B').append(NL);
451 }
452 expectedContent.append("null").append("null").append(NL);
453 ByteArrayOutputStream read = new ByteArrayOutputStream(150_000);
454 out.writeTo(read);
455 assertThat(read.toString()).isEqualTo(expectedContent.toString());
456
457 out.free();
458 }
459
460 public void testFreeOnDeferredFile() throws Exception {
461 Utf8RecodingDeferredFileOutputStream out = new Utf8RecodingDeferredFileOutputStream("test");
462 setInternalState(out, "cache", ByteBuffer.allocate(0));
463 Path path = mock(Path.class);
464 File file = mock(File.class);
465 when(path.toFile()).thenReturn(file);
466 setInternalState(out, "file", path);
467 RandomAccessFile storage = mock(RandomAccessFile.class);
468 doThrow(IOException.class).when(storage).close();
469 setInternalState(out, "storage", storage);
470 out.free();
471 assertThat((boolean) getInternalState(out, "closed")).isTrue();
472 verify(file, times(1)).deleteOnExit();
473 }
474
475 public void testCacheOnDeferredFile() throws Exception {
476 Utf8RecodingDeferredFileOutputStream out = new Utf8RecodingDeferredFileOutputStream("test");
477 byte[] b1 = invokeMethod(out, "getLargeCache", 1);
478 byte[] b2 = invokeMethod(out, "getLargeCache", 1);
479 assertThat(b1).isSameAs(b2);
480 assertThat(b1).hasSize(1);
481
482 byte[] b3 = invokeMethod(out, "getLargeCache", 2);
483 assertThat(b3).isNotSameAs(b1);
484 assertThat(b3).hasSize(2);
485
486 byte[] b4 = invokeMethod(out, "getLargeCache", 1);
487 assertThat(b4).isSameAs(b3);
488 assertThat(b3).hasSize(2);
489 }
490
491 public void testSyncOnDeferredFile() throws Exception {
492 Utf8RecodingDeferredFileOutputStream out = new Utf8RecodingDeferredFileOutputStream("test");
493 Buffer cache = ByteBuffer.wrap(new byte[] {1, 2, 3});
494 cache.position(3);
495 setInternalState(out, "cache", cache);
496 assertThat((boolean) getInternalState(out, "isDirty")).isFalse();
497 setInternalState(out, "isDirty", true);
498 File file = new File(reportDir, "test");
499 setInternalState(out, "file", file.toPath());
500 RandomAccessFile storage = new RandomAccessFile(file, "rw");
501 setInternalState(out, "storage", storage);
502 invokeMethod(out, "sync");
503 assertThat((boolean) getInternalState(out, "isDirty")).isFalse();
504 storage.seek(0L);
505 assertThat(storage.read()).isEqualTo(1);
506 assertThat(storage.read()).isEqualTo(2);
507 assertThat(storage.read()).isEqualTo(3);
508 assertThat(storage.read()).isEqualTo(-1);
509 assertThat(storage.length()).isEqualTo(3L);
510 assertThat(cache.position()).isEqualTo(0);
511 assertThat(cache.limit()).isEqualTo(3);
512 storage.seek(3L);
513 invokeMethod(out, "sync");
514 assertThat((boolean) getInternalState(out, "isDirty")).isFalse();
515 assertThat(storage.length()).isEqualTo(3L);
516 assertThat(out.getByteCount()).isEqualTo(3L);
517 assertThat((boolean) getInternalState(out, "closed")).isFalse();
518 out.free();
519 assertThat((boolean) getInternalState(out, "closed")).isTrue();
520
521 out.free();
522 assertThat((boolean) getInternalState(out, "closed")).isTrue();
523 }
524
525 private boolean defaultCharsetSupportsSpecialChar() {
526
527 return "\u0115\u00DC".equals(new String("\u0115\u00DC".getBytes()));
528 }
529
530 private Utf8RecodingDeferredFileOutputStream createStdOutput(String content) throws IOException {
531 Utf8RecodingDeferredFileOutputStream stdOut = new Utf8RecodingDeferredFileOutputStream("fds2");
532 stdOut.write(content, false);
533 return stdOut;
534 }
535 }