From f03e1c59378c2195bdf37bcf2e23478a58307afc Mon Sep 17 00:00:00 2001 From: "torlando-agent[bot]" <281092095+torlando-agent[bot]@users.noreply.github.com> Date: Sat, 8 Aug 2026 18:10:02 +0000 Subject: [PATCH] fix: serialize map style activation lifecycle --- lib/tdeck_ui/UI/LXMF/MapScreen.cpp | 93 +++++++++++--- lib/tdeck_ui/UI/LXMF/MapScreen.h | 7 +- lib/tdeck_ui/UI/LXMF/MapStyleSelector.cpp | 44 ++++++- lib/tdeck_ui/UI/LXMF/MapStyleSelector.h | 18 ++- .../build_scripts/test_map_screen_contract.py | 26 +++- tests/native/test_map_style_selector.cpp | 119 +++++++++++++++++- 6 files changed, 279 insertions(+), 28 deletions(-) diff --git a/lib/tdeck_ui/UI/LXMF/MapScreen.cpp b/lib/tdeck_ui/UI/LXMF/MapScreen.cpp index f6de883e..1a9508b0 100644 --- a/lib/tdeck_ui/UI/LXMF/MapScreen.cpp +++ b/lib/tdeck_ui/UI/LXMF/MapScreen.cpp @@ -108,8 +108,10 @@ MapScreen::MapScreen(lv_obj_t* parent) decoded_cache_pixels_{}, approximation_halos_{}, markers_{}, marker_labels_{}, presenter_(), style_selector_(), storage_(), pack_(storage_), style_catalog_(storage_), style_request_{}, style_request_pending_(false), + style_request_lifecycle_epoch_(0U), pack_attribution_{}, - screen_visible_(false), pack_refresh_epoch_(0U), + screen_visible_(false), style_lifecycle_epoch_(0U), + style_lifecycle_exhausted_(false), pack_refresh_epoch_(0U), compressed_staging_(nullptr), state_mutex_(nullptr), worker_task_(nullptr), worker_exited_(true), worker_started_(false), @@ -351,7 +353,9 @@ void MapScreen::publishPackAttribution() { unlockState(); } -void MapScreen::publishStyleCatalog() { +void MapScreen::publishStyleCatalog(std::uint32_t activation_token, + std::uint32_t request_lifecycle_epoch, + bool activation_failed) { Pyxis::MapStyleSummary summaries[Pyxis::MapStyleSelector::MAX_STYLES] = {}; char active_id[Pyxis::MapPackManifest::PACK_ID_CAPACITY] = {}; const std::size_t count = style_catalog_.count(); @@ -367,8 +371,18 @@ void MapScreen::publishStyleCatalog() { } } if (!lockState(portMAX_DELAY)) return; - (void)style_selector_.setCatalog(style_catalog_.generation(), summaries, count, - active_id[0] == '\0' ? nullptr : active_id); + const bool retain_error = activation_failed && + screen_visible_.load(std::memory_order_acquire) && + !style_lifecycle_exhausted_ && + request_lifecycle_epoch == style_lifecycle_epoch_; + if (activation_token == 0U) { + (void)style_selector_.setCatalog(style_catalog_.generation(), summaries, count, + active_id[0] == '\0' ? nullptr : active_id); + } else { + (void)style_selector_.reconcileActivation( + activation_token, style_catalog_.generation(), summaries, count, + active_id[0] == '\0' ? nullptr : active_id, retain_error); + } unlockState(); } @@ -409,12 +423,26 @@ void MapScreen::workerLoop() { } Pyxis::MapStyleRequest style_request{}; + std::uint32_t style_request_lifecycle_epoch = 0U; bool have_style_request = false; if (lockState(pdMS_TO_TICKS(20))) { - if (style_request_pending_ && screen_visible) { - style_request = style_request_; - style_request_pending_ = false; - have_style_request = true; + if (style_request_pending_) { + const bool style_screen_visible = + screen_visible_.load(std::memory_order_acquire); + if (!style_screen_visible || style_lifecycle_exhausted_ || + style_request_lifecycle_epoch_ != style_lifecycle_epoch_) { + (void)style_selector_.cancelPending(style_request_.token); + style_request_pending_ = false; + style_request_ = Pyxis::MapStyleRequest(); + } else if (style_selector_.activationOwnedBy(style_request_.token)) { + style_request = style_request_; + style_request_lifecycle_epoch = style_request_lifecycle_epoch_; + style_request_pending_ = false; + have_style_request = true; + } else { + style_request_pending_ = false; + style_request_ = Pyxis::MapStyleRequest(); + } } unlockState(); } @@ -440,23 +468,34 @@ void MapScreen::workerLoop() { sizeof(style_completion.style_id) - 1U); style_completion.success = success; if (lockState(portMAX_DELAY)) { - (void)style_selector_.complete(style_completion); + const bool same_visible_lifecycle = + screen_visible_.load(std::memory_order_acquire) && + !style_lifecycle_exhausted_ && + style_request_lifecycle_epoch == style_lifecycle_epoch_; + const bool completed = style_selector_.complete(style_completion); + if (completed && !same_visible_lifecycle && !success) { + (void)style_selector_.clearError(); + } if (success) { presenter_.invalidateTiles(); requests_released_ = false; } unlockState(); } - if (success) { - // activate() already advanced the durable catalog generation and - // updated active flags. Publish that in-memory truth before any - // fallible post-commit SD rediscovery so the next request cannot - // carry a stale generation token. - publishStyleCatalog(); + // Reconcile the reservation owner's catalog on every outcome. A + // durable activate() may have advanced its generation even when the + // subsequent pack reload failed; stale-generation failures can also + // follow a refresh that raced the queued request. Keep the lease until + // both the in-memory truth and any successful rediscovery are published. + publishStyleCatalog(style_request.token, + style_request_lifecycle_epoch, !success); + if (style_catalog_.discover() == Hardware::TDeck::MapStyleCatalogResult::OK) { + publishStyleCatalog(style_request.token, + style_request_lifecycle_epoch, !success); } - if (success && style_catalog_.discover() == - Hardware::TDeck::MapStyleCatalogResult::OK) { - publishStyleCatalog(); + if (lockState(portMAX_DELAY)) { + (void)style_selector_.releaseActivation(style_request.token); + unlockState(); } continue; } @@ -754,7 +793,18 @@ void MapScreen::show() { void MapScreen::hide() { screen_visible_.store(false, std::memory_order_release); if (worker_task_) xTaskNotifyGive(worker_task_); - if (lockState(pdMS_TO_TICKS(100))) { + if (lockState(portMAX_DELAY)) { + if (style_lifecycle_epoch_ == UINT32_MAX) { + style_lifecycle_exhausted_ = true; + } else { + ++style_lifecycle_epoch_; + } + if (style_request_pending_) { + (void)style_selector_.cancelPending(style_request_.token); + style_request_pending_ = false; + style_request_ = Pyxis::MapStyleRequest(); + } + (void)style_selector_.clearError(); presenter_.hide(); unlockState(); } @@ -818,7 +868,10 @@ void MapScreen::onStyle(lv_event_t* event) { MapScreen* screen = fromEvent(event); bool queued = false; if (screen && screen->lockState(pdMS_TO_TICKS(100))) { - if (screen->style_selector_.requestNext(screen->style_request_)) { + if (!screen->style_lifecycle_exhausted_ && + screen->style_selector_.requestNext(screen->style_request_)) { + screen->style_request_lifecycle_epoch_ = + screen->style_lifecycle_epoch_; screen->style_request_pending_ = true; queued = true; } diff --git a/lib/tdeck_ui/UI/LXMF/MapScreen.h b/lib/tdeck_ui/UI/LXMF/MapScreen.h index 61b0c193..de8aecd3 100644 --- a/lib/tdeck_ui/UI/LXMF/MapScreen.h +++ b/lib/tdeck_ui/UI/LXMF/MapScreen.h @@ -85,8 +85,11 @@ private: Hardware::TDeck::MapStyleCatalog style_catalog_; Pyxis::MapStyleRequest style_request_; bool style_request_pending_; + std::uint32_t style_request_lifecycle_epoch_; char pack_attribution_[Pyxis::MapPackManifest::ATTRIBUTION_CAPACITY]; std::atomic screen_visible_; + std::uint32_t style_lifecycle_epoch_; + bool style_lifecycle_exhausted_; std::atomic pack_refresh_epoch_; std::uint8_t* compressed_staging_; SemaphoreHandle_t state_mutex_; @@ -105,7 +108,9 @@ private: static void workerEntry(void* context); void workerLoop(); void publishPackAttribution(); - void publishStyleCatalog(); + void publishStyleCatalog(std::uint32_t activation_token = 0U, + std::uint32_t request_lifecycle_epoch = 0U, + bool activation_failed = false); Pyxis::MapTileLoadResult loadTile(const Pyxis::MapTileRequest& request); Pyxis::MapTileLoadResult readTile(const Pyxis::MapTileRequest& request); Pyxis::MapTileLoadResult readCompressedTile(const Pyxis::MapTileRequest& request); diff --git a/lib/tdeck_ui/UI/LXMF/MapStyleSelector.cpp b/lib/tdeck_ui/UI/LXMF/MapStyleSelector.cpp index f4af8c66..ac1c9b8f 100644 --- a/lib/tdeck_ui/UI/LXMF/MapStyleSelector.cpp +++ b/lib/tdeck_ui/UI/LXMF/MapStyleSelector.cpp @@ -10,7 +10,7 @@ namespace Pyxis { MapStyleSelector::MapStyleSelector() : styles_(), count_(0U), active_index_(-1), pending_index_(0U), catalog_generation_(0U), next_token_(1U), pending_token_(0U), - state_(State::DISCOVERING) {} + activation_token_(0U), state_(State::DISCOVERING) {} std::uint32_t MapStyleSelector::advance(std::uint32_t value) { ++value; @@ -35,6 +35,14 @@ bool MapStyleSelector::validString(const char* value, std::size_t capacity, bool MapStyleSelector::setCatalog(std::uint32_t generation, const MapStyleSummary* styles, std::size_t count, const char* active_id) { + if (activation_token_ != 0U) return false; + return applyCatalog(generation, styles, count, active_id, State::READY); +} + +bool MapStyleSelector::applyCatalog(std::uint32_t generation, + const MapStyleSummary* styles, + std::size_t count, const char* active_id, + State next_state) { if (generation == 0U || count > MAX_STYLES || (count != 0U && styles == NULL) || (count == 0U && active_id != NULL)) return false; @@ -61,7 +69,7 @@ bool MapStyleSelector::setCatalog(std::uint32_t generation, pending_token_ = 0U; catalog_generation_ = generation; next_token_ = advance(next_token_); - state_ = State::READY; + state_ = next_state; return true; } @@ -75,12 +83,44 @@ bool MapStyleSelector::requestNext(MapStyleRequest& output) { output.token = pending_token_; output.catalog_generation = catalog_generation_; std::strcpy(output.style_id, styles_[pending_index_].id); + activation_token_ = pending_token_; state_ = State::APPLYING; return true; } +bool MapStyleSelector::reconcileActivation( + std::uint32_t expected_token, std::uint32_t generation, + const MapStyleSummary* styles, std::size_t count, const char* active_id, + bool retain_error) { + if (!activationOwnedBy(expected_token)) return false; + return applyCatalog(generation, styles, count, active_id, + retain_error ? State::ERROR : State::READY); +} + +bool MapStyleSelector::releaseActivation(std::uint32_t expected_token) { + if (activation_token_ == 0U || activation_token_ != expected_token) return false; + activation_token_ = 0U; + return true; +} + +bool MapStyleSelector::cancelPending(std::uint32_t expected_token) { + if (!activationOwnedBy(expected_token) || state_ != State::APPLYING || + pending_token_ != expected_token) return false; + pending_token_ = 0U; + activation_token_ = 0U; + state_ = State::READY; + return true; +} + +bool MapStyleSelector::clearError() { + if (state_ != State::ERROR) return false; + state_ = State::READY; + return true; +} + bool MapStyleSelector::complete(const MapStyleCompletion& completion) { if (state_ != State::APPLYING || completion.token != pending_token_ || + completion.token != activation_token_ || completion.catalog_generation != catalog_generation_ || std::strcmp(completion.style_id, styles_[pending_index_].id) != 0) return false; pending_token_ = 0U; diff --git a/lib/tdeck_ui/UI/LXMF/MapStyleSelector.h b/lib/tdeck_ui/UI/LXMF/MapStyleSelector.h index a44c8e60..ce4ed4a8 100644 --- a/lib/tdeck_ui/UI/LXMF/MapStyleSelector.h +++ b/lib/tdeck_ui/UI/LXMF/MapStyleSelector.h @@ -40,14 +40,26 @@ public: bool setCatalog(std::uint32_t generation, const MapStyleSummary* styles, std::size_t count, const char* active_id); bool requestNext(MapStyleRequest& output); + bool activationOwnedBy(std::uint32_t expected_token) const { + return expected_token != 0U && activation_token_ == expected_token; + } + bool reconcileActivation(std::uint32_t expected_token, + std::uint32_t generation, + const MapStyleSummary* styles, + std::size_t count, const char* active_id, + bool retain_error); + bool releaseActivation(std::uint32_t expected_token); + bool cancelPending(std::uint32_t expected_token); + bool clearError(); bool complete(const MapStyleCompletion& completion); State state() const { return state_; } std::size_t count() const { return count_; } bool canCycle() const { return (count_ > 1U || (count_ == 1U && active_index_ < 0)) && - state_ != State::APPLYING; + state_ != State::APPLYING && activation_token_ == 0U; } + bool activationInFlight() const { return activation_token_ != 0U; } const char* activeId() const; const char* activeLabel() const; std::uint32_t generation() const { return catalog_generation_; } @@ -60,8 +72,12 @@ private: std::uint32_t catalog_generation_; std::uint32_t next_token_; std::uint32_t pending_token_; + std::uint32_t activation_token_; State state_; + bool applyCatalog(std::uint32_t generation, const MapStyleSummary* styles, + std::size_t count, const char* active_id, + State next_state); static bool validString(const char* value, std::size_t capacity, bool identifier); static std::uint32_t advance(std::uint32_t value); }; diff --git a/tests/build_scripts/test_map_screen_contract.py b/tests/build_scripts/test_map_screen_contract.py index 5c8243e2..6ad16e26 100644 --- a/tests/build_scripts/test_map_screen_contract.py +++ b/tests/build_scripts/test_map_screen_contract.py @@ -160,16 +160,23 @@ def test_style_switch_is_bounded_and_worker_owned(): assert "lv_obj_t* style_label_;" in header assert "Pyxis::MapStyleRequest style_request_;" in header assert "bool style_request_pending_;" in header + assert "std::uint32_t style_request_lifecycle_epoch_;" in header + assert "std::uint32_t style_lifecycle_epoch_;" in header + assert "bool style_lifecycle_exhausted_;" in header callback = function_body(source, "void MapScreen::onStyle(lv_event_t* event)") assert "style_selector_.requestNext" in callback assert "style_request_pending_ = true" in callback + assert "style_request_lifecycle_epoch_" in callback + assert "screen->style_lifecycle_epoch_;" in callback assert "style_catalog_.activate" not in callback assert "pack_.initialize" not in callback worker = function_body(source, "void MapScreen::workerLoop()") assert "style_catalog_.discover()" in worker assert "style_catalog_.activate" in worker + assert "style_selector_.activationOwnedBy(style_request_.token)" in worker + assert "style_selector_.releaseActivation(style_request.token)" in worker assert "pack_.initialize()" in worker assert "presenter_.invalidateTiles()" in worker activation = worker.index("style_catalog_.activate") @@ -177,10 +184,15 @@ def test_style_switch_is_bounded_and_worker_owned(): cache_invalidation = worker.index("decoded_tile_cache_.clear()", committed_reload) selector_completion = worker.index("style_selector_.complete", committed_reload) tile_invalidation = worker.index("presenter_.invalidateTiles()", committed_reload) - committed_catalog = worker.index("publishStyleCatalog();", tile_invalidation) + committed_catalog = worker.index("publishStyleCatalog(style_request.token", tile_invalidation) rediscovery = worker.index("style_catalog_.discover()", committed_reload) assert activation < committed_reload < cache_invalidation assert cache_invalidation < selector_completion < tile_invalidation < committed_catalog < rediscovery + assert "publishStyleCatalog(style_request.token" in worker + assert "style_request_lifecycle_epoch, !success" in worker + assert "retain_style_error" not in worker + release = worker.index("style_selector_.releaseActivation(style_request.token)") + assert rediscovery < release assert "success = discovery" not in worker assert worker.index("style_catalog_.activate") < worker.index("presenter_.takeRequest") @@ -191,6 +203,18 @@ def test_style_switch_is_bounded_and_worker_owned(): assert "pack_.initialize" not in body assert "lv_group_add_obj(group, style_button_)" in source assert "lv_group_remove_obj(style_button_)" in source + publisher = function_body(source, "void MapScreen::publishStyleCatalog(") + assert "activation_failed &&" in publisher + assert "screen_visible_.load" in publisher + assert "!style_lifecycle_exhausted_" in publisher + assert "request_lifecycle_epoch == style_lifecycle_epoch_" in publisher + assert publisher.index("lockState(portMAX_DELAY)") < publisher.index("activation_failed &&") + hide = function_body(source, "void MapScreen::hide()") + assert "lockState(portMAX_DELAY)" in hide + assert "style_lifecycle_exhausted_" in hide + assert "UINT32_MAX" in hide + assert "style_selector_.cancelPending(style_request_.token)" in hide + assert "style_selector_.clearError()" in hide def test_ui_manager_services_before_lock_and_hides_map_everywhere(): diff --git a/tests/native/test_map_style_selector.cpp b/tests/native/test_map_style_selector.cpp index 331f2995..bba89ee7 100644 --- a/tests/native/test_map_style_selector.cpp +++ b/tests/native/test_map_style_selector.cpp @@ -47,6 +47,7 @@ void cyclesDeterministicallyAndBoundsPendingIntent() { CHECK(std::strcmp(request.style_id, "positron") == 0); CHECK(request.token != 0U); CHECK(selector.state() == Pyxis::MapStyleSelector::State::APPLYING); + CHECK(selector.activationInFlight()); Pyxis::MapStyleRequest duplicate{}; CHECK(!selector.requestNext(duplicate)); @@ -67,6 +68,8 @@ void cyclesDeterministicallyAndBoundsPendingIntent() { CHECK(selector.state() == Pyxis::MapStyleSelector::State::ERROR); CHECK(std::strcmp(selector.activeId(), "dark-matter") == 0); + CHECK(!selector.requestNext(request)); + CHECK(selector.releaseActivation(failure.token)); CHECK(selector.requestNext(request)); Pyxis::MapStyleCompletion success{}; success.token = request.token; @@ -74,6 +77,7 @@ void cyclesDeterministicallyAndBoundsPendingIntent() { success.success = true; std::strcpy(success.style_id, request.style_id); CHECK(selector.complete(success)); + CHECK(selector.releaseActivation(success.token)); CHECK(selector.state() == Pyxis::MapStyleSelector::State::READY); CHECK(std::strcmp(selector.activeId(), "positron") == 0); CHECK(std::strcmp(selector.activeLabel(), "Positron") == 0); @@ -91,13 +95,16 @@ void rejectsMalformedCatalogsAndStaleCompletions() { CHECK(selector.setCatalog(3U, styles, 2U, "osm-bright")); Pyxis::MapStyleRequest request{}; CHECK(selector.requestNext(request)); - CHECK(selector.setCatalog(4U, styles, 2U, "osm-bright")); + CHECK(!selector.setCatalog(4U, styles, 2U, "osm-bright")); Pyxis::MapStyleCompletion old{}; old.token = request.token; - old.catalog_generation = request.catalog_generation; - old.success = true; + old.catalog_generation = request.catalog_generation + 1U; + old.success = false; std::strcpy(old.style_id, request.style_id); CHECK(!selector.complete(old)); + old.catalog_generation = request.catalog_generation; + CHECK(selector.complete(old)); + CHECK(selector.releaseActivation(old.token)); CHECK(std::strcmp(selector.activeId(), "osm-bright") == 0); } @@ -112,6 +119,108 @@ void permitsSoleInstalledStyleWhenNoRecognizedStyleIsActive() { CHECK(selector.requestNext(request)); CHECK(std::strcmp(request.style_id, "positron") == 0); } + +void activationLeaseSpansCatalogReconciliation() { + Pyxis::MapStyleSelector selector; + const Pyxis::MapStyleSummary styles[] = { + style("osm-bright", "Bright"), style("toner", "Toner")}; + CHECK(selector.setCatalog(7U, styles, 2U, "osm-bright")); + + Pyxis::MapStyleRequest first{}; + CHECK(selector.requestNext(first)); + CHECK(selector.activationOwnedBy(first.token)); + CHECK(!selector.activationOwnedBy(first.token + 1U)); + CHECK(!selector.releaseActivation(first.token + 1U)); + + Pyxis::MapStyleCompletion success{}; + success.token = first.token; + success.catalog_generation = first.catalog_generation; + success.success = true; + std::strcpy(success.style_id, first.style_id); + CHECK(selector.complete(success)); + + CHECK(!selector.setCatalog(8U, styles, 2U, "toner")); + CHECK(selector.reconcileActivation(first.token, 8U, styles, 2U, "toner", false)); + Pyxis::MapStyleRequest second{}; + CHECK(!selector.requestNext(second)); + CHECK(selector.releaseActivation(first.token)); + CHECK(!selector.activationInFlight()); + CHECK(selector.requestNext(second)); + CHECK(second.catalog_generation == 8U); + CHECK(std::strcmp(second.style_id, "osm-bright") == 0); +} + +void cancellationIsScopedToUnborrowedOwner() { + Pyxis::MapStyleSelector selector; + const Pyxis::MapStyleSummary styles[] = { + style("osm-bright", "Bright"), style("toner", "Toner")}; + CHECK(selector.setCatalog(12U, styles, 2U, "osm-bright")); + Pyxis::MapStyleRequest request{}; + CHECK(selector.requestNext(request)); + CHECK(!selector.cancelPending(request.token + 1U)); + CHECK(selector.cancelPending(request.token)); + CHECK(selector.state() == Pyxis::MapStyleSelector::State::READY); + + CHECK(selector.requestNext(request)); + CHECK(selector.cancelPending(request.token)); + CHECK(selector.requestNext(request)); + Pyxis::MapStyleCompletion failure{}; + failure.token = request.token; + failure.catalog_generation = request.catalog_generation; + failure.success = false; + std::strcpy(failure.style_id, request.style_id); + CHECK(selector.complete(failure)); + CHECK(selector.clearError()); + CHECK(selector.state() == Pyxis::MapStyleSelector::State::READY); + CHECK(selector.releaseActivation(request.token)); +} + +void committedActivationFailureStillReconcilesGeneration() { + Pyxis::MapStyleSelector selector; + const Pyxis::MapStyleSummary styles[] = { + style("osm-bright", "Bright"), style("toner", "Toner")}; + CHECK(selector.setCatalog(21U, styles, 2U, "osm-bright")); + Pyxis::MapStyleRequest request{}; + CHECK(selector.requestNext(request)); + + Pyxis::MapStyleCompletion reload_failure{}; + reload_failure.token = request.token; + reload_failure.catalog_generation = request.catalog_generation; + reload_failure.success = false; + std::strcpy(reload_failure.style_id, request.style_id); + CHECK(selector.complete(reload_failure)); + CHECK(selector.reconcileActivation(request.token, 22U, styles, 2U, + "toner", true)); + CHECK(selector.state() == Pyxis::MapStyleSelector::State::ERROR); + CHECK(selector.generation() == 22U); + CHECK(std::strcmp(selector.activeId(), "toner") == 0); + CHECK(selector.releaseActivation(request.token)); + + Pyxis::MapStyleRequest retry{}; + CHECK(selector.requestNext(retry)); + CHECK(retry.catalog_generation == 22U); + CHECK(std::strcmp(retry.style_id, "osm-bright") == 0); +} + +void hiddenLifecycleCannotReintroduceFailureDuringReconciliation() { + Pyxis::MapStyleSelector selector; + const Pyxis::MapStyleSummary styles[] = { + style("osm-bright", "Bright"), style("toner", "Toner")}; + CHECK(selector.setCatalog(30U, styles, 2U, "osm-bright")); + Pyxis::MapStyleRequest request{}; + CHECK(selector.requestNext(request)); + Pyxis::MapStyleCompletion failure{}; + failure.token = request.token; + failure.catalog_generation = request.catalog_generation; + failure.success = false; + std::strcpy(failure.style_id, request.style_id); + CHECK(selector.complete(failure)); + CHECK(selector.clearError()); + CHECK(selector.reconcileActivation(request.token, 31U, styles, 2U, + "toner", false)); + CHECK(selector.state() == Pyxis::MapStyleSelector::State::READY); + CHECK(selector.releaseActivation(request.token)); +} } int main() { @@ -119,6 +228,10 @@ int main() { cyclesDeterministicallyAndBoundsPendingIntent(); rejectsMalformedCatalogsAndStaleCompletions(); permitsSoleInstalledStyleWhenNoRecognizedStyleIsActive(); + activationLeaseSpansCatalogReconciliation(); + cancellationIsScopedToUnborrowedOwner(); + committedActivationFailureStillReconcilesGeneration(); + hiddenLifecycleCannotReintroduceFailureDuringReconciliation(); std::cout << "map style selector: " << passed << " passed, " << failed << " failed\n"; return failed == 0 ? EXIT_SUCCESS : EXIT_FAILURE; }