forked from lthn/blockchain
364 lines
10 KiB
C++
364 lines
10 KiB
C++
|
|
// Copyright (c) 2025 Zano Project
|
||
|
|
// Distributed under the MIT/X11 software license, see the accompanying
|
||
|
|
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||
|
|
|
||
|
|
#include "epee/include/include_base_utils.h"
|
||
|
|
#include "crypto/crypto.h"
|
||
|
|
#include "crypto/crypto-sugar.h"
|
||
|
|
#include "gtest/gtest.h"
|
||
|
|
#include <boost/filesystem.hpp>
|
||
|
|
#include <boost/filesystem/fstream.hpp>
|
||
|
|
#include <cstdint>
|
||
|
|
#include <vector>
|
||
|
|
|
||
|
|
#include "common/pod_array_file_container.h"
|
||
|
|
|
||
|
|
// helper: returns a unique temp file path
|
||
|
|
static boost::filesystem::path make_temp_file()
|
||
|
|
{
|
||
|
|
return boost::filesystem::temp_directory_path() / boost::filesystem::unique_path("pod_test_%%%%-%%%%.bin");
|
||
|
|
}
|
||
|
|
|
||
|
|
//======================================================================
|
||
|
|
// typed test fixture for pod_array_file_container<T>
|
||
|
|
// container = alias for pod_array_file_container<T>
|
||
|
|
// tmp_path = temp file for test
|
||
|
|
// SetUp() = generate temp path (SetUp from ::testing::Test)
|
||
|
|
// TearDown() = remove temp file (TearDown from ::testing::Test)
|
||
|
|
//======================================================================
|
||
|
|
|
||
|
|
template <typename T>
|
||
|
|
class pod_array_file_typed_test : public ::testing::Test
|
||
|
|
{
|
||
|
|
protected:
|
||
|
|
using container = tools::pod_array_file_container<T>;
|
||
|
|
boost::filesystem::path tmp_path;
|
||
|
|
|
||
|
|
void SetUp() override
|
||
|
|
{
|
||
|
|
tmp_path = make_temp_file();
|
||
|
|
}
|
||
|
|
|
||
|
|
void TearDown() override
|
||
|
|
{
|
||
|
|
if (boost::filesystem::exists(tmp_path))
|
||
|
|
boost::filesystem::remove(tmp_path);
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
// list of integral types to test
|
||
|
|
using integral_types = ::testing::Types<
|
||
|
|
int8_t, uint8_t,
|
||
|
|
int16_t, uint16_t,
|
||
|
|
int32_t, uint32_t,
|
||
|
|
int64_t, uint64_t
|
||
|
|
>;
|
||
|
|
// register typed tests
|
||
|
|
TYPED_TEST_CASE(pod_array_file_typed_test, integral_types);
|
||
|
|
|
||
|
|
// push back and get items:
|
||
|
|
// write values [-1,0,1] and verify they are read back correctly
|
||
|
|
TYPED_TEST(pod_array_file_typed_test, push_back_and_get_items)
|
||
|
|
{
|
||
|
|
typename TestFixture::container c;
|
||
|
|
ASSERT_TRUE(c.open(this->tmp_path.wstring(), true));
|
||
|
|
|
||
|
|
std::vector<TypeParam> values =
|
||
|
|
{
|
||
|
|
static_cast<TypeParam>(-1),
|
||
|
|
0,
|
||
|
|
static_cast<TypeParam>(1)
|
||
|
|
};
|
||
|
|
|
||
|
|
for (auto v : values)
|
||
|
|
ASSERT_TRUE(c.push_back(v));
|
||
|
|
|
||
|
|
EXPECT_EQ(c.size(), values.size());
|
||
|
|
|
||
|
|
TypeParam read_value;
|
||
|
|
for (size_t i = 0; i < values.size(); ++i)
|
||
|
|
{
|
||
|
|
ASSERT_TRUE(c.get_item(i, read_value));
|
||
|
|
EXPECT_EQ(read_value, values[i]);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// ensure get_item returns false for index >= size
|
||
|
|
TYPED_TEST(pod_array_file_typed_test, get_item_out_of_range)
|
||
|
|
{
|
||
|
|
typename TestFixture::container c;
|
||
|
|
ASSERT_TRUE(c.open(this->tmp_path.wstring(), true));
|
||
|
|
|
||
|
|
TypeParam dummy;
|
||
|
|
EXPECT_FALSE(c.get_item(0, dummy));
|
||
|
|
EXPECT_FALSE(c.get_item(100, dummy));
|
||
|
|
}
|
||
|
|
|
||
|
|
// --------------------------------------------------------------------
|
||
|
|
|
||
|
|
typedef uint32_t pod_t;
|
||
|
|
typedef tools::pod_array_file_container<pod_t> pod_container;
|
||
|
|
|
||
|
|
// open fails if not exist without create:
|
||
|
|
// open() false when file missing and create_if_not_exist=false
|
||
|
|
TEST(pod_array_file_container, open_fails_if_not_exist_without_create)
|
||
|
|
{
|
||
|
|
auto path = make_temp_file();
|
||
|
|
pod_container c;
|
||
|
|
std::string reason;
|
||
|
|
bool opened = c.open(path.wstring(), false, nullptr, &reason);
|
||
|
|
EXPECT_FALSE(opened);
|
||
|
|
EXPECT_TRUE(reason.find("not exist") != std::string::npos);
|
||
|
|
}
|
||
|
|
|
||
|
|
// open creates file when not exist:
|
||
|
|
// open() with create flag creates empty file and size=0
|
||
|
|
TEST(pod_array_file_container, open_creates_file_when_not_exist)
|
||
|
|
{
|
||
|
|
auto path = make_temp_file();
|
||
|
|
pod_container c;
|
||
|
|
bool corrupted = true;
|
||
|
|
ASSERT_TRUE(c.open(path.wstring(), true, &corrupted, nullptr));
|
||
|
|
EXPECT_FALSE(corrupted);
|
||
|
|
EXPECT_EQ(c.size(), 0u);
|
||
|
|
c.close();
|
||
|
|
EXPECT_TRUE(boost::filesystem::exists(path));
|
||
|
|
}
|
||
|
|
|
||
|
|
// --------------------------------------------------------------------
|
||
|
|
|
||
|
|
// POD struct with fixed-size fields
|
||
|
|
struct test_struct
|
||
|
|
{
|
||
|
|
crypto::public_key pubkey;
|
||
|
|
crypto::key_image key;
|
||
|
|
};
|
||
|
|
|
||
|
|
typedef tools::pod_array_file_container<test_struct> struct_container;
|
||
|
|
|
||
|
|
// push back and get items struct:
|
||
|
|
// write two test_struct and verify memory equality
|
||
|
|
TEST(pod_array_file_container_struct, push_back_and_get_items_struct)
|
||
|
|
{
|
||
|
|
auto path = make_temp_file();
|
||
|
|
struct_container c;
|
||
|
|
ASSERT_TRUE(c.open(path.wstring(), true));
|
||
|
|
|
||
|
|
test_struct a1{}, a2{};
|
||
|
|
|
||
|
|
// use random values for pubkey and key
|
||
|
|
a1.pubkey = (crypto::scalar_t::random() * crypto::c_point_G).to_public_key();
|
||
|
|
a1.key = (crypto::scalar_t::random() * crypto::c_point_G).to_key_image();
|
||
|
|
a2.pubkey = (crypto::scalar_t::random() * crypto::c_point_G).to_public_key();
|
||
|
|
a2.key = (crypto::scalar_t::random() * crypto::c_point_G).to_key_image();
|
||
|
|
|
||
|
|
ASSERT_TRUE(c.push_back(a1));
|
||
|
|
ASSERT_TRUE(c.push_back(a2));
|
||
|
|
EXPECT_EQ(c.size(), 2u);
|
||
|
|
|
||
|
|
test_struct got;
|
||
|
|
ASSERT_TRUE(c.get_item(0, got));
|
||
|
|
EXPECT_EQ(got.pubkey, a1.pubkey);
|
||
|
|
EXPECT_EQ(got.key, a1.key);
|
||
|
|
ASSERT_TRUE(c.get_item(1, got));
|
||
|
|
EXPECT_EQ(got.pubkey, a2.pubkey);
|
||
|
|
EXPECT_EQ(got.key, a2.key);
|
||
|
|
}
|
||
|
|
|
||
|
|
// get item out of range struct:
|
||
|
|
// get_item false when no items written
|
||
|
|
TEST(pod_array_file_container_struct, get_item_out_of_range_struct)
|
||
|
|
{
|
||
|
|
auto path = make_temp_file();
|
||
|
|
struct_container c;
|
||
|
|
ASSERT_TRUE(c.open(path.wstring(), true));
|
||
|
|
test_struct dummy;
|
||
|
|
EXPECT_FALSE(c.get_item(0, dummy));
|
||
|
|
}
|
||
|
|
|
||
|
|
// corrupted file truncation struct:
|
||
|
|
// simulate corrupted file tail and check truncation flag and size
|
||
|
|
TEST(pod_array_file_container_struct, corrupted_file_truncation_struct)
|
||
|
|
{
|
||
|
|
auto path = make_temp_file();
|
||
|
|
{
|
||
|
|
// write a valid test_struct followed by garbage data
|
||
|
|
boost::filesystem::ofstream out(path, std::ios::binary | std::ios::out);
|
||
|
|
test_struct tmp{};
|
||
|
|
std::fill(std::begin(tmp.pubkey.data), std::end(tmp.pubkey.data), 'X');
|
||
|
|
std::fill(std::begin(tmp.key.data), std::end(tmp.key.data), 'Y');
|
||
|
|
out.write(reinterpret_cast<char*>(&tmp), sizeof(tmp));
|
||
|
|
const char garbage[5] = {1,2,3,4,5};
|
||
|
|
out.write(garbage, sizeof(garbage));
|
||
|
|
}
|
||
|
|
|
||
|
|
struct_container c;
|
||
|
|
bool corrupted = false;
|
||
|
|
std::string reason;
|
||
|
|
ASSERT_TRUE(c.open(path.wstring(), false, &corrupted, &reason));
|
||
|
|
EXPECT_TRUE(corrupted);
|
||
|
|
EXPECT_EQ(c.size(), 1u);
|
||
|
|
|
||
|
|
test_struct got;
|
||
|
|
ASSERT_TRUE(c.get_item(0, got));
|
||
|
|
|
||
|
|
for (size_t i = 0; i < sizeof(got.pubkey.data); ++i)
|
||
|
|
EXPECT_EQ(got.pubkey.data[i], 'X');
|
||
|
|
for (size_t i = 0; i < sizeof(got.key.data); ++i)
|
||
|
|
EXPECT_EQ(got.key.data[i], 'Y');
|
||
|
|
|
||
|
|
EXPECT_TRUE(reason.find("truncated") != std::string::npos);
|
||
|
|
}
|
||
|
|
|
||
|
|
// persistence between opens struct:
|
||
|
|
// write multiple structs, reopen file, verify data persists
|
||
|
|
TEST(pod_array_file_container_struct, persistence_between_opens_struct)
|
||
|
|
{
|
||
|
|
auto path = make_temp_file();
|
||
|
|
{
|
||
|
|
// write 3 test_struct with different pubkey and key values
|
||
|
|
struct_container c;
|
||
|
|
ASSERT_TRUE(c.open(path.wstring(), true));
|
||
|
|
for (int i = 0; i < 3; ++i)
|
||
|
|
{
|
||
|
|
test_struct tmp{};
|
||
|
|
tmp.pubkey.data[0] = '0' + i;
|
||
|
|
tmp.key.data[0] = 'A' + i;
|
||
|
|
ASSERT_TRUE(c.push_back(tmp));
|
||
|
|
}
|
||
|
|
EXPECT_EQ(c.size(), 3u);
|
||
|
|
}
|
||
|
|
|
||
|
|
// reopen and verify data
|
||
|
|
struct_container c2;
|
||
|
|
ASSERT_TRUE(c2.open(path.wstring(), false));
|
||
|
|
EXPECT_EQ(c2.size(), 3u);
|
||
|
|
test_struct got;
|
||
|
|
for (int i = 0; i < 3; ++i)
|
||
|
|
{
|
||
|
|
ASSERT_TRUE(c2.get_item(i, got));
|
||
|
|
EXPECT_EQ(got.pubkey.data[0], '0' + i);
|
||
|
|
EXPECT_EQ(got.key.data[0], 'A' + i);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// size bytes and size:
|
||
|
|
// check size_bytes() matches raw byte count and size() element count
|
||
|
|
TEST(pod_array_file_container, size_bytes_and_size)
|
||
|
|
{
|
||
|
|
auto path = make_temp_file();
|
||
|
|
pod_container c;
|
||
|
|
ASSERT_TRUE(c.open(path.wstring(), true));
|
||
|
|
EXPECT_EQ(c.size_bytes(), 0u);
|
||
|
|
EXPECT_EQ(c.size(), 0u);
|
||
|
|
// push one element
|
||
|
|
pod_t value = 42;
|
||
|
|
ASSERT_TRUE(c.push_back(value));
|
||
|
|
EXPECT_EQ(c.size_bytes(), sizeof(pod_t));
|
||
|
|
EXPECT_EQ(c.size(), 1u);
|
||
|
|
}
|
||
|
|
|
||
|
|
// operations after close:
|
||
|
|
// ensure push_back and get_item fail after close()
|
||
|
|
TEST(pod_array_file_container, operations_after_close)
|
||
|
|
{
|
||
|
|
auto path = make_temp_file();
|
||
|
|
pod_container c;
|
||
|
|
ASSERT_TRUE(c.open(path.wstring(), true));
|
||
|
|
ASSERT_TRUE(c.push_back(123u));
|
||
|
|
c.close();
|
||
|
|
// after close, operations should return false
|
||
|
|
EXPECT_FALSE(c.push_back(456u));
|
||
|
|
pod_t dummy = 0;
|
||
|
|
EXPECT_FALSE(c.get_item(0, dummy));
|
||
|
|
}
|
||
|
|
|
||
|
|
// open fails if cannot open (directory):
|
||
|
|
// attempt to open a directory path should fail with "file could not be opened"
|
||
|
|
TEST(pod_array_file_container, open_fails_if_cannot_open)
|
||
|
|
{
|
||
|
|
// create a directory instead of a file
|
||
|
|
auto dir_path = make_temp_file();
|
||
|
|
boost::filesystem::create_directory(dir_path);
|
||
|
|
pod_container c;
|
||
|
|
std::string reason;
|
||
|
|
bool opened = c.open(dir_path.wstring(), true, nullptr, &reason);
|
||
|
|
EXPECT_FALSE(opened);
|
||
|
|
EXPECT_TRUE(reason.find("could not be opened") != std::string::npos);
|
||
|
|
boost::filesystem::remove(dir_path);
|
||
|
|
}
|
||
|
|
|
||
|
|
// corrupted file truncation uint32:
|
||
|
|
// simulate corrupted file tail on uint32_t and check truncation
|
||
|
|
TEST(pod_array_file_container, corrupted_file_truncation_uint32)
|
||
|
|
{
|
||
|
|
auto path = make_temp_file();
|
||
|
|
{
|
||
|
|
boost::filesystem::ofstream out(path, std::ios::binary | std::ios::out);
|
||
|
|
pod_t v = 0x12345678u;
|
||
|
|
out.write(reinterpret_cast<char*>(&v), sizeof(v));
|
||
|
|
const char junk[3] = {9,8,7};
|
||
|
|
out.write(junk, sizeof(junk));
|
||
|
|
}
|
||
|
|
pod_container c;
|
||
|
|
bool corrupted = false;
|
||
|
|
std::string reason;
|
||
|
|
ASSERT_TRUE(c.open(path.wstring(), false, &corrupted, &reason));
|
||
|
|
EXPECT_TRUE(corrupted);
|
||
|
|
EXPECT_EQ(c.size(), 1u);
|
||
|
|
pod_t read_v;
|
||
|
|
ASSERT_TRUE(c.get_item(0, read_v));
|
||
|
|
EXPECT_EQ(read_v, 0x12345678u);
|
||
|
|
EXPECT_TRUE(reason.find("truncated") != std::string::npos);
|
||
|
|
}
|
||
|
|
|
||
|
|
// operations without open:
|
||
|
|
// ensure push_back/get_item/size_bytes/size behave when container never opened
|
||
|
|
TEST(pod_array_file_container, operations_without_open)
|
||
|
|
{
|
||
|
|
pod_container c;
|
||
|
|
EXPECT_FALSE(c.push_back(1u));
|
||
|
|
pod_t dummy;
|
||
|
|
EXPECT_FALSE(c.get_item(0, dummy));
|
||
|
|
EXPECT_EQ(c.size_bytes(), 0u);
|
||
|
|
EXPECT_EQ(c.size(), 0u);
|
||
|
|
}
|
||
|
|
|
||
|
|
// checks stream state transitions
|
||
|
|
TEST(pod_array_file_container, is_opened_and_in_good_state)
|
||
|
|
{
|
||
|
|
auto path = make_temp_file();
|
||
|
|
pod_container c;
|
||
|
|
|
||
|
|
// not opened yet
|
||
|
|
EXPECT_FALSE(c.is_opened_and_in_good_state());
|
||
|
|
|
||
|
|
// open for create
|
||
|
|
ASSERT_TRUE(c.open(path.wstring(), true));
|
||
|
|
EXPECT_TRUE(c.is_opened_and_in_good_state());
|
||
|
|
|
||
|
|
// after close
|
||
|
|
c.close();
|
||
|
|
EXPECT_FALSE(c.is_opened_and_in_good_state());
|
||
|
|
}
|
||
|
|
|
||
|
|
// wipes file contents and resets size
|
||
|
|
TEST(pod_array_file_container, clear_resets_file)
|
||
|
|
{
|
||
|
|
auto path = make_temp_file();
|
||
|
|
pod_container c;
|
||
|
|
ASSERT_TRUE(c.open(path.wstring(), true));
|
||
|
|
|
||
|
|
// add some elements
|
||
|
|
ASSERT_TRUE(c.push_back(123u));
|
||
|
|
ASSERT_TRUE(c.push_back(456u));
|
||
|
|
EXPECT_EQ(c.size(), 2u);
|
||
|
|
|
||
|
|
// clear the container
|
||
|
|
ASSERT_TRUE(c.clear());
|
||
|
|
EXPECT_EQ(c.size(), 0u);
|
||
|
|
|
||
|
|
// file should still be usable
|
||
|
|
ASSERT_TRUE(c.push_back(789u));
|
||
|
|
EXPECT_EQ(c.size(), 1u);
|
||
|
|
}
|