1local inprocess = require "specl.inprocess" 2local hell = require "specl.shell" 3local std = require "specl.std" 4 5badargs = require "specl.badargs" 6 7local top_srcdir = os.getenv "top_srcdir" or "." 8local top_builddir = os.getenv "top_builddir" or "." 9 10package.path = std.package.normalize ( 11 top_builddir .. "/lib/?.lua", 12 top_builddir .. "/lib/?/init.lua", 13 top_srcdir .. "/lib/?.lua", 14 top_srcdir .. "/lib/?/init.lua", 15 package.path 16 ) 17 18 19-- Allow user override of LUA binary used by hell.spawn, falling 20-- back to environment PATH search for "lua" if nothing else works. 21local LUA = os.getenv "LUA" or "lua" 22 23 24-- Tweak _DEBUG without tripping over Specl nested environments. 25setdebug = require "std.debug"._setdebug 26 27 28-- Make sure we have a maxn even when _VERSION ~= 5.1 29-- @fixme remove this when we get unpack from specl.std 30maxn = table.maxn or function (t) 31 local n = 0 32 for k in pairs (t) do 33 if type (k) == "number" and k > n then n = k end 34 end 35 return n 36end 37 38 39-- Take care to always unpack upto the highest numeric index, for 40-- consistency across Lua versions. 41local _unpack = table.unpack or unpack 42 43-- @fixme pick this up from specl.std with the next release 44function unpack (t, i, j) 45 return _unpack (t, i or 1, j or maxn (t)) 46end 47 48 49-- In case we're not using a bleeding edge release of Specl... 50badargs.result = badargs.result or function (fname, i, want, got) 51 if want == nil then i, want = i - 1, i end -- numbers only for narg error 52 53 if got == nil and type (want) == "number" then 54 local s = "bad result #%d from '%s' (no more than %d result%s expected, got %d)" 55 return s:format (i + 1, fname, i, i == 1 and "" or "s", want) 56 end 57 58 local function showarg (s) 59 return ("|" .. s .. "|"): 60 gsub ("|%?", "|nil|"): 61 gsub ("|nil|", "|no value|"): 62 gsub ("|any|", "|any value|"): 63 gsub ("|#", "|non-empty "): 64 gsub ("|func|", "|function|"): 65 gsub ("|file|", "|FILE*|"): 66 gsub ("^|", ""): 67 gsub ("|$", ""): 68 gsub ("|([^|]+)$", "or %1"): 69 gsub ("|", ", ") 70 end 71 72 return string.format ("bad result #%d from '%s' (%s expected, got %s)", 73 i, fname, showarg (want), got or "no value") 74end 75 76 77-- Wrap up badargs function in a succinct single call. 78function init (M, mname, fname) 79 local name = (mname .. "." .. fname):gsub ("^%.", "") 80 return M[fname], 81 function (...) return badargs.format (name, ...) end, 82 function (...) return badargs.result (name, ...) end 83end 84 85 86-- A copy of base.lua:prototype, so that an unloadable base.lua doesn't 87-- prevent everything else from working. 88function prototype (o) 89 return (getmetatable (o) or {})._type or io.type (o) or type (o) 90end 91 92 93function nop () end 94 95 96-- Error message specifications use this to shorten argument lists. 97-- Copied from functional.lua to avoid breaking all tests if functional 98-- cannot be loaded correctly. 99function bind (f, fix) 100 return function (...) 101 local arg = {} 102 for i, v in pairs (fix) do 103 arg[i] = v 104 end 105 local i = 1 106 for _, v in pairs {...} do 107 while arg[i] ~= nil do i = i + 1 end 108 arg[i] = v 109 end 110 return f (unpack (arg)) 111 end 112end 113 114 115local function mkscript (code) 116 local f = os.tmpname () 117 local h = io.open (f, "w") 118 h:write (code) 119 h:close () 120 return f 121end 122 123 124--- Run some Lua code with the given arguments and input. 125-- @string code valid Lua code 126-- @tparam[opt={}] string|table arg single argument, or table of 127-- arguments for the script invocation. 128-- @string[opt] stdin standard input contents for the script process 129-- @treturn specl.shell.Process|nil status of resulting process if 130-- execution was successful, otherwise nil 131function luaproc (code, arg, stdin) 132 local f = mkscript (code) 133 if type (arg) ~= "table" then arg = {arg} end 134 local cmd = {LUA, f, unpack (arg)} 135 -- inject env and stdin keys separately to avoid truncating `...` in 136 -- cmd constructor 137 cmd.env = { LUA_PATH=package.path, LUA_INIT="", LUA_INIT_5_2="" } 138 cmd.stdin = stdin 139 local proc = hell.spawn (cmd) 140 os.remove (f) 141 return proc 142end 143 144 145--- Concatenate the contents of listed existing files. 146-- @string ... names of existing files 147-- @treturn string concatenated contents of those files 148function concat_file_content (...) 149 local t = {} 150 for _, name in ipairs {...} do 151 h = io.open (name) 152 t[#t + 1] = h:read "*a" 153 end 154 return table.concat (t) 155end 156 157 158local function tabulate_output (code) 159 local proc = luaproc (code) 160 if proc.status ~= 0 then return error (proc.errout) end 161 local r = {} 162 proc.output:gsub ("(%S*)[%s]*", 163 function (x) 164 if x ~= "" then r[x] = true end 165 end) 166 return r 167end 168 169 170--- Show changes to tables wrought by a require statement. 171-- There are a few modes to this function, controlled by what named 172-- arguments are given. Lists new keys in T1 after `require "import"`: 173-- 174-- show_apis {added_to=T1, by=import} 175-- 176-- List keys returned from `require "import"`, which have the same 177-- value in T1: 178-- 179-- show_apis {from=T1, used_by=import} 180-- 181-- List keys from `require "import"`, which are also in T1 but with 182-- a different value: 183-- 184-- show_apis {from=T1, enhanced_by=import} 185-- 186-- List keys from T2, which are also in T1 but with a different value: 187-- 188-- show_apis {from=T1, enhanced_in=T2} 189-- 190-- @tparam table argt one of the combinations above 191-- @treturn table a list of keys according to criteria above 192function show_apis (argt) 193 local added_to, from, not_in, enhanced_in, enhanced_after, by = 194 argt.added_to, argt.from, argt.not_in, argt.enhanced_in, 195 argt.enhanced_after, argt.by 196 197 if added_to and by then 198 return tabulate_output ([[ 199 local before, after = {}, {} 200 for k in pairs (]] .. added_to .. [[) do 201 before[k] = true 202 end 203 204 local M = require "]] .. by .. [[" 205 for k in pairs (]] .. added_to .. [[) do 206 after[k] = true 207 end 208 209 for k in pairs (after) do 210 if not before[k] then print (k) end 211 end 212 ]]) 213 214 elseif from and not_in then 215 return tabulate_output ([[ 216 local from = ]] .. from .. [[ 217 local M = require "]] .. not_in .. [[" 218 219 for k in pairs (M) do 220 -- M[1] is typically the module namespace name, don't match 221 -- that! 222 if k ~= 1 and from[k] ~= M[k] then print (k) end 223 end 224 ]]) 225 226 elseif from and enhanced_in then 227 return tabulate_output ([[ 228 local from = ]] .. from .. [[ 229 local M = require "]] .. enhanced_in .. [[" 230 231 for k, v in pairs (M) do 232 if from[k] ~= M[k] and from[k] ~= nil then print (k) end 233 end 234 ]]) 235 236 elseif from and enhanced_after then 237 return tabulate_output ([[ 238 local before, after = {}, {} 239 local from = ]] .. from .. [[ 240 241 for k, v in pairs (from) do before[k] = v end 242 ]] .. enhanced_after .. [[ 243 for k, v in pairs (from) do after[k] = v end 244 245 for k, v in pairs (before) do 246 if after[k] ~= nil and after[k] ~= v then print (k) end 247 end 248 ]]) 249 end 250 251 assert (false, "missing argument to show_apis") 252end 253 254 255-- Stub inprocess.capture if necessary; new in Specl 12. 256capture = inprocess.capture or 257 function (f, arg) return nil, nil, f (unpack (arg or {})) end 258 259 260do 261 -- Custom matcher for set size and set membership. 262 263 local util = require "specl.util" 264 local matchers = require "specl.matchers" 265 266 local Matcher, matchers, q = 267 matchers.Matcher, matchers.matchers, matchers.stringify 268 269 matchers.have_size = Matcher { 270 function (self, actual, expect) 271 local size = 0 272 for _ in pairs (actual) do size = size + 1 end 273 return size == expect 274 end, 275 276 actual = "table", 277 278 format_expect = function (self, expect) 279 return " a table containing " .. expect .. " elements, " 280 end, 281 282 format_any_of = function (self, alternatives) 283 return " a table with any of " .. 284 util.concat (alternatives, util.QUOTED) .. " elements, " 285 end, 286 } 287 288 matchers.have_member = Matcher { 289 function (self, actual, expect) 290 return actual[expect] ~= nil 291 end, 292 293 actual = "set", 294 295 format_expect = function (self, expect) 296 return " a set containing " .. q (expect) .. ", " 297 end, 298 299 format_any_of = function (self, alternatives) 300 return " a set containing any of " .. 301 util.concat (alternatives, util.QUOTED) .. ", " 302 end, 303 } 304 305 -- Alias that doesn't tickle sc_error_message_uppercase. 306 matchers.raise = matchers.error 307end 308