aboutsummaryrefslogtreecommitdiff
path: root/spec/spec_helper.lua
diff options
context:
space:
mode:
Diffstat (limited to 'spec/spec_helper.lua')
-rw-r--r--spec/spec_helper.lua277
1 files changed, 277 insertions, 0 deletions
diff --git a/spec/spec_helper.lua b/spec/spec_helper.lua
new file mode 100644
index 000000000000..1ba7a3a7559f
--- /dev/null
+++ b/spec/spec_helper.lua
@@ -0,0 +1,277 @@
+--[[
+ LYAML binding for Lua 5.1, 5.2, 5.3 & 5.4
+ Copyright (C) 2013-2022 Gary V. Vaughan
+]]
+
+do
+ local std = require 'specl.std'
+ local spawn = require 'specl.shell'.spawn
+ local objdir = spawn('./build-aux/luke --value=objdir').output
+
+
+ package.path = std.package.normalize(
+ './lib/?.lua',
+ './lib/?/init.lua',
+ package.path
+ )
+ package.cpath = std.package.normalize(
+ './' .. objdir:match("^objdir='(.*)'") .. '/?.so',
+ './' .. objdir:match("^objdir='(.*)'") .. '/?.dll',
+ package.cpath
+ )
+end
+
+local hell = require 'specl.shell'
+
+
+yaml = require 'yaml'
+
+BOM = string.char(254, 255) -- UTF-16 Byte Order Mark
+
+-- Allow use of bare 'pack' and 'unpack' even in Lua > 5.2.
+pack = table.pack or function(...) return {n = select('#', ...), ...} end
+unpack = table.unpack or unpack
+list = pack
+
+
+function dump(e)
+ print(std.string.prettytostring(e))
+end
+
+
+function github_issue(n)
+ return 'see http://github.com/gvvaughan/lyaml/issues/' .. tostring(n)
+end
+
+
+-- Output a list of event tables to the given emitter.
+function emitevents(emitter, list)
+ for _, v in ipairs(list) do
+ if type(v) == 'string' then
+ ok, msg = emitter.emit {type=v}
+ elseif type(v) == 'table' then
+ ok, msg = emitter.emit(v)
+ else
+ error 'expected table or string argument'
+ end
+
+ if not ok then
+ error(msg)
+ elseif ok and msg then
+ return msg
+ end
+ end
+end
+
+
+-- Create a new emitter and send STREAM_START, listed events and STREAM_END.
+function emit(list)
+ local emitter = yaml.emitter()
+ emitter.emit {type='STREAM_START'}
+ emitevents(emitter, list)
+ local _, msg = emitter.emit {type='STREAM_END'}
+ return msg
+end
+
+
+-- Create a new parser for STR, and consume the first N events.
+function consume(n, str)
+ local e = yaml.parser(str)
+ for n = 1, n do
+ e()
+ end
+ return e
+end
+
+
+-- Return a new table with only elements of T that have keys listed
+-- in the following arguments.
+function filter(t, ...)
+ local u = {}
+ for _, k in ipairs {...} do
+ u[k] = t[k]
+ end
+ return u
+end
+
+
+function iscallable(x)
+ return type(x) == 'function' or type((getmetatable(x) or {}).__call) == 'function'
+end
+
+
+local function mkscript(code)
+ local f = os.tmpname()
+ local h = io.open(f, 'w')
+ -- TODO: Move this into specl, or expose arguments so that we can
+ -- turn this on and off based on specl `--coverage` arg.
+ h:write "pcall(require, 'luacov')"
+ h:write(code)
+ h:close()
+ return f
+end
+
+
+-- Allow user override of LUA binary used by hell.spawn, falling
+-- back to environment PATH search for 'lua' if nothing else works.
+local LUA = os.getenv 'LUA' or 'lua'
+
+
+--- Run some Lua code with the given arguments and input.
+-- @string code valid Lua code
+-- @tparam[opt={}] string|table arg single argument, or table of
+-- arguments for the script invocation.
+-- @string[opt] stdin standard input contents for the script process
+-- @treturn specl.shell.Process|nil status of resulting process if
+-- execution was successful, otherwise nil
+function luaproc(code, arg, stdin)
+ local f = mkscript(code)
+ if type(arg) ~= 'table' then arg = {arg} end
+ local cmd = {LUA, f, unpack(arg)}
+ -- inject env and stdin keys separately to avoid truncating `...` in
+ -- cmd constructor
+ cmd.env = { LUA_PATH=package.path, LUA_INIT='', LUA_INIT_5_2='' }
+ cmd.stdin = stdin
+ local proc = hell.spawn(cmd)
+ os.remove(f)
+ return proc
+end
+
+
+local function tabulate_output(code)
+ local proc = luaproc(code)
+ if proc.status ~= 0 then return error(proc.errout) end
+ local r = {}
+ proc.output:gsub('(%S*)[%s]*',
+ function(x)
+ if x ~= '' then r[x] = true end
+ end)
+ return r
+end
+
+
+--- Show changes to tables wrought by a require statement.
+-- There are a few modes to this function, controlled by what named
+-- arguments are given. Lists new keys in T1 after `require "import"`:
+--
+-- show_apis {added_to=T1, by=import}
+--
+-- @tparam table argt one of the combinations above
+-- @treturn table a list of keys according to criteria above
+function show_apis(argt)
+ return tabulate_output([[
+ local before, after = {}, {}
+ for k in pairs(]] .. argt.added_to .. [[) do
+ before[k] = true
+ end
+
+ local M = require ']] .. argt.by .. [['
+ for k in pairs(]] .. argt.added_to .. [[) do
+ after[k] = true
+ end
+
+ for k in pairs(after) do
+ if not before[k] then print(k) end
+ end
+ ]])
+end
+
+
+
+--[[ ========= ]]--
+--[[ Call Spy. ]]--
+--[[ ========= ]]--
+
+
+spy = function(fn)
+ return setmetatable({}, {
+ __call = function(self, ...)
+ self[#self + 1] = list(...)
+ return fn(...)
+ end,
+ })
+end
+
+
+do
+ --[[ ================ ]]--
+ --[[ Custom matchers. ]]--
+ --[[ ================ ]]--
+
+ local matchers = require 'specl.matchers'
+ local eqv = require 'specl.std'.operator.eqv
+ local str = require 'specl.std'.string.tostring
+
+ local Matcher, matchers = matchers.Matcher, matchers.matchers
+ local concat = table.concat
+
+
+ matchers.be_called_with = Matcher {
+ function(self, actual, expected)
+ for i,v in ipairs(expected or {}) do
+ if not eqv(actual[i], v) then
+ return false
+ end
+ end
+ return true
+ end,
+
+ actual = 'argmuents',
+
+ format_expect = function(self, expect)
+ return ' arguments (' .. str(expect) .. '), '
+ end,
+ }
+
+ matchers.be_callable = Matcher {
+ function(self, actual, _)
+ return iscallable(actual)
+ end,
+
+ actual = 'callable',
+
+ format_expect = function(self, expect)
+ return ' callable, '
+ end,
+ }
+
+ matchers.be_falsey = Matcher {
+ function(self, actual, _)
+ return not actual and true or false
+ end,
+
+ actual = 'falsey',
+
+ format_expect = function(self, expect)
+ return ' falsey, '
+ end,
+ }
+
+ matchers.be_truthy = Matcher {
+ function(self, actual, _)
+ return actual and true or false
+ end,
+
+ actual = 'truthy',
+
+ format_expect = function(self, expect)
+ return ' truthy, '
+ end,
+ }
+
+ matchers.have_type = Matcher {
+ function(self, actual, expected)
+ return type(actual) == expected or (getmetatable(actual) or {})._type == expected
+ end,
+
+ actual = 'type',
+
+ format_expect = function(self, expect)
+ local article = 'a'
+ if match(expect, '^[aehiou]') then
+ article = 'an'
+ end
+ return concat{' ', article, ' ', expect, ', '}
+ end
+ }
+end