Modify TestSuiteModel and TestSuiteNode to propagate a properties map created in the test runner.

PiperOrigin-RevId: 290368369
diff --git a/src/java_tools/junitrunner/java/com/google/testing/junit/runner/model/TestSuiteModel.java b/src/java_tools/junitrunner/java/com/google/testing/junit/runner/model/TestSuiteModel.java
index 8dafb2d..3a90867 100644
--- a/src/java_tools/junitrunner/java/com/google/testing/junit/runner/model/TestSuiteModel.java
+++ b/src/java_tools/junitrunner/java/com/google/testing/junit/runner/model/TestSuiteModel.java
@@ -25,6 +25,7 @@
 import java.io.OutputStream;
 import java.io.StringWriter;
 import java.util.Collection;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.LinkedList;
 import java.util.List;
@@ -37,9 +38,9 @@
 import org.junit.runner.manipulation.Filter;
 
 /**
- * Model of the tests that will be run. The model is agnostic of the particular
- * type of test run (JUnit3 or JUnit4). The test runner uses this class to build
- * the model, and then updates the model during the test run.
+ * Model of the tests that will be run. The model is agnostic of the particular type of test run
+ * (JUnit3 or JUnit4). The test runner uses this class to build the model, and then updates the
+ * model during the test run.
  *
  * <p>The leaf nodes in the model are test cases; the other nodes are test suites.
  */
@@ -71,20 +72,17 @@
     return rootNode.getDescription();
   }
 
-  /**
-   * Gets the sharding filter to use; {@link Filter#ALL} if not sharding.
-   */
+  /** Gets the sharding filter to use; {@link Filter#ALL} if not sharding. */
   public Filter getShardingFilter() {
     return shardingFilter;
   }
 
   /**
-   * Returns the test case node with the given test description.<p>
+   * Returns the test case node with the given test description.
    *
-   * Note that in theory this should never return {@code null}, but
-   * if it did we would not want to throw a {@code NullPointerException}
-   * because JUnit4 would catch the exception and remove our test
-   * listener!
+   * <p>Note that in theory this should never return {@code null}, but if it did we would not want
+   * to throw a {@code NullPointerException} because JUnit4 would catch the exception and remove our
+   * test listener!
    */
   private TestCaseNode getTestCase(Description description) {
     // The description shouldn't be null, but in the test runner code we avoid throwing exceptions.
@@ -102,8 +100,8 @@
   }
 
   /**
-   * Indicate that the test run has started. This should be called after all
-   * filtering has been completed.
+   * Indicate that the test run has started. This should be called after all filtering has been
+   * completed.
    *
    * @param topLevelDescription the root {@link Description} node.
    */
@@ -147,16 +145,16 @@
     }
   }
 
-  /**
-   * Indicate that the entire test run was interrupted.
-   */
+  /** Indicate that the entire test run was interrupted. */
   public void testRunInterrupted() {
     rootNode.testInterrupted(now());
   }
 
   /**
-   * Indicate that the test case with the given key has requested that
-   * a property be written in the XML.<p>
+   * Indicate that the test case with the given key has requested that a property be written in the
+   * XML.
+   *
+   * <p>
    *
    * @param description key for a test case
    * @param name The property name.
@@ -170,8 +168,8 @@
   }
 
   /**
-   * Adds a failure to the test with the given key. If the specified test is suite, the failure
-   * will be added to all its children.
+   * Adds a failure to the test with the given key. If the specified test is suite, the failure will
+   * be added to all its children.
    *
    * @param description key for a test case
    */
@@ -199,7 +197,6 @@
     }
   }
 
-
   /**
    * Indicates that the test case with the given key was ignored or suppressed
    *
@@ -212,9 +209,7 @@
     }
   }
 
-  /**
-   * Indicate that the test case with the given description has finished.
-   */
+  /** Indicate that the test case with the given description has finished. */
   public void testFinished(Description description) {
     TestCaseNode testCase = getTestCase(description);
     if (testCase != null) {
@@ -279,9 +274,7 @@
     }
   }
 
-  /**
-   * A builder for creating a model of a test suite.
-   */
+  /** A builder for creating a model of a test suite. */
   public static class Builder {
     private final TestClock testClock;
     private final Map<Description, TestNode> testsMap = new ConcurrentHashMap<>();
@@ -305,10 +298,21 @@
     }
 
     /**
-     * Build a model with the given name, including the given suites. This method
-     * should be called before any command line filters are applied.
+     * Build a model with the given name, including the given suites. This method should be called
+     * before any command line filters are applied.
      */
     public TestSuiteModel build(String suiteName, Description... topLevelSuites) {
+      return build(suiteName, Collections.emptyMap(), topLevelSuites);
+    }
+
+    /**
+     * Build a model with the given name, including the given suites. This method should be called
+     * before any command line filters are applied.
+     *
+     * <p>The given {@code properties} map will be applied to the root {@link TestSuiteNode}.
+     */
+    public TestSuiteModel build(
+        String suiteName, Map<String, String> properties, Description... topLevelSuites) {
       if (buildWasCalled) {
         throw new IllegalStateException("Builder.build() was already called");
       }
@@ -316,7 +320,7 @@
       if (shardingEnvironment.isShardingEnabled()) {
         shardingFilter = getShardingFilter(topLevelSuites);
       }
-      rootNode = new TestSuiteNode(Description.createSuiteDescription(suiteName));
+      rootNode = new TestSuiteNode(Description.createSuiteDescription(suiteName), properties);
       for (Description topLevelSuite : topLevelSuites) {
         addTestSuite(rootNode, topLevelSuite);
         rootNode.getDescription().addChild(topLevelSuite);
diff --git a/src/java_tools/junitrunner/java/com/google/testing/junit/runner/model/TestSuiteNode.java b/src/java_tools/junitrunner/java/com/google/testing/junit/runner/model/TestSuiteNode.java
index 79a9e96..a55c5ce 100644
--- a/src/java_tools/junitrunner/java/com/google/testing/junit/runner/model/TestSuiteNode.java
+++ b/src/java_tools/junitrunner/java/com/google/testing/junit/runner/model/TestSuiteNode.java
@@ -21,6 +21,7 @@
 import java.util.Collections;
 import java.util.LinkedList;
 import java.util.List;
+import java.util.Map;
 import org.junit.runner.Description;
 
 /**
@@ -28,9 +29,15 @@
  */
 class TestSuiteNode extends TestNode {
   private final List<TestNode> children = new ArrayList<>();
+  private final Map<String, String> properties;
 
   TestSuiteNode(Description description) {
+    this(description, Collections.emptyMap());
+  }
+
+  TestSuiteNode(Description description, Map<String, String> properties) {
     super(description);
+    this.properties = properties;
   }
 
   // VisibleForTesting
@@ -109,7 +116,7 @@
     return new TestResult.Builder()
         .name(getDescription().getDisplayName())
         .className("")
-        .properties(Collections.<String, String>emptyMap())
+        .properties(properties)
         .failures(Collections.<Throwable>emptyList())
         .runTimeInterval(runTime)
         .status(Status.SKIPPED)
diff --git a/src/java_tools/junitrunner/javatests/com/google/testing/junit/runner/model/TestSuiteNodeTest.java b/src/java_tools/junitrunner/javatests/com/google/testing/junit/runner/model/TestSuiteNodeTest.java
index 09ef917..0494767 100644
--- a/src/java_tools/junitrunner/javatests/com/google/testing/junit/runner/model/TestSuiteNodeTest.java
+++ b/src/java_tools/junitrunner/javatests/com/google/testing/junit/runner/model/TestSuiteNodeTest.java
@@ -20,6 +20,7 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyZeroInteractions;
 
+import com.google.common.collect.ImmutableMap;
 import com.google.testing.junit.runner.util.TestClock.TestInstant;
 import java.time.Duration;
 import java.time.Instant;
@@ -30,9 +31,7 @@
 import org.mockito.Mock;
 import org.mockito.runners.MockitoJUnitRunner;
 
-/**
- * Unit test for {@link TestSuiteNode}.
- */
+/** Unit test for {@link TestSuiteNode}. */
 @RunWith(MockitoJUnitRunner.class)
 public class TestSuiteNodeTest {
 
@@ -87,4 +86,12 @@
     verifyZeroInteractions(dynamicTestCaseDescription);
   }
 
+  @Test
+  public void testProperties() {
+    ImmutableMap<String, String> properties = ImmutableMap.of("key", "value");
+    testSuiteNode = new TestSuiteNode(Description.createSuiteDescription("suite"), properties);
+
+    TestResult result = testSuiteNode.getResult();
+    assertThat(result.getProperties()).containsExactlyEntriesIn(properties);
+  }
 }