1#!/usr/local/bin/recon 2 3local argparse = require 'argparse' 4local darwin = require 'darwin' 5local plist = require 'plist' 6local kcdata = require 'kcdata' 7local lfs = require 'lfs' 8local sysctl = require 'sysctl' 9 10require 'strict' 11 12local parser = argparse(){ 13 name = 'kernpost_report', 14} 15 16local FMT_RAW <const> = 'raw' 17local FMT_PLIST <const> = 'plist' 18local FMT_BUNDLE <const> = 'resultbundle' 19 20local export = parser:command('export') 21export:option{ 22 name = '-f --format', 23 description = 'the format to export the tests in', 24 choices = { FMT_RAW, FMT_PLIST, FMT_BUNDLE, }, 25 count = '?', 26} 27export:option{ 28 name = '-o --output', 29 description = 'where to write the report, must be a directory', 30 count = 1, 31} 32 33local function write_file(path, data) 34 local f, err = io.open(path, 'w') 35 if not f then 36 io.stderr:write(path, ': failed to open for writing: ', err, '\n') 37 os.exit(1) 38 end 39 f:write(data) 40 f:close() 41end 42 43local function default_test_info(config) 44 return { 45 version = 2, 46 test_category = 'unittest', 47 Project = 'xnu', 48 ['boot-args'] = assert(config.boot_args), 49 osVersion = assert(config.osversion), 50 mach_timebase_info = config.mach_timebase_info, 51 } 52end 53 54local function current_timezone_offset() 55 local now = os.time() 56 local timezone = os.difftime(now, os.time(os.date("!*t", now))) 57 local h, m = math.modf(timezone / 3600) 58 return ("%+.2d:%.2d"):format(h, 60 * m) 59end 60local timezone_offset = current_timezone_offset() 61 62local function convert_time(raw_time, tb) 63 local time_secs = raw_time * tb.numer / tb.denom / 1e9 64 local boottime_secs = darwin.mach_boottime_usec() / 1e6 65 local walltime_secs = math.modf(boottime_secs + time_secs) 66 local time_str = os.date('%Y-%m-%dT%H:%M:%S', walltime_secs) 67 local time_ms_str = time_str .. '.' .. tostring( 68 math.floor(walltime_secs / 1e6)) 69 return time_ms_str .. timezone_offset 70end 71 72local function write_test_result(config, test_data, dir) 73 local info_tbl = default_test_info(config) 74 75 local name = test_data.test_name 76 local test_dir = dir .. '/test_' .. name 77 lfs.mkdir(test_dir) 78 79 info_tbl.test_id = name 80 81 local test_pass = test_data.retval ~= nil and 82 test_data.retval == test_data.expected_retval 83 info_tbl.result_code = test_pass and 200 or 400 84 85 local tb = config.mach_timebase_info 86 info_tbl.result_started = convert_time(test_data.begin_time, tb) 87 info_tbl.beginTimeRaw = test_data.begin_time 88 89 info_tbl.result_finished = convert_time(test_data.end_time, tb) 90 info_tbl.endTimeRaw = test_data.end_time 91 92 local info_path <const> = test_dir .. '/Info.plist' 93 local info_plist, err = plist.encode(info_tbl) 94 if not info_plist then 95 io.stderr:write('error: failed to serialize test Info.plist: ', err, '\n') 96 os.exit(1) 97 end 98 write_file(info_path, info_plist) 99 100 lfs.mkdir(test_dir .. '/Attachments') 101 lfs.mkdir(test_dir .. '/Diagnostics') 102 103 local status_path = test_dir .. '/' .. (test_pass and 'PASS' or 'FAIL') .. 104 '.status' 105 write_file(status_path, '') 106end 107 108local function write_result_bundle(data, dir) 109 lfs.mkdir(dir) 110 local config = data.xnupost_testconfig 111 for _, test in ipairs(config.xnupost_test_config) do 112 write_test_result(config, test, dir) 113 end 114end 115 116export:action(function (args) 117 local dir = args.output 118 if lfs.attributes(dir, 'mode') ~= 'directory' then 119 io.stderr:write(dir, ': output path must be a directory\n') 120 os.exit(1) 121 end 122 123 local raw_data, err = sysctl('debug.xnupost_get_tests') 124 if not raw_data then 125 io.stderr:write('error: failed to retrieve test data from kernel: ', err, 126 '\n') 127 os.exit(1) 128 end 129 130 if args.format == FMT_RAW then 131 write_file(dir .. '/xnupost.kcdata', raw_data) 132 elseif args.format == FMT_PLIST then 133 local tbl_data 134 tbl_data, err = kcdata.decode(raw_data) 135 if not tbl_data then 136 io.stderr:write('error: failed to deserialize kernel data: ', err, '\n') 137 os.exit(1) 138 end 139 local data 140 data, err = plist.encode(tbl_data, 'xml') 141 if not data then 142 io.stderr:write('error: failed to serialize kernel data to plist: ', err, 143 '\n') 144 os.exit(1) 145 end 146 write_file(dir .. '/xnupost.plist', data) 147 elseif args.format == FMT_BUNDLE then 148 local tbl_data 149 tbl_data, err = kcdata.decode(raw_data) 150 if not tbl_data then 151 io.stderr:write('error: failed to deserialize kernel data: ', err, '\n') 152 os.exit(1) 153 end 154 write_result_bundle(tbl_data, dir .. '/xnupost') 155 end 156end) 157 158parser:parse(arg) 159