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