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

gnodet pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/maven.git


The following commit(s) were added to refs/heads/master by this push:
     new 1fd7f879a4 [MNG-5729] Fix transfer rate computation (#1969)
1fd7f879a4 is described below

commit 1fd7f879a4d4253b6b1eb655b5b7b666c5274d00
Author: Guillaume Nodet <gno...@gmail.com>
AuthorDate: Sat Dec 14 00:03:27 2024 +0100

    [MNG-5729] Fix transfer rate computation (#1969)
---
 impl/maven-cli/pom.xml                             |   5 +
 .../transfer/AbstractMavenTransferListener.java    |   9 +-
 .../maven/cling/transfer/FileSizeFormat.java       |  16 +
 .../transfer/ConsoleMavenTransferListenerTest.java | 139 ++++++++
 .../maven/cling/transfer/FileSizeFormatTest.java   | 356 +++++++++++++++++++++
 .../transfer/SimplexTransferListenerTest.java      | 112 +++++++
 .../maven/internal/impl/AbstractSession.java       |   6 +
 7 files changed, 637 insertions(+), 6 deletions(-)

diff --git a/impl/maven-cli/pom.xml b/impl/maven-cli/pom.xml
index bd8c0b61de..97f35179fe 100644
--- a/impl/maven-cli/pom.xml
+++ b/impl/maven-cli/pom.xml
@@ -89,6 +89,11 @@ under the License.
       <artifactId>junit-jupiter-api</artifactId>
       <scope>test</scope>
     </dependency>
+    <dependency>
+      <groupId>org.mockito</groupId>
+      <artifactId>mockito-core</artifactId>
+      <scope>test</scope>
+    </dependency>
     <dependency>
       <groupId>com.google.jimfs</groupId>
       <artifactId>jimfs</artifactId>
diff --git 
a/impl/maven-cli/src/main/java/org/apache/maven/cling/transfer/AbstractMavenTransferListener.java
 
b/impl/maven-cli/src/main/java/org/apache/maven/cling/transfer/AbstractMavenTransferListener.java
index 4cba621f55..fa4341b8f1 100644
--- 
a/impl/maven-cli/src/main/java/org/apache/maven/cling/transfer/AbstractMavenTransferListener.java
+++ 
b/impl/maven-cli/src/main/java/org/apache/maven/cling/transfer/AbstractMavenTransferListener.java
@@ -20,7 +20,6 @@ package org.apache.maven.cling.transfer;
 
 import java.io.PrintWriter;
 import java.time.Duration;
-import java.time.Instant;
 
 import org.apache.maven.api.MonotonicClock;
 import org.apache.maven.api.services.MessageBuilder;
@@ -83,13 +82,11 @@ public abstract class AbstractMavenTransferListener extends 
AbstractTransferList
         message.resetStyle().append(resource.getResourceName());
         message.style(STYLE).append(" (").append(format.format(contentLength));
 
-        Duration duration =
-                
Duration.between(Instant.ofEpochMilli(resource.getTransferStartTime()), 
MonotonicClock.now());
+        Duration duration = Duration.between(resource.getStartTime(), 
MonotonicClock.now());
         if ((duration.getSeconds() | duration.getNano()) > 0) { // 
duration.isPositive()
-            long bytesPerSecond = Math.round(contentLength / (double) 
duration.toSeconds());
+            double bytesPerSecond = contentLength / (double) 
duration.toSeconds();
             message.append(" at ");
-            format.format(message, bytesPerSecond);
-            message.append("/s");
+            format.formatRate(message, bytesPerSecond);
         }
 
         message.append(')').resetStyle();
diff --git 
a/impl/maven-cli/src/main/java/org/apache/maven/cling/transfer/FileSizeFormat.java
 
b/impl/maven-cli/src/main/java/org/apache/maven/cling/transfer/FileSizeFormat.java
index a46e0b035b..d29eb01540 100644
--- 
a/impl/maven-cli/src/main/java/org/apache/maven/cling/transfer/FileSizeFormat.java
+++ 
b/impl/maven-cli/src/main/java/org/apache/maven/cling/transfer/FileSizeFormat.java
@@ -152,6 +152,22 @@ public class FileSizeFormat {
         format(builder, size, unit, false);
     }
 
+    public void formatRate(MessageBuilder builder, double rate) {
+        ScaleUnit unit = ScaleUnit.getScaleUnit(Math.round(rate));
+        double scaledRate = rate / unit.bytes();
+        if (unit == ScaleUnit.BYTE || scaledRate < 0.05d || scaledRate >= 
10.0d) {
+            builder.append(Long.toString(Math.round(scaledRate)));
+        } else {
+            builder.append(Double.toString(Math.round(scaledRate * 10d) / 
10d));
+        }
+        if (unit == ScaleUnit.BYTE) {
+            builder.append(" B");
+        } else {
+            builder.append(" ").append(unit.symbol());
+        }
+        builder.append("/s");
+    }
+
     private void format(MessageBuilder builder, long size, ScaleUnit unit, 
boolean omitSymbol) {
         if (size < 0L) {
             throw new IllegalArgumentException("file size cannot be negative: 
" + size);
diff --git 
a/impl/maven-cli/src/test/java/org/apache/maven/cling/transfer/ConsoleMavenTransferListenerTest.java
 
b/impl/maven-cli/src/test/java/org/apache/maven/cling/transfer/ConsoleMavenTransferListenerTest.java
new file mode 100644
index 0000000000..d06c4d18e3
--- /dev/null
+++ 
b/impl/maven-cli/src/test/java/org/apache/maven/cling/transfer/ConsoleMavenTransferListenerTest.java
@@ -0,0 +1,139 @@
+/*
+ * 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.cling.transfer;
+
+import java.io.File;
+import java.io.PrintWriter;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+import org.apache.maven.jline.JLineMessageBuilderFactory;
+import org.eclipse.aether.DefaultRepositorySystemSession;
+import org.eclipse.aether.transfer.TransferCancelledException;
+import org.eclipse.aether.transfer.TransferEvent;
+import org.eclipse.aether.transfer.TransferListener;
+import org.eclipse.aether.transfer.TransferResource;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+class ConsoleMavenTransferListenerTest {
+
+    private CountDownLatch startLatch;
+    private CountDownLatch endLatch;
+
+    @Test
+    void testTransferProgressedWithPrintResourceNames() throws Exception {
+        int size = 1000;
+        ExecutorService service = Executors.newFixedThreadPool(size * 2);
+        startLatch = new CountDownLatch(size);
+        endLatch = new CountDownLatch(size);
+        Map<String, String> output = new ConcurrentHashMap<String, String>();
+
+        TransferListener listener = new SimplexTransferListener(new 
ConsoleMavenTransferListener(
+                new JLineMessageBuilderFactory(),
+                new PrintWriter(System.out) {
+
+                    @Override
+                    public void print(Object o) {
+
+                        String string = o.toString();
+                        int i = string.length() - 1;
+                        while (i >= 0) {
+                            char c = string.charAt(i);
+                            if (c == '\n' || c == '\r' || c == ' ') i--;
+                            else break;
+                        }
+
+                        string = string.substring(0, i + 1).trim();
+                        output.put(string, string);
+                        System.out.print(o);
+                    }
+                },
+                true));
+        TransferResource resource =
+                new TransferResource(null, null, 
"http://maven.org/test/test-resource";, new File(""), null);
+        resource.setContentLength(size - 1);
+
+        DefaultRepositorySystemSession session = new 
DefaultRepositorySystemSession(h -> false); // no close handle
+
+        // warm up
+        test(listener, session, resource, 0);
+
+        for (int i = 1; i < size; i++) {
+            final int bytes = i;
+
+            service.execute(() -> {
+                test(listener, session, resource, bytes);
+            });
+        }
+
+        // start all threads at once
+        try {
+            startLatch.await();
+        } catch (InterruptedException e) {
+            e.printStackTrace();
+        }
+
+        // wait for all thread to end
+        try {
+            endLatch.await();
+        } catch (InterruptedException e) {
+            e.printStackTrace();
+        }
+
+        // despite all are back, we need to make sure all the events are 
processed (are async)
+        // this one should block until all processed
+        listener.transferSucceeded(new TransferEvent.Builder(session, resource)
+                .setType(TransferEvent.EventType.SUCCEEDED)
+                .build());
+
+        StringBuilder message = new StringBuilder("Messages [");
+        boolean test = true;
+        for (int i = 0; i < 999; i++) {
+            boolean ok = output.containsKey("Progress (1): test-resource (" + 
i + "/999 B)");
+            if (!ok) {
+                System.out.println("false : " + i);
+                message.append(i + ",");
+            }
+            test = test & ok;
+        }
+        assertTrue(test, message + "] are missing in " + output);
+    }
+
+    private void test(
+            TransferListener listener,
+            DefaultRepositorySystemSession session,
+            TransferResource resource,
+            final int bytes) {
+        TransferEvent event = new TransferEvent.Builder(session, resource)
+                .setType(TransferEvent.EventType.PROGRESSED)
+                .setTransferredBytes(bytes)
+                .build();
+        startLatch.countDown();
+        try {
+            listener.transferProgressed(event);
+        } catch (TransferCancelledException e) {
+        }
+        endLatch.countDown();
+    }
+}
diff --git 
a/impl/maven-cli/src/test/java/org/apache/maven/cling/transfer/FileSizeFormatTest.java
 
b/impl/maven-cli/src/test/java/org/apache/maven/cling/transfer/FileSizeFormatTest.java
new file mode 100644
index 0000000000..9a969f0359
--- /dev/null
+++ 
b/impl/maven-cli/src/test/java/org/apache/maven/cling/transfer/FileSizeFormatTest.java
@@ -0,0 +1,356 @@
+/*
+ * 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.cling.transfer;
+
+import org.apache.maven.api.services.MessageBuilder;
+import org.apache.maven.cling.transfer.FileSizeFormat.ScaleUnit;
+import org.apache.maven.internal.impl.DefaultMessageBuilder;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+class FileSizeFormatTest {
+
+    @Test
+    void testNegativeSize() {
+        FileSizeFormat format = new FileSizeFormat();
+
+        long negativeSize = -100L;
+        assertThrows(IllegalArgumentException.class, () -> 
format.format(negativeSize));
+    }
+
+    @Test
+    void testSize() {
+        FileSizeFormat format = new FileSizeFormat();
+
+        long _0_bytes = 0L;
+        assertEquals("0 B", format.format(_0_bytes));
+
+        long _5_bytes = 5L;
+        assertEquals("5 B", format.format(_5_bytes));
+
+        long _10_bytes = 10L;
+        assertEquals("10 B", format.format(_10_bytes));
+
+        long _15_bytes = 15L;
+        assertEquals("15 B", format.format(_15_bytes));
+
+        long _999_bytes = 999L;
+        assertEquals("999 B", format.format(_999_bytes));
+
+        long _1000_bytes = 1000L;
+        assertEquals("1.0 kB", format.format(_1000_bytes));
+
+        long _5500_bytes = 5500L;
+        assertEquals("5.5 kB", format.format(_5500_bytes));
+
+        long _10_kilobytes = 10L * 1000L;
+        assertEquals("10 kB", format.format(_10_kilobytes));
+
+        long _15_kilobytes = 15L * 1000L;
+        assertEquals("15 kB", format.format(_15_kilobytes));
+
+        long _999_kilobytes = 999L * 1000L;
+        assertEquals("999 kB", format.format(_999_kilobytes));
+
+        long _1000_kilobytes = 1000L * 1000L;
+        assertEquals("1.0 MB", format.format(_1000_kilobytes));
+
+        long _5500_kilobytes = 5500L * 1000L;
+        assertEquals("5.5 MB", format.format(_5500_kilobytes));
+
+        long _10_megabytes = 10L * 1000L * 1000L;
+        assertEquals("10 MB", format.format(_10_megabytes));
+
+        long _15_megabytes = 15L * 1000L * 1000L;
+        assertEquals("15 MB", format.format(_15_megabytes));
+
+        long _999_megabytes = 999L * 1000L * 1000L;
+        assertEquals("999 MB", format.format(_999_megabytes));
+
+        long _1000_megabytes = 1000L * 1000L * 1000L;
+        assertEquals("1.0 GB", format.format(_1000_megabytes));
+
+        long _5500_megabytes = 5500L * 1000L * 1000L;
+        assertEquals("5.5 GB", format.format(_5500_megabytes));
+
+        long _10_gigabytes = 10L * 1000L * 1000L * 1000L;
+        assertEquals("10 GB", format.format(_10_gigabytes));
+
+        long _15_gigabytes = 15L * 1000L * 1000L * 1000L;
+        assertEquals("15 GB", format.format(_15_gigabytes));
+
+        long _1000_gigabytes = 1000L * 1000L * 1000L * 1000L;
+        assertEquals("1000 GB", format.format(_1000_gigabytes));
+    }
+
+    @Test
+    void testSizeWithSelectedScaleUnit() {
+        FileSizeFormat format = new FileSizeFormat();
+
+        long _0_bytes = 0L;
+        assertEquals("0 B", format.format(_0_bytes));
+        assertEquals("0 B", format.format(_0_bytes, ScaleUnit.BYTE));
+        assertEquals("0 kB", format.format(_0_bytes, ScaleUnit.KILOBYTE));
+        assertEquals("0 MB", format.format(_0_bytes, ScaleUnit.MEGABYTE));
+        assertEquals("0 GB", format.format(_0_bytes, ScaleUnit.GIGABYTE));
+
+        long _5_bytes = 5L;
+        assertEquals("5 B", format.format(_5_bytes));
+        assertEquals("5 B", format.format(_5_bytes, ScaleUnit.BYTE));
+        assertEquals("0 kB", format.format(_5_bytes, ScaleUnit.KILOBYTE));
+        assertEquals("0 MB", format.format(_5_bytes, ScaleUnit.MEGABYTE));
+        assertEquals("0 GB", format.format(_5_bytes, ScaleUnit.GIGABYTE));
+
+        long _49_bytes = 49L;
+        assertEquals("49 B", format.format(_49_bytes));
+        assertEquals("49 B", format.format(_49_bytes, ScaleUnit.BYTE));
+        assertEquals("0 kB", format.format(_49_bytes, ScaleUnit.KILOBYTE));
+        assertEquals("0 MB", format.format(_49_bytes, ScaleUnit.MEGABYTE));
+        assertEquals("0 GB", format.format(_49_bytes, ScaleUnit.GIGABYTE));
+
+        long _50_bytes = 50L;
+        assertEquals("50 B", format.format(_50_bytes));
+        assertEquals("50 B", format.format(_50_bytes, ScaleUnit.BYTE));
+        assertEquals("0.1 kB", format.format(_50_bytes, ScaleUnit.KILOBYTE));
+        assertEquals("0 MB", format.format(_50_bytes, ScaleUnit.MEGABYTE));
+        assertEquals("0 GB", format.format(_50_bytes, ScaleUnit.GIGABYTE));
+
+        long _999_bytes = 999L;
+        assertEquals("999 B", format.format(_999_bytes));
+        assertEquals("999 B", format.format(_999_bytes, ScaleUnit.BYTE));
+        assertEquals("1.0 kB", format.format(_999_bytes, ScaleUnit.KILOBYTE));
+        assertEquals("0 MB", format.format(_999_bytes, ScaleUnit.MEGABYTE));
+        assertEquals("0 GB", format.format(_999_bytes, ScaleUnit.GIGABYTE));
+
+        long _1000_bytes = 1000L;
+        assertEquals("1.0 kB", format.format(_1000_bytes));
+        assertEquals("1000 B", format.format(_1000_bytes, ScaleUnit.BYTE));
+        assertEquals("1.0 kB", format.format(_1000_bytes, ScaleUnit.KILOBYTE));
+        assertEquals("0 MB", format.format(_1000_bytes, ScaleUnit.MEGABYTE));
+        assertEquals("0 GB", format.format(_1000_bytes, ScaleUnit.GIGABYTE));
+
+        long _49_kilobytes = 49L * 1000L;
+        assertEquals("49 kB", format.format(_49_kilobytes));
+        assertEquals("49000 B", format.format(_49_kilobytes, ScaleUnit.BYTE));
+        assertEquals("49 kB", format.format(_49_kilobytes, 
ScaleUnit.KILOBYTE));
+        assertEquals("0 MB", format.format(_49_kilobytes, ScaleUnit.MEGABYTE));
+        assertEquals("0 GB", format.format(_49_kilobytes, ScaleUnit.GIGABYTE));
+
+        long _50_kilobytes = 50L * 1000L;
+        assertEquals("50 kB", format.format(_50_kilobytes));
+        assertEquals("50000 B", format.format(_50_kilobytes, ScaleUnit.BYTE));
+        assertEquals("50 kB", format.format(_50_kilobytes, 
ScaleUnit.KILOBYTE));
+        assertEquals("0.1 MB", format.format(_50_kilobytes, 
ScaleUnit.MEGABYTE));
+        assertEquals("0 GB", format.format(_50_kilobytes, ScaleUnit.GIGABYTE));
+
+        long _999_kilobytes = 999L * 1000L;
+        assertEquals("999 kB", format.format(_999_kilobytes));
+        assertEquals("999000 B", format.format(_999_kilobytes, 
ScaleUnit.BYTE));
+        assertEquals("999 kB", format.format(_999_kilobytes, 
ScaleUnit.KILOBYTE));
+        assertEquals("1.0 MB", format.format(_999_kilobytes, 
ScaleUnit.MEGABYTE));
+        assertEquals("0 GB", format.format(_999_kilobytes, 
ScaleUnit.GIGABYTE));
+
+        long _1000_kilobytes = 1000L * 1000L;
+        assertEquals("1.0 MB", format.format(_1000_kilobytes));
+        assertEquals("1000000 B", format.format(_1000_kilobytes, 
ScaleUnit.BYTE));
+        assertEquals("1000 kB", format.format(_1000_kilobytes, 
ScaleUnit.KILOBYTE));
+        assertEquals("1.0 MB", format.format(_1000_kilobytes, 
ScaleUnit.MEGABYTE));
+        assertEquals("0 GB", format.format(_1000_kilobytes, 
ScaleUnit.GIGABYTE));
+
+        long _49_megabytes = 49L * 1000L * 1000L;
+        assertEquals("49 MB", format.format(_49_megabytes));
+        assertEquals("49000000 B", format.format(_49_megabytes, 
ScaleUnit.BYTE));
+        assertEquals("49000 kB", format.format(_49_megabytes, 
ScaleUnit.KILOBYTE));
+        assertEquals("49 MB", format.format(_49_megabytes, 
ScaleUnit.MEGABYTE));
+        assertEquals("0 GB", format.format(_49_megabytes, ScaleUnit.GIGABYTE));
+
+        long _50_megabytes = 50L * 1000L * 1000L;
+        assertEquals("50 MB", format.format(_50_megabytes));
+        assertEquals("50000000 B", format.format(_50_megabytes, 
ScaleUnit.BYTE));
+        assertEquals("50000 kB", format.format(_50_megabytes, 
ScaleUnit.KILOBYTE));
+        assertEquals("50 MB", format.format(_50_megabytes, 
ScaleUnit.MEGABYTE));
+        assertEquals("0.1 GB", format.format(_50_megabytes, 
ScaleUnit.GIGABYTE));
+
+        long _999_megabytes = 999L * 1000L * 1000L;
+        assertEquals("999 MB", format.format(_999_megabytes));
+        assertEquals("999000000 B", format.format(_999_megabytes, 
ScaleUnit.BYTE));
+        assertEquals("999000 kB", format.format(_999_megabytes, 
ScaleUnit.KILOBYTE));
+        assertEquals("999 MB", format.format(_999_megabytes, 
ScaleUnit.MEGABYTE));
+        assertEquals("1.0 GB", format.format(_999_megabytes, 
ScaleUnit.GIGABYTE));
+
+        long _1000_megabytes = 1000L * 1000L * 1000L;
+        assertEquals("1.0 GB", format.format(_1000_megabytes));
+        assertEquals("1000000000 B", format.format(_1000_megabytes, 
ScaleUnit.BYTE));
+        assertEquals("1000000 kB", format.format(_1000_megabytes, 
ScaleUnit.KILOBYTE));
+        assertEquals("1000 MB", format.format(_1000_megabytes, 
ScaleUnit.MEGABYTE));
+        assertEquals("1.0 GB", format.format(_1000_megabytes, 
ScaleUnit.GIGABYTE));
+    }
+
+    @Test
+    void testNegativeProgressedSize() {
+        FileSizeFormat format = new FileSizeFormat();
+
+        long negativeProgressedSize = -100L;
+        assertThrows(IllegalArgumentException.class, () -> 
format.formatProgress(negativeProgressedSize, 10L));
+    }
+
+    @Test
+    void testNegativeProgressedSizeBiggerThanSize() {
+        FileSizeFormat format = new FileSizeFormat();
+
+        assertThrows(IllegalArgumentException.class, () -> 
format.formatProgress(100L, 10L));
+    }
+
+    @Test
+    void testProgressedSizeWithoutSize() {
+        FileSizeFormat format = new FileSizeFormat();
+
+        long _0_bytes = 0L;
+        assertEquals("0 B", format.formatProgress(_0_bytes, -1L));
+
+        long _1000_bytes = 1000L;
+        assertEquals("1.0 kB", format.formatProgress(_1000_bytes, -1L));
+
+        long _1000_kilobytes = 1000L * 1000L;
+        assertEquals("1.0 MB", format.formatProgress(_1000_kilobytes, -1L));
+
+        long _1000_megabytes = 1000L * 1000L * 1000L;
+        assertEquals("1.0 GB", format.formatProgress(_1000_megabytes, -1L));
+    }
+
+    @Test
+    void testProgressedBothZero() {
+        FileSizeFormat format = new FileSizeFormat();
+
+        long _0_bytes = 0L;
+        assertEquals("0 B", format.formatProgress(_0_bytes, _0_bytes));
+    }
+
+    @Test
+    void testProgressedSizeWithSize() {
+        FileSizeFormat format = new FileSizeFormat();
+
+        long _0_bytes = 0L;
+        long _400_bytes = 400L;
+        long _800_bytes = 2L * _400_bytes;
+        assertEquals("0/800 B", format.formatProgress(_0_bytes, _800_bytes));
+        assertEquals("400/800 B", format.formatProgress(_400_bytes, 
_800_bytes));
+        assertEquals("800 B", format.formatProgress(_800_bytes, _800_bytes));
+
+        long _4000_bytes = 4000L;
+        long _8000_bytes = 2L * _4000_bytes;
+        long _50_kilobytes = 50000L;
+        assertEquals("0/8.0 kB", format.formatProgress(_0_bytes, _8000_bytes));
+        assertEquals("0.4/8.0 kB", format.formatProgress(_400_bytes, 
_8000_bytes));
+        assertEquals("4.0/8.0 kB", format.formatProgress(_4000_bytes, 
_8000_bytes));
+        assertEquals("8.0 kB", format.formatProgress(_8000_bytes, 
_8000_bytes));
+        assertEquals("8.0/50 kB", format.formatProgress(_8000_bytes, 
_50_kilobytes));
+        assertEquals("16/50 kB", format.formatProgress(2L * _8000_bytes, 
_50_kilobytes));
+        assertEquals("50 kB", format.formatProgress(_50_kilobytes, 
_50_kilobytes));
+
+        long _500_kilobytes = 500000L;
+        long _1000_kilobytes = 2L * _500_kilobytes;
+        ;
+        long _5000_kilobytes = 5L * _1000_kilobytes;
+        long _15_megabytes = 3L * _5000_kilobytes;
+        assertEquals("0/5.0 MB", format.formatProgress(_0_bytes, 
_5000_kilobytes));
+        assertEquals("0.5/5.0 MB", format.formatProgress(_500_kilobytes, 
_5000_kilobytes));
+        assertEquals("1.0/5.0 MB", format.formatProgress(_1000_kilobytes, 
_5000_kilobytes));
+        assertEquals("5.0 MB", format.formatProgress(_5000_kilobytes, 
_5000_kilobytes));
+        assertEquals("5.0/15 MB", format.formatProgress(_5000_kilobytes, 
_15_megabytes));
+        assertEquals("15 MB", format.formatProgress(_15_megabytes, 
_15_megabytes));
+
+        long _500_megabytes = 500000000L;
+        long _1000_megabytes = 2L * _500_megabytes;
+        long _5000_megabytes = 5L * _1000_megabytes;
+        long _15_gigabytes = 3L * _5000_megabytes;
+        assertEquals("0/500 MB", format.formatProgress(_0_bytes, 
_500_megabytes));
+        assertEquals("1.0/5.0 GB", format.formatProgress(_1000_megabytes, 
_5000_megabytes));
+        assertEquals("5.0 GB", format.formatProgress(_5000_megabytes, 
_5000_megabytes));
+        assertEquals("5.0/15 GB", format.formatProgress(_5000_megabytes, 
_15_gigabytes));
+        assertEquals("15 GB", format.formatProgress(_15_gigabytes, 
_15_gigabytes));
+    }
+
+    @Test
+    void testFormatRate() {
+        FileSizeFormat format = new FileSizeFormat();
+
+        // Test bytes per second
+        MessageBuilder builder = new DefaultMessageBuilder();
+        format.formatRate(builder, 5.0);
+        assertEquals("5 B/s", builder.build());
+
+        // Test kilobytes per second
+        builder = new DefaultMessageBuilder();
+        format.formatRate(builder, 5500.0);
+        assertEquals("5.5 kB/s", builder.build());
+
+        // Test megabytes per second
+        builder = new DefaultMessageBuilder();
+        format.formatRate(builder, 5500000.0);
+        assertEquals("5.5 MB/s", builder.build());
+
+        // Test gigabytes per second
+        builder = new DefaultMessageBuilder();
+        format.formatRate(builder, 5500000000.0);
+        assertEquals("5.5 GB/s", builder.build());
+    }
+
+    @Test
+    void testFormatRateThresholds() {
+        FileSizeFormat format = new FileSizeFormat();
+
+        // Test value less than 0.05
+        MessageBuilder builder = new DefaultMessageBuilder();
+        format.formatRate(builder, 45.0); // 45 B/s
+        assertEquals("45 B/s", builder.build());
+
+        // Test value greater than or equal to 10
+        builder = new DefaultMessageBuilder();
+        format.formatRate(builder, 15000.0); // 15 kB/s
+        assertEquals("15 kB/s", builder.build());
+
+        // Test value between 0.05 and 10
+        builder = new DefaultMessageBuilder();
+        format.formatRate(builder, 5500.0); // 5.5 kB/s
+        assertEquals("5.5 kB/s", builder.build());
+    }
+
+    @Test
+    void testFormatRateEdgeCases() {
+        FileSizeFormat format = new FileSizeFormat();
+
+        // Test zero rate
+        MessageBuilder builder = new DefaultMessageBuilder();
+        format.formatRate(builder, 0.0);
+        assertEquals("0 B/s", builder.build());
+
+        // Test rate at exactly 1000 (1 kB/s)
+        builder = new DefaultMessageBuilder();
+        format.formatRate(builder, 1000.0);
+        assertEquals("1.0 kB/s", builder.build());
+
+        // Test rate at exactly 1000000 (1 MB/s)
+        builder = new DefaultMessageBuilder();
+        format.formatRate(builder, 1000000.0);
+        assertEquals("1.0 MB/s", builder.build());
+    }
+}
diff --git 
a/impl/maven-cli/src/test/java/org/apache/maven/cling/transfer/SimplexTransferListenerTest.java
 
b/impl/maven-cli/src/test/java/org/apache/maven/cling/transfer/SimplexTransferListenerTest.java
new file mode 100644
index 0000000000..36abafaf5b
--- /dev/null
+++ 
b/impl/maven-cli/src/test/java/org/apache/maven/cling/transfer/SimplexTransferListenerTest.java
@@ -0,0 +1,112 @@
+/*
+ * 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.cling.transfer;
+
+import java.io.File;
+
+import org.eclipse.aether.DefaultRepositorySystemSession;
+import org.eclipse.aether.RepositorySystemSession;
+import org.eclipse.aether.transfer.TransferCancelledException;
+import org.eclipse.aether.transfer.TransferEvent;
+import org.eclipse.aether.transfer.TransferListener;
+import org.eclipse.aether.transfer.TransferResource;
+import org.junit.jupiter.api.Test;
+import org.mockito.Mockito;
+
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+class SimplexTransferListenerTest {
+    @Test
+    void cancellation() throws InterruptedException {
+        TransferListener delegate = new TransferListener() {
+            @Override
+            public void transferInitiated(TransferEvent event) throws 
TransferCancelledException {
+                throw new TransferCancelledException();
+            }
+
+            @Override
+            public void transferStarted(TransferEvent event) throws 
TransferCancelledException {
+                throw new TransferCancelledException();
+            }
+
+            @Override
+            public void transferProgressed(TransferEvent event) throws 
TransferCancelledException {
+                throw new TransferCancelledException();
+            }
+
+            @Override
+            public void transferCorrupted(TransferEvent event) throws 
TransferCancelledException {
+                throw new TransferCancelledException();
+            }
+
+            @Override
+            public void transferSucceeded(TransferEvent event) {}
+
+            @Override
+            public void transferFailed(TransferEvent event) {}
+        };
+
+        SimplexTransferListener listener = new 
SimplexTransferListener(delegate);
+
+        TransferResource resource =
+                new TransferResource(null, null, 
"http://maven.org/test/test-resource";, new File("file"), null);
+        DefaultRepositorySystemSession session = new 
DefaultRepositorySystemSession(h -> false); // no close handle
+
+        // for technical reasons we cannot throw here, even if delegate does 
cancel transfer
+        listener.transferInitiated(event(session, resource, 
TransferEvent.EventType.INITIATED));
+
+        Thread.sleep(500); // to make sure queue is processed, cancellation 
applied
+
+        // subsequent call will cancel
+        assertThrows(
+                TransferCancelledException.class,
+                () -> listener.transferStarted(event(session, resource, 
TransferEvent.EventType.STARTED)));
+    }
+
+    @Test
+    void handlesAbsentTransferSource() throws InterruptedException, 
TransferCancelledException {
+        TransferResource resource = new TransferResource(null, null, 
"http://maven.org/test/test-resource";, null, null);
+
+        RepositorySystemSession session = 
Mockito.mock(RepositorySystemSession.class);
+        TransferListener delegate = Mockito.mock(TransferListener.class);
+        SimplexTransferListener listener = new 
SimplexTransferListener(delegate);
+
+        TransferEvent transferInitiatedEvent = event(session, resource, 
TransferEvent.EventType.INITIATED);
+        TransferEvent transferStartedEvent = event(session, resource, 
TransferEvent.EventType.STARTED);
+        TransferEvent transferProgressedEvent = event(session, resource, 
TransferEvent.EventType.PROGRESSED);
+        TransferEvent transferSucceededEvent = event(session, resource, 
TransferEvent.EventType.SUCCEEDED);
+
+        listener.transferInitiated(transferInitiatedEvent);
+        listener.transferStarted(transferStartedEvent);
+        listener.transferProgressed(transferProgressedEvent);
+        listener.transferSucceeded(transferSucceededEvent);
+
+        Thread.sleep(500); // to make sure queue is processed, cancellation 
applied
+
+        Mockito.verify(delegate).transferInitiated(transferInitiatedEvent);
+        Mockito.verify(delegate).transferStarted(transferStartedEvent);
+        Mockito.verify(delegate).transferProgressed(transferProgressedEvent);
+        Mockito.verify(delegate).transferSucceeded(transferSucceededEvent);
+    }
+
+    private static TransferEvent event(
+            RepositorySystemSession session, TransferResource resource, 
TransferEvent.EventType type) {
+        return new TransferEvent.Builder(session, 
resource).setType(type).build();
+    }
+}
diff --git 
a/impl/maven-impl/src/main/java/org/apache/maven/internal/impl/AbstractSession.java
 
b/impl/maven-impl/src/main/java/org/apache/maven/internal/impl/AbstractSession.java
index e0a59e07ea..a224563131 100644
--- 
a/impl/maven-impl/src/main/java/org/apache/maven/internal/impl/AbstractSession.java
+++ 
b/impl/maven-impl/src/main/java/org/apache/maven/internal/impl/AbstractSession.java
@@ -44,6 +44,7 @@ import org.apache.maven.api.Exclusion;
 import org.apache.maven.api.Language;
 import org.apache.maven.api.Listener;
 import org.apache.maven.api.LocalRepository;
+import org.apache.maven.api.MonotonicClock;
 import org.apache.maven.api.Node;
 import org.apache.maven.api.Packaging;
 import org.apache.maven.api.PathScope;
@@ -92,6 +93,7 @@ import org.eclipse.aether.DefaultRepositorySystemSession;
 import org.eclipse.aether.RepositorySystem;
 import org.eclipse.aether.RepositorySystemSession;
 import org.eclipse.aether.artifact.ArtifactType;
+import org.eclipse.aether.transfer.TransferResource;
 
 import static org.apache.maven.internal.impl.Utils.map;
 import static org.apache.maven.internal.impl.Utils.nonNull;
@@ -113,6 +115,10 @@ public abstract class AbstractSession implements 
InternalSession {
     private final Map<org.eclipse.aether.graph.Dependency, Dependency> 
allDependencies =
             Collections.synchronizedMap(new WeakHashMap<>());
 
+    static {
+        TransferResource.setClock(MonotonicClock.get());
+    }
+
     public AbstractSession(
             RepositorySystemSession session,
             RepositorySystem repositorySystem,

Reply via email to