Skip to content

Commit 1d973df

Browse files
committed
Release v1.2.1
1 parent ff4959e commit 1d973df

10 files changed

Lines changed: 197 additions & 47 deletions

File tree

Docs/Documentation.md

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -259,7 +259,7 @@ So, the `beforeAll` function will run before all tests in the suite, the `before
259259

260260
The `beforeEach` function is used to run a piece of code before each test in a section. This is useful when you want to set up some data before each test. You can put a `beforeEach` function in any section and it will run before each test in that section.
261261

262-
When `describe` blocks are nested, **all ancestor `beforeEach` hooks run in order from outermost to innermost** before each test. This matches Jest/Vitest behaviour and allows shared setup to be split across describe levels.
262+
When `describe` blocks are nested, **all ancestor `beforeEach` hooks run in order from outermost to innermost** before each test. This matches Jest/Vitest behavior and allows shared setup to be split across describe levels.
263263

264264
```gml
265265
describe("outer", function() {
@@ -617,9 +617,12 @@ The `simulateEvent` function is used to simulate an event for an object. This fu
617617

618618
The `simulateTimesource` function is used to simulate a [timesource](https://manual.gamemaker.io/lts/en/GameMaker_Language/GML_Reference/Time_Sources/Time_Sources.htm). This function takes the exact same parameters than a GameMaker native timesource, but it will not actually create a timesource, it will just virtually simulate the event. This is useful when you want to test pseudo-async events like a custom garbage collector or waiting some frames for an event to happen.
619619

620-
This function creates a constructor that will be used to simulate the timesource and has 2 methods to call for it to work: `.start()` that mimics the behaviour of [time_source_start()](https://manual.gamemaker.io/lts/en/GameMaker_Language/GML_Reference/Time_Sources/time_source_start.htm), and `.stop()` that mimics the behaviour of [time_source_stop()](https://manual.gamemaker.io/lts/en/GameMaker_Language/GML_Reference/Time_Sources/time_source_stop.htm).
620+
This function creates a constructor that will be used to simulate the timesource and has 2 methods to call for it to work: `.start()` that mimics the behavior of [time_source_start()](https://manual.gamemaker.io/lts/en/GameMaker_Language/GML_Reference/Time_Sources/time_source_start.htm), and `.stop()` that mimics the behavior of [time_source_stop()](https://manual.gamemaker.io/lts/en/GameMaker_Language/GML_Reference/Time_Sources/time_source_stop.htm).
621621

622-
**If you want to strictly call the GameMaker's built-in functions you can use `original_time_source_create()`, `original_time_source_start()`, etc**
622+
**If you want to strictly call the GameMaker's built-in functions during tests you can use `original_time_source_create()`, `original_time_source_start()`, `original_time_source_exists()`, etc**
623+
624+
> [!IMPORTANT]
625+
> The library redefines `time_source_create`, `time_source_start`, `time_source_stop`, `time_source_pause`, `time_source_resume`, `time_source_destroy`, `time_source_exists` and `call_later` so they can be mocked during tests. Mocking is **only** active while your suites are actually running. Timesources created by your game or by third-party libraries (Input, Scribble, etc.) at boot or during normal gameplay hit the real GameMaker functions and behave normally. If you need the built-in behavior explicitly at any time, call the `original_` prefixed version.
623626
624627
### simulateCallLater ![](https://img.shields.io/badge/v1.1-00cbca?style=flat)
625628

Docs/Home.md

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -25,15 +25,16 @@ The minimum version of GameMaker required to use this library is **GameMaker 202
2525

2626
❌: Not compatible.
2727

28-
| GameMaker Version | GMTL v1.0.x | GMTL v1.1.x |
29-
| ----------------: | :---------: | :---------: |
30-
| Studio 1.4.x |||
31-
| Studio 2 - 2.2 |||
32-
| Studio 2.3.x |||
33-
| 2022.x & 2022 LTS |||
34-
| 2023.1 - 2023.3 | ⚠️ ||
35-
| 2023.4 - 2023.11 |||
36-
| 2023 LTS |||
37-
| 2024.x / 2024 LTS |\* |\* |
28+
| GameMaker Version | GMTL v1.0.x | GMTL v1.1.x | GMTL v1.2.x |
29+
| ----------------: | :---------: | :---------: | :---------: |
30+
| Studio 1.4.x ||||
31+
| Studio 2 - 2.2 ||||
32+
| Studio 2.3.x ||||
33+
| 2022.x & 2022 LTS ||||
34+
| 2023.1 - 2023.3 | ⚠️ |||
35+
| 2023.4 - 2023.11 ||| ⚠️ |
36+
| 2023 LTS ||||
37+
| 2024.x / 2024 LTS ||\* ||
38+
| 2026 LTS |||\* |
3839

3940
> [!NOTE] > **Author's comment:** Depending if anyone is interested in using this library with older versions of GameMaker, I could make a version compatible up to **GameMaker Studio 2.3.7** as I did with the compatibility update in the [**GML-Extended**](https://github.com/DAndrewBox/GML-Extended/) library.

Docs/Troubleshooting.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ Also confirm your script files are not inside a folder starting with `GMTL_` - t
7979
### My functions don't appear in the coverage report
8080

8181
The scanner only indexes top-level named functions (`function name() { ... }`) defined at script scope. It skips:
82+
8283
- Functions whose folder starts with `GMTL_` (unless whitelisted)
8384
- Anonymous / lambda functions
8485
- Constructor methods defined inside a constructor body
@@ -105,3 +106,7 @@ This issue should be solved since v1.1.0, but if you still encounter it, you can
105106
I'm not really sure what causes this issue, but it seems to be related to the import process of the library that GameMaker does. If you encounter this issue, please [create an issue](https://github.com/DAndrewBox/GM-Testing-Library/issues) so I can investigate it further.
106107

107108
---
109+
110+
### Timesources are not working in tests or not working when created before the test suite
111+
112+
This issue is caused by the timesources not being initialized correctly in the test environment. It was fixed in v1.2.1, I heavily recommend updating to the latest version of the library if you encounter any issues before opening a new issue.

GM-Testing-Library/scripts/GMTL_definitions/GMTL_definitions.gml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
#macro gmtl_timesources gmtl_internal.timesources
3434
#macro gmtl_has_finished __gmtl_internal_fn_has_finished()
3535
#macro gmtl_is_initializing __gmtl_internal_fn_is_initializing()
36+
#macro gmtl_is_running __gmtl_internal_fn_is_running()
3637

3738
#macro gmtl_test_before_all gmtl_internal.tests.before_all
3839
#macro gmtl_test_before_all_ran gmtl_internal.tests.before_all_ran
@@ -129,6 +130,7 @@
129130
#macro original_time_source_pause time_source_pause
130131
#macro original_time_source_resume time_source_resume
131132
#macro original_time_source_destroy time_source_destroy
133+
#macro original_time_source_exists time_source_exists
132134
#macro original_call_later call_later
133135

134136
#macro time_source_create __gmtl_internal_fn_time_source_create
@@ -137,4 +139,5 @@
137139
#macro time_source_pause __gmtl_internal_fn_time_source_pause
138140
#macro time_source_resume __gmtl_internal_fn_time_source_resume
139141
#macro time_source_destroy __gmtl_internal_fn_time_source_destroy
142+
#macro time_source_exists __gmtl_internal_fn_time_source_exists
140143
#macro call_later __gmtl_internal_fn_call_later

GM-Testing-Library/scripts/GMTL_demo_tests/GMTL_demo_tests.gml

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -764,3 +764,76 @@ suite(function() {
764764
});
765765
});
766766
});
767+
768+
// Regression - timesource mocking scope
769+
// Guards the regression reported in the GitHub issue: timesources created by game / 3rd-party
770+
// code during the boot window were being silently mocked (returned unusable structs that
771+
// never ticked and were unknown to time_source_exists). Mocking must be scoped to the
772+
// window where suites are actually executing (gmtl_is_running), and time_source_exists
773+
// must recognize mocked timesources.
774+
suite(function() {
775+
describe("Regression - timesource mocking is scoped to the test run", function() {
776+
// Issue repro: the exact symptom the user hit - time_source_exists() returning
777+
// false for a freshly created/started timesource.
778+
it("time_source_exists() recognizes a mocked timesource through its lifecycle", function() {
779+
var _ts = time_source_create(time_source_game, 2, time_source_units_seconds, show_debug_message, ["Hello World"], -1);
780+
expect(time_source_exists(_ts)).toBeTruthy(); // exists after creation
781+
782+
time_source_start(_ts);
783+
expect(time_source_exists(_ts)).toBeTruthy(); // exists after starting
784+
785+
time_source_destroy(_ts);
786+
expect(time_source_exists(_ts)).toBeFalsy(); // gone after destroy
787+
});
788+
789+
// End-to-end through the PUBLIC time_source_create / time_source_start macros
790+
// (not simulateTimeSource) so the mock create+start path is exercised.
791+
it("time_source_create() + time_source_start() fire the callback during simulated frames", function() {
792+
var _inst = create(100, 100, o_gmtl_demo_timer);
793+
expect(_inst.timer_test_value).toBeEqual(0);
794+
795+
var _ts = time_source_create(time_source_game, 5, time_source_units_frames, function(_inst) {
796+
_inst.timer_test_value = 100;
797+
}, [_inst], 1);
798+
time_source_start(_ts);
799+
800+
simulateFrameWait(5);
801+
expect(_inst.timer_test_value).toBeEqual(100);
802+
803+
instance_destroy(_inst);
804+
});
805+
806+
// PUBLIC call_later macro should be mocked and fire during simulated frames too.
807+
it("call_later() is mocked and fires during simulated frames", function() {
808+
global.__gmtl_reg_call_later = 0;
809+
call_later(10, time_source_units_frames, function() {
810+
global.__gmtl_reg_call_later = 77;
811+
});
812+
813+
simulateFrameWait(10);
814+
expect(global.__gmtl_reg_call_later).toBeEqual(77);
815+
});
816+
817+
// The core fix: OUTSIDE the test-run window the mock must delegate to the real
818+
// engine, so game / 3rd-party (Input, Scribble, ...) timesources work normally.
819+
// We temporarily flip the internal running flag to emulate boot / normal gameplay.
820+
it("timesources created outside the test-run window are REAL, not mocked", function() {
821+
var _prev_running = gmtl_internal.running;
822+
gmtl_internal.running = false;
823+
try {
824+
var _ts = time_source_create(time_source_game, 1, time_source_units_seconds, function() {}, [], 1);
825+
826+
// Real engine returns a numeric id, not a mocked GMTL_TimeSource struct.
827+
expect(is_struct(_ts)).toBeFalsy();
828+
829+
// ...and the real engine recognizes it as existing.
830+
expect(time_source_exists(_ts)).toBeTruthy();
831+
832+
time_source_destroy(_ts);
833+
} finally {
834+
// Always restore so a failure here cannot leak into other tests.
835+
gmtl_internal.running = _prev_running;
836+
}
837+
});
838+
});
839+
});

GM-Testing-Library/scripts/GMTL_dependencies/GMTL_dependencies.gml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,10 @@ function __gmtl_dep_fn_string_percentage(_val, _max) {
88
var _n = power(10, _dec);
99
return round(_x * _n) / _n;
1010
}
11-
11+
12+
// Guard against divide-by-zero when there are no suites/tests/functions to report.
13+
if (_max == 0) return 0;
14+
1215
return round_dec(100 * (_val / _max), 2);
1316
}
1417
return string(percentage(_val, _max)) + "%";

GM-Testing-Library/scripts/GMTL_init/GMTL_init.gml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,11 @@ gml_pragma("global", "__gmtl_init()");
2323
/// @func __gmtl_setup()
2424
/// @ignore
2525
function __gmtl_setup() {
26+
// Idempotent: never wipe an already-initialized state. Suites are registered
27+
// synchronously by suite() and, depending on the game's global-init order, that can
28+
// happen before this pragma runs. A second setup call must not discard them.
29+
if (variable_global_exists("__gmtl_internal")) return;
30+
2631
global.__gmtl_async_event_map = -1;
2732
gmtl_internal = {
2833
indent: 0,
@@ -73,6 +78,7 @@ function __gmtl_setup() {
7378
},
7479
timesources: [],
7580
initializing: true,
81+
running: false,
7682
finished: false,
7783
};
7884
}
@@ -96,9 +102,16 @@ function __gmtl_init() {
96102

97103
var _t_start = get_timer();
98104
var _suites_len = array_length(gmtl_suite_list);
105+
106+
// Mocking is only active while suites are actually executing. Any timesource,
107+
// input, etc. created by game/library code OUTSIDE this window hits the real
108+
// engine functions. Prevents boot-window timesources from being silently mocked.
109+
gmtl_internal.running = true;
99110
for (var i = 0; i < _suites_len; i++) {
100111
__gmtl_internal_fn_call_suite(gmtl_suite_list[i]);
101112
}
113+
gmtl_internal.running = false;
114+
102115
__gmtl_internal_fn_finish_suites(_t_start);
103116

104117
if (gmtl_show_coverage) {

0 commit comments

Comments
 (0)