diff --git a/.github/workflows/zipmerge-test.yml b/.github/workflows/zipmerge-test.yml new file mode 100644 index 00000000000..8492b045c04 --- /dev/null +++ b/.github/workflows/zipmerge-test.yml @@ -0,0 +1,23 @@ +name: "Test zipmerge code" + +on: + pull_request: + paths: + - "misc/bazel/internal/zipmerge/**" + - "MODULE.bazel" + - ".bazelrc*" + branches: + - main + - "rc/*" + +permissions: + contents: read + +jobs: + test: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + - run: | + bazel test //misc/bazel/internal/zipmerge:test diff --git a/MODULE.bazel b/MODULE.bazel index f9ffd739b46..dff694ce0d8 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -24,6 +24,7 @@ bazel_dep(name = "nlohmann_json", version = "3.11.3", repo_name = "json") bazel_dep(name = "fmt", version = "10.0.0") bazel_dep(name = "gazelle", version = "0.36.0") bazel_dep(name = "rules_dotnet", version = "0.15.1") +bazel_dep(name = "googletest", version = "1.14.0.bcr.1") bazel_dep(name = "buildifier_prebuilt", version = "6.4.0", dev_dependency = True) diff --git a/misc/bazel/internal/zipmerge/BUILD.bazel b/misc/bazel/internal/zipmerge/BUILD.bazel index 332d74ecab5..bb944cc0343 100644 --- a/misc/bazel/internal/zipmerge/BUILD.bazel +++ b/misc/bazel/internal/zipmerge/BUILD.bazel @@ -4,9 +4,6 @@ cc_library( "zipmerge.cpp", ], hdrs = ["zipmerge.h"], - visibility = ["//visibility:public"], - # this is to make internal repo be able to keep on testing this code - # before we fully port tests here ) cc_binary( @@ -20,4 +17,12 @@ cc_binary( ], ) -#TODO port tests from internal repo +cc_test( + name = "test", + size = "small", + deps = [ + ":lib", + "@bazel_tools//tools/cpp/runfiles", + "@googletest//:gtest_main", + ], +) diff --git a/misc/bazel/internal/zipmerge/test-files/CPython-partial.zip b/misc/bazel/internal/zipmerge/test-files/CPython-partial.zip new file mode 100644 index 00000000000..a5a30ec3f90 Binary files /dev/null and b/misc/bazel/internal/zipmerge/test-files/CPython-partial.zip differ diff --git a/misc/bazel/internal/zipmerge/test-files/CPython.zip b/misc/bazel/internal/zipmerge/test-files/CPython.zip new file mode 100644 index 00000000000..2cc0dd85e39 Binary files /dev/null and b/misc/bazel/internal/zipmerge/test-files/CPython.zip differ diff --git a/misc/bazel/internal/zipmerge/test-files/almost-minimal.zip b/misc/bazel/internal/zipmerge/test-files/almost-minimal.zip new file mode 100644 index 00000000000..44541199b78 Binary files /dev/null and b/misc/bazel/internal/zipmerge/test-files/almost-minimal.zip differ diff --git a/misc/bazel/internal/zipmerge/test-files/empty.zip b/misc/bazel/internal/zipmerge/test-files/empty.zip new file mode 100644 index 00000000000..15cb0ecb3e2 Binary files /dev/null and b/misc/bazel/internal/zipmerge/test-files/empty.zip differ diff --git a/misc/bazel/internal/zipmerge/test-files/minimal-x3.zip b/misc/bazel/internal/zipmerge/test-files/minimal-x3.zip new file mode 100644 index 00000000000..a9854656fb0 Binary files /dev/null and b/misc/bazel/internal/zipmerge/test-files/minimal-x3.zip differ diff --git a/misc/bazel/internal/zipmerge/test-files/minimal.zip b/misc/bazel/internal/zipmerge/test-files/minimal.zip new file mode 100644 index 00000000000..6948de0b3e6 Binary files /dev/null and b/misc/bazel/internal/zipmerge/test-files/minimal.zip differ diff --git a/misc/bazel/internal/zipmerge/test-files/slf4j-api-classes-with-footers.jar b/misc/bazel/internal/zipmerge/test-files/slf4j-api-classes-with-footers.jar new file mode 100644 index 00000000000..b3723a437c2 Binary files /dev/null and b/misc/bazel/internal/zipmerge/test-files/slf4j-api-classes-with-footers.jar differ diff --git a/misc/bazel/internal/zipmerge/test-files/slf4j-api-classes-without-footers.jar b/misc/bazel/internal/zipmerge/test-files/slf4j-api-classes-without-footers.jar new file mode 100644 index 00000000000..498cc896632 Binary files /dev/null and b/misc/bazel/internal/zipmerge/test-files/slf4j-api-classes-without-footers.jar differ diff --git a/misc/bazel/internal/zipmerge/zipmerge_test.cpp b/misc/bazel/internal/zipmerge/zipmerge_test.cpp new file mode 100644 index 00000000000..4ac907d60d2 --- /dev/null +++ b/misc/bazel/internal/zipmerge/zipmerge_test.cpp @@ -0,0 +1,155 @@ +#include "misc/bazel/internal/zipmerge/zipmerge.h" + +#include +#include +#include +#include + +#include +#include +#include "tools/cpp/runfiles/runfiles.h" + +using bazel::tools::cpp::runfiles::Runfiles; +using namespace std::string_literals; + +namespace codeql_testing { + +TEST(Zipmerge, ReadAndWrite) { + char buf[7] = {0}; + write2(buf + 1, 0xF2F1U); + write4(buf + 3, 0xF6F5F4F3UL); + EXPECT_STREQ(buf, "\x00\xF1\xF2\xF3\xF4\xF5\xF6"); + EXPECT_EQ(read2(buf + 1), 0xF2F1U); + EXPECT_EQ(read4(buf + 3), 0xF6F5F4F3UL); +} + +TEST(Zipmerge, AppendCd) { + output_cd.length = 0; + append_cd((const uint8_t*)"a", 1); + append_cd((const uint8_t*)"bcd", 3); + append_cd((const uint8_t*)"efghijklmno", 11); + EXPECT_EQ(output_cd.length, 15); + std::string_view bytes{reinterpret_cast(output_cd.bytes), 15}; + EXPECT_EQ(bytes, "abcdefghijklmno"); +} + +TEST(Zipmerge, ShouldIncludeFilenameNow) { + EXPECT_TRUE(should_include_filename_now((const uint8_t*)"x", 1)); + EXPECT_FALSE(should_include_filename_now((const uint8_t*)"x", 1)); + EXPECT_TRUE(should_include_filename_now((const uint8_t*)"y", 1)); + EXPECT_TRUE(should_include_filename_now((const uint8_t*)"yy", 2)); + EXPECT_FALSE(should_include_filename_now((const uint8_t*)"x", 1)); + EXPECT_FALSE(should_include_filename_now((const uint8_t*)"yy", 2)); +} + +TEST(Zipmerge, FindEocd) { + uint8_t buf[500] = {0}; + auto i = 0u; + for (auto& b : buf) { + b = i % 256; + } + memcpy(buf + 17, eocd_signature.data(), eocd_signature.size()); + memcpy(buf + 101, eocd_signature.data(), eocd_signature.size()); + EXPECT_EQ(find_eocd(buf, sizeof(buf)), buf + 101); +} + +const size_t num_hash_bytes = 128 / 8; +const size_t num_hash_uint64s = 2; + +std::string read_file(std::string_view filename) { + std::ifstream f(filename, std::ios::binary); + EXPECT_TRUE(f) << "Could not open '" << filename << "' (" << std::strerror(errno) << ")"; + if (!f) { + return 0; + } + std::stringstream contents; + contents << f.rdbuf(); + return contents.str(); +} + +std::string get_file(const char* name) { + static auto runfiles = []{ +std::string error; + auto ret = Runfiles::CreateForTest(&error); + EXPECT_TRUE(ret) << error; +return ret; +}(); + return runfiles->Rlocation("_main/misc/bazel/internal/zipmerge/test-files/"s + name); +} + +void expect_same_file(const char* actual, const char* expected) { + auto expected_file = get_file(expected); + auto actual_contents = read_file(actual); + unlink(actual); // If tests start failing, you might want to comment out this unlink in order to + // inspect the output. + ASSERT_EQ(actual_contents, read_file(expected_file)) + << "contents of " << actual << " do not match contents of " << expected_file; +} + +template +const char* zipmerge(Args*... inputs) { + reset(); + const char* output = nullptr; + std::vector args{"self"}; + std::array flags{{inputs...}}; + auto i = 0u; + for (; i < flags.size() && std::string_view{flags[i]}.starts_with("-"); ++i) { + args.push_back(flags[i]); + } + output = flags[i]; + args.push_back(output); + ++i; + for (; i < flags.size(); ++i) { + args.push_back(std::string_view{flags[i]}.starts_with("-") ? flags[i] : get_file(flags[i])); + } + EXPECT_EQ(zipmerge_main(args.size(), args.data()), 0); + return output; +} + +TEST(Zipmerge, Identity) { + expect_same_file(zipmerge("out.zip", "CPython.zip"), "CPython.zip"); +} + +TEST(Zipmerge, Idempotent) { + expect_same_file(zipmerge("out.zip", "CPython.zip", "CPython.zip", "CPython.zip"), "CPython.zip"); +} + +TEST(Zipmerge, RemoveEverything) { + expect_same_file(zipmerge("--remove=CPython", "out.zip", "CPython.zip"), "empty.zip"); +} + +TEST(Zipmerge, RemoveEverythingWildcard) { + expect_same_file(zipmerge("--remove=*on", "out.zip", "CPython.zip"), "empty.zip"); +} + +TEST(Zipmerge, RemovePrefixedPaths) { + expect_same_file(zipmerge("--remove=My/CPython", "out.zip", "--prefix=My", "CPython.zip"), + "empty.zip"); +} +TEST(Zipmerge, RemoveSome) { + expect_same_file(zipmerge("--remove=CPython/Extensions.qll", "--remove=CPython/ReturnTypeTrap.ql", + "out.zip", "CPython.zip"), + "CPython-partial.zip"); +} + +TEST(Zipmerge, RemoveSomeWildcard) { + expect_same_file(zipmerge("--remove=CPython/E*.qll", "--remove=CPython/R*", "--remove=CP*l", + "out.zip", "CPython.zip"), + "CPython-partial.zip"); +} + +TEST(Zipmerge, Prefix) { + expect_same_file( + zipmerge("out.zip", "minimal.zip", "--prefix=a", "minimal.zip", "--prefix=b", "minimal.zip"), + "minimal-x3.zip"); +} + +TEST(Zipmerge, InputFileOrder) { + expect_same_file(zipmerge("out.zip", "minimal.zip", "almost-minimal.zip"), "almost-minimal.zip"); +} + +TEST(Zipmerge, LocalFileFooters) { + expect_same_file(zipmerge("out.jar", "slf4j-api-classes-with-footers.jar"), + "slf4j-api-classes-without-footers.jar"); +} +} // namespace codeql_testing