diff options
Diffstat (limited to 'Data/BuiltIn/Libraries/lua-stdlib/spec/spec_helper.lua')
-rw-r--r-- | Data/BuiltIn/Libraries/lua-stdlib/spec/spec_helper.lua | 416 |
1 files changed, 416 insertions, 0 deletions
diff --git a/Data/BuiltIn/Libraries/lua-stdlib/spec/spec_helper.lua b/Data/BuiltIn/Libraries/lua-stdlib/spec/spec_helper.lua new file mode 100644 index 0000000..642712f --- /dev/null +++ b/Data/BuiltIn/Libraries/lua-stdlib/spec/spec_helper.lua @@ -0,0 +1,416 @@ +--[[ + General Lua Libraries for Lua 5.1, 5.2 & 5.3 + Copyright (C) 2011-2018 stdlib authors +]] + +local typecheck +have_typecheck, typecheck = pcall(require, 'typecheck') + +local inprocess = require 'specl.inprocess' +local hell = require 'specl.shell' +local std = require 'specl.std' + +badargs = require 'specl.badargs' + + +local top_srcdir = os.getenv 'top_srcdir' or '.' +local top_builddir = os.getenv 'top_builddir' or '.' + +package.path = std.package.normalize( + top_builddir .. '/lib/?.lua', + top_builddir .. '/lib/?/init.lua', + top_srcdir .. '/lib/?.lua', + top_srcdir .. '/lib/?/init.lua', + package.path +) + + +-- 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' + + +-- Simplified version for specifications, does not support functable +-- valued __len metamethod, so don't write examples that need that! +function len(x) + local __len = getmetatable(x) or {} + if type(__len) == 'function' then + return __len(x) + end + if type(x) ~= 'table' then + return #x + end + + local n = #x + for i = 1, n do + if x[i] == nil then + return i -1 + end + end + return n +end + + +-- Make sure we have a maxn even when _VERSION ~= 5.1 +-- @fixme remove this when we get unpack from specl.std +maxn = table.maxn or function(t) + local n = 0 + for k in pairs(t) do + if type(k) == 'number' and k > n then + n = k + end + end + return n +end + + +pack = table.pack or function(...) + return {n=select('#', ...), ...} +end + + +-- Take care to always unpack upto the highest numeric index, for +-- consistency across Lua versions. +local _unpack = table.unpack or unpack + +-- @fixme pick this up from specl.std with the next release +function unpack(t, i, j) + return _unpack(t, tonumber(i) or 1, tonumber(j or t.n or len(t))) +end + + +-- In case we're not using a bleeding edge release of Specl... +_diagnose = badargs.diagnose +badargs.diagnose = function(...) + if have_typecheck then + return _diagnose(...) + end +end + +badargs.result = badargs.result or function(fname, i, want, got) + if want == nil then -- numbers only for narg error + i, want = i - 1, i + end + + if got == nil and type(want) == 'number' then + local s = "bad result #%d from '%s'(no more than %d result%s expected, got %d)" + return s:format(i + 1, fname, i, i == 1 and '' or 's', want) + end + + local function showarg(s) + return('|' .. s .. '|'): + gsub('|%?', '|nil|'): + gsub('|nil|', '|no value|'): + gsub('|any|', '|any value|'): + gsub('|#', '|non-empty '): + gsub('|func|', '|function|'): + gsub('|file|', '|FILE*|'): + gsub('^|', ''): + gsub('|$', ''): + gsub('|([^|]+)$', 'or %1'): + gsub('|', ', ') + end + + return string.format("bad result #%d from '%s'(%s expected, got %s)", + i, fname, showarg(want), got or 'no value') +end + + +-- Wrap up badargs function in a succinct single call. +function init(M, mname, fname) + local name =(mname .. '.' .. fname):gsub('^%.', '') + return M[fname], + function(...) + return badargs.format(name, ...) + end, + function(...) + return badargs.result(name, ...) + end +end + + +-- A copy of base.lua:type, so that an unloadable base.lua doesn't +-- prevent everything else from working. +function objtype(o) + return(getmetatable(o) or {})._type or io.type(o) or type(o) +end + + +function nop() end + + +-- Error message specifications use this to shorten argument lists. +-- Copied from functional.lua to avoid breaking all tests if functional +-- cannot be loaded correctly. +function bind(f, fix) + return function(...) + local arg = {} + for i, v in pairs(fix) do + arg[i] = v + end + local i = 1 + for _, v in pairs {...} do + while arg[i] ~= nil do + i = i + 1 + end + arg[i] = v + end + return f(unpack(arg)) + end +end + + +local function mkscript(code) + local f = os.tmpname() + local h = io.open(f, 'w') + h:write(code) + h:close() + return f +end + + +--- 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 + + +--- Check deprecation output when calling a named function in the given module. +-- Note that the script fragments passed in *argstr* and *objectinit* +-- can reference the module table as `M`, and(where it would make sense) +-- an object prototype as `P` and instance as `obj`. +-- @param deprecate value of `std._debug.deprecate` +-- @string module dot delimited module path to load +-- @string fname name of a function in the table returned by requiring *module* +-- @param[opt=''] args arguments to pass to *fname* call, must be stringifiable +-- @string[opt=nil] objectinit object initializer to instantiate an +-- object for object method deprecation check +-- @treturn specl.shell.Process|nil status of resulting process if +-- execution was successful, otherwise nil +function deprecation(deprecate, module, fname, args, objectinit) + args = args or '' + local script + if objectinit == nil then + script = string.format([[ + require 'std._debug'.deprecate = %s + M = require '%s' + P = M.prototype + print(M.%s(%s)) + ]], tostring(deprecate), module, fname, tostring(args)) + else + script = string.format([[ + require 'std._debug'.deprecate = %s + local M = require '%s' + local P = M.prototype + local obj = P(%s) + print(obj:%s(%s)) + ]], tostring(deprecate), module, objectinit, fname, tostring(args)) + end + return luaproc(script) +end + + +--- Concatenate the contents of listed existing files. +-- @string ... names of existing files +-- @treturn string concatenated contents of those files +function concat_file_content(...) + local t = {} + for _, name in ipairs {...} do + h = io.open(name) + t[#t + 1] = h:read '*a' + end + return table.concat(t) +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} +-- +-- List keys returned from `require 'import'`, which have the same +-- value in T1: +-- +-- show_apis {from=T1, used_by=import} +-- +-- List keys from `require 'import'`, which are also in T1 but with +-- a different value: +-- +-- show_apis {from=T1, enhanced_by=import} +-- +-- List keys from T2, which are also in T1 but with a different value: +-- +-- show_apis {from=T1, enhanced_in=T2} +-- +-- @tparam table argt one of the combinations above +-- @treturn table a list of keys according to criteria above +function show_apis(argt) + local added_to, from, not_in, enhanced_in, enhanced_after, by = + argt.added_to, argt.from, argt.not_in, argt.enhanced_in, + argt.enhanced_after, argt.by + + if added_to and by then + return tabulate_output([[ + local before, after = {}, {} + for k in pairs(]] .. added_to .. [[) do + before[k] = true + end + + local M = require ']] .. by .. [[' + for k in pairs(]] .. added_to .. [[) do + after[k] = true + end + + for k in pairs(after) do + if not before[k] then + print(k) + end + end + ]]) + + elseif from and not_in then + return tabulate_output([[ + local _ENV = require 'std.normalize' { + from = ']] .. from .. [[', + M = require ']] .. not_in .. [[', + } + + for k in pairs(M) do + -- M[1] is typically the module namespace name, don't match + -- that! + if k ~= 1 and from[k] ~= M[k] then + print(k) + end + end + ]]) + + elseif from and enhanced_in then + return tabulate_output([[ + local _ENV = require 'std.normalize' { + from = ']] .. from .. [[', + M = require ']] .. enhanced_in .. [[', + } + + for k, v in pairs(M) do + if from[k] ~= M[k] and from[k] ~= nil then + print(k) + end + end + ]]) + + elseif from and enhanced_after then + return tabulate_output([[ + local _ENV = require 'std.normalize' { + from = ']] .. from .. [[', + } + local before, after = {}, {} + for k, v in pairs(from) do + before[k] = v + end + ]] .. enhanced_after .. [[ + for k, v in pairs(from) do + after[k] = v + end + + for k, v in pairs(before) do + if after[k] ~= nil and after[k] ~= v then + print(k) + end + end + ]]) + end + + assert(false, 'missing argument to show_apis') +end + + +-- Stub inprocess.capture if necessary; new in Specl 12. +capture = inprocess.capture or function(f, arg) + return nil, nil, f(unpack(arg or {})) +end + + +do + -- Custom matcher for set size and set membership. + + local util = require 'specl.util' + local matchers = require 'specl.matchers' + + local Matcher, matchers, q = + matchers.Matcher, matchers.matchers, matchers.stringify + + matchers.have_size = Matcher { + function(self, actual, expect) + local size = 0 + for _ in pairs(actual) do + size = size + 1 + end + return size == expect + end, + + actual = 'table', + + format_expect = function(self, expect) + return ' a table containing ' .. expect .. ' elements, ' + end, + + format_any_of = function(self, alternatives) + return ' a table with any of ' .. + util.concat(alternatives, util.QUOTED) .. ' elements, ' + end, + } + + matchers.have_member = Matcher { + function(self, actual, expect) + return actual[expect] ~= nil + end, + + actual = 'set', + + format_expect = function(self, expect) + return ' a set containing ' .. q(expect) .. ', ' + end, + + format_any_of = function(self, alternatives) + return ' a set containing any of ' .. + util.concat(alternatives, util.QUOTED) .. ', ' + end, + } + + -- Alias that doesn't tickle sc_error_message_uppercase. + matchers.raise = matchers.error +end |