add first unit tests

the ci has always had a 'mvn test' step, but there was nothing to
test. add junit 6 and pin surefire 3.6.0, extract the os detection
into a testable fromOsName method, and cover the movement step math
- including the axis-aligned cases that used to produce infinite
steps and never converge.
This commit is contained in:
bea
2026-09-29 11:51:13 +02:00
parent e551d24bff
commit ed90b246d6
4 changed files with 174 additions and 6 deletions
+13
View File
@@ -31,6 +31,13 @@
<artifactId>jnativehook</artifactId>
<version>2.2.2</version>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>6.1.3</version>
<scope>test</scope>
</dependency>
</dependencies>
<distributionManagement>
@@ -48,6 +55,12 @@
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.6.0</version>
</plugin>
<plugin>
<groupId>org.sonarsource.scanner.maven</groupId>
<artifactId>sonar-maven-plugin</artifactId>
@@ -6,14 +6,18 @@ public class SystemUtil
{
public static OperatingSystem getHostSystem() {
String osName = System.getProperty("os.name").toLowerCase(Locale.ENGLISH);
if (osName.contains("win")) {
return fromOsName(System.getProperty("os.name"));
}
public static OperatingSystem fromOsName(String osName) {
String normalizedOsName = osName.toLowerCase(Locale.ENGLISH);
if (normalizedOsName.contains("win")) {
return OperatingSystem.WINDOWS;
} else if (osName.contains("nix") ||
osName.contains("nux") ||
osName.contains("aix")) {
} else if (normalizedOsName.contains("nix") ||
normalizedOsName.contains("nux") ||
normalizedOsName.contains("aix")) {
return OperatingSystem.LINUX;
} else if (osName.contains("mac")) {
} else if (normalizedOsName.contains("mac")) {
return OperatingSystem.MAC_OS;
} else {
return OperatingSystem.UNKNOWN;
@@ -0,0 +1,117 @@
package wtf.beatrice.autosqueal.controls;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.fail;
class SingleStepMovementTaskTest
{
private static final int MAX_TICKS = 20_000;
private static final float DELTA = 0.0f;
@Test
void noStepWhenAlreadyAtDestination() {
assertEquals(0.0f, SingleStepMovementTask.computeStepX(0, 0), DELTA);
assertEquals(0.0f, SingleStepMovementTask.computeStepY(0, 0), DELTA);
}
@Test
void axisAlignedMovementsProduceNoInfiniteSteps() {
// moving only horizontally: the y step must be 0, not Infinity or NaN
assertEquals(1.0f, SingleStepMovementTask.computeStepX(100, 0), DELTA);
assertEquals(0.0f, SingleStepMovementTask.computeStepY(100, 0), DELTA);
// moving only vertically: the x step must be 0, not Infinity or NaN
assertEquals(0.0f, SingleStepMovementTask.computeStepX(0, 100), DELTA);
assertEquals(1.0f, SingleStepMovementTask.computeStepY(0, 100), DELTA);
}
@Test
void diagonalMovementsMoveBothAxes() {
assertEquals(1.0f, SingleStepMovementTask.computeStepX(100, 100), DELTA);
assertEquals(1.0f, SingleStepMovementTask.computeStepY(100, 100), DELTA);
assertEquals(0.5f, SingleStepMovementTask.computeStepX(50, 100), DELTA);
assertEquals(1.0f, SingleStepMovementTask.computeStepY(50, 100), DELTA);
assertEquals(1.0f, SingleStepMovementTask.computeStepX(100, 50), DELTA);
assertEquals(0.5f, SingleStepMovementTask.computeStepY(100, 50), DELTA);
}
@Test
void stepsAreNeverInfiniteOrNaN() {
for (int lengthX = 0; lengthX <= 400; lengthX += 7) {
for (int lengthY = 0; lengthY <= 400; lengthY += 7) {
float stepX = SingleStepMovementTask.computeStepX(lengthX, lengthY);
float stepY = SingleStepMovementTask.computeStepY(lengthX, lengthY);
assertFalse(Float.isInfinite(stepX), "infinite stepX for lengths [" + lengthX + ", " + lengthY + "]");
assertFalse(Float.isNaN(stepX), "NaN stepX for lengths [" + lengthX + ", " + lengthY + "]");
assertFalse(Float.isInfinite(stepY), "infinite stepY for lengths [" + lengthX + ", " + lengthY + "]");
assertFalse(Float.isNaN(stepY), "NaN stepY for lengths [" + lengthX + ", " + lengthY + "]");
}
}
}
@Test
void advanceMovesTowardTheDestination() {
assertEquals(5.0f, SingleStepMovementTask.advance(5.0f, 5.0f, 1.0f), DELTA); // already there
assertEquals(9.0f, SingleStepMovementTask.advance(10.0f, 5.0f, 1.0f), DELTA); // decreasing
assertEquals(6.0f, SingleStepMovementTask.advance(5.0f, 10.0f, 1.0f), DELTA); // increasing
}
@Test
void adjustedStepUsesExactRemainderWhenClose() {
assertEquals(0.4f, SingleStepMovementTask.adjustedStep(10.0f, 10.4f, 1.0f), 0.00001f);
assertEquals(0.4f, SingleStepMovementTask.adjustedStep(10.8f, 10.4f, 1.0f), 0.00001f);
assertEquals(1.0f, SingleStepMovementTask.adjustedStep(10.0f, 20.0f, 1.0f), DELTA);
}
@Test
void movementConvergesToDestination() {
record MovementCase(float startX, float startY, int destX, int destY) {}
MovementCase[] cases = {
new MovementCase(100, 100, 500, 500), // plain diagonal
new MovementCase(500, 500, 100, 100), // going up-left
new MovementCase(100, 100, 500, 100), // horizontal (used to break: infinite step)
new MovementCase(500, 100, 100, 100), // horizontal, other direction
new MovementCase(100, 100, 100, 500), // vertical (used to break: infinite step)
new MovementCase(100, 500, 100, 100), // vertical, other direction
new MovementCase(105, 105, 105, 105), // already at destination
new MovementCase(0, 0, 2000, 1000), // steep diagonal
new MovementCase(2000, 1000, 0, 0), // same, backwards
new MovementCase(37, 173, 2311, 997), // random-ish, both directions mixed
};
for (MovementCase move : cases) {
float x = move.startX();
float y = move.startY();
int lengthX = Math.round(Math.abs(x - move.destX()));
int lengthY = Math.round(Math.abs(y - move.destY()));
float stepX = SingleStepMovementTask.computeStepX(lengthX, lengthY);
float stepY = SingleStepMovementTask.computeStepY(lengthX, lengthY);
boolean converged = false;
for (int tick = 0; tick < MAX_TICKS; tick++) {
if (Math.round(x) == move.destX() && Math.round(y) == move.destY()) {
converged = true;
break;
}
stepX = SingleStepMovementTask.adjustedStep(x, move.destX(), stepX);
stepY = SingleStepMovementTask.adjustedStep(y, move.destY(), stepY);
x = SingleStepMovementTask.advance(x, move.destX(), stepX);
y = SingleStepMovementTask.advance(y, move.destY(), stepY);
}
if (!converged) {
fail("movement did not converge: stuck at [" + Math.round(x) + ", " + Math.round(y)
+ "] while heading to [" + move.destX() + ", " + move.destY() + "]");
}
}
}
}
@@ -0,0 +1,34 @@
package wtf.beatrice.autosqueal.util;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
class SystemUtilTest
{
@Test
void detectsWindows() {
assertEquals(SystemUtil.OperatingSystem.WINDOWS, SystemUtil.fromOsName("Windows 11"));
}
@Test
void detectsLinux() {
assertEquals(SystemUtil.OperatingSystem.LINUX, SystemUtil.fromOsName("Linux"));
assertEquals(SystemUtil.OperatingSystem.LINUX, SystemUtil.fromOsName("GNU/Linux"));
}
@Test
void detectsMacOS() {
assertEquals(SystemUtil.OperatingSystem.MAC_OS, SystemUtil.fromOsName("Mac OS X"));
}
@Test
void unknownForOtherSystems() {
assertEquals(SystemUtil.OperatingSystem.UNKNOWN, SystemUtil.fromOsName("SunOS"));
}
@Test
void hostSystemMatchesOsName() {
assertEquals(SystemUtil.fromOsName(System.getProperty("os.name")), SystemUtil.getHostSystem());
}
}