xref: /openbmc/libbej/test/bej_decoder_test.cpp (revision 7daa5f8e)
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