From 950ded11d008601872229a9d207432a8e9c853db Mon Sep 17 00:00:00 2001 From: Kevin Cooney Date: Sat, 23 Aug 2025 20:30:27 -0700 Subject: [PATCH 1/6] Update InputValidationTest to use @RunWith(Enclosed.class) --- .../java/com/team2813/lib2813/util/InputValidationTest.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/src/test/java/com/team2813/lib2813/util/InputValidationTest.java b/lib/src/test/java/com/team2813/lib2813/util/InputValidationTest.java index d7b44eb2..7d5071f8 100644 --- a/lib/src/test/java/com/team2813/lib2813/util/InputValidationTest.java +++ b/lib/src/test/java/com/team2813/lib2813/util/InputValidationTest.java @@ -5,7 +5,10 @@ import static org.junit.Assert.assertThrows; import org.junit.Test; +import org.junit.experimental.runners.Enclosed; +import org.junit.runner.RunWith; +@RunWith(Enclosed.class) public class InputValidationTest { // Tests for the `InputValidation.checkCanId(...)` method. public static class CheckCanIdTest { From 6244419775360875f3631d91a10c1e71c8f1b9d6 Mon Sep 17 00:00:00 2001 From: Kevin Cooney Date: Sat, 23 Aug 2025 13:20:18 -0700 Subject: [PATCH 2/6] Add logging to try to diagnose SIGSEGV issues --- .../groovy/java-common-conventions.gradle | 52 ++++++++++++++++++- gradle.properties | 1 + 2 files changed, 52 insertions(+), 1 deletion(-) create mode 100644 gradle.properties diff --git a/buildSrc/src/main/groovy/java-common-conventions.gradle b/buildSrc/src/main/groovy/java-common-conventions.gradle index 61f351ed..81b6efee 100644 --- a/buildSrc/src/main/groovy/java-common-conventions.gradle +++ b/buildSrc/src/main/groovy/java-common-conventions.gradle @@ -1,3 +1,7 @@ +import groovy.time.TimeCategory +import org.gradle.api.tasks.testing.logging.TestExceptionFormat +import org.gradle.api.tasks.testing.logging.TestLogEvent + plugins { // Apply the java-library plugin for API and implementation separation. id 'java-library' @@ -9,11 +13,57 @@ java { targetCompatibility = JavaVersion.VERSION_17 } -tasks.withType(JavaCompile) { +tasks.withType(JavaCompile).configureEach { // Configure string concat to always inline compile options.compilerArgs.add '-XDstringConcat=inline' } +tasks.withType(Test).configureEach { testTask -> + + // Display test results as tests run, and a summary at the end. + // See https://stackoverflow.com/a/36130467/95725 + testLogging { + // set options for log level LIFECYCLE + events TestLogEvent.FAILED, + TestLogEvent.PASSED, + TestLogEvent.SKIPPED, + TestLogEvent.STANDARD_OUT + exceptionFormat TestExceptionFormat.FULL + showExceptions true + showCauses true + showStackTraces true + + // set options for log level DEBUG and INFO + debug { + events TestLogEvent.STARTED, + TestLogEvent.FAILED, + TestLogEvent.PASSED, + TestLogEvent.SKIPPED, + TestLogEvent.STANDARD_ERROR, + TestLogEvent.STANDARD_OUT + exceptionFormat TestExceptionFormat.FULL + } + info.events = debug.events + info.exceptionFormat = debug.exceptionFormat + } + + afterSuite { TestDescriptor desc, TestResult result -> + if (!desc.parent) { // will match the outermost suite + def output = "${testTask.project.name}:${testTask.name} results: ${result.resultType} " + + "(${result.testCount} tests, " + + "${result.successfulTestCount} passed, " + + "${result.failedTestCount} failed, " + + "${result.skippedTestCount} skipped) " + + "in ${TimeCategory.minus(new Date(result.endTime), new Date(result.startTime))}" + def startItem = '│ ', endItem = ' │' + def repeatLength = startItem.length() + output.length() + endItem.length() - 2 + println('\n┌' + ('─' * repeatLength) + '┐') + println(startItem + output + endItem) + println('└' + ('─' * repeatLength) + '┘\n') + } + } +} + tasks.named('jar') { manifest { attributes( diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 00000000..6c7b3314 --- /dev/null +++ b/gradle.properties @@ -0,0 +1 @@ +org.gradle.logging.level=info From e2e89177f289b3477f5ef42a2bedae372ad2028c Mon Sep 17 00:00:00 2001 From: Kevin Cooney Date: Sat, 23 Aug 2025 21:34:50 -0700 Subject: [PATCH 3/6] Archive crash logs --- .github/workflows/gradle.yml | 11 ++++++++++- .gitignore | 2 ++ .../src/main/groovy/java-common-conventions.gradle | 6 ++++++ 3 files changed, 18 insertions(+), 1 deletion(-) diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index 8c6da727..6a74e6bb 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -17,4 +17,13 @@ jobs: - name: Setup Gradle uses: gradle/gradle-build-action@v3 - name: Run build with Gradle Wrapper - run: ./gradlew build + run: ./gradlew build -PgearHeads.testJvmArg='-XX:ErrorFile=${{ github.workspace }}/hs_err_pid%p.log' + - name: Archive test artifacts + if: success() || failure() + uses: actions/upload-artifact@v4 + with: + name: test-logs + path: | + */build/reports/tests/test + hs_err_pid*.log + if-no-files-found: ignore diff --git a/.gitignore b/.gitignore index 93f34568..e4fe6314 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,8 @@ bin ctre_sim +# JVM crash logs; see http://www.java.com/en/download/help/error_hotspot.xml +hs_err_pid* # Created by https://www.toptal.com/developers/gitignore/api/intellij # Edit at https://www.toptal.com/developers/gitignore?templates=intellij diff --git a/buildSrc/src/main/groovy/java-common-conventions.gradle b/buildSrc/src/main/groovy/java-common-conventions.gradle index 81b6efee..9156895f 100644 --- a/buildSrc/src/main/groovy/java-common-conventions.gradle +++ b/buildSrc/src/main/groovy/java-common-conventions.gradle @@ -20,6 +20,12 @@ tasks.withType(JavaCompile).configureEach { tasks.withType(Test).configureEach { testTask -> + // Allow adding a JVM arg when running tests via a Gradle Property named 'gearHeads.testJvmArg'. + // In .github/workflows/gradle.yml this is used to specify where to put JVM crash logs. + if (project.hasProperty('gearHeads.testJvmArg')) { + jvmArgs project.properties['gearHeads.testJvmArg'] + } + // Display test results as tests run, and a summary at the end. // See https://stackoverflow.com/a/36130467/95725 testLogging { From c11966ed87af130ddbeb0251969bd638e4531a3b Mon Sep 17 00:00:00 2001 From: Kevin Cooney Date: Thu, 4 Sep 2025 00:45:42 -0700 Subject: [PATCH 4/6] Try not to log STANDARD_OUT when running tests at info or lifecycle --- .../groovy/java-common-conventions.gradle | 21 ++++++++----------- limelight/build.gradle | 2 +- 2 files changed, 10 insertions(+), 13 deletions(-) diff --git a/buildSrc/src/main/groovy/java-common-conventions.gradle b/buildSrc/src/main/groovy/java-common-conventions.gradle index 9156895f..8da1a6be 100644 --- a/buildSrc/src/main/groovy/java-common-conventions.gradle +++ b/buildSrc/src/main/groovy/java-common-conventions.gradle @@ -29,24 +29,21 @@ tasks.withType(Test).configureEach { testTask -> // Display test results as tests run, and a summary at the end. // See https://stackoverflow.com/a/36130467/95725 testLogging { - // set options for log level LIFECYCLE - events TestLogEvent.FAILED, - TestLogEvent.PASSED, - TestLogEvent.SKIPPED, - TestLogEvent.STANDARD_OUT + // Set options for log level LIFECYCLE exceptionFormat TestExceptionFormat.FULL showExceptions true showCauses true showStackTraces true + events = [TestLogEvent.FAILED, TestLogEvent.SKIPPED] - // set options for log level DEBUG and INFO + // Set options for log level DEBUG and INFO debug { - events TestLogEvent.STARTED, - TestLogEvent.FAILED, - TestLogEvent.PASSED, - TestLogEvent.SKIPPED, - TestLogEvent.STANDARD_ERROR, - TestLogEvent.STANDARD_OUT + events = [TestLogEvent.STARTED, + TestLogEvent.FAILED, + TestLogEvent.PASSED, + TestLogEvent.SKIPPED, + TestLogEvent.STANDARD_ERROR, + TestLogEvent.STANDARD_OUT] exceptionFormat TestExceptionFormat.FULL } info.events = debug.events diff --git a/limelight/build.gradle b/limelight/build.gradle index 5b39a260..607856cb 100644 --- a/limelight/build.gradle +++ b/limelight/build.gradle @@ -39,7 +39,7 @@ dependencies { wpi.java.configureTestTasks(test) -tasks.named('test') { +test { // Use JUnit 4 for tests useJUnit() } From 6d1b9a88deb845c32afe0993f5d91cbf4c1c2117 Mon Sep 17 00:00:00 2001 From: Kevin Cooney Date: Thu, 4 Sep 2025 01:44:09 -0700 Subject: [PATCH 5/6] Pass --info to gradlew in gradle workflow; remove gradle.properties --- .github/workflows/gradle.yml | 2 +- gradle.properties | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) delete mode 100644 gradle.properties diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index 6a74e6bb..c89f6a12 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -17,7 +17,7 @@ jobs: - name: Setup Gradle uses: gradle/gradle-build-action@v3 - name: Run build with Gradle Wrapper - run: ./gradlew build -PgearHeads.testJvmArg='-XX:ErrorFile=${{ github.workspace }}/hs_err_pid%p.log' + run: ./gradlew --info build -PgearHeads.testJvmArg='-XX:ErrorFile=${{ github.workspace }}/hs_err_pid%p.log' - name: Archive test artifacts if: success() || failure() uses: actions/upload-artifact@v4 diff --git a/gradle.properties b/gradle.properties deleted file mode 100644 index 6c7b3314..00000000 --- a/gradle.properties +++ /dev/null @@ -1 +0,0 @@ -org.gradle.logging.level=info From 6269fac67d4df0de67403aa5e4a14a7491dfab63 Mon Sep 17 00:00:00 2001 From: Kevin Cooney Date: Fri, 5 Sep 2025 17:56:51 -0700 Subject: [PATCH 6/6] Try closing NetworkTableInstance after a delay --- .../lib2813/preferences/IsolatedPreferences.java | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/lib/src/test/java/com/team2813/lib2813/preferences/IsolatedPreferences.java b/lib/src/test/java/com/team2813/lib2813/preferences/IsolatedPreferences.java index e7544411..b33af67e 100644 --- a/lib/src/test/java/com/team2813/lib2813/preferences/IsolatedPreferences.java +++ b/lib/src/test/java/com/team2813/lib2813/preferences/IsolatedPreferences.java @@ -3,6 +3,9 @@ import edu.wpi.first.networktables.NetworkTable; import edu.wpi.first.networktables.NetworkTableInstance; import edu.wpi.first.wpilibj.Preferences; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; import org.junit.rules.ExternalResource; /** @@ -10,6 +13,8 @@ * tests. */ public final class IsolatedPreferences extends ExternalResource { + private static final ScheduledExecutorService CLOSE_EXECUTOR = + Executors.newSingleThreadScheduledExecutor(); private NetworkTableInstance tempInstance; /** Gets the {@link NetworkTable} that contains the preference values. */ @@ -19,16 +24,14 @@ public NetworkTable getPreferencesTable() { @Override protected void before() { + NetworkTableInstance.getDefault(); tempInstance = NetworkTableInstance.create(); Preferences.setNetworkTableInstance(tempInstance); } @Override protected void after() { - try { - Preferences.setNetworkTableInstance(NetworkTableInstance.getDefault()); - } finally { - tempInstance.close(); - } + Preferences.setNetworkTableInstance(NetworkTableInstance.getDefault()); + CLOSE_EXECUTOR.schedule(() -> tempInstance.close(), 10, TimeUnit.MILLISECONDS); } }