Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
godotengine
GitHub Repository: godotengine/godot
Path: blob/master/modules/gdscript/tests/gdscript_test_runner.cpp
10278 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(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
#ifdef DEBUG_ENABLED
149
// Set all warning levels to "Warn" in order to test them properly, even the ones that default to error.
150
ProjectSettings::get_singleton()->set_setting("debug/gdscript/warnings/enable", true);
151
for (int i = 0; i < (int)GDScriptWarning::WARNING_MAX; i++) {
152
if (i == GDScriptWarning::UNTYPED_DECLARATION || i == GDScriptWarning::INFERRED_DECLARATION) {
153
// TODO: Add ability for test scripts to specify which warnings to enable/disable for testing.
154
continue;
155
}
156
String warning_setting = GDScriptWarning::get_settings_path_from_code((GDScriptWarning::Code)i);
157
ProjectSettings::get_singleton()->set_setting(warning_setting, (int)GDScriptWarning::WARN);
158
}
159
#endif
160
161
// Enable printing to show results
162
CoreGlobals::print_line_enabled = true;
163
CoreGlobals::print_error_enabled = true;
164
}
165
166
GDScriptTestRunner::~GDScriptTestRunner() {
167
test_function_name = StringName();
168
if (do_init_languages) {
169
finish_language();
170
}
171
}
172
173
#ifndef DEBUG_ENABLED
174
static String strip_warnings(const String &p_expected) {
175
// On release builds we don't have warnings. Here we remove them from the output before comparison
176
// so it doesn't fail just because of difference in warnings.
177
String expected_no_warnings;
178
for (String line : p_expected.split("\n")) {
179
if (line.begins_with("~~ ")) {
180
continue;
181
}
182
expected_no_warnings += line + "\n";
183
}
184
return expected_no_warnings.strip_edges() + "\n";
185
}
186
#endif
187
188
int GDScriptTestRunner::run_tests() {
189
if (!make_tests()) {
190
FAIL("An error occurred while making the tests.");
191
return -1;
192
}
193
194
if (!generate_class_index()) {
195
FAIL("An error occurred while generating class index.");
196
return -1;
197
}
198
199
int failed = 0;
200
for (int i = 0; i < tests.size(); i++) {
201
GDScriptTest test = tests[i];
202
if (print_filenames) {
203
print_line(test.get_source_relative_filepath());
204
}
205
GDScriptTest::TestResult result = test.run_test();
206
207
String expected = FileAccess::get_file_as_string(test.get_output_file());
208
#ifndef DEBUG_ENABLED
209
expected = strip_warnings(expected);
210
#endif
211
INFO(test.get_source_file());
212
if (!result.passed) {
213
INFO(expected);
214
failed++;
215
}
216
217
CHECK_MESSAGE(result.passed, (result.passed ? String() : result.output));
218
}
219
220
return failed;
221
}
222
223
bool GDScriptTestRunner::generate_outputs() {
224
is_generating = true;
225
226
if (!make_tests()) {
227
print_line("Failed to generate a test output.");
228
return false;
229
}
230
231
if (!generate_class_index()) {
232
return false;
233
}
234
235
for (int i = 0; i < tests.size(); i++) {
236
GDScriptTest test = tests[i];
237
if (print_filenames) {
238
print_line(test.get_source_relative_filepath());
239
} else {
240
OS::get_singleton()->print(".");
241
}
242
243
bool result = test.generate_output();
244
245
if (!result) {
246
print_line("\nCould not generate output for " + test.get_source_file());
247
return false;
248
}
249
}
250
print_line("\nGenerated output files for " + itos(tests.size()) + " tests successfully.");
251
252
return true;
253
}
254
255
bool GDScriptTestRunner::make_tests_for_dir(const String &p_dir) {
256
Error err = OK;
257
Ref<DirAccess> dir(DirAccess::open(p_dir, &err));
258
259
if (err != OK) {
260
return false;
261
}
262
263
String current_dir = dir->get_current_dir();
264
265
dir->list_dir_begin();
266
String next = dir->get_next();
267
268
while (!next.is_empty()) {
269
if (dir->current_is_dir()) {
270
if (next == "." || next == ".." || next == "completion" || next == "lsp") {
271
next = dir->get_next();
272
continue;
273
}
274
if (!make_tests_for_dir(current_dir.path_join(next))) {
275
return false;
276
}
277
} else {
278
// `*.notest.gd` files are skipped.
279
if (next.ends_with(".notest.gd")) {
280
next = dir->get_next();
281
continue;
282
} else if (binary_tokens && next.ends_with(".textonly.gd")) {
283
next = dir->get_next();
284
continue;
285
} else if (next.get_extension().to_lower() == "gd") {
286
#ifndef DEBUG_ENABLED
287
// On release builds, skip tests marked as debug only.
288
Error open_err = OK;
289
Ref<FileAccess> script_file(FileAccess::open(current_dir.path_join(next), FileAccess::READ, &open_err));
290
if (open_err != OK) {
291
ERR_PRINT(vformat(R"(Couldn't open test file "%s".)", next));
292
next = dir->get_next();
293
continue;
294
} else {
295
if (script_file->get_line() == "#debug-only") {
296
next = dir->get_next();
297
continue;
298
}
299
}
300
#endif
301
302
String out_file = next.get_basename() + ".out";
303
ERR_FAIL_COND_V_MSG(!is_generating && !dir->file_exists(out_file), false, "Could not find output file for " + next);
304
305
if (next.ends_with(".bin.gd")) {
306
// Test text mode first.
307
GDScriptTest text_test(current_dir.path_join(next), current_dir.path_join(out_file), source_dir);
308
tests.push_back(text_test);
309
// Test binary mode even without `--use-binary-tokens`.
310
GDScriptTest bin_test(current_dir.path_join(next), current_dir.path_join(out_file), source_dir);
311
bin_test.set_tokenizer_mode(GDScriptTest::TOKENIZER_BUFFER);
312
tests.push_back(bin_test);
313
} else {
314
GDScriptTest test(current_dir.path_join(next), current_dir.path_join(out_file), source_dir);
315
if (binary_tokens) {
316
test.set_tokenizer_mode(GDScriptTest::TOKENIZER_BUFFER);
317
}
318
tests.push_back(test);
319
}
320
}
321
}
322
323
next = dir->get_next();
324
}
325
326
dir->list_dir_end();
327
328
return true;
329
}
330
331
bool GDScriptTestRunner::make_tests() {
332
Error err = OK;
333
Ref<DirAccess> dir(DirAccess::open(source_dir, &err));
334
335
ERR_FAIL_COND_V_MSG(err != OK, false, "Could not open specified test directory.");
336
337
source_dir = dir->get_current_dir() + "/"; // Make it absolute path.
338
return make_tests_for_dir(dir->get_current_dir());
339
}
340
341
static bool generate_class_index_recursive(const String &p_dir) {
342
Error err = OK;
343
Ref<DirAccess> dir(DirAccess::open(p_dir, &err));
344
345
if (err != OK) {
346
return false;
347
}
348
349
String current_dir = dir->get_current_dir();
350
351
dir->list_dir_begin();
352
String next = dir->get_next();
353
354
StringName gdscript_name = GDScriptLanguage::get_singleton()->get_name();
355
while (!next.is_empty()) {
356
if (dir->current_is_dir()) {
357
if (next == "." || next == ".." || next == "completion" || next == "lsp") {
358
next = dir->get_next();
359
continue;
360
}
361
if (!generate_class_index_recursive(current_dir.path_join(next))) {
362
return false;
363
}
364
} else {
365
if (!next.ends_with(".gd")) {
366
next = dir->get_next();
367
continue;
368
}
369
String base_type;
370
String source_file = current_dir.path_join(next);
371
bool is_abstract = false;
372
bool is_tool = false;
373
String class_name = GDScriptLanguage::get_singleton()->get_global_class_name(source_file, &base_type, nullptr, &is_abstract, &is_tool);
374
if (class_name.is_empty()) {
375
next = dir->get_next();
376
continue;
377
}
378
ERR_FAIL_COND_V_MSG(ScriptServer::is_global_class(class_name), false,
379
"Class name '" + class_name + "' from " + source_file + " is already used in " + ScriptServer::get_global_class_path(class_name));
380
381
ScriptServer::add_global_class(class_name, base_type, gdscript_name, source_file, is_abstract, is_tool);
382
}
383
384
next = dir->get_next();
385
}
386
387
dir->list_dir_end();
388
389
return true;
390
}
391
392
bool GDScriptTestRunner::generate_class_index() {
393
Error err = OK;
394
Ref<DirAccess> dir(DirAccess::open(source_dir, &err));
395
396
ERR_FAIL_COND_V_MSG(err != OK, false, "Could not open specified test directory.");
397
398
source_dir = dir->get_current_dir() + "/"; // Make it absolute path.
399
return generate_class_index_recursive(dir->get_current_dir());
400
}
401
402
GDScriptTest::GDScriptTest(const String &p_source_path, const String &p_output_path, const String &p_base_dir) {
403
source_file = p_source_path;
404
output_file = p_output_path;
405
base_dir = p_base_dir;
406
_print_handler.printfunc = print_handler;
407
_error_handler.errfunc = error_handler;
408
}
409
410
void GDScriptTestRunner::handle_cmdline() {
411
List<String> cmdline_args = OS::get_singleton()->get_cmdline_args();
412
413
for (List<String>::Element *E = cmdline_args.front(); E; E = E->next()) {
414
String &cmd = E->get();
415
if (cmd == "--gdscript-generate-tests") {
416
String path;
417
if (E->next()) {
418
path = E->next()->get();
419
} else {
420
path = "modules/gdscript/tests/scripts";
421
}
422
423
GDScriptTestRunner runner(path, false, cmdline_args.find("--print-filenames") != nullptr);
424
425
bool completed = runner.generate_outputs();
426
int failed = completed ? 0 : -1;
427
exit(failed);
428
}
429
}
430
}
431
432
void GDScriptTest::enable_stdout() {
433
// TODO: this could likely be handled by doctest or `tests/test_macros.h`.
434
OS::get_singleton()->set_stdout_enabled(true);
435
OS::get_singleton()->set_stderr_enabled(true);
436
}
437
438
void GDScriptTest::disable_stdout() {
439
// TODO: this could likely be handled by doctest or `tests/test_macros.h`.
440
OS::get_singleton()->set_stdout_enabled(false);
441
OS::get_singleton()->set_stderr_enabled(false);
442
}
443
444
void GDScriptTest::print_handler(void *p_this, const String &p_message, bool p_error, bool p_rich) {
445
TestResult *result = (TestResult *)p_this;
446
result->output += p_message + "\n";
447
}
448
449
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) {
450
ErrorHandlerData *data = (ErrorHandlerData *)p_this;
451
GDScriptTest *self = data->self;
452
TestResult *result = data->result;
453
454
result->status = GDTEST_RUNTIME_ERROR;
455
456
String header = _error_handler_type_string(p_type);
457
458
// Only include the file, line, and function for script errors,
459
// otherwise the test outputs changes based on the platform/compiler.
460
if (p_type == ERR_HANDLER_SCRIPT) {
461
header += vformat(" at %s:%d on %s()",
462
String::utf8(p_file).trim_prefix(self->base_dir).replace_char('\\', '/'),
463
p_line,
464
String::utf8(p_function));
465
}
466
467
StringBuilder error_string;
468
error_string.append(vformat(">> %s: %s\n", header, String::utf8(p_error)));
469
if (strlen(p_explanation) > 0) {
470
error_string.append(vformat(">> %s\n", String::utf8(p_explanation)));
471
}
472
473
result->output += error_string.as_string();
474
}
475
476
bool GDScriptTest::check_output(const String &p_output) const {
477
Error err = OK;
478
String expected = FileAccess::get_file_as_string(output_file, &err);
479
480
ERR_FAIL_COND_V_MSG(err != OK, false, "Error when opening the output file.");
481
482
String got = p_output.strip_edges(); // TODO: may be hacky.
483
got += "\n"; // Make sure to insert newline for CI static checks.
484
485
#ifndef DEBUG_ENABLED
486
expected = strip_warnings(expected);
487
#endif
488
489
return got == expected;
490
}
491
492
String GDScriptTest::get_text_for_status(GDScriptTest::TestStatus p_status) const {
493
switch (p_status) {
494
case GDTEST_OK:
495
return "GDTEST_OK";
496
case GDTEST_LOAD_ERROR:
497
return "GDTEST_LOAD_ERROR";
498
case GDTEST_PARSER_ERROR:
499
return "GDTEST_PARSER_ERROR";
500
case GDTEST_ANALYZER_ERROR:
501
return "GDTEST_ANALYZER_ERROR";
502
case GDTEST_COMPILER_ERROR:
503
return "GDTEST_COMPILER_ERROR";
504
case GDTEST_RUNTIME_ERROR:
505
return "GDTEST_RUNTIME_ERROR";
506
}
507
return "";
508
}
509
510
GDScriptTest::TestResult GDScriptTest::execute_test_code(bool p_is_generating) {
511
disable_stdout();
512
513
TestResult result;
514
result.status = GDTEST_OK;
515
result.output = String();
516
result.passed = false;
517
518
Error err = OK;
519
520
// Create script.
521
Ref<GDScript> script;
522
script.instantiate();
523
script->set_path(source_file);
524
if (tokenizer_mode == TOKENIZER_TEXT) {
525
err = script->load_source_code(source_file);
526
} else {
527
String code = FileAccess::get_file_as_string(source_file, &err);
528
if (!err) {
529
Vector<uint8_t> buffer = GDScriptTokenizerBuffer::parse_code_string(code, GDScriptTokenizerBuffer::COMPRESS_ZSTD);
530
script->set_binary_tokens_source(buffer);
531
}
532
}
533
if (err != OK) {
534
enable_stdout();
535
result.status = GDTEST_LOAD_ERROR;
536
result.passed = false;
537
ERR_FAIL_V_MSG(result, "\nCould not load source code for: '" + source_file + "'");
538
}
539
540
// Test parsing.
541
GDScriptParser parser;
542
if (tokenizer_mode == TOKENIZER_TEXT) {
543
err = parser.parse(script->get_source_code(), source_file, false);
544
} else {
545
err = parser.parse_binary(script->get_binary_tokens_source(), source_file);
546
}
547
if (err != OK) {
548
enable_stdout();
549
result.status = GDTEST_PARSER_ERROR;
550
result.output = get_text_for_status(result.status) + "\n";
551
552
const List<GDScriptParser::ParserError> &errors = parser.get_errors();
553
if (!errors.is_empty()) {
554
// Only the first error since the following might be cascading.
555
result.output += errors.front()->get().message + "\n"; // TODO: line, column?
556
}
557
if (!p_is_generating) {
558
result.passed = check_output(result.output);
559
}
560
return result;
561
}
562
563
// Test type-checking.
564
GDScriptAnalyzer analyzer(&parser);
565
err = analyzer.analyze();
566
if (err != OK) {
567
enable_stdout();
568
result.status = GDTEST_ANALYZER_ERROR;
569
result.output = get_text_for_status(result.status) + "\n";
570
571
StringBuilder error_string;
572
for (const GDScriptParser::ParserError &error : parser.get_errors()) {
573
error_string.append(vformat(">> ERROR at line %d: %s\n", error.line, error.message));
574
}
575
result.output += error_string.as_string();
576
if (!p_is_generating) {
577
result.passed = check_output(result.output);
578
}
579
return result;
580
}
581
582
#ifdef DEBUG_ENABLED
583
StringBuilder warning_string;
584
for (const GDScriptWarning &warning : parser.get_warnings()) {
585
warning_string.append(vformat("~~ WARNING at line %d: (%s) %s\n", warning.start_line, warning.get_name(), warning.get_message()));
586
}
587
result.output += warning_string.as_string();
588
#endif
589
590
// Test compiling.
591
GDScriptCompiler compiler;
592
err = compiler.compile(&parser, script.ptr(), false);
593
if (err != OK) {
594
enable_stdout();
595
result.status = GDTEST_COMPILER_ERROR;
596
result.output = get_text_for_status(result.status) + "\n";
597
result.output += compiler.get_error() + "\n";
598
if (!p_is_generating) {
599
result.passed = check_output(result.output);
600
}
601
return result;
602
}
603
604
// `*.norun.gd` files are allowed to not contain a `test()` function (no runtime testing).
605
if (source_file.ends_with(".norun.gd")) {
606
enable_stdout();
607
result.status = GDTEST_OK;
608
result.output = get_text_for_status(result.status) + "\n" + result.output;
609
if (!p_is_generating) {
610
result.passed = check_output(result.output);
611
}
612
return result;
613
}
614
615
// Test running.
616
const HashMap<StringName, GDScriptFunction *>::ConstIterator test_function_element = script->get_member_functions().find(GDScriptTestRunner::test_function_name);
617
if (!test_function_element) {
618
enable_stdout();
619
result.status = GDTEST_LOAD_ERROR;
620
result.output = "";
621
result.passed = false;
622
ERR_FAIL_V_MSG(result, "\nCould not find test function on: '" + source_file + "'");
623
}
624
625
// Setup output handlers.
626
ErrorHandlerData error_data(&result, this);
627
628
_print_handler.userdata = &result;
629
_error_handler.userdata = &error_data;
630
add_print_handler(&_print_handler);
631
add_error_handler(&_error_handler);
632
633
err = script->reload();
634
if (err) {
635
enable_stdout();
636
result.status = GDTEST_LOAD_ERROR;
637
result.output = "";
638
result.passed = false;
639
remove_print_handler(&_print_handler);
640
remove_error_handler(&_error_handler);
641
ERR_FAIL_V_MSG(result, "\nCould not reload script: '" + source_file + "'");
642
}
643
644
// Create object instance for test.
645
Object *obj = ClassDB::instantiate(script->get_native()->get_name());
646
Ref<RefCounted> obj_ref;
647
if (obj->is_ref_counted()) {
648
obj_ref = Ref<RefCounted>(Object::cast_to<RefCounted>(obj));
649
}
650
obj->set_script(script);
651
GDScriptInstance *instance = static_cast<GDScriptInstance *>(obj->get_script_instance());
652
653
// Call test function.
654
Callable::CallError call_err;
655
instance->callp(GDScriptTestRunner::test_function_name, nullptr, 0, call_err);
656
657
// Tear down output handlers.
658
remove_print_handler(&_print_handler);
659
remove_error_handler(&_error_handler);
660
661
// Check results.
662
if (call_err.error != Callable::CallError::CALL_OK) {
663
enable_stdout();
664
result.status = GDTEST_LOAD_ERROR;
665
result.passed = false;
666
ERR_FAIL_V_MSG(result, "\nCould not call test function on: '" + source_file + "'");
667
}
668
669
result.output = get_text_for_status(result.status) + "\n" + result.output;
670
if (!p_is_generating) {
671
result.passed = check_output(result.output);
672
}
673
674
if (obj_ref.is_null()) {
675
memdelete(obj);
676
}
677
678
enable_stdout();
679
680
GDScriptCache::remove_script(script->get_path());
681
682
return result;
683
}
684
685
GDScriptTest::TestResult GDScriptTest::run_test() {
686
return execute_test_code(false);
687
}
688
689
bool GDScriptTest::generate_output() {
690
TestResult result = execute_test_code(true);
691
if (result.status == GDTEST_LOAD_ERROR) {
692
return false;
693
}
694
695
Error err = OK;
696
Ref<FileAccess> out_file = FileAccess::open(output_file, FileAccess::WRITE, &err);
697
if (err != OK) {
698
return false;
699
}
700
701
String output = result.output.strip_edges(); // TODO: may be hacky.
702
output += "\n"; // Make sure to insert newline for CI static checks.
703
704
out_file->store_string(output);
705
706
return true;
707
}
708
709
} // namespace GDScriptTests
710
711