forked from lthn/blockchain
unit_tests: added a test for pod array file container, fixed clear() (#535)
* add test for pod array file container * add tests for pod, clear and is open * header update --------- Co-authored-by: sowle <crypto.sowle@gmail.com>
This commit is contained in:
parent
dfbda0a77f
commit
c637e16848
2 changed files with 366 additions and 5 deletions
|
|
@ -137,12 +137,9 @@ namespace tools
|
|||
|
||||
// close and re-open stream with trunc bit
|
||||
m_stream.close();
|
||||
m_stream.open(m_filename, std::ios::binary | std::ios::trunc | std::ios::in);
|
||||
m_stream.open(m_filename, std::ios::binary | std::ios::trunc | std::ios::in | std::ios::out);
|
||||
|
||||
if (m_stream.rdstate() != std::ios::eofbit)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
return is_opened_and_in_good_state();
|
||||
}
|
||||
|
||||
private:
|
||||
|
|
|
|||
364
tests/unit_tests/pod_array_file_container.cpp
Normal file
364
tests/unit_tests/pod_array_file_container.cpp
Normal file
|
|
@ -0,0 +1,364 @@
|
|||
// 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);
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue