geopro/tests/data/store/test_streaming_write.cpp

160 lines
5.7 KiB
C++
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#include "data/store/ChunkedVolumeStore.hpp"
#include "core/algo/GprVolumeBuilder.hpp"
#include <gtest/gtest.h>
#include <filesystem>
#include <cstdint>
#include <vector>
using namespace geopro::data;
namespace {
// 非常量体:体素随 (i,j,k) 变化,确保逐块对拍真正区分块内容(非全相同)。
geopro::core::BuiltI16 makeBuilt(int nx, int ny, int nz) {
geopro::core::BuiltI16 b;
b.vol = geopro::core::ScalarVolumeI16(nx, ny, nz);
for (int k = 0; k < nz; ++k)
for (int j = 0; j < ny; ++j)
for (int i = 0; i < nx; ++i)
b.vol.at(i, j, k) =
static_cast<std::int16_t>((i * 131 + j * 17 + k * 7) % 251);
b.quant = {0.5, -3.0};
b.origin = {{10.0, 20.0, 30.0}};
b.spacing = {{2.0, 3.0, 4.0}};
b.vminPhys = -3.0;
b.vmaxPhys = 122.0;
return b;
}
int ceilDiv(int n, int brick) { return (n + brick - 1) / brick; }
int extent(int n, int b, int brick) {
const int got = n - b * brick;
return got < brick ? got : brick;
}
// 从整卷切出 (bx,by,bz) 块体素(块内 i 最快、k 最慢,与 write 同布局)。
std::vector<std::int16_t> sliceBrickFrom(const geopro::core::BuiltI16& b, int bx,
int by, int bz, int brick) {
const auto& vol = b.vol;
const int bw = extent(vol.nx(), bx, brick);
const int bh = extent(vol.ny(), by, brick);
const int bd = extent(vol.nz(), bz, brick);
std::vector<std::int16_t> out(static_cast<std::size_t>(bw) * bh * bd);
const int i0 = bx * brick, j0 = by * brick, k0 = bz * brick;
std::size_t w = 0;
for (int kk = 0; kk < bd; ++kk)
for (int jj = 0; jj < bh; ++jj)
for (int ii = 0; ii < bw; ++ii)
out[w++] = vol.at(i0 + ii, j0 + jj, k0 + kk);
return out;
}
std::string tmp(const char* name) {
return (std::filesystem::temp_directory_path() / name).string();
}
} // namespace
// 核心验收:流式逐块写 vs 非流式整卷 write逐块 readBrick + meta 完全一致。
TEST(StreamingVolumeWriter, MatchesNonStreamingWrite) {
auto b = makeBuilt(100, 40, 30); // 100/40/30 均非整除 64 → 含边缘块
const int brick = 64;
auto dirA = tmp("swA"), dirB = tmp("swB");
std::filesystem::remove_all(dirA);
std::filesystem::remove_all(dirB);
ChunkedVolumeStore::write(dirA, b, brick); // 金标准
StoreMeta m = ChunkedVolumeStore::readMeta(dirA); // 复用同一 meta 元信息
const int bX = ceilDiv(m.nx, brick);
const int bY = ceilDiv(m.ny, brick);
const int bZ = ceilDiv(m.nz, brick);
{
StreamingVolumeWriter w(dirB, m);
// 故意打乱写入顺序(与 write 的固定遍历顺序不同),验证顺序无关。
for (int bx = 0; bx < bX; ++bx)
for (int by = 0; by < bY; ++by)
for (int bz = 0; bz < bZ; ++bz)
w.writeBrick(bx, by, bz, sliceBrickFrom(b, bx, by, bz, brick));
w.finalize();
}
ChunkedVolumeStore A(dirA), B(dirB);
for (int bz = 0; bz < bZ; ++bz)
for (int by = 0; by < bY; ++by)
for (int bx = 0; bx < bX; ++bx)
EXPECT_EQ(A.readBrick(bx, by, bz), B.readBrick(bx, by, bz))
<< "brick mismatch at " << bx << "," << by << "," << bz;
EXPECT_EQ(B.meta().nx, A.meta().nx);
EXPECT_EQ(B.meta().ny, A.meta().ny);
EXPECT_EQ(B.meta().nz, A.meta().nz);
EXPECT_EQ(B.meta().brick, A.meta().brick);
EXPECT_EQ(B.meta().origin, A.meta().origin);
EXPECT_EQ(B.meta().spacing, A.meta().spacing);
EXPECT_EQ(B.meta().quant.scale, A.meta().quant.scale);
EXPECT_EQ(B.meta().quant.offset, A.meta().quant.offset);
EXPECT_EQ(B.meta().vminPhys, A.meta().vminPhys);
EXPECT_EQ(B.meta().vmaxPhys, A.meta().vmaxPhys);
std::filesystem::remove_all(dirA);
std::filesystem::remove_all(dirB);
}
// 同一块重复写 → 抛异常(约定:每块只写一次)。
TEST(StreamingVolumeWriter, DuplicateBrickThrows) {
auto b = makeBuilt(70, 30, 20);
auto dirA = tmp("swDup");
std::filesystem::remove_all(dirA);
ChunkedVolumeStore::write(dirA, b, 64);
StoreMeta m = ChunkedVolumeStore::readMeta(dirA);
auto dirB = tmp("swDupB");
std::filesystem::remove_all(dirB);
{
// 持久 data.bin 句柄随 writer 生命周期持有,需先析构 writer 再删目录Windows 文件锁)。
StreamingVolumeWriter w(dirB, m);
w.writeBrick(0, 0, 0, sliceBrickFrom(b, 0, 0, 0, 64));
EXPECT_THROW(w.writeBrick(0, 0, 0, sliceBrickFrom(b, 0, 0, 0, 64)),
std::runtime_error);
}
std::filesystem::remove_all(dirA);
std::filesystem::remove_all(dirB);
}
// 缺块 finalize → 抛异常(约定:所有块必须写齐)。
TEST(StreamingVolumeWriter, MissingBrickFinalizeThrows) {
auto b = makeBuilt(70, 30, 20); // bX=2,bY=1,bZ=1 → 共 2 块
auto dirA = tmp("swMiss");
std::filesystem::remove_all(dirA);
ChunkedVolumeStore::write(dirA, b, 64);
StoreMeta m = ChunkedVolumeStore::readMeta(dirA);
auto dirB = tmp("swMissB");
std::filesystem::remove_all(dirB);
{
StreamingVolumeWriter w(dirB, m);
w.writeBrick(0, 0, 0, sliceBrickFrom(b, 0, 0, 0, 64)); // 只写 1 块,缺 (1,0,0)
EXPECT_THROW(w.finalize(), std::runtime_error);
}
std::filesystem::remove_all(dirA);
std::filesystem::remove_all(dirB);
}
// 块体素大小不符bw*bh*bd 不匹配)→ 抛异常。
TEST(StreamingVolumeWriter, WrongVoxelCountThrows) {
auto b = makeBuilt(70, 30, 20);
auto dirA = tmp("swSize");
std::filesystem::remove_all(dirA);
ChunkedVolumeStore::write(dirA, b, 64);
StoreMeta m = ChunkedVolumeStore::readMeta(dirA);
auto dirB = tmp("swSizeB");
std::filesystem::remove_all(dirB);
{
StreamingVolumeWriter w(dirB, m);
std::vector<std::int16_t> bad(10); // 远小于 64*30*20
EXPECT_THROW(w.writeBrick(0, 0, 0, bad), std::runtime_error);
}
std::filesystem::remove_all(dirA);
std::filesystem::remove_all(dirB);
}