Path: blob/master/modules/gdscript/tests/test_gdscript.cpp
10278 views
/**************************************************************************/1/* test_gdscript.cpp */2/**************************************************************************/3/* This file is part of: */4/* GODOT ENGINE */5/* https://godotengine.org */6/**************************************************************************/7/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */8/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */9/* */10/* Permission is hereby granted, free of charge, to any person obtaining */11/* a copy of this software and associated documentation files (the */12/* "Software"), to deal in the Software without restriction, including */13/* without limitation the rights to use, copy, modify, merge, publish, */14/* distribute, sublicense, and/or sell copies of the Software, and to */15/* permit persons to whom the Software is furnished to do so, subject to */16/* the following conditions: */17/* */18/* The above copyright notice and this permission notice shall be */19/* included in all copies or substantial portions of the Software. */20/* */21/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */22/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */23/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */24/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */25/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */26/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */27/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */28/**************************************************************************/2930#include "test_gdscript.h"3132#include "../gdscript_analyzer.h"33#include "../gdscript_compiler.h"34#include "../gdscript_parser.h"35#include "../gdscript_tokenizer.h"36#include "../gdscript_tokenizer_buffer.h"3738#include "core/config/project_settings.h"39#include "core/io/file_access.h"40#include "core/os/os.h"41#include "core/string/string_builder.h"4243#ifdef TOOLS_ENABLED44#include "editor/settings/editor_settings.h"45#endif4647namespace GDScriptTests {4849static void test_tokenizer(const String &p_code, const Vector<String> &p_lines) {50GDScriptTokenizerText tokenizer;51tokenizer.set_source_code(p_code);5253int tab_size = 4;54#ifdef TOOLS_ENABLED55if (EditorSettings::get_singleton()) {56tab_size = EditorSettings::get_singleton()->get_setting("text_editor/behavior/indent/size");57}58#endif // TOOLS_ENABLED59String tab = String(" ").repeat(tab_size);6061GDScriptTokenizer::Token current = tokenizer.scan();62while (current.type != GDScriptTokenizer::Token::TK_EOF) {63StringBuilder token;64token += " --> "; // Padding for line number.6566if (current.start_line != current.end_line) {67// Print "vvvvvv" to point at the token.68StringBuilder pointer;69pointer += " "; // Padding for line number.7071int line_width = 0;72if (current.start_line - 1 >= 0 && current.start_line - 1 < p_lines.size()) {73line_width = p_lines[current.start_line - 1].replace("\t", tab).length();74}7576const int offset = MAX(0, current.start_column - 1);77const int width = MAX(0, line_width - current.start_column + 1);78pointer += String::chr(' ').repeat(offset) + String::chr('v').repeat(width);7980print_line(pointer.as_string());81}8283for (int l = current.start_line; l <= current.end_line && l <= p_lines.size(); l++) {84print_line(vformat("%04d %s", l, p_lines[l - 1]).replace("\t", tab));85}8687{88// Print "^^^^^^" to point at the token.89StringBuilder pointer;90pointer += " "; // Padding for line number.9192if (current.start_line == current.end_line) {93const int offset = MAX(0, current.start_column - 1);94const int width = MAX(0, current.end_column - current.start_column);95pointer += String::chr(' ').repeat(offset) + String::chr('^').repeat(width);96} else {97const int width = MAX(0, current.end_column - 1);98pointer += String::chr('^').repeat(width);99}100101print_line(pointer.as_string());102}103104token += current.get_name();105106if (current.type == GDScriptTokenizer::Token::ERROR || current.type == GDScriptTokenizer::Token::LITERAL || current.type == GDScriptTokenizer::Token::IDENTIFIER || current.type == GDScriptTokenizer::Token::ANNOTATION) {107token += "(";108token += Variant::get_type_name(current.literal.get_type());109token += ") ";110token += current.literal;111}112113print_line(token.as_string());114115print_line("-------------------------------------------------------");116117current = tokenizer.scan();118}119120print_line(current.get_name()); // Should be EOF121}122123static void test_tokenizer_buffer(const Vector<uint8_t> &p_buffer, const Vector<String> &p_lines);124125static void test_tokenizer_buffer(const String &p_code, const Vector<String> &p_lines) {126Vector<uint8_t> binary = GDScriptTokenizerBuffer::parse_code_string(p_code, GDScriptTokenizerBuffer::COMPRESS_NONE);127test_tokenizer_buffer(binary, p_lines);128}129130static void test_tokenizer_buffer(const Vector<uint8_t> &p_buffer, const Vector<String> &p_lines) {131GDScriptTokenizerBuffer tokenizer;132tokenizer.set_code_buffer(p_buffer);133134int tab_size = 4;135#ifdef TOOLS_ENABLED136if (EditorSettings::get_singleton()) {137tab_size = EditorSettings::get_singleton()->get_setting("text_editor/behavior/indent/size");138}139#endif // TOOLS_ENABLED140String tab = String(" ").repeat(tab_size);141142GDScriptTokenizer::Token current = tokenizer.scan();143while (current.type != GDScriptTokenizer::Token::TK_EOF) {144StringBuilder token;145token += " --> "; // Padding for line number.146147for (int l = current.start_line; l <= current.end_line && l <= p_lines.size(); l++) {148print_line(vformat("%04d %s", l, p_lines[l - 1]).replace("\t", tab));149}150151token += current.get_name();152153if (current.type == GDScriptTokenizer::Token::ERROR || current.type == GDScriptTokenizer::Token::LITERAL || current.type == GDScriptTokenizer::Token::IDENTIFIER || current.type == GDScriptTokenizer::Token::ANNOTATION) {154token += "(";155token += Variant::get_type_name(current.literal.get_type());156token += ") ";157token += current.literal;158}159160print_line(token.as_string());161162print_line("-------------------------------------------------------");163164current = tokenizer.scan();165}166167print_line(current.get_name()); // Should be EOF168}169170static void test_parser(const String &p_code, const String &p_script_path, const Vector<String> &p_lines) {171GDScriptParser parser;172Error err = parser.parse(p_code, p_script_path, false);173174if (err != OK) {175const List<GDScriptParser::ParserError> &errors = parser.get_errors();176for (const GDScriptParser::ParserError &error : errors) {177print_line(vformat("%02d:%02d: %s", error.line, error.column, error.message));178}179}180181GDScriptAnalyzer analyzer(&parser);182err = analyzer.analyze();183184if (err != OK) {185const List<GDScriptParser::ParserError> &errors = parser.get_errors();186for (const GDScriptParser::ParserError &error : errors) {187print_line(vformat("%02d:%02d: %s", error.line, error.column, error.message));188}189}190191#ifdef TOOLS_ENABLED192GDScriptParser::TreePrinter printer;193printer.print_tree(parser);194#endif195}196197static void disassemble_function(const GDScriptFunction *p_func, const Vector<String> &p_lines) {198ERR_FAIL_NULL(p_func);199200String arg_string;201bool is_first_arg = true;202for (const PropertyInfo &arg_info : p_func->get_method_info().arguments) {203if (!is_first_arg) {204arg_string += ", ";205}206arg_string += arg_info.name;207is_first_arg = false;208}209if (p_func->is_vararg()) {210// `MethodInfo` does not support the rest parameter name.211arg_string += (p_func->get_argument_count() == 0) ? "...args" : ", ...args";212}213214print_line(vformat("Function %s(%s)", p_func->get_name(), arg_string));215#ifdef TOOLS_ENABLED216p_func->disassemble(p_lines);217#endif218print_line("");219print_line("");220}221222static void recursively_disassemble_functions(const Ref<GDScript> p_script, const Vector<String> &p_lines) {223print_line(vformat("Class %s", p_script->get_fully_qualified_name()));224print_line("");225print_line("");226227const GDScriptFunction *implicit_initializer = p_script->get_implicit_initializer();228if (implicit_initializer != nullptr) {229disassemble_function(implicit_initializer, p_lines);230}231232const GDScriptFunction *implicit_ready = p_script->get_implicit_ready();233if (implicit_ready != nullptr) {234disassemble_function(implicit_ready, p_lines);235}236237const GDScriptFunction *static_initializer = p_script->get_static_initializer();238if (static_initializer != nullptr) {239disassemble_function(static_initializer, p_lines);240}241242for (const KeyValue<GDScriptFunction *, GDScript::LambdaInfo> &E : p_script->get_lambda_info()) {243disassemble_function(E.key, p_lines);244}245246for (const KeyValue<StringName, GDScriptFunction *> &E : p_script->get_member_functions()) {247disassemble_function(E.value, p_lines);248}249250for (const KeyValue<StringName, Ref<GDScript>> &E : p_script->get_subclasses()) {251recursively_disassemble_functions(E.value, p_lines);252}253}254255static void test_compiler(const String &p_code, const String &p_script_path, const Vector<String> &p_lines) {256GDScriptParser parser;257Error err = parser.parse(p_code, p_script_path, false);258259if (err != OK) {260print_line("Error in parser:");261const List<GDScriptParser::ParserError> &errors = parser.get_errors();262for (const GDScriptParser::ParserError &error : errors) {263print_line(vformat("%02d:%02d: %s", error.line, error.column, error.message));264}265return;266}267268GDScriptAnalyzer analyzer(&parser);269err = analyzer.analyze();270271if (err != OK) {272print_line("Error in analyzer:");273const List<GDScriptParser::ParserError> &errors = parser.get_errors();274for (const GDScriptParser::ParserError &error : errors) {275print_line(vformat("%02d:%02d: %s", error.line, error.column, error.message));276}277return;278}279280GDScriptCompiler compiler;281Ref<GDScript> script;282script.instantiate();283script->set_path(p_script_path);284285err = compiler.compile(&parser, script.ptr(), false);286287if (err) {288print_line("Error in compiler:");289print_line(vformat("%02d:%02d: %s", compiler.get_error_line(), compiler.get_error_column(), compiler.get_error()));290return;291}292293recursively_disassemble_functions(script, p_lines);294}295296void test(TestType p_type) {297List<String> cmdlargs = OS::get_singleton()->get_cmdline_args();298299if (cmdlargs.is_empty()) {300return;301}302303String test = cmdlargs.back()->get();304if (!test.ends_with(".gd") && !test.ends_with(".gdc")) {305print_line("This test expects a path to a GDScript file as its last parameter. Got: " + test);306return;307}308309Ref<FileAccess> fa = FileAccess::open(test, FileAccess::READ);310ERR_FAIL_COND_MSG(fa.is_null(), "Could not open file: " + test);311312// Initialize the language for the test routine.313init_language(fa->get_path_absolute().get_base_dir());314315// Load global classes.316TypedArray<Dictionary> script_classes = ProjectSettings::get_singleton()->get_global_class_list();317for (int i = 0; i < script_classes.size(); i++) {318Dictionary c = script_classes[i];319if (!c.has("class") || !c.has("language") || !c.has("path") || !c.has("base") || !c.has("is_abstract") || !c.has("is_tool")) {320continue;321}322ScriptServer::add_global_class(c["class"], c["base"], c["language"], c["path"], c["is_abstract"], c["is_tool"]);323}324325Vector<uint8_t> buf;326uint64_t flen = fa->get_length();327buf.resize(flen + 1);328fa->get_buffer(buf.ptrw(), flen);329buf.write[flen] = 0;330331String code = String::utf8((const char *)&buf[0]);332333Vector<String> lines;334int last = 0;335for (int i = 0; i <= code.length(); i++) {336if (code[i] == '\n' || code[i] == 0) {337lines.push_back(code.substr(last, i - last));338last = i + 1;339}340}341342switch (p_type) {343case TEST_TOKENIZER:344test_tokenizer(code, lines);345break;346case TEST_TOKENIZER_BUFFER:347if (test.ends_with(".gdc")) {348test_tokenizer_buffer(buf, lines);349} else {350test_tokenizer_buffer(code, lines);351}352break;353case TEST_PARSER:354test_parser(code, test, lines);355break;356case TEST_COMPILER:357test_compiler(code, test, lines);358break;359case TEST_BYTECODE:360print_line("Not implemented.");361}362363finish_language();364}365} // namespace GDScriptTests366367368