-- SPDX-FileCopyrightText: © 2022—2026 Vladimir Zorin -- SPDX-License-Identifier: LicenseRef-OWL-1.0-or-later -- Licensed under OWL v1.0+. See LICENSE. local testimony = require("testimony") local lev = require("lev") local ps = require("std.ps") local testify = testimony.new("== lev fd_reader ==") -- ========================================================================= -- set_nonblocking -- ========================================================================= testify:that("set_nonblocking: toggles and reports previous state", function() local p = assert(ps.pipe()) -- Pipe fds start in blocking mode. local was = lev.set_nonblocking(p.out, true) testimony.assert_false(was, "expected previous state to be blocking") local was2 = lev.set_nonblocking(p.out, true) testimony.assert_true(was2, "expected previous state to be non-blocking") local was3 = lev.set_nonblocking(p.out, false) testimony.assert_true(was3, "expected previous state to be non-blocking") p:close_inn() p:close_out() end) -- ========================================================================= -- fd_reader: immediate read -- ========================================================================= testify:that("fd_reader: reads data already in the pipe", function() local p = assert(ps.pipe()) lev.set_nonblocking(p.out, true) p:write("hello") local got lev.run(function() local r = lev.fd_reader(p.out) got = r:read(64) end) testimony.assert_equal("hello", got) p:close_inn() p:close_out() end) -- ========================================================================= -- fd_reader: cooperative wait interleaves with the loop -- ========================================================================= testify:that("fd_reader: yields until data arrives, loop keeps running", function() local p = assert(ps.pipe()) lev.set_nonblocking(p.out, true) local got, ticks = nil, 0 lev.run(function() -- Ticker proves the event loop runs while the reader waits. local ticker = lev.spawn(function() for _ = 1, 3 do lev.sleep(0.01) ticks = ticks + 1 end end) -- Writer delivers data after a delay. lev.spawn(function() lev.sleep(0.025) p:write("world") end) local r = lev.fd_reader(p.out) got = r:read(64) lev.await(ticker) end) testimony.assert_equal("world", got) testimony.assert_true(ticks >= 1, "expected the loop to tick while reading was blocked") p:close_inn() p:close_out() end) -- ========================================================================= -- fd_reader: EOF -> "closed" -- ========================================================================= testify:that("fd_reader: returns closed on EOF", function() local p = assert(ps.pipe()) lev.set_nonblocking(p.out, true) local data, err lev.run(function() lev.spawn(function() lev.sleep(0.01) p:close_inn() -- close write end -> EOF on read end end) local r = lev.fd_reader(p.out) data, err = r:read(64) end) testimony.assert_nil(data) testimony.assert_equal("closed", err) p:close_out() end) -- ========================================================================= -- fd_reader: read_available drains without blocking -- ========================================================================= testify:that("fd_reader: read_available drains buffered bytes, no block", function() local p = assert(ps.pipe()) lev.set_nonblocking(p.out, true) p:write("abc") local data, status lev.run(function() local r = lev.fd_reader(p.out) data, status = r:read_available(64) end) testimony.assert_equal("abc", data) testimony.assert_equal("ok", status) p:close_inn() p:close_out() end) testify:that("fd_reader: read_available on empty pipe returns empty, no block", function() local p = assert(ps.pipe()) lev.set_nonblocking(p.out, true) local data, status lev.run(function() local r = lev.fd_reader(p.out) data, status = r:read_available(64) end) testimony.assert_equal("", data) testimony.assert_equal("ok", status) p:close_inn() p:close_out() end) -- ========================================================================= -- fd_reader: cancel token interrupts a blocked read -- ========================================================================= testify:that("fd_reader: cancel token interrupts a blocked read", function() local p = assert(ps.pipe()) lev.set_nonblocking(p.out, true) local data, err lev.run(function() local token = lev.cancel_token() lev.spawn(function() lev.sleep(0.01) token:cancel() end) local r = lev.fd_reader(p.out) data, err = r:read(64, { cancel = token }) end) testimony.assert_nil(data) testimony.assert_equal("cancelled", err) p:close_inn() p:close_out() end) -- ========================================================================= -- fd_reader: timeout interrupts a blocked read -- ========================================================================= testify:that("fd_reader: timeout returns when no data arrives", function() local p = assert(ps.pipe()) lev.set_nonblocking(p.out, true) local data, err lev.run(function() local r = lev.fd_reader(p.out) data, err = r:read(64, { timeout = 0.02 }) end) testimony.assert_nil(data) testimony.assert_not_nil(err) p:close_inn() p:close_out() end) -- ========================================================================= -- Conclude -- ========================================================================= testify:conclude()