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.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.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 DIRECTORY_PREFIX = 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() + "-" + DIRECTORY_PREFIX.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.2",
109                 false,
110                 false,
111                 false,
112                 false,
113                 true,
114                 true);
115         reporter.cleanTestHistoryMap();
116 
117         ReportEntry reportEntry = new SimpleReportEntry(
118                 NORMAL_RUN, 0L, getClass().getName(), null, getClass().getName(), null, 12);
119         WrappedReportEntry testSetReportEntry =
120                 new WrappedReportEntry(reportEntry, ReportEntryType.SUCCESS, 12, null, null, systemProps());
121         stats.testSucceeded(testSetReportEntry);
122         reporter.testSetCompleted(testSetReportEntry, stats);
123 
124         expectedReportFile = new File(reportDir, "TEST-" + getClass().getName() + ".xml");
125         assertTrue(
126                 "Report file (" + expectedReportFile.getAbsolutePath() + ") doesn't exist",
127                 expectedReportFile.exists());
128     }
129 
130     public void testAllFieldsSerialized() throws IOException {
131         ReportEntry reportEntry =
132                 new SimpleReportEntry(NORMAL_RUN, 0L, getClass().getName(), null, TEST_ONE, null, 12);
133         WrappedReportEntry testSetReportEntry =
134                 new WrappedReportEntry(reportEntry, SUCCESS, 12, null, null, systemProps());
135         expectedReportFile = new File(reportDir, "TEST-" + getClass().getName() + ".xml");
136 
137         stats.testSucceeded(testSetReportEntry);
138         StackTraceWriter stackTraceWriter = new DeserializedStacktraceWriter("A fud msg", "trimmed", "fail at foo");
139         Utf8RecodingDeferredFileOutputStream stdOut = new Utf8RecodingDeferredFileOutputStream("fds");
140         String stdOutPrefix;
141         String stdErrPrefix;
142         if (defaultCharsetSupportsSpecialChar()) {
143             stdErrPrefix = "std-\u0115rr";
144             stdOutPrefix = "st]]>d-o\u00DCt";
145         } else {
146             stdErrPrefix = "std-err";
147             stdOutPrefix = "st]]>d-out";
148         }
149 
150         stdOut.write(stdOutPrefix + "<null>!\u0020\u0000\u001F", false);
151 
152         Utf8RecodingDeferredFileOutputStream stdErr = new Utf8RecodingDeferredFileOutputStream("fds");
153 
154         stdErr.write(stdErrPrefix + "?&-&amp;&#163;\u0020\u0000\u001F", false);
155         WrappedReportEntry t2 = new WrappedReportEntry(
156                 new SimpleReportEntry(NORMAL_RUN, 0L, getClass().getName(), null, TEST_TWO, null, stackTraceWriter, 13),
157                 ReportEntryType.ERROR,
158                 13,
159                 stdOut,
160                 stdErr);
161 
162         stats.testSucceeded(t2);
163         StatelessXmlReporter reporter = new StatelessXmlReporter(
164                 reportDir,
165                 null,
166                 false,
167                 0,
168                 new ConcurrentHashMap<String, Deque<WrappedReportEntry>>(),
169                 XSD,
170                 "3.0.2",
171                 false,
172                 false,
173                 false,
174                 false,
175                 true,
176                 true);
177         reporter.testSetCompleted(testSetReportEntry, stats);
178 
179         FileInputStream fileInputStream = new FileInputStream(expectedReportFile);
180 
181         Xpp3Dom testSuite = Xpp3DomBuilder.build(new InputStreamReader(fileInputStream, UTF_8));
182         assertEquals("testsuite", testSuite.getName());
183         Xpp3Dom properties = testSuite.getChild("properties");
184         assertEquals(System.getProperties().size(), properties.getChildCount());
185         Xpp3Dom child = properties.getChild(1);
186         assertFalse(isEmpty(child.getAttribute("value")));
187         assertFalse(isEmpty(child.getAttribute("name")));
188 
189         Xpp3Dom[] testcase = testSuite.getChildren("testcase");
190         Xpp3Dom tca = testcase[0];
191         assertEquals(TEST_ONE, tca.getAttribute("name"));
192         assertEquals("0.012", tca.getAttribute("time"));
193         assertEquals(getClass().getName(), tca.getAttribute("classname"));
194 
195         Xpp3Dom tcb = testcase[1];
196         assertEquals(TEST_TWO, tcb.getAttribute("name"));
197         assertEquals("0.013", tcb.getAttribute("time"));
198         assertEquals(getClass().getName(), tcb.getAttribute("classname"));
199         Xpp3Dom errorNode = tcb.getChild("error");
200         assertNotNull(errorNode);
201         assertEquals("A fud msg", errorNode.getAttribute("message"));
202         assertEquals("fail at foo", errorNode.getAttribute("type"));
203         assertEquals(
204                 stdOutPrefix + "<null>! &amp#0;&amp#31;",
205                 tcb.getChild("system-out").getValue());
206 
207         assertEquals(
208                 stdErrPrefix + "?&-&amp;&#163; &amp#0;&amp#31;",
209                 tcb.getChild("system-err").getValue());
210     }
211 
212     public void testOutputRerunFlakyFailure() throws IOException {
213         WrappedReportEntry testSetReportEntry = new WrappedReportEntry(
214                 new SimpleReportEntry(NORMAL_RUN, 0L, getClass().getName(), null, TEST_ONE, null, 12),
215                 ReportEntryType.SUCCESS,
216                 12,
217                 null,
218                 null,
219                 systemProps());
220         expectedReportFile = new File(reportDir, "TEST-" + getClass().getName() + ".xml");
221 
222         stats.testSucceeded(testSetReportEntry);
223         StackTraceWriter stackTraceWriterOne = new DeserializedStacktraceWriter("A fud msg", "trimmed", "fail at foo");
224         StackTraceWriter stackTraceWriterTwo =
225                 new DeserializedStacktraceWriter("A fud msg two", "trimmed two", "fail at foo two");
226 
227         String firstRunOut = "first run out";
228         String firstRunErr = "first run err";
229         String secondRunOut = "second run out";
230         String secondRunErr = "second run err";
231 
232         String cls = getClass().getName();
233         WrappedReportEntry testTwoFirstError = new WrappedReportEntry(
234                 new SimpleReportEntry(NORMAL_RUN, 0L, cls, null, TEST_TWO, null, stackTraceWriterOne, 5),
235                 ReportEntryType.ERROR,
236                 5,
237                 createStdOutput(firstRunOut),
238                 createStdOutput(firstRunErr));
239 
240         WrappedReportEntry testTwoSecondError = new WrappedReportEntry(
241                 new SimpleReportEntry(RERUN_TEST_AFTER_FAILURE, 1L, cls, null, TEST_TWO, null, stackTraceWriterTwo, 13),
242                 ReportEntryType.ERROR,
243                 13,
244                 createStdOutput(secondRunOut),
245                 createStdOutput(secondRunErr));
246 
247         WrappedReportEntry testThreeFirstRun = new WrappedReportEntry(
248                 new SimpleReportEntry(NORMAL_RUN, 2L, cls, null, TEST_THREE, null, stackTraceWriterOne, 13),
249                 ReportEntryType.FAILURE,
250                 13,
251                 createStdOutput(firstRunOut),
252                 createStdOutput(firstRunErr));
253 
254         WrappedReportEntry testThreeSecondRun = new WrappedReportEntry(
255                 new SimpleReportEntry(
256                         RERUN_TEST_AFTER_FAILURE, 3L, cls, null, TEST_THREE, null, stackTraceWriterTwo, 2),
257                 ReportEntryType.SUCCESS,
258                 2,
259                 createStdOutput(secondRunOut),
260                 createStdOutput(secondRunErr));
261 
262         stats.testSucceeded(testTwoFirstError);
263         stats.testSucceeded(testThreeFirstRun);
264         rerunStats.testSucceeded(testTwoSecondError);
265         rerunStats.testSucceeded(testThreeSecondRun);
266 
267         StatelessXmlReporter reporter = new StatelessXmlReporter(
268                 reportDir,
269                 null,
270                 false,
271                 1,
272                 new HashMap<String, Deque<WrappedReportEntry>>(),
273                 XSD,
274                 "3.0.2",
275                 false,
276                 false,
277                 false,
278                 false,
279                 true,
280                 true);
281 
282         reporter.testSetCompleted(testSetReportEntry, stats);
283         reporter.testSetCompleted(testSetReportEntry, rerunStats);
284 
285         FileInputStream fileInputStream = new FileInputStream(expectedReportFile);
286 
287         Xpp3Dom testSuite = Xpp3DomBuilder.build(new InputStreamReader(fileInputStream, UTF_8));
288         assertEquals("testsuite", testSuite.getName());
289         assertEquals("0.012", testSuite.getAttribute("time"));
290         Xpp3Dom properties = testSuite.getChild("properties");
291         assertEquals(System.getProperties().size(), properties.getChildCount());
292         Xpp3Dom child = properties.getChild(1);
293         assertFalse(isEmpty(child.getAttribute("value")));
294         assertFalse(isEmpty(child.getAttribute("name")));
295 
296         Xpp3Dom[] testcase = testSuite.getChildren("testcase");
297         Xpp3Dom testCaseOne = testcase[0];
298         assertEquals(TEST_ONE, testCaseOne.getAttribute("name"));
299         assertEquals("0.012", testCaseOne.getAttribute("time"));
300         assertEquals(getClass().getName(), testCaseOne.getAttribute("classname"));
301 
302         Xpp3Dom testCaseTwo = testcase[1];
303         assertEquals(TEST_TWO, testCaseTwo.getAttribute("name"));
304         // Run time for a rerun failing test is the run time of the first run
305         assertEquals("0.005", testCaseTwo.getAttribute("time"));
306         assertEquals(getClass().getName(), testCaseTwo.getAttribute("classname"));
307         Xpp3Dom errorNode = testCaseTwo.getChild("error");
308         Xpp3Dom rerunErrorNode = testCaseTwo.getChild("rerunError");
309         assertNotNull(errorNode);
310         assertNotNull(rerunErrorNode);
311 
312         assertEquals("A fud msg", errorNode.getAttribute("message"));
313         assertEquals("fail at foo", errorNode.getAttribute("type"));
314 
315         // Check rerun error node contains all the information
316         assertEquals(firstRunOut, testCaseTwo.getChild("system-out").getValue());
317         assertEquals(firstRunErr, testCaseTwo.getChild("system-err").getValue());
318         assertEquals(secondRunOut, rerunErrorNode.getChild("system-out").getValue());
319         assertEquals(secondRunErr, rerunErrorNode.getChild("system-err").getValue());
320         assertEquals("A fud msg two", rerunErrorNode.getAttribute("message"));
321         assertEquals("fail at foo two", rerunErrorNode.getAttribute("type"));
322 
323         // Check flaky failure node
324         Xpp3Dom testCaseThree = testcase[2];
325         assertEquals(TEST_THREE, testCaseThree.getAttribute("name"));
326         // Run time for a flaky test is the run time of the first successful run
327         assertEquals("0.002", testCaseThree.getAttribute("time"));
328         assertEquals(getClass().getName(), testCaseThree.getAttribute("classname"));
329         Xpp3Dom flakyFailureNode = testCaseThree.getChild("flakyFailure");
330         assertNotNull(flakyFailureNode);
331         assertEquals(firstRunOut, flakyFailureNode.getChild("system-out").getValue());
332         assertEquals(firstRunErr, flakyFailureNode.getChild("system-err").getValue());
333         // system-out and system-err should not be present for flaky failures
334         assertNull(testCaseThree.getChild("system-out"));
335         assertNull(testCaseThree.getChild("system-err"));
336     }
337 
338     public void testOutputRerunFlakyAssumption() throws IOException {
339         expectedReportFile = new File(reportDir, "TEST-" + getClass().getName() + ".xml");
340 
341         StackTraceWriter stackTraceWriterOne = new DeserializedStacktraceWriter("A fud msg", "trimmed", "fail at foo");
342 
343         StackTraceWriter stackTraceWriterTwo =
344                 new DeserializedStacktraceWriter("A fud msg two", "trimmed two", "fail at foo two");
345 
346         String firstRunOut = "first run out";
347         String firstRunErr = "first run err";
348         String secondRunOut = "second run out";
349         String secondRunErr = "second run err";
350 
351         WrappedReportEntry testTwoFirstError = new WrappedReportEntry(
352                 new SimpleReportEntry(
353                         NORMAL_RUN, 1L, getClass().getName(), null, TEST_TWO, null, stackTraceWriterOne, 5),
354                 ERROR,
355                 5,
356                 createStdOutput(firstRunOut),
357                 createStdOutput(firstRunErr));
358 
359         stats.testSucceeded(testTwoFirstError);
360 
361         WrappedReportEntry testTwoSecondError = new WrappedReportEntry(
362                 new SimpleReportEntry(
363                         RERUN_TEST_AFTER_FAILURE,
364                         1L,
365                         getClass().getName(),
366                         null,
367                         TEST_TWO,
368                         null,
369                         stackTraceWriterTwo,
370                         13),
371                 SKIPPED,
372                 13,
373                 createStdOutput(secondRunOut),
374                 createStdOutput(secondRunErr));
375 
376         rerunStats.testSucceeded(testTwoSecondError);
377 
378         StatelessXmlReporter reporter = new StatelessXmlReporter(
379                 reportDir, null, false, 1, new HashMap<>(), XSD, "3.0.2", false, false, false, false, true, true);
380 
381         WrappedReportEntry testSetReportEntry = new WrappedReportEntry(
382                 new SimpleReportEntry(
383                         RERUN_TEST_AFTER_FAILURE, 1L, getClass().getName(), null, null, null, stackTraceWriterOne, 5),
384                 ERROR,
385                 20,
386                 createStdOutput(firstRunOut),
387                 createStdOutput(firstRunErr));
388 
389         reporter.testSetCompleted(testSetReportEntry, stats);
390         reporter.testSetCompleted(testSetReportEntry, rerunStats);
391 
392         FileInputStream fileInputStream = new FileInputStream(expectedReportFile);
393 
394         Xpp3Dom testSuite = Xpp3DomBuilder.build(new InputStreamReader(fileInputStream, UTF_8));
395         assertEquals("testsuite", testSuite.getName());
396         assertEquals("0.02", testSuite.getAttribute("time"));
397 
398         Xpp3Dom[] testcase = testSuite.getChildren("testcase");
399         assertEquals(1, testcase.length);
400         Xpp3Dom testCaseOne = testcase[0];
401         assertEquals(getClass().getName(), testCaseOne.getAttribute("classname"));
402         assertEquals(TEST_TWO, testCaseOne.getAttribute("name"));
403         assertEquals("0.005", testCaseOne.getAttribute("time"));
404 
405         Xpp3Dom[] testCaseElements = testCaseOne.getChildren();
406         assertEquals(3, testCaseElements.length);
407         assertEquals("error", testCaseElements[0].getName());
408         assertEquals("system-out", testCaseElements[1].getName());
409         assertEquals("system-err", testCaseElements[2].getName());
410         long linesWithComments = readAllLines(expectedReportFile.toPath(), UTF_8).stream()
411                 .filter(line -> line.contains("<!-- a skipped test execution in re-run phase -->"))
412                 .count();
413         assertEquals(1, linesWithComments);
414     }
415 
416     public void testNoWritesOnDeferredFile() throws Exception {
417         Utf8RecodingDeferredFileOutputStream out = new Utf8RecodingDeferredFileOutputStream("test");
418         out.free();
419         out.write("a", false);
420         assertThat((boolean) getInternalState(out, "isDirty")).isFalse();
421     }
422 
423     public void testLengthOnDeferredFile() throws Exception {
424         Utf8RecodingDeferredFileOutputStream out = new Utf8RecodingDeferredFileOutputStream("test");
425 
426         assertThat(out.getByteCount()).isZero();
427 
428         File f = File.createTempFile("test", "tmp");
429         RandomAccessFile storage = new RandomAccessFile(f, "rw");
430         setInternalState(out, "storage", storage);
431         setInternalState(out, "file", f.toPath());
432         storage.writeByte(0);
433         storage.getFD().sync();
434         assertThat(out.getByteCount()).isEqualTo(1);
435 
436         storage.close();
437         assertThat(f.delete()).isTrue();
438         assertThat(out.getByteCount()).isZero();
439         out.free();
440     }
441 
442     @SuppressWarnings("checkstyle:magicnumber")
443     public void testWritesOnDeferredFile() throws Exception {
444         Utf8RecodingDeferredFileOutputStream out = new Utf8RecodingDeferredFileOutputStream("test");
445         for (int i = 0; i < 33_000; i++) {
446             out.write("A", false);
447             out.write("B", true);
448         }
449         out.write(null, false);
450         out.write(null, true);
451 
452         assertThat(out.getByteCount()).isEqualTo(33_000 * (1 + 1 + NL.length()) + 4 + 4 + NL.length());
453 
454         StringBuilder expectedContent = new StringBuilder(150_000);
455         for (int i = 0; i < 33_000; i++) {
456             expectedContent.append('A').append('B').append(NL);
457         }
458         expectedContent.append("null").append("null").append(NL);
459         ByteArrayOutputStream read = new ByteArrayOutputStream(150_000);
460         out.writeTo(read);
461         assertThat(read.toString()).isEqualTo(expectedContent.toString());
462 
463         out.free();
464     }
465 
466     public void testFreeOnDeferredFile() throws Exception {
467         Utf8RecodingDeferredFileOutputStream out = new Utf8RecodingDeferredFileOutputStream("test");
468         setInternalState(out, "cache", ByteBuffer.allocate(0));
469         Path path = mock(Path.class);
470         File file = mock(File.class);
471         when(path.toFile()).thenReturn(file);
472         setInternalState(out, "file", path);
473         RandomAccessFile storage = mock(RandomAccessFile.class);
474         doThrow(IOException.class).when(storage).close();
475         setInternalState(out, "storage", storage);
476         out.free();
477         assertThat((boolean) getInternalState(out, "closed")).isTrue();
478         verify(file, times(1)).deleteOnExit();
479     }
480 
481     public void testCacheOnDeferredFile() throws Exception {
482         Utf8RecodingDeferredFileOutputStream out = new Utf8RecodingDeferredFileOutputStream("test");
483         byte[] b1 = invokeMethod(out, "getLargeCache", 1);
484         byte[] b2 = invokeMethod(out, "getLargeCache", 1);
485         assertThat(b1).isSameAs(b2);
486         assertThat(b1).hasSize(1);
487 
488         byte[] b3 = invokeMethod(out, "getLargeCache", 2);
489         assertThat(b3).isNotSameAs(b1);
490         assertThat(b3).hasSize(2);
491 
492         byte[] b4 = invokeMethod(out, "getLargeCache", 1);
493         assertThat(b4).isSameAs(b3);
494         assertThat(b3).hasSize(2);
495     }
496 
497     public void testSyncOnDeferredFile() throws Exception {
498         Utf8RecodingDeferredFileOutputStream out = new Utf8RecodingDeferredFileOutputStream("test");
499         Buffer cache = ByteBuffer.wrap(new byte[] {1, 2, 3});
500         cache.position(3);
501         setInternalState(out, "cache", cache);
502         assertThat((boolean) getInternalState(out, "isDirty")).isFalse();
503         setInternalState(out, "isDirty", true);
504         File file = new File(reportDir, "test");
505         setInternalState(out, "file", file.toPath());
506         RandomAccessFile storage = new RandomAccessFile(file, "rw");
507         setInternalState(out, "storage", storage);
508         invokeMethod(out, "sync");
509         assertThat((boolean) getInternalState(out, "isDirty")).isFalse();
510         storage.seek(0L);
511         assertThat(storage.read()).isEqualTo(1);
512         assertThat(storage.read()).isEqualTo(2);
513         assertThat(storage.read()).isEqualTo(3);
514         assertThat(storage.read()).isEqualTo(-1);
515         assertThat(storage.length()).isEqualTo(3L);
516         assertThat(cache.position()).isEqualTo(0);
517         assertThat(cache.limit()).isEqualTo(3);
518         storage.seek(3L);
519         invokeMethod(out, "sync");
520         assertThat((boolean) getInternalState(out, "isDirty")).isFalse();
521         assertThat(storage.length()).isEqualTo(3L);
522         assertThat(out.getByteCount()).isEqualTo(3L);
523         assertThat((boolean) getInternalState(out, "closed")).isFalse();
524         out.free();
525         assertThat((boolean) getInternalState(out, "closed")).isTrue();
526         // todo assertThat( file ).doesNotExist();
527         out.free();
528         assertThat((boolean) getInternalState(out, "closed")).isTrue();
529     }
530 
531     public void testReporterHandlesATestWithoutMessageAndWithEmptyStackTrace() {
532         StackTraceWriter stackTraceWriterOne = new DeserializedStacktraceWriter(null, null, "");
533 
534         WrappedReportEntry testReport = new WrappedReportEntry(
535                 new SimpleReportEntry(
536                         NORMAL_RUN, 1L, getClass().getName(), null, "a test name", null, stackTraceWriterOne, 5),
537                 ERROR,
538                 5,
539                 null,
540                 null);
541 
542         StatelessXmlReporter reporter = new StatelessXmlReporter(
543                 reportDir, null, false, 1, new HashMap<>(), XSD, "3.0.2", false, false, false, false, true, true);
544 
545         reporter.testSetCompleted(testReport, stats);
546     }
547 
548     private boolean defaultCharsetSupportsSpecialChar() {
549         // some charsets are not able to deal with \u0115 on both ways of the conversion
550         return "\u0115\u00DC".equals(new String("\u0115\u00DC".getBytes()));
551     }
552 
553     private Utf8RecodingDeferredFileOutputStream createStdOutput(String content) throws IOException {
554         Utf8RecodingDeferredFileOutputStream stdOut = new Utf8RecodingDeferredFileOutputStream("fds2");
555         stdOut.write(content, false);
556         return stdOut;
557     }
558 }