Skip to content

Testing

opencode-agent[bot] edited this page Sep 27, 2026 · 3 revisions

Testing

JNode utilizes a combination of standard JUnit testing on the host JVM and full-system boot testing using virtual machines to verify functionality.

Overview

Testing an operating system is challenging because much of the code cannot run in a standard environment. JNode divides testing into two distinct phases:

  1. Unit Testing (Host JVM): Fast, automated tests that run on the standard Java JVM during the build process.
  2. Boot Testing (Target VM): Manual or scripted tests that run the compiled JNode OS inside a virtual machine (QEMU, VirtualBox, VMware) to verify low-level system behavior.

Unit Testing

JNode uses JUnit 4.5, along with JMock and Mockito for mocking dependencies.

  • Location: Test files are located in the src/test/ directory of each subproject (e.g., core/src/test/, fs/src/test/). There are nearly 400 test files across the repository.
  • Execution:
    • To run all tests across the entire project: sh build.sh tests
    • To run tests for a specific subproject: cd <subproject> && ant test

Limitations of Unit Testing

Because unit tests run on the host JVM (the JVM you use to build JNode, usually OpenJDK on Linux/Windows), they cannot test:

  • Classes that use @MagicPermission or org.jnode.vm.Unsafe.
  • Code that relies on the JNode JIT compilers or specific VmProcessor behaviors.
  • Native hardware interactions.

For these, developers use mocks, or rely on Boot Testing.

The build-tests.xml Convention

Every subproject that has tests ships a build-tests.xml with an all-junit target. all/build.xml's tests target discovers them automatically:

<target name="tests" depends="assemble">
  <subant><fileset dir="${root.dir}" includes="**/build-tests.xml"/></subant>
  <antcall target="check-plugins"/>
</target>
sh build.sh tests                                # every subproject
sh build.sh -f <subproject>/build-tests.xml all-junit   # one subproject

Currently: core, fs, net, shell, cli, gui. Each uses <junit fork="on" haltonfailure="on"> with <classpath refid="cp-test"/>, compiling via the jnode.compile.test preset (host JDK against the JNode core jar) into <subproject>/build/testclasses and writing JUnit XML into <subproject>/build/reports/junit.

The Two shell Test Tiers

shell is unusual in having two independent host-JVM tiers:

Tier Entry point Target What it runs
JUnit org.jnode.test.shell.AllTests all-junit @Test classes, e.g. DefaultInterpreterSequenceTest, DefaultTokenizerTest, ExecuteJavaPackagePrivateTest
Black-box XML TestHarness (shell/src/test/org/jnode/shell/harness/TestHarness.java) all-blackbox, bjorne XML testSpec scripts run in AS_SCRIPT/INTERACTIVE modes

Neither tier runs on JNode itself. TestEmu.initEmu (TestEmu.java:50-77) picks org.jnode.emu.Emu on the host vs ShellUtils.getCurrentShell() on JNode. See Shell-Commands for the harness's temp-directory lifecycle and fixture semantics.

The gui Subproject

gui/build-tests.xml is the newest build-tests.xml (added 2026-09-24) and the first host-JVM unit-test infrastructure for gui. Everything previously under gui/src/test/org/jnode/test/gui/*.java (JInternalFrameTest, JDPTest, SwingTest, AWTFrameTest, …) is a manual main() demo app in no suite.

gui/src/test/org/jnode/awt/swingpeers/GuiTestSuite.java is a @RunWith(Suite.class) over SwingToolkitClippingTest, which lives in the same package as SwingToolkit so it can call the package-private getWindowPaintRegions. It is headless-safe: the fixture uses a plain JInternalFrame("source") on an 800×600 JDesktopPane, because constructing a real java.awt.Frame (or SwingBaseWindow, whose constructor chain is super(title, true, true, true, true)) requires a display the forked test JVM does not have. That is why getWindowPaintRegions takes a JInternalFrame rather than a SwingBaseWindow.

The four tests cover: null when the window is fully visible, one uncovered band for a single occluder, region collapse for two occluders (the subtract chain 1 → 2 → 1), and the empty-list "paint nothing" contract. The desktop-bounds intersection branch and the SwingWindow/null/z < 0 early returns are not covered. See GUI-AWT and JNode-Graphics2D.

Guest Test Programs

Some VM-side tests are main()-style programs in the guest rather than JUnit classes, in core/src/test/org/jnode/test/bugs/ (same style as bug778001.java):

Program Covers
ConversionTest 19 assertions on JLS §5.1.3 float/double → int/long for NaN, ±Infinity, ±1.5, ±0.0, 1e20, -1e20, Long.MAX_VALUE/MIN_VALUE
RintTest strictfp Math.rint ties-to-even and the twoToThe52 trick, which depend on the x87 control word
StrictMathTest In-VM sin/cos/tan, including the large-argument path that used to livelock

See X87-FPU-Support.

VersionTest as the Reference Example

core/src/test/org/jnode/test/util/VersionTest.java went from 41 to 95 @Test methods (282 → 772 lines) with no production change, and is the best template for a well-organised expansion. The 54 new tests are grouped by comment banners:

Lines Banner Tests Pins down
289-400 Expanded assertions: getters 11 all four constructors with/without a tag; getMinor() is Math.max(0, minor) so the internal Undefined = -1 sentinel surfaces as 0; tag parsing from "1.2-foo-bar", "1.2.3-foo.bar", "5-foo", "4.3.2.1-rel"
401-554 Expanded assertions: compareTo 19 lexicographic ordering per field, untagged < tagged, numeric-dominates-tag, reflexive, symmetric, String-vs-int equivalence, "5" < "5.0" because Undefined sorts below a defined value
555-626 Expanded assertions: toString / parsing 8 "0.0.0.0", "0.0", 10-sample round trip, tag-with-hyphen vs tag-with-dot
627-694 Expanded assertions: hashCode / equals 8 the exact formula 16909060 == new Version(1,2,3,4).hashCode() (i.e. 0x01020304); equals is not overridden, so testEqualsDistinctEqualValue asserts compareTo == 0 and equals == false
695-772 Expanded assertions: error cases 8 @Test(expected=IllegalArgumentException.class) for empty tags and >4 parts, @Test(expected=NullPointerException.class) for new Version((String) null), per-field negative-int checks, and isCompatibleViaCompareTo (the idiom plugins use to check a minimum version)

Boot Testing

Boot testing involves actually building the JNode ISO and booting it.

1. Building the ISO

You must build a "lite" bootable CD-ROM image.

  • x86 (32-bit): sh build.sh cd-x86-lite
  • x86_64 (64-bit): sh build.sh cd-x86_64-lite

This produces all/build/cdroms/jnode-x86-lite.iso.

2. Running in an Emulator

JNode is regularly tested against standard virtual machines.

QEMU

The fastest way to test is using QEMU. The project provides a helper script:

./qemu.sh

This script automatically configures QEMU with the correct memory settings, network cards, and serial ports expected by JNode, and boots the ISO.

VirtualBox / VMware

You can also create a new Virtual Machine in VirtualBox or VMware.

  • OS Type: Other / Unknown
  • Memory: 512MB to 1GB
  • Storage: Mount the generated .iso as the primary CD-ROM drive.

Debugging Boot Crashes

If JNode crashes during early boot, it will usually trigger an exception handler in kernel.asm or ints.asm, which dumps the CPU state (Registers, Stack Trace) to the VGA text console and the serial port.

  • Serial Console: Output is echoed to COM1. In QEMU, this can be redirected to the terminal or a file for easier analysis.
  • Debugger (kdb): JNode has a very rudimentary kernel debugger built in, but it is primarily used for post-mortem analysis of crashes.

JDWP Test Suite

JNode includes a Python-based test suite for the JDWP debugging backend:

Location: tests/jdwp/

Running Tests

cd tests/jdwp
python3 -m pytest -v

Requirements

  • JNode VM running with JDWP listener on port 8000
  • Network connectivity between host and VM
  • Python with pytest installed on host

Test Coverage

The test suite includes 52 tests covering:

  • VirtualMachine command set (version, classes, capabilities)
  • ReferenceType command set (signature, fields, methods)
  • ThreadReference command set (status, suspend/resume, stack trace)
  • ObjectReference command set (field values, monitor info)
  • StackFrame command set (local variables, this object)
  • ClassType command set (superclass, instances, values)
  • Method invocation via reflection

Related Pages

  • Build-System — Details on how the tests integrate into the Ant build pipeline.
  • Boot-Sequence — Understanding the boot process helps diagnose boot test failures.
  • GitHub-Actions-Workflows — The Java CI workflow that runs build.sh, ./test.sh all, and the QEMU boot jobs.
  • Agent-Issue-Pipeline — The heal job and postCiFixOnce that react to a red CI run.
  • Shell-Commands — TestHarness temp-directory lifecycle and black-box spec semantics.
  • GUI-AWT — The gui JUnit suite and headless-safety constraints.
  • X87-FPU-Support — Guest test programs covering float→int conversion, rint, and StrictMath.

Clone this wiki locally