From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from smtp49.i.mail.ru (smtp49.i.mail.ru [94.100.177.109]) (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 794B84696C9 for ; Mon, 20 Apr 2020 01:25:18 +0300 (MSK) From: Leonid Vasiliev Date: Mon, 20 Apr 2020 01:25:08 +0300 Message-Id: <88f364d989c23cd688770f6890b8d69da8928d21.1587334824.git.lvasiliev@tarantool.org> In-Reply-To: References: In-Reply-To: References: Subject: [Tarantool-patches] [PATCH V6 06/10] error: add error MsgPack encoding List-Id: Tarantool development patches List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , To: v.shpilevoy@tarantool.org, alexander.turenko@tarantool.org Cc: tarantool-patches@dev.tarantool.org Co-authored-by: Vladislav Shpilevoy Closes #4398 @TarantoolBot document Title: Error objects encoding in MessagePack Until now an error sent over IProto, or serialized into MessagePack was turned into a string consisting of the error message. As a result, all other error object attributes were lost, including type of the object. On client side seeing a string it was not possible to tell whether the string is a real string, or it is a serialized error. To deal with that the error objects encoding is reworked from the scratch. Now, when session setting `error_marshaling_enabled` is true, all fibers of that session will encode error objects as a new MP_EXT type - MP_ERROR (0x03). ``` +--------+----------+========+ | MP_EXT | MP_ERROR | MP_MAP | +--------+----------+========+ MP_ERROR: { MP_ERROR_STACK: [ { MP_ERROR_TYPE: , MP_ERROR_FILE: , MP_ERROR_LINE: , MP_ERROR_MESSAGE: , MP_ERROR_ERRNO: , MP_ERROR_CODE: , MP_ERROR_FIELDS: { : ..., : ..., ... }, ... }, ... ] } ``` On the top level there is a single key: `MP_ERROR_STACK = 0x00`. More keys can be added in future, and a client should ignore all unknown keys to keep compatibility with new versions. Every error in the stack is a map with the following keys: * `MP_ERROR_TYPE = 0x00` - error type. This is what is visible in `.base_type` field; * `MP_ERROR_FILE = 0x01` - file name from `.trace`; * `MP_ERROR_LINE = 0x02` - line from `.trace`; * `MP_ERROR_MESSAGE = 0x03` - error message from `.message`; * `MP_ERROR_ERRNO = 0x04` - errno saved at the moment of the error creation. Visible in `.errno`; * `MP_ERROR_CODE = 0x05` - error code. Visible in `.code` and in C function `box_error_code()`. * `MP_ERROR_FIELDS = 0x06` - additional fields depending on error type. For example, AccessDenied error type stores here fields `access_type`, `object_type`, `object_name`. Connector's code should ignore unknown keys met here, and be ready, that for some existing errors new fields can be added, old can be dropped. --- src/box/CMakeLists.txt | 6 +- src/box/lua/init.c | 36 +++ src/box/mp_error.cc | 533 +++++++++++++++++++++++++++++++++++ src/box/mp_error.h | 60 ++++ src/lib/core/mp_extension_types.h | 1 + src/lua/error.c | 2 +- src/lua/error.h | 1 + src/lua/msgpack.c | 2 + src/lua/utils.c | 8 +- test/box-tap/extended_error.test.lua | 124 ++++++++ test/unit/CMakeLists.txt | 2 + test/unit/mp_error.cc | 473 +++++++++++++++++++++++++++++++ test/unit/mp_error.result | 44 +++ 13 files changed, 1286 insertions(+), 6 deletions(-) create mode 100644 src/box/mp_error.cc create mode 100644 src/box/mp_error.h create mode 100755 test/box-tap/extended_error.test.lua create mode 100644 test/unit/mp_error.cc create mode 100644 test/unit/mp_error.result diff --git a/src/box/CMakeLists.txt b/src/box/CMakeLists.txt index 5ed7eae..6ca540d 100644 --- a/src/box/CMakeLists.txt +++ b/src/box/CMakeLists.txt @@ -34,15 +34,15 @@ set_property(DIRECTORY PROPERTY ADDITIONAL_MAKE_CLEAN_FILES ${lua_sources}) include_directories(${ZSTD_INCLUDE_DIRS}) include_directories(${CMAKE_BINARY_DIR}/src/box/sql) -add_library(box_error STATIC error.cc errcode.c vclock.c) -target_link_libraries(box_error core stat) +add_library(box_error STATIC error.cc errcode.c vclock.c mp_error.cc) +target_link_libraries(box_error core stat mpstream) add_library(vclock STATIC vclock.c) target_link_libraries(vclock core) add_library(xrow STATIC xrow.c iproto_constants.c) target_link_libraries(xrow server core small vclock misc box_error - scramble mpstream ${MSGPUCK_LIBRARIES}) + scramble ${MSGPUCK_LIBRARIES}) add_library(tuple STATIC tuple.c diff --git a/src/box/lua/init.c b/src/box/lua/init.c index 7899c16..cac62b9 100644 --- a/src/box/lua/init.c +++ b/src/box/lua/init.c @@ -42,6 +42,8 @@ #include "box/txn.h" #include "box/func.h" #include "box/vclock.h" +#include "box/session.h" +#include "box/mp_error.h" #include "box/lua/error.h" #include "box/lua/tuple.h" @@ -64,6 +66,8 @@ #include "box/lua/key_def.h" #include "box/lua/merger.h" +#include "mpstream/mpstream.h" + static uint32_t CTID_STRUCT_TXN_SAVEPOINT_PTR = 0; extern char session_lua[], @@ -408,9 +412,40 @@ luamp_encode_extension_box(struct lua_State *L, int idx, tuple_to_mpstream(tuple, stream); return MP_ARRAY; } + struct error *err = luaL_iserror(L, idx); + struct serializer_opts *opts = ¤t_session()->meta.serializer_opts; + if (err != NULL && opts->error_marshaling_enabled) + error_to_mpstream(err, stream); + return MP_EXT; } +/** + * A MsgPack extensions handler that supports errors decode. + */ +static void +luamp_decode_extension_box(struct lua_State *L, const char **data) +{ + assert(mp_typeof(**data) == MP_EXT); + int8_t ext_type; + uint32_t len = mp_decode_extl(data, &ext_type); + + if (ext_type != MP_ERROR) { + luaL_error(L, "Unsuported MsgPack extension type: %u", + ext_type); + return; + } + + struct error *err = error_unpack(data, len); + if (err == NULL) { + luaL_error(L, "Can not parse an error from MsgPack"); + return; + } + + luaT_pusherror(L, err); + return; +} + #include "say.h" void @@ -452,6 +487,7 @@ box_lua_init(struct lua_State *L) lua_pop(L, 1); luamp_set_encode_extension(luamp_encode_extension_box); + luamp_set_decode_extension(luamp_decode_extension_box); /* Load Lua extension */ for (const char **s = lua_sources; *s; s += 2) { diff --git a/src/box/mp_error.cc b/src/box/mp_error.cc new file mode 100644 index 0000000..e7d2808 --- /dev/null +++ b/src/box/mp_error.cc @@ -0,0 +1,533 @@ +/* + * Copyright 2010-2020, Tarantool AUTHORS, please see AUTHORS file. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * 1. Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL + * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ +#include "box/mp_error.h" +#include "box/error.h" +#include "mpstream/mpstream.h" +#include "msgpuck.h" +#include "mp_extension_types.h" +#include "fiber.h" + +/** + * MP_ERROR format: + * + * MP_ERROR: { + * MP_ERROR_STACK: [ + * { + * MP_ERROR_TYPE: , + * MP_ERROR_FILE: , + * MP_ERROR_LINE: , + * MP_ERROR_MESSAGE: , + * MP_ERROR_ERRNO: , + * MP_ERROR_CODE: , + * MP_ERROR_FIELDS: { + * : ..., + * : ..., + * ... + * }, + * ... + * }, + * ... + * ] + * } + */ + +/** + * MP_ERROR keys + */ +enum { + MP_ERROR_STACK = 0x00 +}; + +/** + * Keys of individual error in the stack. + */ +enum { + /** Error type. */ + MP_ERROR_TYPE = 0x00, + /** File name from trace. */ + MP_ERROR_FILE = 0x01, + /** Line from trace. */ + MP_ERROR_LINE = 0x02, + /** Error message. */ + MP_ERROR_MESSAGE = 0x03, + /** Errno at the moment of error creation. */ + MP_ERROR_ERRNO = 0x04, + /** Error code. */ + MP_ERROR_CODE = 0x05, + /* + * Type-specific fields stored as a map + * {string key = value}. + */ + MP_ERROR_FIELDS = 0x06 +}; + +/** + * The structure is used for storing parameters + * during decoding MP_ERROR. + */ +struct mp_error { + uint32_t code; + uint32_t line; + uint32_t saved_errno; + const char *type; + const char *file; + const char *message; + const char *custom_type; + const char *ad_object_type; + const char *ad_object_name; + const char *ad_access_type; +}; + +static void +mp_error_create(struct mp_error *mp_error) +{ + memset(mp_error, 0, sizeof(*mp_error)); +} + +static uint32_t +mp_sizeof_error(const struct error *error) +{ + uint32_t errcode = box_error_code(error); + + bool is_custom = false; + bool is_access_denied = false; + + if (strcmp(error->type->name, "CustomError") == 0) { + is_custom = true; + } else if (strcmp(error->type->name, "AccessDeniedError") == 0) { + is_access_denied = true; + } + + uint32_t details_num = 6; + uint32_t data_size = 0; + + data_size += mp_sizeof_uint(MP_ERROR_TYPE); + data_size += mp_sizeof_str(strlen(error->type->name)); + data_size += mp_sizeof_uint(MP_ERROR_LINE); + data_size += mp_sizeof_uint(error->line); + data_size += mp_sizeof_uint(MP_ERROR_FILE); + data_size += mp_sizeof_str(strlen(error->file)); + data_size += mp_sizeof_uint(MP_ERROR_MESSAGE); + data_size += mp_sizeof_str(strlen(error->errmsg)); + data_size += mp_sizeof_uint(MP_ERROR_ERRNO); + data_size += mp_sizeof_uint(error->saved_errno); + data_size += mp_sizeof_uint(MP_ERROR_CODE); + data_size += mp_sizeof_uint(errcode); + + if (is_access_denied) { + ++details_num; + data_size += mp_sizeof_uint(MP_ERROR_FIELDS); + data_size += mp_sizeof_map(3); + AccessDeniedError *ad_err = type_cast(AccessDeniedError, error); + data_size += mp_sizeof_str(strlen("object_type")); + data_size += mp_sizeof_str(strlen(ad_err->object_type())); + data_size += mp_sizeof_str(strlen("object_name")); + data_size += mp_sizeof_str(strlen(ad_err->object_name())); + data_size += mp_sizeof_str(strlen("access_type")); + data_size += mp_sizeof_str(strlen(ad_err->access_type())); + } else if (is_custom) { + ++details_num; + data_size += mp_sizeof_uint(MP_ERROR_FIELDS); + data_size += mp_sizeof_map(1); + data_size += mp_sizeof_str(strlen("custom_type")); + data_size += + mp_sizeof_str(strlen(box_error_custom_type(error))); + } + + data_size += mp_sizeof_map(details_num); + + return data_size; +} + +static inline char * +mp_encode_str0(char *data, const char *str) +{ + return mp_encode_str(data, str, strlen(str)); +} + +static void +mp_encode_error_one(const struct error *error, char **data) +{ + uint32_t errcode = box_error_code(error); + + bool is_custom = false; + bool is_access_denied = false; + + if (strcmp(error->type->name, "CustomError") == 0) { + is_custom = true; + } else if (strcmp(error->type->name, "AccessDeniedError") == 0) { + is_access_denied = true; + } + + uint32_t details_num = 6; + if (is_access_denied || is_custom) + ++details_num; + + *data = mp_encode_map(*data, details_num); + *data = mp_encode_uint(*data, MP_ERROR_TYPE); + *data = mp_encode_str0(*data, error->type->name); + *data = mp_encode_uint(*data, MP_ERROR_LINE); + *data = mp_encode_uint(*data, error->line); + *data = mp_encode_uint(*data, MP_ERROR_FILE); + *data = mp_encode_str0(*data, error->file); + *data = mp_encode_uint(*data, MP_ERROR_MESSAGE); + *data = mp_encode_str0(*data, error->errmsg); + *data = mp_encode_uint(*data, MP_ERROR_ERRNO); + *data = mp_encode_uint(*data, error->saved_errno); + *data = mp_encode_uint(*data, MP_ERROR_CODE); + *data = mp_encode_uint(*data, errcode); + + if (is_access_denied) { + *data = mp_encode_uint(*data, MP_ERROR_FIELDS); + *data = mp_encode_map(*data, 3); + AccessDeniedError *ad_err = type_cast(AccessDeniedError, error); + *data = mp_encode_str0(*data, "object_type"); + *data = mp_encode_str0(*data, ad_err->object_type()); + *data = mp_encode_str0(*data, "object_name"); + *data = mp_encode_str0(*data, ad_err->object_name()); + *data = mp_encode_str0(*data, "access_type"); + *data = mp_encode_str0(*data, ad_err->access_type()); + } else if (is_custom) { + *data = mp_encode_uint(*data, MP_ERROR_FIELDS); + *data = mp_encode_map(*data, 1); + *data = mp_encode_str0(*data, "custom_type"); + *data = mp_encode_str0(*data, box_error_custom_type(error)); + } +} + +static struct error * +error_build_xc(struct mp_error *mp_error) +{ + /* + * To create an error the "raw" constructor is used + * because OOM error must be thrown in OOM case. + * Builders returns a pointer to the static OOM error + * in OOM case. + */ + struct error *err = NULL; + if (mp_error->type == NULL || mp_error->message == NULL || + mp_error->file == NULL) { +missing_fields: + diag_set(ClientError, ER_INVALID_MSGPACK, + "Missing mandatory error fields"); + return NULL; + } + + if (strcmp(mp_error->type, "ClientError") == 0) { + ClientError *e = new ClientError(mp_error->file, mp_error->line, + ER_UNKNOWN); + e->m_errcode = mp_error->code; + err = e; + } else if (strcmp(mp_error->type, "CustomError") == 0) { + if (mp_error->custom_type == NULL) + goto missing_fields; + err = new CustomError(mp_error->file, mp_error->line, + mp_error->custom_type, mp_error->code); + } else if (strcmp(mp_error->type, "AccessDeniedError") == 0) { + if (mp_error->ad_access_type == NULL || + mp_error->ad_object_type == NULL || + mp_error->ad_object_name == NULL) + goto missing_fields; + err = new AccessDeniedError(mp_error->file, mp_error->line, + mp_error->ad_access_type, + mp_error->ad_object_type, + mp_error->ad_object_name, "", + false); + } else if (strcmp(mp_error->type, "XlogError") == 0) { + err = new XlogError(&type_XlogError, mp_error->file, + mp_error->line); + } else if (strcmp(mp_error->type, "XlogGapError") == 0) { + err = new XlogGapError(mp_error->file, mp_error->line, + mp_error->message); + } else if (strcmp(mp_error->type, "SystemError") == 0) { + err = new SystemError(mp_error->file, mp_error->line, + "%s", mp_error->message); + } else if (strcmp(mp_error->type, "SocketError") == 0) { + err = new SocketError(mp_error->file, mp_error->line, "", ""); + error_format_msg(err, "%s", mp_error->message); + } else if (strcmp(mp_error->type, "OutOfMemory") == 0) { + err = new OutOfMemory(mp_error->file, mp_error->line, + 0, "", ""); + } else if (strcmp(mp_error->type, "TimedOut") == 0) { + err = new TimedOut(mp_error->file, mp_error->line); + } else if (strcmp(mp_error->type, "ChannelIsClosed") == 0) { + err = new ChannelIsClosed(mp_error->file, mp_error->line); + } else if (strcmp(mp_error->type, "FiberIsCancelled") == 0) { + err = new FiberIsCancelled(mp_error->file, mp_error->line); + } else if (strcmp(mp_error->type, "LuajitError") == 0) { + err = new LuajitError(mp_error->file, mp_error->line, + mp_error->message); + } else if (strcmp(mp_error->type, "IllegalParams") == 0) { + err = new IllegalParams(mp_error->file, mp_error->line, + "%s", mp_error->message); + } else if (strcmp(mp_error->type, "CollationError") == 0) { + err = new CollationError(mp_error->file, mp_error->line, + "%s", mp_error->message); + } else if (strcmp(mp_error->type, "SwimError") == 0) { + err = new SwimError(mp_error->file, mp_error->line, + "%s", mp_error->message); + } else if (strcmp(mp_error->type, "CryptoError") == 0) { + err = new CryptoError(mp_error->file, mp_error->line, + "%s", mp_error->message); + } else { + err = new ClientError(mp_error->file, mp_error->line, + ER_UNKNOWN); + } + err->saved_errno = mp_error->saved_errno; + error_format_msg(err, "%s", mp_error->message); + return err; +} + +static inline const char * +region_strdup(struct region *region, const char *str, uint32_t len) +{ + char *res = (char *)region_alloc(region, len + 1); + if (res == NULL) { + diag_set(OutOfMemory, len + 1, "region_alloc", "res"); + return NULL; + } + memcpy(res, str, len); + res[len] = 0; + return res; +} + +static inline const char * +mp_decode_and_copy_str(const char **data, struct region *region) +{ + if (mp_typeof(**data) != MP_STR) { + diag_set(ClientError, ER_INVALID_MSGPACK, + "Invalid MP_ERROR MsgPack format"); + return NULL; + } + uint32_t str_len; + const char *str = mp_decode_str(data, &str_len); + return region_strdup(region, str, str_len);; +} + +static inline bool +str_nonterm_is_eq(const char *l, const char *r, uint32_t r_len) +{ + return r_len == strlen(l) && memcmp(l, r, r_len) == 0; +} + +static int +mp_decode_error_fields(const char **data, struct mp_error *mp_err, + struct region *region) +{ + if (mp_typeof(**data) != MP_MAP) + return -1; + uint32_t map_sz = mp_decode_map(data); + const char *key; + uint32_t key_len; + for (uint32_t i = 0; i < map_sz; ++i) { + if (mp_typeof(**data) != MP_STR) + return -1; + key = mp_decode_str(data, &key_len); + if (str_nonterm_is_eq("object_type", key, key_len)) { + mp_err->ad_object_type = + mp_decode_and_copy_str(data, region); + if (mp_err->ad_object_type == NULL) + return -1; + } else if (str_nonterm_is_eq("object_name", key, key_len)) { + mp_err->ad_object_name = + mp_decode_and_copy_str(data, region); + if (mp_err->ad_object_name == NULL) + return -1; + } else if (str_nonterm_is_eq("access_type", key, key_len)) { + mp_err->ad_access_type = + mp_decode_and_copy_str(data, region); + if (mp_err->ad_access_type == NULL) + return -1; + } else if (str_nonterm_is_eq("custom_type", key, key_len)) { + mp_err->custom_type = + mp_decode_and_copy_str(data, region); + if (mp_err->custom_type == NULL) + return -1; + } else { + mp_next(data); + } + } + return 0; +} + +static struct error * +mp_decode_error_one(const char **data) +{ + struct mp_error mp_err; + mp_error_create(&mp_err); + struct region *region = &fiber()->gc; + uint32_t region_svp = region_used(region); + struct error *err = NULL; + uint32_t map_size; + + if (mp_typeof(**data) != MP_MAP) + goto error; + + map_size = mp_decode_map(data); + for (uint32_t i = 0; i < map_size; ++i) { + if (mp_typeof(**data) != MP_UINT) + goto error; + + uint64_t key = mp_decode_uint(data); + switch(key) { + case MP_ERROR_TYPE: + mp_err.type = mp_decode_and_copy_str(data, region); + if (mp_err.type == NULL) + goto finish; + break; + case MP_ERROR_FILE: + mp_err.file = mp_decode_and_copy_str(data, region); + if (mp_err.file == NULL) + goto finish; + break; + case MP_ERROR_LINE: + if (mp_typeof(**data) != MP_UINT) + goto error; + mp_err.line = mp_decode_uint(data); + break; + case MP_ERROR_MESSAGE: + mp_err.message = mp_decode_and_copy_str(data, region); + if (mp_err.message == NULL) + goto finish; + break; + case MP_ERROR_ERRNO: + if (mp_typeof(**data) != MP_UINT) + goto error; + mp_err.saved_errno = mp_decode_uint(data); + break; + case MP_ERROR_CODE: + if (mp_typeof(**data) != MP_UINT) + goto error; + mp_err.code = mp_decode_uint(data); + break; + case MP_ERROR_FIELDS: + if (mp_decode_error_fields(data, &mp_err, region) != 0) + goto finish; + break; + default: + mp_next(data); + } + } + + try { + err = error_build_xc(&mp_err); + } catch (OutOfMemory *e) { + assert(err == NULL && !diag_is_empty(diag_get())); + } +finish: + region_truncate(region, region_svp); + return err; + +error: + diag_set(ClientError, ER_INVALID_MSGPACK, + "Invalid MP_ERROR MsgPack format"); + goto finish; +} + +void +error_to_mpstream(const struct error *error, struct mpstream *stream) +{ + uint32_t err_cnt = 0; + uint32_t data_size = mp_sizeof_map(1); + data_size += mp_sizeof_uint(MP_ERROR_STACK); + for (const struct error *it = error; it != NULL; it = it->cause) { + err_cnt++; + data_size += mp_sizeof_error(it); + } + + data_size += mp_sizeof_array(err_cnt); + uint32_t data_size_ext = mp_sizeof_ext(data_size); + char *ptr = mpstream_reserve(stream, data_size_ext); + char *data = ptr; + data = mp_encode_extl(data, MP_ERROR, data_size); + data = mp_encode_map(data, 1); + data = mp_encode_uint(data, MP_ERROR_STACK); + data = mp_encode_array(data, err_cnt); + for (const struct error *it = error; it != NULL; it = it->cause) { + mp_encode_error_one(it, &data); + } + + assert(data == ptr + data_size_ext); + mpstream_advance(stream, data_size_ext); +} + +struct error * +error_unpack(const char **data, uint32_t len) +{ + const char *end = *data + len; + struct error *err = NULL; + + if (mp_typeof(**data) != MP_MAP) { + diag_set(ClientError, ER_INVALID_MSGPACK, + "Invalid MP_ERROR MsgPack format"); + return NULL; + } + uint32_t map_size = mp_decode_map(data); + for (uint32_t i = 0; i < map_size; ++i) { + if (mp_typeof(**data) != MP_UINT) { + diag_set(ClientError, ER_INVALID_MSGPACK, + "Invalid MP_ERROR MsgPack format"); + return NULL; + } + uint64_t key = mp_decode_uint(data); + switch(key) { + case MP_ERROR_STACK: { + if (mp_typeof(**data) != MP_ARRAY) { + diag_set(ClientError, ER_INVALID_MSGPACK, + "Invalid MP_ERROR MsgPack format"); + return NULL; + } + uint32_t stack_sz = mp_decode_array(data); + struct error *effect = NULL; + for (uint32_t i = 0; i < stack_sz; i++) { + struct error *cur = mp_decode_error_one(data); + if (cur == NULL) + return NULL; + if (err == NULL) { + err = cur; + effect = cur; + continue; + } + + error_set_prev(effect, cur); + effect = cur; + } + break; + } + default: + mp_next(data); + } + } + + (void)end; + assert(*data == end); + return err; +} diff --git a/src/box/mp_error.h b/src/box/mp_error.h new file mode 100644 index 0000000..5c746d9 --- /dev/null +++ b/src/box/mp_error.h @@ -0,0 +1,60 @@ +#pragma once +/* + * Copyright 2010-2020, Tarantool AUTHORS, please see AUTHORS file. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * 1. Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL + * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#if defined(__cplusplus) +extern "C" { +#endif /* defined(__cplusplus) */ + +#include + +struct mpstream; + +/** + * @brief Encode error to mpstream as MP_ERROR. + * @param error pointer to struct error for encoding. + * @param stream pointer to output stream. + */ +void +error_to_mpstream(const struct error *error, struct mpstream *stream); + +/** + * @brief Unpack MP_ERROR to error. + * @param data pointer to MP_ERROR. + * @param len data size. + * @return struct error * or NULL if failed. + */ +struct error * +error_unpack(const char **data, uint32_t len); + +#if defined(__cplusplus) +} /* extern "C" */ +#endif /* defined(__cplusplus) */ diff --git a/src/lib/core/mp_extension_types.h b/src/lib/core/mp_extension_types.h index 7d42f21..e3ff9f5 100644 --- a/src/lib/core/mp_extension_types.h +++ b/src/lib/core/mp_extension_types.h @@ -43,6 +43,7 @@ enum mp_extension_type { MP_UNKNOWN_EXTENSION = 0, MP_DECIMAL = 1, MP_UUID = 2, + MP_ERROR = 3, mp_extension_type_MAX, }; diff --git a/src/lua/error.c b/src/lua/error.c index 18a990a..616aa7f 100644 --- a/src/lua/error.c +++ b/src/lua/error.c @@ -34,7 +34,7 @@ #include #include "utils.h" -static int CTID_CONST_STRUCT_ERROR_REF = 0; +uint32_t CTID_CONST_STRUCT_ERROR_REF = 0; struct error * luaL_iserror(struct lua_State *L, int narg) diff --git a/src/lua/error.h b/src/lua/error.h index 16cdaf7..54635bd 100644 --- a/src/lua/error.h +++ b/src/lua/error.h @@ -37,6 +37,7 @@ extern "C" { #endif /* defined(__cplusplus) */ +extern uint32_t CTID_CONST_STRUCT_ERROR_REF; /** \cond public */ struct error; diff --git a/src/lua/msgpack.c b/src/lua/msgpack.c index 5c1bf8e..d482c9b 100644 --- a/src/lua/msgpack.c +++ b/src/lua/msgpack.c @@ -195,6 +195,8 @@ restart: /* used by MP_EXT of unidentified subtype */ case MP_UUID: mpstream_encode_uuid(stream, field->uuidval); break; + case MP_ERROR: + return luamp_encode_extension(L, top, stream); default: /* Run trigger if type can't be encoded */ type = luamp_encode_extension(L, top, stream); diff --git a/src/lua/utils.c b/src/lua/utils.c index 42a226c..d410a3d 100644 --- a/src/lua/utils.c +++ b/src/lua/utils.c @@ -37,6 +37,8 @@ #include #include +#include "serializer_opts.h" + int luaL_nil_ref = LUA_REFNIL; int luaL_map_metatable_ref = LUA_REFNIL; int luaL_array_metatable_ref = LUA_REFNIL; @@ -650,8 +652,6 @@ luaL_tofield(struct lua_State *L, struct luaL_serializer *cfg, const struct serializer_opts *opts, int index, struct luaL_field *field) { - /* opts will be used for encode MP_ERROR in the future */ - (void)opts; if (index < 0) index = lua_gettop(L) + index + 1; @@ -759,6 +759,10 @@ luaL_tofield(struct lua_State *L, struct luaL_serializer *cfg, } else if (cd->ctypeid == CTID_UUID) { field->ext_type = MP_UUID; field->uuidval = (struct tt_uuid *) cdata; + } else if (cd->ctypeid == CTID_CONST_STRUCT_ERROR_REF && + opts != NULL && + opts->error_marshaling_enabled) { + field->ext_type = MP_ERROR; } else { field->ext_type = MP_UNKNOWN_EXTENSION; } diff --git a/test/box-tap/extended_error.test.lua b/test/box-tap/extended_error.test.lua new file mode 100755 index 0000000..1681419 --- /dev/null +++ b/test/box-tap/extended_error.test.lua @@ -0,0 +1,124 @@ +#! /usr/bin/env tarantool + +local netbox = require('net.box') +local os = require('os') +local tap = require('tap') + +box.cfg{ + listen = os.getenv('LISTEN') +} + +-- +-- gh-4398: error objects after transmission through network +-- should not loose their fields and type. +-- + +-- Create AccessDeniedError. It is going to be used in the tests +-- below. +function forbidden_function() + return nil +end +local user = box.session.user() +box.session.su('admin') +box.schema.func.create('forbidden_function') +box.session.su('guest') +local tmp = box.func.forbidden_function +local access_denied_error +tmp, access_denied_error = pcall(tmp.call, tmp) +box.session.su('admin') +box.schema.func.drop('forbidden_function') +box.session.su(user) + +local test = tap.test('Error marshaling') +test:plan(6) + +function error_new(...) + return box.error.new(...) +end + +function error_new_stacked(args1, args2) + local e1 = box.error.new(args1) + local e2 = box.error.new(args2) + e1:set_prev(e2) + return e1 +end + +function error_access_denied() + return access_denied_error +end + +local function check_error(err, check_list) + assert(type(check_list) == 'table') + if type(err.trace) ~= 'table' or err.trace[1] == nil or + err.trace[1].file == nil or err.trace[1].line == nil then + return false + end + for k, v in pairs(check_list) do + if err[k] ~= v then + return false + end + end + return true +end + +box.schema.user.grant('guest', 'super') +local c = netbox.connect(box.cfg.listen) +c:eval('box.session.settings.error_marshaling_enabled = true') +local err = c:call('error_new', {{code = 1000, reason = 'Reason'}}) +local checks = { + code = 1000, + message = 'Reason', + base_type = 'ClientError', + type = 'ClientError', +} +test:ok(check_error(err, checks), "ClientError marshaling") + +err = c:call('error_new', {{ + code = 1001, reason = 'Reason2', type = 'MyError' +}}) +checks = { + code = 1001, + message = 'Reason2', + base_type = 'CustomError', + type = 'MyError', +} +test:ok(check_error(err, checks), "CustomError marshaling") + +err = c:call('error_access_denied') +checks = { + code = 42, + type = 'AccessDeniedError', + base_type = 'AccessDeniedError', + message = "Execute access to function 'forbidden_function' is denied for user 'guest'", + object_type = 'function', + object_name = 'forbidden_function', + access_type = 'Execute', +} +test:ok(check_error(err, checks), "AccessDeniedError marshaling") + +err = c:call('error_new_stacked', { + {code = 1003, reason = 'Reason3', type = 'MyError2'}, + {code = 1004, reason = 'Reason4'} +}) +local err1 = err +local err2 = err.prev +test:isnt(err2, nil, 'Stack is received') +checks = { + code = 1003, + message = 'Reason3', + base_type = 'CustomError', + type = 'MyError2' +} +test:ok(check_error(err1, checks), "First error in the stack") +checks = { + code = 1004, + message = 'Reason4', + base_type = 'ClientError', + type = 'ClientError' +} +test:ok(check_error(err2, checks), "Second error in the stack") + +c:close() +box.schema.user.revoke('guest', 'super') + +os.exit(test:check() and 0 or 1) diff --git a/test/unit/CMakeLists.txt b/test/unit/CMakeLists.txt index e1d506f..24586c2 100644 --- a/test/unit/CMakeLists.txt +++ b/test/unit/CMakeLists.txt @@ -69,6 +69,8 @@ add_executable(xrow.test xrow.cc) target_link_libraries(xrow.test xrow unit) add_executable(decimal.test decimal.c) target_link_libraries(decimal.test core unit) +add_executable(mp_error.test mp_error.cc) +target_link_libraries(mp_error.test box_error core unit) add_executable(fiber.test fiber.cc) set_source_files_properties(fiber.cc PROPERTIES COMPILE_FLAGS -O0) diff --git a/test/unit/mp_error.cc b/test/unit/mp_error.cc new file mode 100644 index 0000000..e02deac --- /dev/null +++ b/test/unit/mp_error.cc @@ -0,0 +1,473 @@ +/* + * Copyright 2010-2020, Tarantool AUTHORS, please see AUTHORS file. + * + * Redistribution and use in source and iproto forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * 1. Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * 2. Redistributions in iproto form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL + * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ +#include "unit.h" +#include "exception.h" +#include "fiber.h" +#include "memory.h" +#include "msgpuck.h" +#include "tt_static.h" + +#include "box/error.h" +#include "box/mp_error.h" + +enum { + MP_ERROR_STACK = 0x00 +}; + +enum { + MP_ERROR_TYPE = 0x00, + MP_ERROR_FILE = 0x01, + MP_ERROR_LINE = 0x02, + MP_ERROR_MESSAGE = 0x03, + MP_ERROR_ERRNO = 0x04, + MP_ERROR_CODE = 0x05, + MP_ERROR_FIELDS = 0x06 +}; + +struct mp_error { + uint32_t code; + uint32_t line; + int32_t saved_errno; + uint32_t unknown_uint_field; + const char *type; + const char *file; + const char *message; + const char *custom_type; + const char *ad_object_type; + const char *ad_object_name; + const char *ad_access_type; + const char *unknown_str_field; +}; + +const char *standard_errors[] = { + "XlogError", + "XlogGapError", + "SystemError", + "SocketError", + "OutOfMemory", + "TimedOut", + "ChannelIsClosed", + "FiberIsCancelled", + "LuajitError", + "IllegalParams", + "CollationError", + "SwimError", + "CryptoError", +}; + +enum { + TEST_STANDARD_ERRORS_NUM = + sizeof(standard_errors) / sizeof(standard_errors[0]), +}; + +static inline char * +mp_encode_str0(char *data, const char *str) +{ + return mp_encode_str(data, str, strlen(str)); +} + +/** Note, not the same as mp_encode_error(). */ +static char * +mp_encode_mp_error(const struct mp_error *e, char *data) +{ + uint32_t fields_num = 6; + int field_count = (e->custom_type != NULL) + + (e->ad_object_type != NULL) + + (e->ad_object_name != NULL) + + (e->ad_access_type != NULL) + + (e->unknown_str_field != NULL); + fields_num += (field_count != 0); + fields_num += (e->unknown_uint_field != 0); + + data = mp_encode_map(data, fields_num); + data = mp_encode_uint(data, MP_ERROR_TYPE); + data = mp_encode_str0(data, e->type); + data = mp_encode_uint(data, MP_ERROR_FILE); + data = mp_encode_str0(data, e->file); + data = mp_encode_uint(data, MP_ERROR_LINE); + data = mp_encode_uint(data, e->line); + data = mp_encode_uint(data, MP_ERROR_MESSAGE); + data = mp_encode_str0(data, e->message); + data = mp_encode_uint(data, MP_ERROR_ERRNO); + data = mp_encode_uint(data, e->saved_errno); + data = mp_encode_uint(data, MP_ERROR_CODE); + data = mp_encode_uint(data, e->code); + if (e->unknown_uint_field != 0) { + data = mp_encode_uint(data, UINT64_MAX); + data = mp_encode_uint(data, e->unknown_uint_field); + } + if (field_count != 0) { + data = mp_encode_uint(data, MP_ERROR_FIELDS); + data = mp_encode_map(data, field_count); + if (e->custom_type != NULL) { + data = mp_encode_str0(data, "custom_type"); + data = mp_encode_str0(data, e->custom_type); + } + if (e->ad_object_type != NULL) { + data = mp_encode_str0(data, "object_type"); + data = mp_encode_str0(data, e->ad_object_type); + } + if (e->ad_object_name != NULL) { + data = mp_encode_str0(data, "object_name"); + data = mp_encode_str0(data, e->ad_object_name); + } + if (e->ad_access_type != NULL) { + data = mp_encode_str0(data, "access_type"); + data = mp_encode_str0(data, e->ad_access_type); + } + if (e->unknown_str_field != NULL) { + data = mp_encode_str0(data, "unknown_field"); + data = mp_encode_str0(data, e->unknown_str_field); + } + } + return data; +} + +static char * +mp_encode_error_header(char *data, int stack_size) +{ + data = mp_encode_map(data, 1); + data = mp_encode_uint(data, MP_ERROR_STACK); + data = mp_encode_array(data, stack_size); + return data; +} + +static char * +mp_encode_test_error_stack(char *data) +{ + data = mp_encode_error_header(data, TEST_STANDARD_ERRORS_NUM + 3); + /* + * CustomError + */ + struct mp_error err; + memset(&err, 0, sizeof(err)); + err.code = 123; + err.line = 1; + err.saved_errno = 2; + err.type = "CustomError"; + err.file = "File1"; + err.message = "Message1"; + err.custom_type = "MyType"; + data = mp_encode_mp_error(&err, data); + /* + * AccessDeniedError + */ + memset(&err, 0, sizeof(err)); + err.code = 42; + err.line = 3; + err.saved_errno = 4; + err.type = "AccessDeniedError"; + err.file = "File2"; + err.message = "Message2"; + err.ad_object_type = "ObjectType"; + err.ad_object_name = "ObjectName"; + err.ad_access_type = "AccessType"; + data = mp_encode_mp_error(&err, data); + /* + * ClientError + */ + memset(&err, 0, sizeof(err)); + err.code = 123; + err.line = 5; + err.saved_errno = 6; + err.type = "ClientError"; + err.file = "File3"; + err.message = "Message4"; + data = mp_encode_mp_error(&err, data); + + /* + * All errors with standard fields only. + */ + for (uint8_t i = 0; i < TEST_STANDARD_ERRORS_NUM; ++i) { + memset(&err, 0, sizeof(err)); + err.code = i; + err.line = i; + err.saved_errno = i; + err.type = standard_errors[i]; + err.file = tt_sprintf("File%d", i); + err.message = tt_sprintf("Message%d", i); + data = mp_encode_mp_error(&err, data); + } + + return data; +} + +static bool +error_is_eq_mp_error(struct error *err, struct mp_error *check) +{ + if (err->saved_errno != check->saved_errno) + return false; + if (strcmp(err->type->name, check->type) != 0) + return false; + if (strcmp(err->file, check->file) != 0) + return false; + if (err->line != check->line) + return false; + if (strcmp(err->errmsg, check->message) != 0) + return false; + + if (strcmp(check->type, "ClientError") == 0) { + if (box_error_code(err) != check->code) + return false; + } else if (strcmp(check->type, "CustomError") == 0) { + CustomError *cust_err = type_cast(CustomError, err); + if (box_error_code(err) != check->code || + strcmp(cust_err->custom_type(), check->custom_type) != 0) + return false; + } else if (strcmp(check->type, "AccessDeniedError") == 0) { + AccessDeniedError *ad_err = type_cast(AccessDeniedError, err); + if (box_error_code(err) != check->code || + strcmp(ad_err->access_type(), check->ad_access_type) != 0 || + strcmp(ad_err->object_name(), check->ad_object_name) != 0 || + strcmp(ad_err->object_type(), check->ad_object_type) != 0) + return false; + } + return true; +} + +void +test_stack_error_decode() +{ + header(); + plan(TEST_STANDARD_ERRORS_NUM + 4); + + char buffer[2048]; + memset(buffer, 0, sizeof(buffer)); + char *end = mp_encode_test_error_stack(buffer); + + uint32_t len = end - buffer; + const char *pos = buffer; + struct error *err1 = error_unpack(&pos, len); + error_ref(err1); + struct error *err2 = err1->cause; + struct error *err3 = err2->cause; + + struct mp_error check; + memset(&check, 0, sizeof(check)); + check.code = 123; + check.line = 1; + check.saved_errno = 2; + check.type = "CustomError"; + check.file = "File1"; + check.message = "Message1"; + check.custom_type = "MyType"; + ok(error_is_eq_mp_error(err1, &check), "check CustomError"); + + memset(&check, 0, sizeof(check)); + check.code = 42; + check.line = 3; + check.saved_errno = 4; + check.type = "AccessDeniedError"; + check.file = "File2"; + check.message = "Message2"; + check.ad_object_type = "ObjectType"; + check.ad_object_name = "ObjectName"; + check.ad_access_type = "AccessType"; + ok(error_is_eq_mp_error(err2, &check), "check AccessDeniedError"); + + memset(&check, 0, sizeof(check)); + check.code = 123; + check.line = 5; + check.saved_errno = 6; + check.type = "ClientError"; + check.file = "File3"; + check.message = "Message4"; + ok(error_is_eq_mp_error(err3, &check), "check ClientError"); + + struct error *cur_err = err3; + int i = 0; + while(cur_err->cause) { + cur_err = cur_err->cause; + memset(&check, 0, sizeof(check)); + check.code = i; + check.line = i; + check.saved_errno = i; + check.type = standard_errors[i]; + check.file = tt_sprintf("File%d", i); + check.message = tt_sprintf("Message%d", i); + ok(error_is_eq_mp_error(cur_err, &check), "check %s", + standard_errors[i]); + ++i; + } + is(i, TEST_STANDARD_ERRORS_NUM, "stack size"); + error_unref(err1); + check_plan(); + footer(); +} + +void +test_decode_unknown_type() +{ + header(); + plan(1); + char buffer[2048]; + memset(buffer, 0, sizeof(buffer)); + + char *data = mp_encode_error_header(buffer, 1); + struct mp_error err; + memset(&err, 0, sizeof(err)); + err.code = 1; + err.line = 2; + err.saved_errno = 3; + err.type = "SomeNewError"; + err.file = "File1"; + err.message = "Message1"; + data = mp_encode_mp_error(&err, data); + + uint32_t len = data - buffer; + const char *pos = buffer; + struct error *unpacked = error_unpack(&pos, len); + error_ref(unpacked); + err.code = 0; + err.type = "ClientError"; + ok(error_is_eq_mp_error(unpacked, &err), "check SomeNewError"); + error_unref(unpacked); + + check_plan(); + footer(); +} + +void +test_fail_not_enough_fields() +{ + header(); + plan(2); + char buffer[2048]; + memset(buffer, 0, sizeof(buffer)); + + char *data = mp_encode_error_header(buffer, 1); + struct mp_error err; + memset(&err, 0, sizeof(err)); + err.code = 42; + err.line = 3; + err.saved_errno = 4; + err.type = "AccessDeniedError"; + err.file = "File1"; + err.message = "Message1"; + err.ad_object_type = "ObjectType"; + err.ad_access_type = "AccessType"; + data = mp_encode_mp_error(&err, data); + + uint32_t len = data - buffer; + const char *pos = buffer; + struct error *unpacked = error_unpack(&pos, len); + + is(unpacked, NULL, "check not enough additional fields"); + ok(!diag_is_empty(diag_get()), "error about parsing problem is set"); + check_plan(); + footer(); +} + +void +test_unknown_fields() +{ + header(); + plan(1); + char buffer[2048]; + memset(buffer, 0, sizeof(buffer)); + + char *data = mp_encode_error_header(buffer, 1); + struct mp_error err; + memset(&err, 0, sizeof(err)); + err.code = 0; + err.line = 1; + err.saved_errno = 0; + err.type = "SystemError"; + err.file = "File"; + err.message = "Message"; + err.unknown_uint_field = 55; + data = mp_encode_mp_error(&err, data); + + uint32_t len = data - buffer; + const char *pos = buffer; + struct error *unpacked = error_unpack(&pos, len); + error_ref(unpacked); + data = mp_encode_mp_error(&err, data); + + ok(error_is_eq_mp_error(unpacked, &err), "check unknown fields"); + error_unref(unpacked); + check_plan(); +} + +void +test_unknown_additional_fields() +{ + header(); + plan(1); + char buffer[2048]; + memset(buffer, 0, sizeof(buffer)); + + char *data = mp_encode_error_header(buffer, 1); + struct mp_error err; + memset(&err, 0, sizeof(err)); + err.code = 42; + err.line = 3; + err.saved_errno = 4; + err.type = "AccessDeniedError"; + err.file = "File"; + err.message = "Message"; + err.ad_object_type = "ObjectType"; + err.ad_object_name = "ObjectName"; + err.ad_access_type = "AccessType"; + err.unknown_str_field = "unknown_field"; + data = mp_encode_mp_error(&err, data); + + uint32_t len = data - buffer; + const char *pos = buffer; + struct error *unpacked = error_unpack(&pos, len); + error_ref(unpacked); + ok(error_is_eq_mp_error(unpacked, &err), + "check unknown additional field"); + error_unref(unpacked); + + check_plan(); + footer(); +} + +int +main(void) +{ + header(); + plan(5); + memory_init(); + fiber_init(fiber_c_invoke); + + test_stack_error_decode(); + test_decode_unknown_type(); + test_fail_not_enough_fields(); + test_unknown_fields(); + test_unknown_additional_fields(); + + fiber_free(); + memory_free(); + footer(); + return check_plan(); +} diff --git a/test/unit/mp_error.result b/test/unit/mp_error.result new file mode 100644 index 0000000..6ef37b4 --- /dev/null +++ b/test/unit/mp_error.result @@ -0,0 +1,44 @@ + *** main *** +1..5 + *** test_stack_error_decode *** + 1..17 + ok 1 - check CustomError + ok 2 - check AccessDeniedError + ok 3 - check ClientError + ok 4 - check XlogError + ok 5 - check XlogGapError + ok 6 - check SystemError + ok 7 - check SocketError + ok 8 - check OutOfMemory + ok 9 - check TimedOut + ok 10 - check ChannelIsClosed + ok 11 - check FiberIsCancelled + ok 12 - check LuajitError + ok 13 - check IllegalParams + ok 14 - check CollationError + ok 15 - check SwimError + ok 16 - check CryptoError + ok 17 - stack size +ok 1 - subtests + *** test_stack_error_decode: done *** + *** test_decode_unknown_type *** + 1..1 + ok 1 - check SomeNewError +ok 2 - subtests + *** test_decode_unknown_type: done *** + *** test_fail_not_enough_fields *** + 1..2 + ok 1 - check not enough additional fields + ok 2 - error about parsing problem is set +ok 3 - subtests + *** test_fail_not_enough_fields: done *** + *** test_unknown_fields *** + 1..1 + ok 1 - check unknown fields +ok 4 - subtests + *** test_unknown_additional_fields *** + 1..1 + ok 1 - check unknown additional field +ok 5 - subtests + *** test_unknown_additional_fields: done *** + *** main: done *** -- 2.7.4