diff options
Diffstat (limited to 'spec/spec_helper.lua')
-rw-r--r-- | spec/spec_helper.lua | 277 |
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 |