Skip to content

Commit 9886ebf

Browse files
bedaHovorkaclaude
andcommitted
Add comprehensive unit test suite with 237 tests across 13 test classes
This commit introduces extensive test coverage for the railway interlocking simulator: **Test Coverage:** - Utility tests (204 tests): Doubleton, EnumUnorientedGraph, HashMapGraph, TreeMultiMap - Context tests (10 tests): DefaultContext operations, concurrent XML serialization - Simulation tests (16 tests): Train behavior, InOutWorker, ShuntingLoop scenarios - XML tests (7 tests): XMLContextFactory parsing and validation with 10 fixture files **Test Infrastructure:** - MockSimulationContext: Mock implementation for isolated testing - TestContextBuilder: Fluent API for building test contexts - TestFixtures: Shared test data and configurations - TestTrackBuilder: Simplified track layout construction **Build Enhancements:** - Mockito 5.7.0 integration for mocking framework - Test resources directory support in build.xml - JUnit assertions enabled (-ea flag) - XML test result reporting **Source Code Fixes:** - DefaultContext.moveCell: Implement cell movement logic - XMLContextFactory.saveContext: Fix isolated node serialization - Train/InOutWorker: Add null pointer validation - Doubleton/TreeMultiMap: Add deprecation notices **Documentation Updates:** - CLAUDE.md: Update test coverage documentation (237 tests, 13 classes) - Package structure: Add testutil and test fixtures sections - Test utilities: Document builders and mock implementations All tests pass with `ant test`. This establishes a strong foundation for maintaining code quality while preserving the legacy 2007 codebase. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
1 parent b46bbbb commit 9886ebf

33 files changed

Lines changed: 4352 additions & 28 deletions

CLAUDE.md

Lines changed: 51 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -284,13 +284,28 @@ src/
284284
│ └── resources/cz/vutbr/fit/interlockSim/
285285
│ └── resource/ - XML schemas and configuration files
286286
└── test/
287-
└── java/cz/vutbr/fit/interlockSim/
288-
├── util/
289-
│ └── Array2DMapTest.java - Array2DMap unit tests (JUnit 5)
290-
├── objects/cells/
291-
│ └── CellTest.java - Cell unit tests (JUnit 5)
292-
└── context/
293-
└── ContextTest.java - Context unit tests (JUnit 5)
287+
├── java/cz/vutbr/fit/interlockSim/
288+
│ ├── context/ - Context and serialization tests
289+
│ │ ├── ConcurrentSaveTest.java
290+
│ │ └── DefaultContextTest.java
291+
│ ├── sim/ - Simulation scenario tests
292+
│ │ ├── InOutWorkerTest.java
293+
│ │ ├── ShuntingLoopTest.java
294+
│ │ └── TrainTest.java
295+
│ ├── testutil/ - Test utilities and builders
296+
│ │ ├── MockSimulationContext.java
297+
│ │ ├── TestContextBuilder.java
298+
│ │ ├── TestFixtures.java
299+
│ │ └── TestTrackBuilder.java
300+
│ ├── util/ - Utility class tests
301+
│ │ ├── DoubletonTest.java
302+
│ │ ├── EnumUnorientedGraphTest.java
303+
│ │ ├── HashMapGraphTest.java
304+
│ │ └── TreeMultiMapTest.java
305+
│ └── xml/ - XML parsing and validation tests
306+
│ └── XMLContextFactoryTest.java
307+
└── resources/cz/vutbr/fit/interlockSim/xml/
308+
└── fixtures/ - Test XML files (10 fixtures)
294309
295310
jdisco/ - Third-party discrete event simulation library (Java 6, separate Maven module)
296311
```
@@ -317,17 +332,42 @@ This is a working historical codebase from 2007. Stability and preservation are
317332

318333
## Testing
319334

320-
JUnit 5.10.1 tests with AssertJ assertions located in `src/test/java/cz/vutbr/fit/interlockSim/`. All dependencies are managed via Apache Ivy.
335+
Comprehensive JUnit 5.10.1 test suite with AssertJ assertions located in `src/test/java/cz/vutbr/fit/interlockSim/`. All dependencies are managed via Apache Ivy.
321336

322337
**Test framework:**
323338
- JUnit 5 (Jupiter API and Engine)
324339
- JUnit Platform for Ant integration
325340
- AssertJ 3.24.2 for fluent assertions
326341

327-
**Current tests:**
342+
**Test coverage (237 tests across 13 test classes):**
343+
344+
**Utility tests:**
328345
- `Array2DMapTest` - 10 tests for 2D array-based map implementation
329-
- `CellTest` - 2 tests for cell segment and direction logic
330-
- `ContextTest` - 4 tests for railway network context operations
346+
- `DoubletonTest` - 66 tests for immutable ordered pair data structure
347+
- `EnumUnorientedGraphTest` - 55 tests for enum-based unoriented graph
348+
- `HashMapGraphTest` - 48 tests for HashMap-based graph implementation
349+
- `TreeMultiMapTest` - 25 tests for tree-based multimap implementation
350+
351+
**Context tests:**
352+
- `DefaultContextTest` - 8 tests for railway network context operations
353+
- `ConcurrentSaveTest` - 2 tests for thread-safe XML serialization
354+
355+
**Simulation tests:**
356+
- `TrainTest` - 6 tests for train behavior and state management
357+
- `InOutWorkerTest` - 8 tests for entry/exit point worker operations
358+
- `ShuntingLoopTest` - 2 tests for shunting loop simulation scenario
359+
360+
**XML tests:**
361+
- `XMLContextFactoryTest` - 7 tests for XML parsing and validation with 10 fixture files
362+
363+
**Test utilities:**
364+
- `MockSimulationContext` - Mock implementation for testing
365+
- `TestContextBuilder` - Fluent builder for test contexts
366+
- `TestFixtures` - Shared test data and configurations
367+
- `TestTrackBuilder` - Fluent builder for test track layouts
368+
369+
**Test resources:**
370+
- `src/test/resources/cz/vutbr/fit/interlockSim/xml/fixtures/` - 10 XML test fixtures for parser validation
331371

332372
**Run tests:**
333373
```bash

build.xml

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ Ivy integration: 2025
3131
<property name="src.main.java" location="src/main/java"/>
3232
<property name="src.test.java" location="src/test/java"/>
3333
<property name="src.main.resources" location="src/main/resources"/>
34+
<property name="src.test.resources" location="src/test/resources"/>
3435

3536
<!-- Build directories -->
3637
<property name="build.main" location="build/main"/>
@@ -171,21 +172,32 @@ Or use Docker:
171172
debug="true">
172173
<classpath refid="test.classpath"/>
173174
</javac>
175+
176+
<!-- Copy test resources to build directory -->
177+
<copy todir="${build.test}">
178+
<fileset dir="${src.test.resources}"/>
179+
</copy>
174180
</target>
175181

176182
<target name="test" depends="compile-test" description="Run tests">
177183
<echo message="Running tests..."/>
184+
<mkdir dir="${build.test}/test-results"/>
178185
<junitlauncher printsummary="yes"
179186
haltonfailure="yes">
180187
<classpath>
181188
<path refid="test.classpath"/>
182189
<pathelement location="${build.test}"/>
183190
</classpath>
184191

185-
<testclasses outputdir="${build.test}">
192+
<testclasses outputdir="${build.test}/test-results">
186193
<fileset dir="${build.test}">
187194
<include name="**/*Test.class"/>
188195
</fileset>
196+
<!-- Enable Java assertions for tests (requires fork) -->
197+
<fork>
198+
<jvmarg value="-ea"/>
199+
</fork>
200+
<listener type="legacy-xml" sendSysOut="true" sendSysErr="true"/>
189201
<listener type="legacy-plain" sendSysOut="true"/>
190202
</testclasses>
191203
</junitlauncher>

ivy.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,10 @@
4040
<!-- AssertJ: Fluent assertions library -->
4141
<dependency org="org.assertj" name="assertj-core" rev="3.24.2" conf="test->default"/>
4242

43+
<!-- Mockito: Mocking framework for testing -->
44+
<dependency org="org.mockito" name="mockito-core" rev="5.7.0" conf="test->default"/>
45+
<dependency org="org.mockito" name="mockito-junit-jupiter" rev="5.7.0" conf="test->default"/>
46+
4347
<!-- SLF4J API 1.7.x: Logging facade (matches jDisco dependency) -->
4448
<dependency org="org.slf4j" name="slf4j-api" rev="1.7.36" conf="compile->default"/>
4549

src/main/java/cz/vutbr/fit/interlockSim/context/DefaultContext.java

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -381,17 +381,14 @@ public void removeLine(TrackBlock line) {
381381
}
382382

383383
public void moveCell(Point from, Point to) {
384-
// EXTENSION
385-
// final Cell fromCell = getGrid().get(from);
386-
// if (!(fromCell instanceof NodeCell)) return;
387-
// final Cell toCell = getGrid().get(to);
388-
// if (toCell != null) {
389-
// setChanged();
390-
// notifyObservers("Move: Target occupied");
391-
// return;
392-
// }
393-
// final NodeCell nodeCell = (NodeCell) fromCell;
394-
// //zkusit najit
384+
final Cell fromCell = getGrid().get(from);
385+
if (!(fromCell instanceof NodeCell)) return;
386+
387+
final Cell toCell = getGrid().get(to);
388+
if (toCell != null) return;
389+
390+
putCell(to, (NodeCell)fromCell);
391+
removeCell(from);
395392
}
396393

397394
public Segment getSegment(final PathSeparator separator, final Track track, final Track secondEndTrack) {

src/main/java/cz/vutbr/fit/interlockSim/sim/InOutWorker.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,12 @@ public class InOutWorker extends LoopProcess {
4040
* @param out
4141
*/
4242
public InOutWorker(SimulationContext context, InOut out) {
43+
if (context == null) {
44+
throw new NullPointerException("context must not be null");
45+
}
46+
if (out == null) {
47+
throw new NullPointerException("out must not be null");
48+
}
4349
this.inOut = out;
4450
this.context = context;
4551
this.next = context.getNextTrackSection(inOut, null);

src/main/java/cz/vutbr/fit/interlockSim/sim/Train.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -495,6 +495,12 @@ protected void derivatives() {
495495
* @param timetable
496496
*/
497497
public Train(SimulationContext context, Timetable timetable) {
498+
if (context == null) {
499+
throw new NullPointerException("context must not be null");
500+
}
501+
if (timetable == null) {
502+
throw new NullPointerException("timetable must not be null");
503+
}
498504
this.context = context;
499505
this.timetable = timetable;
500506
this.length = timetable.getLength();

src/main/java/cz/vutbr/fit/interlockSim/util/Doubleton.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818
* Represents combination of two elements
1919
* @param <T> type of elements
2020
* @param <V> type of additional information
21+
*
22+
* @deprecated should be replaced in kotlin with library thing like Pair
2123
*/
2224
public class Doubleton<T,V> extends AbstractSet<T> {
2325
enum IteratorState {

src/main/java/cz/vutbr/fit/interlockSim/util/TreeMultiMap.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@
2222
*
2323
* @param <K> key
2424
* @param <V> value
25+
*
26+
* @deprecated should be replaced by some standard library implementation in Kotlin
2527
*/
2628
public class TreeMultiMap<K,V> {
2729
private final SortedMap<K,Set<V>> map = new TreeMap<K,Set<V>>();

src/main/java/cz/vutbr/fit/interlockSim/xml/XMLContextFactory.java

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -339,11 +339,28 @@ public boolean saveContext(Context context, File file) {
339339
stringBuilder.append(">\n");
340340
fileWriter.write(stringBuilder.toString());
341341

342-
for (Point p : xmlContext.getGraph().nodeSet()) {
342+
// Save all NodeCells from the grid (including isolated nodes)
343+
// Use LinkedHashSet to avoid duplicates while preserving insertion order
344+
final java.util.Set<Point> allNodes = new java.util.LinkedHashSet<Point>();
345+
346+
// Add all nodes from the graph (nodes with connections)
347+
allNodes.addAll(xmlContext.getGraph().nodeSet());
348+
349+
// Add any isolated NodeCells from the grid that aren't in the graph
350+
for (java.util.Map.Entry<Point, Cell> entry : railWayNetGrid) {
351+
if (entry.getValue() instanceof cz.vutbr.fit.interlockSim.objects.cells.NodeCell) {
352+
allNodes.add(entry.getKey());
353+
}
354+
}
355+
356+
// Write all nodes to XML
357+
for (Point p : allNodes) {
343358
final Cell cell = railWayNetGrid.get(p);
344-
StringBuilder builder = tagFor(p, cell);
345-
spacing(builder, 1);
346-
fileWriter.write(builder.toString());
359+
if (cell instanceof cz.vutbr.fit.interlockSim.objects.cells.NodeCell) {
360+
StringBuilder builder = tagFor(p, cell);
361+
spacing(builder, 1);
362+
fileWriter.write(builder.toString());
363+
}
347364
}
348365

349366
for (Map.Entry<Doubleton<Point, Segment>, TrackBlock> i : xmlContext.getGraph().entrySet()) {
@@ -360,10 +377,11 @@ public boolean saveContext(Context context, File file) {
360377

361378
fileWriter.write("</" + ROOT_ELEMENT_NAME + ">\n");
362379
fileWriter.close();
380+
return true;
363381
} catch (IOException e) {
364382
assert false : e;
383+
return false;
365384
}
366-
return false;
367385
}
368386

369387
//jednotici metoda...

0 commit comments

Comments
 (0)