test-tool run-command: learn to run (parts of) the testsuite
Git for Windows jumps through hoops to provide a development environment that allows to build Git and to run its test suite. To that end, an entire MSYS2 system, including GNU make and GCC is offered as "the Git for Windows SDK". It does come at a price: an initial download of said SDK weighs in with several hundreds of megabytes, and the unpacked SDK occupies ~2GB of disk space. A much more native development environment on Windows is Visual Studio. To help contributors use that environment, we already have a Makefile target `vcxproj` that generates a commit with project files (and other generated files), and Git for Windows' `vs/master` branch is continuously re-generated using that target. The idea is to allow building Git in Visual Studio, and to run individual tests using a Portable Git. The one missing thing is a way to run the entire test suite: neither `make` nor `prove` are required to run Git, therefore Git for Windows does not support those commands in the Portable Git. To help with that, add a simple test helper that exercises the `run_processes_parallel()` function to allow for running test scripts in parallel (which is really necessary, especially on Windows, as Git's test suite takes such a long time to run). This will also come in handy for the upcoming change to our Azure Pipeline: we will use this helper in a Portable Git to test the Visual Studio build of Git. Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de> Signed-off-by: Junio C Hamano <gitster@pobox.com>
This commit is contained in:
parent
5d65ad17a9
commit
be5d88e112
@ -10,9 +10,14 @@
|
||||
|
||||
#include "test-tool.h"
|
||||
#include "git-compat-util.h"
|
||||
#include "cache.h"
|
||||
#include "run-command.h"
|
||||
#include "argv-array.h"
|
||||
#include "strbuf.h"
|
||||
#include "parse-options.h"
|
||||
#include "string-list.h"
|
||||
#include "thread-utils.h"
|
||||
#include "wildmatch.h"
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
|
||||
@ -50,11 +55,159 @@ static int task_finished(int result,
|
||||
return 1;
|
||||
}
|
||||
|
||||
struct testsuite {
|
||||
struct string_list tests, failed;
|
||||
int next;
|
||||
int quiet, immediate, verbose, verbose_log, trace, write_junit_xml;
|
||||
};
|
||||
#define TESTSUITE_INIT \
|
||||
{ STRING_LIST_INIT_DUP, STRING_LIST_INIT_DUP, -1, 0, 0, 0, 0, 0, 0 }
|
||||
|
||||
static int next_test(struct child_process *cp, struct strbuf *err, void *cb,
|
||||
void **task_cb)
|
||||
{
|
||||
struct testsuite *suite = cb;
|
||||
const char *test;
|
||||
if (suite->next >= suite->tests.nr)
|
||||
return 0;
|
||||
|
||||
test = suite->tests.items[suite->next++].string;
|
||||
argv_array_pushl(&cp->args, "sh", test, NULL);
|
||||
if (suite->quiet)
|
||||
argv_array_push(&cp->args, "--quiet");
|
||||
if (suite->immediate)
|
||||
argv_array_push(&cp->args, "-i");
|
||||
if (suite->verbose)
|
||||
argv_array_push(&cp->args, "-v");
|
||||
if (suite->verbose_log)
|
||||
argv_array_push(&cp->args, "-V");
|
||||
if (suite->trace)
|
||||
argv_array_push(&cp->args, "-x");
|
||||
if (suite->write_junit_xml)
|
||||
argv_array_push(&cp->args, "--write-junit-xml");
|
||||
|
||||
strbuf_addf(err, "Output of '%s':\n", test);
|
||||
*task_cb = (void *)test;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int test_finished(int result, struct strbuf *err, void *cb,
|
||||
void *task_cb)
|
||||
{
|
||||
struct testsuite *suite = cb;
|
||||
const char *name = (const char *)task_cb;
|
||||
|
||||
if (result)
|
||||
string_list_append(&suite->failed, name);
|
||||
|
||||
strbuf_addf(err, "%s: '%s'\n", result ? "FAIL" : "SUCCESS", name);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int test_failed(struct strbuf *out, void *cb, void *task_cb)
|
||||
{
|
||||
struct testsuite *suite = cb;
|
||||
const char *name = (const char *)task_cb;
|
||||
|
||||
string_list_append(&suite->failed, name);
|
||||
strbuf_addf(out, "FAILED TO START: '%s'\n", name);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const char * const testsuite_usage[] = {
|
||||
"test-run-command testsuite [<options>] [<pattern>...]",
|
||||
NULL
|
||||
};
|
||||
|
||||
static int testsuite(int argc, const char **argv)
|
||||
{
|
||||
struct testsuite suite = TESTSUITE_INIT;
|
||||
int max_jobs = 1, i, ret;
|
||||
DIR *dir;
|
||||
struct dirent *d;
|
||||
struct option options[] = {
|
||||
OPT_BOOL('i', "immediate", &suite.immediate,
|
||||
"stop at first failed test case(s)"),
|
||||
OPT_INTEGER('j', "jobs", &max_jobs, "run <N> jobs in parallel"),
|
||||
OPT_BOOL('q', "quiet", &suite.quiet, "be terse"),
|
||||
OPT_BOOL('v', "verbose", &suite.verbose, "be verbose"),
|
||||
OPT_BOOL('V', "verbose-log", &suite.verbose_log,
|
||||
"be verbose, redirected to a file"),
|
||||
OPT_BOOL('x', "trace", &suite.trace, "trace shell commands"),
|
||||
OPT_BOOL(0, "write-junit-xml", &suite.write_junit_xml,
|
||||
"write JUnit-style XML files"),
|
||||
OPT_END()
|
||||
};
|
||||
|
||||
memset(&suite, 0, sizeof(suite));
|
||||
suite.tests.strdup_strings = suite.failed.strdup_strings = 1;
|
||||
|
||||
argc = parse_options(argc, argv, NULL, options,
|
||||
testsuite_usage, PARSE_OPT_STOP_AT_NON_OPTION);
|
||||
|
||||
if (max_jobs <= 0)
|
||||
max_jobs = online_cpus();
|
||||
|
||||
dir = opendir(".");
|
||||
if (!dir)
|
||||
die("Could not open the current directory");
|
||||
while ((d = readdir(dir))) {
|
||||
const char *p = d->d_name;
|
||||
|
||||
if (*p != 't' || !isdigit(p[1]) || !isdigit(p[2]) ||
|
||||
!isdigit(p[3]) || !isdigit(p[4]) || p[5] != '-' ||
|
||||
!ends_with(p, ".sh"))
|
||||
continue;
|
||||
|
||||
/* No pattern: match all */
|
||||
if (!argc) {
|
||||
string_list_append(&suite.tests, p);
|
||||
continue;
|
||||
}
|
||||
|
||||
for (i = 0; i < argc; i++)
|
||||
if (!wildmatch(argv[i], p, 0)) {
|
||||
string_list_append(&suite.tests, p);
|
||||
break;
|
||||
}
|
||||
}
|
||||
closedir(dir);
|
||||
|
||||
if (!suite.tests.nr)
|
||||
die("No tests match!");
|
||||
if (max_jobs > suite.tests.nr)
|
||||
max_jobs = suite.tests.nr;
|
||||
|
||||
fprintf(stderr, "Running %d tests (%d at a time)\n",
|
||||
suite.tests.nr, max_jobs);
|
||||
|
||||
ret = run_processes_parallel(max_jobs, next_test, test_failed,
|
||||
test_finished, &suite);
|
||||
|
||||
if (suite.failed.nr > 0) {
|
||||
ret = 1;
|
||||
fprintf(stderr, "%d tests failed:\n\n", suite.failed.nr);
|
||||
for (i = 0; i < suite.failed.nr; i++)
|
||||
fprintf(stderr, "\t%s\n", suite.failed.items[i].string);
|
||||
}
|
||||
|
||||
string_list_clear(&suite.tests, 0);
|
||||
string_list_clear(&suite.failed, 0);
|
||||
|
||||
return !!ret;
|
||||
}
|
||||
|
||||
int cmd__run_command(int argc, const char **argv)
|
||||
{
|
||||
struct child_process proc = CHILD_PROCESS_INIT;
|
||||
int jobs;
|
||||
|
||||
if (argc > 1 && !strcmp(argv[1], "testsuite"))
|
||||
exit(testsuite(argc - 1, argv + 1));
|
||||
|
||||
if (argc < 3)
|
||||
return 1;
|
||||
while (!strcmp(argv[1], "env")) {
|
||||
|
Loading…
Reference in New Issue
Block a user