diff --git a/core/os/os.h b/core/os/os.h index 48dae99188a..8e5c0c26e02 100644 --- a/core/os/os.h +++ b/core/os/os.h @@ -86,11 +86,13 @@ public: protected: friend class Main; + // Needed by tests to setup command-line args. + friend int test_main(int argc, char *argv[]); HasServerFeatureCallback has_server_feature_callback = nullptr; RenderThreadMode _render_thread_mode = RENDER_THREAD_SAFE; - // functions used by main to initialize/deinitialize the OS + // Functions used by Main to initialize/deinitialize the OS. void add_logger(Logger *p_logger); virtual void initialize() = 0; diff --git a/modules/gdscript/SCsub b/modules/gdscript/SCsub index e58a1d8edcf..5c8cbdf8693 100644 --- a/modules/gdscript/SCsub +++ b/modules/gdscript/SCsub @@ -17,3 +17,7 @@ if env["tools"]: # Using a define in the disabled case, to avoid having an extra define # in regular builds where all modules are enabled. env_gdscript.Append(CPPDEFINES=["GDSCRIPT_NO_LSP"]) + +if env["tests"]: + env_gdscript.Append(CPPDEFINES=["TESTS_ENABLED"]) + env_gdscript.add_source_files(env.modules_sources, "./tests/*.cpp") diff --git a/modules/gdscript/register_types.cpp b/modules/gdscript/register_types.cpp index c554cbac05e..7dad878eb1d 100644 --- a/modules/gdscript/register_types.cpp +++ b/modules/gdscript/register_types.cpp @@ -39,6 +39,11 @@ #include "gdscript_cache.h" #include "gdscript_tokenizer.h" +#ifdef TESTS_ENABLED +#include "tests/test_gdscript.h" +#include "tests/test_macros.h" +#endif + GDScriptLanguage *script_language_gd = nullptr; Ref resource_loader_gd; Ref resource_saver_gd; @@ -153,3 +158,26 @@ void unregister_gdscript_types() { GDScriptParser::cleanup(); GDScriptAnalyzer::cleanup(); } + +#ifdef TESTS_ENABLED +void test_tokenizer() { + TestGDScript::test(TestGDScript::TestType::TEST_TOKENIZER); +} + +void test_parser() { + TestGDScript::test(TestGDScript::TestType::TEST_PARSER); +} + +void test_compiler() { + TestGDScript::test(TestGDScript::TestType::TEST_COMPILER); +} + +void test_bytecode() { + TestGDScript::test(TestGDScript::TestType::TEST_BYTECODE); +} + +REGISTER_TEST_COMMAND("gdscript-tokenizer", &test_tokenizer); +REGISTER_TEST_COMMAND("gdscript-parser", &test_parser); +REGISTER_TEST_COMMAND("gdscript-compiler", &test_compiler); +REGISTER_TEST_COMMAND("gdscript-bytecode", &test_bytecode); +#endif diff --git a/tests/test_gdscript.cpp b/modules/gdscript/tests/test_gdscript.cpp similarity index 94% rename from tests/test_gdscript.cpp rename to modules/gdscript/tests/test_gdscript.cpp index a909f216ee9..68d9984b435 100644 --- a/tests/test_gdscript.cpp +++ b/modules/gdscript/tests/test_gdscript.cpp @@ -35,10 +35,6 @@ #include "core/os/os.h" #include "core/string_builder.h" -#include "modules/modules_enabled.gen.h" - -#ifdef MODULE_GDSCRIPT_ENABLED - #include "modules/gdscript/gdscript_analyzer.h" #include "modules/gdscript/gdscript_compiler.h" #include "modules/gdscript/gdscript_parser.h" @@ -183,21 +179,21 @@ static void test_compiler(const String &p_code, const String &p_script_path, con } } -MainLoop *test(TestType p_type) { +void test(TestType p_type) { List cmdlargs = OS::get_singleton()->get_cmdline_args(); if (cmdlargs.empty()) { - return nullptr; + return; } String test = cmdlargs.back()->get(); if (!test.ends_with(".gd")) { print_line("This test expects a path to a GDScript file as its last parameter. Got: " + test); - return nullptr; + return; } FileAccessRef fa = FileAccess::open(test, FileAccess::READ); - ERR_FAIL_COND_V_MSG(!fa, nullptr, "Could not open file: " + test); + ERR_FAIL_COND_MSG(!fa, "Could not open file: " + test); Vector buf; int flen = fa->get_len(); @@ -230,21 +226,6 @@ MainLoop *test(TestType p_type) { case TEST_BYTECODE: print_line("Not implemented."); } - - return nullptr; } } // namespace TestGDScript - -#else - -namespace TestGDScript { - -MainLoop *test(TestType p_type) { - ERR_PRINT("The GDScript module is disabled, therefore GDScript tests cannot be used."); - return nullptr; -} - -} // namespace TestGDScript - -#endif diff --git a/tests/test_gdscript.h b/modules/gdscript/tests/test_gdscript.h similarity index 97% rename from tests/test_gdscript.h rename to modules/gdscript/tests/test_gdscript.h index 6595da14307..5aa962dcf8f 100644 --- a/tests/test_gdscript.h +++ b/modules/gdscript/tests/test_gdscript.h @@ -31,8 +31,6 @@ #ifndef TEST_GDSCRIPT_H #define TEST_GDSCRIPT_H -#include "core/os/main_loop.h" - namespace TestGDScript { enum TestType { @@ -42,7 +40,8 @@ enum TestType { TEST_BYTECODE, }; -MainLoop *test(TestType p_type); +void test(TestType p_type); + } // namespace TestGDScript #endif // TEST_GDSCRIPT_H diff --git a/tests/SCsub b/tests/SCsub index 84c9fc1ffed..a2864d805b4 100644 --- a/tests/SCsub +++ b/tests/SCsub @@ -6,9 +6,6 @@ env.tests_sources = [] env_tests = env.Clone() -# Enable test framework and inform it of configuration method. -env_tests.Append(CPPDEFINES=["DOCTEST_CONFIG_IMPLEMENT"]) - # We must disable the THREAD_LOCAL entirely in doctest to prevent crashes on debugging # Since we link with /MT thread_local is always expired when the header is used # So the debugger crashes the engine and it causes weird errors diff --git a/tests/test_macros.cpp b/tests/test_macros.cpp new file mode 100644 index 00000000000..2317223b230 --- /dev/null +++ b/tests/test_macros.cpp @@ -0,0 +1,42 @@ +/*************************************************************************/ +/* test_macros.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#define DOCTEST_CONFIG_IMPLEMENT +#include "test_macros.h" + +Map *test_commands = nullptr; + +int register_test_command(String p_command, TestFunc p_function) { + if (!test_commands) { + test_commands = new Map; + } + test_commands->insert(p_command, p_function); + return 0; +} diff --git a/tests/test_macros.h b/tests/test_macros.h index 45ba8581dd3..3486c68bb77 100644 --- a/tests/test_macros.h +++ b/tests/test_macros.h @@ -31,6 +31,9 @@ #ifndef TEST_MACROS_H #define TEST_MACROS_H +#include "core/map.h" +#include "core/variant.h" + // See documentation for doctest at: // https://github.com/onqtam/doctest/blob/master/doc/markdown/readme.md#reference #include "thirdparty/doctest/doctest.h" @@ -104,4 +107,17 @@ DOCTEST_STRINGIFY_VARIANT(PackedVector2Array); DOCTEST_STRINGIFY_VARIANT(PackedVector3Array); DOCTEST_STRINGIFY_VARIANT(PackedColorArray); +// Register test commands to be launched from the command-line. +// For instance: REGISTER_TEST_COMMAND("gdscript-parser" &test_parser_func). +// Example usage: `godot --test gdscript-parser`. + +typedef void (*TestFunc)(); +extern Map *test_commands; +int register_test_command(String p_command, TestFunc p_function); + +#define REGISTER_TEST_COMMAND(m_command, m_function) \ + DOCTEST_GLOBAL_NO_WARNINGS(DOCTEST_ANONYMOUS(_DOCTEST_ANON_VAR_)) = \ + register_test_command(m_command, m_function); \ + DOCTEST_GLOBAL_NO_WARNINGS_END() + #endif // TEST_MACROS_H diff --git a/tests/test_main.cpp b/tests/test_main.cpp index 43c1ef331fd..1c9535174bb 100644 --- a/tests/test_main.cpp +++ b/tests/test_main.cpp @@ -37,7 +37,6 @@ #include "test_class_db.h" #include "test_color.h" #include "test_expression.h" -#include "test_gdscript.h" #include "test_gradient.h" #include "test_gui.h" #include "test_math.h" @@ -56,40 +55,63 @@ #include "tests/test_macros.h" int test_main(int argc, char *argv[]) { + bool run_tests = true; + + // Convert arguments to Godot's command-line. + List args; + + for (int i = 0; i < argc; i++) { + args.push_back(String::utf8(argv[i])); + } + OS::get_singleton()->set_cmdline("", args); + + // Run custom test tools. + if (test_commands) { + for (Map::Element *E = test_commands->front(); E; E = E->next()) { + if (args.find(E->key())) { + const TestFunc &test_func = E->get(); + test_func(); + run_tests = false; + break; + } + } + if (!run_tests) { + delete test_commands; + return 0; + } + } // Doctest runner. doctest::Context test_context; - List valid_arguments; + List test_args; // Clean arguments of "--test" from the args. - int argument_count = 0; for (int x = 0; x < argc; x++) { if (strncmp(argv[x], "--test", 6) != 0) { - valid_arguments.push_back(String(argv[x])); - argument_count++; + test_args.push_back(String(argv[x])); } } // Convert Godot command line arguments back to standard arguments. - char **args = new char *[valid_arguments.size()]; - for (int x = 0; x < valid_arguments.size(); x++) { + char **doctest_args = new char *[test_args.size()]; + for (int x = 0; x < test_args.size(); x++) { // Operation to convert Godot string to non wchar string. - CharString cs = valid_arguments[x].utf8(); + CharString cs = test_args[x].utf8(); const char *str = cs.get_data(); // Allocate the string copy. - args[x] = new char[strlen(str) + 1]; + doctest_args[x] = new char[strlen(str) + 1]; // Copy this into memory. - std::memcpy(args[x], str, strlen(str) + 1); + memcpy(doctest_args[x], str, strlen(str) + 1); } - test_context.applyCommandLine(valid_arguments.size(), args); + test_context.applyCommandLine(test_args.size(), doctest_args); test_context.setOption("order-by", "name"); test_context.setOption("abort-after", 5); test_context.setOption("no-breaks", true); - for (int x = 0; x < valid_arguments.size(); x++) { - delete[] args[x]; + for (int x = 0; x < test_args.size(); x++) { + delete[] doctest_args[x]; } - delete[] args; + delete[] doctest_args; return test_context.run(); }