local socket = require"socket" host = host or "localhost" port = port or "8383" function pass(...) local s = string.format(unpack(arg)) io.stderr:write(s, "\n") end function fail(...) local s = string.format(unpack(arg)) io.stderr:write("ERROR: ", s, "!\n") socket.sleep(3) os.exit() end function warn(...) local s = string.format(unpack(arg)) io.stderr:write("WARNING: ", s, "\n") end function remote(...) local s = string.format(unpack(arg)) s = string.gsub(s, "\n", ";") s = string.gsub(s, "%s+", " ") s = string.gsub(s, "^%s*", "") control:send(s .. "\n") control:receive() end function test(test) io.stderr:write("----------------------------------------------\n", "testing: ", test, "\n", "----------------------------------------------\n") end function check_timeout(tm, sl, elapsed, err, opp, mode, alldone) if tm < sl then if opp == "send" then if not err then warn("must be buffered") elseif err == "timeout" then pass("proper timeout") else fail("unexpected error '%s'", err) end else if err ~= "timeout" then fail("should have timed out") else pass("proper timeout") end end else if mode == "total" then if elapsed > tm then if err ~= "timeout" then fail("should have timed out") else pass("proper timeout") end elseif elapsed < tm then if err then fail(err) else pass("ok") end else if alldone then if err then fail("unexpected error '%s'", err) else pass("ok") end else if err ~= "timeout" then fail(err) else pass("proper timeoutk") end end end else if err then fail(err) else pass("ok") end end end end if not socket._DEBUG then fail("Please define LUASOCKET_DEBUG and recompile LuaSocket") end io.stderr:write("----------------------------------------------\n", "LuaSocket Test Procedures\n", "----------------------------------------------\n") start = socket.gettime() function reconnect() io.stderr:write("attempting data connection... ") if data then data:close() end remote [[ if data then data:close() data = nil end data = server:accept() data:setoption("tcp-nodelay", true) ]] data, err = socket.connect(host, port) if not data then fail(err) else pass("connected!") end data:setoption("tcp-nodelay", true) end pass("attempting control connection...") control, err = socket.connect(host, port) if err then fail(err) else pass("connected!") end control:setoption("tcp-nodelay", true) ------------------------------------------------------------------------ function test_methods(sock, methods) for _, v in pairs(methods) do if type(sock[v]) ~= "function" then fail(sock.class .. " method '" .. v .. "' not registered") end end pass(sock.class .. " methods are ok") end ------------------------------------------------------------------------ function test_mixed(len) reconnect() local inter = math.ceil(len/4) local p1 = "unix " .. string.rep("x", inter) .. "line\n" local p2 = "dos " .. string.rep("y", inter) .. "line\r\n" local p3 = "raw " .. string.rep("z", inter) .. "bytes" local p4 = "end" .. string.rep("w", inter) .. "bytes" local bp1, bp2, bp3, bp4 remote (string.format("str = data:receive(%d)", string.len(p1)+string.len(p2)+string.len(p3)+string.len(p4))) sent, err = data:send(p1..p2..p3..p4) if err then fail(err) end remote "data:send(str); data:close()" bp1, err = data:receive() if err then fail(err) end bp2, err = data:receive() if err then fail(err) end bp3, err = data:receive(string.len(p3)) if err then fail(err) end bp4, err = data:receive("*a") if err then fail(err) end if bp1.."\n" == p1 and bp2.."\r\n" == p2 and bp3 == p3 and bp4 == p4 then pass("patterns match") else fail("patterns don't match") end end ------------------------------------------------------------------------ function test_asciiline(len) reconnect() local str, str10, back, err str = string.rep("x", math.mod(len, 10)) str10 = string.rep("aZb.c#dAe?", math.floor(len/10)) str = str .. str10 remote "str = data:receive()" sent, err = data:send(str.."\n") if err then fail(err) end remote "data:send(str ..'\\n')" back, err = data:receive() if err then fail(err) end if back == str then pass("lines match") else fail("lines don't match") end end ------------------------------------------------------------------------ function test_rawline(len) reconnect() local str, str10, back, err str = string.rep(string.char(47), math.mod(len, 10)) str10 = string.rep(string.char(120,21,77,4,5,0,7,36,44,100), math.floor(len/10)) str = str .. str10 remote "str = data:receive()" sent, err = data:send(str.."\n") if err then fail(err) end remote "data:send(str..'\\n')" back, err = data:receive() if err then fail(err) end if back == str then pass("lines match") else fail("lines don't match") end end ------------------------------------------------------------------------ function test_raw(len) reconnect() local half = math.floor(len/2) local s1, s2, back, err s1 = string.rep("x", half) s2 = string.rep("y", len-half) remote (string.format("str = data:receive(%d)", len)) sent, err = data:send(s1) if err then fail(err) end sent, err = data:send(s2) if err then fail(err) end remote "data:send(str)" back, err = data:receive(len) if err then fail(err) end if back == s1..s2 then pass("blocks match") else fail("blocks don't match") end end ------------------------------------------------------------------------ function test_totaltimeoutreceive(len, tm, sl) reconnect() local str, err, partial pass("%d bytes, %ds total timeout, %ds pause", len, tm, sl) remote (string.format ([[ data:settimeout(%d) str = string.rep('a', %d) data:send(str) print('server: sleeping for %ds') socket.sleep(%d) print('server: woke up') data:send(str) ]], 2*tm, len, sl, sl)) data:settimeout(tm, "total") local t = socket.gettime() str, err, partial, elapsed = data:receive(2*len) check_timeout(tm, sl, elapsed, err, "receive", "total", string.len(str or partial) == 2*len) end ------------------------------------------------------------------------ function test_totaltimeoutsend(len, tm, sl) reconnect() local str, err, total pass("%d bytes, %ds total timeout, %ds pause", len, tm, sl) remote (string.format ([[ data:settimeout(%d) str = data:receive(%d) print('server: sleeping for %ds') socket.sleep(%d) print('server: woke up') str = data:receive(%d) ]], 2*tm, len, sl, sl, len)) data:settimeout(tm, "total") str = string.rep("a", 2*len) total, err, partial, elapsed = data:send(str) check_timeout(tm, sl, elapsed, err, "send", "total", total == 2*len) end ------------------------------------------------------------------------ function test_blockingtimeoutreceive(len, tm, sl) reconnect() local str, err, partial pass("%d bytes, %ds blocking timeout, %ds pause", len, tm, sl) remote (string.format ([[ data:settimeout(%d) str = string.rep('a', %d) data:send(str) print('server: sleeping for %ds') socket.sleep(%d) print('server: woke up') data:send(str) ]], 2*tm, len, sl, sl)) data:settimeout(tm) str, err, partial, elapsed = data:receive(2*len) check_timeout(tm, sl, elapsed, err, "receive", "blocking", string.len(str or partial) == 2*len) end ------------------------------------------------------------------------ function test_blockingtimeoutsend(len, tm, sl) reconnect() local str, err, total pass("%d bytes, %ds blocking timeout, %ds pause", len, tm, sl) remote (string.format ([[ data:settimeout(%d) str = data:receive(%d) print('server: sleeping for %ds') socket.sleep(%d) print('server: woke up') str = data:receive(%d) ]], 2*tm, len, sl, sl, len)) data:settimeout(tm) str = string.rep("a", 2*len) total, err, partial, elapsed = data:send(str) check_timeout(tm, sl, elapsed, err, "send", "blocking", total == 2*len) end ------------------------------------------------------------------------ function empty_connect() reconnect() if data then data:close() data = nil end remote [[ if data then data:close() data = nil end data = server:accept() ]] data, err = socket.connect("", port) if not data then pass("ok") data = socket.connect(host, port) else pass("gethostbyname returns localhost on empty string...") end end ------------------------------------------------------------------------ function isclosed(c) return c:getfd() == -1 or c:getfd() == (2^32-1) end function active_close() reconnect() if isclosed(data) then fail("should not be closed") end data:close() if not isclosed(data) then fail("should be closed") end data = nil local udp = socket.udp() if isclosed(udp) then fail("should not be closed") end udp:close() if not isclosed(udp) then fail("should be closed") end pass("ok") end ------------------------------------------------------------------------ function test_closed() local back, partial, err local str = 'little string' reconnect() pass("trying read detection") remote (string.format ([[ data:send('%s') data:close() data = nil ]], str)) -- try to get a line back, err, partial = data:receive() if not err then fail("should have gotten 'closed'.") elseif err ~= "closed" then fail("got '"..err.."' instead of 'closed'.") elseif str ~= partial then fail("didn't receive partial result.") else pass("graceful 'closed' received") end reconnect() pass("trying write detection") remote [[ data:close() data = nil ]] total, err, partial = data:send(string.rep("ugauga", 100000)) if not err then pass("failed: output buffer is at least %d bytes long!", total) elseif err ~= "closed" then fail("got '"..err.."' instead of 'closed'.") else pass("graceful 'closed' received after %d bytes were sent", partial) end end ------------------------------------------------------------------------ function test_selectbugs() local r, s, e = socket.select(nil, nil, 0.1) assert(type(r) == "table" and type(s) == "table" and (e == "timeout" or e == "error")) pass("both nil: ok") local udp = socket.udp() udp:close() r, s, e = socket.select({ udp }, { udp }, 0.1) assert(type(r) == "table" and type(s) == "table" and (e == "timeout" or e == "error")) pass("closed sockets: ok") e = pcall(socket.select, "wrong", 1, 0.1) assert(e == false) e = pcall(socket.select, {}, 1, 0.1) assert(e == false) pass("invalid input: ok") end ------------------------------------------------------------------------ function accept_timeout() io.stderr:write("accept with timeout (if it hangs, it failed): ") local s, e = socket.bind("*", 0, 0) assert(s, e) local t = socket.gettime() s:settimeout(1) local c, e = s:accept() assert(not c, "should not accept") assert(e == "timeout", string.format("wrong error message (%s)", e)) t = socket.gettime() - t assert(t < 2, string.format("took to long to give up (%gs)", t)) s:close() pass("good") end ------------------------------------------------------------------------ function connect_timeout() io.stderr:write("connect with timeout (if it hangs, it failed!): ") local t = socket.gettime() local c, e = socket.tcp() assert(c, e) c:settimeout(0.1) local t = socket.gettime() local r, e = c:connect("10.0.0.1", 81) print(r, e) assert(not r, "should not connect") assert(socket.gettime() - t < 2, "took too long to give up.") c:close() print("ok") end ------------------------------------------------------------------------ function accept_errors() io.stderr:write("not listening: ") local d, e = socket.bind("*", 0) assert(d, e); local c, e = socket.tcp(); assert(c, e); d:setfd(c:getfd()) d:settimeout(2) local r, e = d:accept() assert(not r and e) print("ok: ", e) io.stderr:write("not supported: ") local c, e = socket.udp() assert(c, e); d:setfd(c:getfd()) local r, e = d:accept() assert(not r and e) print("ok: ", e) end ------------------------------------------------------------------------ function connect_errors() io.stderr:write("connection refused: ") local c, e = socket.connect("localhost", 1); assert(not c and e) print("ok: ", e) io.stderr:write("host not found: ") local c, e = socket.connect("host.is.invalid", 1); assert(not c and e, e) print("ok: ", e) end ------------------------------------------------------------------------ function rebind_test() local c = socket.bind("localhost", 0) local i, p = c:getsockname() local s, e = socket.tcp() assert(s, e) s:setoption("reuseaddr", false) r, e = s:bind("localhost", p) assert(not r, "managed to rebind!") assert(e) print("ok: ", e) end ------------------------------------------------------------------------ function getstats_test() reconnect() local t = 0 for i = 1, 25 do local c = math.random(1, 100) remote (string.format ([[ str = data:receive(%d) data:send(str) ]], c)) data:send(string.rep("a", c)) data:receive(c) t = t + c local r, s, a = data:getstats() assert(r == t, "received count failed" .. tostring(r) .. "/" .. tostring(t)) assert(s == t, "sent count failed" .. tostring(s) .. "/" .. tostring(t)) end print("ok") end ------------------------------------------------------------------------ function test_nonblocking(size) reconnect() print("Testing " .. 2*size .. " bytes") remote(string.format([[ data:send(string.rep("a", %d)) socket.sleep(0.5) data:send(string.rep("b", %d) .. "\n") ]], size, size)) local err = "timeout" local part = "" local str data:settimeout(0) while 1 do str, err, part = data:receive("*l", part) if err ~= "timeout" then break end end assert(str == (string.rep("a", size) .. string.rep("b", size))) reconnect() remote(string.format([[ str = data:receive(%d) socket.sleep(0.5) str = data:receive(2*%d, str) data:send(str) ]], size, size)) data:settimeout(0) local start = 0 while 1 do ret, err, start = data:send(str, start+1) if err ~= "timeout" then break end end data:send("\n") data:settimeout(-1) local back = data:receive(2*size) assert(back == str, "'" .. back .. "' vs '" .. str .. "'") print("ok") end ------------------------------------------------------------------------ function test_readafterclose() local back, partial, err local str = 'little string' reconnect() pass("trying repeated '*a' pattern") remote (string.format ([[ data:send('%s') data:close() data = nil ]], str)) back, err, partial = data:receive("*a") assert(back == str, "unexpected data read") back, err, partial = data:receive("*a") assert(back == nil and err == "closed", "should have returned 'closed'") print("ok") reconnect() pass("trying active close before '*a'") remote (string.format ([[ data:close() data = nil ]])) data:close() back, err, partial = data:receive("*a") assert(back == nil and err == "closed", "should have returned 'closed'") print("ok") reconnect() pass("trying active close before '*l'") remote (string.format ([[ data:close() data = nil ]])) data:close() back, err, partial = data:receive() assert(back == nil and err == "closed", "should have returned 'closed'") print("ok") reconnect() pass("trying active close before raw 1") remote (string.format ([[ data:close() data = nil ]])) data:close() back, err, partial = data:receive(1) assert(back == nil and err == "closed", "should have returned 'closed'") print("ok") reconnect() pass("trying active close before raw 0") remote (string.format ([[ data:close() data = nil ]])) data:close() back, err, partial = data:receive(0) assert(back == nil and err == "closed", "should have returned 'closed'") print("ok") end test("method registration") test_methods(socket.tcp(), { "accept", "bind", "close", "connect", "dirty", "getfd", "getpeername", "getsockname", "getstats", "setstats", "listen", "receive", "send", "setfd", "setoption", "setpeername", "setsockname", "settimeout", "shutdown", }) test_methods(socket.udp(), { "close", "getpeername", "dirty", "getfd", "getpeername", "getsockname", "receive", "receivefrom", "send", "sendto", "setfd", "setoption", "setpeername", "setsockname", "settimeout" }) test("testing read after close") test_readafterclose() test("select function") test_selectbugs() test("connect function") connect_timeout() empty_connect() connect_errors() test("rebinding: ") rebind_test() test("active close: ") active_close() test("closed connection detection: ") test_closed() test("accept function: ") accept_timeout() accept_errors() test("getstats test") getstats_test() test("character line") test_asciiline(1) test_asciiline(17) test_asciiline(200) test_asciiline(4091) test_asciiline(80199) test_asciiline(8000000) test_asciiline(80199) test_asciiline(4091) test_asciiline(200) test_asciiline(17) test_asciiline(1) test("mixed patterns") test_mixed(1) test_mixed(17) test_mixed(200) test_mixed(4091) test_mixed(801990) test_mixed(4091) test_mixed(200) test_mixed(17) test_mixed(1) test("binary line") test_rawline(1) test_rawline(17) test_rawline(200) test_rawline(4091) test_rawline(80199) test_rawline(8000000) test_rawline(80199) test_rawline(4091) test_rawline(200) test_rawline(17) test_rawline(1) test("raw transfer") test_raw(1) test_raw(17) test_raw(200) test_raw(4091) test_raw(80199) test_raw(8000000) test_raw(80199) test_raw(4091) test_raw(200) test_raw(17) test_raw(1) test("non-blocking transfer") test_nonblocking(1) test_nonblocking(17) test_nonblocking(200) test_nonblocking(4091) test_nonblocking(80199) test_nonblocking(800000) test_nonblocking(80199) test_nonblocking(4091) test_nonblocking(200) test_nonblocking(17) test_nonblocking(1) test("total timeout on send") test_totaltimeoutsend(800091, 1, 3) test_totaltimeoutsend(800091, 2, 3) test_totaltimeoutsend(800091, 5, 2) test_totaltimeoutsend(800091, 3, 1) test("total timeout on receive") test_totaltimeoutreceive(800091, 1, 3) test_totaltimeoutreceive(800091, 2, 3) test_totaltimeoutreceive(800091, 3, 2) test_totaltimeoutreceive(800091, 3, 1) test("blocking timeout on send") test_blockingtimeoutsend(800091, 1, 3) test_blockingtimeoutsend(800091, 2, 3) test_blockingtimeoutsend(800091, 3, 2) test_blockingtimeoutsend(800091, 3, 1) test("blocking timeout on receive") test_blockingtimeoutreceive(800091, 1, 3) test_blockingtimeoutreceive(800091, 2, 3) test_blockingtimeoutreceive(800091, 3, 2) test_blockingtimeoutreceive(800091, 3, 1) test(string.format("done in %.2fs", socket.gettime() - start))