From 2ed783f2678b38b041da86c8bf0d6e08b28a133c Mon Sep 17 00:00:00 2001
From: Kevin Clark <kevin.clark@gmail.com>
Date: Fri, 12 Jan 2024 23:51:52 -0800
Subject: [PATCH 1/4] Add JUnit5 and trivially use it in junction core

Setting up basic usage before going deeper. This pins JUnit5 5.10.1 and
removes the unused JUnit4.
---
 WORKSPACE                                     |  16 +
 generate_library_deps.py                      |   7 +-
 junction/core/BUILD                           |  18 +-
 .../littletonrobotics/junction/AutoLog.java   |   0
 .../junction/AutoLogOutput.java               |   0
 .../junction/AutoLogOutputManager.java        |   0
 .../junction/CheckInstall.java                |   0
 .../junction/LogDataReceiver.java             |   0
 .../junction/LogFileUtil.java                 |   0
 .../junction/LogReplaySource.java             |   0
 .../littletonrobotics/junction/LogTable.java  |   0
 .../junction/LoggedRobot.java                 |   0
 .../littletonrobotics/junction/Logger.java    |   0
 .../junction/ReceiverThread.java              |   0
 .../junction/console/ConsoleSource.java       |   0
 .../junction/console/RIOConsoleSource.java    |   0
 .../junction/console/SimConsoleSource.java    |   0
 .../junction/inputs/LoggableInputs.java       |   0
 .../junction/inputs/LoggedDriverStation.java  |   0
 .../inputs/LoggedPowerDistribution.java       |   0
 .../junction/inputs/LoggedSystemStats.java    |   0
 .../networktables/LoggedDashboardBoolean.java |   0
 .../networktables/LoggedDashboardChooser.java |   0
 .../networktables/LoggedDashboardInput.java   |   0
 .../networktables/LoggedDashboardNumber.java  |   0
 .../networktables/LoggedDashboardString.java  |   0
 .../junction/networktables/NT4Publisher.java  |   0
 .../junction/rlog/RLOGEncoder.java            |   0
 .../junction/rlog/RLOGServer.java             |   0
 .../junction/wpilog/WPILOGConstants.java      |   0
 .../junction/wpilog/WPILOGReader.java         |   0
 .../junction/wpilog/WPILOGWriter.java         |   0
 .../junction/LogTableTest.java                |  26 ++
 library_deps.bzl                              |   2 +-
 maven_install.json                            | 286 +++++++++++++++---
 35 files changed, 317 insertions(+), 38 deletions(-)
 rename junction/core/src/{ => main}/org/littletonrobotics/junction/AutoLog.java (100%)
 rename junction/core/src/{ => main}/org/littletonrobotics/junction/AutoLogOutput.java (100%)
 rename junction/core/src/{ => main}/org/littletonrobotics/junction/AutoLogOutputManager.java (100%)
 rename junction/core/src/{ => main}/org/littletonrobotics/junction/CheckInstall.java (100%)
 rename junction/core/src/{ => main}/org/littletonrobotics/junction/LogDataReceiver.java (100%)
 rename junction/core/src/{ => main}/org/littletonrobotics/junction/LogFileUtil.java (100%)
 rename junction/core/src/{ => main}/org/littletonrobotics/junction/LogReplaySource.java (100%)
 rename junction/core/src/{ => main}/org/littletonrobotics/junction/LogTable.java (100%)
 rename junction/core/src/{ => main}/org/littletonrobotics/junction/LoggedRobot.java (100%)
 rename junction/core/src/{ => main}/org/littletonrobotics/junction/Logger.java (100%)
 rename junction/core/src/{ => main}/org/littletonrobotics/junction/ReceiverThread.java (100%)
 rename junction/core/src/{ => main}/org/littletonrobotics/junction/console/ConsoleSource.java (100%)
 rename junction/core/src/{ => main}/org/littletonrobotics/junction/console/RIOConsoleSource.java (100%)
 rename junction/core/src/{ => main}/org/littletonrobotics/junction/console/SimConsoleSource.java (100%)
 rename junction/core/src/{ => main}/org/littletonrobotics/junction/inputs/LoggableInputs.java (100%)
 rename junction/core/src/{ => main}/org/littletonrobotics/junction/inputs/LoggedDriverStation.java (100%)
 rename junction/core/src/{ => main}/org/littletonrobotics/junction/inputs/LoggedPowerDistribution.java (100%)
 rename junction/core/src/{ => main}/org/littletonrobotics/junction/inputs/LoggedSystemStats.java (100%)
 rename junction/core/src/{ => main}/org/littletonrobotics/junction/networktables/LoggedDashboardBoolean.java (100%)
 rename junction/core/src/{ => main}/org/littletonrobotics/junction/networktables/LoggedDashboardChooser.java (100%)
 rename junction/core/src/{ => main}/org/littletonrobotics/junction/networktables/LoggedDashboardInput.java (100%)
 rename junction/core/src/{ => main}/org/littletonrobotics/junction/networktables/LoggedDashboardNumber.java (100%)
 rename junction/core/src/{ => main}/org/littletonrobotics/junction/networktables/LoggedDashboardString.java (100%)
 rename junction/core/src/{ => main}/org/littletonrobotics/junction/networktables/NT4Publisher.java (100%)
 rename junction/core/src/{ => main}/org/littletonrobotics/junction/rlog/RLOGEncoder.java (100%)
 rename junction/core/src/{ => main}/org/littletonrobotics/junction/rlog/RLOGServer.java (100%)
 rename junction/core/src/{ => main}/org/littletonrobotics/junction/wpilog/WPILOGConstants.java (100%)
 rename junction/core/src/{ => main}/org/littletonrobotics/junction/wpilog/WPILOGReader.java (100%)
 rename junction/core/src/{ => main}/org/littletonrobotics/junction/wpilog/WPILOGWriter.java (100%)
 create mode 100644 junction/core/src/test/org/littletonrobotics/junction/LogTableTest.java

diff --git a/WORKSPACE b/WORKSPACE
index 3e4d900a..b003095f 100644
--- a/WORKSPACE
+++ b/WORKSPACE
@@ -163,6 +163,22 @@ http_archive(
     urls = ["https://github.com/google/googletest/archive/e2239ee6043f73722e7aa812a459f54a28552929.zip"],
 )
 
+# JUnit5 is used for unit testing in Java. We pull that in via contrib_rules_jvm.
+http_archive(
+    name = "contrib_rules_jvm",
+    sha256 = "bfb24b0959b98d1f4b2181896a42b0e01a869b7994d53158d48e3ef979aafd89",
+    strip_prefix = "rules_jvm-0.21.4",
+    url = "https://github.com/bazel-contrib/rules_jvm/releases/download/v0.21.4/rules_jvm-v0.21.4.tar.gz",
+)
+
+load("@contrib_rules_jvm//:repositories.bzl", "contrib_rules_jvm_deps")
+
+contrib_rules_jvm_deps()
+
+load("@contrib_rules_jvm//:setup.bzl", "contrib_rules_jvm_setup")
+
+contrib_rules_jvm_setup()
+
 # This makes WPILib's source repo (allwpilib) available as a repository within our Bazel workspace.
 #new_git_repository(
 #    name = "allwpilib",
diff --git a/generate_library_deps.py b/generate_library_deps.py
index d944cf2f..5cdc36bf 100644
--- a/generate_library_deps.py
+++ b/generate_library_deps.py
@@ -37,9 +37,12 @@
     "com.fasterxml.jackson.core:jackson-databind:2.15.2",
     "org.ejml:ejml-simple:0.43.1",
     "org.ejml:ejml-core:0.43.1",
-    "junit:junit:4.13.2",
     "com.squareup:javapoet:1.13.0",
-    "us.hebi.quickbuf:quickbuf-runtime:1.3.2"
+    "us.hebi.quickbuf:quickbuf-runtime:1.3.2",
+    "org.junit.jupiter:junit-jupiter-api:5.10.1",
+    "org.junit.jupiter:junit-jupiter-engine:5.10.1",
+    "org.junit.platform:junit-platform-launcher:1.10.1",
+    "org.junit.platform:junit-platform-reporting:1.10.1",
 ]
 
 # WPILib dependencies to pull from frcmaven.  If no version is provided, the above version is used.
diff --git a/junction/core/BUILD b/junction/core/BUILD
index 3669da45..0e8bfe42 100644
--- a/junction/core/BUILD
+++ b/junction/core/BUILD
@@ -1,8 +1,9 @@
 load("//build_tools/repo:java_export.bzl", "java_export")
+load("@contrib_rules_jvm//java:defs.bzl", "java_test_suite")
 
 java_library(
     name = "core",
-    srcs = glob(["src/**/*.java"]),
+    srcs = glob(["src/main/**/*.java"]),
     tags = ["ci_build"],
     visibility = ["//visibility:public"],
     deps = [
@@ -27,3 +28,18 @@ java_export(
     visibility = ["//visibility:public"],
     runtime_deps = [":core"],
 )
+
+java_test_suite(
+    name = "core-tests",
+    srcs = glob(["src/test/**/*.java"]),
+    runner = "junit5",
+    runtime_deps = [
+        "@maven//:org_junit_jupiter_junit_jupiter_engine",
+        "@maven//:org_junit_platform_junit_platform_launcher",
+        "@maven//:org_junit_platform_junit_platform_reporting",
+    ],
+    deps = [
+        ":core-export",
+        "@maven//:org_junit_jupiter_junit_jupiter_api",
+    ],
+)
diff --git a/junction/core/src/org/littletonrobotics/junction/AutoLog.java b/junction/core/src/main/org/littletonrobotics/junction/AutoLog.java
similarity index 100%
rename from junction/core/src/org/littletonrobotics/junction/AutoLog.java
rename to junction/core/src/main/org/littletonrobotics/junction/AutoLog.java
diff --git a/junction/core/src/org/littletonrobotics/junction/AutoLogOutput.java b/junction/core/src/main/org/littletonrobotics/junction/AutoLogOutput.java
similarity index 100%
rename from junction/core/src/org/littletonrobotics/junction/AutoLogOutput.java
rename to junction/core/src/main/org/littletonrobotics/junction/AutoLogOutput.java
diff --git a/junction/core/src/org/littletonrobotics/junction/AutoLogOutputManager.java b/junction/core/src/main/org/littletonrobotics/junction/AutoLogOutputManager.java
similarity index 100%
rename from junction/core/src/org/littletonrobotics/junction/AutoLogOutputManager.java
rename to junction/core/src/main/org/littletonrobotics/junction/AutoLogOutputManager.java
diff --git a/junction/core/src/org/littletonrobotics/junction/CheckInstall.java b/junction/core/src/main/org/littletonrobotics/junction/CheckInstall.java
similarity index 100%
rename from junction/core/src/org/littletonrobotics/junction/CheckInstall.java
rename to junction/core/src/main/org/littletonrobotics/junction/CheckInstall.java
diff --git a/junction/core/src/org/littletonrobotics/junction/LogDataReceiver.java b/junction/core/src/main/org/littletonrobotics/junction/LogDataReceiver.java
similarity index 100%
rename from junction/core/src/org/littletonrobotics/junction/LogDataReceiver.java
rename to junction/core/src/main/org/littletonrobotics/junction/LogDataReceiver.java
diff --git a/junction/core/src/org/littletonrobotics/junction/LogFileUtil.java b/junction/core/src/main/org/littletonrobotics/junction/LogFileUtil.java
similarity index 100%
rename from junction/core/src/org/littletonrobotics/junction/LogFileUtil.java
rename to junction/core/src/main/org/littletonrobotics/junction/LogFileUtil.java
diff --git a/junction/core/src/org/littletonrobotics/junction/LogReplaySource.java b/junction/core/src/main/org/littletonrobotics/junction/LogReplaySource.java
similarity index 100%
rename from junction/core/src/org/littletonrobotics/junction/LogReplaySource.java
rename to junction/core/src/main/org/littletonrobotics/junction/LogReplaySource.java
diff --git a/junction/core/src/org/littletonrobotics/junction/LogTable.java b/junction/core/src/main/org/littletonrobotics/junction/LogTable.java
similarity index 100%
rename from junction/core/src/org/littletonrobotics/junction/LogTable.java
rename to junction/core/src/main/org/littletonrobotics/junction/LogTable.java
diff --git a/junction/core/src/org/littletonrobotics/junction/LoggedRobot.java b/junction/core/src/main/org/littletonrobotics/junction/LoggedRobot.java
similarity index 100%
rename from junction/core/src/org/littletonrobotics/junction/LoggedRobot.java
rename to junction/core/src/main/org/littletonrobotics/junction/LoggedRobot.java
diff --git a/junction/core/src/org/littletonrobotics/junction/Logger.java b/junction/core/src/main/org/littletonrobotics/junction/Logger.java
similarity index 100%
rename from junction/core/src/org/littletonrobotics/junction/Logger.java
rename to junction/core/src/main/org/littletonrobotics/junction/Logger.java
diff --git a/junction/core/src/org/littletonrobotics/junction/ReceiverThread.java b/junction/core/src/main/org/littletonrobotics/junction/ReceiverThread.java
similarity index 100%
rename from junction/core/src/org/littletonrobotics/junction/ReceiverThread.java
rename to junction/core/src/main/org/littletonrobotics/junction/ReceiverThread.java
diff --git a/junction/core/src/org/littletonrobotics/junction/console/ConsoleSource.java b/junction/core/src/main/org/littletonrobotics/junction/console/ConsoleSource.java
similarity index 100%
rename from junction/core/src/org/littletonrobotics/junction/console/ConsoleSource.java
rename to junction/core/src/main/org/littletonrobotics/junction/console/ConsoleSource.java
diff --git a/junction/core/src/org/littletonrobotics/junction/console/RIOConsoleSource.java b/junction/core/src/main/org/littletonrobotics/junction/console/RIOConsoleSource.java
similarity index 100%
rename from junction/core/src/org/littletonrobotics/junction/console/RIOConsoleSource.java
rename to junction/core/src/main/org/littletonrobotics/junction/console/RIOConsoleSource.java
diff --git a/junction/core/src/org/littletonrobotics/junction/console/SimConsoleSource.java b/junction/core/src/main/org/littletonrobotics/junction/console/SimConsoleSource.java
similarity index 100%
rename from junction/core/src/org/littletonrobotics/junction/console/SimConsoleSource.java
rename to junction/core/src/main/org/littletonrobotics/junction/console/SimConsoleSource.java
diff --git a/junction/core/src/org/littletonrobotics/junction/inputs/LoggableInputs.java b/junction/core/src/main/org/littletonrobotics/junction/inputs/LoggableInputs.java
similarity index 100%
rename from junction/core/src/org/littletonrobotics/junction/inputs/LoggableInputs.java
rename to junction/core/src/main/org/littletonrobotics/junction/inputs/LoggableInputs.java
diff --git a/junction/core/src/org/littletonrobotics/junction/inputs/LoggedDriverStation.java b/junction/core/src/main/org/littletonrobotics/junction/inputs/LoggedDriverStation.java
similarity index 100%
rename from junction/core/src/org/littletonrobotics/junction/inputs/LoggedDriverStation.java
rename to junction/core/src/main/org/littletonrobotics/junction/inputs/LoggedDriverStation.java
diff --git a/junction/core/src/org/littletonrobotics/junction/inputs/LoggedPowerDistribution.java b/junction/core/src/main/org/littletonrobotics/junction/inputs/LoggedPowerDistribution.java
similarity index 100%
rename from junction/core/src/org/littletonrobotics/junction/inputs/LoggedPowerDistribution.java
rename to junction/core/src/main/org/littletonrobotics/junction/inputs/LoggedPowerDistribution.java
diff --git a/junction/core/src/org/littletonrobotics/junction/inputs/LoggedSystemStats.java b/junction/core/src/main/org/littletonrobotics/junction/inputs/LoggedSystemStats.java
similarity index 100%
rename from junction/core/src/org/littletonrobotics/junction/inputs/LoggedSystemStats.java
rename to junction/core/src/main/org/littletonrobotics/junction/inputs/LoggedSystemStats.java
diff --git a/junction/core/src/org/littletonrobotics/junction/networktables/LoggedDashboardBoolean.java b/junction/core/src/main/org/littletonrobotics/junction/networktables/LoggedDashboardBoolean.java
similarity index 100%
rename from junction/core/src/org/littletonrobotics/junction/networktables/LoggedDashboardBoolean.java
rename to junction/core/src/main/org/littletonrobotics/junction/networktables/LoggedDashboardBoolean.java
diff --git a/junction/core/src/org/littletonrobotics/junction/networktables/LoggedDashboardChooser.java b/junction/core/src/main/org/littletonrobotics/junction/networktables/LoggedDashboardChooser.java
similarity index 100%
rename from junction/core/src/org/littletonrobotics/junction/networktables/LoggedDashboardChooser.java
rename to junction/core/src/main/org/littletonrobotics/junction/networktables/LoggedDashboardChooser.java
diff --git a/junction/core/src/org/littletonrobotics/junction/networktables/LoggedDashboardInput.java b/junction/core/src/main/org/littletonrobotics/junction/networktables/LoggedDashboardInput.java
similarity index 100%
rename from junction/core/src/org/littletonrobotics/junction/networktables/LoggedDashboardInput.java
rename to junction/core/src/main/org/littletonrobotics/junction/networktables/LoggedDashboardInput.java
diff --git a/junction/core/src/org/littletonrobotics/junction/networktables/LoggedDashboardNumber.java b/junction/core/src/main/org/littletonrobotics/junction/networktables/LoggedDashboardNumber.java
similarity index 100%
rename from junction/core/src/org/littletonrobotics/junction/networktables/LoggedDashboardNumber.java
rename to junction/core/src/main/org/littletonrobotics/junction/networktables/LoggedDashboardNumber.java
diff --git a/junction/core/src/org/littletonrobotics/junction/networktables/LoggedDashboardString.java b/junction/core/src/main/org/littletonrobotics/junction/networktables/LoggedDashboardString.java
similarity index 100%
rename from junction/core/src/org/littletonrobotics/junction/networktables/LoggedDashboardString.java
rename to junction/core/src/main/org/littletonrobotics/junction/networktables/LoggedDashboardString.java
diff --git a/junction/core/src/org/littletonrobotics/junction/networktables/NT4Publisher.java b/junction/core/src/main/org/littletonrobotics/junction/networktables/NT4Publisher.java
similarity index 100%
rename from junction/core/src/org/littletonrobotics/junction/networktables/NT4Publisher.java
rename to junction/core/src/main/org/littletonrobotics/junction/networktables/NT4Publisher.java
diff --git a/junction/core/src/org/littletonrobotics/junction/rlog/RLOGEncoder.java b/junction/core/src/main/org/littletonrobotics/junction/rlog/RLOGEncoder.java
similarity index 100%
rename from junction/core/src/org/littletonrobotics/junction/rlog/RLOGEncoder.java
rename to junction/core/src/main/org/littletonrobotics/junction/rlog/RLOGEncoder.java
diff --git a/junction/core/src/org/littletonrobotics/junction/rlog/RLOGServer.java b/junction/core/src/main/org/littletonrobotics/junction/rlog/RLOGServer.java
similarity index 100%
rename from junction/core/src/org/littletonrobotics/junction/rlog/RLOGServer.java
rename to junction/core/src/main/org/littletonrobotics/junction/rlog/RLOGServer.java
diff --git a/junction/core/src/org/littletonrobotics/junction/wpilog/WPILOGConstants.java b/junction/core/src/main/org/littletonrobotics/junction/wpilog/WPILOGConstants.java
similarity index 100%
rename from junction/core/src/org/littletonrobotics/junction/wpilog/WPILOGConstants.java
rename to junction/core/src/main/org/littletonrobotics/junction/wpilog/WPILOGConstants.java
diff --git a/junction/core/src/org/littletonrobotics/junction/wpilog/WPILOGReader.java b/junction/core/src/main/org/littletonrobotics/junction/wpilog/WPILOGReader.java
similarity index 100%
rename from junction/core/src/org/littletonrobotics/junction/wpilog/WPILOGReader.java
rename to junction/core/src/main/org/littletonrobotics/junction/wpilog/WPILOGReader.java
diff --git a/junction/core/src/org/littletonrobotics/junction/wpilog/WPILOGWriter.java b/junction/core/src/main/org/littletonrobotics/junction/wpilog/WPILOGWriter.java
similarity index 100%
rename from junction/core/src/org/littletonrobotics/junction/wpilog/WPILOGWriter.java
rename to junction/core/src/main/org/littletonrobotics/junction/wpilog/WPILOGWriter.java
diff --git a/junction/core/src/test/org/littletonrobotics/junction/LogTableTest.java b/junction/core/src/test/org/littletonrobotics/junction/LogTableTest.java
new file mode 100644
index 00000000..1271afbe
--- /dev/null
+++ b/junction/core/src/test/org/littletonrobotics/junction/LogTableTest.java
@@ -0,0 +1,26 @@
+package org.littletonrobotics.junction;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.littletonrobotics.junction.LogTable.LogValue;
+
+public class LogTableTest {
+    @Test
+    void logTableCanRoundtripIntegers() {
+        LogTable table = new LogTable(0);
+        table.put("int", 5);
+        LogValue val = table.get("int");
+        Assertions.assertEquals(5, val.getInteger());
+    }
+
+    @Test
+    void logTableCanRoundtripIntArrays() {
+        long[] expected = { 1, 2, 3 };
+
+        LogTable table = new LogTable(0);
+        table.put("int-array", expected);
+
+        LogValue val = table.get("int-array");
+        Assertions.assertArrayEquals(expected, val.getIntegerArray());
+    }
+}
diff --git a/library_deps.bzl b/library_deps.bzl
index c092494b..0cda41c7 100644
--- a/library_deps.bzl
+++ b/library_deps.bzl
@@ -4,7 +4,7 @@
 
 load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive", "http_file")
 
-MAVEN_ARTIFACTS = ['io.github.classgraph:classgraph:4.8.128', 'com.fasterxml.jackson.core:jackson-annotations:2.15.2', 'com.fasterxml.jackson.core:jackson-core:2.15.2', 'com.fasterxml.jackson.core:jackson-databind:2.15.2', 'org.ejml:ejml-simple:0.43.1', 'org.ejml:ejml-core:0.43.1', 'junit:junit:4.13.2', 'com.squareup:javapoet:1.13.0', 'us.hebi.quickbuf:quickbuf-runtime:1.3.2']
+MAVEN_ARTIFACTS = ['io.github.classgraph:classgraph:4.8.128', 'com.fasterxml.jackson.core:jackson-annotations:2.15.2', 'com.fasterxml.jackson.core:jackson-core:2.15.2', 'com.fasterxml.jackson.core:jackson-databind:2.15.2', 'org.ejml:ejml-simple:0.43.1', 'org.ejml:ejml-core:0.43.1', 'com.squareup:javapoet:1.13.0', 'us.hebi.quickbuf:quickbuf-runtime:1.3.2', 'org.junit.jupiter:junit-jupiter-api:5.10.1', 'org.junit.jupiter:junit-jupiter-engine:5.10.1', 'org.junit.platform:junit-platform-launcher:1.10.1', 'org.junit.platform:junit-platform-reporting:1.10.1']
 
 WPILIB_VERSION = "2024.3.1"
 
diff --git a/maven_install.json b/maven_install.json
index d8b6417d..760978ce 100644
--- a/maven_install.json
+++ b/maven_install.json
@@ -1,8 +1,8 @@
 {
     "dependency_tree": {
         "__AUTOGENERATED_FILE_DO_NOT_MODIFY_THIS_FILE_MANUALLY": "THERE_IS_NO_DATA_ONLY_ZUUL",
-        "__INPUT_ARTIFACTS_HASH": 1638417235,
-        "__RESOLVED_ARTIFACTS_HASH": -1774475685,
+        "__INPUT_ARTIFACTS_HASH": 66404023,
+        "__RESOLVED_ARTIFACTS_HASH": -1080566257,
         "conflict_resolution": {},
         "dependencies": [
             {
@@ -128,34 +128,26 @@
                 "url": "https://repo1.maven.org/maven2/io/github/classgraph/classgraph/4.8.128/classgraph-4.8.128.jar"
             },
             {
-                "coord": "junit:junit:jar:sources:4.13.2",
-                "dependencies": [
-                    "org.hamcrest:hamcrest-core:jar:sources:1.3"
-                ],
-                "directDependencies": [
-                    "org.hamcrest:hamcrest-core:jar:sources:1.3"
-                ],
-                "file": "v1/https/repo1.maven.org/maven2/junit/junit/4.13.2/junit-4.13.2-sources.jar",
+                "coord": "org.apiguardian:apiguardian-api:jar:sources:1.1.2",
+                "dependencies": [],
+                "directDependencies": [],
+                "file": "v1/https/repo1.maven.org/maven2/org/apiguardian/apiguardian-api/1.1.2/apiguardian-api-1.1.2-sources.jar",
                 "mirror_urls": [
-                    "https://repo1.maven.org/maven2/junit/junit/4.13.2/junit-4.13.2-sources.jar"
+                    "https://repo1.maven.org/maven2/org/apiguardian/apiguardian-api/1.1.2/apiguardian-api-1.1.2-sources.jar"
                 ],
-                "sha256": "34181df6482d40ea4c046b063cb53c7ffae94bdf1b1d62695bdf3adf9dea7e3a",
-                "url": "https://repo1.maven.org/maven2/junit/junit/4.13.2/junit-4.13.2-sources.jar"
+                "sha256": "277a7a4315412817beb6655b324dc7276621e95ebff00b8bf65e17a27b685e2d",
+                "url": "https://repo1.maven.org/maven2/org/apiguardian/apiguardian-api/1.1.2/apiguardian-api-1.1.2-sources.jar"
             },
             {
-                "coord": "junit:junit:4.13.2",
-                "dependencies": [
-                    "org.hamcrest:hamcrest-core:1.3"
-                ],
-                "directDependencies": [
-                    "org.hamcrest:hamcrest-core:1.3"
-                ],
-                "file": "v1/https/repo1.maven.org/maven2/junit/junit/4.13.2/junit-4.13.2.jar",
+                "coord": "org.apiguardian:apiguardian-api:1.1.2",
+                "dependencies": [],
+                "directDependencies": [],
+                "file": "v1/https/repo1.maven.org/maven2/org/apiguardian/apiguardian-api/1.1.2/apiguardian-api-1.1.2.jar",
                 "mirror_urls": [
-                    "https://repo1.maven.org/maven2/junit/junit/4.13.2/junit-4.13.2.jar"
+                    "https://repo1.maven.org/maven2/org/apiguardian/apiguardian-api/1.1.2/apiguardian-api-1.1.2.jar"
                 ],
-                "sha256": "8e495b634469d64fb8acfa3495a065cbacc8a0fff55ce1e31007be4c16dc57d3",
-                "url": "https://repo1.maven.org/maven2/junit/junit/4.13.2/junit-4.13.2.jar"
+                "sha256": "b509448ac506d607319f182537f0b35d71007582ec741832a1f111e5b5b70b38",
+                "url": "https://repo1.maven.org/maven2/org/apiguardian/apiguardian-api/1.1.2/apiguardian-api-1.1.2.jar"
             },
             {
                 "coord": "org.ejml:ejml-cdense:jar:sources:0.43.1",
@@ -422,26 +414,252 @@
                 "url": "https://repo1.maven.org/maven2/org/ejml/ejml-zdense/0.43.1/ejml-zdense-0.43.1.jar"
             },
             {
-                "coord": "org.hamcrest:hamcrest-core:jar:sources:1.3",
+                "coord": "org.junit.jupiter:junit-jupiter-api:jar:sources:5.10.1",
+                "dependencies": [
+                    "org.apiguardian:apiguardian-api:jar:sources:1.1.2",
+                    "org.junit.platform:junit-platform-commons:jar:sources:1.10.1",
+                    "org.opentest4j:opentest4j:jar:sources:1.3.0"
+                ],
+                "directDependencies": [
+                    "org.apiguardian:apiguardian-api:jar:sources:1.1.2",
+                    "org.junit.platform:junit-platform-commons:jar:sources:1.10.1",
+                    "org.opentest4j:opentest4j:jar:sources:1.3.0"
+                ],
+                "file": "v1/https/repo1.maven.org/maven2/org/junit/jupiter/junit-jupiter-api/5.10.1/junit-jupiter-api-5.10.1-sources.jar",
+                "mirror_urls": [
+                    "https://repo1.maven.org/maven2/org/junit/jupiter/junit-jupiter-api/5.10.1/junit-jupiter-api-5.10.1-sources.jar"
+                ],
+                "sha256": "551e054c2e84b79d087f0410c8e6e3dd3e83ae54129593380d48550da441b5ef",
+                "url": "https://repo1.maven.org/maven2/org/junit/jupiter/junit-jupiter-api/5.10.1/junit-jupiter-api-5.10.1-sources.jar"
+            },
+            {
+                "coord": "org.junit.jupiter:junit-jupiter-api:5.10.1",
+                "dependencies": [
+                    "org.apiguardian:apiguardian-api:1.1.2",
+                    "org.junit.platform:junit-platform-commons:1.10.1",
+                    "org.opentest4j:opentest4j:1.3.0"
+                ],
+                "directDependencies": [
+                    "org.apiguardian:apiguardian-api:1.1.2",
+                    "org.junit.platform:junit-platform-commons:1.10.1",
+                    "org.opentest4j:opentest4j:1.3.0"
+                ],
+                "file": "v1/https/repo1.maven.org/maven2/org/junit/jupiter/junit-jupiter-api/5.10.1/junit-jupiter-api-5.10.1.jar",
+                "mirror_urls": [
+                    "https://repo1.maven.org/maven2/org/junit/jupiter/junit-jupiter-api/5.10.1/junit-jupiter-api-5.10.1.jar"
+                ],
+                "sha256": "60d5c398c32dc7039b99282514ad6064061d8417cf959a1f6bd2038cc907c913",
+                "url": "https://repo1.maven.org/maven2/org/junit/jupiter/junit-jupiter-api/5.10.1/junit-jupiter-api-5.10.1.jar"
+            },
+            {
+                "coord": "org.junit.jupiter:junit-jupiter-engine:jar:sources:5.10.1",
+                "dependencies": [
+                    "org.apiguardian:apiguardian-api:jar:sources:1.1.2",
+                    "org.junit.jupiter:junit-jupiter-api:jar:sources:5.10.1",
+                    "org.junit.platform:junit-platform-commons:jar:sources:1.10.1",
+                    "org.junit.platform:junit-platform-engine:jar:sources:1.10.1",
+                    "org.opentest4j:opentest4j:jar:sources:1.3.0"
+                ],
+                "directDependencies": [
+                    "org.apiguardian:apiguardian-api:jar:sources:1.1.2",
+                    "org.junit.jupiter:junit-jupiter-api:jar:sources:5.10.1",
+                    "org.junit.platform:junit-platform-engine:jar:sources:1.10.1"
+                ],
+                "file": "v1/https/repo1.maven.org/maven2/org/junit/jupiter/junit-jupiter-engine/5.10.1/junit-jupiter-engine-5.10.1-sources.jar",
+                "mirror_urls": [
+                    "https://repo1.maven.org/maven2/org/junit/jupiter/junit-jupiter-engine/5.10.1/junit-jupiter-engine-5.10.1-sources.jar"
+                ],
+                "sha256": "00ffb37a2d4a1f5ab224c4cf44b2d050b3d132ca650e9c4251605daf9cd6da9b",
+                "url": "https://repo1.maven.org/maven2/org/junit/jupiter/junit-jupiter-engine/5.10.1/junit-jupiter-engine-5.10.1-sources.jar"
+            },
+            {
+                "coord": "org.junit.jupiter:junit-jupiter-engine:5.10.1",
+                "dependencies": [
+                    "org.apiguardian:apiguardian-api:1.1.2",
+                    "org.junit.jupiter:junit-jupiter-api:5.10.1",
+                    "org.junit.platform:junit-platform-commons:1.10.1",
+                    "org.junit.platform:junit-platform-engine:1.10.1",
+                    "org.opentest4j:opentest4j:1.3.0"
+                ],
+                "directDependencies": [
+                    "org.apiguardian:apiguardian-api:1.1.2",
+                    "org.junit.jupiter:junit-jupiter-api:5.10.1",
+                    "org.junit.platform:junit-platform-engine:1.10.1"
+                ],
+                "file": "v1/https/repo1.maven.org/maven2/org/junit/jupiter/junit-jupiter-engine/5.10.1/junit-jupiter-engine-5.10.1.jar",
+                "mirror_urls": [
+                    "https://repo1.maven.org/maven2/org/junit/jupiter/junit-jupiter-engine/5.10.1/junit-jupiter-engine-5.10.1.jar"
+                ],
+                "sha256": "02930dfe495f93fe70b26550ace3a28f7e1b900c84426c2e4626ce020c7282d6",
+                "url": "https://repo1.maven.org/maven2/org/junit/jupiter/junit-jupiter-engine/5.10.1/junit-jupiter-engine-5.10.1.jar"
+            },
+            {
+                "coord": "org.junit.platform:junit-platform-commons:jar:sources:1.10.1",
+                "dependencies": [
+                    "org.apiguardian:apiguardian-api:jar:sources:1.1.2"
+                ],
+                "directDependencies": [
+                    "org.apiguardian:apiguardian-api:jar:sources:1.1.2"
+                ],
+                "file": "v1/https/repo1.maven.org/maven2/org/junit/platform/junit-platform-commons/1.10.1/junit-platform-commons-1.10.1-sources.jar",
+                "mirror_urls": [
+                    "https://repo1.maven.org/maven2/org/junit/platform/junit-platform-commons/1.10.1/junit-platform-commons-1.10.1-sources.jar"
+                ],
+                "sha256": "8ae867cc64b5f6bbb73e7786e0ac2a69ddf6f1e460e08274feef84b249036e53",
+                "url": "https://repo1.maven.org/maven2/org/junit/platform/junit-platform-commons/1.10.1/junit-platform-commons-1.10.1-sources.jar"
+            },
+            {
+                "coord": "org.junit.platform:junit-platform-commons:1.10.1",
+                "dependencies": [
+                    "org.apiguardian:apiguardian-api:1.1.2"
+                ],
+                "directDependencies": [
+                    "org.apiguardian:apiguardian-api:1.1.2"
+                ],
+                "file": "v1/https/repo1.maven.org/maven2/org/junit/platform/junit-platform-commons/1.10.1/junit-platform-commons-1.10.1.jar",
+                "mirror_urls": [
+                    "https://repo1.maven.org/maven2/org/junit/platform/junit-platform-commons/1.10.1/junit-platform-commons-1.10.1.jar"
+                ],
+                "sha256": "7d9855ee3f3f71f015eb1479559bf923783243c24fbfbd8b29bed8e8099b5672",
+                "url": "https://repo1.maven.org/maven2/org/junit/platform/junit-platform-commons/1.10.1/junit-platform-commons-1.10.1.jar"
+            },
+            {
+                "coord": "org.junit.platform:junit-platform-engine:jar:sources:1.10.1",
+                "dependencies": [
+                    "org.apiguardian:apiguardian-api:jar:sources:1.1.2",
+                    "org.junit.platform:junit-platform-commons:jar:sources:1.10.1",
+                    "org.opentest4j:opentest4j:jar:sources:1.3.0"
+                ],
+                "directDependencies": [
+                    "org.apiguardian:apiguardian-api:jar:sources:1.1.2",
+                    "org.junit.platform:junit-platform-commons:jar:sources:1.10.1",
+                    "org.opentest4j:opentest4j:jar:sources:1.3.0"
+                ],
+                "file": "v1/https/repo1.maven.org/maven2/org/junit/platform/junit-platform-engine/1.10.1/junit-platform-engine-1.10.1-sources.jar",
+                "mirror_urls": [
+                    "https://repo1.maven.org/maven2/org/junit/platform/junit-platform-engine/1.10.1/junit-platform-engine-1.10.1-sources.jar"
+                ],
+                "sha256": "17ac74964fcd82c57130623afe72a99105ca107fc96cb53f169b3a8c9c444c83",
+                "url": "https://repo1.maven.org/maven2/org/junit/platform/junit-platform-engine/1.10.1/junit-platform-engine-1.10.1-sources.jar"
+            },
+            {
+                "coord": "org.junit.platform:junit-platform-engine:1.10.1",
+                "dependencies": [
+                    "org.apiguardian:apiguardian-api:1.1.2",
+                    "org.junit.platform:junit-platform-commons:1.10.1",
+                    "org.opentest4j:opentest4j:1.3.0"
+                ],
+                "directDependencies": [
+                    "org.apiguardian:apiguardian-api:1.1.2",
+                    "org.junit.platform:junit-platform-commons:1.10.1",
+                    "org.opentest4j:opentest4j:1.3.0"
+                ],
+                "file": "v1/https/repo1.maven.org/maven2/org/junit/platform/junit-platform-engine/1.10.1/junit-platform-engine-1.10.1.jar",
+                "mirror_urls": [
+                    "https://repo1.maven.org/maven2/org/junit/platform/junit-platform-engine/1.10.1/junit-platform-engine-1.10.1.jar"
+                ],
+                "sha256": "baa48e470d6dee7369a0a8820c51da89c1463279eda6e13a304d11f45922c760",
+                "url": "https://repo1.maven.org/maven2/org/junit/platform/junit-platform-engine/1.10.1/junit-platform-engine-1.10.1.jar"
+            },
+            {
+                "coord": "org.junit.platform:junit-platform-launcher:jar:sources:1.10.1",
+                "dependencies": [
+                    "org.apiguardian:apiguardian-api:jar:sources:1.1.2",
+                    "org.junit.platform:junit-platform-commons:jar:sources:1.10.1",
+                    "org.junit.platform:junit-platform-engine:jar:sources:1.10.1",
+                    "org.opentest4j:opentest4j:jar:sources:1.3.0"
+                ],
+                "directDependencies": [
+                    "org.apiguardian:apiguardian-api:jar:sources:1.1.2",
+                    "org.junit.platform:junit-platform-engine:jar:sources:1.10.1"
+                ],
+                "file": "v1/https/repo1.maven.org/maven2/org/junit/platform/junit-platform-launcher/1.10.1/junit-platform-launcher-1.10.1-sources.jar",
+                "mirror_urls": [
+                    "https://repo1.maven.org/maven2/org/junit/platform/junit-platform-launcher/1.10.1/junit-platform-launcher-1.10.1-sources.jar"
+                ],
+                "sha256": "f98514c156fe6a257659e2c4e964193ffc1d68df1e003d0d0470b24b9055787e",
+                "url": "https://repo1.maven.org/maven2/org/junit/platform/junit-platform-launcher/1.10.1/junit-platform-launcher-1.10.1-sources.jar"
+            },
+            {
+                "coord": "org.junit.platform:junit-platform-launcher:1.10.1",
+                "dependencies": [
+                    "org.apiguardian:apiguardian-api:1.1.2",
+                    "org.junit.platform:junit-platform-commons:1.10.1",
+                    "org.junit.platform:junit-platform-engine:1.10.1",
+                    "org.opentest4j:opentest4j:1.3.0"
+                ],
+                "directDependencies": [
+                    "org.apiguardian:apiguardian-api:1.1.2",
+                    "org.junit.platform:junit-platform-engine:1.10.1"
+                ],
+                "file": "v1/https/repo1.maven.org/maven2/org/junit/platform/junit-platform-launcher/1.10.1/junit-platform-launcher-1.10.1.jar",
+                "mirror_urls": [
+                    "https://repo1.maven.org/maven2/org/junit/platform/junit-platform-launcher/1.10.1/junit-platform-launcher-1.10.1.jar"
+                ],
+                "sha256": "ded414c504e88d02270331071969084e1b2fd9bcf8443f35d44da2c6e3301bc2",
+                "url": "https://repo1.maven.org/maven2/org/junit/platform/junit-platform-launcher/1.10.1/junit-platform-launcher-1.10.1.jar"
+            },
+            {
+                "coord": "org.junit.platform:junit-platform-reporting:jar:sources:1.10.1",
+                "dependencies": [
+                    "org.apiguardian:apiguardian-api:jar:sources:1.1.2",
+                    "org.junit.platform:junit-platform-commons:jar:sources:1.10.1",
+                    "org.junit.platform:junit-platform-engine:jar:sources:1.10.1",
+                    "org.junit.platform:junit-platform-launcher:jar:sources:1.10.1",
+                    "org.opentest4j:opentest4j:jar:sources:1.3.0"
+                ],
+                "directDependencies": [
+                    "org.apiguardian:apiguardian-api:jar:sources:1.1.2",
+                    "org.junit.platform:junit-platform-launcher:jar:sources:1.10.1"
+                ],
+                "file": "v1/https/repo1.maven.org/maven2/org/junit/platform/junit-platform-reporting/1.10.1/junit-platform-reporting-1.10.1-sources.jar",
+                "mirror_urls": [
+                    "https://repo1.maven.org/maven2/org/junit/platform/junit-platform-reporting/1.10.1/junit-platform-reporting-1.10.1-sources.jar"
+                ],
+                "sha256": "3da58799df48f4a280f4e4ea7a92d5e97d2e357ac1b98885afc04a4445457713",
+                "url": "https://repo1.maven.org/maven2/org/junit/platform/junit-platform-reporting/1.10.1/junit-platform-reporting-1.10.1-sources.jar"
+            },
+            {
+                "coord": "org.junit.platform:junit-platform-reporting:1.10.1",
+                "dependencies": [
+                    "org.apiguardian:apiguardian-api:1.1.2",
+                    "org.junit.platform:junit-platform-commons:1.10.1",
+                    "org.junit.platform:junit-platform-engine:1.10.1",
+                    "org.junit.platform:junit-platform-launcher:1.10.1",
+                    "org.opentest4j:opentest4j:1.3.0"
+                ],
+                "directDependencies": [
+                    "org.apiguardian:apiguardian-api:1.1.2",
+                    "org.junit.platform:junit-platform-launcher:1.10.1"
+                ],
+                "file": "v1/https/repo1.maven.org/maven2/org/junit/platform/junit-platform-reporting/1.10.1/junit-platform-reporting-1.10.1.jar",
+                "mirror_urls": [
+                    "https://repo1.maven.org/maven2/org/junit/platform/junit-platform-reporting/1.10.1/junit-platform-reporting-1.10.1.jar"
+                ],
+                "sha256": "222245aba7091f6660aa72d491e8c621faf19146a58126d058249aa8abf151df",
+                "url": "https://repo1.maven.org/maven2/org/junit/platform/junit-platform-reporting/1.10.1/junit-platform-reporting-1.10.1.jar"
+            },
+            {
+                "coord": "org.opentest4j:opentest4j:jar:sources:1.3.0",
                 "dependencies": [],
                 "directDependencies": [],
-                "file": "v1/https/repo1.maven.org/maven2/org/hamcrest/hamcrest-core/1.3/hamcrest-core-1.3-sources.jar",
+                "file": "v1/https/repo1.maven.org/maven2/org/opentest4j/opentest4j/1.3.0/opentest4j-1.3.0-sources.jar",
                 "mirror_urls": [
-                    "https://repo1.maven.org/maven2/org/hamcrest/hamcrest-core/1.3/hamcrest-core-1.3-sources.jar"
+                    "https://repo1.maven.org/maven2/org/opentest4j/opentest4j/1.3.0/opentest4j-1.3.0-sources.jar"
                 ],
-                "sha256": "e223d2d8fbafd66057a8848cc94222d63c3cedd652cc48eddc0ab5c39c0f84df",
-                "url": "https://repo1.maven.org/maven2/org/hamcrest/hamcrest-core/1.3/hamcrest-core-1.3-sources.jar"
+                "sha256": "724a24e3a68267d5ebac9411389a15638a71e50c62448ffa58f59c34d5c1ebb2",
+                "url": "https://repo1.maven.org/maven2/org/opentest4j/opentest4j/1.3.0/opentest4j-1.3.0-sources.jar"
             },
             {
-                "coord": "org.hamcrest:hamcrest-core:1.3",
+                "coord": "org.opentest4j:opentest4j:1.3.0",
                 "dependencies": [],
                 "directDependencies": [],
-                "file": "v1/https/repo1.maven.org/maven2/org/hamcrest/hamcrest-core/1.3/hamcrest-core-1.3.jar",
+                "file": "v1/https/repo1.maven.org/maven2/org/opentest4j/opentest4j/1.3.0/opentest4j-1.3.0.jar",
                 "mirror_urls": [
-                    "https://repo1.maven.org/maven2/org/hamcrest/hamcrest-core/1.3/hamcrest-core-1.3.jar"
+                    "https://repo1.maven.org/maven2/org/opentest4j/opentest4j/1.3.0/opentest4j-1.3.0.jar"
                 ],
-                "sha256": "66fdef91e9739348df7a096aa384a5685f4e875584cce89386a7a47251c4d8e9",
-                "url": "https://repo1.maven.org/maven2/org/hamcrest/hamcrest-core/1.3/hamcrest-core-1.3.jar"
+                "sha256": "48e2df636cab6563ced64dcdff8abb2355627cb236ef0bf37598682ddf742f1b",
+                "url": "https://repo1.maven.org/maven2/org/opentest4j/opentest4j/1.3.0/opentest4j-1.3.0.jar"
             },
             {
                 "coord": "us.hebi.quickbuf:quickbuf-runtime:jar:sources:1.3.2",

From 4afbe90d46fad42fb6349e97f9c9f3af077e99c5 Mon Sep 17 00:00:00 2001
From: Kevin Clark <kevin.clark@gmail.com>
Date: Sat, 13 Jan 2024 08:38:42 -0800
Subject: [PATCH 2/4] Tests: LogTable protobuf/struct serialization

---
 junction/core/BUILD                           |  1 +
 .../junction/LogTableTest.java                | 43 ++++++++++++++++++-
 2 files changed, 42 insertions(+), 2 deletions(-)

diff --git a/junction/core/BUILD b/junction/core/BUILD
index 0e8bfe42..c183aa20 100644
--- a/junction/core/BUILD
+++ b/junction/core/BUILD
@@ -40,6 +40,7 @@ java_test_suite(
     ],
     deps = [
         ":core-export",
+        "//third_party/wpilib:wpilibj-compileonly",
         "@maven//:org_junit_jupiter_junit_jupiter_api",
     ],
 )
diff --git a/junction/core/src/test/org/littletonrobotics/junction/LogTableTest.java b/junction/core/src/test/org/littletonrobotics/junction/LogTableTest.java
index 1271afbe..c3e2d053 100644
--- a/junction/core/src/test/org/littletonrobotics/junction/LogTableTest.java
+++ b/junction/core/src/test/org/littletonrobotics/junction/LogTableTest.java
@@ -1,12 +1,14 @@
 package org.littletonrobotics.junction;
 
+import edu.wpi.first.math.geometry.Rotation2d;
+import edu.wpi.first.math.geometry.proto.Rotation2dProto;
 import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.Test;
 import org.littletonrobotics.junction.LogTable.LogValue;
 
 public class LogTableTest {
     @Test
-    void logTableCanRoundtripIntegers() {
+    void supportsIntegers() {
         LogTable table = new LogTable(0);
         table.put("int", 5);
         LogValue val = table.get("int");
@@ -14,7 +16,7 @@ void logTableCanRoundtripIntegers() {
     }
 
     @Test
-    void logTableCanRoundtripIntArrays() {
+    void supportsIntArrays() {
         long[] expected = { 1, 2, 3 };
 
         LogTable table = new LogTable(0);
@@ -23,4 +25,41 @@ void logTableCanRoundtripIntArrays() {
         LogValue val = table.get("int-array");
         Assertions.assertArrayEquals(expected, val.getIntegerArray());
     }
+
+    @Test
+    void supportsProtobufSerialization() {
+        Rotation2d expected = new Rotation2d(1, 2);
+
+        LogTable table = new LogTable(0);
+        // We're forcing protobuf based serialization so Struct based doesn't run
+        table.put("rot", Rotation2d.proto, expected);
+
+        Rotation2d actual = table.get("rot", Rotation2d.proto, new Rotation2d());
+        Assertions.assertEquals(expected, actual);
+    }
+
+    @Test
+    void supportsStructSerialization() {
+        Rotation2d expected = new Rotation2d(1, 2);
+
+        LogTable table = new LogTable(0);
+        // We're forcing Struct based serialization so Protobuf based doesn't run
+        table.put("rot", Rotation2d.struct, expected);
+
+        Rotation2d actual = table.get("rot", Rotation2d.struct, new Rotation2d());
+        Assertions.assertEquals(expected, actual);
+    }
+
+    @Test
+    void supportsStructArraySerialization() {
+        Rotation2d first = new Rotation2d(1, 2);
+        Rotation2d second = new Rotation2d(3, 4);
+
+        LogTable table = new LogTable(0);
+        // We're forcing Struct serialization
+        table.put("rot", Rotation2d.struct, first, second);
+
+        Rotation2d[] actual = table.get("rot", Rotation2d.struct, new Rotation2d(0, 0), new Rotation2d(0,0));
+        Assertions.assertArrayEquals(new Rotation2d[] { first, second }, actual);
+    }
 }

From 21703fce561ce23ec92c5bf4133c1d1b45ad5408 Mon Sep 17 00:00:00 2001
From: Kevin Clark <kevin.clark@gmail.com>
Date: Sat, 13 Jan 2024 09:23:52 -0800
Subject: [PATCH 3/4] Refactor LogTable struct writes

---
 .../littletonrobotics/junction/LogTable.java  | 45 +++++++++++--------
 .../junction/LogTableTest.java                | 38 +++++++++-------
 2 files changed, 50 insertions(+), 33 deletions(-)

diff --git a/junction/core/src/main/org/littletonrobotics/junction/LogTable.java b/junction/core/src/main/org/littletonrobotics/junction/LogTable.java
index 61d6265d..305eede9 100644
--- a/junction/core/src/main/org/littletonrobotics/junction/LogTable.java
+++ b/junction/core/src/main/org/littletonrobotics/junction/LogTable.java
@@ -359,15 +359,7 @@ private void addStructSchema(Struct<?> struct, Set<String> seen) {
   public <T> void put(String key, Struct<T> struct, T value) {
     if (value == null) return;
     if (writeAllowed(key, LoggableType.Raw)) {
-      addStructSchema(struct, new HashSet<>());
-      if (!structBuffers.containsKey(struct.getTypeString())) {
-        structBuffers.put(struct.getTypeString(), StructBuffer.create(struct));
-      }
-      StructBuffer<T> buffer = (StructBuffer<T>) structBuffers.get(struct.getTypeString());
-      ByteBuffer bb = buffer.write(value);
-      byte[] array = new byte[bb.position()];
-      bb.position(0);
-      bb.get(array);
+      byte[] array = pack(struct, value);
       put(key, new LogValue(array, struct.getTypeString()));
     }
   }
@@ -380,19 +372,36 @@ public <T> void put(String key, Struct<T> struct, T value) {
   public <T> void put(String key, Struct<T> struct, T... value) {
     if (value == null) return;
     if (writeAllowed(key, LoggableType.Raw)) {
-      addStructSchema(struct, new HashSet<>());
-      if (!structBuffers.containsKey(struct.getTypeString())) {
-        structBuffers.put(struct.getTypeString(), StructBuffer.create(struct));
-      }
-      StructBuffer<T> buffer = (StructBuffer<T>) structBuffers.get(struct.getTypeString());
-      ByteBuffer bb = buffer.writeArray(value);
-      byte[] array = new byte[bb.position()];
-      bb.position(0);
-      bb.get(array);
+      byte[] array = pack(struct, value);
       put(key, new LogValue(array, struct.getTypeString() + "[]"));
     }
   }
 
+  private <T> StructBuffer<T> bufferForStruct(Struct<T> struct) {
+    addStructSchema(struct, new HashSet<>());
+    if (!structBuffers.containsKey(struct.getTypeString())) {
+      structBuffers.put(struct.getTypeString(), StructBuffer.create(struct));
+    }
+   return (StructBuffer<T>) structBuffers.get(struct.getTypeString());
+  }
+
+  private <T> byte[] pack(Struct<T> struct, T[] value) {
+    StructBuffer<T> buffer = bufferForStruct(struct);
+    return copyBytes(buffer.writeArray(value));
+  }
+
+  private <T> byte[] pack(Struct<T> struct, T value) {
+    StructBuffer<T> buffer = bufferForStruct(struct);
+    return copyBytes(buffer.write(value));
+  }
+
+  private byte[] copyBytes(ByteBuffer bb) {
+    byte[] array = new byte[bb.position()];
+    bb.position(0);
+    bb.get(array);
+    return array;
+  }
+
   /**
    * Writes a new protobuf value to the table. Skipped if the key already exists
    * as a different type.
diff --git a/junction/core/src/test/org/littletonrobotics/junction/LogTableTest.java b/junction/core/src/test/org/littletonrobotics/junction/LogTableTest.java
index c3e2d053..95c64aba 100644
--- a/junction/core/src/test/org/littletonrobotics/junction/LogTableTest.java
+++ b/junction/core/src/test/org/littletonrobotics/junction/LogTableTest.java
@@ -1,53 +1,62 @@
 package org.littletonrobotics.junction;
 
 import edu.wpi.first.math.geometry.Rotation2d;
-import edu.wpi.first.math.geometry.proto.Rotation2dProto;
 import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
-import org.littletonrobotics.junction.LogTable.LogValue;
 
 public class LogTableTest {
+    private LogTable table;
+
+    @BeforeEach
+    void setup() {
+        table = new LogTable(0);
+    }
+
     @Test
     void supportsIntegers() {
-        LogTable table = new LogTable(0);
         table.put("int", 5);
-        LogValue val = table.get("int");
-        Assertions.assertEquals(5, val.getInteger());
+        Assertions.assertEquals(5, table.get("int").getInteger());
     }
 
     @Test
     void supportsIntArrays() {
         long[] expected = { 1, 2, 3 };
 
-        LogTable table = new LogTable(0);
         table.put("int-array", expected);
 
-        LogValue val = table.get("int-array");
-        Assertions.assertArrayEquals(expected, val.getIntegerArray());
+        Assertions.assertArrayEquals(expected, table.get("int-array").getIntegerArray());
     }
 
     @Test
     void supportsProtobufSerialization() {
         Rotation2d expected = new Rotation2d(1, 2);
 
-        LogTable table = new LogTable(0);
         // We're forcing protobuf based serialization so Struct based doesn't run
         table.put("rot", Rotation2d.proto, expected);
 
-        Rotation2d actual = table.get("rot", Rotation2d.proto, new Rotation2d());
-        Assertions.assertEquals(expected, actual);
+        Assertions.assertEquals(expected, table.get("rot", Rotation2d.proto, new Rotation2d()));
+    }
+
+    @Test
+    void protobufIsntWrittenIfKeyAlreadyInUse() {
+        Rotation2d rot = new Rotation2d(1, 2);
+
+        table.put("rot", 5);
+        table.put("rot", Rotation2d.proto, rot); // This should be skipped
+
+        Assertions.assertNotEquals(rot, table.get("rot", Rotation2d.proto, new Rotation2d()));
+        Assertions.assertEquals(5, table.get("rot").getInteger());
     }
 
     @Test
     void supportsStructSerialization() {
         Rotation2d expected = new Rotation2d(1, 2);
 
-        LogTable table = new LogTable(0);
         // We're forcing Struct based serialization so Protobuf based doesn't run
         table.put("rot", Rotation2d.struct, expected);
 
-        Rotation2d actual = table.get("rot", Rotation2d.struct, new Rotation2d());
-        Assertions.assertEquals(expected, actual);
+        Assertions.assertEquals(expected, table.get("rot", Rotation2d.struct, new Rotation2d()));
     }
 
     @Test
@@ -55,7 +64,6 @@ void supportsStructArraySerialization() {
         Rotation2d first = new Rotation2d(1, 2);
         Rotation2d second = new Rotation2d(3, 4);
 
-        LogTable table = new LogTable(0);
         // We're forcing Struct serialization
         table.put("rot", Rotation2d.struct, first, second);
 

From dd29641f2ae9a567271955c48873df7e6815bd2a Mon Sep 17 00:00:00 2001
From: Kevin Clark <kevin.clark@gmail.com>
Date: Sat, 13 Jan 2024 09:59:08 -0800
Subject: [PATCH 4/4] Minor cleanup to LogTable

Use copyBytes again, and make it static.
---
 .../main/org/littletonrobotics/junction/LogTable.java    | 9 ++-------
 .../org/littletonrobotics/junction/LogTableTest.java     | 1 +
 2 files changed, 3 insertions(+), 7 deletions(-)

diff --git a/junction/core/src/main/org/littletonrobotics/junction/LogTable.java b/junction/core/src/main/org/littletonrobotics/junction/LogTable.java
index 305eede9..2b0384eb 100644
--- a/junction/core/src/main/org/littletonrobotics/junction/LogTable.java
+++ b/junction/core/src/main/org/littletonrobotics/junction/LogTable.java
@@ -25,8 +25,6 @@
 import java.util.Objects;
 import java.util.Set;
 
-import org.littletonrobotics.junction.LogTable.LogValue;
-
 import edu.wpi.first.units.ImmutableMeasure;
 import edu.wpi.first.units.MutableMeasure;
 import edu.wpi.first.units.Measure;
@@ -395,7 +393,7 @@ private <T> byte[] pack(Struct<T> struct, T value) {
     return copyBytes(buffer.write(value));
   }
 
-  private byte[] copyBytes(ByteBuffer bb) {
+  private static byte[] copyBytes(ByteBuffer bb) {
     byte[] array = new byte[bb.position()];
     bb.position(0);
     bb.get(array);
@@ -419,10 +417,7 @@ public <T, MessageType extends ProtoMessage<?>> void put(String key, Protobuf<T,
       ByteBuffer bb;
       try {
         bb = buffer.write(value);
-        byte[] array = new byte[bb.position()];
-        bb.position(0);
-        bb.get(array);
-        put(key, new LogValue(array, proto.getTypeString()));
+        put(key, new LogValue(copyBytes(bb), proto.getTypeString()));
       } catch (IOException e) {
         e.printStackTrace();
       }
diff --git a/junction/core/src/test/org/littletonrobotics/junction/LogTableTest.java b/junction/core/src/test/org/littletonrobotics/junction/LogTableTest.java
index 95c64aba..0a03005f 100644
--- a/junction/core/src/test/org/littletonrobotics/junction/LogTableTest.java
+++ b/junction/core/src/test/org/littletonrobotics/junction/LogTableTest.java
@@ -49,6 +49,7 @@ void protobufIsntWrittenIfKeyAlreadyInUse() {
         Assertions.assertEquals(5, table.get("rot").getInteger());
     }
 
+
     @Test
     void supportsStructSerialization() {
         Rotation2d expected = new Rotation2d(1, 2);