#include "BinaryDict.hpp"
#include "TextDictTestBase.hpp"
namespace opencc {
class BinaryDictTest : public TextDictTestBase {
protected:
BinaryDictTest()
: binDict(new BinaryDict(textDict->GetLexicon())), fileName("dict.bin"){};
template <typename FIELD>
static std::string WriteCraftedBinaryDict(
uint64_t numItems, uint64_t keyTotalLength, const std::string& keyBuffer,
uint64_t valueTotalLength, const std::string& valueBuffer,
const std::vector<std::tuple<uint64_t, uint64_t, std::vector<uint64_t>>>&
items) {
const std::string path = "crafted_binary_dict.bin";
FILE* fp = fopen(path.c_str(), "wb");
const auto writeField = [fp](uint64_t value) {
FIELD field = static_cast<FIELD>(value);
fwrite(&field, sizeof(field), 1, fp);
};
writeField(numItems);
writeField(keyTotalLength);
fwrite(keyBuffer.data(), sizeof(char), keyBuffer.size(), fp);
writeField(valueTotalLength);
fwrite(valueBuffer.data(), sizeof(char), valueBuffer.size(), fp);
for (const auto& [numValues, keyOffset, valueOffsets] : items) {
writeField(numValues);
writeField(keyOffset);
for (uint64_t vo : valueOffsets) {
writeField(vo);
}
}
fclose(fp);
return path;
}
const BinaryDictPtr binDict;
const std::string fileName;
};
TEST_F(BinaryDictTest, Serialization) {
binDict->opencc::SerializableDict::SerializeToFile(fileName);
}
TEST_F(BinaryDictTest, Deserialization) {
const BinaryDictPtr& deserialized =
SerializableDict::NewFromFile<BinaryDict>(fileName);
const LexiconPtr& lex1 = binDict->GetLexicon();
const LexiconPtr& lex2 = deserialized->GetLexicon();
EXPECT_EQ(lex1->Length(), lex2->Length());
for (size_t i = 0; i < lex1->Length(); i++) {
EXPECT_EQ(lex1->At(i)->Key(), lex2->At(i)->Key());
EXPECT_EQ(lex1->At(i)->NumValues(), lex2->At(i)->NumValues());
}
const TextDictPtr deserializedTextDict(new TextDict(lex2));
TestDict(deserializedTextDict);
}
TEST_F(BinaryDictTest, SerializedLayoutIsWordSizeIndependent) {
binDict->opencc::SerializableDict::SerializeToFile(fileName);
FILE* fp = fopen(fileName.c_str(), "rb");
ASSERT_NE(fp, nullptr);
fseek(fp, 0L, SEEK_END);
const size_t fileSize = static_cast<size_t>(ftell(fp));
fseek(fp, 0L, SEEK_SET);
uint32_t numItems;
ASSERT_EQ(fread(&numItems, sizeof(numItems), 1, fp), 1U);
fclose(fp);
EXPECT_EQ(numItems, binDict->GetLexicon()->Length());
size_t expectedSize = 3 * sizeof(uint32_t);
for (const auto& entry : *binDict->GetLexicon()) {
expectedSize += entry->Key().length() + 1; expectedSize += 2 * sizeof(uint32_t); expectedSize += entry->NumValues() * sizeof(uint32_t); for (const std::string& value : entry->Values()) {
expectedSize += value.length() + 1; }
}
EXPECT_EQ(fileSize, expectedSize);
}
TEST_F(BinaryDictTest, EmptyDictRoundTrip) {
const std::string path = "empty_binary_dict.bin";
const BinaryDictPtr emptyDict(new BinaryDict(LexiconPtr(new Lexicon)));
emptyDict->opencc::SerializableDict::SerializeToFile(path);
FILE* fp = fopen(path.c_str(), "rb");
ASSERT_NE(fp, nullptr);
fseek(fp, 0L, SEEK_END);
EXPECT_EQ(ftell(fp), static_cast<long>(3 * sizeof(uint32_t)));
fclose(fp);
const auto deserialized = SerializableDict::NewFromFile<BinaryDict>(path);
EXPECT_EQ(deserialized->GetLexicon()->Length(), 0U);
std::remove(path.c_str());
}
TEST_F(BinaryDictTest, RejectsHugeKeyTotalLength) {
std::string path = WriteCraftedBinaryDict<uint64_t>(
1, 0x7000000000ULL, "", 0, "", {});
EXPECT_THROW(SerializableDict::NewFromFile<BinaryDict>(path), InvalidFormat);
std::remove(path.c_str());
path = WriteCraftedBinaryDict<uint32_t>(1, 0x70000000ULL, "", 0, "", {});
EXPECT_THROW(SerializableDict::NewFromFile<BinaryDict>(path), InvalidFormat);
std::remove(path.c_str());
}
TEST_F(BinaryDictTest, RejectsHugeValueTotalLength) {
std::string keyBuf = {'k', '\0'};
std::string path = WriteCraftedBinaryDict<uint64_t>(
1, 2, keyBuf, 0x7000000000ULL, "", {});
EXPECT_THROW(SerializableDict::NewFromFile<BinaryDict>(path), InvalidFormat);
std::remove(path.c_str());
path = WriteCraftedBinaryDict<uint32_t>(1, 2, keyBuf, 0x70000000ULL, "", {});
EXPECT_THROW(SerializableDict::NewFromFile<BinaryDict>(path), InvalidFormat);
std::remove(path.c_str());
}
TEST_F(BinaryDictTest, RejectsKeyOffsetOutOfBounds) {
std::string keyBuf = {'h', 'i', '\0'};
std::string valBuf = {'v', '\0'};
std::string path = WriteCraftedBinaryDict<uint32_t>(
1, 3, keyBuf, 2, valBuf, {{1, 100, {0}}});
EXPECT_THROW(SerializableDict::NewFromFile<BinaryDict>(path), InvalidFormat);
std::remove(path.c_str());
}
TEST_F(BinaryDictTest, RejectsValueOffsetOutOfBounds) {
std::string keyBuf = {'h', 'i', '\0'};
std::string valBuf = {'v', '\0'};
std::string path = WriteCraftedBinaryDict<uint32_t>(
1, 3, keyBuf, 2, valBuf, {{1, 0, {100}}});
EXPECT_THROW(SerializableDict::NewFromFile<BinaryDict>(path), InvalidFormat);
std::remove(path.c_str());
}
TEST_F(BinaryDictTest, AcceptsWellFormedFile) {
std::string keyBuf = {'h', 'i', '\0'};
std::string valBuf = {'v', '\0'};
std::string path = WriteCraftedBinaryDict<uint32_t>(
1, 3, keyBuf, 2, valBuf, {{1, 0, {0}}});
const auto deserialized = SerializableDict::NewFromFile<BinaryDict>(path);
EXPECT_EQ(deserialized->GetLexicon()->Length(), 1);
std::remove(path.c_str());
}
TEST_F(BinaryDictTest, AcceptsLegacy64BitLayout) {
std::string keyBuf = {'h', 'i', '\0'};
std::string valBuf = {'v', '\0'};
std::string path = WriteCraftedBinaryDict<uint64_t>(
1, 3, keyBuf, 2, valBuf, {{1, 0, {0}}});
const auto deserialized = SerializableDict::NewFromFile<BinaryDict>(path);
ASSERT_EQ(deserialized->GetLexicon()->Length(), 1);
EXPECT_EQ(deserialized->GetLexicon()->At(0)->Key(), "hi");
std::remove(path.c_str());
}
}