78 lines
5.6 KiB
C++
78 lines
5.6 KiB
C++
#include "../src/gui_icons.hpp"
|
|
#include <iostream>
|
|
|
|
int main() {
|
|
wl::require_utf8_process(); CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED);
|
|
auto hwnd = CreateWindowW(L"STATIC", L"Icon cache test", 0, 0, 0, 0, 0, HWND_MESSAGE, nullptr, GetModuleHandleW(nullptr), nullptr);
|
|
unsigned checks = 0, failures = 0;
|
|
auto check = [&](bool ok, const char* what) { ++checks; if (!ok) ++failures; std::cout << (ok ? "PASS " : "FAIL ") << what << '\n'; };
|
|
auto until = [](auto condition) {
|
|
auto deadline = GetTickCount64() + 5000;
|
|
for (;;) { if (condition()) return true; if (GetTickCount64() >= deadline) return false; Sleep(1); }
|
|
};
|
|
struct Provider {
|
|
std::mutex mutex;
|
|
std::unordered_map<std::string, unsigned> calls;
|
|
wl::Handle entered{CreateEventW(nullptr, TRUE, FALSE, nullptr)}, release{CreateEventW(nullptr, TRUE, FALSE, nullptr)};
|
|
std::atomic<bool> block = false, fail = false;
|
|
unsigned count(const std::string& key) { std::lock_guard lock(mutex); return calls[key]; }
|
|
};
|
|
auto provider = std::make_shared<Provider>();
|
|
uint64_t now = 0;
|
|
wl::AsyncIcons cache([provider](const wl::IconRequest& request) {
|
|
unsigned call;
|
|
{ std::lock_guard lock(provider->mutex); call = ++provider->calls[request.path]; }
|
|
if (request.type_only) return wl::FileIcon{nullptr, 10};
|
|
if (provider->block.exchange(false)) { SetEvent(provider->entered.value); WaitForSingleObject(provider->release.value, 5000); }
|
|
if (provider->fail) return wl::FileIcon{};
|
|
int base = request.path.find("one") != std::string::npos ? 100 : 200;
|
|
return wl::FileIcon{nullptr, base + static_cast<int>(call)};
|
|
}, 4, [&] { return now; });
|
|
cache.start(hwnd, WM_APP + 4);
|
|
cache.lookup(".exe");
|
|
check(until([&] { cache.collect(); return cache.lookup(".exe") == 10; }), "generic type icon becomes available first");
|
|
wl::Result one; one.path = "C:\\one\\Same.exe"; one.modified = 1; one.attributes = FILE_ATTRIBUTE_NORMAL;
|
|
wl::Result two = one; two.path = "C:\\two\\Same.exe";
|
|
provider->block = true;
|
|
check(cache.lookup(one) == 10, "uncached file immediately returns its type fallback");
|
|
check(WaitForSingleObject(provider->entered.value, 1000) == WAIT_OBJECT_0, "real-file icon request was queued");
|
|
for (unsigned i = 0; i < 100; ++i) cache.lookup(one);
|
|
check(provider->count(one.path) == 1, "repaints deduplicate a pending full-path lookup");
|
|
SetEvent(provider->release.value);
|
|
check(until([&] { cache.collect(); return cache.has_file_icon(one); }) && cache.lookup(one) == 101, "actual file icon replaces the fallback");
|
|
cache.lookup(two);
|
|
check(until([&] { cache.collect(); return cache.has_file_icon(two); }) && cache.lookup(two) == 201, "same basename and extension in another directory get an independent icon");
|
|
for (unsigned i = 0; i < 100; ++i) { cache.lookup(one); cache.lookup(two); }
|
|
check(provider->count(one.path) == 1 && provider->count(two.path) == 1, "repeated searches reuse the full-path cache");
|
|
++one.modified; cache.lookup(one);
|
|
check(until([&] { cache.collect(); return cache.has_file_icon(one); }) && cache.lookup(one) == 102, "changed file metadata refreshes its icon");
|
|
ResetEvent(provider->entered.value); ResetEvent(provider->release.value); provider->block = true;
|
|
++one.modified; cache.lookup(one);
|
|
check(WaitForSingleObject(provider->entered.value, 1000) == WAIT_OBJECT_0, "old file revision is loading");
|
|
++one.modified; cache.lookup(one); SetEvent(provider->release.value);
|
|
check(until([&] { cache.collect(); return cache.has_file_icon(one); }) && cache.lookup(one) == 104, "late completion from an older revision cannot overwrite the newer icon");
|
|
provider->fail = true; ++two.modified; cache.lookup(two);
|
|
until([&] { return provider->count(two.path) == 2; });
|
|
check(until([&] { return cache.collect(); }) && cache.lookup(two) == 10, "failed lookup preserves the type fallback");
|
|
for (unsigned i = 0; i < 100; ++i) cache.lookup(two);
|
|
check(provider->count(two.path) == 2, "failed lookups are throttled instead of retried on every repaint");
|
|
provider->fail = false; now = 5001; cache.lookup(two);
|
|
check(until([&] { cache.collect(); return cache.has_file_icon(two); }) && cache.lookup(two) == 203, "failed lookups can recover after the retry interval");
|
|
auto three = one; three.path = "C:\\three\\Same.exe";
|
|
auto four = one; four.path = "C:\\four\\Same.exe";
|
|
cache.lookup(three); until([&] { cache.collect(); return cache.has_file_icon(three); });
|
|
cache.lookup(four); until([&] { cache.collect(); return cache.has_file_icon(four); });
|
|
cache.lookup(one);
|
|
check(until([&] { cache.collect(); return cache.has_file_icon(one); }) && provider->count(one.path) == 5, "bounded LRU cache reloads evicted paths");
|
|
cache.stop();
|
|
|
|
wl::AsyncIcons real; real.start(hwnd, WM_APP + 5);
|
|
wl::Result executable; executable.path = wl::utf8(wl::module_path()); executable.attributes = FILE_ATTRIBUTE_NORMAL;
|
|
real.lookup(executable);
|
|
check(until([&] { real.collect(); return real.has_file_icon(executable); }), "real Shell lookup loads this executable's embedded icon asynchronously");
|
|
auto expected = wl::load_file_icon({executable.path, executable.attributes, false});
|
|
check(real.lookup(executable) == expected.index && expected.index != real.lookup(".exe"), "cached executable icon matches its full-path Shell icon and differs from generic EXE");
|
|
real.stop(); DestroyWindow(hwnd); CoUninitialize();
|
|
std::cout << checks << " checks, " << failures << " failures\n"; return failures ? 1 : 0;
|
|
}
|