-----------------------------------------------------------------------------
-- LuaSocket automated test module
-- testclnt.lua
-- This is the client module. It connects with the server module and executes
-- all tests.
-----------------------------------------------------------------------------

-----------------------------------------------------------------------------
-- Read command definitions
-----------------------------------------------------------------------------
HOST = HOST or "localhost"
assert(dofile("testcmd.lua"))
test_debug_mode()

-----------------------------------------------------------------------------
-- Start control connection
-----------------------------------------------------------------------------
new_test("initializing...")
while control == nil do
	print("client: trying control connection...")
	control, err = connect(HOST, PORT)
	if control then
		print("client: control connection stablished!") 
	else
		sleep(2)
	end
end

-----------------------------------------------------------------------------
-- Make sure server is ready for data transmission
-----------------------------------------------------------------------------
function sync()
	send_command(SYNC)
	get_command()
end

-----------------------------------------------------------------------------
-- Close and reopen data connection, to get rid of any unread blocks
-----------------------------------------------------------------------------
function reconnect()
	if data then 
		data:close() 
		send_command(CLOSE)
		data = nil
	end
	while data == nil do
		send_command(CONNECT)
		data = connect(HOST, PORT)
		if not data then 
			print("client: waiting for data connection.") 
			sleep(1)
		end
	end
	sync()
end

-----------------------------------------------------------------------------
-- Tests the command connection
-----------------------------------------------------------------------------
function test_command(cmd, par)
	local cmd_back, par_back
	reconnect()
	send_command(COMMAND)
	write("testing command ")
	print_command(cmd, par)
	send_command(cmd, par)
	cmd_back, par_back = get_command()
	if cmd_back ~= cmd or par_back ~= par then
		fail(cmd)
	else
		pass()
	end
end

-----------------------------------------------------------------------------
-- Tests ASCII line transmission
-- Input
--   len: length of line to be tested
-----------------------------------------------------------------------------
function test_asciiline(len)
	local str, str10, back, err
	reconnect()
	send_command(ECHO_LINE)
	str = strrep("x", mod(len, 10))
	str10 = strrep("aZb.c#dAe?", floor(len/10))
	str = str .. str10
	write("testing ", len, " byte(s) line\n")
	err = data:send(str, "\n")
	if err then fail(err) end
	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

-----------------------------------------------------------------------------
-- Tests multiple pattern transmission
-- Input
--   len: length of line to be tested
-----------------------------------------------------------------------------
function test_multiple()
	local p1 = "unix line\n"
	local p2 = "dos line\r\n"
	local p3 = "raw bytes"
	local bp1, bp2, bp3
	reconnect()
	send_command(ECHO_BLOCK, strlen(p1)+strlen(p2)+strlen(p3))
	err = data:send(p1, p2, p3)
	if err then fail(err) end
	bp1, bp2, bp3, err = data:receive("*lu", "*l", strlen(p3))
	if err then fail(err) end
	if bp1.."\n" == p1 and bp2.."\r\n" == p2 and bp3 == p3 then 
		pass("patterns match")
	else fail("patterns don't match") end
end

-----------------------------------------------------------------------------
-- Tests closed connection detection
-----------------------------------------------------------------------------
function test_closed()
	local str = "This is our little test line"
	local len = strlen(str)
	local back, err, total
	reconnect()
	print("testing close while reading line")
	send_command(ECHO_BLOCK, len)
	data:send(str)
	send_command(CLOSE)
	-- try to get a line 
	back, err = data:receive()
	if not err then fail("shold have gotten 'closed'.")
	elseif err ~= "closed" then fail("got '"..err.."' instead of 'closed'.")
	elseif str ~= back then fail("didn't receive what i should 'closed'.")
	else pass("rightfull 'closed' received") end
	reconnect()
	print("testing close while reading block")
	send_command(ECHO_BLOCK, len)
	data:send(str)
	send_command(CLOSE)
	-- try to get a line 
	back, err = data:receive(2*len)
	if not err then fail("shold have gotten 'closed'.")
	elseif err ~= "closed" then fail("got '"..err.."' instead of 'closed'.")
	elseif str ~= back then fail("didn't receive what I should.")
	else pass("rightfull 'closed' received") end
end

-----------------------------------------------------------------------------
-- Tests binary line transmission
-- Input
--   len: length of line to be tested
-----------------------------------------------------------------------------
function test_rawline(len)
	local str, str10, back, err
	reconnect()
	send_command(ECHO_LINE)
	str = strrep(strchar(47), mod(len, 10))
	str10 = strrep(strchar(120,21,77,4,5,0,7,36,44,100), floor(len/10))
	str = str .. str10
	write("testing ", len, " byte(s) line\n")
	err = data:send(str, "\n")
	if err then fail(err) end
	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

-----------------------------------------------------------------------------
-- Tests block transmission
-- Input
--   len: length of block to be tested
-----------------------------------------------------------------------------
function test_block(len)
	local half = floor(len/2)
	local s1, s2, back, err
	reconnect()
	send_command(ECHO_BLOCK, len)
	write("testing ", len, " byte(s) block\n")
	s1 = strrep("x", half)
	err = data:send(s1)
	if err then fail(err) end
	s2 = strrep("y", len-half)
	err = data:send(s2)
	if err then fail(err) end
	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

-----------------------------------------------------------------------------
-- Tests if return-timeout was respected 
--   delta: time elapsed during transfer
--   t: timeout value
--   s: time server slept
--   err: error code returned by I/O operation
--   o: operation being executed
-----------------------------------------------------------------------------
function blockedtimed_out(t, s, err, o)
	if err == "timeout" then
		if s >= t then
			pass("got rightfull forced timeout")
			return 1
		else
			pass("got natural cause timeout")
			return 1
		end
	elseif s > t then
		if o == "send" then
			pass("must have been buffered (may be wrong)")
		else
			fail("should have gotten timeout")
		end
	end
end

-----------------------------------------------------------------------------
-- Tests blocked-timeout conformance
-- Input
--   len: length of block to be tested
--   t: timeout value
--   s: server sleep between transfers
-----------------------------------------------------------------------------
function test_blockedtimeout(len, t, s)
	local str, err, back, total
	reconnect()
	send_command(RECEIVE_BLOCK, len)
	send_command(SLEEP, s)
	send_command(RECEIVE_BLOCK, len)
	write("testing ", len, " bytes, ", t, 
		"s block timeout, ", s, "s sleep\n")
	data:timeout(t)
	str = strrep("a", 2*len)
	err, total = data:send(str)
	if blockedtimed_out(t, s, err, "send") then return end
	if err then fail(err) end
	send_command(SEND_BLOCK)
	send_command(SLEEP, s)
	send_command(SEND_BLOCK)
	back, err = data:receive(2*len)
	if blockedtimed_out(t, s, err, "receive") then return end
	if err then fail(err) end
	if back == str then pass("blocks match")
	else fail("blocks don't match") end
end

-----------------------------------------------------------------------------
-- Tests if return-timeout was respected 
--   delta: time elapsed during transfer
--   t: timeout value
--   err: error code returned by I/O operation
-----------------------------------------------------------------------------
function returntimed_out(delta, t, err)
	if err == "timeout" then
		if delta >= t then
			pass("got rightfull timeout")
			return 1
		else
			fail("shouldn't have gotten timeout")
		end
	elseif delta > t then
		pass(format("but took %fs longer than should have", delta - t))
	end
end

-----------------------------------------------------------------------------
-- Tests return-timeout conformance
-- Input
--   len: length of block to be tested
--   t: timeout value
--   s: server sleep between transfers
-----------------------------------------------------------------------------
function test_returntimeout(len, t, s)
	local str, err, back, delta, total
	reconnect()
	send_command(RECEIVE_BLOCK, len)
	send_command(SLEEP, s)
	send_command(RECEIVE_BLOCK, len)
	write("testing ", len, " bytes, ", t, 
		"s return timeout, ", s, "s sleep\n")
	data:timeout(t, "return")
	str = strrep("a", 2*len)
	err, total, delta = data:send(str)
	print("sent in " .. delta .. "s")
	if returntimed_out(delta, t, err) then return end
	if err then fail("unexpected error: " .. err) end
	send_command(SEND_BLOCK)
	send_command(SLEEP, s)
	send_command(SEND_BLOCK)
	back, err, delta = data:receive(2*len)
	print("received in " .. delta .. "s")
	if returntimed_out(delta, t, err) then return end
	if err then fail("unexpected error: " .. err) end
	if back == str then pass("blocks match")
	else fail("blocks don't match") end
end




-----------------------------------------------------------------------------
-- Tests read patterns
-----------------------------------------------------------------------------
function test_word()
    local b1 = "  \t one two    three  \n this_is_a_very"
    local b2 = "_big_word "
    send_command(ECHO_BLOCK, strlen(b1)+strlen(b2))
    err = data:send(b1, b2)
    local a1, a2, a3, a4
    a1, a2, a3, a4, err = data:receive("*w", "*w", "*w", "*w")
	if err then fail(err) end
    _, err = data:receive(1) -- get last space
	if err then fail(err) end
    if a1 ~= "one" or a2 ~= "two" or a3 ~= "three" or
       a4 ~= "this_is_a_very_big_word" then fail("'*w' failed") end
    pass("'*w' is ok")
end

function test_patterns()
	local dos_line1 = "this the first dos line"
	local dos_line2 = "this is another dos line"
	local unix_line1 = "this the first unix line"
	local unix_line2 = "this is another unix line"
	local block = dos_line1 .. "\r\n" ..  dos_line2 .. "\r\n"
	reconnect()
    block = block .. unix_line1 .. "\n" .. unix_line2 .. "\n"
	block = block .. block
	send_command(ECHO_BLOCK, strlen(block))
	err = data:send(block)
	if err then fail(err) end
	local back = data:receive("*l")
	if back ~= dos_line1 then fail("'*l' failed") end
	back = data:receive("*l")
	if back ~= dos_line2 then fail("'*l' failed") end
	back = data:receive("*lu")
	if back ~= unix_line1 then fail("'*lu' failed") end
	back = data:receive("*lu")
	if back ~= unix_line2 then fail("'*lu' failed") end
	back = data:receive()
	if back ~= dos_line1 then fail("default failed") end
	back = data:receive()
	if back ~= dos_line2 then fail("default failed") end
	back = data:receive("*lu")
	if back ~= unix_line1 then fail("'*lu' failed") end
	back = data:receive("*lu")
	if back ~= unix_line2 then fail("'*lu' failed") end
	pass("line patterns are ok")
	send_command(ECHO_BLOCK, strlen(block))
	err = data:send(block)
	if err then fail(err) end
	back = data:receive(strlen(block))
	if back ~= block then fail("number failed") end
	pass("number is ok")
	test_word()
	send_command(ECHO_BLOCK, strlen(block))
	send_command(SLEEP, 1)
	send_command(CLOSE)
	err = data:send(block)
	if err then fail(err) end
	back = data:receive("*a")
	if back ~= block then fail("'*a' failed") end
	pass("'*a' is ok")
end

-----------------------------------------------------------------------------
-- Execute all tests
-----------------------------------------------------------------------------
start = time()

new_test("control connection test")
test_command(EXIT)
test_command(CONNECT)
test_command(CLOSE)
test_command(ECHO_BLOCK, 12234)
test_command(SLEEP, 1111)
test_command(ECHO_LINE)

--a = [[
new_test("connection close test")
test_closed()

new_test("read pattern test")
test_patterns()

new_test("multiple pattern test")
test_multiple()

new_test("character string test")
test_asciiline(1)
test_asciiline(17)
test_asciiline(200)
test_asciiline(3000)
test_asciiline(80000)
test_asciiline(800000)

new_test("binary string test")
test_rawline(1)
test_rawline(17)
test_rawline(200)
test_rawline(3000)
test_rawline(8000)
test_rawline(80000)
test_rawline(800000)

new_test("blocking transfer test")
test_block(1)
test_block(17)
test_block(200)
test_block(3000)
test_block(80000)
test_block(800000)

new_test("non-blocking transfer test")
-- the value is not important, we only want 
-- to test non-blockin I/O anyways
data:timeout(200)
test_block(1)
test_block(17)
test_block(200)
test_block(3000)
test_block(80000)
test_block(800000)

new_test("blocked timeout test")
test_blockedtimeout(80, 1, 2)
test_blockedtimeout(80, 2, 2)
test_blockedtimeout(80, 3, 2)
test_blockedtimeout(800, 1, 0)
test_blockedtimeout(8000, 2, 3)
test_blockedtimeout(80000, 2, 1)
test_blockedtimeout(800000, 0.01, 0)

new_test("return timeout test")
test_returntimeout(80, 2, 1)
test_returntimeout(80, 1, 2)
test_returntimeout(8000, 1, 2)
test_returntimeout(80000, 2, 1)
test_returntimeout(800000, 0.1, 0)
test_returntimeout(800000, 2, 1)
--]]

-----------------------------------------------------------------------------
-- Close connection and exit server. We are done.
-----------------------------------------------------------------------------
print("client: closing connection with server")
send_command(CLOSE)
send_command(EXIT)
control:close()

new_test("the library has passed all tests")
print(format("time elapsed: %6.2fs", time() - start))
print("client: exiting...")
exit()