diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 61a0dc8..12a15de 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -1,20 +1,20 @@ name: test -on: [push] +on: + push: + paths-ignore: + - '**.md' + - 'LICENSE' jobs: luacheck: runs-on: ubuntu-latest + container: + image: ghcr.io/mah0x211/lua-ci:latest steps: - name: Checkout uses: actions/checkout@v2 - - - name: Setup Lua - uses: leafo/gh-actions-lua@v8.0.0 - - - name: Setup Luarocks - uses: leafo/gh-actions-luarocks@v4 - name: Install Tools run: luarocks install luacheck @@ -24,50 +24,54 @@ jobs: luacheck . test: + needs: luacheck + permissions: + id-token: write runs-on: ubuntu-latest + container: + image: ghcr.io/mah0x211/lua-ci:latest strategy: matrix: lua-version: - - "5.1" - - "5.2" - - "5.3" - - "5.4" - - "luajit-2.0.5" - - "luajit-openresty" + - "5.1.:latest" + - "5.2.:latest" + - "5.3.:latest" + - "5.4.:latest" + - "lj-v2.1:latest" steps: + - + name: Switch Lua Version + run: | + lenv -g use ${{ matrix.lua-version }} + lua -v || true - name: Checkout uses: actions/checkout@v2 - with: - submodules: 'true' - - - name: Setup Lua ${{ matrix.lua-version }} - uses: leafo/gh-actions-lua@v8.0.0 - with: - luaVersion: ${{ matrix.lua-version }} - - name: Setup Luarocks - uses: leafo/gh-actions-luarocks@v4 + name: Install + run: | + ACT_COVERAGE=1 luarocks make - - name: Install Test Tools + name: Install Tools run: | luarocks install testcase + luarocks install assert luarocks install errno - luarocks install llsocket luarocks install signal luarocks install luacov - - - name: Install - run: | - luarocks make - name: Run Test run: | testcase --coverage ./test/ - - name: Upload lua coverage to Codecov - uses: codecov/codecov-action@v2 + name: Generate coverage reports + run: | + sh ./covgen.sh + - + name: Upload coverage to Codecov + uses: codecov/codecov-action@v5 with: - files: ./luacov.report.out + use_oidc: true + disable_search: true + files: ./luacov.report.out,./coverage/lcov.info flags: unittests - diff --git a/act.lua b/act.lua index 4c8406e..666c294 100644 --- a/act.lua +++ b/act.lua @@ -138,7 +138,7 @@ local function pwaitpid(sec, wpid, ...) end while true do - local res, err, again = waitpid(wpid, 'nohang', ...) + local res, err, again = waitpid(wpid, nil, 'nohang', ...) if res then return res elseif not again then diff --git a/covgen.sh b/covgen.sh new file mode 100755 index 0000000..700ea4b --- /dev/null +++ b/covgen.sh @@ -0,0 +1,7 @@ +#!/usr/bin/env sh + +set -ex + +mkdir -p ./coverage +lcov -c -d ./src -o coverage/lcov.info.all +lcov -r coverage/lcov.info.all '*/include/*' -o coverage/lcov.info diff --git a/rockspecs/act-scm-1.rockspec b/rockspecs/act-scm-1.rockspec index b5217c7..12d880c 100644 --- a/rockspecs/act-scm-1.rockspec +++ b/rockspecs/act-scm-1.rockspec @@ -1,3 +1,4 @@ +rockspec_format = "3.0" package = "act" version = "scm-1" source = { @@ -11,24 +12,45 @@ description = { } dependencies = { "lua >= 5.1", - "lauxhlib >= 0.5", - "denque >= 0.5", - "fork >= 0.2", - "metamodule >= 0.4", - "minheap >= 0.2", - "reco >= 1.6", - "epoll >= 0.5.0", - "kqueue >= 0.6.0", - "time-clock >= 0.4.0", - "time-sleep >= 0.2.1", - "waitpid >= 0.1.0", + "lauxhlib >= 0.6.3", + "denque >= 0.5.2", + "fork >= 0.4.2", + "metamodule >= 0.5.1", + "minheap >= 0.2.0", + "reco >= 1.6.1", + "epoll >= 0.6.0", + "kqueue >= 0.7.0", + "time-clock >= 0.5.2", + "time-sleep >= 0.2.3", + "waitpid >= 0.3.4", +} +build_dependencies = { + "luarocks-build-hooks >= 0.8.0", } build = { - type = "builtin", + type = "hooks", + before_build = { + "$(extra-vars)", + }, + extra_variables = { + CFLAGS = "-Wall -Wno-trigraphs -Wmissing-field-initializers -Wreturn-type -Wmissing-braces -Wparentheses -Wno-switch -Wunused-function -Wunused-label -Wunused-parameter -Wunused-variable -Wunused-value -Wuninitialized -Wunknown-pragmas -Wshadow -Wsign-compare", + }, + conditional_variables = { + ACT_COVERAGE = { + CFLAGS = "--coverage", + LIBFLAG = "--coverage", + }, + }, modules = { act = "act.lua", ["act.aux"] = "lib/aux.lua", - ["act.bitset"] = "src/bitset.c", + ["act.bitset"] = { + sources = "src/bitset.c", + incdirs = { + "src", + "$(DEP_LAUXHLIB_INCDIR)", + }, + }, ["act.callee"] = "lib/callee.lua", ["act.context"] = "lib/context.lua", ["act.coro"] = "lib/coro.lua", @@ -42,6 +64,11 @@ build = { ["act.poller"] = "lib/poller.lua", ["act.pool"] = "lib/pool.lua", ["act.runq"] = "lib/runq.lua", - ["act.stack"] = "src/stack.c", + ["act.stack"] = { + sources = "src/stack.c", + incdirs = { + "$(DEP_LAUXHLIB_INCDIR)", + }, + }, }, } diff --git a/src/bitset.c b/src/bitset.c index 714133b..50e631d 100644 --- a/src/bitset.c +++ b/src/bitset.c @@ -20,8 +20,14 @@ * IN THE SOFTWARE. */ +// project #include "bitset.h" +// depend #include "lauxhlib.h" +// lua +#include +// system +#include #define MODULE_MT "act.bitset" diff --git a/src/getcpus.c b/src/getcpus.c index c19b3a1..98a17cf 100644 --- a/src/getcpus.c +++ b/src/getcpus.c @@ -19,9 +19,10 @@ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ -#include // lua #include +// system +#include static int getcpus_lua(lua_State *L) { diff --git a/src/ignsigpipe.c b/src/ignsigpipe.c index 55d9fa2..f75ccba 100644 --- a/src/ignsigpipe.c +++ b/src/ignsigpipe.c @@ -22,9 +22,10 @@ * this code copied from lua-nosigpipe */ -#include -// lualib +// lua #include +// system +#include LUALIB_API int luaopen_act_ignsigpipe(lua_State *L) { diff --git a/src/stack.c b/src/stack.c index a919d3e..e0161b0 100644 --- a/src/stack.c +++ b/src/stack.c @@ -20,7 +20,10 @@ * IN THE SOFTWARE. */ +// depend #include "lauxhlib.h" +// lua +#include #define MODULE_MT "act.stack" @@ -89,7 +92,6 @@ static int set_lua(lua_State *L) { int argc = lua_gettop(L) - 1; act_stack_t *s = luaL_checkudata(L, 1, MODULE_MT); - lua_Integer n = 0; // clear arguments lua_settop(s->L, 0); diff --git a/test/act_fork_waitpid_test.lua b/test/act_fork_waitpid_test.lua index 739a9d4..13e5ea6 100644 --- a/test/act_fork_waitpid_test.lua +++ b/test/act_fork_waitpid_test.lua @@ -1,7 +1,6 @@ local with_luacov = require('luacov').with_luacov local testcase = require('testcase') local getpid = require('testcase.getpid') -local errno = require('errno') local act = require('act') local gettime = require('time.clock').gettime @@ -123,11 +122,11 @@ function testcase.waitpid() return end - -- test that return error if no child process exists + -- test that return all nil if no child process exists assert(act.run(with_luacov(function() local res, err, timeout = act.waitpid() assert.is_nil(res) - assert.equal(err.type, errno.ECHILD) + assert.is_nil(err) assert.is_nil(timeout) end))) diff --git a/test/bitset_test.lua b/test/bitset_test.lua new file mode 100644 index 0000000..23dfb3e --- /dev/null +++ b/test/bitset_test.lua @@ -0,0 +1,98 @@ +local testcase = require('testcase') +local assert = require('assert') +local bitset = require('act.bitset') + +function testcase.new_and_tostring() + local bs = bitset() + assert.match(tostring(bs), 'act.bitset: 0x', false) +end + +function testcase.get_set_unset() + local bs = bitset() + -- initial value is 0 + assert.is_false(bs:get(0)) + assert.is_false(bs:get(100)) + -- set then get returns 1 + assert.is_true(bs:set(10)) + assert.is_true(bs:get(10)) + -- unset then get returns 0 + assert.is_true(bs:unset(10)) + assert.is_false(bs:get(10)) +end + +function testcase.set_resizes_when_pos_exceeds_capacity() + local bs = bitset() + -- initial capacity is 4096 bits, set beyond it triggers resize + assert.is_true(bs:set(8192)) + assert.is_true(bs:get(8192)) + assert.is_false(bs:get(8000)) +end + +function testcase.get_out_of_range_returns_error() + local bs = bitset() + -- get does not resize; out-of-range returns nil + ERANGE message + local v, err, errno = bs:get(1000000) + assert.is_nil(v) + assert.is_string(err) + assert.greater(errno, 0) +end + +function testcase.unset_out_of_range_returns_error() + local bs = bitset() + -- unset does not resize; out-of-range returns nil + ERANGE message + local ok, err, errno = bs:unset(1000000) + assert.is_nil(ok) + assert.is_string(err) + assert.greater(errno, 0) +end + +function testcase.ffz_empty_partial_full() + local bs = bitset() + -- empty: first zero is position 0 + assert.equal(bs:ffz(), 0) + -- after setting bit 0, first zero is 1 + bs:set(0) + assert.equal(bs:ffz(), 1) + -- after setting bit 1, first zero is 2 + bs:set(1) + assert.equal(bs:ffz(), 2) +end + +function testcase.ffz_returns_capacity_when_full() + local bs = bitset() + for pos = 0, 4095 do + bs:set(pos) + end + -- when bitset is fully set, ffz reports nbit (no zero bit found) + assert.equal(bs:ffz(), 4096) +end + +function testcase.add_returns_consecutive_positions() + local bs = bitset() + assert.equal(bs:add(), 0) + assert.equal(bs:add(), 1) + assert.equal(bs:add(), 2) + bs:unset(1) + -- next add reuses freed position + assert.equal(bs:add(), 1) + assert.equal(bs:add(), 3) +end + +function testcase.add_grows_beyond_initial_capacity() + local bs = bitset() + -- exhaust initial capacity + for _ = 0, 4095 do + bs:add() + end + -- next add must trigger resize and succeed + assert.equal(bs:add(), 4096) + assert.is_true(bs:get(4096)) +end + +function testcase.gc_cleanup() + local bs = bitset() + bs:set(0) + bs = nil -- luacheck: ignore 311 + collectgarbage('collect') + collectgarbage('collect') +end diff --git a/test/stack_test.lua b/test/stack_test.lua new file mode 100644 index 0000000..b2a1157 --- /dev/null +++ b/test/stack_test.lua @@ -0,0 +1,129 @@ +local testcase = require('testcase') +local assert = require('assert') +local stack = require('act.stack') + +function testcase.new_empty_and_tostring() + local s = stack() + assert.match(tostring(s), 'act.stack: 0x', false) + assert.equal(#s, 0) +end + +function testcase.new_with_initial_values() + local s = stack('a', 'b', 'c') + assert.equal(#s, 3) + -- values are taken in order; pop returns the last pushed + assert.equal(s:pop(), 'c') + assert.equal(s:pop(), 'b') + assert.equal(s:pop(), 'a') + assert.equal(#s, 0) +end + +function testcase.push_pop() + local s = stack() + s:push('x', 'y', 'z') + assert.equal(#s, 3) + assert.equal(s:pop(), 'z') + assert.equal(s:pop(), 'y') + assert.equal(s:pop(), 'x') +end + +function testcase.pop_when_empty_returns_nothing() + local s = stack() + assert.equal(select('#', s:pop()), 0) +end + +function testcase.push_with_no_args_is_noop() + local s = stack('a') + s:push() + assert.equal(#s, 1) +end + +function testcase.unshift_prepends_to_head() + local s = stack('b', 'c') + s:unshift('a0', 'a1') + -- head is now a0,a1,b,c -> pop yields c,b,a1,a0 + assert.equal(s:pop(), 'c') + assert.equal(s:pop(), 'b') + assert.equal(s:pop(), 'a1') + assert.equal(s:pop(), 'a0') +end + +function testcase.unshift_with_no_args_is_noop() + local s = stack('a', 'b') + s:unshift() + assert.equal(#s, 2) +end + +function testcase.insert_at_head() + local s = stack('b', 'c') + -- idx <= 1 prepends + s:insert(1, 'a0', 'a1') + assert.equal(s:pop(), 'c') + assert.equal(s:pop(), 'b') + assert.equal(s:pop(), 'a1') + assert.equal(s:pop(), 'a0') +end + +function testcase.insert_at_middle() + local s = stack('a', 'b', 'c', 'd') + -- insert at idx=2 within current length + s:insert(2, 'X', 'Y') + assert.equal(#s, 6) +end + +function testcase.insert_beyond_length_appends() + local s = stack('a', 'b') + -- idx > tail and not <=1: extra args are still appended at the tail + s:insert(99, 'X') + assert.equal(#s, 3) + assert.equal(s:pop(), 'X') +end + +function testcase.insert_with_no_extra_args_is_noop() + local s = stack('a', 'b') + s:insert(1) + assert.equal(#s, 2) +end + +function testcase.set_replaces_contents() + local s = stack('a', 'b', 'c') + s:set('x', 'y') + assert.equal(#s, 2) + assert.equal(s:pop(), 'y') + assert.equal(s:pop(), 'x') +end + +function testcase.set_with_no_args_clears() + local s = stack('a', 'b') + s:set() + assert.equal(#s, 0) +end + +function testcase.clear_returns_all_values() + local s = stack('a', 'b', 'c') + local a, b, c = s:clear() + assert.equal(a, 'a') + assert.equal(b, 'b') + assert.equal(c, 'c') + assert.equal(#s, 0) +end + +function testcase.clear_with_args_appends_then_returns_all() + local s = stack('a') + local a, b, c = s:clear('b', 'c') + assert.equal(a, 'a') + assert.equal(b, 'b') + assert.equal(c, 'c') + assert.equal(#s, 0) +end + +function testcase.clear_when_empty_returns_nothing() + local s = stack() + assert.equal(select('#', s:clear()), 0) +end + +function testcase.gc_cleanup() + stack('a', 'b', 'c') + collectgarbage('collect') + collectgarbage('collect') +end