1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269
|
local utils = require 'busted.utils'
local path = require 'pl.path'
local tablex = require 'pl.tablex'
local exit = require 'busted.compatibility'.exit
local execute = require 'busted.compatibility'.execute
return function(options)
local appName = ''
local options = options or {}
local cli = require 'cliargs.core'()
local configLoader = require 'busted.modules.configuration_loader'()
-- Default cli arg values
local defaultOutput = options.output or 'utfTerminal'
local defaultLoaders = 'lua,moonscript'
local defaultPattern = '_spec'
local defaultSeed = '/dev/urandom or os.time()'
local lpathprefix = './src/?.lua;./src/?/?.lua;./src/?/init.lua'
local cpathprefix = path.is_windows and './csrc/?.dll;./csrc/?/?.dll;' or './csrc/?.so;./csrc/?/?.so;'
local cliArgsParsed = {}
local function makeList(values)
return type(values) == 'table' and values or { values }
end
local function fixupList(values, sep)
local sep = sep or ','
local list = type(values) == 'table' and values or { values }
local olist = {}
for _, v in ipairs(list) do
tablex.insertvalues(olist, utils.split(v, sep))
end
return olist
end
local function processOption(key, value, altkey, opt)
if altkey then cliArgsParsed[altkey] = value end
cliArgsParsed[key] = value
return true
end
local function processArg(key, value)
cliArgsParsed[key] = value
return true
end
local function processArgList(key, value)
local list = cliArgsParsed[key] or {}
tablex.insertvalues(list, utils.split(value, ','))
processArg(key, list)
return true
end
local function processNumber(key, value, altkey, opt)
local number = tonumber(value)
if not number then
return nil, 'argument to ' .. opt:gsub('=.*', '') .. ' must be a number'
end
if altkey then cliArgsParsed[altkey] = number end
cliArgsParsed[key] = number
return true
end
local function processList(key, value, altkey, opt)
local list = cliArgsParsed[key] or {}
tablex.insertvalues(list, utils.split(value, ','))
processOption(key, list, altkey, opt)
return true
end
local function processMultiOption(key, value, altkey, opt)
local list = cliArgsParsed[key] or {}
table.insert(list, value)
processOption(key, list, altkey, opt)
return true
end
local function append(s1, s2, sep)
local sep = sep or ''
if not s1 then return s2 end
return s1 .. sep .. s2
end
local function processLoaders(key, value, altkey, opt)
local loaders = append(cliArgsParsed[key], value, ',')
processOption(key, loaders, altkey, opt)
return true
end
local function processPath(key, value, altkey, opt)
local lpath = append(cliArgsParsed[key], value, ';')
processOption(key, lpath, altkey, opt)
return true
end
local function processDir(key, value, altkey, opt)
local dpath = path.normpath(path.join(cliArgsParsed[key] or '', value))
processOption(key, dpath, altkey, opt)
return true
end
local function processShuffle(key, value, altkey, opt)
processOption('shuffle-files', value, nil, opt)
processOption('shuffle-tests', value, nil, opt)
end
local function processSort(key, value, altkey, opt)
processOption('sort-files', value, nil, opt)
processOption('sort-tests', value, nil, opt)
end
-- Load up the command-line interface options
cli:flag('--version', 'prints the program version and exits', false, processOption)
if not options.standalone then
cli:splat('ROOT', 'test script file/folder. Folders will be traversed for any file that matches the --pattern option.', 'spec', 999, processArgList)
cli:option('-p, --pattern=PATTERN', 'only run test files matching the Lua pattern', defaultPattern, processMultiOption)
cli:option('--exclude-pattern=PATTERN', 'do not run test files matching the Lua pattern, takes precedence over --pattern', nil, processMultiOption)
end
cli:option('-e STATEMENT', 'execute statement STATEMENT', nil, processMultiOption)
cli:option('-o, --output=LIBRARY', 'output library to load', defaultOutput, processOption)
cli:option('-C, --directory=DIR', 'change to directory DIR before running tests. If multiple options are specified, each is interpreted relative to the previous one.', './', processDir)
cli:option('-f, --config-file=FILE', 'load configuration options from FILE', nil, processOption)
cli:option('--coverage-config-file=FILE', 'load luacov configuration options from FILE', nil, processOption)
cli:option('-t, --tags=TAGS', 'only run tests with these #tags', {}, processList)
cli:option('--exclude-tags=TAGS', 'do not run tests with these #tags, takes precedence over --tags', {}, processList)
cli:option('--filter=PATTERN', 'only run test names matching the Lua pattern', {}, processMultiOption)
cli:option('--name=NAME', 'run test with the given full name', {}, processMultiOption)
cli:option('--filter-out=PATTERN', 'do not run test names matching the Lua pattern, takes precedence over --filter', {}, processMultiOption)
cli:option('--exclude-names-file=FILE', 'do not run the tests with names listed in the given file, takes precedence over --filter', nil, processOption)
cli:option('--log-success=FILE', 'append the name of each successful test to the given file', nil, processOption)
cli:option('-m, --lpath=PATH', 'optional path to be prefixed to the Lua module search path', lpathprefix, processPath)
cli:option('--cpath=PATH', 'optional path to be prefixed to the Lua C module search path', cpathprefix, processPath)
cli:option('-r, --run=RUN', 'config to run from .busted file', nil, processOption)
cli:option('--repeat=COUNT', 'run the tests repeatedly', '1', processNumber)
cli:option('--seed=SEED', 'random seed value to use for shuffling test order', defaultSeed, processNumber)
cli:option('--lang=LANG', 'language for error messages', 'en', processOption)
cli:option('--loaders=NAME', 'test file loaders', defaultLoaders, processLoaders)
cli:option('--helper=PATH', 'A helper script that is run before tests', nil, processOption)
cli:option('--lua=LUA', 'The path to the lua interpreter busted should run under', nil, processOption)
cli:option('-Xoutput OPTION', 'pass `OPTION` as an option to the output handler. If `OPTION` contains commas, it is split into multiple options at the commas.', {}, processList)
cli:option('-Xhelper OPTION', 'pass `OPTION` as an option to the helper script. If `OPTION` contains commas, it is split into multiple options at the commas.', {}, processList)
cli:flag('-c, --[no-]coverage', 'do code coverage analysis (requires `LuaCov` to be installed)', false, processOption)
cli:flag('-v, --[no-]verbose', 'verbose output of errors', false, processOption)
cli:flag('-s, --[no-]enable-sound', 'executes `say` command if available', false, processOption)
cli:flag('-l, --list', 'list the names of all tests instead of running them', false, processOption)
cli:flag('--ignore-lua', 'Whether or not to ignore the lua directive', false, processOption)
cli:flag('--[no-]lazy', 'use lazy setup/teardown as the default', false, processOption)
cli:flag('--[no-]auto-insulate', 'enable file insulation', true, processOption)
cli:flag('-k, --[no-]keep-going', 'continue as much as possible after an error or failure', true, processOption)
cli:flag('-R, --[no-]recursive', 'recurse into subdirectories', true, processOption)
cli:flag('--[no-]shuffle', 'randomize file and test order, takes precedence over --sort (--shuffle-test and --shuffle-files)', processShuffle)
cli:flag('--[no-]shuffle-files', 'randomize file execution order, takes precedence over --sort-files', processOption)
cli:flag('--[no-]shuffle-tests', 'randomize test order within a file, takes precedence over --sort-tests', processOption)
cli:flag('--[no-]sort', 'sort file and test order (--sort-tests and --sort-files)', processSort)
cli:flag('--[no-]sort-files', 'sort file execution order', processOption)
cli:flag('--[no-]sort-tests', 'sort test order within a file', processOption)
cli:flag('--[no-]suppress-pending', 'suppress `pending` test output', false, processOption)
cli:flag('--[no-]defer-print', 'defer print to when test suite is complete', false, processOption)
local function parse(args)
-- Parse the cli arguments
local cliArgs, cliErr = cli:parse(args)
if not cliArgs then
if cliErr:match("^Usage") then
return { help = true, helpText = cliErr }, nil
end
return nil, appName .. ': error: ' .. cliErr .. '; re-run with --help for usage.'
end
-- Load busted config file if available
local bustedConfigFilePath
if cliArgs.f then
-- if the file is given, then we require it to exist
if not path.isfile(cliArgs.f) then
return nil, ("specified config file '%s' not found"):format(cliArgs.f)
end
bustedConfigFilePath = cliArgs.f
else
-- try default file
bustedConfigFilePath = path.normpath(path.join(cliArgs.directory, '.busted'))
if not path.isfile(bustedConfigFilePath) then
bustedConfigFilePath = nil -- clear default file, since it doesn't exist
end
end
if bustedConfigFilePath then
local bustedConfigFile, err = loadfile(bustedConfigFilePath)
if not bustedConfigFile then
return nil, ("failed loading config file `%s`: %s"):format(bustedConfigFilePath, err)
else
local ok, config = pcall(function()
local conf, err = configLoader(bustedConfigFile(), cliArgsParsed, cliArgs)
return conf or error(err, 0)
end)
if not ok then
return nil, appName .. ': error: ' .. config
else
cliArgs = config
end
end
else
cliArgs = tablex.merge(cliArgs, cliArgsParsed, true)
end
-- Switch lua, we should rebuild this feature once luarocks changes how it
-- handles executeable lua files.
if cliArgs['lua'] and not cliArgs['ignore-lua'] then
local _, code = execute(
cliArgs['lua']..' '..args[0]..' --ignore-lua '..table.concat(args, ' ')
)
exit(code)
end
-- Ensure multi-options are in a list
cliArgs.e = makeList(cliArgs.e)
cliArgs.pattern = makeList(cliArgs.pattern)
cliArgs.p = cliArgs.pattern
cliArgs['exclude-pattern'] = makeList(cliArgs['exclude-pattern'])
cliArgs.filter = makeList(cliArgs.filter)
cliArgs['filter-out'] = makeList(cliArgs['filter-out'])
-- Fixup options in case options from config file are not of the right form
cliArgs.tags = fixupList(cliArgs.tags)
cliArgs.t = cliArgs.tags
cliArgs['exclude-tags'] = fixupList(cliArgs['exclude-tags'])
cliArgs.loaders = fixupList(cliArgs.loaders)
cliArgs.Xoutput = fixupList(cliArgs.Xoutput)
cliArgs.Xhelper = fixupList(cliArgs.Xhelper)
-- We report an error if the same tag appears in both `options.tags`
-- and `options.excluded_tags` because it does not make sense for the
-- user to tell Busted to include and exclude the same tests at the
-- same time.
for _, excluded in pairs(cliArgs['exclude-tags']) do
for _, included in pairs(cliArgs.tags) do
if excluded == included then
return nil, appName .. ': error: Cannot use --tags and --exclude-tags for the same tags'
end
end
end
cliArgs['repeat'] = tonumber(cliArgs['repeat'])
return cliArgs
end
return {
set_name = function(self, name)
appName = name
return cli:set_name(name)
end,
set_silent = function(self, name)
appName = name
return cli:set_silent(name)
end,
parse = function(self, args)
return parse(args)
end
}
end
|