mirror of
https://github.com/KytyPS5/KytyPS5.git
synced 2026-10-19 18:36:53 +02:00
4e8d8c849d
* launcher: keep primary update feed answer when the fallback request fails When the primary feed is reachable but its tag differs from the build tag, the result was discarded and the GitHub API fallback was consulted. If the fallback request failed (api.github.com rate-limits unauthenticated requests), the launcher reported an error even though the primary feed had already returned a usable update answer with a working release URL. Keep the primary feed answer and show it if the fallback fails. * emulator: validate numeric CLI arguments --screen-width, --screen-height, --gpu, and --vblank-frequency were parsed with Common::ToInt32/ToInt64, which ignores std::from_chars errors and returns 0, so values like "--screen-width abc" or "--gpu xyz" were silently accepted: zero screen size, or GPU 0 selected instead of auto (-1). Reject invalid values with an error message like the other options, using from_chars-based helpers (ParseUint32/ParseInt32); reject zero screen dimensions. * tests: add LeastRecentlyUsedCache regression tests Cover the LRU cache walk order, tick filtering in ForEachItemBelow, early-exit boolean callbacks, Touch reordering and same-tick skips, Free, and freed-id reuse by later inserts. Modeled on BitArrayTests. * tests: use a tick cutoff that detects a stale newest-item tick Collect(cache, 35) also visits c when Touch(c, 35) failed to update its tick (30 <= 35). Use a cutoff of 30 and expect only b. * Run LRU cache tests in CI --------- Co-authored-by: nmzik <Nmzik@mail.ru>
157 lines
5.4 KiB
C++
157 lines
5.4 KiB
C++
#include "common/lruCache.h"
|
|
|
|
#include <cstdint>
|
|
#include <cstdio>
|
|
#include <cstdlib>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
namespace {
|
|
|
|
void Check(bool value, const char *message) {
|
|
if (!value) {
|
|
std::fprintf(stderr, "LruCacheTests: failed: %s\n", message);
|
|
std::abort();
|
|
}
|
|
}
|
|
|
|
// Collect the objects reachable through ForEachItemBelow(), in visit order.
|
|
std::vector<std::string> Collect(Common::LeastRecentlyUsedCache<std::string, uint64_t> &cache,
|
|
uint64_t tick) {
|
|
std::vector<std::string> visited;
|
|
cache.ForEachItemBelow(tick, [&](const std::string &object) { visited.push_back(object); });
|
|
return visited;
|
|
}
|
|
|
|
void TestInsertAndVisitOrder() {
|
|
Common::LeastRecentlyUsedCache<std::string, uint64_t> cache;
|
|
const auto a = cache.Insert("a", 10);
|
|
const auto b = cache.Insert("b", 20);
|
|
const auto c = cache.Insert("c", 30);
|
|
|
|
Check(a != b && b != c && a != c, "Insert returned duplicate ids");
|
|
|
|
const auto all = Collect(cache, UINT64_MAX);
|
|
Check(all.size() == 3 && all[0] == "a" && all[1] == "b" && all[2] == "c",
|
|
"inserted items are not visited in insertion order");
|
|
|
|
const auto below = Collect(cache, 20);
|
|
Check(below.size() == 2 && below[0] == "a" && below[1] == "b",
|
|
"ForEachItemBelow visited an item above the tick");
|
|
|
|
const auto none = Collect(cache, 5);
|
|
Check(none.empty(), "ForEachItemBelow visited items below an empty tick");
|
|
}
|
|
|
|
void TestEarlyExitCallback() {
|
|
Common::LeastRecentlyUsedCache<std::string, uint64_t> cache;
|
|
(void)cache.Insert("a", 10);
|
|
(void)cache.Insert("b", 20);
|
|
(void)cache.Insert("c", 30);
|
|
|
|
std::vector<std::string> visited;
|
|
cache.ForEachItemBelow(UINT64_MAX, [&](const std::string &object) {
|
|
visited.push_back(object);
|
|
return true; // stop after the first item
|
|
});
|
|
Check(visited.size() == 1 && visited[0] == "a", "boolean callback did not stop the walk");
|
|
|
|
std::vector<std::string> all_visited;
|
|
cache.ForEachItemBelow(UINT64_MAX, [&](const std::string &object) {
|
|
all_visited.push_back(object);
|
|
return false;
|
|
});
|
|
Check(all_visited.size() == 3, "false-returning callback did not visit every item");
|
|
}
|
|
|
|
void TestTouchReordersAndSkips() {
|
|
Common::LeastRecentlyUsedCache<std::string, uint64_t> cache;
|
|
const auto a = cache.Insert("a", 10);
|
|
const auto b = cache.Insert("b", 20);
|
|
const auto c = cache.Insert("c", 30);
|
|
|
|
// Touching the last item must not change the order.
|
|
cache.Touch(c, 35);
|
|
auto order = Collect(cache, UINT64_MAX);
|
|
Check(order.size() == 3 && order[0] == "a" && order[1] == "b" && order[2] == "c",
|
|
"touching the newest item changed the order");
|
|
|
|
// An older or equal tick must be ignored.
|
|
cache.Touch(a, 10);
|
|
cache.Touch(a, 5);
|
|
order = Collect(cache, UINT64_MAX);
|
|
Check(order[0] == "a" && order[1] == "b" && order[2] == "c",
|
|
"an ignored touch changed the order");
|
|
|
|
// Touching the oldest item moves it to the newest end.
|
|
cache.Touch(a, 40);
|
|
order = Collect(cache, UINT64_MAX);
|
|
Check(order.size() == 3 && order[0] == "b" && order[1] == "c" && order[2] == "a",
|
|
"touching an item did not move it to the newest end");
|
|
|
|
// The touched item now carries its new tick for tick filtering
|
|
// (a=40, b=20, c=35 after the touches above). A cutoff of 30 detects
|
|
// a stale c tick: c is visited only if Touch(c, 35) updated it.
|
|
const auto below = Collect(cache, 30);
|
|
Check(below.size() == 1 && below[0] == "b",
|
|
"touched item did not adopt its new tick");
|
|
|
|
// Touch the middle item.
|
|
cache.Touch(b, 45);
|
|
order = Collect(cache, UINT64_MAX);
|
|
Check(order.size() == 3 && order[0] == "c" && order[1] == "a" && order[2] == "b",
|
|
"touching a middle item did not move it to the end");
|
|
}
|
|
|
|
void TestFreeAndIdReuse() {
|
|
Common::LeastRecentlyUsedCache<std::string, uint64_t> cache;
|
|
const auto a = cache.Insert("a", 10);
|
|
const auto b = cache.Insert("b", 20);
|
|
const auto c = cache.Insert("c", 30);
|
|
|
|
// Free the first and the last items.
|
|
cache.Free(a);
|
|
cache.Free(c);
|
|
auto order = Collect(cache, UINT64_MAX);
|
|
Check(order.size() == 1 && order[0] == "b", "Free left a freed item in the walk");
|
|
|
|
// Freed ids must be reused by later inserts.
|
|
const auto d = cache.Insert("d", 40);
|
|
Check(d == a || d == c, "Insert did not reuse a freed id");
|
|
const auto e = cache.Insert("e", 50);
|
|
Check(e != d && (e == a || e == c), "Insert reused the same freed id twice");
|
|
|
|
order = Collect(cache, UINT64_MAX);
|
|
Check(order.size() == 3 && order[0] == "b" && order[1] == "d" && order[2] == "e",
|
|
"reused ids are not visited in tick order");
|
|
|
|
// Touching a reused id must not disturb the other items.
|
|
cache.Touch(d, 60);
|
|
order = Collect(cache, UINT64_MAX);
|
|
Check(order.size() == 3 && order[0] == "b" && order[1] == "e" && order[2] == "d",
|
|
"touching a reused id changed the wrong items");
|
|
|
|
// Free everything, then rebuild.
|
|
cache.Free(b);
|
|
cache.Free(d);
|
|
cache.Free(e);
|
|
Check(Collect(cache, UINT64_MAX).empty(), "freeing every item left items in the walk");
|
|
|
|
const auto again = cache.Insert("again", 70);
|
|
const auto order2 = Collect(cache, UINT64_MAX);
|
|
Check(order2.size() == 1 && order2[0] == "again", "insert after a full free failed");
|
|
cache.Touch(again, 70);
|
|
Check(Collect(cache, 70).size() == 1, "touching a rebuilt item failed");
|
|
}
|
|
|
|
} // namespace
|
|
|
|
int main() {
|
|
TestInsertAndVisitOrder();
|
|
TestEarlyExitCallback();
|
|
TestTouchReordersAndSkips();
|
|
TestFreeAndIdReuse();
|
|
std::puts("LruCacheTests: all cases passed");
|
|
return 0;
|
|
}
|