From 0227f75c0b132fc362f4007a62dac9ae82336850 Mon Sep 17 00:00:00 2001
From: Garima Singh <garima.singh@microsoft.com>
Date: Wed, 18 Sep 2019 16:03:59 -0400
Subject: [PATCH 09/17] tests: add a helper to stress test argument quoting

On Windows, we have to do all the command-line argument quoting
ourselves. Worse: we have to have two versions of said quoting, one for
MSYS2 programs (which have their own dequoting rules) and the rest.

We care mostly about the rest, and to make sure that that works, let's
have a stress test that comes up with all kinds of awkward arguments,
verifying that a spawned sub-process receives those unharmed.

Signed-off-by: Garima Singh <garima.singh@microsoft.com>
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 t/helper/test-run-command.c | 118 +++++++++++++++++++++++++++++++++++-
 1 file changed, 116 insertions(+), 2 deletions(-)

diff --git a/t/helper/test-run-command.c b/t/helper/test-run-command.c
index d24d157379..6c801cb529 100644
--- a/t/helper/test-run-command.c
+++ b/t/helper/test-run-command.c
@@ -12,8 +12,8 @@
 #include "run-command.h"
 #include "argv-array.h"
 #include "strbuf.h"
-#include <string.h>
-#include <errno.h>
+#include "gettext.h"
+#include "parse-options.h"
 
 static int number_callbacks;
 static int parallel_next(struct child_process *cp,
@@ -49,11 +49,125 @@ static int task_finished(int result,
 	return 1;
 }
 
+static uint64_t my_random_next = 1234;
+
+static uint64_t my_random(void)
+{
+	uint64_t res = my_random_next;
+	my_random_next = my_random_next * 1103515245 + 12345;
+	return res;
+}
+
+static int quote_stress_test(int argc, const char **argv)
+{
+	/*
+	 * We are running a quote-stress test.
+	 * spawn a subprocess that runs quote-stress with a
+	 * special option that echoes back the arguments that
+	 * were passed in.
+	 */
+	char special[] = ".?*\\^_\"'`{}()[]<>@~&+:;$%"; // \t\r\n\a";
+	int i, j, k, trials = 100;
+	struct strbuf out = STRBUF_INIT;
+	struct argv_array args = ARGV_ARRAY_INIT;
+	struct option options[] = {
+		OPT_INTEGER('n', "trials", &trials, "Number of trials"),
+		OPT_END()
+	};
+	const char * const usage[] = {
+		"test-run-command quote-stress-test <options>",
+		NULL
+	};
+
+	argc = parse_options(argc, argv, NULL, options, usage, 0);
+
+	for (i = 0; i < trials; i++) {
+		struct child_process cp = CHILD_PROCESS_INIT;
+		size_t arg_count = 1 + (my_random() % 5), arg_offset;
+		int ret = 0;
+
+		argv_array_clear(&args);
+		argv_array_pushl(&args, "test-run-command",
+				 "quote-echo", NULL);
+		arg_offset = args.argc;
+		for (j = 0; j < arg_count; j++) {
+			char buf[20];
+			size_t min_len = 1;
+			size_t arg_len = min_len +
+				(my_random() % (ARRAY_SIZE(buf) - min_len));
+
+			for (k = 0; k < arg_len; k++)
+				buf[k] = special[my_random() %
+					ARRAY_SIZE(special)];
+			buf[arg_len] = '\0';
+
+			argv_array_push(&args, buf);
+		}
+
+		cp.argv = args.argv;
+		strbuf_reset(&out);
+		if (pipe_command(&cp, NULL, 0, &out, 0, NULL, 0) < 0)
+			return error("Failed to spawn child process");
+
+		for (j = 0, k = 0; j < arg_count; j++) {
+			const char *arg = args.argv[j + arg_offset];
+
+			if (strcmp(arg, out.buf + k))
+				ret = error("incorrectly quoted arg: '%s', "
+					    "echoed back as '%s'",
+					     arg, out.buf + k);
+			k += strlen(out.buf + k) + 1;
+		}
+
+		if (k != out.len)
+			ret = error("got %d bytes, but consumed only %d",
+				     (int)out.len, (int)k);
+
+		if (ret) {
+			fprintf(stderr, "Trial #%d failed. Arguments:\n", i);
+			for (j = 0; j < arg_count; j++)
+				fprintf(stderr, "arg #%d: '%s'\n",
+					(int)j, args.argv[j + arg_offset]);
+
+			strbuf_release(&out);
+			argv_array_clear(&args);
+
+			return ret;
+		}
+
+		if (i && (i % 100) == 0)
+			fprintf(stderr, "Trials completed: %d\n", (int)i);
+	}
+
+	strbuf_release(&out);
+	argv_array_clear(&args);
+
+	return 0;
+}
+
+static int quote_echo(int argc, const char **argv)
+{
+	while (argc > 1) {
+		fwrite(argv[1], strlen(argv[1]), 1, stdout);
+		fputc('\0', stdout);
+		argv++;
+		argc--;
+	}
+
+	return 0;
+}
+
 int cmd_main(int argc, const char **argv)
 {
 	struct child_process proc = CHILD_PROCESS_INIT;
 	int jobs;
 
+	if (argc >= 2 && !strcmp(argv[1], "quote-stress-test"))
+		return !!quote_stress_test(argc - 1, argv + 1);
+
+	if (argc >= 2 && !strcmp(argv[1], "quote-echo"))
+		return !!quote_echo(argc - 1, argv + 1);
+
 	if (argc < 3)
 		return 1;
 	proc.argv = (const char **)argv + 2;
-- 
2.24.0

