From b0798fcdce954e49f81ee64479c233f39a0fea52 Mon Sep 17 00:00:00 2001 From: devdinc <234956748+devdinc@users.noreply.github.com> Date: Thu, 15 Jan 2026 17:08:11 +0300 Subject: [PATCH 01/25] Stable but has some todos --- testframework.sk | 174 +++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 154 insertions(+), 20 deletions(-) diff --git a/testframework.sk b/testframework.sk index aedfbdf..517f81f 100644 --- a/testframework.sk +++ b/testframework.sk @@ -1,20 +1,35 @@ +import: + ch.njol.skript.lang.parser.ParserInstance + local effect testFail %string%[, %-string%]: trigger: set {_msg} to " %expr-2%" if expr-2 is set else "" send "[Skript] [&cTEST FAILURE] %expr-1%%{_msg}%" to console +local effect ...: + trigger: + set {_none} to {_anothernone} + event "skriptTest": - pattern: test %string% + pattern: test %string% [when <(.+)>] event-values: string, boolean check: + set {_ok} to true set {-test.sk::tests::%current script%::%expr-1%} to "%expr-1%" if "%current script%/%expr-1%" is event-string: - continue + set {_raw} to first element of regex-1 + if {_raw} is set: + set {_cond} to Condition.parse({_raw}, "Can't understand condition: " + {_raw}) + + if {_cond} is set: + set {_ok} to {_cond}.check(event) + continue if {_ok} is true on load: delete {-test.sk::*} -expression all tests [with test name %-string%] [([with]in|from) %-script%]: +plural expression all tests [with test name %-string%] [([with]in|from) %-script%]: + return type: strings parse: set {_scoped} to false if expr-2 is not set continue @@ -43,7 +58,7 @@ effect (1:|2:auto)run [test][s] %strings%: set {_tests::*} to expr-1 set {_alltests::*} to all tests loop {_tests::*}: - delete {-test.sk::errors::%loop-value%} + delete {-test.sk::errors::%loop-value%::*} set {_list::string} to loop-value set {_list::boolean} to true if parse mark is 2 else false call custom event "skriptTest" with {_list::*} @@ -53,7 +68,7 @@ effect (1:|2:auto)run [test][s] %strings%: then: add 1 to {_forgottenTestResults} continue loop - add 1 to {_testFails} if {-test.sk::errors::%loop-value%} is greater than 0 + add 1 to {_testFails} if size of {-test.sk::errors::%loop-value%::*} is greater than 0 set {_validTestAmount} to size of {_tests::*} - {_forgottenTestResults} if {_validTestAmount} is not 0: send "[Skript] %{_validTestAmount} - {_testFails}%/%{_validTestAmount}% tests passed." to console @@ -70,13 +85,30 @@ on load: import: ch.njol.skript.lang.Condition -effect (3:|4:(no|without) (halt[ing]|fail[(-| )]safe|abort[ing])) assert(1:|1: true|2: false)[ with (6:|5:no )[[error] message][ %-string%]]\: <(.+)>: +effect: + patterns: + (3:|4:(no|without) (halt[ing]|fail[(-| )](safe|fast)|abort[ing])) assert(1:|1: true|2: false)[ with (6:|5:no )[[error] message][ %-string%]]\: <(.+)> + (3:|4:(no|without) (halt[ing]|fail[(-| )](safe|fast)|abort[ing])) assert <(.+)> (1:|2:to fail)[ with (6:|5:no )[[error][ message]][ %-string%]] usable in: custom event "skriptTest" - trigger: + parse: set {_raw} to first element of regex-1 + + set {_parser} to ParserInstance.get() + set {_parser}.[ParserInstance]isActive to true + set {_backup} to {_parser}.backup() + + {_parser}.setCurrentScript(current script) + {_parser}.setCurrentEvent("assert condition", (custom event "skriptTest").getClass()) + set {_cond} to Condition.parse({_raw}, "Can't understand condition: " + {_raw}) + + set {_parser}.[ParserInstance]isActive to false + {_parser}.restoreBackup({_backup}) + continue + trigger: set {_ok} to {_cond}.check(event) + set {_label} to "assert true" if parse tags contains "1" else "assert false" set {_test} to event.getEventValue("string") if all: @@ -95,20 +127,20 @@ effect (3:|4:(no|without) (halt[ing]|fail[(-| )]safe|abort[ing])) assert(1:|1: t {_bool1} is true {_bool2} is true then: - if parse tags contains "6": + if parse tags does not contain "5": testFail "Test ""%{_test}%"" with condition ""%{_label}%: %{_raw}%"" failed", expr-1 - add 1 to {-test.sk::errors::%{_test}%} + add "%expr-1%" to {-test.sk::errors::%{_test}%::*} if parse tags contains "3": delay effect -effect (3:|4:(no|without) (halt[ing]|fail[(-| )]safe|abort[ing])) fail test[ with (6:|5:no )[[error] message][ %-string%]]: +effect (3:|4:(no|without) (halt[ing]|fail[(-| )](safe|fast)|abort[ing])) fail test[ with (6:|5:no )[[error] message][ %-string%]]: usable in: custom event "skriptTest" trigger: set {_test} to event.getEventValue("string") - if parse tags contains "6": + if parse tags does not contain "5": testFail "Test ""%{_test}%"" failed", expr-1 - add 1 to {-test.sk::errors::%{_test}%} + add "%expr-1%" to {-test.sk::errors::%{_test}%::*} if parse tags contains "3": delay effect @@ -119,12 +151,114 @@ effect stop auto [test] execution [here]: set {_bool} to event.getEventValue("boolean") if {_bool} is true: delay effect + +expression [event(-| )]test: + return type: string + usable in: + custom event "skriptTest" + get: + return event.getEventValue("string") + +condition %string% (1:is|2:is not|2:isn't) autorun: + usable in: + custom event "skriptTest" + check: + set {_bool} to event.getEventValue("boolean") + if {_bool} is true: + parse mark is 1 + continue + else: + parse mark is 2 + continue + +condition parse: + usable in: + custom event "skriptTest" + check: + ... + # TODO last parse logs can be added by retaining logs, im not sure when to start and stop retaining them tho. -test "f39f0f4a-31ee-4b71-87e9-38ddba3a2313 hidden test": - set {_test} to event-string # contains folder so we are fine - without halting assert true with no error message: {_none} is set - without halting assert true with no error message: {_none} is set - without halting assert true with no error message: {-test.sk::errors::%{_test}%} is 2 - broadcast "Something very wrong occurred with test framework(error no 1)" if {-test.sk::errors::%{_test}%} is 3 - assert true with no error message: {_none} is set - broadcast "Something very wrong occurred with test framework(error no 2)" +plural expression test errors[ for [test[s]] %-strings%]: + return type: strings + get: + if expr-1 is not set: + set {_test} to event.getEventValue("string") + return {-test.sk::errors::%{_test}%::*} + else: + loop ...expr-1: + set {_test} to loop-value + add {-test.sk::errors::%{_test}%::*} to {_r::*} + return {_r::*} + +# TODO add skript test suite syntax documentation and tests for this +# last parse logs +# test block/world/location/pig/entity/(player?) + +test "f39f0f4a-31ee-4b71-87e9-38ddba3a2313 assert true passes": + assert true: {_none} is not set + +test "f39f0f4a-31ee-4b71-87e9-38ddba3a2313 assert false passes": + assert false: {_none} is set + +test "f39f0f4a-31ee-4b71-87e9-38ddba3a2313 framework internal consistency": + without halting assert true: size of all tests > 0 + without halting assert true: "%current script%" is set + assert true: size of test errors is 0 + + +test "f39f0f4a-31ee-4b71-87e9-38ddba3a2313 assert true failure is recorded": + without halting assert true with no error message: {_none} is set + assert true: size of test errors is 1 + +test "f39f0f4a-31ee-4b71-87e9-38ddba3a2313 assert error message is stored": + without halting assert true with no error message "msg1": {_none} is set + assert true with message "check failed": test errors contains "msg1" + +test "f39f0f4a-31ee-4b71-87e9-38ddba3a2313 no error message produces empty suffix": + without halting assert true with no error message: {_none} is set + assert true: size of test errors is 1 + +test "f39f0f4a-31ee-4b71-87e9-38ddba3a2313 halting assert stops execution": + assert true with no error message: {_none} is set + broadcast "ERROR: this should never execute" + +test "f39f0f4a-31ee-4b71-87e9-38ddba3a2313 without halting continues execution": + without halting assert true with no error message: {_none} is set + assert true: {_none} is not set + +test "f39f0f4a-31ee-4b71-87e9-38ddba3a2313 fail test records error": + without halting fail test with no error message + assert true: size of test errors is 1 + +test "f39f0f4a-31ee-4b71-87e9-38ddba3a2313 fail test halts by default": + fail test with no error message + broadcast "ERROR: fail test did not halt" + +test "f39f0f4a-31ee-4b71-87e9-38ddba3a2313 autorun flag is true": + assert true: event-test is autorun + +test "f39f0f4a-31ee-4b71-87e9-38ddba3a2313 manual run flag is false" when {_none} is set: + fail test + +test "f39f0f4a-31ee-4b71-87e9-38ddba3a2313 stop auto execution works": + stop auto test execution here + broadcast "ERROR: auto execution not stopped" + +test "f39f0f4a-31ee-4b71-87e9-38ddba3a2313 all tests returns this test": + set {_all::*} to all tests + assert true: {_all::*} contains "%current script%/f39f0f4a-31ee-4b71-87e9-38ddba3a2313 all tests returns this test" + +test "f39f0f4a-31ee-4b71-87e9-38ddba3a2313 all tests scoped by script": + set {_all::*} to all tests within current script + assert true: size of {_all::*} > 0 + +test "f39f0f4a-31ee-4b71-87e9-38ddba3a2313 errors do not leak between tests A": + without halting assert true with no message: {_none} is set + assert true: size of test errors is 1 + +test "f39f0f4a-31ee-4b71-87e9-38ddba3a2313 errors do not leak between tests B": + assert true: size of test errors is 0 + +test "f39f0f4a-31ee-4b71-87e9-38ddba3a2313 event-test returns correct name": + assert true: event-test is event-string + From 8648e928d01d6011ee380e83e4082a883515fe81 Mon Sep 17 00:00:00 2001 From: devdinc <234956748+devdinc@users.noreply.github.com> Date: Thu, 15 Jan 2026 23:20:51 +0300 Subject: [PATCH 02/25] Update testframework.sk --- testframework.sk | 108 ++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 88 insertions(+), 20 deletions(-) diff --git a/testframework.sk b/testframework.sk index 517f81f..32d089a 100644 --- a/testframework.sk +++ b/testframework.sk @@ -1,15 +1,16 @@ +using reflection + import: ch.njol.skript.lang.parser.ParserInstance + org.bukkit.Bukkit + ch.njol.skript.test.utils.TestOfflinePlayer + ch.njol.skript.lang.Condition local effect testFail %string%[, %-string%]: trigger: set {_msg} to " %expr-2%" if expr-2 is set else "" send "[Skript] [&cTEST FAILURE] %expr-1%%{_msg}%" to console -local effect ...: - trigger: - set {_none} to {_anothernone} - event "skriptTest": pattern: test %string% [when <(.+)>] event-values: string, boolean @@ -24,9 +25,6 @@ event "skriptTest": if {_cond} is set: set {_ok} to {_cond}.check(event) continue if {_ok} is true - -on load: - delete {-test.sk::*} plural expression all tests [with test name %-string%] [([with]in|from) %-script%]: return type: strings @@ -76,14 +74,15 @@ effect (1:|2:auto)run [test][s] %strings%: send "[Skript] No tests found." to console on load: + delete {-test.sk::*} + set {_block} to test-block's type wait 1 tick run test "f39f0f4a-31ee-4b71-87e9-38ddba3a2313" wait 1 tick set {_tests::*} to all tests autorun {_tests::*} - -import: - ch.njol.skript.lang.Condition + set test-block to {_block} + broadcast "Test block is permanent!" if test-block is not {_block} effect: patterns: @@ -154,14 +153,14 @@ effect stop auto [test] execution [here]: expression [event(-| )]test: return type: string - usable in: - custom event "skriptTest" +# usable in: +# custom event "skriptTest" get: return event.getEventValue("string") condition %string% (1:is|2:is not|2:isn't) autorun: - usable in: - custom event "skriptTest" + # usable in: + # custom event "skriptTest" check: set {_bool} to event.getEventValue("boolean") if {_bool} is true: @@ -171,13 +170,43 @@ condition %string% (1:is|2:is not|2:isn't) autorun: parse mark is 2 continue +### this section is experimental. + +import: + ch.njol.skript.ScriptLoader + ch.njol.skript.log.SkriptLogger + condition parse: + parse: + set {_parser} to ParserInstance.get() + set {_parseSection} to {_parser}.getNode() + set {_parseBackup} to {_parser}.backup() + # nodes are not fully loaded here yet + continue usable in: custom event "skriptTest" check: - ... - # TODO last parse logs can be added by retaining logs, im not sure when to start and stop retaining them tho. + {_parser}.restoreBackup({_parseBackup}) + set {_logger} to SkriptLogger.startRetainingLog() + ScriptLoader.loadItems({_parseSection}) + loop ...{_logger}.getLog(): + add loop-value.getMessage() to {_logs::*} + set {-test.sk::latestLogs::*} to {_logs::*} + {_logger}.close() + {_parser}.reset() +local effect no errors\: <.+>: + trigger: + stop + +plural expression latest parse logs: + usable in: + custom event "skriptTest" + get: + return {-test.sk::latestLogs::*} + +# ---- + plural expression test errors[ for [test[s]] %-strings%]: return type: strings get: @@ -189,10 +218,30 @@ plural expression test errors[ for [test[s]] %-strings%]: set {_test} to loop-value add {-test.sk::errors::%{_test}%::*} to {_r::*} return {_r::*} - -# TODO add skript test suite syntax documentation and tests for this -# last parse logs -# test block/world/location/pig/entity/(player?) + +expression [the] test(-| )world: + return type: world + get: + return Bukkit.getWorlds().get(0) + +expression [the] test(-| )location: + return type: location + get: + return test-world.getSpawnLocation().add(10, 1, 0) + +expression [the] test(-| )block: + return type: block + get: + return test-location.getBlock() + set: + set block at test-location to change value + +expression [the] test(-| )offline[-| ]player: + return type: offlineplayer + get: + set {_instance} to new TestOfflinePlayer() + # TestOfflinePlayer.[TestOfflinePlayer]PLAYER_PROFILE.setProperty(new ProfileProperty("textures", "ewogICJ0aW1lc3RhbXAiIDogMTc0NzQyOTg2MTQwOCwKICAicHJvZmlsZUlkIiA6ICI2OWUzNzAyNjJjN2Q0MjU1YWM3NjliMTNhNWZlOGY3NCIsCiAgInByb2ZpbGVOYW1lIiA6ICJTYWh2ZGUiLAogICJzaWduYXR1cmVSZXF1aXJlZCIgOiB0cnVlLAogICJ0ZXh0dXJlcyIgOiB7CiAgICAiU0tJTiIgOiB7CiAgICAgICJ1cmwiIDogImh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvNTE2MGFiZWVhNDI1YzZmODMyYjc0NmE0NTQ0YzVmYjlhOTgxYjAyZTFiZDg1ZmVhNWM3ZWY4MzFiZGM4NzRmMyIKICAgIH0KICB9Cn0=")); + return {_instance} test "f39f0f4a-31ee-4b71-87e9-38ddba3a2313 assert true passes": assert true: {_none} is not set @@ -239,6 +288,9 @@ test "f39f0f4a-31ee-4b71-87e9-38ddba3a2313 autorun flag is true": test "f39f0f4a-31ee-4b71-87e9-38ddba3a2313 manual run flag is false" when {_none} is set: fail test + +test "f39f0f4a-31ee-4b71-87e9-38ddba3a2313 manual run flag" when event-test is not autorun: + fail test test "f39f0f4a-31ee-4b71-87e9-38ddba3a2313 stop auto execution works": stop auto test execution here @@ -261,4 +313,20 @@ test "f39f0f4a-31ee-4b71-87e9-38ddba3a2313 errors do not leak between tests B": test "f39f0f4a-31ee-4b71-87e9-38ddba3a2313 event-test returns correct name": assert true: event-test is event-string + +test "f39f0f4a-31ee-4b71-87e9-38ddba3a2313 test-block is temporary": + test block is chest: + set test-block to trapped chest + else if test block is trapped chest: + set test-block to chest + else: + set test-block to ender chest + + # test is continued at on load end + +test "f39f0f4a-31ee-4b71-87e9-38ddba3a2313 parse section": + parse: + no errors: abc # just an internal stuff to make sure test.sk loads without errors + assert latest parse logs contains "Can't understand this condition/effect: no errors: abc" + From cbba79eebf1a19bc5222c838a71e33640334e9b2 Mon Sep 17 00:00:00 2001 From: devdinc <234956748+devdinc@users.noreply.github.com> Date: Thu, 15 Jan 2026 23:52:21 +0300 Subject: [PATCH 03/25] Update testframework.md --- docs/testframework.md | 346 ++++++++++++++++++++++++++++-------------- 1 file changed, 233 insertions(+), 113 deletions(-) diff --git a/docs/testframework.md b/docs/testframework.md index f7c3c4e..f87560d 100644 --- a/docs/testframework.md +++ b/docs/testframework.md @@ -1,28 +1,60 @@ -# Skript Testing Framework +Below is a **single, unified Markdown document** that combines both inputs, removes duplication, and reconciles terminology while preserving all technical detail. Structure has been normalized so it reads as one authoritative specification rather than two overlapping documents. -## Purpose +--- + +# Skript Runtime Testing Framework + +## Overview + +This document describes a **self-contained runtime testing framework for Skript**. The framework replicates much of Skript’s internal development test suite while extending it with runtime-safe execution, reflection-based inspection, and deterministic event-driven control. + +Unlike Skript’s native internal tests, this framework: + +* Runs entirely at runtime +* Is safe for production servers +* Supports both **automatic (autorun)** and **manual** execution +* Tracks failures centrally with optional console suppression + +All framework state is stored under the `-test.sk::*` namespace. + +--- + +## Design Goals + +The framework is intentionally minimal and predictable, prioritizing correctness and isolation over flexibility. + +It is designed to: + +* Allow tests to be written directly in `.sk` files +* Achieve functional parity with Skript’s internal test actions where feasible +* Enable meta-testing of Skript syntax and parser behavior +* Support fail-fast behavior with optional non-halting assertions +* Prevent state leakage between tests -This module implements a lightweight, self-contained **testing framework for Skript**. It allows scripts to define tests, automatically discover them, execute them deterministically, and report failures with precise diagnostics. +--- -The framework is designed to: +## Feature Parity with Skript’s Native Test Suite -* Run entirely inside Skript -* Avoid external dependencies -* Support both manual and automatic test execution -* Fail fast while still allowing optional non-halting assertions +| Feature | Status | Description | +| ------------------------- | ------------ | ----------------------------------------------------- | +| **Test structure** | Achieved | `test %string%` mirrors native test declarations | +| **Conditional execution** | Achieved | `when ` skips tests dynamically | +| **Assertions** | Achieved | `assert ` and `assert to fail` | +| **Explicit failure** | Achieved | `fail test` effect | +| **Parser inspection** | Experimental | Parse sections and log capture | --- ## High-Level Architecture -The framework is built around four core ideas: +The framework is built around four core principles: -1. **Tests are custom events** identified by name -2. **Tests are registered implicitly** during script parsing -3. **Execution is event-driven**, not inline -4. **Failures are tracked centrally** and reported after execution +1. **Tests are custom events** +2. **Tests are registered implicitly at parse time** +3. **Execution is event-driven, not inline** +4. **Failures are tracked centrally per test** -All internal state is stored under the `-test.sk::*` variable namespace. +Each test executes inside a dedicated `skriptTest` event context. --- @@ -31,73 +63,75 @@ All internal state is stored under the `-test.sk::*` variable namespace. ### Syntax ```skript -test "": +test "" [when ]: ``` -* `` must be unique **within the script**. -* The test body runs inside the `skriptTest` custom event. -* The event provides two event-values: +* `` must be unique **within the script** +* Tests are registered automatically when the script is parsed +* The optional `when` condition is evaluated immediately before execution - * `string` – the fully-qualified test identifier - * `boolean` – whether the test is running in autorun mode +### Event Context -### Example +Inside a test, the framework provides: -```skript -test "basic arithmetic": - assert true: 2 + 2 = 4 -``` +* **`event-string` / `event-test`** + Fully-qualified test identifier: + + ``` +