#include "data/store/ChunkedVolumeStore.hpp" #include "core/algo/GprVolumeBuilder.hpp" #include #include #include #include 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((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 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 out(static_cast(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); 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 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); }