Subject: Re: [PATCH v4 8/7] wildmatch test: skip file creation tests on Windows proper

From: Duy Nguyen <[email-masked]>
Date: Wed, 10 Jan 2018 16:07:24 +0700

On Mon, Jan 08, 2018 at 01:25:04PM +0100, Johannes Schindelin wrote:
> I agree that it would make a ton of sense to use a proper, portable test
> framework written in pure, portable C.
> 
> However, this ship has long sailed, hasn't it?

If you meant converting the whole test suite, oh yeah that's not gonna
happen. But it's still possible to have some tests written in C.

I played a bit with this. The assumption is if it's agreed that we can
get something bare bone (but functional) in then we could start having
more and more C-based unit tests in future and also improve the C
framework to be on par with shell one on the side.

There are still some minor problems with my patch, and a bunch of
optional features not supported. But the numbers looks unexpectedly
promising. 0.7 seconds on the shell version and 0.03 on the C one.

One disadvantage of this though, if this kind of framework does not
get popular, then any new test feature must be added at both places
but it's a waste of time to support both. So...

Anyway here it is. t3071 is the same as t3070 (this is on master)

 Makefile                             |   2 +
 t/helper/test-3071-wildmatch.c (new) | 273 ++++++++++++++++++++++++++++++++++++++++++++++++++
 t/t3071-wildmatch.sh (new +x)        |   3 +
 test-lib.c (new)                     |  97 ++++++++++++++++++
 test-lib.h (new)                     |   5 +

-- 8< --
diff --git a/Makefile b/Makefile
index 2a81ae22e9..567387b558 100644
--- a/Makefile
+++ b/Makefile
@@ -644,6 +644,7 @@ X =
 
 PROGRAMS += $(patsubst %.o,git-%$X,$(PROGRAM_OBJS))
 
+TEST_PROGRAMS_NEED_X += test-3071-wildmatch
 TEST_PROGRAMS_NEED_X += test-chmtime
 TEST_PROGRAMS_NEED_X += test-ctype
 TEST_PROGRAMS_NEED_X += test-config
@@ -895,6 +896,7 @@ LIB_OBJS += sub-process.o
 LIB_OBJS += symlinks.o
 LIB_OBJS += tag.o
 LIB_OBJS += tempfile.o
+LIB_OBJS += test-lib.o
 LIB_OBJS += tmp-objdir.o
 LIB_OBJS += trace.o
 LIB_OBJS += trailer.o
diff --git a/t/helper/test-3071-wildmatch.c b/t/helper/test-3071-wildmatch.c
new file mode 100644
index 0000000000..24a657202d
--- /dev/null
+++ b/t/helper/test-3071-wildmatch.c
@@ -0,0 +1,273 @@
+#include "cache.h"
+#include "test-lib.h"
+
+struct match_input {
+	int expect_true;
+	const char *text;
+	const char *pattern;
+};
+
+static struct match_input match_tests[] = {
+	/* Basic wildmatch features */
+	{ 1, "foo", "foo" },
+	{ 0, "foo", "bar" },
+	{ 1, "", "" },
+	{ 1, "foo", "???" },
+	{ 0, "foo", "??" },
+	{ 1, "foo", "*" },
+	{ 1, "foo", "f*" },
+	{ 0, "foo", "*f" },
+	{ 1, "foo", "*foo*" },
+	{ 1, "foobar", "*ob*a*r*" },
+	{ 1, "aaaaaaabababab", "*ab" },
+	{ 1, "foo*", "foo\\*" },
+	{ 0, "foobar", "foo\\*bar" },
+	{ 1, "f\\oo", "f\\\\oo" },
+	{ 1, "ball", "*[al]?" },
+	{ 0, "ten", "[ten]" },
+	{ 0, "ten", "**[!te]" },
+	{ 0, "ten", "**[!ten]" },
+	{ 1, "ten", "t[a-g]n" },
+	{ 0, "ten", "t[!a-g]n" },
+	{ 1, "ton", "t[!a-g]n" },
+	{ 1, "ton", "t[^a-g]n" },
+	{ 1, "a]b", "a[]]b" },
+	{ 1, "a-b", "a[]-]b" },
+	{ 1, "a]b", "a[]-]b" },
+	{ 0, "aab", "a[]-]b" },
+	{ 1, "aab", "a[]a-]b" },
+	{ 1, "]", "]" },
+
+	/* Extended slash-matching features */
+	{ 0, "foo/baz/bar", "foo*bar" },
+	{ 0, "foo/baz/bar", "foo**bar" },
+	{ 0, "foobazbar", "foo**bar" },
+	{ 1, "foo/baz/bar", "foo/**/bar" },
+	{ 1, "foo/baz/bar", "foo/**/**/bar" },
+	{ 1, "foo/b/a/z/bar", "foo/**/bar" },
+	{ 1, "foo/b/a/z/bar", "foo/**/**/bar" },
+	{ 1, "foo/bar", "foo/**/bar" },
+	{ 1, "foo/bar", "foo/**/**/bar" },
+	{ 0, "foo/bar", "foo?bar" },
+	{ 0, "foo/bar", "foo[/]bar" },
+	{ 0, "foo/bar", "foo[^a-z]bar" },
+	{ 0, "foo/bar", "f[^eiu][^eiu][^eiu][^eiu][^eiu]r" },
+	{ 1, "foo-bar", "f[^eiu][^eiu][^eiu][^eiu][^eiu]r" },
+	{ 1, "foo", "**/foo" },
+	{ 1, "XXX/foo", "**/foo" },
+	{ 1, "bar/baz/foo", "**/foo" },
+	{ 0, "bar/baz/foo", "*/foo" },
+	{ 0, "foo/bar/baz", "**/bar*" },
+	{ 1, "deep/foo/bar/baz", "**/bar/*" },
+	{ 0, "deep/foo/bar/baz/", "**/bar/*" },
+	{ 1, "deep/foo/bar/baz/", "**/bar/**" },
+	{ 0, "deep/foo/bar", "**/bar/*" },
+	{ 1, "deep/foo/bar/", "**/bar/**" },
+	{ 0, "foo/bar/baz", "**/bar**" },
+	{ 1, "foo/bar/baz/x", "*/bar/**" },
+	{ 0, "deep/foo/bar/baz/x", "*/bar/**" },
+	{ 1, "deep/foo/bar/baz/x", "**/bar/*/*" },
+
+	/* Various additional tests */
+	{ 0, "acrt", "a[c-c]st" },
+	{ 1, "acrt", "a[c-c]rt" },
+	{ 0, "]", "[!]-]" },
+	{ 1, "a", "[!]-]" },
+	{ 0, "", "\\" },
+	{ 0, "\\", "\\" },
+	{ 0, "XXX/\\", "*/\\" },
+	{ 1, "XXX/\\", "*/\\\\" },
+	{ 1, "foo", "foo" },
+	{ 1, "@foo", "@foo" },
+	{ 0, "foo", "@foo" },
+	{ 1, "[ab]", "\\[ab]" },
+	{ 1, "[ab]", "[[]ab]" },
+	{ 1, "[ab]", "[[:]ab]" },
+	{ 0, "[ab]", "[[::]ab]" },
+	{ 1, "[ab]", "[[:digit]ab]" },
+	{ 1, "[ab]", "[\\[:]ab]" },
+	{ 1, "?a?b", "\\??\\?b" },
+	{ 1, "abc", "\\a\\b\\c" },
+	{ 0, "foo", "" },
+	{ 1, "foo/bar/baz/to", "**/t[o]" },
+
+	/* Character class tests */
+	{ 1, "a1B", "[[:alpha:]][[:digit:]][[:upper:]]" },
+	{ 0, "a", "[[:digit:][:upper:][:space:]]" },
+	{ 1, "A", "[[:digit:][:upper:][:space:]]" },
+	{ 1, "1", "[[:digit:][:upper:][:space:]]" },
+	{ 0, "1", "[[:digit:][:upper:][:spaci:]]" },
+	{ 1, " ", "[[:digit:][:upper:][:space:]]" },
+	{ 0, ".", "[[:digit:][:upper:][:space:]]" },
+	{ 1, ".", "[[:digit:][:punct:][:space:]]" },
+	{ 1, "5", "[[:xdigit:]]" },
+	{ 1, "f", "[[:xdigit:]]" },
+	{ 1, "D", "[[:xdigit:]]" },
+	{ 1, "_", "[[:alnum:][:alpha:][:blank:][:cntrl:][:digit:][:graph:][:lower:][:print:][:punct:][:space:][:upper:][:xdigit:]]" },
+	{ 1, ".", "[^[:alnum:][:alpha:][:blank:][:cntrl:][:digit:][:lower:][:space:][:upper:][:xdigit:]]" },
+	{ 1, "5", "[a-c[:digit:]x-z]" },
+	{ 1, "b", "[a-c[:digit:]x-z]" },
+	{ 1, "y", "[a-c[:digit:]x-z]" },
+	{ 0, "q", "[a-c[:digit:]x-z]" },
+
+	/* Additional tests, including some malformed wildmats */
+	{ 1, "]", "[\\\\-^]" },
+	{ 0, "[", "[\\\\-^]" },
+	{ 1, "-", "[\\-_]" },
+	{ 1, "]", "[\\]]" },
+	{ 0, "\\]", "[\\]]" },
+	{ 0, "\\", "[\\]]" },
+	{ 0, "ab", "a[]b" },
+	{ 0, "a[]b", "a[]b" },
+	{ 0, "ab[", "ab[" },
+	{ 0, "ab", "[!" },
+	{ 0, "ab", "[-" },
+	{ 1, "-", "[-]" },
+	{ 0, "-", "[a-" },
+	{ 0, "-", "[!a-" },
+	{ 1, "-", "[--A]" },
+	{ 1, "5", "[--A]" },
+	{ 1, " ", "[ --]" },
+	{ 1, "$", "[ --]" },
+	{ 1, "-", "[ --]" },
+	{ 0, "0", "[ --]" },
+	{ 1, "-", "[---]" },
+	{ 1, "-", "[------]" },
+	{ 0, "j", "[a-e-n]" },
+	{ 1, "-", "[a-e-n]" },
+	{ 1, "a", "[!------]" },
+	{ 0, "[", "[]-a]" },
+	{ 1, "^", "[]-a]" },
+	{ 0, "^", "[!]-a]" },
+	{ 1, "[", "[!]-a]" },
+	{ 1, "^", "[a^bc]" },
+	{ 1, "-b]", "[a-]b]" },
+	{ 0, "\\", "[\\]" },
+	{ 1, "\\", "[\\\\]" },
+	{ 0, "\\", "[!\\\\]" },
+	{ 1, "G", "[A-\\\\]" },
+	{ 0, "aaabbb", "b*a" },
+	{ 0, "aabcaa", "*ba*" },
+	{ 1, ",", "[,]" },
+	{ 1, ",", "[\\\\,]" },
+	{ 1, "\\", "[\\\\,]" },
+	{ 1, "-", "[,-.]" },
+	{ 0, "+", "[,-.]" },
+	{ 0, "-.]", "[,-.]" },
+	{ 1, "2", "[\\1-\\3]" },
+	{ 1, "3", "[\\1-\\3]" },
+	{ 0, "4", "[\\1-\\3]" },
+	{ 1, "\\", "[[-\\]]" },
+	{ 1, "[", "[[-\\]]" },
+	{ 1, "]", "[[-\\]]" },
+	{ 0, "-", "[[-\\]]" },
+
+	/* Test recursion and the abort code (use "wildtest -i" to see iteration counts) */
+	{ 1, "-adobe-courier-bold-o-normal--12-120-75-75-m-70-iso8859-1", "-*-*-*-*-*-*-12-*-*-*-m-*-*-*" },
+	{ 0, "-adobe-courier-bold-o-normal--12-120-75-75-X-70-iso8859-1", "-*-*-*-*-*-*-12-*-*-*-m-*-*-*" },
+	{ 0, "-adobe-courier-bold-o-normal--12-120-75-75-/-70-iso8859-1", "-*-*-*-*-*-*-12-*-*-*-m-*-*-*" },
+	{ 1, "XXX/adobe/courier/bold/o/normal//12/120/75/75/m/70/iso8859/1", "XXX/*/*/*/*/*/*/12/*/*/*/m/*/*/*" },
+	{ 0, "XXX/adobe/courier/bold/o/normal//12/120/75/75/X/70/iso8859/1", "XXX/*/*/*/*/*/*/12/*/*/*/m/*/*/*" },
+	{ 1, "abcd/abcdefg/abcdefghijk/abcdefghijklmnop.txt", "**/*a*b*g*n*t" },
+	{ 0, "abcd/abcdefg/abcdefghijk/abcdefghijklmnop.txtz", "**/*a*b*g*n*t" },
+	{ 0, "foo", "*/*/*" },
+	{ 0, "foo/bar", "*/*/*" },
+	{ 1, "foo/bba/arr", "*/*/*" },
+	{ 0, "foo/bb/aa/rr", "*/*/*" },
+	{ 1, "foo/bb/aa/rr", "**/**/**" },
+	{ 1, "abcXdefXghi", "*X*i" },
+	{ 0, "ab/cXd/efXg/hi", "*X*i" },
+	{ 1, "ab/cXd/efXg/hi", "*/*X*/*/*i" },
+	{ 1, "ab/cXd/efXg/hi", "**/*X*/**/*i" },
+
+	/* Case-sensitivity features */
+	{ 0, "a", "[A-Z]" },
+	{ 1, "A", "[A-Z]" },
+	{ 0, "A", "[a-z]" },
+	{ 1, "a", "[a-z]" },
+	{ 0, "a", "[[:upper:]]" },
+	{ 1, "A", "[[:upper:]]" },
+	{ 0, "A", "[[:lower:]]" },
+	{ 1, "a", "[[:lower:]]" },
+	{ 0, "A", "[B-Za]" },
+	{ 1, "a", "[B-Za]" },
+	{ 0, "A", "[B-a]" },
+	{ 1, "a", "[B-a]" },
+	{ 0, "z", "[Z-y]" },
+	{ 1, "Z", "[Z-y]" },
+};
+
+static struct match_input pathmatch_tests[] = {
+	{ 1, "foo", "foo" },
+	{ 0, "foo", "fo" },
+	{ 1, "foo/bar", "foo/bar" },
+	{ 1, "foo/bar", "foo/*" },
+	{ 1, "foo/bba/arr", "foo/*" },
+	{ 1, "foo/bba/arr", "foo/**" },
+	{ 1, "foo/bba/arr", "foo*" },
+	{ 1, "foo/bba/arr", "foo**" },
+	{ 1, "foo/bba/arr", "foo/*arr" },
+	{ 1, "foo/bba/arr", "foo/**arr" },
+	{ 0, "foo/bba/arr", "foo/*z" },
+	{ 0, "foo/bba/arr", "foo/**z" },
+	{ 1, "foo/bar", "foo?bar" },
+	{ 1, "foo/bar", "foo[/]bar" },
+	{ 1, "foo/bar", "foo[^a-z]bar" },
+	{ 0, "foo", "*/*/*" },
+	{ 0, "foo/bar", "*/*/*" },
+	{ 1, "foo/bba/arr", "*/*/*" },
+	{ 1, "foo/bb/aa/rr", "*/*/*" },
+	{ 1, "abcXdefXghi", "*X*i" },
+	{ 1, "ab/cXd/efXg/hi", "*/*X*/*/*i" },
+	{ 1, "ab/cXd/efXg/hi", "*Xg*i" },
+};
+
+static struct match_input icase_match_tests[] = {
+	{ 1, "a", "[A-Z]" },
+	{ 1, "A", "[A-Z]" },
+	{ 1, "A", "[a-z]" },
+	{ 1, "a", "[a-z]" },
+	{ 1, "a", "[[:upper:]]" },
+	{ 1, "A", "[[:upper:]]" },
+	{ 1, "A", "[[:lower:]]" },
+	{ 1, "a", "[[:lower:]]" },
+	{ 1, "A", "[B-Za]" },
+	{ 1, "a", "[B-Za]" },
+	{ 1, "A", "[B-a]" },
+	{ 1, "a", "[B-a]" },
+	{ 1, "z", "[Z-y]" },
+	{ 1, "Z", "[Z-y]" },
+};
+
+static void test_match(const char *name,
+		       const struct match_input *input,
+		       unsigned flags)
+{
+	int ret;
+
+	start_test("%s:  %s match '%s' '%s'",
+		   name,
+		   input->expect_true ? "  " : "no",
+		   input->text, input->pattern);
+	ret = wildmatch(input->pattern, input->text, flags);
+	end_test(input->expect_true ? ret == 0 : ret != 0);
+}
+
+int cmd_main(int ac, const char **av)
+{
+	int i;
+
+	init_test_suite(ac, av);
+
+	for (i = 0; i < ARRAY_SIZE(match_tests); i++)
+		test_match("wildmatch", match_tests + i, WM_PATHNAME);
+
+	for (i = 0; i < ARRAY_SIZE(pathmatch_tests); i++)
+		test_match("pathmatch", pathmatch_tests + i, 0);
+
+	for (i = 0; i < ARRAY_SIZE(icase_match_tests); i++)
+		test_match("iwildmatch", icase_match_tests + i,
+			   WM_PATHNAME | WM_CASEFOLD);
+
+	all_tests_done();
+}
diff --git a/t/t3071-wildmatch.sh b/t/t3071-wildmatch.sh
new file mode 100755
index 0000000000..6e83b4d684
--- /dev/null
+++ b/t/t3071-wildmatch.sh
@@ -0,0 +1,3 @@
+#!/bin/sh
+
+exec helper/test-3071-wildmatch t3071-wildmatch "$@"
diff --git a/test-lib.c b/test-lib.c
new file mode 100644
index 0000000000..8e8b7cd6df
--- /dev/null
+++ b/test-lib.c
@@ -0,0 +1,97 @@
+#include "cache.h"
+#include "test-lib.h"
+
+static int test_failure;
+static int test_count;
+static int test_fixed;
+static int test_broken;
+static int test_success;
+
+static const char *suite_name;
+static int harness_active;
+static char *test_name;
+
+void init_test_suite(int ac, const char **av)
+{
+	char cwd[PATH_MAX];
+
+	harness_active = getenv("HARNESS_ACTIVE") != NULL;
+	suite_name = av[1];
+
+	getcwd(cwd, sizeof(cwd));
+	setenv("TEST_DIRECTORY", cwd, 0);
+	setenv("TEST_OUTPUT_DIRECTORY", getenv("TEST_DIRECTORY"), 0);
+
+	ac--;
+	av++;
+
+	dup2(1, 3);
+}
+
+static int skip_test(void)
+{
+	return 0; /* not supported yet */
+}
+
+int start_test(const char *fmt, ...)
+{
+	va_list ap;
+
+	va_start(ap, fmt);
+	test_name = xstrvfmt(fmt, ap);
+	va_end(ap);
+	test_count++;
+
+	if (skip_test())
+		return 0;
+
+	return 1;
+}
+
+void end_test(int ok)
+{
+	if (ok) {
+		test_success++;
+		dprintf(3, "ok %d - %s\n", test_count, test_name);
+	} else {
+		test_failure++;
+		dprintf(3, "not ok %d - %s\n", test_count, test_name);
+	}
+
+	free(test_name);
+	test_name = NULL;
+
+	/* -i not supported yet */
+}
+
+void all_tests_done(void)
+{
+	if (!getenv("HARNESS_ACTIVE")) {
+		struct strbuf sb = STRBUF_INIT;
+		FILE *fp;
+
+		strbuf_addf(&sb, "mkdir -p %s/test-results",
+			    getenv("TEST_OUTPUT_DIRECTORY"));
+		system(sb.buf);
+		strbuf_release(&sb);
+
+		strbuf_addf(&sb, "%s/test-results/%s.counts",
+			    getenv("TEST_OUTPUT_DIRECTORY"),
+			    suite_name);
+		fp = fopen(sb.buf, "w");
+		strbuf_release(&sb);
+
+		fprintf(fp, "total %d\nsuccess %d\n"
+			"fixed %d\nbroken %d\nfailed %d\n\n",
+			test_count, test_success,
+			test_fixed, test_broken, test_failure);
+		fclose(fp);
+	}
+	if (test_failure) {
+		dprintf(3, "# failed %d among FIXME\n", test_failure);
+	} else {
+		dprintf(3, "# passed all FIXME\n");
+	}
+	dprintf(3, "1..%d\n", test_count);
+	exit(0);
+}
diff --git a/test-lib.h b/test-lib.h
new file mode 100644
index 0000000000..f8122c1719
--- /dev/null
+++ b/test-lib.h
@@ -0,0 +1,5 @@
+void init_test_suite(int ac, const char **av);
+void all_tests_done(void) NORETURN;
+
+int start_test(const char *fmt, ...);
+void end_test(int ok);
-- 8< --

--
Duy