Files
msime-windows/server/tests/src/test_settings_performance.cpp
T
Evan 42238a2af6 style: 用 .clang-format 重排 server / windows / ui / log
一次性重排,无任何行为改动。四个目录各自的 .clang-format 放在仓库里已久,但全组织只有
MSIME-Linux 在 CI 里执行 clang-format 检查,这边 149 个文件里有 149 个不符合自己声明的风格。

已逐文件核对语义等价:去掉空白、注释与相邻字符串字面量拆分之后,全部 149 个文件与重排前逐字节
相同。差异只有三类——缩进与换行、clang-format 补的 } // namespace 结尾注释、以及长字符串被拆成
相邻字面量(C++ 保证自动拼接)。

这个提交会写进 .git-blame-ignore-revs。
2026-09-06 12:13:47 +09:00

111 lines
4.2 KiB
C++

#include "tests/includes/test_framework.h"
#include "settings/dictionary_page.h"
#include "settings/serial_task_queue.h"
#include <chrono>
#include <future>
#include <set>
TEST_CASE(settings_worker_preserves_order_and_drains_on_close)
{
const auto owner = std::this_thread::get_id();
std::promise<void> started, release;
auto wait = release.get_future().share();
std::vector<int> work, delivered;
bool off_ui = false;
{
SerialTaskQueue queue([] {}, [] {});
REQUIRE(queue.Submit([&] {
off_ui = std::this_thread::get_id() != owner;
started.set_value();
wait.wait();
work.push_back(1);
return [&] { delivered.push_back(1); };
}));
const auto status = started.get_future().wait_for(std::chrono::seconds(5));
// Release even if scheduling failed so a failed test cannot deadlock destruction.
if (status != std::future_status::ready)
release.set_value();
REQUIRE(status == std::future_status::ready);
REQUIRE(queue.Submit([&] {
work.push_back(2);
return [&] { delivered.push_back(2); };
}));
queue.Stop();
REQUIRE(!queue.Submit([] { return SerialTaskQueue::Completion{}; }));
queue.Drain(false);
REQUIRE(delivered.empty());
release.set_value();
}
REQUIRE(off_ui);
REQUIRE_EQ(work, (std::vector<int>{1, 2}));
REQUIRE(delivered.empty()); // Destruction never invokes a callback into a closed UI.
}
TEST_CASE(settings_worker_delivers_results_only_on_owner_and_survives_errors)
{
const auto owner = std::this_thread::get_id();
std::promise<void> done;
int errors = 0, delivered = 0;
SerialTaskQueue queue([] {},
[&] {
REQUIRE(std::this_thread::get_id() == owner);
++errors;
},
[&] { done.set_value(); });
queue.Submit([]() -> SerialTaskQueue::Completion { throw std::runtime_error("test"); });
queue.Submit([&] {
return [&] {
REQUIRE(std::this_thread::get_id() == owner);
++delivered;
};
});
queue.Stop();
REQUIRE(done.get_future().wait_for(std::chrono::seconds(5)) == std::future_status::ready);
REQUIRE_EQ(errors, 0);
REQUIRE_EQ(delivered, 0);
queue.Drain();
REQUIRE_EQ(errors, 1);
REQUIRE_EQ(delivered, 1);
}
TEST_CASE(dictionary_pages_cover_ties_without_duplicates_or_unbounded_results)
{
namespace json = boost::json;
sqlite3 *db = nullptr;
REQUIRE(sqlite3_open(":memory:", &db) == SQLITE_OK);
struct Close
{
sqlite3 *db;
~Close()
{
sqlite3_close(db);
}
} close{db};
REQUIRE(sqlite3_exec(db, "CREATE TABLE words(code TEXT,word TEXT PRIMARY KEY,weight INTEGER)", nullptr, nullptr,
nullptr) == SQLITE_OK);
for (int i = 0; i < 205; ++i)
{
const auto sql = "INSERT INTO words VALUES('a','word" + std::to_string(i) + "',100)";
REQUIRE(sqlite3_exec(db, sql.c_str(), nullptr, nullptr, nullptr) == SQLITE_OK);
}
std::set<std::string> seen;
for (int offset : {0, 100, 200})
{
json::object request{{"offset", offset}, {"limit", 100}};
const auto sql = "SELECT code,word,weight FROM words ORDER BY weight DESC,code,word" +
SettingsDictionary::Paging::Sql(request);
sqlite3_stmt *stmt = nullptr;
REQUIRE(sqlite3_prepare_v2(db, sql.c_str(), -1, &stmt, nullptr) == SQLITE_OK);
auto result = SettingsDictionary::Paging::Read(stmt, request);
sqlite3_finalize(stmt);
REQUIRE(result.at("ok").as_bool());
REQUIRE_EQ(result.at("offset").as_int64(), offset);
REQUIRE_EQ(result.at("hasMore").as_bool(), offset < 200);
REQUIRE_EQ(result.at("rows").as_array().size(), offset < 200 ? size_t{100} : size_t{5});
for (const auto &row : result.at("rows").as_array())
REQUIRE(seen.insert(json::value_to<std::string>(row.at("word"))).second);
}
REQUIRE_EQ(seen.size(), size_t{205});
REQUIRE_EQ(SettingsDictionary::Paging::Limit({{"limit", 999999}}), 200);
}