Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ jobs:
run: |
sudo apt-get update
sudo apt-get install -y lua5.4 liblua5.4-dev cmake
sudo ln -sf /usr/bin/lua5.4 /usr/bin/lua

- uses: extractions/setup-just@v2

Expand All @@ -43,9 +44,12 @@ jobs:

- uses: extractions/setup-just@v2

- name: Install GitHub CLI (if missing)
- name: Install GitHub CLI and Lua (if missing)
run: |
which gh || sudo apt-get install -y gh
sudo apt-get update
sudo apt-get install -y lua5.4 liblua5.4-dev
sudo ln -sf /usr/bin/lua5.4 /usr/bin/lua

- name: Check formatting
run: |
Expand Down
9 changes: 9 additions & 0 deletions .luacov
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
return {
include = {
"lua/roda/.*"
},
exclude = {
"spec/.*",
"%.lux/.*"
}
}
14 changes: 12 additions & 2 deletions justfile
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ lint:
[doc("Lint for CI (Lua 5.4)")]
[group('ci')]
lint-ci:
lx --lua-version 5.4 lint
lx --lua-version 5.4 --lua-dir {{ lua_prefix }} lint

[doc("Run code quality checks")]
[group('dev')]
Expand All @@ -112,11 +112,21 @@ test-unit: build-luv
[group('test')]
test: test-unit

[doc("Run unit tests with coverage and generate report")]
[group('test')]
test-coverage: build-luv
@echo "Running unit tests with coverage..."
LUA_CPATH="{{ build_dir }}/?.so;;" lx --lua-version 5.5 --lua-dir {{ lua_prefix }} test -- --coverage
@echo "Generating coverage report..."
@luacov_src=$(find .lux/5.5/test_dependencies/5.5 -maxdepth 1 -name '*luacov*' -type d -print -quit 2>/dev/null)/src; \
lx exec --no-loader lua -- -e "package.path = package.path .. ';"$luacov_src"/?.lua'; local r = require('luacov.runner'); r.run_report(r.load_config())"
@echo "Coverage report written to luacov.report.out"

[doc("Run unit tests for CI (Lua 5.4)")]
[group('ci')]
test-ci: build-luv
@echo "Running unit tests for CI..."
LUA_CPATH="{{ build_dir }}/?.so;;" lx --lua-version 5.4 test
LUA_CPATH="{{ build_dir }}/?.so;;" lx --lua-version 5.4 --lua-dir {{ lua_prefix }} test

# --- Build ---

Expand Down
26 changes: 17 additions & 9 deletions lua/roda/util.lua
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,24 @@ function M.bracket(acquire, release)
return function(use)
return function(on_complete)
local resource = acquire()
use(
resource,
-- Release the resource and trigger the final continuation
function(...)
release(resource)
if on_complete then
on_complete(...)
end
local released = false

local function safe_release(...)
if released then
return
end
released = true
release(resource)
if on_complete then
on_complete(...)
end
)
end

local success, err = pcall(use, resource, safe_release)
if not success then
safe_release()
error(err, 0)
end
end
end
end
Expand Down
1 change: 1 addition & 0 deletions lux.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ dev = "git+https://github.com/tkolleh/roda.lua.git"

[test_dependencies]
busted = "2.3.0-1"
luacov = ">= 0.15.0"

[test]
type = "busted"
Expand Down
72 changes: 61 additions & 11 deletions spec/roda_spec.lua
Original file line number Diff line number Diff line change
@@ -1,18 +1,17 @@
---@diagnostic disable: undefined-global
local roda = require("roda")

describe("roda module", function()
-- Mock stream to capture output without affecting terminal
local function mock_stream()
return {
output = {},
write = function(self, str)
table.insert(self.output, str)
end,
flush = function() end,
}
end
local function mock_stream()
return {
output = {},
write = function(self, str)
table.insert(self.output, str)
end,
flush = function() end,
}
end

describe("roda module", function()
describe("constructor", function()
it("should create spinner with string text", function()
local spinner = roda("Loading...")
Expand Down Expand Up @@ -367,6 +366,57 @@ describe("roda.promise", function()
end)
end)

describe("roda.roda alias", function()
it("should create a spinner when called directly", function()
local spinner = roda.roda("Direct call")
assert.is_not_nil(spinner)
assert.equals("Direct call", spinner:getText())
end)

it("should behave identically to roda.new", function()
local s1 = roda.roda({ text = "test", color = "green" })
local s2 = roda.new({ text = "test", color = "green" })
assert.equals(s1:getText(), s2:getText())
assert.equals(s1:getColor(), s2:getColor())
end)
end)

describe("render edge cases", function()
it("should return self when render called while not spinning", function()
local stream = mock_stream()
local spinner = roda("Test")
spinner._stream = stream
local result = spinner:render()
assert.equals(spinner, result)
assert.equals(0, #stream.output)
end)

it("should wrap frame index after reaching the end", function()
local stream = mock_stream()
local spinner = roda({ spinner = "line" })
spinner._stream = stream
spinner:start()
local frames_count = #spinner._spinner.frames
for _ = 1, frames_count - 1 do
spinner:render()
end
assert.equals(1, spinner._frame_index)
spinner:stop()
end)

it("should render when setText is called while spinning", function()
local stream = mock_stream()
local spinner = roda("Original")
spinner._stream = stream
spinner:start()
local count_before = #stream.output
spinner:setText("Updated")
assert.is_true(#stream.output > count_before)
assert.equals("Updated", spinner:getText())
spinner:stop()
end)
end)

describe("submodule exports", function()
it("should export ansi submodule", function()
assert.is_table(roda.ansi)
Expand Down
140 changes: 140 additions & 0 deletions spec/util_spec.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
---@diagnostic disable: undefined-global
local fp = require("roda.util")

describe("roda.util", function()
describe("bracket", function()
it("should acquire resource and pass it to use", function()
local acquired = false
local resource_value = "test-resource"
local received_resource = nil

local bracket = fp.bracket(function()
acquired = true
return resource_value
end, function() end)

local use_fn = bracket(function(resource, done)
received_resource = resource
done()
end)

use_fn(function() end)

assert.is_true(acquired)
assert.equals(resource_value, received_resource)
end)

it("should release resource via release callback", function()
local released = false
local released_resource = nil

local bracket = fp.bracket(function()
return "res"
end, function(r)
released = true
released_resource = r
end)

local use_fn = bracket(function(resource, done)
done("result")
end)

use_fn(function() end)

assert.is_true(released)
assert.equals("res", released_resource)
end)

it("should pass callback values to on_complete", function()
local on_complete_result = nil

local bracket = fp.bracket(function()
return "res"
end, function() end)

local use_fn = bracket(function(resource, done)
done("value1", "value2")
end)

use_fn(function(...)
on_complete_result = { ... }
end)

assert.equals("value1", on_complete_result[1])
assert.equals("value2", on_complete_result[2])
end)

it("should call release before on_complete", function()
local call_order = {}

local bracket = fp.bracket(function()
return "res"
end, function()
table.insert(call_order, "release")
end)

local use_fn = bracket(function(resource, done)
done()
end)

use_fn(function()
table.insert(call_order, "on_complete")
end)

assert.equals("release", call_order[1])
assert.equals("on_complete", call_order[2])
end)

it("should not call on_complete when it is nil", function()
local bracket = fp.bracket(function()
return "res"
end, function() end)

local use_fn = bracket(function(resource, done)
done("result")
end)

assert.has_no.errors(function()
use_fn(nil)
end)
end)

it("should call release only once even if done is called multiple times", function()
local release_count = 0
local bracket = fp.bracket(function()
return "res"
end, function()
release_count = release_count + 1
end)

local use_fn = bracket(function(resource, done)
done()
done()
done()
end)

use_fn(function() end)

assert.equals(1, release_count)
end)

it("should call release and re-throw if use throws a synchronous error", function()
local released = false
local bracket = fp.bracket(function()
return "res"
end, function()
released = true
end)

local use_fn = bracket(function(resource, done)
error("synchronous error")
end)

assert.has_error(function()
use_fn(function() end)
end, "synchronous error")

assert.is_true(released)
end)
end)
end)
Loading