diff options
| author | Hisham Muhammad <hisham@gobolinux.org> | 2024-02-21 22:30:40 -0300 |
|---|---|---|
| committer | Hisham Muhammad <hisham@gobolinux.org> | 2024-02-23 04:33:35 -0300 |
| commit | 79bd1739d8ca004ddd0b2fa5e24da4a6f4b776fa (patch) | |
| tree | b8ead4801340bd958fccbcb97c35cc7d463f2435 /spec/util/quick.lua | |
| parent | a75d447e10bb7842cdbed0bb494697ddd88fd455 (diff) | |
| download | luarocks-79bd1739d8ca004ddd0b2fa5e24da4a6f4b776fa.tar.gz luarocks-79bd1739d8ca004ddd0b2fa5e24da4a6f4b776fa.tar.bz2 luarocks-79bd1739d8ca004ddd0b2fa5e24da4a6f4b776fa.zip | |
tests: introduce quick tests
Diffstat (limited to 'spec/util/quick.lua')
| -rw-r--r-- | spec/util/quick.lua | 384 |
1 files changed, 384 insertions, 0 deletions
diff --git a/spec/util/quick.lua b/spec/util/quick.lua new file mode 100644 index 00000000..c8bfb61a --- /dev/null +++ b/spec/util/quick.lua | |||
| @@ -0,0 +1,384 @@ | |||
| 1 | local quick = {} | ||
| 2 | |||
| 3 | local dir_sep = package.config:sub(1, 1) | ||
| 4 | |||
| 5 | local cfg, dir, fs, versions | ||
| 6 | local initialized = false | ||
| 7 | |||
| 8 | local function initialize() | ||
| 9 | if initialized then | ||
| 10 | return | ||
| 11 | end | ||
| 12 | initialized = true | ||
| 13 | |||
| 14 | cfg = require("luarocks.core.cfg") | ||
| 15 | dir = require("luarocks.dir") | ||
| 16 | fs = require("luarocks.fs") | ||
| 17 | versions = require("spec.util.versions") | ||
| 18 | cfg.init() | ||
| 19 | fs.init() | ||
| 20 | end | ||
| 21 | |||
| 22 | local function native_slash(pathname) | ||
| 23 | return (pathname:gsub("[/\\]", dir_sep)) | ||
| 24 | end | ||
| 25 | |||
| 26 | local function parse_cmd(line) | ||
| 27 | local cmd, arg = line:match("^%s*([A-Z_]+):%s*(.*)%s*$") | ||
| 28 | return cmd, arg | ||
| 29 | end | ||
| 30 | |||
| 31 | local function is_blank(line) | ||
| 32 | return not not line:match("^%s*$") | ||
| 33 | end | ||
| 34 | |||
| 35 | local function is_hr(line) | ||
| 36 | return not not line:match("^%-%-%-%-%-") | ||
| 37 | end | ||
| 38 | |||
| 39 | local function parse(filename) | ||
| 40 | local fd = assert(io.open(filename, "r")) | ||
| 41 | local input = assert(fd:read("*a")) | ||
| 42 | fd:close() | ||
| 43 | |||
| 44 | initialize() | ||
| 45 | |||
| 46 | local tests = {} | ||
| 47 | |||
| 48 | local cur_line = 0 | ||
| 49 | local cur_test | ||
| 50 | local cur_op | ||
| 51 | local cur_block | ||
| 52 | local cur_block_name | ||
| 53 | local stack = { "start" } | ||
| 54 | |||
| 55 | local function start_test(arg) | ||
| 56 | cur_test = { | ||
| 57 | name = arg, | ||
| 58 | ops = {}, | ||
| 59 | } | ||
| 60 | cur_op = nil | ||
| 61 | table.insert(tests, cur_test) | ||
| 62 | table.insert(stack, "test") | ||
| 63 | end | ||
| 64 | |||
| 65 | local function fail(msg) | ||
| 66 | io.stderr:write("Error reading " .. filename .. ":" .. cur_line .. ": " .. msg .. "\n") | ||
| 67 | os.exit(1) | ||
| 68 | end | ||
| 69 | |||
| 70 | local function bool_arg(cmd, cur_block, field, arg) | ||
| 71 | if arg ~= "true" and arg ~= "false" then | ||
| 72 | fail(cmd .. " argument must be 'true' or 'false'") | ||
| 73 | end | ||
| 74 | cur_block[field] = (arg == "true") | ||
| 75 | end | ||
| 76 | |||
| 77 | local test_env = require("spec.util.test_env") | ||
| 78 | local function expand_vars(line) | ||
| 79 | if not line then | ||
| 80 | return nil | ||
| 81 | end | ||
| 82 | return (line:gsub("%%%b{}", function(var) | ||
| 83 | var = var:sub(3, -2) | ||
| 84 | local fn, fnarg = var:match("^%s*([a-z_]+)%s*%(%s*([^)]+)%s*%)%s*$") | ||
| 85 | |||
| 86 | local value | ||
| 87 | if var == "tmpdir" then | ||
| 88 | value = "%{tmpdir}" | ||
| 89 | elseif var == "url(tmpdir)" then | ||
| 90 | value = "%{url(tmpdir)}" | ||
| 91 | elseif fn == "url" then | ||
| 92 | value = expand_vars(fnarg) | ||
| 93 | value = value:gsub("\\", "/") | ||
| 94 | elseif fn == "path" then | ||
| 95 | value = expand_vars(fnarg) | ||
| 96 | value = value:gsub("[/\\]", dir_sep) | ||
| 97 | elseif fn == "version" then | ||
| 98 | value = versions[fnarg:lower()] or "" | ||
| 99 | elseif fn == "version_" then | ||
| 100 | value = (versions[fnarg:lower()] or ""):gsub("[%.%-]", "_") | ||
| 101 | else | ||
| 102 | value = test_env.testing_paths[var] | ||
| 103 | or test_env.env_variables[var] | ||
| 104 | or test_env[var] | ||
| 105 | or "" | ||
| 106 | end | ||
| 107 | |||
| 108 | return value | ||
| 109 | end)) | ||
| 110 | end | ||
| 111 | |||
| 112 | for line in input:gmatch("[^\n]*") do | ||
| 113 | cur_line = cur_line + 1 | ||
| 114 | |||
| 115 | local state = stack[#stack] | ||
| 116 | if state == "start" then | ||
| 117 | local cmd, arg = parse_cmd(line) | ||
| 118 | if cmd == "TEST" then | ||
| 119 | start_test(arg) | ||
| 120 | elseif cmd then | ||
| 121 | fail("expected TEST, got " .. cmd) | ||
| 122 | elseif is_blank(line) then | ||
| 123 | -- skip blank lines and arbitrary text, | ||
| 124 | -- which is interpreted as a comment | ||
| 125 | end | ||
| 126 | elseif state == "test" then | ||
| 127 | local cmd, arg = parse_cmd(line) | ||
| 128 | arg = expand_vars(arg) | ||
| 129 | if cmd == "FILE" then | ||
| 130 | cur_op = { | ||
| 131 | op = "FILE", | ||
| 132 | name = arg, | ||
| 133 | data = {}, | ||
| 134 | } | ||
| 135 | table.insert(cur_test.ops, cur_op) | ||
| 136 | cur_block = cur_op | ||
| 137 | cur_block_name = "FILE" | ||
| 138 | table.insert(stack, "block start") | ||
| 139 | elseif cmd == "RUN" then | ||
| 140 | local program, args = arg:match("([^ ]+)%s*(.*)$") | ||
| 141 | if not program then | ||
| 142 | fail("expected a program argument in RUN") | ||
| 143 | end | ||
| 144 | |||
| 145 | cur_op = { | ||
| 146 | op = "RUN", | ||
| 147 | exit = 0, | ||
| 148 | exit_line = cur_line, | ||
| 149 | line = cur_line, | ||
| 150 | program = program, | ||
| 151 | args = args, | ||
| 152 | } | ||
| 153 | table.insert(cur_test.ops, cur_op) | ||
| 154 | elseif cmd == "EXISTS" then | ||
| 155 | cur_op = { | ||
| 156 | op = "EXISTS", | ||
| 157 | file = dir.normalize(arg), | ||
| 158 | } | ||
| 159 | table.insert(cur_test.ops, cur_op) | ||
| 160 | elseif cmd == "NOT_EXISTS" then | ||
| 161 | cur_op = { | ||
| 162 | op = "NOT_EXISTS", | ||
| 163 | file = dir.normalize(arg), | ||
| 164 | } | ||
| 165 | table.insert(cur_test.ops, cur_op) | ||
| 166 | elseif cmd == "MKDIR" then | ||
| 167 | cur_op = { | ||
| 168 | op = "MKDIR", | ||
| 169 | file = dir.normalize(arg), | ||
| 170 | line = cur_line, | ||
| 171 | } | ||
| 172 | table.insert(cur_test.ops, cur_op) | ||
| 173 | elseif cmd == "EXIT" then | ||
| 174 | if not cur_op or cur_op.op ~= "RUN" then | ||
| 175 | fail("EXIT must be given in the context of a RUN") | ||
| 176 | end | ||
| 177 | |||
| 178 | local code = tonumber(arg) | ||
| 179 | if not code and not (code >= 0 and code <= 128) then | ||
| 180 | fail("EXIT code must be a number in the range 0-128, got " .. arg) | ||
| 181 | end | ||
| 182 | |||
| 183 | cur_op.exit = code | ||
| 184 | cur_op.exit_line = cur_line | ||
| 185 | elseif cmd == "STDERR" then | ||
| 186 | if not cur_op or cur_op.op ~= "RUN" then | ||
| 187 | fail("STDERR must be given in the context of a RUN") | ||
| 188 | end | ||
| 189 | if cur_op.stderr then | ||
| 190 | fail("STDERR was already declared") | ||
| 191 | end | ||
| 192 | |||
| 193 | cur_op.stderr = { | ||
| 194 | data = {} | ||
| 195 | } | ||
| 196 | cur_block = cur_op.stderr | ||
| 197 | cur_block_name = "STDERR" | ||
| 198 | table.insert(stack, "block start") | ||
| 199 | elseif cmd == "STDOUT" then | ||
| 200 | if not cur_op or cur_op.op ~= "RUN" then | ||
| 201 | fail("STDOUT must be given in the context of a RUN") | ||
| 202 | end | ||
| 203 | if cur_op.stdout then | ||
| 204 | fail("STDOUT was already declared") | ||
| 205 | end | ||
| 206 | |||
| 207 | cur_op.stdout = { | ||
| 208 | data = {} | ||
| 209 | } | ||
| 210 | cur_block = cur_op.stdout | ||
| 211 | cur_block_name = "STDOUT" | ||
| 212 | table.insert(stack, "block start") | ||
| 213 | elseif cmd == "TEST" then | ||
| 214 | table.remove(stack) | ||
| 215 | start_test(arg) | ||
| 216 | elseif cmd then | ||
| 217 | fail("expected a command, got " .. cmd) | ||
| 218 | else | ||
| 219 | -- skip blank lines and arbitrary text, | ||
| 220 | -- which is interpreted as a comment | ||
| 221 | end | ||
| 222 | elseif state == "block start" then | ||
| 223 | local cmd, arg = parse_cmd(line) | ||
| 224 | if is_blank(line) then | ||
| 225 | -- skip | ||
| 226 | elseif is_hr(line) then | ||
| 227 | stack[#stack] = "block data" | ||
| 228 | cur_block.start = cur_line | ||
| 229 | elseif cmd == "PLAIN" then | ||
| 230 | bool_arg("PLAIN", cur_block, "plain", arg) | ||
| 231 | else | ||
| 232 | fail("expected '-----' to start " .. cur_block_name .. " block") | ||
| 233 | end | ||
| 234 | elseif state == "block data" then | ||
| 235 | if is_hr(line) then | ||
| 236 | cur_block = nil | ||
| 237 | table.remove(stack) | ||
| 238 | else | ||
| 239 | if not cur_block.plain then | ||
| 240 | line = expand_vars(line) | ||
| 241 | end | ||
| 242 | table.insert(cur_block.data, line) | ||
| 243 | end | ||
| 244 | end | ||
| 245 | end | ||
| 246 | |||
| 247 | return tests | ||
| 248 | end | ||
| 249 | |||
| 250 | function quick.compile(filename, env) | ||
| 251 | local tests = parse(filename) | ||
| 252 | |||
| 253 | -- local dev_null = (package.config:sub(1, 1) == "/") | ||
| 254 | -- and "/dev/null" | ||
| 255 | -- or "NUL" | ||
| 256 | |||
| 257 | local cmd_helpers = { | ||
| 258 | ["luarocks"] = "luarocks_cmd", | ||
| 259 | ["luarocks-admin"] = "luarocks_admin_cmd", | ||
| 260 | } | ||
| 261 | |||
| 262 | for tn, t in ipairs(tests) do | ||
| 263 | local code = {} | ||
| 264 | local function write(...) | ||
| 265 | table.insert(code, table.concat({...})) | ||
| 266 | end | ||
| 267 | |||
| 268 | write([=[ local test_env = require("spec.util.test_env") ]=]) | ||
| 269 | write([=[ local lfs = require("lfs") ]=]) | ||
| 270 | write([=[ local fs = require("lfs") ]=]) | ||
| 271 | write([=[ local dir_sep = package.config:sub(1, 1) ]=]) | ||
| 272 | write([=[ local luarocks_cmd = test_env.execute_helper(test_env.Q(test_env.testing_paths.lua) .. " " .. test_env.testing_paths.src_dir .. "/bin/luarocks", false, test_env.env_variables):sub(1, -5) ]=]) | ||
| 273 | write([=[ local luarocks_admin_cmd = test_env.execute_helper(test_env.Q(test_env.testing_paths.lua) .. " " .. test_env.testing_paths.src_dir .. "/bin/luarocks-admin", false, test_env.env_variables):sub(1, -5) ]=]) | ||
| 274 | |||
| 275 | write(([=[ local function error_message(line, msg, input) ]=])) | ||
| 276 | write(([=[ local out = {"\n\n", %q, ":", line, ": ", msg} ]=]):format(filename)) | ||
| 277 | write(([=[ if input then ]=])) | ||
| 278 | write(([=[ if input:match("\n") then ]=])) | ||
| 279 | write(([=[ table.insert(out, "\n") ]=])) | ||
| 280 | write(([=[ table.insert(out, ("-"):rep(40)) ]=])) | ||
| 281 | write(([=[ table.insert(out, "\n") ]=])) | ||
| 282 | write(([=[ table.insert(out, input) ]=])) | ||
| 283 | write(([=[ table.insert(out, ("-"):rep(40)) ]=])) | ||
| 284 | write(([=[ table.insert(out, "\n") ]=])) | ||
| 285 | write(([=[ else ]=])) | ||
| 286 | write(([=[ table.insert(out, ": ") ]=])) | ||
| 287 | write(([=[ table.insert(out, input) ]=])) | ||
| 288 | write(([=[ end ]=])) | ||
| 289 | write(([=[ end ]=])) | ||
| 290 | write(([=[ return table.concat(out) ]=])) | ||
| 291 | write(([=[ end ]=])) | ||
| 292 | |||
| 293 | write([=[ return function() ]=]) | ||
| 294 | write([=[ test_env.run_in_tmp(function(tmpdir) ]=]) | ||
| 295 | write([=[ local function handle_tmpdir(s) ]=]) | ||
| 296 | write([=[ return (s:gsub("%%{url%(tmpdir%)}", (tmpdir:gsub("\\", "/"))) ]=]) | ||
| 297 | write([=[ :gsub("%%{tmpdir}", (tmpdir:gsub("[\\/]", dir_sep)))) ]=]) | ||
| 298 | write([=[ end ]=]) | ||
| 299 | for _, op in ipairs(t.ops) do | ||
| 300 | if op.op == "FILE" then | ||
| 301 | write([=[ test_env.write_file(handle_tmpdir("]=], op.name, [=["), handle_tmpdir([=====[ ]=]) | ||
| 302 | for _, line in ipairs(op.data) do | ||
| 303 | write(line) | ||
| 304 | end | ||
| 305 | write([=[ ]=====]), finally) ]=]) | ||
| 306 | elseif op.op == "EXISTS" then | ||
| 307 | write(([=[ assert.truthy(lfs.attributes(%q)) ]=]):format(op.file)) | ||
| 308 | elseif op.op == "NOT_EXISTS" then | ||
| 309 | write(([=[ assert.falsy(lfs.attributes(%q)) ]=]):format(op.file)) | ||
| 310 | elseif op.op == "MKDIR" then | ||
| 311 | local bits = {} | ||
| 312 | op.file = native_slash(op.file) | ||
| 313 | if op.file:sub(1, 1) == dir_sep then bits[1] = "" end | ||
| 314 | write([=[ local ok, err ]=]) | ||
| 315 | for p in op.file:gmatch("[^" .. dir_sep .. "]+") do | ||
| 316 | table.insert(bits, p) | ||
| 317 | local d = table.concat(bits, dir_sep) | ||
| 318 | write(([=[ ok, err = lfs.mkdir(%q) ]=]):format(d, d)) | ||
| 319 | end | ||
| 320 | write(([=[ assert.truthy((lfs.attributes(%q) or {}).mode == "directory", error_message(%d, "MKDIR failed: " .. %q .. " - " .. (err or "") )) ]=]):format(op.file, op.line, op.file)) | ||
| 321 | elseif op.op == "RUN" then | ||
| 322 | local cmd_helper = cmd_helpers[op.program] or op.program | ||
| 323 | local redirs = " 1>stdout.txt 2>stderr.txt " | ||
| 324 | write(([=[ local ok, _, code = os.execute(%s .. " " .. %q .. %q) ]=]):format(cmd_helper, op.args, redirs)) | ||
| 325 | write([=[ if type(ok) == "number" then code = (ok >= 256 and ok / 256 or ok) end ]=]) | ||
| 326 | |||
| 327 | write([=[ local fd_stderr = assert(io.open("stderr.txt", "r")) ]=]) | ||
| 328 | write([=[ local stderr_data = fd_stderr:read("*a") ]=]) | ||
| 329 | write([=[ fd_stderr:close() ]=]) | ||
| 330 | |||
| 331 | write([=[ if stderr_data:match("please report") then ]=]) | ||
| 332 | write(([=[ assert(false, error_message(%d, "RUN crashed: ", stderr_data)) ]=]):format(op.line)) | ||
| 333 | write([=[ end ]=]) | ||
| 334 | |||
| 335 | if op.stdout then | ||
| 336 | write([=[ local fd_stdout = assert(io.open("stdout.txt", "r")) ]=]) | ||
| 337 | write([=[ local stdout_data = fd_stdout:read("*a") ]=]) | ||
| 338 | write([=[ fd_stdout:close() ]=]) | ||
| 339 | |||
| 340 | write([=[ do ]=]) | ||
| 341 | write([=[ local block_at = 1 ]=]) | ||
| 342 | write([=[ local s, e, line ]=]) | ||
| 343 | for i, line in ipairs(op.stdout.data) do | ||
| 344 | write(([=[ line = %q ]=]):format(line)) | ||
| 345 | write(([=[ s, e = string.find(stdout_data, line, block_at, true) ]=])) | ||
| 346 | write(([=[ assert(s, error_message(%d, "STDOUT did not match: " .. line, stdout_data)) ]=]):format(op.stdout.start + i)) | ||
| 347 | write(([=[ block_at = e + 1 ]=]):format(i)) | ||
| 348 | end | ||
| 349 | write([=[ end ]=]) | ||
| 350 | end | ||
| 351 | |||
| 352 | if op.stderr then | ||
| 353 | write([=[ do ]=]) | ||
| 354 | write([=[ local block_at = 1 ]=]) | ||
| 355 | write([=[ local s, e, line ]=]) | ||
| 356 | for i, line in ipairs(op.stderr.data) do | ||
| 357 | write(([=[ line = %q ]=]):format(line)) | ||
| 358 | write(([=[ s, e = string.find(stderr_data, line, block_at, true) ]=])) | ||
| 359 | write(([=[ assert(s, error_message(%d, "STDERR did not match: " .. line, stderr_data)) ]=]):format(op.stderr.start + i)) | ||
| 360 | write(([=[ block_at = e + 1 ]=]):format(i)) | ||
| 361 | end | ||
| 362 | write([=[ end ]=]) | ||
| 363 | end | ||
| 364 | |||
| 365 | if op.exit then | ||
| 366 | write(([=[ assert.same(%d, code, error_message(%d, "EXIT did not match: " .. %d, stderr_data)) ]=]):format(op.exit, op.exit_line, op.exit)) | ||
| 367 | end | ||
| 368 | end | ||
| 369 | end | ||
| 370 | write([=[ end) ]=]) | ||
| 371 | write([=[ end ]=]) | ||
| 372 | |||
| 373 | local program = table.concat(code, "\n") | ||
| 374 | local chunk = assert(load(program, "@" .. filename .. ": test " .. tn, "t", env or _ENV)) | ||
| 375 | if env and setfenv then | ||
| 376 | setfenv(chunk, env) | ||
| 377 | end | ||
| 378 | t.fn = chunk() | ||
| 379 | end | ||
| 380 | |||
| 381 | return tests | ||
| 382 | end | ||
| 383 | |||
| 384 | return quick | ||
