Skip to content

Commit 43203ea

Browse files
committed
test-run-command: learn to run (parts of) the testsuite
Instead of relying on the presence of `make`, or `prove`, we might just as well use our own facilities to run the test suite. This helps e.g. when trying to verify a Git for Windows installation without requiring to download a full Git for Windows SDK (which would use up 600+ megabytes of bandwidth, and over a gigabyte of disk space). Of course, it still requires the test helpers to be build *somewhere*, and the Git version should at least roughly match the version from which the test suite comes. At the same time, this new way to run the test suite allows to validate that a BusyBox-backed MinGit works as expected (verifying that BusyBox' functionality is enough to at least pass the test suite). Signed-off-by: Johannes Schindelin <[email protected]>
1 parent 9134b8c commit 43203ea

File tree

1 file changed

+143
-0
lines changed

1 file changed

+143
-0
lines changed

t/helper/test-run-command.c

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,14 @@
1010

1111
#include "test-tool.h"
1212
#include "git-compat-util.h"
13+
#include "cache.h"
1314
#include "run-command.h"
1415
#include "argv-array.h"
1516
#include "strbuf.h"
17+
#include "parse-options.h"
18+
#include "string-list.h"
19+
#include "thread-utils.h"
20+
#include "wildmatch.h"
1621
#include <string.h>
1722
#include <errno.h>
1823

@@ -50,6 +55,141 @@ static int task_finished(int result,
5055
return 1;
5156
}
5257

58+
struct testsuite {
59+
struct string_list tests, failed;
60+
int next;
61+
int quiet, immediate, verbose, trace;
62+
};
63+
64+
static int next_test(struct child_process *cp, struct strbuf *err, void *cb,
65+
void **task_cb)
66+
{
67+
struct testsuite *suite = cb;
68+
const char *test;
69+
if (suite->next >= suite->tests.nr)
70+
return 0;
71+
72+
test = suite->tests.items[suite->next++].string;
73+
argv_array_pushl(&cp->args, "sh", test, NULL);
74+
if (suite->quiet)
75+
argv_array_push(&cp->args, "--quiet");
76+
if (suite->immediate)
77+
argv_array_push(&cp->args, "-i");
78+
if (suite->verbose)
79+
argv_array_push(&cp->args, "-v");
80+
if (suite->trace)
81+
argv_array_push(&cp->args, "-x");
82+
83+
strbuf_addf(err, "Output of '%s':\n", test);
84+
*task_cb = (void *)test;
85+
86+
return 1;
87+
}
88+
89+
static int test_finished(int result, struct strbuf *err, void *cb,
90+
void *task_cb)
91+
{
92+
struct testsuite *suite = cb;
93+
const char *name = (const char *)task_cb;
94+
95+
if (result)
96+
string_list_append(&suite->failed, name);
97+
98+
strbuf_addf(err, "%s: '%s'\n", result ? "FAIL" : "SUCCESS", name);
99+
100+
return 0;
101+
}
102+
103+
static int test_failed(struct strbuf *out, void *cb, void *task_cb)
104+
{
105+
struct testsuite *suite = cb;
106+
const char *name = (const char *)task_cb;
107+
108+
string_list_append(&suite->failed, name);
109+
strbuf_addf(out, "FAILED TO START: '%s'\n", name);
110+
111+
return 0;
112+
}
113+
114+
static const char * const testsuite_usage[] = {
115+
"test-run-command testsuite [<options>] [<pattern>...]",
116+
NULL
117+
};
118+
119+
static int testsuite(int argc, const char **argv)
120+
{
121+
struct testsuite suite;
122+
int max_jobs = 1, i, ret;
123+
DIR *dir;
124+
struct dirent *d;
125+
struct option options[] = {
126+
OPT_BOOL('i', "immediate", &suite.immediate,
127+
"stop at first failed test case(s)"),
128+
OPT_INTEGER('j', "jobs", &max_jobs, "run <N> jobs in parallel"),
129+
OPT_BOOL('q', "quiet", &suite.quiet, "be terse"),
130+
OPT_BOOL('v', "verbose", &suite.verbose, "be verbose"),
131+
OPT_BOOL('x', "trace", &suite.trace, "trace shell commands"),
132+
OPT_END()
133+
};
134+
135+
memset(&suite, 0, sizeof(suite));
136+
suite.tests.strdup_strings = suite.failed.strdup_strings = 1;
137+
138+
argc = parse_options(argc, argv, NULL, options,
139+
testsuite_usage, PARSE_OPT_STOP_AT_NON_OPTION);
140+
141+
if (max_jobs <= 0)
142+
max_jobs = online_cpus();
143+
144+
dir = opendir(".");
145+
if (!dir)
146+
die("Could not open the current directory");
147+
while ((d = readdir(dir))) {
148+
const char *p = d->d_name;
149+
150+
if (*p != 't' || !isdigit(p[1]) || !isdigit(p[2]) ||
151+
!isdigit(p[3]) || !isdigit(p[4]) || p[5] != '-' ||
152+
!ends_with(p, ".sh"))
153+
continue;
154+
155+
/* No pattern: match all */
156+
if (!argc) {
157+
string_list_append(&suite.tests, p);
158+
continue;
159+
}
160+
161+
for (i = 0; i < argc; i++)
162+
if (!wildmatch(argv[i], p, 0)) {
163+
string_list_append(&suite.tests, p);
164+
break;
165+
}
166+
}
167+
closedir(dir);
168+
169+
if (!suite.tests.nr)
170+
die("No tests match!");
171+
if (max_jobs > suite.tests.nr)
172+
max_jobs = suite.tests.nr;
173+
174+
fprintf(stderr, "Running %d tests (%d at a time)\n",
175+
suite.tests.nr, max_jobs);
176+
177+
ret = run_processes_parallel(max_jobs, next_test, test_failed,
178+
test_finished, &suite);
179+
180+
if (suite.failed.nr > 0) {
181+
ret = 1;
182+
fprintf(stderr, "%d tests failed:\n\n", suite.failed.nr);
183+
for (i = 0; i < suite.failed.nr; i++)
184+
fprintf(stderr, "\t%s\n", suite.failed.items[i].string);
185+
}
186+
187+
string_list_clear(&suite.tests, 0);
188+
string_list_clear(&suite.failed, 0);
189+
190+
return !!ret;
191+
}
192+
53193
static int inherit_handle(const char *argv0)
54194
{
55195
struct child_process cp = CHILD_PROCESS_INIT;
@@ -95,6 +235,9 @@ int cmd__run_command(int argc, const char **argv)
95235
struct child_process proc = CHILD_PROCESS_INIT;
96236
int jobs;
97237

238+
if (argc > 1 && !strcmp(argv[1], "testsuite"))
239+
exit(testsuite(argc - 1, argv + 1));
240+
98241
if (argc < 2)
99242
return 1;
100243
if (!strcmp(argv[1], "inherited-handle"))

0 commit comments

Comments
 (0)