This is an automated email from the ASF dual-hosted git repository.
shuber pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/unomi.git
The following commit(s) were added to refs/heads/master by this push:
new c59708e98 UNOMI-912 Add new progress information to integration tests
run. (#735)
c59708e98 is described below
commit c59708e988a347f9675bb153652ee1e9cf818ffc
Author: Serge Huber <[email protected]>
AuthorDate: Wed Nov 26 14:16:17 2025 +0100
UNOMI-912 Add new progress information to integration tests run. (#735)
- Start banner for integration tests
- Progress bar during test execution is now displayed with ETA, number of
successful and failed tests
- At the end of the execution a CSV compatible output of the slowest tests
is generated
---
.../test/java/org/apache/unomi/itests/AllITs.java | 3 +-
.../org/apache/unomi/itests/ProgressListener.java | 405 +++++++++++++++++++++
.../org/apache/unomi/itests/ProgressSuite.java | 186 ++++++++++
3 files changed, 592 insertions(+), 2 deletions(-)
diff --git a/itests/src/test/java/org/apache/unomi/itests/AllITs.java
b/itests/src/test/java/org/apache/unomi/itests/AllITs.java
index c50e88464..1edb74105 100644
--- a/itests/src/test/java/org/apache/unomi/itests/AllITs.java
+++ b/itests/src/test/java/org/apache/unomi/itests/AllITs.java
@@ -21,7 +21,6 @@ import
org.apache.unomi.itests.migration.Migrate16xToCurrentVersionIT;
import org.apache.unomi.itests.graphql.*;
import org.apache.unomi.itests.migration.MigrationIT;
import org.junit.runner.RunWith;
-import org.junit.runners.Suite;
import org.junit.runners.Suite.SuiteClasses;
/**
@@ -29,7 +28,7 @@ import org.junit.runners.Suite.SuiteClasses;
*
* @author Sergiy Shyrkov
*/
-@RunWith(Suite.class)
+@RunWith(ProgressSuite.class)
@SuiteClasses({
Migrate16xToCurrentVersionIT.class,
MigrationIT.class,
diff --git a/itests/src/test/java/org/apache/unomi/itests/ProgressListener.java
b/itests/src/test/java/org/apache/unomi/itests/ProgressListener.java
new file mode 100644
index 000000000..b39355a43
--- /dev/null
+++ b/itests/src/test/java/org/apache/unomi/itests/ProgressListener.java
@@ -0,0 +1,405 @@
+/*
+ * 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.unomi.itests;
+
+import org.junit.runner.Description;
+import org.junit.runner.Result;
+import org.junit.runner.notification.Failure;
+import org.junit.runner.notification.RunListener;
+
+import java.util.PriorityQueue;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * A comprehensive JUnit test run listener that provides enhanced progress
reporting
+ * with visual elements, timing information, and motivational quotes during
test execution.
+ *
+ * <p>This listener extends JUnit's {@link RunListener} to provide real-time
feedback
+ * about test execution progress. It features:</p>
+ * <ul>
+ * <li>ASCII art logo display at test suite startup</li>
+ * <li>Real-time progress bar with percentage completion</li>
+ * <li>Colorized output (when ANSI is supported)</li>
+ * <li>Estimated time remaining calculations</li>
+ * <li>Test success/failure counters</li>
+ * <li>Top 10 slowest tests tracking and reporting</li>
+ * <li>Motivational quotes displayed at progress milestones</li>
+ * <li>CSV-formatted performance data output</li>
+ * </ul>
+ *
+ * <p>The listener automatically detects ANSI color support based on the
terminal
+ * environment and adjusts output accordingly. When ANSI is not supported,
+ * plain text output is used instead.</p>
+ *
+ * <p>Example usage in test configuration:</p>
+ * <pre>{@code
+ * JUnitCore core = new JUnitCore();
+ * ProgressListener listener = new ProgressListener(totalTestCount,
completedCounter);
+ * core.addListener(listener);
+ * core.run(testClasses);
+ * }</pre>
+ *
+ * <p>The listener tracks test execution times and maintains a priority queue
+ * of the slowest tests, which is reported at the end of the test run along
+ * with CSV-formatted data for further analysis.</p>
+ *
+ * @author Apache Unomi
+ * @since 3.0.0
+ * @see org.junit.runner.notification.RunListener
+ * @see org.junit.runner.Description
+ * @see org.junit.runner.Result
+ */
+public class ProgressListener extends RunListener {
+
+ /** ANSI escape code to reset text formatting */
+ private static final String RESET = "\u001B[0m";
+ /** ANSI escape code for green text color */
+ private static final String GREEN = "\u001B[32m";
+ /** ANSI escape code for yellow text color */
+ private static final String YELLOW = "\u001B[33m";
+ /** ANSI escape code for red text color */
+ private static final String RED = "\u001B[31m";
+ /** ANSI escape code for cyan text color */
+ private static final String CYAN = "\u001B[36m";
+ /** ANSI escape code for blue text color */
+ private static final String BLUE = "\u001B[34m";
+
+ /** Array of motivational quotes displayed at progress milestones */
+ private static final String[] QUOTES = {
+ "Success is not final, failure is not fatal: It is the courage to
continue that counts. - Winston Churchill",
+ "Believe you can and you're halfway there. - Theodore Roosevelt",
+ "Don't watch the clock; do what it does. Keep going. - Sam
Levenson",
+ "It does not matter how slowly you go as long as you do not stop.
- Confucius",
+ "Hardships often prepare ordinary people for an extraordinary
destiny. - C.S. Lewis"
+ };
+
+ /**
+ * Inner class representing a test execution time record.
+ * Used to track individual test performance for reporting the slowest
tests.
+ */
+ private static class TestTime {
+ /** The display name of the test */
+ String name;
+ /** The execution time in milliseconds */
+ long time;
+
+ /**
+ * Creates a new test time record.
+ *
+ * @param name the display name of the test
+ * @param time the execution time in milliseconds
+ */
+ TestTime(String name, long time) {
+ this.name = name;
+ this.time = time;
+ }
+ }
+
+ /** Total number of tests to be executed */
+ private final int totalTests;
+ /** Thread-safe counter for completed tests */
+ private final AtomicInteger completedTests;
+ /** Thread-safe counter for successful tests */
+ private final AtomicInteger successfulTests = new AtomicInteger(0);
+ /** Thread-safe counter for failed tests */
+ private final AtomicInteger failedTests = new AtomicInteger(0);
+ /** Priority queue to track the slowest tests (limited to top 10) */
+ private final PriorityQueue<TestTime> slowTests;
+ /** Flag indicating whether ANSI color codes are supported in the terminal
*/
+ private final boolean ansiSupported;
+ /** Timestamp when the test suite started */
+ private long startTime = System.currentTimeMillis();
+ /** Timestamp when the current individual test started */
+ private long startTestTime = System.currentTimeMillis();
+
+ /**
+ * Creates a new ProgressListener instance.
+ *
+ * @param totalTests the total number of tests that will be executed
+ * @param completedTests a thread-safe counter that tracks the number of
completed tests
+ * (this should be shared with the test runner for
accurate progress tracking)
+ */
+ public ProgressListener(int totalTests, AtomicInteger completedTests) {
+ this.totalTests = totalTests;
+ this.completedTests = completedTests;
+ this.slowTests = new PriorityQueue<>((t1, t2) -> Long.compare(t1.time,
t2.time));
+ this.ansiSupported = isAnsiSupported();
+ }
+
+ /**
+ * Determines if the current terminal supports ANSI color codes.
+ *
+ * @return true if ANSI colors are supported, false otherwise
+ */
+ private boolean isAnsiSupported() {
+ String term = System.getenv("TERM");
+ return System.console() != null && term != null &&
term.contains("xterm");
+ }
+
+ /**
+ * Applies ANSI color codes to text if the terminal supports them.
+ *
+ * @param text the text to colorize
+ * @param color the ANSI color code to apply
+ * @return the colorized text if ANSI is supported, otherwise the original
text
+ */
+ private String colorize(String text, String color) {
+ if (ansiSupported) {
+ return color + text + RESET;
+ }
+ return text;
+ }
+
+ /**
+ * Called when the test run starts. Displays an ASCII art logo and welcome
message.
+ *
+ * @param description the description of the test run
+ */
+ @Override
+ public void testRunStarted(Description description) {
+ startTime = System.currentTimeMillis();
+
+ // Provided ASCII Art Logo
+ String[] logoLines = {
+ " ____ ___ A P A C H E .__ ",
+ " | | \\____ ____ _____ |__| ",
+ " | | / \\ / _ \\ / \\| | ",
+ " | | / | ( <_> ) Y Y \\ | ",
+ " |______/|___| /\\____/|__|_| /__| ",
+ " \\/ \\/ ",
+ " ",
+ " I N T E G R A T I O N T E S T S "
+ };
+
+ // Box dimensions
+ int totalWidth = 68;
+ String topBorder = "╔" + "═".repeat(totalWidth) + "╗";
+ String bottomBorder = "╚" + "═".repeat(totalWidth) + "╝";
+
+ // Print the top border
+ System.out.println(colorize(topBorder, CYAN));
+
+ // Center-align each logo line
+ for (String line : logoLines) {
+ int padding = (totalWidth - line.length()) / 2;
+ String paddedLine = " ".repeat(padding) + line + "
".repeat(totalWidth - padding - line.length());
+ System.out.println(colorize("║" + paddedLine + "║", CYAN));
+ }
+
+ // Print the progress message
+ String progressMessage = "Starting test suite with " + totalTests + "
tests. Good luck!";
+ int progressPadding = (totalWidth - progressMessage.length()) / 2;
+ String paddedProgressMessage = " ".repeat(progressPadding) +
progressMessage + " ".repeat(totalWidth - progressPadding -
progressMessage.length());
+
+ System.out.println(colorize("║" + paddedProgressMessage + "║", CYAN));
+
+ // Print the bottom border
+ System.out.println(colorize(bottomBorder, CYAN));
+ }
+
+ /**
+ * Called when an individual test starts. Records the start time for
timing calculations.
+ *
+ * @param description the description of the test that started
+ */
+ @Override
+ public void testStarted(Description description) {
+ startTestTime = System.currentTimeMillis();
+ }
+
+ /**
+ * Called when an individual test finishes successfully. Updates counters
and displays progress.
+ *
+ * @param description the description of the test that finished
+ */
+ @Override
+ public void testFinished(Description description) {
+ long testDuration = System.currentTimeMillis() - startTestTime;
+ completedTests.incrementAndGet();
+ successfulTests.incrementAndGet(); // Default to success unless a
failure is recorded separately.
+ slowTests.add(new TestTime(description.getDisplayName(),
testDuration));
+ if (slowTests.size() > 10) {
+ // Remove the smallest time, keeping only the top 5 longest
+ slowTests.poll();
+ }
+ displayProgress();
+ }
+
+ /**
+ * Called when a test fails. Updates failure counters and displays the
failure message.
+ *
+ * @param failure the failure information
+ */
+ @Override
+ public void testFailure(Failure failure) {
+ successfulTests.decrementAndGet(); // Remove the previous success
count for this test.
+ failedTests.incrementAndGet();
+ System.out.println(colorize("Test failed: " +
failure.getDescription(), RED));
+ displayProgress();
+ }
+
+ /**
+ * Called when the entire test run finishes. Displays final statistics and
performance data.
+ *
+ * @param result the final result of the test run
+ */
+ @Override
+ public void testRunFinished(Result result) {
+ long elapsedTime = System.currentTimeMillis() - startTime;
+ String resultMessage = result.wasSuccessful()
+ ? colorize("SUCCESS!", GREEN)
+ : colorize("FAILURE", RED);
+
System.out.printf("%s═══════════════════════════════════════════════════════════%n"
+
+ "Test suite finished in %s%s%s. Result: %s%n" +
+ "Successful: %s%d%s, Failed: %s%d%s%n" +
+
"═══════════════════════════════════════════════════════════%n",
+ ansiSupported ? CYAN : "",
+ ansiSupported ? YELLOW : "",
+ formatTime(elapsedTime),
+ ansiSupported ? RESET : "",
+ resultMessage,
+ ansiSupported ? GREEN : "",
+ successfulTests.get(),
+ ansiSupported ? RESET : "",
+ ansiSupported ? RED : "",
+ failedTests.get(),
+ ansiSupported ? RESET : "");
+
+ // Display the top 10 slowest tests
+ System.out.printf("Top 10 Slowest Tests:%n");
+ // Prepare CSV data
+ StringBuilder csvBuilder = new StringBuilder();
+ csvBuilder.append("Rank,Test Name,Duration (ms)\n");
+
+ AtomicInteger rank = new AtomicInteger(1);
+ slowTests.stream()
+ .sorted((t1, t2) -> Long.compare(t2.time, t1.time)) // Sort by
descending order
+ .limit(10)
+ .forEach(test ->
csvBuilder.append(String.format("%d,\"%s\",%d%n",
+ rank.getAndIncrement(), escapeCsv(test.name),
test.time)));
+
+ // Output CSV
+ System.out.println(csvBuilder.toString());
+
System.out.println("═══════════════════════════════════════════════════════════");
+
+ }
+
+ /**
+ * Escapes special characters for CSV compatibility.
+ *
+ * @param value the string value to escape
+ * @return the escaped string suitable for CSV output
+ */
+ private String escapeCsv(String value) {
+ if (value.contains(",") || value.contains("\"") ||
value.contains("\n")) {
+ return "\"" + value.replace("\"", "\"\"") + "\"";
+ }
+ return value;
+ }
+
+ /**
+ * Displays the current progress of the test run including progress bar,
+ * percentage completion, estimated time remaining, and success/failure
counts.
+ * Also displays motivational quotes at progress milestones.
+ */
+ private void displayProgress() {
+ int completed = completedTests.get();
+ long elapsedTime = System.currentTimeMillis() - startTime;
+
+ // Avoid division by very low completed count; use a floor value
+ int stableCompleted = Math.max(completed, 1);
+ double averageTestTimeMillis = elapsedTime / (double) stableCompleted;
+
+ // Calculate estimated time remaining
+ long estimatedRemainingTime = (long) (averageTestTimeMillis *
(totalTests - completed));
+ String progressBar = generateProgressBar(((double) completed /
totalTests) * 100);
+ String humanReadableTime = formatTime(estimatedRemainingTime);
+
+ System.out.printf("[%s] %sProgress: %s%.2f%%%s (%d/%d tests).
Estimated time remaining: %s%s%s. " +
+ "Successful: %s%d%s, Failed: %s%d%s%n",
+ progressBar,
+ ansiSupported ? BLUE : "",
+ ansiSupported ? GREEN : "",
+ ((double) completed / totalTests) * 100,
+ ansiSupported ? RESET : "",
+ completed,
+ totalTests,
+ ansiSupported ? YELLOW : "",
+ humanReadableTime,
+ ansiSupported ? RESET : "",
+ ansiSupported ? GREEN : "",
+ successfulTests.get(),
+ ansiSupported ? RESET : "",
+ ansiSupported ? RED : "",
+ failedTests.get(),
+ ansiSupported ? RESET : "");
+
+ if (completed % Math.max(1, totalTests / 10) == 0 && completed <
totalTests) {
+ String quote = QUOTES[completed % QUOTES.length];
+ System.out.println(colorize("Motivational Quote: " + quote,
YELLOW));
+ }
+ }
+
+ /**
+ * Formats a time duration in milliseconds into a human-readable string.
+ *
+ * @param timeInMillis the time duration in milliseconds
+ * @return a formatted time string (e.g., "1h 23m 45s" or "2m 30s")
+ */
+ private String formatTime(long timeInMillis) {
+ long seconds = timeInMillis / 1000;
+ long hours = seconds / 3600;
+ long minutes = (seconds % 3600) / 60;
+ seconds = seconds % 60;
+
+ if (hours > 999) {
+ // Fallback for extremely large times
+ return ">999h";
+ }
+
+ StringBuilder timeBuilder = new StringBuilder();
+ if (hours > 0) {
+ timeBuilder.append(hours).append("h ");
+ }
+ if (minutes > 0 || hours > 0) { // Show minutes if hours are non-zero
+ timeBuilder.append(minutes).append("m ");
+ }
+ timeBuilder.append(seconds).append("s");
+
+ return timeBuilder.toString().trim(); // Trim any trailing spaces
+ }
+
+ /**
+ * Generates a visual progress bar based on the completion percentage.
+ *
+ * @param progressPercentage the completion percentage (0.0 to 100.0)
+ * @return a string representation of the progress bar with appropriate
colors
+ */
+ private String generateProgressBar(double progressPercentage) {
+ int totalBars = 30;
+ int completedBars = (int) (progressPercentage / (100.0 / totalBars));
+ StringBuilder progressBar = new StringBuilder();
+ for (int i = 0; i < completedBars; i++) {
+ progressBar.append(ansiSupported ? GREEN + "█" + RESET : "#");
+ }
+ for (int i = completedBars; i < totalBars; i++) {
+ progressBar.append(ansiSupported ? "░" : "-");
+ }
+ return progressBar.toString();
+ }
+
+}
diff --git a/itests/src/test/java/org/apache/unomi/itests/ProgressSuite.java
b/itests/src/test/java/org/apache/unomi/itests/ProgressSuite.java
new file mode 100644
index 000000000..0c9f70af2
--- /dev/null
+++ b/itests/src/test/java/org/apache/unomi/itests/ProgressSuite.java
@@ -0,0 +1,186 @@
+/*
+ * 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.unomi.itests;
+
+import org.junit.Test;
+import org.junit.runner.Description;
+import org.junit.runner.notification.RunNotifier;
+import org.junit.runners.Suite;
+import org.junit.runners.model.InitializationError;
+
+import java.lang.reflect.Method;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * A custom JUnit test suite runner that provides enhanced progress reporting
+ * during test execution by integrating with the {@link ProgressListener}.
+ *
+ * <p>This suite extends JUnit's standard {@link Suite} runner to automatically
+ * count test methods across the entire class hierarchy and provide real-time
+ * progress feedback. It features:</p>
+ * <ul>
+ * <li>Automatic test method counting across class hierarchies</li>
+ * <li>Integration with {@link ProgressListener} for enhanced progress
reporting</li>
+ * <li>Thread-safe progress tracking using atomic counters</li>
+ * <li>Support for nested test classes and inheritance</li>
+ * </ul>
+ *
+ * <p>The suite automatically counts all methods annotated with {@code @Test}
+ * in the specified test classes and their superclasses, providing an accurate
+ * total count for progress reporting.</p>
+ *
+ * <p>Example usage:</p>
+ * <pre>{@code
+ * @RunWith(ProgressSuite.class)
+ * @Suite.SuiteClasses({
+ * TestClass1.class,
+ * TestClass2.class,
+ * TestClass3.class
+ * })
+ * public class AllTestsSuite {
+ * // This class serves as a container for the test suite
+ * }
+ * }</pre>
+ *
+ * <p>The suite will automatically:</p>
+ * <ul>
+ * <li>Count all test methods in the specified classes and their
hierarchies</li>
+ * <li>Create a {@link ProgressListener} with the accurate test count</li>
+ * <li>Display real-time progress with visual elements and timing
information</li>
+ * <li>Provide detailed performance statistics at completion</li>
+ * </ul>
+ *
+ * @author Apache Unomi
+ * @since 3.0.0
+ * @see org.junit.runners.Suite
+ * @see org.apache.unomi.itests.ProgressListener
+ * @see org.junit.runner.RunWith
+ * @see org.junit.runners.Suite.SuiteClasses
+ */
+public class ProgressSuite extends Suite {
+
+ /** Total number of test methods across all classes in the suite */
+ private final int totalTests;
+ /** Thread-safe counter for completed tests, shared with ProgressListener
*/
+ private final AtomicInteger completedTests = new AtomicInteger(0);
+
+ /**
+ * Creates a new ProgressSuite instance for the specified test suite class.
+ *
+ * <p>The constructor initializes the suite by:</p>
+ * <ul>
+ * <li>Extracting test classes from the {@code @Suite.SuiteClasses}
annotation</li>
+ * <li>Counting all test methods across the class hierarchies</li>
+ * <li>Initializing the progress tracking infrastructure</li>
+ * </ul>
+ *
+ * @param klass the test suite class that must be annotated with {@code
@Suite.SuiteClasses}
+ * @throws InitializationError if the class is not properly annotated or
if there are
+ * issues with the test class configuration
+ */
+ public ProgressSuite(Class<?> klass) throws InitializationError {
+ super(klass, getAnnotatedClasses(klass));
+ this.totalTests = countTestMethods(getAnnotatedClasses(klass));
+ }
+
+ /**
+ * Extracts the test classes from the {@code @Suite.SuiteClasses}
annotation.
+ *
+ * @param klass the test suite class to examine
+ * @return an array of test classes specified in the annotation
+ * @throws InitializationError if the class is not annotated with {@code
@Suite.SuiteClasses}
+ */
+ private static Class<?>[] getAnnotatedClasses(Class<?> klass) throws
InitializationError {
+ Suite.SuiteClasses annotation =
klass.getAnnotation(Suite.SuiteClasses.class);
+ if (annotation == null) {
+ throw new InitializationError(
+ String.format("Class '%s' must have a @Suite.SuiteClasses
annotation", klass.getName()));
+ }
+ return annotation.value();
+ }
+
+ /**
+ * Counts the total number of test methods across all specified test
classes.
+ *
+ * @param testClasses array of test classes to count methods in
+ * @return the total number of methods annotated with {@code @Test}
+ */
+ private static int countTestMethods(Class<?>[] testClasses) {
+ int count = 0;
+ for (Class<?> testClass : testClasses) {
+ count += countTestMethodsInClassHierarchy(testClass);
+ }
+ return count;
+ }
+
+ /**
+ * Recursively counts test methods in a class and its entire inheritance
hierarchy.
+ *
+ * <p>This method traverses the class hierarchy upward from the given
class,
+ * counting all methods annotated with {@code @Test} in each class. It
stops
+ * at {@code Object.class} to avoid counting system methods.</p>
+ *
+ * @param clazz the class to count test methods in (including superclasses)
+ * @return the number of test methods found in this class and its hierarchy
+ */
+ private static int countTestMethodsInClassHierarchy(Class<?> clazz) {
+ int count = 0;
+ if (clazz == null || clazz == Object.class) {
+ return 0; // Stop at the base class
+ }
+ for (Method method : clazz.getDeclaredMethods()) {
+ if (method.isAnnotationPresent(Test.class)) {
+ count++;
+ }
+ }
+ // Recurse into the superclass
+ count += countTestMethodsInClassHierarchy(clazz.getSuperclass());
+ return count;
+ }
+
+ /**
+ * Executes the test suite with enhanced progress reporting.
+ *
+ * <p>This method overrides the standard suite execution to integrate
+ * the {@link ProgressListener} for real-time progress feedback. It:</p>
+ * <ul>
+ * <li>Creates a {@link ProgressListener} with the accurate test
count</li>
+ * <li>Manually triggers the test run started event (since the listener
+ * is registered after this event would normally be fired)</li>
+ * <li>Registers the listener with the run notifier</li>
+ * <li>Delegates to the parent suite execution</li>
+ * </ul>
+ *
+ * <p>Note: Two separate {@link ProgressListener} instances are created:
+ * one for manual event triggering and another for the notifier. This is
+ * necessary because the test run started event is fired before listeners
+ * can be registered.</p>
+ *
+ * @param notifier the run notifier to use for test execution notifications
+ */
+ @Override
+ public void run(RunNotifier notifier) {
+ ProgressListener listener = new ProgressListener(totalTests,
completedTests);
+ Description suiteDescription = getDescription();
+ // We call this manually as we register the listener after this event
has already been triggered.
+ listener.testRunStarted(suiteDescription);
+
+ notifier.addListener(new ProgressListener(totalTests, completedTests));
+ super.run(notifier);
+ }
+
+}