blob: 6ce6da5172ae0fccae289392c940cb9cde2c7b2d [file]
/*****************************************************************************\
* json_patch-test.c - Unit tests for the serializer/json-patch plugin
*****************************************************************************
* Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
*
* This file is part of Slurm, a resource management program.
* For details, see <https://slurm.schedmd.com/>.
* Please also read the included file: DISCLAIMER.
*
* Slurm is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at your option)
* any later version.
*
* In addition, as a special exception, the copyright holders give permission
* to link the code of portions of this program with the OpenSSL library under
* certain conditions as described in each individual source file, and
* distribute linked combinations including the two. You must obey the GNU
* General Public License in all respects for all of the code used other than
* OpenSSL. If you modify file(s) with this exception, you may extend this
* exception to your version of the file(s), but you are not obligated to do
* so. If you do not wish to do so, delete this exception statement from your
* version. If you delete this exception statement from all source files in
* the program, then also delete it here.
*
* You should have received a copy of the GNU General Public License along
* with Slurm; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
\*****************************************************************************/
#include <check.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "slurm/slurm_errno.h"
#include "src/common/data.h"
#include "src/common/kv.h"
#include "src/common/log.h"
#include "src/common/macros.h"
#include "src/common/read_config.h"
#include "src/common/xmalloc.h"
#include "src/common/xstring.h"
#include "src/interfaces/serializer.h"
#include "scheck.h"
static void _setup(void)
{
/* plugins load from the installed Slurm's default plugin dir */
ck_assert(!serializer_g_init());
}
static void _teardown(void)
{
serializer_g_fini();
}
static data_t *_from_json(const char *str)
{
data_t *d = NULL;
ck_assert(!serialize_g_string_to_data(&d, str, strlen(str),
MIME_TYPE_JSON));
ck_assert(d);
return d;
}
/* patch applies, result matches expected */
static void _check_apply(const char *target, const char *patch,
const char *expected)
{
data_t *doc = _from_json(target);
data_t *want = _from_json(expected);
data_t *ops = _from_json(patch);
int expect_rc = (data_get_list_length(ops) &&
data_check_match(doc, want, true)) ?
SLURM_NO_CHANGE_IN_DATA :
SLURM_SUCCESS;
int rc = serialize_g_string_to_data(&doc, patch, strlen(patch),
MIME_TYPE_JSON_PATCH);
ck_assert_msg(rc == expect_rc,
"patch %s on %s: rc %d (%s), expected %d (%s)", patch,
target, rc, slurm_strerror(rc), expect_rc,
slurm_strerror(expect_rc));
ck_assert_msg(data_check_match(doc, want, true),
"patch %s on %s gave an unexpected result", patch,
target);
FREE_NULL_DATA(ops);
FREE_NULL_DATA(want);
FREE_NULL_DATA(doc);
}
/* patch is rejected with expect_rc and the target is left unchanged */
static void _check_fail(const char *target, const char *patch, int expect_rc)
{
data_t *doc = _from_json(target);
data_t *before = _from_json(target);
int rc = serialize_g_string_to_data(&doc, patch, strlen(patch),
MIME_TYPE_JSON_PATCH);
ck_assert_msg(rc == expect_rc,
"patch %s on %s: rc %d (%s), expected %d (%s)", patch,
target, rc, slurm_strerror(rc), expect_rc,
slurm_strerror(expect_rc));
/* RFC 6902 section 5: a failed patch changes nothing */
ck_assert_msg(data_check_match(doc, before, true),
"failed patch %s on %s still changed the document", patch,
target);
FREE_NULL_DATA(before);
FREE_NULL_DATA(doc);
}
START_TEST(test_dict_ops)
{
_check_apply("{\"a\":1,\"c\":3}",
"[{\"op\":\"add\",\"path\":\"/b\",\"value\":2},"
"{\"op\":\"replace\",\"path\":\"/a\",\"value\":10},"
"{\"op\":\"remove\",\"path\":\"/c\"}]",
"{\"a\":10,\"b\":2}");
}
END_TEST
START_TEST(test_list_ops)
{
/* insert into the middle, append with "-", then remove the head */
_check_apply("{\"l\":[1,3]}",
"[{\"op\":\"add\",\"path\":\"/l/1\",\"value\":2},"
"{\"op\":\"add\",\"path\":\"/l/-\",\"value\":4},"
"{\"op\":\"remove\",\"path\":\"/l/0\"}]",
"{\"l\":[2,3,4]}");
}
END_TEST
START_TEST(test_pointer_escapes)
{
/* "~1" is '/' and "~0" is '~' */
_check_apply("{\"a/b\":1,\"~x\":2}",
"[{\"op\":\"replace\",\"path\":\"/a~1b\",\"value\":9},"
"{\"op\":\"replace\",\"path\":\"/~0x\",\"value\":8}]",
"{\"a/b\":9,\"~x\":8}");
}
END_TEST
START_TEST(test_move_copy)
{
_check_apply("{\"a\":{\"x\":1},\"b\":2}",
"[{\"op\":\"move\",\"from\":\"/a\",\"path\":\"/c\"},"
"{\"op\":\"copy\",\"from\":\"/b\",\"path\":\"/d\"}]",
"{\"b\":2,\"c\":{\"x\":1},\"d\":2}");
}
END_TEST
START_TEST(test_copy_same_array_index)
{
_check_apply("[1,2]",
"[{\"op\":\"copy\",\"from\":\"/0\",\"path\":\"/0\"}]",
"[1,1,2]");
_check_apply("[1,2]",
"[{\"op\":\"move\",\"from\":\"/0\",\"path\":\"/0\"}]",
"[1,2]");
_check_apply("{\"a\":1}",
"[{\"op\":\"copy\",\"from\":\"/a\",\"path\":\"/a\"}]",
"{\"a\":1}");
}
END_TEST
START_TEST(test_noop_patch)
{
/* empty operation lists succeed without SLURM_NO_CHANGE_IN_DATA */
_check_apply("{\"a\":1}", "[]", "{\"a\":1}");
_check_apply("{\"a\":1}", "[ \n ]", "{\"a\":1}");
/* replacing a value with itself changes nothing */
_check_apply("{\"a\":{\"b\":[1,2]}}",
"[{\"op\":\"replace\",\"path\":\"/a/b\",\"value\":[1,2]}]",
"{\"a\":{\"b\":[1,2]}}");
/* an add undone by a remove nets no change */
_check_apply("{\"a\":1}",
"[{\"op\":\"add\",\"path\":\"/b\",\"value\":2},"
"{\"op\":\"remove\",\"path\":\"/b\"}]",
"{\"a\":1}");
/* a test-only patch changes nothing */
_check_apply("{\"a\":1}",
"[{\"op\":\"test\",\"path\":\"/a\",\"value\":1}]",
"{\"a\":1}");
/* a nonempty no-op does not replace the document's trees */
{
data_t *doc = _from_json("{\"a\":{\"b\":1}}");
data_t *child = data_key_get(doc, "a");
const char *patch =
"[{\"op\":\"test\",\"path\":\"/a/b\",\"value\":1}]";
int rc = serialize_g_string_to_data(&doc, patch, strlen(patch),
MIME_TYPE_JSON_PATCH);
ck_assert_int_eq(rc, SLURM_NO_CHANGE_IN_DATA);
ck_assert_ptr_eq(data_key_get(doc, "a"), child);
FREE_NULL_DATA(doc);
}
}
END_TEST
START_TEST(test_test_op)
{
/* a matching test gates the replace */
_check_apply("{\"a\":1}",
"[{\"op\":\"test\",\"path\":\"/a\",\"value\":1},"
"{\"op\":\"replace\",\"path\":\"/a\",\"value\":2}]",
"{\"a\":2}");
/* an integer matches a float holding the same JSON number */
_check_apply("{\"n\":2}",
"[{\"op\":\"test\",\"path\":\"/n\",\"value\":2.0}]",
"{\"n\":2}");
/* a failed test fails the whole patch and changes nothing */
_check_fail("{\"a\":1}",
"[{\"op\":\"replace\",\"path\":\"/a\",\"value\":9},"
"{\"op\":\"test\",\"path\":\"/a\",\"value\":1}]",
ESLURM_JSON_PATCH_TEST_FAILED);
}
END_TEST
START_TEST(test_operation_iteration)
{
/* RFC 6902 sections 4.1 through 4.6, in order. */
static const char *operations[] = {
"{\"op\":\"add\",\"path\":\"/c\",\"value\":3}",
"{\"op\":\"remove\",\"path\":\"/a\"}",
"{\"op\":\"replace\",\"path\":\"/a\",\"value\":4}",
"{\"op\":\"move\",\"from\":\"/a\",\"path\":\"/c\"}",
"{\"op\":\"copy\",\"from\":\"/a\",\"path\":\"/c\"}",
"{\"op\":\"test\",\"path\":\"/a\",\"value\":1}",
};
static const int errors[ARRAY_SIZE(operations)] = {
SLURM_SUCCESS,
ESLURM_DATA_PATH_NOT_FOUND,
ESLURM_DATA_PATH_NOT_FOUND,
ESLURM_DATA_PATH_NOT_FOUND,
ESLURM_DATA_PATH_NOT_FOUND,
ESLURM_JSON_PATCH_TEST_FAILED,
};
/* Rows are first operations; columns are second. NULL means rollback. */
static const char *expected[][ARRAY_SIZE(operations)] = {
/* add */
{
"{\"a\":1,\"b\":2,\"c\":3}",
"{\"b\":2,\"c\":3}",
"{\"a\":4,\"b\":2,\"c\":3}",
"{\"b\":2,\"c\":1}",
"{\"a\":1,\"b\":2,\"c\":1}",
"{\"a\":1,\"b\":2,\"c\":3}",
},
/* remove */
{ "{\"b\":2,\"c\":3}" },
/* replace */
{
"{\"a\":4,\"b\":2,\"c\":3}",
"{\"b\":2}",
"{\"a\":4,\"b\":2}",
"{\"b\":2,\"c\":4}",
"{\"a\":4,\"b\":2,\"c\":4}",
},
/* move */
{ "{\"b\":2,\"c\":3}" },
/* copy */
{
"{\"a\":1,\"b\":2,\"c\":3}",
"{\"b\":2,\"c\":1}",
"{\"a\":4,\"b\":2,\"c\":1}",
"{\"b\":2,\"c\":1}",
"{\"a\":1,\"b\":2,\"c\":1}",
"{\"a\":1,\"b\":2,\"c\":1}",
},
/* test */
{
"{\"a\":1,\"b\":2,\"c\":3}",
"{\"b\":2}",
"{\"a\":4,\"b\":2}",
"{\"b\":2,\"c\":1}",
"{\"a\":1,\"b\":2,\"c\":1}",
"{\"a\":1,\"b\":2}",
},
};
ck_assert_uint_eq(ARRAY_SIZE(expected), ARRAY_SIZE(operations));
for (size_t i = 0; i < ARRAY_SIZE(operations); i++) {
for (size_t j = 0; j < ARRAY_SIZE(operations); j++) {
char *patch = xstrdup_printf("[%s,%s]", operations[i],
operations[j]);
if (expected[i][j])
_check_apply("{\"a\":1,\"b\":2}", patch,
expected[i][j]);
else
_check_fail("{\"a\":1,\"b\":2}", patch,
errors[j]);
xfree(patch);
}
}
_check_apply("{\"a\":1}", "[]", "{\"a\":1}");
_check_apply("{\"a\":1}",
"[{\"op\":\"replace\",\"path\":\"/a\",\"value\":2},"
"{\"op\":\"test\",\"path\":\"/a\",\"value\":2},"
"{\"op\":\"replace\",\"path\":\"/a\",\"value\":3}]",
"{\"a\":3}");
_check_fail("{\"a\":1}",
"[{\"op\":\"replace\",\"path\":\"/a\",\"value\":2},"
"{\"op\":\"remove\",\"path\":\"/missing\"},"
"{\"op\":\"bogus\",\"path\":\"/a\"}]",
ESLURM_DATA_PATH_NOT_FOUND);
}
END_TEST
START_TEST(test_root_pointer)
{
/* "" addresses the whole document (RFC 6901 section 5) */
_check_apply("{\"a\":1}",
"[{\"op\":\"add\",\"path\":\"\",\"value\":{\"b\":2}}]",
"{\"b\":2}");
_check_apply("{\"a\":1}",
"[{\"op\":\"replace\",\"path\":\"\",\"value\":[1]}]",
"[1]");
_check_apply("{\"a\":1}",
"[{\"op\":\"test\",\"path\":\"\",\"value\":{\"a\":1}}]",
"{\"a\":1}");
_check_apply("{\"a\":1}",
"[{\"op\":\"copy\",\"from\":\"\",\"path\":\"/z\"}]",
"{\"a\":1,\"z\":{\"a\":1}}");
/* removing the whole document is not possible */
_check_fail("{\"a\":1}", "[{\"op\":\"remove\",\"path\":\"\"}]",
ESLURM_JSON_PATCH_FAILED);
}
END_TEST
START_TEST(test_invalid_patches)
{
/* patch document must be an array of operations */
_check_fail("{\"a\":1}", "{}", ESLURM_DATA_EXPECTED_LIST);
/* unknown op */
_check_fail("{\"a\":1}", "[{\"op\":\"bogus\",\"path\":\"/a\"}]",
ESLURM_JSON_PATCH_INVALID);
/* path must be empty or begin with '/' */
_check_fail("{\"a\":1}",
"[{\"op\":\"add\",\"path\":\"a\",\"value\":1}]",
ESLURM_JSON_POINTER_INVALID);
/* removing a missing entry */
_check_fail("{\"a\":1}", "[{\"op\":\"remove\",\"path\":\"/nope\"}]",
ESLURM_DATA_PATH_NOT_FOUND);
}
END_TEST
START_TEST(test_invalid_from)
{
static const char *ops[] = { "move", "copy" };
static const char *from[] = {
"",
",\"from\":null",
",\"from\":1",
",\"from\":true",
",\"from\":{}",
",\"from\":[]",
};
for (size_t i = 0; i < ARRAY_SIZE(ops); i++) {
for (size_t j = 0; j < ARRAY_SIZE(from); j++) {
char *patch = xstrdup_printf(
"[{\"op\":\"%s\",\"path\":\"/b\"%s}]", ops[i],
from[j]);
_check_fail("{\"a\":1}", patch,
ESLURM_JSON_PATCH_INVALID);
xfree(patch);
}
}
}
END_TEST
START_TEST(test_external_tool)
{
const char *target = "{\"a\":1,\"l\":[1,2,3]}";
const char *patch = "[{\"op\":\"replace\",\"path\":\"/a\",\"value\":2},"
"{\"op\":\"remove\",\"path\":\"/l/1\"}]";
char *dir = NULL;
char cmd[4096], buf[64 * 1024];
data_t *doc, *want;
FILE *fp;
int rc;
/* cross-check needs python3 with the jsonpatch module */
if (system("python3 -c 'import jsonpatch' >/dev/null 2>&1")) {
printf("%s: python3 jsonpatch module not found, skipping\n",
__func__);
return;
}
doc = _from_json(target);
rc = serialize_g_string_to_data(&doc, patch, strlen(patch),
MIME_TYPE_JSON_PATCH);
ck_assert_msg(!rc, "patch failed: %s", slurm_strerror(rc));
/* apply the same patch with the external tool */
ck_assert((dir = scheck_mkdtemp("json_patch")));
scheck_write_file(dir, "target.json", target);
scheck_write_file(dir, "patch.json", patch);
snprintf(cmd, sizeof(cmd),
"python3 -c 'import jsonpatch, json, sys;"
" d = json.load(open(sys.argv[1]));"
" p = json.load(open(sys.argv[2]));"
" print(json.dumps(jsonpatch.apply_patch(d, p)))'"
" %s/target.json %s/patch.json",
dir, dir);
ck_assert((fp = popen(cmd, "r")));
ck_assert(fgets(buf, sizeof(buf), fp));
ck_assert(!pclose(fp));
want = _from_json(buf);
ck_assert_msg(data_check_match(doc, want, true),
"plugin result differs from external tool: %s", buf);
xfree(dir);
FREE_NULL_DATA(want);
FREE_NULL_DATA(doc);
}
END_TEST
START_TEST(test_openapi_mime_types)
{
const char **all = get_mime_type_array(false);
const char **openapi = get_mime_type_array(true);
bool found = false;
for (int i = 0; all[i]; i++) {
if (!xstrcmp(all[i], MIME_TYPE_JSON_PATCH))
found = true;
}
ck_assert_msg(found, "JSON Patch must remain registered");
ck_assert_ptr_nonnull(resolve_mime_type(MIME_TYPE_JSON_PATCH, NULL));
for (int i = 0; openapi[i]; i++) {
ck_assert_str_ne(openapi[i], MIME_TYPE_JSON_PATCH);
ck_assert_str_ne(openapi[i], MIME_TYPE_URL_ENCODED);
}
}
END_TEST
START_TEST(test_no_patch_creation)
{
data_t *doc = _from_json("{\"a\":1}");
data_t *null_doc = NULL;
char *out = NULL;
size_t len = 0;
const char *patch = "[{\"op\":\"remove\",\"path\":\"/a\"}]";
int rc;
/* creating a patch is not supported */
rc = serialize_g_data_to_string(&out, &len, doc, MIME_TYPE_JSON_PATCH,
SER_FLAGS_COMPACT);
ck_assert_msg(rc == ESLURM_NOT_SUPPORTED,
"dump gave rc %d (%s), expected %d (%s)", rc,
slurm_strerror(rc), ESLURM_NOT_SUPPORTED,
slurm_strerror(ESLURM_NOT_SUPPORTED));
ck_assert(!out);
/* a patch needs the target document in dest */
rc = serialize_g_string_to_data(&null_doc, patch, strlen(patch),
MIME_TYPE_JSON_PATCH);
ck_assert_msg(rc, "a NULL target must be rejected");
ck_assert(!null_doc);
FREE_NULL_DATA(doc);
}
END_TEST
START_TEST(test_kv_clear)
{
data_t *src = (_i % 2) ? data_new() : NULL;
data_t *dst = _from_json("{\"a\":1}");
kv_op_t op = (_i / 2) ? KV_OP_PATCH : KV_OP_SET;
ck_assert_int_eq(kv_op_apply(src, op, &dst, &op, NULL), SLURM_SUCCESS);
ck_assert_ptr_null(dst);
ck_assert_int_eq(op, KV_OP_NONE);
FREE_NULL_DATA(src);
}
END_TEST
START_TEST(test_kv_patch_alias)
{
data_t *kv =
_from_json("[{\"op\":\"add\",\"path\":\"/a\",\"value\":1}]");
kv_op_t op = KV_OP_PATCH;
data_t *before;
ck_assert_int_eq(kv_op_apply(kv, op, &kv, &op, NULL), SLURM_SUCCESS);
ck_assert_int_eq(op, KV_OP_SET);
ck_assert_int_eq(data_get_int(data_key_get(kv, "a")), 1);
FREE_NULL_DATA(kv);
kv = _from_json("[{\"op\":\"replace\",\"path\":\"\",\"value\":7}]");
op = KV_OP_PATCH;
before = kv;
ck_assert_int_eq(kv_op_apply(kv, op, &kv, &op, NULL),
ESLURM_KV_NOT_DICT);
ck_assert_ptr_eq(kv, before);
ck_assert_int_eq(op, KV_OP_PATCH);
FREE_NULL_DATA(kv);
}
END_TEST
START_TEST(test_kv_result_operation)
{
data_t *dst = NULL, *patch = _from_json("[]"), *before;
kv_op_t op = KV_OP_PATCH;
ck_assert_int_eq(kv_op_apply(patch, op, &dst, &op, NULL),
SLURM_SUCCESS);
ck_assert_ptr_null(dst);
ck_assert_int_eq(op, KV_OP_NONE);
FREE_NULL_DATA(patch);
patch = _from_json("[{\"op\":\"add\",\"path\":\"/a\",\"value\":1}]");
op = KV_OP_PATCH;
ck_assert_int_eq(kv_op_apply(patch, op, &dst, &op, NULL),
SLURM_SUCCESS);
ck_assert_int_eq(op, KV_OP_SET);
ck_assert_int_eq(data_get_int(data_key_get(dst, "a")), 1);
FREE_NULL_DATA(patch);
/* Source and destination document and operation may alias. */
ck_assert_int_eq(kv_op_apply(dst, op, &dst, &op, NULL), SLURM_SUCCESS);
ck_assert_int_eq(op, KV_OP_SET);
ck_assert_int_eq(data_get_int(data_key_get(dst, "a")), 1);
patch = _from_json("[{\"op\":\"test\",\"path\":\"/a\",\"value\":1}]");
op = KV_OP_PATCH;
ck_assert_int_eq(kv_op_apply(patch, op, &dst, &op, NULL),
SLURM_NO_CHANGE_IN_DATA);
ck_assert_int_eq(op, KV_OP_SET);
FREE_NULL_DATA(patch);
before = dst;
ck_assert_int_eq(kv_op_apply(NULL, KV_OP_NONE, &dst, &op, NULL),
SLURM_SUCCESS);
ck_assert_ptr_eq(dst, before);
ck_assert_int_eq(op, KV_OP_SET);
patch = _from_json("[{\"op\":\"replace\",\"path\":\"\",\"value\":7}]");
op = KV_OP_PATCH;
ck_assert_int_eq(kv_op_apply(patch, op, &dst, &op, NULL),
ESLURM_KV_NOT_DICT);
ck_assert_ptr_eq(dst, before);
ck_assert_int_eq(op, KV_OP_PATCH);
FREE_NULL_DATA(patch);
patch = _from_json("[{\"op\":\"remove\",\"path\":\"/a\"}]");
ck_assert_int_eq(kv_op_apply(patch, op, &dst, &op, NULL),
SLURM_SUCCESS);
ck_assert_ptr_null(dst);
ck_assert_int_eq(op, KV_OP_NONE);
FREE_NULL_DATA(patch);
dst = _from_json("{\"a\":1}");
ck_assert_int_eq(kv_op_apply(NULL, KV_OP_PATCH, &dst, NULL, NULL),
SLURM_SUCCESS);
ck_assert_ptr_null(dst);
}
END_TEST
START_TEST(test_kv_error_messages)
{
const char *notice = "job_submit notice";
char *err_msg = xstrdup(notice);
data_t *src = NULL, *dst = NULL;
ck_assert_int_eq(kv_op_apply(NULL, KV_OP_NONE, &dst, NULL, &err_msg),
SLURM_SUCCESS);
ck_assert_str_eq(err_msg, notice);
ck_assert_int_eq(kv_parse(KV_OP_SET, "[]", &src, &err_msg),
ESLURM_KV_NOT_DICT);
ck_assert_ptr_null(src);
ck_assert_ptr_eq(xstrstr(err_msg, notice), err_msg);
ck_assert_ptr_nonnull(xstrstr(err_msg, "\nKV must be a JSON object"));
xfree(err_msg);
err_msg = xstrdup(notice);
src = data_set_list(data_new());
ck_assert_int_eq(kv_op_apply(src, KV_OP_SET, &dst, NULL, &err_msg),
ESLURM_KV_NOT_DICT);
ck_assert_str_eq(err_msg,
"job_submit notice\nKV must be a JSON object");
FREE_NULL_DATA(src);
xfree(err_msg);
err_msg = xstrdup(notice);
dst = _from_json("{\"a\":1}");
src = _from_json("[{\"op\":\"test\",\"path\":\"/a\",\"value\":1}]");
ck_assert_int_eq(kv_op_apply(src, KV_OP_PATCH, &dst, NULL, &err_msg),
SLURM_NO_CHANGE_IN_DATA);
ck_assert_str_eq(err_msg, notice);
FREE_NULL_DATA(src);
src = _from_json("[{\"op\":\"remove\",\"path\":\"/missing\"}]");
ck_assert_int_eq(kv_op_apply(src, KV_OP_PATCH, &dst, NULL, &err_msg),
ESLURM_DATA_PATH_NOT_FOUND);
ck_assert_ptr_eq(xstrstr(err_msg, notice), err_msg);
ck_assert_ptr_nonnull(xstrstr(err_msg, "\nUnable to apply KVPatch:"));
FREE_NULL_DATA(src);
FREE_NULL_DATA(dst);
xfree(err_msg);
}
END_TEST
static Suite *_suite_json_patch(void)
{
Suite *s = suite_create("json-patch");
TCase *tc_core = tcase_create("json-patch");
tcase_add_checked_fixture(tc_core, _setup, _teardown);
tcase_add_test(tc_core, test_dict_ops);
tcase_add_test(tc_core, test_list_ops);
tcase_add_test(tc_core, test_pointer_escapes);
tcase_add_test(tc_core, test_root_pointer);
tcase_add_test(tc_core, test_move_copy);
tcase_add_test(tc_core, test_copy_same_array_index);
tcase_add_test(tc_core, test_noop_patch);
tcase_add_test(tc_core, test_test_op);
tcase_add_test(tc_core, test_operation_iteration);
tcase_add_test(tc_core, test_invalid_patches);
tcase_add_test(tc_core, test_invalid_from);
tcase_add_test(tc_core, test_external_tool);
tcase_add_test(tc_core, test_openapi_mime_types);
tcase_add_test(tc_core, test_no_patch_creation);
tcase_add_loop_test(tc_core, test_kv_clear, 0, 4);
tcase_add_test(tc_core, test_kv_patch_alias);
tcase_add_test(tc_core, test_kv_result_operation);
tcase_add_test(tc_core, test_kv_error_messages);
suite_add_tcase(s, tc_core);
return s;
}
int main(void)
{
int number_failed;
log_options_t log_opts = LOG_OPTS_INITIALIZER;
const char *debug_env = getenv("SLURM_DEBUG");
const char *debug_flags_env = getenv("SLURM_DEBUG_FLAGS");
if (debug_env)
log_opts.stderr_level = log_string2num(debug_env);
if (debug_flags_env)
debug_str2flags(debug_flags_env, &slurm_conf.debug_flags);
log_init("json-patch-test", log_opts, 0, NULL);
SRunner *sr = srunner_create(_suite_json_patch());
srunner_run_all(sr, CK_ENV);
number_failed = srunner_ntests_failed(sr);
srunner_free(sr);
log_fini();
return (number_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE;
}