t0052: add simple-ipc tests and t/helper/test-simple-ipc tool

Create t0052-simple-ipc.sh with unit tests for the "simple-ipc" mechanism. Create t/helper/test-simple-ipc test tool to exercise the "simple-ipc" functions. When the tool is invoked with "run-daemon", it runs a server to listen for "simple-ipc" connections on a test socket or named pipe and responds to a set of commands to exercise/stress the communication setup. When the tool is invoked with "start-daemon", it spawns a "run-daemon" command in the background and waits for the server to become ready before exiting. (This helps make unit tests in t0052 more predictable and avoids the need for arbitrary sleeps in the test script.) The tool also has a series of client "send" commands to send commands and data to a server instance. Signed-off-by: Jeff Hostetler <jeffhost@microsoft.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Jeff Hostetler committed Mar 22, 2021 at 10:29 UTC 36a7eb68760b7a45ce6b16be300dd04fbe9bd029
5 files changed +912
Makefile
+1
@@ -740,6 +740,7 @@ TEST_BUILTINS_OBJS += test-serve-v2.o
740 TEST_BUILTINS_OBJS += test-sha1.o
741 TEST_BUILTINS_OBJS += test-sha256.o
742 TEST_BUILTINS_OBJS += test-sigchain.o
743 +TEST_BUILTINS_OBJS += test-simple-ipc.o
744 TEST_BUILTINS_OBJS += test-strcmp-offset.o
745 TEST_BUILTINS_OBJS += test-string-list.o
746 TEST_BUILTINS_OBJS += test-submodule-config.o
t/helper/test-simple-ipc.c new
+787
@@ -0,0 +1,787 @@
1 +/*
2 + * test-simple-ipc.c: verify that the Inter-Process Communication works.
3 + */
4 +
5 +#include "test-tool.h"
6 +#include "cache.h"
7 +#include "strbuf.h"
8 +#include "simple-ipc.h"
9 +#include "parse-options.h"
10 +#include "thread-utils.h"
11 +#include "strvec.h"
12 +
13 +#ifndef SUPPORTS_SIMPLE_IPC
14 +int cmd__simple_ipc(int argc, const char **argv)
15 +{
16 + die("simple IPC not available on this platform");
17 +}
18 +#else
19 +
20 +/*
21 + * The test daemon defines an "application callback" that supports a
22 + * series of commands (see `test_app_cb()`).
23 + *
24 + * Unknown commands are caught here and we send an error message back
25 + * to the client process.
26 + */
27 +static int app__unhandled_command(const char *command,
28 + ipc_server_reply_cb *reply_cb,
29 + struct ipc_server_reply_data *reply_data)
30 +{
31 + struct strbuf buf = STRBUF_INIT;
32 + int ret;
33 +
34 + strbuf_addf(&buf, "unhandled command: %s", command);
35 + ret = reply_cb(reply_data, buf.buf, buf.len);
36 + strbuf_release(&buf);
37 +
38 + return ret;
39 +}
40 +
41 +/*
42 + * Reply with a single very large buffer. This is to ensure that
43 + * long response are properly handled -- whether the chunking occurs
44 + * in the kernel or in the (probably pkt-line) layer.
45 + */
46 +#define BIG_ROWS (10000)
47 +static int app__big_command(ipc_server_reply_cb *reply_cb,
48 + struct ipc_server_reply_data *reply_data)
49 +{
50 + struct strbuf buf = STRBUF_INIT;
51 + int row;
52 + int ret;
53 +
54 + for (row = 0; row < BIG_ROWS; row++)
55 + strbuf_addf(&buf, "big: %.75d\n", row);
56 +
57 + ret = reply_cb(reply_data, buf.buf, buf.len);
58 + strbuf_release(&buf);
59 +
60 + return ret;
61 +}
62 +
63 +/*
64 + * Reply with a series of lines. This is to ensure that we can incrementally
65 + * compute the response and chunk it to the client.
66 + */
67 +#define CHUNK_ROWS (10000)
68 +static int app__chunk_command(ipc_server_reply_cb *reply_cb,
69 + struct ipc_server_reply_data *reply_data)
70 +{
71 + struct strbuf buf = STRBUF_INIT;
72 + int row;
73 + int ret;
74 +
75 + for (row = 0; row < CHUNK_ROWS; row++) {
76 + strbuf_setlen(&buf, 0);
77 + strbuf_addf(&buf, "big: %.75d\n", row);
78 + ret = reply_cb(reply_data, buf.buf, buf.len);
79 + }
80 +
81 + strbuf_release(&buf);
82 +
83 + return ret;
84 +}
85 +
86 +/*
87 + * Slowly reply with a series of lines. This is to model an expensive to
88 + * compute chunked response (which might happen if this callback is running
89 + * in a thread and is fighting for a lock with other threads).
90 + */
91 +#define SLOW_ROWS (1000)
92 +#define SLOW_DELAY_MS (10)
93 +static int app__slow_command(ipc_server_reply_cb *reply_cb,
94 + struct ipc_server_reply_data *reply_data)
95 +{
96 + struct strbuf buf = STRBUF_INIT;
97 + int row;
98 + int ret;
99 +
100 + for (row = 0; row < SLOW_ROWS; row++) {
101 + strbuf_setlen(&buf, 0);
102 + strbuf_addf(&buf, "big: %.75d\n", row);
103 + ret = reply_cb(reply_data, buf.buf, buf.len);
104 + sleep_millisec(SLOW_DELAY_MS);
105 + }
106 +
107 + strbuf_release(&buf);
108 +
109 + return ret;
110 +}
111 +
112 +/*
113 + * The client sent a command followed by a (possibly very) large buffer.
114 + */
115 +static int app__sendbytes_command(const char *received,
116 + ipc_server_reply_cb *reply_cb,
117 + struct ipc_server_reply_data *reply_data)
118 +{
119 + struct strbuf buf_resp = STRBUF_INIT;
120 + const char *p = "?";
121 + int len_ballast = 0;
122 + int k;
123 + int errs = 0;
124 + int ret;
125 +
126 + if (skip_prefix(received, "sendbytes ", &p))
127 + len_ballast = strlen(p);
128 +
129 + /*
130 + * Verify that the ballast is n copies of a single letter.
131 + * And that the multi-threaded IO layer didn't cross the streams.
132 + */
133 + for (k = 1; k < len_ballast; k++)
134 + if (p[k] != p[0])
135 + errs++;
136 +
137 + if (errs)
138 + strbuf_addf(&buf_resp, "errs:%d\n", errs);
139 + else
140 + strbuf_addf(&buf_resp, "rcvd:%c%08d\n", p[0], len_ballast);
141 +
142 + ret = reply_cb(reply_data, buf_resp.buf, buf_resp.len);
143 +
144 + strbuf_release(&buf_resp);
145 +
146 + return ret;
147 +}
148 +
149 +/*
150 + * An arbitrary fixed address to verify that the application instance
151 + * data is handled properly.
152 + */
153 +static int my_app_data = 42;
154 +
155 +static ipc_server_application_cb test_app_cb;
156 +
157 +/*
158 + * This is the "application callback" that sits on top of the
159 + * "ipc-server". It completely defines the set of commands supported
160 + * by this application.
161 + */
162 +static int test_app_cb(void *application_data,
163 + const char *command,
164 + ipc_server_reply_cb *reply_cb,
165 + struct ipc_server_reply_data *reply_data)
166 +{
167 + /*
168 + * Verify that we received the application-data that we passed
169 + * when we started the ipc-server. (We have several layers of
170 + * callbacks calling callbacks and it's easy to get things mixed
171 + * up (especially when some are "void*").)
172 + */
173 + if (application_data != (void*)&my_app_data)
174 + BUG("application_cb: application_data pointer wrong");
175 +
176 + if (!strcmp(command, "quit")) {
177 + /*
178 + * The client sent a "quit" command. This is an async
179 + * request for the server to shutdown.
180 + *
181 + * We DO NOT send the client a response message
182 + * (because we have nothing to say and the other
183 + * server threads have not yet stopped).
184 + *
185 + * Tell the ipc-server layer to start shutting down.
186 + * This includes: stop listening for new connections
187 + * on the socket/pipe and telling all worker threads
188 + * to finish/drain their outgoing responses to other
189 + * clients.
190 + *
191 + * This DOES NOT force an immediate sync shutdown.
192 + */
193 + return SIMPLE_IPC_QUIT;
194 + }
195 +
196 + if (!strcmp(command, "ping")) {
197 + const char *answer = "pong";
198 + return reply_cb(reply_data, answer, strlen(answer));
199 + }
200 +
201 + if (!strcmp(command, "big"))
202 + return app__big_command(reply_cb, reply_data);
203 +
204 + if (!strcmp(command, "chunk"))
205 + return app__chunk_command(reply_cb, reply_data);
206 +
207 + if (!strcmp(command, "slow"))
208 + return app__slow_command(reply_cb, reply_data);
209 +
210 + if (starts_with(command, "sendbytes "))
211 + return app__sendbytes_command(command, reply_cb, reply_data);
212 +
213 + return app__unhandled_command(command, reply_cb, reply_data);
214 +}
215 +
216 +struct cl_args
217 +{
218 + const char *subcommand;
219 + const char *path;
220 + const char *token;
221 +
222 + int nr_threads;
223 + int max_wait_sec;
224 + int bytecount;
225 + int batchsize;
226 +
227 + char bytevalue;
228 +};
229 +
230 +static struct cl_args cl_args = {
231 + .subcommand = NULL,
232 + .path = "ipc-test",
233 + .token = NULL,
234 +
235 + .nr_threads = 5,
236 + .max_wait_sec = 60,
237 + .bytecount = 1024,
238 + .batchsize = 10,
239 +
240 + .bytevalue = 'x',
241 +};
242 +
243 +/*
244 + * This process will run as a simple-ipc server and listen for IPC commands
245 + * from client processes.
246 + */
247 +static int daemon__run_server(void)
248 +{
249 + int ret;
250 +
251 + struct ipc_server_opts opts = {
252 + .nr_threads = cl_args.nr_threads,
253 + };
254 +
255 + /*
256 + * Synchronously run the ipc-server. We don't need any application
257 + * instance data, so pass an arbitrary pointer (that we'll later
258 + * verify made the round trip).
259 + */
260 + ret = ipc_server_run(cl_args.path, &opts, test_app_cb, (void*)&my_app_data);
261 + if (ret == -2)
262 + error(_("socket/pipe already in use: '%s'"), cl_args.path);
263 + else if (ret == -1)
264 + error_errno(_("could not start server on: '%s'"), cl_args.path);
265 +
266 + return ret;
267 +}
268 +
269 +#ifndef GIT_WINDOWS_NATIVE
270 +/*
271 + * This is adapted from `daemonize()`. Use `fork()` to directly create and
272 + * run the daemon in a child process.
273 + */
274 +static int spawn_server(pid_t *pid)
275 +{
276 + struct ipc_server_opts opts = {
277 + .nr_threads = cl_args.nr_threads,
278 + };
279 +
280 + *pid = fork();
281 +
282 + switch (*pid) {
283 + case 0:
284 + if (setsid() == -1)
285 + error_errno(_("setsid failed"));
286 + close(0);
287 + close(1);
288 + close(2);
289 + sanitize_stdfds();
290 +
291 + return ipc_server_run(cl_args.path, &opts, test_app_cb,
292 + (void*)&my_app_data);
293 +
294 + case -1:
295 + return error_errno(_("could not spawn daemon in the background"));
296 +
297 + default:
298 + return 0;
299 + }
300 +}
301 +#else
302 +/*
303 + * Conceptually like `daemonize()` but different because Windows does not
304 + * have `fork(2)`. Spawn a normal Windows child process but without the
305 + * limitations of `start_command()` and `finish_command()`.
306 + */
307 +static int spawn_server(pid_t *pid)
308 +{
309 + char test_tool_exe[MAX_PATH];
310 + struct strvec args = STRVEC_INIT;
311 + int in, out;
312 +
313 + GetModuleFileNameA(NULL, test_tool_exe, MAX_PATH);
314 +
315 + in = open("/dev/null", O_RDONLY);
316 + out = open("/dev/null", O_WRONLY);
317 +
318 + strvec_push(&args, test_tool_exe);
319 + strvec_push(&args, "simple-ipc");
320 + strvec_push(&args, "run-daemon");
321 + strvec_pushf(&args, "--name=%s", cl_args.path);
322 + strvec_pushf(&args, "--threads=%d", cl_args.nr_threads);
323 +
324 + *pid = mingw_spawnvpe(args.v[0], args.v, NULL, NULL, in, out, out);
325 + close(in);
326 + close(out);
327 +
328 + strvec_clear(&args);
329 +
330 + if (*pid < 0)
331 + return error(_("could not spawn daemon in the background"));
332 +
333 + return 0;
334 +}
335 +#endif
336 +
337 +/*
338 + * This is adapted from `wait_or_whine()`. Watch the child process and
339 + * let it get started and begin listening for requests on the socket
340 + * before reporting our success.
341 + */
342 +static int wait_for_server_startup(pid_t pid_child)
343 +{
344 + int status;
345 + pid_t pid_seen;
346 + enum ipc_active_state s;
347 + time_t time_limit, now;
348 +
349 + time(&time_limit);
350 + time_limit += cl_args.max_wait_sec;
351 +
352 + for (;;) {
353 + pid_seen = waitpid(pid_child, &status, WNOHANG);
354 +
355 + if (pid_seen == -1)
356 + return error_errno(_("waitpid failed"));
357 +
358 + else if (pid_seen == 0) {
359 + /*
360 + * The child is still running (this should be
361 + * the normal case). Try to connect to it on
362 + * the socket and see if it is ready for
363 + * business.
364 + *
365 + * If there is another daemon already running,
366 + * our child will fail to start (possibly
367 + * after a timeout on the lock), but we don't
368 + * care (who responds) if the socket is live.
369 + */
370 + s = ipc_get_active_state(cl_args.path);
371 + if (s == IPC_STATE__LISTENING)
372 + return 0;
373 +
374 + time(&now);
375 + if (now > time_limit)
376 + return error(_("daemon not online yet"));
377 +
378 + continue;
379 + }
380 +
381 + else if (pid_seen == pid_child) {
382 + /*
383 + * The new child daemon process shutdown while
384 + * it was starting up, so it is not listening
385 + * on the socket.
386 + *
387 + * Try to ping the socket in the odd chance
388 + * that another daemon started (or was already
389 + * running) while our child was starting.
390 + *
391 + * Again, we don't care who services the socket.
392 + */
393 + s = ipc_get_active_state(cl_args.path);
394 + if (s == IPC_STATE__LISTENING)
395 + return 0;
396 +
397 + /*
398 + * We don't care about the WEXITSTATUS() nor
399 + * any of the WIF*(status) values because
400 + * `cmd__simple_ipc()` does the `!!result`
401 + * trick on all function return values.
402 + *
403 + * So it is sufficient to just report the
404 + * early shutdown as an error.
405 + */
406 + return error(_("daemon failed to start"));
407 + }
408 +
409 + else
410 + return error(_("waitpid is confused"));
411 + }
412 +}
413 +
414 +/*
415 + * This process will start a simple-ipc server in a background process and
416 + * wait for it to become ready. This is like `daemonize()` but gives us
417 + * more control and better error reporting (and makes it easier to write
418 + * unit tests).
419 + */
420 +static int daemon__start_server(void)
421 +{
422 + pid_t pid_child;
423 + int ret;
424 +
425 + /*
426 + * Run the actual daemon in a background process.
427 + */
428 + ret = spawn_server(&pid_child);
429 + if (pid_child <= 0)
430 + return ret;
431 +
432 + /*
433 + * Let the parent wait for the child process to get started
434 + * and begin listening for requests on the socket.
435 + */
436 + ret = wait_for_server_startup(pid_child);
437 +
438 + return ret;
439 +}
440 +
441 +/*
442 + * This process will run a quick probe to see if a simple-ipc server
443 + * is active on this path.
444 + *
445 + * Returns 0 if the server is alive.
446 + */
447 +static int client__probe_server(void)
448 +{
449 + enum ipc_active_state s;
450 +
451 + s = ipc_get_active_state(cl_args.path);
452 + switch (s) {
453 + case IPC_STATE__LISTENING:
454 + return 0;
455 +
456 + case IPC_STATE__NOT_LISTENING:
457 + return error("no server listening at '%s'", cl_args.path);
458 +
459 + case IPC_STATE__PATH_NOT_FOUND:
460 + return error("path not found '%s'", cl_args.path);
461 +
462 + case IPC_STATE__INVALID_PATH:
463 + return error("invalid pipe/socket name '%s'", cl_args.path);
464 +
465 + case IPC_STATE__OTHER_ERROR:
466 + default:
467 + return error("other error for '%s'", cl_args.path);
468 + }
469 +}
470 +
471 +/*
472 + * Send an IPC command token to an already-running server daemon and
473 + * print the response.
474 + *
475 + * This is a simple 1 word command/token that `test_app_cb()` (in the
476 + * daemon process) will understand.
477 + */
478 +static int client__send_ipc(void)
479 +{
480 + const char *command = "(no-command)";
481 + struct strbuf buf = STRBUF_INIT;
482 + struct ipc_client_connect_options options
483 + = IPC_CLIENT_CONNECT_OPTIONS_INIT;
484 +
485 + if (cl_args.token && *cl_args.token)
486 + command = cl_args.token;
487 +
488 + options.wait_if_busy = 1;
489 + options.wait_if_not_found = 0;
490 +
491 + if (!ipc_client_send_command(cl_args.path, &options, command, &buf)) {
492 + if (buf.len) {
493 + printf("%s\n", buf.buf);
494 + fflush(stdout);
495 + }
496 + strbuf_release(&buf);
497 +
498 + return 0;
499 + }
500 +
501 + return error("failed to send '%s' to '%s'", command, cl_args.path);
502 +}
503 +
504 +/*
505 + * Send an IPC command to an already-running server and ask it to
506 + * shutdown. "send quit" is an async request and queues a shutdown
507 + * event in the server, so we spin and wait here for it to actually
508 + * shutdown to make the unit tests a little easier to write.
509 + */
510 +static int client__stop_server(void)
511 +{
512 + int ret;
513 + time_t time_limit, now;
514 + enum ipc_active_state s;
515 +
516 + time(&time_limit);
517 + time_limit += cl_args.max_wait_sec;
518 +
519 + cl_args.token = "quit";
520 +
521 + ret = client__send_ipc();
522 + if (ret)
523 + return ret;
524 +
525 + for (;;) {
526 + sleep_millisec(100);
527 +
528 + s = ipc_get_active_state(cl_args.path);
529 +
530 + if (s != IPC_STATE__LISTENING) {
531 + /*
532 + * The socket/pipe is gone and/or has stopped
533 + * responding. Lets assume that the daemon
534 + * process has exited too.
535 + */
536 + return 0;
537 + }
538 +
539 + time(&now);
540 + if (now > time_limit)
541 + return error(_("daemon has not shutdown yet"));
542 + }
543 +}
544 +
545 +/*
546 + * Send an IPC command followed by ballast to confirm that a large
547 + * message can be sent and that the kernel or pkt-line layers will
548 + * properly chunk it and that the daemon receives the entire message.
549 + */
550 +static int do_sendbytes(int bytecount, char byte, const char *path,
551 + const struct ipc_client_connect_options *options)
552 +{
553 + struct strbuf buf_send = STRBUF_INIT;
554 + struct strbuf buf_resp = STRBUF_INIT;
555 +
556 + strbuf_addstr(&buf_send, "sendbytes ");
557 + strbuf_addchars(&buf_send, byte, bytecount);
558 +
559 + if (!ipc_client_send_command(path, options, buf_send.buf, &buf_resp)) {
560 + strbuf_rtrim(&buf_resp);
561 + printf("sent:%c%08d %s\n", byte, bytecount, buf_resp.buf);
562 + fflush(stdout);
563 + strbuf_release(&buf_send);
564 + strbuf_release(&buf_resp);
565 +
566 + return 0;
567 + }
568 +
569 + return error("client failed to sendbytes(%d, '%c') to '%s'",
570 + bytecount, byte, path);
571 +}
572 +
573 +/*
574 + * Send an IPC command with ballast to an already-running server daemon.
575 + */
576 +static int client__sendbytes(void)
577 +{
578 + struct ipc_client_connect_options options
579 + = IPC_CLIENT_CONNECT_OPTIONS_INIT;
580 +
581 + options.wait_if_busy = 1;
582 + options.wait_if_not_found = 0;
583 + options.uds_disallow_chdir = 0;
584 +
585 + return do_sendbytes(cl_args.bytecount, cl_args.bytevalue, cl_args.path,
586 + &options);
587 +}
588 +
589 +struct multiple_thread_data {
590 + pthread_t pthread_id;
591 + struct multiple_thread_data *next;
592 + const char *path;
593 + int bytecount;
594 + int batchsize;
595 + int sum_errors;
596 + int sum_good;
597 + char letter;
598 +};
599 +
600 +static void *multiple_thread_proc(void *_multiple_thread_data)
601 +{
602 + struct multiple_thread_data *d = _multiple_thread_data;
603 + int k;
604 + struct ipc_client_connect_options options
605 + = IPC_CLIENT_CONNECT_OPTIONS_INIT;
606 +
607 + options.wait_if_busy = 1;
608 + options.wait_if_not_found = 0;
609 + /*
610 + * A multi-threaded client should not be randomly calling chdir().
611 + * The test will pass without this restriction because the test is
612 + * not otherwise accessing the filesystem, but it makes us honest.
613 + */
614 + options.uds_disallow_chdir = 1;
615 +
616 + trace2_thread_start("multiple");
617 +
618 + for (k = 0; k < d->batchsize; k++) {
619 + if (do_sendbytes(d->bytecount + k, d->letter, d->path, &options))
620 + d->sum_errors++;
621 + else
622 + d->sum_good++;
623 + }
624 +
625 + trace2_thread_exit();
626 + return NULL;
627 +}
628 +
629 +/*
630 + * Start a client-side thread pool. Each thread sends a series of
631 + * IPC requests. Each request is on a new connection to the server.
632 + */
633 +static int client__multiple(void)
634 +{
635 + struct multiple_thread_data *list = NULL;
636 + int k;
637 + int sum_join_errors = 0;
638 + int sum_thread_errors = 0;
639 + int sum_good = 0;
640 +
641 + for (k = 0; k < cl_args.nr_threads; k++) {
642 + struct multiple_thread_data *d = xcalloc(1, sizeof(*d));
643 + d->next = list;
644 + d->path = cl_args.path;
645 + d->bytecount = cl_args.bytecount + cl_args.batchsize*(k/26);
646 + d->batchsize = cl_args.batchsize;
647 + d->sum_errors = 0;
648 + d->sum_good = 0;
649 + d->letter = 'A' + (k % 26);
650 +
651 + if (pthread_create(&d->pthread_id, NULL, multiple_thread_proc, d)) {
652 + warning("failed to create thread[%d] skipping remainder", k);
653 + free(d);
654 + break;
655 + }
656 +
657 + list = d;
658 + }
659 +
660 + while (list) {
661 + struct multiple_thread_data *d = list;
662 +
663 + if (pthread_join(d->pthread_id, NULL))
664 + sum_join_errors++;
665 +
666 + sum_thread_errors += d->sum_errors;
667 + sum_good += d->sum_good;
668 +
669 + list = d->next;
670 + free(d);
671 + }
672 +
673 + printf("client (good %d) (join %d), (errors %d)\n",
674 + sum_good, sum_join_errors, sum_thread_errors);
675 +
676 + return (sum_join_errors + sum_thread_errors) ? 1 : 0;
677 +}
678 +
679 +int cmd__simple_ipc(int argc, const char **argv)
680 +{
681 + const char * const simple_ipc_usage[] = {
682 + N_("test-helper simple-ipc is-active [<name>] [<options>]"),
683 + N_("test-helper simple-ipc run-daemon [<name>] [<threads>]"),
684 + N_("test-helper simple-ipc start-daemon [<name>] [<threads>] [<max-wait>]"),
685 + N_("test-helper simple-ipc stop-daemon [<name>] [<max-wait>]"),
686 + N_("test-helper simple-ipc send [<name>] [<token>]"),
687 + N_("test-helper simple-ipc sendbytes [<name>] [<bytecount>] [<byte>]"),
688 + N_("test-helper simple-ipc multiple [<name>] [<threads>] [<bytecount>] [<batchsize>]"),
689 + NULL
690 + };
691 +
692 + const char *bytevalue = NULL;
693 +
694 + struct option options[] = {
695 +#ifndef GIT_WINDOWS_NATIVE
696 + OPT_STRING(0, "name", &cl_args.path, N_("name"), N_("name or pathname of unix domain socket")),
697 +#else
698 + OPT_STRING(0, "name", &cl_args.path, N_("name"), N_("named-pipe name")),
699 +#endif
700 + OPT_INTEGER(0, "threads", &cl_args.nr_threads, N_("number of threads in server thread pool")),
701 + OPT_INTEGER(0, "max-wait", &cl_args.max_wait_sec, N_("seconds to wait for daemon to start or stop")),
702 +
703 + OPT_INTEGER(0, "bytecount", &cl_args.bytecount, N_("number of bytes")),
704 + OPT_INTEGER(0, "batchsize", &cl_args.batchsize, N_("number of requests per thread")),
705 +
706 + OPT_STRING(0, "byte", &bytevalue, N_("byte"), N_("ballast character")),
707 + OPT_STRING(0, "token", &cl_args.token, N_("token"), N_("command token to send to the server")),
708 +
709 + OPT_END()
710 + };
711 +
712 + if (argc < 2)
713 + usage_with_options(simple_ipc_usage, options);
714 +
715 + if (argc == 2 && !strcmp(argv[1], "-h"))
716 + usage_with_options(simple_ipc_usage, options);
717 +
718 + if (argc == 2 && !strcmp(argv[1], "SUPPORTS_SIMPLE_IPC"))
719 + return 0;
720 +
721 + cl_args.subcommand = argv[1];
722 +
723 + argc--;
724 + argv++;
725 +
726 + argc = parse_options(argc, argv, NULL, options, simple_ipc_usage, 0);
727 +
728 + if (cl_args.nr_threads < 1)
729 + cl_args.nr_threads = 1;
730 + if (cl_args.max_wait_sec < 0)
731 + cl_args.max_wait_sec = 0;
732 + if (cl_args.bytecount < 1)
733 + cl_args.bytecount = 1;
734 + if (cl_args.batchsize < 1)
735 + cl_args.batchsize = 1;
736 +
737 + if (bytevalue && *bytevalue)
738 + cl_args.bytevalue = bytevalue[0];
739 +
740 + /*
741 + * Use '!!' on all dispatch functions to map from `error()` style
742 + * (returns -1) style to `test_must_fail` style (expects 1). This
743 + * makes shell error messages less confusing.
744 + */
745 +
746 + if (!strcmp(cl_args.subcommand, "is-active"))
747 + return !!client__probe_server();
748 +
749 + if (!strcmp(cl_args.subcommand, "run-daemon"))
750 + return !!daemon__run_server();
751 +
752 + if (!strcmp(cl_args.subcommand, "start-daemon"))
753 + return !!daemon__start_server();
754 +
755 + /*
756 + * Client commands follow. Ensure a server is running before
757 + * sending any data. This might be overkill, but then again
758 + * this is a test harness.
759 + */
760 +
761 + if (!strcmp(cl_args.subcommand, "stop-daemon")) {
762 + if (client__probe_server())
763 + return 1;
764 + return !!client__stop_server();
765 + }
766 +
767 + if (!strcmp(cl_args.subcommand, "send")) {
768 + if (client__probe_server())
769 + return 1;
770 + return !!client__send_ipc();
771 + }
772 +
773 + if (!strcmp(cl_args.subcommand, "sendbytes")) {
774 + if (client__probe_server())
775 + return 1;
776 + return !!client__sendbytes();
777 + }
778 +
779 + if (!strcmp(cl_args.subcommand, "multiple")) {
780 + if (client__probe_server())
781 + return 1;
782 + return !!client__multiple();
783 + }
784 +
785 + die("Unhandled subcommand: '%s'", cl_args.subcommand);
786 +}
787 +#endif
t/helper/test-tool.c
+1
@@ -64,6 +64,7 @@ static struct test_cmd cmds[] = {
64 { "sha1", cmd__sha1 },
65 { "sha256", cmd__sha256 },
66 { "sigchain", cmd__sigchain },
67 + { "simple-ipc", cmd__simple_ipc },
68 { "strcmp-offset", cmd__strcmp_offset },
69 { "string-list", cmd__string_list },
70 { "submodule-config", cmd__submodule_config },
t/helper/test-tool.h
+1
@@ -54,6 +54,7 @@ int cmd__sha1(int argc, const char **argv);
54 int cmd__oid_array(int argc, const char **argv);
55 int cmd__sha256(int argc, const char **argv);
56 int cmd__sigchain(int argc, const char **argv);
57 +int cmd__simple_ipc(int argc, const char **argv);
58 int cmd__strcmp_offset(int argc, const char **argv);
59 int cmd__string_list(int argc, const char **argv);
60 int cmd__submodule_config(int argc, const char **argv);
t/t0052-simple-ipc.sh new
+122
@@ -0,0 +1,122 @@
1 +#!/bin/sh
2 +
3 +test_description='simple command server'
4 +
5 +. ./test-lib.sh
6 +
7 +test-tool simple-ipc SUPPORTS_SIMPLE_IPC || {
8 + skip_all='simple IPC not supported on this platform'
9 + test_done
10 +}
11 +
12 +stop_simple_IPC_server () {
13 + test-tool simple-ipc stop-daemon
14 +}
15 +
16 +test_expect_success 'start simple command server' '
17 + test_atexit stop_simple_IPC_server &&
18 + test-tool simple-ipc start-daemon --threads=8 &&
19 + test-tool simple-ipc is-active
20 +'
21 +
22 +test_expect_success 'simple command server' '
23 + test-tool simple-ipc send --token=ping >actual &&
24 + echo pong >expect &&
25 + test_cmp expect actual
26 +'
27 +
28 +test_expect_success 'servers cannot share the same path' '
29 + test_must_fail test-tool simple-ipc run-daemon &&
30 + test-tool simple-ipc is-active
31 +'
32 +
33 +test_expect_success 'big response' '
34 + test-tool simple-ipc send --token=big >actual &&
35 + test_line_count -ge 10000 actual &&
36 + grep -q "big: [0]*9999\$" actual
37 +'
38 +
39 +test_expect_success 'chunk response' '
40 + test-tool simple-ipc send --token=chunk >actual &&
41 + test_line_count -ge 10000 actual &&
42 + grep -q "big: [0]*9999\$" actual
43 +'
44 +
45 +test_expect_success 'slow response' '
46 + test-tool simple-ipc send --token=slow >actual &&
47 + test_line_count -ge 100 actual &&
48 + grep -q "big: [0]*99\$" actual
49 +'
50 +
51 +# Send an IPC with n=100,000 bytes of ballast. This should be large enough
52 +# to force both the kernel and the pkt-line layer to chunk the message to the
53 +# daemon and for the daemon to receive it in chunks.
54 +#
55 +test_expect_success 'sendbytes' '
56 + test-tool simple-ipc sendbytes --bytecount=100000 --byte=A >actual &&
57 + grep "sent:A00100000 rcvd:A00100000" actual
58 +'
59 +
60 +# Start a series of <threads> client threads that each make <batchsize>
61 +# IPC requests to the server. Each (<threads> * <batchsize>) request
62 +# will open a new connection to the server and randomly bind to a server
63 +# thread. Each client thread exits after completing its batch. So the
64 +# total number of live client threads will be smaller than the total.
65 +# Each request will send a message containing at least <bytecount> bytes
66 +# of ballast. (Responses are small.)
67 +#
68 +# The purpose here is to test threading in the server and responding to
69 +# many concurrent client requests (regardless of whether they come from
70 +# 1 client process or many). And to test that the server side of the
71 +# named pipe/socket is stable. (On Windows this means that the server
72 +# pipe is properly recycled.)
73 +#
74 +# On Windows it also lets us adjust the connection timeout in the
75 +# `ipc_client_send_command()`.
76 +#
77 +# Note it is easy to drive the system into failure by requesting an
78 +# insane number of threads on client or server and/or increasing the
79 +# per-thread batchsize or the per-request bytecount (ballast).
80 +# On Windows these failures look like "pipe is busy" errors.
81 +# So I've chosen fairly conservative values for now.
82 +#
83 +# We expect output of the form "sent:<letter><length> ..."
84 +# With terms (7, 19, 13) we expect:
85 +# <letter> in [A-G]
86 +# <length> in [19+0 .. 19+(13-1)]
87 +# and (7 * 13) successful responses.
88 +#
89 +test_expect_success 'stress test threads' '
90 + test-tool simple-ipc multiple \
91 + --threads=7 \
92 + --bytecount=19 \
93 + --batchsize=13 \
94 + >actual &&
95 + test_line_count = 92 actual &&
96 + grep "good 91" actual &&
97 + grep "sent:A" <actual >actual_a &&
98 + cat >expect_a <<-EOF &&
99 + sent:A00000019 rcvd:A00000019
100 + sent:A00000020 rcvd:A00000020
101 + sent:A00000021 rcvd:A00000021
102 + sent:A00000022 rcvd:A00000022
103 + sent:A00000023 rcvd:A00000023
104 + sent:A00000024 rcvd:A00000024
105 + sent:A00000025 rcvd:A00000025
106 + sent:A00000026 rcvd:A00000026
107 + sent:A00000027 rcvd:A00000027
108 + sent:A00000028 rcvd:A00000028
109 + sent:A00000029 rcvd:A00000029
110 + sent:A00000030 rcvd:A00000030
111 + sent:A00000031 rcvd:A00000031
112 + EOF
113 + test_cmp expect_a actual_a
114 +'
115 +
116 +test_expect_success 'stop-daemon works' '
117 + test-tool simple-ipc stop-daemon &&
118 + test_must_fail test-tool simple-ipc is-active &&
119 + test_must_fail test-tool simple-ipc send --token=ping
120 +'
121 +
122 +test_done