--- /dev/null
+MIT License Terms
+=================
+
+Copyright (c) 2012 Olivine Labs, LLC.
+
+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.
\ No newline at end of file
--- /dev/null
+-- This is a dummy file so it can be used in busted's specs
+-- without adding ./?/init.lua to the lua path
+return require 'busted.init'
--- /dev/null
+return {
+ getfenv = getfenv or function(f)
+ f = (type(f) == 'function' and f or debug.getinfo(f + 1, 'f').func)
+ local name, value
+ local up = 0
+
+ repeat
+ up = up + 1
+ name, value = debug.getupvalue(f, up)
+ until name == '_ENV' or name == nil
+
+ return value
+ end,
+
+ setfenv = setfenv or function(f, t)
+ f = (type(f) == 'function' and f or debug.getinfo(f + 1, 'f').func)
+ local name
+ local up = 0
+
+ repeat
+ up = up + 1
+ name = debug.getupvalue(f, up)
+ until name == '_ENV' or name == nil
+
+ if name then
+ debug.upvaluejoin(f, up, function() return name end, 1)
+ debug.setupvalue(f, up, t)
+ end
+
+ if f ~= 0 then return f end
+ end,
+
+ unpack = table.unpack or unpack,
+
+ osexit = function(code, close)
+ if close and _VERSION == 'Lua 5.1' then
+ -- From Lua 5.1 manual:
+ -- > The userdata itself is freed only in the next
+ -- > garbage-collection cycle.
+ -- Call collectgarbage() while collectgarbage('count')
+ -- changes + 3 times, at least 3 times,
+ -- at max 100 times (to prevent infinite loop).
+ local times_const = 0
+ for i = 1, 100 do
+ local count_before = collectgarbage("count")
+ collectgarbage()
+ local count_after = collectgarbage("count")
+ if count_after == count_before then
+ times_const = times_const + 1
+ if times_const > 3 then
+ break
+ end
+ else
+ times_const = 0
+ end
+ end
+ end
+ os.exit(code, close)
+ end,
+}
--- /dev/null
+local tablex = require 'pl.tablex'
+
+local function save()
+ local g = {}
+ for k,_ in next, _G, nil do
+ g[k] = rawget(_G, k)
+ end
+ return {
+ gmt = getmetatable(_G),
+ g = g,
+ loaded = tablex.copy(package.loaded)
+ }
+end
+
+local function restore(state)
+ setmetatable(_G, state.gmt)
+ for k,_ in next, _G, nil do
+ rawset(_G, k, state.g[k])
+ end
+ for k,_ in pairs(package.loaded) do
+ package.loaded[k] = state.loaded[k]
+ end
+end
+
+return function()
+ local context = {}
+
+ local data = {}
+ local parents = {}
+ local children = {}
+ local stack = {}
+
+ 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.clear()
+ data = {}
+ parents = {}
+ children = {}
+ stack = {}
+ ctx = data
+ end
+
+ function ref.attach(child)
+ if not children[ctx] then children[ctx] = {} end
+ parents[child] = ctx
+ table.insert(children[ctx], child)
+ end
+
+ function ref.children(parent)
+ return children[parent] or {}
+ end
+
+ function ref.parent(child)
+ return parents[child]
+ end
+
+ function ref.push(current)
+ if not parents[current] then error('Detached child. Cannot push.') end
+ if ctx ~= current and current.descriptor == 'file' then
+ current.state = save()
+ end
+ table.insert(stack, ctx)
+ ctx = current
+ end
+
+ function ref.pop()
+ local current = ctx
+ ctx = table.remove(stack)
+ if ctx ~= current and current.state then
+ restore(current.state)
+ current.state = nil
+ end
+ if not ctx then error('Context stack empty. Cannot pop.') end
+ end
+
+ return ref
+ end
+
+ return context
+end
--- /dev/null
+local getfenv = require 'busted.compatibility'.getfenv
+local setfenv = require 'busted.compatibility'.setfenv
+local unpack = require 'busted.compatibility'.unpack
+local path = require 'pl.path'
+local pretty = require 'pl.pretty'
+local throw = error
+
+local failureMt = {
+ __index = {},
+ __tostring = function(e) return tostring(e.message) end,
+ __type = 'failure'
+}
+
+local failureMtNoString = {
+ __index = {},
+ __type = 'failure'
+}
+
+local pendingMt = {
+ __index = {},
+ __tostring = function(p) return p.message end,
+ __type = 'pending'
+}
+
+local function metatype(obj)
+ local otype = type(obj)
+ return otype == 'table' and (getmetatable(obj) or {}).__type or otype
+end
+
+local function hasToString(obj)
+ return type(obj) == 'string' or (getmetatable(obj) or {}).__tostring
+end
+
+return function()
+ local mediator = require 'mediator'()
+
+ local busted = {}
+ busted.version = '2.0.rc6-0'
+
+ local root = require 'busted.context'()
+ busted.context = root.ref()
+
+ local environment = require 'busted.environment'(busted.context)
+ busted.environment = {
+ set = environment.set,
+
+ wrap = function(callable)
+ if (type(callable) == 'function' or getmetatable(callable).__call) then
+ -- prioritize __call if it exists, like in files
+ environment.wrap((getmetatable(callable) or {}).__call or callable)
+ end
+ end
+ }
+
+ busted.executors = {}
+ local executors = {}
+
+ busted.status = require 'busted.status'
+
+ function busted.getTrace(element, level, msg)
+ level = level or 3
+
+ local thisdir = path.dirname(debug.getinfo(1, 'Sl').source)
+ local info = debug.getinfo(level, 'Sl')
+ while info.what == 'C' or info.short_src:match('luassert[/\\].*%.lua$') or
+ (info.source:sub(1,1) == '@' and thisdir == path.dirname(info.source)) do
+ level = level + 1
+ info = debug.getinfo(level, 'Sl')
+ end
+
+ info.traceback = debug.traceback('', level)
+ info.message = msg
+
+ local file = busted.getFile(element)
+ return file.getTrace(file.name, info)
+ end
+
+ function busted.rewriteMessage(element, message, trace)
+ local file = busted.getFile(element)
+ local msg = hasToString(message) and tostring(message)
+ msg = msg or (message ~= nil and pretty.write(message) or 'Nil error')
+ msg = (file.rewriteMessage and file.rewriteMessage(file.name, msg) or msg)
+
+ local hasFileLine = msg:match('^[^\n]-:%d+: .*')
+ if not hasFileLine then
+ local trace = trace or busted.getTrace(element, 3, message)
+ local fileline = trace.short_src .. ':' .. trace.currentline .. ': '
+ msg = fileline .. msg
+ end
+
+ return msg
+ end
+
+ function busted.publish(...)
+ return mediator:publish(...)
+ end
+
+ function busted.subscribe(...)
+ return mediator:subscribe(...)
+ end
+
+ function busted.getFile(element)
+ local parent = 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
+
+ function busted.fail(msg, level)
+ local rawlevel = (type(level) ~= 'number' or level <= 0) and level
+ local level = level or 1
+ local _, emsg = pcall(throw, msg, rawlevel or level+2)
+ local e = { message = emsg }
+ setmetatable(e, hasToString(msg) and failureMt or failureMtNoString)
+ throw(e, rawlevel or level+1)
+ end
+
+ function busted.pending(msg)
+ local p = { message = msg }
+ setmetatable(p, pendingMt)
+ throw(p)
+ end
+
+ function busted.replaceErrorWithFail(callable)
+ local env = {}
+ local f = (getmetatable(callable) or {}).__call or callable
+ setmetatable(env, { __index = getfenv(f) })
+ env.error = busted.fail
+ setfenv(f, env)
+ 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 == 'pending' or errType == 'failure') and errType or 'error')
+ trace = busted.getTrace(element, 3, msg)
+ message = busted.rewriteMessage(element, msg, trace)
+ 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
+
+ local publish = function(f)
+ busted.publish({ 'register', descriptor }, name, f, trace)
+ end
+
+ if fn then publish(fn) else return publish end
+ 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.alias(alias, descriptor)
+ local publisher = busted.executors[descriptor]
+ busted.executors[alias] = publisher
+ environment.set(alias, publisher)
+ 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 and not busted.skipAll then
+ busted.safe(v.descriptor, function() executor(v) end, v)
+ end
+ end
+ end
+
+ return busted
+end
--- /dev/null
+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
--- /dev/null
+local setfenv = require 'busted.compatibility'.setfenv
+
+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
--- /dev/null
+local unpack = require 'busted.compatibility'.unpack
+local shuffle = require 'busted.utils'.shuffle
+
+local function sort(elements)
+ table.sort(elements, function(t1, t2)
+ if t1.name and t2.name then
+ return t1.name < t2.name
+ end
+ return t2.name ~= nil
+ end)
+ return elements
+end
+
+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 init(busted)
+ 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 parent = busted.context.parent(element)
+ setmetatable(element.env, {
+ __newindex = function(self, key, value)
+ if not parent.env then parent.env = {} end
+ parent.env[key] = value
+ end
+ })
+
+ 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.name)
+
+ busted.environment.wrap(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)
+ elseif busted.sort then
+ sort(busted.context.children(file))
+ 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)
+ elseif busted.sort then
+ sort(busted.context.children(describe))
+ 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 parent = busted.context.parent(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 pass, ancestor = execAll('before_each', parent, true)
+ if pass then
+ status:update(busted.safe('it', 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(element)
+ busted.publish({ 'test', 'start' }, element, parent)
+ busted.publish({ 'test', 'end' }, element, parent, 'pending')
+ end
+
+ busted.register('file', file)
+
+ busted.register('describe', describe)
+
+ busted.register('it', it)
+
+ busted.register('pending', pending)
+
+ busted.register('setup')
+ busted.register('teardown')
+ busted.register('before_each')
+ busted.register('after_each')
+
+ busted.alias('context', 'describe')
+ busted.alias('spec', 'it')
+ busted.alias('test', 'it')
+
+ local assert = require 'luassert'
+ local spy = require 'luassert.spy'
+ local mock = require 'luassert.mock'
+ local stub = require 'luassert.stub'
+
+ busted.environment.set('assert', assert)
+ busted.environment.set('spy', spy)
+ busted.environment.set('mock', mock)
+ busted.environment.set('stub', stub)
+
+ busted.replaceErrorWithFail(assert)
+ busted.replaceErrorWithFail(assert.True)
+
+ return busted
+end
+
+return setmetatable({}, {
+ __call = function(self, busted)
+ local root = busted.context.get()
+ init(busted)
+
+ return setmetatable(self, {
+ __index = function(self, key)
+ return rawget(root.env, key) or busted.executors[key]
+ end,
+
+ __newindex = function(self, key, value)
+ error('Attempt to modify busted')
+ end
+ })
+ end
+})
--- /dev/null
+local s = require('say')
+
+s:set_namespace('ar')
+
+-- 'Pending: test.lua @ 12 \n description
+s:set('output.pending', 'عالِق')
+s:set('output.failure', 'فَشَل')
+s:set('output.failure', 'نَجاح')
+
+s:set('output.pending_plural', 'عالِق')
+s:set('output.failure_plural', 'إخْفاقات')
+s:set('output.success_plural', 'نَجاحات')
+
+s:set('output.pending_zero', 'عالِق')
+s:set('output.failure_zero', 'إخْفاقات')
+s:set('output.success_zero', 'نَجاحات')
+
+s:set('output.pending_single', 'عالِق')
+s:set('output.failure_single', 'فَشَل')
+s:set('output.success_single', 'نَجاح')
+
+s:set('output.seconds', 'ثَوانٍ')
+
+-- definitions following are not used within the 'say' namespace
+return {
+ failure_messages = {
+ 'فَشِلَت %d مِنْ الإِختِبارات',
+ 'فَشِلَت إخْتِباراتُك',
+ 'برمجيَّتُكَ ضَعيْفة، أنْصَحُكَ بالتَّقاعُد',
+ 'تقع برمجيَّتُكَ في مَنطِقَةِ الخَطَر',
+ 'أقترِحُ ألّا تَتَقَدَّم بالإختِبار، علَّ يبْقى الطابِقُ مَستوراَ',
+ 'جَدَّتي، فِي أَثْناءِ نَومِها، تَكتبُ بَرمَجياتٍ أفْضلُ مِن هذه',
+ 'يَوَدُّ ليْ مُساعَدَتُكْ، لَكِنّْ...'
+ },
+ success_messages = {
+ 'رائِع! تَمَّ إجْتِيازُ جَميعُ الإختِباراتِ بِنَجاحٍ',
+ 'قُل ما شِئت، لا أكتَرِث: busted شَهِدَ لي!',
+ 'حَقَّ عَليْكَ الإفتِخار',
+ 'نَجاحٌ مُبْهِر!',
+ 'عَليكَ بالإحتِفال؛ نَجَحَت جَميعُ التَجارُب'
+ }
+}
--- /dev/null
+local s = require('say')
+
+s:set_namespace('de')
+
+-- 'Pending: test.lua @ 12 \n description
+s:set('output.pending', 'Noch nicht erledigt')
+s:set('output.failure', 'Fehlgeschlagen')
+s:set('output.success', 'Erfolgreich')
+
+s:set('output.pending_plural', 'übersprungen')
+s:set('output.failure_plural', 'fehlgeschlagen')
+s:set('output.success_plural', 'erfolgreich')
+
+s:set('output.pending_zero', 'übersprungen')
+s:set('output.failure_zero', 'fehlgeschlagen')
+s:set('output.success_zero', 'erfolgreich')
+
+s:set('output.pending_single', 'übersprungen')
+s:set('output.failure_single', 'fehlgeschlagen')
+s:set('output.success_single', 'erfolgreich')
+
+s:set('output.seconds', 'Sekunden')
+
+-- definitions following are not used within the 'say' namespace
+return {
+ failure_messages = {
+ 'Du hast %d kaputte Tests.',
+ 'Deine Tests sind kaputt.',
+ 'Dein Code ist schlecht; du solltest dich schlecht fühlen.',
+ 'Dein Code befindet sich in der Gefahrenzone.',
+ 'Ein seltsames Spiel. Der einzig gewinnbringende Zug ist nicht zu testen.',
+ 'Meine Großmutter hat auf einem 386er bessere Tests geschrieben.',
+ 'Immer wenn ein Test fehlschlägt, stirbt ein kleines Kätzchen.',
+ 'Das fühlt sich schlecht an, oder?'
+ },
+ success_messages = {
+ 'Yeah, die Tests laufen durch.',
+ 'Fühlt sich gut an, oder?',
+ 'Großartig!',
+ 'Tests sind durchgelaufen, Zeit für ein Bier.',
+ }
+}
--- /dev/null
+local s = require('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')
+
+s:set('output.no_test_files_match', 'No test files found matching Lua pattern: %s')
+
+-- 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',
+ }
+}
--- /dev/null
+local s = require('say')
+
+s:set_namespace('fr')
+
+-- 'Pending: test.lua @ 12 \n description
+s:set('output.pending', 'En attente')
+s:set('output.failure', 'Echec')
+s:set('output.success', 'Reussite')
+
+s:set('output.pending_plural', 'en attente')
+s:set('output.failure_plural', 'echecs')
+s:set('output.success_plural', 'reussites')
+
+s:set('output.pending_zero', 'en attente')
+s:set('output.failure_zero', 'echec')
+s:set('output.success_zero', 'reussite')
+
+s:set('output.pending_single', 'en attente')
+s:set('output.failure_single', 'echec')
+s:set('output.success_single', 'reussite')
+
+s:set('output.seconds', 'secondes')
+
+s:set('output.no_test_files_match', 'Aucun test n\'est pourrait trouvé qui corresponde au motif de Lua: %s')
+
+-- definitions following are not used within the 'say' namespace
+return {
+ failure_messages = {
+ 'Vous avez %d test(s) qui a/ont echoue(s)',
+ 'Vos tests ont echoue.',
+ 'Votre code source est mauvais et vous devrez vous sentir mal',
+ 'Vous avez un code source de Destruction Massive',
+ 'Jeu plutot etrange game. Le seul moyen de gagner est de ne pas l\'essayer',
+ 'Meme ma grand-mere ecrivait de meilleurs tests sur un PIII x86',
+ 'A chaque erreur, prenez une biere',
+ 'Ca craint, mon pote'
+ },
+ success_messages = {
+ 'Oh yeah, tests reussis',
+ 'Pas grave, y\'a eu du succes',
+ 'C\'est du bon, mon pote. Que du bon!',
+ 'Reussi, haut la main!',
+ 'Test reussi. Un de plus. Offre toi une biere, sur mon compte!',
+ }
+}
--- /dev/null
+local s = require('say')
+
+s:set_namespace('ja')
+
+-- 'Pending: test.lua @ 12 \n description
+s:set('output.pending', '保留')
+s:set('output.failure', '失敗')
+s:set('output.success', '成功')
+
+s:set('output.pending_plural', '保留')
+s:set('output.failure_plural', '失敗')
+s:set('output.success_plural', '成功')
+
+s:set('output.pending_zero', '保留')
+s:set('output.failure_zero', '失敗')
+s:set('output.success_zero', '成功')
+
+s:set('output.pending_single', '保留')
+s:set('output.failure_single', '失敗')
+s:set('output.success_single', '成功')
+
+s:set('output.seconds', '秒')
+
+-- definitions following are not used within the 'say' namespace
+return {
+ failure_messages = {
+ '%d個の仕様が破綻しています',
+ '仕様が破綻しています',
+ 'あなたの書くコードは良くないので反省するべきです',
+ 'あなたの書くコードは危険地帯にあります',
+ 'おかしなゲームです。勝利する唯一の方法はテストをしないことです',
+ '私の祖母でもPentium Pentium III x86の上でもっといいコードを書いていましたよ',
+ 'いつも失敗しているのでビールでも飲みましょう',
+ '罪悪感を持ちましょう',
+ },
+ success_messages = {
+ 'オォーイェー、テストが通った',
+ '問題ない、テストがある',
+ '順調ですね',
+ '大成功',
+ 'テストが通ったし、ビールでも飲もう',
+ }
+}
--- /dev/null
+local s = require('say')
+
+s:set_namespace('nl')
+
+-- 'Pending: test.lua @ 12 \n description
+s:set('output.pending', 'Hangend')
+s:set('output.failure', 'Mislukt')
+s:set('output.success', 'Succes')
+
+s:set('output.pending_plural', 'hangenden')
+s:set('output.failure_plural', 'mislukkingen')
+s:set('output.success_plural', 'successen')
+
+s:set('output.pending_zero', 'hangend')
+s:set('output.failure_zero', 'mislukt')
+s:set('output.success_zero', 'successen')
+
+s:set('output.pending_single', 'hangt')
+s:set('output.failure_single', 'mislukt')
+s:set('output.success_single', 'succes')
+
+s:set('output.seconds', 'seconden')
+
+-- definitions following are not used within the 'say' namespace
+return {
+ failure_messages = {
+ 'Je hebt %d busted specs',
+ 'Je specs zijn busted',
+ 'Je code is slecht en zo zou jij je ook moeten voelen',
+ 'Je code zit in de Gevaren Zone',
+ 'Vreemd spelletje. The enige manier om te winnen is door niet te testen',
+ 'Mijn oma schreef betere specs op een 3 86',
+ 'Elke keer dat iets mislukt, nog een biertje drinken',
+ 'Voelt klote man'
+ },
+ success_messages = {
+ 'Joeperdepoep, de specs zijn er door',
+ 'Doet er niet toe, had specs',
+ 'Voelt goed, man',
+ 'Fantastisch success',
+ 'Testen geslaagd, neem nog een biertje',
+ }
+}
--- /dev/null
+local s = require('say')
+
+s:set_namespace('ru')
+
+-- 'Pending: test.lua @ 12 \n description
+s:set('output.pending', 'Ожидает')
+s:set('output.failure', 'Поломалcя')
+s:set('output.success', 'Прошeл')
+
+s:set('output.pending_plural', 'ожидают')
+s:set('output.failure_plural', 'поломалиcь')
+s:set('output.success_plural', 'прошли')
+
+s:set('output.pending_zero', 'ожидающих')
+s:set('output.failure_zero', 'поломанных')
+s:set('output.success_zero', 'прошедших')
+
+s:set('output.pending_single', 'ожидает')
+s:set('output.failure_single', 'поломался')
+s:set('output.success_single', 'прошел')
+
+s:set('output.seconds', 'секунд')
+
+---- definitions following are not used within the 'say' namespace
+return {
+ failure_messages = {
+ 'У тебя %d просратых тестов',
+ 'Твои тесты поломаны',
+ 'Твой код говеный - пойди напейся!'
+ },
+ success_messages = {
+ 'Поехали!',
+ 'Жизнь - хороша!',
+ 'Ффух в этот раз пронесло!',
+ 'Ура!'
+ }
+}
--- /dev/null
+local s = require('say')
+
+s:set_namespace('th')
+
+-- 'Pending: test.lua @ 12 \n description
+s:set('output.pending', 'อยู่ระหว่างดำเนินการ')
+s:set('output.failure', 'ล้มเหลว')
+s:set('output.success', 'สำเร็จ')
+
+s:set('output.pending_plural', 'อยู่ระหว่างดำเนินการ')
+s:set('output.failure_plural', 'ล้มเหลว')
+s:set('output.success_plural', 'สำเร็จ')
+
+s:set('output.pending_zero', 'อยู่ระหว่างดำเนินการ')
+s:set('output.failure_zero', 'ล้มเหลว')
+s:set('output.success_zero', 'สำเร็จ')
+
+s:set('output.pending_single', 'อยู่ระหว่างดำเนินการ')
+s:set('output.failure_single', 'ล้มเหลว')
+s:set('output.success_single', 'สำเร็จ')
+
+s:set('output.seconds', 'วินาที')
+
+-- definitions following are not used within the 'say' namespace
+return {
+ failure_messages = {
+ 'คุณมี %d บัสเต็ดสเปค',
+ 'สเปคของคุณคือ บัสเต็ด',
+ 'โค้ดของคุณไม่ดีเลย คุณควรรู้สึกแย่น่ะ',
+ 'โค้ดของคุณอยู่ในเขตอันตราย!',
+ 'มันแปลกๆน่ะ วิธีที่จะชนะไม่ได้มีแค่เทสอย่างเดียว',
+ 'ยายผมเขียนสเปคดีกว่านี้อีก บนเครื่อง 386',
+ 'ทุกๆครั้งที่ล้มเหลว, ดื่มเบียร์แก้วใหม่',
+ 'แย่จัง นายท่าน'
+ },
+ success_messages = {
+ 'อุ๊ตะ!!!, สเปคผ่าน!',
+ 'ไม่สำคัญ, มีสเปค',
+ 'ฟินเลยดิ นายท่าน',
+ 'สำเร็จ ยอดเยี่ยม',
+ 'เทสผ่าน, ดื่มเบียร์ๆๆๆ',
+ }
+}
--- /dev/null
+local s = require('say')
+
+s:set_namespace('ua')
+
+-- 'Pending: test.lua @ 12 \n description
+s:set('output.pending', 'Очікує')
+s:set('output.failure', 'Зламався')
+s:set('output.success', 'Пройшов')
+
+s:set('output.pending_plural', 'очікують')
+s:set('output.failure_plural', 'зламались')
+s:set('output.success_plural', 'пройшли')
+
+s:set('output.pending_zero', 'очікуючих')
+s:set('output.failure_zero', 'зламаних')
+s:set('output.success_zero', 'пройдених')
+
+s:set('output.pending_single', 'очікує')
+s:set('output.failure_single', 'зламався')
+s:set('output.success_single', 'пройшов')
+
+s:set('output.seconds', 'секунд')
+
+
+---- definitions following are not used within the 'say' namespace
+return {
+ failure_messages = {
+ 'Ти зрадив %d тестів!',
+ 'Ой йо..',
+ 'Вороги поламали наші тести!'
+ },
+ success_messages = {
+ 'Слава Україні! Героям Слава!',
+ 'Тестування успішно пройдено!',
+ 'Всі баги знищено!'
+ }
+}
--- /dev/null
+local s = require('say')
+
+s:set_namespace('zh')
+
+-- 'Pending: test.lua @ 12 \n description
+s:set('output.pending', '开发中')
+s:set('output.failure', '失败')
+s:set('output.success', '成功')
+
+s:set('output.pending_plural', '开发中')
+s:set('output.failure_plural', '失败')
+s:set('output.success_plural', '成功')
+
+s:set('output.pending_zero', '开发中')
+s:set('output.failure_zero', '失败')
+s:set('output.success_zero', '成功')
+
+s:set('output.pending_single', '开发中')
+s:set('output.failure_single', '失败')
+s:set('output.success_single', '成功')
+
+s:set('output.seconds', '秒')
+
+-- definitions following are not used within the 'say' namespace
+return {
+ failure_messages = {
+ '你一共提交了[%d]个测试用例',
+ '又出错了!',
+ '到底哪里不对呢?',
+ '出错了,又要加班了!',
+ '囧,出Bug了!',
+ '据说比尔盖兹也写了一堆Bug,别灰心!',
+ '又出错了,休息一下吧',
+ 'Bug好多,心情好坏!'
+ },
+ success_messages = {
+ '牛X,测试通过了!',
+ '测试通过了,感觉不错吧,兄弟!',
+ '哥们,干得漂亮!',
+ '终于通过了!干一杯先!',
+ '阿弥陀佛~,菩萨显灵了!',
+ }
+}
--- /dev/null
+return function()
+ local tablex = require 'pl.tablex'
+
+ -- Function to load the .busted configuration file if available
+ local loadBustedConfigurationFile = function(configFile, config, defaults)
+ if type(configFile) ~= 'table' then
+ return config, '.busted file does not return a table.'
+ end
+
+ local defaults = defaults or {}
+ local run = config.run or defaults.run
+
+ if run and run ~= '' then
+ local runConfig = configFile[run]
+
+ if type(runConfig) == 'table' then
+ config = tablex.merge(runConfig, config, true)
+ else
+ return config, 'Task `' .. run .. '` not found, or not a table.'
+ end
+ end
+
+ if type(configFile.default) == 'table' then
+ config = tablex.merge(configFile.default, config, true)
+ end
+
+ config = tablex.merge(defaults, config, true)
+
+ return config
+ end
+
+ return loadBustedConfigurationFile
+end
+
--- /dev/null
+local path = require 'pl.path'
+
+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)
+ return path.extension(filename) == '.lua'
+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
--- /dev/null
+local path = require 'pl.path'
+
+local ok, moonscript, line_tables, util = pcall(function()
+ return require 'moonscript', require 'moonscript.line_tables', require 'moonscript.util'
+end)
+
+local _cache = {}
+
+-- find the line number of `pos` chars into fname
+local lookup_line = function(fname, pos)
+ if not _cache[fname] then
+ local f = io.open(fname)
+ _cache[fname] = f:read('*a')
+ f:close()
+ end
+
+ return util.pos_to_line(_cache[fname], pos)
+end
+
+local rewrite_linenumber = function(fname, lineno)
+ local tbl = line_tables['@' .. fname]
+ if fname and tbl then
+ for i = lineno, 0 ,-1 do
+ if tbl[i] then
+ return lookup_line(fname, tbl[i])
+ end
+ end
+ end
+
+ return lineno
+end
+
+local rewrite_filename = function(filename)
+ -- sometimes moonscript gives files like [string "./filename.moon"], so
+ -- we'll chop it up to only get the filename.
+ return filename:match('string "(.+)"') or filename
+end
+
+local rewrite_traceback = function(fname, trace)
+ local rewrite_one = function(line, pattern, sub)
+ if line == nil then return '' end
+
+ local fname, lineno = line:match(pattern)
+
+ if fname and lineno then
+ fname = rewrite_filename(fname)
+ local new_lineno = rewrite_linenumber(fname, tonumber(lineno))
+ if new_lineno then
+ line = line:gsub(sub:format(tonumber(lineno)), sub:format(tonumber(new_lineno)))
+ end
+ end
+
+ return line
+ end
+
+ local lines = {}
+ local j = 0
+
+ for line in trace:gmatch('[^\r\n]+') do
+ j = j + 1
+ line = rewrite_one(line, '%s*(.-):(%d+): ', ':%d:')
+ line = rewrite_one(line, '<(.*):(%d+)>', ':%d>')
+ lines[j] = line
+ end
+
+ return '\n' .. table.concat(lines, trace:match('[\r\n]+')) .. '\n'
+end
+
+local ret = {}
+
+local getTrace = function(filename, info)
+ local index = info.traceback:find('\n%s*%[C]')
+ info.traceback = info.traceback:sub(1, index)
+
+ info.short_src = rewrite_filename(info.short_src)
+ info.traceback = rewrite_traceback(filename, info.traceback)
+ info.linedefined = rewrite_linenumber(filename, info.linedefined)
+ info.currentline = rewrite_linenumber(filename, info.currentline)
+
+ return info
+end
+
+local rewriteMessage = function(filename, message)
+ local fname, line, msg = message:match('^([^\n]-):(%d+): (.*)')
+ if not fname then
+ return message
+ end
+
+ fname = rewrite_filename(fname)
+ line = rewrite_linenumber(fname, tonumber(line))
+
+ return fname .. ':' .. tostring(line) .. ': ' .. msg
+end
+
+ret.match = function(busted, filename)
+ return ok and path.extension(filename) == '.moon'
+end
+
+
+ret.load = function(busted, filename)
+ local file, err = moonscript.loadfile(filename)
+ if not file then
+ busted.publish({ 'error', 'file' }, { descriptor = 'file', name = filename }, nil, err, {})
+ end
+ return file, getTrace, rewriteMessage
+end
+
+return ret
--- /dev/null
+local path = require 'pl.path'
+
+local ret = {}
+local ok, terralib = pcall(function() return require 'terralib' end)
+
+local getTrace = function(filename, info)
+ local index = info.traceback:find('\n%s*%[C]')
+ info.traceback = info.traceback:sub(1, index)
+ return info
+end
+
+ret.match = function(busted, filename)
+ return ok and path.extension(filename) == '.t'
+end
+
+ret.load = function(busted, filename)
+ local file, err = terralib.loadfile(filename)
+ if not file then
+ busted.publish({ 'error', 'file' }, { descriptor = 'file', name = filename }, nil, err, {})
+ end
+ return file, getTrace
+end
+
+return ret
--- /dev/null
+local utils = require 'busted.utils'
+local hasMoon, moonscript = pcall(require, 'moonscript')
+
+return function()
+ local loadHelper = function(helper, hpath, options, busted)
+ local success, err = pcall(function()
+ arg = options.arguments
+ if helper:match('%.lua$') then
+ dofile(utils.normpath(hpath))
+ elseif hasMoon and helper:match('%.moon$') then
+ moonscript.dofile(utils.normpath(hpath))
+ else
+ require(helper)
+ end
+ end)
+
+ if not success then
+ busted.publish({ 'error', 'helper' }, { descriptor = 'helper', name = helper }, nil, err, {})
+ end
+ end
+
+ return loadHelper
+end
--- /dev/null
+return function()
+ -- Function to initialize luacov if available
+ local loadLuaCov = function()
+ local result, luacov = pcall(require, 'luacov.runner')
+
+ if not result then
+ return print('LuaCov not found on the system, try running without --coverage option, or install LuaCov first')
+ end
+
+ -- call it to start
+ luacov()
+
+ -- exclude busted files
+ table.insert(luacov.configuration.exclude, 'busted_bootstrap$')
+ table.insert(luacov.configuration.exclude, 'busted%.')
+ table.insert(luacov.configuration.exclude, 'luassert%.')
+ table.insert(luacov.configuration.exclude, 'say%.')
+ table.insert(luacov.configuration.exclude, 'pl%.')
+ end
+
+ return loadLuaCov
+end
--- /dev/null
+local utils = require 'busted.utils'
+local hasMoon, moonscript = pcall(require, 'moonscript')
+
+return function()
+ local loadOutputHandler = function(output, opath, options, busted, defaultOutput)
+ local handler
+
+ local success, err = pcall(function()
+ if output:match('%.lua$') then
+ handler = dofile(utils.normpath(opath))
+ elseif hasMoon and output:match('%.moon$') then
+ handler = moonscript.dofile(utils.normpath(opath))
+ else
+ handler = require('busted.outputHandlers.' .. output)
+ end
+ end)
+
+ if not success and err:match("module '.-' not found:") then
+ success, err = pcall(function() handler = require(output) end)
+ end
+
+ if not success then
+ busted.publish({ 'error', 'output' }, { descriptor = 'output', name = output }, nil, err, {})
+ handler = require('busted.outputHandlers.' .. defaultOutput)
+ end
+
+ return handler(options, busted)
+ end
+
+ return loadOutputHandler
+end
--- /dev/null
+local s = require 'say'
+
+return function(busted, loaders, options)
+ local path = require 'pl.path'
+ local dir = require 'pl.dir'
+ local tablex = require 'pl.tablex'
+ local shuffle = require 'busted.utils'.shuffle
+ local fileLoaders = {}
+
+ for _, v in pairs(loaders) do
+ local loader = require('busted.modules.files.'..v)
+ fileLoaders[#fileLoaders+1] = loader
+ end
+
+ local getTestFiles = function(rootFile, pattern)
+ local fileList
+
+ if path.isfile(rootFile) then
+ fileList = { rootFile }
+ elseif path.isdir(rootFile) then
+ local getfiles = options.recursive and dir.getallfiles or dir.getfiles
+ fileList = getfiles(rootFile)
+
+ fileList = tablex.filter(fileList, function(filename)
+ return path.basename(filename):find(pattern)
+ end)
+
+ fileList = tablex.filter(fileList, function(filename)
+ if path.is_windows then
+ return not filename:find('%\\%.%w+.%w+')
+ else
+ return not filename:find('/%.%w+.%w+')
+ end
+ end)
+ else
+ fileList = {}
+ end
+
+ 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)
+
+ if options.shuffle then
+ shuffle(fileList, options.seed)
+ elseif options.sort then
+ table.sort(fileList)
+ end
+
+ for i, fileName in ipairs(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
+
+ if #fileList == 0 then
+ busted.publish({ 'error' }, {}, nil, s('output.no_test_files_match'):format(pattern), {})
+ end
+
+ return fileList
+ end
+
+ return loadTestFiles, loadTestFile, getTestFiles
+end
+
--- /dev/null
+local pretty = require 'pl.pretty'
+
+return function(options, busted)
+ local handler = require 'busted.outputHandlers.base'(busted)
+
+ handler.suiteEnd = function()
+ local total = handler.successesCount + handler.errorsCount + handler.failuresCount
+ print('1..' .. total)
+
+ local success = 'ok %u - %s'
+ local failure = 'not ' .. success
+ local counter = 0
+
+ for i,t in pairs(handler.successes) do
+ counter = counter + 1
+ print(success:format(counter, t.name))
+ end
+
+ local showFailure = function(t)
+ counter = counter + 1
+ local message = t.message
+ local trace = t.trace or {}
+
+ if message == nil then
+ message = 'Nil error'
+ elseif type(message) ~= 'string' then
+ message = pretty.write(message)
+ end
+
+ print(failure:format(counter, t.name))
+ print('# ' .. t.element.trace.short_src .. ' @ ' .. t.element.trace.currentline)
+ print('# Failure message: ' .. message:gsub('\n', '\n# '))
+ if options.verbose and trace.traceback then
+ print('# ' .. trace.traceback:gsub('^\n', '', 1):gsub('\n', '\n# '))
+ end
+ end
+
+ for i,t in pairs(handler.errors) do
+ showFailure(t)
+ end
+ for i,t in pairs(handler.failures) do
+ showFailure(t)
+ end
+
+ return nil, true
+ end
+
+ busted.subscribe({ 'suite', 'end' }, handler.suiteEnd)
+
+ return handler
+end
--- /dev/null
+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)
+ require('busted.languages.en')
+ handler.options = options
+
+ if options.language ~= 'en' then
+ require('busted.languages.' .. options.language)
+ end
+
+ busted.subscribe({ 'suite', 'reinitialize' }, handler.baseSuiteRepeat, { priority = 1 })
+ busted.subscribe({ 'suite', 'start' }, handler.baseSuiteStart, { priority = 1 })
+ busted.subscribe({ 'suite', 'end' }, handler.baseSuiteEnd, { priority = 1 })
+ busted.subscribe({ 'test', 'start' }, handler.baseTestStart, { priority = 1, predicate = handler.cancelOnPending })
+ busted.subscribe({ 'test', 'end' }, handler.baseTestEnd, { priority = 1, predicate = handler.cancelOnPending })
+ busted.subscribe({ 'pending' }, handler.basePending, { priority = 1, predicate = handler.cancelOnPending })
+ busted.subscribe({ 'failure', 'it' }, handler.baseTestFailure, { priority = 1 })
+ busted.subscribe({ 'error', 'it' }, handler.baseTestError, { priority = 1 })
+ busted.subscribe({ 'failure' }, handler.baseError, { priority = 1 })
+ busted.subscribe({ 'error' }, handler.baseError, { priority = 1 })
+ 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()
+ handler.startTime = os.clock()
+ return nil, true
+ end
+
+ handler.baseSuiteRepeat = function()
+ handler.successes = {}
+ handler.successesCount = 0
+ handler.pendings = {}
+ handler.pendingsCount = 0
+ handler.failures = {}
+ handler.failuresCount = 0
+ handler.errors = {}
+ handler.errorsCount = 0
+ handler.inProgress = {}
+
+ return nil, true
+ end
+
+ handler.baseSuiteEnd = function()
+ 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
+
+ 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
+ -- failure count already incremented in error handler
+ elseif status == 'error' then
+ -- error count already incremented in error handler
+ insertTable = handler.errors
+ isError = true
+ end
+
+ local formatted = handler.format(element, parent, element.message, debug, isError)
+
+ local id = tostring(element)
+ if handler.inProgress[id] then
+ for k, v in pairs(handler.inProgress[id]) do
+ formatted[k] = v
+ end
+
+ handler.inProgress[id] = nil
+ end
+
+ table.insert(insertTable, formatted)
+
+ return nil, true
+ end
+
+ local function saveInProgress(element, message, debug)
+ local id = tostring(element)
+ handler.inProgress[id].message = message
+ handler.inProgress[id].trace = debug
+ end
+
+ local function saveError(element, parent, message, debug)
+ if parent.randomseed then
+ message = 'Random Seed: ' .. parent.randomseed .. '\n' .. message
+ end
+ saveInProgress(element, message, debug)
+ end
+
+ handler.basePending = function(element, parent, message, debug)
+ saveInProgress(element, message, debug)
+ return nil, true
+ end
+
+ handler.baseTestFailure = function(element, parent, message, debug)
+ handler.failuresCount = handler.failuresCount + 1
+ saveError(element, parent, message, debug)
+ return nil, true
+ end
+
+ handler.baseTestError = function(element, parent, message, debug)
+ handler.errorsCount = handler.errorsCount + 1
+ saveError(element, parent, message, debug)
+ return nil, true
+ end
+
+ handler.baseError = function(element, parent, message, debug)
+ if element.descriptor ~= 'it' then
+ handler.errorsCount = handler.errorsCount + 1
+ table.insert(handler.errors, handler.format(element, parent, message, debug, true))
+ end
+
+ return nil, true
+ end
+
+ return handler
+end
--- /dev/null
+local json = require 'dkjson'
+
+return function(options, busted)
+ local handler = require 'busted.outputHandlers.base'(busted)
+
+ handler.suiteEnd = function()
+ print(json.encode({
+ pendings = handler.pendings,
+ successes = handler.successes,
+ failures = handler.failures,
+ errors = handler.errors,
+ duration = handler.getDuration()
+ }))
+
+ return nil, true
+ end
+
+ busted.subscribe({ 'suite', 'end' }, handler.suiteEnd)
+
+ return handler
+end
--- /dev/null
+local xml = require 'pl.xml'
+local socket = require("socket")
+local string = require("string")
+
+return function(options, busted)
+ local handler = require 'busted.outputHandlers.base'(busted)
+ local top = {
+ start_time = socket.gettime(),
+ xml_doc = xml.new('testsuites', {
+ tests = 0,
+ errors = 0,
+ failures = 0,
+ skip = 0,
+ })
+ }
+ local stack = {}
+ local testStartTime
+
+ handler.suiteStart = function(count, total)
+ local suite = {
+ start_time = socket.gettime(),
+ xml_doc = xml.new('testsuite', {
+ name = 'Run ' .. count .. ' of ' .. total,
+ tests = 0,
+ errors = 0,
+ failures = 0,
+ skip = 0,
+ timestamp = os.date('!%Y-%m-%dT%T'),
+ })
+ }
+ top.xml_doc:add_direct_child(suite.xml_doc)
+ table.insert(stack, top)
+ top = suite
+
+ return nil, true
+ end
+
+ local function elapsed(start_time)
+ return string.format("%.2f", (socket.gettime() - start_time))
+ end
+
+ handler.suiteEnd = function(count, total)
+ local suite = top
+ suite.xml_doc.attr.time = elapsed(suite.start_time)
+
+ top = table.remove(stack)
+ top.xml_doc.attr.tests = top.xml_doc.attr.tests + suite.xml_doc.attr.tests
+ top.xml_doc.attr.errors = top.xml_doc.attr.errors + suite.xml_doc.attr.errors
+ top.xml_doc.attr.failures = top.xml_doc.attr.failures + suite.xml_doc.attr.failures
+ top.xml_doc.attr.skip = top.xml_doc.attr.skip + suite.xml_doc.attr.skip
+
+ return nil, true
+ end
+
+ handler.exit = function()
+ top.xml_doc.attr.time = elapsed(top.start_time)
+ print(xml.tostring(top.xml_doc, '', '\t', nil, false))
+
+ return nil, true
+ end
+
+ local function testStatus(element, parent, message, status, trace)
+ local testcase_node = xml.new('testcase', {
+ classname = element.trace.short_src .. ':' .. element.trace.currentline,
+ name = handler.getFullName(element),
+ time = elapsed(testStartTime)
+ })
+ top.xml_doc:add_direct_child(testcase_node)
+
+ if status ~= 'success' then
+ testcase_node:addtag(status)
+ if message then testcase_node:text(message) end
+ if trace and trace.traceback then testcase_node:text(trace.traceback) end
+ testcase_node:up()
+ end
+ end
+
+ handler.testStart = function(element, parent)
+ testStartTime = socket.gettime()
+
+ return nil, true
+ end
+
+ handler.testEnd = function(element, parent, status)
+ top.xml_doc.attr.tests = top.xml_doc.attr.tests + 1
+
+ if status == 'success' then
+ testStatus(element, parent, nil, 'success')
+ elseif status == 'pending' then
+ top.xml_doc.attr.skip = top.xml_doc.attr.skip + 1
+ local formatted = handler.pendings[#handler.pendings]
+ local trace = element.trace ~= formatted.trace and formatted.trace
+ testStatus(element, parent, formatted.message, 'skipped', trace)
+ end
+
+ return nil, true
+ end
+
+ handler.failureTest = function(element, parent, message, trace)
+ top.xml_doc.attr.failures = top.xml_doc.attr.failures + 1
+ testStatus(element, parent, message, 'failure', trace)
+ return nil, true
+ end
+
+ handler.errorTest = function(element, parent, message, trace)
+ top.xml_doc.attr.errors = top.xml_doc.attr.errors + 1
+ testStatus(element, parent, message, 'error', trace)
+ return nil, true
+ end
+
+ handler.error = function(element, parent, message, trace)
+ if element.descriptor ~= 'it' then
+ top.xml_doc.attr.errors = top.xml_doc.attr.errors + 1
+ top.xml_doc:addtag('error')
+ top.xml_doc:text(message)
+ if trace and trace.traceback then
+ top.xml_doc:text(trace.traceback)
+ end
+ top.xml_doc:up()
+ end
+
+ return nil, true
+ end
+
+ busted.subscribe({ 'exit' }, handler.exit)
+ busted.subscribe({ 'suite', 'start' }, handler.suiteStart)
+ busted.subscribe({ 'suite', 'end' }, handler.suiteEnd)
+ busted.subscribe({ 'test', 'start' }, handler.testStart, { predicate = handler.cancelOnPending })
+ busted.subscribe({ 'test', 'end' }, handler.testEnd, { predicate = handler.cancelOnPending })
+ busted.subscribe({ 'error', 'it' }, handler.errorTest)
+ busted.subscribe({ 'failure', 'it' }, handler.failureTest)
+ busted.subscribe({ 'error' }, handler.error)
+ busted.subscribe({ 'failure' }, handler.error)
+
+ return handler
+end
--- /dev/null
+local s = require 'say'
+local pretty = require 'pl.pretty'
+
+return function(options, busted)
+ local handler = require 'busted.outputHandlers.base'(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' .. pretty.write(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 = pretty.write(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
+
+ io.write(string)
+ io.flush()
+ end
+
+ return nil, true
+ end
+
+ handler.suiteStart = function(count, total)
+ local runString = (total > 1 and '\nRepeating all tests (run %d of %d) . . .\n\n' or '')
+ io.write(runString:format(count, total))
+ io.flush()
+ end
+
+ handler.suiteEnd = function()
+ 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)
+ io.write(errorDot)
+ io.flush()
+
+ return nil, true
+ end
+
+ busted.subscribe({ 'test', 'end' }, handler.testEnd, { predicate = handler.cancelOnPending })
+ busted.subscribe({ 'suite', 'start' }, handler.suiteStart)
+ busted.subscribe({ 'suite', 'end' }, handler.suiteEnd)
+ busted.subscribe({ 'error', 'file' }, handler.error)
+ busted.subscribe({ 'failure', 'file' }, handler.error)
+ busted.subscribe({ 'error', 'describe' }, handler.error)
+ busted.subscribe({ 'failure', 'describe' }, handler.error)
+
+ return handler
+end
--- /dev/null
+local app = require 'pl.app'
+return function(options, busted)
+ local handler = require 'busted.outputHandlers.base'(busted)
+ local language = require('busted.languages.' .. options.language)
+
+ handler.suiteEnd = function()
+ local system = app.platform()
+ local sayer_pre, sayer_post
+ local messages
+
+ if system == 'Linux' then
+ sayer_pre = 'espeak -s 160 '
+ sayer_post = ' > /dev/null 2>&1'
+ elseif system and system:match('^Windows') then
+ sayer_pre = 'echo '
+ sayer_post = ' | ptts'
+ else
+ sayer_pre = 'say '
+ sayer_post = ''
+ end
+
+ if handler.failuresCount > 0 then
+ messages = language.failure_messages
+ else
+ messages = language.success_messages
+ end
+
+ io.popen(sayer_pre .. '"' .. messages[math.random(1, #messages)] .. '"' .. sayer_post)
+
+ return nil, true
+ end
+
+ busted.subscribe({ 'suite', 'end' }, handler.suiteEnd)
+
+ return handler
+end
--- /dev/null
+local ansicolors = require 'ansicolors'
+local s = require 'say'
+local pretty = require 'pl.pretty'
+
+return function(options, busted)
+ local handler = require 'busted.outputHandlers.base'(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) ..
+ '\n' .. ansicolors('%{bright}' .. name)
+
+ if type(pending.message) == 'string' then
+ string = string .. '\n' .. pending.message
+ elseif pending.message ~= nil then
+ string = string .. '\n' .. pretty.write(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 = pretty.write(failure.message)
+ end
+
+ return string
+ end
+
+ local failureDescription = function(failure, isError)
+ local string = ansicolors('%{red}' .. s('output.failure')) .. ' → '
+ if isError then
+ string = ansicolors('%{magenta}' .. s('output.error')) .. ' → '
+ end
+
+ if not failure.element.trace or not failure.element.trace.short_src then
+ string = string ..
+ ansicolors('%{cyan}' .. failureMessage(failure)) .. '\n' ..
+ ansicolors('%{bright}' .. failure.name)
+ else
+ string = string ..
+ ansicolors('%{cyan}' .. failure.element.trace.short_src) .. ' @ ' ..
+ ansicolors('%{cyan}' .. failure.element.trace.currentline) .. '\n' ..
+ ansicolors('%{bright}' .. 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 ansicolors('%{green}' .. successes) .. ' ' .. successString .. ' / ' ..
+ ansicolors('%{red}' .. failures) .. ' ' .. failureString .. ' / ' ..
+ ansicolors('%{magenta}' .. errors) .. ' ' .. errorString .. ' / ' ..
+ ansicolors('%{yellow}' .. pendings) .. ' ' .. pendingString .. ' : ' ..
+ ansicolors('%{bright}' .. 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
+
+ io.write(string)
+ io.flush()
+ end
+
+ return nil, true
+ end
+
+ handler.suiteStart = function(count, total)
+ local runString = (total > 1 and '\nRepeating all tests (run %d of %d) . . .\n\n' or '')
+ io.write(runString:format(count, total))
+ io.flush()
+ end
+
+ handler.suiteEnd = function(count, total)
+ 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)
+ io.write(errorDot)
+ io.flush()
+
+ return nil, true
+ end
+
+ busted.subscribe({ 'test', 'end' }, handler.testEnd, { predicate = handler.cancelOnPending })
+ busted.subscribe({ 'suite', 'start' }, handler.suiteStart)
+ busted.subscribe({ 'suite', 'end' }, handler.suiteEnd)
+ busted.subscribe({ 'error', 'file' }, handler.error)
+ busted.subscribe({ 'failure', 'file' }, handler.error)
+ busted.subscribe({ 'error', 'describe' }, handler.error)
+ busted.subscribe({ 'failure', 'describe' }, handler.error)
+
+ return handler
+end
--- /dev/null
+-- Busted command-line runner
+
+local path = require 'pl.path'
+local term = require 'term'
+local utils = require 'busted.utils'
+local loaded = false
+
+return function(options)
+ if loaded then return else loaded = true end
+
+ local opt = options or {}
+ local isBatch = opt.batch
+ local cli = require 'cliargs'
+ local busted = require 'busted.core'()
+
+ local configLoader = require 'busted.modules.configuration_loader'()
+ local helperLoader = require 'busted.modules.helper_loader'()
+ local outputHandlerLoader = require 'busted.modules.output_handler_loader'()
+
+ local luacov = require 'busted.modules.luacov'()
+
+ local osexit = require 'busted.compatibility'.osexit
+
+ require 'busted'(busted)
+
+ -- Default cli arg values
+ local defaultOutput = term.isatty(io.stdout) and 'utfTerminal' or 'plainTerminal'
+ local defaultLoaders = 'lua,moonscript'
+ local defaultPattern = '_spec'
+ local defaultSeed = 'os.time()'
+ local lpathprefix = './src/?.lua;./src/?/?.lua;./src/?/init.lua'
+ local cpathprefix = path.is_windows and './csrc/?.dll;./csrc/?/?.dll;' or './csrc/?.so;./csrc/?/?.so;'
+
+ local level = 2
+ local info = debug.getinfo(level, 'Sf')
+ local source = info.source
+ local fileName = source:sub(1,1) == '@' and source:sub(2) or source
+
+ local cliArgsParsed = {}
+
+ local function processOption(key, value, altkey, opt)
+ if altkey then cliArgsParsed[altkey] = value end
+ cliArgsParsed[key] = value
+ return true
+ end
+
+ local function processNumber(key, value, altkey, opt)
+ local number = tonumber(value)
+ if not number then
+ return nil, 'argument to ' .. opt:gsub('=.*', '') .. ' must be a number'
+ end
+ if altkey then cliArgsParsed[altkey] = number end
+ cliArgsParsed[key] = number
+ return true
+ end
+
+ local function processVersion()
+ -- Return early if asked for the version
+ print(busted.version)
+ osexit(0, true)
+ end
+
+ -- Load up the command-line interface options
+ cli:set_name(path.basename(fileName))
+ cli:add_flag('--version', 'prints the program version and exits', processVersion)
+
+ if isBatch then
+ cli:optarg('ROOT', 'test script file/folder. Folders will be traversed for any file that matches the --pattern option.', 'spec', 1)
+
+ cli:add_option('-p, --pattern=PATTERN', 'only run test files matching the Lua pattern', defaultPattern, processOption)
+ end
+
+ cli:add_option('-o, --output=LIBRARY', 'output library to load', defaultOutput, processOption)
+ cli:add_option('-d, --cwd=cwd', 'path to current working directory', './', processOption)
+ cli:add_option('-t, --tags=TAGS', 'only run tests with these #tags', nil, processOption)
+ cli:add_option('--exclude-tags=TAGS', 'do not run tests with these #tags, takes precedence over --tags', nil, processOption)
+ cli:add_option('--filter=PATTERN', 'only run test names matching the Lua pattern', nil, processOption)
+ cli:add_option('--filter-out=PATTERN', 'do not run test names matching the Lua pattern, takes precedence over --filter', nil, processOption)
+ cli:add_option('-m, --lpath=PATH', 'optional path to be prefixed to the Lua module search path', lpathprefix, processOption)
+ cli:add_option('--cpath=PATH', 'optional path to be prefixed to the Lua C module search path', cpathprefix, processOption)
+ cli:add_option('-r, --run=RUN', 'config to run from .busted file', nil, processOption)
+ cli:add_option('--repeat=COUNT', 'run the tests repeatedly', '1', processNumber)
+ cli:add_option('--seed=SEED', 'random seed value to use for shuffling test order', defaultSeed, processNumber)
+ cli:add_option('--lang=LANG', 'language for error messages', 'en', processOption)
+ cli:add_option('--loaders=NAME', 'test file loaders', defaultLoaders, processOption)
+ cli:add_option('--helper=PATH', 'A helper script that is run before tests', nil, processOption)
+
+ cli:add_option('-Xoutput OPTION', 'pass `OPTION` as an option to the output handler. If `OPTION` contains commas, it is split into multiple options at the commas.', nil, processOption)
+ cli:add_option('-Xhelper OPTION', 'pass `OPTION` as an option to the helper script. If `OPTION` contains commas, it is split into multiple options at the commas.', nil, processOption)
+
+ cli:add_flag('-c, --coverage', 'do code coverage analysis (requires `LuaCov` to be installed)', processOption)
+ cli:add_flag('-v, --verbose', 'verbose output of errors', processOption)
+ cli:add_flag('-s, --enable-sound', 'executes `say` command if available', processOption)
+ cli:add_flag('-l, --list', 'list the names of all tests instead of running them', processOption)
+ cli:add_flag('--no-keep-going', 'quit after first error or failure', processOption)
+ cli:add_flag('--no-recursive', 'do not recurse into subdirectories', processOption)
+ cli:add_flag('--shuffle', 'randomize file and test order, takes precedence over --sort (--shuffle-test and --shuffle-files)', processOption)
+ cli:add_flag('--shuffle-files', 'randomize file execution order, takes precedence over --sort-files', processOption)
+ cli:add_flag('--shuffle-tests', 'randomize test order within a file, takes precedence over --sort-tests', processOption)
+ cli:add_flag('--sort', 'sort file and test order (--sort-tests and --sort-files)', processOption)
+ cli:add_flag('--sort-files', 'sort file execution order', processOption)
+ cli:add_flag('--sort-tests', 'sort test order within a file', processOption)
+ cli:add_flag('--suppress-pending', 'suppress `pending` test output', processOption)
+ cli:add_flag('--defer-print', 'defer print to when test suite is complete', processOption)
+
+ -- Parse the cli arguments
+ local cliArgs = cli:parse(arg)
+ if not cliArgs then
+ osexit(1, true)
+ end
+
+ -- Load current working directory
+ local fpath = utils.normpath(cliArgs.cwd)
+
+ -- Load busted config file if available
+ local configFile = { }
+ local bustedConfigFilePath = utils.normpath(path.join(fpath, '.busted'))
+ local bustedConfigFile = pcall(function() configFile = loadfile(bustedConfigFilePath)() end)
+ if bustedConfigFile then
+ local config, err = configLoader(configFile, cliArgsParsed, cliArgs)
+ if err then
+ print('Error: ' .. err)
+ osexit(1, true)
+ else
+ cliArgs = config
+ end
+ end
+
+ local tags = {}
+ local excludeTags = {}
+
+ if cliArgs.tags and cliArgs.tags ~= '' then
+ tags = utils.split(cliArgs.tags, ',')
+ end
+
+ if cliArgs['exclude-tags'] and cliArgs['exclude-tags'] ~= '' then
+ excludeTags = utils.split(cliArgs['exclude-tags'], ',')
+ end
+
+ -- If coverage arg is passed in, load LuaCovsupport
+ if cliArgs.coverage then
+ luacov()
+ end
+
+ -- Add additional package paths based on lpath and cpath cliArgs
+ if #cliArgs.lpath > 0 then
+ lpathprefix = cliArgs.lpath
+ lpathprefix = lpathprefix:gsub('^%.([/%\\])', fpath .. '%1')
+ lpathprefix = lpathprefix:gsub(';%.([/%\\])', ';' .. fpath .. '%1')
+ package.path = (lpathprefix .. ';' .. package.path):gsub(';;',';')
+ end
+
+ if #cliArgs.cpath > 0 then
+ cpathprefix = cliArgs.cpath
+ cpathprefix = cpathprefix:gsub('^%.([/%\\])', fpath .. '%1')
+ cpathprefix = cpathprefix:gsub(';%.([/%\\])', ';' .. fpath .. '%1')
+ package.cpath = (cpathprefix .. ';' .. package.cpath):gsub(';;',';')
+ end
+
+ local loaders = {}
+ if #cliArgs.loaders > 0 then
+ string.gsub(cliArgs.loaders, '([^,]+)', function(c) loaders[#loaders+1] = c end)
+ end
+
+ -- 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(excludeTags) do
+ for _, included in pairs(tags) do
+ if excluded == included then
+ print('Error: Cannot use --tags and --exclude-tags for the same tags')
+ osexit(1, true)
+ end
+ end
+ end
+
+ -- watch for test errors
+ local failures = 0
+ local errors = 0
+ local quitOnError = cliArgs['no-keep-going']
+
+ busted.subscribe({ 'error', 'output' }, function(element, parent, message)
+ print('Error: Cannot load output library: ' .. element.name .. '\n' .. message)
+ return nil, true
+ end)
+
+ busted.subscribe({ 'error', 'helper' }, function(element, parent, message)
+ print('Error: Cannot load helper script: ' .. element.name .. '\n' .. message)
+ return nil, true
+ end)
+
+ busted.subscribe({ 'error' }, function(element, parent, message)
+ errors = errors + 1
+ busted.skipAll = quitOnError
+ return nil, true
+ end)
+
+ busted.subscribe({ 'failure' }, function(element, parent, message)
+ if element.descriptor == 'it' then
+ failures = failures + 1
+ else
+ errors = errors + 1
+ end
+ busted.skipAll = quitOnError
+ return nil, true
+ end)
+
+ -- Set up output handler to listen to events
+ local outputHandlerOptions = {
+ verbose = cliArgs.verbose,
+ suppressPending = cliArgs['suppress-pending'],
+ language = cliArgs.lang,
+ deferPrint = cliArgs['defer-print'],
+ arguments = utils.split(cliArgs.Xoutput or '', ',') or {}
+ }
+
+ local opath = utils.normpath(path.join(fpath, cliArgs.output))
+ local outputHandler = outputHandlerLoader(cliArgs.output, opath, outputHandlerOptions, busted, defaultOutput)
+ outputHandler:subscribe(outputHandlerOptions)
+
+ if cliArgs['enable-sound'] then
+ require 'busted.outputHandlers.sound'(outputHandlerOptions, busted)
+ end
+
+ -- Set up randomization options
+ busted.sort = cliArgs['sort-tests'] or cliArgs.sort
+ busted.randomize = cliArgs['shuffle-tests'] or cliArgs.shuffle
+ busted.randomseed = tonumber(cliArgs.seed) or os.time()
+
+ local getFullName = function(name)
+ local parent = busted.context.get()
+ local names = { name }
+
+ 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
+
+ local hasTag = function(name, tag)
+ local found = name:find('#' .. tag)
+ return (found ~= nil)
+ end
+
+ local filterExcludeTags = function(name)
+ for i, tag in pairs(excludeTags) do
+ if hasTag(name, tag) then
+ return nil, false
+ end
+ end
+ return nil, true
+ end
+
+ local filterTags = function(name)
+ local fullname = getFullName(name)
+ for i, tag in pairs(tags) do
+ if hasTag(fullname, tag) then
+ return nil, true
+ end
+ end
+ return nil, (#tags == 0)
+ end
+
+ local filterOutNames = function(name)
+ local found = (getFullName(name):find(cliArgs['filter-out']) ~= nil)
+ return nil, not found
+ end
+
+ local filterNames = function(name)
+ local found = (getFullName(name):find(cliArgs.filter) ~= nil)
+ return nil, found
+ end
+
+ local printNameOnly = function(name, fn, trace)
+ local fullname = getFullName(name)
+ if trace and trace.what == 'Lua' then
+ print(trace.short_src .. ':' .. trace.currentline .. ': ' .. fullname)
+ else
+ print(fullname)
+ end
+ return nil, false
+ end
+
+ local ignoreAll = function()
+ return nil, false
+ end
+
+ local skipOnError = function()
+ return nil, (failures == 0 and errors == 0)
+ end
+
+ local applyFilter = function(descriptors, name, fn)
+ if cliArgs[name] and cliArgs[name] ~= '' then
+ for _, descriptor in ipairs(descriptors) do
+ busted.subscribe({ 'register', descriptor }, fn, { priority = 1 })
+ end
+ end
+ end
+
+ if cliArgs.list then
+ busted.subscribe({ 'suite', 'start' }, ignoreAll, { priority = 1 })
+ busted.subscribe({ 'suite', 'end' }, ignoreAll, { priority = 1 })
+ applyFilter({ 'setup', 'teardown', 'before_each', 'after_each' }, 'list', ignoreAll)
+ applyFilter({ 'it', 'pending' }, 'list', printNameOnly)
+ end
+
+ applyFilter({ 'setup', 'teardown', 'before_each', 'after_each' }, 'no-keep-going', skipOnError)
+ applyFilter({ 'file', 'describe', 'it', 'pending' }, 'no-keep-going', skipOnError)
+
+ -- The following filters are applied in reverse order
+ applyFilter({ 'it', 'pending' } , 'filter' , filterNames )
+ applyFilter({ 'describe', 'it', 'pending' }, 'filter-out' , filterOutNames )
+ applyFilter({ 'it', 'pending' } , 'tags' , filterTags )
+ applyFilter({ 'describe', 'it', 'pending' }, 'exclude-tags', filterExcludeTags)
+
+ -- Set up helper script
+ if cliArgs.helper and cliArgs.helper ~= '' then
+ local helperOptions = {
+ verbose = cliArgs.verbose,
+ language = cliArgs.lang,
+ arguments = utils.split(cliArgs.Xhelper or '', ',') or {}
+ }
+
+ local hpath = utils.normpath(path.join(fpath, cliArgs.helper))
+ helperLoader(cliArgs.helper, hpath, helperOptions, busted)
+ end
+
+ -- Set up test loader options
+ local testFileLoaderOptions = {
+ verbose = cliArgs.verbose,
+ sort = cliArgs['sort-files'] or cliArgs.sort,
+ shuffle = cliArgs['shuffle-files'] or cliArgs.shuffle,
+ recursive = not cliArgs['no-recursive'],
+ seed = busted.randomseed
+ }
+
+ -- Load test directory
+ local rootFile = cliArgs.ROOT and utils.normpath(path.join(fpath, cliArgs.ROOT)) or fileName
+ local pattern = cliArgs.pattern
+ local testFileLoader = require 'busted.modules.test_file_loader'(busted, loaders, testFileLoaderOptions)
+ local fileList = testFileLoader(rootFile, pattern)
+
+ if not cliArgs.ROOT then
+ local ctx = busted.context.get()
+ local file = busted.context.children(ctx)[1]
+ getmetatable(file.run).__call = info.func
+ end
+
+ busted.subscribe({'suite', 'reinitialize'}, function()
+ local oldctx = busted.context.get()
+ local children = busted.context.children(oldctx)
+
+ busted.context.clear()
+ local ctx = busted.context.get()
+ for k, v in pairs(oldctx) do
+ ctx[k] = v
+ end
+
+ for _, child in pairs(children) do
+ for descriptor, _ in pairs(busted.executors) do
+ child[descriptor] = nil
+ end
+ busted.context.attach(child)
+ end
+
+ busted.randomseed = tonumber(cliArgs.seed) or os.time()
+
+ return nil, true
+ end)
+
+ local runs = tonumber(cliArgs['repeat']) or 1
+ for i = 1, runs do
+ if i > 1 then
+ busted.publish({ 'suite', 'reinitialize' })
+ end
+
+ busted.publish({ 'suite', 'start' }, i, runs)
+ busted.execute()
+ busted.publish({ 'suite', 'end' }, i, runs)
+
+ if quitOnError and (failures > 0 or errors > 0) then
+ break
+ end
+ end
+
+ busted.publish({ 'exit' })
+
+ local exit = 0
+ if failures > 0 or errors > 0 then
+ exit = failures + errors
+ if exit > 255 then
+ exit = 255
+ end
+ end
+ osexit(exit, true)
+end
--- /dev/null
+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
--- /dev/null
+local path = require 'pl.path'
+
+math.randomseed(os.time())
+
+-- Do not use pl.path.normpath
+-- It is broken for paths with leading '../../'
+local function normpath(fpath)
+ if type(fpath) ~= 'string' then
+ error(fpath .. ' is not a string')
+ end
+ local sep = '/'
+ if path.is_windows then
+ sep = '\\'
+ if fpath:match '^\\\\' then -- UNC
+ return '\\\\' .. normpath(fpath:sub(3))
+ end
+ fpath = fpath:gsub('/','\\')
+ end
+ local np_gen1, np_gen2 = '([^SEP]+)SEP(%.%.SEP?)', 'SEP+%.?SEP'
+ local np_pat1 = np_gen1:gsub('SEP', sep)
+ local np_pat2 = np_gen2:gsub('SEP', sep)
+ local k
+ repeat -- /./ -> /
+ fpath, k = fpath:gsub(np_pat2, sep)
+ until k == 0
+ repeat -- A/../ -> (empty)
+ local oldpath = fpath
+ fpath, k = fpath:gsub(np_pat1, function(d, up)
+ if d == '..' then return nil end
+ if d == '.' then return up end
+ return ''
+ end)
+ until k == 0 or oldpath == fpath
+ if fpath == '' then fpath = '.' end
+ return fpath
+end
+
+return {
+ split = require 'pl.utils'.split,
+
+ normpath = normpath,
+
+ shuffle = function(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
+}