#include "data_source_sqlite.h"
#include <atomic>
#include <cstdlib>
#include <filesystem>
#include <gtest/gtest.h>
#include <thread>
#include <unistd.h>
namespace
{
TEST(DataSourceSqlite, History)
{
auto opened = DataSourceSqlite::open(":memory:");
ASSERT_TRUE(opened.has_value()) << "Open memory database";
DataSourceInterface& source = **opened;
ASSERT_TRUE(!source.latest("missing").value()) << "Missing latest result";
ASSERT_TRUE(source.history("missing", 0, 100).value().empty())
<< "Missing history";
const std::string service_id = "blog'; DROP TABLE status_history; --";
const StatusRecord newer{service_id, 2200000000, 5000000000,
ProbeStatus::GOOD};
const StatusRecord older{service_id, 2199999999, 0, ProbeStatus::BAD};
const StatusRecord tied{service_id, 2200000000, 42, ProbeStatus::OTHER};
ASSERT_TRUE(source.save(newer).has_value()) << "Save 64-bit fields";
ASSERT_TRUE(source.save(older).has_value()) << "Save out of order";
ASSERT_TRUE(source.save(tied).has_value()) << "Retain same-second results";
ASSERT_TRUE(source.save({"other", 2200000001, 1, ProbeStatus::GOOD})
.has_value()) << "Save separate service";
ASSERT_TRUE(source.latest(service_id).value().value() == tied)
<< "Latest uses timestamp and insertion order";
const auto rows = source.history(service_id, 2199999999, 2200000001)
.value();
ASSERT_TRUE((rows == std::vector<StatusRecord>{older, newer, tied}))
<< "Ordered history and bound service ID";
ASSERT_TRUE((source.history(service_id, 2199999999, 2200000000).value() ==
std::vector<StatusRecord>{older})) << "Exclusive upper bound";
ASSERT_TRUE(source.history(service_id, 0, 0).value().empty())
<< "Empty range";
ASSERT_TRUE(!source.history(service_id, 1, 0)) << "Reject reversed bounds";
ASSERT_TRUE(!source.save({"", 0, 0, ProbeStatus::GOOD}))
<< "Reject empty ID";
ASSERT_TRUE(!source.save({"x", 0, -1, ProbeStatus::GOOD}))
<< "Reject negative duration";
ASSERT_TRUE(!source.save({"x", 0, 0, static_cast<ProbeStatus>(3)}))
<< "Reject former NA value";
ASSERT_TRUE(!source.save({"x", 0, 0, static_cast<ProbeStatus>(99)}))
<< "Reject invalid status";
ASSERT_TRUE(!DataSourceSqlite::open("")) << "Reject empty path";
}
void writeResults(DataSourceInterface& source, std::atomic<bool>& success,
int worker)
{
for(int i = 0; i < 100; ++i)
{
if(!source.save({"service", worker * 100 + i, 1,
ProbeStatus::GOOD}) || !source.latest("service"))
{
success = false;
}
}
}
TEST(DataSourceSqlite, ConcurrentAccess)
{
auto source = DataSourceSqlite::open(":memory:").value();
std::atomic<bool> success{true};
std::vector<std::thread> workers;
for(int worker = 0; worker < 8; ++worker)
{
workers.emplace_back(writeResults, std::ref(*source),
std::ref(success), worker);
}
for(auto& worker : workers)
{
worker.join();
}
EXPECT_TRUE(success.load()) << "Concurrent reads and writes";
ASSERT_TRUE(source->history("service", 0, 800).value().size() == 800)
<< "Concurrent writes retain all results";
}
class DataSourceSqliteFile : public testing::Test
{
protected:
void SetUp() override
{
path = (std::filesystem::temp_directory_path() /
"status_tracker_XXXXXX").string();
const int descriptor = mkstemp(path.data());
ASSERT_GE(descriptor, 0);
close(descriptor);
}
void TearDown() override
{
std::error_code error;
std::filesystem::remove(path, error);
std::filesystem::remove(path + "-wal", error);
std::filesystem::remove(path + "-shm", error);
}
std::string path;
};
TEST_F(DataSourceSqliteFile, Persistence)
{
const StatusRecord record{"persistent", 123, 456, ProbeStatus::GOOD};
{
auto opened = DataSourceSqlite::open(path);
ASSERT_TRUE(opened) << opened.error().msg();
auto& source = *opened;
ASSERT_TRUE(source->save(record));
auto second = DataSourceSqlite::open(path);
ASSERT_TRUE(second) << second.error().msg();
auto latest = (*second)->latest("persistent");
ASSERT_TRUE(latest);
ASSERT_TRUE(*latest);
EXPECT_EQ(**latest, record);
}
{
auto opened = DataSourceSqlite::open(path);
ASSERT_TRUE(opened) << opened.error().msg();
auto latest = (*opened)->latest("persistent");
ASSERT_TRUE(latest);
ASSERT_TRUE(*latest);
EXPECT_EQ(**latest, record);
}
EXPECT_FALSE(DataSourceSqlite::open(path + "/missing.sqlite"));
}
}