From 1611c6d595926e6398d9a5606c7a895aa9731c7e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Tilmann=20Z=C3=A4schke?= <tilmann.zaeschke@inf.ethz.ch>
Date: Mon, 8 Sep 2025 17:54:02 +0200
Subject: [PATCH 1/3] CI script

---
 .github/workflows/build.yml                   | 94 +++++++++++++++++++
 CHANGELOG.md                                  |  2 +
 .../java/org/scion/multiping/PingAll.java     | 50 +++++-----
 .../util/DownloadAssignmentsFromWeb.java      |  3 +-
 .../scion/multiping/util/DonwloaderTest.java  | 39 ++++++++
 5 files changed, 165 insertions(+), 23 deletions(-)
 create mode 100644 .github/workflows/build.yml
 create mode 100644 src/test/java/org/scion/multiping/util/DonwloaderTest.java

diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
new file mode 100644
index 0000000..4197061
--- /dev/null
+++ b/.github/workflows/build.yml
@@ -0,0 +1,94 @@
+name: Java
+
+on: [push]
+
+env:
+  time: 3
+  time_windows: 5
+
+jobs:
+  format-check:
+    name: Check Code Formatting
+    continue-on-error: true
+    runs-on: ubuntu-latest
+    steps:
+      - name: Checkout
+        uses: actions/checkout@v4
+        with:
+          fetch-depth: 1
+      - name: Check Format
+        run: |
+          mvn --batch-mode -Pverify-format clean compile
+        shell: 'bash'
+
+  codecov:
+    name: Codecov
+    continue-on-error: true
+    runs-on: ubuntu-latest
+    steps:
+      - name: Checkout
+        uses: actions/checkout@v4
+      - name: Set up JDK
+        uses: actions/setup-java@v4
+        with:
+          java-version: 21
+          distribution: 'temurin'
+#      - name: Install dependencies
+#        run: mvn install -DskipTests=true -Dmaven.javadoc.skip=true -B -V
+      - name: Run tests and collect coverage
+        timeout-minutes: ${{ fromJSON(env.time) }}
+        run: mvn -B test
+      - name: Upload coverage reports to Codecov
+        uses: codecov/codecov-action@v4
+        env:
+          CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
+
+  build:
+    runs-on: ubuntu-latest
+    strategy:
+      matrix:
+        # run different builds with the listed java versions
+        java: [ 8, 11, 17, 21 ]
+    name: "build-ubuntu Java ${{ matrix.java }}"
+    steps:
+      - uses: actions/checkout@v4
+      - name: Set up JDK
+        uses: actions/setup-java@v4
+        with:
+          java-version: ${{ matrix.java }}
+          distribution: 'temurin'
+      - name: Build with Maven
+        timeout-minutes: ${{ fromJSON(env.time) }}
+        run: mvn --batch-mode --update-snapshots install
+
+
+  build-windows:
+    runs-on: windows-latest
+    steps:
+      - uses: actions/checkout@v4
+      - name: Set up JDK
+        uses: actions/setup-java@v4
+        with:
+          java-version: 8
+          distribution: 'temurin'
+      - name: Build with Maven
+        timeout-minutes: ${{ fromJSON(env.time_windows) }}
+        run: mvn --batch-mode --update-snapshots install
+
+
+  build-macos:
+    runs-on: macos-latest
+    steps:
+      - uses: actions/checkout@v4
+      - name: Set up JDK
+        uses: actions/setup-java@v4
+        with:
+          java-version: 17
+          distribution: 'temurin'
+      - name: Enable 127.0.0.x addresses
+        run: |
+          chmod +x ./config/enable-macos-loopback.sh
+          ./config/enable-macos-loopback.sh
+      - name: Build with Maven
+        timeout-minutes: ${{ fromJSON(env.time) }}
+        run: mvn --batch-mode --update-snapshots install
\ No newline at end of file
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 3126a5c..8390539 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -13,6 +13,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
   [#14](https://github.com/netsec-ethz/scion-java-multiping/pull/14)
 - Added output of median and average values
   [#15](https://github.com/netsec-ethz/scion-java-multiping/pull/15)
+- Simple CI script to test formatting and JUnit
+  [#15](https://github.com/netsec-ethz/scion-java-multiping/pull/15)
 
 ### Fixed
 
diff --git a/src/main/java/org/scion/multiping/PingAll.java b/src/main/java/org/scion/multiping/PingAll.java
index d6d6f36..360eb90 100644
--- a/src/main/java/org/scion/multiping/PingAll.java
+++ b/src/main/java/org/scion/multiping/PingAll.java
@@ -24,8 +24,10 @@
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
+import java.util.function.Function;
+import java.util.function.Predicate;
+import java.util.function.ToDoubleFunction;
 import java.util.stream.Collectors;
-
 import org.scion.jpan.*;
 import org.scion.jpan.internal.PathRawParser;
 import org.scion.multiping.util.*;
@@ -89,7 +91,7 @@ private enum Policy {
   }
 
   private static final Policy POLICY = Policy.FASTEST_TR_ASYNC;
-  private static final boolean SHOW_PATH = !true;
+  private static final boolean SHOW_PATH = false;
 
   public static void main(String[] args) throws IOException {
     PRINT = true;
@@ -124,18 +126,12 @@ public static void main(String[] args) throws IOException {
     Result maxPaths = results.stream().max(Comparator.comparingInt(Result::getPathCount)).get();
 
     // avg/median:
-    double avgPing =
-        results.stream().filter(Result::isSuccess).mapToDouble(Result::getPingMs).average().orElse(-1);
-    double avgHops =
-        results.stream().filter(r -> r.getHopCount() > 0).mapToInt(Result::getHopCount).average().orElse(-1);
-    double avgPaths =
-        results.stream().filter(r -> r.getPathCount() > 0).mapToInt(Result::getPathCount).average().orElse(-1);
-    List<Double> pings = results.stream().filter(Result::isSuccess).map(Result::getPingMs).sorted().collect(Collectors.toList());
-    double medianPing = pings.isEmpty() ? -1 : pings.get(pings.size() / 2);
-    List<Integer> hops = results.stream().map(Result::getHopCount).filter(hopCount -> hopCount > 0).sorted().collect(Collectors.toList());
-    int medianHops = hops.isEmpty() ? -1 : hops.get(hops.size() / 2);
-    List<Integer> paths = results.stream().map(Result::getPathCount).filter(pathCount -> pathCount > 0).sorted().collect(Collectors.toList());
-    int medianPaths = paths.isEmpty() ? -1 : paths.get(paths.size() / 2);
+    double avgPing = avg(results, Result::isSuccess, Result::getPingMs);
+    double avgHops = avg(results, r -> r.getHopCount() > 0, Result::getHopCount);
+    double avgPaths = avg(results, r -> r.getPathCount() > 0, Result::getPathCount);
+    double medianPing = median(results, Result::isSuccess, Result::getPingMs).orElse(-1.0);
+    int medianHops = median(results, r -> r.getHopCount() > 0, Result::getHopCount).orElse(-1);
+    int medianPaths = median(results, r -> r.getPathCount() > 0, Result::getPathCount).orElse(-1);
 
     println("");
     println("Max hops         = " + maxHops.getHopCount() + ":    " + maxHops);
@@ -404,15 +400,16 @@ public void onException(Throwable t) {
         }
       }
 
-    // Wait for all messages to be received
-    try {
-      if (!barrier.await(1100, TimeUnit.MILLISECONDS)) {
-        throw new IllegalStateException("Missing messages: " + barrier.getCount() + "/" + paths.size());
-      }
-    } catch (InterruptedException e) {
+      // Wait for all messages to be received
+      try {
+        if (!barrier.await(1100, TimeUnit.MILLISECONDS)) {
+          throw new IllegalStateException(
+              "Missing messages: " + barrier.getCount() + "/" + paths.size());
+        }
+      } catch (InterruptedException e) {
         Thread.currentThread().interrupt();
         throw new IllegalStateException(e);
-    }
+      }
 
     } catch (IOException e) {
       println("ERROR: " + e.getMessage());
@@ -441,4 +438,15 @@ public void onException(Throwable t) {
     }
     return best;
   }
+
+  private static double avg(
+      List<Result> list, Predicate<Result> filter, ToDoubleFunction<Result> mapper) {
+    return list.stream().filter(filter).mapToDouble(mapper).average().orElse(-1);
+  }
+
+  private static <T> Optional<T> median(
+      List<Result> list, Predicate<Result> filter, Function<Result, T> mapper) {
+    List<T> list2 = list.stream().filter(filter).map(mapper).sorted().collect(Collectors.toList());
+    return list2.isEmpty() ? Optional.empty() : Optional.of(list2.get(list2.size() / 2));
+  }
 }
diff --git a/src/main/java/org/scion/multiping/util/DownloadAssignmentsFromWeb.java b/src/main/java/org/scion/multiping/util/DownloadAssignmentsFromWeb.java
index aad5709..12c2c28 100644
--- a/src/main/java/org/scion/multiping/util/DownloadAssignmentsFromWeb.java
+++ b/src/main/java/org/scion/multiping/util/DownloadAssignmentsFromWeb.java
@@ -17,7 +17,6 @@
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.List;
-
 import org.jsoup.Jsoup;
 import org.jsoup.nodes.Document;
 import org.jsoup.nodes.Element;
@@ -25,7 +24,7 @@
 
 public class DownloadAssignmentsFromWeb {
   private static final String HTTPS_URL =
-          "https://docs.anapaya.net/en/latest/resources/isd-as-assignments/";
+      "https://docs.anapaya.net/en/latest/resources/isd-as-assignments/";
 
   public static void main(String[] args) throws IOException {
     new DownloadAssignmentsFromWeb().jsoup();
diff --git a/src/test/java/org/scion/multiping/util/DonwloaderTest.java b/src/test/java/org/scion/multiping/util/DonwloaderTest.java
new file mode 100644
index 0000000..24d10fd
--- /dev/null
+++ b/src/test/java/org/scion/multiping/util/DonwloaderTest.java
@@ -0,0 +1,39 @@
+// Copyright 2025 ETH Zurich
+//
+// Licensed 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.scion.multiping.util;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+import java.util.List;
+import org.junit.jupiter.api.Test;
+import org.scion.jpan.ScionUtil;
+
+class DonwloaderTest {
+
+  @Test
+  void findAssignments() {
+    List<ParseAssignments.HostEntry> list = DownloadAssignmentsFromWeb.getList();
+    assertFalse(list.isEmpty());
+
+    boolean found = false;
+    for (ParseAssignments.HostEntry e : list) {
+      if (e.getIsdAs() == ScionUtil.parseIA("64-2:0:9")) {
+        found = true;
+        break;
+      }
+    }
+    assertTrue(found);
+  }
+}

From 9c61bc2617590a6c83bb4af2d6d66d015a9be3c4 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Tilmann=20Z=C3=A4schke?= <tilmann.zaeschke@inf.ethz.ch>
Date: Mon, 8 Sep 2025 17:54:36 +0200
Subject: [PATCH 2/3] CI script

---
 .github/workflows/build.yml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 4197061..5a124f4 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -48,7 +48,7 @@ jobs:
     strategy:
       matrix:
         # run different builds with the listed java versions
-        java: [ 8, 11, 17, 21 ]
+        java: [ 8, 21 ]
     name: "build-ubuntu Java ${{ matrix.java }}"
     steps:
       - uses: actions/checkout@v4

From 05a4b3c92f6cde15f74490eb90d76c24eb1ea0ee Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Tilmann=20Z=C3=A4schke?= <tilmann.zaeschke@inf.ethz.ch>
Date: Mon, 8 Sep 2025 17:55:44 +0200
Subject: [PATCH 3/3] CI script

---
 .github/workflows/build.yml | 4 ----
 1 file changed, 4 deletions(-)

diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 5a124f4..c1f9dfd 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -85,10 +85,6 @@ jobs:
         with:
           java-version: 17
           distribution: 'temurin'
-      - name: Enable 127.0.0.x addresses
-        run: |
-          chmod +x ./config/enable-macos-loopback.sh
-          ./config/enable-macos-loopback.sh
       - name: Build with Maven
         timeout-minutes: ${{ fromJSON(env.time) }}
         run: mvn --batch-mode --update-snapshots install
\ No newline at end of file
