Rhythm Selection & Cleanup
ci / checks (push) Successful in 1h16m10s

This commit is contained in:
khodowany
2026-09-26 02:50:22 -05:00
parent 4d6348d354
commit 9fd5abd075
91 changed files with 4692 additions and 2484 deletions
+208
View File
@@ -14,6 +14,7 @@
#include <vamp_pico/http_client.hpp>
#include <vamp_pico/input.hpp>
#include <vamp_pico/artwork.hpp>
namespace {
@@ -232,6 +233,123 @@ BOOST_AUTO_TEST_CASE(pico_http_client_has_stable_ownership) {
namespace {
auto title_bytes() -> std::string {
auto bytes = std::string(vamp_protocol::title_image_size, '\0');
bytes.replace(0U, 8U, "VPTI\x28\x01\x40\x00", 8U);
bytes[8] = static_cast<char>(0xa3);
return bytes;
}
auto title_request(char digit = 'a') -> vamp_pico::remote_request {
auto request = vamp_pico::remote_request{vamp_pico::request_kind::TITLE};
request.title_id.fill(digit);
request.title_id.back() = 0;
return request;
}
auto title_response() -> std::string {
return "HTTP/1.1 200 OK\r\nContent-Type: application/octet-stream\r\nContent-Length: " +
std::to_string(vamp_protocol::title_image_size) + "\r\nConnection: close\r\n\r\n" + title_bytes();
}
}
BOOST_AUTO_TEST_CASE(pico_title_identity_parser_is_strict_and_transactional) {
auto state = vamp_pico::remote_display_state{};
auto identifier = std::string(64U, 'a');
BOOST_REQUIRE(vamp_pico::parse_remote_display_body("active=1\ntitle_image=" + identifier + "\npage=2\npages=3\n", state));
BOOST_TEST(std::string{state.title_image.data()} == identifier);
for (auto invalid : {identifier + "a", std::string(64U, 'A'), std::string{"../../title"}, std::string(63U, 'a')}) {
BOOST_CHECK(!vamp_pico::parse_remote_display_body("title_image=" + invalid + "\n", state));
BOOST_TEST(std::string{state.title_image.data()} == identifier);
BOOST_TEST(state.page == 2U);
}
BOOST_REQUIRE(vamp_pico::parse_remote_display_body("active=0\ntitle_image=\n", state));
BOOST_TEST(state.title_image[0] == 0);
}
BOOST_AUTO_TEST_CASE(pico_title_cache_discards_old_selection_and_validates_before_copy) {
auto cache = vamp_pico::title_cache{};
auto first = std::string(64U, 'a');
auto second = std::string(64U, 'b');
auto image = title_bytes();
BOOST_CHECK(cache.select(first));
BOOST_CHECK(!cache.ready());
BOOST_REQUIRE(cache.accept(first, image));
image[8] = 0;
BOOST_TEST(cache.image().at(0U, 0U) == 10U);
BOOST_TEST(cache.image().at(1U, 0U) == 3U);
BOOST_TEST(cache.image().at(296U, 0U) == 0U);
BOOST_CHECK(!cache.select(first));
BOOST_CHECK(cache.ready());
BOOST_CHECK(cache.select(second));
BOOST_CHECK(!cache.ready());
BOOST_CHECK(!cache.accept(first, image));
BOOST_CHECK(!cache.accept(second, image.substr(1U)));
image[4] = 0;
BOOST_CHECK(!cache.accept(second, image));
BOOST_CHECK(cache.accept(second, title_bytes()));
BOOST_CHECK(cache.select(""));
BOOST_CHECK(!cache.ready());
BOOST_CHECK(!cache.accept(second, title_bytes()));
}
BOOST_AUTO_TEST_CASE(pico_title_failure_is_visible_until_success_or_selection_change) {
auto cache = vamp_pico::title_cache{};
auto first = std::string(64U, 'a');
auto second = std::string(64U, 'b');
cache.select(first);
BOOST_CHECK(cache.fail(first));
BOOST_CHECK(cache.failed());
BOOST_CHECK(!cache.ready());
BOOST_CHECK(!cache.select(first));
BOOST_CHECK(cache.failed());
BOOST_REQUIRE(cache.accept(first, title_bytes()));
BOOST_CHECK(!cache.failed());
BOOST_CHECK(cache.ready());
cache.select(second);
BOOST_CHECK(!cache.failed());
BOOST_CHECK(!cache.fail(first));
BOOST_CHECK(cache.fail(second));
cache.select("");
BOOST_CHECK(!cache.failed());
BOOST_CHECK(cache.fail(""));
BOOST_CHECK(cache.failed());
}
BOOST_AUTO_TEST_CASE(pico_page_counters_preserve_catalog_maximum_without_truncation) {
BOOST_TEST(std::string{vamp_pico::counter_text(10000U, 10000U).data()} == "10000/10000");
BOOST_TEST(std::string{vamp_pico::counter_text(1U, 10000U).data()} == "1/10000");
BOOST_TEST(std::string{vamp_pico::counter_text(0U, 10000U).data()}.empty());
BOOST_TEST(std::string{vamp_pico::counter_text(10001U, 10001U).data()}.empty());
}
BOOST_AUTO_TEST_CASE(pico_title_requests_follow_commands_and_do_not_postpone_status) {
auto scheduler = vamp_pico::request_scheduler{};
BOOST_REQUIRE(scheduler.next(0U));
scheduler.complete(10U);
scheduler.want_title(std::string(64U, 'a'));
auto title = scheduler.next(11U);
BOOST_REQUIRE(title);
BOOST_CHECK(title->kind == vamp_pico::request_kind::TITLE);
BOOST_CHECK(!scheduler.next(100U));
BOOST_CHECK(scheduler.enqueue(vamp_protocol::page_command::NEXT) == vamp_pico::enqueue_result::QUEUED);
scheduler.cancel_title();
take_command(scheduler, 100U, vamp_protocol::page_command::NEXT);
scheduler.complete(110U);
scheduler.want_title(std::string(64U, 'b'));
title = scheduler.next(111U);
BOOST_REQUIRE(title);
BOOST_CHECK(title->kind == vamp_pico::request_kind::TITLE);
BOOST_TEST(std::string{title->title_id.data()} == std::string(64U, 'b'));
scheduler.complete(1600U);
auto status = scheduler.next(1600U);
BOOST_REQUIRE(status);
BOOST_CHECK(status->kind == vamp_pico::request_kind::DISPLAY);
}
namespace {
struct fake_network {
std::array<tcp_pcb, 64> pcbs{};
std::size_t allocated{};
@@ -695,3 +813,93 @@ BOOST_FIXTURE_TEST_CASE(pico_http_oversized_response_is_rejected_without_truncat
BOOST_TEST(network.acknowledged == 1025U);
BOOST_TEST(network.aborts == 1U);
}
BOOST_FIXTURE_TEST_CASE(pico_http_title_binary_is_fragmented_bounded_and_owned, fake_lwip_fixture) {
auto client = vamp_pico::http_client{};
auto request = title_request();
BOOST_CHECK(client.start(request, 0U) == vamp_pico::http_start_result::STARTED);
auto &pcb = network.pcbs[0];
BOOST_CHECK(deliver_connected(pcb) == ERR_OK);
BOOST_CHECK(network.written.find("POST /api/remote/title HTTP/1.1\r\n") == 0U);
auto marker = network.written.find("\r\n\r\n");
BOOST_REQUIRE(marker != std::string::npos);
BOOST_TEST(network.written.substr(marker + 4U) == "{\"id\":\"" + std::string(64U, 'a') + "\"}");
BOOST_TEST(network.written.size() - marker - 4U == 73U);
auto response = title_response();
for (auto offset = std::size_t{}; offset < response.size(); offset += 137U) {
BOOST_CHECK(deliver_data(pcb, std::string_view{response}.substr(offset, 137U)) == ERR_OK);
}
BOOST_CHECK(deliver_eof(pcb) == ERR_OK);
auto result = client.take_result();
BOOST_REQUIRE(result);
BOOST_REQUIRE(result->success);
BOOST_CHECK(result->request.kind == vamp_pico::request_kind::TITLE);
BOOST_CHECK(client.title_image() == title_bytes());
BOOST_TEST(network.acknowledged == response.size());
client.start({}, 1U);
BOOST_CHECK(client.title_image().empty());
}
BOOST_FIXTURE_TEST_CASE(pico_http_title_rejects_incorrect_binary_framing, fake_lwip_fixture) {
auto client = vamp_pico::http_client{};
auto valid = title_response();
auto extra = valid;
extra.insert(extra.find("\r\n\r\n"), "\r\nContent-Length: 9480");
auto chunked = valid;
chunked.insert(chunked.find("\r\n\r\n"), "\r\nTransfer-Encoding: chunked");
auto compressed = valid;
compressed.insert(compressed.find("\r\n\r\n"), "\r\nContent-Encoding: gzip");
auto wrong_type = valid;
wrong_type.replace(wrong_type.find("application/octet-stream"), 24U, "text/plain");
auto wrong_length = valid;
wrong_length.replace(wrong_length.find("9480"), 4U, "9479");
auto wrong_image = valid;
wrong_image[wrong_image.find("VPTI") + 4U] = 0;
for (auto const &response : {valid.substr(0U, valid.size() - 1U), valid + "x", extra, chunked, compressed, wrong_type, wrong_length, wrong_image}) {
BOOST_CHECK(client.start(title_request(), 0U) == vamp_pico::http_start_result::STARTED);
auto &pcb = network.pcbs[network.allocated - 1U];
BOOST_CHECK(deliver_connected(pcb) == ERR_OK);
BOOST_CHECK(deliver_data(pcb, response) == ERR_OK);
deliver_eof(pcb);
require_failure(client, 1U);
BOOST_CHECK(client.title_image().empty());
}
}
BOOST_FIXTURE_TEST_CASE(pico_http_title_timeout_overflow_and_bad_identifier_are_failures, fake_lwip_fixture) {
auto client = vamp_pico::http_client{};
BOOST_CHECK(client.start(title_request('z'), 0U) == vamp_pico::http_start_result::STARTED);
require_failure(client, 0U);
BOOST_TEST(network.allocated == 0U);
client.start(title_request(), 100U);
BOOST_CHECK(client.poll(1599U) == vamp_pico::http_status::IN_FLIGHT);
require_failure(client, 1600U);
client.start(title_request(), 1601U);
auto &pcb = network.pcbs[1];
deliver_connected(pcb);
BOOST_CHECK(deliver_data(pcb, std::string(vamp_protocol::title_image_size + 1025U, 'x')) == ERR_ABRT);
require_failure(client, 1602U);
}
BOOST_FIXTURE_TEST_CASE(pico_http_title_preemption_detaches_late_callbacks, fake_lwip_fixture) {
auto client = vamp_pico::http_client{};
auto scheduler = vamp_pico::request_scheduler{};
scheduler.want_title(std::string(64U, 'a'));
auto request = scheduler.next(0U);
BOOST_REQUIRE(request);
client.start(*request, 0U);
auto &old = network.pcbs[0];
deliver_connected(old);
deliver_data(old, std::string_view{title_response()}.substr(0U, 200U));
scheduler.enqueue(vamp_protocol::page_command::SELECT);
client.cancel();
scheduler.cancel_title();
request = scheduler.next(1U);
BOOST_REQUIRE(request);
BOOST_CHECK(request->kind == vamp_pico::request_kind::COMMAND);
client.start(*request, 1U);
deliver_data(old, title_response());
deliver_eof(old);
BOOST_CHECK(client.poll(2U) == vamp_pico::http_status::IN_FLIGHT);
BOOST_CHECK(client.title_image().empty());
}