Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
godotengine
GitHub Repository: godotengine/godot
Path: blob/master/modules/gdscript/tests/gdscript_test_runner.cpp
20836 views
1
/**************************************************************************/
2
/* gdscript_test_runner.cpp */
3
/**************************************************************************/
4
/* This file is part of: */
5
/* GODOT ENGINE */
6
/* https://godotengine.org */
7
/**************************************************************************/
8
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
9
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
10
/* */
11
/* Permission is hereby granted, free of charge, to any person obtaining */
12
/* a copy of this software and associated documentation files (the */
13
/* "Software"), to deal in the Software without restriction, including */
14
/* without limitation the rights to use, copy, modify, merge, publish, */
15
/* distribute, sublicense, and/or sell copies of the Software, and to */
16
/* permit persons to whom the Software is furnished to do so, subject to */
17
/* the following conditions: */
18
/* */
19
/* The above copyright notice and this permission notice shall be */
20
/* included in all copies or substantial portions of the Software. */
21
/* */
22
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
23
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
24
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
25
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
26
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
27
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
28
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
29
/**************************************************************************/
30
31
#include "gdscript_test_runner.h"
32
33
#include "../gdscript.h"
34
#include "../gdscript_analyzer.h"
35
#include "../gdscript_compiler.h"
36
#include "../gdscript_parser.h"
37
#include "../gdscript_tokenizer_buffer.h"
38
39
#include "core/config/project_settings.h"
40
#include "core/core_globals.h"
41
#include "core/io/dir_access.h"
42
#include "core/io/file_access_pack.h"
43
#include "core/os/os.h"
44
#include "core/string/string_builder.h"
45
#include "scene/resources/packed_scene.h"
46
47
#include "tests/test_macros.h"
48
49
namespace GDScriptTests {
50
51
void init_autoloads() {
52
HashMap<StringName, ProjectSettings::AutoloadInfo> autoloads(ProjectSettings::get_singleton()->get_autoload_list());
53
54
// First pass, add the constants so they exist before any script is loaded.
55
for (const KeyValue<StringName, ProjectSettings::AutoloadInfo> &E : ProjectSettings::get_singleton()->get_autoload_list()) {
56
const ProjectSettings::AutoloadInfo &info = E.value;
57
58
if (info.is_singleton) {
59
for (int i = 0; i < ScriptServer::get_language_count(); i++) {
60
ScriptServer::get_language(i)->add_global_constant(info.name, Variant());
61
}
62
}
63
}
64
65
// Second pass, load into global constants.
66
for (const KeyValue<StringName, ProjectSettings::AutoloadInfo> &E : ProjectSettings::get_singleton()->get_autoload_list()) {
67
const ProjectSettings::AutoloadInfo &info = E.value;
68
69
if (!info.is_singleton) {
70
// Skip non-singletons since we don't have a scene tree here anyway.
71
continue;
72
}
73
74
Node *n = nullptr;
75
if (ResourceLoader::get_resource_type(info.path) == "PackedScene") {
76
// Cache the scene reference before loading it (for cyclic references)
77
Ref<PackedScene> scn;
78
scn.instantiate();
79
scn->set_path(ResourceUID::ensure_path(info.path));
80
scn->reload_from_file();
81
ERR_CONTINUE_MSG(scn.is_null(), vformat("Failed to instantiate an autoload, can't load from path: %s.", info.path));
82
83
if (scn.is_valid()) {
84
n = scn->instantiate();
85
}
86
} else {
87
Ref<Resource> res = ResourceLoader::load(info.path);
88
ERR_CONTINUE_MSG(res.is_null(), vformat("Failed to instantiate an autoload, can't load from path: %s.", info.path));
89
90
Ref<Script> scr = res;
91
if (scr.is_valid()) {
92
StringName ibt = scr->get_instance_base_type();
93
bool valid_type = ClassDB::is_parent_class(ibt, "Node");
94
ERR_CONTINUE_MSG(!valid_type, vformat("Failed to instantiate an autoload, script '%s' does not inherit from 'Node'.", info.path));
95
96
Object *obj = ClassDB::instantiate(ibt);
97
ERR_CONTINUE_MSG(!obj, vformat("Failed to instantiate an autoload, cannot instantiate '%s'.", ibt));
98
99
n = Object::cast_to<Node>(obj);
100
n->set_script(scr);
101
}
102
}
103
104
ERR_CONTINUE_MSG(!n, vformat("Failed to instantiate an autoload, path is not pointing to a scene or a script: %s.", info.path));
105
n->set_name(info.name);
106
107
for (int i = 0; i < ScriptServer::get_language_count(); i++) {
108
ScriptServer::get_language(i)->add_global_constant(info.name, n);
109
}
110
}
111
}
112
113
void init_language(const String &p_base_path) {
114
// Setup project settings since it's needed by the languages to get the global scripts.
115
// This also sets up the base resource path.
116
Error err = ProjectSettings::get_singleton()->setup(p_base_path, String(), true);
117
if (err) {
118
print_line("Could not load project settings.");
119
// Keep going since some scripts still work without this.
120
}
121
122
// Initialize the language for the test routine.
123
GDScriptLanguage::get_singleton()->init();
124
init_autoloads();
125
}
126
127
void finish_language() {
128
GDScriptLanguage::get_singleton()->finish();
129
ScriptServer::global_classes_clear();
130
}
131
132
StringName GDScriptTestRunner::test_function_name;
133
134
GDScriptTestRunner::GDScriptTestRunner(const String &p_source_dir, bool p_init_language, bool p_print_filenames, bool p_use_binary_tokens) {
135
test_function_name = StringName("test");
136
do_init_languages = p_init_language;
137
print_filenames = p_print_filenames;
138
binary_tokens = p_use_binary_tokens;
139
140
source_dir = p_source_dir;
141
if (!source_dir.ends_with("/")) {
142
source_dir += "/";
143
}
144
145
if (do_init_languages) {
146
init_language(p_source_dir);
147
}
148
149
#ifdef DEBUG_ENABLED
150
// Set all warning levels to "Warn" in order to test them properly, even the ones that default to error.
151
ProjectSettings::get_singleton()->set_setting("debug/gdscript/warnings/enable", true);
152
for (int i = 0; i < (int)GDScriptWarning::WARNING_MAX; i++) {
153
if (i == GDScriptWarning::UNTYPED_DECLARATION || i == GDScriptWarning::INFERRED_DECLARATION) {
154
// TODO: Add ability for test scripts to specify which warnings to enable/disable for testing.
155
continue;
156
}
157
const String setting_path = GDScriptWarning::get_setting_path_from_code((GDScriptWarning::Code)i);
158
ProjectSettings::get_singleton()->set_setting(setting_path, (int)GDScriptWarning::WARN);
159
}
160
161
// Force the call, since the language is initialized **before** applying project settings
162
// and the `settings_changed` signal is emitted with `call_deferred()`.
163
GDScriptParser::update_project_settings();
164
#endif // DEBUG_ENABLED
165
166
// Enable printing to show results.
167
CoreGlobals::print_line_enabled = true;
168
CoreGlobals::print_error_enabled = true;
169
}
170
171
GDScriptTestRunner::~GDScriptTestRunner() {
172
test_function_name = StringName();
173
if (do_init_languages) {
174
finish_language();
175
}
176
}
177
178
#ifndef DEBUG_ENABLED
179
static String strip_warnings(const String &p_expected) {
180
// On release builds we don't have warnings. Here we remove them from the output before comparison
181
// so it doesn't fail just because of difference in warnings.
182
String expected_no_warnings;
183
for (String line : p_expected.split("\n")) {
184
if (line.begins_with("~~ ")) {
185
continue;
186
}
187
expected_no_warnings += line + "\n";
188
}
189
return expected_no_warnings.strip_edges() + "\n";
190
}
191
#endif
192
193
int GDScriptTestRunner::run_tests() {
194
if (!make_tests()) {
195
FAIL("An error occurred while making the tests.");
196
return -1;
197
}
198
199
if (!generate_class_index()) {
200
FAIL("An error occurred while generating class index.");
201
return -1;
202
}
203
204
int failed = 0;
205
for (int i = 0; i < tests.size(); i++) {
206
GDScriptTest test = tests[i];
207
if (print_filenames) {
208
print_line(test.get_source_relative_filepath());
209
}
210
GDScriptTest::TestResult result = test.run_test();
211
212
String expected = FileAccess::get_file_as_string(test.get_output_file());
213
#ifndef DEBUG_ENABLED
214
expected = strip_warnings(expected);
215
#endif
216
INFO(test.get_source_file());
217
if (!result.passed) {
218
INFO(expected);
219
failed++;
220
}
221
222
CHECK_MESSAGE(result.passed, (result.passed ? String() : result.output));
223
}
224
225
return failed;
226
}
227
228
bool GDScriptTestRunner::generate_outputs() {
229
is_generating = true;
230
231
if (!make_tests()) {
232
print_line("Failed to generate a test output.");
233
return false;
234
}
235
236
if (!generate_class_index()) {
237
return false;
238
}
239
240
for (int i = 0; i < tests.size(); i++) {
241
GDScriptTest test = tests[i];
242
if (print_filenames) {
243
print_line(test.get_source_relative_filepath());
244
} else {
245
OS::get_singleton()->print(".");
246
}
247
248
bool result = test.generate_output();
249
250
if (!result) {
251
print_line("\nCould not generate output for " + test.get_source_file());
252
return false;
253
}
254
}
255
print_line("\nGenerated output files for " + itos(tests.size()) + " tests successfully.");
256
257
return true;
258
}
259
260
bool GDScriptTestRunner::make_tests_for_dir(const String &p_dir) {
261
Error err = OK;
262
Ref<DirAccess> dir(DirAccess::open(p_dir, &err));
263
264
if (err != OK) {
265
return false;
266
}
267
268
String current_dir = dir->get_current_dir();
269
270
dir->list_dir_begin();
271
String next = dir->get_next();
272
273
while (!next.is_empty()) {
274
if (dir->current_is_dir()) {
275
if (next == "." || next == ".." || next == "completion" || next == "lsp") {
276
next = dir->get_next();
277
continue;
278
}
279
if (!make_tests_for_dir(current_dir.path_join(next))) {
280
return false;
281
}
282
} else {
283
// `*.notest.gd` files are skipped.
284
if (next.ends_with(".notest.gd")) {
285
next = dir->get_next();
286
continue;
287
} else if (binary_tokens && next.ends_with(".textonly.gd")) {
288
next = dir->get_next();
289
continue;
290
} else if (next.has_extension("gd")) {
291
#ifndef DEBUG_ENABLED
292
// On release builds, skip tests marked as debug only.
293
Error open_err = OK;
294
Ref<FileAccess> script_file(FileAccess::open(current_dir.path_join(next), FileAccess::READ, &open_err));
295
if (open_err != OK) {
296
ERR_PRINT(vformat(R"(Couldn't open test file "%s".)", next));
297
next = dir->get_next();
298
continue;
299
} else {
300
if (script_file->get_line() == "#debug-only") {
301
next = dir->get_next();
302
continue;
303
}
304
}
305
#endif
306
307
String out_file = next.get_basename() + ".out";
308
ERR_FAIL_COND_V_MSG(!is_generating && !dir->file_exists(out_file), false, "Could not find output file for " + next);
309
310
if (next.ends_with(".bin.gd")) {
311
// Test text mode first.
312
GDScriptTest text_test(current_dir.path_join(next), current_dir.path_join(out_file), source_dir);
313
tests.push_back(text_test);
314
// Test binary mode even without `--use-binary-tokens`.
315
GDScriptTest bin_test(current_dir.path_join(next), current_dir.path_join(out_file), source_dir);
316
bin_test.set_tokenizer_mode(GDScriptTest::TOKENIZER_BUFFER);
317
tests.push_back(bin_test);
318
} else {
319
GDScriptTest test(current_dir.path_join(next), current_dir.path_join(out_file), source_dir);
320
if (binary_tokens) {
321
test.set_tokenizer_mode(GDScriptTest::TOKENIZER_BUFFER);
322
}
323
tests.push_back(test);
324
}
325
}
326
}
327
328
next = dir->get_next();
329
}
330
331
dir->list_dir_end();
332
333
return true;
334
}
335
336
bool GDScriptTestRunner::make_tests() {
337
Error err = OK;
338
Ref<DirAccess> dir(DirAccess::open(source_dir, &err));
339
340
ERR_FAIL_COND_V_MSG(err != OK, false, "Could not open specified test directory.");
341
342
source_dir = dir->get_current_dir() + "/"; // Make it absolute path.
343
return make_tests_for_dir(dir->get_current_dir());
344
}
345
346
static bool generate_class_index_recursive(const String &p_dir) {
347
Error err = OK;
348
Ref<DirAccess> dir(DirAccess::open(p_dir, &err));
349
350
if (err != OK) {
351
return false;
352
}
353
354
String current_dir = dir->get_current_dir();
355
356
dir->list_dir_begin();
357
String next = dir->get_next();
358
359
StringName gdscript_name = GDScriptLanguage::get_singleton()->get_name();
360
while (!next.is_empty()) {
361
if (dir->current_is_dir()) {
362
if (next == "." || next == ".." || next == "completion" || next == "lsp") {
363
next = dir->get_next();
364
continue;
365
}
366
if (!generate_class_index_recursive(current_dir.path_join(next))) {
367
return false;
368
}
369
} else {
370
if (!next.ends_with(".gd")) {
371
next = dir->get_next();
372
continue;
373
}
374
String base_type;
375
String source_file = current_dir.path_join(next);
376
bool is_abstract = false;
377
bool is_tool = false;
378
String class_name = GDScriptLanguage::get_singleton()->get_global_class_name(source_file, &base_type, nullptr, &is_abstract, &is_tool);
379
if (class_name.is_empty()) {
380
next = dir->get_next();
381
continue;
382
}
383
ERR_FAIL_COND_V_MSG(ScriptServer::is_global_class(class_name), false,
384
"Class name '" + class_name + "' from " + source_file + " is already used in " + ScriptServer::get_global_class_path(class_name));
385
386
ScriptServer::add_global_class(class_name, base_type, gdscript_name, source_file, is_abstract, is_tool);
387
}
388
389
next = dir->get_next();
390
}
391
392
dir->list_dir_end();
393
394
return true;
395
}
396
397
bool GDScriptTestRunner::generate_class_index() {
398
Error err = OK;
399
Ref<DirAccess> dir(DirAccess::open(source_dir, &err));
400
401
ERR_FAIL_COND_V_MSG(err != OK, false, "Could not open specified test directory.");
402
403
source_dir = dir->get_current_dir() + "/"; // Make it absolute path.
404
return generate_class_index_recursive(dir->get_current_dir());
405
}
406
407
GDScriptTest::GDScriptTest(const String &p_source_path, const String &p_output_path, const String &p_base_dir) {
408
source_file = p_source_path;
409
output_file = p_output_path;
410
base_dir = p_base_dir;
411
_print_handler.printfunc = print_handler;
412
_error_handler.errfunc = error_handler;
413
}
414
415
void GDScriptTestRunner::handle_cmdline() {
416
List<String> cmdline_args = OS::get_singleton()->get_cmdline_args();
417
418
for (List<String>::Element *E = cmdline_args.front(); E; E = E->next()) {
419
String &cmd = E->get();
420
if (cmd == "--gdscript-generate-tests") {
421
String path;
422
if (E->next()) {
423
path = E->next()->get();
424
} else {
425
path = "modules/gdscript/tests/scripts";
426
}
427
428
GDScriptTestRunner runner(path, false, cmdline_args.find("--print-filenames") != nullptr);
429
430
bool completed = runner.generate_outputs();
431
int failed = completed ? 0 : -1;
432
exit(failed);
433
}
434
}
435
}
436
437
void GDScriptTest::enable_stdout() {
438
// TODO: this could likely be handled by doctest or `tests/test_macros.h`.
439
OS::get_singleton()->set_stdout_enabled(true);
440
OS::get_singleton()->set_stderr_enabled(true);
441
}
442
443
void GDScriptTest::disable_stdout() {
444
// TODO: this could likely be handled by doctest or `tests/test_macros.h`.
445
OS::get_singleton()->set_stdout_enabled(false);
446
OS::get_singleton()->set_stderr_enabled(false);
447
}
448
449
void GDScriptTest::print_handler(void *p_this, const String &p_message, bool p_error, bool p_rich) {
450
TestResult *result = (TestResult *)p_this;
451
result->output += p_message + "\n";
452
}
453
454
void GDScriptTest::error_handler(void *p_this, const char *p_function, const char *p_file, int p_line, const char *p_error, const char *p_explanation, bool p_editor_notify, ErrorHandlerType p_type) {
455
ErrorHandlerData *data = (ErrorHandlerData *)p_this;
456
GDScriptTest *self = data->self;
457
TestResult *result = data->result;
458
459
result->status = GDTEST_RUNTIME_ERROR;
460
461
String header = _error_handler_type_string(p_type);
462
463
// Only include the file, line, and function for script errors,
464
// otherwise the test outputs changes based on the platform/compiler.
465
if (p_type == ERR_HANDLER_SCRIPT) {
466
header += vformat(" at %s:%d on %s()",
467
String::utf8(p_file).trim_prefix(self->base_dir).replace_char('\\', '/'),
468
p_line,
469
String::utf8(p_function));
470
}
471
472
StringBuilder error_string;
473
error_string.append(vformat(">> %s: %s\n", header, String::utf8(p_error)));
474
if (strlen(p_explanation) > 0) {
475
error_string.append(vformat(">> %s\n", String::utf8(p_explanation)));
476
}
477
478
result->output += error_string.as_string();
479
}
480
481
bool GDScriptTest::check_output(const String &p_output) const {
482
Error err = OK;
483
String expected = FileAccess::get_file_as_string(output_file, &err);
484
485
ERR_FAIL_COND_V_MSG(err != OK, false, "Error when opening the output file.");
486
487
String got = p_output.strip_edges(); // TODO: may be hacky.
488
got += "\n"; // Make sure to insert newline for CI static checks.
489
490
#ifndef DEBUG_ENABLED
491
expected = strip_warnings(expected);
492
#endif
493
494
return got == expected;
495
}
496
497
String GDScriptTest::get_text_for_status(GDScriptTest::TestStatus p_status) const {
498
switch (p_status) {
499
case GDTEST_OK:
500
return "GDTEST_OK";
501
case GDTEST_LOAD_ERROR:
502
return "GDTEST_LOAD_ERROR";
503
case GDTEST_PARSER_ERROR:
504
return "GDTEST_PARSER_ERROR";
505
case GDTEST_ANALYZER_ERROR:
506
return "GDTEST_ANALYZER_ERROR";
507
case GDTEST_COMPILER_ERROR:
508
return "GDTEST_COMPILER_ERROR";
509
case GDTEST_RUNTIME_ERROR:
510
return "GDTEST_RUNTIME_ERROR";
511
}
512
return "";
513
}
514
515
GDScriptTest::TestResult GDScriptTest::execute_test_code(bool p_is_generating) {
516
disable_stdout();
517
518
TestResult result;
519
result.status = GDTEST_OK;
520
result.output = String();
521
result.passed = false;
522
523
Error err = OK;
524
525
// Create script.
526
Ref<GDScript> script;
527
script.instantiate();
528
script->set_path(source_file);
529
if (tokenizer_mode == TOKENIZER_TEXT) {
530
err = script->load_source_code(source_file);
531
} else {
532
String code = FileAccess::get_file_as_string(source_file, &err);
533
if (!err) {
534
Vector<uint8_t> buffer = GDScriptTokenizerBuffer::parse_code_string(code, GDScriptTokenizerBuffer::COMPRESS_ZSTD);
535
script->set_binary_tokens_source(buffer);
536
}
537
}
538
if (err != OK) {
539
enable_stdout();
540
result.status = GDTEST_LOAD_ERROR;
541
result.passed = false;
542
ERR_FAIL_V_MSG(result, "\nCould not load source code for: '" + source_file + "'");
543
}
544
545
// Test parsing.
546
GDScriptParser parser;
547
if (tokenizer_mode == TOKENIZER_TEXT) {
548
err = parser.parse(script->get_source_code(), source_file, false);
549
} else {
550
err = parser.parse_binary(script->get_binary_tokens_source(), source_file);
551
}
552
if (err != OK) {
553
enable_stdout();
554
result.status = GDTEST_PARSER_ERROR;
555
result.output = get_text_for_status(result.status) + "\n";
556
557
const List<GDScriptParser::ParserError> &errors = parser.get_errors();
558
if (!errors.is_empty()) {
559
// Only the first error since the following might be cascading.
560
result.output += errors.front()->get().message + "\n"; // TODO: line, column?
561
}
562
if (!p_is_generating) {
563
result.passed = check_output(result.output);
564
}
565
return result;
566
}
567
568
// Test type-checking.
569
GDScriptAnalyzer analyzer(&parser);
570
err = analyzer.analyze();
571
if (err != OK) {
572
enable_stdout();
573
result.status = GDTEST_ANALYZER_ERROR;
574
result.output = get_text_for_status(result.status) + "\n";
575
576
StringBuilder error_string;
577
for (const GDScriptParser::ParserError &error : parser.get_errors()) {
578
error_string.append(vformat(">> ERROR at line %d: %s\n", error.start_line, error.message));
579
}
580
result.output += error_string.as_string();
581
if (!p_is_generating) {
582
result.passed = check_output(result.output);
583
}
584
return result;
585
}
586
587
#ifdef DEBUG_ENABLED
588
StringBuilder warning_string;
589
for (const GDScriptWarning &warning : parser.get_warnings()) {
590
warning_string.append(vformat("~~ WARNING at line %d: (%s) %s\n", warning.start_line, warning.get_name(), warning.get_message()));
591
}
592
result.output += warning_string.as_string();
593
#endif
594
595
// Test compiling.
596
GDScriptCompiler compiler;
597
err = compiler.compile(&parser, script.ptr(), false);
598
if (err != OK) {
599
enable_stdout();
600
result.status = GDTEST_COMPILER_ERROR;
601
result.output = get_text_for_status(result.status) + "\n";
602
result.output += compiler.get_error() + "\n";
603
if (!p_is_generating) {
604
result.passed = check_output(result.output);
605
}
606
return result;
607
}
608
609
// `*.norun.gd` files are allowed to not contain a `test()` function (no runtime testing).
610
if (source_file.ends_with(".norun.gd")) {
611
enable_stdout();
612
result.status = GDTEST_OK;
613
result.output = get_text_for_status(result.status) + "\n" + result.output;
614
if (!p_is_generating) {
615
result.passed = check_output(result.output);
616
}
617
return result;
618
}
619
620
// Test running.
621
const HashMap<StringName, GDScriptFunction *>::ConstIterator test_function_element = script->get_member_functions().find(GDScriptTestRunner::test_function_name);
622
if (!test_function_element) {
623
enable_stdout();
624
result.status = GDTEST_LOAD_ERROR;
625
result.output = "";
626
result.passed = false;
627
ERR_FAIL_V_MSG(result, "\nCould not find test function on: '" + source_file + "'");
628
}
629
630
// Setup output handlers.
631
ErrorHandlerData error_data(&result, this);
632
633
_print_handler.userdata = &result;
634
_error_handler.userdata = &error_data;
635
add_print_handler(&_print_handler);
636
add_error_handler(&_error_handler);
637
638
err = script->reload();
639
if (err) {
640
enable_stdout();
641
result.status = GDTEST_LOAD_ERROR;
642
result.output = "";
643
result.passed = false;
644
remove_print_handler(&_print_handler);
645
remove_error_handler(&_error_handler);
646
ERR_FAIL_V_MSG(result, "\nCould not reload script: '" + source_file + "'");
647
}
648
649
// Create object instance for test.
650
Object *obj = ClassDB::instantiate(script->get_native()->get_name());
651
Ref<RefCounted> obj_ref;
652
if (obj->is_ref_counted()) {
653
obj_ref = Ref<RefCounted>(Object::cast_to<RefCounted>(obj));
654
}
655
obj->set_script(script);
656
GDScriptInstance *instance = static_cast<GDScriptInstance *>(obj->get_script_instance());
657
658
// Call test function.
659
Callable::CallError call_err;
660
instance->callp(GDScriptTestRunner::test_function_name, nullptr, 0, call_err);
661
662
// Tear down output handlers.
663
remove_print_handler(&_print_handler);
664
remove_error_handler(&_error_handler);
665
666
// Check results.
667
if (call_err.error != Callable::CallError::CALL_OK) {
668
enable_stdout();
669
result.status = GDTEST_LOAD_ERROR;
670
result.passed = false;
671
ERR_FAIL_V_MSG(result, "\nCould not call test function on: '" + source_file + "'");
672
}
673
674
result.output = get_text_for_status(result.status) + "\n" + result.output;
675
if (!p_is_generating) {
676
result.passed = check_output(result.output);
677
}
678
679
if (obj_ref.is_null()) {
680
memdelete(obj);
681
}
682
683
enable_stdout();
684
685
GDScriptCache::remove_script(script->get_path());
686
687
return result;
688
}
689
690
GDScriptTest::TestResult GDScriptTest::run_test() {
691
return execute_test_code(false);
692
}
693
694
bool GDScriptTest::generate_output() {
695
TestResult result = execute_test_code(true);
696
if (result.status == GDTEST_LOAD_ERROR) {
697
return false;
698
}
699
700
Error err = OK;
701
Ref<FileAccess> out_file = FileAccess::open(output_file, FileAccess::WRITE, &err);
702
if (err != OK) {
703
return false;
704
}
705
706
String output = result.output.strip_edges(); // TODO: may be hacky.
707
output += "\n"; // Make sure to insert newline for CI static checks.
708
709
out_file->store_string(output);
710
711
return true;
712
}
713
714
} // namespace GDScriptTests
715
716