Skip to content

Commit

Permalink
bytecode: fix GDScript 2.0 tests
Browse files Browse the repository at this point in the history
  • Loading branch information
nikitalita committed Dec 29, 2024
1 parent 722d792 commit f72d460
Show file tree
Hide file tree
Showing 2 changed files with 190 additions and 57 deletions.
142 changes: 86 additions & 56 deletions tests/test_bytecode.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#include "test_common.h"
#include "tests/test_macros.h"

#include <core/version_generated.gen.h>
#include <modules/gdscript/gdscript_tokenizer_buffer.h>
#include <utility/common.h>
#include <utility/glob.h>
Expand Down Expand Up @@ -104,12 +105,46 @@ inline String remove_comments(const String &script_text) {
return new_text;
}

inline void test_script_text(const String &script_name, const String &helper_script_text, int revision, bool helper_script) {
inline void output_diff(const String &script_name, const String &decompiled_string_stripped, const String &helper_script_text_stripped) {
// write the script to a temp path
auto old_path = get_tmp_path().path_join(script_name + ".old.gd");
auto new_path = get_tmp_path().path_join(script_name + ".new.gd");
gdre::ensure_dir(get_tmp_path());
auto fa = FileAccess::open(new_path, FileAccess::WRITE);
if (fa.is_valid()) {
fa->store_string(decompiled_string_stripped);
fa->flush();
fa->close();
auto fa2 = FileAccess::open(old_path, FileAccess::WRITE);
if (fa2.is_valid()) {
fa2->store_string(helper_script_text_stripped);
fa2->flush();
fa2->close();
auto thingy = { String("-u"), old_path, new_path };
List<String> args;
for (auto &arg : thingy) {
args.push_back(arg);
}
String pipe;
OS::get_singleton()->execute("diff", args, &pipe);
auto temp_path_diff = new_path + ".diff";
auto fa_diff = FileAccess::open(temp_path_diff, FileAccess::WRITE);
if (fa_diff.is_valid()) {
fa_diff->store_string(pipe);
fa_diff->flush();
fa_diff->close();
}
}
}
}

inline void test_script_text(const String &script_name, const String &helper_script_text, int revision, bool helper_script, bool no_text_equality_check) {
auto decomp = GDScriptDecomp::create_decomp_for_commit(revision);
CHECK(decomp.is_valid());
auto bytecode = decomp->compile_code_string(helper_script_text);
auto compile_error_message = decomp->get_error_message();
CHECK(compile_error_message == "");
CHECK(bytecode.size() > 0);
CHECK(decomp->get_error_message() == "");
auto result = decomp->test_bytecode(bytecode, false);
// TODO: remove BYTECODE_TEST_UNKNOWN and just make it PASS, there are no proper pass cases now
CHECK(result == GDScriptDecomp::BYTECODE_TEST_UNKNOWN);
Expand All @@ -130,55 +165,40 @@ inline void test_script_text(const String &script_name, const String &helper_scr
CHECK(decomp->get_error_message() == "");
// no whitespace
auto decompiled_string = decomp->get_script_text();
CHECK(decompiled_string != "");
auto helper_script_text_stripped = remove_comments(helper_script_text).replace("\"\"\"", "\"");
auto decompiled_string_stripped = remove_comments(decompiled_string).replace("\"\"\"", "\"");
#if DEBUG_ENABLED
if (decompiled_string_stripped != helper_script_text_stripped) {
// write the script to a temp path
auto old_path = get_tmp_path().path_join(script_name + ".old.gd");
auto new_path = get_tmp_path().path_join(script_name + ".new.gd");
gdre::ensure_dir(get_tmp_path());
auto fa = FileAccess::open(new_path, FileAccess::WRITE);
if (fa.is_valid()) {
fa->store_string(decompiled_string);
fa->flush();
fa->close();
auto fa2 = FileAccess::open(old_path, FileAccess::WRITE);
if (fa2.is_valid()) {
fa2->store_string(helper_script_text_stripped);
fa2->flush();
fa2->close();
auto thingy = { String("-u"), old_path, new_path };
List<String> args;
for (auto &arg : thingy) {
args.push_back(arg);
}
String pipe;
OS::get_singleton()->execute("diff", args, &pipe);
auto temp_path_diff = new_path + ".diff";
auto fa_diff = FileAccess::open(temp_path_diff, FileAccess::WRITE);
if (fa_diff.is_valid()) {
fa_diff->store_string(pipe);
fa_diff->flush();
fa_diff->close();
}
}
auto helper_script_text_stripped = remove_comments(helper_script_text).replace("\"\"\"", "\"").replace("'", "\"");
if (!helper_script_text_stripped.strip_edges().is_empty()) {
if (decompiled_string == "") {
int i = 0;

Check warning on line 171 in tests/test_bytecode.h

View workflow job for this annotation

GitHub Actions / 🍎 macOS Editor

unused variable 'i'
}
CHECK(decompiled_string != "");
}

auto decompiled_string_stripped = remove_comments(decompiled_string).replace("\"\"\"", "\"").replace("'", "\"");

#if DEBUG_ENABLED
if (!no_text_equality_check && gdre::remove_whitespace(decompiled_string_stripped) != gdre::remove_whitespace(helper_script_text_stripped)) {
output_diff(script_name, decompiled_string_stripped, helper_script_text_stripped);
}
#endif
CHECK(gdre::remove_whitespace(decompiled_string_stripped) == gdre::remove_whitespace(helper_script_text_stripped));
if (!no_text_equality_check) {
CHECK(gdre::remove_whitespace(decompiled_string_stripped) == gdre::remove_whitespace(helper_script_text_stripped));
}
auto recompiled_bytecode = decomp->compile_code_string(decompiled_string);
CHECK(decomp->get_error_message() == "");
CHECK(recompiled_bytecode.size() > 0);
auto recompiled_result = decomp->test_bytecode(recompiled_bytecode, false);
CHECK(recompiled_result == GDScriptDecomp::BYTECODE_TEST_UNKNOWN);
err = decomp->test_bytecode_match(bytecode, recompiled_bytecode);
#if DEBUG_ENABLED
if (err) {
output_diff(script_name, decompiled_string_stripped, helper_script_text_stripped);
}
#endif
CHECK(decomp->get_error_message() == "");
CHECK(err == OK);
}

inline void test_script(const String &helper_script_path, int revision, bool helper_script) {
inline void test_script(const String &helper_script_path, int revision, bool helper_script, bool no_text_equality_check) {
// tests are located in modules/gdsdecomp/helpers
auto da = DirAccess::create_for_path(helper_script_path);
CHECK(da.is_valid());
Expand All @@ -188,16 +208,23 @@ inline void test_script(const String &helper_script_path, int revision, bool hel
CHECK(err == OK);
CHECK(helper_script_text != "");
auto script_name = helper_script_path.get_file().get_basename();
test_script_text(script_name, helper_script_text, revision, helper_script);
test_script_text(script_name, helper_script_text, revision, helper_script, no_text_equality_check);
}

TEST_CASE("[GDSDecomp][Bytecode] Compiling") {
SUBCASE("GDScriptTokenizer outputs bytecode_version == LATEST_GDSCRIPT_VERSION") {
auto buf = GDScriptTokenizerBuffer::parse_code_string("", GDScriptTokenizerBuffer::CompressMode::COMPRESS_NONE);
CHECK(buf.size() >= 8);
int this_ver = decode_uint32(&buf[4]);
CHECK(this_ver == GDScriptDecomp::LATEST_GDSCRIPT_VERSION);
}
TEST_CASE("[GDSDecomp][Bytecode] GDScriptTokenizer outputs bytecode_version == LATEST_GDSCRIPT_VERSION") {
auto buf = GDScriptTokenizerBuffer::parse_code_string("", GDScriptTokenizerBuffer::CompressMode::COMPRESS_NONE);
CHECK(buf.size() >= 8);
int this_ver = decode_uint32(&buf[4]);
CHECK(this_ver == GDScriptDecomp::LATEST_GDSCRIPT_VERSION);
}

TEST_CASE("[GDSDecomp][Bytecode] Bytecode for current engine version has same number of tokens") {
auto decomp = GDScriptDecomp::create_decomp_for_version(vformat("%d.%d.%d", VERSION_MAJOR, VERSION_MINOR, VERSION_PATCH));
CHECK(decomp.is_valid());
CHECK(decomp->get_token_max() == GDScriptTokenizerBuffer::Token::TK_MAX);
}

TEST_CASE("[GDSDecomp][Bytecode] Compiling Helper Scripts") {
for (int i = 0; tests[i].script != nullptr; i++) {
auto &script_to_revision = tests[i];
String sub_case_name = vformat("Testing compiling script %s, revision %07x", String(script_to_revision.script), script_to_revision.revision);
Expand All @@ -208,29 +235,32 @@ TEST_CASE("[GDSDecomp][Bytecode] Compiling") {
CHECK(da.is_valid());
CHECK(da->dir_exists(helpers_path));
auto helper_script_path = helpers_path.path_join(script_to_revision.script) + ".gd";
test_script(helper_script_path, script_to_revision.revision, true);
test_script(helper_script_path, script_to_revision.revision, true, false);
}
}
//modules/gdscript/tests/scripts
String gdscript_tests_path = "modules/gdscript/tests/scripts";
}

TEST_CASE("[GDSDecomp][Bytecode][GDScript2.0] Compiling GDScript Tests") {
auto cwd = GDRESettings::get_singleton()->get_cwd();
String gdscript_tests_path = GDRESettings::get_singleton()->get_cwd().path_join("modules/gdscript/tests/scripts");
auto gdscript_test_scripts = Glob::rglob(gdscript_tests_path.path_join("**/*.gd"), true);
auto gdscript_test_error_scripts = Vector<String>();
for (int i = 0; i < gdscript_test_scripts.size(); i++) {
// remove any that contain ".notest." or "/error/"
auto &script_path = gdscript_test_scripts[i];
if (script_path.contains(".notest.") || script_path.contains("/error/")) {
if (script_path.contains("/error/")) {
gdscript_test_error_scripts.push_back(script_path);
}
gdscript_test_scripts.erase(script_path);
auto script_path = gdscript_test_scripts[i].trim_prefix(cwd + "/");
if (script_path.contains(".notest.") || script_path.contains("error") || script_path.contains("completion")) {
gdscript_test_error_scripts.push_back(script_path);
gdscript_test_scripts.erase(gdscript_test_scripts[i]);
i--;
} else {
gdscript_test_scripts.write[i] = script_path;
}
}

for (auto &script_path : gdscript_test_scripts) {
auto sub_case_name = vformat("Testing compiling script %s", script_path);
SUBCASE(sub_case_name.utf8().get_data()) {
test_script(script_path, 0x77af6ca, false);
test_script(script_path, 0x77af6ca, false, true);
}
}
}
Expand Down
105 changes: 104 additions & 1 deletion tests/test_gdre_project_loading.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@

#include <core/version_generated.gen.h>
#include <utility/file_access_gdre.h>
#include <utility/import_exporter.h>
#include <utility/pck_dumper.h>

inline Error create_test_pck(const String &pck_path, const HashMap<String, String> &paths) {
PCKPacker pck;
Expand Down Expand Up @@ -147,7 +149,6 @@ TEST_CASE("[GDSDecomp] FileAccessGDRE tests") {
};

CHECK(create_test_pck(tmp_pck_path, files) == OK);
auto resource_path = ProjectSettings::get_singleton()->get_resource_path();

CHECK(!FileAccess::exists("res://test.txt"));
CHECK(GDREPackedData::get_current_dir_access_class(DirAccess::ACCESS_RESOURCES) == GDREPackedData::get_os_dir_access_class_name());
Expand All @@ -167,3 +168,105 @@ TEST_CASE("[GDSDecomp] FileAccessGDRE tests") {
gdre::rimraf(tmp_test_file);
gdre::rimraf(tmp_pck_path);
}

// Disabling this for now; fragile and kind of redundant.
#if 0
static constexpr const char *const export_presets =
R"([preset.0]

name="Dumb"
platform="Windows Desktop"
runnable=true
advanced_options=false
dedicated_server=false
custom_features=""
export_filter="all_resources"
include_filter=""
exclude_filter=""
export_path="test.exe"
encryption_include_filters=""
encryption_exclude_filters=""
encrypt_pck=false
encrypt_directory=false
script_export_mode=1

[preset.0.options]
texture_format/bptc=false
texture_format/s3tc=true
texture_format/etc=false
texture_format/etc2=false
texture_format/no_bptc_fallbacks=true
)";

TEST_CASE("[GDSDecomp] uh oh") {
// get the path to the currently executing binary
String gdscript_tests_path = "modules/gdscript/tests/scripts";

auto scripts_path = GDRESettings::get_singleton()->get_cwd().path_join(gdscript_tests_path);
auto da = DirAccess::open(scripts_path);
CHECK(da.is_valid());
CHECK(da->dir_exists(scripts_path));
// copy the scripts dir to a temporary directory
auto temp_scripts_path = get_tmp_path().path_join("gdscripts_project");
CHECK(da->copy_dir(scripts_path, temp_scripts_path) == OK);
CHECK(store_file_as_string(temp_scripts_path.path_join("export_presets.cfg"), export_presets) == OK);

auto exec_path = OS::get_singleton()->get_executable_path();
auto pck_path = get_tmp_path().path_join("gdscripts.pck");
List<String> args = { "--headless", "-e", "--path", temp_scripts_path, "--export-pack", "Dumb", pck_path };
String pipe;
Error export_cmd_error = OS::get_singleton()->execute(exec_path, args, &pipe);
if (export_cmd_error) {
print_line(pipe);
}
CHECK(export_cmd_error == OK);
// split the output into lines
auto lines = pipe.split("\n");
HashSet<String> packed_files;
for (const auto &line : lines) {
// check if the line is a file path
String ln = line.strip_edges();
if (ln.begins_with("savepack:") && ln.contains("Storing File: ")) {
// get the file path
auto file = line.get_slice("Storing File: ", 1).strip_edges();
packed_files.insert(file);
}
}
auto settings = GDRESettings::get_singleton();
CHECK(settings->load_project({ pck_path }, false) == OK);
CHECK(settings->is_pack_loaded());
CHECK(settings->pack_has_project_config());
CHECK(settings->is_project_config_loaded());
CHECK(settings->get_pack_path() == pck_path);
CHECK(settings->get_ver_major() == VERSION_MAJOR);
CHECK(settings->get_ver_minor() == VERSION_MINOR);
auto decomp = GDScriptDecomp::create_decomp_for_version(vformat("%d.%d.%d", VERSION_MAJOR, VERSION_MINOR, VERSION_PATCH));
CHECK(decomp.is_valid());
CHECK(settings->get_bytecode_revision() != 0);
CHECK(settings->get_bytecode_revision() == decomp->get_bytecode_rev());
for (const auto &file : packed_files) {
CHECK(settings->has_res_path(file));
}
String output_dir = get_tmp_path().path_join("gdscripts_project_decompiled");
PckDumper dumper;
Vector<String> broken_files;
int checked_files;
CHECK(dumper._check_md5_all_files(broken_files, checked_files, nullptr) == OK);
CHECK(broken_files.size() == 0);
CHECK(dumper.pck_dump_to_dir(output_dir, {}) == OK);
HashMap<String, String> pck_files;
for (const auto &file : packed_files) {
String output_file_path = output_dir.path_join(file.trim_prefix("res://"));
pck_files[file] = output_file_path;
}
test_pck_files(pck_files);
ImportExporter import_exporter;
CHECK(import_exporter.export_imports(output_dir, {}) == OK);
auto export_report = import_exporter.get_report();
CHECK(export_report->get_failed().size() == 0);
CHECK(settings->unload_project() == OK);
gdre::rimraf(output_dir);
gdre::rimraf(pck_path);
gdre::rimraf(temp_scripts_path);
}
#endif

0 comments on commit f72d460

Please sign in to comment.