A ruleset that decides claims, or blocks a deployment, or reorders stock, needs to be tested —
not eyeballed. ISO Prolog has no standard test framework (SWI ships the non-standard plunit);
Tln has one built in, and it needs no database.
given / when / expect
A .tln.test file seeds facts in a given block, runs a named block with when, and asserts on
the result:
test "greets the world" {
given {
record 1 type "greeting"
attr 1 "name" "World"
}
when detect "Hello, World"
expect {
flagged 1
label contains "Hello, World!"
count == 1
}
}
The given block is the fact schema you’d otherwise load from an external system — record and
attr triples — so tests are hermetic and fast (the runner materializes them in memory; no
FactStore required).
Assertions
flagged <id> not flagged <id>
label contains "<text>"
priority == LOW|MEDIUM|HIGH|CRITICAL
count == <n>
A realistic test pins down exactly which records should and shouldn’t fire — here, an overdue- service detection separating active-overdue vehicles from up-to-date ones, inactive ones, and non-vehicles:
test "Overdue service flags only overdue vehicles" {
given {
record 501 type "item" category "Vehicles" status "active"
attr 501 "km" 45000
attr 501 "last_service_km" 20000
attr 501 "name" "Truck A"
record 502 type "item" category "Vehicles" status "active"
attr 502 "km" 25000
attr 502 "last_service_km" 25000
attr 502 "name" "Van B"
}
when detect "Service overdue"
expect {
flagged 501
not flagged 502
label contains "Truck A"
}
}
Running
tln test rules.tln rules.tln.test
# ==> rules.tln.test: 1 test(s)
#
# 1 passed, 0 failed
tln test rules.tln rules.tln.test -run "Overdue" -v # filter + verbose
tln test rules.tln rules.tln.test --junit out.xml # CI-friendly report
Because the engine is deterministic, a passing test stays passing for the same facts — the property that makes an expert system safe to put in front of real decisions.