+80
-5
@@ -18,6 +18,9 @@
|
||||
#include <boost/asio/use_future.hpp>
|
||||
#include <boost/json.hpp>
|
||||
#include <openssl/sha.h>
|
||||
#include <raylib.h>
|
||||
|
||||
#include <unistd.h>
|
||||
|
||||
#include <vamp_zero/app.hpp>
|
||||
#include <vamp_zero/database.hpp>
|
||||
@@ -118,10 +121,15 @@ auto config_file(std::filesystem::path const &root, vamp_zero::config const &con
|
||||
return path.string();
|
||||
}
|
||||
|
||||
auto fixture_page() -> std::string {
|
||||
auto bytes = std::array<std::uchar_t, 24>{137, 80, 78, 71, 13, 10, 26, 10, 0, 0, 0, 13,
|
||||
73, 72, 68, 82, 0, 0, 3, 208, 0, 0, 1, 228};
|
||||
return {reinterpret_cast<std::char_t const *>(bytes.data()), bytes.size()};
|
||||
auto fixture_page(std::int32_t width = 976, std::int32_t height = 484) -> std::string {
|
||||
auto image = GenImageColor(width, height, WHITE);
|
||||
auto size = std::int32_t{};
|
||||
auto bytes = ExportImageToMemory(image, ".png", &size);
|
||||
UnloadImage(image);
|
||||
BOOST_REQUIRE(bytes != nullptr);
|
||||
auto result = std::string{reinterpret_cast<std::char_t const *>(bytes), static_cast<std::size_t>(size)};
|
||||
MemFree(bytes);
|
||||
return result;
|
||||
}
|
||||
|
||||
auto checksum(std::string const &bytes) -> std::string {
|
||||
@@ -354,7 +362,7 @@ BOOST_AUTO_TEST_CASE(readiness_rejects_corrupt_checksums_and_escaping_pages) {
|
||||
std::filesystem::create_symlink(root.path / "outside.png", page);
|
||||
} else {
|
||||
auto bytes = fixture_page();
|
||||
bytes.back() = 0;
|
||||
bytes.back() ^= 1;
|
||||
write_file(page, bytes);
|
||||
}
|
||||
auto ready = run_once(root.path, config);
|
||||
@@ -387,6 +395,73 @@ BOOST_AUTO_TEST_CASE(readiness_rejects_unusable_database_and_runtime_paths) {
|
||||
BOOST_TEST(read_file(root.path / "blocked") == "regular file");
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(readiness_decodes_pages_even_when_checksums_match) {
|
||||
for (auto kind : {"truncated", "missing pixels", "wrong dimensions", "oversized"}) {
|
||||
auto root = temporary_directory{};
|
||||
auto config = fixture_config(root.path);
|
||||
make_assets(config);
|
||||
auto bytes = fixture_page(std::string_view{kind} == "wrong dimensions" ? 10 : 976);
|
||||
if (std::string_view{kind} == "truncated") {
|
||||
bytes.resize(24U);
|
||||
} else if (std::string_view{kind} == "missing pixels") {
|
||||
bytes.resize(33U);
|
||||
} else if (std::string_view{kind} == "oversized") {
|
||||
bytes[16] = 127;
|
||||
}
|
||||
auto score_path = std::filesystem::path{config.runtime.assets_path} / "songs" / "first.json";
|
||||
auto score = boost::json::parse(read_file(score_path));
|
||||
auto &page = score.at("presentation").at("pages").as_array()[0].as_object();
|
||||
write_file(score_path.parent_path() / std::string{page.at("path").as_string()}, bytes);
|
||||
page["sha256"] = checksum(bytes);
|
||||
write_file(score_path, boost::json::serialize(score));
|
||||
auto ready = run_once(root.path, config);
|
||||
BOOST_REQUIRE_MESSAGE(!ready, kind);
|
||||
BOOST_TEST(ready.error().describe().find("first.pages/1.png") != std::string::npos);
|
||||
BOOST_TEST(!std::filesystem::exists(config.runtime.heartbeat_path));
|
||||
}
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(readiness_rejects_actual_heartbeat_directories_and_symlinks) {
|
||||
for (auto kind : {"directory", "symlink", "dangling symlink"}) {
|
||||
auto root = temporary_directory{};
|
||||
auto config = fixture_config(root.path);
|
||||
make_assets(config);
|
||||
std::filesystem::create_directories(std::filesystem::path{config.runtime.heartbeat_path}.parent_path());
|
||||
if (std::string_view{kind} == "directory") {
|
||||
std::filesystem::create_directory(config.runtime.heartbeat_path);
|
||||
} else {
|
||||
if (std::string_view{kind} == "symlink") {
|
||||
write_file(root.path / "target", "untouched");
|
||||
}
|
||||
std::filesystem::create_symlink(root.path / "target", config.runtime.heartbeat_path);
|
||||
}
|
||||
auto ready = run_once(root.path, config);
|
||||
BOOST_REQUIRE(!ready);
|
||||
BOOST_TEST(ready.error().describe().find(config.runtime.heartbeat_path) != std::string::npos);
|
||||
require_no_probe(config, true);
|
||||
if (std::string_view{kind} == "symlink") {
|
||||
BOOST_TEST(read_file(root.path / "target") == "untouched");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(readiness_rejects_unwritable_parent_without_leaving_probes) {
|
||||
if (::geteuid() == 0) {
|
||||
BOOST_TEST_MESSAGE("Permission check requires an unprivileged test process");
|
||||
return;
|
||||
}
|
||||
auto root = temporary_directory{};
|
||||
auto config = fixture_config(root.path);
|
||||
make_assets(config);
|
||||
auto parent = std::filesystem::path{config.runtime.heartbeat_path}.parent_path();
|
||||
std::filesystem::create_directory(parent);
|
||||
std::filesystem::permissions(parent, std::filesystem::perms::owner_read | std::filesystem::perms::owner_exec);
|
||||
auto ready = run_once(root.path, config);
|
||||
std::filesystem::permissions(parent, std::filesystem::perms::owner_all);
|
||||
BOOST_TEST(!ready);
|
||||
require_no_probe(config);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(readiness_rejects_occupied_bind_and_releases_successful_probe) {
|
||||
auto root = temporary_directory{};
|
||||
auto config = fixture_config(root.path);
|
||||
|
||||
Reference in New Issue
Block a user