mirror of
https://github.com/SquidDev-CC/CC-Tweaked
synced 2024-12-13 19:50:31 +00:00
Improve display of runtime errors (#1320)
- Bump Cobalt to 0.6.0. We now track both line and column numbers of each bytecode instruction, allowing us to map an error to a concrete position. - `loadfile` (and similar functions) now use the full path, rather than the file name. Cobalt truncates this to 30 characters (rather than the previous 60) so this should be less noisy. - The shell, edit and Lua REPL now display the corresponding source code alongside an error. Note this is incredibly limited right now - it won't cope with errors which cross coroutine boundaries. Supporting this is on the roadmap, but requires some careful API design.
This commit is contained in:
parent
62e3c5f9aa
commit
5502412181
@ -14,7 +14,7 @@ parchmentMc = "1.19.2"
|
|||||||
asm = "9.3"
|
asm = "9.3"
|
||||||
autoService = "1.0.1"
|
autoService = "1.0.1"
|
||||||
checkerFramework = "3.12.0"
|
checkerFramework = "3.12.0"
|
||||||
cobalt = "0.5.12"
|
cobalt = "0.6.0"
|
||||||
fastutil = "8.5.9"
|
fastutil = "8.5.9"
|
||||||
guava = "31.1-jre"
|
guava = "31.1-jre"
|
||||||
jetbrainsAnnotations = "23.0.0"
|
jetbrainsAnnotations = "23.0.0"
|
||||||
@ -51,7 +51,7 @@ fabric-loom = "1.0-SNAPSHOT"
|
|||||||
forgeGradle = "5.1.+"
|
forgeGradle = "5.1.+"
|
||||||
githubRelease = "2.2.12"
|
githubRelease = "2.2.12"
|
||||||
ideaExt = "1.1.6"
|
ideaExt = "1.1.6"
|
||||||
illuaminate = "0.1.0-12-ga03e9cd"
|
illuaminate = "0.1.0-13-g689d73d"
|
||||||
librarian = "1.+"
|
librarian = "1.+"
|
||||||
minotaur = "2.+"
|
minotaur = "2.+"
|
||||||
mixinGradle = "0.7.+"
|
mixinGradle = "0.7.+"
|
||||||
|
@ -7,7 +7,7 @@ local expect
|
|||||||
|
|
||||||
do
|
do
|
||||||
local h = fs.open("rom/modules/main/cc/expect.lua", "r")
|
local h = fs.open("rom/modules/main/cc/expect.lua", "r")
|
||||||
local f, err = loadstring(h.readAll(), "@expect.lua")
|
local f, err = loadstring(h.readAll(), "@/rom/modules/main/cc/expect.lua")
|
||||||
h.close()
|
h.close()
|
||||||
|
|
||||||
if not f then error(err) end
|
if not f then error(err) end
|
||||||
@ -467,7 +467,7 @@ function loadfile(filename, mode, env)
|
|||||||
local file = fs.open(filename, "r")
|
local file = fs.open(filename, "r")
|
||||||
if not file then return nil, "File not found" end
|
if not file then return nil, "File not found" end
|
||||||
|
|
||||||
local func, err = load(file.readAll(), "@" .. fs.getName(filename), mode, env)
|
local func, err = load(file.readAll(), "@/" .. fs.combine(filename), mode, env)
|
||||||
file.close()
|
file.close()
|
||||||
return func, err
|
return func, err
|
||||||
end
|
end
|
||||||
|
@ -0,0 +1,119 @@
|
|||||||
|
--[[- Internal tools for working with errors.
|
||||||
|
|
||||||
|
:::warning
|
||||||
|
This is an internal module and SHOULD NOT be used in your own code. It may
|
||||||
|
be removed or changed at any time.
|
||||||
|
:::
|
||||||
|
|
||||||
|
@local
|
||||||
|
]]
|
||||||
|
|
||||||
|
local expect = require "cc.expect".expect
|
||||||
|
local error_printer = require "cc.internal.error_printer"
|
||||||
|
|
||||||
|
local function find_frame(thread, file, line)
|
||||||
|
-- Scan the first 16 frames for something interesting.
|
||||||
|
for offset = 0, 15 do
|
||||||
|
local frame = debug.getinfo(thread, offset, "Sl")
|
||||||
|
if not frame then break end
|
||||||
|
|
||||||
|
if frame.short_src == file and frame.what ~= "C" and frame.currentline == line then
|
||||||
|
return frame
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
--[[- Attempt to call the provided function `func` with the provided arguments.
|
||||||
|
|
||||||
|
@tparam function func The function to call.
|
||||||
|
@param ... Arguments to this function.
|
||||||
|
|
||||||
|
@treturn[1] true If the function ran successfully.
|
||||||
|
|
||||||
|
@treturn[2] false If the function failed.
|
||||||
|
@return[2] The error message
|
||||||
|
@treturn[2] coroutine The thread where the error occurred.
|
||||||
|
]]
|
||||||
|
local function try(func, ...)
|
||||||
|
expect(1, func, "function")
|
||||||
|
|
||||||
|
local co = coroutine.create(func)
|
||||||
|
local ok, result = coroutine.resume(co, ...)
|
||||||
|
|
||||||
|
while coroutine.status(co) ~= "dead" do
|
||||||
|
local event = table.pack(os.pullEventRaw(result))
|
||||||
|
if result == nil or event[1] == result or event[1] == "terminate" then
|
||||||
|
ok, result = coroutine.resume(co, table.unpack(event, 1, event.n))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if not ok then return false, result, co end
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
--[[- Report additional context about an error.
|
||||||
|
|
||||||
|
@param err The error to report.
|
||||||
|
@tparam coroutine thread The coroutine where the error occurred.
|
||||||
|
@tparam[opt] { [string] = string } source_map Map of chunk names to their contents.
|
||||||
|
]]
|
||||||
|
local function report(err, thread, source_map)
|
||||||
|
expect(2, thread, "thread")
|
||||||
|
expect(3, source_map, "table", "nil")
|
||||||
|
|
||||||
|
if type(err) ~= "string" then return end
|
||||||
|
|
||||||
|
local file, line = err:match("^([^:]+):(%d+):")
|
||||||
|
if not file then return end
|
||||||
|
line = tonumber(line)
|
||||||
|
|
||||||
|
local frame = find_frame(thread, file, line)
|
||||||
|
if not frame or not frame.currentcolumn then return end
|
||||||
|
|
||||||
|
local column = frame.currentcolumn
|
||||||
|
local line_contents
|
||||||
|
if source_map and source_map[frame.source] then
|
||||||
|
-- File exists in the source map.
|
||||||
|
local pos, contents = 1, source_map[frame.source]
|
||||||
|
-- Try to remap our position. The interface for this only makes sense
|
||||||
|
-- for single line sources, but that's sufficient for where we need it
|
||||||
|
-- (the REPL).
|
||||||
|
if type(contents) == "table" then
|
||||||
|
column = column - contents.offset
|
||||||
|
contents = contents.contents
|
||||||
|
end
|
||||||
|
|
||||||
|
for _ = 1, line - 1 do
|
||||||
|
local next_pos = contents:find("\n", pos)
|
||||||
|
if not next_pos then return end
|
||||||
|
pos = next_pos + 1
|
||||||
|
end
|
||||||
|
|
||||||
|
local end_pos = contents:find("\n", pos)
|
||||||
|
line_contents = contents:sub(pos, end_pos and end_pos - 1 or #contents)
|
||||||
|
|
||||||
|
elseif frame.source:sub(1, 2) == "@/" then
|
||||||
|
-- Read the file from disk.
|
||||||
|
local handle = fs.open(frame.source:sub(3), "r")
|
||||||
|
if not handle then return end
|
||||||
|
for _ = 1, line - 1 do handle.readLine() end
|
||||||
|
|
||||||
|
line_contents = handle.readLine()
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Could not determine the line. Bail.
|
||||||
|
if not line_contents or #line_contents == "" then return end
|
||||||
|
|
||||||
|
error_printer({
|
||||||
|
get_pos = function() return line, column end,
|
||||||
|
get_line = function() return line_contents end,
|
||||||
|
}, {
|
||||||
|
{ tag = "annotate", start_pos = column, end_pos = column, msg = "" },
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
return {
|
||||||
|
try = try,
|
||||||
|
report = report,
|
||||||
|
}
|
@ -51,17 +51,21 @@ end
|
|||||||
|
|
||||||
local runHandler = [[multishell.setTitle(multishell.getCurrent(), %q)
|
local runHandler = [[multishell.setTitle(multishell.getCurrent(), %q)
|
||||||
local current = term.current()
|
local current = term.current()
|
||||||
local contents = %q
|
local contents, name = %q, %q
|
||||||
local fn, err = load(contents, %q, nil, _ENV)
|
local fn, err = load(contents, name, nil, _ENV)
|
||||||
if fn then
|
if fn then
|
||||||
local ok, err = pcall(fn, ...)
|
local exception = require "cc.internal.exception"
|
||||||
|
local ok, err, co = exception.try(fn, ...)
|
||||||
|
|
||||||
term.redirect(current)
|
term.redirect(current)
|
||||||
term.setTextColor(term.isColour() and colours.yellow or colours.white)
|
term.setTextColor(term.isColour() and colours.yellow or colours.white)
|
||||||
term.setBackgroundColor(colours.black)
|
term.setBackgroundColor(colours.black)
|
||||||
term.setCursorBlink(false)
|
term.setCursorBlink(false)
|
||||||
|
|
||||||
if not ok then printError(err) end
|
if not ok then
|
||||||
|
printError(err)
|
||||||
|
exception.report(err, co, { [name] = contents })
|
||||||
|
end
|
||||||
else
|
else
|
||||||
local parser = require "cc.internal.syntax"
|
local parser = require "cc.internal.syntax"
|
||||||
if parser.parse_program(contents) then printError(err) end
|
if parser.parse_program(contents) then printError(err) end
|
||||||
@ -452,7 +456,7 @@ local tMenuFuncs = {
|
|||||||
return
|
return
|
||||||
end
|
end
|
||||||
local ok = save(sTempPath, function(file)
|
local ok = save(sTempPath, function(file)
|
||||||
file.write(runHandler:format(sTitle, table.concat(tLines, "\n"), "@" .. fs.getName(sPath)))
|
file.write(runHandler:format(sTitle, table.concat(tLines, "\n"), "@/" .. sPath))
|
||||||
end)
|
end)
|
||||||
if ok then
|
if ok then
|
||||||
local nTask = shell.openTab("/" .. sTempPath)
|
local nTask = shell.openTab("/" .. sTempPath)
|
||||||
|
@ -6,13 +6,14 @@ if #tArgs > 0 then
|
|||||||
end
|
end
|
||||||
|
|
||||||
local pretty = require "cc.pretty"
|
local pretty = require "cc.pretty"
|
||||||
|
local exception = require "cc.internal.exception"
|
||||||
|
|
||||||
local bRunning = true
|
local running = true
|
||||||
local tCommandHistory = {}
|
local tCommandHistory = {}
|
||||||
local tEnv = {
|
local tEnv = {
|
||||||
["exit"] = setmetatable({}, {
|
["exit"] = setmetatable({}, {
|
||||||
__tostring = function() return "Call exit() to exit." end,
|
__tostring = function() return "Call exit() to exit." end,
|
||||||
__call = function() bRunning = false end,
|
__call = function() running = false end,
|
||||||
}),
|
}),
|
||||||
["_echo"] = function(...)
|
["_echo"] = function(...)
|
||||||
return ...
|
return ...
|
||||||
@ -44,7 +45,8 @@ print("Interactive Lua prompt.")
|
|||||||
print("Call exit() to exit.")
|
print("Call exit() to exit.")
|
||||||
term.setTextColour(colours.white)
|
term.setTextColour(colours.white)
|
||||||
|
|
||||||
while bRunning do
|
local chunk_idx, chunk_map = 1, {}
|
||||||
|
while running do
|
||||||
--if term.isColour() then
|
--if term.isColour() then
|
||||||
-- term.setTextColour( colours.yellow )
|
-- term.setTextColour( colours.yellow )
|
||||||
--end
|
--end
|
||||||
@ -74,27 +76,32 @@ while bRunning do
|
|||||||
term.setTextColour(colours.white)
|
term.setTextColour(colours.white)
|
||||||
end
|
end
|
||||||
|
|
||||||
local nForcePrint = 0
|
local name, offset = "=lua[" .. chunk_idx .. "]", 0
|
||||||
local func, err = load(input, "=lua", "t", tEnv)
|
|
||||||
local func2 = load("return _echo(" .. input .. ");", "=lua", "t", tEnv)
|
local force_print = 0
|
||||||
|
local func, err = load(input, name, "t", tEnv)
|
||||||
|
|
||||||
|
local expr_func = load("return _echo(" .. input .. ");", name, "t", tEnv)
|
||||||
if not func then
|
if not func then
|
||||||
if func2 then
|
if expr_func then
|
||||||
func = func2
|
func = expr_func
|
||||||
err = nil
|
offset = 13
|
||||||
nForcePrint = 1
|
force_print = 1
|
||||||
end
|
|
||||||
else
|
|
||||||
if func2 then
|
|
||||||
func = func2
|
|
||||||
end
|
end
|
||||||
|
elseif expr_func then
|
||||||
|
func = expr_func
|
||||||
|
offset = 13
|
||||||
end
|
end
|
||||||
|
|
||||||
if func then
|
if func then
|
||||||
local tResults = table.pack(pcall(func))
|
chunk_map[name] = { contents = input, offset = offset }
|
||||||
if tResults[1] then
|
chunk_idx = chunk_idx + 1
|
||||||
|
|
||||||
|
local results = table.pack(exception.try(func))
|
||||||
|
if results[1] then
|
||||||
local n = 1
|
local n = 1
|
||||||
while n < tResults.n or n <= nForcePrint do
|
while n < results.n or n <= force_print do
|
||||||
local value = tResults[n + 1]
|
local value = results[n + 1]
|
||||||
local ok, serialised = pcall(pretty.pretty, value, {
|
local ok, serialised = pcall(pretty.pretty, value, {
|
||||||
function_args = settings.get("lua.function_args"),
|
function_args = settings.get("lua.function_args"),
|
||||||
function_source = settings.get("lua.function_source"),
|
function_source = settings.get("lua.function_source"),
|
||||||
@ -107,7 +114,8 @@ while bRunning do
|
|||||||
n = n + 1
|
n = n + 1
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
printError(tResults[2])
|
printError(results[2])
|
||||||
|
require "cc.internal.exception".report(results[2], results[3], chunk_map)
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
local parser = require "cc.internal.syntax"
|
local parser = require "cc.internal.syntax"
|
||||||
|
@ -67,6 +67,7 @@ do
|
|||||||
require = env.require
|
require = env.require
|
||||||
end
|
end
|
||||||
local expect = require("cc.expect").expect
|
local expect = require("cc.expect").expect
|
||||||
|
local exception = require "cc.internal.exception"
|
||||||
|
|
||||||
-- Colours
|
-- Colours
|
||||||
local promptColour, textColour, bgColour
|
local promptColour, textColour, bgColour
|
||||||
@ -146,7 +147,7 @@ local function executeProgram(remainingRecursion, path, args)
|
|||||||
local env = setmetatable(createShellEnv(dir), { __index = _G })
|
local env = setmetatable(createShellEnv(dir), { __index = _G })
|
||||||
env.arg = args
|
env.arg = args
|
||||||
|
|
||||||
local func, err = load(contents, "@" .. fs.getName(path), nil, env)
|
local func, err = load(contents, "@/" .. path, nil, env)
|
||||||
if not func then
|
if not func then
|
||||||
-- We had a syntax error. Attempt to run it through our own parser if
|
-- We had a syntax error. Attempt to run it through our own parser if
|
||||||
-- the file is "small enough", otherwise report the original error.
|
-- the file is "small enough", otherwise report the original error.
|
||||||
@ -166,13 +167,16 @@ local function executeProgram(remainingRecursion, path, args)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local ok, err = pcall(func, table.unpack(args))
|
local ok, err, co = exception.try(func, table.unpack(args, 1, args.n))
|
||||||
if ok then
|
|
||||||
return true
|
if ok then return true end
|
||||||
else
|
|
||||||
if err and err ~= "" then printError(err) end
|
if err and err ~= "" then
|
||||||
return false
|
printError(err)
|
||||||
|
exception.report(err, co)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
return false
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Run a program with the supplied arguments.
|
--- Run a program with the supplied arguments.
|
||||||
|
@ -6,6 +6,7 @@
|
|||||||
package dan200.computercraft.core;
|
package dan200.computercraft.core;
|
||||||
|
|
||||||
import it.unimi.dsi.fastutil.ints.Int2IntMap;
|
import it.unimi.dsi.fastutil.ints.Int2IntMap;
|
||||||
|
import it.unimi.dsi.fastutil.ints.Int2IntMaps;
|
||||||
import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
|
import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
|
||||||
import it.unimi.dsi.fastutil.ints.IntSet;
|
import it.unimi.dsi.fastutil.ints.IntSet;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
@ -17,19 +18,14 @@ import org.squiddev.cobalt.compiler.LuaC;
|
|||||||
import java.io.*;
|
import java.io.*;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.*;
|
import java.util.ArrayDeque;
|
||||||
import java.util.stream.Collectors;
|
import java.util.Collections;
|
||||||
import java.util.stream.Stream;
|
import java.util.Map;
|
||||||
|
import java.util.Queue;
|
||||||
|
|
||||||
class LuaCoverage {
|
class LuaCoverage {
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(LuaCoverage.class);
|
private static final Logger LOG = LoggerFactory.getLogger(LuaCoverage.class);
|
||||||
private static final Path ROOT = new File("src/main/resources/data/computercraft/lua").toPath();
|
private static final Path ROOT = new File("src/main/resources/data/computercraft/lua").toPath();
|
||||||
private static final Path BIOS = ROOT.resolve("bios.lua");
|
|
||||||
private static final Path APIS = ROOT.resolve("rom/apis");
|
|
||||||
private static final Path STARTUP = ROOT.resolve("rom/startup.lua");
|
|
||||||
private static final Path SHELL = ROOT.resolve("rom/programs/shell.lua");
|
|
||||||
private static final Path MULTISHELL = ROOT.resolve("rom/programs/advanced/multishell.lua");
|
|
||||||
private static final Path TREASURE = ROOT.resolve("treasure");
|
|
||||||
|
|
||||||
private final Map<String, Int2IntMap> coverage;
|
private final Map<String, Int2IntMap> coverage;
|
||||||
private final String blank;
|
private final String blank;
|
||||||
@ -49,26 +45,20 @@ class LuaCoverage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void write(Writer out) throws IOException {
|
void write(Writer out) throws IOException {
|
||||||
Files.find(ROOT, Integer.MAX_VALUE, (path, attr) -> attr.isRegularFile() && !path.startsWith(TREASURE)).forEach(path -> {
|
Files.find(ROOT, Integer.MAX_VALUE, (path, attr) -> attr.isRegularFile()).forEach(path -> {
|
||||||
var relative = ROOT.relativize(path);
|
var relative = ROOT.relativize(path);
|
||||||
var full = relative.toString().replace('\\', '/');
|
var full = relative.toString().replace('\\', '/');
|
||||||
if (!full.endsWith(".lua")) return;
|
if (!full.endsWith(".lua")) return;
|
||||||
|
|
||||||
var possiblePaths = Stream.of(
|
var possiblePaths = coverage.remove("/" + full);
|
||||||
coverage.remove("/" + full),
|
if (possiblePaths == null) possiblePaths = coverage.remove(full);
|
||||||
path.equals(BIOS) ? coverage.remove("bios.lua") : null,
|
if (possiblePaths == null) {
|
||||||
path.equals(STARTUP) ? coverage.remove("startup.lua") : null,
|
possiblePaths = Int2IntMaps.EMPTY_MAP;
|
||||||
path.equals(SHELL) ? coverage.remove("shell.lua") : null,
|
LOG.warn("{} has no coverage data", full);
|
||||||
path.equals(MULTISHELL) ? coverage.remove("multishell.lua") : null,
|
}
|
||||||
path.startsWith(APIS) ? coverage.remove(path.getFileName().toString()) : null
|
|
||||||
);
|
|
||||||
var files = possiblePaths
|
|
||||||
.filter(Objects::nonNull)
|
|
||||||
.flatMap(x -> x.int2IntEntrySet().stream())
|
|
||||||
.collect(Collectors.toUnmodifiableMap(Map.Entry::getKey, Map.Entry::getValue, Integer::sum));
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
writeCoverageFor(out, path, files);
|
writeCoverageFor(out, path, possiblePaths);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
throw new UncheckedIOException(e);
|
throw new UncheckedIOException(e);
|
||||||
}
|
}
|
||||||
@ -116,24 +106,26 @@ class LuaCoverage {
|
|||||||
|
|
||||||
private static IntSet getActiveLines(File file) throws IOException {
|
private static IntSet getActiveLines(File file) throws IOException {
|
||||||
IntSet activeLines = new IntOpenHashSet();
|
IntSet activeLines = new IntOpenHashSet();
|
||||||
|
Queue<Prototype> queue = new ArrayDeque<>();
|
||||||
|
|
||||||
try (InputStream stream = new FileInputStream(file)) {
|
try (InputStream stream = new FileInputStream(file)) {
|
||||||
var proto = LuaC.compile(stream, "@" + file.getPath());
|
var proto = LuaC.compile(stream, "@" + file.getPath());
|
||||||
Queue<Prototype> queue = new ArrayDeque<>();
|
|
||||||
queue.add(proto);
|
queue.add(proto);
|
||||||
|
|
||||||
while ((proto = queue.poll()) != null) {
|
|
||||||
var lines = proto.lineinfo;
|
|
||||||
if (lines != null) {
|
|
||||||
for (var line : lines) {
|
|
||||||
activeLines.add(line);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (proto.p != null) Collections.addAll(queue, proto.p);
|
|
||||||
}
|
|
||||||
} catch (CompileException e) {
|
} catch (CompileException e) {
|
||||||
throw new IllegalStateException("Cannot compile", e);
|
throw new IllegalStateException("Cannot compile", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Prototype proto;
|
||||||
|
while ((proto = queue.poll()) != null) {
|
||||||
|
var lines = proto.lineInfo;
|
||||||
|
if (lines != null) {
|
||||||
|
for (var line : lines) {
|
||||||
|
activeLines.add(line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (proto.children != null) Collections.addAll(queue, proto.children);
|
||||||
|
}
|
||||||
|
|
||||||
return activeLines;
|
return activeLines;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -23,7 +23,7 @@ public class ComputerTest {
|
|||||||
try {
|
try {
|
||||||
ComputerBootstrap.run("print('Hello') while true do end", ComputerBootstrap.MAX_TIME);
|
ComputerBootstrap.run("print('Hello') while true do end", ComputerBootstrap.MAX_TIME);
|
||||||
} catch (AssertionError e) {
|
} catch (AssertionError e) {
|
||||||
if (e.getMessage().equals("test.lua:1: Too long without yielding")) return;
|
if (e.getMessage().equals("/test.lua:1: Too long without yielding")) return;
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -39,7 +39,9 @@ describe("The parallel library", function()
|
|||||||
end)
|
end)
|
||||||
|
|
||||||
it("passes errors to the caller", function()
|
it("passes errors to the caller", function()
|
||||||
expect.error(parallel.waitForAny, function() error("Test error") end):str_match("Test error$")
|
local ok, err = pcall(parallel.waitForAny, function() error("Test error") end)
|
||||||
|
if ok then fail("Expected function to error") end
|
||||||
|
expect(tostring(err)):str_match("Test error$")
|
||||||
end)
|
end)
|
||||||
|
|
||||||
it("returns the number of the function that exited first", function()
|
it("returns the number of the function that exited first", function()
|
||||||
@ -98,7 +100,9 @@ describe("The parallel library", function()
|
|||||||
end)
|
end)
|
||||||
|
|
||||||
it("passes errors to the caller", function()
|
it("passes errors to the caller", function()
|
||||||
expect.error(parallel.waitForAll, function() error("Test error") end):str_match("Test error$")
|
local ok, err = pcall(parallel.waitForAll, function() error("Test error") end)
|
||||||
|
if ok then fail("Expected function to error") end
|
||||||
|
expect(tostring(err)):str_match("Test error$")
|
||||||
end)
|
end)
|
||||||
|
|
||||||
it("completes all functions before exiting", function()
|
it("completes all functions before exiting", function()
|
||||||
|
@ -28,8 +28,6 @@ describe("The Lua base library", function()
|
|||||||
end)
|
end)
|
||||||
|
|
||||||
describe("loadfile", function()
|
describe("loadfile", function()
|
||||||
local loadfile = _G.native_loadfile or loadfile
|
|
||||||
|
|
||||||
local function make_file()
|
local function make_file()
|
||||||
local tmp = fs.open("test-files/out.lua", "w")
|
local tmp = fs.open("test-files/out.lua", "w")
|
||||||
tmp.write("return _ENV")
|
tmp.write("return _ENV")
|
||||||
@ -48,7 +46,7 @@ describe("The Lua base library", function()
|
|||||||
|
|
||||||
it("prefixes the filename with @", function()
|
it("prefixes the filename with @", function()
|
||||||
local info = debug.getinfo(loadfile("/rom/startup.lua"), "S")
|
local info = debug.getinfo(loadfile("/rom/startup.lua"), "S")
|
||||||
expect(info):matches { short_src = "startup.lua", source = "@startup.lua" }
|
expect(info):matches { short_src = "/rom/startup.lua", source = "@/rom/startup.lua" }
|
||||||
end)
|
end)
|
||||||
|
|
||||||
it("loads a file with the global environment", function()
|
it("loads a file with the global environment", function()
|
||||||
|
Loading…
Reference in New Issue
Block a user