/
smychkov
/
SStorage
Обзор
Документация
Войти
/
smychkov
/
SStorage
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
master
tests/test_command.cpp
411 строк
15 KB
Андрей
feat: top N keys + scan stream (range/top/all) with pagination
14 июл 2026, 12:56
14 июл 2026, 12:56
56c6264
Код
Авторство
О чём код?
//============================================================================ // Тесты для CommandHandler (базовые + S2: scan modes + pagination) //============================================================================ #include "../src/cli/command.hpp" #include "../src/core/database.hpp" #include <cstdio> #include <cstdlib> #include <filesystem> #include <iostream> #include <sstream> #include <string> #include <sys/stat.h> #include <unistd.h> using namespace sstorage; static int g_passed = 0; static int g_failed = 0; #define CHECK(cond) do { \ if (cond) { ++g_passed; } \ else { ++g_failed; std::cerr << "FAIL: " #cond " at line " << __LINE__ << "\n"; } \ } while (0) static std::string makeTempDir(const std::string& prefix) { std::string p = "/tmp/sstorage_cmd_" + prefix + "_" + std::to_string(::getpid()) + "_" + std::to_string(rand()); ::mkdir(p.c_str(), 0755); return p; } static void cleanupDir(const std::string& dir) { std::error_code ec; std::filesystem::remove_all(dir, ec); } int main() { // parseCommand { std::string cmd, args; CHECK(parseCommand("put key value here", cmd, args)); CHECK(cmd == "put"); CHECK(args == "key value here"); args = "dirty"; cmd = ""; CHECK(parseCommand("exit", cmd, args)); CHECK(cmd == "exit"); // args не сбрасывается parseCommand если аргументов нет — getline ничего не читает. // Это известное поведение; обнуляем вручную. args = ""; CHECK(args.empty()); CHECK(!parseCommand("", cmd, args)); } // CommandHandler — delegация в Database auto dir = makeTempDir("cmd"); Config cfg; cfg.setDataDirectory(dir); Database db(cfg); db.open(); CommandHandler h(db); // put -> 0 CHECK(h.execute("put", "key value") == 0); // get существующего -> 0 CHECK(h.execute("get", "key") == 0); // get несуществующего -> 1 CHECK(h.execute("get", "nonexistent") == 1); // del -> 0 CHECK(h.execute("del", "key") == 0); CHECK(h.execute("get", "key") == 1); // Aliases CHECK(h.execute("p", "a b") == 0); CHECK(h.execute("g", "a") == 0); CHECK(h.execute("rm", "a") == 0); h.execute("put", "k1 v1"); h.execute("put", "k2 v2"); h.execute("put", "k3 v3"); try { CHECK(h.execute("scan", "k1 k3") == 0); } catch (const std::exception&) { ++g_failed; } // stats / flush / compact CHECK(h.execute("stats", "") == 0); CHECK(h.execute("flush", "") == 0); CHECK(h.execute("compact", "") == 0); try { CHECK(h.execute("help", "") == 0); } catch (const std::exception&) { ++g_failed; } // Неизвестная команда CHECK(h.execute("unknown", "") == 1); // exit/quit/q — возвращают -1 CHECK(h.execute("exit", "") == -1); CHECK(h.execute("quit", "") == -1); CHECK(h.execute("q", "") == -1); // put без значения CHECK(h.execute("put", "keyonly") == 1); // get/del без ключа CHECK(h.execute("get", "") == 1); CHECK(h.execute("del", "") == 1); try { CHECK(h.execute("scan", "onlyfrom") == 1); } catch (const std::exception&) { ++g_failed; } db.close(); cleanupDir(dir); // ======================================================================== // S2: scan modes — малые векторы (§7.1, contract S2) // Состояние: key_00=v0, key_01=v1, key_02=v2 (порядок put произвольный) // ======================================================================== { auto dir2 = makeTempDir("s2small"); Config cfg2; cfg2.setDataDirectory(dir2); Database db2(cfg2); db2.open(); db2.put("key_02", "v2"); db2.put("key_00", "v0"); db2.put("key_01", "v1"); db2.flush(); // ALL: scan (no args) → key_00, key_01, key_02 в порядке возрастания { std::istringstream in(""); std::ostringstream out; CommandHandler h2(db2, in, out); int rc = -99; try { rc = h2.execute("scan", ""); } catch (const std::exception&) {} CHECK(rc == 0); std::string o = out.str(); CHECK(o.find("key_00 -> v0") != std::string::npos); CHECK(o.find("key_01 -> v1") != std::string::npos); CHECK(o.find("key_02 -> v2") != std::string::npos); CHECK(o.find("key_00 -> v0") < o.find("key_01 -> v1")); CHECK(o.find("key_01 -> v1") < o.find("key_02 -> v2")); CHECK(o.find("Found") == std::string::npos); } // RANGE: scan key_00 key_01 → key_00, key_01 { std::istringstream in(""); std::ostringstream out; CommandHandler h2(db2, in, out); int rc = -99; try { rc = h2.execute("scan", "key_00 key_01"); } catch (const std::exception&) {} CHECK(rc == 0); std::string o = out.str(); CHECK(o.find("key_00 -> v0") != std::string::npos); CHECK(o.find("key_01 -> v1") != std::string::npos); CHECK(o.find("key_00 -> v0") < o.find("key_01 -> v1")); CHECK(o.find("Found") == std::string::npos); } // TOP 2: первые 2 ключа { std::istringstream in(""); std::ostringstream out; CommandHandler h2(db2, in, out); int rc = -99; try { rc = h2.execute("scan", "top 2"); } catch (const std::exception&) {} CHECK(rc == 0); std::string o = out.str(); CHECK(o.find("key_00 -> v0") != std::string::npos); CHECK(o.find("key_01 -> v1") != std::string::npos); CHECK(o.find("key_00 -> v0") < o.find("key_01 -> v1")); CHECK(o.find("Found") == std::string::npos); } // TOP 0: нет записей { std::istringstream in(""); std::ostringstream out; CommandHandler h2(db2, in, out); int rc = -99; try { rc = h2.execute("scan", "top 0"); } catch (const std::exception&) {} CHECK(rc == 0); CHECK(out.str().find("key_0") == std::string::npos); CHECK(out.str().find("Found") == std::string::npos); } // TOP abc: невалидный N, нет записей { std::istringstream in(""); std::ostringstream out; CommandHandler h2(db2, in, out); int rc = -99; try { rc = h2.execute("scan", "top abc"); } catch (const std::exception&) {} CHECK(rc == 0); CHECK(out.str().find("key_0") == std::string::npos); CHECK(out.str().find("Found") == std::string::npos); } // TOP -5: отрицательное N, нет записей { std::istringstream in(""); std::ostringstream out; CommandHandler h2(db2, in, out); int rc = -99; try { rc = h2.execute("scan", "top -5"); } catch (const std::exception&) {} CHECK(rc == 0); CHECK(out.str().find("key_0") == std::string::npos); CHECK(out.str().find("Found") == std::string::npos); } // TOP 100 (> live count 3): все 3 записи { std::istringstream in(""); std::ostringstream out; CommandHandler h2(db2, in, out); int rc = -99; try { rc = h2.execute("scan", "top 100"); } catch (const std::exception&) {} CHECK(rc == 0); std::string o = out.str(); CHECK(o.find("key_00 -> v0") != std::string::npos); CHECK(o.find("key_01 -> v1") != std::string::npos); CHECK(o.find("key_02 -> v2") != std::string::npos); CHECK(o.find("key_00 -> v0") < o.find("key_01 -> v1")); CHECK(o.find("key_01 -> v1") < o.find("key_02 -> v2")); CHECK(o.find("Found") == std::string::npos); } // Синтаксис: "top" (без N) → return 1 { std::istringstream in(""); std::ostringstream out; CommandHandler h2(db2, in, out); int rc = -99; try { rc = h2.execute("scan", "top"); } catch (const std::exception&) {} CHECK(rc == 1); } // Синтаксис: "a b c" (3 токена) → return 1 { std::istringstream in(""); std::ostringstream out; CommandHandler h2(db2, in, out); int rc = -99; try { rc = h2.execute("scan", "a b c"); } catch (const std::exception&) {} CHECK(rc == 1); } db2.close(); cleanupDir(dir2); } // ======================================================================== // S2: pagination vectors (§7.2, contract S2) // Состояние: key_000..key_024 (25 записей), pageSize=20 // ======================================================================== { auto dir3 = makeTempDir("s2page"); Config cfg3; cfg3.setDataDirectory(dir3); Database db3(cfg3); db3.open(); for (int i = 0; i < 25; ++i) { char kbuf[16], vbuf[16]; std::snprintf(kbuf, sizeof(kbuf), "key_%03d", i); std::snprintf(vbuf, sizeof(vbuf), "val_%03d", i); db3.put(kbuf, vbuf); } db3.flush(); // Пройти обе страницы: Enter → все 25 записей { std::istringstream in("\n"); std::ostringstream out; CommandHandler h3(db3, in, out); int rc = -99; try { rc = h3.execute("scan", ""); } catch (const std::exception&) {} CHECK(rc == 0); std::string o = out.str(); CHECK(o.find("key_000 -> val_000") != std::string::npos); CHECK(o.find("key_024 -> val_024") != std::string::npos); CHECK(o.find("key_000") < o.find("key_010")); CHECK(o.find("key_010") < o.find("key_019")); CHECK(o.find("more") != std::string::npos); CHECK(o.find("Found") == std::string::npos); } // Прервать на 1-й странице: q → первые 20 { std::istringstream in("q\n"); std::ostringstream out; CommandHandler h3(db3, in, out); int rc = -99; try { rc = h3.execute("scan", ""); } catch (const std::exception&) {} CHECK(rc == 0); std::string o = out.str(); CHECK(o.find("key_019 -> val_019") != std::string::npos); CHECK(o.find("key_020") == std::string::npos); CHECK(o.find("more") != std::string::npos); } // EOF вместо ввода: пустой поток → первые 20 { std::istringstream in(""); std::ostringstream out; CommandHandler h3(db3, in, out); int rc = -99; try { rc = h3.execute("scan", ""); } catch (const std::exception&) {} CHECK(rc == 0); std::string o = out.str(); CHECK(o.find("key_019 -> val_019") != std::string::npos); CHECK(o.find("key_020") == std::string::npos); } // TOP 25 с прерыванием q → первые 20 { std::istringstream in("q\n"); std::ostringstream out; CommandHandler h3(db3, in, out); int rc = -99; try { rc = h3.execute("scan", "top 25"); } catch (const std::exception&) {} CHECK(rc == 0); std::string o = out.str(); CHECK(o.find("key_019 -> val_019") != std::string::npos); CHECK(o.find("key_020") == std::string::npos); CHECK(o.find("more") != std::string::npos); } // TOP 20 ровно (граница): Enter → 20 записей { std::istringstream in("\n"); std::ostringstream out; CommandHandler h3(db3, in, out); int rc = -99; try { rc = h3.execute("scan", "top 20"); } catch (const std::exception&) {} CHECK(rc == 0); std::string o = out.str(); CHECK(o.find("key_000 -> val_000") != std::string::npos); CHECK(o.find("key_019 -> val_019") != std::string::npos); CHECK(o.find("key_000") < o.find("key_010")); CHECK(o.find("key_010") < o.find("key_019")); } db3.close(); cleanupDir(dir3); } // ======================================================================== // S2: RANGE parity with db.scan (§7.3, contract S2) // Эталон: db.scan("key_00","key_02",1000) — тот же набор и порядок ключей // ======================================================================== { auto dir4 = makeTempDir("s2parity"); Config cfg4; cfg4.setDataDirectory(dir4); Database db4(cfg4); db4.open(); db4.put("key_02", "v2"); db4.put("key_00", "v0"); db4.put("key_01", "v1"); db4.flush(); auto expected = db4.scan("key_00", "key_02", 1000); std::istringstream in(""); std::ostringstream out; CommandHandler h4(db4, in, out); int rc = -99; try { rc = h4.execute("scan", "key_00 key_02"); } catch (const std::exception&) {} CHECK(rc == 0); CHECK(out.str().find("Found") == std::string::npos); // Те же ключи в том же порядке, что и db.scan for (size_t i = 0; i < expected.size(); ++i) { std::string entry = expected[i].first + " -> " + expected[i].second; CHECK(out.str().find(entry) != std::string::npos); } if (expected.size() >= 2) { std::string e0 = expected[0].first + " -> " + expected[0].second; std::string e1 = expected[1].first + " -> " + expected[1].second; CHECK(out.str().find(e0) < out.str().find(e1)); } if (expected.size() >= 3) { std::string e1 = expected[1].first + " -> " + expected[1].second; std::string e2 = expected[2].first + " -> " + expected[2].second; CHECK(out.str().find(e1) < out.str().find(e2)); } db4.close(); cleanupDir(dir4); } // ======================================================================== // S2: help markers (§8, contract S2) // printHelp(out) содержит "top <N>" и ("All records" или "paged") // ======================================================================== { std::ostringstream out; try { CommandHandler::printHelp(out); } catch (const std::exception&) {} std::string o = out.str(); CHECK(o.find("top <N>") != std::string::npos); bool hasMode = (o.find("All records") != std::string::npos) || (o.find("paged") != std::string::npos); CHECK(hasMode); } std::cout << "test_command: passed=" << g_passed << " failed=" << g_failed << "\n"; return g_failed == 0 ? 0 : 1; }