Github user bodewig commented on a diff in the pull request:
https://github.com/apache/ant/pull/60#discussion_r172043835
--- Diff:
src/main/org/apache/tools/ant/taskdefs/optional/junitlauncher/AbstractJUnitResultFormatter.java
---
@@ -0,0 +1,295 @@
+package org.apache.tools.ant.taskdefs.optional.junitlauncher;
+
+import org.apache.tools.ant.Project;
+import org.apache.tools.ant.Task;
+import org.apache.tools.ant.util.FileUtils;
+import org.junit.platform.engine.TestSource;
+import org.junit.platform.engine.support.descriptor.ClassSource;
+import org.junit.platform.launcher.TestIdentifier;
+import org.junit.platform.launcher.TestPlan;
+
+import java.io.BufferedReader;
+import java.io.ByteArrayInputStream;
+import java.io.Closeable;
+import java.io.FileOutputStream;
+import java.io.FileReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.io.Writer;
+import java.nio.BufferOverflowException;
+import java.nio.ByteBuffer;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Objects;
+import java.util.Optional;
+
+/**
+ * Contains some common behaviour that's used by our internal {@link
TestResultFormatter}s
+ */
+abstract class AbstractJUnitResultFormatter implements TestResultFormatter
{
+
+
+ protected static String NEW_LINE =
System.getProperty("line.separator");
+ protected Task task;
+
+ private SysOutErrContentStore sysOutStore;
+ private SysOutErrContentStore sysErrStore;
+
+ @Override
+ public void sysOutAvailable(final byte[] data) {
+ if (this.sysOutStore == null) {
+ this.sysOutStore = new SysOutErrContentStore(true);
+ }
+ try {
+ this.sysOutStore.store(data);
+ } catch (IOException e) {
+ handleException(e);
+ return;
+ }
+ }
+
+ @Override
+ public void sysErrAvailable(final byte[] data) {
+ if (this.sysErrStore == null) {
+ this.sysErrStore = new SysOutErrContentStore(false);
+ }
+ try {
+ this.sysErrStore.store(data);
+ } catch (IOException e) {
+ handleException(e);
+ return;
+ }
+ }
+
+ @Override
+ public void setExecutingTask(final Task task) {
+ this.task = task;
+ }
+
+ /**
+ * @return Returns true if there's any stdout data, that was generated
during the
+ * tests, is available for use. Else returns false.
+ */
+ boolean hasSysOut() {
+ return this.sysOutStore != null && this.sysOutStore.hasData();
+ }
+
+ /**
+ * @return Returns true if there's any stderr data, that was generated
during the
+ * tests, is available for use. Else returns false.
+ */
+ boolean hasSysErr() {
+ return this.sysErrStore != null && this.sysErrStore.hasData();
+ }
+
+ /**
+ * @return Returns a {@link Reader} for reading any stdout data that
was generated
+ * during the test execution. It is expected that the {@link
#hasSysOut()} be first
+ * called to see if any such data is available and only if there is,
then this method
+ * be called
+ * @throws IOException If there's any I/O problem while creating the
{@link Reader}
+ */
+ Reader getSysOutReader() throws IOException {
+ return this.sysOutStore.getReader();
+ }
+
+ /**
+ * @return Returns a {@link Reader} for reading any stderr data that
was generated
+ * during the test execution. It is expected that the {@link
#hasSysErr()} be first
+ * called to see if any such data is available and only if there is,
then this method
+ * be called
+ * @throws IOException If there's any I/O problem while creating the
{@link Reader}
+ */
+ Reader getSysErrReader() throws IOException {
+ return this.sysErrStore.getReader();
+ }
+
+ /**
+ * Writes out any stdout data that was generated during the
+ * test execution. If there was no such data then this method just
returns.
+ *
+ * @param writer The {@link Writer} to use. Cannot be null.
+ * @throws IOException If any I/O problem occurs during writing the
data
+ */
+ void writeSysOut(final Writer writer) throws IOException {
+ Objects.requireNonNull(writer, "Writer cannot be null");
+ this.writeFrom(this.sysOutStore, writer);
+ }
+
+ /**
+ * Writes out any stderr data that was generated during the
+ * test execution. If there was no such data then this method just
returns.
+ *
+ * @param writer The {@link Writer} to use. Cannot be null.
+ * @throws IOException If any I/O problem occurs during writing the
data
+ */
+ void writeSysErr(final Writer writer) throws IOException {
+ Objects.requireNonNull(writer, "Writer cannot be null");
+ this.writeFrom(this.sysErrStore, writer);
+ }
+
+ static Optional<TestIdentifier> traverseAndFindTestClass(final
TestPlan testPlan, final TestIdentifier testIdentifier) {
+ if (isTestClass(testIdentifier).isPresent()) {
+ return Optional.of(testIdentifier);
+ }
+ final Optional<TestIdentifier> parent =
testPlan.getParent(testIdentifier);
+ return parent.isPresent() ? traverseAndFindTestClass(testPlan,
parent.get()) : Optional.empty();
+ }
+
+ static Optional<ClassSource> isTestClass(final TestIdentifier
testIdentifier) {
+ if (testIdentifier == null) {
+ return Optional.empty();
+ }
+ final Optional<TestSource> source = testIdentifier.getSource();
+ if (!source.isPresent()) {
+ return Optional.empty();
+ }
+ final TestSource testSource = source.get();
+ if (testSource instanceof ClassSource) {
+ return Optional.of((ClassSource) testSource);
+ }
+ return Optional.empty();
+ }
+
+ private void writeFrom(final SysOutErrContentStore store, final Writer
writer) throws IOException {
+ final char[] chars = new char[1024];
+ int numRead = -1;
+ try (final Reader reader = store.getReader()) {
+ while ((numRead = reader.read(chars)) != -1) {
+ writer.write(chars, 0, numRead);
+ }
+ }
+ }
+
+ @Override
+ public void close() throws IOException {
+ FileUtils.close(this.sysOutStore);
+ FileUtils.close(this.sysErrStore);
+ }
+
+ protected void handleException(final Throwable t) {
+ // we currently just log it and move on.
+ task.getProject().log("Exception in listener " +
this.getClass().getName(), t, Project.MSG_DEBUG);
+ }
+
+
+ /*
+ A "store" for sysout/syserr content that gets sent to the
AbstractJUnitResultFormatter.
+ This store first uses a relatively decent sized in-memory buffer for
storing the sysout/syserr
+ content. This in-memory buffer will be used as long as it can fit in
the new content that
+ keeps coming in. When the size limit is reached, this store switches
to a file based store
+ by creating a temporarily file and writing out the already in-memory
held buffer content
+ and any new content that keeps arriving to this store. Once the file
has been created,
+ the in-memory buffer will never be used any more and in fact is
destroyed as soon as the
+ file is created.
+ Instances of this class are not thread-safe and users of this class
are expected to use necessary thread
+ safety guarantees, if they want to use an instance of this class by
multiple threads.
+ */
+ private static final class SysOutErrContentStore implements Closeable {
+ private static final int DEFAULT_CAPACITY_IN_BYTES = 50 * 1024; //
50 KB
+ private static final Reader EMPTY_READER = new Reader() {
+ @Override
+ public int read(final char[] cbuf, final int off, final int
len) throws IOException {
+ return -1;
+ }
+
+ @Override
+ public void close() throws IOException {
+ }
+ };
+
+ private final String tmpFileSuffix;
+ private ByteBuffer inMemoryStore =
ByteBuffer.allocate(DEFAULT_CAPACITY_IN_BYTES);
+ private boolean usingFileStore = false;
+ private Path filePath;
+ private FileOutputStream fileOutputStream;
+
+ private SysOutErrContentStore(final boolean isSysOut) {
+ this.tmpFileSuffix = isSysOut ? ".sysout" : ".syserr";
+ }
+
+ private void store(final byte[] data) throws IOException {
+ if (this.usingFileStore) {
+ this.storeToFile(data, 0, data.length);
+ return;
+ }
+ // we haven't yet created a file store and the data can fit in
memory,
+ // so we write it in our buffer
+ try {
+ this.inMemoryStore.put(data);
+ return;
+ } catch (BufferOverflowException boe) {
+ // the buffer capacity can't hold this incoming data, so
this
+ // incoming data hasn't been transferred to the buffer.
let's
+ // now fall back to a file store
+ this.usingFileStore = true;
+ }
+ // since the content couldn't be transferred into in-memory
buffer,
+ // we now create a file and transfer already (previously)
stored in-memory
+ // content into that file, before finally transferring this
new content
+ // into the file too. We then finally discard this in-memory
buffer and
+ // just keep using the file store instead
+ this.fileOutputStream = createFileStore();
+ // first the existing in-memory content
+ storeToFile(this.inMemoryStore.array(), 0,
this.inMemoryStore.position());
+ storeToFile(data, 0, data.length);
+ // discard the in-memory store
+ this.inMemoryStore = null;
+ }
+
+ private void storeToFile(final byte[] data, final int offset,
final int length) throws IOException {
+ if (this.fileOutputStream == null) {
+ // no backing file was created so we can't do anything
+ return;
+ }
+ this.fileOutputStream.write(data, offset, length);
+ }
+
+ private FileOutputStream createFileStore() throws IOException {
+ this.filePath = Files.createTempFile(null, this.tmpFileSuffix);
+ this.filePath.toFile().deleteOnExit();
--- End diff --
`FileUtils.createTempFile`? Probably not worth it.
---
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]