Standalone integration tests for the Python feature subsystems used by the Crossfire game server's map scripts.
No running Crossfire server is required. The test suite mocks the Crossfire C extension module and exercises the real Python library code directly against a temporary on-disk database, then cleans up after itself.
| System | Module / File | Coverage |
|---|---|---|
| Banking | CFBank |
deposit, accumulation, withdrawal, overdraft rejection, multiple accounts, account removal |
| Postal | CFMail |
send types 1/2/3, count, receive empties queue, message body, empty-mailbox receive |
| Message Boards | CFBoard |
write, list, getauthor, delete by id, invalid delete, independent board namespaces |
| Citylife / Player Log | CFLog |
create, login count, IP tracking, kick and muzzle counters and dates, remove, timestamp parse |
| Guild System | CFGuilds |
guild registry (add, establish, points, quest points, status), member CRUD, full rank ladder, rank floor/ceiling, demerits cap, dues updating guild points, SearchGuilds |
| Citylife NPC config | fixtures/world.citylife |
parse correctness, required fields, zone/point bounds, archetype names, Scorn and Navar coverage, Scorn County fall-through, archetype cross-reference against arch library, server init order, null guard in add_npc_to_point |
| City Bells config | fixtures/world.bells |
parse correctness, region structure, fallback coverage, Scorn/Darcap/Navar god entries, message format (%god substitution), god name cross-reference against arch library, cfcitybell_close() clears wrong container (server bug), StartupStage mechanism ensures .bells hook registered before load_assets() |
198 checks across three test suites: 100 for the five Python subsystems, 60 for the citylife NPC configuration, and 38 for the city bells configuration. See sample-test-output.md for a captured run showing expected results.
- Python 3.8 or later
- No external packages; only standard library modules are used (
sqlite3,shelve,tempfile, etc.)
From the project root directory:
python3 tests/test_feature_systems.py
python3 tests/test_citylife_config.py
python3 tests/test_bells_config.py
Each test suite is independent and can be run individually. Exit code 0
means all checks passed. Exit code 1 means one or more checks failed;
the summary printed at the end lists every failure by description.
crossfire-feature-testing/
├── lib/ Source modules under test (from crossfire-maps)
│ ├── CFBank.py Banking system
│ ├── CFBoard.py Message board system
│ ├── CFDataFile.py Flat-file data storage (patched, see below)
│ ├── CFGuilds.py Guild system
│ ├── CFLog.py Player login/citylife log
│ ├── CFMail.py Postal system
│ └── CFSqlDb.py SQLite database helper
├── fixtures/
│ ├── world.citylife NPC spawn config (from crossfire-maps)
│ └── world.bells City bell region config (from crossfire-maps)
├── patches/
│ ├── cfdatafile-putdata-fix.patch crossfire-maps patch (unified diff, patch -p1)
│ ├── cfdatafile-putdata-writeup.txt Sourceforge bug report and write-up
│ └── cfcitybell-close-fix.patch crossfire-server patch (unified diff, patch -p1)
├── tests/
│ ├── test_feature_systems.py Python subsystem tests (100 checks)
│ ├── test_citylife_config.py NPC spawn config tests (60 checks)
│ └── test_bells_config.py City bell config tests (38 checks)
├── .github/
│ └── workflows/
│ └── ci.yml GitHub Actions: runs tests on Python 3.8 through 3.12
├── CLAUDE.md Claude Code project context
├── README.md
├── sample-test-output.md Captured test run showing expected pass/fail results
├── LICENSE
├── requirements.txt
└── .gitignore
The Crossfire Python map scripts import a C extension module called Crossfire
that is only available inside a running Crossfire server process. The test
suite installs a lightweight Python mock of that module into sys.modules
before importing any library module. The mock satisfies two needs:
-
Directory paths --
CFDataFileandCFBoardevaluateCrossfire.LocalDirectory()at class definition time to set storage paths. The mock returns atempfile.mkdtemp()directory created just before the import, so all file I/O goes to a throw-away location that is deleted when the tests finish. -
Player lookups --
CFBank.convert_legacy_balancecallsCrossfire.FindPlayer(name). The mock returnsNone, which causes that function to return early without error.
No other Crossfire server facilities are needed by the tested subsystems.
lib/CFDataFile.py includes a fix for a bug present in the upstream
crossfire-maps source. The original putData method did:
header = dic['#']
del dic['#'] # mutates the caller's dict in-place
index = list(dic.keys())
index.sort()The del dic['#'] removes the header key from CFData.datadb permanently.
Any second write through the same CFData instance raises KeyError: '#'.
The fix avoids mutating the dict:
header = dic['#']
index = sorted(k for k in dic if k != '#')A patch for the upstream repository was submitted and accepted. The fix is
now present in the upstream crossfire-maps source. See
patches/cfdatafile-putdata-fix.patch.
Running the test suites against the live server source identified several
bugs in crossfire-server. Three were fixed upstream in commit 03ec12549
(init order via StartupStage), f0d97962c (null guard in
add_npc_to_point), and cb3923edd (object_free flags). One remains
unfixed:
cfcitybell_close() clears the wrong container —
cfcitybell_close() frees each Region object in the module's regions
map and then calls all_regions.clear(). all_regions is the server-wide
game region list (common/region.cpp), not the module's map. Clearing it
on module shutdown destroys all region data in memory for the remainder of
the server process. The correct call is regions.clear().
A patch is in patches/cfcitybell-close-fix.patch
(unified diff, apply with patch -p1 from the crossfire-server root).
The lib/ modules are taken from the
crossfire-maps
repository. This test suite is independent of that repository and is intended
to be run against whichever version of those modules you have locally.
The library modules in lib/ are copyright their respective authors and are
distributed under the GNU General Public License version 2 or later, the same
license as the Crossfire project. See LICENSE for the full text.
The test code in tests/ is also released under GPL v2+.