fix: retire legacy map tile fallback

This commit is contained in:
torlando-tech
2026-08-08 05:15:28 +00:00
parent b9481ef6b2
commit ca59aee704
7 changed files with 35 additions and 319 deletions
+5 -83
View File
@@ -2,7 +2,6 @@
// SPDX-License-Identifier: MIT
#include "MapScreen.h"
#include "MapTileLookupPolicy.h"
#include "MapTileStreamReader.h"
#ifdef ARDUINO
@@ -26,8 +25,6 @@ namespace LXMF {
namespace {
constexpr std::size_t TILE_PIXEL_COUNT = 256U * 256U;
constexpr std::uint32_t STORE_BYTE_QUOTA = 64U * 1024U * 1024U;
constexpr std::uint16_t STORE_ENTRY_CAPACITY = 128U;
class PackReadStream final : public Pyxis::MapTileReadStream {
public:
@@ -61,35 +58,6 @@ private:
Hardware::TDeck::MapTilePack& pack_;
};
class LiveReadStream final : public Pyxis::MapTileReadStream {
public:
explicit LiveReadStream(Hardware::TDeck::MapTileStore& store) : store_(store) {}
Pyxis::MapTileStreamResult begin(const Hardware::TDeck::TileKey& key,
std::uint32_t& size) override {
const Hardware::TDeck::TileStoreResult result = store_.beginGet(key, size);
if (result == Hardware::TDeck::TileStoreResult::OK) return Pyxis::MapTileStreamResult::OK;
if (result == Hardware::TDeck::TileStoreResult::MISS) return Pyxis::MapTileStreamResult::MISS;
if (result == Hardware::TDeck::TileStoreResult::STORAGE_UNAVAILABLE ||
result == Hardware::TDeck::TileStoreResult::NOT_INITIALIZED) {
return Pyxis::MapTileStreamResult::STORAGE_UNAVAILABLE;
}
return Pyxis::MapTileStreamResult::IO_ERROR;
}
Pyxis::MapTileStreamResult read(std::uint8_t* output, std::size_t capacity,
std::size_t& count) override {
const Hardware::TDeck::TileStoreResult result =
store_.readGetChunk(output, capacity, count);
if (result == Hardware::TDeck::TileStoreResult::OK) return Pyxis::MapTileStreamResult::OK;
if (result == Hardware::TDeck::TileStoreResult::STORAGE_UNAVAILABLE) {
return Pyxis::MapTileStreamResult::STORAGE_UNAVAILABLE;
}
return Pyxis::MapTileStreamResult::IO_ERROR;
}
void end() override { store_.endGet(); }
private:
Hardware::TDeck::MapTileStore& store_;
};
class AtomicStopSource final : public Pyxis::MapTileStopSource {
public:
explicit AtomicStopSource(const std::atomic<bool>& stop) : stop_(stop) {}
@@ -117,10 +85,6 @@ lv_obj_t* createToolbarButton(lv_obj_t* parent, const char* text,
} // namespace
using Hardware::TDeck::MapTileStore;
static_assert(MapTileStore::HARD_MAX_ENTRIES == 128,
"map cache index contract changed; revisit bounded RAM budget");
static_assert(sizeof(lv_color_t) == 2U,
"offline tile buffers assume LVGL RGB565 true color");
@@ -131,14 +95,11 @@ MapScreen::MapScreen(lv_obj_t* parent)
recenter_button_(nullptr), pan_buttons_{}, tile_images_{},
tile_descriptors_{}, tile_pixels_{}, decoded_tile_cache_(TILE_PIXEL_COUNT),
decoded_cache_pixels_{}, approximation_halos_{}, markers_{}, marker_labels_{},
presenter_(), storage_(),
store_config_{STORE_ENTRY_CAPACITY, STORE_BYTE_QUOTA,
MAX_COMPRESSED_TILE_BYTES},
store_(storage_, store_config_), pack_(storage_), pack_attribution_{},
presenter_(), storage_(), pack_(storage_), pack_attribution_{},
screen_visible_(false), pack_refresh_epoch_(0U),
compressed_staging_(nullptr),
state_mutex_(nullptr), worker_task_(nullptr),
worker_exited_(true), worker_started_(false), store_initialized_(false),
worker_exited_(true), worker_started_(false),
requests_released_(false),
has_location_fix_(false), center_initialized_(false), current_location_{}, dragging_(false),
last_drag_point_{0, 0}, back_callback_() {
@@ -372,8 +333,6 @@ void MapScreen::publishPackAttribution() {
}
void MapScreen::workerLoop() {
Hardware::TDeck::TileStoreResult initialized = store_.initialize();
store_initialized_ = initialized == Hardware::TDeck::TileStoreResult::OK;
std::uint32_t handled_pack_refresh_epoch =
pack_refresh_epoch_.load(std::memory_order_acquire);
(void)pack_.initialize();
@@ -446,37 +405,16 @@ Pyxis::MapTileLoadResult MapScreen::readTile(
return Pyxis::MapTileLoadResult::READY;
}
struct LocalReadContext {
MapScreen* screen;
const Pyxis::MapTileRequest* request;
} context = {this, &request};
return Pyxis::MapTileLookupPolicy::readLocal(
&context,
[](void* opaque, Pyxis::MapTileLookupPolicy::LocalSource source) ->
Pyxis::MapTileLoadResult {
LocalReadContext* local = static_cast<LocalReadContext*>(opaque);
const CompressedTileSource mapped =
source == Pyxis::MapTileLookupPolicy::LocalSource::PACK
? CompressedTileSource::PACK
: CompressedTileSource::LIVE_STORE;
return local->screen->readCompressedTile(*local->request, mapped);
});
return readCompressedTile(request);
}
Pyxis::MapTileLoadResult MapScreen::readCompressedTile(
const Pyxis::MapTileRequest& request, CompressedTileSource source) {
if (source == CompressedTileSource::LIVE_STORE && !store_initialized_) {
return Pyxis::MapTileLoadResult::STORAGE_UNAVAILABLE;
}
const Pyxis::MapTileRequest& request) {
PackReadStream pack_stream(pack_);
LiveReadStream live_stream(store_);
Pyxis::MapTileReadStream& stream = source == CompressedTileSource::PACK
? static_cast<Pyxis::MapTileReadStream&>(pack_stream)
: static_cast<Pyxis::MapTileReadStream&>(live_stream);
AtomicStopSource stop(stop_requested_);
std::size_t total = 0U;
const Pyxis::MapTileStreamResult read = Pyxis::MapTileStreamReader::readExact(
stream, stop, request.key, compressed_staging_, MAX_COMPRESSED_TILE_BYTES,
pack_stream, stop, request.key, compressed_staging_, MAX_COMPRESSED_TILE_BYTES,
READ_CHUNK_BYTES, total);
if (read == Pyxis::MapTileStreamResult::MISS) {
return Pyxis::MapTileLoadResult::MISS;
@@ -503,14 +441,6 @@ Pyxis::MapTileLoadResult MapScreen::readCompressedTile(
&width, &height, &decode_state, compressed_staging_, total);
if (decode_error != 0U || width != 256U || height != 256U) {
lodepng_state_cleanup(&decode_state);
if (source == CompressedTileSource::LIVE_STORE) {
const Hardware::TDeck::TileStoreResult removed =
store_.removeTile(request.key);
return (removed == Hardware::TDeck::TileStoreResult::OK ||
removed == Hardware::TDeck::TileStoreResult::MISS)
? Pyxis::MapTileLoadResult::INVALID_PNG
: Pyxis::MapTileLoadResult::IO_ERROR;
}
return Pyxis::MapTileLoadResult::INVALID_PNG;
}
decode_state.info_raw.colortype = LCT_RGB;
@@ -520,14 +450,6 @@ Pyxis::MapTileLoadResult MapScreen::readCompressedTile(
lodepng_state_cleanup(&decode_state);
if (decode_error != 0U || rgb == nullptr || width != 256U || height != 256U) {
if (rgb) lv_mem_free(rgb);
if (source == CompressedTileSource::LIVE_STORE) {
const Hardware::TDeck::TileStoreResult removed =
store_.removeTile(request.key);
return (removed == Hardware::TDeck::TileStoreResult::OK ||
removed == Hardware::TDeck::TileStoreResult::MISS)
? Pyxis::MapTileLoadResult::INVALID_PNG
: Pyxis::MapTileLoadResult::IO_ERROR;
}
return Pyxis::MapTileLoadResult::INVALID_PNG;
}
if (request.slot_index >= TILE_COUNT || !tile_pixels_[request.slot_index]) {
+1 -10
View File
@@ -17,7 +17,6 @@
#include <freertos/semphr.h>
#include <freertos/task.h>
#include "Hardware/TDeck/MapTileStore.h"
#include "Hardware/TDeck/MapTileStoreSD.h"
#include "Hardware/TDeck/MapTilePack.h"
@@ -77,8 +76,6 @@ private:
Pyxis::MapScreenPresenter presenter_;
Hardware::TDeck::MapTileStoreSD storage_;
Hardware::TDeck::TileStoreConfig store_config_;
Hardware::TDeck::MapTileStore store_;
Hardware::TDeck::MapTilePack pack_;
char pack_attribution_[Pyxis::MapPackManifest::ATTRIBUTION_CAPACITY];
std::atomic<bool> screen_visible_;
@@ -89,7 +86,6 @@ private:
std::atomic<bool> stop_requested_;
std::atomic<bool> worker_exited_;
bool worker_started_;
bool store_initialized_;
bool requests_released_;
bool has_location_fix_;
bool center_initialized_;
@@ -99,16 +95,11 @@ private:
BackCallback back_callback_;
static void workerEntry(void* context);
enum class CompressedTileSource : std::uint8_t {
PACK = 0,
LIVE_STORE
};
void workerLoop();
void publishPackAttribution();
Pyxis::MapTileLoadResult loadTile(const Pyxis::MapTileRequest& request);
Pyxis::MapTileLoadResult readTile(const Pyxis::MapTileRequest& request);
Pyxis::MapTileLoadResult readCompressedTile(
const Pyxis::MapTileRequest& request, CompressedTileSource source);
Pyxis::MapTileLoadResult readCompressedTile(const Pyxis::MapTileRequest& request);
bool startWorker();
void stopWorker();
bool lockState(TickType_t ticks = portMAX_DELAY);
@@ -1,52 +0,0 @@
// Copyright (c) 2026 Pyxis contributors
// SPDX-License-Identifier: MIT
#ifndef UI_LXMF_MAP_TILE_LOOKUP_POLICY_H
#define UI_LXMF_MAP_TILE_LOOKUP_POLICY_H
#include <cstddef>
#include <cstdint>
#include "MapScreenPresenter.h"
namespace Pyxis {
/** Portable policy for pack -> live cache -> optional network orchestration. */
class MapTileLookupPolicy {
public:
enum class LocalSource : std::uint8_t { PACK = 0, LIVE_STORE };
typedef MapTileLoadResult (*ReadLocalSource)(void*, LocalSource);
static MapTileLoadResult readLocal(void* context, ReadLocalSource read) {
if (read == NULL) return MapTileLoadResult::IO_ERROR;
const MapTileLoadResult pack = read(context, LocalSource::PACK);
if (pack == MapTileLoadResult::READY) return pack;
const MapTileLoadResult live = read(context, LocalSource::LIVE_STORE);
return resolveLocal(pack, live);
}
static MapTileLoadResult resolveLocal(MapTileLoadResult pack,
MapTileLoadResult live) {
if (pack == MapTileLoadResult::READY) return pack;
if (live != MapTileLoadResult::MISS) return live;
if (pack == MapTileLoadResult::IO_ERROR ||
pack == MapTileLoadResult::STORAGE_UNAVAILABLE) return pack;
return MapTileLoadResult::MISS;
}
static bool shouldStartOnline(MapTileLoadResult local,
bool enabled,
bool visible,
bool decode_failed,
std::uint32_t request_epoch,
std::uint32_t current_epoch) {
return enabled && visible && !decode_failed &&
request_epoch == current_epoch &&
(local == MapTileLoadResult::MISS ||
local == MapTileLoadResult::INVALID_PNG);
}
};
} // namespace Pyxis
#endif
+22 -32
View File
@@ -41,7 +41,9 @@ def test_fixed_pool_and_cache_contracts():
assert re.search(r"#define\s+LV_IMG_CACHE_DEF_SIZE\s+0\b", lv_conf)
assert "std::vector" not in presenter + screen_h + screen_cpp
assert "std::map" not in presenter + screen_h + screen_cpp
assert "static_assert(MapTileStore::HARD_MAX_ENTRIES == 128" in screen_cpp
assert '#include "Hardware/TDeck/MapTileStore.h"' not in screen_h
assert "Hardware::TDeck::MapTileStore store_;" not in screen_h
assert "TileStoreConfig" not in screen_h + screen_cpp
assert "pack_.metadata().attribution" in screen_cpp
assert '"No active map pack"' in screen_cpp
assert "presenter_.visibleTileStatus" in screen_cpp
@@ -59,7 +61,7 @@ def test_worker_predecodes_and_render_path_has_no_io():
assert "lodepng_decode(" in source
assert "lodepng_inspect" in source
assert "max_output_size" in source
assert "store_.removeTile(request.key)" in source
assert "store_.removeTile(request.key)" not in source
assert "decode_failed_keys_" not in header
assert "decodeFailedFor" not in source
assert "markDecodeFailed" not in source
@@ -75,7 +77,7 @@ def test_worker_predecodes_and_render_path_has_no_io():
assert "MAX_COMPLETIONS_PER_TICK = 1" in text(UI / "MapScreen.h")
def test_selected_pack_is_worker_owned_read_only_and_precedes_live_cache():
def test_selected_pack_is_the_only_sd_tile_source():
source = text(UI / "MapScreen.cpp")
header = text(UI / "MapScreen.h")
assert '#include "Hardware/TDeck/MapTilePack.h"' in header
@@ -85,51 +87,39 @@ def test_selected_pack_is_worker_owned_read_only_and_precedes_live_cache():
assert "pack_(storage_)" in constructor
worker = function_body(source, "void MapScreen::workerLoop()")
assert worker.index("store_.initialize()") < worker.index("pack_.initialize()")
assert "pack_.initialize()" in worker
assert "store_.initialize()" not in worker
read_tile = function_body(source, "Pyxis::MapTileLoadResult MapScreen::readTile(")
assert read_tile.index("decoded_tile_cache_.get") < read_tile.index("PACK")
assert read_tile.index("PACK") < read_tile.index("LIVE_STORE")
assert (read_tile.index("decoded_tile_cache_.get") <
read_tile.index("readCompressedTile(request)"))
assert "LIVE_STORE" not in read_tile
pack_read = function_body(
source, "Pyxis::MapTileLoadResult MapScreen::readCompressedTile(")
assert ("source == CompressedTileSource::LIVE_STORE && !store_initialized_"
in pack_read)
assert "MapTileStreamReader::readExact" in pack_read
assert "PackReadStream pack_stream(pack_)" in pack_read
assert "LiveReadStream live_stream(store_)" in pack_read
assert "LiveReadStream" not in source
pack_adapter = source[source.index("class PackReadStream"):
source.index("class LiveReadStream")]
live_adapter = source[source.index("class LiveReadStream"):
source.index("class AtomicStopSource")]
assert "pack_.beginGet" in pack_adapter
assert "pack_.readGetChunk" in pack_adapter
assert "pack_.endGet" in pack_adapter
assert "remove" not in pack_adapter
assert "store_.beginGet" in live_adapter
assert "store_.readGetChunk" in live_adapter
assert "store_.endGet" in live_adapter
remove_token = "store_.removeTile(request.key)"
remove_offsets = []
cursor = 0
while True:
offset = pack_read.find(remove_token, cursor)
if offset < 0:
break
remove_offsets.append(offset)
cursor = offset + len(remove_token)
assert len(remove_offsets) == 2
for offset in remove_offsets:
remove_block = pack_read[max(0, offset - 220):offset + 80]
assert "source == CompressedTileSource::LIVE_STORE" in remove_block
assert '#include "Hardware/TDeck/MapTileStore.h"' not in header
assert "Hardware::TDeck::MapTileStore store_;" not in header
assert "MapTileLookupPolicy" not in source + header
assert not (UI / "MapTileLookupPolicy.h").exists()
assert not (UI / "MapTileLookupPolicy.cpp").exists()
assert "store_initialized_" not in source + header
assert "store_config_" not in source + header
assert "LIVE_STORE" not in source + header
assert "/pyxis-map/tiles" not in source + header
# Covered-missing, uncovered, and corrupt immutable-pack tiles all continue
# to the mutable legacy cache, but production never starts online acquisition.
# Covered-missing, uncovered, and corrupt immutable-pack tiles remain typed
# pack results. Production never falls through to an untyped style-less cache.
assert "MapTilePackResult::UNCOVERED" in pack_adapter
assert "MapTilePackResult::TILE_MISSING" in pack_adapter
assert "MapTileLookupPolicy::readLocal" in read_tile
assert "static MapTileLoadResult resolveLocal" in text(UI / "MapTileLookupPolicy.h")
assert "MapTileLookupPolicy::shouldStartOnline" not in source
load_tile = function_body(source, "Pyxis::MapTileLoadResult MapScreen::loadTile(")
assert "return readTile(request);" in load_tile
@@ -78,7 +78,7 @@ def test_dormant_downloader_chain_remains_verified_but_is_not_map_screen_wired()
for value in fingerprints)
def test_production_map_screen_is_strictly_sd_pack_first_without_network_acquisition():
def test_production_map_screen_uses_only_sd_packs_without_network_acquisition():
screen = MAP_SCREEN.read_text()
header = (ROOT / "lib/tdeck_ui/UI/LXMF/MapScreen.h").read_text()
settings = SETTINGS.read_text()
@@ -92,7 +92,10 @@ def test_production_map_screen_is_strictly_sd_pack_first_without_network_acquisi
assert forbidden not in screen
assert forbidden not in header
assert "return readTile(request);" in screen
assert "MapTileLookupPolicy::readLocal" in screen
assert "MapTileLookupPolicy" not in screen + header
assert "LiveReadStream" not in screen
assert "LIVE_STORE" not in screen + header
assert "store_.initialize()" not in screen
assert "MapTilePack pack_" in header
assert "pack_.initialize()" in screen
assert "pack_refresh_epoch_.fetch_add" in screen
@@ -126,7 +129,8 @@ def test_recent_decoded_tiles_use_a_fixed_psram_lru_before_sd_decode():
assert "decoded_tile_cache_.get" in screen
read_tile = screen[screen.index("Pyxis::MapTileLoadResult MapScreen::readTile("):
screen.index("Pyxis::MapTileLoadResult MapScreen::readCompressedTile(")]
assert read_tile.index("decoded_tile_cache_.get") < read_tile.index("MapTileLookupPolicy::readLocal")
assert (read_tile.index("decoded_tile_cache_.get") <
read_tile.index("readCompressedTile(request)"))
assert "decoded_tile_cache_.put" in screen
assert "MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT" in screen
assert "decoded_cache_pixels_[Pyxis::DecodedTileCache::CAPACITY]" in header
@@ -1,106 +0,0 @@
#include "UI/LXMF/MapTileLookupPolicy.h"
#include <cstdlib>
#include <iostream>
using Pyxis::MapTileLoadResult;
using Pyxis::MapTileLookupPolicy;
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; }
void testLocalPrecedenceTable() {
beginTest();
CHECK(MapTileLookupPolicy::resolveLocal(MapTileLoadResult::READY,
MapTileLoadResult::IO_ERROR) ==
MapTileLoadResult::READY);
const MapTileLoadResult fallthrough[] = {
MapTileLoadResult::MISS, MapTileLoadResult::INVALID_PNG,
MapTileLoadResult::TOO_LARGE
};
for (std::size_t index = 0U; index < sizeof(fallthrough) / sizeof(fallthrough[0]); ++index) {
CHECK(MapTileLookupPolicy::resolveLocal(fallthrough[index],
MapTileLoadResult::READY) ==
MapTileLoadResult::READY);
CHECK(MapTileLookupPolicy::resolveLocal(fallthrough[index],
MapTileLoadResult::MISS) ==
MapTileLoadResult::MISS);
}
CHECK(MapTileLookupPolicy::resolveLocal(MapTileLoadResult::IO_ERROR,
MapTileLoadResult::MISS) ==
MapTileLoadResult::IO_ERROR);
CHECK(MapTileLookupPolicy::resolveLocal(MapTileLoadResult::STORAGE_UNAVAILABLE,
MapTileLoadResult::MISS) ==
MapTileLoadResult::STORAGE_UNAVAILABLE);
CHECK(MapTileLookupPolicy::resolveLocal(MapTileLoadResult::IO_ERROR,
MapTileLoadResult::READY) ==
MapTileLoadResult::READY);
CHECK(MapTileLookupPolicy::resolveLocal(MapTileLoadResult::MISS,
MapTileLoadResult::INVALID_PNG) ==
MapTileLoadResult::INVALID_PNG);
}
void testOnlineGate() {
beginTest();
CHECK(MapTileLookupPolicy::shouldStartOnline(MapTileLoadResult::MISS,
true, true, false, 7U, 7U));
CHECK(MapTileLookupPolicy::shouldStartOnline(MapTileLoadResult::INVALID_PNG,
true, true, false, 7U, 7U));
CHECK(!MapTileLookupPolicy::shouldStartOnline(MapTileLoadResult::READY,
true, true, false, 7U, 7U));
CHECK(!MapTileLookupPolicy::shouldStartOnline(MapTileLoadResult::MISS,
false, true, false, 7U, 7U));
CHECK(!MapTileLookupPolicy::shouldStartOnline(MapTileLoadResult::MISS,
true, false, false, 7U, 7U));
CHECK(!MapTileLookupPolicy::shouldStartOnline(MapTileLoadResult::MISS,
true, true, true, 7U, 7U));
CHECK(!MapTileLookupPolicy::shouldStartOnline(MapTileLoadResult::MISS,
true, true, false, 7U, 8U));
CHECK(!MapTileLookupPolicy::shouldStartOnline(MapTileLoadResult::IO_ERROR,
true, true, false, 7U, 7U));
}
struct FakeLocalSources {
MapTileLoadResult pack;
MapTileLoadResult live;
int calls[2];
std::size_t count;
};
MapTileLoadResult readFake(void* opaque, MapTileLookupPolicy::LocalSource source) {
FakeLocalSources* fake = static_cast<FakeLocalSources*>(opaque);
fake->calls[fake->count++] = source == MapTileLookupPolicy::LocalSource::PACK ? 0 : 1;
return source == MapTileLookupPolicy::LocalSource::PACK ? fake->pack : fake->live;
}
void testExecutableSourceOrderAndFallback() {
beginTest();
FakeLocalSources ready = {MapTileLoadResult::READY, MapTileLoadResult::IO_ERROR, {0, 0}, 0U};
CHECK(MapTileLookupPolicy::readLocal(&ready, readFake) == MapTileLoadResult::READY);
CHECK(ready.count == 1U && ready.calls[0] == 0);
const MapTileLoadResult fallthrough[] = {
MapTileLoadResult::MISS, MapTileLoadResult::INVALID_PNG,
MapTileLoadResult::TOO_LARGE, MapTileLoadResult::IO_ERROR
};
for (std::size_t index = 0U; index < sizeof(fallthrough) / sizeof(fallthrough[0]); ++index) {
FakeLocalSources fake = {fallthrough[index], MapTileLoadResult::READY, {0, 0}, 0U};
CHECK(MapTileLookupPolicy::readLocal(&fake, readFake) == MapTileLoadResult::READY);
CHECK(fake.count == 2U && fake.calls[0] == 0 && fake.calls[1] == 1);
}
CHECK(MapTileLookupPolicy::readLocal(NULL, NULL) == MapTileLoadResult::IO_ERROR);
}
} // namespace
int main() {
testLocalPrecedenceTable();
testOnlineGate();
testExecutableSourceOrderAndFallback();
std::cout << "map tile lookup policy: " << tests_run << " tests passed\n";
return 0;
}
@@ -1,33 +0,0 @@
from __future__ import annotations
from pathlib import Path
import os
import subprocess
import pytest
from native_test import find_cxx
ROOT = Path(__file__).resolve().parents[2]
SOURCE = ROOT / "tests/native/test_map_tile_lookup_policy.cpp"
@pytest.mark.parametrize("sanitize", [False, True], ids=["strict-cxx11", "asan-ubsan"])
def test_map_tile_lookup_policy(tmp_path: Path, sanitize: bool) -> None:
binary = tmp_path / "test_map_tile_lookup_policy"
command = [
find_cxx(), "-std=c++11", "-Wall", "-Wextra", "-Werror", "-pedantic",
"-Wconversion", "-Wsign-conversion", f"-I{ROOT / 'lib/tdeck_ui'}",
str(SOURCE), "-o", str(binary),
]
if sanitize:
command[1:1] = ["-fsanitize=address,undefined", "-fno-omit-frame-pointer"]
compiled = subprocess.run(command, capture_output=True, text=True, timeout=60)
assert compiled.returncode == 0, compiled.stdout + compiled.stderr
env = os.environ.copy()
if sanitize:
env["ASAN_OPTIONS"] = "detect_leaks=1:halt_on_error=1"
env["UBSAN_OPTIONS"] = "halt_on_error=1:print_stacktrace=1"
ran = subprocess.run([str(binary)], capture_output=True, text=True, timeout=60, env=env)
assert ran.returncode == 0, ran.stdout + ran.stderr
assert ran.stdout == "map tile lookup policy: 3 tests passed\n"