Lazy output stream for test.xml, does not produce a file if no test xml data was produced

This fixes #5289.

The general problem is, if an exception is thrown during creation of the `TestSuiteModel`, the test runner will exit, closing off the OutputStream it had created for writing the test.xml. Unfortunately, if nothing was actually written to the stream, closing it will create a 0-byte test.xml on disk regardless.

This PR wraps the OutputStream in a lazy producer, only creating the actual stream if something was written to it. This guarantees test.xml will contain actual content.

Closes #6000.

PiperOrigin-RevId: 211799139
diff --git a/src/java_tools/junitrunner/java/com/google/testing/junit/runner/junit4/ProvideXmlStreamFactory.java b/src/java_tools/junitrunner/java/com/google/testing/junit/runner/junit4/ProvideXmlStreamFactory.java
index dc953e9..e69dd8c 100644
--- a/src/java_tools/junitrunner/java/com/google/testing/junit/runner/junit4/ProvideXmlStreamFactory.java
+++ b/src/java_tools/junitrunner/java/com/google/testing/junit/runner/junit4/ProvideXmlStreamFactory.java
@@ -16,6 +16,7 @@
 
 import com.google.testing.junit.runner.util.Factory;
 import com.google.testing.junit.runner.util.Supplier;
+import java.io.IOException;
 import java.io.OutputStream;
 
 /**
@@ -25,18 +26,82 @@
   private final Supplier<JUnit4Config> configSupplier;
 
   public ProvideXmlStreamFactory(Supplier<JUnit4Config> configSupplier) {
-    assert configSupplier != null;
+    if (configSupplier == null) {
+      throw new IllegalStateException();
+    }
+
     this.configSupplier = configSupplier;
   }
 
   @Override
   public OutputStream get() {
-    OutputStream outputStream = JUnit4RunnerModule.provideXmlStream(configSupplier.get());
-    assert outputStream != null;
+    OutputStream outputStream =
+        new LazyOutputStream(
+            new Supplier<OutputStream>() {
+              @Override
+              public OutputStream get() {
+                return JUnit4RunnerModule.provideXmlStream(configSupplier.get());
+              }
+            });
+
     return outputStream;
   }
 
   public static Factory<OutputStream> create(Supplier<JUnit4Config> configSupplier) {
     return new ProvideXmlStreamFactory(configSupplier);
   }
+
+  private static class LazyOutputStream extends OutputStream {
+    private Supplier<OutputStream> supplier;
+    private volatile OutputStream delegate;
+
+    public LazyOutputStream(Supplier<OutputStream> supplier) {
+      this.supplier = supplier;
+    }
+
+    private OutputStream ensureDelegate() {
+      OutputStream delegate0 = delegate;
+      if (delegate0 != null) {
+        return delegate0;
+      }
+
+      synchronized (this) {
+        if (delegate == null) {
+          delegate = supplier.get();
+          supplier = null;
+        }
+      }
+
+      return delegate;
+    }
+
+    @Override
+    public void write(int b) throws IOException {
+      ensureDelegate().write(b);
+    }
+
+    @Override
+    public void write(byte[] b, int off, int len) throws IOException {
+      ensureDelegate().write(b, off, len);
+    }
+
+    @Override
+    public void write(byte[] b) throws IOException {
+      ensureDelegate().write(b);
+    }
+
+    @Override
+    public void close() throws IOException {
+      if (delegate != null) {
+        delegate.close();
+      }
+    }
+
+    @Override
+    public void flush() throws IOException {
+      if (delegate != null) {
+        delegate.flush();
+      }
+    }
+  }
 }