]> git.saurik.com Git - apple/xnu.git/blob - tools/tests/kernpost_test_report/kernpost_test_report.m
xnu-7195.101.1.tar.gz
[apple/xnu.git] / tools / tests / kernpost_test_report / kernpost_test_report.m
1 #import <Foundation/Foundation.h>
2 #include <kcdata.h>
3 #import <kdd.h>
4 #include <mach/mach_time.h>
5 #include <stdio.h>
6 #include <stdlib.h>
7 #include <string.h>
8 #include <sys/param.h>
9 #include <sys/stat.h>
10 #include <sys/sysctl.h>
11 #include <sysexits.h>
12 #include <unistd.h>
13
14 #define FREE_BUF(_buf) \
15 do { \
16 if (_buf) { \
17 free(_buf); \
18 _buf = NULL; \
19 } \
20 } while (0);
21
22 #define ERR(_msg_format, ...) fprintf(stderr, "error: " _msg_format "\n", ##__VA_ARGS__)
23
24 #define PERR(_msg) perror("error: " _msg)
25
26 /* XNUPost KCData constants */
27 NSString * const kXNUPostKCDataKeyTestConfig = @"xnupost_testconfig";
28 NSString * const kXNUPostKCDataKeyOSVersion = @"osversion";
29 NSString * const kXNUPostKCDataKeyBootargs = @"boot_args";
30 NSString * const kXNUPostKCDataKeyMachTBInfo = @"mach_timebase_info";
31 NSString * const kXNUPostKCDataKeyMachTBInfoDenom = @"denom";
32 NSString * const kXNUPostKCDataKeyMachTBInfoNumer = @"numer";
33 NSString * const kXNUPostKCDataKeySubTestConfig = @"xnupost_test_config";
34 NSString * const kXNUPostKCDataKeyTestName = @"test_name";
35 NSString * const kXNUPostKCDataKeyBeginTime = @"begin_time";
36 NSString * const kXNUPostKCDataKeyEndTime = @"end_time";
37 NSString * const kXNUPostKCDataKeyRetval = @"retval";
38 NSString * const kXNUPostKCDataKeyExpectedRetval = @"expected_retval";
39
40 /* Resultbundle info constants */
41 NSString * const kRBInfoKeyVersion = @"version";
42 NSString * const kRBInfoKeyCategory = @"test_category";
43 NSString * const kRBInfoKeyTestID = @"test_id";
44 NSString * const kRBInfoKeyProject = @"Project";
45 NSString * const kRBInfoKeyBootargs = @"boot-args";
46 NSString * const kRBInfoKeyOSVersion = @"osVersion";
47 NSString * const kRBInfoKeyResultCode = @"result_code";
48 NSString * const kRBInfoKeyResultStarted = @"result_started";
49 NSString * const kRBInfoKeyResultFinished = @"result_finished";
50 NSString * const kRBInfoKeyMachTBInfo = @"mach_timebase_info";
51 NSString * const kRBInfoKeyMachTBInfoDenom = @"denom";
52 NSString * const kRBInfoKeyMachTBInfoNumer = @"numer";
53 NSString * const kRBInfoKeyBeginTimeRaw = @"beginTimeRaw";
54 NSString * const kRBInfoKeyEndTimeRaw = @"endTimeRaw";
55
56 NSNumber * const kResultBundleVersion = @2;
57 NSString * const kResultBundleCategory = @"unittest";
58 NSString * const kResultBundleProject = @"xnu";
59 NSNumber * const kResultCodePass = @200;
60 NSNumber * const kResultCodeFail = @400;
61
62 #define COMMAND_EXPORT (0)
63 static int g_command = COMMAND_EXPORT;
64 #define OUTPUT_FORMAT_RAW (0)
65 #define OUTPUT_FORMAT_PLIST_XML (1)
66 #define OUTPUT_FORMAT_RESULTBUNDLE (2)
67 static int g_output_format = OUTPUT_FORMAT_RAW;
68 static char * g_output_dir = NULL;
69
70 static void
71 usage(void)
72 {
73 const char * progname = getprogname();
74 fprintf(stderr,
75 "Usage:\t%s COMMAND [OPTIONS]\n\n"
76 "\t%s export -o OUTPUT_DIR_PATH [-f raw|plist|resultbundle]\n"
77 "\nSupported command:\n"
78 "\texport\n",
79 progname, progname);
80 }
81
82 static void
83 parse_export_options(int argc, char * argv[])
84 {
85 int ch;
86 bool error = false;
87
88 while ((ch = getopt(argc, argv, "o:f:")) != -1) {
89 switch (ch) {
90 case 'o':
91 g_output_dir = optarg;
92 break;
93 case 'f':
94 if (strncmp(optarg, "raw", 4) == 0) {
95 g_output_format = OUTPUT_FORMAT_RAW;
96 } else if (strncmp(optarg, "plist", 6) == 0) {
97 g_output_format = OUTPUT_FORMAT_PLIST_XML;
98 } else if (strncmp(optarg, "resultbundle", 13) == 0) {
99 g_output_format = OUTPUT_FORMAT_RESULTBUNDLE;
100 } else {
101 error = true;
102 }
103 break;
104 default:
105 error = true;
106 break;
107 }
108 }
109
110 if (g_output_dir == NULL) {
111 error = true;
112 }
113
114 struct stat path_stat;
115 if (stat(g_output_dir, &path_stat)) {
116 PERR("Failed to access output dir");
117 error = true;
118 } else if (!S_ISDIR(path_stat.st_mode)) {
119 ERR("error: Output path must be a directory");
120 error = true;
121 }
122
123 if (error) {
124 usage();
125 exit(EX_USAGE);
126 }
127 }
128
129 static void
130 parse_options(int argc, char * argv[])
131 {
132 if (argc > 1) {
133 char * cmd = argv[1];
134 argc--;
135 argv++;
136 if (strncmp(cmd, "export", 7) == 0) {
137 g_command = COMMAND_EXPORT;
138 parse_export_options(argc, argv);
139 } else {
140 usage();
141 exit(EX_USAGE);
142 }
143 } else {
144 usage();
145 exit(EX_USAGE);
146 }
147 }
148
149 static void
150 retrieve_test_data(void ** raw_buf_p, size_t * raw_size_p)
151 {
152 int rc = sysctlbyname("debug.xnupost_get_tests", NULL, raw_size_p, NULL, 0);
153 if (rc == 0 && *raw_size_p > 0) {
154 *raw_buf_p = malloc(*raw_size_p);
155 if (*raw_buf_p) {
156 rc = sysctlbyname("debug.xnupost_get_tests", *raw_buf_p, raw_size_p, NULL, 0);
157 if (0 != rc) {
158 PERR("Failed to get KCData through sysctl");
159 }
160 } else {
161 PERR("Failed to allocate KCData raw buffer");
162 }
163 } else {
164 PERR("Failed to get size through sysctl");
165 }
166 }
167
168 static void
169 export_raw(void * raw_buf, size_t raw_size)
170 {
171 if (raw_buf) {
172 char output_path[MAXPATHLEN];
173 snprintf(output_path, MAXPATHLEN, "%s/xnupost.kcdata", g_output_dir);
174 FILE * output_fp = fopen(output_path, "w");
175 if (output_fp) {
176 fwrite(raw_buf, raw_size, 1, output_fp);
177 fclose(output_fp);
178 } else {
179 PERR("Failed to open output path");
180 }
181 }
182 }
183
184 static void
185 export_to_plist(void * raw_buf, size_t raw_size)
186 {
187 if (raw_buf) {
188 char output_path[MAXPATHLEN];
189 snprintf(output_path, MAXPATHLEN, "%s/xnupost.plist", g_output_dir);
190 NSError * nsError = nil;
191 NSDictionary * parsed_dict = parseKCDataBuffer(raw_buf, raw_size, &nsError);
192 if (parsed_dict) {
193 NSData * plist_data = [NSPropertyListSerialization dataWithPropertyList:parsed_dict
194 format:NSPropertyListXMLFormat_v1_0
195 options:0
196 error:&nsError];
197 if (plist_data) {
198 if (![plist_data writeToFile:[NSString stringWithUTF8String:output_path] atomically:YES]) {
199 ERR("Failed to write plist to %s", output_path);
200 }
201 } else {
202 ERR("Failed to serialize result plist: %s", nsError.localizedDescription.UTF8String);
203 }
204 } else {
205 ERR("Failed to parse KCData to plist: %s", nsError.localizedDescription.UTF8String);
206 }
207 }
208 }
209
210 #define RESULTBUNDLE_TIME_STR_SIZE (30) // 0000-00-00T00:00:00.000+00:00'\0'
211 #define RESULTBUNLDE_TIME_MS_INDEX (20)
212 #define RESULTBUNLDE_TIME_TZ_COLON_INDEX (26)
213 #define RESULTBUNDLE_TIME_MS_STR_SIZE (4) // 000'\0'
214 #define MSEC_PER_USEC 1000ull
215
216 static void
217 get_estimated_time_str_resultbundle(char * output_str, uint64_t mach_abs_time_usec)
218 {
219 uint64_t est_usec = mach_boottime_usec() + mach_abs_time_usec;
220 time_t est_sec = (time_t)(est_usec / USEC_PER_SEC);
221 uint64_t est_usec_fraction = est_usec % USEC_PER_SEC;
222 struct tm tm_info;
223 int i = 0;
224
225 localtime_r(&est_sec, &tm_info);
226 strftime(output_str, RESULTBUNDLE_TIME_STR_SIZE, "%Y-%m-%dT%H:%M:%S.000%z", &tm_info);
227
228 /* Fill out milliseconds */
229 char ms_str[RESULTBUNDLE_TIME_MS_STR_SIZE] = {0};
230 snprintf(ms_str, RESULTBUNDLE_TIME_MS_STR_SIZE, "%03llu", est_usec_fraction / MSEC_PER_USEC);
231 for (i = 0; i < 3; i++) {
232 output_str[RESULTBUNLDE_TIME_MS_INDEX + i] = ms_str[i];
233 }
234
235 /* Add colon for timezone offset */
236 for (i = RESULTBUNDLE_TIME_STR_SIZE - 1; i > RESULTBUNLDE_TIME_TZ_COLON_INDEX; i--) {
237 output_str[i] = output_str[i - 1];
238 }
239 output_str[RESULTBUNLDE_TIME_TZ_COLON_INDEX] = ':';
240 }
241
242 static void
243 create_subtest_bundle_config(NSDictionary * testconfig, NSDictionary * subtest, char * bundle_dir)
244 {
245 NSString * testName = subtest[kXNUPostKCDataKeyTestName];
246 NSNumber * tbInfoDenom = testconfig[kXNUPostKCDataKeyMachTBInfo][kXNUPostKCDataKeyMachTBInfoDenom];
247 NSNumber * tbInfoNumer = testconfig[kXNUPostKCDataKeyMachTBInfo][kXNUPostKCDataKeyMachTBInfoNumer];
248 struct mach_timebase_info tb_info;
249 tb_info.denom = tbInfoDenom.unsignedIntValue;
250 tb_info.numer = tbInfoNumer.unsignedIntValue;
251 NSNumber * beginTimeRaw = subtest[kXNUPostKCDataKeyBeginTime];
252 NSNumber * endTimeRaw = subtest[kXNUPostKCDataKeyEndTime];
253 uint64_t begin_time_usec = (beginTimeRaw.unsignedLongLongValue * tb_info.numer) / (tb_info.denom * NSEC_PER_USEC);
254 uint64_t end_time_usec = (endTimeRaw.unsignedLongLongValue * tb_info.numer) / (tb_info.denom * NSEC_PER_USEC);
255 bool test_status =
256 subtest[kXNUPostKCDataKeyRetval] && (subtest[kXNUPostKCDataKeyRetval] == subtest[kXNUPostKCDataKeyExpectedRetval]);
257
258 char output_path[MAXPATHLEN];
259 char * output_dir_end = NULL;
260
261 snprintf(output_path, MAXPATHLEN, "%s/test_%s", bundle_dir, testName.UTF8String);
262 if (mkdir(output_path, 0777)) {
263 PERR("Failed to create subtest bundle dir");
264 }
265 output_dir_end = output_path + strlen(output_path);
266
267 *output_dir_end = '\0';
268 strlcat(output_path, "/Attachments", MAXPATHLEN);
269 if (mkdir(output_path, 0777)) {
270 PERR("Failed to create subtest Attachments dir");
271 }
272
273 *output_dir_end = '\0';
274 strlcat(output_path, "/Diagnostics", MAXPATHLEN);
275 if (mkdir(output_path, 0777)) {
276 PERR("Failed to create subtest Diagnostics dir");
277 }
278
279 NSMutableDictionary * rbInfo = [NSMutableDictionary new];
280 rbInfo[kRBInfoKeyVersion] = kResultBundleVersion;
281 rbInfo[kRBInfoKeyCategory] = kResultBundleCategory;
282 rbInfo[kRBInfoKeyTestID] = testName;
283 rbInfo[kRBInfoKeyProject] = kResultBundleProject;
284 rbInfo[kRBInfoKeyOSVersion] = testconfig[kXNUPostKCDataKeyOSVersion];
285 rbInfo[kRBInfoKeyBootargs] = testconfig[kXNUPostKCDataKeyBootargs];
286 rbInfo[kRBInfoKeyResultCode] = test_status ? kResultCodePass : kResultCodeFail;
287
288 char estimated_time_str[RESULTBUNDLE_TIME_STR_SIZE];
289 get_estimated_time_str_resultbundle(estimated_time_str, begin_time_usec);
290 rbInfo[kRBInfoKeyResultStarted] = [NSString stringWithUTF8String:estimated_time_str];
291 get_estimated_time_str_resultbundle(estimated_time_str, end_time_usec);
292 rbInfo[kRBInfoKeyResultFinished] = [NSString stringWithUTF8String:estimated_time_str];
293
294 rbInfo[kRBInfoKeyMachTBInfo] = @{kRBInfoKeyMachTBInfoDenom : tbInfoDenom, kRBInfoKeyMachTBInfoNumer : tbInfoNumer};
295
296 rbInfo[kRBInfoKeyBeginTimeRaw] = beginTimeRaw;
297 rbInfo[kRBInfoKeyEndTimeRaw] = endTimeRaw;
298
299 *output_dir_end = '\0';
300 strlcat(output_path, "/Info.plist", MAXPATHLEN);
301 NSURL * output_url = [NSURL fileURLWithFileSystemRepresentation:output_path isDirectory:NO relativeToURL:nil];
302 NSError * writeError = nil;
303 if (![rbInfo writeToURL:output_url error:&writeError]) {
304 ERR("Failed to write Info.plist file: %s", writeError.localizedDescription.UTF8String);
305 }
306
307 *output_dir_end = '\0';
308 strlcat(output_path, test_status ? "/PASS.status" : "/FAIL.status", MAXPATHLEN);
309 int fd = open(output_path, O_CREAT | O_TRUNC | O_WRONLY, 0666);
310 if (fd == -1) {
311 PERR("Failed to create subtest status file");
312 } else {
313 close(fd);
314 }
315 }
316
317 static void
318 export_to_resultbundle(void * raw_buf, size_t raw_size)
319 {
320 if (raw_buf) {
321 NSError * nsError = nil;
322 NSDictionary * parsed_dict = parseKCDataBuffer(raw_buf, raw_size, &nsError);
323 if (parsed_dict) {
324 NSDictionary * testconfig = parsed_dict[kXNUPostKCDataKeyTestConfig];
325 NSArray * subtests = testconfig[kXNUPostKCDataKeySubTestConfig];
326
327 char bundle_dir[MAXPATHLEN];
328 snprintf(bundle_dir, MAXPATHLEN, "%s/xnupost", g_output_dir);
329 if (mkdir(bundle_dir, 0777)) {
330 PERR("Failed to create result bundle dir");
331 }
332
333 for (NSDictionary * subtest in subtests) {
334 create_subtest_bundle_config(testconfig, subtest, bundle_dir);
335 }
336 } else {
337 ERR("Failed to parse KCData to plist: %s", nsError.localizedDescription.UTF8String);
338 }
339 }
340 }
341
342 static void
343 execute_export(void)
344 {
345 void * raw_buf = NULL;
346 size_t raw_size = 0;
347 retrieve_test_data(&raw_buf, &raw_size);
348 switch (g_output_format) {
349 case OUTPUT_FORMAT_PLIST_XML:
350 export_to_plist(raw_buf, raw_size);
351 break;
352 case OUTPUT_FORMAT_RESULTBUNDLE:
353 export_to_resultbundle(raw_buf, raw_size);
354 break;
355 case OUTPUT_FORMAT_RAW:
356 default:
357 export_raw(raw_buf, raw_size);
358 break;
359 }
360
361 FREE_BUF(raw_buf);
362 }
363
364 int
365 main(int argc, char * argv[])
366 {
367 parse_options(argc, argv);
368 switch (g_command) {
369 case COMMAND_EXPORT:
370 execute_export();
371 break;
372 default:
373 usage();
374 exit(EX_USAGE);
375 break;
376 }
377
378 return 0;
379 }