1 /* check-sources:disable-copyright-check */
2 /* unit test the code in droplet.c */
3 #include <sys/types.h>
4 #include <stdio.h>
5 #include <sys/stat.h>
6 #include <fcntl.h>
7 #include <unistd.h>
8 #include <stdlib.h>
9 #include <check.h>
10 #include <droplet.h>
11 #include <droplet/backend.h>
12 #include <arpa/inet.h>
13 #include <errno.h>
14
15 #include "testutils.h"
16 #include "utest_main.h"
17
18 #ifdef __linux__
19
20 static int bound_sock = -1;
21
get_bound_port(char * host,int maxlen)22 static int get_bound_port(char* host, int maxlen)
23 {
24 struct sockaddr_in sin;
25 socklen_t len;
26 int sock;
27 int r;
28
29 /* only one at a time */
30 fail_if(bound_sock >= 0, NULL);
31
32 /* create a TCP socket */
33 sock = socket(PF_INET, SOCK_STREAM, 0);
34 fail_if(sock < 0, strerror(errno));
35
36 /* bind the socket to some port on localhost */
37 memset(&sin, 0, sizeof(sin));
38 sin.sin_family = AF_INET;
39 sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
40 sin.sin_port = 0; /* kernel allocates */
41 r = bind(sock, (struct sockaddr*)&sin, sizeof(sin));
42 fail_if(r < 0, strerror(errno));
43
44 /* note: we never listen() on the socket, so attempts
45 * to connect() should fail immediately with ECONNREFUSED */
46
47 /* find out what port the kernel chose for us */
48 len = sizeof(sin);
49 r = getsockname(sock, (struct sockaddr*)&sin, &len);
50 fail_if(r < 0, strerror(errno));
51
52 bound_sock = sock;
53
54 snprintf(host, maxlen, "%s:%d", inet_ntoa(sin.sin_addr), ntohs(sin.sin_port));
55 return 0;
56 }
57
release_bound_port(void)58 static void release_bound_port(void)
59 {
60 if (bound_sock >= 0) {
61 close(bound_sock);
62 bound_sock = -1;
63 }
64 }
65
setup(void)66 static void setup(void)
67 {
68 /* Make sure there's a new unique directory which
69 * is returned as our home directory by getpwuid() */
70 home = make_unique_directory();
71
72 dpl_init();
73 }
74
teardown(void)75 static void teardown(void)
76 {
77 release_bound_port();
78
79 rmtree(home);
80 free(home);
81 home = NULL;
82 }
83
START_TEST(ctx_new_defaults_test)84 START_TEST(ctx_new_defaults_test)
85 {
86 dpl_ctx_t* ctx;
87 char* dropdir;
88 char* profile;
89
90 /* Make sure there's a directory ~/.droplet
91 * containing a file default.profile */
92 dropdir = make_sub_directory(home, ".droplet");
93 profile = write_file(dropdir, "default.profile",
94 "host = localhost\n"
95 "base_path = /polaroid\n");
96
97 /* create a context with all defaults */
98 unsetenv("DPLDIR");
99 unsetenv("DPLPROFILE");
100 ctx = dpl_ctx_new(NULL, NULL);
101 dpl_assert_ptr_not_null(ctx);
102 dpl_assert_str_eq(ctx->droplet_dir, dropdir);
103 dpl_assert_str_eq(ctx->profile_name, "default");
104 dpl_assert_str_eq(ctx->base_path, "/polaroid");
105
106 dpl_ctx_free(ctx);
107 free(dropdir);
108 free(profile);
109 }
110 END_TEST
111
START_TEST(ctx_new_dropdir_test)112 START_TEST(ctx_new_dropdir_test)
113 {
114 dpl_ctx_t* ctx;
115 char* dropdir;
116 char* profile;
117
118 /* Make sure there's a new unique directory,
119 * which is *NOT* ~/.droplet, and contains a
120 * file default.profile
121 * random word courtesy http://hipsteripsum.me/ */
122 dropdir = make_sub_directory(home, "williamsburg");
123 profile = write_file(dropdir, "default.profile",
124 "host = localhost\n"
125 "base_path = /polaroid\n");
126
127 /* create a context with all droplet dir named via the environment */
128 setenv("DPLDIR", dropdir, /*overwrite*/ 1);
129 unsetenv("DPLPROFILE");
130 ctx = dpl_ctx_new(NULL, NULL);
131 dpl_assert_ptr_not_null(ctx);
132 dpl_assert_str_eq(ctx->droplet_dir, dropdir);
133 dpl_assert_str_eq(ctx->profile_name, "default");
134 dpl_assert_str_eq(ctx->base_path, "/polaroid");
135
136 dpl_ctx_free(ctx);
137 free(dropdir);
138 free(profile);
139 }
140 END_TEST
141
START_TEST(ctx_new_dropdir_profile_test)142 START_TEST(ctx_new_dropdir_profile_test)
143 {
144 dpl_ctx_t* ctx;
145 char* dropdir;
146 char* profile;
147
148 /* Make sure there's a new unique directory,
149 * which is *NOT* ~/.droplet, and contains a
150 * file profile *NOT* called default.profile. */
151 dropdir = make_sub_directory(home, "quinoa");
152 profile = write_file(dropdir, "sriracha.profile",
153 "host = localhost\n"
154 "base_path = /polaroid\n");
155
156 /* create a context with profile named via the environment */
157 setenv("DPLDIR", dropdir, /*overwrite*/ 1);
158 setenv("DPLPROFILE", "sriracha", /*overwrite*/ 1);
159 ctx = dpl_ctx_new(NULL, NULL);
160 dpl_assert_ptr_not_null(ctx);
161 dpl_assert_str_eq(ctx->droplet_dir, dropdir);
162 dpl_assert_str_eq(ctx->profile_name, "sriracha");
163 dpl_assert_str_eq(ctx->base_path, "/polaroid");
164
165 dpl_ctx_free(ctx);
166 free(dropdir);
167 free(profile);
168 }
169 END_TEST
170
START_TEST(ctx_new_from_dict_test)171 START_TEST(ctx_new_from_dict_test)
172 {
173 dpl_ctx_t* ctx;
174 dpl_dict_t* profile;
175 char* dropdir;
176
177 /* At this point we have a pretend ~ with NO
178 * .droplet/ directory in it. We don't create
179 * a dropdir on disk at all, nor a profile, it's
180 * all in memory. */
181 dropdir = strconcat(home, "/trust-fund", (char*)NULL); /* never created */
182
183 profile = dpl_dict_new(13);
184 dpl_assert_ptr_not_null(profile);
185 dpl_assert_int_eq(DPL_SUCCESS, dpl_dict_add(profile, "host", "localhost", 0));
186 dpl_assert_int_eq(DPL_SUCCESS,
187 dpl_dict_add(profile, "base_path", "/polaroid", 0));
188 dpl_assert_int_eq(DPL_SUCCESS,
189 dpl_dict_add(profile, "droplet_dir", dropdir, 0));
190 dpl_assert_int_eq(DPL_SUCCESS,
191 dpl_dict_add(profile, "profile_name", "viral", 0));
192 /* need this to disable the event log, otherwise the droplet_dir needs to
193 * exist */
194 dpl_assert_int_eq(DPL_SUCCESS, dpl_dict_add(profile, "pricing_dir", "", 0));
195
196 /* create a context with all defaults */
197 unsetenv("DPLDIR");
198 unsetenv("DPLPROFILE");
199 ctx = dpl_ctx_new_from_dict(profile);
200 dpl_assert_ptr_not_null(ctx);
201 dpl_assert_str_eq(ctx->droplet_dir, dropdir);
202 dpl_assert_str_eq(ctx->profile_name, "viral");
203 dpl_assert_str_eq(ctx->base_path, "/polaroid");
204
205 dpl_ctx_free(ctx);
206 dpl_dict_free(profile);
207 free(dropdir);
208 }
209 END_TEST
210
211 /* actually set some non-default parameters to check they're parsed */
START_TEST(ctx_new_params_test)212 START_TEST(ctx_new_params_test)
213 {
214 dpl_ctx_t* ctx;
215 char* dropdir;
216 char* profile;
217 char* ssl_cert_file;
218 char* ssl_key_file;
219 char* ssl_ca_cert_file;
220 char* profdata;
221 char* pricing;
222 static const char ssl_cert[] =
223 /* This is actually a chain, starting with the client cert
224 * and ending with the CA cert, in that order */
225 # include "ssldata/client-cert.c"
226 # include "ssldata/demoCA/cacert.c"
227 ;
228 static const char ssl_key[] =
229 # include "ssldata/client.c"
230 ;
231 static const char ssl_ca_cert[] =
232 # include "ssldata/demoCA/cacert.c"
233 ;
234
235 /* Make sure there's a directory ~/.droplet
236 * containing a file default.profile */
237 dropdir = make_sub_directory(home, ".droplet");
238 ssl_cert_file = write_file(dropdir, "disrupt.pem", ssl_cert);
239 ssl_key_file = write_file(dropdir, "fingerstache.pem", ssl_key);
240 ssl_ca_cert_file = write_file(home, "stumptown.pem", ssl_ca_cert);
241 pricing = write_file(dropdir, "pitchfork.pricing", "# this file is empty\n");
242 profdata = strconcat(
243 "use_https = true\n"
244 "host = localhost\n"
245 "blacklist_expiretime = 42\n"
246 "header_size = 12345\n"
247 "base_path = /polaroid\n"
248 "access_key = letterpress\n"
249 "secret_key = freegan\n"
250 "ssl_cert_file = disrupt.pem\n"
251 "ssl_key_file = fingerstache.pem\n"
252 "ssl_password = mixtape\n"
253 "ssl_ca_list = ",
254 home,
255 "/stumptown.pem\n"
256 "pricing = pitchfork\n"
257 "read_buf_size = 8765\n"
258 "encrypt_key = distillery\n"
259 "backend = swift\n"
260 "encode_slashes = true\n",
261 (char*)NULL);
262 profile = write_file(dropdir, "default.profile", profdata);
263
264 /* create a context with all defaults */
265 unsetenv("DPLDIR");
266 unsetenv("DPLPROFILE");
267 ctx = dpl_ctx_new(NULL, NULL);
268 dpl_assert_ptr_not_null(ctx);
269 dpl_assert_str_eq(ctx->droplet_dir, dropdir);
270 dpl_assert_str_eq(ctx->profile_name, "default");
271 dpl_assert_str_eq(ctx->base_path, "/polaroid");
272 dpl_assert_bit_eq(ctx->use_https, 1);
273 dpl_assert_int_eq(ctx->blacklist_expiretime, 42);
274 dpl_assert_int_eq(ctx->header_size, 12345);
275 dpl_assert_str_eq(ctx->access_key, "letterpress");
276 dpl_assert_str_eq(ctx->secret_key, "freegan");
277 dpl_assert_str_eq(ctx->ssl_cert_file, ssl_cert_file);
278 dpl_assert_str_eq(ctx->ssl_key_file, ssl_key_file);
279 dpl_assert_str_eq(ctx->ssl_password, "mixtape");
280 dpl_assert_str_eq(ctx->ssl_ca_list, ssl_ca_cert_file);
281 dpl_assert_str_eq(ctx->pricing, "pitchfork");
282 dpl_assert_ptr_null(ctx->pricing_dir);
283 dpl_assert_int_eq(ctx->read_buf_size, 8765);
284 dpl_assert_str_eq(ctx->encrypt_key, "distillery");
285 dpl_assert_str_eq(ctx->backend->name, "swift");
286 dpl_assert_bit_eq(ctx->encode_slashes, 1);
287
288 dpl_ctx_free(ctx);
289 free(dropdir);
290 free(profile);
291 free(ssl_cert_file);
292 free(ssl_key_file);
293 free(ssl_ca_cert_file);
294 free(pricing);
295 free(profdata);
296 }
297 END_TEST
298
redirect_stdio(FILE * fp,const char * name)299 static void redirect_stdio(FILE* fp, const char* name)
300 {
301 char* logfile;
302 int fd;
303
304 logfile = strconcat(home, "/", name, ".log", (char*)NULL);
305 fd = open(logfile, O_WRONLY | O_CREAT | O_TRUNC, 0666);
306 if (fd < 0) {
307 perror(logfile);
308 fail();
309 }
310 fflush(fp);
311 if (dup2(fd, fileno(fp)) < 0) {
312 perror("dup2");
313 fail();
314 }
315 close(fd);
316 free(logfile);
317 }
318
319 # define MAXLOGGED 128
320 static struct {
321 dpl_log_level_t level;
322 char* message;
323 } logged[MAXLOGGED];
324 static int nlogged = 0;
325
log_find(const char * needle)326 static int log_find(const char* needle)
327 {
328 int i;
329
330 for (i = 0; i < nlogged; i++) {
331 if (strstr(logged[i].message, needle)) return i;
332 }
333
334 return -1;
335 }
336
log_func(dpl_ctx_t * ctx,dpl_log_level_t level,const char * message)337 static void log_func(dpl_ctx_t* ctx, dpl_log_level_t level, const char* message)
338 {
339 fail_unless(nlogged < MAXLOGGED);
340 logged[nlogged].level = level;
341 logged[nlogged].message = strdup(message);
342 nlogged++;
343 }
344
START_TEST(logging_test)345 START_TEST(logging_test)
346 {
347 dpl_ctx_t* ctx;
348 dpl_dict_t* profile;
349 char* dropdir;
350 off_t logsize;
351 int nerrs;
352
353 /* because libcheck forks a new process for each testcase, we
354 * can feel free to futz around with the process state like stderr */
355 redirect_stdio(stdout, "stdout");
356 redirect_stdio(stderr, "stderr");
357
358 /* At this point we have a pretend ~ with NO
359 * .droplet/ directory in it. We don't create
360 * a dropdir on disk at all, nor a profile, it's
361 * all in memory. */
362 dropdir = strconcat(home, "/trust-fund", (char*)NULL); /* never created */
363
364 profile = dpl_dict_new(13);
365 dpl_assert_ptr_not_null(profile);
366 /* the host is an unparseable IPv4 address literal, which should
367 * result in a predicable error */
368 dpl_assert_int_eq(DPL_SUCCESS,
369 dpl_dict_add(profile, "host", "123.456.789.012", 0));
370 dpl_assert_int_eq(DPL_SUCCESS,
371 dpl_dict_add(profile, "droplet_dir", dropdir, 0));
372 dpl_assert_int_eq(DPL_SUCCESS,
373 dpl_dict_add(profile, "profile_name", "viral", 0));
374 /* need this to disable the event log, otherwise the droplet_dir needs to
375 * exist */
376 dpl_assert_int_eq(DPL_SUCCESS, dpl_dict_add(profile, "pricing_dir", "", 0));
377 unsetenv("DPLDIR");
378 unsetenv("DPLPROFILE");
379
380 /* Create a context which will fail with a logged error message.
381 * By default error messages go to stderr only. */
382 dpl_assert_int_eq(0, file_size(stdout));
383 dpl_assert_int_eq(0, file_size(stderr));
384 ctx = dpl_ctx_new_from_dict(profile);
385 dpl_assert_ptr_null(ctx);
386 /* nothing went to stdout */
387 dpl_assert_int_eq(0, file_size(stdout));
388 /* something went to stderr */
389 logsize = file_size(stderr);
390 fail_unless(logsize > 0);
391
392 /* Create a context which will fail with a logged error message.
393 * Calling dpl_set_log_func redirects errors to the given function. */
394 dpl_assert_int_eq(nlogged, 0);
395 dpl_set_log_func(log_func);
396 ctx = dpl_ctx_new_from_dict(profile);
397 dpl_assert_ptr_null(ctx);
398 /* at least one error message logged */
399 dpl_assert_int_ne(nlogged, 0);
400 /* nothing went to stdout */
401 dpl_assert_int_eq(0, file_size(stdout));
402 /* nothing more went to stderr */
403 dpl_assert_int_eq(logsize, file_size(stderr));
404 /* errors mention the broken address */
405 dpl_assert_int_ne(-1, log_find("123.456.789.012"));
406
407 /* Create a context which will fail with a logged error message.
408 * Calling dpl_set_log_func(NULL) redirects errors back to stderr. */
409 nerrs = nlogged;
410 dpl_set_log_func(NULL);
411 ctx = dpl_ctx_new_from_dict(profile);
412 dpl_assert_ptr_null(ctx);
413 /* no more errors went to the log function */
414 dpl_assert_int_eq(nerrs, nlogged);
415 /* nothing went to stdout */
416 dpl_assert_int_eq(0, file_size(stdout));
417 /* some more went to stderr */
418 fail_unless(file_size(stderr) > logsize);
419
420 /* cleanup */
421 dpl_dict_free(profile);
422 free(dropdir);
423 }
424 END_TEST
425
426 /* Create a context talking to a valid address on which nothing
427 * is listening. This will fail during connect, and should log
428 * a message. */
START_TEST(connect_logging_test)429 START_TEST(connect_logging_test)
430 {
431 dpl_ctx_t* ctx;
432 dpl_dict_t* profile;
433 dpl_req_t* req;
434 dpl_conn_t* conn = NULL;
435 char* dropdir;
436 int r;
437 dpl_status_t s;
438 char host[128];
439
440 /* because libcheck forks a new process for each testcase, we
441 * can feel free to futz around with the process state like stderr */
442 redirect_stdio(stdout, "stdout");
443 redirect_stdio(stderr, "stderr");
444
445 /* At this point we have a pretend ~ with NO
446 * .droplet/ directory in it. We don't create
447 * a dropdir on disk at all, nor a profile, it's
448 * all in memory. */
449 dropdir = strconcat(home, "/trust-fund", (char*)NULL); /* never created */
450
451 r = get_bound_port(host, sizeof(host));
452 fail_if(r < 0, NULL);
453
454 profile = dpl_dict_new(13);
455 dpl_assert_ptr_not_null(profile);
456 dpl_assert_int_eq(DPL_SUCCESS, dpl_dict_add(profile, "host", host, 0));
457 dpl_assert_int_eq(DPL_SUCCESS,
458 dpl_dict_add(profile, "droplet_dir", dropdir, 0));
459 dpl_assert_int_eq(DPL_SUCCESS,
460 dpl_dict_add(profile, "profile_name", "viral", 0));
461 /* need this to disable the event log, otherwise the droplet_dir needs to
462 * exist */
463 dpl_assert_int_eq(DPL_SUCCESS, dpl_dict_add(profile, "pricing_dir", "", 0));
464 unsetenv("DPLDIR");
465 unsetenv("DPLPROFILE");
466
467 dpl_assert_int_eq(nlogged, 0);
468 dpl_set_log_func(log_func);
469
470 /* create the profile */
471 ctx = dpl_ctx_new_from_dict(profile);
472 dpl_assert_ptr_not_null(ctx);
473 /* no errors so far */
474 dpl_assert_int_eq(nlogged, 0);
475 /* nothing went to stdout */
476 dpl_assert_int_eq(0, file_size(stdout));
477 /* nothing went to stderr */
478 dpl_assert_int_eq(0, file_size(stderr));
479
480 req = dpl_req_new(ctx);
481 dpl_assert_ptr_not_null(req);
482 req->behavior_flags &= ~DPL_BEHAVIOR_VIRTUAL_HOSTING;
483
484 /* attempt to connect */
485 s = dpl_try_connect(ctx, req, &conn);
486 dpl_assert_int_eq(DPL_FAILURE, s);
487 dpl_assert_ptr_null(conn);
488 /* at least 1 error logged */
489 dpl_assert_int_ne(nlogged, 0);
490 /* an error mentions the broken address */
491 dpl_assert_int_ne(-1, log_find(host));
492 /* an error mentions the libc error message */
493 dpl_assert_int_ne(-1, log_find(strerror(ECONNREFUSED)));
494 /* nothing went to stdout */
495 dpl_assert_int_eq(0, file_size(stdout));
496 /* nothing went to stderr */
497 dpl_assert_int_eq(0, file_size(stderr));
498
499 dpl_req_free(req);
500 dpl_ctx_free(ctx);
501 dpl_dict_free(profile);
502 release_bound_port();
503 free(dropdir);
504 }
505 END_TEST
506
507
profile_suite()508 Suite* profile_suite()
509 {
510 Suite* s = suite_create("profile");
511 TCase* t = tcase_create("base");
512 tcase_set_timeout(t, 45);
513 tcase_add_checked_fixture(t, setup, teardown);
514 tcase_add_test(t, ctx_new_defaults_test);
515 tcase_add_test(t, ctx_new_dropdir_test);
516 tcase_add_test(t, ctx_new_dropdir_profile_test);
517 tcase_add_test(t, ctx_new_from_dict_test);
518 tcase_add_test(t, ctx_new_params_test);
519 tcase_add_test(t, logging_test);
520 tcase_add_test(t, connect_logging_test);
521 suite_add_tcase(s, t);
522 return s;
523 }
524
525 #endif /* __linux__ */
526