-
Notifications
You must be signed in to change notification settings - Fork 2
ci/test: add automated simulation testing to CI/CD and unit test suite #127
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: 2026-offseason-bot
Are you sure you want to change the base?
Changes from 4 commits
78b50d4
b69ea01
67a2777
5bfe423
bec6b0a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,21 @@ | ||
| #!/usr/bin/env bash | ||
| set -e | ||
|
|
||
| # Change to repo root | ||
| cd "$(dirname "$0")/.." | ||
|
|
||
| if [ "$1" = "--fix" ]; then | ||
| echo "=== Running Spotless Apply ===" | ||
| ./gradlew spotlessApply | ||
| else | ||
| echo "=== Running Spotless Check ===" | ||
| ./gradlew spotlessCheck | ||
| fi | ||
|
|
||
| echo "=== Running SpotBugs Static Analysis ===" | ||
| ./gradlew spotbugsMain | ||
|
|
||
| echo "=== Running Compile and Tests (Unit & Sim Tests) ===" | ||
| ./gradlew test -Pheadless=true | ||
|
|
||
| echo "=== Verification SUCCESSFUL ===" |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,46 @@ | ||
| package frc.robot.sim; | ||
|
|
||
| import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; | ||
| import static org.junit.jupiter.api.Assertions.assertNotNull; | ||
|
|
||
| import edu.wpi.first.wpilibj2.command.CommandScheduler; | ||
| import frc.robot.auton.Auton; | ||
| import frc.robot.subsystems.swerve.Swerve; | ||
| import org.junit.jupiter.api.DisplayName; | ||
| import org.junit.jupiter.api.Test; | ||
|
|
||
| /** Automated simulation tests for the autonomous routine chooser and execution. */ | ||
| public class AutonSimTest extends SimTestBase { | ||
|
|
||
| @Test | ||
| @DisplayName("Auton initializes the SendableChooser and default option without exceptions") | ||
| void testAutonChooserInitialization() { | ||
| assertDoesNotThrow( | ||
| () -> { | ||
| RobotStack stack = createRobotStack(); | ||
| try (Swerve swerve = stack.swerve) { | ||
| Auton auton = new Auton(stack.superStructure); | ||
| assertNotNull(auton.getPathChooser()); | ||
| assertNotNull(auton.getPathChooser().getSelected()); | ||
| } | ||
| }); | ||
| } | ||
|
|
||
| @Test | ||
| @DisplayName("Auton init schedules the selected auto and steps simulation time cleanly") | ||
| void testAutonInitAndStep() { | ||
| assertDoesNotThrow( | ||
| () -> { | ||
| RobotStack stack = createRobotStack(); | ||
| try (Swerve swerve = stack.swerve) { | ||
| Auton auton = new Auton(stack.superStructure); | ||
| CommandScheduler.getInstance().registerSubsystem(stack.superStructure); | ||
|
|
||
| enableAutonomousSim(); | ||
| auton.init(); | ||
|
|
||
| stepSim(0.020, 0.50); | ||
| } | ||
| }); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,68 @@ | ||
| package frc.robot.sim; | ||
|
|
||
| import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; | ||
| import static org.junit.jupiter.api.Assertions.assertNotNull; | ||
|
|
||
| import edu.wpi.first.wpilibj2.command.CommandScheduler; | ||
| import frc.robot.subsystems.hood.Hood; | ||
| import frc.robot.subsystems.intakeExtension.IntakeExtension; | ||
| import frc.robot.subsystems.turret.Turret; | ||
| import org.junit.jupiter.api.DisplayName; | ||
| import org.junit.jupiter.api.Test; | ||
|
|
||
| /** Automated simulation tests for individual mechanism subsystems. */ | ||
| public class MechanismSimTest extends SimTestBase { | ||
|
|
||
| @Test | ||
| @DisplayName("Hood subsystem initializes and runs simulation steps without exceptions") | ||
| void testHoodSimPeriodic() { | ||
| assertDoesNotThrow( | ||
| () -> { | ||
| Hood hood = new Hood(new Hood.HoodConfig()); | ||
| CommandScheduler.getInstance().registerSubsystem(hood); | ||
|
|
||
| enableTeleopSim(); | ||
| stepSim(0.020, 0.50); | ||
|
|
||
| assertNotNull(hood.getSim()); | ||
| }); | ||
| } | ||
|
|
||
| @Test | ||
| @DisplayName("Turret subsystem initializes and runs simulation steps without exceptions") | ||
| void testTurretSimPeriodic() { | ||
| assertDoesNotThrow( | ||
| () -> { | ||
| Turret turret = new Turret(new Turret.TurretConfig()); | ||
| CommandScheduler.getInstance().registerSubsystem(turret); | ||
|
|
||
| enableTeleopSim(); | ||
| stepSim(0.020, 0.50); | ||
|
|
||
| assertNotNull(turret.getSim()); | ||
| }); | ||
| } | ||
|
|
||
| @Test | ||
| @DisplayName( | ||
| "IntakeExtension subsystem initializes and runs simulation steps without exceptions") | ||
| void testIntakeExtensionSimPeriodic() { | ||
| assertDoesNotThrow( | ||
| () -> { | ||
| IntakeExtension.Left.LeftConfig leftConfig = | ||
| new IntakeExtension.Left.LeftConfig(); | ||
| IntakeExtension.Right.RightConfig rightConfig = | ||
| new IntakeExtension.Right.RightConfig(leftConfig); | ||
| IntakeExtension intakeExtension = | ||
| new IntakeExtension( | ||
| new IntakeExtension.IntakeExtensionConfig( | ||
| leftConfig, rightConfig)); | ||
| CommandScheduler.getInstance().registerSubsystem(intakeExtension); | ||
|
|
||
| enableTeleopSim(); | ||
| stepSim(0.020, 0.50); | ||
|
|
||
| assertNotNull(intakeExtension.getSim()); | ||
| }); | ||
| } | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,37 @@ | ||
| package frc.robot.sim; | ||
|
|
||
| import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; | ||
| import static org.junit.jupiter.api.Assertions.assertNotNull; | ||
|
|
||
| import edu.wpi.first.wpilibj2.command.CommandScheduler; | ||
| import frc.robot.RobotSim; | ||
| import frc.robot.subsystems.swerve.Swerve; | ||
| import org.junit.jupiter.api.DisplayName; | ||
| import org.junit.jupiter.api.Test; | ||
|
|
||
| /** Automated simulation tests for the RobotSim visualizer and fuel physics simulation. */ | ||
| public class RobotSimTest extends SimTestBase { | ||
|
|
||
| @Test | ||
| @DisplayName("RobotSim initializes visualizer and fuel physics and updates cleanly") | ||
| void testRobotSimCreationAndPeriodic() { | ||
| assertDoesNotThrow( | ||
| () -> { | ||
| RobotStack stack = createRobotStack(); | ||
| try (Swerve swerve = stack.swerve) { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
rg -n -C2 'sourceCompatibility|targetCompatibility|JavaLanguageVersion' build.gradle build.gradle.kts 2>/dev/null || true
rg -n -C2 'try \(Swerve swerve = stack\.swerve\)' src/test/java/frc/robot/simRepository: Spectrum3847/2026-Spectrum Length of output: 2536 🏁 Script executed: #!/bin/bash
set -euo pipefail
printf '%s\n' '--- test files ---'
cat -n src/test/java/frc/robot/sim/RobotSimTest.java
cat -n src/test/java/frc/robot/sim/SuperStructureSimTest.java
cat -n src/test/java/frc/robot/sim/AutonSimTest.java
printf '%s\n' '--- RobotStack and Swerve declarations/usages ---'
rg -n -C3 'class RobotStack|record RobotStack|stack\.swerve|class Swerve|interface Swerve|implements .*AutoCloseable|close\(' src/main/java src/test/java build.gradle gradle.properties 2>/dev/null || true
printf '%s\n' '--- PMD configuration and Java toolchain ---'
rg -n -C3 'pmd|PMD|sourceCompatibility|targetCompatibility|toolchain|JavaLanguageVersion' . -g '!build' -g '!node_modules' -g '!vendor' 2>/dev/null || trueRepository: Spectrum3847/2026-Spectrum Length of output: 13135 🏁 Script executed: #!/bin/bash
set -euo pipefail
tmpdir="$(mktemp -d)"
trap 'rm -rf "$tmpdir"' EXIT
cat >"$tmpdir/TryResourceField.java" <<'JAVA'
public class TryResourceField {
static class Resource implements AutoCloseable {
public void close() {}
}
static class Holder {
Resource resource = new Resource();
void method() {
try (resource) {
System.out.println("body");
}
}
}
public static void main(String[] args) {
new Holder().method();
}
}
JAVA
if command -v javac >/dev/null 2>&1; then
javac --release 17 "$tmpdir/TryResourceField.java"
echo 'Java 17 accepts an instance-field try-with-resources reference.'
else
echo 'javac is unavailable; source-level behavior requires manual verification.'
fiRepository: Spectrum3847/2026-Spectrum Length of output: 238 🌐 Web query:
💡 Result: As of Java 17 and later, the Java Language Specification (JLS) allows a try-with-resources statement to use an existing variable as a resource, provided that the variable is final or effectively final [1][2]. This capability was introduced in Java 9 to simplify resource management [1][2]. Under the JLS, the resource specification in a try-with-resources statement can consist of: 1. A formal local variable declaration (the original Java 7 approach) [2][3]. 2. A reference to an existing final or effectively final local variable [1][2]. It is important to note that field access or other arbitrary expressions (such as array access or method calls) are not permitted directly within the resource specification [4]. The resource must be a simple variable name that refers to an object implementing AutoCloseable [4]. If you need to manage a resource accessed via a field, you must first assign that field to a local variable and ensure that the local variable remains effectively final before using it in the try-with-resources statement [4]. A variable is considered effectively final if its value is not changed after it is initialized [2]. If you attempt to use a non-final or non-effectively final variable (e.g., a variable reassigned later in the scope) as a resource, the compiler will issue an error [2]. The requirement for a final or effectively final variable ensures that the resource being closed is the same one intended throughout the block's lifecycle, avoiding ambiguity [5]. Citations:
🌐 Web query:
💡 Result: In a try-with-resources statement in Java, the resources declared within the resource specification act as local variables [1]. These variables are scoped strictly to the try-with-resources statement itself, meaning they are accessible within the associated try block and any corresponding catch or finally blocks [1]. Regarding VariableAccess and FieldAccess, there are no special restrictions imposed by the try-with-resources construct that prevent you from accessing class fields or other variables that are in scope [1]. You can access instance or static fields of an object, or any other accessible variables, from within the body of the try-with-resources statement just as you would in any other block of code [1]. The resource variables declared in the header are simply treated as local variable declarations, adhering to standard Java scoping and access rules [1]. Citations: 🌐 Web query:
💡 Result: In Java 17, the try-with-resources statement does not support arbitrary field access expressions (such as Citations:
🏁 Script executed: #!/bin/bash
set -euo pipefail
python3 - <<'PY'
from urllib.request import urlopen
from re import search
urls = [
"https://docs.oracle.com/javase/specs/jls/se17/html/jls-14.html",
"https://docs.oracle.com/javase/specs/jls/se17/jls17.pdf",
]
for url in urls:
try:
data = urlopen(url, timeout=15).read()
print(f"URL: {url}")
print(f"HTTP bytes: {len(data)}")
if url.endswith(".html"):
text = data.decode("utf-8", "replace")
for needle in ("Resource:", "VariableAccess:", "FieldAccess:"):
pos = text.find(needle)
print(f"--- {needle} ---")
print(text[pos:pos + 1800] if pos >= 0 else "not found")
except Exception as exc:
print(f"URL: {url}\nERROR: {exc}")
PY
printf '%s\n' '--- available Java tools ---'
for tool in javac java ecj kotlinc groovy; do
if command -v "$tool" >/dev/null 2>&1; then
printf '%s: %s\n' "$tool" "$(command -v "$tool")"
fi
doneRepository: Spectrum3847/2026-Spectrum Length of output: 626 🏁 Script executed: #!/bin/bash
set -euo pipefail
if command -v curl >/dev/null 2>&1; then
curl --silent --show-error --fail --insecure \
https://docs.oracle.com/javase/specs/jls/se17/html/jls-14.html |
sed -n '/id="jls-14.20.3"/,/id="jls-14.20.4"/p' |
grep -E -A8 -B3 'Resource:|VariableAccess:|FieldAccess:' || true
fi
printf '%s\n' '--- Java compiler module availability ---'
java --list-modules 2>/dev/null | grep -E '^(jdk.compiler|java.compiler)@' || true
printf '%s\n' '--- Java runtime version ---'
java -version 2>&1Repository: Spectrum3847/2026-Spectrum Length of output: 483 🏁 Script executed: #!/bin/bash
set -euo pipefail
tmpdir="$(mktemp -d)"
trap 'rm -rf "$tmpdir"' EXIT
cat >"$tmpdir/TryResourceField.java" <<'JAVA'
public class TryResourceField {
static final class Resource implements AutoCloseable {
`@Override`
public void close() {}
}
static final class RobotStack {
public final Resource swerve = new Resource();
}
static void useStack() {
RobotStack stack = new RobotStack();
try (stack.swerve) {
System.out.println("body");
}
}
}
JAVA
java --add-modules jdk.compiler com.sun.tools.javac.Main \
--release 17 -d "$tmpdir/out" "$tmpdir/TryResourceField.java"
echo 'Java 17 accepted try (stack.swerve).'Repository: Spectrum3847/2026-Spectrum Length of output: 201 Remove the unused resource locals and imports. At all four sites, use 🧰 Tools🪛 PMD (7.26.0)[Medium] 21-21: UnusedLocalVariable (Best Practices): Avoid unused local variables such as 'swerve'. (UnusedLocalVariable (Best Practices)) 📍 Affects 3 files
🤖 Prompt for AI AgentsSource: Linters/SAST tools |
||
| RobotSim robotSim = new RobotSim(stack.superStructure); | ||
| CommandScheduler.getInstance().registerSubsystem(stack.superStructure); | ||
|
|
||
| assertNotNull(robotSim.getBallSim()); | ||
| assertNotNull(RobotSim.leftView); | ||
| assertNotNull(RobotSim.topView); | ||
|
|
||
| enableTeleopSim(); | ||
| stepSim(0.020, 0.50); | ||
|
|
||
| robotSim.updateArticulatedMechanisms(); | ||
| robotSim.getBallSim().tick(); | ||
| } | ||
| }); | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
Repository: Spectrum3847/2026-Spectrum
Length of output: 1453
Remove the invalid
@param dttag.tick()has nodtparameter. Keep the description focused on notifying callbacks with the computed elapsed time.🤖 Prompt for AI Agents