Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- local function _W(f) local e=setmetatable({}, {__index = getfenv()}) return setfenv(f,e)() or e end
- local say=_W(function()
- local registry = { }
- local current_namespace
- local fallback_namespace
- local s = {
- _COPYRIGHT = "Copyright (c) 2012 Olivine Labs, LLC.",
- _DESCRIPTION = "A simple string key/value store for i18n or any other case where you want namespaced strings.",
- _VERSION = "Say 1.2",
- set_namespace = function(self, namespace)
- current_namespace = namespace
- if not registry[current_namespace] then
- registry[current_namespace] = {}
- end
- end,
- set_fallback = function(self, namespace)
- fallback_namespace = namespace
- if not registry[fallback_namespace] then
- registry[fallback_namespace] = {}
- end
- end,
- set = function(self, key, value)
- registry[current_namespace][key] = value
- end
- }
- local __meta = {
- __call = function(self, key, vars)
- vars = vars or {}
- local str = registry[current_namespace][key] or registry[fallback_namespace][key]
- if str == nil then
- return key
- end
- str = tostring(str)
- local strings = {}
- for i,v in ipairs(vars) do
- table.insert(strings, tostring(v))
- end
- return #strings > 0 and str:format(unpack(strings)) or str
- end,
- __index = function(self, key)
- return registry[key]
- end
- }
- s:set_fallback('en')
- s:set_namespace('en')
- if _TEST then
- s._registry = registry -- force different name to make sure with _TEST behaves exactly as without _TEST
- end
- return setmetatable(s, __meta)
- end)
- do
- local s = say
- s:set_namespace('en')
- -- 'Pending: test.lua @ 12 \n description
- s:set('output.pending', 'Pending')
- s:set('output.failure', 'Failure')
- s:set('output.error', 'Error')
- s:set('output.success', 'Success')
- s:set('output.pending_plural', 'pending')
- s:set('output.failure_plural', 'failures')
- s:set('output.error_plural', 'errors')
- s:set('output.success_plural', 'successes')
- s:set('output.pending_zero', 'pending')
- s:set('output.failure_zero', 'failures')
- s:set('output.error_zero', 'errors')
- s:set('output.success_zero', 'successes')
- s:set('output.pending_single', 'pending')
- s:set('output.failure_single', 'failure')
- s:set('output.error_single', 'error')
- s:set('output.success_single', 'success')
- s:set('output.seconds', 'seconds')
- -- definitions following are not used within the 'say' namespace
- --[[
- return {
- failure_messages = {
- 'You have %d busted specs',
- 'Your specs are busted',
- 'Your code is bad and you should feel bad',
- 'Your code is in the Danger Zone',
- 'Strange game. The only way to win is not to test',
- 'My grandmother wrote better specs on a 3 86',
- 'Every time there\'s a failure, drink another beer',
- 'Feels bad man'
- },
- success_messages = {
- 'Aww yeah, passing specs',
- 'Doesn\'t matter, had specs',
- 'Feels good, man',
- 'Great success',
- 'Tests pass, drink another beer',
- }
- }
- ]]
- end
- local cliargs=_W(function()
- --[[
- Copyright (c) 2012 Ahmad Amireh
- Permission is hereby granted, free of charge, to any person obtaining a copy of this
- software and associated documentation files (the "Software"), to deal in the Software
- without restriction, including without limitation the rights to use, copy, modify,
- merge, publish, distribute, sublicense, and/or sell copies of the Software,
- and to permit persons to whom the Software is furnished to do so,
- subject to the following conditions:
- The above copyright notice and this permission notice shall be included in all copies
- or substantial portions of the Software.
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
- INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
- PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
- LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
- TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
- OTHER DEALINGS IN THE SOFTWARE.
- ]]
- local cli, _
- -- ------- --
- -- Helpers --
- -- ------- --
- local split = function(str, pat)
- local t = {}
- local fpat = "(.-)" .. pat
- local last_end = 1
- local s, e, cap = str:find(fpat, 1)
- while s do
- if s ~= 1 or cap ~= "" then
- table.insert(t,cap)
- end
- last_end = e+1
- s, e, cap = str:find(fpat, last_end)
- end
- if last_end <= #str then
- cap = str:sub(last_end)
- table.insert(t, cap)
- end
- return t
- end
- local buildline = function(words, size, overflow)
- -- if overflow is set, a word longer than size, will overflow the size
- -- otherwise it will be chopped in line-length pieces
- local line = ""
- if string.len(words[1]) > size then
- -- word longer than line
- if overflow then
- line = words[1]
- table.remove(words, 1)
- else
- line = words[1]:sub(1, size)
- words[1] = words[1]:sub(size + 1, -1)
- end
- else
- while words[1] and (#line + string.len(words[1]) + 1 <= size) or (line == "" and #words[1] == size) do
- if line == "" then
- line = words[1]
- else
- line = line .. " " .. words[1]
- end
- table.remove(words, 1)
- end
- end
- return line, words
- end
- local wordwrap = function(str, size, pad, overflow)
- -- if overflow is set, then words longer than a line will overflow
- -- otherwise, they'll be chopped in pieces
- pad = pad or 0
- local line = ""
- local out = ""
- local padstr = string.rep(" ", pad)
- local words = split(str, ' ')
- while words[1] do
- line, words = buildline(words, size, overflow)
- if out == "" then
- out = padstr .. line
- else
- out = out .. "\n" .. padstr .. line
- end
- end
- return out
- end
- local function disect(key)
- -- characters allowed are a-z, A-Z, 0-9
- -- extended + values also allow; # @ _ + -
- local k, ek, v
- local dummy
- -- if there is no comma, between short and extended, add one
- _, _, dummy = key:find("^%-([%a%d])[%s]%-%-")
- if dummy then key = key:gsub("^%-[%a%d][%s]%-%-", "-"..dummy..", --", 1) end
- -- for a short key + value, replace space by "="
- _, _, dummy = key:find("^%-([%a%d])[%s]")
- if dummy then key = key:gsub("^%-([%a%d])[ ]", "-"..dummy.."=", 1) end
- -- if there is no "=", then append one
- if not key:find("=") then key = key .. "=" end
- -- get value
- _, _, v = key:find(".-%=(.+)")
- -- get key(s), remove spaces
- key = split(key, "=")[1]:gsub(" ", "")
- -- get short key & extended key
- _, _, k = key:find("^%-([%a%d]+)")
- _, _, ek = key:find("%-%-(.+)$")
- if v == "" then v = nil end
- return k,ek,v
- end
- function cli_error(msg, noprint)
- local msg = cli.name .. ": error: " .. msg .. '; re-run with --help for usage.'
- if not noprint then print(msg) end
- return nil, msg
- end
- -- -------- --
- -- CLI Main --
- -- -------- --
- cli = {
- name = "",
- required = {},
- optional = {},
- optargument = {maxcount = 0},
- colsz = { 0, 0 }, -- column width, help text. Set to 0 for auto detect
- maxlabel = 0,
- }
- --- Assigns the name of the program which will be used for logging.
- function cli:set_name(name)
- self.name = name
- end
- -- Used internally to lookup an entry using either its short or expanded keys
- function cli:__lookup(k, ek, t)
- t = t or self.optional
- local _
- for _,entry in ipairs(t) do
- if k and entry.key == k then return entry end
- if ek and entry.expanded_key == ek then return entry end
- end
- return nil
- end
- --- Defines a required argument.
- --- Required arguments have no special notation and are order-sensitive.
- --- *Note:* the value will be stored in `args[@key]`.
- --- *Aliases: `add_argument`*
- ---
- --- ### Parameters
- --- 1. **key**: the argument's "name" that will be displayed to the user
- --- 1. **desc**: a description of the argument
- ---
- --- ### Usage example
- --- The following will parse the argument (if specified) and set its value in `args["root"]`:
- --- `cli:add_arg("root", "path to where root scripts can be found")`
- function cli:add_arg(key, desc)
- assert(type(key) == "string" and type(desc) == "string", "Key and description are mandatory arguments (Strings)")
- if self:__lookup(key, nil, self.required) then
- error("Duplicate argument: " .. key .. ", please rename one of them.")
- end
- table.insert(self.required, { key = key, desc = desc, value = nil })
- if #key > self.maxlabel then self.maxlabel = #key end
- end
- --- Defines an optional argument (or more than one).
- --- There can be only 1 optional argument, and is has to be the last one on the argumentlist.
- --- *Note:* the value will be stored in `args[@key]`. The value will be a 'string' if 'maxcount == 1',
- --- or a table if 'maxcount > 1'
- ---
- --- ### Parameters
- --- 1. **key**: the argument's "name" that will be displayed to the user
- --- 1. **desc**: a description of the argument
- --- 1. **default**: *optional*; specify a default value (the default is "")
- --- 1. **maxcount**: *optional*; specify the maximum number of occurences allowed (default is 1)
- ---
- --- ### Usage example
- --- The following will parse the argument (if specified) and set its value in `args["root"]`:
- --- `cli:add_arg("root", "path to where root scripts can be found", "", 2)`
- --- The value returned will be a table with at least 1 entry and a maximum of 2 entries
- function cli:optarg(key, desc, default, maxcount)
- assert(type(key) == "string" and type(desc) == "string", "Key and description are mandatory arguments (Strings)")
- default = default or ""
- assert(type(default) == "string", "Default value must either be omitted or be a string")
- maxcount = maxcount or 1
- maxcount = tonumber(maxcount)
- assert(maxcount and maxcount>0 and maxcount<1000,"Maxcount must be a number from 1 to 999")
- self.optargument = { key = key, desc = desc, default = default, maxcount = maxcount, value = nil }
- if #key > self.maxlabel then self.maxlabel = #key end
- end
- --- Defines an option.
- --- Optional arguments can use 3 different notations, and can accept a value.
- --- *Aliases: `add_option`*
- ---
- --- ### Parameters
- --- 1. **key**: the argument identifier, can be either `-key`, or `-key, --expanded-key`:
- --- if the first notation is used then a value can be defined after a space (`'-key VALUE'`),
- --- if the 2nd notation is used then a value can be defined after an `=` (`'-key, --expanded-key=VALUE'`).
- --- As a final option it is possible to only use the expanded key (eg. `'--expanded-key'`) both with and
- --- without a value specified.
- --- 1. **desc**: a description for the argument to be shown in --help
- --- 1. **default**: *optional*; specify a default value (the default is "")
- ---
- --- ### Usage example
- --- The following option will be stored in `args["i"]` and `args["input"]` with a default value of `my_file.txt`:
- --- `cli:add_option("-i, --input=FILE", "path to the input file", "my_file.txt")`
- function cli:add_opt(key, desc, default)
- -- parameterize the key if needed, possible variations:
- -- 1. -key
- -- 2. -key VALUE
- -- 3. -key, --expanded
- -- 4. -key, --expanded=VALUE
- -- 5. -key --expanded
- -- 6. -key --expanded=VALUE
- -- 7. --expanded
- -- 8. --expanded=VALUE
- assert(type(key) == "string" and type(desc) == "string", "Key and description are mandatory arguments (Strings)")
- assert(type(default) == "string" or default == nil or default == false, "Default argument: expected a string or nil")
- local k, ek, v = disect(key)
- if default == false and v ~= nil then
- error("A flag type option cannot have a value set; " .. key)
- end
- -- guard against duplicates
- if self:__lookup(k, ek) then
- error("Duplicate option: " .. (k or ek) .. ", please rename one of them.")
- end
- -- set defaults
- if v == nil then default = false end -- no value, so its a flag
- if default == nil then default = "" end
- -- below description of full entry record, nils included for reference
- local entry = {
- key = k,
- expanded_key = ek,
- desc = desc,
- default = default,
- label = key,
- flag = (default == false),
- value = default,
- }
- table.insert(self.optional, entry)
- if #key > self.maxlabel then self.maxlabel = #key end
- end
- --- Define a flag argument (on/off). This is a convenience helper for cli.add_opt().
- --- See cli.add_opt() for more information.
- ---
- --- ### Parameters
- -- 1. **key**: the argument's key
- -- 1. **desc**: a description of the argument to be displayed in the help listing
- function cli:add_flag(key, desc)
- self:add_opt(key, desc, false)
- end
- --- Parses the arguments found in #arg and returns a table with the populated values.
- --- (NOTE: after succesful parsing, the module will delete itself to free resources)
- --- *Aliases: `parse_args`*
- ---
- --- ### Parameters
- --- 1. **noprint**: set this flag to prevent any information (error or help info) from being printed
- --- 1. **dump**: set this flag to dump the parsed variables for debugging purposes, alternatively
- --- set the first option to --__DEBUG__ (option with 2 trailing and leading underscores) to dump at runtime.
- ---
- --- ### Returns
- --- 1. a table containing the keys specified when the arguments were defined along with the parsed values,
- --- or nil + error message (--help option is considered an error and returns nil + help message)
- function cli:parse(arg, noprint, dump)
- arg = arg or _G.arg or {}
- local args = {}
- for k,v in pairs(arg) do args[k] = v end -- copy global args local
- -- starts with --help? display the help listing and abort!
- if args[1] and (args[1] == "--help" or args[1] == "-h") then
- return nil, self:print_help(noprint)
- end
- -- starts with --__DUMP__; set dump to true to dump the parsed arguments
- if dump == nil then
- if args[1] and args[1] == "--__DUMP__" then
- dump = true
- table.remove(args, 1) -- delete it to prevent further parsing
- end
- end
- while args[1] do
- local entry = nil
- local opt = args[1]
- local _, optpref, optkey, optkey2, optval
- _, _, optpref, optkey = opt:find("^(%-[%-]?)(.+)") -- split PREFIX & NAME+VALUE
- if optkey then
- _, _, optkey2, optval = optkey:find("(.-)[=](.+)") -- split value and key
- if optval then
- optkey = optkey2
- end
- end
- if not optpref then
- break -- no optional prefix, so options are done
- end
- if optkey:sub(-1,-1) == "=" then -- check on a blank value eg. --insert=
- optval = ""
- optkey = optkey:sub(1,-2)
- end
- if optkey then
- entry =
- self:__lookup(optpref == '-' and optkey or nil,
- optpref == '--' and optkey or nil)
- end
- if not optkey or not entry then
- local option_type = optval and "option" or "flag"
- return cli_error("unknown/bad " .. option_type .. "; " .. opt, noprint)
- end
- table.remove(args,1)
- if optpref == "-" then
- if optval then
- return cli_error("short option does not allow value through '='; "..opt, noprint)
- end
- if entry.flag then
- optval = true
- else
- -- not a flag, value is in the next argument
- optval = args[1]
- table.remove(args, 1)
- end
- elseif optpref == "--" then
- -- using the expanded-key notation
- entry = self:__lookup(nil, optkey)
- if entry then
- if entry.flag then
- if optval then
- return cli_error("flag --" .. optkey .. " does not take a value", noprint)
- else
- optval = true
- end
- else
- if not optval then
- return cli_error("option --" .. optkey .. " requires a value to be set", noprint)
- end
- end
- else
- return cli_error("unknown/bad flag; " .. opt, noprint)
- end
- end
- entry.value = optval
- end
- -- missing any required arguments, or too many?
- if #args < #self.required or #args > #self.required + self.optargument.maxcount then
- if self.optargument.maxcount > 0 then
- return cli_error("bad number of arguments; " .. #self.required .."-" .. #self.required + self.optargument.maxcount .. " argument(s) must be specified, not " .. #args, noprint)
- else
- return cli_error("bad number of arguments; " .. #self.required .. " argument(s) must be specified, not " .. #args, noprint)
- end
- end
- -- deal with required args here
- for i, entry in ipairs(self.required) do
- entry.value = args[1]
- table.remove(args, 1)
- end
- -- deal with the last optional argument
- while args[1] do
- if self.optargument.maxcount > 1 then
- self.optargument.value = self.optargument.value or {}
- table.insert(self.optargument.value, args[1])
- else
- self.optargument.value = args[1]
- end
- table.remove(args,1)
- end
- -- if necessary set the defaults for the last optional argument here
- if self.optargument.maxcount > 0 and not self.optargument.value then
- if self.optargument.maxcount == 1 then
- self.optargument.value = self.optargument.default
- else
- self.optargument.value = { self.optargument.default }
- end
- end
- -- populate the results table
- local results = {}
- if self.optargument.maxcount > 0 then
- results[self.optargument.key] = self.optargument.value
- end
- for _, entry in pairs(self.required) do
- results[entry.key] = entry.value
- end
- for _, entry in pairs(self.optional) do
- if entry.key then results[entry.key] = entry.value end
- if entry.expanded_key then results[entry.expanded_key] = entry.value end
- end
- if dump then
- print("\n======= Provided command line =============")
- print("\nNumber of arguments: ", #arg)
- for i,v in ipairs(arg) do -- use gloabl 'arg' not the modified local 'args'
- print(string.format("%3i = '%s'", i, v))
- end
- print("\n======= Parsed command line ===============")
- if #self.required > 0 then print("\nArguments:") end
- for i,v in ipairs(self.required) do
- print(" " .. v.key .. string.rep(" ", self.maxlabel + 2 - #v.key) .. " => '" .. v.value .. "'")
- end
- if self.optargument.maxcount > 0 then
- print("\nOptional arguments:")
- print(" " .. self.optargument.key .. "; allowed are " .. tostring(self.optargument.maxcount) .. " arguments")
- if self.optargument.maxcount == 1 then
- print(" " .. self.optargument.key .. string.rep(" ", self.maxlabel + 2 - #self.optargument.key) .. " => '" .. self.optargument.key .. "'")
- else
- for i = 1, self.optargument.maxcount do
- if self.optargument.value[i] then
- print(" " .. tostring(i) .. string.rep(" ", self.maxlabel + 2 - #tostring(i)) .. " => '" .. tostring(self.optargument.value[i]) .. "'")
- end
- end
- end
- end
- if #self.optional > 0 then print("\nOptional parameters:") end
- local doubles = {}
- for _, v in pairs(self.optional) do
- if not doubles[v] then
- local m = v.value
- if type(m) == "string" then
- m = "'"..m.."'"
- else
- m = tostring(m) .." (" .. type(m) .. ")"
- end
- print(" " .. v.label .. string.rep(" ", self.maxlabel + 2 - #v.label) .. " => " .. m)
- doubles[v] = v
- end
- end
- print("\n===========================================\n\n")
- return cli_error("commandline dump created as requested per '--__DUMP__' option", noprint)
- end
- return results
- end
- --- Prints the USAGE heading.
- ---
- --- ### Parameters
- ---1. **noprint**: set this flag to prevent the line from being printed
- ---
- --- ### Returns
- --- 1. a string with the USAGE message.
- function cli:print_usage(noprint)
- -- print the USAGE heading
- local msg = "Usage: " .. tostring(self.name)
- if self.optional[1] then
- msg = msg .. " [OPTIONS] "
- end
- if self.required[1] then
- for _,entry in ipairs(self.required) do
- msg = msg .. " " .. entry.key .. " "
- end
- end
- if self.optargument.maxcount == 1 then
- msg = msg .. " [" .. self.optargument.key .. "]"
- elseif self.optargument.maxcount == 2 then
- msg = msg .. " [" .. self.optargument.key .. "-1 [" .. self.optargument.key .. "-2]]"
- elseif self.optargument.maxcount > 2 then
- msg = msg .. " [" .. self.optargument.key .. "-1 [" .. self.optargument.key .. "-2 [...]]]"
- end
- if not noprint then print(msg) end
- return msg
- end
- --- Prints the HELP information.
- ---
- --- ### Parameters
- --- 1. **noprint**: set this flag to prevent the information from being printed
- ---
- --- ### Returns
- --- 1. a string with the HELP message.
- function cli:print_help(noprint)
- local msg = self:print_usage(true) .. "\n"
- local col1 = self.colsz[1]
- local col2 = self.colsz[2]
- if col1 == 0 then col1 = cli.maxlabel end
- col1 = col1 + 3 --add margins
- if col2 == 0 then col2 = 72 - col1 end
- if col2 <10 then col2 = 10 end
- local append = function(label, desc)
- label = " " .. label .. string.rep(" ", col1 - (#label + 2))
- desc = wordwrap(desc, col2) -- word-wrap
- desc = desc:gsub("\n", "\n" .. string.rep(" ", col1)) -- add padding
- msg = msg .. label .. desc .. "\n"
- end
- if self.required[1] then
- msg = msg .. "\nARGUMENTS: \n"
- for _,entry in ipairs(self.required) do
- append(entry.key, entry.desc .. " (required)")
- end
- end
- if self.optargument.maxcount >0 then
- append(self.optargument.key, self.optargument.desc .. " (optional, default: " .. self.optargument.default .. ")")
- end
- if self.optional[1] then
- msg = msg .. "\nOPTIONS: \n"
- for _,entry in ipairs(self.optional) do
- local desc = entry.desc
- if not entry.flag and entry.default and #tostring(entry.default) > 0 then
- desc = desc .. " (default: " .. entry.default .. ")"
- end
- append(entry.label, desc)
- end
- end
- if not noprint then print(msg) end
- return msg
- end
- --- Sets the amount of space allocated to the argument keys and descriptions in the help listing.
- --- The sizes are used for wrapping long argument keys and descriptions.
- --- ### Parameters
- --- 1. **key_cols**: the number of columns assigned to the argument keys, set to 0 to auto detect (default: 0)
- --- 1. **desc_cols**: the number of columns assigned to the argument descriptions, set to 0 to auto set the total width to 72 (default: 0)
- function cli:set_colsz(key_cols, desc_cols)
- self.colsz = { key_cols or self.colsz[1], desc_cols or self.colsz[2] }
- end
- -- finalize setup
- cli._COPYRIGHT = "Copyright (C) 2011-2012 Ahmad Amireh"
- cli._LICENSE = "The code is released under the MIT terms. Feel free to use it in both open and closed software as you please."
- cli._DESCRIPTION = "Commandline argument parser for Lua"
- cli._VERSION = "cliargs 2.0-1"
- -- aliases
- cli.add_argument = cli.add_arg
- cli.add_option = cli.add_opt
- cli.parse_args = cli.parse -- backward compatibility
- -- test aliases for local functions
- if _TEST then
- cli.split = split
- cli.wordwrap = wordwrap
- end
- return cli
- end)
- local outputHandlerBase=_W(function()
- return function(busted)
- local handler = {
- successes = {},
- successesCount = 0,
- pendings = {},
- pendingsCount = 0,
- failures = {},
- failuresCount = 0,
- errors = {},
- errorsCount = 0,
- inProgress = {}
- }
- handler.cancelOnPending = function(element, parent, status)
- return not ((element.descriptor == 'pending' or status == 'pending') and handler.options.suppressPending)
- end
- handler.subscribe = function(handler, options)
- handler.options = options
- busted.subscribe({ 'suite', 'start' }, handler.baseSuiteStart)
- busted.subscribe({ 'suite', 'end' }, handler.baseSuiteEnd)
- busted.subscribe({ 'test', 'start' }, handler.baseTestStart, { predicate = handler.cancelOnPending })
- busted.subscribe({ 'test', 'end' }, handler.baseTestEnd, { predicate = handler.cancelOnPending })
- busted.subscribe({ 'pending' }, handler.basePending, { predicate = handler.cancelOnPending })
- busted.subscribe({ 'failure' }, handler.baseError)
- busted.subscribe({ 'error' }, handler.baseError)
- end
- handler.getFullName = function(context)
- local parent = busted.context.parent(context)
- local names = { (context.name or context.descriptor) }
- while parent and (parent.name or parent.descriptor) and
- parent.descriptor ~= 'file' do
- table.insert(names, 1, parent.name or parent.descriptor)
- parent = busted.context.parent(parent)
- end
- return table.concat(names, ' ')
- end
- handler.format = function(element, parent, message, debug, isError)
- local formatted = {
- trace = debug or element.trace,
- element = element,
- name = handler.getFullName(element),
- message = message,
- isError = isError
- }
- formatted.element.trace = element.trace or debug
- return formatted
- end
- handler.getDuration = function()
- if not handler.endTime or not handler.startTime then
- return 0
- end
- return handler.endTime - handler.startTime
- end
- handler.baseSuiteStart = function(name, parent)
- handler.startTime = os.clock()
- return nil, true
- end
- handler.baseSuiteEnd = function(name, parent)
- handler.endTime = os.clock()
- return nil, true
- end
- handler.baseTestStart = function(element, parent)
- handler.inProgress[tostring(element)] = {}
- return nil, true
- end
- handler.baseTestEnd = function(element, parent, status, debug)
- local isError
- local insertTable
- local id = tostring(element)
- if status == 'success' then
- insertTable = handler.successes
- handler.successesCount = handler.successesCount + 1
- elseif status == 'pending' then
- insertTable = handler.pendings
- handler.pendingsCount = handler.pendingsCount + 1
- elseif status == 'failure' then
- insertTable = handler.failures
- handler.failuresCount = handler.failuresCount + 1
- elseif status == 'error' then
- insertTable = handler.errors
- handler.errorsCount = handler.errorsCount + 1
- isError = true
- end
- insertTable[id] = handler.format(element, parent, element.message, debug, isError)
- if handler.inProgress[id] then
- for k, v in pairs(handler.inProgress[id]) do
- insertTable[id][k] = v
- end
- handler.inProgress[id] = nil
- end
- return nil, true
- end
- handler.basePending = function(element, parent, message, debug)
- if element.descriptor == 'it' then
- local id = tostring(element)
- handler.inProgress[id].message = message
- handler.inProgress[id].trace = debug
- end
- return nil, true
- end
- handler.baseError = function(element, parent, message, debug)
- if element.descriptor == 'it' then
- if parent.randomseed then
- message = 'Random Seed: ' .. parent.randomseed .. '\n' .. message
- end
- local id = tostring(element)
- handler.inProgress[id].message = message
- handler.inProgress[id].trace = debug
- else
- handler.errorsCount = handler.errorsCount + 1
- table.insert(handler.errors, handler.format(element, parent, message, debug, true))
- end
- return nil, true
- end
- return handler
- end
- end)
- local ansicolors=_W(function()
- --- Simple module for handling colours
- -- use like colors("%{red}HELLO %{green bluebg}World %{reset}Awesome?"):print()
- local isColor,setBack,setFore = term.isColor,term.setBackgroundColor,term.setTextColor
- local back,fore=colors.black,colors.white
- local insert=table.insert
- local function colorGenerate(color, background)
- local func = background and setBack or setFore
- return function()
- if isColor() then func(color) end
- end
- end
- local escape = {
- reset = function()
- setBack(back)
- setFore(fore)
- end,
- }
- -- Load in keys
- for name, color in pairs(colors) do
- if type(color) == "number" then
- escape[name] = colorGenerate(color)
- escape[name .. "bg"] = colorGenerate(color, true)
- end
- end
- for name, color in pairs(colours) do
- if type(color) == "number" and not escape[name] then
- escape[name] = colorGenerate(color)
- escape[name .. "bg"] = colorGenerate(color, true)
- end
- end
- -- Define a basic object
- local ansiObjectMethods = {}
- local ansiObject = {__index = ansiObjectMethods}
- function ansiObject:__concat(val)
- local buffer = setmetatable({}, ansiObject)
- if type(self) == "table" then
- for _, value in ipairs(self) do
- insert(buffer, value)
- end
- else
- insert(buffer, tostring(self))
- end
- if type(val) == "table" then
- for _, value in ipairs(val) do
- insert(buffer, value)
- end
- else
- insert(buffer, tostring(val))
- end
- return buffer
- end
- function ansiObject:__tostring(val)
- local type=type
- local buffer = {}
- for _, v in ipairs(self) do
- if type(v) ~= "function" then
- insert(buffer, v)
- end
- end
- return table.concat(buffer)
- end
- function ansiObjectMethods:write()
- local reset = escape.reset
- reset()
- local type,write = type,write
- for _, v in ipairs(self) do
- if type(v) == "function" then
- v()
- else
- write(v)
- end
- end
- reset()
- end
- ansiObject.__call = ansiObjectMethods.print
- function ansiObjectMethods:print()
- self:write()
- print()
- end
- return function(str)
- str = tostring(str or '')
- local buffer = setmetatable({}, ansiObject)
- local pos,len = 1,#str
- if len == 0 then return buffer end
- while pos <= len do
- local first,last,keys = str:find("%%{(.-)}", pos)
- if first == nil then
- insert(buffer, str:sub(pos))
- break
- end
- local gap = str:sub(pos, first - 1)
- if #gap > 0 then
- insert(buffer, gap)
- end
- for key in keys:gmatch("%w+") do
- insert(buffer, (assert(escape[key], "Cannot find key " .. (key or "<nil>"))))
- end
- pos = last + 1
- end
- return buffer
- end
- end)
- local colorTerminal=_W(function()
- local s = say
- return function(options, busted)
- local handler = outputHandlerBase(busted)
- local successDot = ansicolors('%{green}+')
- local failureDot = ansicolors('%{red}-')
- local errorDot = ansicolors('%{magenta}*')
- local pendingDot = ansicolors('%{yellow}.')
- local pendingDescription = function(pending)
- local name = pending.name
- local string = ansicolors('%{yellow}' .. s('output.pending')) .. ' => ' ..
- ansicolors('%{cyan}' .. pending.trace.short_src) .. ' @ ' ..
- ansicolors('%{cyan}' .. pending.trace.currentline) ..
- ansicolors('%{white}\n' .. name)
- if type(pending.message) == 'string' then
- string = string .. '\n' .. pending.message
- elseif pending.message ~= nil then
- string = string .. '\n' .. textutils.serialize(pending.message)
- end
- return string
- end
- local failureMessage = function(failure)
- local string
- if type(failure.message) == 'string' then
- string = failure.message
- elseif failure.message == nil then
- string = 'Nil error'
- else
- string = textutils.serialize(failure.message)
- end
- return string
- end
- local failureDescription = function(failure, isError)
- local string = '%{red}' .. s('output.failure') .. ' => '
- if isError then
- string = '%{magenta}' .. s('output.error') .. ' => '
- end
- if not failure.element.trace or not failure.element.trace.short_src then
- string = string ..
- '%{cyan}' .. failureMessage(failure) .. '\n' .. '%{white}' .. failure.name
- else
- string = string ..
- '%{cyan}' .. failure.element.trace.short_src .. ' @ ' ..
- '%{cyan}' .. failure.element.trace.currentline .. '\n' ..
- '%{white}' .. failure.name .. '\n%{lightGray}' .. failureMessage(failure)
- end
- if options.verbose and failure.trace and failure.trace.traceback then
- string = string .. '\n%{gray}' .. failure.trace.traceback
- end
- return ansicolors(string)
- end
- local statusString = function()
- local successString = s('output.success_plural')
- local failureString = s('output.failure_plural')
- local pendingString = s('output.pending_plural')
- local errorString = s('output.error_plural')
- local ms = handler.getDuration()
- local successes = handler.successesCount
- local pendings = handler.pendingsCount
- local failures = handler.failuresCount
- local errors = handler.errorsCount
- if successes == 0 then
- successString = s('output.success_zero')
- elseif successes == 1 then
- successString = s('output.success_single')
- end
- if failures == 0 then
- failureString = s('output.failure_zero')
- elseif failures == 1 then
- failureString = s('output.failure_single')
- end
- if pendings == 0 then
- pendingString = s('output.pending_zero')
- elseif pendings == 1 then
- pendingString = s('output.pending_single')
- end
- if errors == 0 then
- errorString = s('output.error_zero')
- elseif errors == 1 then
- errorString = s('output.error_single')
- end
- local formattedTime = ('%.6f'):format(ms):gsub('([0-9])0+$', '%1')
- return ansicolors('%{green}' .. successes) .. ' ' .. successString .. ' / ' ..
- ansicolors('%{red}' .. failures) .. ' ' .. failureString .. ' / ' ..
- ansicolors('%{magenta}' .. errors) .. ' ' .. errorString .. ' / ' ..
- ansicolors('%{yellow}' .. pendings) .. ' ' .. pendingString .. ' : ' ..
- ansicolors('%{white}' .. formattedTime) .. ' ' .. s('output.seconds')
- end
- handler.testEnd = function(element, parent, status, debug)
- if not options.deferPrint then
- local string = successDot
- if status == 'pending' then
- string = pendingDot
- elseif status == 'failure' then
- string = failureDot
- elseif status == 'error' then
- string = errorDot
- end
- string:write()
- end
- return nil, true
- end
- handler.suiteEnd = function(name, parent)
- print('')
- statusString():print()
- for i, pending in pairs(handler.pendings) do
- print('')
- pendingDescription(pending):print()
- end
- for i, err in pairs(handler.failures) do
- print('')
- failureDescription(err):print()
- end
- for i, err in pairs(handler.errors) do
- print('')
- failureDescription(err, true):print()
- end
- return nil, true
- end
- handler.error = function(element, parent, message, debug)
- errorDot:write()
- return nil, true
- end
- busted.subscribe({ 'test', 'end' }, handler.testEnd, { predicate = handler.cancelOnPending })
- busted.subscribe({ 'suite', 'end' }, handler.suiteEnd)
- busted.subscribe({ 'error', 'file' }, handler.error)
- busted.subscribe({ 'error', 'describe' }, handler.error)
- return handler
- end
- end)
- local state=_W(function()
- -- maintains a state of the assert engine in a linked-list fashion
- -- records; formatters, parameters, spies and stubs
- local state_mt = {
- __call = function(self)
- self:revert()
- end }
- local nilvalue = {} -- unique ID to refer to nil values for parameters
- -- will hold the current state
- local current
- -- exported module table
- local state = {}
- ------------------------------------------------------
- -- Reverts to a (specific) snapshot.
- -- @param self (optional) the snapshot to revert to. If not provided, it will revert to the last snapshot.
- state.revert = function(self)
- if not self then
- -- no snapshot given, so move 1 up
- self = current
- if not self.previous then
- -- top of list, no previous one, nothing to do
- return
- end
- end
- if getmetatable(self) ~= state_mt then error("Value provided is not a valid snapshot", 2) end
- if self.next then
- self.next:revert()
- end
- -- revert formatters in 'last'
- self.formatters = {}
- -- revert parameters in 'last'
- self.parameters = {}
- -- revert spies/stubs in 'last'
- while self.spies[1] do
- self.spies[1]:revert()
- table.remove(self.spies, 1)
- end
- setmetatable(self, nil) -- invalidate as a snapshot
- current = self.previous
- current.next = nil
- end
- ------------------------------------------------------
- -- Creates a new snapshot.
- -- @return snapshot table
- state.snapshot = function()
- local s = current
- local new = setmetatable ({
- formatters = {},
- parameters = {},
- spies = {},
- previous = current,
- revert = state.revert,
- }, state_mt)
- if current then current.next = new end
- current = new
- return current
- end
- -- FORMATTERS
- state.add_formatter = function(callback)
- table.insert(current.formatters, 1, callback)
- end
- state.remove_formatter = function(callback, s)
- s = s or current
- for i, v in ipairs(s.formatters) do
- if v == callback then
- table.remove(s.formatters, i)
- break
- end
- end
- -- wasn't found, so traverse up 1 state
- if s.previous then
- state.remove_formatter(callback, s.previous)
- end
- end
- state.format_argument = function(val, s)
- s = s or current
- for _, fmt in ipairs(s.formatters) do
- local valfmt = fmt(val)
- if valfmt ~= nil then return valfmt end
- end
- -- nothing found, check snapshot 1 up in list
- if s.previous then
- return state.format_argument(val, s.previous)
- end
- return nil -- end of list, couldn't format
- end
- -- PARAMETERS
- state.set_parameter = function(name, value)
- if value == nil then value = nilvalue end
- current.parameters[name] = value
- end
- state.get_parameter = function(name, s)
- s = s or current
- local val = s.parameters[name]
- if val == nil and s.previous then
- -- not found, so check 1 up in list
- return state.get_parameter(name, s.previous)
- end
- if val ~= nilvalue then
- return val
- end
- return nil
- end
- -- SPIES / STUBS
- state.add_spy = function(spy)
- table.insert(current.spies, 1, spy)
- end
- state.snapshot() -- create initial state
- return state
- end)
- local assert=_W(function()
- local s = say
- local astate = state
- local obj -- the returned module table
- -- list of namespaces
- local namespace = {}
- local errorlevel = function()
- -- find the first level, not defined in the same file as this
- -- code file to properly report the error
- local level = 1
- local _, info = pcall(error, "", level+1)
- local thisfile = info:match("[^:]+")
- while thisfile and info:find(thisfile, 1, plain) do
- level = level + 1
- _, info = pcall(error, "", level)
- end
- if level > 1 then level = level - 1 end -- deduct call to errorlevel() itself
- return level
- end
- local function extract_keys(assert_string)
- -- get a list of token separated by _
- local tokens = {}
- for token in assert_string:lower():gmatch('[^_]+') do
- table.insert(tokens, token)
- end
- -- find valid keys by coalescing tokens as needed, starting from the end
- local keys = {}
- local key = nil
- for i = #tokens, 1, -1 do
- local token = tokens[i]
- key = key and (token .. '_' .. key) or token
- if namespace.modifier[key] or namespace.assertion[key] then
- table.insert(keys, 1, key)
- key = nil
- end
- end
- -- if there's anything left we didn't recognize it
- if key then
- error("luassert: unknown modifier/assertion: '" .. key .."'", errorlevel())
- end
- return keys
- end
- local __assertion_meta = {
- __call = function(self, ...)
- local state = self.state
- local arguments = {...}
- arguments.n = select('#',...) -- add argument count for trailing nils
- local val = self.callback(state, arguments)
- local data_type = type(val)
- if data_type == "boolean" then
- if val ~= state.mod then
- if state.mod then
- error(s(self.positive_message, obj:format(arguments)) or "assertion failed!", errorlevel())
- else
- error(s(self.negative_message, obj:format(arguments)) or "assertion failed!", errorlevel())
- end
- else
- return state
- end
- end
- return val
- end
- }
- local __state_meta = {
- __call = function(self, payload, callback)
- self.payload = payload or rawget(self, "payload")
- if callback then callback(self) end
- return self
- end,
- __index = function(self, key)
- local keys = extract_keys(key)
- -- execute modifiers and assertions
- local ret = nil
- for _, key in ipairs(keys) do
- if namespace.modifier[key] then
- namespace.modifier[key].state = self
- ret = self(nil, namespace.modifier[key])
- elseif namespace.assertion[key] then
- namespace.assertion[key].state = self
- ret = namespace.assertion[key]
- end
- end
- return ret
- end
- }
- obj = {
- state = function() return setmetatable({mod=true, payload=nil}, __state_meta) end,
- -- registers a function in namespace
- register = function(self, nspace, name, callback, positive_message, negative_message)
- -- register
- local lowername = name:lower()
- if not namespace[nspace] then
- namespace[nspace] = {}
- end
- namespace[nspace][lowername] = setmetatable({
- callback = callback,
- name = lowername,
- positive_message=positive_message,
- negative_message=negative_message
- }, __assertion_meta)
- end,
- -- registers a formatter
- -- a formatter takes a single argument, and converts it to a string, or returns nil if it cannot format the argument
- add_formatter = function(self, callback)
- astate.add_formatter(callback)
- end,
- -- unregisters a formatter
- remove_formatter = function(self, fmtr)
- astate.remove_formatter(fmtr)
- end,
- format = function(self, args)
- -- args.n specifies the number of arguments in case of 'trailing nil' arguments which get lost
- local nofmt = args.nofmt or {} -- arguments in this list should not be formatted
- for i = 1, (args.n or #args) do -- cannot use pairs because table might have nils
- if not nofmt[i] then
- local val = args[i]
- local valfmt = astate.format_argument(val)
- if valfmt == nil then valfmt = tostring(val) end -- no formatter found
- args[i] = valfmt
- end
- end
- return args
- end,
- set_parameter = function(self, name, value)
- astate.set_parameter(name, value)
- end,
- get_parameter = function(self, name)
- return astate.get_parameter(name)
- end,
- add_spy = function(self, spy)
- astate.add_spy(spy)
- end,
- snapshot = function(self)
- return astate.snapshot()
- end,
- }
- local __meta = {
- __call = function(self, bool, message, ...)
- if not bool then
- error(message or "assertion failed!", 2)
- end
- return bool , message , ...
- end,
- __index = function(self, key)
- return rawget(self, key) or self.state()[key]
- end,
- }
- return setmetatable(obj, __meta)
- end)
- local util=_W(function()
- local util = {}
- function util.deepcompare(t1,t2,ignore_mt)
- local ty1 = type(t1)
- local ty2 = type(t2)
- if ty1 ~= ty2 then return false end
- -- non-table types can be directly compared
- if ty1 ~= 'table' then return t1 == t2 end
- local mt1 = getmetatable(t1)
- local mt2 = getmetatable(t2)
- -- would equality be determined by metatable __eq?
- if mt1 and mt1 == mt2 and type(mt1) == "table" and mt1.__eq then
- -- then use that unless asked not to
- if not ignore_mt then return t1 == t2 end
- else -- we can skip the deep comparison below if t1 and t2 share identity
- if t1 == t2 then return true end
- end
- for k1,v1 in pairs(t1) do
- local v2 = t2[k1]
- if v2 == nil or not util.deepcompare(v1,v2) then return false end
- end
- for k2,_ in pairs(t2) do
- -- only check wether each element has a t1 counterpart, actual comparison
- -- has been done in first loop above
- if t1[k2] == nil then return false end
- end
- return true
- end
- -----------------------------------------------
- -- table.insert() replacement that respects nil values.
- -- The function will use table field 'n' as indicator of the
- -- table length, if not set, it will be added.
- -- @param t table into which to insert
- -- @param pos (optional) position in table where to insert. NOTE: not optional if you want to insert a nil-value!
- -- @param val value to insert
- -- @return No return values
- function util.tinsert(...)
- -- check optional POS value
- local args = {...}
- local c = select('#',...)
- local t = args[1]
- local pos = args[2]
- local val = args[3]
- if c < 3 then
- val = pos
- pos = nil
- end
- -- set length indicator n if not present (+1)
- t.n = (t.n or #t) + 1
- if not pos then
- pos = t.n
- elseif pos > t.n then
- -- out of our range
- t[pos] = val
- t.n = pos
- end
- -- shift everything up 1 pos
- for i = t.n, pos + 1, -1 do
- t[i]=t[i-1]
- end
- -- add element to be inserted
- t[pos] = val
- end
- -----------------------------------------------
- -- table.remove() replacement that respects nil values.
- -- The function will use table field 'n' as indicator of the
- -- table length, if not set, it will be added.
- -- @param t table from which to remove
- -- @param pos (optional) position in table to remove
- -- @return No return values
- function util.tremove(t, pos)
- -- set length indicator n if not present (+1)
- t.n = t.n or #t
- if not pos then
- pos = t.n
- elseif pos > t.n then
- -- out of our range
- t[pos] = nil
- return
- end
- -- shift everything up 1 pos
- for i = pos, t.n do
- t[i]=t[i+1]
- end
- -- set size, clean last
- t[t.n] = nil
- t.n = t.n - 1
- end
- -----------------------------------------------
- -- Checks an element to be callable.
- -- The type must either be a function or have a metatable
- -- containing an '__call' function.
- -- @param object element to inspect on being callable or not
- -- @return boolean, true if the object is callable
- function util.callable(object)
- return type(object) == "function" or (type(object) == "table" and type((getmetatable(object) or {}).__call) == "function")
- end
- return util
- end)
- do
- -- module will not return anything, only register assertions with the main assert engine
- -- assertions take 2 parameters;
- -- 1) state
- -- 2) arguments list. The list has a member 'n' with the argument count to check for trailing nils
- -- returns; boolean; whether assertion passed
- local s = say
- local function unique(state, arguments)
- local list = arguments[1]
- local deep = arguments[2]
- for k,v in pairs(list) do
- for k2, v2 in pairs(list) do
- if k ~= k2 then
- if deep and util.deepcompare(v, v2, true) then
- return false
- else
- if v == v2 then
- return false
- end
- end
- end
- end
- end
- return true
- end
- local function equals(state, arguments)
- local argcnt = arguments.n
- assert(argcnt > 1, s("assertion.internal.argtolittle", { "equals", 2, tostring(argcnt) }))
- for i = 2,argcnt do
- if arguments[1] ~= arguments[i] then
- -- switch arguments for proper output message
- util.tinsert(arguments, 1, arguments[i])
- util.tremove(arguments, i + 1)
- return false
- end
- end
- return true
- end
- local function same(state, arguments)
- local argcnt = arguments.n
- assert(argcnt > 1, s("assertion.internal.argtolittle", { "same", 2, tostring(argcnt) }))
- local prev = nil
- for i = 2,argcnt do
- if type(arguments[1]) == 'table' and type(arguments[i]) == 'table' then
- if not util.deepcompare(arguments[1], arguments[i], true) then
- -- switch arguments for proper output message
- util.tinsert(arguments, 1, arguments[i])
- util.tremove(arguments, i + 1)
- return false
- end
- else
- if arguments[1] ~= arguments[i] then
- -- switch arguments for proper output message
- util.tinsert(arguments, 1, arguments[i])
- util.tremove(arguments, i + 1)
- return false
- end
- end
- end
- return true
- end
- local function truthy(state, arguments)
- return arguments[1] ~= false and arguments[1] ~= nil
- end
- local function falsy(state, arguments)
- return not truthy(state, arguments)
- end
- local function has_error(state, arguments)
- local func = arguments[1]
- local err_expected = arguments[2]
- assert(util.callable(func), s("assertion.internal.badargtype", { "error", "function, or callable object", type(func) }))
- local ok, err_actual = pcall(func)
- arguments[1] = err_actual
- arguments[2] = err_expected
- if ok or err_expected == nil then
- return not ok
- elseif type(err_actual) == 'string' and type(err_expected) == 'string' then
- return err_actual:find(err_expected, nil, true) ~= nil
- end
- return same(state, {err_expected, err_actual, ["n"] = 2})
- end
- local function is_true(state, arguments)
- util.tinsert(arguments, 2, true)
- arguments.n = arguments.n + 1
- return arguments[1] == arguments[2]
- end
- local function is_false(state, arguments)
- util.tinsert(arguments, 2, false)
- arguments.n = arguments.n + 1
- return arguments[1] == arguments[2]
- end
- local function is_type(state, arguments, etype)
- util.tinsert(arguments, 2, "type " .. etype)
- arguments.nofmt = arguments.nofmt or {}
- arguments.nofmt[2] = true
- arguments.n = arguments.n + 1
- return arguments.n > 1 and type(arguments[1]) == etype
- end
- local function returned_arguments(state, arguments)
- arguments[1] = tostring(arguments[1])
- arguments[2] = tostring(arguments.n - 1)
- arguments.nofmt = arguments.nofmt or {}
- arguments.nofmt[1] = true
- arguments.nofmt[2] = true
- if arguments.n < 2 then arguments.n = 2 end
- return arguments[1] == arguments[2]
- end
- local function is_boolean(state, arguments) return is_type(state, arguments, "boolean") end
- local function is_number(state, arguments) return is_type(state, arguments, "number") end
- local function is_string(state, arguments) return is_type(state, arguments, "string") end
- local function is_table(state, arguments) return is_type(state, arguments, "table") end
- local function is_nil(state, arguments) return is_type(state, arguments, "nil") end
- local function is_userdata(state, arguments) return is_type(state, arguments, "userdata") end
- local function is_function(state, arguments) return is_type(state, arguments, "function") end
- local function is_thread(state, arguments) return is_type(state, arguments, "thread") end
- assert:register("assertion", "true", is_true, "assertion.same.positive", "assertion.same.negative")
- assert:register("assertion", "false", is_false, "assertion.same.positive", "assertion.same.negative")
- assert:register("assertion", "boolean", is_boolean, "assertion.same.positive", "assertion.same.negative")
- assert:register("assertion", "number", is_number, "assertion.same.positive", "assertion.same.negative")
- assert:register("assertion", "string", is_string, "assertion.same.positive", "assertion.same.negative")
- assert:register("assertion", "table", is_table, "assertion.same.positive", "assertion.same.negative")
- assert:register("assertion", "nil", is_nil, "assertion.same.positive", "assertion.same.negative")
- assert:register("assertion", "userdata", is_userdata, "assertion.same.positive", "assertion.same.negative")
- assert:register("assertion", "function", is_function, "assertion.same.positive", "assertion.same.negative")
- assert:register("assertion", "thread", is_thread, "assertion.same.positive", "assertion.same.negative")
- assert:register("assertion", "returned_arguments", returned_arguments, "assertion.returned_arguments.positive", "assertion.returned_arguments.negative")
- assert:register("assertion", "same", same, "assertion.same.positive", "assertion.same.negative")
- assert:register("assertion", "equals", equals, "assertion.equals.positive", "assertion.equals.negative")
- assert:register("assertion", "equal", equals, "assertion.equals.positive", "assertion.equals.negative")
- assert:register("assertion", "unique", unique, "assertion.unique.positive", "assertion.unique.negative")
- assert:register("assertion", "error", has_error, "assertion.error.positive", "assertion.error.negative")
- assert:register("assertion", "errors", has_error, "assertion.error.positive", "assertion.error.negative")
- assert:register("assertion", "truthy", truthy, "assertion.truthy.positive", "assertion.truthy.negative")
- assert:register("assertion", "falsy", falsy, "assertion.falsy.positive", "assertion.falsy.negative")
- end
- do
- -- module will not return anything, only register assertions/modifiers with the main assert engine
- local function is(state)
- return state
- end
- local function is_not(state)
- state.mod = not state.mod
- return state
- end
- assert:register("modifier", "is", is)
- assert:register("modifier", "are", is)
- assert:register("modifier", "was", is)
- assert:register("modifier", "has", is)
- assert:register("modifier", "not", is_not)
- assert:register("modifier", "no", is_not)
- end
- do
- -- module will not return anything, only register formatters with the main assert engine
- local function fmt_string(arg)
- if type(arg) == "string" then
- return string.format("(string) '%s'", arg)
- end
- end
- local function fmt_number(arg)
- if type(arg) == "number" then
- return string.format("(number) %s", tostring(arg))
- end
- end
- local function fmt_boolean(arg)
- if type(arg) == "boolean" then
- return string.format("(boolean) %s", tostring(arg))
- end
- end
- local function fmt_nil(arg)
- if type(arg) == "nil" then
- return "(nil)"
- end
- end
- local type_priorities = {
- number = 1,
- boolean = 2,
- string = 3,
- table = 4,
- ["function"] = 5,
- userdata = 6,
- thread = 7
- }
- local function is_in_array_part(key, length)
- return type(key) == "number" and 1 <= key and key <= length and math.floor(key) == key
- end
- local function get_sorted_keys(t)
- local keys = {}
- local nkeys = 0
- for key in pairs(t) do
- nkeys = nkeys + 1
- keys[nkeys] = key
- end
- local length = #t
- local function key_comparator(key1, key2)
- local type1, type2 = type(key1), type(key2)
- local priority1 = is_in_array_part(key1, length) and 0 or type_priorities[type1] or 8
- local priority2 = is_in_array_part(key2, length) and 0 or type_priorities[type2] or 8
- if priority1 == priority2 then
- if type1 == "string" or type1 == "number" then
- return key1 < key2
- elseif type1 == "boolean" then
- return key1 -- put true before false
- end
- else
- return priority1 < priority2
- end
- end
- table.sort(keys, key_comparator)
- return keys, nkeys
- end
- local function fmt_table(arg)
- local tmax = assert:get_parameter("TableFormatLevel")
- local ft
- ft = function(t, l)
- local result = ""
- local keys, nkeys = get_sorted_keys(t)
- for i = 1, nkeys do
- local k = keys[i]
- local v = t[k]
- if type(v) == "table" then
- if l < tmax or tmax < 0 then
- result = result .. string.format(string.rep(" ",l * 2) .. "[%s] = {\n%s }\n", tostring(k), tostring(ft(v, l + 1):sub(1,-2)))
- else
- result = result .. string.format(string.rep(" ",l * 2) .. "[%s] = { ... more }\n", tostring(k))
- end
- else
- if type(v) == "string" then v = "'"..v.."'" end
- result = result .. string.format(string.rep(" ",l * 2) .. "[%s] = %s\n", tostring(k), tostring(v))
- end
- end
- return result
- end
- if type(arg) == "table" then
- local result
- if tmax == 0 then
- if next(arg) then
- result = "(table): { ... more }"
- else
- result = "(table): { }"
- end
- else
- result = "(table): {\n" .. ft(arg, 1):sub(1,-2) .. " }\n"
- result = result:gsub("{\n }\n", "{ }\n") -- cleanup empty tables
- result = result:sub(1,-2) -- remove trailing newline
- end
- return result
- end
- end
- local function fmt_function(arg)
- if type(arg) == "function" then
- --local debug_info = debug.getinfo(arg)
- --return string.format("%s @ line %s in %s", tostring(arg), tostring(debug_info.linedefined), tostring(debug_info.source))
- return tostring(arg)
- end
- end
- local function fmt_userdata(arg)
- if type(arg) == "userdata" then
- return string.format("(userdata) '%s'", tostring(arg))
- end
- end
- local function fmt_thread(arg)
- if type(arg) == "thread" then
- return string.format("(thread) '%s'", tostring(arg))
- end
- end
- assert:add_formatter(fmt_string)
- assert:add_formatter(fmt_number)
- assert:add_formatter(fmt_boolean)
- assert:add_formatter(fmt_nil)
- assert:add_formatter(fmt_table)
- assert:add_formatter(fmt_function)
- assert:add_formatter(fmt_userdata)
- assert:add_formatter(fmt_thread)
- -- Set default table display depth for table formatter
- assert:set_parameter("TableFormatLevel", 3)
- end
- do
- local s = say
- s:set_namespace('en')
- s:set("assertion.same.positive", "Expected objects to be the same.\nPassed in:\n%s\nExpected:\n%s")
- s:set("assertion.same.negative", "Expected objects to not be the same.\nPassed in:\n%s\nDid not expect:\n%s")
- s:set("assertion.equals.positive", "Expected objects to be equal.\nPassed in:\n%s\nExpected:\n%s")
- s:set("assertion.equals.negative", "Expected objects to not be equal.\nPassed in:\n%s\nDid not expect:\n%s")
- s:set("assertion.unique.positive", "Expected object to be unique:\n%s")
- s:set("assertion.unique.negative", "Expected object to not be unique:\n%s")
- s:set("assertion.error.positive", "Expected a different error.\nCaught:\n%s\nExpected:\n%s")
- s:set("assertion.error.negative", "Expected no error, but caught:\n%s")
- s:set("assertion.truthy.positive", "Expected to be truthy, but value was:\n%s")
- s:set("assertion.truthy.negative", "Expected to not be truthy, but value was:\n%s")
- s:set("assertion.falsy.positive", "Expected to be falsy, but value was:\n%s")
- s:set("assertion.falsy.negative", "Expected to not be falsy, but value was:\n%s")
- s:set("assertion.called.positive", "Expected to be called %s time(s), but was called %s time(s)")
- s:set("assertion.called.negative", "Expected not to be called exactly %s time(s), but it was.")
- s:set("assertion.called_with.positive", "Function was not called with the arguments")
- s:set("assertion.called_with.negative", "Function was called with the arguments")
- s:set("assertion.returned_arguments.positive", "Expected to be called with %s argument(s), but was called with %s")
- s:set("assertion.returned_arguments.negative", "Expected not to be called with %s argument(s), but was called with %s")
- -- errors
- s:set("assertion.internal.argtolittle", "the '%s' function requires a minimum of %s arguments, got: %s")
- s:set("assertion.internal.badargtype", "the '%s' function requires a %s as an argument, got: %s")
- end
- local luassert=_W(function()
- assert._COPYRIGHT = "Copyright (c) 2012 Olivine Labs, LLC."
- assert._DESCRIPTION = "Extends Lua's built-in assertions to provide additional tests and the ability to create your own."
- assert._VERSION = "Luassert 1.4"
- return assert
- end)
- local outputHandlerLoader=_W(function()
- local terminals = {
- colorTerminal = colorTerminal,
- }
- return function()
- local loadOutputHandler = function(output, options, busted, defaultOutput)
- local func
- if type(output) == "function" then
- func = output(options, busted)
- else
- func = terminals[output] or dofile(output)
- end
- return func(options, busted)
- end
- return loadOutputHandler
- end
- end)
- local environment=_W(function()
- return function(context)
- local environment = {}
- local function getEnv(self, key)
- if not self then return nil end
- return
- self.env and self.env[key] or
- getEnv(context.parent(self), key) or
- _G[key]
- end
- local function setEnv(self, key, value)
- if not self.env then self.env = {} end
- self.env[key] = value
- end
- local function __index(self, key)
- return getEnv(context.get(), key)
- end
- local function __newindex(self, key, value)
- setEnv(context.get(), key, value)
- end
- local env = setmetatable({}, { __index=__index, __newindex=__newindex })
- function environment.wrap(fn)
- return setfenv(fn, env)
- end
- function environment.set(key, value)
- local env = context.get('env')
- if not env then
- env = {}
- context.set('env', env)
- end
- env[key] = value
- end
- return environment
- end
- end)
- local spy=_W(function()
- -- module will return spy table, and register its assertions with the main assert engine
- -- Spy metatable
- local spy_mt = {
- __call = function(self, ...)
- local arguments = {...}
- arguments.n = select('#',...) -- add argument count for trailing nils
- table.insert(self.calls, arguments)
- return self.callback(...)
- end }
- local spy -- must make local before defining table, because table contents refers to the table (recursion)
- spy = {
- new = function(callback)
- if not util.callable(callback) then
- error("Cannot spy on type '" .. type(callback) .. "', only on functions or callable elements", 2)
- end
- local s = setmetatable(
- {
- calls = {},
- callback = callback,
- target_table = nil, -- these will be set when using 'spy.on'
- target_key = nil,
- revert = function(self)
- if not self.reverted then
- if self.target_table and self.target_key then
- self.target_table[self.target_key] = self.callback
- end
- self.reverted = true
- end
- return self.callback
- end,
- called = function(self, times)
- if times then
- return (#self.calls == times), #self.calls
- end
- return (#self.calls > 0), #self.calls
- end,
- called_with = function(self, args)
- for _,v in ipairs(self.calls) do
- if util.deepcompare(v, args) then
- return true
- end
- end
- return false
- end
- }, spy_mt)
- assert:add_spy(s) -- register with the current state
- return s
- end,
- is_spy = function(object)
- return type(object) == "table" and getmetatable(object) == spy_mt
- end,
- on = function(target_table, target_key)
- local s = spy.new(target_table[target_key])
- target_table[target_key] = s
- -- store original data
- s.target_table = target_table
- s.target_key = target_key
- return s
- end
- }
- local function set_spy(state)
- end
- local function called_with(state, arguments)
- if rawget(state, "payload") and rawget(state, "payload").called_with then
- return state.payload:called_with(arguments)
- else
- error("'called_with' must be chained after 'spy(aspy)'")
- end
- end
- local function called(state, arguments)
- local num_times = arguments[1]
- if state.payload and type(state.payload) == "table" and state.payload.called then
- local result, count = state.payload:called(num_times)
- arguments[1] = tostring(arguments[1])
- table.insert(arguments, 2, tostring(count))
- arguments.n = arguments.n + 1
- arguments.nofmt = arguments.nofmt or {}
- arguments.nofmt[1] = true
- arguments.nofmt[2] = true
- return result
- elseif state.payload and type(state.payload) == "function" then
- error("When calling 'spy(aspy)', 'aspy' must not be the original function, but the spy function replacing the original")
- else
- error("'called_with' must be chained after 'spy(aspy)'")
- end
- end
- assert:register("modifier", "spy", set_spy)
- assert:register("assertion", "called_with", called_with, "assertion.called_with.positive", "assertion.called_with.negative")
- assert:register("assertion", "called", called, "assertion.called.positive", "assertion.called.negative")
- return spy
- end)
- local stub=_W(function()
- -- module will return a stub module table
- local stubfunc = function() end
- local stub = {}
- function stub.new(object, key)
- if object == nil and key == nil then
- -- called without arguments, create a 'blank' stub
- object = {}
- key = ""
- end
- assert(type(object) == "table" and key ~= nil, "stub.new(): Can only create stub on a table key, call with 2 params; table, key")
- assert(object[key] == nil or util.callable(object[key]), "stub.new(): The element for which to create a stub must either be callable, or be nil")
- local old_elem = object[key] -- keep existing element (might be nil!)
- object[key] = stubfunc -- set the stubfunction
- local s = spy.on(object, key) -- create a spy on top of the stub function
- local spy_revert = s.revert -- keep created revert function
- s.revert = function(self) -- wrap revert function to restore original element
- if not self.reverted then
- spy_revert(self)
- object[key] = old_elem
- self.reverted = true
- end
- return old_elem
- end
- return s
- end
- function stub.is_stub(object)
- return spy.is_spy(object) and object.callback == stubfunc
- end
- local function set_stub(state)
- end
- assert:register("modifier", "stub", set_stub)
- return setmetatable( stub, {
- __call = function(self, ...)
- -- stub originally was a function only. Now that it is a module table
- -- the __call method is required for backward compatibility
- -- NOTE: this deviates from spy, which has no __call method
- return stub.new(...)
- end })
- end)
- local configurationLoader=_W(function()
- local function merge(old, new)
- local r = {}
- for k, v in pairs(old) do r[k]=v end
- for k, v in pairs(new) do r[k]=v end
- return r
- end
- return function()
- -- Function to load the .busted configuration file if available
- local loadBustedConfigurationFile = function(configFile, config)
- if type(configFile) ~= 'table' then
- return config, '.busted file does not return a table.'
- end
- local run = config.run
- if run and run ~= '' then
- local runConfig = configFile[run]
- if type(runConfig) == 'table' then
- config = merge(config, runConfig)
- return config
- else
- return config, 'Task `' .. run .. '` not found, or not a table.'
- end
- end
- if configFile and type(configFile.default) == 'table' then
- config = merge(config, configFile.default)
- end
- return config
- end
- return loadBustedConfigurationFile
- end
- end)
- local fileType_Lua=_W(function()
- local ret = {}
- local getTrace = function(filename, info)
- -- local index = info.traceback:find('\n%s*%[C]')
- -- info.traceback = info.traceback:sub(1, index)
- return info, false
- end
- ret.match = function(busted, filename)
- local path, name, ext = filename:match('(.-)([^\\/\\\\]-%.?([^%.\\/]*))$')
- if ext == 'lua' or ext == "" or ext == nil then
- return true
- end
- return false
- end
- ret.load = function(busted, filename)
- local file, err = loadfile(filename)
- if not file then
- busted.publish({ 'error', 'file' }, { descriptor = 'file', name = filename }, nil, err, {})
- end
- return file, getTrace
- end
- return ret
- end)
- local testFileLoader=_W(function()
- local fileLoaderTypes = {
- lua = fileType_Lua,
- }
- return function(busted, loaders)
- local fileLoaders = {}
- for _, l in ipairs(loaders) do
- local loader
- if type(l) == "table" then
- loader = l
- elseif type(l) == "function" then
- loader = l()
- else
- loader = fileLoaderTypes[l] or dofile(l)
- end
- fileLoaders[#fileLoaders + 1] = loader
- end
- local getTestFiles = function(rootFile, pattern)
- local fileList = {}
- local add
- function add(file)
- if fs.isDir(file) then
- for _, v in ipairs(fs.list(file)) do
- add(fs.combine(file, v))
- end
- else
- if not file:find('/%.%w+.%w+') and fs.getName(file):find(pattern) then
- table.insert(fileList, file)
- end
- end
- end
- add(rootFile)
- table.sort(fileList)
- return fileList
- end
- -- runs a testfile, loading its tests
- local loadTestFile = function(busted, filename)
- for _, v in pairs(fileLoaders) do
- if v.match(busted, filename) then
- return v.load(busted, filename)
- end
- end
- end
- local loadTestFiles = function(rootFile, pattern, loaders)
- local fileList = getTestFiles(rootFile, pattern)
- for i, fileName in pairs(fileList) do
- local testFile, getTrace, rewriteMessage = loadTestFile(busted, fileName, loaders)
- if testFile then
- local file = setmetatable({
- getTrace = getTrace,
- rewriteMessage = rewriteMessage
- }, {
- __call = testFile
- })
- busted.executors.file(fileName, file)
- end
- end
- return fileList
- end
- return loadTestFiles, loadTestFile, getTestFiles
- end
- end)
- local context=_W(function()
- return function()
- local context = {}
- local data = {}
- local parents = {}
- local children = {}
- function context.ref()
- local ref = {}
- local ctx = data
- function ref.get(key)
- if not key then return ctx end
- return ctx[key]
- end
- function ref.set(key, value)
- ctx[key] = value
- end
- function ref.attach(child)
- if not children[ctx] then children[ctx] = {} end
- parents[child] = ctx
- children[ctx][#children[ctx]+1] = child
- end
- function ref.children(parent)
- return children[parent] or {}
- end
- function ref.parent(child)
- return parents[child]
- end
- function ref.push(child)
- if not parents[child] then error('Detached child. Cannot push.') end
- ctx = child
- end
- function ref.pop()
- ctx = parents[ctx]
- end
- return ref
- end
- return context
- end
- end)
- local mediator=_W(function()
- local function Subscriber(fn, options)
- return {
- options = options or {},
- fn = fn,
- channel = nil,
- id = math.random(1000000000), -- sounds reasonable, rite?
- update = function(self, options)
- if options then
- self.fn = options.fn or self.fn
- self.options = options.options or self.options
- end
- end
- }
- end
- -- Channel class and functions --
- local function Channel(namespace, parent)
- return {
- stopped = false,
- namespace = namespace,
- callbacks = {},
- channels = {},
- parent = parent,
- addSubscriber = function(self, fn, options)
- local callback = Subscriber(fn, options)
- local priority = (#self.callbacks + 1)
- options = options or {}
- if options.priority and
- options.priority >= 0 and
- options.priority < priority
- then
- priority = options.priority
- end
- table.insert(self.callbacks, priority, callback)
- return callback
- end,
- getSubscriber = function(self, id)
- for i=1, #self.callbacks do
- local callback = self.callbacks[i]
- if callback.id == id then return { index = i, value = callback } end
- end
- local sub
- for _, channel in pairs(self.channels) do
- sub = channel:getSubscriber(id)
- if sub then break end
- end
- return sub
- end,
- setPriority = function(self, id, priority)
- local callback = self:getSubscriber(id)
- if callback.value then
- table.remove(self.callbacks, callback.index)
- table.insert(self.callbacks, priority, callback.value)
- end
- end,
- addChannel = function(self, namespace)
- self.channels[namespace] = Channel(namespace, self)
- return self.channels[namespace]
- end,
- hasChannel = function(self, namespace)
- return namespace and self.channels[namespace] and true
- end,
- getChannel = function(self, namespace)
- return self.channels[namespace] or self:addChannel(namespace)
- end,
- removeSubscriber = function(self, id)
- local callback = self:getSubscriber(id)
- if callback and callback.value then
- for _, channel in pairs(self.channels) do
- channel:removeSubscriber(id)
- end
- return table.remove(self.callbacks, callback.index)
- end
- end,
- publish = function(self, result, ...)
- for i = 1, #self.callbacks do
- local callback = self.callbacks[i]
- -- if it doesn't have a predicate, or it does and it's true then run it
- if not callback.options.predicate or callback.options.predicate(...) then
- -- just take the first result and insert it into the result table
- local value, continue = callback.fn(...)
- if value then table.insert(result, value) end
- if not continue then return result end
- end
- end
- if parent then
- return parent:publish(result, ...)
- else
- return result
- end
- end
- }
- end
- -- Mediator class and functions --
- local Mediator = setmetatable(
- {
- Channel = Channel,
- Subscriber = Subscriber
- },
- {
- __call = function (fn, options)
- return {
- channel = Channel('root'),
- getChannel = function(self, channelNamespace)
- local channel = self.channel
- for i=1, #channelNamespace do
- channel = channel:getChannel(channelNamespace[i])
- end
- return channel
- end,
- subscribe = function(self, channelNamespace, fn, options)
- return self:getChannel(channelNamespace):addSubscriber(fn, options)
- end,
- getSubscriber = function(self, id, channelNamespace)
- return self:getChannel(channelNamespace):getSubscriber(id)
- end,
- removeSubscriber = function(self, id, channelNamespace)
- return self:getChannel(channelNamespace):removeSubscriber(id)
- end,
- publish = function(self, channelNamespace, ...)
- return self:getChannel(channelNamespace):publish({}, ...)
- end
- }
- end
- })
- return Mediator
- end)
- local status=_W(function()
- local function get_status(status)
- local smap = {
- ['success'] = 'success',
- ['pending'] = 'pending',
- ['failure'] = 'failure',
- ['error'] = 'error',
- ['true'] = 'success',
- ['false'] = 'failure',
- ['nil'] = 'error',
- }
- return smap[tostring(status)] or 'error'
- end
- return function(inital_status)
- local objstat = get_status(inital_status)
- local obj = {
- success = function(self) return (objstat == 'success') end,
- pending = function(self) return (objstat == 'pending') end,
- failure = function(self) return (objstat == 'failure') end,
- error = function(self) return (objstat == 'error') end,
- get = function(self)
- return objstat
- end,
- set = function(self, status)
- objstat = get_status(status)
- end,
- update = function(self, status)
- -- prefer current failure/error status over new status
- status = get_status(status)
- if objstat == 'success' or (objstat == 'pending' and status ~= 'success') then
- objstat = status
- end
- end
- }
- return setmetatable(obj, {
- __index = {},
- __tostring = function(self) return objstat end
- })
- end
- end)
- local mock=_W(function()
- -- module will return a single mock function, no table nor register any assertions
- local function mock(object, dostub, func, self, key)
- local data_type = type(object)
- if data_type == "table" then
- if spy.is_spy(object) then
- -- this table is a function already wrapped as a spy, so nothing to do here
- else
- for k,v in pairs(object) do
- object[k] = mock(v, dostub, func, object, k)
- end
- end
- elseif data_type == "function" then
- if dostub then
- return stub(self, key, func)
- elseif self==nil then
- return spy.new(object)
- else
- return spy.on(self, key)
- end
- end
- return object
- end
- return mock
- end)
- local core=_W(function()
- local metatype = function(obj)
- local otype = type(obj)
- if otype == 'table' then
- local mt = getmetatable(obj)
- if mt and mt.__type then
- return mt.__type
- end
- end
- return otype
- end
- local failureMt = {
- __index = {},
- __tostring = function(e) return e.message end,
- __type = 'failure'
- }
- local pendingMt = {
- __index = {},
- __tostring = function(p) return p.message end,
- __type = 'pending'
- }
- local throw = error
- return function()
- local mediator = mediator()
- local busted = {}
- busted.version = '2.0.rc3-0'
- local root = context()
- busted.context = root.ref()
- local environment = environment(busted.context)
- -- Kinda hacky sets
- environment.set("assert", luassert)
- environment.set("spy", spy)
- environment.set("stub", stub)
- environment.set("mock", mock)
- busted.executors = {}
- local executors = {}
- busted.status = status
- busted.getTrace = function(element, level, msg)
- level = level or 3
- -- Find first non-current program trace
- local _, info = pcall(error, "", level)
- local current = '^' .. shell.getRunningProgram() .. ':'
- while info:match(current) or info:match('pcall') do
- level = level + 1
- local _, info = pcall(error, "", level)
- end
- level = level + 1
- local traceback = ""
- for i = level, level+20 do
- local _, info = pcall(error, "", i)
- local info = info:match("[^:]+:[0-9]+")
- if not info then break end
- traceback = traceback .. "\n " .. info
- end
- local _, info = pcall(error, "", level)
- local source, line = info:match("([^:]+):([0-9]+)")
- local info = {
- traceback = traceback:sub(2), -- Remove first \n
- message = msg,
- source = source,
- short_src = source,
- currentline = line or "<unknown>",
- }
- local file = busted.getFile(element)
- return file.getTrace(file.name, info)
- end
- busted.rewriteMessage = function(element, message)
- local file = busted.getFile(element)
- return file.rewriteMessage and file.rewriteMessage(file.name, message) or message
- end
- function busted.publish(...)
- return mediator:publish(...)
- end
- function busted.subscribe(...)
- return mediator:subscribe(...)
- end
- function busted.getFile(element)
- local current, parent = element, busted.context.parent(element)
- while parent do
- if parent.file then
- local file = parent.file[1]
- return {
- name = file.name,
- getTrace = file.run.getTrace,
- rewriteMessage = file.run.rewriteMessage
- }
- end
- if parent.descriptor == 'file' then
- return {
- name = parent.name,
- getTrace = parent.run.getTrace,
- rewriteMessage = parent.run.rewriteMessage
- }
- end
- parent = busted.context.parent(parent)
- end
- return parent
- end
- busted.fail = throw
- function busted.pending(msg)
- local p = { message = msg }
- setmetatable(p, pendingMt)
- throw(p)
- end
- function busted.replaceErrorWithFail(callable)
- local env = {}
- local f = getmetatable(callable).__call or callable
- setmetatable(env, { __index = getfenv(f) })
- env.error = busted.fail
- setfenv(f, env)
- end
- function busted.wrapEnv(callable)
- if (type(callable) == 'function' or getmetatable(callable).__call) then
- -- prioritize __call if it exists, like in files
- environment.wrap(getmetatable(callable).__call or callable)
- end
- end
- function busted.safe(descriptor, run, element)
- busted.context.push(element)
- local trace, message
- local status = 'success'
- local ret = { xpcall(run, function(msg)
- local errType = metatype(msg)
- status = (errType == 'string' and 'error' or errType)
- message = busted.rewriteMessage(element, tostring(msg))
- trace = busted.getTrace(element, 3, msg)
- end) }
- if not ret[1] then
- busted.publish({ status, descriptor }, element, busted.context.parent(element), message, trace)
- end
- ret[1] = busted.status(status)
- busted.context.pop()
- return unpack(ret)
- end
- function busted.register(descriptor, executor)
- executors[descriptor] = executor
- local publisher = function(name, fn)
- if not fn and type(name) == 'function' then
- fn = name
- name = nil
- end
- local trace
- local ctx = busted.context.get()
- if busted.context.parent(ctx) then
- trace = busted.getTrace(ctx, 3, name)
- end
- busted.publish({ 'register', descriptor }, name, fn, trace)
- end
- busted.executors[descriptor] = publisher
- if descriptor ~= 'file' then
- environment.set(descriptor, publisher)
- end
- busted.subscribe({ 'register', descriptor }, function(name, fn, trace)
- local ctx = busted.context.get()
- local plugin = {
- descriptor = descriptor,
- name = name,
- run = fn,
- trace = trace
- }
- busted.context.attach(plugin)
- if not ctx[descriptor] then
- ctx[descriptor] = { plugin }
- else
- ctx[descriptor][#ctx[descriptor]+1] = plugin
- end
- end)
- end
- function busted.execute(current)
- if not current then current = busted.context.get() end
- for _, v in pairs(busted.context.children(current)) do
- local executor = executors[v.descriptor]
- if executor then
- busted.safe(v.descriptor, function() return executor(v) end, v)
- end
- end
- end
- return busted
- end
- end)
- local init=_W(function()
- math.randomseed(os.time())
- local function shuffle(t, seed)
- if seed then math.randomseed(seed) end
- local n = #t
- while n >= 2 do
- local k = math.random(n)
- t[n], t[k] = t[k], t[n]
- n = n - 1
- end
- return t
- end
- return function(busted)
- local function remove(descriptors, element)
- for _, descriptor in ipairs(descriptors) do
- element.env[descriptor] = function(...)
- error("'" .. descriptor .. "' not supported inside current context block", 2)
- end
- end
- end
- local function exec(descriptor, element)
- if not element.env then element.env = {} end
- remove({ 'randomize' }, element)
- remove({ 'pending' }, element)
- remove({ 'describe', 'context', 'it', 'spec', 'test' }, element)
- remove({ 'setup', 'teardown', 'before_each', 'after_each' }, element)
- local ret = { busted.safe(descriptor, element.run, element) }
- return unpack(ret)
- end
- local function execAll(descriptor, current, propagate)
- local parent = busted.context.parent(current)
- if propagate and parent then
- local success, ancestor = execAll(descriptor, parent, propagate)
- if not success then
- return success, ancestor
- end
- end
- local list = current[descriptor] or {}
- local success = true
- for _, v in pairs(list) do
- if not exec(descriptor, v):success() then
- success = nil
- end
- end
- return success, current
- end
- local function dexecAll(descriptor, current, propagate)
- local parent = busted.context.parent(current)
- local list = current[descriptor] or {}
- local success = true
- for _, v in pairs(list) do
- if not exec(descriptor, v):success() then
- success = nil
- end
- end
- if propagate and parent then
- if not dexecAll(descriptor, parent, propagate) then
- success = nil
- end
- end
- return success
- end
- local file = function(file)
- busted.publish({ 'file', 'start' }, file)
- busted.wrapEnv(file.run)
- if not file.env then file.env = {} end
- local randomize = busted.randomize
- file.env.randomize = function() randomize = true end
- if busted.safe('file', file.run, file):success() then
- if randomize then
- file.randomseed = busted.randomseed
- shuffle(busted.context.children(file), busted.randomseed)
- end
- if execAll('setup', file) then
- busted.execute(file)
- end
- dexecAll('teardown', file)
- end
- busted.publish({ 'file', 'end' }, file.name)
- end
- local describe = function(describe)
- local parent = busted.context.parent(describe)
- busted.publish({ 'describe', 'start' }, describe, parent)
- if not describe.env then describe.env = {} end
- local randomize = busted.randomize
- describe.env.randomize = function() randomize = true end
- if busted.safe('describe', describe.run, describe):success() then
- if randomize then
- describe.randomseed = busted.randomseed
- shuffle(busted.context.children(describe), busted.randomseed)
- end
- if execAll('setup', describe) then
- busted.execute(describe)
- end
- dexecAll('teardown', describe)
- end
- busted.publish({ 'describe', 'end' }, describe, parent)
- end
- local it = function(element)
- local finally
- busted.publish({ 'test', 'start' }, element, parent)
- if not element.env then element.env = {} end
- remove({ 'randomize' }, element)
- remove({ 'describe', 'context', 'it', 'spec', 'test' }, element)
- remove({ 'setup', 'teardown', 'before_each', 'after_each' }, element)
- element.env.finally = function(fn) finally = fn end
- element.env.pending = function(msg) busted.pending(msg) end
- local status = busted.status('success')
- local updateErrorStatus = function(descriptor)
- if element.message then element.message = element.message .. '\n' end
- element.message = (element.message or '') .. 'Error in ' .. descriptor
- status:update('error')
- end
- local parent = busted.context.parent(element)
- local pass, ancestor = execAll('before_each', parent, true)
- if pass then
- status:update(busted.safe('element', element.run, element))
- else
- updateErrorStatus('before_each')
- end
- if not element.env.done then
- remove({ 'pending' }, element)
- if finally then status:update(busted.safe('finally', finally, element)) end
- if not dexecAll('after_each', ancestor, true) then
- updateErrorStatus('after_each')
- end
- busted.publish({ 'test', 'end' }, element, parent, tostring(status))
- end
- end
- local pending = function(element)
- local parent = busted.context.parent(pending)
- busted.publish({ 'test', 'start' }, element, parent)
- busted.publish({ 'test', 'end' }, element, parent, 'pending')
- end
- busted.register('file', file)
- busted.register('describe', describe)
- busted.register('context', describe)
- busted.register('it', it)
- busted.register('spec', it)
- busted.register('test', it)
- busted.register('pending', pending)
- busted.register('setup')
- busted.register('teardown')
- busted.register('before_each')
- busted.register('after_each')
- busted.replaceErrorWithFail(assert)
- busted.replaceErrorWithFail(assert.True)
- return busted
- end
- end)
- local api=_W(function()
- local pairs,find = pairs,string.find
- function loadConfig(path, default)
- local success, result = pcall(function() return loadfile(path)() end)
- if success then
- return configurationLoader()(result, default)
- else
- return false, false
- end
- end
- function loadOutput(busted, output, options, default)
- local outputHandler = outputHandlerLoader()(output, options, busted, default)
- outputHandler:subscribe(options)
- return outputHandler
- end
- function loadFiles(busted, loaders, rootFile, pattern)
- return testFileLoader(busted, loaders)(rootFile, pattern)
- end
- function execute(busted)
- busted.publish({ 'suite', 'start' })
- busted.execute()
- busted.publish({ 'suite', 'end' })
- end
- local hasTag = function(name, tag)
- return find(name, '#' .. tag) ~= nil
- end
- tags = {
- hasTag = hasTag,
- subscribeTags = function(busted, include, exclude)
- -- 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(exclude) do
- for _, included in pairs(include) do
- if excluded == included then
- error('Cannot use --tags and --exclude-tags for the same tags')
- end
- end
- end
- local function checkTags(name)
- for _, tag in ipairs(exclude) do
- if hasTag(name, tag) then
- return nil, false
- end
- end
- for _, tag in ipairs(include) do
- if hasTag(name, tag) then
- return nil, true
- end
- end
- return nil, #include == 0
- end
- busted.subscribe({ 'register', 'describe' }, checkTags, { priority = 1 })
- busted.subscribe({ 'register', 'it' }, checkTags, { priority = 1 })
- busted.subscribe({ 'register', 'pending' }, checkTags, { priority = 1 })
- end
- }
- function run(options, default)
- local busted = core()
- init(busted)
- local path = options.cwd
- local errors, errorCount = {}, 0
- local config = loadConfig(fs.combine(path, '.busted'), options)
- if config then
- options = config
- elseif err then
- errors[#errors + 1] = err
- errors = errors + 1
- end
- -- Load test directory
- local rootFile = fs.combine(path, options.root)
- busted.subscribe({ 'error' }, function(element, parent, status)
- if element.descriptor == 'output' then
- errors[#errors + 1] = 'Cannot load output library: ' .. element.name
- end
- errorCount = errorCount + 1
- return nil, true
- end)
- busted.subscribe({ 'failure' }, function(element, parent, status)
- errorCount = errorCount + 1
- return nil, true
- end)
- -- Set up randomization options
- busted.randomize = options.randomize
- local randomseed = tonumber(options.seed)
- if randomseed then
- busted.randomseed = randomseed
- else
- errors[#errors + 1] = 'Argument to --seed must be a number'
- errorCount = errorCount + 1
- busted.randomseed = default.seed
- end
- -- Set up output handler to listen to events
- local outputHandlerOptions = {
- verbose = options.verbose,
- suppressPending = options['suppress-pending'],
- deferPrint = options['defer-print']
- }
- loadOutput(busted, options.output, outputHandlerOptions, default.output)
- local includeTags, excludeTags = options.tags, options['exclude-tags']
- if #includeTags > 0 or #excludeTags > 0 then
- tags.subscribeTags(busted, includeTags, excludeTags)
- end
- if options.env then
- busted.subscribe({ 'file', 'start' }, function(file)
- if not file.env then file.env = {} end
- for k, v in pairs(options.env) do
- file.env[k] = v
- end
- end)
- end
- local pattern = options.pattern
- local fileList = loadFiles(busted, options.loaders, rootFile, pattern)
- if #fileList == 0 then
- errors[#errors + 1] = 'No test files found matching Lua pattern: ' .. pattern
- errorCount = errorCount + 1
- end
- execute(busted)
- return errorCount, errors
- end
- end)
- local plainTerminal=_W(function()
- local s = say
- return function(options, busted)
- local handler = outputHandlerBase(busted)
- local successDot = '+'
- local failureDot = '-'
- local errorDot = '*'
- local pendingDot = '.'
- local pendingDescription = function(pending)
- local name = pending.name
- local string = s('output.pending') .. ' → ' ..
- pending.trace.short_src .. ' @ ' ..
- pending.trace.currentline ..
- '\n' .. name
- if type(pending.message) == 'string' then
- string = string .. '\n' .. pending.message
- elseif pending.message ~= nil then
- string = string .. '\n' .. textutils.serialize(pending.message)
- end
- return string
- end
- local failureMessage = function(failure)
- local string
- if type(failure.message) == 'string' then
- string = failure.message
- elseif failure.message == nil then
- string = 'Nil error'
- else
- string = textutils.serialize(failure.message)
- end
- return string
- end
- local failureDescription = function(failure, isError)
- local string = s('output.failure') .. ' → '
- if isError then
- string = s('output.error') .. ' → '
- end
- if not failure.element.trace or not failure.element.trace.short_src then
- string = string ..
- failureMessage(failure) .. '\n' ..
- failure.name
- else
- string = string ..
- failure.element.trace.short_src .. ' @ ' ..
- failure.element.trace.currentline .. '\n' ..
- failure.name .. '\n' ..
- failureMessage(failure)
- end
- if options.verbose and failure.trace and failure.trace.traceback then
- string = string .. '\n' .. failure.trace.traceback
- end
- return string
- end
- local statusString = function()
- local successString = s('output.success_plural')
- local failureString = s('output.failure_plural')
- local pendingString = s('output.pending_plural')
- local errorString = s('output.error_plural')
- local ms = handler.getDuration()
- local successes = handler.successesCount
- local pendings = handler.pendingsCount
- local failures = handler.failuresCount
- local errors = handler.errorsCount
- if successes == 0 then
- successString = s('output.success_zero')
- elseif successes == 1 then
- successString = s('output.success_single')
- end
- if failures == 0 then
- failureString = s('output.failure_zero')
- elseif failures == 1 then
- failureString = s('output.failure_single')
- end
- if pendings == 0 then
- pendingString = s('output.pending_zero')
- elseif pendings == 1 then
- pendingString = s('output.pending_single')
- end
- if errors == 0 then
- errorString = s('output.error_zero')
- elseif errors == 1 then
- errorString = s('output.error_single')
- end
- local formattedTime = ('%.6f'):format(ms):gsub('([0-9])0+$', '%1')
- return successes .. ' ' .. successString .. ' / ' ..
- failures .. ' ' .. failureString .. ' / ' ..
- errors .. ' ' .. errorString .. ' / ' ..
- pendings .. ' ' .. pendingString .. ' : ' ..
- formattedTime .. ' ' .. s('output.seconds')
- end
- handler.testEnd = function(element, parent, status, debug)
- if not options.deferPrint then
- local string = successDot
- if status == 'pending' then
- string = pendingDot
- elseif status == 'failure' then
- string = failureDot
- elseif status == 'error' then
- string = errorDot
- end
- write(string)
- end
- return nil, true
- end
- handler.suiteEnd = function(name, parent)
- print('')
- print(statusString())
- for i, pending in pairs(handler.pendings) do
- print('')
- print(pendingDescription(pending))
- end
- for i, err in pairs(handler.failures) do
- print('')
- print(failureDescription(err))
- end
- for i, err in pairs(handler.errors) do
- print('')
- print(failureDescription(err, true))
- end
- return nil, true
- end
- handler.error = function(element, parent, message, debug)
- write(errorDot)
- return nil, true
- end
- busted.subscribe({ 'test', 'end' }, handler.testEnd, { predicate = handler.cancelOnPending })
- busted.subscribe({ 'suite', 'end' }, handler.suiteEnd)
- busted.subscribe({ 'error', 'file' }, handler.error)
- busted.subscribe({ 'error', 'describe' }, handler.error)
- return handler
- end
- end)
- local done=_W(function()
- local M = {}
- -- adds tokens to the current wait list, does not change order/unordered
- M.wait = function(self, ...)
- local tlist = { ... }
- for _, token in ipairs(tlist) do
- if type(token) ~= 'string' then
- error('Wait tokens must be strings. Got '..type(token), 2)
- end
- table.insert(self.tokens, token)
- end
- end
- -- set list as unordered, adds tokens to current wait list
- M.wait_unordered = function(self, ...)
- self.ordered = false
- self:wait(...)
- end
- -- set list as ordered, adds tokens to current wait list
- M.wait_ordered = function(self, ...)
- self.ordered = true
- self:wait(...)
- end
- -- generates a message listing tokens received/open
- M.tokenlist = function(self)
- local list
- if #self.tokens_done == 0 then
- list = 'No tokens received.'
- else
- list = 'Tokens received ('..tostring(#self.tokens_done)..')'
- local s = ': '
- for _,t in ipairs(self.tokens_done) do
- list = list .. s .. '\''..t..'\''
- s = ', '
- end
- list = list .. '.'
- end
- if #self.tokens == 0 then
- list = list .. ' No more tokens expected.'
- else
- list = list .. ' Tokens not received ('..tostring(#self.tokens)..')'
- local s = ': '
- for _, t in ipairs(self.tokens) do
- list = list .. s .. '\''..t..'\''
- s = ', '
- end
- list = list .. '.'
- end
- return list
- end
- -- marks a token as completed, checks for ordered/unordered, checks for completeness
- M.done = function(self, ...) self:_done(...) end -- extra wrapper for same error level constant as __call method
- M._done = function(self, token)
- if token then
- if type(token) ~= 'string' then
- error('Wait tokens must be strings. Got '..type(token), 3)
- end
- if self.ordered then
- if self.tokens[1] == token then
- table.remove(self.tokens, 1)
- table.insert(self.tokens_done, token)
- else
- if self.tokens[1] then
- error(('Bad token, expected \'%s\' got \'%s\'. %s'):format(self.tokens[1], token, self:tokenlist()), 3)
- else
- error(('Bad token (no more tokens expected) got \'%s\'. %s'):format(token, self:tokenlist()), 3)
- end
- end
- else
- -- unordered
- for i, t in ipairs(self.tokens) do
- if t == token then
- table.remove(self.tokens, i)
- table.insert(self.tokens_done, token)
- token = nil
- break
- end
- end
- if token then
- error(('Unknown token \'%s\'. %s'):format(token, self:tokenlist()), 3)
- end
- end
- end
- if not next(self.tokens) then
- -- no more tokens, so we're really done...
- self.done_cb()
- end
- end
- -- wraps a done callback into a done-object supporting tokens to sign-off
- M.new = function(done_callback)
- local obj = {
- tokens = {},
- tokens_done = {},
- done_cb = done_callback,
- ordered = true, -- default for sign off of tokens
- }
- return setmetatable( obj, {
- __call = function(self, ...)
- self:_done(...)
- end,
- __index = M,
- })
- end
- return M
- end)
- return {say=say, cliargs=cliargs, outputHandlerBase=outputHandlerBase, ansicolors=ansicolors, colorTerminal=colorTerminal, state=state, assert=assert, util=util, luassert=luassert, outputHandlerLoader=outputHandlerLoader, environment=environment, spy=spy, stub=stub, configurationLoader=configurationLoader, fileType_Lua=fileType_Lua, testFileLoader=testFileLoader, context=context, mediator=mediator, status=status, mock=mock, core=core, init=init, api=api, plainTerminal=plainTerminal, done=done, }
Advertisement
Add Comment
Please, Sign In to add comment