[dpdk-dev] [PATCH v2 07/10] telemetry: add tests for telemetry api

Kevin Laatz kevin.laatz at intel.com
Wed Oct 3 19:36:09 CEST 2018


From: Ciara Power <ciara.power at intel.com>

This patch adds all tests for the Telemetry API.
The tests added include a parser test, selftest, and socket
messaging tests.

The parser tests pass valid and invalid messages to the parser
to ensure the correct return values are received.
The selftest tests basic functions in the Telemetry API such as
registering, unregistering, and initialisation.
The socket messaging tests pass messages through the socket and
validates the return message, to ensure the Telemetry API is
responding correctly.

Signed-off-by: Ciara Power <ciara.power at intel.com>
Signed-off-by: Brian Archbold <brian.archbold at intel.com>
Signed-off-by: Kevin Laatz <kevin.laatz at intel.com>
---
 lib/librte_telemetry/Makefile                     |   1 +
 lib/librte_telemetry/meson.build                  |   4 +-
 lib/librte_telemetry/rte_telemetry.c              | 651 +++++++++++++++++++++-
 lib/librte_telemetry/rte_telemetry.h              |  12 +
 lib/librte_telemetry/rte_telemetry_internal.h     |   3 +
 lib/librte_telemetry/rte_telemetry_parser_test.c  | 534 ++++++++++++++++++
 lib/librte_telemetry/rte_telemetry_parser_test.h  |  39 ++
 lib/librte_telemetry/rte_telemetry_socket_tests.h |  36 ++
 lib/librte_telemetry/rte_telemetry_version.map    |   1 +
 9 files changed, 1278 insertions(+), 3 deletions(-)
 create mode 100644 lib/librte_telemetry/rte_telemetry_parser_test.c
 create mode 100644 lib/librte_telemetry/rte_telemetry_parser_test.h
 create mode 100644 lib/librte_telemetry/rte_telemetry_socket_tests.h

diff --git a/lib/librte_telemetry/Makefile b/lib/librte_telemetry/Makefile
index 95c7296..1a05069 100644
--- a/lib/librte_telemetry/Makefile
+++ b/lib/librte_telemetry/Makefile
@@ -22,6 +22,7 @@ LIBABIVER := 1
 # library source files
 SRCS-$(CONFIG_RTE_LIBRTE_TELEMETRY) := rte_telemetry.c
 SRCS-$(CONFIG_RTE_LIBRTE_TELEMETRY) += rte_telemetry_parser.c
+SRCS-$(CONFIG_RTE_LIBRTE_TELEMETRY) += rte_telemetry_parser_test.c
 
 # export include files
 SYMLINK-$(CONFIG_RTE_LIBRTE_TELEMETRY)-include := rte_telemetry.h
diff --git a/lib/librte_telemetry/meson.build b/lib/librte_telemetry/meson.build
index 7450f96..57dd83d 100644
--- a/lib/librte_telemetry/meson.build
+++ b/lib/librte_telemetry/meson.build
@@ -1,8 +1,8 @@
 # SPDX-License-Identifier: BSD-3-Clause
 # Copyright(c) 2018 Intel Corporation
 
-sources = files('rte_telemetry.c', 'rte_telemetry_parser.c')
-headers = files('rte_telemetry.h', 'rte_telemetry_internal.h', 'rte_telemetry_parser.h')
+sources = files('rte_telemetry.c', 'rte_telemetry_parser.c', 'rte_telemetry_parser_test.c')
+headers = files('rte_telemetry.h', 'rte_telemetry_internal.h', 'rte_telemetry_parser.h', 'rte_telemetry_parser_test.h')
 deps += ['metrics', 'ethdev']
 cflags += '-DALLOW_EXPERIMENTAL_API'
 jansson = cc.find_library('jansson', required: true)
diff --git a/lib/librte_telemetry/rte_telemetry.c b/lib/librte_telemetry/rte_telemetry.c
index 5b1d2ef..43f7a93 100644
--- a/lib/librte_telemetry/rte_telemetry.c
+++ b/lib/librte_telemetry/rte_telemetry.c
@@ -17,16 +17,34 @@
 #include "rte_telemetry.h"
 #include "rte_telemetry_internal.h"
 #include "rte_telemetry_parser.h"
+#include "rte_telemetry_parser_test.h"
+#include "rte_telemetry_socket_tests.h"
 
 #define BUF_SIZE 1024
 #define ACTION_POST 1
 #define SLEEP_TIME 10
 
 #define DEFAULT_DPDK_PATH "/var/run/.rte_telemetry"
+#define SELFTEST_VALID_CLIENT "/var/run/valid_client"
+#define SELFTEST_INVALID_CLIENT "/var/run/invalid_client"
+#define SOCKET_TEST_CLIENT_PATH "/var/run/client"
 
 const char *socket_path = DEFAULT_DPDK_PATH;
 static telemetry_impl *static_telemetry;
 
+struct telemetry_message_test {
+	char *test_name;
+	int (*test_func_ptr)(struct telemetry_impl *telemetry, int fd);
+};
+
+struct json_data {
+	char *status_code;
+	char *data;
+	int port;
+	char *stat_name;
+	int stat_value;
+};
+
 int32_t
 rte_telemetry_is_port_active(int port_id)
 {
@@ -635,7 +653,7 @@ rte_telemetry_reg_ethdev_to_metrics(uint16_t port_id)
 static int32_t
 rte_telemetry_initial_accept(struct telemetry_impl *telemetry)
 {
-	int pid;
+	int pid, ret;
 
 	RTE_ETH_FOREACH_DEV(pid) {
 		telemetry->reg_index = rte_telemetry_reg_ethdev_to_metrics(pid);
@@ -648,6 +666,18 @@ rte_telemetry_initial_accept(struct telemetry_impl *telemetry)
 	}
 
 	telemetry->metrics_register_done = 1;
+	ret = rte_telemetry_socket_messaging_testing(telemetry->reg_index,
+		telemetry->server_fd);
+	if (ret < 0)
+		return -1;
+
+	ret = rte_telemetry_parser_test(telemetry);
+	if (ret < 0) {
+		TELEMETRY_LOG_ERR("Parser Tests Failed");
+		return -1;
+	}
+
+	TELEMETRY_LOG_INFO("Success - All Parser Tests Passed");
 
 	return 0;
 }
@@ -1113,6 +1143,625 @@ rte_telemetry_parse_client_message(struct telemetry_impl *telemetry, char *buf)
 	return -1;
 }
 
+int32_t
+rte_telemetry_dummy_client_socket(const char *valid_client_path)
+{
+	int sockfd = socket(AF_UNIX, SOCK_STREAM, 0);
+	struct sockaddr_un addr = {0};
+
+	if (sockfd < 0) {
+		TELEMETRY_LOG_ERR("Test socket creation failure");
+		return -1;
+	}
+
+	addr.sun_family = AF_UNIX;
+	strlcpy(addr.sun_path, valid_client_path, sizeof(addr.sun_path));
+	unlink(valid_client_path);
+
+	if (bind(sockfd, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
+		TELEMETRY_LOG_ERR("Test socket binding failure");
+		return -1;
+	}
+
+	if (listen(sockfd, 1) < 0) {
+		TELEMETRY_LOG_ERR("Listen failure");
+		return -1;
+	}
+
+	return sockfd;
+}
+
+int32_t
+rte_telemetry_selftest(void)
+{
+	const char *invalid_client_path = SELFTEST_INVALID_CLIENT;
+	const char *valid_client_path = SELFTEST_VALID_CLIENT;
+	int ret, sockfd;
+
+	TELEMETRY_LOG_INFO("Selftest");
+
+	ret = rte_telemetry_init();
+	if (ret < 0) {
+		TELEMETRY_LOG_ERR("Valid initialisation test failed");
+		return -1;
+	}
+
+	TELEMETRY_LOG_INFO("Success - Valid initialisation test passed");
+
+	ret = rte_telemetry_init();
+	if (ret != -EALREADY) {
+		TELEMETRY_LOG_ERR("Invalid initialisation test failed");
+		return -1;
+	}
+
+	TELEMETRY_LOG_INFO("Success - Invalid initialisation test passed");
+
+	ret = rte_telemetry_unregister_client(static_telemetry,
+			invalid_client_path);
+	if (ret != -EPERM) {
+		TELEMETRY_LOG_ERR("Invalid unregister test failed");
+		return -1;
+	}
+
+	TELEMETRY_LOG_INFO("Success - Invalid unregister test passed");
+
+	sockfd = rte_telemetry_dummy_client_socket(valid_client_path);
+	if (sockfd < 0) {
+		TELEMETRY_LOG_ERR("Test socket creation failed");
+		return -1;
+	}
+
+	ret = rte_telemetry_register_client(static_telemetry, valid_client_path);
+	if (ret != 0) {
+		TELEMETRY_LOG_ERR("Valid register test failed: %i", ret);
+		return -1;
+	}
+
+	accept(sockfd, NULL, NULL);
+	TELEMETRY_LOG_INFO("Success - Valid register test passed");
+
+	ret = rte_telemetry_register_client(static_telemetry, valid_client_path);
+	if (ret != -EINVAL) {
+		TELEMETRY_LOG_ERR("Invalid register test failed: %i", ret);
+		return -1;
+	}
+
+	TELEMETRY_LOG_INFO("Success - Invalid register test passed");
+
+	ret = rte_telemetry_unregister_client(static_telemetry,
+		invalid_client_path);
+	if (ret != -1) {
+		TELEMETRY_LOG_ERR("Invalid unregister test failed: %i", ret);
+		return -1;
+	}
+
+	TELEMETRY_LOG_INFO("Success - Invalid unregister test passed");
+
+	ret = rte_telemetry_unregister_client(static_telemetry, valid_client_path);
+	if (ret != 0) {
+		TELEMETRY_LOG_ERR("Valid unregister test failed: %i", ret);
+		return -1;
+	}
+
+	TELEMETRY_LOG_INFO("Success - Valid unregister test passed");
+
+	ret = rte_telemetry_cleanup();
+	if (ret < 0) {
+		TELEMETRY_LOG_ERR("Cleanup test failed");
+		return -1;
+	}
+
+	TELEMETRY_LOG_INFO("Success - Valid cleanup test passed");
+
+	return 0;
+}
+
+int32_t
+rte_telemetry_socket_messaging_testing(int index, int socket)
+{
+	struct telemetry_impl *telemetry = calloc(1, sizeof(telemetry_impl));
+	int fd, bad_send_fd, send_fd, bad_fd, bad_recv_fd, recv_fd, ret;
+
+	if (!telemetry) {
+		TELEMETRY_LOG_ERR("Could not initialize Telemetry API");
+		return -1;
+	}
+
+	telemetry->server_fd = socket;
+	telemetry->reg_index = index;
+	TELEMETRY_LOG_INFO("Beginning Telemetry socket message Selftest");
+	rte_telemetry_socket_test_setup(telemetry, &send_fd, &recv_fd);
+	TELEMETRY_LOG_INFO("Register valid client test");
+
+	ret = rte_telemetry_socket_register_test(telemetry, &fd, send_fd,
+		recv_fd);
+	if (ret < 0) {
+		TELEMETRY_LOG_ERR("Register valid client test failed!");
+		free(telemetry);
+		return -1;
+	}
+
+	TELEMETRY_LOG_INFO("Success - Register valid client test passed!");
+
+	TELEMETRY_LOG_INFO("Register invalid/same client test");
+	ret = rte_telemetry_socket_test_setup(telemetry, &bad_send_fd,
+		&bad_recv_fd);
+	ret = rte_telemetry_socket_register_test(telemetry, &bad_fd,
+		bad_send_fd, bad_recv_fd);
+	if (!ret) {
+		TELEMETRY_LOG_ERR("Register invalid/same client test failed!");
+		free(telemetry);
+		return -1;
+	}
+
+	TELEMETRY_LOG_INFO("Success - Register invalid/same client test passed!");
+
+	ret = rte_telemetry_json_socket_message_test(telemetry, fd);
+	if (ret < 0) {
+		free(telemetry);
+		return -1;
+	}
+
+	free(telemetry);
+	return 0;
+}
+
+int32_t
+rte_telemetry_socket_register_test(struct telemetry_impl *telemetry, int *fd,
+	int send_fd, int recv_fd)
+{
+	int ret;
+	char good_req_string[BUF_SIZE];
+
+	snprintf(good_req_string, sizeof(good_req_string),
+	"{\"action\":1,\"command\":\"clients\",\"data\":{\"client_path\""
+		":\"%s\"}}", SOCKET_TEST_CLIENT_PATH);
+
+	listen(recv_fd, 1);
+
+	ret = send(send_fd, good_req_string, strlen(good_req_string), 0);
+	if (ret < 0) {
+		TELEMETRY_LOG_ERR("Could not send message over socket");
+		return -1;
+	}
+
+	rte_telemetry_run(telemetry);
+
+	if (telemetry->register_fail_count != 0)
+		return -1;
+
+	*fd = accept(recv_fd, NULL, NULL);
+
+	return 0;
+}
+
+int32_t
+rte_telemetry_socket_test_setup(struct telemetry_impl *telemetry, int *send_fd,
+	int *recv_fd)
+{
+	int ret;
+	const char *client_path = SOCKET_TEST_CLIENT_PATH;
+	struct sockaddr_un addr = {0};
+	struct sockaddr_un addrs = {0};
+	*send_fd = socket(AF_UNIX, SOCK_STREAM, 0);
+	*recv_fd = socket(AF_UNIX, SOCK_STREAM, 0);
+
+	listen(telemetry->server_fd, 5);
+	addr.sun_family = AF_UNIX;
+	strlcpy(addr.sun_path, socket_path, sizeof(addr.sun_path));
+
+	ret = connect(*send_fd, (struct sockaddr *) &addr, sizeof(addr));
+	if (ret < 0) {
+		TELEMETRY_LOG_ERR("Could not connect socket");
+		return -1;
+	}
+
+	telemetry->accept_fd = accept(telemetry->server_fd, NULL, NULL);
+
+	addrs.sun_family = AF_UNIX;
+	strlcpy(addrs.sun_path, client_path, sizeof(addrs.sun_path));
+	unlink(client_path);
+
+	ret = bind(*recv_fd, (struct sockaddr *)&addrs, sizeof(addrs));
+	if (ret < 0) {
+		TELEMETRY_LOG_ERR("Could not bind socket");
+		return -1;
+	}
+
+	return 0;
+}
+
+static int32_t
+rte_telemetry_stat_parse(char *buf, struct json_data *json_data_struct)
+{
+	json_error_t error;
+	json_t *root = json_loads(buf, 0, &error);
+	int arraylen, i;
+	json_t *status, *dataArray, *port, *stats, *name, *value, *dataArrayObj,
+	       *statsArrayObj;
+
+	stats = NULL;
+	port = NULL;
+	name = NULL;
+
+	if (!buf) {
+		TELEMETRY_LOG_ERR("JSON message is NULL");
+		return -EINVAL;
+	}
+
+	if (!root) {
+		TELEMETRY_LOG_ERR("Could not load JSON object from data passed in : %s",
+				error.text);
+		return -EPERM;
+	} else if (!json_is_object(root)) {
+		TELEMETRY_LOG_ERR("JSON Request is not a JSON object");
+		json_decref(root);
+		return -EINVAL;
+	}
+
+	status = json_object_get(root, "status_code");
+	if (!status) {
+		TELEMETRY_LOG_ERR("Request does not have status field");
+		return -EINVAL;
+	} else if (!json_is_string(status)) {
+		TELEMETRY_LOG_ERR("Status value is not a string");
+		return -EINVAL;
+	}
+
+	json_data_struct->status_code = strdup(json_string_value(status));
+
+	dataArray = json_object_get(root, "data");
+	if (!dataArray) {
+		TELEMETRY_LOG_ERR("Request does not have data field");
+		return -EINVAL;
+	}
+
+	arraylen = json_array_size(dataArray);
+	if (arraylen == 0) {
+		json_data_struct->data = "null";
+		return -EINVAL;
+	}
+
+	for (i = 0; i < arraylen; i++) {
+		dataArrayObj = json_array_get(dataArray, i);
+		port = json_object_get(dataArrayObj, "port");
+		stats = json_object_get(dataArrayObj, "stats");
+	}
+
+	if (!port) {
+		TELEMETRY_LOG_ERR("Request does not have port field");
+		return -EINVAL;
+	}
+
+	if (!json_is_integer(port)) {
+		TELEMETRY_LOG_ERR("Port value is not an integer");
+		return -EINVAL;
+	}
+
+	json_data_struct->port = json_integer_value(port);
+
+	if (!stats) {
+		TELEMETRY_LOG_ERR("Request does not have stats field");
+		return -EINVAL;
+	}
+
+	arraylen = json_array_size(stats);
+	for (i = 0; i < arraylen; i++) {
+		statsArrayObj = json_array_get(stats, i);
+		name = json_object_get(statsArrayObj, "name");
+		value = json_object_get(statsArrayObj, "value");
+	}
+
+	if (!name) {
+		TELEMETRY_LOG_ERR("Request does not have name field");
+		return -EINVAL;
+	}
+
+	if (!json_is_string(name)) {
+		TELEMETRY_LOG_ERR("Stat name value is not a string");
+		return -EINVAL;
+	}
+
+	json_data_struct->stat_name = strdup(json_string_value(name));
+
+	if (!value) {
+		TELEMETRY_LOG_ERR("Request does not have value field");
+		return -EINVAL;
+	}
+
+	if (!json_is_integer(value)) {
+		TELEMETRY_LOG_ERR("Stat value is not an integer");
+		return -EINVAL;
+	}
+
+	json_data_struct->stat_value = json_integer_value(value);
+
+	return 0;
+}
+
+static void
+rte_telemetry_free_test_data(struct json_data *data)
+{
+	free(data->status_code);
+	free(data->stat_name);
+	free(data);
+}
+
+int32_t
+rte_telemetry_valid_json_test(struct telemetry_impl *telemetry, int fd)
+{
+	int ret;
+	int port = 0;
+	int value = 0;
+	int fail_count = 0;
+	int buffer_read = 0;
+	char buf[BUF_SIZE];
+	struct json_data *data_struct;
+	errno = 0;
+	const char *status = "Status OK: 200";
+	const char *name = "rx_good_packets";
+	const char *valid_json_message = "{\"action\":0,\"command\":"
+	"\"ports_stats_values_by_name\",\"data\":{\"ports\""
+	":[0],\"stats\":[\"rx_good_packets\"]}}";
+
+	ret = send(fd, valid_json_message, strlen(valid_json_message), 0);
+	if (ret < 0) {
+		TELEMETRY_LOG_ERR("Could not send message over socket");
+		return -1;
+	}
+
+	rte_telemetry_run(telemetry);
+	buffer_read = recv(fd, buf, BUF_SIZE-1, 0);
+
+	if (buffer_read == -1) {
+		TELEMETRY_LOG_ERR("Read error");
+		return -1;
+	}
+
+	buf[buffer_read] = '\0';
+	data_struct = calloc(1, sizeof(struct json_data));
+	ret = rte_telemetry_stat_parse(buf, data_struct);
+
+	if (ret < 0) {
+		TELEMETRY_LOG_ERR("Could not parse stats");
+		fail_count++;
+	}
+
+	if (strcmp(data_struct->status_code, status) != 0) {
+		TELEMETRY_LOG_ERR("Status code is invalid");
+		fail_count++;
+	}
+
+	if (data_struct->port != port) {
+		TELEMETRY_LOG_ERR("Port is invalid");
+		fail_count++;
+	}
+
+	if (strcmp(data_struct->stat_name, name) != 0) {
+		TELEMETRY_LOG_ERR("Stat name is invalid");
+		fail_count++;
+	}
+
+	if (data_struct->stat_value != value) {
+		TELEMETRY_LOG_ERR("Stat value is invalid");
+		fail_count++;
+	}
+
+	rte_telemetry_free_test_data(data_struct);
+	if (fail_count > 0)
+		return -1;
+
+	TELEMETRY_LOG_INFO("Success - Passed valid JSON message test passed");
+
+	return 0;
+}
+
+int32_t
+rte_telemetry_invalid_json_test(struct telemetry_impl *telemetry, int fd)
+{
+	int ret;
+	char buf[BUF_SIZE];
+	int fail_count = 0;
+	const char *invalid_json = "{]";
+	const char *status = "Status Error: Unknown";
+	const char *data = "null";
+	struct json_data *data_struct;
+	int buffer_read = 0;
+	errno = 0;
+
+	ret = send(fd, invalid_json, strlen(invalid_json), 0);
+	if (ret < 0) {
+		TELEMETRY_LOG_ERR("Could not send message over socket");
+		return -1;
+	}
+
+	rte_telemetry_run(telemetry);
+	buffer_read = recv(fd, buf, BUF_SIZE-1, 0);
+
+	if (buffer_read == -1) {
+		TELEMETRY_LOG_ERR("Read error");
+		return -1;
+	}
+
+	buf[buffer_read] = '\0';
+
+	data_struct = calloc(1, sizeof(struct json_data));
+	ret = rte_telemetry_stat_parse(buf, data_struct);
+
+	if (ret < 0)
+		TELEMETRY_LOG_ERR("Could not parse stats");
+
+	if (strcmp(data_struct->status_code, status) != 0) {
+		TELEMETRY_LOG_ERR("Status code is invalid");
+		fail_count++;
+	}
+
+	if (strcmp(data_struct->data, data) != 0) {
+		TELEMETRY_LOG_ERR("Data status is invalid");
+		fail_count++;
+	}
+
+	rte_telemetry_free_test_data(data_struct);
+	if (fail_count > 0)
+		return -1;
+
+	TELEMETRY_LOG_INFO("Success - Passed invalid JSON message test");
+
+	return 0;
+}
+
+int32_t
+rte_telemetry_json_contents_test(struct telemetry_impl *telemetry, int fd)
+{
+	int ret;
+	char buf[BUF_SIZE];
+	int fail_count = 0;
+	char *status = "Status Error: Invalid Argument 404";
+	char *data = "null";
+	struct json_data *data_struct;
+	const char *invalid_contents = "{\"action\":0,\"command\":"
+	"\"ports_stats_values_by_name\",\"data\":{\"ports\""
+	":[0],\"stats\":[\"some_invalid_param\","
+	"\"another_invalid_param\"]}}";
+	int buffer_read = 0;
+	errno = 0;
+
+	ret = send(fd, invalid_contents, strlen(invalid_contents), 0);
+	if (ret < 0) {
+		TELEMETRY_LOG_ERR("Could not send message over socket");
+		return -1;
+	}
+
+	rte_telemetry_run(telemetry);
+	buffer_read = recv(fd, buf, BUF_SIZE-1, 0);
+
+	if (buffer_read == -1) {
+		TELEMETRY_LOG_ERR("Read error");
+		return -1;
+	}
+
+	buf[buffer_read] = '\0';
+	data_struct = calloc(1, sizeof(struct json_data));
+	ret = rte_telemetry_stat_parse(buf, data_struct);
+
+	if (ret < 0)
+		TELEMETRY_LOG_ERR("Could not parse stats");
+
+	if (strcmp(data_struct->status_code, status) != 0) {
+		TELEMETRY_LOG_ERR("Status code is invalid");
+		fail_count++;
+	}
+
+	if (strcmp(data_struct->data, data) != 0) {
+		TELEMETRY_LOG_ERR("Data status is invalid");
+		fail_count++;
+	}
+
+	rte_telemetry_free_test_data(data_struct);
+	if (fail_count > 0)
+		return -1;
+
+	TELEMETRY_LOG_INFO("Success - Passed invalid JSON content test");
+
+	return 0;
+}
+
+int32_t
+rte_telemetry_json_empty_test(struct telemetry_impl *telemetry, int fd)
+{
+	int ret;
+	char buf[BUF_SIZE];
+	int fail_count = 0;
+	const char *status = "Status Error: Invalid Argument 404";
+	char *data = "null";
+	struct json_data *data_struct;
+	const char *empty_json  = "{}";
+	int buffer_read = 0;
+	errno = 0;
+
+	ret = (send(fd, empty_json, strlen(empty_json), 0));
+	if (ret < 0) {
+		TELEMETRY_LOG_ERR("Could not send message over socket");
+		return -1;
+	}
+
+	rte_telemetry_run(telemetry);
+	buffer_read = recv(fd, buf, BUF_SIZE-1, 0);
+
+	if (buffer_read == -1) {
+		TELEMETRY_LOG_ERR("Read error");
+		return -1;
+	}
+
+	buf[buffer_read] = '\0';
+	data_struct = calloc(1, sizeof(struct json_data));
+	ret = rte_telemetry_stat_parse(buf, data_struct);
+
+	if (ret < 0)
+		TELEMETRY_LOG_ERR("Could not parse stats");
+
+	if (strcmp(data_struct->status_code, status) != 0) {
+		TELEMETRY_LOG_ERR("Status code is invalid");
+		fail_count++;
+	}
+
+	if (strcmp(data_struct->data, data) != 0) {
+		TELEMETRY_LOG_ERR("Data status is invalid");
+		fail_count++;
+	}
+
+	rte_telemetry_free_test_data(data_struct);
+
+	if (fail_count > 0)
+		return -1;
+
+	TELEMETRY_LOG_INFO("Success - Passed JSON empty message test");
+
+	return 0;
+}
+
+int32_t
+rte_telemetry_json_socket_message_test(struct telemetry_impl *telemetry, int fd)
+{
+	uint16_t i;
+	int ret, fail_count;
+
+	fail_count = 0;
+	struct telemetry_message_test socket_json_tests[] = {
+		{.test_name = "Invalid JSON test",
+			.test_func_ptr = rte_telemetry_invalid_json_test},
+		{.test_name = "Valid JSON test",
+			.test_func_ptr = rte_telemetry_valid_json_test},
+		{.test_name = "JSON contents test",
+			.test_func_ptr = rte_telemetry_json_contents_test},
+		{.test_name = "JSON empty tests",
+			.test_func_ptr = rte_telemetry_json_empty_test}
+		};
+
+#define NUM_TESTS RTE_DIM(socket_json_tests)
+
+	for (i = 0; i < NUM_TESTS; i++) {
+		TELEMETRY_LOG_INFO("%s", socket_json_tests[i].test_name);
+		ret = (socket_json_tests[i].test_func_ptr)
+			(telemetry, fd);
+		if (ret < 0) {
+			TELEMETRY_LOG_ERR("%s failed",
+					socket_json_tests[i].test_name);
+			fail_count++;
+		}
+	}
+
+	if (fail_count > 0) {
+		TELEMETRY_LOG_ERR("Failed %i JSON socket message test(s)",
+				fail_count);
+		return -1;
+	}
+
+	TELEMETRY_LOG_INFO("Success - All JSON tests passed");
+
+	return 0;
+}
+
 int telemetry_log_level;
 RTE_INIT(rte_telemetry_register);
 
diff --git a/lib/librte_telemetry/rte_telemetry.h b/lib/librte_telemetry/rte_telemetry.h
index f7ecb7b..975c305 100644
--- a/lib/librte_telemetry/rte_telemetry.h
+++ b/lib/librte_telemetry/rte_telemetry.h
@@ -33,4 +33,16 @@ rte_telemetry_init(void);
 int32_t
 rte_telemetry_cleanup(void);
 
+/**
+ * Runs various tests to ensure telemetry initialisation and register/unregister
+ * functions are working correctly.
+ *
+ * @return
+ *  0 on success when all tests have passed
+ * @return
+ *  -1 on failure when the test has failed
+ */
+int32_t
+rte_telemetry_selftest(void);
+
 #endif
diff --git a/lib/librte_telemetry/rte_telemetry_internal.h b/lib/librte_telemetry/rte_telemetry_internal.h
index 0082cb2..de7afda 100644
--- a/lib/librte_telemetry/rte_telemetry_internal.h
+++ b/lib/librte_telemetry/rte_telemetry_internal.h
@@ -75,4 +75,7 @@ int32_t
 rte_telemetry_send_ports_stats_values(uint32_t *metric_ids, int num_metric_ids,
 	uint32_t *port_ids, int num_port_ids, struct telemetry_impl *telemetry);
 
+int32_t
+rte_telemetry_socket_messaging_testing(int index, int socket);
+
 #endif
diff --git a/lib/librte_telemetry/rte_telemetry_parser_test.c b/lib/librte_telemetry/rte_telemetry_parser_test.c
new file mode 100644
index 0000000..13a9ca8
--- /dev/null
+++ b/lib/librte_telemetry/rte_telemetry_parser_test.c
@@ -0,0 +1,534 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2018 Intel Corporation
+ */
+
+#include <stdio.h>
+#include <stdint.h>
+#include <string.h>
+#include <errno.h>
+#include <jansson.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <unistd.h>
+
+#include <rte_common.h>
+#include <rte_tailq.h>
+#include <rte_string_fns.h>
+
+#include "rte_telemetry_parser.h"
+
+enum choices {
+	INV_ACTION_VAL,
+	INV_COMMAND_VAL,
+	INV_DATA_VAL,
+	INV_ACTION_FIELD,
+	INV_COMMAND_FIELD,
+	INV_DATA_FIELD,
+	INV_JSON_FORMAT,
+	VALID_REQ
+};
+
+
+#define TEST_CLIENT "/var/run/test_client"
+
+int32_t
+rte_telemetry_create_test_socket(struct telemetry_impl *telemetry,
+	const char *test_client_path)
+{
+	int ret, sockfd;
+	struct sockaddr_un addr = {0};
+	struct telemetry_client *client;
+
+	if (!telemetry) {
+		TELEMETRY_LOG_ERR("Telemetry argument has not been initialised");
+		return -EINVAL;
+	}
+
+	sockfd = socket(AF_UNIX, SOCK_STREAM, 0);
+	if (sockfd < 0) {
+		TELEMETRY_LOG_ERR("Test socket creation failure");
+		return -1;
+	}
+
+	addr.sun_family = AF_UNIX;
+	strlcpy(addr.sun_path, test_client_path, sizeof(addr.sun_path));
+	unlink(test_client_path);
+
+	if (bind(sockfd, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
+		TELEMETRY_LOG_ERR("Test socket binding failure");
+		return -1;
+	}
+
+	if (listen(sockfd, 1) < 0) {
+		TELEMETRY_LOG_ERR("Listen failure");
+		return -1;
+	}
+
+	ret = rte_telemetry_register_client(telemetry, test_client_path);
+	if (ret < 0) {
+		TELEMETRY_LOG_ERR("Register dummy client failed: %i", ret);
+		return -1;
+	}
+
+	ret = accept(sockfd, NULL, NULL);
+	if (ret < 0) {
+		TELEMETRY_LOG_ERR("Socket accept failed");
+		return -1;
+	}
+
+	TAILQ_FOREACH(client, &telemetry->client_list_head, client_list)
+		telemetry->request_client = client;
+
+	return 0;
+}
+
+int32_t
+rte_telemetry_format_port_stat_ids(int *port_ids, int num_port_ids,
+	const char * const *stat_names, int num_stat_names, json_t **data)
+{
+
+	int ret;
+	json_t *stat_names_json_array = NULL;
+	json_t *port_ids_json_array = NULL;
+	uint32_t i;
+
+	if (num_port_ids < 0) {
+		TELEMETRY_LOG_ERR("Port Ids Count invalid");
+		goto fail;
+	}
+
+	*data = json_object();
+	if (!*data) {
+		TELEMETRY_LOG_ERR("Data json object creation failed");
+		goto fail;
+	}
+
+	port_ids_json_array = json_array();
+	if (!port_ids_json_array) {
+		TELEMETRY_LOG_ERR("port_ids_json_array creation failed");
+		goto fail;
+	}
+
+	for (i = 0; i < (uint32_t)num_port_ids; i++) {
+		ret = json_array_append(port_ids_json_array,
+				json_integer(port_ids[i]));
+		if (ret < 0) {
+			TELEMETRY_LOG_ERR("JSON array creation failed");
+			goto fail;
+		}
+	}
+
+	ret = json_object_set_new(*data, "ports", port_ids_json_array);
+	if (ret < 0) {
+		TELEMETRY_LOG_ERR("Setting 'ports' value in data object failed");
+		goto fail;
+	}
+
+	if (stat_names) {
+		if (num_stat_names < 0) {
+			TELEMETRY_LOG_ERR("Stat Names Count invalid");
+			goto fail;
+		}
+
+		stat_names_json_array = json_array();
+		if (!stat_names_json_array) {
+			TELEMETRY_LOG_ERR("stat_names_json_array creation failed");
+			goto fail;
+		}
+
+		uint32_t i;
+		for (i = 0; i < (uint32_t)num_stat_names; i++) {
+			ret = json_array_append(stat_names_json_array,
+				 json_string(stat_names[i]));
+			if (ret < 0) {
+				TELEMETRY_LOG_ERR("JSON array creation failed");
+				goto fail;
+			}
+		}
+
+		ret = json_object_set_new(*data, "stats", stat_names_json_array);
+		if (ret < 0) {
+			TELEMETRY_LOG_ERR("Setting 'stats' value in data object failed");
+			goto fail;
+		}
+	}
+
+	return 0;
+
+fail:
+	if (*data)
+		json_decref(*data);
+	if (stat_names_json_array)
+		json_decref(stat_names_json_array);
+	if (port_ids_json_array)
+		json_decref(port_ids_json_array);
+	return -1;
+}
+
+int32_t
+rte_telemetry_create_json_request(int action, char *command,
+	const char *client_path, int *port_ids, int num_port_ids,
+	const char * const *stat_names, int num_stat_names, char **request,
+	int inv_choice)
+{
+	int ret;
+	json_t *root = json_object();
+	json_t *data;
+
+	if (!root) {
+		TELEMETRY_LOG_ERR("Could not create root json object");
+		goto fail;
+	}
+
+	if (inv_choice == INV_ACTION_FIELD) {
+		ret = json_object_set_new(root, "ac--on", json_integer(action));
+		if (ret < 0) {
+			TELEMETRY_LOG_ERR("Setting invalid action field in root object failed");
+			goto fail;
+		}
+	} else {
+		ret = json_object_set_new(root, "action", json_integer(action));
+		if (ret < 0) {
+			TELEMETRY_LOG_ERR("Setting valid action field in root object failed");
+			goto fail;
+		}
+	}
+
+	if (inv_choice == INV_COMMAND_FIELD) {
+		ret = json_object_set_new(root, "co---nd", json_string(command));
+		if (ret < 0) {
+			TELEMETRY_LOG_ERR("Setting invalid command field in root object failed");
+			goto fail;
+		}
+	} else {
+		ret = json_object_set_new(root, "command", json_string(command));
+		if (ret < 0) {
+			TELEMETRY_LOG_ERR("Setting valid command field in root object failed");
+			goto fail;
+		}
+	}
+
+	data = json_null();
+	if (client_path) {
+		data = json_object();
+		if (!data) {
+			TELEMETRY_LOG_ERR("Data json object creation failed");
+			goto fail;
+		}
+
+		ret = json_object_set_new(data, "client_path",
+				json_string(client_path));
+		if (ret < 0) {
+			TELEMETRY_LOG_ERR("Setting valid client_path field in data object failed");
+			goto fail;
+		}
+
+	} else if (port_ids) {
+		ret = rte_telemetry_format_port_stat_ids(port_ids, num_port_ids,
+				stat_names, num_stat_names, &data);
+		if (ret < 0) {
+			TELEMETRY_LOG_ERR("Formatting Port/Stat arrays failed");
+			goto fail;
+		}
+
+	}
+
+	if (inv_choice == INV_DATA_FIELD) {
+		ret = json_object_set_new(root, "d--a", data);
+		if (ret < 0) {
+			TELEMETRY_LOG_ERR("Setting invalid data field in data object failed");
+			goto fail;
+		}
+	} else {
+		ret = json_object_set_new(root, "data", data);
+		if (ret < 0) {
+			TELEMETRY_LOG_ERR("Setting valid data field in data object failed");
+			goto fail;
+		}
+	}
+
+	*request = json_dumps(root, 0);
+	if (!*request) {
+		TELEMETRY_LOG_ERR("Converting JSON root object to char* failed");
+		goto fail;
+	}
+
+	json_decref(root);
+	return 0;
+
+fail:
+	if (root)
+		json_decref(root);
+	return -1;
+}
+
+int32_t
+rte_telemetry_send_get_ports_and_stats_request(struct telemetry_impl *telemetry,
+	int action_choice, char *command_choice, int inv_choice)
+{
+	int ret;
+	char *request;
+	char *client_path_data = NULL;
+
+	if (!telemetry) {
+		TELEMETRY_LOG_ERR("Telemetry argument has not been initialised");
+		return -EINVAL;
+	}
+
+
+	if (inv_choice == INV_ACTION_VAL)
+		action_choice = -1;
+	else if (inv_choice == INV_COMMAND_VAL)
+		command_choice = "INVALID_COMMAND";
+	else if (inv_choice == INV_DATA_VAL)
+		client_path_data = "INVALID_DATA";
+
+	ret = rte_telemetry_create_json_request(action_choice, command_choice,
+		client_path_data, NULL, -1, NULL, -1, &request, inv_choice);
+	if (ret < 0) {
+		TELEMETRY_LOG_ERR("Could not create JSON Request");
+		return -1;
+	}
+
+	if (inv_choice == INV_JSON_FORMAT)
+		request++;
+
+	ret = rte_telemetry_parse(telemetry, request);
+	if (ret < 0) {
+		TELEMETRY_LOG_WARN("Could not parse JSON Request");
+		return -1;
+	}
+
+	return 0;
+}
+
+int32_t
+rte_telemetry_send_get_ports_details_request(struct telemetry_impl *telemetry,
+	int action_choice, int *port_ids, int num_port_ids, int inv_choice)
+{
+	int ret;
+	char *request;
+	if (!telemetry) {
+		TELEMETRY_LOG_ERR("Telemetry argument has not been initialised");
+		return -EINVAL;
+	}
+
+	char *command = "ports_details";
+
+	if (inv_choice == INV_ACTION_VAL)
+		action_choice = -1;
+	else if (inv_choice == INV_COMMAND_VAL)
+		command = "INVALID_COMMAND";
+	else if (inv_choice == INV_DATA_VAL)
+		port_ids = NULL;
+
+
+	ret = rte_telemetry_create_json_request(action_choice, command, NULL,
+		port_ids, num_port_ids, NULL, -1, &request, inv_choice);
+	if (ret < 0) {
+		TELEMETRY_LOG_ERR("Could not create JSON Request");
+		return -1;
+	}
+
+	if (inv_choice == INV_JSON_FORMAT)
+		request++;
+
+	ret = rte_telemetry_parse(telemetry, request);
+	if (ret < 0) {
+		TELEMETRY_LOG_WARN("Could not parse JSON Request");
+		return -1;
+	}
+
+	return 0;
+}
+
+int32_t
+rte_telemetry_send_stats_values_by_name_request(struct telemetry_impl
+	*telemetry, int action_choice, int *port_ids, int num_port_ids,
+	const char * const *stat_names, int num_stat_names,
+	int inv_choice)
+{
+	int ret;
+	char *request;
+	char *command = "ports_stats_values_by_name";
+
+	if (!telemetry) {
+		TELEMETRY_LOG_ERR("Telemetry argument has not been initialised");
+		return -EINVAL;
+	}
+
+	if (inv_choice == INV_ACTION_VAL)
+		action_choice = -1;
+	else if (inv_choice == INV_COMMAND_VAL)
+		command = "INVALID_COMMAND";
+	else if (inv_choice == INV_DATA_VAL) {
+		port_ids = NULL;
+		stat_names = NULL;
+	}
+
+	ret = rte_telemetry_create_json_request(action_choice, command, NULL,
+		port_ids, num_port_ids, stat_names, num_stat_names, &request,
+		inv_choice);
+	if (ret < 0) {
+		TELEMETRY_LOG_ERR("Could not create JSON Request");
+		return -1;
+	}
+
+	if (inv_choice == INV_JSON_FORMAT)
+		request++;
+
+	ret = rte_telemetry_parse(telemetry, request);
+	if (ret < 0) {
+		TELEMETRY_LOG_WARN("Could not parse JSON Request");
+		return -1;
+	}
+
+	return 0;
+}
+
+int32_t
+rte_telemetry_send_unreg_request(struct telemetry_impl *telemetry,
+	int action_choice, const char *client_path, int inv_choice)
+{
+	int ret;
+	char *request;
+
+	if (!telemetry) {
+		TELEMETRY_LOG_ERR("Telemetry argument has not been initialised");
+		return -EINVAL;
+	}
+
+	char *command = "clients";
+
+	if (inv_choice == INV_ACTION_VAL)
+		action_choice = -1;
+	else if (inv_choice == INV_COMMAND_VAL)
+		command = "INVALID_COMMAND";
+	else if (inv_choice == INV_DATA_VAL)
+		client_path = NULL;
+
+	ret = rte_telemetry_create_json_request(action_choice, command,
+		client_path, NULL, -1, NULL, -1, &request, inv_choice);
+	if (ret < 0) {
+		TELEMETRY_LOG_ERR("Could not create JSON Request");
+		return -1;
+	}
+
+	if (inv_choice == INV_JSON_FORMAT)
+		request++;
+
+	ret = rte_telemetry_parse(telemetry, request);
+	if (ret < 0) {
+		TELEMETRY_LOG_WARN("Could not parse JSON Request");
+		return -1;
+	}
+
+	return 0;
+}
+
+int32_t
+rte_telemetry_parser_test(struct telemetry_impl *telemetry)
+{
+	int ret;
+	const char *client_path = TEST_CLIENT;
+
+	if (!telemetry) {
+		TELEMETRY_LOG_ERR("Telemetry argument has not been initialised");
+		return -EINVAL;
+	}
+
+	ret = rte_telemetry_create_test_socket(telemetry, client_path);
+	if (ret < 0) {
+		TELEMETRY_LOG_ERR("Could not create test request client socket");
+		return -1;
+	}
+
+	int port_ids[] = {0, 1};
+	int num_port_ids = RTE_DIM(port_ids);
+
+	static const char * const stat_names[] = {"tx_good_packets",
+		"rx_good_packets"};
+	int num_stat_names = RTE_DIM(stat_names);
+
+	static const char * const test_types[] = {
+		"INVALID ACTION VALUE TESTS",
+		"INVALID COMMAND VALUE TESTS",
+		"INVALID DATA VALUE TESTS",
+		"INVALID ACTION FIELD TESTS",
+		"INVALID COMMAND FIELD TESTS",
+		"INVALID DATA FIELD TESTS",
+		"INVALID JSON FORMAT TESTS",
+		"VALID TESTS"
+	};
+
+
+#define NUM_TEST_TYPES (sizeof(test_types)/sizeof(const char * const))
+
+	uint32_t i;
+	for (i = 0; i < NUM_TEST_TYPES; i++) {
+		TELEMETRY_LOG_INFO("%s", test_types[i]);
+
+		ret = rte_telemetry_send_get_ports_and_stats_request(telemetry,
+			ACTION_GET, "ports", i);
+		if (ret != 0 && i == VALID_REQ) {
+			TELEMETRY_LOG_ERR("Get ports valid test failed");
+			return -EPERM;
+		} else if (ret != -1 && i != VALID_REQ) {
+			TELEMETRY_LOG_ERR("Get ports invalid test failed");
+			return -EPERM;
+		}
+
+		TELEMETRY_LOG_INFO("Success - Get ports test passed");
+
+		ret = rte_telemetry_send_get_ports_details_request(telemetry,
+			ACTION_GET, port_ids, num_port_ids, i);
+		if (ret != 0 && i == VALID_REQ) {
+			TELEMETRY_LOG_ERR("Get ports details valid");
+			return -EPERM;
+		} else if (ret != -1 && i != VALID_REQ) {
+			TELEMETRY_LOG_ERR("Get ports details invalid");
+			return -EPERM;
+		}
+
+		TELEMETRY_LOG_INFO("Success - Get ports details test passed");
+
+		ret = rte_telemetry_send_get_ports_and_stats_request(telemetry,
+			ACTION_GET, "port_stats", i);
+		if (ret != 0  && i == VALID_REQ) {
+			TELEMETRY_LOG_ERR("Get port stats valid test");
+			return -EPERM;
+		} else if (ret != -1 && i != VALID_REQ) {
+			TELEMETRY_LOG_ERR("Get ports stats invalid test failed");
+			return -EPERM;
+		}
+
+		TELEMETRY_LOG_INFO("Success - Get ports stats test passed");
+
+		ret = rte_telemetry_send_stats_values_by_name_request(telemetry,
+			ACTION_GET, port_ids, num_port_ids, stat_names,
+			num_stat_names, i);
+		if (ret != 0 && i == VALID_REQ) {
+			TELEMETRY_LOG_ERR("Get ports stats values by name valid test failed");
+			return -EPERM;
+		} else if (ret != -1 && i != VALID_REQ) {
+			TELEMETRY_LOG_ERR("Get ports stats values by name invalid test failed");
+			return -EPERM;
+		}
+
+		TELEMETRY_LOG_INFO("Success - Get ports stats values by name test passed");
+
+		ret = rte_telemetry_send_unreg_request(telemetry, ACTION_DELETE,
+			client_path, i);
+		if (ret != 0 && i == VALID_REQ) {
+			TELEMETRY_LOG_ERR("Deregister valid test failed");
+			return -EPERM;
+		} else if (ret != -1 && i != VALID_REQ) {
+			TELEMETRY_LOG_ERR("Deregister invalid test failed");
+			return -EPERM;
+		}
+
+		TELEMETRY_LOG_INFO("Success - Deregister test passed");
+	}
+
+	return 0;
+}
diff --git a/lib/librte_telemetry/rte_telemetry_parser_test.h b/lib/librte_telemetry/rte_telemetry_parser_test.h
new file mode 100644
index 0000000..6ada852
--- /dev/null
+++ b/lib/librte_telemetry/rte_telemetry_parser_test.h
@@ -0,0 +1,39 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2018 Intel Corporation
+ */
+
+#ifndef _RTE_TELEMETRY_PARSER_TEST_H_
+#define _RTE_TELEMETRY_PARSER_TEST_H_
+
+int32_t
+rte_telemetry_parser_test(struct telemetry_impl *telemetry);
+
+int32_t
+rte_telemetry_format_port_stat_ids(int *port_ids, int num_port_ids,
+	const char * const stat_names, int num_stat_names, json_t **data);
+
+int32_t
+rte_telemetry_create_json_request(int action, char *command,
+	const char *client_path, int *port_ids, int num_port_ids,
+	const char * const stat_names, int num_stat_names, char **request,
+	int inv_choice);
+
+int32_t
+rte_telemetry_send_get_ports_and_stats_request(struct telemetry_impl *telemetry,
+	int action_choice, char *command_choice, int inv_choice);
+
+int32_t
+rte_telemetry_send_get_ports_details_request(struct telemetry_impl *telemetry,
+	int action_choice, int *port_ids, int num_port_ids, int inv_choice);
+
+int32_t
+rte_telemetry_send_stats_values_by_name_request(struct telemetry_impl
+	*telemetry, int action_choice, int *port_ids, int num_port_ids,
+	const char * const stat_names, int num_stat_names,
+	int inv_choice);
+
+int32_t
+rte_telemetry_send_unreg_request(int action_choice, const char *client_path,
+	int inv_choice);
+
+#endif
diff --git a/lib/librte_telemetry/rte_telemetry_socket_tests.h b/lib/librte_telemetry/rte_telemetry_socket_tests.h
new file mode 100644
index 0000000..db9167c
--- /dev/null
+++ b/lib/librte_telemetry/rte_telemetry_socket_tests.h
@@ -0,0 +1,36 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2018 Intel Corporation
+ */
+
+#include <stdbool.h>
+
+#include "rte_telemetry_internal.h"
+
+#ifndef _RTE_TELEMETRY_SOCKET_TESTING_H_
+#define _RTE_TELEMETRY_SOCKET_TESTING_H_
+
+int32_t
+rte_telemetry_json_socket_message_test(struct telemetry_impl *telemetry,
+	int fd);
+
+int32_t
+rte_telemetry_invalid_json_test(struct telemetry_impl *telemetry, int fd);
+
+int32_t
+rte_telemetry_valid_json_test(struct telemetry_impl *telemetry, int fd);
+
+int32_t
+rte_telemetry_json_contents_test(struct telemetry_impl *telemetry, int fd);
+
+int32_t
+rte_telemetry_json_empty_test(struct telemetry_impl *telemetry, int fd);
+
+int32_t
+rte_telemetry_socket_register_test(struct telemetry_impl *telemetry, int *fd,
+	int send_fd, int recv_fd);
+
+int32_t
+rte_telemetry_socket_test_setup(struct telemetry_impl *telemetry, int *send_fd,
+	int *recv_fd);
+
+#endif
diff --git a/lib/librte_telemetry/rte_telemetry_version.map b/lib/librte_telemetry/rte_telemetry_version.map
index 992d227..98459fc 100644
--- a/lib/librte_telemetry/rte_telemetry_version.map
+++ b/lib/librte_telemetry/rte_telemetry_version.map
@@ -2,5 +2,6 @@ DPDK_18.11 {
 	global:
 
 	rte_telemetry_init;
+	rte_telemetry_selftest;
 	local: *;
 };
-- 
2.9.5



More information about the dev mailing list