From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from smtp47.i.mail.ru (smtp47.i.mail.ru [94.100.177.107]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by dev.tarantool.org (Postfix) with ESMTPS id AE13F46971A for ; Tue, 10 Dec 2019 17:36:44 +0300 (MSK) From: Ilya Kosarev Date: Tue, 10 Dec 2019 17:36:38 +0300 Message-Id: <20191210143638.16751-1-i.kosarev@tarantool.org> Subject: [Tarantool-patches] [PATCH v3] test: fix flaky socket test List-Id: Tarantool development patches List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , To: tarantool-patches@dev.tarantool.org Cc: v.shpilevoy@tarantool.org test: fix flaky socket test socket.test had a number of flaky problems: - socket readiness expectation - race conditions on socket shutdown in emulation test cases - tcp_server stability in socket receive inconsistent behavior case Now they are solved. Port randomization is improved. Socket test is not fragile anymore. Closes #4451, #4426, #4469 --- Branch: https://github.com/tarantool/tarantool/tree/i.kosarev/gh-4426-4451-fix-socket-test Issues: https://github.com/tarantool/tarantool/issues/4426 https://github.com/tarantool/tarantool/issues/4451 https://github.com/tarantool/tarantool/issues/4469 Changes in v2: - reconsidered socket readiness expectation - reduced conditions waiting time Changes in v3: - reconsidered expectations to unify them - simplified randomization test/app/socket.result | 159 +++++++++++++++++++++++++-------------- test/app/socket.test.lua | 123 +++++++++++++++++++----------- test/app/suite.ini | 1 - 3 files changed, 178 insertions(+), 105 deletions(-) diff --git a/test/app/socket.result b/test/app/socket.result index fd299424c96..25ad9034374 100644 --- a/test/app/socket.result +++ b/test/app/socket.result @@ -42,7 +42,7 @@ test_run:cmd("push filter '(error: .builtin/.*[.]lua):[0-9]+' to '\\1'") --- - true ... -WAIT_COND_TIME = 10 +WAIT_COND_TIME = 100 --- ... socket('PF_INET', 'SOCK_STREAM', 'tcp121222'); @@ -107,11 +107,11 @@ s:nonblock(true) --- - true ... -s:readable(.1) +s:readable(WAIT_COND_TIME) --- - true ... -s:wait(.1) +s:wait(WAIT_COND_TIME) --- - RW ... @@ -183,7 +183,7 @@ s:writable(0) --- - true ... -s:wait(.01) +s:wait(WAIT_COND_TIME) --- - RW ... @@ -227,11 +227,11 @@ s:syswrite(ffi.cast('const char *', ping), #ping) --- - 6 ... -s:readable(1) +s:readable(WAIT_COND_TIME) --- - true ... -s:wait(.01) +s:wait(WAIT_COND_TIME) --- - RW ... @@ -278,14 +278,14 @@ s:error() --- - null ... -s:listen(128) +s:listen(WAIT_COND_TIME) --- - true ... sevres = {} --- ... -type(require('fiber').create(function() s:readable() do local sc = s:accept() table.insert(sevres, sc) sc:syswrite('ok') sc:close() end end)) +type(require('fiber').create(function() s:readable(WAIT_COND_TIME) do local sc = s:accept() table.insert(sevres, sc) sc:syswrite('ok') sc:close() end end)) --- - userdata ... @@ -308,7 +308,7 @@ sc:nonblock(true) --- - true ... -sc:readable(.5) +sc:readable(WAIT_COND_TIME) --- - true ... @@ -440,7 +440,7 @@ s:bind('127.0.0.1', 0) --- - true ... -s:listen(128) +s:listen(WAIT_COND_TIME) --- - true ... @@ -451,7 +451,7 @@ sc:sysconnect('127.0.0.1', s:name().port) or errno() == errno.EINPROGRESS --- - true ... -sc:writable(10) +sc:writable(WAIT_COND_TIME) --- - true ... @@ -485,7 +485,7 @@ sa:read(3) --- - orl ... -sc:writable() +sc:writable(WAIT_COND_TIME) --- - true ... @@ -513,7 +513,7 @@ sa:read(1, .01) --- - null ... -sc:writable() +sc:writable(WAIT_COND_TIME) --- - true ... @@ -546,7 +546,7 @@ sc:send('Hello') --- - 5 ... -sa:readable() +sa:readable(WAIT_COND_TIME) --- - true ... @@ -646,7 +646,7 @@ sc ~= nil --- - true ... -s:listen(1234) +s:listen(WAIT_COND_TIME) --- - true ... @@ -665,7 +665,7 @@ sc:error() --- - null ... -s:readable() +s:readable(WAIT_COND_TIME) --- - true ... @@ -754,7 +754,7 @@ string.match(tostring(sc), ', peer') == nil --- - true ... -sc:writable() +sc:writable(WAIT_COND_TIME) --- - true ... @@ -830,7 +830,7 @@ sc:sendto('127.0.0.1', s:name().port, 'Hello, world') --- - 12 ... -s:readable(10) +s:readable(WAIT_COND_TIME) --- - true ... @@ -842,7 +842,7 @@ sc:sendto('127.0.0.1', s:name().port, 'Hello, world, 2') --- - 15 ... -s:readable(10) +s:readable(WAIT_COND_TIME) --- - true ... @@ -898,7 +898,7 @@ sc:sendto('127.0.0.1', s:name().port, 'Hello, World!') --- - 13 ... -s:readable(1) +s:readable(WAIT_COND_TIME) --- - true ... @@ -913,7 +913,7 @@ s:sendto(from.host, from.port, 'Hello, hello!') --- - 13 ... -sc:readable(1) +sc:readable(WAIT_COND_TIME) --- - true ... @@ -957,7 +957,7 @@ s:bind('127.0.0.1', 0) port = s:name().port --- ... -s:listen() +s:listen(WAIT_COND_TIME) --- - true ... @@ -1004,7 +1004,7 @@ socket.tcp_connect('unix/', path), errno() == errno.ECONNREFUSED - null - true ... -s:listen() +s:listen(WAIT_COND_TIME) --- - true ... @@ -1074,6 +1074,9 @@ master:setsockopt('SOL_SOCKET', 'SO_REUSEADDR', true) --- - true ... +math.randomseed(fiber.time()) +--- +... port = 32768 + math.random(0, 32767) --- ... @@ -1086,7 +1089,7 @@ test_run:cmd("setopt delimiter ';'") ... test_run:wait_cond(function() local ok = master:bind('127.0.0.1', port) - local ok = ok and master:listen() + local ok = ok and master:listen(WAIT_COND_TIME) if not ok then port = 32768 + math.random(32768) return false, master:error() @@ -1099,7 +1102,7 @@ end, WAIT_COND_TIME); function gh361() local s = socket('PF_INET', 'SOCK_STREAM', 'tcp') s:sysconnect('127.0.0.1', port) - s:wait() + s:wait(WAIT_COND_TIME) res = s:read(1200) end; --- @@ -1146,7 +1149,7 @@ s:error() --- - null ... -s:listen(128) +s:listen(WAIT_COND_TIME) --- - true ... @@ -1156,7 +1159,7 @@ test_run:cmd("setopt delimiter ';'") ... f = fiber.create(function() for i=1,2 do - s:readable() + s:readable(WAIT_COND_TIME) local sc = s:accept() sc:write('ok!') sc:shutdown() @@ -1532,7 +1535,7 @@ s:bind('unix/', path) --- - true ... -s:listen() +s:listen(WAIT_COND_TIME) --- - true ... @@ -1759,7 +1762,7 @@ s:bind('127.0.0.1', 0) -- error handling - null - Invalid argument ... -s:listen(10) +s:listen(WAIT_COND_TIME) --- - 1 ... @@ -1822,8 +1825,14 @@ test_run:cmd("setopt delimiter ';'") --- - true ... +socket_opened = true cfiber = fiber.create(function(sc, rch, wch) - while sc:send(wch:get()) and rch:put(sc:receive("*l")) do end + while socket_opened do + sc:send(wch:get()) + local data = sc:receive("*l") + if not socket_opened then sc:close() end + rch:put(data) + end end, sc, rch, wch); --- ... @@ -1936,6 +1945,9 @@ c:receive("*l") --- - ... +socket_opened = false +--- +... wch:put("Fu") --- - true @@ -1944,10 +1956,6 @@ c:send("354 Please type your message\n") --- - 29 ... -sc:close() ---- -- 1 -... c:receive("*l", "Line: ") --- - null @@ -2231,7 +2239,17 @@ test_run:cmd("setopt delimiter ''"); receiving_socket = socket('AF_INET', 'SOCK_DGRAM', 'udp') --- ... -receiving_socket:bind('127.0.0.1', 0) +test_run:cmd("setopt delimiter ';'") +--- +- true +... +test_run:wait_cond(function() + return receiving_socket:bind('127.0.0.1', 32768 + math.random(0, 32767)) +end, WAIT_COND_TIME); +--- +- true +... +test_run:cmd("setopt delimiter ''"); --- - true ... @@ -2625,11 +2643,21 @@ listening_socket:setsockopt('SOL_SOCKET', 'SO_REUSEADDR', true) --- - true ... -listening_socket:bind('127.0.0.1', 0) +test_run:cmd("setopt delimiter ';'") +--- +- true +... +test_run:wait_cond(function() + return listening_socket:bind('127.0.0.1', 32768 + math.random(0, 32767)) +end, WAIT_COND_TIME); --- - true ... -listening_socket:listen() +test_run:cmd("setopt delimiter ''"); +--- +- true +... +listening_socket:listen(WAIT_COND_TIME) --- - true ... @@ -2643,8 +2671,18 @@ sending_socket:sysconnect('127.0.0.1', listening_socket_port) or errno() == errn --- - true ... -receiving_socket = listening_socket:accept() +test_run:cmd("setopt delimiter ';'") +--- +- true +... +receiving_socket = test_run:wait_cond(function() + return listening_socket:accept() +end, WAIT_COND_TIME); +--- +... +test_run:cmd("setopt delimiter ''"); --- +- true ... sending_socket:write(message) --- @@ -2816,7 +2854,7 @@ test_run:cmd("clear filter") --- - true ... --- case: sicket receive inconsistent behavior +-- case: socket receive inconsistent behavior chan = fiber.channel() --- ... @@ -2826,41 +2864,46 @@ counter = 0 fn = function(s) counter = 0; while true do s:write((tostring(counter)):rep(chan:get())); counter = counter + 1 end end --- ... -srv = socket.tcp_server('0.0.0.0', 8888, fn) +srv = nil --- ... -s = socket.connect('localhost', 8888) +test_run:cmd("setopt delimiter ';'") --- +- true ... -chan:put(5) +test_run:wait_cond(function() + port = 32768 + math.random(0, 32767) + srv = socket.tcp_server('0.0.0.0', port, fn) + return srv ~= nil +end, WAIT_COND_TIME); --- - true ... -chan:put(5) +receive1 = nil; receive2 = nil; --- -- true ... -s:receive(5) +if srv ~= nil then + s = socket.connect('localhost', port) + chan:put(5) + chan:put(5) + receive1 = s:receive(5) + chan:put(5) + s:settimeout(1) + receive2 = s:receive('*a') + s:close() + srv:close() +end; --- -- '00000' ... -chan:put(5) +test_run:cmd("setopt delimiter ''"); --- - true ... -s:settimeout(1) +receive1 --- -- 1 +- '00000' ... -s:receive('*a') +receive2 --- - '1111122222' ... -s:close() ---- -- 1 -... -srv:close() ---- -- true -... diff --git a/test/app/socket.test.lua b/test/app/socket.test.lua index c72d41763f4..97dac794e37 100644 --- a/test/app/socket.test.lua +++ b/test/app/socket.test.lua @@ -13,7 +13,7 @@ env = require('test_run') test_run = env.new() test_run:cmd("push filter '(error: .builtin/.*[.]lua):[0-9]+' to '\\1'") -WAIT_COND_TIME = 10 +WAIT_COND_TIME = 100 socket('PF_INET', 'SOCK_STREAM', 'tcp121222'); @@ -39,8 +39,8 @@ s:nonblock(false) s:nonblock() s:nonblock(true) -s:readable(.1) -s:wait(.1) +s:readable(WAIT_COND_TIME) +s:wait(WAIT_COND_TIME) socket.iowait(s:fd(), 'RW') socket.iowait(s:fd(), 3) socket.iowait(s:fd(), 'R') @@ -58,7 +58,7 @@ s:errno() > 0 s:error() s:writable(.00000000000001) s:writable(0) -s:wait(.01) +s:wait(WAIT_COND_TIME) socket.iowait(nil, nil, -1) socket.iowait(nil, nil, 0.0001) socket.iowait(-1, nil, 0.0001) @@ -75,8 +75,8 @@ ping = msgpack.encode(string.len(ping)) .. ping -- test syswrite with char * s:syswrite(ffi.cast('const char *', ping), #ping) -s:readable(1) -s:wait(.01) +s:readable(WAIT_COND_TIME) +s:wait(WAIT_COND_TIME) pong = s:sysread() string.len(pong) @@ -91,16 +91,16 @@ s:setsockopt('SOL_SOCKET', 'SO_REUSEADDR', true) s:error() s:bind('127.0.0.1', 0) s:error() -s:listen(128) +s:listen(WAIT_COND_TIME) sevres = {} -type(require('fiber').create(function() s:readable() do local sc = s:accept() table.insert(sevres, sc) sc:syswrite('ok') sc:close() end end)) +type(require('fiber').create(function() s:readable(WAIT_COND_TIME) do local sc = s:accept() table.insert(sevres, sc) sc:syswrite('ok') sc:close() end end)) #sevres sc = socket('PF_INET', 'SOCK_STREAM', 'tcp') sc:nonblock(false) sc:sysconnect('127.0.0.1', s:name().port) sc:nonblock(true) -sc:readable(.5) +sc:readable(WAIT_COND_TIME) sc:sysread() string.match(tostring(sc), ', peer') ~= nil #sevres @@ -135,12 +135,12 @@ s:close() s = socket('PF_INET', 'SOCK_STREAM', 'tcp') s:setsockopt('SOL_SOCKET', 'SO_REUSEADDR', true) s:bind('127.0.0.1', 0) -s:listen(128) +s:listen(WAIT_COND_TIME) sc = socket('PF_INET', 'SOCK_STREAM', 'tcp') sc:sysconnect('127.0.0.1', s:name().port) or errno() == errno.EINPROGRESS -sc:writable(10) +sc:writable(WAIT_COND_TIME) sc:write('Hello, world') sa, addr = s:accept() @@ -150,14 +150,14 @@ addr2.family == addr.family sa:nonblock(1) sa:read(8) sa:read(3) -sc:writable() +sc:writable(WAIT_COND_TIME) sc:write(', again') sa:read(8) sa:error() string.len(sa:read(0)) type(sa:read(0)) sa:read(1, .01) -sc:writable() +sc:writable(WAIT_COND_TIME) -- gh-3979 Check for errors when argument is negative. @@ -170,7 +170,7 @@ sc:send('abc') sa:read(3) sc:send('Hello') -sa:readable() +sa:readable(WAIT_COND_TIME) sa:recv() sa:recv() @@ -198,14 +198,14 @@ path = 'tarantool-test-socket' os.remove(path) s:bind('unix/', path) sc ~= nil -s:listen(1234) +s:listen(WAIT_COND_TIME) sc = socket('PF_UNIX', 'SOCK_STREAM', 0) sc:nonblock(true) sc:sysconnect('unix/', path) sc:error() -s:readable() +s:readable(WAIT_COND_TIME) sa = s:accept() sa:nonblock(true) sa:send('Hello, world') @@ -243,7 +243,7 @@ sc:getsockopt('SOL_SOCKET', 'SO_ERROR') sc:nonblock(true) sc:sysconnect('127.0.0.1', 3458) or errno() == errno.EINPROGRESS or errno() == errno.ECONNREFUSED string.match(tostring(sc), ', peer') == nil -sc:writable() +sc:writable(WAIT_COND_TIME) string.match(tostring(sc), ', peer') == nil socket_error = sc:getsockopt('SOL_SOCKET', 'SO_ERROR') socket_error == errno.ECONNREFUSED or socket_error == 0 @@ -266,11 +266,11 @@ s = socket('AF_INET', 'SOCK_DGRAM', 'udp') s:bind('127.0.0.1', 0) sc = socket('AF_INET', 'SOCK_DGRAM', 'udp') sc:sendto('127.0.0.1', s:name().port, 'Hello, world') -s:readable(10) +s:readable(WAIT_COND_TIME) s:recv() sc:sendto('127.0.0.1', s:name().port, 'Hello, world, 2') -s:readable(10) +s:readable(WAIT_COND_TIME) d, from = s:recvfrom() from.port > 0 from.port = 'Random port' @@ -286,11 +286,11 @@ sc = socket('AF_INET', 'SOCK_DGRAM', 'udp') sc:nonblock(true) sc:sendto('127.0.0.1', s:name().port) sc:sendto('127.0.0.1', s:name().port, 'Hello, World!') -s:readable(1) +s:readable(WAIT_COND_TIME) data, from = s:recvfrom(10) data s:sendto(from.host, from.port, 'Hello, hello!') -sc:readable(1) +sc:readable(WAIT_COND_TIME) data_r, from_r = sc:recvfrom() data_r from_r.host @@ -307,7 +307,7 @@ socket.tcp_connect('127.0.0.1', 80, 0.00000000001) s = socket('AF_INET', 'SOCK_STREAM', 'tcp') s:bind('127.0.0.1', 0) port = s:name().port -s:listen() +s:listen(WAIT_COND_TIME) sc, e = socket.tcp_connect('127.0.0.1', port), errno() sc ~= nil e == 0 @@ -321,7 +321,7 @@ _ = os.remove(path) s = socket('AF_UNIX', 'SOCK_STREAM', 0) s:bind('unix/', path) socket.tcp_connect('unix/', path), errno() == errno.ECONNREFUSED -s:listen() +s:listen(WAIT_COND_TIME) sc, e = socket.tcp_connect('unix/', path), errno() sc ~= nil e @@ -343,6 +343,7 @@ s = nil -- random port master = socket('PF_INET', 'SOCK_STREAM', 'tcp') master:setsockopt('SOL_SOCKET', 'SO_REUSEADDR', true) +math.randomseed(fiber.time()) port = 32768 + math.random(0, 32767) -- SO_REUSEADDR allows to bind to the same source addr:port twice, -- so listen() can return EADDRINUSE and so we check it within @@ -350,7 +351,7 @@ port = 32768 + math.random(0, 32767) test_run:cmd("setopt delimiter ';'") test_run:wait_cond(function() local ok = master:bind('127.0.0.1', port) - local ok = ok and master:listen() + local ok = ok and master:listen(WAIT_COND_TIME) if not ok then port = 32768 + math.random(32768) return false, master:error() @@ -360,7 +361,7 @@ end, WAIT_COND_TIME); function gh361() local s = socket('PF_INET', 'SOCK_STREAM', 'tcp') s:sysconnect('127.0.0.1', port) - s:wait() + s:wait(WAIT_COND_TIME) res = s:read(1200) end; test_run:cmd("setopt delimiter ''"); @@ -377,11 +378,11 @@ s:setsockopt('SOL_SOCKET', 'SO_REUSEADDR', true) s:error() s:bind('unix/', path) s:error() -s:listen(128) +s:listen(WAIT_COND_TIME) test_run:cmd("setopt delimiter ';'") f = fiber.create(function() for i=1,2 do - s:readable() + s:readable(WAIT_COND_TIME) local sc = s:accept() sc:write('ok!') sc:shutdown() @@ -506,7 +507,7 @@ _ = os.remove(path) -- Test that socket is closed on GC s = socket('AF_UNIX', 'SOCK_STREAM', 0) s:bind('unix/', path) -s:listen() +s:listen(WAIT_COND_TIME) s = nil while socket.tcp_connect('unix/', path) do collectgarbage('collect') end _ = os.remove(path) @@ -602,7 +603,7 @@ s:setoption('tcp-nodelay', true) s:setoption('unknown', true) s:bind('127.0.0.1', 0) s:bind('127.0.0.1', 0) -- error handling -s:listen(10) +s:listen(WAIT_COND_TIME) s -- transformed to tcp{server} socket host, port, family = s:getsockname() host == '127.0.0.1', type(port) == 'string', family == 'inet' @@ -619,8 +620,14 @@ s:settimeout(100500) rch, wch = fiber.channel(1), fiber.channel(1) sc = socket.connect(host, port) test_run:cmd("setopt delimiter ';'") +socket_opened = true cfiber = fiber.create(function(sc, rch, wch) - while sc:send(wch:get()) and rch:put(sc:receive("*l")) do end + while socket_opened do + sc:send(wch:get()) + local data = sc:receive("*l") + if not socket_opened then sc:close() end + rch:put(data) + end end, sc, rch, wch); test_run:cmd("setopt delimiter ''"); @@ -651,9 +658,9 @@ rch:get() wch:put("DATA\n") c:receive(4) c:receive("*l") +socket_opened = false wch:put("Fu") c:send("354 Please type your message\n") -sc:close() c:receive("*l", "Line: ") c:receive() c:receive(10) @@ -756,7 +763,11 @@ end; test_run:cmd("setopt delimiter ''"); receiving_socket = socket('AF_INET', 'SOCK_DGRAM', 'udp') -receiving_socket:bind('127.0.0.1', 0) +test_run:cmd("setopt delimiter ';'") +test_run:wait_cond(function() + return receiving_socket:bind('127.0.0.1', 32768 + math.random(0, 32767)) +end, WAIT_COND_TIME); +test_run:cmd("setopt delimiter ''"); receiving_socket_port = receiving_socket:name().port sending_socket = socket('AF_INET', 'SOCK_DGRAM', 'udp') @@ -891,12 +902,20 @@ message = string.rep('x', message_len) listening_socket = socket('AF_INET', 'SOCK_STREAM', 'tcp') listening_socket:setsockopt('SOL_SOCKET', 'SO_REUSEADDR', true) -listening_socket:bind('127.0.0.1', 0) -listening_socket:listen() +test_run:cmd("setopt delimiter ';'") +test_run:wait_cond(function() + return listening_socket:bind('127.0.0.1', 32768 + math.random(0, 32767)) +end, WAIT_COND_TIME); +test_run:cmd("setopt delimiter ''"); +listening_socket:listen(WAIT_COND_TIME) listening_socket_port = listening_socket:name().port sending_socket = socket('AF_INET', 'SOCK_STREAM', 'tcp') sending_socket:sysconnect('127.0.0.1', listening_socket_port) or errno() == errno.EINPROGRESS -receiving_socket = listening_socket:accept() +test_run:cmd("setopt delimiter ';'") +receiving_socket = test_run:wait_cond(function() + return listening_socket:accept() +end, WAIT_COND_TIME); +test_run:cmd("setopt delimiter ''"); sending_socket:write(message) -- case: recvfrom reads first 512 bytes from the message with tcp @@ -960,18 +979,30 @@ server:close() test_run:cmd("clear filter") --- case: sicket receive inconsistent behavior +-- case: socket receive inconsistent behavior chan = fiber.channel() counter = 0 fn = function(s) counter = 0; while true do s:write((tostring(counter)):rep(chan:get())); counter = counter + 1 end end -srv = socket.tcp_server('0.0.0.0', 8888, fn) -s = socket.connect('localhost', 8888) -chan:put(5) -chan:put(5) -s:receive(5) -chan:put(5) -s:settimeout(1) -s:receive('*a') -s:close() -srv:close() +srv = nil +test_run:cmd("setopt delimiter ';'") +test_run:wait_cond(function() + port = 32768 + math.random(0, 32767) + srv = socket.tcp_server('0.0.0.0', port, fn) + return srv ~= nil +end, WAIT_COND_TIME); +receive1 = nil; receive2 = nil; +if srv ~= nil then + s = socket.connect('localhost', port) + chan:put(5) + chan:put(5) + receive1 = s:receive(5) + chan:put(5) + s:settimeout(1) + receive2 = s:receive('*a') + s:close() + srv:close() +end; +test_run:cmd("setopt delimiter ''"); +receive1 +receive2 diff --git a/test/app/suite.ini b/test/app/suite.ini index 79432e29a76..dd802d98cf9 100644 --- a/test/app/suite.ini +++ b/test/app/suite.ini @@ -7,4 +7,3 @@ use_unix_sockets = True use_unix_sockets_iproto = True is_parallel = True pretest_clean = True -fragile = socket.test.lua ; gh-4426 gh-4451 -- 2.17.1