Tarantool development patches archive
 help / color / mirror / Atom feed
From: Serge Petrenko via Tarantool-patches <tarantool-patches@dev.tarantool.org>
To: Vladislav Shpilevoy <v.shpilevoy@tarantool.org>,
	tarantool-patches@dev.tarantool.org, vdavydov@tarantool.org
Subject: Re: [Tarantool-patches] [PATCH 2/9] error: introduce error_payload
Date: Mon, 8 Nov 2021 18:14:11 +0300	[thread overview]
Message-ID: <68ebfc9b-5e2c-91c0-106a-8c309a31c1b3@tarantool.org> (raw)
In-Reply-To: <0fa9f99b412387269401bed410c480b456eeb604.1636156453.git.v.shpilevoy@tarantool.org>



06.11.2021 02:56, Vladislav Shpilevoy пишет:
> It is a dictionary-like struct which stores keys with binary data
> values. The values are supposed to be arbitrary MessagePack blobs:
> number, string, bool, UUID, array, map, anything.
>
> The payload is an array inside instead of a hash table because
> number of keys will be <= 3 in all cases. And even when it will be
> public, it is very unlikely it will be bigger.
>
> Object of error_payload in a future patch will be stored in struct
> error and will allow to extend it dynamically with more members.
>
> This in turn is needed to extend ER_READONLY error with more
> details about why it happened.
>
> Part of #5568

Hi! Thanks for the patch!
Please, find 6 comments below.

> ---
>   src/lib/core/CMakeLists.txt  |   1 +
>   src/lib/core/error_payload.c | 282 +++++++++++++++++++++
>   src/lib/core/error_payload.h | 166 +++++++++++++
>   src/lib/uuid/mp_uuid.c       |  26 ++
>   src/lib/uuid/tt_uuid.h       |  24 ++
>   test/unit/CMakeLists.txt     |   2 +
>   test/unit/error.c            | 461 +++++++++++++++++++++++++++++++++++
>   test/unit/error.result       | 160 ++++++++++++
>   8 files changed, 1122 insertions(+)
>   create mode 100644 src/lib/core/error_payload.c
>   create mode 100644 src/lib/core/error_payload.h
>   create mode 100644 test/unit/error.c
>   create mode 100644 test/unit/error.result
>
> diff --git a/src/lib/core/CMakeLists.txt b/src/lib/core/CMakeLists.txt
> index 0cc742a1c..860758515 100644
> --- a/src/lib/core/CMakeLists.txt
> +++ b/src/lib/core/CMakeLists.txt
> @@ -21,6 +21,7 @@ set(core_sources
>       fio.c
>       exception.cc
>       errinj.c
> +    error_payload.c
>       reflection.c
>       assoc.c
>       util.c
> diff --git a/src/lib/core/error_payload.c b/src/lib/core/error_payload.c
> new file mode 100644
> index 000000000..98ef08ada
> --- /dev/null
> +++ b/src/lib/core/error_payload.c
> @@ -0,0 +1,282 @@
> +/*
> + * SPDX-License-Identifier: BSD-2-Clause
> + *
> + * Copyright 2010-2021, Tarantool AUTHORS, please see AUTHORS file.
> + */
> +#include "error_payload.h"
> +#include "msgpuck.h"
> +
> +void
> +error_payload_create(struct error_payload *p)
> +{
> +	p->count = 0;
> +	p->fields = NULL;
> +}
> +
> +void
> +error_payload_destroy(struct error_payload *p)
> +{
> +	for (int i = 0; i < p->count; ++i) {
> +		TRASH(p->fields[i]);
> +		free(p->fields[i]);
> +	}
> +	free(p->fields);
> +	TRASH(p);
> +}
> +
> +const char *
> +error_payload_get_str(const struct error_payload *e, const char *name)
> +{
> +	const struct error_field *f = error_payload_find(e, name);
> +	if (f == NULL)
> +		return NULL;
> +	const char *data = f->data;
> +	if (mp_typeof(*data) != MP_STR)
> +		return NULL;
> +	uint32_t len;
> +	/*
> +	 * 0 is explicitly written after encoded string to be able to return
> +	 * them without copying.
> +	 */
> +	data = mp_decode_str(&data, &len);
> +	assert(data[len] == 0);

1. Nit: `data[len] == '\0'`. Here and everywhere else. This is up to you,
    I'm just more used to '\0'.

> +	return data;
> +}
> +
> +void
> +error_payload_set_str(struct error_payload *e, const char *name,
> +		      const char *value)
> +{
> +	uint32_t len = strlen(value);
> +	char *data = error_payload_prepare(
> +		e, name, mp_sizeof_str(len), 1)->data;
> +	/*
> +	 * 1 extra byte in the end is reserved to append 0 after the encoded
> +	 * string. To be able to return it from get() without copying.
> +	 */

2. This comment is almost the same as the one in payload_get_str().
    I propose to leave only one of them. Preferably the one in set_str().

> +	*mp_encode_str(data, value, len) = 0;
> +}
> +
> +bool
> +error_payload_get_uint(const struct error_payload *e, const char *name,
> +		       uint64_t *value)
> +{
> +	const struct error_field *f = error_payload_find(e, name);
> +	if (f == NULL)
> +		goto not_found;
> +	const char *data = f->data;
> +	if (mp_typeof(*data) != MP_UINT)
> +		goto not_found;
> +	*value = mp_decode_uint(&data);
> +	return true;
> +
> +not_found:
> +	*value = 0;
> +	return false;
> +}
> +

3. Consider this diff here.
    (Similar places below do need a label, but this one doesn't).

============================

diff --git a/src/lib/core/error_payload.c b/src/lib/core/error_payload.c
index 98ef08ada..cfa30919e 100644
--- a/src/lib/core/error_payload.c
+++ b/src/lib/core/error_payload.c
@@ -62,17 +62,14 @@ error_payload_get_uint(const struct error_payload 
*e, const char *name,
                        uint64_t *value)
  {
         const struct error_field *f = error_payload_find(e, name);
+       *value = 0;
         if (f == NULL)
-               goto not_found;
+               return false;
         const char *data = f->data;
         if (mp_typeof(*data) != MP_UINT)
-               goto not_found;
+               return false;
         *value = mp_decode_uint(&data);
         return true;
-
-not_found:
-       *value = 0;
-       return false;
  }

  void

============================

> +void
> +error_payload_set_uint(struct error_payload *e, const char *name,
> +		       uint64_t value)
> +{
> +	char *data = error_payload_prepare(
> +		e, name, mp_sizeof_uint(value), 0)->data;
> +	mp_encode_uint(data, value);
> +}
> +
> +bool
> +error_payload_get_int(const struct error_payload *e, const char *name,
> +		      int64_t *value)
> +{
> +	const struct error_field *f = error_payload_find(e, name);
> +	if (f == NULL)
> +		goto not_found;
> +	const char *data = f->data;
> +	if (mp_typeof(*data) == MP_UINT) {
> +		uint64_t u = mp_decode_uint(&data);
> +		if (u > INT64_MAX)
> +			goto not_found;
> +		*value = u;
> +		return true;
> +	} else if (mp_typeof(*data) == MP_INT) {
> +		*value = mp_decode_int(&data);
> +		return true;
> +	}
> +not_found:
> +	*value = 0;
> +	return false;
> +}
> +
> +void
> +error_payload_set_int(struct error_payload *e, const char *name, int64_t value)
> +{
> +	if (value >= 0)
> +		return error_payload_set_uint(e, name, value);
> +	char *data = error_payload_prepare(
> +		e, name, mp_sizeof_int(value), 0)->data;
> +	mp_encode_int(data, value);
> +}
> +
> +bool
> +error_payload_get_double(const struct error_payload *e, const char *name,
> +			 double *value)
> +{
> +	const struct error_field *f = error_payload_find(e, name);
> +	if (f == NULL)
> +		goto not_found;
> +	const char *data = f->data;
> +	if (mp_typeof(*data) == MP_DOUBLE) {
> +		*value = mp_decode_double(&data);
> +		return true;
> +	} else if (mp_typeof(*data) == MP_FLOAT) {
> +		*value = mp_decode_float(&data);
> +		return true;
> +	}

4. Why do you check for both MP_DOUBLE and MP_FLOAT here?
    You only encode MP_DOUBLE.

    I think we might have set_float() and get_float() one day, but looks
    like they're not needed now.
    get_float() would only accept MP_FLOAT then, and get_double() should
    only accept MP_DOUBLE.

> +not_found:
> +	*value = 0;
> +	return false;
> +}
> +
> +void
> +error_payload_set_double(struct error_payload *e, const char *name,
> +			 double value)
> +{
> +	char *data = error_payload_prepare(
> +		e, name, mp_sizeof_double(value), 0)->data;
> +	mp_encode_double(data, value);
> +}
> +
> +bool
> +error_payload_get_bool(const struct error_payload *e, const char *name,
> +		       bool *value)
> +{
> +	const struct error_field *f = error_payload_find(e, name);
> +	if (f == NULL)
> +		goto not_found;
> +	const char *data = f->data;
> +	if (mp_typeof(*data) != MP_BOOL)
> +		goto not_found;
> +	*value = mp_decode_bool(&data);
> +	return true;
> +
> +not_found:
> +	*value = false;
> +	return false;
> +}
> +
> +void
> +error_payload_set_bool(struct error_payload *e, const char *name, bool value)
> +{
> +	char *data = error_payload_prepare(
> +		e, name, mp_sizeof_bool(value), 0)->data;
> +	mp_encode_bool(data, value);
> +}
> +
> +const char *
> +error_payload_get_mp(const struct error_payload *e, const char *name,
> +		     uint32_t *size)
> +{
> +	const struct error_field *f = error_payload_find(e, name);
> +	if (f == NULL) {
> +		*size = 0;
> +		return NULL;
> +	}
> +	*size = f->size;
> +	return f->data;
> +}
> +
> +void
> +error_payload_set_mp(struct error_payload *e, const char *name,
> +		     const char *src, uint32_t size)
> +{
> +	char *data;
> +	if (mp_typeof(*src) == MP_STR) {
> +		data = error_payload_prepare(e, name, size, 1)->data;
> +		/*
> +		 * Add the terminating zero after the buffer so as get_str()
> +		 * would work without copying.
> +		 */

5. Maybe shorten this comment to "\sa error_payload_set_str()" ?

> +		data[size] = 0;
> +	} else {
> +		data = error_payload_prepare(e, name, size, 0)->data;
> +	}
> +	memcpy(data, src, size);
> +}
> +
> +void
> +error_payload_unset(struct error_payload *e, const char *name)
> +{
> +	struct error_field **fields = e->fields;
> +	for (int i = 0; i < e->count; ++i) {
> +		struct error_field *f = fields[i];
> +		if (strcmp(name, f->name) != 0)
> +			continue;
> +		TRASH(f);
> +		free(f);
> +		int count = --e->count;
> +		if (count == 0) {
> +			free(fields);
> +			e->fields = NULL;
> +			return;
> +		}
> +		/* Cyclic deletion. Order does not matter in a dictionary. */
> +		fields[i] = fields[count];
> +		return;
> +	}
> +}
> +
> +void
> +error_payload_move(struct error_payload *dst, struct error_payload *src)
> +{
> +	for (int i = 0; i < dst->count; ++i) {
> +		TRASH(dst->fields[i]);
> +		free(dst->fields[i]);
> +	}
> +	free(dst->fields);
> +	dst->fields = src->fields;
> +	dst->count = src->count;
> +	src->fields = NULL;
> +	src->count = 0;
> +}
> +
> +const struct error_field *
> +error_payload_find(const struct error_payload *e, const char *name)
> +{
> +	for (int i = 0; i < e->count; ++i) {
> +		const struct error_field *f = e->fields[i];
> +		if (strcmp(name, f->name) == 0)
> +			return f;
> +	}
> +	return NULL;
> +}
> +
> +struct error_field *
> +error_payload_prepare(struct error_payload *e, const char *name,
> +		      uint32_t value_size, uint32_t extra)
> +{
> +	struct error_field *f;
> +	uint32_t name_size = strlen(name) + 1;
> +	uint32_t data_offset = name_size + sizeof(*f);
> +	uint32_t total = data_offset + value_size + extra;
> +
> +	struct error_field **fields = e->fields;
> +	int count = e->count;
> +	int i;
> +	for (i = 0; i < count; ++i) {
> +		f = fields[i];
> +		if (strcmp(name, f->name) != 0)
> +			continue;
> +		f = xrealloc(f, total);
> +		goto set;
> +	}
> +	e->count = ++count;
> +	fields = xrealloc(fields, sizeof(fields[0]) * count);
> +	e->fields = fields;
> +	f = xmalloc(total);
> +	memcpy(f->name, name, name_size);
> +set:
> +	f->data = (char *)f + data_offset;
> +	f->size = value_size;
> +	fields[i] = f;
> +	return f;
> +}
> diff --git a/src/lib/core/error_payload.h b/src/lib/core/error_payload.h
> new file mode 100644
> index 000000000..b6bf9ceee
> --- /dev/null
> +++ b/src/lib/core/error_payload.h
> @@ -0,0 +1,166 @@
> +/*
> + * SPDX-License-Identifier: BSD-2-Clause
> + *
> + * Copyright 2010-2021, Tarantool AUTHORS, please see AUTHORS file.
> + */
> +#pragma once
> +
> +#include "trivia/util.h"
> +
> +#if defined(__cplusplus)
> +extern "C" {
> +#endif /* defined(__cplusplus) */
> +
> +/** Single field of error payload. */
> +struct error_field {
> +	/** Data size. */
> +	uint32_t size;
> +	/** MessagePack field value. */
> +	char *data;
> +	/** Zero terminated field name. */
> +	char name[0];
> +};
> +
> +/** An array of key-value pairs. */
> +struct error_payload {
> +	/** Number of fields. */
> +	int count;
> +	/**
> +	 * Array of field pointers. Not very optimized, but the errors are
> +	 * supposed to be rare. Also not storing them by values simplifies
> +	 * addition of new fields and their removal.
> +	 */
> +	struct error_field **fields;
> +};
> +
> +/** Create error payload. */
> +void
> +error_payload_create(struct error_payload *p);
> +
> +/** Destroy error payload. */
> +void
> +error_payload_destroy(struct error_payload *p);
> +
> +/**
> + * Get value of a payload field as a string. If it is not string or is not
> + * found - return NULL.
> + */
> +const char *
> +error_payload_get_str(const struct error_payload *e, const char *name);
> +
> +/**
> + * Set value of a payload field to a string. If the field existed before, it is
> + * overwritten.
> + */
> +void
> +error_payload_set_str(struct error_payload *e, const char *name,
> +		      const char *value);
> +
> +/**
> + * Get value of a payload field as a uint. If it is not uint or is not found -
> + * return false and the out parameter is set to 0.
> + */
> +bool
> +error_payload_get_uint(const struct error_payload *e, const char *name,
> +		       uint64_t *value);
> +
> +/**
> + * Set value of a payload field to a uint. If the field existed before, it is
> + * overwritten.
> + */
> +void
> +error_payload_set_uint(struct error_payload *e, const char *name,
> +		       uint64_t value);
> +
> +/**
> + * Get value of a payload field as an int. If it is not int, or is not found, or
> + * does not fit int64_t - return false and the out parameter is set to 0.
> + */
> +bool
> +error_payload_get_int(const struct error_payload *e, const char *name,
> +		      int64_t *value);
> +
> +/**
> + * Set value of a payload field to an int. If the field existed before, it is
> + * overwritten.
> + */
> +void
> +error_payload_set_int(struct error_payload *e, const char *name, int64_t value);
> +
> +/**
> + * Get value of a payload field as a double. If it is not double or is not
> + * found - return false and the out parameter is set to 0.
> + */
> +bool
> +error_payload_get_double(const struct error_payload *e, const char *name,
> +			 double *value);
> +
> +/**
> + * Set value of a payload field to a double. If the field existed before, it is
> + * overwritten.
> + */
> +void
> +error_payload_set_double(struct error_payload *e, const char *name,
> +			 double value);
> +
> +/**
> + * Get value of a payload field as a bool. If it is not bool or is not found -
> + * return false and the out parameter is set to false.
> + */
> +bool
> +error_payload_get_bool(const struct error_payload *e, const char *name,
> +		       bool *value);
> +
> +/**
> + * Set value of a payload field to a bool. If the field existed before, it is
> + * overwritten.
> + */
> +void
> +error_payload_set_bool(struct error_payload *e, const char *name, bool value);
> +
> +/**
> + * Get MessagePack value of a payload field. If it is not found - return NULL
> + * and the out parameter is set to 0.
> + */
> +const char *
> +error_payload_get_mp(const struct error_payload *e, const char *name,
> +		     uint32_t *size);
> +
> +/**
> + * Set value of a payload field to a MessagePack buffer. If the field existed
> + * before, it is overwritten.
> + */
> +void
> +error_payload_set_mp(struct error_payload *e, const char *name,
> +		     const char *src, uint32_t size);
> +
> +/** Remove the given field from the payload. */
> +void
> +error_payload_unset(struct error_payload *e, const char *name);
> +
> +/**
> + * Move all fields of one payload into another. Old fields of the destination
> + * are all deleted. The source stays valid but empty.
> + */
> +void
> +error_payload_move(struct error_payload *dst, struct error_payload *src);
> +
> +/** Find a payload field by name. */
> +const struct error_field *
> +error_payload_find(const struct error_payload *e, const char *name);
> +
> +/**
> + * Prepare a payload field to get a new value. If the field didn't exist, it is
> + * added. If it existed then it is reallocated if was necessary and should be
> + * overwritten. Name is already filled in the field. Only need to fill the
> + * value.
> + * Extra parameter allows to allocate extra memory for arbitrary usage after the
> + * error field and its value.
> + */
> +struct error_field *
> +error_payload_prepare(struct error_payload *e, const char *name,
> +		      uint32_t value_size, uint32_t extra);
> +
> +#if defined(__cplusplus)
> +} /* extern "C" */
> +#endif /* defined(__cplusplus) */
> diff --git a/src/lib/uuid/mp_uuid.c b/src/lib/uuid/mp_uuid.c
> index b2341ae36..a60716eb5 100644
> --- a/src/lib/uuid/mp_uuid.c
> +++ b/src/lib/uuid/mp_uuid.c
> @@ -32,6 +32,7 @@
>   #include "mp_uuid.h"
>   #include "msgpuck.h"
>   #include "mp_extension_types.h"
> +#include "error_payload.h"
>   
>   inline uint32_t
>   mp_sizeof_uuid(void)
> @@ -113,3 +114,28 @@ mp_fprint_uuid(FILE *file, const char **data, uint32_t len)
>   		return -1;
>   	return fprintf(file, "%s", tt_uuid_str(&uuid));
>   }
> +
> +bool
> +error_payload_get_uuid(const struct error_payload *e, const char *name,
> +		       struct tt_uuid *uuid)
> +{
> +	const struct error_field *f = error_payload_find(e, name);
> +	if (f == NULL)
> +		goto not_found;
> +	const char *data = f->data;
> +	if (mp_decode_uuid(&data, uuid) == NULL)
> +		goto not_found;
> +	return true;
> +
> +not_found:
> +	*uuid = uuid_nil;
> +	return false;
> +}
> +

6. I propose to get rid of the label here and set uuid to nil 
unconditionally.

    Would it be too expensive to do 2 struct assignments instead of one?
    (uuid = uuid_nil; uuid = decoded_value).

> +void
> +error_payload_set_uuid(struct error_payload *e, const char *name,
> +		       const struct tt_uuid *uuid)
> +{
> +	char *data = error_payload_prepare(e, name, mp_sizeof_uuid(), 0)->data;
> +	mp_encode_uuid(data, uuid);
> +}
> diff --git a/src/lib/uuid/tt_uuid.h b/src/lib/uuid/tt_uuid.h
> index 70c3b98b1..866764e77 100644
> --- a/src/lib/uuid/tt_uuid.h
> +++ b/src/lib/uuid/tt_uuid.h
> @@ -41,6 +41,8 @@
>   extern "C" {
>   #endif
>   
> +struct error_payload;
> +
>   enum { UUID_LEN = 16, UUID_STR_LEN = 36 };
>   
>   /**
> @@ -182,6 +184,28 @@ tt_uuid_str(const struct tt_uuid *uu);
>   int
>   tt_uuid_from_strl(const char *in, size_t len, struct tt_uuid *uu);
>   
> +/**
> + * Error payload couldn't be implemented in libcore because requires UUID type
> + * and its methods. That would be a cyclic dep. Thus in places where need to use
> + * UUID fields in error payload also need to include libuuid.
> + */
> +
> +/**
> + * Get value of a payload field as a UUID. If it is not UUID or is not found -
> + * return false and the out parameter is set to UUID with zeros.
> + */
> +bool
> +error_payload_get_uuid(const struct error_payload *e, const char *name,
> +		       struct tt_uuid *uuid);
> +
> +/**
> + * Set value of a payload field to a UUID. If the field existed before, it is
> + * overwritten.
> + */
> +void
> +error_payload_set_uuid(struct error_payload *e, const char *name,
> +		       const struct tt_uuid *uuid);
> +
>   #if defined(__cplusplus)
>   } /* extern "C" */
>   #endif
> diff --git a/test/unit/CMakeLists.txt b/test/unit/CMakeLists.txt
> index ae8b5b9ac..0bb71ccce 100644
> --- a/test/unit/CMakeLists.txt
> +++ b/test/unit/CMakeLists.txt
> @@ -60,6 +60,8 @@ add_executable(xmalloc.test xmalloc.c core_test_utils.c)
>   target_link_libraries(xmalloc.test unit)
>   add_executable(datetime.test datetime.c)
>   target_link_libraries(datetime.test tzcode core cdt unit)
> +add_executable(error.test error.c core_test_utils.c)
> +target_link_libraries(error.test unit core uuid)
>   
>   add_executable(bps_tree.test bps_tree.cc)
>   target_link_libraries(bps_tree.test small misc)
> diff --git a/test/unit/error.c b/test/unit/error.c
> new file mode 100644
> index 000000000..ef17c56ec
> --- /dev/null
> +++ b/test/unit/error.c
> @@ -0,0 +1,461 @@
> +/*
> + * SPDX-License-Identifier: BSD-2-Clause
> + *
> + * Copyright 2010-2021, Tarantool AUTHORS, please see AUTHORS file.
> + */
> +#include "error_payload.h"
> +#include "msgpuck.h"
> +#include "random.h"
> +#include "unit.h"
> +#include "uuid/mp_uuid.h"
> +#include "uuid/tt_uuid.h"
> +
> +#include <float.h>
> +
> +static void
> +test_payload_field_str(void)
> +{
> +	header();
> +	plan(15);
> +
> +	struct error_payload p;
> +	error_payload_create(&p);
> +	is(p.count, 0, "no fields in the beginning");
> +	is(error_payload_get_str(&p, "key"), NULL, "get_str() empty");
> +
> +	error_payload_set_str(&p, "key1", "value1");
> +	is(p.count, 1, "++count");
> +	is(strcmp(error_payload_get_str(&p, "key1"), "value1"), 0,
> +	   "get_str() finds");
> +
> +	error_payload_set_str(&p, "key2", "value2");
> +	is(p.count, 2, "++count");
> +	is(strcmp(error_payload_get_str(&p, "key1"), "value1"), 0,
> +	   "get_str() finds old");
> +	is(strcmp(error_payload_get_str(&p, "key2"), "value2"), 0,
> +	   "get_str() finds new");
> +	is(error_payload_find(&p, "key1")->size,
> +	   mp_sizeof_str(strlen("value1")),
> +	   "size does not include terminating zero");
> +
> +	error_payload_set_str(&p, "key1", "new_value1");
> +	is(p.count, 2, "field count is same");
> +	is(strcmp(error_payload_get_str(&p, "key1"), "new_value1"), 0,
> +	   "get_str() finds new value");
> +	is(strcmp(error_payload_get_str(&p, "key2"), "value2"), 0,
> +	   "get_str() finds other key old value");
> +
> +	error_payload_unset(&p, "key2");
> +	is(p.count, 1, "--count");
> +	is(strcmp(error_payload_get_str(&p, "key1"), "new_value1"), 0,
> +	   "get_str() finds new value");
> +	is(error_payload_get_str(&p, "key2"), NULL,
> +	   "get_str() can't find deleted value");
> +
> +	error_payload_set_uint(&p, "key2", 1);
> +	is(error_payload_get_str(&p, "key2"), NULL, "wrong type");
> +
> +	error_payload_destroy(&p);
> +
> +	check_plan();
> +	footer();
> +}
> +
> +static void
> +test_payload_field_uint(void)
> +{
> +	header();
> +	plan(17);
> +
> +	struct error_payload p;
> +	error_payload_create(&p);
> +	uint64_t val = 1;
> +	ok(!error_payload_get_uint(&p, "key", &val) && val == 0,
> +	   "get_uint() empty");
> +
> +	error_payload_set_uint(&p, "key1", 1);
> +	is(p.count, 1, "++count");
> +	ok(error_payload_get_uint(&p, "key1", &val), "get_uint() finds");
> +	is(val, 1, "value match");
> +
> +	val = 100;
> +	ok(!error_payload_get_uint(&p, "key2", &val), "get_uint() fails");
> +	is(val, 0, "value is default");
> +
> +	is(error_payload_find(&p, "key1")->size, mp_sizeof_uint(1),
> +	   "small number size");
> +
> +	error_payload_set_uint(&p, "key2", UINT32_MAX);
> +	ok(error_payload_get_uint(&p, "key2", &val), "get_uint() 32 bit");
> +	is(val, UINT32_MAX, "value match");
> +	is(error_payload_find(&p, "key2")->size, mp_sizeof_uint(UINT32_MAX),
> +	   "middle number size");
> +	is(p.count, 2, "field count is same");
> +
> +	error_payload_set_uint(&p, "key1", UINT64_MAX);
> +	ok(error_payload_get_uint(&p, "key1", &val) && val == UINT64_MAX,
> +	   "value 1");
> +	ok(error_payload_get_uint(&p, "key2", &val) && val == UINT32_MAX,
> +	   "value 2");
> +
> +	error_payload_unset(&p, "key2");
> +	is(p.count, 1, "--count");
> +	ok(error_payload_get_uint(&p, "key1", &val) && val == UINT64_MAX,
> +	   "remained value");
> +	ok(!error_payload_get_uint(&p, "key2", &val) && val == 0,
> +	   "deleted value");
> +
> +	error_payload_set_str(&p, "key2", "1");
> +	ok(!error_payload_get_uint(&p, "key2", &val) && val == 0, "wrong type");
> +
> +	error_payload_destroy(&p);
> +
> +	check_plan();
> +	footer();
> +}
> +
> +static void
> +test_payload_field_int(void)
> +{
> +	header();
> +	plan(20);
> +
> +	struct error_payload p;
> +	error_payload_create(&p);
> +	int64_t val = 1;
> +	ok(!error_payload_get_int(&p, "key", &val) && val == 0,
> +	   "get_int() empty");
> +
> +	error_payload_set_int(&p, "key1", 1);
> +	is(p.count, 1, "++count");
> +	ok(error_payload_get_int(&p, "key1", &val), "get_int() finds");
> +	is(val, 1, "value match");
> +
> +	val = 100;
> +	ok(!error_payload_get_int(&p, "key2", &val), "get_int() fails");
> +	is(val, 0, "value is default");
> +
> +	is(error_payload_find(&p, "key1")->size, mp_sizeof_uint(1),
> +	   "small number size");
> +
> +	error_payload_set_int(&p, "key2", UINT32_MAX);
> +	ok(error_payload_get_int(&p, "key2", &val), "get_int() 32 bit");
> +	is(val, UINT32_MAX, "value match");
> +	is(error_payload_find(&p, "key2")->size, mp_sizeof_uint(UINT32_MAX),
> +	   "middle number size");
> +	is(p.count, 2, "field count is same");
> +
> +	error_payload_set_int(&p, "key1", INT64_MAX);
> +	ok(error_payload_get_int(&p, "key1", &val) && val == INT64_MAX,
> +	   "value 1");
> +	ok(error_payload_get_int(&p, "key2", &val) && val == UINT32_MAX,
> +	   "value 2");
> +
> +	error_payload_unset(&p, "key2");
> +	is(p.count, 1, "--count");
> +	ok(error_payload_get_int(&p, "key1", &val) && val == INT64_MAX,
> +	   "remained value");
> +	ok(!error_payload_get_int(&p, "key2", &val) && val == 0,
> +	   "deleted value");
> +
> +	error_payload_set_str(&p, "key2", "1");
> +	ok(!error_payload_get_int(&p, "key2", &val) && val == 0, "wrong type");
> +
> +	error_payload_set_uint(&p, "key2", (uint64_t)INT64_MAX + 1);
> +	ok(!error_payload_get_int(&p, "key2", &val) && val == 0, "overflow");
> +
> +	error_payload_set_uint(&p, "key2", 100);
> +	ok(error_payload_get_int(&p, "key2", &val) && val == 100, "conversion");
> +
> +	error_payload_set_int(&p, "key2", INT64_MIN);
> +	ok(error_payload_get_int(&p, "key2", &val) && val == INT64_MIN,
> +	   "min value");
> +
> +	error_payload_destroy(&p);
> +
> +	check_plan();
> +	footer();
> +}
> +
> +static void
> +test_payload_field_double(void)
> +{
> +	header();
> +	plan(13);
> +
> +	struct error_payload p;
> +	error_payload_create(&p);
> +	double val = 1;
> +	ok(!error_payload_get_double(&p, "key", &val) && val == 0,
> +	   "get_double() empty");
> +
> +	error_payload_set_double(&p, "key1", 1.5);
> +	is(p.count, 1, "++count");
> +	ok(error_payload_get_double(&p, "key1", &val), "get_double() finds");
> +	is(val, 1.5, "value match");
> +
> +	val = 1;
> +	ok(!error_payload_get_double(&p, "key2", &val), "get_double() fails");
> +	is(val, 0, "value is default");
> +
> +	is(error_payload_find(&p, "key1")->size, mp_sizeof_double(1.5), "size");
> +
> +	error_payload_set_double(&p, "key2", DBL_MAX);
> +	ok(error_payload_get_double(&p, "key1", &val) && val == 1.5, "value 1");
> +	ok(error_payload_get_double(&p, "key2", &val) && val == DBL_MAX,
> +	   "value 2");
> +
> +	error_payload_unset(&p, "key2");
> +	is(p.count, 1, "--count");
> +	ok(error_payload_get_double(&p, "key1", &val) && val == 1.5,
> +	   "remained value");
> +	ok(!error_payload_get_double(&p, "key2", &val) && val == 0,
> +	   "deleted value");
> +
> +	error_payload_set_str(&p, "key2", "1");
> +	ok(!error_payload_get_double(&p, "key2", &val) && val == 0,
> +	   "wrong type");
> +
> +	error_payload_destroy(&p);
> +
> +	check_plan();
> +	footer();
> +}
> +
> +static void
> +test_payload_field_bool(void)
> +{
> +	header();
> +	plan(13);
> +
> +	struct error_payload p;
> +	error_payload_create(&p);
> +	bool val = true;
> +	ok(!error_payload_get_bool(&p, "key", &val) && !val,
> +	   "get_bool() empty");
> +
> +	error_payload_set_bool(&p, "key1", true);
> +	is(p.count, 1, "++count");
> +	ok(error_payload_get_bool(&p, "key1", &val), "get_bool() finds");
> +	ok(val, "value match");
> +
> +	val = true;
> +	ok(!error_payload_get_bool(&p, "key2", &val), "get_bool() fails");
> +	ok(!val, "value is default");
> +
> +	error_payload_set_bool(&p, "key2", false);
> +	ok(error_payload_get_bool(&p, "key2", &val), "get_bool() finds");
> +	ok(!val, "value match");
> +
> +	is(error_payload_find(&p, "key1")->size, mp_sizeof_bool(true), "size");
> +
> +	error_payload_unset(&p, "key2");
> +	is(p.count, 1, "--count");
> +	ok(error_payload_get_bool(&p, "key1", &val) && val, "remained value");
> +	ok(!error_payload_get_bool(&p, "key2", &val) && !val, "deleted value");
> +
> +	error_payload_set_str(&p, "key2", "true");
> +	ok(!error_payload_get_bool(&p, "key2", &val) && !val, "wrong type");
> +
> +	error_payload_destroy(&p);
> +
> +	check_plan();
> +	footer();
> +}
> +
> +static void
> +test_payload_field_uuid(void)
> +{
> +	header();
> +	plan(17);
> +
> +	struct error_payload p;
> +	error_payload_create(&p);
> +	struct tt_uuid val1;
> +	tt_uuid_create(&val1);
> +	ok(!error_payload_get_uuid(&p, "key", &val1), "get_uuid() empty");
> +	ok(tt_uuid_is_nil(&val1), "default value");
> +
> +	tt_uuid_create(&val1);
> +	error_payload_set_uuid(&p, "key1", &val1);
> +	is(p.count, 1, "++count");
> +	struct tt_uuid val2;
> +	ok(error_payload_get_uuid(&p, "key1", &val2), "get_uuid() finds");
> +	ok(tt_uuid_is_equal(&val1, &val2), "value match");
> +
> +	ok(!error_payload_get_uuid(&p, "key2", &val2), "get_uuid() fails");
> +	ok(tt_uuid_is_nil(&val2), "value is default");
> +
> +	tt_uuid_create(&val2);
> +	error_payload_set_uuid(&p, "key2", &val2);
> +	struct tt_uuid val3;
> +	ok(error_payload_get_uuid(&p, "key2", &val3), "get_uuid() finds");
> +	ok(tt_uuid_is_equal(&val3, &val2), "value match");
> +
> +	is(error_payload_find(&p, "key1")->size, mp_sizeof_uuid(), "size");
> +
> +	error_payload_unset(&p, "key2");
> +	is(p.count, 1, "--count");
> +	ok(error_payload_get_uuid(&p, "key1", &val3), "remained value");
> +	ok(tt_uuid_is_equal(&val1, &val3), "value match");
> +	ok(!error_payload_get_uuid(&p, "key2", &val3), "deleted value");
> +	ok(tt_uuid_is_nil(&val3), "value match");
> +
> +	error_payload_set_str(&p, "key2", "1");
> +	ok(!error_payload_get_uuid(&p, "key2", &val3), "wrong type");
> +	ok(tt_uuid_is_nil(&val3), "value match");
> +
> +	error_payload_destroy(&p);
> +
> +	check_plan();
> +	footer();
> +}
> +
> +static void
> +test_payload_field_mp(void)
> +{
> +	header();
> +	plan(6);
> +	char buf[1024];
> +	char *data;
> +	const char *cdata;
> +	uint32_t size;
> +
> +	struct error_payload p;
> +	error_payload_create(&p);
> +
> +	data = mp_encode_str(buf, "value1", 6);
> +	error_payload_set_mp(&p, "key1", buf, data - buf);
> +	is(strcmp(error_payload_get_str(&p, "key1"), "value1"), 0, "mp str");
> +
> +	cdata = error_payload_get_mp(&p, "key1", &size);
> +	is(memcmp(cdata, buf, size), 0, "mp str cmp");
> +
> +	data = mp_encode_uint(buf, 100);
> +	error_payload_set_mp(&p, "key2", buf, data - buf);
> +	uint64_t val;
> +	ok(error_payload_get_uint(&p, "key2", &val) && val == 100, "mp uint");
> +
> +	cdata = error_payload_get_mp(&p, "key2", &size);
> +	is(memcmp(cdata, buf, size), 0, "mp uint cmp");
> +
> +	data = mp_encode_array(buf, 1);
> +	data = mp_encode_uint(data, 2);
> +	error_payload_set_mp(&p, "key3", buf, data - buf);
> +
> +	cdata = error_payload_get_mp(&p, "key3", &size);
> +	is(memcmp(cdata, buf, size), 0, "mp array");
> +
> +	ok(!error_payload_get_uint(&p, "key3", &val) && val == 0,
> +	   "mp uint from array");
> +
> +	error_payload_destroy(&p);
> +
> +	check_plan();
> +	footer();
> +}
> +
> +static void
> +test_payload_unset(void)
> +{
> +	header();
> +	plan(13);
> +
> +	struct error_payload p;
> +	error_payload_create(&p);
> +
> +	error_payload_set_uint(&p, "key1", 1);
> +	error_payload_set_uint(&p, "key2", 2);
> +	error_payload_set_uint(&p, "key3", 3);
> +	error_payload_set_uint(&p, "key4", 4);
> +	error_payload_set_uint(&p, "key5", 5);
> +
> +	error_payload_unset(&p, "key5");
> +	is(p.count, 4, "unset last, count");
> +	is(error_payload_find(&p, "key5"), NULL, "unset last, key");
> +
> +	error_payload_unset(&p, "key1");
> +	is(p.count, 3, "unset first, count");
> +	is(error_payload_find(&p, "key1"), NULL, "unset first, key");
> +
> +	uint64_t val;
> +	ok(error_payload_get_uint(&p, "key2", &val) && val == 2, "check key2");
> +	ok(error_payload_get_uint(&p, "key3", &val) && val == 3, "check key3");
> +	ok(error_payload_get_uint(&p, "key4", &val) && val == 4, "check key4");
> +
> +	is(strcmp(p.fields[0]->name, "key4"), 0, "deletion is cyclic");
> +
> +	error_payload_unset(&p, "key2");
> +	is(p.count, 2, "unset middle, count");
> +	is(error_payload_find(&p, "key2"), NULL, "unset middle, key");
> +	ok(error_payload_get_uint(&p, "key3", &val) && val == 3, "check key3");
> +	ok(error_payload_get_uint(&p, "key4", &val) && val == 4, "check key4");
> +
> +	error_payload_unset(&p, "key3");
> +	error_payload_unset(&p, "key4");
> +
> +	is(p.count, 0, "unset all");
> +
> +	error_payload_destroy(&p);
> +
> +	check_plan();
> +	footer();
> +}
> +
> +static void
> +test_payload_move(void)
> +{
> +	header();
> +	plan(7);
> +
> +	struct error_payload p1, p2;
> +	error_payload_create(&p1);
> +	error_payload_create(&p2);
> +
> +	error_payload_move(&p1, &p2);
> +	ok(p1.count == 0 && p1.fields == NULL, "empty");
> +
> +	error_payload_set_str(&p1, "key", "value");
> +	error_payload_move(&p1, &p2);
> +	ok(p1.count == 0 && p1.fields == NULL, "emptied on move");
> +
> +	error_payload_set_str(&p1, "key", "value");
> +	error_payload_set_str(&p2, "key1", "value1");
> +	error_payload_set_str(&p2, "key2", "value2");
> +	error_payload_move(&p1, &p2);
> +	is(p1.count, 2, "got 2 fields");
> +	isnt(p1.fields, NULL, "got 2 fields");
> +	is(strcmp(error_payload_get_str(&p1, "key1"), "value1"), 0, "key1");
> +	is(strcmp(error_payload_get_str(&p1, "key2"), "value2"), 0, "key2");
> +	is(error_payload_get_str(&p1, "key"), NULL, "key");
> +
> +	error_payload_destroy(&p2);
> +	error_payload_destroy(&p1);
> +
> +	check_plan();
> +	footer();
> +}
> +
> +int
> +main(void)
> +{
> +	header();
> +	plan(9);
> +
> +	random_init();
> +
> +	test_payload_field_str();
> +	test_payload_field_uint();
> +	test_payload_field_int();
> +	test_payload_field_double();
> +	test_payload_field_bool();
> +	test_payload_field_uuid();
> +	test_payload_field_mp();
> +	test_payload_unset();
> +	test_payload_move();
> +
> +	random_free();
> +
> +	footer();
> +	return check_plan();
> +}
> diff --git a/test/unit/error.result b/test/unit/error.result
> new file mode 100644
> index 000000000..f0c4266e6
> --- /dev/null
> +++ b/test/unit/error.result
> @@ -0,0 +1,160 @@
> +	*** main ***
> +1..9
> +	*** test_payload_field_str ***
> +    1..15
> +    ok 1 - no fields in the beginning
> +    ok 2 - get_str() empty
> +    ok 3 - ++count
> +    ok 4 - get_str() finds
> +    ok 5 - ++count
> +    ok 6 - get_str() finds old
> +    ok 7 - get_str() finds new
> +    ok 8 - size does not include terminating zero
> +    ok 9 - field count is same
> +    ok 10 - get_str() finds new value
> +    ok 11 - get_str() finds other key old value
> +    ok 12 - --count
> +    ok 13 - get_str() finds new value
> +    ok 14 - get_str() can't find deleted value
> +    ok 15 - wrong type
> +ok 1 - subtests
> +	*** test_payload_field_str: done ***
> +	*** test_payload_field_uint ***
> +    1..17
> +    ok 1 - get_uint() empty
> +    ok 2 - ++count
> +    ok 3 - get_uint() finds
> +    ok 4 - value match
> +    ok 5 - get_uint() fails
> +    ok 6 - value is default
> +    ok 7 - small number size
> +    ok 8 - get_uint() 32 bit
> +    ok 9 - value match
> +    ok 10 - middle number size
> +    ok 11 - field count is same
> +    ok 12 - value 1
> +    ok 13 - value 2
> +    ok 14 - --count
> +    ok 15 - remained value
> +    ok 16 - deleted value
> +    ok 17 - wrong type
> +ok 2 - subtests
> +	*** test_payload_field_uint: done ***
> +	*** test_payload_field_int ***
> +    1..20
> +    ok 1 - get_int() empty
> +    ok 2 - ++count
> +    ok 3 - get_int() finds
> +    ok 4 - value match
> +    ok 5 - get_int() fails
> +    ok 6 - value is default
> +    ok 7 - small number size
> +    ok 8 - get_int() 32 bit
> +    ok 9 - value match
> +    ok 10 - middle number size
> +    ok 11 - field count is same
> +    ok 12 - value 1
> +    ok 13 - value 2
> +    ok 14 - --count
> +    ok 15 - remained value
> +    ok 16 - deleted value
> +    ok 17 - wrong type
> +    ok 18 - overflow
> +    ok 19 - conversion
> +    ok 20 - min value
> +ok 3 - subtests
> +	*** test_payload_field_int: done ***
> +	*** test_payload_field_double ***
> +    1..13
> +    ok 1 - get_double() empty
> +    ok 2 - ++count
> +    ok 3 - get_double() finds
> +    ok 4 - value match
> +    ok 5 - get_double() fails
> +    ok 6 - value is default
> +    ok 7 - size
> +    ok 8 - value 1
> +    ok 9 - value 2
> +    ok 10 - --count
> +    ok 11 - remained value
> +    ok 12 - deleted value
> +    ok 13 - wrong type
> +ok 4 - subtests
> +	*** test_payload_field_double: done ***
> +	*** test_payload_field_bool ***
> +    1..13
> +    ok 1 - get_bool() empty
> +    ok 2 - ++count
> +    ok 3 - get_bool() finds
> +    ok 4 - value match
> +    ok 5 - get_bool() fails
> +    ok 6 - value is default
> +    ok 7 - get_bool() finds
> +    ok 8 - value match
> +    ok 9 - size
> +    ok 10 - --count
> +    ok 11 - remained value
> +    ok 12 - deleted value
> +    ok 13 - wrong type
> +ok 5 - subtests
> +	*** test_payload_field_bool: done ***
> +	*** test_payload_field_uuid ***
> +    1..17
> +    ok 1 - get_uuid() empty
> +    ok 2 - default value
> +    ok 3 - ++count
> +    ok 4 - get_uuid() finds
> +    ok 5 - value match
> +    ok 6 - get_uuid() fails
> +    ok 7 - value is default
> +    ok 8 - get_uuid() finds
> +    ok 9 - value match
> +    ok 10 - size
> +    ok 11 - --count
> +    ok 12 - remained value
> +    ok 13 - value match
> +    ok 14 - deleted value
> +    ok 15 - value match
> +    ok 16 - wrong type
> +    ok 17 - value match
> +ok 6 - subtests
> +	*** test_payload_field_uuid: done ***
> +	*** test_payload_field_mp ***
> +    1..6
> +    ok 1 - mp str
> +    ok 2 - mp str cmp
> +    ok 3 - mp uint
> +    ok 4 - mp uint cmp
> +    ok 5 - mp array
> +    ok 6 - mp uint from array
> +ok 7 - subtests
> +	*** test_payload_field_mp: done ***
> +	*** test_payload_unset ***
> +    1..13
> +    ok 1 - unset last, count
> +    ok 2 - unset last, key
> +    ok 3 - unset first, count
> +    ok 4 - unset first, key
> +    ok 5 - check key2
> +    ok 6 - check key3
> +    ok 7 - check key4
> +    ok 8 - deletion is cyclic
> +    ok 9 - unset middle, count
> +    ok 10 - unset middle, key
> +    ok 11 - check key3
> +    ok 12 - check key4
> +    ok 13 - unset all
> +ok 8 - subtests
> +	*** test_payload_unset: done ***
> +	*** test_payload_move ***
> +    1..7
> +    ok 1 - empty
> +    ok 2 - emptied on move
> +    ok 3 - got 2 fields
> +    ok 4 - got 2 fields
> +    ok 5 - key1
> +    ok 6 - key2
> +    ok 7 - key
> +ok 9 - subtests
> +	*** test_payload_move: done ***
> +	*** main: done ***

-- 
Serge Petrenko


  reply	other threads:[~2021-11-08 15:14 UTC|newest]

Thread overview: 24+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2021-11-05 23:56 [Tarantool-patches] [PATCH 0/9] ER_READONLY reason Vladislav Shpilevoy via Tarantool-patches
2021-11-05 23:56 ` [Tarantool-patches] [PATCH 1/9] diag: return created error from diag_set() Vladislav Shpilevoy via Tarantool-patches
2021-11-05 23:56 ` [Tarantool-patches] [PATCH 2/9] error: introduce error_payload Vladislav Shpilevoy via Tarantool-patches
2021-11-08 15:14   ` Serge Petrenko via Tarantool-patches [this message]
2021-11-11 23:50     ` Vladislav Shpilevoy via Tarantool-patches
2021-11-12  6:29       ` Serge Petrenko via Tarantool-patches
2021-11-05 23:56 ` [Tarantool-patches] [PATCH 3/9] error: move code to struct error from ClientError Vladislav Shpilevoy via Tarantool-patches
2021-11-08 15:15   ` Serge Petrenko via Tarantool-patches
2021-11-11 23:50     ` Vladislav Shpilevoy via Tarantool-patches
2021-11-12  6:31       ` Serge Petrenko via Tarantool-patches
2021-11-05 23:56 ` [Tarantool-patches] [PATCH 4/9] error: use error_payload to store optional members Vladislav Shpilevoy via Tarantool-patches
2021-11-05 23:56 ` [Tarantool-patches] [PATCH 5/9] error: use error_payload in MessagePack codecs Vladislav Shpilevoy via Tarantool-patches
2021-11-05 23:56 ` [Tarantool-patches] [PATCH 6/9] error: use error_payload in Lua Vladislav Shpilevoy via Tarantool-patches
2021-11-05 23:56 ` [Tarantool-patches] [PATCH 7/9] luatest: copy config in cluster:build_server() Vladislav Shpilevoy via Tarantool-patches
2021-11-05 23:56 ` [Tarantool-patches] [PATCH 8/9] luatest: add new helpers for 'server' object Vladislav Shpilevoy via Tarantool-patches
2021-11-08 15:16   ` Serge Petrenko via Tarantool-patches
2021-11-11 23:51     ` Vladislav Shpilevoy via Tarantool-patches
2021-11-05 23:56 ` [Tarantool-patches] [PATCH 9/9] box: enrich ER_READONLY with new details Vladislav Shpilevoy via Tarantool-patches
2021-11-06 19:30   ` Cyrill Gorcunov via Tarantool-patches
2021-11-07 16:45     ` Vladislav Shpilevoy via Tarantool-patches
2021-11-07 20:19       ` Cyrill Gorcunov via Tarantool-patches
2021-11-08 15:18   ` Serge Petrenko via Tarantool-patches
2021-11-11 23:52     ` Vladislav Shpilevoy via Tarantool-patches
2021-11-08 14:25 ` [Tarantool-patches] [PATCH 0/9] ER_READONLY reason Vladimir Davydov via Tarantool-patches

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=68ebfc9b-5e2c-91c0-106a-8c309a31c1b3@tarantool.org \
    --to=tarantool-patches@dev.tarantool.org \
    --cc=sergepetrenko@tarantool.org \
    --cc=v.shpilevoy@tarantool.org \
    --cc=vdavydov@tarantool.org \
    --subject='Re: [Tarantool-patches] [PATCH 2/9] error: introduce error_payload' \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox