af9912efaf
The JUnit XML format lends itself to be presented in a powerful UI, where you can drill down to the information you are interested in very quickly. For test failures, this usually means that you want to see the detailed trace of the failing tests. With Travis CI, we passed the `--verbose-log` option to get those traces. However, that seems excessive, as we do not need/use the logs in almost all of those cases: only when a test fails do we have a way to include the trace. So let's do something different when using Azure DevOps: let's run all the tests with `--quiet` first, and only if a failure is encountered, try to trace the commands as they are executed. Of course, we cannot turn on `--verbose-log` after the fact. So let's just re-run the test with all the same options, adding `--verbose-log`. And then munging the output file into the JUnit XML on the fly. Note: there is an off chance that re-running the test in verbose mode "fixes" the failures (and this does happen from time to time!). That is a possibility we should be able to live with. Ideally, we would label this as "Passed upon rerun", and Azure Pipelines even know about that outcome, but it is not available when using the JUnit XML format for now: https://github.com/Microsoft/azure-pipelines-agent/blob/master/src/Agent.Worker/TestResults/JunitResultReader.cs Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de> Signed-off-by: Junio C Hamano <gitster@pobox.com>
331 lines
8.7 KiB
C
331 lines
8.7 KiB
C
#include "test-tool.h"
|
|
#include "cache.h"
|
|
#include "string-list.h"
|
|
#include "utf8.h"
|
|
|
|
/*
|
|
* A "string_list_each_func_t" function that normalizes an entry from
|
|
* GIT_CEILING_DIRECTORIES. If the path is unusable for some reason,
|
|
* die with an explanation.
|
|
*/
|
|
static int normalize_ceiling_entry(struct string_list_item *item, void *unused)
|
|
{
|
|
char *ceil = item->string;
|
|
|
|
if (!*ceil)
|
|
die("Empty path is not supported");
|
|
if (!is_absolute_path(ceil))
|
|
die("Path \"%s\" is not absolute", ceil);
|
|
if (normalize_path_copy(ceil, ceil) < 0)
|
|
die("Path \"%s\" could not be normalized", ceil);
|
|
return 1;
|
|
}
|
|
|
|
static void normalize_argv_string(const char **var, const char *input)
|
|
{
|
|
if (!strcmp(input, "<null>"))
|
|
*var = NULL;
|
|
else if (!strcmp(input, "<empty>"))
|
|
*var = "";
|
|
else
|
|
*var = input;
|
|
|
|
if (*var && (**var == '<' || **var == '('))
|
|
die("Bad value: %s\n", input);
|
|
}
|
|
|
|
struct test_data {
|
|
const char *from; /* input: transform from this ... */
|
|
const char *to; /* output: ... to this. */
|
|
const char *alternative; /* output: ... or this. */
|
|
};
|
|
|
|
/*
|
|
* Compatibility wrappers for OpenBSD, whose basename(3) and dirname(3)
|
|
* have const parameters.
|
|
*/
|
|
static char *posix_basename(char *path)
|
|
{
|
|
return basename(path);
|
|
}
|
|
|
|
static char *posix_dirname(char *path)
|
|
{
|
|
return dirname(path);
|
|
}
|
|
|
|
static int test_function(struct test_data *data, char *(*func)(char *input),
|
|
const char *funcname)
|
|
{
|
|
int failed = 0, i;
|
|
char buffer[1024];
|
|
char *to;
|
|
|
|
for (i = 0; data[i].to; i++) {
|
|
if (!data[i].from)
|
|
to = func(NULL);
|
|
else {
|
|
xsnprintf(buffer, sizeof(buffer), "%s", data[i].from);
|
|
to = func(buffer);
|
|
}
|
|
if (!strcmp(to, data[i].to))
|
|
continue;
|
|
if (!data[i].alternative)
|
|
error("FAIL: %s(%s) => '%s' != '%s'\n",
|
|
funcname, data[i].from, to, data[i].to);
|
|
else if (!strcmp(to, data[i].alternative))
|
|
continue;
|
|
else
|
|
error("FAIL: %s(%s) => '%s' != '%s', '%s'\n",
|
|
funcname, data[i].from, to, data[i].to,
|
|
data[i].alternative);
|
|
failed = 1;
|
|
}
|
|
return failed;
|
|
}
|
|
|
|
static struct test_data basename_data[] = {
|
|
/* --- POSIX type paths --- */
|
|
{ NULL, "." },
|
|
{ "", "." },
|
|
{ ".", "." },
|
|
{ "..", ".." },
|
|
{ "/", "/" },
|
|
{ "//", "/", "//" },
|
|
{ "///", "/", "//" },
|
|
{ "////", "/", "//" },
|
|
{ "usr", "usr" },
|
|
{ "/usr", "usr" },
|
|
{ "/usr/", "usr" },
|
|
{ "/usr//", "usr" },
|
|
{ "/usr/lib", "lib" },
|
|
{ "usr/lib", "lib" },
|
|
{ "usr/lib///", "lib" },
|
|
|
|
#if defined(__MINGW32__) || defined(_MSC_VER)
|
|
/* --- win32 type paths --- */
|
|
{ "\\usr", "usr" },
|
|
{ "\\usr\\", "usr" },
|
|
{ "\\usr\\\\", "usr" },
|
|
{ "\\usr\\lib", "lib" },
|
|
{ "usr\\lib", "lib" },
|
|
{ "usr\\lib\\\\\\", "lib" },
|
|
{ "C:/usr", "usr" },
|
|
{ "C:/usr", "usr" },
|
|
{ "C:/usr/", "usr" },
|
|
{ "C:/usr//", "usr" },
|
|
{ "C:/usr/lib", "lib" },
|
|
{ "C:usr/lib", "lib" },
|
|
{ "C:usr/lib///", "lib" },
|
|
{ "C:", "." },
|
|
{ "C:a", "a" },
|
|
{ "C:/", "/" },
|
|
{ "C:///", "/" },
|
|
{ "\\", "\\", "/" },
|
|
{ "\\\\", "\\", "/" },
|
|
{ "\\\\\\", "\\", "/" },
|
|
#endif
|
|
{ NULL, NULL }
|
|
};
|
|
|
|
static struct test_data dirname_data[] = {
|
|
/* --- POSIX type paths --- */
|
|
{ NULL, "." },
|
|
{ "", "." },
|
|
{ ".", "." },
|
|
{ "..", "." },
|
|
{ "/", "/" },
|
|
{ "//", "/", "//" },
|
|
{ "///", "/", "//" },
|
|
{ "////", "/", "//" },
|
|
{ "usr", "." },
|
|
{ "/usr", "/" },
|
|
{ "/usr/", "/" },
|
|
{ "/usr//", "/" },
|
|
{ "/usr/lib", "/usr" },
|
|
{ "usr/lib", "usr" },
|
|
{ "usr/lib///", "usr" },
|
|
|
|
#if defined(__MINGW32__) || defined(_MSC_VER)
|
|
/* --- win32 type paths --- */
|
|
{ "\\", "\\" },
|
|
{ "\\\\", "\\\\" },
|
|
{ "\\usr", "\\" },
|
|
{ "\\usr\\", "\\" },
|
|
{ "\\usr\\\\", "\\" },
|
|
{ "\\usr\\lib", "\\usr" },
|
|
{ "usr\\lib", "usr" },
|
|
{ "usr\\lib\\\\\\", "usr" },
|
|
{ "C:a", "C:." },
|
|
{ "C:/", "C:/" },
|
|
{ "C:///", "C:/" },
|
|
{ "C:/usr", "C:/" },
|
|
{ "C:/usr/", "C:/" },
|
|
{ "C:/usr//", "C:/" },
|
|
{ "C:/usr/lib", "C:/usr" },
|
|
{ "C:usr/lib", "C:usr" },
|
|
{ "C:usr/lib///", "C:usr" },
|
|
{ "\\\\\\", "\\" },
|
|
{ "\\\\\\\\", "\\" },
|
|
{ "C:", "C:.", "." },
|
|
#endif
|
|
{ NULL, NULL }
|
|
};
|
|
|
|
static int is_dotgitmodules(const char *path)
|
|
{
|
|
return is_hfs_dotgitmodules(path) || is_ntfs_dotgitmodules(path);
|
|
}
|
|
|
|
int cmd__path_utils(int argc, const char **argv)
|
|
{
|
|
if (argc == 3 && !strcmp(argv[1], "normalize_path_copy")) {
|
|
char *buf = xmallocz(strlen(argv[2]));
|
|
int rv = normalize_path_copy(buf, argv[2]);
|
|
if (rv)
|
|
buf = "++failed++";
|
|
puts(buf);
|
|
return 0;
|
|
}
|
|
|
|
if (argc >= 2 && !strcmp(argv[1], "real_path")) {
|
|
while (argc > 2) {
|
|
puts(real_path(argv[2]));
|
|
argc--;
|
|
argv++;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
if (argc >= 2 && !strcmp(argv[1], "absolute_path")) {
|
|
while (argc > 2) {
|
|
puts(absolute_path(argv[2]));
|
|
argc--;
|
|
argv++;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
if (argc == 4 && !strcmp(argv[1], "longest_ancestor_length")) {
|
|
int len;
|
|
struct string_list ceiling_dirs = STRING_LIST_INIT_DUP;
|
|
char *path = xstrdup(argv[2]);
|
|
|
|
/*
|
|
* We have to normalize the arguments because under
|
|
* Windows, bash mangles arguments that look like
|
|
* absolute POSIX paths or colon-separate lists of
|
|
* absolute POSIX paths into DOS paths (e.g.,
|
|
* "/foo:/foo/bar" might be converted to
|
|
* "D:\Src\msysgit\foo;D:\Src\msysgit\foo\bar"),
|
|
* whereas longest_ancestor_length() requires paths
|
|
* that use forward slashes.
|
|
*/
|
|
if (normalize_path_copy(path, path))
|
|
die("Path \"%s\" could not be normalized", argv[2]);
|
|
string_list_split(&ceiling_dirs, argv[3], PATH_SEP, -1);
|
|
filter_string_list(&ceiling_dirs, 0,
|
|
normalize_ceiling_entry, NULL);
|
|
len = longest_ancestor_length(path, &ceiling_dirs);
|
|
string_list_clear(&ceiling_dirs, 0);
|
|
free(path);
|
|
printf("%d\n", len);
|
|
return 0;
|
|
}
|
|
|
|
if (argc >= 4 && !strcmp(argv[1], "prefix_path")) {
|
|
const char *prefix = argv[2];
|
|
int prefix_len = strlen(prefix);
|
|
int nongit_ok;
|
|
setup_git_directory_gently(&nongit_ok);
|
|
while (argc > 3) {
|
|
puts(prefix_path(prefix, prefix_len, argv[3]));
|
|
argc--;
|
|
argv++;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
if (argc == 4 && !strcmp(argv[1], "strip_path_suffix")) {
|
|
char *prefix = strip_path_suffix(argv[2], argv[3]);
|
|
printf("%s\n", prefix ? prefix : "(null)");
|
|
return 0;
|
|
}
|
|
|
|
if (argc == 3 && !strcmp(argv[1], "print_path")) {
|
|
puts(argv[2]);
|
|
return 0;
|
|
}
|
|
|
|
if (argc == 4 && !strcmp(argv[1], "relative_path")) {
|
|
struct strbuf sb = STRBUF_INIT;
|
|
const char *in, *prefix, *rel;
|
|
normalize_argv_string(&in, argv[2]);
|
|
normalize_argv_string(&prefix, argv[3]);
|
|
rel = relative_path(in, prefix, &sb);
|
|
if (!rel)
|
|
puts("(null)");
|
|
else
|
|
puts(strlen(rel) > 0 ? rel : "(empty)");
|
|
strbuf_release(&sb);
|
|
return 0;
|
|
}
|
|
|
|
if (argc == 2 && !strcmp(argv[1], "basename"))
|
|
return test_function(basename_data, posix_basename, argv[1]);
|
|
|
|
if (argc == 2 && !strcmp(argv[1], "dirname"))
|
|
return test_function(dirname_data, posix_dirname, argv[1]);
|
|
|
|
if (argc > 2 && !strcmp(argv[1], "is_dotgitmodules")) {
|
|
int res = 0, expect = 1, i;
|
|
for (i = 2; i < argc; i++)
|
|
if (!strcmp("--not", argv[i]))
|
|
expect = !expect;
|
|
else if (expect != is_dotgitmodules(argv[i]))
|
|
res = error("'%s' is %s.gitmodules", argv[i],
|
|
expect ? "not " : "");
|
|
else
|
|
fprintf(stderr, "ok: '%s' is %s.gitmodules\n",
|
|
argv[i], expect ? "" : "not ");
|
|
return !!res;
|
|
}
|
|
|
|
if (argc > 2 && !strcmp(argv[1], "file-size")) {
|
|
int res = 0, i;
|
|
struct stat st;
|
|
|
|
for (i = 2; i < argc; i++)
|
|
if (stat(argv[i], &st))
|
|
res = error_errno("Cannot stat '%s'", argv[i]);
|
|
else
|
|
printf("%"PRIuMAX"\n", (uintmax_t)st.st_size);
|
|
return !!res;
|
|
}
|
|
|
|
if (argc == 4 && !strcmp(argv[1], "skip-n-bytes")) {
|
|
int fd = open(argv[2], O_RDONLY), offset = atoi(argv[3]);
|
|
char buffer[65536];
|
|
|
|
if (fd < 0)
|
|
die_errno("could not open '%s'", argv[2]);
|
|
if (lseek(fd, offset, SEEK_SET) < 0)
|
|
die_errno("could not skip %d bytes", offset);
|
|
for (;;) {
|
|
ssize_t count = read(fd, buffer, sizeof(buffer));
|
|
if (count < 0)
|
|
die_errno("could not read '%s'", argv[2]);
|
|
if (!count)
|
|
break;
|
|
if (write(1, buffer, count) < 0)
|
|
die_errno("could not write to stdout");
|
|
}
|
|
close(fd);
|
|
return 0;
|
|
}
|
|
|
|
fprintf(stderr, "%s: unknown function name: %s\n", argv[0],
|
|
argv[1] ? argv[1] : "(there was none)");
|
|
return 1;
|
|
}
|