/*
* This file is part of Cockpit.
*
* Copyright (C) 2014 Red Hat, Inc.
*
* Cockpit is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or
* (at your option) any later version.
*
* Cockpit is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Cockpit; If not, see .
*/
#include "config.h"
#include "cockpitjson.h"
#include "common/cockpittest.h"
#include
#include
static const gchar *test_data =
"{"
" \"string\": \"value\","
" \"number\": 55.4,"
" \"array\": [ \"one\", \"two\", \"three\" ],"
" \"object\": { \"test\": \"one\" },"
" \"bool\": true,"
" \"null\": null"
"}";
typedef struct {
JsonObject *root;
} TestCase;
static void
setup (TestCase *tc,
gconstpointer data)
{
GError *error = NULL;
JsonNode *node;
node = cockpit_json_parse (test_data, -1, &error);
g_assert_no_error (error);
g_assert (json_node_get_node_type (node) == JSON_NODE_OBJECT);
tc->root = json_node_dup_object (node);
json_node_free (node);
}
static void
teardown (TestCase *tc,
gconstpointer data)
{
json_object_unref (tc->root);
}
static void
test_get_string (TestCase *tc,
gconstpointer data)
{
gboolean ret;
const gchar *value;
ret = cockpit_json_get_string (tc->root, "string", NULL, &value);
g_assert (ret == TRUE);
g_assert_cmpstr (value, ==, "value");
ret = cockpit_json_get_string (tc->root, "string", NULL, NULL);
g_assert (ret == TRUE);
ret = cockpit_json_get_string (tc->root, "unknown", NULL, &value);
g_assert (ret == TRUE);
g_assert_cmpstr (value, ==, NULL);
ret = cockpit_json_get_string (tc->root, "unknown", "default", &value);
g_assert (ret == TRUE);
g_assert_cmpstr (value, ==, "default");
ret = cockpit_json_get_string (tc->root, "number", NULL, &value);
g_assert (ret == FALSE);
ret = cockpit_json_get_string (tc->root, "number", NULL, NULL);
g_assert (ret == FALSE);
}
static void
test_get_int (TestCase *tc,
gconstpointer data)
{
gboolean ret;
gint64 value;
ret = cockpit_json_get_int (tc->root, "number", 0, &value);
g_assert (ret == TRUE);
g_assert_cmpint (value, ==, 55);
ret = cockpit_json_get_int (tc->root, "number", 0, NULL);
g_assert (ret == TRUE);
ret = cockpit_json_get_int (tc->root, "unknown", 66, &value);
g_assert (ret == TRUE);
g_assert_cmpint (value, ==, 66);
ret = cockpit_json_get_int (tc->root, "string", 66, &value);
g_assert (ret == FALSE);
ret = cockpit_json_get_int (tc->root, "string", 66, NULL);
g_assert (ret == FALSE);
}
static void
test_get_double (TestCase *tc,
gconstpointer data)
{
gboolean ret;
gdouble value;
ret = cockpit_json_get_double (tc->root, "number", 0, &value);
g_assert (ret == TRUE);
g_assert_cmpfloat (value, ==, 55.4);
ret = cockpit_json_get_double (tc->root, "number", 0, NULL);
g_assert (ret == TRUE);
ret = cockpit_json_get_double (tc->root, "unknown", 66.4, &value);
g_assert (ret == TRUE);
g_assert_cmpfloat (value, ==, 66.4);
ret = cockpit_json_get_double (tc->root, "string", 66.4, &value);
g_assert (ret == FALSE);
ret = cockpit_json_get_double (tc->root, "string", 66.4, NULL);
g_assert (ret == FALSE);
}
static void
test_get_bool (TestCase *tc,
gconstpointer data)
{
gboolean ret;
gboolean value;
ret = cockpit_json_get_bool (tc->root, "bool", FALSE, &value);
g_assert (ret == TRUE);
g_assert_cmpint (value, ==, TRUE);
ret = cockpit_json_get_bool (tc->root, "bool", FALSE, NULL);
g_assert (ret == TRUE);
ret = cockpit_json_get_bool (tc->root, "unknown", TRUE, &value);
g_assert (ret == TRUE);
g_assert_cmpint (value, ==, TRUE);
ret = cockpit_json_get_bool (tc->root, "unknown", FALSE, &value);
g_assert (ret == TRUE);
g_assert_cmpint (value, ==, FALSE);
ret = cockpit_json_get_bool (tc->root, "string", FALSE, &value);
g_assert (ret == FALSE);
ret = cockpit_json_get_bool (tc->root, "string", FALSE, NULL);
g_assert (ret == FALSE);
}
static void
test_get_null (TestCase *tc,
gconstpointer data)
{
gboolean ret;
gboolean present;
ret = cockpit_json_get_null (tc->root, "null", NULL);
g_assert (ret == TRUE);
present = FALSE;
ret = cockpit_json_get_null (tc->root, "null", &present);
g_assert (ret == TRUE);
g_assert (present == TRUE);
ret = cockpit_json_get_null (tc->root, "unknown", NULL);
g_assert (ret == TRUE);
ret = cockpit_json_get_null (tc->root, "unknown", &present);
g_assert (ret == TRUE);
g_assert (present == FALSE);
ret = cockpit_json_get_null (tc->root, "number", NULL);
g_assert (ret == FALSE);
}
static void
test_get_strv (TestCase *tc,
gconstpointer data)
{
const gchar *defawlt[] = { "1", "2", NULL };
gboolean ret;
gchar **value;
ret = cockpit_json_get_strv (tc->root, "array", NULL, &value);
g_assert (ret == TRUE);
g_assert (value != NULL);
g_assert_cmpstr (value[0], ==, "one");
g_assert_cmpstr (value[1], ==, "two");
g_assert_cmpstr (value[2], ==, "three");
g_assert_cmpstr (value[3], ==, NULL);
g_free (value);
ret = cockpit_json_get_strv (tc->root, "unknown", NULL, &value);
g_assert (ret == TRUE);
g_assert (value == NULL);
ret = cockpit_json_get_strv (tc->root, "unknown", defawlt, &value);
g_assert (ret == TRUE);
g_assert (value != NULL);
g_assert_cmpstr (value[0], ==, "1");
g_assert_cmpstr (value[1], ==, "2");
g_assert_cmpstr (value[2], ==, NULL);
g_free (value);
ret = cockpit_json_get_strv (tc->root, "number", NULL, &value);
g_assert (ret == FALSE);
}
static void
test_get_array (TestCase *tc,
gconstpointer data)
{
JsonArray *defawlt = json_array_new ();
gboolean ret;
JsonArray *value;
ret = cockpit_json_get_array (tc->root, "array", NULL, &value);
g_assert (ret == TRUE);
g_assert (value != NULL);
g_assert_cmpstr (json_array_get_string_element (value, 0), ==, "one");
g_assert_cmpstr (json_array_get_string_element (value, 1), ==, "two");
g_assert_cmpstr (json_array_get_string_element (value, 2), ==, "three");
ret = cockpit_json_get_array (tc->root, "array", NULL, NULL);
g_assert (ret == TRUE);
ret = cockpit_json_get_array (tc->root, "unknown", NULL, &value);
g_assert (ret == TRUE);
g_assert (value == NULL);
ret = cockpit_json_get_array (tc->root, "unknown", defawlt, &value);
g_assert (ret == TRUE);
g_assert (value == defawlt);
ret = cockpit_json_get_array (tc->root, "number", NULL, &value);
g_assert (ret == FALSE);
ret = cockpit_json_get_array (tc->root, "number", NULL, NULL);
g_assert (ret == FALSE);
json_array_unref (defawlt);
}
static void
test_get_object (TestCase *tc,
gconstpointer data)
{
JsonObject *defawlt = json_object_new ();
gboolean ret;
JsonObject *value;
ret = cockpit_json_get_object (tc->root, "object", NULL, &value);
g_assert (ret == TRUE);
g_assert (value != NULL);
g_assert_cmpstr (json_object_get_string_member (value, "test"), ==, "one");
ret = cockpit_json_get_object (tc->root, "object", NULL, NULL);
g_assert (ret == TRUE);
ret = cockpit_json_get_object (tc->root, "unknown", NULL, &value);
g_assert (ret == TRUE);
g_assert (value == NULL);
ret = cockpit_json_get_object (tc->root, "unknown", defawlt, &value);
g_assert (ret == TRUE);
g_assert (value == defawlt);
ret = cockpit_json_get_object (tc->root, "number", NULL, &value);
g_assert (ret == FALSE);
ret = cockpit_json_get_object (tc->root, "array", NULL, NULL);
g_assert (ret == FALSE);
json_object_unref (defawlt);
}
static void
test_hashtable_objects (void)
{
JsonObject *object = json_object_new ();
GHashTable *ht = NULL;
const gchar *fields[] = {
"test",
"test2",
"test4",
"test5",
NULL,
};
json_object_set_string_member (object, "test", "one");
json_object_set_string_member (object, "test2", "two");
json_object_set_string_member (object, "test3", "three");
json_object_set_null_member (object, "test4");
json_object_set_string_member (object, "test5", "five");
ht = cockpit_json_to_hash_table (object, fields);
g_assert_cmpstr (g_hash_table_lookup (ht, "test"), ==, "one");
g_assert_cmpstr (g_hash_table_lookup (ht, "test2"), ==, "two");
g_assert_cmpstr (g_hash_table_lookup (ht, "test5"), ==, "five");
g_assert_false (g_hash_table_contains (ht, "test3"));
g_assert_false (g_hash_table_contains (ht, "test4"));
json_object_unref (object);
object = cockpit_json_from_hash_table (ht, fields);
g_assert_cmpstr (json_object_get_string_member (object, "test"), ==, "one");
g_assert_cmpstr (json_object_get_string_member (object, "test2"), ==, "two");
g_assert_cmpstr (json_object_get_string_member (object, "test5"), ==, "five");
g_assert_false (json_object_has_member (object, "test3"));
g_assert_true (json_object_get_null_member (object, "test4"));
json_object_unref (object);
g_hash_table_unref (ht);
}
static void
test_int_hash (void)
{
gint64 one = 1;
gint64 two = G_MAXINT;
gint64 copy = 1;
g_assert_cmpuint (cockpit_json_int_hash (&one), !=, cockpit_json_int_hash (&two));
g_assert_cmpuint (cockpit_json_int_hash (&one), ==, cockpit_json_int_hash (&one));
g_assert_cmpuint (cockpit_json_int_hash (&one), ==, cockpit_json_int_hash (©));
}
static void
test_int_equal (void)
{
gint64 one = 1;
gint64 two = G_MAXINT;
gint64 copy = 1;
g_assert (!cockpit_json_int_equal (&one, &two));
g_assert (cockpit_json_int_equal (&one, &one));
g_assert (cockpit_json_int_equal (&one, ©));
}
static void
test_parser_trims (void)
{
GError *error = NULL;
JsonNode *node;
/* Test that the parser trims whitespace, as long as something is present */
node = cockpit_json_parse (" 55 ", -1, &error);
g_assert_no_error (error);
g_assert (node);
g_assert_cmpint (json_node_get_node_type (node), ==, JSON_NODE_VALUE);
g_assert_cmpint (json_node_get_value_type (node), ==, G_TYPE_INT64);
json_node_free (node);
node = cockpit_json_parse (" \"xx\" ", -1, &error);
g_assert_no_error (error);
g_assert (node);
g_assert_cmpint (json_node_get_node_type (node), ==, JSON_NODE_VALUE);
g_assert_cmpint (json_node_get_value_type (node), ==, G_TYPE_STRING);
json_node_free (node);
node = cockpit_json_parse (" {\"xx\":5} ", -1, &error);
g_assert_no_error (error);
g_assert (node);
g_assert_cmpint (json_node_get_node_type (node), ==, JSON_NODE_OBJECT);
json_node_free (node);
}
static void
test_parser_empty (void)
{
GError *error = NULL;
JsonNode *node;
node = cockpit_json_parse ("", 0, &error);
g_assert_error (error, JSON_PARSER_ERROR, JSON_PARSER_ERROR_PARSE);
g_error_free (error);
g_assert (node == NULL);
}
typedef struct {
const gchar *name;
gboolean equal;
const gchar *a;
const gchar *b;
} FixtureEqual;
static const FixtureEqual equal_fixtures[] = {
{ "nulls", TRUE,
NULL,
NULL },
{ "null-non-null", FALSE,
NULL,
"555" },
{ "non-null-null", FALSE,
"555",
NULL },
{ "number-string", FALSE,
"555",
"\"str\"" },
{ "string-string", TRUE,
"\"str\"",
"\"str\"" },
{ "string-string-ne", FALSE,
"\"xxxx\"",
"\"str\"" },
{ "int-int", TRUE,
"555",
"555" },
{ "int-int-ne", FALSE,
"555",
"556" },
{ "double-double", TRUE,
"555.0",
"555.00" },
{ "boolean-boolean", TRUE,
"true",
"true" },
{ "boolean-boolean-ne", FALSE,
"true",
"false" },
{ "null-null", TRUE,
"null",
"null" },
{ "array-string", FALSE,
"[]",
"\"str\"" },
{ "array-array", TRUE,
"[1, 2.0, 3]",
"[1, 2.00, 3]" },
{ "array-array-ne", FALSE,
"[1, 2.0, 3]",
"[1, 4.00, 3]" },
{ "array-array-length", FALSE,
"[1, 2.0, 3]",
"[1]" },
{ "object-object", TRUE,
"{\"one\": 1, \"two\": \"2.0\"}",
"{\"one\": 1, \"two\": \"2.0\"}" },
{ "object-object-order", TRUE,
"{\"one\": 1, \"two\": \"2.0\"}",
"{\"two\": \"2.0\", \"one\": 1}" },
{ "object-object-missing", FALSE,
"{\"one\": 1, \"two\": \"2.0\"}",
"{\"two\": \"2.0\"}" },
{ "object-object-value", FALSE,
"{\"one\": 1, \"two\": \"2.0\"}",
"{\"one\": 1, \"two\": \"2\"}" },
};
static void
test_equal (gconstpointer data)
{
const FixtureEqual *fixture = data;
JsonNode *a = NULL;
JsonNode *b = NULL;
GError *error = NULL;
if (fixture->a)
a = cockpit_json_parse (fixture->a, -1, &error);
if (fixture->b)
b = cockpit_json_parse (fixture->b, -1, &error);
g_assert (cockpit_json_equal (a, b) == fixture->equal);
json_node_free (a);
json_node_free (b);
}
static void
test_utf8_invalid (void)
{
const gchar *input = "\"\xff\xff\"";
GError *error = NULL;
if (cockpit_json_parse (input, -1, &error))
g_assert_not_reached ();
g_assert_error (error, JSON_PARSER_ERROR, JSON_PARSER_ERROR_INVALID_DATA);
g_error_free (error);
}
typedef struct {
const gchar *str;
const gchar *expect;
} FixtureString;
static const FixtureString string_fixtures[] = {
{ "abc", "\"abc\"" },
{ "a\x7fxc", "\"a\\u007fxc\"" },
{ "a\033xc", "\"a\\u001bxc\"" },
{ "a\nxc", "\"a\\nxc\"" },
{ "a\\xc", "\"a\\\\xc\"" },
{ "Barney B\303\244r", "\"Barney B\303\244r\"" },
};
static void
test_string_encode (gconstpointer data)
{
const FixtureString *fixture = data;
JsonNode *node;
gsize length;
gchar *output;
node = json_node_init_string (json_node_alloc (), fixture->str);
output = cockpit_json_write (node, &length);
g_assert_cmpstr (output, ==, fixture->expect);
g_assert_cmpuint (length, ==, strlen (fixture->expect));
g_free (output);
json_node_free (node);
}
static const gchar *patch_data =
"{"
" \"string\": \"value\","
" \"number\": 55,"
" \"array\": [ \"one\", \"two\", \"three\" ],"
" \"bool\": true,"
" \"null\": null,"
" \"object\": {"
" \"one\": 1,"
" \"two\": 2,"
" \"nested\": {"
" \"three\": 3"
" }"
" }"
"}";
typedef struct {
const gchar *name;
const gchar *patch;
const gchar *result;
} PatchFixture;
static PatchFixture patch_fixtures[] = {
{
"simple-value",
"{\"string\": 5}",
"{"
" \"string\": 5,"
" \"number\": 55,"
" \"array\": [ \"one\", \"two\", \"three\" ],"
" \"bool\": true,"
" \"null\": null,"
" \"object\": {"
" \"one\": 1,"
" \"two\": 2,"
" \"nested\": {"
" \"three\": 3"
" }"
" }"
"}",
},
{
"multi-value",
"{"
" \"array\": [ 5 ],"
" \"number\": { \"test\": true }"
"}",
"{"
" \"string\": \"value\","
" \"number\": { \"test\": true },"
" \"array\": [ 5 ],"
" \"bool\": true,"
" \"null\": null,"
" \"object\": {"
" \"one\": 1,"
" \"two\": 2,"
" \"nested\": {"
" \"three\": 3"
" }"
" }"
"}",
},
{
"add-and-remove",
"{"
" \"array\": null,"
" \"number\": null,"
" \"object\": null,"
" \"added\": 42"
"}",
"{"
" \"string\": \"value\","
" \"bool\": true,"
" \"null\": null,"
" \"added\": 42"
"}",
},
{
"nested-objects",
"{"
" \"object\": {"
" \"one\": \"uno\","
" \"nested\": null,"
" \"three\": \"tres\""
" }"
"}",
"{"
" \"string\": \"value\","
" \"number\": 55,"
" \"array\": [ \"one\", \"two\", \"three\" ],"
" \"bool\": true,"
" \"null\": null,"
" \"object\": {"
" \"one\": \"uno\","
" \"two\": 2,"
" \"three\": \"tres\""
" }"
"}",
}
};
static void
test_patch (gconstpointer data)
{
const PatchFixture *fixture = data;
GError *error = NULL;
JsonObject *object;
JsonObject *with;
object = cockpit_json_parse_object (patch_data, -1, &error);
g_assert_no_error (error);
with = cockpit_json_parse_object (fixture->patch, -1, &error);
g_assert_no_error (error);
cockpit_json_patch (object, with);
cockpit_assert_json_eq (object, fixture->result);
json_object_unref (object);
json_object_unref (with);
}
static void
test_write_infinite_nan (void)
{
JsonArray *array;
gchar *string;
JsonNode *node;
array = json_array_new ();
json_array_add_double_element (array, 3.0); /* number */
json_array_add_double_element (array, 1.0/0.0); /* INFINITY */
json_array_add_double_element (array, sqrt (-1)); /* NaN */
node = json_node_new (JSON_NODE_ARRAY);
json_node_take_array (node, array);
string = cockpit_json_write (node, NULL);
g_assert_cmpstr (string, ==, "[3,null,null]");
json_node_free (node);
g_free (string);
}
int
main (int argc,
char *argv[])
{
gchar *escaped;
gchar *name;
gint i;
cockpit_test_init (&argc, &argv);
g_test_add_func ("/json/int-equal", test_int_equal);
g_test_add_func ("/json/int-hash", test_int_hash);
g_test_add_func ("/json/utf8-invalid", test_utf8_invalid);
g_test_add ("/json/get-string", TestCase, NULL,
setup, test_get_string, teardown);
g_test_add ("/json/get-int", TestCase, NULL,
setup, test_get_int, teardown);
g_test_add ("/json/get-double", TestCase, NULL,
setup, test_get_double, teardown);
g_test_add ("/json/get-bool", TestCase, NULL,
setup, test_get_bool, teardown);
g_test_add ("/json/get-null", TestCase, NULL,
setup, test_get_null, teardown);
g_test_add ("/json/get-strv", TestCase, NULL,
setup, test_get_strv, teardown);
g_test_add ("/json/get-array", TestCase, NULL,
setup, test_get_array, teardown);
g_test_add ("/json/get-object", TestCase, NULL,
setup, test_get_object, teardown);
g_test_add_func ("/json/parser-trims", test_parser_trims);
g_test_add_func ("/json/parser-empty", test_parser_empty);
for (i = 0; i < G_N_ELEMENTS (equal_fixtures); i++)
{
name = g_strdup_printf ("/json/equal/%s", equal_fixtures[i].name);
g_test_add_data_func (name, equal_fixtures + i, test_equal);
g_free (name);
}
for (i = 0; i < G_N_ELEMENTS (string_fixtures); i++)
{
escaped = g_strcanon (g_strdup (string_fixtures[i].str), COCKPIT_TEST_CHARS, '_');
name = g_strdup_printf ("/json/string/%s%d", escaped, i);
g_test_add_data_func (name, string_fixtures + i, test_string_encode);
g_free (escaped);
g_free (name);
}
for (i = 0; i < G_N_ELEMENTS (patch_fixtures); i++)
{
name = g_strdup_printf ("/json/patch/%s", patch_fixtures[i].name);
g_test_add_data_func (name, patch_fixtures + i, test_patch);
g_free (name);
}
g_test_add_func ("/json/write/infinite-nan", test_write_infinite_nan);
g_test_add_func ("/json/hashtable-objects", test_hashtable_objects);
return g_test_run ();
}