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-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 + "?&-&amp;&#163;\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>! &amp#0;&amp#31;",
201                 tcb.getChild("system-out").getValue());
202 
203         assertEquals(
204                 stdErrPrefix + "?&-&amp;&#163; &amp#0;&amp#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         // Run time for a rerun failing test is the run time of the first run
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         // Check rerun error node contains all the information
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         // Check flaky failure node
318         Xpp3Dom testCaseThree = testcase[2];
319         assertEquals(TEST_THREE, testCaseThree.getAttribute("name"));
320         // Run time for a flaky test is the run time of the first successful run
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         // system-out and system-err should not be present for flaky failures
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         // todo assertThat( file ).doesNotExist();
521         out.free();
522         assertThat((boolean) getInternalState(out, "closed")).isTrue();
523     }
524 
525     private boolean defaultCharsetSupportsSpecialChar() {
526         // some charsets are not able to deal with \u0115 on both ways of the conversion
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 }