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 /// ```lua
/// local net = require("lb:net") /// 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) /// socket:set_nodelay(true)
/// ``` /// ```
pub extern "Lua" fn bind_tcp( pub extern "Lua" fn bind_tcp(
@ -257,15 +257,16 @@ impl lb_netlib {
/// ///
/// # Example /// # Example
/// ///
/// ```lua /// ```lua,no_run
/// local net = require("lb:net") /// 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 /// for stream in listener do
/// print("client connected: ", stream:remote_addr()) /// print("client connected: ", stream:peer_addr())
/// end /// end
/// ```
pub extern "Lua" fn listen_tcp( pub extern "Lua" fn listen_tcp(
addr: OneOf<(&str, &lb_ipaddr, &lb_socketaddr)>, addr: OneOf<(&str, &lb_ipaddr, &lb_socketaddr)>,
port: Option<u16>, port: Option<u16>,
@ -287,9 +288,10 @@ impl lb_netlib {
/// ///
/// ```lua /// ```lua
/// local net = require("lb:net") /// 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") /// stream:write("Hello, server!\n")
/// ``` /// ```
pub async extern "Lua" fn connect_tcp( 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. /// 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 task = require("lb:task")
/// local net = require("lb:net") /// local net = require("lb:net")
/// local socket = net.connect_tcp("127.0.0.1:1234") /// local socket = net.connect_tcp("127.0.0.1:1234")
@ -194,17 +194,17 @@ impl lb_tcpsocket {
/// local reader = spawn(function() /// local reader = spawn(function()
/// for chunk in socket, 1024 do /// for chunk in socket, 1024 do
/// print("received: ", chunk) /// print("received: ", chunk)
/// done /// end
/// ///
/// print("done reading") /// print("done reading")
/// end) /// end)
/// ///
/// local writer = spawn(function() /// local writer = spawn(function()
/// for i = 1, 10 do /// for i = 1, 10 do
/// local msg = ("message %d\n"):format(i) /// local msg = ("message %d"):format(i)
/// socket:write(msg) /// socket:write(msg)
/// print("sent: ", msg) /// print("sent: ", msg)
/// done /// end
/// ///
/// print("done writing") /// print("done writing")
/// end) /// 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 /// 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: /// 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 /// while true do
/// local chunk = socket:read_partial(1024) /// local chunk = socket:read_partial(1024)
/// if chunk == nil then break end /// 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: /// 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 net = require("lb:net")
/// local listener = net.listen_tcp("127.0.0.1") /// local listener = net.listen_tcp("127.0.0.1")
/// ///

View File

@ -12,6 +12,7 @@ local color = {
reset = "\x1b[0m", reset = "\x1b[0m",
pass = "\x1b[32;1m", -- green pass = "\x1b[32;1m", -- green
fail = "\x1b[31;1m", -- red fail = "\x1b[31;1m", -- red
faint = "\x1b[2;39;49m", -- faint
} }
local icon = { local icon = {
@ -24,6 +25,12 @@ local function style(name, s)
return ("%s%s%s"):format(color[name], s, color.reset) return ("%s%s%s"):format(color[name], s, color.reset)
end 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 function create_test(name, f, group)
local test = { type = "test", name = name or "", group = group, state = "pending", f = f } local test = { type = "test", name = name or "", group = group, state = "pending", f = f }
local fenv = setmetatable({}, { __index = global, __newindex = global }) local fenv = setmetatable({}, { __index = global, __newindex = global })
@ -67,13 +74,13 @@ local function trace(msg)
end end
local function run_test(test) local function run_test(test)
local ok, res = xpcall(test.f, trace, test) local ok, trace = xpcall(test.f, trace, test)
if ok then if ok then
test.state = "pass" 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 else
test.state = "fail" 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 end
collectgarbage() -- gc after each test to test destructors collectgarbage() -- gc after each test to test destructors
return test return test
@ -118,28 +125,28 @@ local function main(item)
end end
end end
local elapsed = time:elapsed_secs() local elapsed = time:elapsed_secs()
local code = 1 local retcode
if fail == 0 then if fail == 0 then
print("", style("pass", ("%s %d tests passed"):format(icon.check, pass))) print(style("pass", ("\t%s %d tests passed"):format(icon.check, pass)))
code = 0 retcode = 0
else else
print( print(
"", ("\t%s, %s"):format(
("%s, %s"):format(
style("pass", ("%s %d tests passed"):format(icon.check, pass)), style("pass", ("%s %d tests passed"):format(icon.check, pass)),
style("fail", ("%s %d tests failed"):format(icon.cross, fail)) style("fail", ("%s %d tests failed"):format(icon.cross, fail))
) )
) )
retcode = 1
end end
if elapsed < 1000 then 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 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 end
cx = nil cx = nil
collectgarbage() collectgarbage()
check_refs() check_refs() -- check that all refs were properly unref'ed in destructors
return code -- report error to cargo return retcode -- report error to cargo
end end
return main(create_group("", function() return main(create_group("", function()
@ -152,6 +159,34 @@ return main(create_group("", function()
end end
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("tests", "**/*.lua")
include("crates", "*/tests/**/*.lua") include("crates", "*/tests/**/*.lua")
include_doctest("src", "**/*.rs")
include_doctest("crates", "*/src/**/*.rs")
end)) end))