/
smychkov
/
SStorage
Обзор
Документация
Войти
/
smychkov
/
SStorage
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
master
tests/test_wal.cpp
189 строк
6 KB
Андрей Смычков
feat: WAL with fsync-per-write + group commit
25 апр 2026, 09:02
25 апр 2026, 09:02
3e7bc7b
Код
Авторство
О чём код?
//============================================================================ // Тесты для WAL (базовые: append, replay, rotate, corruption) //============================================================================ #include "../src/wal/wal.hpp" #include <cstdio> #include <cstdlib> #include <filesystem> #include <fstream> #include <iostream> #include <string> #include <sys/stat.h> #include <thread> #include <unistd.h> #include <vector> 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_" + 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() { // 1. Базовый append + replay { auto dir = makeTempDir("wal_basic"); { WriteAheadLog wal(dir, 1); CHECK(wal.open()); CHECK(wal.append(Record("k1", "v1", 1))); CHECK(wal.append(Record("k2", "v2", 2))); CHECK(wal.append(Record::makeTombstone("k3", 3))); wal.close(); } auto records = WriteAheadLog::replay(WriteAheadLog::buildFilename(dir, 1)); CHECK(records.size() == 3); CHECK(records[0].key() == "k1" && records[0].value() == "v1"); CHECK(records[1].key() == "k2" && records[1].value() == "v2"); CHECK(records[2].key() == "k3" && records[2].isTombstone()); cleanupDir(dir); } // 2. Replay пустого / несуществующего файла — пустой результат { auto records = WriteAheadLog::replay("/tmp/does_not_exist.log"); CHECK(records.empty()); } // 3. Group commit: несколько потоков пишут одновременно { auto dir = makeTempDir("wal_mt"); WriteAheadLog wal(dir, 42); CHECK(wal.open()); constexpr int kThreads = 8; constexpr int kPerThread = 100; std::vector<std::thread> threads; for (int t = 0; t < kThreads; ++t) { threads.emplace_back([&, t]() { for (int i = 0; i < kPerThread; ++i) { std::string key = "t" + std::to_string(t) + "_" + std::to_string(i); wal.append(Record(key, "value", t * 10000 + i)); } }); } for (auto& th : threads) th.join(); wal.close(); auto records = WriteAheadLog::replay(WriteAheadLog::buildFilename(dir, 42)); CHECK(records.size() == kThreads * kPerThread); cleanupDir(dir); } // 4. Повреждение хвоста — replay останавливается корректно { auto dir = makeTempDir("wal_corrupt"); { WriteAheadLog wal(dir, 1); wal.open(); wal.append(Record("good1", "v", 1)); wal.append(Record("good2", "v", 2)); wal.close(); } // Допишем мусор в конец файла { std::ofstream f(WriteAheadLog::buildFilename(dir, 1), std::ios::binary | std::ios::app); const char garbage[] = {0x01, 0x02, 0x03, 0x04, 0x05}; f.write(garbage, sizeof(garbage)); } auto records = WriteAheadLog::replay(WriteAheadLog::buildFilename(dir, 1)); // Две валидные записи должны быть прочитаны, мусор — проигнорирован CHECK(records.size() == 2); CHECK(records[0].key() == "good1"); CHECK(records[1].key() == "good2"); cleanupDir(dir); } // 5. CRC mismatch — запись отбрасывается { auto dir = makeTempDir("wal_crc"); { WriteAheadLog wal(dir, 1); wal.open(); wal.append(Record("key", "value", 1)); wal.close(); } // Испортим один байт в середине файла { std::string path = WriteAheadLog::buildFilename(dir, 1); std::fstream f(path, std::ios::binary | std::ios::in | std::ios::out); f.seekp(10); // где-то в payload char byte = 'X'; f.write(&byte, 1); } auto records = WriteAheadLog::replay(WriteAheadLog::buildFilename(dir, 1)); CHECK(records.empty()); cleanupDir(dir); } // 6. listLogFiles: отсортированный порядок { auto dir = makeTempDir("wal_list"); for (uint64_t n : {5, 1, 3, 10}) { WriteAheadLog wal(dir, n); wal.open(); wal.append(Record("k", "v", n)); wal.close(); } auto files = WriteAheadLog::listLogFiles(dir); CHECK(files.size() == 4); CHECK(files[0].first == 1); CHECK(files[1].first == 3); CHECK(files[2].first == 5); CHECK(files[3].first == 10); cleanupDir(dir); } // 7. remove: удаление файла { auto dir = makeTempDir("wal_remove"); WriteAheadLog wal(dir, 7); wal.open(); wal.append(Record("k", "v", 1)); wal.close(); std::string path = WriteAheadLog::buildFilename(dir, 7); CHECK(WriteAheadLog::remove(path)); // Файл действительно удалён struct stat st; CHECK(::stat(path.c_str(), &st) != 0); cleanupDir(dir); } // 8. Большие записи (много килобайт) { auto dir = makeTempDir("wal_big"); WriteAheadLog wal(dir, 1); wal.open(); std::string big_value(100000, 'X'); CHECK(wal.append(Record("bigkey", big_value, 1))); wal.close(); auto records = WriteAheadLog::replay(WriteAheadLog::buildFilename(dir, 1)); CHECK(records.size() == 1); CHECK(records[0].value() == big_value); cleanupDir(dir); } std::cout << "test_wal: passed=" << g_passed << " failed=" << g_failed << "\n"; return g_failed == 0 ? 0 : 1; }