From 254764e27edfd87a3c2943768abf767fd378aa3f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20B=C3=A4r?= Date: Tue, 20 Aug 2024 11:28:47 +0200 Subject: [PATCH] #91: Fix integration tests for Exasol v8 --- doc/changes/changes_0.2.1.md | 3 +- spec/integration/Issue91_regression_spec.lua | 38 ++++++++++++++++++++ src/luasql/exasol/ExasolWebsocket.lua | 18 +++++----- src/luasql/exasol/luws.lua | 15 ++++---- 4 files changed, 58 insertions(+), 16 deletions(-) create mode 100644 spec/integration/Issue91_regression_spec.lua diff --git a/doc/changes/changes_0.2.1.md b/doc/changes/changes_0.2.1.md index 863fb3e..c9e3c96 100644 --- a/doc/changes/changes_0.2.1.md +++ b/doc/changes/changes_0.2.1.md @@ -4,7 +4,7 @@ Code name: Upgrade to Exasol 8 ## Summary -This release updates integration tests to additionally use Exasol version 8.23.0. +This release updates integration tests to additionally use Exasol version 8.27.0. When running integration tests with `luarocks test` or `busted`, the environment variable `EXASOL_HOST` now has a default value `localhost` to make running tests more convenient. If you run local tests, the Exasol DB is most likely set up to forward the default database port to the localhost anyway. @@ -17,3 +17,4 @@ Integration test now succeeds with TLS 1.3 in addition to TLS 1.2 (version 8.18. * #56: Sped up repeated CI tests * #78: Added tests with Exasol 8 +* #91: Fixed failing tests with Exasol 8 diff --git a/spec/integration/Issue91_regression_spec.lua b/spec/integration/Issue91_regression_spec.lua new file mode 100644 index 0000000..3ba3567 --- /dev/null +++ b/spec/integration/Issue91_regression_spec.lua @@ -0,0 +1,38 @@ +-- This is a regression test for exasol/exosol-driver-lua#91 +-- https://github.com/exasol/exasol-driver-lua/issues/91 +-- +-- At the switch from Exasol 7.1 to 8 the test of the drivers broke (first observed in a test +-- against Exasol 8.23.0. Under certain conditions the tests trigger a message timeout after +-- 5 seconds. +-- +-- The test below is the minimal implementation that triggers the issue. + +require("busted.runner")() +local driver = require("luasql.exasol") +local config = require("config") + +describe("Issue91", function() + local connection_params = config.get_connection_params() + local env = driver.exasol() + + local function create_connection() + return assert(env:connect(connection_params.source_name, connection_params.user, connection_params.password)) + end + + it("must not raise message timeout error", function() + local connectionA = create_connection() + -- If you don't use a cursor here and then immediately close the connection, the problem can + -- be triggered with the next new connection. + connectionA:close() + local connectionB = create_connection() + local cursor = assert(connectionB:execute("SELECT 1")) -- ← here is where the error is raised. + finally(function() + if(not connectionB.closed) then + print("Cleaning up remaining connection after failure.") + connectionB:close() + end + end) + cursor:close() + connectionB:close() + end) +end) diff --git a/src/luasql/exasol/ExasolWebsocket.lua b/src/luasql/exasol/ExasolWebsocket.lua index aa9cc1c..05bf774 100644 --- a/src/luasql/exasol/ExasolWebsocket.lua +++ b/src/luasql/exasol/ExasolWebsocket.lua @@ -37,7 +37,7 @@ end -- This returns a public RSA key used for encrypting the password before sending it with the -- @{luasql.exasol.ExasolWebsocket:send_login_credentials} method. -- @treturn table|nil the public RSA key or `nil` if an error occurred --- @treturn table|nil `nil` if the operation was successful, otherwise the error that occured +-- @treturn table|nil `nil` if the operation was successful, otherwise the error that occurred function ExasolWebsocket:send_login_command() log.debug("Sending login command") return self:_send_json({command = "login", protocolVersion = 3}) @@ -50,7 +50,7 @@ end -- @tparam string encrypted_password the password encrypted with the public key returned by -- @{luasql.exasol.ExasolWebsocket:send_login_command} -- @treturn table|nil response data from the database or `nil` if an error occurred --- @treturn table|nil `nil` if the operation was successful, otherwise the error that occured +-- @treturn table|nil `nil` if the operation was successful, otherwise the error that occurred function ExasolWebsocket:send_login_credentials(username, encrypted_password) log.debug("Sending login credentials") return self:_send_json({username = username, password = encrypted_password, useCompression = false}) @@ -59,7 +59,7 @@ end --- Sends the disconnect command. -- See Exasol API documentation for the -- [`disconnect` command](https://github.com/exasol/websocket-api/blob/master/docs/commands/disconnectV1.md). --- @treturn table|nil `nil` if the operation was successful, otherwise the error that occured +-- @treturn table|nil `nil` if the operation was successful, otherwise the error that occurred function ExasolWebsocket:send_disconnect() log.debug("Sending disconnect command") local _, err = self:_send_json({command = "disconnect"}, true) @@ -71,7 +71,7 @@ end -- [`execute` command](https://github.com/exasol/websocket-api/blob/master/docs/commands/executeV1.md). -- @tparam string statement the SQL statement to execute -- @treturn table|nil the result set response data from the database or `nil` if an error occurred --- @treturn table|nil `nil` if the operation was successful, otherwise the error that occured +-- @treturn table|nil `nil` if the operation was successful, otherwise the error that occurred function ExasolWebsocket:send_execute(statement) local payload = {command = "execute", sqlText = statement, attributes = {}} return self:_send_json(payload) @@ -84,7 +84,7 @@ end -- [`setAttributes` command](https://github.com/exasol/websocket-api/blob/master/docs/commands/setAttributesV1.md). -- @tparam string attribute_name the name of the attribute to set, e.g. `"autocommit"` -- @tparam string|number|boolean|nil attribute_value the value of the attribute to set, e.g. `false` --- @treturn table|nil `nil` if the operation was successful, otherwise the error that occured +-- @treturn table|nil `nil` if the operation was successful, otherwise the error that occurred function ExasolWebsocket:send_set_attribute(attribute_name, attribute_value) if attribute_value == nil or attribute_value == constants.NULL then attribute_value = cjson.null @@ -100,7 +100,7 @@ end -- @tparam number start_position row offset (0-based) from which to begin data retrieval -- @tparam number num_bytes number of bytes to retrieve (max: 64MiB) -- @treturn table|nil result set from the database or `nil` if an error occurred --- @treturn table|nil `nil` if the operation was successful, otherwise the error that occured +-- @treturn table|nil `nil` if the operation was successful, otherwise the error that occurred function ExasolWebsocket:send_fetch(result_set_handle, start_position, num_bytes) local payload = { command = "fetch", @@ -116,7 +116,7 @@ end -- See Exasol API documentation for the -- [`closeResultSet` command](https://github.com/exasol/websocket-api/blob/master/docs/commands/closeResultSetV1.md). -- @tparam number result_set_handle result set handle to close --- @treturn table|nil `nil` if the operation was successful, otherwise the error that occured +-- @treturn table|nil `nil` if the operation was successful, otherwise the error that occurred function ExasolWebsocket:send_close_result_set(result_set_handle) local payload = {command = "closeResultSet", resultSetHandles = {result_set_handle}, attributes = {}} local _, err = self:_send_json(payload) @@ -125,7 +125,7 @@ end --- Extract the error from the given database response. -- @tparam table response the response from the database --- @treturn nil|table `nil` the error that occured or `nil` if the response was successful +-- @treturn nil|table `nil` the error that occurred or `nil` if the response was successful local function get_response_error(response) if response.status == "ok" then return nil @@ -147,7 +147,7 @@ end -- @tparam boolean|nil ignore_response `false` if we expect a response, else `true`. -- Default is `false`. -- @treturn table|nil the received response or nil if ignore_response was `true` or an error occurred --- @treturn table|nil `nil` if the operation was successful, otherwise the error that occured +-- @treturn table|nil `nil` if the operation was successful, otherwise the error that occurred function ExasolWebsocket:_send_json(payload, ignore_response) local raw_payload = cjson.encode(payload) if self.closed then diff --git a/src/luasql/exasol/luws.lua b/src/luasql/exasol/luws.lua index 873cc68..848074b 100644 --- a/src/luasql/exasol/luws.lua +++ b/src/luasql/exasol/luws.lua @@ -32,6 +32,9 @@ local STATE_READMASK = "mask" local MAXMESSAGE = 65535 -- maximum WS message size local CHUNKSIZE = 2048 local DEFAULTMSGTIMEOUT = 0 -- drop connection if no message in this time (0=no timeout) +local WS_UPGRADE_REQUEST_TIMEOUT = 5 +local WS_UPGRADE_RESPONSE_TIMEOUT = 5 +local WS_SEND_FRAME_TIMEOUT = 15 local timenow = socket.gettime or os.time -- use hi-res time if available local unpack = unpack or table.unpack -- luacheck: ignore 143 @@ -127,16 +130,16 @@ local function wsupgrade( wsconn ) -- Send request. D("wsupgrade() sending %1", req) - wsconn.socket:settimeout( 5, "b" ) - wsconn.socket:settimeout( 5, "r" ) + wsconn.socket:settimeout( WS_UPGRADE_REQUEST_TIMEOUT, "b" ) + wsconn.socket:settimeout( WS_UPGRADE_REQUEST_TIMEOUT, "r" ) local nb,err = wsconn.socket:send( req ) if nb == nil then return false, "Failed to send upgrade request: "..tostring(err) end -- Read until we get two consecutive linefeeds. - wsconn.socket:settimeout( 5, "b" ) - wsconn.socket:settimeout( 5, "r" ) + wsconn.socket:settimeout( WS_UPGRADE_RESPONSE_TIMEOUT, "b" ) + wsconn.socket:settimeout( WS_UPGRADE_RESPONSE_TIMEOUT, "r" ) local buf = {} local ntotal = 0 while true do @@ -305,8 +308,8 @@ local function send_frame( wsconn, opcode, fin, s ) end t = nil -- luacheck: ignore 311 D("send_frame() sending frame of %1 bytes for %2", #frame, s) - wsconn.socket:settimeout( 5, "b" ) - wsconn.socket:settimeout( 5, "r" ) + wsconn.socket:settimeout( WS_SEND_FRAME_TIMEOUT, "b" ) + wsconn.socket:settimeout( WS_SEND_FRAME_TIMEOUT, "r" ) -- ??? need retry while nb < payload length while #frame > 0 do local nb,err = wsconn.socket:send( frame )