mirror of
https://github.com/torlando-tech/pyxis.git
synced 2026-09-02 00:58:31 +00:00
408 lines
23 KiB
C++
408 lines
23 KiB
C++
#include "Hardware/TDeck/MapStyleCatalog.h"
|
|
#include "Hardware/TDeck/MapTilePack.h"
|
|
|
|
#include <cstddef>
|
|
#include <cstdint>
|
|
#include <cstdlib>
|
|
#include <cstring>
|
|
#include <iostream>
|
|
|
|
using Hardware::TDeck::ActiveMapSetCodec;
|
|
using Hardware::TDeck::ActiveMapSetView;
|
|
using Hardware::TDeck::MapStyleCatalog;
|
|
using Hardware::TDeck::MapStyleCatalogResult;
|
|
using Hardware::TDeck::MapStyleSummary;
|
|
using Hardware::TDeck::MapTilePack;
|
|
using Hardware::TDeck::MapTileStorage;
|
|
using Hardware::TDeck::TileStoreResult;
|
|
using Pyxis::MapPackManifest;
|
|
using Pyxis::RowSpan;
|
|
|
|
namespace {
|
|
std::size_t tests_run = 0U;
|
|
void fail(const char* expression, int line) {
|
|
std::cerr << "line " << line << ": " << expression << '\n';
|
|
std::exit(1);
|
|
}
|
|
#define CHECK(expression) do { if (!(expression)) fail(#expression, __LINE__); } while (false)
|
|
void beginTest() { ++tests_run; }
|
|
bool rejectCommit(void*) { return false; }
|
|
|
|
std::uint32_t crc32(const std::uint8_t* input, std::size_t length) {
|
|
std::uint32_t crc = UINT32_C(0xffffffff);
|
|
for (std::size_t index = 0U; index < length; ++index) {
|
|
crc ^= input[index];
|
|
for (std::uint8_t bit = 0U; bit < 8U; ++bit) {
|
|
crc = (crc >> 1U) ^ ((crc & 1U) != 0U ? UINT32_C(0xedb88320) : 0U);
|
|
}
|
|
}
|
|
return ~crc;
|
|
}
|
|
void putU16(std::uint8_t* output, std::uint16_t value) {
|
|
output[0] = static_cast<std::uint8_t>(value);
|
|
output[1] = static_cast<std::uint8_t>(value >> 8U);
|
|
}
|
|
void putU32(std::uint8_t* output, std::uint32_t value) {
|
|
output[0] = static_cast<std::uint8_t>(value);
|
|
output[1] = static_cast<std::uint8_t>(value >> 8U);
|
|
output[2] = static_cast<std::uint8_t>(value >> 16U);
|
|
output[3] = static_cast<std::uint8_t>(value >> 24U);
|
|
}
|
|
std::uint32_t getU32(const std::uint8_t* input) {
|
|
return static_cast<std::uint32_t>(input[0]) |
|
|
(static_cast<std::uint32_t>(input[1]) << 8U) |
|
|
(static_cast<std::uint32_t>(input[2]) << 16U) |
|
|
(static_cast<std::uint32_t>(input[3]) << 24U);
|
|
}
|
|
|
|
const char* attributionFor(const char* style) {
|
|
if (std::strcmp(style, "osm-bright") == 0)
|
|
return "(c) OpenMapTiles (c) OpenStreetMap contributors";
|
|
if (std::strcmp(style, "toner") == 0)
|
|
return "(c) MapTiler (c) OpenStreetMap contributors";
|
|
return "(c) OpenMapTiles (c) OpenStreetMap contributors; style (c) CARTO";
|
|
}
|
|
const char* sourceFor(const char* style) {
|
|
if (std::strcmp(style, "osm-bright") == 0) return "Oxed's Map Tile Downloader (OSM Bright)";
|
|
if (std::strcmp(style, "dark-matter") == 0) return "Oxed's Map Tile Downloader (Dark Matter)";
|
|
if (std::strcmp(style, "positron") == 0) return "Oxed's Map Tile Downloader (Positron)";
|
|
return "Oxed's Map Tile Downloader (Toner)";
|
|
}
|
|
const char* licenseFor(const char* style) {
|
|
if (std::strcmp(style, "osm-bright") == 0)
|
|
return "OSM ODbL; style CC-BY-4.0/BSD-3-Clause";
|
|
if (std::strcmp(style, "toner") == 0)
|
|
return "OSM ODbL; style CC-BY-4.0/BSD-3-Clause (Stamen ISC)";
|
|
return "OSM ODbL; style CC-BY-4.0/BSD-3-Clause (CARTO CC-BY-3.0)";
|
|
}
|
|
|
|
std::size_t makeRecord(std::uint8_t* output, const char* style, std::uint32_t generation,
|
|
const char* first_pack = "overview", const char* second_pack = NULL) {
|
|
std::memset(output, 0, ActiveMapSetCodec::MAX_SERIALIZED_SIZE);
|
|
output[0] = 'P'; output[1] = 'M'; output[2] = 'A'; output[3] = 'S'; output[4] = 2U;
|
|
putU32(output + 8U, generation);
|
|
std::size_t position = 12U;
|
|
const std::size_t style_length = std::strlen(style);
|
|
output[position++] = static_cast<std::uint8_t>(style_length);
|
|
std::memcpy(output + position, style, style_length); position += style_length;
|
|
const char* attribution = attributionFor(style);
|
|
const std::size_t attribution_length = std::strlen(attribution);
|
|
output[position++] = static_cast<std::uint8_t>(attribution_length);
|
|
std::memcpy(output + position, attribution, attribution_length); position += attribution_length;
|
|
output[position++] = second_pack == NULL ? 1U : 2U;
|
|
const char* packs[2] = {first_pack, second_pack};
|
|
const std::size_t pack_count = second_pack == NULL ? 1U : 2U;
|
|
for (std::size_t pack = 0U; pack < pack_count; ++pack) {
|
|
const std::size_t id_length = std::strlen(packs[pack]);
|
|
output[position++] = static_cast<std::uint8_t>(id_length);
|
|
std::memcpy(output + position, packs[pack], id_length); position += id_length;
|
|
putU16(output + position, 1U); position += 2U;
|
|
output[position++] = 2U;
|
|
putU32(output + position, 1U); position += 4U;
|
|
putU32(output + position, static_cast<std::uint32_t>(pack + 1U)); position += 4U;
|
|
putU32(output + position, static_cast<std::uint32_t>(pack + 1U)); position += 4U;
|
|
}
|
|
const std::size_t length = position + 4U;
|
|
putU16(output + 6U, static_cast<std::uint16_t>(length));
|
|
putU32(output + position, crc32(output, position));
|
|
return length;
|
|
}
|
|
void refreshCrc(std::uint8_t* record, std::size_t length) {
|
|
putU32(record + length - 4U, crc32(record, length - 4U));
|
|
}
|
|
|
|
struct File {
|
|
char path[96];
|
|
std::uint8_t bytes[ActiveMapSetCodec::MAX_SERIALIZED_SIZE + 1U];
|
|
std::size_t size;
|
|
};
|
|
|
|
class FakeStorage : public MapTileStorage {
|
|
public:
|
|
FakeStorage() : available(true), file_count(0U), read_file(NULL), read_position(0U),
|
|
write_file(NULL), write_size(0U), list_calls(0U), abort_calls(0U), begin_write_calls(0U),
|
|
fail_active_slot_reads(false), fail_write(false), fail_commit(false),
|
|
corrupt_after_commit(false), short_write(false), fail_manifest_reads(false) {}
|
|
|
|
File* find(const char* path) {
|
|
for (std::size_t index = 0U; index < file_count; ++index) {
|
|
if (std::strcmp(files[index].path, path) == 0) return &files[index];
|
|
}
|
|
return NULL;
|
|
}
|
|
void add(const char* path, const std::uint8_t* bytes, std::size_t size) {
|
|
File* file = find(path);
|
|
if (file == NULL) {
|
|
CHECK(file_count < 10U); file = &files[file_count++];
|
|
CHECK(std::strlen(path) < sizeof(file->path)); std::strcpy(file->path, path);
|
|
}
|
|
CHECK(size <= sizeof(file->bytes));
|
|
if (size != 0U) std::memcpy(file->bytes, bytes, size);
|
|
file->size = size;
|
|
}
|
|
void erase(const char* path) {
|
|
for (std::size_t index = 0U; index < file_count; ++index) {
|
|
if (std::strcmp(files[index].path, path) != 0) continue;
|
|
files[index] = files[file_count - 1U]; --file_count; return;
|
|
}
|
|
}
|
|
virtual bool isAvailable() const { return available; }
|
|
virtual TileStoreResult beginRead(const char* path, std::uint32_t& size) {
|
|
if (!available) return TileStoreResult::STORAGE_UNAVAILABLE;
|
|
if (fail_active_slot_reads && std::strstr(path, "active-pack.") != NULL) return TileStoreResult::IO_ERROR;
|
|
if (fail_manifest_reads && std::strstr(path, "manifest.pmp") != NULL) return TileStoreResult::BUSY;
|
|
if (read_file != NULL) return TileStoreResult::BUSY;
|
|
File* file = find(path); if (file == NULL) return TileStoreResult::MISS;
|
|
read_file = file; read_position = 0U; size = static_cast<std::uint32_t>(file->size);
|
|
return TileStoreResult::OK;
|
|
}
|
|
virtual TileStoreResult readChunk(std::uint8_t* output, std::size_t capacity, std::size_t& count) {
|
|
count = 0U; if (read_file == NULL) return TileStoreResult::IO_ERROR;
|
|
const std::size_t remaining = read_file->size - read_position;
|
|
count = remaining < capacity ? remaining : capacity;
|
|
if (count != 0U) std::memcpy(output, read_file->bytes + read_position, count);
|
|
read_position += count; return TileStoreResult::OK;
|
|
}
|
|
virtual void endRead() { read_file = NULL; read_position = 0U; }
|
|
virtual TileStoreResult beginWrite(const char* path) {
|
|
++begin_write_calls;
|
|
if (fail_write || write_file != NULL) return TileStoreResult::IO_ERROR;
|
|
File* file = find(path);
|
|
if (file == NULL) {
|
|
CHECK(file_count < 10U); file = &files[file_count++];
|
|
std::strcpy(file->path, path); file->size = 0U;
|
|
}
|
|
write_file = file; write_size = 0U; return TileStoreResult::OK;
|
|
}
|
|
virtual TileStoreResult writeChunk(const std::uint8_t* data, std::size_t size, std::size_t& written) {
|
|
written = 0U; if (write_file == NULL) return TileStoreResult::IO_ERROR;
|
|
const std::size_t amount = short_write && size > 1U ? size - 1U : size;
|
|
CHECK(write_size + amount <= sizeof(write_file->bytes));
|
|
std::memcpy(write_file->bytes + write_size, data, amount); write_size += amount; written = amount;
|
|
return short_write ? TileStoreResult::IO_ERROR : TileStoreResult::OK;
|
|
}
|
|
virtual TileStoreResult commitWrite() {
|
|
if (write_file == NULL || fail_commit) return TileStoreResult::IO_ERROR;
|
|
write_file->size = write_size;
|
|
if (corrupt_after_commit && write_file->size != 0U) write_file->bytes[write_file->size - 1U] ^= 1U;
|
|
write_file = NULL; write_size = 0U; return TileStoreResult::OK;
|
|
}
|
|
virtual void abortWrite() { ++abort_calls; write_file = NULL; write_size = 0U; }
|
|
virtual TileStoreResult remove(const char*) { return TileStoreResult::IO_ERROR; }
|
|
virtual TileStoreResult rename(const char*, const char*) { return TileStoreResult::IO_ERROR; }
|
|
virtual TileStoreResult stat(const char*, std::uint32_t&) { return TileStoreResult::IO_ERROR; }
|
|
virtual TileStoreResult beginList() { ++list_calls; return TileStoreResult::IO_ERROR; }
|
|
virtual TileStoreResult nextList(char*, std::size_t, bool&) { ++list_calls; return TileStoreResult::IO_ERROR; }
|
|
virtual void endList() { ++list_calls; }
|
|
|
|
bool available; File files[10]; std::size_t file_count; File* read_file; std::size_t read_position;
|
|
File* write_file; std::size_t write_size; std::size_t list_calls; std::size_t abort_calls;
|
|
std::size_t begin_write_calls;
|
|
bool fail_active_slot_reads; bool fail_write; bool fail_commit; bool corrupt_after_commit; bool short_write;
|
|
bool fail_manifest_reads;
|
|
};
|
|
|
|
void addStyleManifest(FakeStorage& storage, const char* style, const char* pack_id,
|
|
bool mismatch = false) {
|
|
MapPackManifest manifest = {};
|
|
std::strcpy(manifest.pack_id, pack_id); std::strcpy(manifest.name, "Style pack");
|
|
std::strcpy(manifest.attribution, mismatch ? "wrong attribution" : attributionFor(style));
|
|
std::strcpy(manifest.source, sourceFor(style)); std::strcpy(manifest.license, licenseFor(style));
|
|
manifest.min_zoom = 2U; manifest.max_zoom = 2U; manifest.tile_count = 1U;
|
|
const RowSpan span = {2U, 1U, 1U, 1U};
|
|
std::uint8_t bytes[MapPackManifest::MAX_SERIALIZED_SIZE]; std::size_t written = 0U;
|
|
CHECK(MapPackManifest::serializeSparse(manifest, &span, 1U, bytes, sizeof(bytes), written) ==
|
|
Pyxis::ManifestResult::OK);
|
|
char path[MapTilePack::PATH_CAPACITY];
|
|
CHECK(MapTilePack::manifestPath(pack_id, path, sizeof(path)) ==
|
|
Hardware::TDeck::MapTilePackResult::OK);
|
|
storage.add(path, bytes, written);
|
|
}
|
|
|
|
void addStyle(FakeStorage& storage, const char* style, std::uint32_t generation = 1U) {
|
|
std::uint8_t record[ActiveMapSetCodec::MAX_SERIALIZED_SIZE];
|
|
const std::size_t length = makeRecord(record, style, generation, style);
|
|
char path[80]; std::strcpy(path, "/pyxis-map/map-sets/"); std::strcat(path, style); std::strcat(path, ".pmas");
|
|
storage.add(path, record, length);
|
|
addStyleManifest(storage, style, style);
|
|
}
|
|
void addActive(FakeStorage& storage, unsigned slot, const char* style, std::uint32_t generation) {
|
|
std::uint8_t record[ActiveMapSetCodec::MAX_SERIALIZED_SIZE];
|
|
const std::size_t length = makeRecord(record, style, generation);
|
|
storage.add(slot == 0U ? MapStyleCatalog::ACTIVE_SLOT_0_PATH : MapStyleCatalog::ACTIVE_SLOT_1_PATH,
|
|
record, length);
|
|
}
|
|
|
|
void testCodecAcceptsCanonicalAndResequences() {
|
|
beginTest(); std::uint8_t input[ActiveMapSetCodec::MAX_SERIALIZED_SIZE];
|
|
std::uint8_t output[ActiveMapSetCodec::MAX_SERIALIZED_SIZE];
|
|
const std::size_t length = makeRecord(input, "osm-bright", 7U, "overview", "detail");
|
|
ActiveMapSetView view = {};
|
|
CHECK(ActiveMapSetCodec::decode(input, length, view));
|
|
CHECK(view.generation == 7U); CHECK(std::strcmp(view.map_set_id, "osm-bright") == 0);
|
|
CHECK(view.pack_count == 2U); CHECK(view.total_span_count == 2U);
|
|
std::size_t written = 0U;
|
|
CHECK(ActiveMapSetCodec::resequence(input, length, 8U, output, sizeof(output), written));
|
|
CHECK(written == length); CHECK(getU32(output + 8U) == 8U);
|
|
CHECK(ActiveMapSetCodec::decode(output, written, view));
|
|
}
|
|
void testCodecRejectsMalformedCanonicalFields() {
|
|
beginTest(); std::uint8_t record[ActiveMapSetCodec::MAX_SERIALIZED_SIZE];
|
|
std::size_t length = makeRecord(record, "osm-bright", 1U, "same", "same");
|
|
ActiveMapSetView view = {}; CHECK(!ActiveMapSetCodec::decode(record, length, view));
|
|
length = makeRecord(record, "osm-bright", 1U);
|
|
record[12U] = 10U; record[13U] = 'O'; refreshCrc(record, length);
|
|
CHECK(!ActiveMapSetCodec::decode(record, length, view));
|
|
length = makeRecord(record, "osm-bright", 1U);
|
|
record[length - 5U] = 2U; refreshCrc(record, length);
|
|
CHECK(!ActiveMapSetCodec::decode(record, length, view));
|
|
CHECK(!ActiveMapSetCodec::decode(record, ActiveMapSetCodec::MAX_SERIALIZED_SIZE + 1U, view));
|
|
}
|
|
void testDiscoveryUsesOnlyAllowlistedExactPaths() {
|
|
beginTest(); FakeStorage storage; addStyle(storage, "osm-bright"); addStyle(storage, "positron");
|
|
std::uint8_t rogue[ActiveMapSetCodec::MAX_SERIALIZED_SIZE];
|
|
storage.add("/pyxis-map/map-sets/rogue.pmas", rogue, makeRecord(rogue, "rogue", 1U));
|
|
MapStyleCatalog catalog(storage); CHECK(catalog.discover() == MapStyleCatalogResult::OK);
|
|
CHECK(catalog.count() == 2U); CHECK(std::strcmp(catalog.style(0U)->id, "osm-bright") == 0);
|
|
CHECK(std::strcmp(catalog.style(1U)->id, "positron") == 0); CHECK(storage.list_calls == 0U);
|
|
}
|
|
void testDiscoveryRejectsMismatchedAndMalformedStyleRecords() {
|
|
beginTest(); FakeStorage mismatch; addStyle(mismatch, "dark-matter");
|
|
File* file = mismatch.find("/pyxis-map/map-sets/dark-matter.pmas"); CHECK(file != NULL);
|
|
file->bytes[13U] = 'x'; refreshCrc(file->bytes, file->size);
|
|
MapStyleCatalog first(mismatch); CHECK(first.discover() == MapStyleCatalogResult::INVALID_STYLE_RECORD);
|
|
|
|
FakeStorage oversized; std::uint8_t bytes[ActiveMapSetCodec::MAX_SERIALIZED_SIZE + 1U] = {};
|
|
oversized.add("/pyxis-map/map-sets/toner.pmas", bytes, sizeof(bytes));
|
|
MapStyleCatalog second(oversized); CHECK(second.discover() == MapStyleCatalogResult::INVALID_STYLE_RECORD);
|
|
}
|
|
void testMissingCurrentRecordIsSynthesizedWithoutWrite() {
|
|
beginTest(); FakeStorage storage; addActive(storage, 0U, "toner", 9U);
|
|
MapStyleCatalog catalog(storage); CHECK(catalog.discover() == MapStyleCatalogResult::OK);
|
|
CHECK(catalog.count() == 1U); const MapStyleSummary* style = catalog.style(0U); CHECK(style != NULL);
|
|
CHECK(std::strcmp(style->id, "toner") == 0); CHECK(style->active); CHECK(style->synthesized);
|
|
CHECK(storage.find("/pyxis-map/map-sets/toner.pmas") == NULL); CHECK(storage.list_calls == 0U);
|
|
}
|
|
void testStaleAndUnknownActivationAreRejectedWithoutWrite() {
|
|
beginTest(); FakeStorage storage; addStyle(storage, "osm-bright"); addStyle(storage, "dark-matter");
|
|
addActive(storage, 0U, "osm-bright", 4U); MapStyleCatalog catalog(storage);
|
|
CHECK(catalog.discover() == MapStyleCatalogResult::OK); const std::uint32_t generation = catalog.generation();
|
|
CHECK(catalog.activate(generation - 1U, "dark-matter") == MapStyleCatalogResult::STALE_CATALOG);
|
|
CHECK(catalog.activate(generation, "rogue") == MapStyleCatalogResult::UNKNOWN_STYLE);
|
|
CHECK(storage.find(MapStyleCatalog::ACTIVE_SLOT_1_PATH) == NULL);
|
|
}
|
|
void testCancellationAbortsBeforePublishingActiveSlot() {
|
|
beginTest(); FakeStorage storage; addStyle(storage, "osm-bright");
|
|
addStyle(storage, "dark-matter"); addActive(storage, 0U, "osm-bright", 4U);
|
|
MapStyleCatalog catalog(storage); CHECK(catalog.discover() == MapStyleCatalogResult::OK);
|
|
CHECK(catalog.activate(catalog.generation(), "dark-matter", &rejectCommit,
|
|
NULL) == MapStyleCatalogResult::CANCELLED);
|
|
CHECK(storage.abort_calls == 1U);
|
|
File* target = storage.find(MapStyleCatalog::ACTIVE_SLOT_1_PATH);
|
|
CHECK(target != NULL && target->size == 0U);
|
|
File* active = storage.find(MapStyleCatalog::ACTIVE_SLOT_0_PATH);
|
|
ActiveMapSetView view = {};
|
|
CHECK(active != NULL && ActiveMapSetCodec::decode(active->bytes, active->size, view));
|
|
CHECK(std::strcmp(view.map_set_id, "osm-bright") == 0);
|
|
}
|
|
void testActivationTargetsMissingThenOlderSlotAndVerifiesBytes() {
|
|
beginTest(); FakeStorage storage; addStyle(storage, "osm-bright"); addStyle(storage, "dark-matter");
|
|
addActive(storage, 0U, "osm-bright", 4U); MapStyleCatalog catalog(storage);
|
|
CHECK(catalog.discover() == MapStyleCatalogResult::OK); std::uint32_t generation = catalog.generation();
|
|
CHECK(catalog.activate(generation, "dark-matter") == MapStyleCatalogResult::OK);
|
|
File* slot1 = storage.find(MapStyleCatalog::ACTIVE_SLOT_1_PATH); CHECK(slot1 != NULL);
|
|
ActiveMapSetView view = {}; CHECK(ActiveMapSetCodec::decode(slot1->bytes, slot1->size, view));
|
|
CHECK(view.generation == 5U); CHECK(std::strcmp(view.map_set_id, "dark-matter") == 0);
|
|
generation = catalog.generation();
|
|
CHECK(catalog.activate(generation, "osm-bright") == MapStyleCatalogResult::OK);
|
|
File* slot0 = storage.find(MapStyleCatalog::ACTIVE_SLOT_0_PATH); CHECK(slot0 != NULL);
|
|
CHECK(ActiveMapSetCodec::decode(slot0->bytes, slot0->size, view)); CHECK(view.generation == 6U);
|
|
}
|
|
void testActivationSemanticallyValidatesBeforeAnySlotWrite() {
|
|
beginTest();
|
|
for (std::size_t case_index = 0U; case_index < 4U; ++case_index) {
|
|
FakeStorage storage; addStyle(storage, "dark-matter"); addActive(storage, 0U, "osm-bright", 3U);
|
|
MapStyleCatalog catalog(storage); CHECK(catalog.discover() == MapStyleCatalogResult::OK);
|
|
File* prior = storage.find(MapStyleCatalog::ACTIVE_SLOT_0_PATH); CHECK(prior != NULL);
|
|
std::uint8_t prior_bytes[ActiveMapSetCodec::MAX_SERIALIZED_SIZE];
|
|
const std::size_t prior_size = prior->size; std::memcpy(prior_bytes, prior->bytes, prior_size);
|
|
if (case_index == 0U) {
|
|
storage.erase("/pyxis-map/packs/dark-matter/manifest.pmp");
|
|
} else if (case_index == 1U) {
|
|
File* manifest = storage.find("/pyxis-map/packs/dark-matter/manifest.pmp");
|
|
CHECK(manifest != NULL); manifest->bytes[0] ^= 1U;
|
|
} else if (case_index == 2U) {
|
|
addStyleManifest(storage, "dark-matter", "dark-matter", true);
|
|
} else {
|
|
storage.fail_manifest_reads = true;
|
|
}
|
|
const MapStyleCatalogResult expected = case_index == 3U
|
|
? MapStyleCatalogResult::ACTIVE_IO_INDETERMINATE
|
|
: MapStyleCatalogResult::INVALID_STYLE_RECORD;
|
|
CHECK(catalog.activate(catalog.generation(), "dark-matter") == expected);
|
|
CHECK(storage.begin_write_calls == 0U);
|
|
prior = storage.find(MapStyleCatalog::ACTIVE_SLOT_0_PATH);
|
|
CHECK(prior != NULL && prior->size == prior_size);
|
|
CHECK(std::memcmp(prior->bytes, prior_bytes, prior_size) == 0);
|
|
CHECK(storage.find(MapStyleCatalog::ACTIVE_SLOT_1_PATH) == NULL);
|
|
}
|
|
}
|
|
void testActivationRejectsIndeterminateConflictAndExhaustion() {
|
|
beginTest(); FakeStorage io; addStyle(io, "dark-matter"); io.fail_active_slot_reads = true;
|
|
MapStyleCatalog first(io); CHECK(first.discover() == MapStyleCatalogResult::ACTIVE_IO_INDETERMINATE);
|
|
|
|
FakeStorage conflict; addStyle(conflict, "dark-matter"); addActive(conflict, 0U, "osm-bright", 7U);
|
|
addActive(conflict, 1U, "positron", 7U); MapStyleCatalog second(conflict);
|
|
CHECK(second.discover() == MapStyleCatalogResult::ACTIVE_CONFLICT);
|
|
|
|
FakeStorage exhausted; addStyle(exhausted, "dark-matter"); addActive(exhausted, 0U, "osm-bright", UINT32_MAX);
|
|
MapStyleCatalog third(exhausted); CHECK(third.discover() == MapStyleCatalogResult::OK);
|
|
CHECK(third.activate(third.generation(), "dark-matter") == MapStyleCatalogResult::GENERATION_EXHAUSTED);
|
|
}
|
|
void testWriteAndReadbackFailuresPreservePriorValidSlot() {
|
|
beginTest(); FakeStorage torn; addStyle(torn, "dark-matter"); addActive(torn, 0U, "osm-bright", 3U);
|
|
MapStyleCatalog first(torn); CHECK(first.discover() == MapStyleCatalogResult::OK); torn.fail_commit = true;
|
|
CHECK(first.activate(first.generation(), "dark-matter") == MapStyleCatalogResult::WRITE_FAILED);
|
|
File* prior = torn.find(MapStyleCatalog::ACTIVE_SLOT_0_PATH); ActiveMapSetView view = {};
|
|
CHECK(prior != NULL && ActiveMapSetCodec::decode(prior->bytes, prior->size, view));
|
|
CHECK(view.generation == 3U && std::strcmp(view.map_set_id, "osm-bright") == 0);
|
|
CHECK(torn.abort_calls == 1U);
|
|
|
|
FakeStorage corrupt; addStyle(corrupt, "dark-matter"); addActive(corrupt, 0U, "osm-bright", 3U);
|
|
MapStyleCatalog second(corrupt); CHECK(second.discover() == MapStyleCatalogResult::OK);
|
|
corrupt.corrupt_after_commit = true;
|
|
CHECK(second.activate(second.generation(), "dark-matter") == MapStyleCatalogResult::READBACK_MISMATCH);
|
|
prior = corrupt.find(MapStyleCatalog::ACTIVE_SLOT_0_PATH);
|
|
CHECK(prior != NULL && ActiveMapSetCodec::decode(prior->bytes, prior->size, view));
|
|
CHECK(view.generation == 3U);
|
|
}
|
|
void testCatalogIsFixedBoundedAndCopiesSummaries() {
|
|
beginTest(); FakeStorage storage; addStyle(storage, "osm-bright"); addStyle(storage, "dark-matter");
|
|
addStyle(storage, "positron"); addStyle(storage, "toner"); MapStyleCatalog catalog(storage);
|
|
CHECK(catalog.discover() == MapStyleCatalogResult::OK); CHECK(catalog.count() == MapStyleCatalog::MAX_STYLES);
|
|
for (std::size_t index = 0U; index < catalog.count(); ++index) {
|
|
CHECK(catalog.style(index) != NULL); CHECK(catalog.style(index)->pack_count == 1U);
|
|
CHECK(std::strcmp(catalog.style(index)->attribution,
|
|
attributionFor(catalog.style(index)->id)) == 0);
|
|
}
|
|
CHECK(catalog.style(MapStyleCatalog::MAX_STYLES) == NULL); CHECK(storage.list_calls == 0U);
|
|
}
|
|
} // namespace
|
|
|
|
int main() {
|
|
testCodecAcceptsCanonicalAndResequences();
|
|
testCodecRejectsMalformedCanonicalFields();
|
|
testDiscoveryUsesOnlyAllowlistedExactPaths();
|
|
testDiscoveryRejectsMismatchedAndMalformedStyleRecords();
|
|
testMissingCurrentRecordIsSynthesizedWithoutWrite();
|
|
testStaleAndUnknownActivationAreRejectedWithoutWrite();
|
|
testCancellationAbortsBeforePublishingActiveSlot();
|
|
testActivationTargetsMissingThenOlderSlotAndVerifiesBytes();
|
|
testActivationSemanticallyValidatesBeforeAnySlotWrite();
|
|
testActivationRejectsIndeterminateConflictAndExhaustion();
|
|
testWriteAndReadbackFailuresPreservePriorValidSlot();
|
|
testCatalogIsFixedBoundedAndCopiesSummaries();
|
|
std::cout << "map style catalog: " << tests_run << " tests passed\n";
|
|
return 0;
|
|
}
|