Run all lua examples in docs as doctests

This commit is contained in:
lumi 2025-06-30 21:59:07 +10:00
parent 81cd901ea6
commit 65b82fcd97
Signed by: luaneko
GPG Key ID: 406809B8763FF07A
3 changed files with 66 additions and 26 deletions

View File

@ -224,9 +224,9 @@ impl lb_netlib {
///
/// ```lua
/// local net = require("lb:net")
/// local socket = net.bind_tcp("127.0.0.1", 8080)
/// local socket = net.bind_tcp("127.0.0.1")
///
/// assert(socket:local_addr() == net.socketaddr("127.0.0.1:8080"))
/// assert(socket:local_addr():ip() == net.ipaddr("127.0.0.1"))
/// socket:set_nodelay(true)
/// ```
pub extern "Lua" fn bind_tcp(
@ -257,15 +257,16 @@ impl lb_netlib {
///
/// # Example
///
/// ```lua
/// ```lua,no_run
/// local net = require("lb:net")
/// local listener = net.listen_tcp("127.0.0.1", 1234)
/// local listener = net.listen_tcp("127.0.0.1")
///
/// assert(listener:local_addr() == net.socketaddr("127.0.0.1:1234"))
/// assert(listener:local_addr():ip() == net.ipaddr("127.0.0.1"))
///
/// for stream in listener do
/// print("client connected: ", stream:remote_addr())
/// print("client connected: ", stream:peer_addr())
/// end
/// ```
pub extern "Lua" fn listen_tcp(
addr: OneOf<(&str, &lb_ipaddr, &lb_socketaddr)>,
port: Option<u16>,
@ -287,9 +288,10 @@ impl lb_netlib {
///
/// ```lua
/// local net = require("lb:net")
/// local stream = net.connect_tcp("127.0.0.1", 1234)
/// local listener = net.listen_tcp("127.0.0.1")
/// local stream = net.connect_tcp("127.0.0.1", listener:local_addr():port())
///
/// assert(stream:remote_addr() == net.socketaddr("127.0.0.1:1234"))
/// assert(stream:peer_addr():ip() == net.ipaddr("127.0.0.1"))
/// stream:write("Hello, server!\n")
/// ```
pub async extern "Lua" fn connect_tcp(

View File

@ -183,7 +183,7 @@ impl lb_tcpsocket {
///
/// This examples spawns a reader task and a writer task to operate on the stream concurrently.
///
/// ```lua
/// ```lua,no_run
/// local task = require("lb:task")
/// local net = require("lb:net")
/// local socket = net.connect_tcp("127.0.0.1:1234")
@ -194,17 +194,17 @@ impl lb_tcpsocket {
/// local reader = spawn(function()
/// for chunk in socket, 1024 do
/// print("received: ", chunk)
/// done
/// end
///
/// print("done reading")
/// end)
///
/// local writer = spawn(function()
/// for i = 1, 10 do
/// local msg = ("message %d\n"):format(i)
/// local msg = ("message %d"):format(i)
/// socket:write(msg)
/// print("sent: ", msg)
/// done
/// end
///
/// print("done writing")
/// end)
@ -215,7 +215,10 @@ impl lb_tcpsocket {
/// The above example uses the socket as an iterator in a generic `for` loop to read data in chunks
/// of up to 1024 bytes. It is equivalent to the following:
///
/// ```lua
/// ```lua,no_run
/// local net = require("lb:net")
/// local socket = net.connect_tcp("127.0.0.1:1234")
///
/// while true do
/// local chunk = socket:read_partial(1024)
/// if chunk == nil then break end
@ -430,7 +433,7 @@ impl lb_tcpstream {
///
/// The listener can be used as an iterator in a generic `for` loop to accept incoming connections:
///
/// ```lua
/// ```lua,no_run
/// local net = require("lb:net")
/// local listener = net.listen_tcp("127.0.0.1")
///

View File

@ -12,6 +12,7 @@ local color = {
reset = "\x1b[0m",
pass = "\x1b[32;1m", -- green
fail = "\x1b[31;1m", -- red
faint = "\x1b[2;39;49m", -- faint
}
local icon = {
@ -24,6 +25,12 @@ local function style(name, s)
return ("%s%s%s"):format(color[name], s, color.reset)
end
local function rjust(s, w)
if w == nil then w = 12 end
if #s >= w then return s end
return (" "):rep(w - #s) .. s
end
local function create_test(name, f, group)
local test = { type = "test", name = name or "", group = group, state = "pending", f = f }
local fenv = setmetatable({}, { __index = global, __newindex = global })
@ -67,13 +74,13 @@ local function trace(msg)
end
local function run_test(test)
local ok, res = xpcall(test.f, trace, test)
local ok, trace = xpcall(test.f, trace, test)
if ok then
test.state = "pass"
print("", ("%s %s"):format(style("pass", "PASS"), name_test(test)))
print(("%s %s"):format(style("pass", rjust("PASS")), name_test(test)))
else
test.state = "fail"
print("", ("%s %s\n\n%s\n"):format(style("fail", "FAIL"), name_test(test), res))
print(("%s %s\n\n%s\n"):format(style("fail", rjust("!!! FAIL")), name_test(test), trace))
end
collectgarbage() -- gc after each test to test destructors
return test
@ -118,28 +125,28 @@ local function main(item)
end
end
local elapsed = time:elapsed_secs()
local code = 1
local retcode
if fail == 0 then
print("", style("pass", ("%s %d tests passed"):format(icon.check, pass)))
code = 0
print(style("pass", ("\t%s %d tests passed"):format(icon.check, pass)))
retcode = 0
else
print(
"",
("%s, %s"):format(
("\t%s, %s"):format(
style("pass", ("%s %d tests passed"):format(icon.check, pass)),
style("fail", ("%s %d tests failed"):format(icon.cross, fail))
)
)
retcode = 1
end
if elapsed < 1000 then
print("", ("%s completed in %.2f ms"):format(icon.chevron, elapsed * 1000))
print(style("faint", ("\t%s completed in %.2f ms"):format(icon.chevron, elapsed * 1000)))
else
print("", ("%s completed in %.2f s"):format(icon.chevron, elapsed))
print(style("faint", ("\t%s completed in %.2f s"):format(icon.chevron, elapsed)))
end
cx = nil
collectgarbage()
check_refs()
return code -- report error to cargo
check_refs() -- check that all refs were properly unref'ed in destructors
return retcode -- report error to cargo
end
return main(create_group("", function()
@ -152,6 +159,34 @@ return main(create_group("", function()
end
end
local function include_doctest(path, pat)
for entry in fs.glob_dir(path, pat) do
local line, doctest = 0, nil
for s in fs.read(entry:path()):gmatch("([^\n]*)\n?") do
line = line + 1
local prefix = s:match("^%s*///")
s = prefix and s:sub(#prefix + 1)
if s and not s:match("^%s*```%s*$") then
if s:match("^%s*```lua$") then
doctest = { line = line, col = #prefix + 2 }
elseif doctest then
table.insert(doctest, (" "):rep(#prefix) .. s)
end
else
if doctest then
local name = ("%s:%d:%d"):format(entry:path(), doctest.line, doctest.col)
local f, err = loadstring(table.concat(doctest, "\n"), "@" .. name)
if not f then error(err) end
test(("%s %s"):format(name, style("faint", "(doctest)")), f)
end
doctest = nil
end
end
end
end
include("tests", "**/*.lua")
include("crates", "*/tests/**/*.lua")
include_doctest("src", "**/*.rs")
include_doctest("crates", "*/src/**/*.rs")
end))