/
smychkov
/
SStorage
Обзор
Документация
Войти
/
smychkov
/
SStorage
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
master
tests/test_grpc.cpp
363 строки
12 KB
Андрей
feat: top N keys + scan stream (range/top/all) with pagination
14 июл 2026, 12:56
14 июл 2026, 12:56
56c6264
Код
Авторство
О чём код?
//============================================================================ // Интеграционные тесты gRPC API //============================================================================ // Поднимаем сервер + создаём gRPC stub, вызываем методы, проверяем ответы. //============================================================================ #include "../src/server/grpc_handler.hpp" #include "../src/server/storage.grpc.pb.h" #include "../src/core/database.hpp" #include <chrono> #include <cstdlib> #include <filesystem> #include <grpcpp/grpcpp.h> #include <iostream> #include <memory> #include <string> #include <sys/stat.h> #include <thread> #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_grpc_" + 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() { auto dir = makeTempDir("test"); Config cfg; cfg.setDataDirectory(dir); Database db(cfg); CHECK(db.open()); uint16_t port = 19080 + (rand() % 1000); GrpcHandler handler(db, port); CHECK(handler.start()); std::this_thread::sleep_for(std::chrono::milliseconds(200)); // Создаём клиентский stub auto channel = grpc::CreateChannel("127.0.0.1:" + std::to_string(port), grpc::InsecureChannelCredentials()); auto stub = Storage::NewStub(channel); // 1. Put + Get { PutRequest req; req.set_key("hello"); req.set_value("world"); PutResponse resp; grpc::ClientContext ctx; auto s = stub->Put(&ctx, req, &resp); CHECK(s.ok()); CHECK(resp.ok()); GetRequest greq; greq.set_key("hello"); GetResponse gresp; grpc::ClientContext gctx; auto gs = stub->Get(&gctx, greq, &gresp); CHECK(gs.ok()); CHECK(gresp.found()); CHECK(gresp.value() == "world"); } // 2. Get несуществующего { GetRequest req; req.set_key("nonexistent"); GetResponse resp; grpc::ClientContext ctx; stub->Get(&ctx, req, &resp); CHECK(!resp.found()); } // 3. Delete { // Сначала кладём { PutRequest r; r.set_key("todel"); r.set_value("v"); PutResponse resp; grpc::ClientContext c; stub->Put(&c, r, &resp); } DeleteRequest req; req.set_key("todel"); DeleteResponse resp; grpc::ClientContext ctx; auto s = stub->Delete(&ctx, req, &resp); CHECK(s.ok()); CHECK(resp.ok()); // Проверяем что нет GetRequest greq; greq.set_key("todel"); GetResponse gresp; grpc::ClientContext gctx; stub->Get(&gctx, greq, &gresp); CHECK(!gresp.found()); } // 4. Scan (streaming) — паритет со старым клиентом (R2) { // Кладём несколько for (char c : {'a', 'b', 'c', 'd'}) { PutRequest r; r.set_key(std::string(1, c)); r.set_value("v"); PutResponse resp; grpc::ClientContext ctx; stub->Put(&ctx, r, &resp); } ScanRequest sreq; sreq.set_from_key("a"); sreq.set_to_key("c"); sreq.set_limit(0); grpc::ClientContext ctx; auto reader = stub->Scan(&ctx, sreq); int count = 0; ScanResponse r; while (reader->Read(&r)) ++count; reader->Finish(); CHECK(count == 3); } // === S4: расширенные режимы Scan (R1–R3, T1–T3, A1–A2) === // Изоляция S4-фикстуры: убираем "hello" из блока #1, // чтобы SCAN_ALL/TOP видели ровно a..e (контракт §5). { DeleteRequest dr; dr.set_key("hello"); DeleteResponse dresp; grpc::ClientContext dctx; stub->Delete(&dctx, dr, &dresp); } // Фикстура: кладём a=1, b=2, c=3, d=4, e=5 (перетираем старые значения) { for (auto& [k, v] : std::vector<std::pair<std::string,std::string>>{ {"a","1"},{"b","2"},{"c","3"},{"d","4"},{"e","5"}}) { PutRequest r; r.set_key(k); r.set_value(v); PutResponse resp; grpc::ClientContext ctx; stub->Put(&ctx, r, &resp); } } // R1: RANGE [a,c] limit=0 → (a,1),(b,2),(c,3) { ScanRequest req; req.set_mode(sstorage::SCAN_RANGE); req.set_from_key("a"); req.set_to_key("c"); req.set_limit(0); grpc::ClientContext ctx; auto reader = stub->Scan(&ctx, req); std::vector<std::pair<std::string,std::string>> results; ScanResponse r; while (reader->Read(&r)) results.emplace_back(r.key(), r.value()); auto status = reader->Finish(); CHECK(status.ok()); CHECK(results.size() == 3); CHECK(results[0] == std::make_pair(std::string("a"), std::string("1"))); CHECK(results[1] == std::make_pair(std::string("b"), std::string("2"))); CHECK(results[2] == std::make_pair(std::string("c"), std::string("3"))); } // R2: mode не задан (default=SCAN_RANGE), [a,c] limit=0 → то же что R1 { ScanRequest req; // НЕ вызываем set_mode — default=0=SCAN_RANGE req.set_from_key("a"); req.set_to_key("c"); req.set_limit(0); grpc::ClientContext ctx; auto reader = stub->Scan(&ctx, req); std::vector<std::pair<std::string,std::string>> results; ScanResponse r; while (reader->Read(&r)) results.emplace_back(r.key(), r.value()); auto status = reader->Finish(); CHECK(status.ok()); CHECK(results.size() == 3); CHECK(results[0] == std::make_pair(std::string("a"), std::string("1"))); CHECK(results[1] == std::make_pair(std::string("b"), std::string("2"))); CHECK(results[2] == std::make_pair(std::string("c"), std::string("3"))); } // R3: RANGE [b,d] limit=2 → (b,2),(c,3) { ScanRequest req; req.set_mode(sstorage::SCAN_RANGE); req.set_from_key("b"); req.set_to_key("d"); req.set_limit(2); grpc::ClientContext ctx; auto reader = stub->Scan(&ctx, req); std::vector<std::pair<std::string,std::string>> results; ScanResponse r; while (reader->Read(&r)) results.emplace_back(r.key(), r.value()); auto status = reader->Finish(); CHECK(status.ok()); CHECK(results.size() == 2); CHECK(results[0] == std::make_pair(std::string("b"), std::string("2"))); CHECK(results[1] == std::make_pair(std::string("c"), std::string("3"))); } // T1: TOP top_n=3 → (a,1),(b,2),(c,3) { ScanRequest req; req.set_mode(sstorage::SCAN_TOP); req.set_top_n(3); grpc::ClientContext ctx; auto reader = stub->Scan(&ctx, req); std::vector<std::pair<std::string,std::string>> results; ScanResponse r; while (reader->Read(&r)) results.emplace_back(r.key(), r.value()); auto status = reader->Finish(); CHECK(status.ok()); CHECK(results.size() == 3); CHECK(results[0] == std::make_pair(std::string("a"), std::string("1"))); CHECK(results[1] == std::make_pair(std::string("b"), std::string("2"))); CHECK(results[2] == std::make_pair(std::string("c"), std::string("3"))); } // T2: TOP top_n=0 → пустой stream { ScanRequest req; req.set_mode(sstorage::SCAN_TOP); req.set_top_n(0); grpc::ClientContext ctx; auto reader = stub->Scan(&ctx, req); int count = 0; ScanResponse r; while (reader->Read(&r)) ++count; auto status = reader->Finish(); CHECK(status.ok()); CHECK(count == 0); } // T3: TOP top_n=100 (>live) → все 5: a..e { ScanRequest req; req.set_mode(sstorage::SCAN_TOP); req.set_top_n(100); grpc::ClientContext ctx; auto reader = stub->Scan(&ctx, req); std::vector<std::pair<std::string,std::string>> results; ScanResponse r; while (reader->Read(&r)) results.emplace_back(r.key(), r.value()); auto status = reader->Finish(); CHECK(status.ok()); CHECK(results.size() == 5); CHECK(results[0] == std::make_pair(std::string("a"), std::string("1"))); CHECK(results[1] == std::make_pair(std::string("b"), std::string("2"))); CHECK(results[2] == std::make_pair(std::string("c"), std::string("3"))); CHECK(results[3] == std::make_pair(std::string("d"), std::string("4"))); CHECK(results[4] == std::make_pair(std::string("e"), std::string("5"))); } // A1: ALL → все 5 живых ASC { ScanRequest req; req.set_mode(sstorage::SCAN_ALL); grpc::ClientContext ctx; auto reader = stub->Scan(&ctx, req); std::vector<std::pair<std::string,std::string>> results; ScanResponse r; while (reader->Read(&r)) results.emplace_back(r.key(), r.value()); auto status = reader->Finish(); CHECK(status.ok()); CHECK(results.size() == 5); CHECK(results[0] == std::make_pair(std::string("a"), std::string("1"))); CHECK(results[1] == std::make_pair(std::string("b"), std::string("2"))); CHECK(results[2] == std::make_pair(std::string("c"), std::string("3"))); CHECK(results[3] == std::make_pair(std::string("d"), std::string("4"))); CHECK(results[4] == std::make_pair(std::string("e"), std::string("5"))); } // A2: ALL после удаления "c" → 4 живых: a,b,d,e { // Удаляем "c" { DeleteRequest dr; dr.set_key("c"); DeleteResponse dresp; grpc::ClientContext dctx; stub->Delete(&dctx, dr, &dresp); } ScanRequest req; req.set_mode(sstorage::SCAN_ALL); grpc::ClientContext ctx; auto reader = stub->Scan(&ctx, req); std::vector<std::pair<std::string,std::string>> results; ScanResponse r; while (reader->Read(&r)) results.emplace_back(r.key(), r.value()); auto status = reader->Finish(); CHECK(status.ok()); CHECK(results.size() == 4); CHECK(results[0] == std::make_pair(std::string("a"), std::string("1"))); CHECK(results[1] == std::make_pair(std::string("b"), std::string("2"))); CHECK(results[2] == std::make_pair(std::string("d"), std::string("4"))); CHECK(results[3] == std::make_pair(std::string("e"), std::string("5"))); } // 5. Stats { StatsRequest req; StatsResponse resp; grpc::ClientContext ctx; auto s = stub->Stats(&ctx, req, &resp); CHECK(s.ok()); CHECK(resp.memtable_size() > 0); } // 6. Flush / Compact { FlushRequest freq; FlushResponse fresp; grpc::ClientContext fctx; CHECK(stub->Flush(&fctx, freq, &fresp).ok()); CHECK(fresp.ok()); CompactRequest creq; CompactResponse cresp; grpc::ClientContext cctx; CHECK(stub->Compact(&cctx, creq, &cresp).ok()); CHECK(cresp.ok()); } handler.stop(); db.close(); cleanupDir(dir); std::cout << "test_grpc: passed=" << g_passed << " failed=" << g_failed << "\n"; return g_failed == 0 ? 0 : 1; }