1 #include "bej_decoder_json.hpp" 2 #include "nlohmann/json.hpp" 3 4 #include <fstream> 5 #include <iostream> 6 #include <memory> 7 #include <optional> 8 #include <span> 9 #include <string_view> 10 11 #include <gmock/gmock-matchers.h> 12 #include <gmock/gmock.h> 13 #include <gtest/gtest.h> 14 15 namespace libbej 16 { 17 18 struct BejTestInputFiles 19 { 20 const char* jsonFile; 21 const char* schemaDictionaryFile; 22 const char* annotationDictionaryFile; 23 const char* errorDictionaryFile; 24 const char* encodedStreamFile; 25 }; 26 27 struct BejTestInputs 28 { 29 const nlohmann::json expectedJson; 30 const uint8_t* schemaDictionary; 31 const uint8_t* annotationDictionary; 32 const uint8_t* errorDictionary; 33 std::span<const uint8_t> encodedStream; 34 }; 35 36 struct BejDecoderTestParams 37 { 38 const std::string testName; 39 const BejTestInputFiles inputFiles; 40 }; 41 42 using BejDecoderTest = testing::TestWithParam<BejDecoderTestParams>; 43 44 const BejTestInputFiles driveOemTestFiles = { 45 .jsonFile = "../test/json/drive_oem.json", 46 .schemaDictionaryFile = "../test/dictionaries/drive_oem_dict.bin", 47 .annotationDictionaryFile = "../test/dictionaries/annotation_dict.bin", 48 .errorDictionaryFile = "", 49 .encodedStreamFile = "../test/encoded/drive_oem_enc.bin", 50 }; 51 52 const BejTestInputFiles circuitTestFiles = { 53 .jsonFile = "../test/json/circuit.json", 54 .schemaDictionaryFile = "../test/dictionaries/circuit_dict.bin", 55 .annotationDictionaryFile = "../test/dictionaries/annotation_dict.bin", 56 .errorDictionaryFile = "", 57 .encodedStreamFile = "../test/encoded/circuit_enc.bin", 58 }; 59 60 const BejTestInputFiles storageTestFiles = { 61 .jsonFile = "../test/json/storage.json", 62 .schemaDictionaryFile = "../test/dictionaries/storage_dict.bin", 63 .annotationDictionaryFile = "../test/dictionaries/annotation_dict.bin", 64 .errorDictionaryFile = "", 65 .encodedStreamFile = "../test/encoded/storage_enc.bin", 66 }; 67 68 const BejTestInputFiles dummySimpleTestFiles = { 69 .jsonFile = "../test/json/dummysimple.json", 70 .schemaDictionaryFile = "../test/dictionaries/dummy_simple_dict.bin", 71 .annotationDictionaryFile = "../test/dictionaries/annotation_dict.bin", 72 .errorDictionaryFile = "", 73 .encodedStreamFile = "../test/encoded/dummy_simple_enc.bin", 74 }; 75 76 // Buffer size for storing a single binary file data. 77 constexpr uint32_t maxBufferSize = 16 * 1024; 78 79 std::streamsize readBinaryFile(const char* fileName, std::span<uint8_t> buffer) 80 { 81 std::ifstream inputStream(fileName, std::ios::binary); 82 if (!inputStream.is_open()) 83 { 84 std::cerr << "Cannot open file: " << fileName << "\n"; 85 return 0; 86 } 87 auto readLength = inputStream.readsome( 88 reinterpret_cast<char*>(buffer.data()), buffer.size_bytes()); 89 if (inputStream.peek() != EOF) 90 { 91 std::cerr << "Failed to read the complete file: " << fileName 92 << " read length: " << readLength << "\n"; 93 return 0; 94 } 95 return readLength; 96 } 97 98 std::optional<BejTestInputs> loadInputs(const BejTestInputFiles& files, 99 bool readErrorDictionary = false) 100 { 101 std::ifstream jsonInput(files.jsonFile); 102 if (!jsonInput.is_open()) 103 { 104 std::cerr << "Cannot open file: " << files.jsonFile << "\n"; 105 return std::nullopt; 106 } 107 nlohmann::json expJson; 108 jsonInput >> expJson; 109 110 static uint8_t schemaDictBuffer[maxBufferSize]; 111 if (readBinaryFile(files.schemaDictionaryFile, 112 std::span(schemaDictBuffer, maxBufferSize)) == 0) 113 { 114 return std::nullopt; 115 } 116 117 static uint8_t annoDictBuffer[maxBufferSize]; 118 if (readBinaryFile(files.annotationDictionaryFile, 119 std::span(annoDictBuffer, maxBufferSize)) == 0) 120 { 121 return std::nullopt; 122 } 123 124 static uint8_t encBuffer[maxBufferSize]; 125 auto encLen = readBinaryFile(files.encodedStreamFile, 126 std::span(encBuffer, maxBufferSize)); 127 if (encLen == 0) 128 { 129 return std::nullopt; 130 } 131 132 static uint8_t errorDict[maxBufferSize]; 133 if (readErrorDictionary) 134 { 135 if (readBinaryFile(files.errorDictionaryFile, 136 std::span(errorDict, maxBufferSize)) == 0) 137 { 138 return std::nullopt; 139 } 140 } 141 142 BejTestInputs inputs = { 143 .expectedJson = expJson, 144 .schemaDictionary = schemaDictBuffer, 145 .annotationDictionary = annoDictBuffer, 146 .errorDictionary = errorDict, 147 .encodedStream = std::span(encBuffer, encLen), 148 }; 149 return inputs; 150 } 151 152 TEST_P(BejDecoderTest, Decode) 153 { 154 const BejDecoderTestParams& test_case = GetParam(); 155 auto inputsOrErr = loadInputs(test_case.inputFiles); 156 EXPECT_TRUE(inputsOrErr); 157 158 BejDictionaries dictionaries = { 159 .schemaDictionary = inputsOrErr->schemaDictionary, 160 .annotationDictionary = inputsOrErr->annotationDictionary, 161 .errorDictionary = inputsOrErr->errorDictionary, 162 }; 163 164 BejDecoderJson decoder; 165 EXPECT_THAT(decoder.decode(dictionaries, inputsOrErr->encodedStream), 0); 166 std::string decoded = decoder.getOutput(); 167 nlohmann::json jsonDecoded = nlohmann::json::parse(decoded); 168 169 // Just comparing nlohmann::json types could lead to errors. It compares the 170 // byte values. So int64 and unit64 comparisons might be incorrect. Eg: 171 // bytes values for -5 and 18446744073709551611 are the same. So compare the 172 // string values. 173 EXPECT_TRUE(jsonDecoded.dump() == inputsOrErr->expectedJson.dump()); 174 } 175 176 /** 177 * TODO: Add more test cases. 178 * - Test Enums inside array elemets 179 * - Array inside an array: is this a valid case? 180 * - Real numbers with exponent part 181 * - Every type inside an array. 182 */ 183 INSTANTIATE_TEST_SUITE_P( 184 , BejDecoderTest, 185 testing::ValuesIn<BejDecoderTestParams>({ 186 {"DriveOEM", driveOemTestFiles}, 187 {"Circuit", circuitTestFiles}, 188 {"Storage", storageTestFiles}, 189 {"DummySimple", dummySimpleTestFiles}, 190 }), 191 [](const testing::TestParamInfo<BejDecoderTest::ParamType>& info) { 192 return info.param.testName; 193 }); 194 195 } // namespace libbej 196