xref: /openbmc/libcper/tests/test-utils.cpp (revision 2c4d7b6d4c76eb4568ced8797bb24f7d92e5d7c2)
1 /**
2  * Defines utility functions for testing CPER-JSON IR output from the cper-parse library.
3  *
4  * Author: Lawrence.Tang@arm.com
5  **/
6 
7 #include <cstdio>
8 #include <cstdlib>
9 #include <fstream>
10 #include <filesystem>
11 #include "test-utils.hpp"
12 
13 #include <libcper/BaseTypes.h>
14 #include <libcper/generator/cper-generate.h>
15 
16 namespace fs = std::filesystem;
17 
18 // Objects that have mutually exclusive fields (and thereforce can't have both
19 // required at the same time) can be added to this list.
20 // Truly optional properties that shouldn't be added to "required" field for
21 // validating the entire schema with validationbits=1
22 const static std::map<std::string, std::vector<std::string> >
23 	optional_properties_map = {
24 		{ "./sections/cper-cxl-protocol.json",
25 		  { "capabilityStructure", "deviceSerial" } },
26 		{ "./sections/cper-cxl-component.json",
27 		  { "cxlComponentEventLog" } },
28 		{ "./sections/cper-ia32x64-processor.json",
29 		  { "addressSpace", "errorType", "participationType",
30 		    "timedOut", "level", "operation", "preciseIP",
31 		    "restartableIP", "overflow", "uncorrected",
32 		    "transactionType" } },
33 	};
34 
loadJson(const char * filePath)35 nlohmann::json loadJson(const char *filePath)
36 {
37 	std::ifstream file(filePath);
38 	if (!file.is_open()) {
39 		std::cerr << "Failed to open file: " << filePath << std::endl;
40 	}
41 	nlohmann::json out = nlohmann::json::parse(file, nullptr, false);
42 	return out;
43 }
44 
45 //Returns a ready-for-use memory stream containing a CPER record with the given sections inside.
generate_record_memstream(const char ** types,UINT16 num_types,char ** buf,size_t * buf_size,int single_section,GEN_VALID_BITS_TEST_TYPE validBitsType)46 FILE *generate_record_memstream(const char **types, UINT16 num_types,
47 				char **buf, size_t *buf_size,
48 				int single_section,
49 				GEN_VALID_BITS_TEST_TYPE validBitsType)
50 {
51 	//Open a memory stream.
52 	FILE *stream = open_memstream(buf, buf_size);
53 
54 	//Generate a section to the stream, close & return.
55 	if (!single_section) {
56 		generate_cper_record(const_cast<char **>(types), num_types,
57 				     stream, validBitsType);
58 	} else {
59 		generate_single_section_record(const_cast<char *>(types[0]),
60 					       stream, validBitsType);
61 	}
62 	fclose(stream);
63 
64 	//Return fmemopen() buffer for reading.
65 	return fmemopen(*buf, *buf_size, "r");
66 }
67 
iterate_make_required_props(nlohmann::json & jsonSchema,std::vector<std::string> & optional_props)68 void iterate_make_required_props(nlohmann::json &jsonSchema,
69 				 std::vector<std::string> &optional_props)
70 {
71 	//id
72 	const auto it_id = jsonSchema.find("$id");
73 	if (it_id != jsonSchema.end()) {
74 		auto id_strptr = it_id->get_ptr<const std::string *>();
75 		std::string id_str = *id_strptr;
76 		if (id_str.find("header") != std::string::npos ||
77 		    id_str.find("section-descriptor") != std::string::npos) {
78 			return;
79 		}
80 	}
81 	//oneOf
82 	const auto it_oneof = jsonSchema.find("oneOf");
83 	if (it_oneof != jsonSchema.end()) {
84 		//Iterate over oneOf properties
85 		for (auto &oneOfProp : *it_oneof) {
86 			iterate_make_required_props(oneOfProp, optional_props);
87 		}
88 	}
89 
90 	//items
91 	const auto it_items = jsonSchema.find("items");
92 	if (it_items != jsonSchema.end()) {
93 		iterate_make_required_props(*it_items, optional_props);
94 	}
95 	//required
96 	const auto it_req = jsonSchema.find("required");
97 	if (it_req == jsonSchema.end()) {
98 		return;
99 	}
100 
101 	//properties
102 	const auto it_prop = jsonSchema.find("properties");
103 	if (it_prop == jsonSchema.end()) {
104 		return;
105 	}
106 	nlohmann::json &propertyFields = *it_prop;
107 	nlohmann::json::array_t property_list;
108 	if (propertyFields.is_object()) {
109 		for (auto &[key, value] : propertyFields.items()) {
110 			const auto it_find_opt_prop =
111 				std::find(optional_props.begin(),
112 					  optional_props.end(), key);
113 			if (it_find_opt_prop == optional_props.end()) {
114 				//Add to list if property is not optional
115 				property_list.push_back(key);
116 			}
117 
118 			iterate_make_required_props(value, optional_props);
119 		}
120 	}
121 
122 	*it_req = property_list;
123 }
124 
125 // Document loader callback function
documentLoader(const std::string & uri,AddRequiredProps add_required_props)126 const nlohmann::json *documentLoader(const std::string &uri,
127 				     AddRequiredProps add_required_props)
128 {
129 	// Load the schema from a file
130 	std::unique_ptr<nlohmann::json> ref_schema =
131 		std::make_unique<nlohmann::json>();
132 	*ref_schema = loadJson(uri.c_str());
133 	if (ref_schema->is_discarded()) {
134 		std::cerr << "Could not open schema file: " << uri << std::endl;
135 	}
136 	if (add_required_props == AddRequiredProps::YES) {
137 		std::vector<std::string> opt = {};
138 		const auto it_optional_file = optional_properties_map.find(uri);
139 		if (it_optional_file != optional_properties_map.end()) {
140 			opt = it_optional_file->second;
141 		}
142 		iterate_make_required_props(*ref_schema, opt);
143 	}
144 
145 	return ref_schema.release();
146 }
147 
148 // Document release callback function
documentRelease(const nlohmann::json * adapter)149 void documentRelease(const nlohmann::json *adapter)
150 {
151 	delete adapter; // Free the adapter memory
152 }
153 
154 std::unique_ptr<valijson::Schema>
load_schema(AddRequiredProps add_required_props,int single_section)155 load_schema(AddRequiredProps add_required_props, int single_section)
156 {
157 	// Load the schema
158 	fs::path pathObj(LIBCPER_JSON_SPEC);
159 
160 	if (single_section) {
161 		pathObj /= "cper-json-section-log.json";
162 	} else {
163 		pathObj /= "cper-json-full-log.json";
164 	}
165 	nlohmann::json schema_root = loadJson(pathObj.c_str());
166 	fs::path base_path(LIBCPER_JSON_SPEC);
167 	try {
168 		fs::current_path(base_path);
169 		// std::cout << "Changed directory to: " << fs::current_path()
170 		// 	  << std::endl;
171 	} catch (const fs::filesystem_error &e) {
172 		std::cerr << "Filesystem error: " << e.what() << std::endl;
173 	}
174 
175 	// Parse the json schema into an internal schema format
176 	std::unique_ptr<valijson::Schema> schema =
177 		std::make_unique<valijson::Schema>();
178 	valijson::SchemaParser parser;
179 	valijson::adapters::NlohmannJsonAdapter schemaDocumentAdapter(
180 		schema_root);
181 
182 	// Set up callbacks for resolving external references
183 	try {
184 		parser.populateSchema(
185 			schemaDocumentAdapter, *schema,
186 			[add_required_props](const std::string &uri) {
187 				return documentLoader(uri, add_required_props);
188 			},
189 			documentRelease);
190 	} catch (std::exception &e) {
191 		std::cerr << "Failed to parse schema: " << e.what()
192 			  << std::endl;
193 	}
194 	return schema;
195 }
196 
schema_validate_from_file(const valijson::Schema & schema,nlohmann::json & jsonData,std::string & error_message)197 int schema_validate_from_file(const valijson::Schema &schema,
198 			      nlohmann::json &jsonData,
199 			      std::string &error_message)
200 {
201 	// Perform validation
202 	valijson::Validator validator(valijson::Validator::kStrongTypes);
203 	valijson::ValidationResults results;
204 	valijson::adapters::NlohmannJsonAdapter targetDocumentAdapter(jsonData);
205 	if (!validator.validate(schema, targetDocumentAdapter, &results)) {
206 		std::cerr << "Validation failed." << std::endl;
207 		valijson::ValidationResults::Error error;
208 		unsigned int errorNum = 1;
209 		while (results.popError(error)) {
210 			std::string context;
211 			for (const std::string &str : error.context) {
212 				context += str;
213 			}
214 
215 			std::cout << "Error #" << errorNum << '\n'
216 				  << "  context: " << context << '\n'
217 				  << "  desc:    " << error.description << '\n';
218 			++errorNum;
219 		}
220 		return 0;
221 	}
222 
223 	error_message = "Schema validation successful";
224 	return 1;
225 }
226