xref: /xnu-12377.41.6/tools/tests/kernpost_test_report/kernpost_test_report.lua (revision bbb1b6f9e71b8cdde6e5cd6f4841f207dee3d828)
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