Initial CMocka testing against web_client.c (issue #7229). (#7264)
* Start of unit-test for http request processing. A CMocka test-driver has been added to replay the live-capture headers against the http request processing code. Enough stubs / mocking code have been written to check that we can inject a chosen raw request into the web_client code and check how it was processed. A parameterised buffer building function can setup requests for testing in `web_buffer` structures. Each endpoint in the API will be checked against a variety of http requests. This PoC demonstrates enough functionality to show building the request on demand for each test-case and cleaning up afterwards.
Andrew Moss committed
Nov 7, 2019 at 15:29 UTC
87a7a044d58a518740f2a14e2266b5631c4aee10
2 files changed
+190
Makefile.am
+22
@@ -644,10 +644,32 @@ if ENABLE_UNITTESTS
644
check_PROGRAMS = \
645
libnetdata/tests/str2ld_testdriver \
646
libnetdata/storage_number/tests/storage_number_testdriver \
647
+ web/api/tests/web_api_testdriver \
648
$(NULL)
649
650
TESTS = $(check_PROGRAMS)
651
652
+ web_api_tests_web_api_testdriver_LDFLAGS = \
653
+ -Wl,--wrap=rrdhost_find_by_hostname \
654
+ -Wl,--wrap=finished_web_request_statistics \
655
+ -Wl,--wrap=config_get \
656
+ -Wl,--wrap=web_client_api_request_v1 \
657
+ -Wl,--wrap=rrdhost_find_by_guid \
658
+ -Wl,--wrap=rrdset_find_byname \
659
+ -Wl,--wrap=rrdset_find \
660
+ -Wl,--wrap=rrdpush_receiver_thread_spawn \
661
+ -Wl,--wrap=debug_int \
662
+ -Wl,--wrap=error_int \
663
+ -Wl,--wrap=info_int \
664
+ $(TEST_LDFLAGS) \
665
+ $(NULL)
666
+ web_api_tests_web_api_testdriver_SOURCES = \
667
+ web/api/tests/web_api.c \
668
+ web/server/web_client.c \
669
+ $(LIBNETDATA_FILES) \
670
+ $(NULL)
671
+ web_api_tests_web_api_testdriver_LDADD = $(NETDATA_COMMON_LIBS) $(TEST_LIBS)
672
+
673
libnetdata_tests_str2ld_testdriver_SOURCES = \
674
libnetdata/tests/test_str2ld.c \
675
$(LIBNETDATA_FILES) \
web/api/tests/web_api.c
new
+168
@@ -0,0 +1,168 @@
1
+// SPDX-License-Identifier: GPL-3.0-or-later
2
+
3
+#include "../../../libnetdata/libnetdata.h"
4
+#include "../../../libnetdata/required_dummies.h"
5
+#include "../../../database/rrd.h"
6
+#include "../../../web/server/web_client.h"
7
+#include <setjmp.h>
8
+#include <cmocka.h>
9
+#include <stdbool.h>
10
+
11
+RRDHOST *__wrap_rrdhost_find_by_hostname(const char *hostname, uint32_t hash)
12
+{
13
+ return NULL;
14
+}
15
+
16
+/* Note: we've got some intricate code inside the global statistics module, might be useful to pull it inside the
17
+ test set instead of mocking it. */
18
+void __wrap_finished_web_request_statistics(
19
+ uint64_t dt, uint64_t bytes_received, uint64_t bytes_sent, uint64_t content_size, uint64_t compressed_content_size)
20
+{
21
+}
22
+
23
+char *__wrap_config_get(struct config *root, const char *section, const char *name, const char *default_value)
24
+{
25
+ if (!strcmp(section, CONFIG_SECTION_WEB) && !strcmp(name, "web files owner"))
26
+ return "netdata";
27
+ return "UNKNOWN FIX ME";
28
+}
29
+
30
+int __wrap_web_client_api_request_v1(RRDHOST *host, struct web_client *w, char *url)
31
+{
32
+ printf("api requests: %s\n", url);
33
+}
34
+
35
+int __wrap_rrdpush_receiver_thread_spawn(RRDHOST *host, struct web_client *w, char *url)
36
+{
37
+ return 0;
38
+}
39
+
40
+RRDHOST *__wrap_rrdhost_find_by_guid(const char *guid, uint32_t hash)
41
+{
42
+ printf("FIXME: rrdset_find_guid\n");
43
+ return NULL;
44
+}
45
+
46
+RRDSET *__wrap_rrdset_find_byname(RRDHOST *host, const char *name)
47
+{
48
+ printf("FIXME: rrdset_find_byname\n");
49
+ return NULL;
50
+}
51
+
52
+RRDSET *__wrap_rrdset_find(RRDHOST *host, const char *id)
53
+{
54
+ printf("FIXME: rrdset_find\n");
55
+ return NULL;
56
+}
57
+
58
+void __wrap_debug_int(const char *file, const char *function, const unsigned long line, const char *fmt, ...)
59
+{
60
+ va_list args;
61
+ va_start(args, fmt);
62
+ printf("DEBUG: ");
63
+ vprintf(fmt, args);
64
+ printf("\n");
65
+ va_end(args);
66
+}
67
+
68
+void __wrap_info_int(const char *file, const char *function, const unsigned long line, const char *fmt, ...)
69
+{
70
+ va_list args;
71
+ va_start(args, fmt);
72
+ printf("INFO: ");
73
+ vprintf(fmt, args);
74
+ printf("\n");
75
+ va_end(args);
76
+}
77
+
78
+void __wrap_error_int(
79
+ const char *prefix, const char *file, const char *function, const unsigned long line, const char *fmt, ...)
80
+{
81
+ va_list args;
82
+ va_start(args, fmt);
83
+ printf("ERROR: ");
84
+ vprintf(fmt, args);
85
+ printf("\n");
86
+ va_end(args);
87
+}
88
+
89
+WEB_SERVER_MODE web_server_mode = WEB_SERVER_MODE_STATIC_THREADED;
90
+char *netdata_configured_web_dir = "UNKNOWN FIXME";
91
+RRDHOST *localhost = NULL;
92
+
93
+struct config netdata_config = { .sections = NULL,
94
+ .mutex = NETDATA_MUTEX_INITIALIZER,
95
+ .index = { .avl_tree = { .root = NULL, .compar = appconfig_section_compare },
96
+ .rwlock = AVL_LOCK_INITIALIZER } };
97
+
98
+const char *http_headers[] = { "Host: 254.254.0.1",
99
+ "User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_" // No ,
100
+ "0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.70 Safari/537.36",
101
+ "Connection: keep-alive",
102
+ "X-Forwarded-For: 1.254.1.251",
103
+ "Cookie: _ga=GA1.1.1227576758.1571113676; _gid=GA1.2.1222321739.1573628979",
104
+ "X-Requested-With: XMLHttpRequest",
105
+ "Accept-Encoding: gzip, deflate",
106
+ "Cache-Control: no-cache, no-store" };
107
+#define MAX_HEADERS (sizeof(http_headers) / (sizeof(const char *)))
108
+
109
+static void build_request(struct web_buffer *wb, const char *url, bool use_cr, size_t num_headers)
110
+{
111
+ buffer_reset(wb);
112
+ buffer_strcat(wb, "GET ");
113
+ buffer_strcat(wb, url);
114
+ buffer_strcat(wb, " HTTP/1.1");
115
+ if (use_cr)
116
+ buffer_strcat(wb, "\r");
117
+ buffer_strcat(wb, "\n");
118
+ for (size_t i = 0; i < num_headers && i < MAX_HEADERS; i++) {
119
+ buffer_strcat(wb, http_headers[i]);
120
+ if (use_cr)
121
+ buffer_strcat(wb, "\r");
122
+ buffer_strcat(wb, "\n");
123
+ }
124
+ if (use_cr)
125
+ buffer_strcat(wb, "\r");
126
+ buffer_strcat(wb, "\n");
127
+}
128
+
129
+static struct web_client *pre_test_setup()
130
+{
131
+ localhost = malloc(sizeof(RRDHOST));
132
+ struct web_client *w = (struct web_client *)malloc(sizeof(struct web_client));
133
+ w->response.data = buffer_create(NETDATA_WEB_RESPONSE_INITIAL_SIZE);
134
+ w->response.header = buffer_create(NETDATA_WEB_RESPONSE_HEADER_SIZE);
135
+ w->response.header_output = buffer_create(NETDATA_WEB_RESPONSE_HEADER_SIZE);
136
+ strcpy(w->origin, "*"); // Simulate web_client_create_on_fd()
137
+ w->cookie1[0] = 0; // Simulate web_client_create_on_fd()
138
+ w->cookie2[0] = 0; // Simulate web_client_create_on_fd()
139
+ return w;
140
+}
141
+
142
+static void post_test_cleanup(struct web_client *w)
143
+{
144
+ buffer_free(w->response.data);
145
+ buffer_free(w->response.header);
146
+ buffer_free(w->response.header_output);
147
+ free(w);
148
+ free(localhost);
149
+}
150
+
151
+static void api_info(void **state)
152
+{
153
+ for (size_t i = 0; i < MAX_HEADERS; i++) {
154
+ struct web_client *w = pre_test_setup();
155
+ build_request(w->response.data, "/api/v1/info", true, i);
156
+ web_client_process_request(w);
157
+ assert_int_equal(w->flags & WEB_CLIENT_FLAG_WAIT_RECEIVE, 0);
158
+ post_test_cleanup(w);
159
+ }
160
+}
161
+
162
+int main(void)
163
+{
164
+ const struct CMUnitTest tests[] = { cmocka_unit_test(api_info) };
165
+ debug_flags = 0xffffffffffff;
166
+
167
+ return cmocka_run_group_tests_name("web_api", tests, NULL, NULL);
168
+}