blob: b38b36c24b8c9ca3f7a97251fdd357dbe2b0d71c [file] [log] [blame]
Lawrence Tangd34f2b12022-07-19 15:36:31 +01001/**
2 * Defines utility functions for testing CPER-JSON IR output from the cper-parse library.
Ed Tanousfedd4572024-07-12 13:56:00 -07003 *
Lawrence Tangd34f2b12022-07-19 15:36:31 +01004 * Author: Lawrence.Tang@arm.com
5 **/
6
John Chungf8fc7052024-05-03 20:05:29 +08007#include <cstdio>
8#include <cstdlib>
Aushim Nagarkattiae8f6d92025-01-29 17:34:44 -08009#include <fstream>
10#include <filesystem>
Lawrence Tangd34f2b12022-07-19 15:36:31 +010011#include "test-utils.hpp"
Karthik Rajagopalan255bd812024-09-06 14:36:34 -070012
Thu Nguyene42fb482024-10-15 14:43:11 +000013#include <libcper/BaseTypes.h>
14#include <libcper/generator/cper-generate.h>
Lawrence Tangd34f2b12022-07-19 15:36:31 +010015
Aushim Nagarkattiae8f6d92025-01-29 17:34:44 -080016namespace fs = std::filesystem;
17
18// Truly optional properties that shouldn't be added to "required" field for
19// validating the entire schema with validationbits=1
20const static std::map<std::string, std::vector<std::string> >
21 optional_properties_map = {
22 { "./sections/cper-arm-processor.json",
23 { "vendorSpecificInfo" } },
24 { "./cper-json-section-log.json", { "header" } },
25 { "./sections/cper-cxl-protocol.json",
26 { "capabilityStructure", "deviceSerial" } },
27 { "./sections/cper-generic-dmar.json",
28 { "faultReason", "description" } },
29 { "./sections/cper-cxl-component.json",
30 { "cxlComponentEventLog" } },
Ed Tanousd6b62632025-03-14 15:30:07 -070031 { "./sections/cper-ia32x64-processor.json",
32 { "addressSpace", "errorType", "participationType",
33 "timedOut", "level", "operation", "preciseIP",
34 "restartableIP", "overflow", "uncorrected",
35 "transactionType" } },
Aushim Nagarkattiae8f6d92025-01-29 17:34:44 -080036 };
37
38nlohmann::json loadJson(const char *filePath)
39{
40 std::ifstream file(filePath);
41 if (!file.is_open()) {
42 std::cerr << "Failed to open file: " << filePath << std::endl;
43 }
44 nlohmann::json out = nlohmann::json::parse(file, nullptr, false);
45 return out;
46}
47
Lawrence Tangd34f2b12022-07-19 15:36:31 +010048//Returns a ready-for-use memory stream containing a CPER record with the given sections inside.
Lawrence Tange407b4c2022-07-21 13:54:01 +010049FILE *generate_record_memstream(const char **types, UINT16 num_types,
John Chungf8fc7052024-05-03 20:05:29 +080050 char **buf, size_t *buf_size,
Aushim Nagarkattiae8f6d92025-01-29 17:34:44 -080051 int single_section,
52 GEN_VALID_BITS_TEST_TYPE validBitsType)
Lawrence Tangd34f2b12022-07-19 15:36:31 +010053{
Lawrence Tange407b4c2022-07-21 13:54:01 +010054 //Open a memory stream.
55 FILE *stream = open_memstream(buf, buf_size);
Lawrence Tangd34f2b12022-07-19 15:36:31 +010056
Lawrence Tange407b4c2022-07-21 13:54:01 +010057 //Generate a section to the stream, close & return.
John Chungf8fc7052024-05-03 20:05:29 +080058 if (!single_section) {
59 generate_cper_record(const_cast<char **>(types), num_types,
Aushim Nagarkattiae8f6d92025-01-29 17:34:44 -080060 stream, validBitsType);
John Chungf8fc7052024-05-03 20:05:29 +080061 } else {
62 generate_single_section_record(const_cast<char *>(types[0]),
Aushim Nagarkattiae8f6d92025-01-29 17:34:44 -080063 stream, validBitsType);
John Chungf8fc7052024-05-03 20:05:29 +080064 }
Lawrence Tange407b4c2022-07-21 13:54:01 +010065 fclose(stream);
Lawrence Tangd34f2b12022-07-19 15:36:31 +010066
Lawrence Tange407b4c2022-07-21 13:54:01 +010067 //Return fmemopen() buffer for reading.
68 return fmemopen(*buf, *buf_size, "r");
John Chungf8fc7052024-05-03 20:05:29 +080069}
Aushim Nagarkattiae8f6d92025-01-29 17:34:44 -080070
71void iterate_make_required_props(nlohmann::json &jsonSchema,
72 std::vector<std::string> &optional_props)
73{
74 //id
75 const auto it_id = jsonSchema.find("$id");
76 if (it_id != jsonSchema.end()) {
77 auto id_strptr = it_id->get_ptr<const std::string *>();
78 std::string id_str = *id_strptr;
79 if (id_str.find("header") != std::string::npos ||
80 id_str.find("section-descriptor") != std::string::npos) {
81 return;
82 }
83 }
84 //oneOf
85 const auto it_oneof = jsonSchema.find("oneOf");
86 if (it_oneof != jsonSchema.end()) {
87 //Iterate over oneOf properties
88 for (auto &oneOfProp : *it_oneof) {
89 iterate_make_required_props(oneOfProp, optional_props);
90 }
91 }
92
93 //items
94 const auto it_items = jsonSchema.find("items");
95 if (it_items != jsonSchema.end()) {
96 iterate_make_required_props(*it_items, optional_props);
97 }
98 //required
99 const auto it_req = jsonSchema.find("required");
100 if (it_req == jsonSchema.end()) {
101 return;
102 }
103
104 //properties
105 const auto it_prop = jsonSchema.find("properties");
106 if (it_prop == jsonSchema.end()) {
107 return;
108 }
109 nlohmann::json &propertyFields = *it_prop;
110 nlohmann::json::array_t property_list;
111 if (propertyFields.is_object()) {
112 for (auto &[key, value] : propertyFields.items()) {
113 const auto it_find_opt_prop =
114 std::find(optional_props.begin(),
115 optional_props.end(), key);
116 if (it_find_opt_prop == optional_props.end()) {
117 //Add to list if property is not optional
118 property_list.push_back(key);
119 }
120
121 iterate_make_required_props(value, optional_props);
122 }
123 }
124
125 *it_req = property_list;
126}
127
128// Document loader callback function
Ed Tanousd6b62632025-03-14 15:30:07 -0700129const nlohmann::json *documentLoader(const std::string &uri,
130 AddRequiredProps add_required_props)
Aushim Nagarkattiae8f6d92025-01-29 17:34:44 -0800131{
132 // Load the schema from a file
Ed Tanousd6b62632025-03-14 15:30:07 -0700133 std::unique_ptr<nlohmann::json> ref_schema =
134 std::make_unique<nlohmann::json>();
Aushim Nagarkattiae8f6d92025-01-29 17:34:44 -0800135 *ref_schema = loadJson(uri.c_str());
136 if (ref_schema->is_discarded()) {
137 std::cerr << "Could not open schema file: " << uri << std::endl;
138 }
Ed Tanousd6b62632025-03-14 15:30:07 -0700139 if (add_required_props == AddRequiredProps::YES) {
140 std::vector<std::string> opt = {};
141 const auto it_optional_file = optional_properties_map.find(uri);
142 if (it_optional_file != optional_properties_map.end()) {
143 opt = it_optional_file->second;
144 }
145 iterate_make_required_props(*ref_schema, opt);
Aushim Nagarkattiae8f6d92025-01-29 17:34:44 -0800146 }
Aushim Nagarkattiae8f6d92025-01-29 17:34:44 -0800147
Ed Tanousd6b62632025-03-14 15:30:07 -0700148 return ref_schema.release();
Aushim Nagarkattiae8f6d92025-01-29 17:34:44 -0800149}
150
151// Document release callback function
152void documentRelease(const nlohmann::json *adapter)
153{
154 delete adapter; // Free the adapter memory
155}
156
Ed Tanousd6b62632025-03-14 15:30:07 -0700157std::unique_ptr<valijson::Schema>
158load_schema(AddRequiredProps add_required_props, int single_section)
Aushim Nagarkattiae8f6d92025-01-29 17:34:44 -0800159{
160 // Load the schema
Ed Tanousd6b62632025-03-14 15:30:07 -0700161 fs::path pathObj(LIBCPER_JSON_SPEC);
Aushim Nagarkattiae8f6d92025-01-29 17:34:44 -0800162
Ed Tanousd6b62632025-03-14 15:30:07 -0700163 if (single_section) {
164 pathObj /= "cper-json-section-log.json";
165 } else {
166 pathObj /= "cper-json-full-log.json";
167 }
168 nlohmann::json schema_root = loadJson(pathObj.c_str());
169 fs::path base_path(LIBCPER_JSON_SPEC);
Aushim Nagarkattiae8f6d92025-01-29 17:34:44 -0800170 try {
171 fs::current_path(base_path);
172 // std::cout << "Changed directory to: " << fs::current_path()
173 // << std::endl;
174 } catch (const fs::filesystem_error &e) {
175 std::cerr << "Filesystem error: " << e.what() << std::endl;
176 }
177
178 // Parse the json schema into an internal schema format
Ed Tanousd6b62632025-03-14 15:30:07 -0700179 std::unique_ptr<valijson::Schema> schema =
180 std::make_unique<valijson::Schema>();
Aushim Nagarkattiae8f6d92025-01-29 17:34:44 -0800181 valijson::SchemaParser parser;
182 valijson::adapters::NlohmannJsonAdapter schemaDocumentAdapter(
183 schema_root);
184
185 // Set up callbacks for resolving external references
186 try {
Ed Tanousd6b62632025-03-14 15:30:07 -0700187 parser.populateSchema(
188 schemaDocumentAdapter, *schema,
189 [add_required_props](const std::string &uri) {
190 return documentLoader(uri, add_required_props);
191 },
192 documentRelease);
Aushim Nagarkattiae8f6d92025-01-29 17:34:44 -0800193 } catch (std::exception &e) {
194 std::cerr << "Failed to parse schema: " << e.what()
195 << std::endl;
Aushim Nagarkattiae8f6d92025-01-29 17:34:44 -0800196 }
Ed Tanousd6b62632025-03-14 15:30:07 -0700197 return schema;
198}
Aushim Nagarkattiae8f6d92025-01-29 17:34:44 -0800199
Ed Tanousd6b62632025-03-14 15:30:07 -0700200int schema_validate_from_file(const valijson::Schema &schema,
201 nlohmann::json &jsonData,
202 std::string &error_message)
203{
Aushim Nagarkattiae8f6d92025-01-29 17:34:44 -0800204 // Perform validation
205 valijson::Validator validator(valijson::Validator::kStrongTypes);
206 valijson::ValidationResults results;
207 valijson::adapters::NlohmannJsonAdapter targetDocumentAdapter(jsonData);
208 if (!validator.validate(schema, targetDocumentAdapter, &results)) {
209 std::cerr << "Validation failed." << std::endl;
210 valijson::ValidationResults::Error error;
211 unsigned int errorNum = 1;
212 while (results.popError(error)) {
213 std::string context;
Ed Tanousbadae112025-03-10 13:38:51 -0700214 for (const std::string &str : error.context) {
215 context += str;
Aushim Nagarkattiae8f6d92025-01-29 17:34:44 -0800216 }
217
Ed Tanousbadae112025-03-10 13:38:51 -0700218 std::cout << "Error #" << errorNum << '\n'
219 << " context: " << context << '\n'
220 << " desc: " << error.description << '\n';
Aushim Nagarkattiae8f6d92025-01-29 17:34:44 -0800221 ++errorNum;
222 }
223 return 0;
224 }
225
226 error_message = "Schema validation successful";
227 return 1;
228}