From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from smtp41.i.mail.ru (smtp41.i.mail.ru [94.100.177.101]) (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 C7F414696C9 for ; Mon, 6 Apr 2020 17:17:21 +0300 (MSK) From: Nikita Pettik Date: Mon, 6 Apr 2020 17:17:15 +0300 Message-Id: <4cbdad521f50e6a1df0cc4d90dd086259ac0c0e5.1586181413.git.korablev@tarantool.org> In-Reply-To: References: In-Reply-To: References: Subject: [Tarantool-patches] [PATCH v3 6/6] iproto: support error stacked diagnostic area 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 This patch introduces support of stacked errors in IProto protocol and in net.box module. Closes #1148 @TarantoolBot document Title: Stacked error diagnostic area Starting from now errors can be organized into lists. To achieve this Lua table representing error object is extended with .prev field and e:set_prev(err) method. .prev field returns previous error if any exist. e:set_prev(err) method expects err to be error object or nil and sets err as previous error of e. For instance: ``` e1 = box.error.new({code = 111, reason = "cause"}) e2 = box.error.new({code = 111, reason = "cause of cause"}) e1:set_prev(e2) assert(e1.prev == e2) -- true ``` Cycles are not allowed for error lists: ``` e2:set_prev(e1) - error: 'builtin/error.lua: Cycles are not allowed' ``` Nil is valid input to :set_prev() method: ``` e1:set_prev(nil) assert(e1.prev == nil) -- true ``` Note that error can be 'previous' only to the one error at once: ``` e1:set_prev(e2) e3:set_prev(e2) assert(e1.prev == nil) -- true assert(e3.prev == e2) -- true ``` Setting previous error does not erase its own previous members: ``` -- e1 -> e2 -> e3 -> e4 e1:set_prev(e2) e2:set_prev(e3) e3:set_prev(e4) e2:set_prev(e5) -- Now there are two lists: e1->e2->e5 and e3->e4 assert(e1.prev == e2) -- true assert(e2.prev == e5) -- true assert(e3.prev == e4) -- true ``` Alternatively: ``` e1:set_prev(e2) e2:set_prev(e3) e3:set_prev(e4) e5:set_prev(e3) -- Now there are two lists: e1->e2 and e5->e3->e4 assert(e1.prev == e2) -- true assert(e2.prev == nil) -- true assert(e5.prev == e3) -- true assert(e3.prev == e4) -- true `` Stacked diagnostics is also supported by IProto protocol. Now responses containing errors always (even if there's only one error to be returned) include new IProto key: IPROTO_ERROR_STACK (0x51). So, body corresponding to error response now looks like: ``` MAP{IPROTO_ERROR : string, IPROTO_ERROR_STACK : ARRAY[MAP{ERROR_CODE : uint, ERROR_MESSAGE : string}, MAP{...}, ...]} ``` where IPROTO_ERROR is 0x31 key, IPROTO_ERROR_STACK is 0x52, ERROR_CODE is 0x01 and ERROR_MESSAGE is 0x02. Instances of older versions (without support of stacked errors in protocol) simply ignore unknown keys and still rely only on IPROTO_ERROR key. --- src/box/error.cc | 21 +++++ src/box/error.h | 16 ++++ src/box/iproto_constants.h | 6 ++ src/box/lua/net_box.lua | 30 +++++- src/box/xrow.c | 80 ++++++++++++++-- test/box-py/iproto.result | 6 +- test/box-py/iproto.test.py | 6 +- test/box/iproto.result | 142 ++++++++++++++++++++++++++++ test/box/iproto.test.lua | 66 +++++++++++++ test/box/net.box.result | 71 ++++++++++++++ test/box/net.box.test.lua | 31 +++++++ test/unit/xrow.cc | 184 ++++++++++++++++++++++++++++++++++++- test/unit/xrow.result | 27 +++++- 13 files changed, 668 insertions(+), 18 deletions(-) create mode 100644 test/box/iproto.result create mode 100644 test/box/iproto.test.lua diff --git a/src/box/error.cc b/src/box/error.cc index 8e77c2e9e..233b312a2 100644 --- a/src/box/error.cc +++ b/src/box/error.cc @@ -102,6 +102,27 @@ box_error_new(const char *file, unsigned line, uint32_t code, return e; } +int +box_error_add(const char *file, unsigned line, uint32_t code, + const char *fmt, ...) +{ + struct error *e = BuildClientError(file, line, ER_UNKNOWN); + ClientError *client_error = type_cast(ClientError, e); + if (client_error) { + client_error->m_errcode = code; + va_list ap; + va_start(ap, fmt); + error_vformat_msg(e, fmt, ap); + va_end(ap); + } + struct diag *d = &fiber()->diag; + if (diag_is_empty(d)) + diag_set_error(d, e); + else + diag_add_error(d, e); + return -1; +} + /* }}} */ struct rmean *rmean_error = NULL; diff --git a/src/box/error.h b/src/box/error.h index ef3ccf104..ca5d5b2ae 100644 --- a/src/box/error.h +++ b/src/box/error.h @@ -137,6 +137,22 @@ box_error_set(const char *file, unsigned line, uint32_t code, /** \endcond public */ +/** + * Add error to the diagnostic area. In contrast to box_error_set() + * it does not replace previous error being set, but rather link + * them into list. + * + * \param code IPROTO error code (enum \link box_error_code \endlink) + * \param format (const char * ) - printf()-like format string + * \param ... - format arguments + * \returns -1 for convention use + * + * \sa enum box_error_code + */ +int +box_error_add(const char *file, unsigned line, uint32_t code, + const char *fmt, ...); + /** * Construct error object without setting it in the diagnostics * area. On the memory allocation fail returns NULL. diff --git a/src/box/iproto_constants.h b/src/box/iproto_constants.h index f9d413a31..7ed829645 100644 --- a/src/box/iproto_constants.h +++ b/src/box/iproto_constants.h @@ -126,6 +126,7 @@ enum iproto_key { /* Leave a gap between SQL keys and additional request keys */ IPROTO_REPLICA_ANON = 0x50, IPROTO_ID_FILTER = 0x51, + IPROTO_ERROR_STACK = 0x52, IPROTO_KEY_MAX }; @@ -150,6 +151,11 @@ enum iproto_ballot_key { IPROTO_BALLOT_IS_LOADING = 0x04, }; +enum iproto_error_key { + IPROTO_ERROR_CODE = 0x01, + IPROTO_ERROR_MESSAGE = 0x02, +}; + #define bit(c) (1ULL<errmsg); + + uint32_t err_cnt = 0; + for (const struct error *it = error; it != NULL; it = it->cause) + err_cnt++; + mpstream_encode_uint(stream, IPROTO_ERROR_STACK); + mpstream_encode_array(stream, err_cnt); + for (const struct error *it = error; it != NULL; it = it->cause) { + mpstream_encode_map(stream, 2); + mpstream_encode_uint(stream, IPROTO_ERROR_CODE); + mpstream_encode_uint(stream, box_error_code(it)); + mpstream_encode_uint(stream, IPROTO_ERROR_MESSAGE); + mpstream_encode_str(stream, it->errmsg); + } } int @@ -1070,6 +1083,51 @@ xrow_encode_auth(struct xrow_header *packet, const char *salt, size_t salt_len, return 0; } +static int +iproto_decode_error_stack(const char **pos) +{ + const char *reason = NULL; + static_assert(TT_STATIC_BUF_LEN >= DIAG_ERRMSG_MAX, "static buffer is "\ + "expected to be larger than error message max length"); + /* + * Erase previously set diag errors. It is required since + * box_error_add() does not replace previous errors. + */ + box_error_clear(); + if (mp_typeof(**pos) != MP_ARRAY) + return -1; + uint32_t stack_sz = mp_decode_array(pos); + for (uint32_t i = 0; i < stack_sz; i++) { + uint64_t code = 0; + if (mp_typeof(**pos) != MP_MAP) + return -1; + uint32_t map_sz = mp_decode_map(pos); + for (uint32_t key_idx = 0; key_idx < map_sz; key_idx++) { + if (mp_typeof(**pos) != MP_UINT) + return -1; + uint64_t key = mp_decode_uint(pos); + if (key == IPROTO_ERROR_CODE) { + if (mp_typeof(**pos) != MP_UINT) + return -1; + code = mp_decode_uint(pos); + if (code > UINT32_MAX) + return -1; + } else if (key == IPROTO_ERROR_MESSAGE) { + if (mp_typeof(**pos) != MP_STR) + return -1; + uint32_t len; + const char *str = mp_decode_str(pos, &len); + reason = tt_cstr(str, len); + } else { + mp_next(pos); + continue; + } + } + box_error_add(__FILE__, __LINE__, code, reason); + } + return 0; +} + void xrow_decode_error(struct xrow_header *row) { @@ -1096,15 +1154,25 @@ xrow_decode_error(struct xrow_header *row) continue; } uint8_t key = mp_decode_uint(&pos); - if (key != IPROTO_ERROR || mp_typeof(*pos) != MP_STR) { - mp_next(&pos); /* value */ + if (key == IPROTO_ERROR && mp_typeof(*pos) == MP_STR) { + /* + * Obsolete way of sending error responses. + * To be deprecated but still should be supported + * to not break backward compatibility. + */ + uint32_t len; + const char *str = mp_decode_str(&pos, &len); + snprintf(error, sizeof(error), "%.*s", len, str); + box_error_set(__FILE__, __LINE__, code, error); + } else if (key == IPROTO_ERROR_STACK) { + if (iproto_decode_error_stack(&pos) != 0) + goto error; + } else { + mp_next(&pos); continue; } - - uint32_t len; - const char *str = mp_decode_str(&pos, &len); - snprintf(error, sizeof(error), "%.*s", len, str); } + return; error: box_error_set(__FILE__, __LINE__, code, error); diff --git a/test/box-py/iproto.result b/test/box-py/iproto.result index 900e6e24f..04e2e220c 100644 --- a/test/box-py/iproto.result +++ b/test/box-py/iproto.result @@ -169,9 +169,9 @@ box.schema.user.revoke('guest', 'read,write,execute', 'universe') # Test bugs gh-272, gh-1654 if the packet was incorrect, respond with # an error code and do not close connection -sync=0, {49: 'Invalid MsgPack - packet header'} -sync=1234, {49: "Missing mandatory field 'space id' in request"} -sync=5678, {49: "Read access to space '_user' is denied for user 'guest'"} +sync=0, Invalid MsgPack - packet header +sync=1234, Missing mandatory field 'space id' in request +sync=5678, Read access to space '_user' is denied for user 'guest' space = box.schema.space.create('test_index_base', { id = 568 }) --- ... diff --git a/test/box-py/iproto.test.py b/test/box-py/iproto.test.py index 77637d8ed..cdd1a71c5 100644 --- a/test/box-py/iproto.test.py +++ b/test/box-py/iproto.test.py @@ -355,15 +355,15 @@ s = c._socket header = { "hello": "world"} body = { "bug": 272 } resp = test_request(header, body) -print 'sync=%d, %s' % (resp['header'][IPROTO_SYNC], resp['body']) +print 'sync=%d, %s' % (resp['header'][IPROTO_SYNC], resp['body'].get(IPROTO_ERROR)) header = { IPROTO_CODE : REQUEST_TYPE_SELECT } header[IPROTO_SYNC] = 1234 resp = test_request(header, body) -print 'sync=%d, %s' % (resp['header'][IPROTO_SYNC], resp['body']) +print 'sync=%d, %s' % (resp['header'][IPROTO_SYNC], resp['body'].get(IPROTO_ERROR)) header[IPROTO_SYNC] = 5678 body = { IPROTO_SPACE_ID: 304, IPROTO_KEY: [], IPROTO_LIMIT: 1 } resp = test_request(header, body) -print 'sync=%d, %s' % (resp['header'][IPROTO_SYNC], resp['body']) +print 'sync=%d, %s' % (resp['header'][IPROTO_SYNC], resp['body'].get(IPROTO_ERROR)) c.close() diff --git a/test/box/iproto.result b/test/box/iproto.result new file mode 100644 index 000000000..b6dc7ed4c --- /dev/null +++ b/test/box/iproto.result @@ -0,0 +1,142 @@ +net_box = require('net.box') +--- +... +urilib = require('uri') +--- +... +msgpack = require('msgpack') +--- +... +test_run = require('test_run').new() +--- +... +IPROTO_REQUEST_TYPE = 0x00 +--- +... +IPROTO_SYNC = 0x01 +--- +... +IPROTO_CALL = 0x0A +--- +... +IPROTO_FUNCTION_NAME = 0x22 +--- +... +IPROTO_TUPLE = 0x21 +--- +... +IPROTO_ERROR = 0x31 +--- +... +IPROTO_ERROR_STACK = 0x52 +--- +... +IPROTO_ERROR_CODE = 0x01 +--- +... +IPROTO_ERROR_MESSAGE = 0x02 +--- +... +-- gh-1148: test capabilities of stacked diagnostics bypassing net.box. +-- +test_run:cmd("setopt delimiter ';'") +--- +- true +... +stack_err = function() + local e1 = box.error.new({code = 111, reason = "e1"}) + local e2 = box.error.new({code = 111, reason = "e2"}) + local e3 = box.error.new({code = 111, reason = "e3"}) + assert(e1 ~= nil) + e2:set_prev(e1) + assert(e2.prev == e1) + e3:set_prev(e2) + box.error(e3) +end; +--- +... +test_run:cmd("setopt delimiter ''"); +--- +- true +... +box.schema.user.grant('guest', 'read, write, execute', 'universe') +--- +... +next_request_id = 16 +--- +... +header = { \ + [IPROTO_REQUEST_TYPE] = IPROTO_CALL, \ + [IPROTO_SYNC] = next_request_id, \ +} +--- +... +body = { \ + [IPROTO_FUNCTION_NAME] = 'stack_err', \ + [IPROTO_TUPLE] = box.tuple.new({nil}) \ +} +--- +... +uri = urilib.parse(box.cfg.listen) +--- +... +sock = net_box.establish_connection(uri.host, uri.service) +--- +... +response = iproto_request(sock, header, body) +--- +... +sock:close() +--- +- true +... +-- Both keys (obsolete and stack ones) are present in response. +-- +assert(response.body[IPROTO_ERROR_STACK] ~= nil) +--- +- true +... +assert(response.body[IPROTO_ERROR] ~= nil) +--- +- true +... +err = response.body[IPROTO_ERROR_STACK][1] +--- +... +assert(err[IPROTO_ERROR_MESSAGE] == response.body[IPROTO_ERROR]) +--- +- true +... +assert(err[IPROTO_ERROR_MESSAGE] == 'e3') +--- +- true +... +assert(err[IPROTO_ERROR_CODE] == 111) +--- +- true +... +err = response.body[IPROTO_ERROR_STACK][2] +--- +... +assert(err[IPROTO_ERROR_MESSAGE] == 'e2') +--- +- true +... +assert(err[IPROTO_ERROR_CODE] == 111) +--- +- true +... +err = response.body[IPROTO_ERROR_STACK][3] +--- +... +assert(err[IPROTO_ERROR_MESSAGE] == 'e1') +--- +- true +... +assert(err[IPROTO_ERROR_CODE] == 111) +--- +- true +... +box.schema.user.revoke('guest', 'read,write,execute', 'universe') +--- +... diff --git a/test/box/iproto.test.lua b/test/box/iproto.test.lua new file mode 100644 index 000000000..6402a22ba --- /dev/null +++ b/test/box/iproto.test.lua @@ -0,0 +1,66 @@ +net_box = require('net.box') +urilib = require('uri') +msgpack = require('msgpack') +test_run = require('test_run').new() + +IPROTO_REQUEST_TYPE = 0x00 + +IPROTO_SYNC = 0x01 +IPROTO_CALL = 0x0A +IPROTO_FUNCTION_NAME = 0x22 +IPROTO_TUPLE = 0x21 +IPROTO_ERROR = 0x31 +IPROTO_ERROR_STACK = 0x52 +IPROTO_ERROR_CODE = 0x01 +IPROTO_ERROR_MESSAGE = 0x02 + +-- gh-1148: test capabilities of stacked diagnostics bypassing net.box. +-- +test_run:cmd("setopt delimiter ';'") +stack_err = function() + local e1 = box.error.new({code = 111, reason = "e1"}) + local e2 = box.error.new({code = 111, reason = "e2"}) + local e3 = box.error.new({code = 111, reason = "e3"}) + assert(e1 ~= nil) + e2:set_prev(e1) + assert(e2.prev == e1) + e3:set_prev(e2) + box.error(e3) +end; +test_run:cmd("setopt delimiter ''"); +box.schema.user.grant('guest', 'read, write, execute', 'universe') + +next_request_id = 16 +header = { \ + [IPROTO_REQUEST_TYPE] = IPROTO_CALL, \ + [IPROTO_SYNC] = next_request_id, \ +} + +body = { \ + [IPROTO_FUNCTION_NAME] = 'stack_err', \ + [IPROTO_TUPLE] = box.tuple.new({nil}) \ +} + +uri = urilib.parse(box.cfg.listen) +sock = net_box.establish_connection(uri.host, uri.service) + +response = iproto_request(sock, header, body) +sock:close() + +-- Both keys (obsolete and stack ones) are present in response. +-- +assert(response.body[IPROTO_ERROR_STACK] ~= nil) +assert(response.body[IPROTO_ERROR] ~= nil) + +err = response.body[IPROTO_ERROR_STACK][1] +assert(err[IPROTO_ERROR_MESSAGE] == response.body[IPROTO_ERROR]) +assert(err[IPROTO_ERROR_MESSAGE] == 'e3') +assert(err[IPROTO_ERROR_CODE] == 111) +err = response.body[IPROTO_ERROR_STACK][2] +assert(err[IPROTO_ERROR_MESSAGE] == 'e2') +assert(err[IPROTO_ERROR_CODE] == 111) +err = response.body[IPROTO_ERROR_STACK][3] +assert(err[IPROTO_ERROR_MESSAGE] == 'e1') +assert(err[IPROTO_ERROR_CODE] == 111) + +box.schema.user.revoke('guest', 'read,write,execute', 'universe') diff --git a/test/box/net.box.result b/test/box/net.box.result index e3dabf7d9..6475707ef 100644 --- a/test/box/net.box.result +++ b/test/box/net.box.result @@ -3902,6 +3902,77 @@ sock:close() --- - true ... +-- gh-1148: test stacked diagnostics. +-- +test_run:cmd("setopt delimiter ';'") +--- +- true +... +stack_err = function() + local e1 = box.error.new({code = 111, reason = "e1"}) + local e2 = box.error.new({code = 111, reason = "e2"}) + local e3 = box.error.new({code = 111, reason = "e3"}) + assert(e1 ~= nil) + e2:set_prev(e1) + assert(e2.prev == e1) + e3:set_prev(e2) + box.error(e3) +end; +--- +... +test_run:cmd("setopt delimiter ''"); +--- +- true +... +box.schema.user.grant('guest', 'read,write,execute', 'universe') +--- +... +c = net.connect(box.cfg.listen) +--- +... +f = function(...) return c:call(...) end +--- +... +r, e3 = pcall(f, 'stack_err') +--- +... +assert(r == false) +--- +- true +... +e3 +--- +- e3 +... +e2 = e3.prev +--- +... +assert(e2 ~= nil) +--- +- true +... +e2 +--- +- e2 +... +e1 = e2.prev +--- +... +assert(e1 ~= nil) +--- +- true +... +e1 +--- +- e1 +... +assert(e1.prev == nil) +--- +- true +... +box.schema.user.revoke('guest', 'read,write,execute', 'universe') +--- +... test_run:wait_log('default', 'Got a corrupted row.*', nil, 10) --- - 'Got a corrupted row:' diff --git a/test/box/net.box.test.lua b/test/box/net.box.test.lua index 8e65ff470..72a4207ee 100644 --- a/test/box/net.box.test.lua +++ b/test/box/net.box.test.lua @@ -1574,6 +1574,37 @@ data = string.fromhex('3C'..string.rep(require('digest').sha1_hex('bcde'), 3)) sock:write(data) sock:close() +-- gh-1148: test stacked diagnostics. +-- +test_run:cmd("setopt delimiter ';'") +stack_err = function() + local e1 = box.error.new({code = 111, reason = "e1"}) + local e2 = box.error.new({code = 111, reason = "e2"}) + local e3 = box.error.new({code = 111, reason = "e3"}) + assert(e1 ~= nil) + e2:set_prev(e1) + assert(e2.prev == e1) + e3:set_prev(e2) + box.error(e3) +end; +test_run:cmd("setopt delimiter ''"); + +box.schema.user.grant('guest', 'read,write,execute', 'universe') +c = net.connect(box.cfg.listen) +f = function(...) return c:call(...) end +r, e3 = pcall(f, 'stack_err') +assert(r == false) +e3 +e2 = e3.prev +assert(e2 ~= nil) +e2 +e1 = e2.prev +assert(e1 ~= nil) +e1 +assert(e1.prev == nil) + +box.schema.user.revoke('guest', 'read,write,execute', 'universe') + test_run:wait_log('default', 'Got a corrupted row.*', nil, 10) test_run:wait_log('default', '00000000:.*', nil, 10) test_run:wait_log('default', '00000010:.*', nil, 10) diff --git a/test/unit/xrow.cc b/test/unit/xrow.cc index 68a334239..dd1a85dc2 100644 --- a/test/unit/xrow.cc +++ b/test/unit/xrow.cc @@ -32,6 +32,7 @@ extern "C" { #include "unit.h" } /* extern "C" */ #include "trivia/util.h" +#include "box/error.h" #include "box/xrow.h" #include "box/iproto_constants.h" #include "uuid/tt_uuid.h" @@ -241,6 +242,186 @@ test_xrow_header_encode_decode() check_plan(); } +static char * +error_stack_entry_encode(char *pos, const char *err_str) +{ + pos = mp_encode_map(pos, 2); + pos = mp_encode_uint(pos, IPROTO_ERROR_CODE); + pos = mp_encode_uint(pos, 159); + pos = mp_encode_uint(pos, IPROTO_ERROR_MESSAGE); + pos = mp_encode_str(pos, err_str, strlen(err_str)); + return pos; +} + +void +test_xrow_error_stack_decode() +{ + plan(21); + char buffer[2048]; + /* + * To start with, let's test the simplest and obsolete + * way of setting errors. + */ + char *pos = mp_encode_map(buffer, 1); + pos = mp_encode_uint(pos, IPROTO_ERROR); + pos = mp_encode_str(pos, "e1", strlen("e1")); + + struct xrow_header header; + header.bodycnt = 666; + header.type = 159 | IPROTO_TYPE_ERROR; + header.body[0].iov_base = buffer; + header.body[0].iov_len = pos - buffer; + + xrow_decode_error(&header); + struct error *last = diag_last_error(diag_get()); + isnt(last, NULL, "xrow_decode succeed: diag has been set"); + is(strcmp(last->errmsg, "e1"), 0, + "xrow_decode succeed: error is parsed"); + + pos = mp_encode_map(buffer, 1); + pos = mp_encode_uint(pos, IPROTO_ERROR_STACK); + pos = mp_encode_array(pos, 2); + pos = error_stack_entry_encode(pos, "e1"); + pos = error_stack_entry_encode(pos, "e2"); + header.body[0].iov_base = buffer; + header.body[0].iov_len = pos - buffer; + + diag_clear(diag_get()); + xrow_decode_error(&header); + last = diag_last_error(diag_get()); + isnt(last, NULL, "xrow_decode succeed: diag has been set"); + is(strcmp(last->errmsg, "e2"), 0, "xrow_decode error stack suceed: " + "e2 at the top of stack"); + last = last->cause; + isnt(last, NULL, "xrow_decode succeed: 'cause' is present in stack") + is(strcmp(last->errmsg, "e1"), 0, "xrow_decode succeed: " + "stack has been parsed"); + last = last->cause; + is(last, NULL, "xrow_decode succeed: only two errors in the stack") + /* + * Let's try decode broken stack. Variations: + * 1. Stack is not encoded as an array; + * 2. Stack doesn't contain maps; + * 3. Stack's map keys are not uints; + * 4. Stack's map values have wrong types; + * 5. Stack's map key is broken (doesn't fit into u8); + * 6. Stack's map contains overflowed (> 2^32) error code; + * In all these cases diag_last should contain empty err. + */ + /* Stack is encoded as map. */ + pos = mp_encode_map(buffer, 1); + pos = mp_encode_uint(pos, IPROTO_ERROR_STACK); + pos = mp_encode_map(pos, 1); + pos = error_stack_entry_encode(pos, "e1"); + header.body[0].iov_base = buffer; + header.body[0].iov_len = pos - buffer; + + diag_clear(diag_get()); + xrow_decode_error(&header); + last = diag_last_error(diag_get()); + isnt(last, NULL, "xrow_decode succeed: diag has been set"); + is(strcmp(last->errmsg, ""), 0, "xrow_decode corrupted stack: " + "stack contains map instead of array"); + + /* Stack doesn't containt map(s) - array instead. */ + pos = mp_encode_map(buffer, 1); + pos = mp_encode_uint(pos, IPROTO_ERROR_STACK); + pos = mp_encode_array(pos, 2); + pos = mp_encode_uint(pos, IPROTO_ERROR_CODE); + pos = mp_encode_uint(pos, IPROTO_ERROR_MESSAGE); + header.body[0].iov_base = buffer; + header.body[0].iov_len = pos - buffer; + + diag_clear(diag_get()); + xrow_decode_error(&header); + last = diag_last_error(diag_get()); + isnt(last, NULL, "xrow_decode succeed: diag has been set"); + is(strcmp(last->errmsg, ""), 0, "xrow_decode corrupted stack: " + "stack contains array values instead of maps"); + + /* Stack's map keys are not uints. */ + pos = mp_encode_map(buffer, 1); + pos = mp_encode_uint(pos, IPROTO_ERROR_STACK); + pos = mp_encode_array(pos, 1); + pos = mp_encode_map(pos, 2); + pos = mp_encode_str(pos, "string instead of uint", + strlen("string instead of uint")); + pos = mp_encode_uint(pos, IPROTO_ERROR_MESSAGE); + pos = mp_encode_uint(pos, IPROTO_ERROR_CODE); + pos = mp_encode_uint(pos, 159); + header.body[0].iov_base = buffer; + header.body[0].iov_len = pos - buffer; + + diag_clear(diag_get()); + xrow_decode_error(&header); + last = diag_last_error(diag_get()); + isnt(last, NULL, "xrow_decode succeed: diag has been set"); + is(strcmp(last->errmsg, ""), 0, "xrow_decode corrupted stack: " + "stack's map keys are not uints"); + + /* Stack's map values have wrong types. */ + pos = mp_encode_map(buffer, 1); + pos = mp_encode_uint(pos, IPROTO_ERROR_STACK); + pos = mp_encode_array(pos, 1); + pos = mp_encode_map(pos, 2); + pos = mp_encode_uint(pos, IPROTO_ERROR_CODE); + pos = mp_encode_uint(pos, 159); + pos = mp_encode_uint(pos, IPROTO_ERROR_MESSAGE); + pos = mp_encode_uint(pos, 666); + header.body[0].iov_base = buffer; + header.body[0].iov_len = pos - buffer; + + diag_clear(diag_get()); + xrow_decode_error(&header); + last = diag_last_error(diag_get()); + isnt(last, NULL, "xrow_decode succeed: diag has been set"); + is(strcmp(last->errmsg, ""), 0, "xrow_decode corrupted stack: " + "stack's map wrong value type"); + + /* Bad key in the packet. */ + pos = mp_encode_map(buffer, 1); + pos = mp_encode_uint(pos, IPROTO_ERROR_STACK); + pos = mp_encode_array(pos, 1); + pos = mp_encode_map(pos, 2); + pos = mp_encode_uint(pos, 0xff00000000 | IPROTO_ERROR_CODE); + pos = mp_encode_uint(pos, 159); + pos = mp_encode_uint(pos, IPROTO_ERROR_MESSAGE); + pos = mp_encode_str(pos, "test msg", strlen("test msg")); + header.body[0].iov_base = buffer; + header.body[0].iov_len = pos - buffer; + + diag_clear(diag_get()); + xrow_decode_error(&header); + last = diag_last_error(diag_get()); + isnt(last, NULL, "xrow_decode succeed: diag has been set"); + is(box_error_code(last), 0, "xrow_decode last error code is default 0"); + is(strcmp(last->errmsg, "test msg"), 0, "xrow_decode corrupted stack: " + "stack's map wrong key"); + + /* Overflow error code. */ + pos = mp_encode_map(buffer, 1); + pos = mp_encode_uint(pos, IPROTO_ERROR_STACK); + pos = mp_encode_array(pos, 1); + pos = mp_encode_map(pos, 2); + pos = mp_encode_uint(pos, IPROTO_ERROR_CODE); + pos = mp_encode_uint(pos, (uint64_t)1 << 40); + pos = mp_encode_uint(pos, IPROTO_ERROR_MESSAGE); + pos = mp_encode_str(pos, "test msg", strlen("test msg")); + header.body[0].iov_base = buffer; + header.body[0].iov_len = pos - buffer; + + diag_clear(diag_get()); + xrow_decode_error(&header); + last = diag_last_error(diag_get()); + isnt(last, NULL, "xrow_decode succeed: diag has been set"); + is(box_error_code(last), 159, "xrow_decode failed, took code from " + "header"); + is(strcmp(last->errmsg, ""), 0, "xrow_decode failed, message is not " + "decoded"); + + check_plan(); +} + void test_request_str() { @@ -279,13 +460,14 @@ main(void) { memory_init(); fiber_init(fiber_c_invoke); - plan(3); + plan(4); random_init(); test_iproto_constants(); test_greeting(); test_xrow_header_encode_decode(); + test_xrow_error_stack_decode(); test_request_str(); random_free(); diff --git a/test/unit/xrow.result b/test/unit/xrow.result index 5ee92ad7b..c78109ae0 100644 --- a/test/unit/xrow.result +++ b/test/unit/xrow.result @@ -1,4 +1,4 @@ -1..3 +1..4 1..40 ok 1 - round trip ok 2 - roundtrip.version_id @@ -53,6 +53,29 @@ ok 1 - subtests ok 9 - decoded sync ok 10 - decoded bodycnt ok 2 - subtests + 1..21 + ok 1 - xrow_decode succeed: diag has been set + ok 2 - xrow_decode succeed: error is parsed + ok 3 - xrow_decode succeed: diag has been set + ok 4 - xrow_decode error stack suceed: e2 at the top of stack + ok 5 - xrow_decode succeed: 'cause' is present in stack + ok 6 - xrow_decode succeed: stack has been parsed + ok 7 - xrow_decode succeed: only two errors in the stack + ok 8 - xrow_decode succeed: diag has been set + ok 9 - xrow_decode corrupted stack: stack contains map instead of array + ok 10 - xrow_decode succeed: diag has been set + ok 11 - xrow_decode corrupted stack: stack contains array values instead of maps + ok 12 - xrow_decode succeed: diag has been set + ok 13 - xrow_decode corrupted stack: stack's map keys are not uints + ok 14 - xrow_decode succeed: diag has been set + ok 15 - xrow_decode corrupted stack: stack's map wrong value type + ok 16 - xrow_decode succeed: diag has been set + ok 17 - xrow_decode last error code is default 0 + ok 18 - xrow_decode corrupted stack: stack's map wrong key + ok 19 - xrow_decode succeed: diag has been set + ok 20 - xrow_decode failed, took code from header + ok 21 - xrow_decode failed, message is not decoded +ok 3 - subtests 1..1 ok 1 - request_str -ok 3 - subtests +ok 4 - subtests -- 2.17.1