View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
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.lang.reflect.Field;
28  import java.lang.reflect.Method;
29  import java.nio.Buffer;
30  import java.nio.ByteBuffer;
31  import java.nio.file.Path;
32  import java.util.Collections;
33  import java.util.Deque;
34  import java.util.HashMap;
35  import java.util.concurrent.ConcurrentHashMap;
36  import java.util.concurrent.atomic.AtomicInteger;
37  
38  import org.apache.maven.plugin.surefire.booterclient.output.DeserializedStacktraceWriter;
39  import org.apache.maven.surefire.api.report.ReportEntry;
40  import org.apache.maven.surefire.api.report.SimpleReportEntry;
41  import org.apache.maven.surefire.api.report.StackTraceWriter;
42  import org.apache.maven.surefire.shared.utils.xml.Xpp3Dom;
43  import org.apache.maven.surefire.shared.utils.xml.Xpp3DomBuilder;
44  import org.junit.jupiter.api.AfterEach;
45  import org.junit.jupiter.api.BeforeEach;
46  import org.junit.jupiter.api.Test;
47  
48  import static java.nio.charset.StandardCharsets.UTF_8;
49  import static java.nio.file.Files.readAllLines;
50  import static org.apache.maven.plugin.surefire.report.ReportEntryType.ERROR;
51  import static org.apache.maven.plugin.surefire.report.ReportEntryType.SKIPPED;
52  import static org.apache.maven.plugin.surefire.report.ReportEntryType.SUCCESS;
53  import static org.apache.maven.surefire.api.report.RunMode.NORMAL_RUN;
54  import static org.apache.maven.surefire.api.report.RunMode.RERUN_TEST_AFTER_FAILURE;
55  import static org.apache.maven.surefire.api.util.internal.ObjectUtils.systemProps;
56  import static org.apache.maven.surefire.api.util.internal.StringUtils.NL;
57  import static org.apache.maven.surefire.shared.utils.StringUtils.isEmpty;
58  import static org.assertj.core.api.Assertions.assertThat;
59  import static org.junit.jupiter.api.Assertions.assertEquals;
60  import static org.junit.jupiter.api.Assertions.assertFalse;
61  import static org.junit.jupiter.api.Assertions.assertNotNull;
62  import static org.junit.jupiter.api.Assertions.assertNull;
63  import static org.junit.jupiter.api.Assertions.assertTrue;
64  import static org.mockito.Mockito.doThrow;
65  import static org.mockito.Mockito.mock;
66  import static org.mockito.Mockito.times;
67  import static org.mockito.Mockito.verify;
68  import static org.mockito.Mockito.when;
69  
70  /**
71   *
72   */
73  @SuppressWarnings({"ResultOfMethodCallIgnored", "checkstyle:magicnumber"})
74  public class StatelessXmlReporterTest {
75      private static final String XSD =
76              "https://maven.apache.org/surefire/maven-surefire-plugin/xsd/surefire-test-report.xsd";
77      private static final String TEST_ONE = "aTestMethod";
78      private static final String TEST_TWO = "bTestMethod";
79      private static final String TEST_THREE = "cTestMethod";
80      private static final AtomicInteger DIRECTORY_PREFIX = new AtomicInteger();
81  
82      private TestSetStats stats;
83      private TestSetStats rerunStats;
84      private File expectedReportFile;
85      private File reportDir;
86  
87      @SuppressWarnings("unchecked")
88      private static <T> T getInternalState(Object target, String fieldName) {
89          try {
90              Class<?> clazz = target.getClass();
91              while (clazz != null) {
92                  try {
93                      Field field = clazz.getDeclaredField(fieldName);
94                      field.setAccessible(true);
95                      return (T) field.get(target);
96                  } catch (NoSuchFieldException e) {
97                      clazz = clazz.getSuperclass();
98                  }
99              }
100             throw new NoSuchFieldException(fieldName);
101         } catch (Exception e) {
102             throw new RuntimeException(e);
103         }
104     }
105 
106     private static void setInternalState(Object target, String fieldName, Object value) {
107         try {
108             Class<?> clazz = target.getClass();
109             while (clazz != null) {
110                 try {
111                     Field field = clazz.getDeclaredField(fieldName);
112                     field.setAccessible(true);
113                     field.set(target, value);
114                     return;
115                 } catch (NoSuchFieldException e) {
116                     clazz = clazz.getSuperclass();
117                 }
118             }
119             throw new NoSuchFieldException(fieldName);
120         } catch (NoSuchFieldException | IllegalAccessException e) {
121             throw new RuntimeException(e);
122         }
123     }
124 
125     @SuppressWarnings("unchecked")
126     private static <T> T invokeMethod(Object target, String methodName, Object... args) throws Exception {
127         Class<?> clazz = target.getClass();
128         while (clazz != null) {
129             for (Method method : clazz.getDeclaredMethods()) {
130                 if (method.getName().equals(methodName) && method.getParameterCount() == args.length) {
131                     method.setAccessible(true);
132                     return (T) method.invoke(target, args);
133                 }
134             }
135             clazz = clazz.getSuperclass();
136         }
137         throw new NoSuchMethodException(methodName);
138     }
139 
140     @BeforeEach
141     protected void setUp() throws Exception {
142         stats = new TestSetStats(false, true);
143         rerunStats = new TestSetStats(false, true);
144 
145         File basedir = new File(".");
146         File target = new File(basedir.getCanonicalFile(), "target");
147         target.mkdir();
148         String reportRelDir = getClass().getSimpleName() + "-" + DIRECTORY_PREFIX.incrementAndGet();
149         reportDir = new File(target, reportRelDir);
150         reportDir.mkdir();
151     }
152 
153     @AfterEach
154     protected void tearDown() {
155         if (expectedReportFile != null) {
156             expectedReportFile.delete();
157         }
158     }
159 
160     @Test
161     public void testFileNameWithoutSuffix() {
162         StatelessXmlReporter reporter = new StatelessXmlReporter(
163                 reportDir,
164                 null,
165                 false,
166                 0,
167                 new ConcurrentHashMap<String, Deque<WrappedReportEntry>>(),
168                 XSD,
169                 "3.0.2",
170                 false,
171                 false,
172                 false,
173                 false,
174                 true,
175                 true,
176                 false);
177         reporter.cleanTestHistoryMap();
178 
179         ReportEntry reportEntry = new SimpleReportEntry(
180                 NORMAL_RUN, 0L, getClass().getName(), null, getClass().getName(), null, 12);
181         WrappedReportEntry testSetReportEntry = new WrappedReportEntry(
182                 reportEntry, ReportEntryType.SUCCESS, 1771085631L, 12, null, null, systemProps());
183         stats.testSucceeded(testSetReportEntry);
184         reporter.testSetCompleted(testSetReportEntry, stats);
185 
186         expectedReportFile = new File(reportDir, "TEST-" + getClass().getName() + ".xml");
187         assertTrue(
188                 expectedReportFile.exists(),
189                 "Report file (" + expectedReportFile.getAbsolutePath() + ") doesn't exist");
190     }
191 
192     @Test
193     public void testAllFieldsSerialized() throws IOException {
194         ReportEntry reportEntry =
195                 new SimpleReportEntry(NORMAL_RUN, 0L, getClass().getName(), null, TEST_ONE, null, 12);
196         WrappedReportEntry testSetReportEntry =
197                 new WrappedReportEntry(reportEntry, SUCCESS, 1771085631L, 12, null, null, systemProps());
198         expectedReportFile = new File(reportDir, "TEST-" + getClass().getName() + ".xml");
199 
200         stats.testSucceeded(testSetReportEntry);
201         StackTraceWriter stackTraceWriter = new DeserializedStacktraceWriter("A fud msg", "trimmed", "fail at foo");
202         Utf8RecodingDeferredFileOutputStream stdOut = new Utf8RecodingDeferredFileOutputStream("fds");
203         String stdOutPrefix;
204         String stdErrPrefix;
205         if (defaultCharsetSupportsSpecialChar()) {
206             stdErrPrefix = "std-\u0115rr";
207             stdOutPrefix = "st]]>d-o\u00DCt";
208         } else {
209             stdErrPrefix = "std-err";
210             stdOutPrefix = "st]]>d-out";
211         }
212 
213         stdOut.write(stdOutPrefix + "<null>!\u0020\u0000\u001F", false, null);
214 
215         Utf8RecodingDeferredFileOutputStream stdErr = new Utf8RecodingDeferredFileOutputStream("fds");
216 
217         stdErr.write(stdErrPrefix + "?&-&amp;&#163;\u0020\u0000\u001F", false, null);
218         WrappedReportEntry t2 = new WrappedReportEntry(
219                 new SimpleReportEntry(NORMAL_RUN, 0L, getClass().getName(), null, TEST_TWO, null, stackTraceWriter, 13),
220                 ReportEntryType.ERROR,
221                 1771085631L,
222                 13,
223                 stdOut,
224                 stdErr);
225 
226         stats.testSucceeded(t2);
227         StatelessXmlReporter reporter = new StatelessXmlReporter(
228                 reportDir,
229                 null,
230                 false,
231                 0,
232                 new ConcurrentHashMap<String, Deque<WrappedReportEntry>>(),
233                 XSD,
234                 "3.0.2",
235                 false,
236                 false,
237                 false,
238                 false,
239                 true,
240                 true,
241                 false);
242         reporter.testSetCompleted(testSetReportEntry, stats);
243 
244         Xpp3Dom testSuite;
245         try (FileInputStream fileInputStream = new FileInputStream(expectedReportFile);
246                 InputStreamReader reader = new InputStreamReader(fileInputStream, UTF_8)) {
247             testSuite = Xpp3DomBuilder.build(reader);
248         }
249         assertEquals("testsuite", testSuite.getName());
250         Xpp3Dom properties = testSuite.getChild("properties");
251         assertEquals(System.getProperties().size(), properties.getChildCount());
252         Xpp3Dom child = properties.getChild(1);
253         assertFalse(isEmpty(child.getAttribute("value")));
254         assertFalse(isEmpty(child.getAttribute("name")));
255 
256         Xpp3Dom[] testcase = testSuite.getChildren("testcase");
257         Xpp3Dom tca = testcase[0];
258         assertEquals(TEST_ONE, tca.getAttribute("name"));
259         assertEquals("0.012", tca.getAttribute("time"));
260         assertEquals(getClass().getName(), tca.getAttribute("classname"));
261 
262         Xpp3Dom tcb = testcase[1];
263         assertEquals(TEST_TWO, tcb.getAttribute("name"));
264         assertEquals("0.013", tcb.getAttribute("time"));
265         assertEquals(getClass().getName(), tcb.getAttribute("classname"));
266         Xpp3Dom errorNode = tcb.getChild("error");
267         assertNotNull(errorNode);
268         assertEquals("A fud msg", errorNode.getAttribute("message"));
269         assertEquals("fail at foo", errorNode.getAttribute("type"));
270         assertEquals(
271                 stdOutPrefix + "<null>! &amp#0;&amp#31;",
272                 tcb.getChild("system-out").getValue());
273 
274         assertEquals(
275                 stdErrPrefix + "?&-&amp;&#163; &amp#0;&amp#31;",
276                 tcb.getChild("system-err").getValue());
277     }
278 
279     @Test
280     public void testOutputRerunFlakyFailure() throws IOException {
281         WrappedReportEntry testSetReportEntry = new WrappedReportEntry(
282                 new SimpleReportEntry(NORMAL_RUN, 0L, getClass().getName(), null, TEST_ONE, null, 12),
283                 ReportEntryType.SUCCESS,
284                 1771085631L,
285                 12,
286                 null,
287                 null,
288                 systemProps());
289         expectedReportFile = new File(reportDir, "TEST-" + getClass().getName() + ".xml");
290 
291         stats.testSucceeded(testSetReportEntry);
292         StackTraceWriter stackTraceWriterOne = new DeserializedStacktraceWriter("A fud msg", "trimmed", "fail at foo");
293         StackTraceWriter stackTraceWriterTwo =
294                 new DeserializedStacktraceWriter("A fud msg two", "trimmed two", "fail at foo two");
295 
296         String firstRunOut = "first run out";
297         String firstRunErr = "first run err";
298         String secondRunOut = "second run out";
299         String secondRunErr = "second run err";
300 
301         String cls = getClass().getName();
302         WrappedReportEntry testTwoFirstError = new WrappedReportEntry(
303                 new SimpleReportEntry(NORMAL_RUN, 0L, cls, null, TEST_TWO, null, stackTraceWriterOne, 5),
304                 ReportEntryType.ERROR,
305                 1771085631L,
306                 5,
307                 createStdOutput(firstRunOut),
308                 createStdOutput(firstRunErr));
309 
310         WrappedReportEntry testTwoSecondError = new WrappedReportEntry(
311                 new SimpleReportEntry(RERUN_TEST_AFTER_FAILURE, 1L, cls, null, TEST_TWO, null, stackTraceWriterTwo, 13),
312                 ReportEntryType.ERROR,
313                 1771085631L,
314                 13,
315                 createStdOutput(secondRunOut),
316                 createStdOutput(secondRunErr));
317 
318         WrappedReportEntry testThreeFirstRun = new WrappedReportEntry(
319                 new SimpleReportEntry(NORMAL_RUN, 2L, cls, null, TEST_THREE, null, stackTraceWriterOne, 13),
320                 ReportEntryType.FAILURE,
321                 1771085631L,
322                 13,
323                 createStdOutput(firstRunOut),
324                 createStdOutput(firstRunErr));
325 
326         WrappedReportEntry testThreeSecondRun = new WrappedReportEntry(
327                 new SimpleReportEntry(
328                         RERUN_TEST_AFTER_FAILURE, 3L, cls, null, TEST_THREE, null, stackTraceWriterTwo, 2),
329                 ReportEntryType.SUCCESS,
330                 1771085631L,
331                 2,
332                 createStdOutput(secondRunOut),
333                 createStdOutput(secondRunErr));
334 
335         stats.testSucceeded(testTwoFirstError);
336         stats.testSucceeded(testThreeFirstRun);
337         rerunStats.testSucceeded(testTwoSecondError);
338         rerunStats.testSucceeded(testThreeSecondRun);
339 
340         StatelessXmlReporter reporter = new StatelessXmlReporter(
341                 reportDir,
342                 null,
343                 false,
344                 1,
345                 new HashMap<String, Deque<WrappedReportEntry>>(),
346                 XSD,
347                 "3.0.2",
348                 false,
349                 false,
350                 false,
351                 false,
352                 true,
353                 true,
354                 false);
355 
356         reporter.testSetCompleted(testSetReportEntry, stats);
357         reporter.testSetCompleted(testSetReportEntry, rerunStats);
358 
359         Xpp3Dom testSuite;
360         try (FileInputStream fileInputStream = new FileInputStream(expectedReportFile);
361                 InputStreamReader reader = new InputStreamReader(fileInputStream, UTF_8)) {
362             testSuite = Xpp3DomBuilder.build(reader);
363         }
364         assertEquals("testsuite", testSuite.getName());
365         assertEquals("0.012", testSuite.getAttribute("time"));
366         Xpp3Dom properties = testSuite.getChild("properties");
367         assertEquals(System.getProperties().size(), properties.getChildCount());
368         Xpp3Dom child = properties.getChild(1);
369         assertFalse(isEmpty(child.getAttribute("value")));
370         assertFalse(isEmpty(child.getAttribute("name")));
371 
372         Xpp3Dom[] testcase = testSuite.getChildren("testcase");
373         Xpp3Dom testCaseOne = testcase[0];
374         assertEquals(TEST_ONE, testCaseOne.getAttribute("name"));
375         assertEquals("0.012", testCaseOne.getAttribute("time"));
376         assertEquals(getClass().getName(), testCaseOne.getAttribute("classname"));
377 
378         Xpp3Dom testCaseTwo = testcase[1];
379         assertEquals(TEST_TWO, testCaseTwo.getAttribute("name"));
380         // Run time for a rerun failing test is the run time of the first run
381         assertEquals("0.005", testCaseTwo.getAttribute("time"));
382         assertEquals(getClass().getName(), testCaseTwo.getAttribute("classname"));
383         Xpp3Dom errorNode = testCaseTwo.getChild("error");
384         Xpp3Dom rerunErrorNode = testCaseTwo.getChild("rerunError");
385         assertNotNull(errorNode);
386         assertNotNull(rerunErrorNode);
387 
388         assertEquals("A fud msg", errorNode.getAttribute("message"));
389         assertEquals("fail at foo", errorNode.getAttribute("type"));
390 
391         // Check rerun error node contains all the information
392         assertEquals(firstRunOut, testCaseTwo.getChild("system-out").getValue());
393         assertEquals(firstRunErr, testCaseTwo.getChild("system-err").getValue());
394         assertEquals(secondRunOut, rerunErrorNode.getChild("system-out").getValue());
395         assertEquals(secondRunErr, rerunErrorNode.getChild("system-err").getValue());
396         assertEquals("A fud msg two", rerunErrorNode.getAttribute("message"));
397         assertEquals("fail at foo two", rerunErrorNode.getAttribute("type"));
398 
399         // Check flaky failure node
400         Xpp3Dom testCaseThree = testcase[2];
401         assertEquals(TEST_THREE, testCaseThree.getAttribute("name"));
402         // Run time for a flaky test is the run time of the first successful run
403         assertEquals("0.002", testCaseThree.getAttribute("time"));
404         assertEquals(getClass().getName(), testCaseThree.getAttribute("classname"));
405         Xpp3Dom flakyFailureNode = testCaseThree.getChild("flakyFailure");
406         assertNotNull(flakyFailureNode);
407         assertEquals(firstRunOut, flakyFailureNode.getChild("system-out").getValue());
408         assertEquals(firstRunErr, flakyFailureNode.getChild("system-err").getValue());
409         // system-out and system-err should not be present for flaky failures
410         assertNull(testCaseThree.getChild("system-out"));
411         assertNull(testCaseThree.getChild("system-err"));
412     }
413 
414     @Test
415     public void testOutputRerunFlakyAssumption() throws IOException {
416         expectedReportFile = new File(reportDir, "TEST-" + getClass().getName() + ".xml");
417 
418         StackTraceWriter stackTraceWriterOne = new DeserializedStacktraceWriter("A fud msg", "trimmed", "fail at foo");
419 
420         StackTraceWriter stackTraceWriterTwo =
421                 new DeserializedStacktraceWriter("A fud msg two", "trimmed two", "fail at foo two");
422 
423         String firstRunOut = "first run out";
424         String firstRunErr = "first run err";
425         String secondRunOut = "second run out";
426         String secondRunErr = "second run err";
427 
428         WrappedReportEntry testTwoFirstError = new WrappedReportEntry(
429                 new SimpleReportEntry(
430                         NORMAL_RUN, 1L, getClass().getName(), null, TEST_TWO, null, stackTraceWriterOne, 5),
431                 ERROR,
432                 1771085631L,
433                 5,
434                 createStdOutput(firstRunOut),
435                 createStdOutput(firstRunErr));
436 
437         stats.testSucceeded(testTwoFirstError);
438 
439         WrappedReportEntry testTwoSecondError = new WrappedReportEntry(
440                 new SimpleReportEntry(
441                         RERUN_TEST_AFTER_FAILURE,
442                         1L,
443                         getClass().getName(),
444                         null,
445                         TEST_TWO,
446                         null,
447                         stackTraceWriterTwo,
448                         13),
449                 SKIPPED,
450                 1771085631L,
451                 13,
452                 createStdOutput(secondRunOut),
453                 createStdOutput(secondRunErr));
454 
455         rerunStats.testSucceeded(testTwoSecondError);
456 
457         StatelessXmlReporter reporter = new StatelessXmlReporter(
458                 reportDir,
459                 null,
460                 false,
461                 1,
462                 new HashMap<>(),
463                 XSD,
464                 "3.0.2",
465                 false,
466                 false,
467                 false,
468                 false,
469                 true,
470                 true,
471                 false);
472 
473         WrappedReportEntry testSetReportEntry = new WrappedReportEntry(
474                 new SimpleReportEntry(
475                         RERUN_TEST_AFTER_FAILURE, 1L, getClass().getName(), null, null, null, stackTraceWriterOne, 5),
476                 ERROR,
477                 1771085631L,
478                 20,
479                 createStdOutput(firstRunOut),
480                 createStdOutput(firstRunErr));
481 
482         reporter.testSetCompleted(testSetReportEntry, stats);
483         reporter.testSetCompleted(testSetReportEntry, rerunStats);
484 
485         Xpp3Dom testSuite;
486         try (FileInputStream fileInputStream = new FileInputStream(expectedReportFile);
487                 InputStreamReader reader = new InputStreamReader(fileInputStream, UTF_8)) {
488             testSuite = Xpp3DomBuilder.build(reader);
489         }
490         assertEquals("testsuite", testSuite.getName());
491         assertEquals("0.02", testSuite.getAttribute("time"));
492 
493         Xpp3Dom[] testcase = testSuite.getChildren("testcase");
494         assertEquals(1, testcase.length);
495         Xpp3Dom testCaseOne = testcase[0];
496         assertEquals(getClass().getName(), testCaseOne.getAttribute("classname"));
497         assertEquals(TEST_TWO, testCaseOne.getAttribute("name"));
498         assertEquals("0.005", testCaseOne.getAttribute("time"));
499 
500         Xpp3Dom[] testCaseElements = testCaseOne.getChildren();
501         assertEquals(3, testCaseElements.length);
502         assertEquals("error", testCaseElements[0].getName());
503         assertEquals("system-out", testCaseElements[1].getName());
504         assertEquals("system-err", testCaseElements[2].getName());
505         long linesWithComments = readAllLines(expectedReportFile.toPath(), UTF_8).stream()
506                 .filter(line -> line.contains("<!-- a skipped test execution in re-run phase -->"))
507                 .count();
508         assertEquals(1, linesWithComments);
509     }
510 
511     @Test
512     public void testNoWritesOnDeferredFile() throws Exception {
513         Utf8RecodingDeferredFileOutputStream out = new Utf8RecodingDeferredFileOutputStream("test");
514         out.free();
515         out.write("a", false, null);
516         assertThat((boolean) getInternalState(out, "isDirty")).isFalse();
517     }
518 
519     @Test
520     public void testLengthOnDeferredFile() throws Exception {
521         Utf8RecodingDeferredFileOutputStream out = new Utf8RecodingDeferredFileOutputStream("test");
522 
523         assertThat(out.getByteCount()).isZero();
524 
525         File f = File.createTempFile("test", "tmp");
526         RandomAccessFile storage = new RandomAccessFile(f, "rw");
527         setInternalState(out, "storage", storage);
528         setInternalState(out, "file", f.toPath());
529         storage.writeByte(0);
530         storage.getFD().sync();
531         assertThat(out.getByteCount()).isEqualTo(1);
532 
533         storage.close();
534         assertThat(f.delete()).isTrue();
535         assertThat(out.getByteCount()).isZero();
536         out.free();
537     }
538 
539     @Test
540     @SuppressWarnings("checkstyle:magicnumber")
541     public void testWritesOnDeferredFile() throws Exception {
542         Utf8RecodingDeferredFileOutputStream out = new Utf8RecodingDeferredFileOutputStream("test");
543         for (int i = 0; i < 33_000; i++) {
544             out.write("A", false, null);
545             out.write("B", true, null);
546         }
547         out.write(null, false, null);
548         out.write(null, true, null);
549 
550         assertThat(out.getByteCount()).isEqualTo(33_000 * (1 + 1 + NL.length()) + 4 + 4 + NL.length());
551 
552         StringBuilder expectedContent = new StringBuilder(150_000);
553         for (int i = 0; i < 33_000; i++) {
554             expectedContent.append('A').append('B').append(NL);
555         }
556         expectedContent.append("null").append("null").append(NL);
557         ByteArrayOutputStream read = new ByteArrayOutputStream(150_000);
558         out.writeTo(read);
559         assertThat(read.toString()).isEqualTo(expectedContent.toString());
560 
561         out.free();
562     }
563 
564     @Test
565     public void testFreeOnDeferredFile() throws Exception {
566         Utf8RecodingDeferredFileOutputStream out = new Utf8RecodingDeferredFileOutputStream("test");
567         setInternalState(out, "cache", ByteBuffer.allocate(0));
568         Path path = mock(Path.class);
569         File file = mock(File.class);
570         when(path.toFile()).thenReturn(file);
571         setInternalState(out, "file", path);
572         RandomAccessFile storage = mock(RandomAccessFile.class);
573         doThrow(IOException.class).when(storage).close();
574         setInternalState(out, "storage", storage);
575         out.free();
576         assertThat((boolean) getInternalState(out, "closed")).isTrue();
577         verify(file, times(1)).deleteOnExit();
578     }
579 
580     @Test
581     public void testCacheOnDeferredFile() throws Exception {
582         Utf8RecodingDeferredFileOutputStream out = new Utf8RecodingDeferredFileOutputStream("test");
583         byte[] b1 = invokeMethod(out, "getLargeCache", 1);
584         byte[] b2 = invokeMethod(out, "getLargeCache", 1);
585         assertThat(b1).isSameAs(b2);
586         assertThat(b1).hasSize(1);
587 
588         byte[] b3 = invokeMethod(out, "getLargeCache", 2);
589         assertThat(b3).isNotSameAs(b1);
590         assertThat(b3).hasSize(2);
591 
592         byte[] b4 = invokeMethod(out, "getLargeCache", 1);
593         assertThat(b4).isSameAs(b3);
594         assertThat(b3).hasSize(2);
595     }
596 
597     @Test
598     public void testSyncOnDeferredFile() throws Exception {
599         Utf8RecodingDeferredFileOutputStream out = new Utf8RecodingDeferredFileOutputStream("test");
600         Buffer cache = ByteBuffer.wrap(new byte[] {1, 2, 3});
601         cache.position(3);
602         setInternalState(out, "cache", cache);
603         assertThat((boolean) getInternalState(out, "isDirty")).isFalse();
604         setInternalState(out, "isDirty", true);
605         File file = new File(reportDir, "test");
606         setInternalState(out, "file", file.toPath());
607         RandomAccessFile storage = new RandomAccessFile(file, "rw");
608         setInternalState(out, "storage", storage);
609         invokeMethod(out, "sync");
610         assertThat((boolean) getInternalState(out, "isDirty")).isFalse();
611         storage.seek(0L);
612         assertThat(storage.read()).isEqualTo(1);
613         assertThat(storage.read()).isEqualTo(2);
614         assertThat(storage.read()).isEqualTo(3);
615         assertThat(storage.read()).isEqualTo(-1);
616         assertThat(storage.length()).isEqualTo(3L);
617         assertThat(cache.position()).isEqualTo(0);
618         assertThat(cache.limit()).isEqualTo(3);
619         storage.seek(3L);
620         invokeMethod(out, "sync");
621         assertThat((boolean) getInternalState(out, "isDirty")).isFalse();
622         assertThat(storage.length()).isEqualTo(3L);
623         assertThat(out.getByteCount()).isEqualTo(3L);
624         assertThat((boolean) getInternalState(out, "closed")).isFalse();
625         out.free();
626         assertThat((boolean) getInternalState(out, "closed")).isTrue();
627         // todo assertThat( file ).doesNotExist();
628         out.free();
629         assertThat((boolean) getInternalState(out, "closed")).isTrue();
630     }
631 
632     @Test
633     public void testReporterHandlesATestWithoutMessageAndWithEmptyStackTrace() {
634         StackTraceWriter stackTraceWriterOne = new DeserializedStacktraceWriter(null, null, "");
635 
636         WrappedReportEntry testReport = new WrappedReportEntry(
637                 new SimpleReportEntry(
638                         NORMAL_RUN, 1L, getClass().getName(), null, "a test name", null, stackTraceWriterOne, 5),
639                 ERROR,
640                 1771085631L,
641                 5,
642                 null,
643                 null);
644 
645         StatelessXmlReporter reporter = new StatelessXmlReporter(
646                 reportDir,
647                 null,
648                 false,
649                 1,
650                 new HashMap<>(),
651                 XSD,
652                 "3.0.2",
653                 false,
654                 false,
655                 false,
656                 false,
657                 true,
658                 true,
659                 false);
660 
661         reporter.testSetCompleted(testReport, stats);
662     }
663 
664     @Test
665     public void testClassnameUsesActualClassNameWhenPhrasedClassNameDisabled() throws IOException {
666         String actualClassName = "MyTest";
667         String displayName = "NewName";
668 
669         ReportEntry reportEntry = new SimpleReportEntry(
670                 NORMAL_RUN,
671                 0L,
672                 actualClassName,
673                 displayName,
674                 actualClassName,
675                 TEST_ONE,
676                 null,
677                 null,
678                 12,
679                 null,
680                 Collections.<String, String>emptyMap());
681         WrappedReportEntry testSetReportEntry =
682                 new WrappedReportEntry(reportEntry, SUCCESS, 1771085631L, 12, null, null, systemProps());
683         expectedReportFile = new File(reportDir, "TEST-" + actualClassName + ".xml");
684 
685         stats.testSucceeded(testSetReportEntry);
686 
687         StatelessXmlReporter reporter = new StatelessXmlReporter(
688                 reportDir,
689                 null,
690                 false,
691                 0,
692                 new ConcurrentHashMap<>(),
693                 XSD,
694                 "3.0.2",
695                 false,
696                 false,
697                 false, // phrasedClassName = false
698                 false,
699                 true,
700                 true,
701                 false);
702         reporter.testSetCompleted(testSetReportEntry, stats);
703 
704         Xpp3Dom testSuite;
705         try (FileInputStream fileInputStream = new FileInputStream(expectedReportFile);
706                 InputStreamReader reader = new InputStreamReader(fileInputStream, UTF_8)) {
707             testSuite = Xpp3DomBuilder.build(reader);
708         }
709         Xpp3Dom testcase = testSuite.getChildren("testcase")[0];
710         assertEquals(
711                 actualClassName,
712                 testcase.getAttribute("classname"),
713                 "classname should be the actual class name, not the @DisplayName value");
714     }
715 
716     private boolean defaultCharsetSupportsSpecialChar() {
717         // some charsets are not able to deal with \u0115 on both ways of the conversion
718         return "\u0115\u00DC".equals(new String("\u0115\u00DC".getBytes()));
719     }
720 
721     private Utf8RecodingDeferredFileOutputStream createStdOutput(String content) throws IOException {
722         Utf8RecodingDeferredFileOutputStream stdOut = new Utf8RecodingDeferredFileOutputStream("fds2");
723         stdOut.write(content, false, null);
724         return stdOut;
725     }
726 }