xref: /openbmc/libcper/tests/test-utils.cpp (revision ae8f6d9aaeaf37332e8924dd2c0b6f320335548c)
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 // Truly optional properties that shouldn't be added to "required" field for
19 // validating the entire schema with validationbits=1
20 const 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" } },
31 	};
32 
loadJson(const char * filePath)33 nlohmann::json loadJson(const char *filePath)
34 {
35 	std::ifstream file(filePath);
36 	if (!file.is_open()) {
37 		std::cerr << "Failed to open file: " << filePath << std::endl;
38 	}
39 	nlohmann::json out = nlohmann::json::parse(file, nullptr, false);
40 	return out;
41 }
42 
43 //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)44 FILE *generate_record_memstream(const char **types, UINT16 num_types,
45 				char **buf, size_t *buf_size,
46 				int single_section,
47 				GEN_VALID_BITS_TEST_TYPE validBitsType)
48 {
49 	//Open a memory stream.
50 	FILE *stream = open_memstream(buf, buf_size);
51 
52 	//Generate a section to the stream, close & return.
53 	if (!single_section) {
54 		generate_cper_record(const_cast<char **>(types), num_types,
55 				     stream, validBitsType);
56 	} else {
57 		generate_single_section_record(const_cast<char *>(types[0]),
58 					       stream, validBitsType);
59 	}
60 	fclose(stream);
61 
62 	//Return fmemopen() buffer for reading.
63 	return fmemopen(*buf, *buf_size, "r");
64 }
65 
iterate_make_required_props(nlohmann::json & jsonSchema,std::vector<std::string> & optional_props)66 void iterate_make_required_props(nlohmann::json &jsonSchema,
67 				 std::vector<std::string> &optional_props)
68 {
69 	//id
70 	const auto it_id = jsonSchema.find("$id");
71 	if (it_id != jsonSchema.end()) {
72 		auto id_strptr = it_id->get_ptr<const std::string *>();
73 		std::string id_str = *id_strptr;
74 		if (id_str.find("header") != std::string::npos ||
75 		    id_str.find("section-descriptor") != std::string::npos) {
76 			return;
77 		}
78 	}
79 	//oneOf
80 	const auto it_oneof = jsonSchema.find("oneOf");
81 	if (it_oneof != jsonSchema.end()) {
82 		//Iterate over oneOf properties
83 		for (auto &oneOfProp : *it_oneof) {
84 			iterate_make_required_props(oneOfProp, optional_props);
85 		}
86 	}
87 
88 	//items
89 	const auto it_items = jsonSchema.find("items");
90 	if (it_items != jsonSchema.end()) {
91 		iterate_make_required_props(*it_items, optional_props);
92 	}
93 	//required
94 	const auto it_req = jsonSchema.find("required");
95 	if (it_req == jsonSchema.end()) {
96 		return;
97 	}
98 
99 	//properties
100 	const auto it_prop = jsonSchema.find("properties");
101 	if (it_prop == jsonSchema.end()) {
102 		return;
103 	}
104 	nlohmann::json &propertyFields = *it_prop;
105 	nlohmann::json::array_t property_list;
106 	if (propertyFields.is_object()) {
107 		for (auto &[key, value] : propertyFields.items()) {
108 			const auto it_find_opt_prop =
109 				std::find(optional_props.begin(),
110 					  optional_props.end(), key);
111 			if (it_find_opt_prop == optional_props.end()) {
112 				//Add to list if property is not optional
113 				property_list.push_back(key);
114 			}
115 
116 			iterate_make_required_props(value, optional_props);
117 		}
118 	}
119 
120 	*it_req = property_list;
121 }
122 
123 // Document loader callback function
documentLoader(const std::string & uri)124 const nlohmann::json *documentLoader(const std::string &uri)
125 {
126 	// Load the schema from a file
127 	nlohmann::json *ref_schema = new nlohmann::json;
128 	*ref_schema = loadJson(uri.c_str());
129 	if (ref_schema->is_discarded()) {
130 		std::cerr << "Could not open schema file: " << uri << std::endl;
131 	}
132 	std::vector<std::string> opt = {};
133 	const auto it_optional_file = optional_properties_map.find(uri);
134 	if (it_optional_file != optional_properties_map.end()) {
135 		opt = it_optional_file->second;
136 	}
137 	iterate_make_required_props(*ref_schema, opt);
138 
139 	return ref_schema;
140 }
141 
142 // Document release callback function
documentRelease(const nlohmann::json * adapter)143 void documentRelease(const nlohmann::json *adapter)
144 {
145 	delete adapter; // Free the adapter memory
146 }
147 
schema_validate_from_file(const char * schema_file_path,nlohmann::json & jsonData,std::string & error_message)148 int schema_validate_from_file(const char *schema_file_path,
149 			      nlohmann::json &jsonData,
150 			      std::string &error_message)
151 {
152 	// Load the schema
153 	nlohmann::json schema_root = loadJson(schema_file_path);
154 	if (schema_root.is_discarded()) {
155 		std::cerr << "Could not open schema file: " << schema_file_path
156 			  << std::endl;
157 		return 0;
158 	}
159 
160 	fs::path pathObj(schema_file_path);
161 	fs::path base_path = pathObj.parent_path();
162 	try {
163 		fs::current_path(base_path);
164 		// std::cout << "Changed directory to: " << fs::current_path()
165 		// 	  << std::endl;
166 	} catch (const fs::filesystem_error &e) {
167 		std::cerr << "Filesystem error: " << e.what() << std::endl;
168 	}
169 
170 	// Parse the json schema into an internal schema format
171 	valijson::Schema schema;
172 	valijson::SchemaParser parser;
173 	valijson::adapters::NlohmannJsonAdapter schemaDocumentAdapter(
174 		schema_root);
175 
176 	// Set up callbacks for resolving external references
177 	try {
178 		parser.populateSchema(schemaDocumentAdapter, schema,
179 				      documentLoader, documentRelease);
180 	} catch (std::exception &e) {
181 		std::cerr << "Failed to parse schema: " << e.what()
182 			  << std::endl;
183 		return 0;
184 	}
185 
186 	// Perform validation
187 	valijson::Validator validator(valijson::Validator::kStrongTypes);
188 	valijson::ValidationResults results;
189 	valijson::adapters::NlohmannJsonAdapter targetDocumentAdapter(jsonData);
190 	if (!validator.validate(schema, targetDocumentAdapter, &results)) {
191 		std::cerr << "Validation failed." << std::endl;
192 		valijson::ValidationResults::Error error;
193 		unsigned int errorNum = 1;
194 		while (results.popError(error)) {
195 			std::string context;
196 			std::vector<std::string>::iterator itr =
197 				error.context.begin();
198 			for (; itr != error.context.end(); itr++) {
199 				context += *itr;
200 			}
201 
202 			std::cout << "Error #" << errorNum << std::endl
203 				  << "  context: " << context << std::endl
204 				  << "  desc:    " << error.description
205 				  << std::endl;
206 			++errorNum;
207 		}
208 		return 0;
209 	}
210 
211 	error_message = "Schema validation successful";
212 	return 1;
213 }
214