1 #import <Foundation/Foundation.h>
4 #include <mach/mach_time.h>
10 #include <sys/sysctl.h>
14 #define FREE_BUF(_buf) \
22 #define ERR(_msg_format, ...) fprintf(stderr, "error: " _msg_format "\n", ##__VA_ARGS__)
24 #define PERR(_msg) perror("error: " _msg)
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";
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";
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;
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;
73 const char * progname = getprogname();
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"
83 parse_export_options(int argc, char * argv[])
88 while ((ch = getopt(argc, argv, "o:f:")) != -1) {
91 g_output_dir = optarg;
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;
110 if (g_output_dir == NULL) {
114 struct stat path_stat;
115 if (stat(g_output_dir, &path_stat)) {
116 PERR("Failed to access output dir");
118 } else if (!S_ISDIR(path_stat.st_mode)) {
119 ERR("error: Output path must be a directory");
130 parse_options(int argc, char * argv[])
133 char * cmd = argv[1];
136 if (strncmp(cmd, "export", 7) == 0) {
137 g_command = COMMAND_EXPORT;
138 parse_export_options(argc, argv);
150 retrieve_test_data(void ** raw_buf_p, size_t * raw_size_p)
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);
156 rc = sysctlbyname("debug.xnupost_get_tests", *raw_buf_p, raw_size_p, NULL, 0);
158 PERR("Failed to get KCData through sysctl");
161 PERR("Failed to allocate KCData raw buffer");
164 PERR("Failed to get size through sysctl");
169 export_raw(void * raw_buf, size_t raw_size)
172 char output_path[MAXPATHLEN];
173 snprintf(output_path, MAXPATHLEN, "%s/xnupost.kcdata", g_output_dir);
174 FILE * output_fp = fopen(output_path, "w");
176 fwrite(raw_buf, raw_size, 1, output_fp);
179 PERR("Failed to open output path");
185 export_to_plist(void * raw_buf, size_t raw_size)
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);
193 NSData * plist_data = [NSPropertyListSerialization dataWithPropertyList:parsed_dict
194 format:NSPropertyListXMLFormat_v1_0
198 if (![plist_data writeToFile:[NSString stringWithUTF8String:output_path] atomically:YES]) {
199 ERR("Failed to write plist to %s", output_path);
202 ERR("Failed to serialize result plist: %s", nsError.localizedDescription.UTF8String);
205 ERR("Failed to parse KCData to plist: %s", nsError.localizedDescription.UTF8String);
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
217 get_estimated_time_str_resultbundle(char * output_str, uint64_t mach_abs_time_usec)
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;
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);
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];
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];
239 output_str[RESULTBUNLDE_TIME_TZ_COLON_INDEX] = ':';
243 create_subtest_bundle_config(NSDictionary * testconfig, NSDictionary * subtest, char * bundle_dir)
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);
256 subtest[kXNUPostKCDataKeyRetval] && (subtest[kXNUPostKCDataKeyRetval] == subtest[kXNUPostKCDataKeyExpectedRetval]);
258 char output_path[MAXPATHLEN];
259 char * output_dir_end = NULL;
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");
265 output_dir_end = output_path + strlen(output_path);
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");
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");
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;
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];
294 rbInfo[kRBInfoKeyMachTBInfo] = @{kRBInfoKeyMachTBInfoDenom : tbInfoDenom, kRBInfoKeyMachTBInfoNumer : tbInfoNumer};
296 rbInfo[kRBInfoKeyBeginTimeRaw] = beginTimeRaw;
297 rbInfo[kRBInfoKeyEndTimeRaw] = endTimeRaw;
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);
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);
311 PERR("Failed to create subtest status file");
318 export_to_resultbundle(void * raw_buf, size_t raw_size)
321 NSError * nsError = nil;
322 NSDictionary * parsed_dict = parseKCDataBuffer(raw_buf, raw_size, &nsError);
324 NSDictionary * testconfig = parsed_dict[kXNUPostKCDataKeyTestConfig];
325 NSArray * subtests = testconfig[kXNUPostKCDataKeySubTestConfig];
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");
333 for (NSDictionary * subtest in subtests) {
334 create_subtest_bundle_config(testconfig, subtest, bundle_dir);
337 ERR("Failed to parse KCData to plist: %s", nsError.localizedDescription.UTF8String);
345 void * raw_buf = NULL;
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);
352 case OUTPUT_FORMAT_RESULTBUNDLE:
353 export_to_resultbundle(raw_buf, raw_size);
355 case OUTPUT_FORMAT_RAW:
357 export_raw(raw_buf, raw_size);
365 main(int argc, char * argv[])
367 parse_options(argc, argv);