This is an automated email from the ASF dual-hosted git repository.

olamy pushed a commit to branch surefire-3.5.x
in repository https://gitbox.apache.org/repos/asf/maven-surefire.git


The following commit(s) were added to refs/heads/surefire-3.5.x by this push:
     new a970fefe4 Introduce reportTestTimestamp option and include timestamp 
for test sets and test cases (#3261) (#3302)
a970fefe4 is described below

commit a970fefe4dbc173acacf79389d812f91f6ef027a
Author: Olivier Lamy <[email protected]>
AuthorDate: Tue Mar 3 09:05:42 2026 +1000

    Introduce reportTestTimestamp option and include timestamp for test sets 
and test cases (#3261) (#3302)
    
    * Introduce reportTestTimestamp option and include start timestamps in XML 
reports
    
    * Explicitly state deprecation reason of StatelessXmlReporter ctor, and 
inline toIsoInstant logic
    
    * Make test/testset startAt getters package private, and remove sleep 
behavior in dummy tests
    
    * Adapt surefire test report format to include timestamp field
    
    Co-authored-by: Kevin Nammour <[email protected]>
---
 .../plugin/surefire/AbstractSurefireMojo.java      | 13 +++
 .../maven/plugin/surefire/CommonReflector.java     |  2 +
 .../surefire/StartupReportConfiguration.java       | 10 +++
 .../DefaultStatelessReportMojoConfiguration.java   |  4 +-
 .../extensions/SurefireStatelessReporter.java      |  3 +-
 .../junit5/JUnit5Xml30StatelessReporter.java       |  3 +-
 .../surefire/report/NullStatelessXmlReporter.java  |  2 +-
 .../surefire/report/StatelessXmlReporter.java      | 21 ++++-
 .../plugin/surefire/report/TestSetRunListener.java | 15 ++--
 .../maven/plugin/surefire/report/TestSetStats.java |  8 ++
 .../plugin/surefire/report/WrappedReportEntry.java | 11 ++-
 .../maven/plugin/surefire/CommonReflectorTest.java |  1 +
 .../surefire/booterclient/ForkStarterTest.java     |  2 +
 .../booterclient/TestSetMockReporterFactory.java   |  1 +
 .../surefire/extensions/StatelessReporterTest.java |  4 +-
 .../report/DefaultReporterFactoryTest.java         |  3 +
 .../surefire/report/StatelessXmlReporterTest.java  | 55 +++++++++++--
 .../surefire/report/WrappedReportEntryTest.java    | 25 +++---
 .../maven/surefire/report/FileReporterTest.java    |  4 +-
 .../site/resources/xsd/surefire-test-report.xsd    |  2 +
 .../StatelessReportMojoConfiguration.java          | 11 ++-
 .../maven/surefire/its/ReportTestTimestampIT.java  | 96 ++++++++++++++++++++++
 .../resources/disable-timestamp-element/pom.xml    | 58 +++++++++++++
 .../src/test/java/TestA.java                       | 18 ++++
 .../resources/enable-timestamp-element/pom.xml     | 58 +++++++++++++
 .../src/test/java/TestA.java                       | 18 ++++
 .../maven/surefire/junitcore/JUnitCoreTester.java  |  1 +
 27 files changed, 415 insertions(+), 34 deletions(-)

diff --git 
a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/AbstractSurefireMojo.java
 
b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/AbstractSurefireMojo.java
index 3b9ed93d5..35addccf6 100644
--- 
a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/AbstractSurefireMojo.java
+++ 
b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/AbstractSurefireMojo.java
@@ -748,6 +748,14 @@ public abstract class AbstractSurefireMojo extends 
AbstractMojo implements Suref
     @Parameter(property = "enablePropertiesElement", defaultValue = "true")
     private boolean enablePropertiesElement;
 
+    /**
+     * Flag for including/excluding the event start timestamp of {@code 
<testsuite />} and {@code <testcase />} elements in XML reports.
+     *
+     * @since 3.5.5
+     */
+    @Parameter(property = "reportTestTimestamp", defaultValue = "false")
+    private boolean reportTestTimestamp;
+
     /**
      * The current build session instance.
      */
@@ -2109,6 +2117,7 @@ private StartupReportConfiguration 
getStartupReportConfiguration(boolean isForki
                 isForking,
                 isEnableOutErrElements(),
                 isEnablePropertiesElement(),
+                isReportTestTimestamp(),
                 xmlReporter,
                 outReporter,
                 testsetReporter,
@@ -3652,6 +3661,10 @@ public boolean isEnablePropertiesElement() {
         return enablePropertiesElement;
     }
 
+    public boolean isReportTestTimestamp() {
+        return reportTestTimestamp;
+    }
+
     @SuppressWarnings("UnusedDeclaration")
     public void setEnablePropertiesElement(boolean enablePropertiesElement) {
         this.enablePropertiesElement = enablePropertiesElement;
diff --git 
a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/CommonReflector.java
 
b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/CommonReflector.java
index 83bf6a0d5..7ae2e54d5 100644
--- 
a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/CommonReflector.java
+++ 
b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/CommonReflector.java
@@ -91,6 +91,7 @@ private Object createStartupReportConfiguration(@Nonnull 
StartupReportConfigurat
                 boolean.class,
                 boolean.class,
                 boolean.class,
+                boolean.class,
                 statelessTestsetReporter,
                 consoleOutputReporter,
                 statelessTestsetInfoReporter,
@@ -111,6 +112,7 @@ private Object createStartupReportConfiguration(@Nonnull 
StartupReportConfigurat
             reporterConfiguration.isForking(),
             reporterConfiguration.isEnableOutErrElements(),
             reporterConfiguration.isEnablePropertiesElement(),
+            reporterConfiguration.isReportTestTimestamp(),
             reporterConfiguration.getXmlReporter().clone(surefireClassLoader),
             
reporterConfiguration.getConsoleOutputReporter().clone(surefireClassLoader),
             
reporterConfiguration.getTestsetReporter().clone(surefireClassLoader),
diff --git 
a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/StartupReportConfiguration.java
 
b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/StartupReportConfiguration.java
index cd805c7c7..45f642b5c 100644
--- 
a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/StartupReportConfiguration.java
+++ 
b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/StartupReportConfiguration.java
@@ -90,6 +90,8 @@ public final class StartupReportConfiguration {
 
     private final boolean enablePropertiesElement;
 
+    private final boolean reportTestTimestamp;
+
     private final SurefireStatelessReporter xmlReporter;
 
     private final SurefireConsoleOutputReporter consoleOutputReporter;
@@ -120,6 +122,7 @@ public StartupReportConfiguration(
             boolean isForking,
             boolean enableOutErrElements,
             boolean enablePropertiesElement,
+            boolean reportTestTimestamp,
             SurefireStatelessReporter xmlReporter,
             SurefireConsoleOutputReporter consoleOutputReporter,
             SurefireStatelessTestsetInfoReporter testsetReporter,
@@ -142,6 +145,7 @@ public StartupReportConfiguration(
         this.isForking = isForking;
         this.enableOutErrElements = enableOutErrElements;
         this.enablePropertiesElement = enablePropertiesElement;
+        this.reportTestTimestamp = reportTestTimestamp;
         this.xmlReporter = xmlReporter;
         this.consoleOutputReporter = consoleOutputReporter;
         this.testsetReporter = testsetReporter;
@@ -183,6 +187,7 @@ public StartupReportConfiguration(
                 isForking,
                 true,
                 true,
+                false,
                 xmlReporter,
                 consoleOutputReporter,
                 testsetReporter,
@@ -236,6 +241,7 @@ public StatelessReportEventListener<WrappedReportEntry, 
TestSetStats> instantiat
                 xsdSchemaLocation,
                 enableOutErrElements,
                 enablePropertiesElement,
+                reportTestTimestamp,
                 testClassMethodRunHistory);
 
         return xmlReporter.isDisable() ? null : 
xmlReporter.createListener(xmlReporterConfig);
@@ -306,6 +312,10 @@ public boolean isEnablePropertiesElement() {
         return enablePropertiesElement;
     }
 
+    public boolean isReportTestTimestamp() {
+        return reportTestTimestamp;
+    }
+
     private File resolveReportsDirectory(Integer forkNumber) {
         return forkNumber == null ? reportsDirectory : 
replaceForkThreadsInPath(reportsDirectory, forkNumber);
     }
diff --git 
a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/DefaultStatelessReportMojoConfiguration.java
 
b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/DefaultStatelessReportMojoConfiguration.java
index 44479a13d..1a1974f0f 100644
--- 
a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/DefaultStatelessReportMojoConfiguration.java
+++ 
b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/DefaultStatelessReportMojoConfiguration.java
@@ -45,6 +45,7 @@ public DefaultStatelessReportMojoConfiguration(
             String xsdSchemaLocation,
             boolean enableOutErrElements,
             boolean enablePropertiesElement,
+            boolean reportTestTimestamp,
             Map<String, Deque<WrappedReportEntry>> testClassMethodRunHistory) {
         super(
                 reportsDirectory,
@@ -53,7 +54,8 @@ public DefaultStatelessReportMojoConfiguration(
                 rerunFailingTestsCount,
                 xsdSchemaLocation,
                 enableOutErrElements,
-                enablePropertiesElement);
+                enablePropertiesElement,
+                reportTestTimestamp);
         this.testClassMethodRunHistory = testClassMethodRunHistory;
     }
 
diff --git 
a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/SurefireStatelessReporter.java
 
b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/SurefireStatelessReporter.java
index 0f0d4ca0d..c8cb31459 100644
--- 
a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/SurefireStatelessReporter.java
+++ 
b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/SurefireStatelessReporter.java
@@ -72,7 +72,8 @@ public StatelessReportEventListener<WrappedReportEntry, 
TestSetStats> createList
                 false,
                 false,
                 configuration.isEnableOutErrElements(),
-                configuration.isEnablePropertiesElement());
+                configuration.isEnablePropertiesElement(),
+                configuration.isReportTestTimestamp());
     }
 
     @Override
diff --git 
a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/junit5/JUnit5Xml30StatelessReporter.java
 
b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/junit5/JUnit5Xml30StatelessReporter.java
index d6719da26..9090a9751 100644
--- 
a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/junit5/JUnit5Xml30StatelessReporter.java
+++ 
b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/junit5/JUnit5Xml30StatelessReporter.java
@@ -110,7 +110,8 @@ public StatelessReportEventListener<WrappedReportEntry, 
TestSetStats> createList
                 getUsePhrasedTestCaseClassName(),
                 getUsePhrasedTestCaseMethodName(),
                 configuration.isEnableOutErrElements(),
-                configuration.isEnablePropertiesElement());
+                configuration.isEnablePropertiesElement(),
+                configuration.isReportTestTimestamp());
     }
 
     @Override
diff --git 
a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/report/NullStatelessXmlReporter.java
 
b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/report/NullStatelessXmlReporter.java
index d4738df40..bcc0d94cc 100644
--- 
a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/report/NullStatelessXmlReporter.java
+++ 
b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/report/NullStatelessXmlReporter.java
@@ -29,7 +29,7 @@ class NullStatelessXmlReporter extends StatelessXmlReporter {
     static final NullStatelessXmlReporter INSTANCE = new 
NullStatelessXmlReporter();
 
     private NullStatelessXmlReporter() {
-        super(null, null, false, 0, null, null, null, false, false, false, 
false, true, true);
+        super(null, null, false, 0, null, null, null, false, false, false, 
false, true, true, false);
     }
 
     @Override
diff --git 
a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/report/StatelessXmlReporter.java
 
b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/report/StatelessXmlReporter.java
index d67cb5440..17547db9b 100644
--- 
a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/report/StatelessXmlReporter.java
+++ 
b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/report/StatelessXmlReporter.java
@@ -26,6 +26,7 @@
 import java.io.OutputStreamWriter;
 import java.io.PrintWriter;
 import java.nio.file.Files;
+import java.time.Instant;
 import java.util.ArrayList;
 import java.util.Deque;
 import java.util.LinkedHashMap;
@@ -120,6 +121,12 @@ public class StatelessXmlReporter implements 
StatelessReportEventListener<Wrappe
 
     private final boolean enablePropertiesElement;
 
+    private final boolean reportTestTimestamp;
+
+    /**
+     * @deprecated Prefer adding a new constructor that accepts a 
configuration object, e.g.
+     *             {@link 
org.apache.maven.surefire.extensions.StatelessReportMojoConfiguration}.
+     */
     @Deprecated
     public StatelessXmlReporter(
             File reportsDirectory,
@@ -134,7 +141,8 @@ public StatelessXmlReporter(
             boolean phrasedClassName,
             boolean phrasedMethodName,
             boolean enableOutErrElements,
-            boolean enablePropertiesElement) {
+            boolean enablePropertiesElement,
+            boolean reportTestTimestamp) {
         this.reportsDirectory = reportsDirectory;
         this.reportNameSuffix = reportNameSuffix;
         this.trimStackTrace = trimStackTrace;
@@ -148,6 +156,7 @@ public StatelessXmlReporter(
         this.phrasedMethodName = phrasedMethodName;
         this.enableOutErrElements = enableOutErrElements;
         this.enablePropertiesElement = enablePropertiesElement;
+        this.reportTestTimestamp = reportTestTimestamp;
     }
 
     @Override
@@ -464,6 +473,11 @@ private void startTestElement(XMLWriter ppw, 
WrappedReportEntry report) throws I
         if (report.getElapsed() != null) {
             ppw.addAttribute("time", String.valueOf(report.getElapsed() / 
ONE_SECOND));
         }
+
+        if (reportTestTimestamp && report.getStartTime() > 0L) {
+            ppw.addAttribute(
+                    "timestamp", 
Instant.ofEpochMilli(report.getStartTime()).toString());
+        }
     }
 
     private void createTestSuiteElement(
@@ -490,6 +504,11 @@ private void createTestSuiteElement(
             ppw.addAttribute("time", String.valueOf(report.getElapsed() / 
ONE_SECOND));
         }
 
+        if (reportTestTimestamp && report.getStartTime() > 0L) {
+            ppw.addAttribute(
+                    "timestamp", 
Instant.ofEpochMilli(report.getStartTime()).toString());
+        }
+
         // Count actual unique test methods and their final results from 
classMethodStatistics (accumulated across
         // reruns)
         int actualTestCount = 0;
diff --git 
a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/report/TestSetRunListener.java
 
b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/report/TestSetRunListener.java
index 3879ef54d..b3cb43616 100644
--- 
a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/report/TestSetRunListener.java
+++ 
b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/report/TestSetRunListener.java
@@ -288,21 +288,26 @@ public void testAssumptionFailure(ReportEntry report) {
 
     private WrappedReportEntry wrap(ReportEntry other, ReportEntryType 
reportEntryType) {
         int estimatedElapsed = 0;
+        // Skipped tests don't call testStart, and thus fallback on current 
time
+        long startTimestamp = System.currentTimeMillis();
+        TestSetStats testSetStats = getTestSetStats(other);
+
         if (reportEntryType != SKIPPED) {
             Integer etime = other.getElapsed();
-            estimatedElapsed = etime == null ? 
getTestSetStats(other).getElapsedSinceLastStart() : etime;
+            estimatedElapsed = etime == null ? 
testSetStats.getElapsedSinceLastStart() : etime;
+            startTimestamp = testSetStats.getTestStartAt();
         }
 
-        return new WrappedReportEntry(other, reportEntryType, 
estimatedElapsed, testStdOut, testStdErr);
+        return new WrappedReportEntry(other, reportEntryType, startTimestamp, 
estimatedElapsed, testStdOut, testStdErr);
     }
 
     private WrappedReportEntry wrapTestSet(TestSetReportEntry other) {
+        TestSetStats testSetStats = getTestSetStats(other);
         return new WrappedReportEntry(
                 other,
                 null,
-                other.getElapsed() != null
-                        ? other.getElapsed()
-                        : getTestSetStats(other).getElapsedSinceTestSetStart(),
+                testSetStats.getTestSetStartAt(),
+                other.getElapsed() != null ? other.getElapsed() : 
testSetStats.getElapsedSinceTestSetStart(),
                 testStdOut,
                 testStdErr,
                 other.getSystemProperties());
diff --git 
a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/report/TestSetStats.java
 
b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/report/TestSetStats.java
index de4a9ce66..6d37d9eff 100644
--- 
a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/report/TestSetStats.java
+++ 
b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/report/TestSetStats.java
@@ -82,11 +82,19 @@ public void testSetStart() {
         lastStartAt = testSetStartAt;
     }
 
+    long getTestSetStartAt() {
+        return testSetStartAt;
+    }
+
     public void testStart() {
         testStartAt = System.currentTimeMillis();
         lastStartAt = testStartAt;
     }
 
+    long getTestStartAt() {
+        return testStartAt;
+    }
+
     private void finishTest(WrappedReportEntry reportEntry) {
         reportEntries.add(reportEntry);
         incrementCompletedCount();
diff --git 
a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/report/WrappedReportEntry.java
 
b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/report/WrappedReportEntry.java
index c443d27cb..4c5f7de21 100644
--- 
a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/report/WrappedReportEntry.java
+++ 
b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/report/WrappedReportEntry.java
@@ -44,6 +44,8 @@ public class WrappedReportEntry implements TestSetReportEntry 
{
 
     private final ReportEntryType reportEntryType;
 
+    private final long startTime;
+
     private final Integer elapsed;
 
     private final Utf8RecodingDeferredFileOutputStream stdout;
@@ -63,12 +65,14 @@ public class WrappedReportEntry implements 
TestSetReportEntry {
     public WrappedReportEntry(
             ReportEntry original,
             ReportEntryType reportEntryType,
+            long startTime,
             Integer estimatedElapsed,
             Utf8RecodingDeferredFileOutputStream stdout,
             Utf8RecodingDeferredFileOutputStream stdErr,
             Map<String, String> systemProperties) {
         this.original = original;
         this.reportEntryType = reportEntryType;
+        this.startTime = startTime;
         this.elapsed = estimatedElapsed;
         this.stdout = stdout;
         this.stdErr = stdErr;
@@ -78,10 +82,15 @@ public WrappedReportEntry(
     public WrappedReportEntry(
             ReportEntry original,
             ReportEntryType reportEntryType,
+            long startTime,
             Integer estimatedElapsed,
             Utf8RecodingDeferredFileOutputStream stdout,
             Utf8RecodingDeferredFileOutputStream stdErr) {
-        this(original, reportEntryType, estimatedElapsed, stdout, stdErr, 
Collections.emptyMap());
+        this(original, reportEntryType, startTime, estimatedElapsed, stdout, 
stdErr, Collections.emptyMap());
+    }
+
+    public long getStartTime() {
+        return startTime;
     }
 
     @Override
diff --git 
a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/CommonReflectorTest.java
 
b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/CommonReflectorTest.java
index 510d5d1d5..8f9ba782a 100644
--- 
a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/CommonReflectorTest.java
+++ 
b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/CommonReflectorTest.java
@@ -85,6 +85,7 @@ public void setup() {
                 false,
                 true,
                 true,
+                false,
                 xmlReporter,
                 consoleOutputReporter,
                 infoReporter,
diff --git 
a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/ForkStarterTest.java
 
b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/ForkStarterTest.java
index 6a774ace8..f5507a924 100644
--- 
a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/ForkStarterTest.java
+++ 
b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/ForkStarterTest.java
@@ -165,6 +165,7 @@ public void processShouldExitWithoutSayingGoodBye() throws 
Exception {
                 true,
                 true,
                 true,
+                false,
                 xmlReporter,
                 outputReporter,
                 statelessTestsetInfoReporter,
@@ -253,6 +254,7 @@ public void processShouldWaitForAck() throws Exception {
                 true,
                 true,
                 true,
+                false,
                 xmlReporter,
                 outputReporter,
                 statelessTestsetInfoReporter,
diff --git 
a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/TestSetMockReporterFactory.java
 
b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/TestSetMockReporterFactory.java
index fe7791ea6..e256250a2 100644
--- 
a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/TestSetMockReporterFactory.java
+++ 
b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/TestSetMockReporterFactory.java
@@ -69,6 +69,7 @@ private static StartupReportConfiguration defaultValue() {
                 true,
                 true,
                 true,
+                false,
                 new SurefireStatelessReporter(),
                 new SurefireConsoleOutputReporter(),
                 new SurefireStatelessTestsetInfoReporter(),
diff --git 
a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/extensions/StatelessReporterTest.java
 
b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/extensions/StatelessReporterTest.java
index c628f8b14..4fe2a49d5 100644
--- 
a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/extensions/StatelessReporterTest.java
+++ 
b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/extensions/StatelessReporterTest.java
@@ -66,7 +66,7 @@ public void shouldCreateConsoleListener() {
         String schema = 
"https://maven.apache.org/surefire/maven-surefire-plugin/xsd/surefire-test-report.xsd";;
         Map<String, Deque<WrappedReportEntry>> testClassMethodRunHistory = new 
HashMap<>();
         DefaultStatelessReportMojoConfiguration config = new 
DefaultStatelessReportMojoConfiguration(
-                reportsDirectory, reportNameSuffix, true, 5, schema, true, 
true, testClassMethodRunHistory);
+                reportsDirectory, reportNameSuffix, true, 5, schema, true, 
true, false, testClassMethodRunHistory);
         SurefireStatelessReporter extension = new SurefireStatelessReporter();
 
         assertThat(extension.getVersion()).isEqualTo("3.0.2");
@@ -141,7 +141,7 @@ public void shouldCreateJUnit5ConsoleListener() {
         String schema = 
"https://maven.apache.org/surefire/maven-surefire-plugin/xsd/surefire-test-report.xsd";;
         Map<String, Deque<WrappedReportEntry>> testClassMethodRunHistory = new 
HashMap<>();
         DefaultStatelessReportMojoConfiguration config = new 
DefaultStatelessReportMojoConfiguration(
-                reportsDirectory, reportNameSuffix, true, 5, schema, true, 
true, testClassMethodRunHistory);
+                reportsDirectory, reportNameSuffix, true, 5, schema, true, 
true, false, testClassMethodRunHistory);
         JUnit5Xml30StatelessReporter extension = new 
JUnit5Xml30StatelessReporter();
 
         assertThat(extension.getVersion()).isEqualTo("3.0.2");
diff --git 
a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/report/DefaultReporterFactoryTest.java
 
b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/report/DefaultReporterFactoryTest.java
index 12ad8db8c..918b9c106 100644
--- 
a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/report/DefaultReporterFactoryTest.java
+++ 
b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/report/DefaultReporterFactoryTest.java
@@ -94,6 +94,7 @@ public void testMergeTestHistoryResult() throws Exception {
                 false,
                 true,
                 true,
+                false,
                 new SurefireStatelessReporter(),
                 new SurefireConsoleOutputReporter(),
                 new SurefireStatelessTestsetInfoReporter(),
@@ -330,6 +331,7 @@ public void testLogger() {
                 false,
                 true,
                 true,
+                false,
                 new SurefireStatelessReporter(),
                 new SurefireConsoleOutputReporter(),
                 new SurefireStatelessTestsetInfoReporter(),
@@ -397,6 +399,7 @@ public void testCreateReporterWithZeroStatistics() {
                 false,
                 true,
                 true,
+                false,
                 new SurefireStatelessReporter(),
                 new SurefireConsoleOutputReporter(),
                 new SurefireStatelessTestsetInfoReporter(),
diff --git 
a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/report/StatelessXmlReporterTest.java
 
b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/report/StatelessXmlReporterTest.java
index 362facb15..59f1a9b38 100644
--- 
a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/report/StatelessXmlReporterTest.java
+++ 
b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/report/StatelessXmlReporterTest.java
@@ -111,13 +111,14 @@ public void testFileNameWithoutSuffix() {
                 false,
                 false,
                 true,
-                true);
+                true,
+                false);
         reporter.cleanTestHistoryMap();
 
         ReportEntry reportEntry = new SimpleReportEntry(
                 NORMAL_RUN, 0L, getClass().getName(), null, 
getClass().getName(), null, 12);
-        WrappedReportEntry testSetReportEntry =
-                new WrappedReportEntry(reportEntry, ReportEntryType.SUCCESS, 
12, null, null, systemProps());
+        WrappedReportEntry testSetReportEntry = new WrappedReportEntry(
+                reportEntry, ReportEntryType.SUCCESS, 1771085631L, 12, null, 
null, systemProps());
         stats.testSucceeded(testSetReportEntry);
         reporter.testSetCompleted(testSetReportEntry, stats);
 
@@ -131,7 +132,7 @@ public void testAllFieldsSerialized() throws IOException {
         ReportEntry reportEntry =
                 new SimpleReportEntry(NORMAL_RUN, 0L, getClass().getName(), 
null, TEST_ONE, null, 12);
         WrappedReportEntry testSetReportEntry =
-                new WrappedReportEntry(reportEntry, SUCCESS, 12, null, null, 
systemProps());
+                new WrappedReportEntry(reportEntry, SUCCESS, 1771085631L, 12, 
null, null, systemProps());
         expectedReportFile = new File(reportDir, "TEST-" + 
getClass().getName() + ".xml");
 
         stats.testSucceeded(testSetReportEntry);
@@ -155,6 +156,7 @@ public void testAllFieldsSerialized() throws IOException {
         WrappedReportEntry t2 = new WrappedReportEntry(
                 new SimpleReportEntry(NORMAL_RUN, 0L, getClass().getName(), 
null, TEST_TWO, null, stackTraceWriter, 13),
                 ReportEntryType.ERROR,
+                1771085631L,
                 13,
                 stdOut,
                 stdErr);
@@ -173,7 +175,8 @@ public void testAllFieldsSerialized() throws IOException {
                 false,
                 false,
                 true,
-                true);
+                true,
+                false);
         reporter.testSetCompleted(testSetReportEntry, stats);
 
         FileInputStream fileInputStream = new 
FileInputStream(expectedReportFile);
@@ -213,6 +216,7 @@ public void testOutputRerunFlakyFailure() throws 
IOException {
         WrappedReportEntry testSetReportEntry = new WrappedReportEntry(
                 new SimpleReportEntry(NORMAL_RUN, 0L, getClass().getName(), 
null, TEST_ONE, null, 12),
                 ReportEntryType.SUCCESS,
+                1771085631L,
                 12,
                 null,
                 null,
@@ -233,6 +237,7 @@ public void testOutputRerunFlakyFailure() throws 
IOException {
         WrappedReportEntry testTwoFirstError = new WrappedReportEntry(
                 new SimpleReportEntry(NORMAL_RUN, 0L, cls, null, TEST_TWO, 
null, stackTraceWriterOne, 5),
                 ReportEntryType.ERROR,
+                1771085631L,
                 5,
                 createStdOutput(firstRunOut),
                 createStdOutput(firstRunErr));
@@ -240,6 +245,7 @@ public void testOutputRerunFlakyFailure() throws 
IOException {
         WrappedReportEntry testTwoSecondError = new WrappedReportEntry(
                 new SimpleReportEntry(RERUN_TEST_AFTER_FAILURE, 1L, cls, null, 
TEST_TWO, null, stackTraceWriterTwo, 13),
                 ReportEntryType.ERROR,
+                1771085631L,
                 13,
                 createStdOutput(secondRunOut),
                 createStdOutput(secondRunErr));
@@ -247,6 +253,7 @@ public void testOutputRerunFlakyFailure() throws 
IOException {
         WrappedReportEntry testThreeFirstRun = new WrappedReportEntry(
                 new SimpleReportEntry(NORMAL_RUN, 2L, cls, null, TEST_THREE, 
null, stackTraceWriterOne, 13),
                 ReportEntryType.FAILURE,
+                1771085631L,
                 13,
                 createStdOutput(firstRunOut),
                 createStdOutput(firstRunErr));
@@ -255,6 +262,7 @@ public void testOutputRerunFlakyFailure() throws 
IOException {
                 new SimpleReportEntry(
                         RERUN_TEST_AFTER_FAILURE, 3L, cls, null, TEST_THREE, 
null, stackTraceWriterTwo, 2),
                 ReportEntryType.SUCCESS,
+                1771085631L,
                 2,
                 createStdOutput(secondRunOut),
                 createStdOutput(secondRunErr));
@@ -277,7 +285,8 @@ public void testOutputRerunFlakyFailure() throws 
IOException {
                 false,
                 false,
                 true,
-                true);
+                true,
+                false);
 
         reporter.testSetCompleted(testSetReportEntry, stats);
         reporter.testSetCompleted(testSetReportEntry, rerunStats);
@@ -352,6 +361,7 @@ public void testOutputRerunFlakyAssumption() throws 
IOException {
                 new SimpleReportEntry(
                         NORMAL_RUN, 1L, getClass().getName(), null, TEST_TWO, 
null, stackTraceWriterOne, 5),
                 ERROR,
+                1771085631L,
                 5,
                 createStdOutput(firstRunOut),
                 createStdOutput(firstRunErr));
@@ -369,6 +379,7 @@ public void testOutputRerunFlakyAssumption() throws 
IOException {
                         stackTraceWriterTwo,
                         13),
                 SKIPPED,
+                1771085631L,
                 13,
                 createStdOutput(secondRunOut),
                 createStdOutput(secondRunErr));
@@ -376,12 +387,26 @@ public void testOutputRerunFlakyAssumption() throws 
IOException {
         rerunStats.testSucceeded(testTwoSecondError);
 
         StatelessXmlReporter reporter = new StatelessXmlReporter(
-                reportDir, null, false, 1, new HashMap<>(), XSD, "3.0.2", 
false, false, false, false, true, true);
+                reportDir,
+                null,
+                false,
+                1,
+                new HashMap<>(),
+                XSD,
+                "3.0.2",
+                false,
+                false,
+                false,
+                false,
+                true,
+                true,
+                false);
 
         WrappedReportEntry testSetReportEntry = new WrappedReportEntry(
                 new SimpleReportEntry(
                         RERUN_TEST_AFTER_FAILURE, 1L, getClass().getName(), 
null, null, null, stackTraceWriterOne, 5),
                 ERROR,
+                1771085631L,
                 20,
                 createStdOutput(firstRunOut),
                 createStdOutput(firstRunErr));
@@ -535,12 +560,26 @@ public void 
testReporterHandlesATestWithoutMessageAndWithEmptyStackTrace() {
                 new SimpleReportEntry(
                         NORMAL_RUN, 1L, getClass().getName(), null, "a test 
name", null, stackTraceWriterOne, 5),
                 ERROR,
+                1771085631L,
                 5,
                 null,
                 null);
 
         StatelessXmlReporter reporter = new StatelessXmlReporter(
-                reportDir, null, false, 1, new HashMap<>(), XSD, "3.0.2", 
false, false, false, false, true, true);
+                reportDir,
+                null,
+                false,
+                1,
+                new HashMap<>(),
+                XSD,
+                "3.0.2",
+                false,
+                false,
+                false,
+                false,
+                true,
+                true,
+                false);
 
         reporter.testSetCompleted(testReport, stats);
     }
diff --git 
a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/report/WrappedReportEntryTest.java
 
b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/report/WrappedReportEntryTest.java
index 6d337467a..04dfb2574 100644
--- 
a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/report/WrappedReportEntryTest.java
+++ 
b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/report/WrappedReportEntryTest.java
@@ -35,7 +35,12 @@ public class WrappedReportEntryTest extends TestCase {
     public void testClassNameOnly() {
         String className = "surefire.testcase.JunitParamsTest";
         WrappedReportEntry wr = new WrappedReportEntry(
-                new SimpleReportEntry(NORMAL_RUN, 1L, className, null, null, 
null), SUCCESS, 12, null, null);
+                new SimpleReportEntry(NORMAL_RUN, 1L, className, null, null, 
null),
+                SUCCESS,
+                1771085631L,
+                12,
+                null,
+                null);
         final String reportName = wr.getReportSourceName();
         assertEquals("surefire.testcase.JunitParamsTest.null", 
wr.getClassMethodName());
         assertEquals("surefire.testcase.JunitParamsTest", reportName);
@@ -47,7 +52,7 @@ public void testClassNameOnly() {
     public void testRegular() {
         ReportEntry reportEntry =
                 new SimpleReportEntry(NORMAL_RUN, 1L, 
"surefire.testcase.JunitParamsTest", null, "testSum", null);
-        WrappedReportEntry wr = new WrappedReportEntry(reportEntry, null, 12, 
null, null);
+        WrappedReportEntry wr = new WrappedReportEntry(reportEntry, null, 
1771085631L, 12, null, null);
         assertEquals("surefire.testcase.JunitParamsTest.testSum", 
wr.getClassMethodName());
         assertEquals("surefire.testcase.JunitParamsTest", 
wr.getReportSourceName());
         assertEquals("surefire.testcase.JunitParamsTest", 
wr.getReportSourceName(""));
@@ -66,7 +71,7 @@ public void testRegular() {
     public void testDisplayNames() {
         ReportEntry reportEntry = new SimpleReportEntry(
                 NORMAL_RUN, 0L, "surefire.testcase.JunitParamsTest", "dn1", 
"testSum", "dn2", "exception");
-        WrappedReportEntry wr = new WrappedReportEntry(reportEntry, ERROR, 12, 
null, null);
+        WrappedReportEntry wr = new WrappedReportEntry(reportEntry, ERROR, 
1771085631L, 12, null, null);
         assertEquals("surefire.testcase.JunitParamsTest.testSum", 
wr.getClassMethodName());
         assertEquals("dn1", wr.getReportSourceName());
         assertEquals("dn1(BDD)", wr.getReportSourceName("BDD"));
@@ -90,7 +95,7 @@ public void testEqualDisplayNames() {
                 "surefire.testcase.JunitParamsTest",
                 "testSum",
                 "testSum");
-        WrappedReportEntry wr = new WrappedReportEntry(reportEntry, FAILURE, 
12, null, null);
+        WrappedReportEntry wr = new WrappedReportEntry(reportEntry, FAILURE, 
1771085631L, 12, null, null);
         assertEquals("surefire.testcase.JunitParamsTest", 
wr.getReportSourceName());
         assertEquals("surefire.testcase.JunitParamsTest(BDD)", 
wr.getReportSourceName("BDD"));
         assertEquals("testSum", wr.getReportName());
@@ -102,7 +107,7 @@ public void testEqualDisplayNames() {
     public void testGetReportNameWithParams() {
         String className = "[0] 1\u002C 2\u002C 3 (testSum)";
         ReportEntry reportEntry = new SimpleReportEntry(NORMAL_RUN, 1L, 
className, null, null, null);
-        WrappedReportEntry wr = new WrappedReportEntry(reportEntry, SKIPPED, 
12, null, null);
+        WrappedReportEntry wr = new WrappedReportEntry(reportEntry, SKIPPED, 
1771085631L, 12, null, null);
         final String reportName = wr.getReportSourceName();
         assertEquals("[0] 1, 2, 3 (testSum)", reportName);
         assertFalse(wr.isSucceeded());
@@ -114,7 +119,7 @@ public void 
testGetReportNameWithGroupWhenSourceTextIsNull() {
         String className = "ClassName";
         String classText = null;
         ReportEntry reportEntry = new SimpleReportEntry(NORMAL_RUN, 1L, 
className, classText, null, null);
-        WrappedReportEntry wr = new WrappedReportEntry(reportEntry, SKIPPED, 
12, null, null);
+        WrappedReportEntry wr = new WrappedReportEntry(reportEntry, SKIPPED, 
1771085631L, 12, null, null);
         assertEquals(className, wr.getReportNameWithGroup());
     }
 
@@ -122,7 +127,7 @@ public void 
testGetReportNameWithGroupWhenSourceTextIsEmpty() {
         String className = "ClassName";
         String classText = "";
         ReportEntry reportEntry = new SimpleReportEntry(NORMAL_RUN, 1L, 
className, classText, null, null);
-        WrappedReportEntry wr = new WrappedReportEntry(reportEntry, SKIPPED, 
12, null, null);
+        WrappedReportEntry wr = new WrappedReportEntry(reportEntry, SKIPPED, 
1771085631L, 12, null, null);
         assertEquals(className, wr.getReportNameWithGroup());
     }
 
@@ -130,7 +135,7 @@ public void 
testGetReportNameWithGroupWhenSourceTextIsBlank() {
         String className = "ClassName";
         String classText = "  ";
         ReportEntry reportEntry = new SimpleReportEntry(NORMAL_RUN, 1L, 
className, classText, null, null);
-        WrappedReportEntry wr = new WrappedReportEntry(reportEntry, SKIPPED, 
12, null, null);
+        WrappedReportEntry wr = new WrappedReportEntry(reportEntry, SKIPPED, 
1771085631L, 12, null, null);
         assertEquals(className, wr.getReportNameWithGroup());
     }
 
@@ -138,14 +143,14 @@ public void 
testGetReportNameWithGroupWhenSourceTextIsProvided() {
         String className = "ClassName";
         String classText = "The Class Name";
         ReportEntry reportEntry = new SimpleReportEntry(NORMAL_RUN, 1L, 
className, classText, null, null);
-        WrappedReportEntry wr = new WrappedReportEntry(reportEntry, SKIPPED, 
12, null, null);
+        WrappedReportEntry wr = new WrappedReportEntry(reportEntry, SKIPPED, 
1771085631L, 12, null, null);
         assertEquals(classText, wr.getReportNameWithGroup());
     }
 
     public void testElapsed() {
         String className = "[0] 1\u002C 2\u002C 3 (testSum)";
         ReportEntry reportEntry = new SimpleReportEntry(NORMAL_RUN, 1L, 
className, null, null, null);
-        WrappedReportEntry wr = new WrappedReportEntry(reportEntry, null, 12, 
null, null);
+        WrappedReportEntry wr = new WrappedReportEntry(reportEntry, null, 
1771085631L, 12, null, null);
         String elapsedTimeSummary = wr.getElapsedTimeSummary();
         assertEquals("[0] 1, 2, 3 (testSum) -- Time elapsed: 0.012 s", 
elapsedTimeSummary);
     }
diff --git 
a/maven-surefire-common/src/test/java/org/apache/maven/surefire/report/FileReporterTest.java
 
b/maven-surefire-common/src/test/java/org/apache/maven/surefire/report/FileReporterTest.java
index 12c159023..d9292ee96 100644
--- 
a/maven-surefire-common/src/test/java/org/apache/maven/surefire/report/FileReporterTest.java
+++ 
b/maven-surefire-common/src/test/java/org/apache/maven/surefire/report/FileReporterTest.java
@@ -47,7 +47,7 @@ public void testFileNameWithoutSuffix() {
         File reportDir = new File("target");
         reportEntry = new SimpleReportEntry(NORMAL_RUN, 1L, 
getClass().getName(), null, TEST_NAME, null);
         WrappedReportEntry wrappedReportEntry =
-                new WrappedReportEntry(reportEntry, ReportEntryType.SUCCESS, 
12, null, null);
+                new WrappedReportEntry(reportEntry, ReportEntryType.SUCCESS, 
1771085631L, 12, null, null);
         reporter = new FileReporter(reportDir, null, Charset.defaultCharset(), 
false, false, false);
         reporter.testSetCompleted(wrappedReportEntry, createTestSetStats(), 
new ArrayList<>());
 
@@ -68,7 +68,7 @@ public void testFileNameWithSuffix() {
         String suffixText = "sampleSuffixText";
         reportEntry = new SimpleReportEntry(NORMAL_RUN, 1L, 
getClass().getName(), null, TEST_NAME, null);
         WrappedReportEntry wrappedReportEntry =
-                new WrappedReportEntry(reportEntry, ReportEntryType.SUCCESS, 
12, null, null);
+                new WrappedReportEntry(reportEntry, ReportEntryType.SUCCESS, 
1771085631L, 12, null, null);
         reporter = new FileReporter(reportDir, suffixText, 
Charset.defaultCharset(), false, false, false);
         reporter.testSetCompleted(wrappedReportEntry, createTestSetStats(), 
new ArrayList<>());
 
diff --git 
a/maven-surefire-plugin/src/site/resources/xsd/surefire-test-report.xsd 
b/maven-surefire-plugin/src/site/resources/xsd/surefire-test-report.xsd
index a70bed30d..72ee09047 100644
--- a/maven-surefire-plugin/src/site/resources/xsd/surefire-test-report.xsd
+++ b/maven-surefire-plugin/src/site/resources/xsd/surefire-test-report.xsd
@@ -116,12 +116,14 @@
             <xs:attribute name="classname" type="xs:string"/>
             <xs:attribute name="group" type="xs:string"/>
             <xs:attribute name="time" type="xs:float" use="required"/>
+            <xs:attribute name="timestamp" type="xs:dateTime"/>
           </xs:complexType>
         </xs:element>
       </xs:sequence>
       <xs:attribute name="version" type="xs:string"/>
       <xs:attribute name="name" type="xs:string" use="required"/>
       <xs:attribute name="time" type="xs:float"/>
+      <xs:attribute name="timestamp" type="xs:dateTime"/>
       <xs:attribute name="tests" type="xs:string" use="required"/>
       <xs:attribute name="errors" type="xs:string" use="required"/>
       <xs:attribute name="skipped" type="xs:string" use="required"/>
diff --git 
a/surefire-extensions-api/src/main/java/org/apache/maven/surefire/extensions/StatelessReportMojoConfiguration.java
 
b/surefire-extensions-api/src/main/java/org/apache/maven/surefire/extensions/StatelessReportMojoConfiguration.java
index 694e50b4d..ac4d666da 100644
--- 
a/surefire-extensions-api/src/main/java/org/apache/maven/surefire/extensions/StatelessReportMojoConfiguration.java
+++ 
b/surefire-extensions-api/src/main/java/org/apache/maven/surefire/extensions/StatelessReportMojoConfiguration.java
@@ -40,6 +40,9 @@ public class StatelessReportMojoConfiguration {
 
     private final boolean enablePropertiesElement;
 
+    private final boolean reportTestTimestamp;
+
+    @SuppressWarnings("checkstyle:parameternumber")
     public StatelessReportMojoConfiguration(
             File reportsDirectory,
             String reportNameSuffix,
@@ -47,7 +50,8 @@ public StatelessReportMojoConfiguration(
             int rerunFailingTestsCount,
             String xsdSchemaLocation,
             boolean enableOutErrElements,
-            boolean enablePropertiesElement) {
+            boolean enablePropertiesElement,
+            boolean reportTestTimestamp) {
         this.reportsDirectory = reportsDirectory;
         this.reportNameSuffix = reportNameSuffix;
         this.trimStackTrace = trimStackTrace;
@@ -55,6 +59,7 @@ public StatelessReportMojoConfiguration(
         this.xsdSchemaLocation = xsdSchemaLocation;
         this.enableOutErrElements = enableOutErrElements;
         this.enablePropertiesElement = enablePropertiesElement;
+        this.reportTestTimestamp = reportTestTimestamp;
     }
 
     public File getReportsDirectory() {
@@ -84,4 +89,8 @@ public boolean isEnableOutErrElements() {
     public boolean isEnablePropertiesElement() {
         return enablePropertiesElement;
     }
+
+    public boolean isReportTestTimestamp() {
+        return reportTestTimestamp;
+    }
 }
diff --git 
a/surefire-its/src/test/java/org/apache/maven/surefire/its/ReportTestTimestampIT.java
 
b/surefire-its/src/test/java/org/apache/maven/surefire/its/ReportTestTimestampIT.java
new file mode 100644
index 000000000..5da12f136
--- /dev/null
+++ 
b/surefire-its/src/test/java/org/apache/maven/surefire/its/ReportTestTimestampIT.java
@@ -0,0 +1,96 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.maven.surefire.its;
+
+import java.time.OffsetDateTime;
+import java.time.format.DateTimeFormatter;
+import java.time.format.DateTimeParseException;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.apache.maven.surefire.its.fixture.OutputValidator;
+import org.apache.maven.surefire.its.fixture.SurefireJUnit4IntegrationTestCase;
+import org.apache.maven.surefire.its.fixture.TestFile;
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+public class ReportTestTimestampIT extends SurefireJUnit4IntegrationTestCase {
+    @Test
+    public void testReportTestTimestampDisabled() {
+        OutputValidator outputValidator = 
unpack("/disable-timestamp-element").executeTest();
+        TestFile reportFile = 
outputValidator.getSurefireReportsXmlFile("TEST-TestA.xml");
+
+        String xml = reportFile.slurpFile();
+
+        // testsuite has no timestamp
+        assertFalse(
+                "Unexpected timestamp on testsuite", 
xml.matches(".*<testsuite[^>]*\\btimestamp=\"[^\"]+\"[^>]*>.*"));
+
+        // no testcase has timestamp
+        int testcaseWithTsCount = countMatches(xml, 
"<testcase[^>]*\\btimestamp=\"[^\"]+\"");
+        assertEquals("Unexpected timestamp on testcase", 0, 
testcaseWithTsCount);
+    }
+
+    @Test
+    public void testReportTestTimestampEnabled() {
+        OutputValidator outputValidator = 
unpack("/enable-timestamp-element").executeTest();
+        TestFile reportFile = 
outputValidator.getSurefireReportsXmlFile("TEST-TestA.xml");
+
+        String xml = reportFile.slurpFile();
+
+        // testsuite has timestamp
+        assertTrue("Missing timestamp on testsuite", 
xml.matches(".*<testsuite[^>]*\\btimestamp=\"[^\"]+\"[^>]*>.*"));
+
+        // each testcase has timestamp
+        int testcaseCount = countMatches(xml, "<testcase");
+        int testcaseWithTsCount = countMatches(xml, 
"<testcase[^>]*\\btimestamp=\"[^\"]+\"");
+        assertEquals("Not all testcases have timestamp", testcaseCount, 
testcaseWithTsCount);
+
+        assertAllTimestampsIso(xml);
+    }
+
+    private static int countMatches(String input, String regex) {
+        Matcher matcher = Pattern.compile(regex).matcher(input);
+        int count = 0;
+        while (matcher.find()) {
+            count++;
+        }
+        return count;
+    }
+
+    private static void assertAllTimestampsIso(String xml) {
+        Matcher matcher = 
Pattern.compile("timestamp=\"([^\"]+)\"").matcher(xml);
+        while (matcher.find()) {
+            String timestamp = matcher.group(1);
+            assertTrue("Invalid ISO timestamp: " + timestamp, 
isIsoTimestamp(timestamp));
+        }
+    }
+
+    private static boolean isIsoTimestamp(String timestamp) {
+        try {
+            DateTimeFormatter.ISO_OFFSET_DATE_TIME.parse(timestamp, 
OffsetDateTime::from);
+            return true;
+        } catch (DateTimeParseException ex) {
+            return false;
+        }
+    }
+}
diff --git a/surefire-its/src/test/resources/disable-timestamp-element/pom.xml 
b/surefire-its/src/test/resources/disable-timestamp-element/pom.xml
new file mode 100644
index 000000000..15e20ea60
--- /dev/null
+++ b/surefire-its/src/test/resources/disable-timestamp-element/pom.xml
@@ -0,0 +1,58 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ Licensed to the Apache Software Foundation (ASF) under one
+  ~ or more contributor license agreements.  See the NOTICE file
+  ~ distributed with this work for additional information
+  ~ regarding copyright ownership.  The ASF licenses this file
+  ~ to you under the Apache License, Version 2.0 (the
+  ~ "License"); you may not use this file except in compliance
+  ~ with the License.  You may obtain a copy of the License at
+  ~
+  ~     http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing,
+  ~ software distributed under the License is distributed on an
+  ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+  ~ KIND, either express or implied.  See the License for the
+  ~ specific language governing permissions and limitations
+  ~ under the License.
+  -->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0"; 
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance";
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
http://maven.apache.org/xsd/maven-4.0.0.xsd";>
+  <modelVersion>4.0.0</modelVersion>
+
+  <parent>
+    <groupId>org.apache.maven.surefire</groupId>
+    <artifactId>it-parent</artifactId>
+    <version>1.0</version>
+    <relativePath>../pom.xml</relativePath>
+  </parent>
+
+  <groupId>org.apache.maven.plugins.surefire</groupId>
+  <artifactId>disable-timestamp-element</artifactId>
+  <version>1.0</version>
+  <url>http://maven.apache.org</url>
+
+  <dependencies>
+    <dependency>
+      <groupId>junit</groupId>
+      <artifactId>junit</artifactId>
+      <version>4.0</version>
+    </dependency>
+  </dependencies>
+
+  <build>
+    <plugins>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-surefire-plugin</artifactId>
+        <version>${surefire.version}</version>
+        <configuration>
+          <testFailureIgnore>true</testFailureIgnore>
+          <reportTestTimestamp>false</reportTestTimestamp>
+        </configuration>
+      </plugin>
+    </plugins>
+  </build>
+</project>
diff --git 
a/surefire-its/src/test/resources/disable-timestamp-element/src/test/java/TestA.java
 
b/surefire-its/src/test/resources/disable-timestamp-element/src/test/java/TestA.java
new file mode 100644
index 000000000..66391e702
--- /dev/null
+++ 
b/surefire-its/src/test/resources/disable-timestamp-element/src/test/java/TestA.java
@@ -0,0 +1,18 @@
+import org.junit.Test;
+import org.junit.Ignore;
+
+public class TestA {
+    @Ignore("Skipping this test still has to report a processing event 
timestamp")
+    @Test
+    public void skipped() throws Exception {
+    }
+
+    @Test
+    public void success() throws Exception {
+    }
+
+    @Test
+    public void failure() throws Exception {
+        throw new Exception("This test is supposed to fail.");
+    }
+}
diff --git a/surefire-its/src/test/resources/enable-timestamp-element/pom.xml 
b/surefire-its/src/test/resources/enable-timestamp-element/pom.xml
new file mode 100644
index 000000000..4bf016d99
--- /dev/null
+++ b/surefire-its/src/test/resources/enable-timestamp-element/pom.xml
@@ -0,0 +1,58 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ Licensed to the Apache Software Foundation (ASF) under one
+  ~ or more contributor license agreements.  See the NOTICE file
+  ~ distributed with this work for additional information
+  ~ regarding copyright ownership.  The ASF licenses this file
+  ~ to you under the Apache License, Version 2.0 (the
+  ~ "License"); you may not use this file except in compliance
+  ~ with the License.  You may obtain a copy of the License at
+  ~
+  ~     http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing,
+  ~ software distributed under the License is distributed on an
+  ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+  ~ KIND, either express or implied.  See the License for the
+  ~ specific language governing permissions and limitations
+  ~ under the License.
+  -->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0"; 
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance";
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
http://maven.apache.org/xsd/maven-4.0.0.xsd";>
+  <modelVersion>4.0.0</modelVersion>
+
+  <parent>
+    <groupId>org.apache.maven.surefire</groupId>
+    <artifactId>it-parent</artifactId>
+    <version>1.0</version>
+    <relativePath>../pom.xml</relativePath>
+  </parent>
+
+  <groupId>org.apache.maven.plugins.surefire</groupId>
+  <artifactId>enable-timestamp-element</artifactId>
+  <version>1.0</version>
+  <url>http://maven.apache.org</url>
+
+  <dependencies>
+    <dependency>
+      <groupId>junit</groupId>
+      <artifactId>junit</artifactId>
+      <version>4.0</version>
+    </dependency>
+  </dependencies>
+
+  <build>
+    <plugins>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-surefire-plugin</artifactId>
+        <version>${surefire.version}</version>
+        <configuration>
+          <testFailureIgnore>true</testFailureIgnore>
+          <reportTestTimestamp>true</reportTestTimestamp>
+        </configuration>
+      </plugin>
+    </plugins>
+  </build>
+</project>
diff --git 
a/surefire-its/src/test/resources/enable-timestamp-element/src/test/java/TestA.java
 
b/surefire-its/src/test/resources/enable-timestamp-element/src/test/java/TestA.java
new file mode 100644
index 000000000..66391e702
--- /dev/null
+++ 
b/surefire-its/src/test/resources/enable-timestamp-element/src/test/java/TestA.java
@@ -0,0 +1,18 @@
+import org.junit.Test;
+import org.junit.Ignore;
+
+public class TestA {
+    @Ignore("Skipping this test still has to report a processing event 
timestamp")
+    @Test
+    public void skipped() throws Exception {
+    }
+
+    @Test
+    public void success() throws Exception {
+    }
+
+    @Test
+    public void failure() throws Exception {
+        throw new Exception("This test is supposed to fail.");
+    }
+}
diff --git 
a/surefire-providers/surefire-junit47/src/test/java/org/apache/maven/surefire/junitcore/JUnitCoreTester.java
 
b/surefire-providers/surefire-junit47/src/test/java/org/apache/maven/surefire/junitcore/JUnitCoreTester.java
index 1a4f74c29..475bba43f 100644
--- 
a/surefire-providers/surefire-junit47/src/test/java/org/apache/maven/surefire/junitcore/JUnitCoreTester.java
+++ 
b/surefire-providers/surefire-junit47/src/test/java/org/apache/maven/surefire/junitcore/JUnitCoreTester.java
@@ -112,6 +112,7 @@ private static StartupReportConfiguration 
defaultStartupReportConfiguration() {
                 false,
                 true,
                 true,
+                false,
                 new SurefireStatelessReporter(),
                 new SurefireConsoleOutputReporter(),
                 new SurefireStatelessTestsetInfoReporter(),

Reply via email to