diff options
Diffstat (limited to 't/helper')
73 files changed, 4989 insertions, 493 deletions
diff --git a/t/helper/.gitignore b/t/helper/.gitignore index 7c9d28a834..8c2ddcce95 100644 --- a/t/helper/.gitignore +++ b/t/helper/.gitignore @@ -1,38 +1,2 @@ -/test-chmtime -/test-ctype -/test-config -/test-date -/test-delta -/test-dump-cache-tree -/test-dump-split-index -/test-dump-untracked-cache +/test-tool /test-fake-ssh -/test-scrap-cache-tree -/test-genrandom -/test-hashmap -/test-index-version -/test-lazy-init-name-hash -/test-line-buffer -/test-match-trees -/test-mergesort -/test-mktemp -/test-online-cpus -/test-parse-options -/test-path-utils -/test-prio-queue -/test-read-cache -/test-ref-store -/test-regex -/test-revision-walking -/test-run-command -/test-sha1 -/test-sha1-array -/test-sigchain -/test-strcmp-offset -/test-string-list -/test-submodule-config -/test-subprocess -/test-svn-fe -/test-urlmatch-normalization -/test-wildmatch -/test-write-cache diff --git a/t/helper/test-advise.c b/t/helper/test-advise.c new file mode 100644 index 0000000000..a7043df1d3 --- /dev/null +++ b/t/helper/test-advise.c @@ -0,0 +1,22 @@ +#include "test-tool.h" +#include "cache.h" +#include "advice.h" +#include "config.h" + +int cmd__advise_if_enabled(int argc, const char **argv) +{ + if (argc != 2) + die("usage: %s <advice>", argv[0]); + + setup_git_directory(); + git_config(git_default_config, NULL); + + /* + * Any advice type can be used for testing, but NESTED_TAG was + * selected here and in t0018 where this command is being + * executed. + */ + advise_if_enabled(ADVICE_NESTED_TAG, argv[1]); + + return 0; +} diff --git a/t/helper/test-bitmap.c b/t/helper/test-bitmap.c new file mode 100644 index 0000000000..134a1e9d76 --- /dev/null +++ b/t/helper/test-bitmap.c @@ -0,0 +1,24 @@ +#include "test-tool.h" +#include "cache.h" +#include "pack-bitmap.h" + +static int bitmap_list_commits(void) +{ + return test_bitmap_commits(the_repository); +} + +int cmd__bitmap(int argc, const char **argv) +{ + setup_git_directory(); + + if (argc != 2) + goto usage; + + if (!strcmp(argv[1], "list-commits")) + return bitmap_list_commits(); + +usage: + usage("\ttest-tool bitmap list-commits"); + + return -1; +} diff --git a/t/helper/test-bloom.c b/t/helper/test-bloom.c new file mode 100644 index 0000000000..ad3ef1cd77 --- /dev/null +++ b/t/helper/test-bloom.c @@ -0,0 +1,97 @@ +#include "git-compat-util.h" +#include "bloom.h" +#include "test-tool.h" +#include "commit.h" + +static struct bloom_filter_settings settings = DEFAULT_BLOOM_FILTER_SETTINGS; + +static void add_string_to_filter(const char *data, struct bloom_filter *filter) { + struct bloom_key key; + int i; + + fill_bloom_key(data, strlen(data), &key, &settings); + printf("Hashes:"); + for (i = 0; i < settings.num_hashes; i++){ + printf("0x%08x|", key.hashes[i]); + } + printf("\n"); + add_key_to_filter(&key, filter, &settings); +} + +static void print_bloom_filter(struct bloom_filter *filter) { + int i; + + if (!filter) { + printf("No filter.\n"); + return; + } + printf("Filter_Length:%d\n", (int)filter->len); + printf("Filter_Data:"); + for (i = 0; i < filter->len; i++) { + printf("%02x|", filter->data[i]); + } + printf("\n"); +} + +static void get_bloom_filter_for_commit(const struct object_id *commit_oid) +{ + struct commit *c; + struct bloom_filter *filter; + setup_git_directory(); + c = lookup_commit(the_repository, commit_oid); + filter = get_or_compute_bloom_filter(the_repository, c, 1, + &settings, + NULL); + print_bloom_filter(filter); +} + +static const char *bloom_usage = "\n" +" test-tool bloom get_murmur3 <string>\n" +" test-tool bloom generate_filter <string> [<string>...]\n" +" test-tool bloom get_filter_for_commit <commit-hex>\n"; + +int cmd__bloom(int argc, const char **argv) +{ + setup_git_directory(); + + if (argc < 2) + usage(bloom_usage); + + if (!strcmp(argv[1], "get_murmur3")) { + uint32_t hashed; + if (argc < 3) + usage(bloom_usage); + hashed = murmur3_seeded(0, argv[2], strlen(argv[2])); + printf("Murmur3 Hash with seed=0:0x%08x\n", hashed); + } + + if (!strcmp(argv[1], "generate_filter")) { + struct bloom_filter filter; + int i = 2; + filter.len = (settings.bits_per_entry + BITS_PER_WORD - 1) / BITS_PER_WORD; + CALLOC_ARRAY(filter.data, filter.len); + + if (argc - 1 < i) + usage(bloom_usage); + + while (argv[i]) { + add_string_to_filter(argv[i], &filter); + i++; + } + + print_bloom_filter(&filter); + } + + if (!strcmp(argv[1], "get_filter_for_commit")) { + struct object_id oid; + const char *end; + if (argc < 3) + usage(bloom_usage); + if (parse_oid_hex(argv[2], &oid, &end)) + die("cannot parse oid '%s'", argv[2]); + init_bloom_filters(); + get_bloom_filter_for_commit(&oid); + } + + return 0; +} diff --git a/t/helper/test-chmtime.c b/t/helper/test-chmtime.c index e760256406..524b55ca49 100644 --- a/t/helper/test-chmtime.c +++ b/t/helper/test-chmtime.c @@ -5,32 +5,43 @@ * * The mtime can be changed to an absolute value: * - * test-chmtime =<seconds> file... + * test-tool chmtime =<seconds> file... * * Relative to the current time as returned by time(3): * - * test-chmtime =+<seconds> (or =-<seconds>) file... + * test-tool chmtime =+<seconds> (or =-<seconds>) file... * * Or relative to the current mtime of the file: * - * test-chmtime <seconds> file... - * test-chmtime +<seconds> (or -<seconds>) file... + * test-tool chmtime <seconds> file... + * test-tool chmtime +<seconds> (or -<seconds>) file... * * Examples: * - * To just print the mtime use --verbose and set the file mtime offset to 0: + * To print the mtime and the file name use --verbose and set + * the file mtime offset to 0: * - * test-chmtime -v +0 file + * test-tool chmtime -v +0 file + * + * To print only the mtime use --get: + * + * test-tool chmtime --get file * * To set the mtime to current time: * - * test-chmtime =+0 file + * test-tool chmtime =+0 file + * + * To set the file mtime offset to +1 and print the new value: + * + * test-tool chmtime --get +1 file * */ +#include "test-tool.h" #include "git-compat-util.h" #include <utime.h> -static const char usage_str[] = "-v|--verbose (+|=|=+|=-|-)<seconds> <file>..."; +static const char usage_str[] = + "(-v|--verbose|-g|--get) (+|=|=+|=-|-)<seconds> <file>..."; static int timespec_arg(const char *arg, long int *set_time, int *set_eq) { @@ -46,7 +57,6 @@ static int timespec_arg(const char *arg, long int *set_time, int *set_eq) } *set_time = strtol(timespec, &test, 10); if (*test) { - fprintf(stderr, "Not a base-10 integer: %s\n", arg + 1); return 0; } if ((*set_eq && *set_time < 0) || *set_eq == 2) { @@ -56,9 +66,10 @@ static int timespec_arg(const char *arg, long int *set_time, int *set_eq) return 1; } -int cmd_main(int argc, const char **argv) +int cmd__chmtime(int argc, const char **argv) { static int verbose; + static int get; int i = 1; /* no mtime change by default */ @@ -68,23 +79,39 @@ int cmd_main(int argc, const char **argv) if (argc < 3) goto usage; - if (strcmp(argv[i], "--verbose") == 0 || strcmp(argv[i], "-v") == 0) { + if (strcmp(argv[i], "--get") == 0 || strcmp(argv[i], "-g") == 0) { + get = 1; + ++i; + } else if (strcmp(argv[i], "--verbose") == 0 || strcmp(argv[i], "-v") == 0) { verbose = 1; ++i; } - if (timespec_arg(argv[i], &set_time, &set_eq)) + + if (i == argc) { + goto usage; + } + + if (timespec_arg(argv[i], &set_time, &set_eq)) { ++i; - else + } else { + if (get == 0) { + fprintf(stderr, "Not a base-10 integer: %s\n", argv[i] + 1); + goto usage; + } + } + + if (i == argc) goto usage; for (; i < argc; i++) { struct stat sb; struct utimbuf utb; + uintmax_t mtime; if (stat(argv[i], &sb) < 0) { - fprintf(stderr, "Failed to stat %s: %s\n", + fprintf(stderr, "Failed to stat %s: %s. Skipping\n", argv[i], strerror(errno)); - return 1; + continue; } #ifdef GIT_WINDOWS_NATIVE @@ -99,8 +126,10 @@ int cmd_main(int argc, const char **argv) utb.actime = sb.st_atime; utb.modtime = set_eq ? set_time : sb.st_mtime + set_time; - if (verbose) { - uintmax_t mtime = utb.modtime < 0 ? 0: utb.modtime; + mtime = utb.modtime < 0 ? 0: utb.modtime; + if (get) { + printf("%"PRIuMAX"\n", mtime); + } else if (verbose) { printf("%"PRIuMAX"\t%s\n", mtime, argv[i]); } diff --git a/t/helper/test-config.c b/t/helper/test-config.c index 1a7b8bd3d6..a6e936721f 100644 --- a/t/helper/test-config.c +++ b/t/helper/test-config.c @@ -1,3 +1,4 @@ +#include "test-tool.h" #include "cache.h" #include "config.h" #include "string-list.h" @@ -32,25 +33,10 @@ * Examples: * * To print the value with highest priority for key "foo.bAr Baz.rock": - * test-config get_value "foo.bAr Baz.rock" + * test-tool config get_value "foo.bAr Baz.rock" * */ -static const char *scope_name(enum config_scope scope) -{ - switch (scope) { - case CONFIG_SCOPE_SYSTEM: - return "system"; - case CONFIG_SCOPE_GLOBAL: - return "global"; - case CONFIG_SCOPE_REPO: - return "repo"; - case CONFIG_SCOPE_CMDLINE: - return "cmdline"; - default: - return "unknown"; - } -} static int iterate_cb(const char *var, const char *value, void *data) { static int nr; @@ -62,7 +48,8 @@ static int iterate_cb(const char *var, const char *value, void *data) printf("value=%s\n", value ? value : "(null)"); printf("origin=%s\n", current_config_origin_type()); printf("name=%s\n", current_config_name()); - printf("scope=%s\n", scope_name(current_config_scope())); + printf("lno=%d\n", current_config_line()); + printf("scope=%s\n", config_scope_name(current_config_scope())); return 0; } @@ -77,7 +64,7 @@ static int early_config_cb(const char *var, const char *value, void *vdata) return 0; } -int cmd_main(int argc, const char **argv) +int cmd__config(int argc, const char **argv) { int i, val; const char *v; @@ -139,7 +126,7 @@ int cmd_main(int argc, const char **argv) goto exit1; } } else if (argc == 3 && !strcmp(argv[1], "get_string")) { - if (!git_config_get_string_const(argv[2], &v)) { + if (!git_config_get_string_tmp(argv[2], &v)) { printf("%s\n", v); goto exit0; } else { diff --git a/t/helper/test-crontab.c b/t/helper/test-crontab.c new file mode 100644 index 0000000000..e7c0137a47 --- /dev/null +++ b/t/helper/test-crontab.c @@ -0,0 +1,35 @@ +#include "test-tool.h" +#include "cache.h" + +/* + * Usage: test-tool cron <file> [-l] + * + * If -l is specified, then write the contents of <file> to stdout. + * Otherwise, write from stdin into <file>. + */ +int cmd__crontab(int argc, const char **argv) +{ + int a; + FILE *from, *to; + + if (argc == 3 && !strcmp(argv[2], "-l")) { + from = fopen(argv[1], "r"); + if (!from) + return 0; + to = stdout; + } else if (argc == 2) { + from = stdin; + to = fopen(argv[1], "w"); + } else + return error("unknown arguments"); + + while ((a = fgetc(from)) != EOF) + fputc(a, to); + + if (argc == 3) + fclose(from); + else + fclose(to); + + return 0; +} diff --git a/t/helper/test-ctype.c b/t/helper/test-ctype.c index bb72c47df5..92c4c2313e 100644 --- a/t/helper/test-ctype.c +++ b/t/helper/test-ctype.c @@ -1,3 +1,4 @@ +#include "test-tool.h" #include "cache.h" static int rc; @@ -28,7 +29,7 @@ static int is_in(const char *s, int ch) #define LOWER "abcdefghijklmnopqrstuvwxyz" #define UPPER "ABCDEFGHIJKLMNOPQRSTUVWXYZ" -int cmd_main(int argc, const char **argv) +int cmd__ctype(int argc, const char **argv) { TEST_CLASS(isdigit, DIGIT); TEST_CLASS(isspace, " \n\r\t"); diff --git a/t/helper/test-date.c b/t/helper/test-date.c index f414a3ac67..099eff4f0f 100644 --- a/t/helper/test-date.c +++ b/t/helper/test-date.c @@ -1,25 +1,37 @@ +#include "test-tool.h" #include "cache.h" static const char *usage_msg = "\n" -" test-date relative [time_t]...\n" -" test-date show:<format> [time_t]...\n" -" test-date parse [date]...\n" -" test-date approxidate [date]...\n" -" test-date is64bit\n" -" test-date time_t-is64bit\n"; - -static void show_relative_dates(const char **argv, struct timeval *now) +" test-tool date relative [time_t]...\n" +" test-tool date human [time_t]...\n" +" test-tool date show:<format> [time_t]...\n" +" test-tool date parse [date]...\n" +" test-tool date approxidate [date]...\n" +" test-tool date timestamp [date]...\n" +" test-tool date getnanos [start-nanos]\n" +" test-tool date is64bit\n" +" test-tool date time_t-is64bit\n"; + +static void show_relative_dates(const char **argv) { struct strbuf buf = STRBUF_INIT; for (; *argv; argv++) { time_t t = atoi(*argv); - show_date_relative(t, 0, now, &buf); + show_date_relative(t, &buf); printf("%s -> %s\n", *argv, buf.buf); } strbuf_release(&buf); } +static void show_human_dates(const char **argv) +{ + for (; *argv; argv++) { + time_t t = atoi(*argv); + printf("%s -> %s\n", *argv, show_date(t, 0, DATE_MODE(HUMAN))); + } +} + static void show_dates(const char **argv, const char *format) { struct date_mode mode; @@ -43,7 +55,7 @@ static void show_dates(const char **argv, const char *format) } } -static void parse_dates(const char **argv, struct timeval *now) +static void parse_dates(const char **argv) { struct strbuf result = STRBUF_INIT; @@ -62,39 +74,54 @@ static void parse_dates(const char **argv, struct timeval *now) strbuf_release(&result); } -static void parse_approxidate(const char **argv, struct timeval *now) +static void parse_approxidate(const char **argv) { for (; *argv; argv++) { timestamp_t t; - t = approxidate_relative(*argv, now); + t = approxidate_relative(*argv); printf("%s -> %s\n", *argv, show_date(t, 0, DATE_MODE(ISO8601))); } } -int cmd_main(int argc, const char **argv) +static void parse_approx_timestamp(const char **argv) { - struct timeval now; - const char *x; - - x = getenv("TEST_DATE_NOW"); - if (x) { - now.tv_sec = atoi(x); - now.tv_usec = 0; + for (; *argv; argv++) { + timestamp_t t; + t = approxidate_relative(*argv); + printf("%s -> %"PRItime"\n", *argv, t); } - else - gettimeofday(&now, NULL); +} + +static void getnanos(const char **argv) +{ + double seconds = getnanotime() / 1.0e9; + + if (*argv) + seconds -= strtod(*argv, NULL); + printf("%lf\n", seconds); +} + +int cmd__date(int argc, const char **argv) +{ + const char *x; argv++; if (!*argv) usage(usage_msg); if (!strcmp(*argv, "relative")) - show_relative_dates(argv+1, &now); + show_relative_dates(argv+1); + else if (!strcmp(*argv, "human")) + show_human_dates(argv+1); else if (skip_prefix(*argv, "show:", &x)) show_dates(argv+1, x); else if (!strcmp(*argv, "parse")) - parse_dates(argv+1, &now); + parse_dates(argv+1); else if (!strcmp(*argv, "approxidate")) - parse_approxidate(argv+1, &now); + parse_approxidate(argv+1); + else if (!strcmp(*argv, "timestamp")) + parse_approx_timestamp(argv+1); + else if (!strcmp(*argv, "getnanos")) + getnanos(argv+1); else if (!strcmp(*argv, "is64bit")) return sizeof(timestamp_t) == 8 ? 0 : 1; else if (!strcmp(*argv, "time_t-is64bit")) diff --git a/t/helper/test-delta.c b/t/helper/test-delta.c index 591730adc4..e749a49c88 100644 --- a/t/helper/test-delta.c +++ b/t/helper/test-delta.c @@ -8,14 +8,15 @@ * published by the Free Software Foundation. */ +#include "test-tool.h" #include "git-compat-util.h" #include "delta.h" #include "cache.h" static const char usage_str[] = - "test-delta (-d|-p) <from_file> <data_file> <out_file>"; + "test-tool delta (-d|-p) <from_file> <data_file> <out_file>"; -int cmd_main(int argc, const char **argv) +int cmd__delta(int argc, const char **argv) { int fd; struct stat st; @@ -33,8 +34,8 @@ int cmd_main(int argc, const char **argv) return 1; } from_size = st.st_size; - from_buf = mmap(NULL, from_size, PROT_READ, MAP_PRIVATE, fd, 0); - if (from_buf == MAP_FAILED) { + from_buf = xmalloc(from_size); + if (read_in_full(fd, from_buf, from_size) < 0) { perror(argv[2]); close(fd); return 1; @@ -47,8 +48,8 @@ int cmd_main(int argc, const char **argv) return 1; } data_size = st.st_size; - data_buf = mmap(NULL, data_size, PROT_READ, MAP_PRIVATE, fd, 0); - if (data_buf == MAP_FAILED) { + data_buf = xmalloc(data_size); + if (read_in_full(fd, data_buf, data_size) < 0) { perror(argv[3]); close(fd); return 1; diff --git a/t/helper/test-dir-iterator.c b/t/helper/test-dir-iterator.c new file mode 100644 index 0000000000..659b6bfa81 --- /dev/null +++ b/t/helper/test-dir-iterator.c @@ -0,0 +1,65 @@ +#include "test-tool.h" +#include "git-compat-util.h" +#include "strbuf.h" +#include "iterator.h" +#include "dir-iterator.h" + +static const char *error_name(int error_number) +{ + switch (error_number) { + case ENOENT: return "ENOENT"; + case ENOTDIR: return "ENOTDIR"; + default: return "ESOMETHINGELSE"; + } +} + +/* + * usage: + * tool-test dir-iterator [--follow-symlinks] [--pedantic] directory_path + */ +int cmd__dir_iterator(int argc, const char **argv) +{ + struct dir_iterator *diter; + unsigned int flags = 0; + int iter_status; + + for (++argv, --argc; *argv && starts_with(*argv, "--"); ++argv, --argc) { + if (strcmp(*argv, "--follow-symlinks") == 0) + flags |= DIR_ITERATOR_FOLLOW_SYMLINKS; + else if (strcmp(*argv, "--pedantic") == 0) + flags |= DIR_ITERATOR_PEDANTIC; + else + die("invalid option '%s'", *argv); + } + + if (!*argv || argc != 1) + die("dir-iterator needs exactly one non-option argument"); + + diter = dir_iterator_begin(*argv, flags); + + if (!diter) { + printf("dir_iterator_begin failure: %s\n", error_name(errno)); + exit(EXIT_FAILURE); + } + + while ((iter_status = dir_iterator_advance(diter)) == ITER_OK) { + if (S_ISDIR(diter->st.st_mode)) + printf("[d] "); + else if (S_ISREG(diter->st.st_mode)) + printf("[f] "); + else if (S_ISLNK(diter->st.st_mode)) + printf("[s] "); + else + printf("[?] "); + + printf("(%s) [%s] %s\n", diter->relative_path, diter->basename, + diter->path.buf); + } + + if (iter_status != ITER_DONE) { + printf("dir_iterator_advance failure\n"); + return 1; + } + + return 0; +} diff --git a/t/helper/test-drop-caches.c b/t/helper/test-drop-caches.c new file mode 100644 index 0000000000..7b4278462b --- /dev/null +++ b/t/helper/test-drop-caches.c @@ -0,0 +1,160 @@ +#include "test-tool.h" +#include "git-compat-util.h" + +#if defined(GIT_WINDOWS_NATIVE) +#include "lazyload.h" + +static int cmd_sync(void) +{ + char Buffer[MAX_PATH]; + DWORD dwRet; + char szVolumeAccessPath[] = "\\\\.\\XXXX:"; + HANDLE hVolWrite; + int success = 0, dos_drive_prefix; + + dwRet = GetCurrentDirectory(MAX_PATH, Buffer); + if ((0 == dwRet) || (dwRet > MAX_PATH)) + return error("Error getting current directory"); + + dos_drive_prefix = has_dos_drive_prefix(Buffer); + if (!dos_drive_prefix) + return error("'%s': invalid drive letter", Buffer); + + memcpy(szVolumeAccessPath, Buffer, dos_drive_prefix); + szVolumeAccessPath[dos_drive_prefix] = '\0'; + + hVolWrite = CreateFile(szVolumeAccessPath, GENERIC_READ | GENERIC_WRITE, + FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL); + if (INVALID_HANDLE_VALUE == hVolWrite) + return error("Unable to open volume for writing, need admin access"); + + success = FlushFileBuffers(hVolWrite); + if (!success) + error("Unable to flush volume"); + + CloseHandle(hVolWrite); + + return !success; +} + +#define STATUS_SUCCESS (0x00000000L) +#define STATUS_PRIVILEGE_NOT_HELD (0xC0000061L) + +typedef enum _SYSTEM_INFORMATION_CLASS { + SystemMemoryListInformation = 80, +} SYSTEM_INFORMATION_CLASS; + +typedef enum _SYSTEM_MEMORY_LIST_COMMAND { + MemoryCaptureAccessedBits, + MemoryCaptureAndResetAccessedBits, + MemoryEmptyWorkingSets, + MemoryFlushModifiedList, + MemoryPurgeStandbyList, + MemoryPurgeLowPriorityStandbyList, + MemoryCommandMax +} SYSTEM_MEMORY_LIST_COMMAND; + +static BOOL GetPrivilege(HANDLE TokenHandle, LPCSTR lpName, int flags) +{ + BOOL bResult; + DWORD dwBufferLength; + LUID luid; + TOKEN_PRIVILEGES tpPreviousState; + TOKEN_PRIVILEGES tpNewState; + + dwBufferLength = 16; + bResult = LookupPrivilegeValueA(0, lpName, &luid); + if (bResult) { + tpNewState.PrivilegeCount = 1; + tpNewState.Privileges[0].Luid = luid; + tpNewState.Privileges[0].Attributes = 0; + bResult = AdjustTokenPrivileges(TokenHandle, 0, &tpNewState, + (DWORD)((LPBYTE)&(tpNewState.Privileges[1]) - (LPBYTE)&tpNewState), + &tpPreviousState, &dwBufferLength); + if (bResult) { + tpPreviousState.PrivilegeCount = 1; + tpPreviousState.Privileges[0].Luid = luid; + tpPreviousState.Privileges[0].Attributes = flags != 0 ? 2 : 0; + bResult = AdjustTokenPrivileges(TokenHandle, 0, &tpPreviousState, + dwBufferLength, 0, 0); + } + } + return bResult; +} + +static int cmd_dropcaches(void) +{ + HANDLE hProcess = GetCurrentProcess(); + HANDLE hToken; + DECLARE_PROC_ADDR(ntdll.dll, DWORD, NtSetSystemInformation, INT, PVOID, ULONG); + SYSTEM_MEMORY_LIST_COMMAND command; + int status; + + if (!OpenProcessToken(hProcess, TOKEN_QUERY | TOKEN_ADJUST_PRIVILEGES, &hToken)) + return error("Can't open current process token"); + + if (!GetPrivilege(hToken, "SeProfileSingleProcessPrivilege", 1)) + return error("Can't get SeProfileSingleProcessPrivilege"); + + CloseHandle(hToken); + + if (!INIT_PROC_ADDR(NtSetSystemInformation)) + return error("Could not find NtSetSystemInformation() function"); + + command = MemoryPurgeStandbyList; + status = NtSetSystemInformation( + SystemMemoryListInformation, + &command, + sizeof(SYSTEM_MEMORY_LIST_COMMAND) + ); + if (status == STATUS_PRIVILEGE_NOT_HELD) + error("Insufficient privileges to purge the standby list, need admin access"); + else if (status != STATUS_SUCCESS) + error("Unable to execute the memory list command %d", status); + + return status; +} + +#elif defined(__linux__) + +static int cmd_sync(void) +{ + return system("sync"); +} + +static int cmd_dropcaches(void) +{ + return system("echo 3 | sudo tee /proc/sys/vm/drop_caches"); +} + +#elif defined(__APPLE__) + +static int cmd_sync(void) +{ + return system("sync"); +} + +static int cmd_dropcaches(void) +{ + return system("sudo purge"); +} + +#else + +static int cmd_sync(void) +{ + return 0; +} + +static int cmd_dropcaches(void) +{ + return error("drop caches not implemented on this platform"); +} + +#endif + +int cmd__drop_caches(int argc, const char **argv) +{ + cmd_sync(); + return cmd_dropcaches(); +} diff --git a/t/helper/test-dump-cache-tree.c b/t/helper/test-dump-cache-tree.c index ebf3aab22d..6a3f88f5f5 100644 --- a/t/helper/test-dump-cache-tree.c +++ b/t/helper/test-dump-cache-tree.c @@ -1,3 +1,4 @@ +#include "test-tool.h" #include "cache.h" #include "tree.h" #include "cache-tree.h" @@ -32,7 +33,7 @@ static int dump_cache_tree(struct cache_tree *it, } else { dump_one(it, pfx, ""); - if (oidcmp(&it->oid, &ref->oid) || + if (!oideq(&it->oid, &ref->oid) || ref->entry_count != it->entry_count || ref->subtree_nr != it->subtree_nr) { /* claims to be valid but is lying */ @@ -54,7 +55,7 @@ static int dump_cache_tree(struct cache_tree *it, return errs; } -int cmd_main(int ac, const char **av) +int cmd__dump_cache_tree(int ac, const char **av) { struct index_state istate; struct cache_tree *another = cache_tree(); diff --git a/t/helper/test-dump-fsmonitor.c b/t/helper/test-dump-fsmonitor.c new file mode 100644 index 0000000000..975f0ac890 --- /dev/null +++ b/t/helper/test-dump-fsmonitor.c @@ -0,0 +1,22 @@ +#include "test-tool.h" +#include "cache.h" + +int cmd__dump_fsmonitor(int ac, const char **av) +{ + struct index_state *istate = the_repository->index; + int i; + + setup_git_directory(); + if (do_read_index(istate, the_repository->index_file, 0) < 0) + die("unable to read index file"); + if (!istate->fsmonitor_last_update) { + printf("no fsmonitor\n"); + return 0; + } + printf("fsmonitor last update %s\n", istate->fsmonitor_last_update); + + for (i = 0; i < istate->cache_nr; i++) + printf((istate->cache[i]->ce_flags & CE_FSMONITOR_VALID) ? "+" : "-"); + + return 0; +} diff --git a/t/helper/test-dump-split-index.c b/t/helper/test-dump-split-index.c index e44430b699..a209880eb3 100644 --- a/t/helper/test-dump-split-index.c +++ b/t/helper/test-dump-split-index.c @@ -1,3 +1,4 @@ +#include "test-tool.h" #include "cache.h" #include "split-index.h" #include "ewah/ewok.h" @@ -7,19 +8,21 @@ static void show_bit(size_t pos, void *data) printf(" %d", (int)pos); } -int cmd_main(int ac, const char **av) +int cmd__dump_split_index(int ac, const char **av) { struct split_index *si; int i; + setup_git_directory(); + do_read_index(&the_index, av[1], 1); - printf("own %s\n", sha1_to_hex(the_index.sha1)); + printf("own %s\n", oid_to_hex(&the_index.oid)); si = the_index.split_index; if (!si) { printf("not a split index\n"); return 0; } - printf("base %s\n", sha1_to_hex(si->base_sha1)); + printf("base %s\n", oid_to_hex(&si->base_oid)); for (i = 0; i < the_index.cache_nr; i++) { struct cache_entry *ce = the_index.cache[i]; printf("%06o %s %d\t%s\n", ce->ce_mode, diff --git a/t/helper/test-dump-untracked-cache.c b/t/helper/test-dump-untracked-cache.c index f752532ffb..cf0f2c7228 100644 --- a/t/helper/test-dump-untracked-cache.c +++ b/t/helper/test-dump-untracked-cache.c @@ -1,3 +1,5 @@ +#define USE_THE_INDEX_COMPATIBILITY_MACROS +#include "test-tool.h" #include "cache.h" #include "dir.h" @@ -23,7 +25,7 @@ static void dump(struct untracked_cache_dir *ucd, struct strbuf *base) len = base->len; strbuf_addf(base, "%s/", ucd->name); printf("%s %s", base->buf, - sha1_to_hex(ucd->exclude_sha1)); + oid_to_hex(&ucd->exclude_oid)); if (ucd->recurse) fputs(" recurse", stdout); if (ucd->check_only) @@ -38,7 +40,7 @@ static void dump(struct untracked_cache_dir *ucd, struct strbuf *base) strbuf_setlen(base, len); } -int cmd_main(int ac, const char **av) +int cmd__dump_untracked_cache(int ac, const char **av) { struct untracked_cache *uc; struct strbuf base = STRBUF_INIT; @@ -54,8 +56,8 @@ int cmd_main(int ac, const char **av) printf("no untracked cache\n"); return 0; } - printf("info/exclude %s\n", sha1_to_hex(uc->ss_info_exclude.sha1)); - printf("core.excludesfile %s\n", sha1_to_hex(uc->ss_excludes_file.sha1)); + printf("info/exclude %s\n", oid_to_hex(&uc->ss_info_exclude.oid)); + printf("core.excludesfile %s\n", oid_to_hex(&uc->ss_excludes_file.oid)); printf("exclude_per_dir %s\n", uc->exclude_per_dir); printf("flags %08x\n", uc->dir_flags); if (uc->root) diff --git a/t/helper/test-example-decorate.c b/t/helper/test-example-decorate.c new file mode 100644 index 0000000000..b9d1200eb9 --- /dev/null +++ b/t/helper/test-example-decorate.c @@ -0,0 +1,75 @@ +#include "test-tool.h" +#include "cache.h" +#include "object.h" +#include "decorate.h" + +int cmd__example_decorate(int argc, const char **argv) +{ + struct decoration n; + struct object_id one_oid = { {1} }; + struct object_id two_oid = { {2} }; + struct object_id three_oid = { {3} }; + struct object *one, *two, *three; + + int decoration_a, decoration_b; + + void *ret; + + int i, objects_noticed = 0; + + /* + * The struct must be zero-initialized. + */ + memset(&n, 0, sizeof(n)); + + /* + * Add 2 objects, one with a non-NULL decoration and one with a NULL + * decoration. + */ + one = lookup_unknown_object(the_repository, &one_oid); + two = lookup_unknown_object(the_repository, &two_oid); + ret = add_decoration(&n, one, &decoration_a); + if (ret) + BUG("when adding a brand-new object, NULL should be returned"); + ret = add_decoration(&n, two, NULL); + if (ret) + BUG("when adding a brand-new object, NULL should be returned"); + + /* + * When re-adding an already existing object, the old decoration is + * returned. + */ + ret = add_decoration(&n, one, NULL); + if (ret != &decoration_a) + BUG("when readding an already existing object, existing decoration should be returned"); + ret = add_decoration(&n, two, &decoration_b); + if (ret) + BUG("when readding an already existing object, existing decoration should be returned"); + + /* + * Lookup returns the added declarations, or NULL if the object was + * never added. + */ + ret = lookup_decoration(&n, one); + if (ret) + BUG("lookup should return added declaration"); + ret = lookup_decoration(&n, two); + if (ret != &decoration_b) + BUG("lookup should return added declaration"); + three = lookup_unknown_object(the_repository, &three_oid); + ret = lookup_decoration(&n, three); + if (ret) + BUG("lookup for unknown object should return NULL"); + + /* + * The user can also loop through all entries. + */ + for (i = 0; i < n.size; i++) { + if (n.entries[i].base) + objects_noticed++; + } + if (objects_noticed != 2) + BUG("should have 2 objects"); + + return 0; +} diff --git a/t/helper/test-fast-rebase.c b/t/helper/test-fast-rebase.c new file mode 100644 index 0000000000..fc2d460904 --- /dev/null +++ b/t/helper/test-fast-rebase.c @@ -0,0 +1,225 @@ +/* + * "git fast-rebase" builtin command + * + * FAST: Forking Any Subprocesses (is) Taboo + * + * This is meant SOLELY as a demo of what is possible. sequencer.c and + * rebase.c should be refactored to use the ideas here, rather than attempting + * to extend this file to replace those (unless Phillip or Dscho say that + * refactoring is too hard and we need a clean slate, but I'm guessing that + * refactoring is the better route). + */ + +#define USE_THE_INDEX_COMPATIBILITY_MACROS +#include "test-tool.h" + +#include "cache-tree.h" +#include "commit.h" +#include "lockfile.h" +#include "merge-ort.h" +#include "refs.h" +#include "revision.h" +#include "sequencer.h" +#include "strvec.h" +#include "tree.h" + +static const char *short_commit_name(struct commit *commit) +{ + return find_unique_abbrev(&commit->object.oid, DEFAULT_ABBREV); +} + +static struct commit *peel_committish(const char *name) +{ + struct object *obj; + struct object_id oid; + + if (get_oid(name, &oid)) + return NULL; + obj = parse_object(the_repository, &oid); + return (struct commit *)peel_to_type(name, 0, obj, OBJ_COMMIT); +} + +static char *get_author(const char *message) +{ + size_t len; + const char *a; + + a = find_commit_header(message, "author", &len); + if (a) + return xmemdupz(a, len); + + return NULL; +} + +static struct commit *create_commit(struct tree *tree, + struct commit *based_on, + struct commit *parent) +{ + struct object_id ret; + struct object *obj; + struct commit_list *parents = NULL; + char *author; + char *sign_commit = NULL; + struct commit_extra_header *extra; + struct strbuf msg = STRBUF_INIT; + const char *out_enc = get_commit_output_encoding(); + const char *message = logmsg_reencode(based_on, NULL, out_enc); + const char *orig_message = NULL; + const char *exclude_gpgsig[] = { "gpgsig", NULL }; + + commit_list_insert(parent, &parents); + extra = read_commit_extra_headers(based_on, exclude_gpgsig); + find_commit_subject(message, &orig_message); + strbuf_addstr(&msg, orig_message); + author = get_author(message); + reset_ident_date(); + if (commit_tree_extended(msg.buf, msg.len, &tree->object.oid, parents, + &ret, author, NULL, sign_commit, extra)) { + error(_("failed to write commit object")); + return NULL; + } + free(author); + strbuf_release(&msg); + + obj = parse_object(the_repository, &ret); + return (struct commit *)obj; +} + +int cmd__fast_rebase(int argc, const char **argv) +{ + struct commit *onto; + struct commit *last_commit = NULL, *last_picked_commit = NULL; + struct object_id head; + struct lock_file lock = LOCK_INIT; + struct strvec rev_walk_args = STRVEC_INIT; + struct rev_info revs; + struct commit *commit; + struct merge_options merge_opt; + struct tree *next_tree, *base_tree, *head_tree; + struct merge_result result; + struct strbuf reflog_msg = STRBUF_INIT; + struct strbuf branch_name = STRBUF_INIT; + + /* + * test-tool stuff doesn't set up the git directory by default; need to + * do that manually. + */ + setup_git_directory(); + + if (argc == 2 && !strcmp(argv[1], "-h")) { + printf("Sorry, I am not a psychiatrist; I can not give you the help you need. Oh, you meant usage...\n"); + exit(129); + } + + if (argc != 5 || strcmp(argv[1], "--onto")) + die("usage: read the code, figure out how to use it, then do so"); + + onto = peel_committish(argv[2]); + strbuf_addf(&branch_name, "refs/heads/%s", argv[4]); + + /* Sanity check */ + if (get_oid("HEAD", &head)) + die(_("Cannot read HEAD")); + assert(oideq(&onto->object.oid, &head)); + + hold_locked_index(&lock, LOCK_DIE_ON_ERROR); + if (repo_read_index(the_repository) < 0) + BUG("Could not read index"); + + repo_init_revisions(the_repository, &revs, NULL); + revs.verbose_header = 1; + revs.max_parents = 1; + revs.cherry_mark = 1; + revs.limited = 1; + revs.reverse = 1; + revs.right_only = 1; + revs.sort_order = REV_SORT_IN_GRAPH_ORDER; + revs.topo_order = 1; + strvec_pushl(&rev_walk_args, "", argv[4], "--not", argv[3], NULL); + + if (setup_revisions(rev_walk_args.nr, rev_walk_args.v, &revs, NULL) > 1) + return error(_("unhandled options")); + + strvec_clear(&rev_walk_args); + + if (prepare_revision_walk(&revs) < 0) + return error(_("error preparing revisions")); + + init_merge_options(&merge_opt, the_repository); + memset(&result, 0, sizeof(result)); + merge_opt.show_rename_progress = 1; + merge_opt.branch1 = "HEAD"; + head_tree = get_commit_tree(onto); + result.tree = head_tree; + last_commit = onto; + while ((commit = get_revision(&revs))) { + struct commit *base; + + fprintf(stderr, "Rebasing %s...\r", + oid_to_hex(&commit->object.oid)); + assert(commit->parents && !commit->parents->next); + base = commit->parents->item; + + next_tree = get_commit_tree(commit); + base_tree = get_commit_tree(base); + + merge_opt.branch2 = short_commit_name(commit); + merge_opt.ancestor = xstrfmt("parent of %s", merge_opt.branch2); + + merge_incore_nonrecursive(&merge_opt, + base_tree, + result.tree, + next_tree, + &result); + + free((char*)merge_opt.ancestor); + merge_opt.ancestor = NULL; + if (!result.clean) + break; + last_picked_commit = commit; + last_commit = create_commit(result.tree, commit, last_commit); + } + /* TODO: There should be some kind of rev_info_free(&revs) call... */ + memset(&revs, 0, sizeof(revs)); + + merge_switch_to_result(&merge_opt, head_tree, &result, 1, !result.clean); + + if (result.clean < 0) + exit(128); + + if (result.clean) { + fprintf(stderr, "\nDone.\n"); + strbuf_addf(&reflog_msg, "finish rebase %s onto %s", + oid_to_hex(&last_picked_commit->object.oid), + oid_to_hex(&last_commit->object.oid)); + if (update_ref(reflog_msg.buf, branch_name.buf, + &last_commit->object.oid, + &last_picked_commit->object.oid, + REF_NO_DEREF, UPDATE_REFS_MSG_ON_ERR)) { + error(_("could not update %s"), argv[4]); + die("Failed to update %s", argv[4]); + } + if (create_symref("HEAD", branch_name.buf, reflog_msg.buf) < 0) + die(_("unable to update HEAD")); + strbuf_release(&reflog_msg); + strbuf_release(&branch_name); + + prime_cache_tree(the_repository, the_repository->index, + result.tree); + } else { + fprintf(stderr, "\nAborting: Hit a conflict.\n"); + strbuf_addf(&reflog_msg, "rebase progress up to %s", + oid_to_hex(&last_picked_commit->object.oid)); + if (update_ref(reflog_msg.buf, "HEAD", + &last_commit->object.oid, + &head, + REF_NO_DEREF, UPDATE_REFS_MSG_ON_ERR)) { + error(_("could not update %s"), argv[4]); + die("Failed to update %s", argv[4]); + } + } + if (write_locked_index(&the_index, &lock, + COMMIT_LOCK | SKIP_IF_UNCHANGED)) + die(_("unable to write %s"), get_index_file()); + return (result.clean == 0); +} diff --git a/t/helper/test-genrandom.c b/t/helper/test-genrandom.c index 8d11d22d98..99b8dc1e2d 100644 --- a/t/helper/test-genrandom.c +++ b/t/helper/test-genrandom.c @@ -4,9 +4,10 @@ * Copyright (C) 2007 by Nicolas Pitre, licensed under the GPL version 2. */ +#include "test-tool.h" #include "git-compat-util.h" -int cmd_main(int argc, const char **argv) +int cmd__genrandom(int argc, const char **argv) { unsigned long count, next = 0; unsigned char *c; diff --git a/t/helper/test-genzeros.c b/t/helper/test-genzeros.c new file mode 100644 index 0000000000..9532f5bac9 --- /dev/null +++ b/t/helper/test-genzeros.c @@ -0,0 +1,21 @@ +#include "test-tool.h" +#include "git-compat-util.h" + +int cmd__genzeros(int argc, const char **argv) +{ + long count; + + if (argc > 2) { + fprintf(stderr, "usage: %s [<count>]\n", argv[0]); + return 1; + } + + count = argc > 1 ? strtol(argv[1], NULL, 0) : -1L; + + while (count < 0 || count--) { + if (putchar(0) == EOF) + return -1; + } + + return 0; +} diff --git a/t/helper/test-hash-speed.c b/t/helper/test-hash-speed.c new file mode 100644 index 0000000000..f40d9ad0c2 --- /dev/null +++ b/t/helper/test-hash-speed.c @@ -0,0 +1,61 @@ +#include "test-tool.h" +#include "cache.h" + +#define NUM_SECONDS 3 + +static inline void compute_hash(const struct git_hash_algo *algo, git_hash_ctx *ctx, uint8_t *final, const void *p, size_t len) +{ + algo->init_fn(ctx); + algo->update_fn(ctx, p, len); + algo->final_fn(final, ctx); +} + +int cmd__hash_speed(int ac, const char **av) +{ + git_hash_ctx ctx; + unsigned char hash[GIT_MAX_RAWSZ]; + clock_t initial, start, end; + unsigned bufsizes[] = { 64, 256, 1024, 8192, 16384 }; + int i; + void *p; + const struct git_hash_algo *algo = NULL; + + if (ac == 2) { + for (i = 1; i < GIT_HASH_NALGOS; i++) { + if (!strcmp(av[1], hash_algos[i].name)) { + algo = &hash_algos[i]; + break; + } + } + } + if (!algo) + die("usage: test-tool hash-speed algo_name"); + + /* Use this as an offset to make overflow less likely. */ + initial = clock(); + + printf("algo: %s\n", algo->name); + + for (i = 0; i < ARRAY_SIZE(bufsizes); i++) { + unsigned long j, kb; + double kb_per_sec; + p = xcalloc(1, bufsizes[i]); + start = end = clock() - initial; + for (j = 0; ((end - start) / CLOCKS_PER_SEC) < NUM_SECONDS; j++) { + compute_hash(algo, &ctx, hash, p, bufsizes[i]); + + /* + * Only check elapsed time every 128 iterations to avoid + * dominating the runtime with system calls. + */ + if (!(j & 127)) + end = clock() - initial; + } + kb = j * bufsizes[i]; + kb_per_sec = kb / (1024 * ((double)end - start) / CLOCKS_PER_SEC); + printf("size %u: %lu iters; %lu KiB; %0.2f KiB/s\n", bufsizes[i], j, kb, kb_per_sec); + free(p); + } + + return 0; +} diff --git a/t/helper/test-hash.c b/t/helper/test-hash.c new file mode 100644 index 0000000000..261c545b9d --- /dev/null +++ b/t/helper/test-hash.c @@ -0,0 +1,58 @@ +#include "test-tool.h" +#include "cache.h" + +int cmd_hash_impl(int ac, const char **av, int algo) +{ + git_hash_ctx ctx; + unsigned char hash[GIT_MAX_HEXSZ]; + unsigned bufsz = 8192; + int binary = 0; + char *buffer; + const struct git_hash_algo *algop = &hash_algos[algo]; + + if (ac == 2) { + if (!strcmp(av[1], "-b")) + binary = 1; + else + bufsz = strtoul(av[1], NULL, 10) * 1024 * 1024; + } + + if (!bufsz) + bufsz = 8192; + + while ((buffer = malloc(bufsz)) == NULL) { + fprintf(stderr, "bufsz %u is too big, halving...\n", bufsz); + bufsz /= 2; + if (bufsz < 1024) + die("OOPS"); + } + + algop->init_fn(&ctx); + + while (1) { + ssize_t sz, this_sz; + char *cp = buffer; + unsigned room = bufsz; + this_sz = 0; + while (room) { + sz = xread(0, cp, room); + if (sz == 0) + break; + if (sz < 0) + die_errno("test-hash"); + this_sz += sz; + cp += sz; + room -= sz; + } + if (this_sz == 0) + break; + algop->update_fn(&ctx, buffer, this_sz); + } + algop->final_fn(hash, &ctx); + + if (binary) + fwrite(hash, 1, algop->rawsz, stdout); + else + puts(hash_to_hex_algop(hash, algop)); + return 0; +} diff --git a/t/helper/test-hashmap.c b/t/helper/test-hashmap.c index 1145d51671..36ff07bd4b 100644 --- a/t/helper/test-hashmap.c +++ b/t/helper/test-hashmap.c @@ -1,8 +1,11 @@ +#include "test-tool.h" #include "git-compat-util.h" #include "hashmap.h" +#include "strbuf.h" struct test_entry { + int padding; /* hashmap entry no longer needs to be the first member */ struct hashmap_entry ent; /* key and value as two \0-terminated strings */ char key[FLEX_ARRAY]; @@ -14,27 +17,30 @@ static const char *get_value(const struct test_entry *e) } static int test_entry_cmp(const void *cmp_data, - const void *entry, - const void *entry_or_key, + const struct hashmap_entry *eptr, + const struct hashmap_entry *entry_or_key, const void *keydata) { const int ignore_case = cmp_data ? *((int *)cmp_data) : 0; - const struct test_entry *e1 = entry; - const struct test_entry *e2 = entry_or_key; + const struct test_entry *e1, *e2; const char *key = keydata; + e1 = container_of(eptr, const struct test_entry, ent); + e2 = container_of(entry_or_key, const struct test_entry, ent); + if (ignore_case) return strcasecmp(e1->key, key ? key : e2->key); else return strcmp(e1->key, key ? key : e2->key); } -static struct test_entry *alloc_test_entry(int hash, char *key, int klen, - char *value, int vlen) +static struct test_entry *alloc_test_entry(unsigned int hash, + char *key, char *value) { - struct test_entry *entry = malloc(sizeof(struct test_entry) + klen - + vlen + 2); - hashmap_entry_init(entry, hash); + size_t klen = strlen(key); + size_t vlen = strlen(value); + struct test_entry *entry = xmalloc(st_add4(sizeof(*entry), klen, vlen, 2)); + hashmap_entry_init(&entry->ent, hash); memcpy(entry->key, key, klen + 1); memcpy(entry->key + klen + 1, value, vlen + 1); return entry; @@ -75,7 +81,7 @@ static unsigned int hash(unsigned int method, unsigned int i, const char *key) /* * Test performance of hashmap.[ch] - * Usage: time echo "perfhashmap method rounds" | test-hashmap + * Usage: time echo "perfhashmap method rounds" | test-tool hashmap */ static void perf_hashmap(unsigned int method, unsigned int rounds) { @@ -85,11 +91,11 @@ static void perf_hashmap(unsigned int method, unsigned int rounds) unsigned int *hashes; unsigned int i, j; - entries = malloc(TEST_SIZE * sizeof(struct test_entry *)); - hashes = malloc(TEST_SIZE * sizeof(int)); + ALLOC_ARRAY(entries, TEST_SIZE); + ALLOC_ARRAY(hashes, TEST_SIZE); for (i = 0; i < TEST_SIZE; i++) { - snprintf(buf, sizeof(buf), "%i", i); - entries[i] = alloc_test_entry(0, buf, strlen(buf), "", 0); + xsnprintf(buf, sizeof(buf), "%i", i); + entries[i] = alloc_test_entry(0, buf, ""); hashes[i] = hash(method, i, entries[i]->key); } @@ -100,11 +106,11 @@ static void perf_hashmap(unsigned int method, unsigned int rounds) /* add entries */ for (i = 0; i < TEST_SIZE; i++) { - hashmap_entry_init(entries[i], hashes[i]); - hashmap_add(&map, entries[i]); + hashmap_entry_init(&entries[i]->ent, hashes[i]); + hashmap_add(&map, &entries[i]->ent); } - hashmap_free(&map, 0); + hashmap_clear(&map); } } else { /* test map lookups */ @@ -113,8 +119,8 @@ static void perf_hashmap(unsigned int method, unsigned int rounds) /* fill the map (sparsely if specified) */ j = (method & TEST_SPARSE) ? TEST_SIZE / 10 : TEST_SIZE; for (i = 0; i < j; i++) { - hashmap_entry_init(entries[i], hashes[i]); - hashmap_add(&map, entries[i]); + hashmap_entry_init(&entries[i]->ent, hashes[i]); + hashmap_add(&map, &entries[i]->ent); } for (j = 0; j < rounds; j++) { @@ -124,7 +130,7 @@ static void perf_hashmap(unsigned int method, unsigned int rounds) } } - hashmap_free(&map, 0); + hashmap_clear(&map); } } @@ -142,94 +148,85 @@ static void perf_hashmap(unsigned int method, unsigned int rounds) * * perfhashmap method rounds -> test hashmap.[ch] performance */ -int cmd_main(int argc, const char **argv) +int cmd__hashmap(int argc, const char **argv) { - char line[1024]; - struct hashmap map; + struct strbuf line = STRBUF_INIT; int icase; + struct hashmap map = HASHMAP_INIT(test_entry_cmp, &icase); /* init hash map */ icase = argc > 1 && !strcmp("ignorecase", argv[1]); - hashmap_init(&map, test_entry_cmp, &icase, 0); /* process commands from stdin */ - while (fgets(line, sizeof(line), stdin)) { + while (strbuf_getline(&line, stdin) != EOF) { char *cmd, *p1 = NULL, *p2 = NULL; - int l1 = 0, l2 = 0, hash = 0; + unsigned int hash = 0; struct test_entry *entry; /* break line into command and up to two parameters */ - cmd = strtok(line, DELIM); + cmd = strtok(line.buf, DELIM); /* ignore empty lines */ if (!cmd || *cmd == '#') continue; p1 = strtok(NULL, DELIM); if (p1) { - l1 = strlen(p1); hash = icase ? strihash(p1) : strhash(p1); p2 = strtok(NULL, DELIM); - if (p2) - l2 = strlen(p2); } - if (!strcmp("hash", cmd) && l1) { - - /* print results of different hash functions */ - printf("%u %u %u %u\n", strhash(p1), memhash(p1, l1), - strihash(p1), memihash(p1, l1)); - - } else if (!strcmp("add", cmd) && l1 && l2) { + if (!strcmp("add", cmd) && p1 && p2) { /* create entry with key = p1, value = p2 */ - entry = alloc_test_entry(hash, p1, l1, p2, l2); + entry = alloc_test_entry(hash, p1, p2); /* add to hashmap */ - hashmap_add(&map, entry); + hashmap_add(&map, &entry->ent); - } else if (!strcmp("put", cmd) && l1 && l2) { + } else if (!strcmp("put", cmd) && p1 && p2) { /* create entry with key = p1, value = p2 */ - entry = alloc_test_entry(hash, p1, l1, p2, l2); + entry = alloc_test_entry(hash, p1, p2); /* add / replace entry */ - entry = hashmap_put(&map, entry); + entry = hashmap_put_entry(&map, entry, ent); /* print and free replaced entry, if any */ puts(entry ? get_value(entry) : "NULL"); free(entry); - } else if (!strcmp("get", cmd) && l1) { - + } else if (!strcmp("get", cmd) && p1) { /* lookup entry in hashmap */ - entry = hashmap_get_from_hash(&map, hash, p1); + entry = hashmap_get_entry_from_hash(&map, hash, p1, + struct test_entry, ent); /* print result */ if (!entry) puts("NULL"); - while (entry) { + hashmap_for_each_entry_from(&map, entry, ent) puts(get_value(entry)); - entry = hashmap_get_next(&map, entry); - } - } else if (!strcmp("remove", cmd) && l1) { + } else if (!strcmp("remove", cmd) && p1) { /* setup static key */ struct hashmap_entry key; + struct hashmap_entry *rm; hashmap_entry_init(&key, hash); /* remove entry from hashmap */ - entry = hashmap_remove(&map, &key, p1); + rm = hashmap_remove(&map, &key, p1); + entry = rm ? container_of(rm, struct test_entry, ent) + : NULL; /* print result and free entry*/ puts(entry ? get_value(entry) : "NULL"); free(entry); } else if (!strcmp("iterate", cmd)) { - struct hashmap_iter iter; - hashmap_iter_init(&map, &iter); - while ((entry = hashmap_iter_next(&iter))) + + hashmap_for_each_entry(&map, &iter, entry, + ent /* member name */) printf("%s %s\n", entry->key, get_value(entry)); } else if (!strcmp("size", cmd)) { @@ -238,7 +235,7 @@ int cmd_main(int argc, const char **argv) printf("%u %u\n", map.tablesize, hashmap_get_size(&map)); - } else if (!strcmp("intern", cmd) && l1) { + } else if (!strcmp("intern", cmd) && p1) { /* test that strintern works */ const char *i1 = strintern(p1); @@ -252,7 +249,7 @@ int cmd_main(int argc, const char **argv) else printf("%s\n", i1); - } else if (!strcmp("perfhashmap", cmd) && l1 && l2) { + } else if (!strcmp("perfhashmap", cmd) && p1 && p2) { perf_hashmap(atoi(p1), atoi(p2)); @@ -263,6 +260,7 @@ int cmd_main(int argc, const char **argv) } } - hashmap_free(&map, 1); + strbuf_release(&line); + hashmap_clear_and_free(&map, struct test_entry, ent); return 0; } diff --git a/t/helper/test-index-version.c b/t/helper/test-index-version.c index f569f6b7ef..fcd10968cc 100644 --- a/t/helper/test-index-version.c +++ b/t/helper/test-index-version.c @@ -1,6 +1,7 @@ +#include "test-tool.h" #include "cache.h" -int cmd_main(int argc, const char **argv) +int cmd__index_version(int argc, const char **argv) { struct cache_header hdr; int version; diff --git a/t/helper/test-json-writer.c b/t/helper/test-json-writer.c new file mode 100644 index 0000000000..37c452535f --- /dev/null +++ b/t/helper/test-json-writer.c @@ -0,0 +1,565 @@ +#include "test-tool.h" +#include "cache.h" +#include "json-writer.h" + +static const char *expect_obj1 = "{\"a\":\"abc\",\"b\":42,\"c\":true}"; +static const char *expect_obj2 = "{\"a\":-1,\"b\":2147483647,\"c\":0}"; +static const char *expect_obj3 = "{\"a\":0,\"b\":4294967295,\"c\":9223372036854775807}"; +static const char *expect_obj4 = "{\"t\":true,\"f\":false,\"n\":null}"; +static const char *expect_obj5 = "{\"abc\\tdef\":\"abc\\\\def\"}"; +static const char *expect_obj6 = "{\"a\":3.14}"; + +static const char *pretty_obj1 = ("{\n" + " \"a\": \"abc\",\n" + " \"b\": 42,\n" + " \"c\": true\n" + "}"); +static const char *pretty_obj2 = ("{\n" + " \"a\": -1,\n" + " \"b\": 2147483647,\n" + " \"c\": 0\n" + "}"); +static const char *pretty_obj3 = ("{\n" + " \"a\": 0,\n" + " \"b\": 4294967295,\n" + " \"c\": 9223372036854775807\n" + "}"); +static const char *pretty_obj4 = ("{\n" + " \"t\": true,\n" + " \"f\": false,\n" + " \"n\": null\n" + "}"); + +static struct json_writer obj1 = JSON_WRITER_INIT; +static struct json_writer obj2 = JSON_WRITER_INIT; +static struct json_writer obj3 = JSON_WRITER_INIT; +static struct json_writer obj4 = JSON_WRITER_INIT; +static struct json_writer obj5 = JSON_WRITER_INIT; +static struct json_writer obj6 = JSON_WRITER_INIT; + +static void make_obj1(int pretty) +{ + jw_object_begin(&obj1, pretty); + { + jw_object_string(&obj1, "a", "abc"); + jw_object_intmax(&obj1, "b", 42); + jw_object_true(&obj1, "c"); + } + jw_end(&obj1); +} + +static void make_obj2(int pretty) +{ + jw_object_begin(&obj2, pretty); + { + jw_object_intmax(&obj2, "a", -1); + jw_object_intmax(&obj2, "b", 0x7fffffff); + jw_object_intmax(&obj2, "c", 0); + } + jw_end(&obj2); +} + +static void make_obj3(int pretty) +{ + jw_object_begin(&obj3, pretty); + { + jw_object_intmax(&obj3, "a", 0); + jw_object_intmax(&obj3, "b", 0xffffffff); + jw_object_intmax(&obj3, "c", 0x7fffffffffffffffULL); + } + jw_end(&obj3); +} + +static void make_obj4(int pretty) +{ + jw_object_begin(&obj4, pretty); + { + jw_object_true(&obj4, "t"); + jw_object_false(&obj4, "f"); + jw_object_null(&obj4, "n"); + } + jw_end(&obj4); +} + +static void make_obj5(int pretty) +{ + jw_object_begin(&obj5, pretty); + { + jw_object_string(&obj5, "abc" "\x09" "def", "abc" "\\" "def"); + } + jw_end(&obj5); +} + +static void make_obj6(int pretty) +{ + jw_object_begin(&obj6, pretty); + { + jw_object_double(&obj6, "a", 2, 3.14159); + } + jw_end(&obj6); +} + +static const char *expect_arr1 = "[\"abc\",42,true]"; +static const char *expect_arr2 = "[-1,2147483647,0]"; +static const char *expect_arr3 = "[0,4294967295,9223372036854775807]"; +static const char *expect_arr4 = "[true,false,null]"; + +static const char *pretty_arr1 = ("[\n" + " \"abc\",\n" + " 42,\n" + " true\n" + "]"); +static const char *pretty_arr2 = ("[\n" + " -1,\n" + " 2147483647,\n" + " 0\n" + "]"); +static const char *pretty_arr3 = ("[\n" + " 0,\n" + " 4294967295,\n" + " 9223372036854775807\n" + "]"); +static const char *pretty_arr4 = ("[\n" + " true,\n" + " false,\n" + " null\n" + "]"); + +static struct json_writer arr1 = JSON_WRITER_INIT; +static struct json_writer arr2 = JSON_WRITER_INIT; +static struct json_writer arr3 = JSON_WRITER_INIT; +static struct json_writer arr4 = JSON_WRITER_INIT; + +static void make_arr1(int pretty) +{ + jw_array_begin(&arr1, pretty); + { + jw_array_string(&arr1, "abc"); + jw_array_intmax(&arr1, 42); + jw_array_true(&arr1); + } + jw_end(&arr1); +} + +static void make_arr2(int pretty) +{ + jw_array_begin(&arr2, pretty); + { + jw_array_intmax(&arr2, -1); + jw_array_intmax(&arr2, 0x7fffffff); + jw_array_intmax(&arr2, 0); + } + jw_end(&arr2); +} + +static void make_arr3(int pretty) +{ + jw_array_begin(&arr3, pretty); + { + jw_array_intmax(&arr3, 0); + jw_array_intmax(&arr3, 0xffffffff); + jw_array_intmax(&arr3, 0x7fffffffffffffffULL); + } + jw_end(&arr3); +} + +static void make_arr4(int pretty) +{ + jw_array_begin(&arr4, pretty); + { + jw_array_true(&arr4); + jw_array_false(&arr4); + jw_array_null(&arr4); + } + jw_end(&arr4); +} + +static char *expect_nest1 = + "{\"obj1\":{\"a\":\"abc\",\"b\":42,\"c\":true},\"arr1\":[\"abc\",42,true]}"; + +static struct json_writer nest1 = JSON_WRITER_INIT; + +static void make_nest1(int pretty) +{ + jw_object_begin(&nest1, pretty); + { + jw_object_sub_jw(&nest1, "obj1", &obj1); + jw_object_sub_jw(&nest1, "arr1", &arr1); + } + jw_end(&nest1); +} + +static char *expect_inline1 = + "{\"obj1\":{\"a\":\"abc\",\"b\":42,\"c\":true},\"arr1\":[\"abc\",42,true]}"; + +static char *pretty_inline1 = + ("{\n" + " \"obj1\": {\n" + " \"a\": \"abc\",\n" + " \"b\": 42,\n" + " \"c\": true\n" + " },\n" + " \"arr1\": [\n" + " \"abc\",\n" + " 42,\n" + " true\n" + " ]\n" + "}"); + +static struct json_writer inline1 = JSON_WRITER_INIT; + +static void make_inline1(int pretty) +{ + jw_object_begin(&inline1, pretty); + { + jw_object_inline_begin_object(&inline1, "obj1"); + { + jw_object_string(&inline1, "a", "abc"); + jw_object_intmax(&inline1, "b", 42); + jw_object_true(&inline1, "c"); + } + jw_end(&inline1); + jw_object_inline_begin_array(&inline1, "arr1"); + { + jw_array_string(&inline1, "abc"); + jw_array_intmax(&inline1, 42); + jw_array_true(&inline1); + } + jw_end(&inline1); + } + jw_end(&inline1); +} + +static char *expect_inline2 = + "[[1,2],[3,4],{\"a\":\"abc\"}]"; + +static char *pretty_inline2 = + ("[\n" + " [\n" + " 1,\n" + " 2\n" + " ],\n" + " [\n" + " 3,\n" + " 4\n" + " ],\n" + " {\n" + " \"a\": \"abc\"\n" + " }\n" + "]"); + +static struct json_writer inline2 = JSON_WRITER_INIT; + +static void make_inline2(int pretty) +{ + jw_array_begin(&inline2, pretty); + { + jw_array_inline_begin_array(&inline2); + { + jw_array_intmax(&inline2, 1); + jw_array_intmax(&inline2, 2); + } + jw_end(&inline2); + jw_array_inline_begin_array(&inline2); + { + jw_array_intmax(&inline2, 3); + jw_array_intmax(&inline2, 4); + } + jw_end(&inline2); + jw_array_inline_begin_object(&inline2); + { + jw_object_string(&inline2, "a", "abc"); + } + jw_end(&inline2); + } + jw_end(&inline2); +} + +/* + * When super is compact, we expect subs to be compacted (even if originally + * pretty). + */ +static const char *expect_mixed1 = + ("{\"obj1\":{\"a\":\"abc\",\"b\":42,\"c\":true}," + "\"arr1\":[\"abc\",42,true]}"); + +/* + * When super is pretty, a compact sub (obj1) is kept compact and a pretty + * sub (arr1) is re-indented. + */ +static const char *pretty_mixed1 = + ("{\n" + " \"obj1\": {\"a\":\"abc\",\"b\":42,\"c\":true},\n" + " \"arr1\": [\n" + " \"abc\",\n" + " 42,\n" + " true\n" + " ]\n" + "}"); + +static struct json_writer mixed1 = JSON_WRITER_INIT; + +static void make_mixed1(int pretty) +{ + jw_init(&obj1); + jw_init(&arr1); + + make_obj1(0); /* obj1 is compact */ + make_arr1(1); /* arr1 is pretty */ + + jw_object_begin(&mixed1, pretty); + { + jw_object_sub_jw(&mixed1, "obj1", &obj1); + jw_object_sub_jw(&mixed1, "arr1", &arr1); + } + jw_end(&mixed1); +} + +static void cmp(const char *test, const struct json_writer *jw, const char *exp) +{ + if (!strcmp(jw->json.buf, exp)) + return; + + printf("error[%s]: observed '%s' expected '%s'\n", + test, jw->json.buf, exp); + exit(1); +} + +#define t(v) do { make_##v(0); cmp(#v, &v, expect_##v); } while (0) +#define p(v) do { make_##v(1); cmp(#v, &v, pretty_##v); } while (0) + +/* + * Run some basic regression tests with some known patterns. + * These tests also demonstrate how to use the jw_ API. + */ +static int unit_tests(void) +{ + /* comptact (canonical) forms */ + t(obj1); + t(obj2); + t(obj3); + t(obj4); + t(obj5); + t(obj6); + + t(arr1); + t(arr2); + t(arr3); + t(arr4); + + t(nest1); + + t(inline1); + t(inline2); + + jw_init(&obj1); + jw_init(&obj2); + jw_init(&obj3); + jw_init(&obj4); + + jw_init(&arr1); + jw_init(&arr2); + jw_init(&arr3); + jw_init(&arr4); + + jw_init(&inline1); + jw_init(&inline2); + + /* pretty forms */ + p(obj1); + p(obj2); + p(obj3); + p(obj4); + + p(arr1); + p(arr2); + p(arr3); + p(arr4); + + p(inline1); + p(inline2); + + /* mixed forms */ + t(mixed1); + jw_init(&mixed1); + p(mixed1); + + return 0; +} + +static void get_s(int line_nr, char **s_in) +{ + *s_in = strtok(NULL, " "); + if (!*s_in) + die("line[%d]: expected: <s>", line_nr); +} + +static void get_i(int line_nr, intmax_t *s_in) +{ + char *s; + char *endptr; + + get_s(line_nr, &s); + + *s_in = strtol(s, &endptr, 10); + if (*endptr || errno == ERANGE) + die("line[%d]: invalid integer value", line_nr); +} + +static void get_d(int line_nr, double *s_in) +{ + char *s; + char *endptr; + + get_s(line_nr, &s); + + *s_in = strtod(s, &endptr); + if (*endptr || errno == ERANGE) + die("line[%d]: invalid float value", line_nr); +} + +static int pretty; + +#define MAX_LINE_LENGTH (64 * 1024) + +static char *get_trimmed_line(char *buf, int buf_size) +{ + int len; + + if (!fgets(buf, buf_size, stdin)) + return NULL; + + len = strlen(buf); + while (len > 0) { + char c = buf[len - 1]; + if (c == '\n' || c == '\r' || c == ' ' || c == '\t') + buf[--len] = 0; + else + break; + } + + while (*buf == ' ' || *buf == '\t') + buf++; + + return buf; +} + +static int scripted(void) +{ + struct json_writer jw = JSON_WRITER_INIT; + char buf[MAX_LINE_LENGTH]; + char *line; + int line_nr = 0; + + line = get_trimmed_line(buf, MAX_LINE_LENGTH); + if (!line) + return 0; + + if (!strcmp(line, "object")) + jw_object_begin(&jw, pretty); + else if (!strcmp(line, "array")) + jw_array_begin(&jw, pretty); + else + die("expected first line to be 'object' or 'array'"); + + while ((line = get_trimmed_line(buf, MAX_LINE_LENGTH)) != NULL) { + char *verb; + char *key; + char *s_value; + intmax_t i_value; + double d_value; + + line_nr++; + + verb = strtok(line, " "); + + if (!strcmp(verb, "end")) { + jw_end(&jw); + } + else if (!strcmp(verb, "object-string")) { + get_s(line_nr, &key); + get_s(line_nr, &s_value); + jw_object_string(&jw, key, s_value); + } + else if (!strcmp(verb, "object-int")) { + get_s(line_nr, &key); + get_i(line_nr, &i_value); + jw_object_intmax(&jw, key, i_value); + } + else if (!strcmp(verb, "object-double")) { + get_s(line_nr, &key); + get_i(line_nr, &i_value); + get_d(line_nr, &d_value); + jw_object_double(&jw, key, i_value, d_value); + } + else if (!strcmp(verb, "object-true")) { + get_s(line_nr, &key); + jw_object_true(&jw, key); + } + else if (!strcmp(verb, "object-false")) { + get_s(line_nr, &key); + jw_object_false(&jw, key); + } + else if (!strcmp(verb, "object-null")) { + get_s(line_nr, &key); + jw_object_null(&jw, key); + } + else if (!strcmp(verb, "object-object")) { + get_s(line_nr, &key); + jw_object_inline_begin_object(&jw, key); + } + else if (!strcmp(verb, "object-array")) { + get_s(line_nr, &key); + jw_object_inline_begin_array(&jw, key); + } + else if (!strcmp(verb, "array-string")) { + get_s(line_nr, &s_value); + jw_array_string(&jw, s_value); + } + else if (!strcmp(verb, "array-int")) { + get_i(line_nr, &i_value); + jw_array_intmax(&jw, i_value); + } + else if (!strcmp(verb, "array-double")) { + get_i(line_nr, &i_value); + get_d(line_nr, &d_value); + jw_array_double(&jw, i_value, d_value); + } + else if (!strcmp(verb, "array-true")) + jw_array_true(&jw); + else if (!strcmp(verb, "array-false")) + jw_array_false(&jw); + else if (!strcmp(verb, "array-null")) + jw_array_null(&jw); + else if (!strcmp(verb, "array-object")) + jw_array_inline_begin_object(&jw); + else if (!strcmp(verb, "array-array")) + jw_array_inline_begin_array(&jw); + else + die("unrecognized token: '%s'", verb); + } + + if (!jw_is_terminated(&jw)) + die("json not terminated: '%s'", jw.json.buf); + + printf("%s\n", jw.json.buf); + + strbuf_release(&jw.json); + return 0; +} + +int cmd__json_writer(int argc, const char **argv) +{ + argc--; /* skip over "json-writer" arg */ + argv++; + + if (argc > 0 && argv[0][0] == '-') { + if (!strcmp(argv[0], "-u") || !strcmp(argv[0], "--unit")) + return unit_tests(); + + if (!strcmp(argv[0], "-p") || !strcmp(argv[0], "--pretty")) + pretty = 1; + } + + return scripted(); +} diff --git a/t/helper/test-lazy-init-name-hash.c b/t/helper/test-lazy-init-name-hash.c index 6368a89345..cd1b4c9736 100644 --- a/t/helper/test-lazy-init-name-hash.c +++ b/t/helper/test-lazy-init-name-hash.c @@ -1,3 +1,4 @@ +#include "test-tool.h" #include "cache.h" #include "parse-options.h" @@ -40,17 +41,13 @@ static void dump_run(void) die("non-threaded code path used"); } - dir = hashmap_iter_first(&the_index.dir_hash, &iter_dir); - while (dir) { + hashmap_for_each_entry(&the_index.dir_hash, &iter_dir, dir, + ent /* member name */) printf("dir %08x %7d %s\n", dir->ent.hash, dir->nr, dir->name); - dir = hashmap_iter_next(&iter_dir); - } - ce = hashmap_iter_first(&the_index.name_hash, &iter_cache); - while (ce) { + hashmap_for_each_entry(&the_index.name_hash, &iter_cache, ce, + ent /* member name */) printf("name %08x %s\n", ce->ent.hash, ce->name); - ce = hashmap_iter_next(&iter_cache); - } discard_cache(); } @@ -112,7 +109,7 @@ static void analyze_run(void) { uint64_t t1s, t1m, t2s, t2m; int cache_nr_limit; - int nr_threads_used; + int nr_threads_used = 0; int i; int nr; @@ -184,14 +181,14 @@ static void analyze_run(void) } } -int cmd_main(int argc, const char **argv) +int cmd__lazy_init_name_hash(int argc, const char **argv) { const char *usage[] = { - "test-lazy-init-name-hash -d (-s | -m)", - "test-lazy-init-name-hash -p [-c c]", - "test-lazy-init-name-hash -a a [--step s] [-c c]", - "test-lazy-init-name-hash (-s | -m) [-c c]", - "test-lazy-init-name-hash -s -m [-c c]", + "test-tool lazy-init-name-hash -d (-s | -m)", + "test-tool lazy-init-name-hash -p [-c c]", + "test-tool lazy-init-name-hash -a a [--step s] [-c c]", + "test-tool lazy-init-name-hash (-s | -m) [-c c]", + "test-tool lazy-init-name-hash -s -m [-c c]", NULL }; struct option options[] = { diff --git a/t/helper/test-line-buffer.c b/t/helper/test-line-buffer.c deleted file mode 100644 index 078dd7e29d..0000000000 --- a/t/helper/test-line-buffer.c +++ /dev/null @@ -1,81 +0,0 @@ -/* - * test-line-buffer.c: code to exercise the svn importer's input helper - */ - -#include "git-compat-util.h" -#include "strbuf.h" -#include "vcs-svn/line_buffer.h" - -static uint32_t strtouint32(const char *s) -{ - char *end; - uintmax_t n = strtoumax(s, &end, 10); - if (*s == '\0' || *end != '\0') - die("invalid count: %s", s); - return (uint32_t) n; -} - -static void handle_command(const char *command, const char *arg, struct line_buffer *buf) -{ - if (starts_with(command, "binary ")) { - struct strbuf sb = STRBUF_INIT; - strbuf_addch(&sb, '>'); - buffer_read_binary(buf, &sb, strtouint32(arg)); - fwrite(sb.buf, 1, sb.len, stdout); - strbuf_release(&sb); - } else if (starts_with(command, "copy ")) { - buffer_copy_bytes(buf, strtouint32(arg)); - } else if (starts_with(command, "skip ")) { - buffer_skip_bytes(buf, strtouint32(arg)); - } else { - die("unrecognized command: %s", command); - } -} - -static void handle_line(const char *line, struct line_buffer *stdin_buf) -{ - const char *arg = strchr(line, ' '); - if (!arg) - die("no argument in line: %s", line); - handle_command(line, arg + 1, stdin_buf); -} - -int cmd_main(int argc, const char **argv) -{ - struct line_buffer stdin_buf = LINE_BUFFER_INIT; - struct line_buffer file_buf = LINE_BUFFER_INIT; - struct line_buffer *input = &stdin_buf; - const char *filename; - char *s; - - if (argc == 1) - filename = NULL; - else if (argc == 2) - filename = argv[1]; - else - usage("test-line-buffer [file | &fd] < script"); - - if (buffer_init(&stdin_buf, NULL)) - die_errno("open error"); - if (filename) { - if (*filename == '&') { - if (buffer_fdinit(&file_buf, strtouint32(filename + 1))) - die_errno("error opening fd %s", filename + 1); - } else { - if (buffer_init(&file_buf, filename)) - die_errno("error opening %s", filename); - } - input = &file_buf; - } - - while ((s = buffer_read_line(&stdin_buf))) - handle_line(s, input); - - if (filename && buffer_deinit(&file_buf)) - die("error reading from %s", filename); - if (buffer_deinit(&stdin_buf)) - die("input error"); - if (ferror(stdout)) - die("output error"); - return 0; -} diff --git a/t/helper/test-match-trees.c b/t/helper/test-match-trees.c index 356d8edef1..4079fdee06 100644 --- a/t/helper/test-match-trees.c +++ b/t/helper/test-match-trees.c @@ -1,7 +1,8 @@ +#include "test-tool.h" #include "cache.h" #include "tree.h" -int cmd_main(int ac, const char **av) +int cmd__match_trees(int ac, const char **av) { struct object_id hash1, hash2, shifted; struct tree *one, *two; @@ -19,8 +20,8 @@ int cmd_main(int ac, const char **av) if (!two) die("not a tree-ish %s", av[2]); - shift_tree(&one->object.oid, &two->object.oid, &shifted, -1); + shift_tree(the_repository, &one->object.oid, &two->object.oid, &shifted, -1); printf("shifted: %s\n", oid_to_hex(&shifted)); - exit(0); + return 0; } diff --git a/t/helper/test-mergesort.c b/t/helper/test-mergesort.c index 335cf6b626..c5cffaa4b7 100644 --- a/t/helper/test-mergesort.c +++ b/t/helper/test-mergesort.c @@ -1,3 +1,4 @@ +#include "test-tool.h" #include "cache.h" #include "mergesort.h" @@ -22,7 +23,7 @@ static int compare_strings(const void *a, const void *b) return strcmp(x->text, y->text); } -int cmd_main(int argc, const char **argv) +int cmd__mergesort(int argc, const char **argv) { struct line *line, *p = NULL, *lines = NULL; struct strbuf sb = STRBUF_INIT; diff --git a/t/helper/test-mktemp.c b/t/helper/test-mktemp.c index 89d9b2f7be..2290688940 100644 --- a/t/helper/test-mktemp.c +++ b/t/helper/test-mktemp.c @@ -1,9 +1,10 @@ /* * test-mktemp.c: code to exercise the creation of temporary files */ +#include "test-tool.h" #include "git-compat-util.h" -int cmd_main(int argc, const char **argv) +int cmd__mktemp(int argc, const char **argv) { if (argc != 2) usage("Expected 1 parameter defining the temporary file template"); diff --git a/t/helper/test-sha1-array.c b/t/helper/test-oid-array.c index edfd52d82a..b16cd0b11b 100644 --- a/t/helper/test-sha1-array.c +++ b/t/helper/test-oid-array.c @@ -1,5 +1,6 @@ +#include "test-tool.h" #include "cache.h" -#include "sha1-array.h" +#include "oid-array.h" static int print_oid(const struct object_id *oid, void *data) { @@ -7,10 +8,13 @@ static int print_oid(const struct object_id *oid, void *data) return 0; } -int cmd_main(int argc, const char **argv) +int cmd__oid_array(int argc, const char **argv) { struct oid_array array = OID_ARRAY_INIT; struct strbuf line = STRBUF_INIT; + int nongit_ok; + + setup_git_directory_gently(&nongit_ok); while (strbuf_getline(&line, stdin) != EOF) { const char *arg; @@ -18,11 +22,11 @@ int cmd_main(int argc, const char **argv) if (skip_prefix(line.buf, "append ", &arg)) { if (get_oid_hex(arg, &oid)) - die("not a hexadecimal SHA1: %s", arg); + die("not a hexadecimal oid: %s", arg); oid_array_append(&array, &oid); } else if (skip_prefix(line.buf, "lookup ", &arg)) { if (get_oid_hex(arg, &oid)) - die("not a hexadecimal SHA1: %s", arg); + die("not a hexadecimal oid: %s", arg); printf("%d\n", oid_array_lookup(&array, &oid)); } else if (!strcmp(line.buf, "clear")) oid_array_clear(&array); diff --git a/t/helper/test-oidmap.c b/t/helper/test-oidmap.c new file mode 100644 index 0000000000..0acf99931e --- /dev/null +++ b/t/helper/test-oidmap.c @@ -0,0 +1,112 @@ +#include "test-tool.h" +#include "cache.h" +#include "oidmap.h" +#include "strbuf.h" + +/* key is an oid and value is a name (could be a refname for example) */ +struct test_entry { + struct oidmap_entry entry; + char name[FLEX_ARRAY]; +}; + +#define DELIM " \t\r\n" + +/* + * Read stdin line by line and print result of commands to stdout: + * + * hash oidkey -> sha1hash(oidkey) + * put oidkey namevalue -> NULL / old namevalue + * get oidkey -> NULL / namevalue + * remove oidkey -> NULL / old namevalue + * iterate -> oidkey1 namevalue1\noidkey2 namevalue2\n... + * + */ +int cmd__oidmap(int argc, const char **argv) +{ + struct strbuf line = STRBUF_INIT; + struct oidmap map = OIDMAP_INIT; + + setup_git_directory(); + + /* init oidmap */ + oidmap_init(&map, 0); + + /* process commands from stdin */ + while (strbuf_getline(&line, stdin) != EOF) { + char *cmd, *p1 = NULL, *p2 = NULL; + struct test_entry *entry; + struct object_id oid; + + /* break line into command and up to two parameters */ + cmd = strtok(line.buf, DELIM); + /* ignore empty lines */ + if (!cmd || *cmd == '#') + continue; + + p1 = strtok(NULL, DELIM); + if (p1) + p2 = strtok(NULL, DELIM); + + if (!strcmp("put", cmd) && p1 && p2) { + + if (get_oid(p1, &oid)) { + printf("Unknown oid: %s\n", p1); + continue; + } + + /* create entry with oid_key = p1, name_value = p2 */ + FLEX_ALLOC_STR(entry, name, p2); + oidcpy(&entry->entry.oid, &oid); + + /* add / replace entry */ + entry = oidmap_put(&map, entry); + + /* print and free replaced entry, if any */ + puts(entry ? entry->name : "NULL"); + free(entry); + + } else if (!strcmp("get", cmd) && p1) { + + if (get_oid(p1, &oid)) { + printf("Unknown oid: %s\n", p1); + continue; + } + + /* lookup entry in oidmap */ + entry = oidmap_get(&map, &oid); + + /* print result */ + puts(entry ? entry->name : "NULL"); + + } else if (!strcmp("remove", cmd) && p1) { + + if (get_oid(p1, &oid)) { + printf("Unknown oid: %s\n", p1); + continue; + } + + /* remove entry from oidmap */ + entry = oidmap_remove(&map, &oid); + + /* print result and free entry*/ + puts(entry ? entry->name : "NULL"); + free(entry); + + } else if (!strcmp("iterate", cmd)) { + + struct oidmap_iter iter; + oidmap_iter_init(&map, &iter); + while ((entry = oidmap_iter_next(&iter))) + printf("%s %s\n", oid_to_hex(&entry->entry.oid), entry->name); + + } else { + + printf("Unknown command %s\n", cmd); + + } + } + + strbuf_release(&line); + oidmap_free(&map, 1); + return 0; +} diff --git a/t/helper/test-online-cpus.c b/t/helper/test-online-cpus.c index 06c09c6b88..8cb0d53840 100644 --- a/t/helper/test-online-cpus.c +++ b/t/helper/test-online-cpus.c @@ -1,7 +1,8 @@ +#include "test-tool.h" #include "git-compat-util.h" #include "thread-utils.h" -int cmd_main(int argc, const char **argv) +int cmd__online_cpus(int argc, const char **argv) { printf("%d\n", online_cpus()); return 0; diff --git a/t/helper/test-parse-options.c b/t/helper/test-parse-options.c index 630c76d127..2051ce57db 100644 --- a/t/helper/test-parse-options.c +++ b/t/helper/test-parse-options.c @@ -1,6 +1,8 @@ +#include "test-tool.h" #include "cache.h" #include "parse-options.h" #include "string-list.h" +#include "trace2.h" static int boolean = 0; static int integer = 0; @@ -35,6 +37,7 @@ static int length_callback(const struct option *opt, const char *arg, int unset) static int number_callback(const struct option *opt, const char *arg, int unset) { + BUG_ON_OPT_NEG(unset); *(int *)opt->value = strtol(arg, NULL, 10); return 0; } @@ -94,11 +97,11 @@ static void show(struct string_list *expect, int *status, const char *fmt, ...) strbuf_release(&buf); } -int cmd_main(int argc, const char **argv) +int cmd__parse_options(int argc, const char **argv) { const char *prefix = "prefix/"; const char *usage[] = { - "test-parse-options <options>", + "test-tool parse-options <options>", "", "A helper function for the parse-options API.", NULL @@ -118,7 +121,8 @@ int cmd_main(int argc, const char **argv) OPT_INTEGER('j', NULL, &integer, "get a integer, too"), OPT_MAGNITUDE('m', "magnitude", &magnitude, "get a magnitude"), OPT_SET_INT(0, "set23", &integer, "set integer to 23", 23), - OPT_DATE('t', NULL, ×tamp, "get timestamp of <time>"), + OPT_CMDMODE(0, "mode1", &integer, "set integer to 1 (cmdmode option)", 1), + OPT_CMDMODE(0, "mode2", &integer, "set integer to 2 (cmdmode option)", 2), OPT_CALLBACK('L', "length", &integer, "str", "get length of <str>", length_callback), OPT_FILENAME('F', "file", &file, "set file to <file>"), @@ -130,7 +134,7 @@ int cmd_main(int argc, const char **argv) OPT_NOOP_NOARG(0, "obsolete"), OPT_STRING_LIST(0, "list", &list, "str", "add str to list"), OPT_GROUP("Magic arguments"), - OPT_ARGUMENT("quux", "means --quux"), + OPT_ARGUMENT("quux", NULL, "means --quux"), OPT_NUMBER_CALLBACK(&integer, "set integer to NUM", number_callback), { OPTION_COUNTUP, '+', NULL, &boolean, NULL, "same as -b", @@ -147,11 +151,16 @@ int cmd_main(int argc, const char **argv) OPT_CALLBACK(0, "expect", &expect, "string", "expected output in the variable dump", collect_expect), + OPT_GROUP("Alias"), + OPT_STRING('A', "alias-source", &string, "string", "get a string"), + OPT_ALIAS('Z', "alias-target", "alias-source"), OPT_END(), }; int i; int ret = 0; + trace2_cmd_name("_parse_"); + argc = parse_options(argc, (const char **)argv, prefix, options, usage, 0); if (length_cb.called) { diff --git a/t/helper/test-parse-pathspec-file.c b/t/helper/test-parse-pathspec-file.c new file mode 100644 index 0000000000..b3e08cef4b --- /dev/null +++ b/t/helper/test-parse-pathspec-file.c @@ -0,0 +1,33 @@ +#include "test-tool.h" +#include "parse-options.h" +#include "pathspec.h" +#include "gettext.h" + +int cmd__parse_pathspec_file(int argc, const char **argv) +{ + struct pathspec pathspec; + const char *pathspec_from_file = NULL; + int pathspec_file_nul = 0, i; + + static const char *const usage[] = { + "test-tool parse-pathspec-file --pathspec-from-file [--pathspec-file-nul]", + NULL + }; + + struct option options[] = { + OPT_PATHSPEC_FROM_FILE(&pathspec_from_file), + OPT_PATHSPEC_FILE_NUL(&pathspec_file_nul), + OPT_END() + }; + + parse_options(argc, argv, NULL, options, usage, 0); + + parse_pathspec_file(&pathspec, 0, 0, NULL, pathspec_from_file, + pathspec_file_nul); + + for (i = 0; i < pathspec.nr; i++) + printf("%s\n", pathspec.items[i].original); + + clear_pathspec(&pathspec); + return 0; +} diff --git a/t/helper/test-partial-clone.c b/t/helper/test-partial-clone.c new file mode 100644 index 0000000000..3f102cfddd --- /dev/null +++ b/t/helper/test-partial-clone.c @@ -0,0 +1,43 @@ +#include "cache.h" +#include "test-tool.h" +#include "repository.h" +#include "object-store.h" + +/* + * Prints the size of the object corresponding to the given hash in a specific + * gitdir. This is similar to "git -C gitdir cat-file -s", except that this + * exercises the code that accesses the object of an arbitrary repository that + * is not the_repository. ("git -C gitdir" makes it so that the_repository is + * the one in gitdir.) + */ +static void object_info(const char *gitdir, const char *oid_hex) +{ + struct repository r; + struct object_id oid; + unsigned long size; + struct object_info oi = {.sizep = &size}; + const char *p; + + if (repo_init(&r, gitdir, NULL)) + die("could not init repo"); + if (parse_oid_hex(oid_hex, &oid, &p)) + die("could not parse oid"); + if (oid_object_info_extended(&r, &oid, &oi, 0)) + die("could not obtain object info"); + printf("%d\n", (int) size); +} + +int cmd__partial_clone(int argc, const char **argv) +{ + setup_git_directory(); + + if (argc < 4) + die("too few arguments"); + + if (!strcmp(argv[1], "object-info")) + object_info(argv[2], argv[3]); + else + die("invalid argument '%s'", argv[1]); + + return 0; +} diff --git a/t/helper/test-path-utils.c b/t/helper/test-path-utils.c index 2b3c5092a1..229ed416b0 100644 --- a/t/helper/test-path-utils.c +++ b/t/helper/test-path-utils.c @@ -1,5 +1,7 @@ +#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 @@ -170,7 +172,126 @@ static struct test_data dirname_data[] = { { NULL, NULL } }; -int cmd_main(int argc, const char **argv) +static int check_dotfile(const char *x, const char **argv, + int (*is_hfs)(const char *), + int (*is_ntfs)(const char *)) +{ + int res = 0, expect = 1; + for (; *argv; argv++) { + if (!strcmp("--not", *argv)) + expect = !expect; + else if (expect != (is_hfs(*argv) || is_ntfs(*argv))) + res = error("'%s' is %s.git%s", *argv, + expect ? "not " : "", x); + else + fprintf(stderr, "ok: '%s' is %s.git%s\n", + *argv, expect ? "" : "not ", x); + } + return !!res; +} + +static int cmp_by_st_size(const void *a, const void *b) +{ + intptr_t x = (intptr_t)((struct string_list_item *)a)->util; + intptr_t y = (intptr_t)((struct string_list_item *)b)->util; + + return x > y ? -1 : (x < y ? +1 : 0); +} + +/* + * A very simple, reproducible pseudo-random generator. Copied from + * `test-genrandom.c`. + */ +static uint64_t my_random_value = 1234; + +static uint64_t my_random(void) +{ + my_random_value = my_random_value * 1103515245 + 12345; + return my_random_value; +} + +/* + * A fast approximation of the square root, without requiring math.h. + * + * It uses Newton's method to approximate the solution of 0 = x^2 - value. + */ +static double my_sqrt(double value) +{ + const double epsilon = 1e-6; + double x = value; + + if (value == 0) + return 0; + + for (;;) { + double delta = (value / x - x) / 2; + if (delta < epsilon && delta > -epsilon) + return x + delta; + x += delta; + } +} + +static int protect_ntfs_hfs_benchmark(int argc, const char **argv) +{ + size_t i, j, nr, min_len = 3, max_len = 20; + char **names; + int repetitions = 15, file_mode = 0100644; + uint64_t begin, end; + double m[3][2], v[3][2]; + uint64_t cumul; + double cumul2; + + if (argc > 1 && !strcmp(argv[1], "--with-symlink-mode")) { + file_mode = 0120000; + argc--; + argv++; + } + + nr = argc > 1 ? strtoul(argv[1], NULL, 0) : 1000000; + ALLOC_ARRAY(names, nr); + + if (argc > 2) { + min_len = strtoul(argv[2], NULL, 0); + if (argc > 3) + max_len = strtoul(argv[3], NULL, 0); + if (min_len > max_len) + die("min_len > max_len"); + } + + for (i = 0; i < nr; i++) { + size_t len = min_len + (my_random() % (max_len + 1 - min_len)); + + names[i] = xmallocz(len); + while (len > 0) + names[i][--len] = (char)(' ' + (my_random() % ('\x7f' - ' '))); + } + + for (protect_ntfs = 0; protect_ntfs < 2; protect_ntfs++) + for (protect_hfs = 0; protect_hfs < 2; protect_hfs++) { + cumul = 0; + cumul2 = 0; + for (i = 0; i < repetitions; i++) { + begin = getnanotime(); + for (j = 0; j < nr; j++) + verify_path(names[j], file_mode); + end = getnanotime(); + printf("protect_ntfs = %d, protect_hfs = %d: %lfms\n", protect_ntfs, protect_hfs, (end-begin) / (double)1e6); + cumul += end - begin; + cumul2 += (end - begin) * (end - begin); + } + m[protect_ntfs][protect_hfs] = cumul / (double)repetitions; + v[protect_ntfs][protect_hfs] = my_sqrt(cumul2 / (double)repetitions - m[protect_ntfs][protect_hfs] * m[protect_ntfs][protect_hfs]); + printf("mean: %lfms, stddev: %lfms\n", m[protect_ntfs][protect_hfs] / (double)1e6, v[protect_ntfs][protect_hfs] / (double)1e6); + } + + for (protect_ntfs = 0; protect_ntfs < 2; protect_ntfs++) + for (protect_hfs = 0; protect_hfs < 2; protect_hfs++) + printf("ntfs=%d/hfs=%d: %lf%% slower\n", protect_ntfs, protect_hfs, (m[protect_ntfs][protect_hfs] - m[0][0]) * 100 / m[0][0]); + + return 0; +} + +int cmd__path_utils(int argc, const char **argv) { if (argc == 3 && !strcmp(argv[1], "normalize_path_copy")) { char *buf = xmallocz(strlen(argv[2])); @@ -182,11 +303,14 @@ int cmd_main(int argc, const char **argv) } if (argc >= 2 && !strcmp(argv[1], "real_path")) { + struct strbuf realpath = STRBUF_INIT; while (argc > 2) { - puts(real_path(argv[2])); + strbuf_realpath(&realpath, argv[2], 1); + puts(realpath.buf); argc--; argv++; } + strbuf_release(&realpath); return 0; } @@ -270,6 +394,103 @@ int cmd_main(int argc, const char **argv) if (argc == 2 && !strcmp(argv[1], "dirname")) return test_function(dirname_data, posix_dirname, argv[1]); + if (argc > 2 && !strcmp(argv[1], "is_dotgitmodules")) { + return check_dotfile("modules", argv + 2, + is_hfs_dotgitmodules, + is_ntfs_dotgitmodules); + } + if (argc > 2 && !strcmp(argv[1], "is_dotgitignore")) { + return check_dotfile("ignore", argv + 2, + is_hfs_dotgitignore, + is_ntfs_dotgitignore); + } + if (argc > 2 && !strcmp(argv[1], "is_dotgitattributes")) { + return check_dotfile("attributes", argv + 2, + is_hfs_dotgitattributes, + is_ntfs_dotgitattributes); + } + if (argc > 2 && !strcmp(argv[1], "is_dotmailmap")) { + return check_dotfile("mailmap", argv + 2, + is_hfs_dotmailmap, + is_ntfs_dotmailmap); + } + + 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; + } + + if (argc > 5 && !strcmp(argv[1], "slice-tests")) { + int res = 0; + long offset, stride, i; + struct string_list list = STRING_LIST_INIT_NODUP; + struct stat st; + + offset = strtol(argv[2], NULL, 10); + stride = strtol(argv[3], NULL, 10); + if (stride < 1) + stride = 1; + for (i = 4; i < argc; i++) + if (stat(argv[i], &st)) + res = error_errno("Cannot stat '%s'", argv[i]); + else + string_list_append(&list, argv[i])->util = + (void *)(intptr_t)st.st_size; + QSORT(list.items, list.nr, cmp_by_st_size); + for (i = offset; i < list.nr; i+= stride) + printf("%s\n", list.items[i].string); + + return !!res; + } + + if (argc > 1 && !strcmp(argv[1], "protect_ntfs_hfs")) + return !!protect_ntfs_hfs_benchmark(argc - 1, argv + 1); + + if (argc > 1 && !strcmp(argv[1], "is_valid_path")) { + int res = 0, expect = 1, i; + + for (i = 2; i < argc; i++) + if (!strcmp("--not", argv[i])) + expect = 0; + else if (expect != is_valid_path(argv[i])) + res = error("'%s' is%s a valid path", + argv[i], expect ? " not" : ""); + else + fprintf(stderr, + "'%s' is%s a valid path\n", + argv[i], expect ? "" : " not"); + + return !!res; + } + fprintf(stderr, "%s: unknown function name: %s\n", argv[0], argv[1] ? argv[1] : "(there was none)"); return 1; diff --git a/t/helper/test-pcre2-config.c b/t/helper/test-pcre2-config.c new file mode 100644 index 0000000000..5258fdddba --- /dev/null +++ b/t/helper/test-pcre2-config.c @@ -0,0 +1,12 @@ +#include "test-tool.h" +#include "cache.h" +#include "grep.h" + +int cmd__pcre2_config(int argc, const char **argv) +{ + if (argc == 2 && !strcmp(argv[1], "has-PCRE2_MATCH_INVALID_UTF")) { + int value = PCRE2_MATCH_INVALID_UTF; + return !value; + } + return 1; +} diff --git a/t/helper/test-pkt-line.c b/t/helper/test-pkt-line.c new file mode 100644 index 0000000000..5e638f0b97 --- /dev/null +++ b/t/helper/test-pkt-line.c @@ -0,0 +1,125 @@ +#include "cache.h" +#include "test-tool.h" +#include "pkt-line.h" + +static void pack_line(const char *line) +{ + if (!strcmp(line, "0000") || !strcmp(line, "0000\n")) + packet_flush(1); + else if (!strcmp(line, "0001") || !strcmp(line, "0001\n")) + packet_delim(1); + else + packet_write_fmt(1, "%s", line); +} + +static void pack(int argc, const char **argv) +{ + if (argc) { /* read from argv */ + int i; + for (i = 0; i < argc; i++) + pack_line(argv[i]); + } else { /* read from stdin */ + char line[LARGE_PACKET_MAX]; + while (fgets(line, sizeof(line), stdin)) { + pack_line(line); + } + } +} + +static void unpack(void) +{ + struct packet_reader reader; + packet_reader_init(&reader, 0, NULL, 0, + PACKET_READ_GENTLE_ON_EOF | + PACKET_READ_CHOMP_NEWLINE); + + while (packet_reader_read(&reader) != PACKET_READ_EOF) { + switch (reader.status) { + case PACKET_READ_EOF: + break; + case PACKET_READ_NORMAL: + printf("%s\n", reader.line); + break; + case PACKET_READ_FLUSH: + printf("0000\n"); + break; + case PACKET_READ_DELIM: + printf("0001\n"); + break; + case PACKET_READ_RESPONSE_END: + printf("0002\n"); + break; + } + } +} + +static void unpack_sideband(void) +{ + struct packet_reader reader; + packet_reader_init(&reader, 0, NULL, 0, + PACKET_READ_GENTLE_ON_EOF | + PACKET_READ_CHOMP_NEWLINE); + + while (packet_reader_read(&reader) != PACKET_READ_EOF) { + int band; + int fd; + + switch (reader.status) { + case PACKET_READ_EOF: + break; + case PACKET_READ_NORMAL: + band = reader.line[0] & 0xff; + if (band < 1 || band > 2) + continue; /* skip non-sideband packets */ + fd = band; + + write_or_die(fd, reader.line + 1, reader.pktlen - 1); + break; + case PACKET_READ_FLUSH: + return; + case PACKET_READ_DELIM: + case PACKET_READ_RESPONSE_END: + break; + } + } +} + +static int send_split_sideband(void) +{ + const char *part1 = "Hello,"; + const char *primary = "\001primary: regular output\n"; + const char *part2 = " world!\n"; + + send_sideband(1, 2, part1, strlen(part1), LARGE_PACKET_MAX); + packet_write(1, primary, strlen(primary)); + send_sideband(1, 2, part2, strlen(part2), LARGE_PACKET_MAX); + packet_response_end(1); + + return 0; +} + +static int receive_sideband(void) +{ + return recv_sideband("sideband", 0, 1); +} + +int cmd__pkt_line(int argc, const char **argv) +{ + if (argc < 2) + die("too few arguments"); + + if (!strcmp(argv[1], "pack")) + pack(argc - 2, argv + 2); + else if (!strcmp(argv[1], "unpack")) + unpack(); + else if (!strcmp(argv[1], "unpack-sideband")) + unpack_sideband(); + else if (!strcmp(argv[1], "send-split-sideband")) + send_split_sideband(); + else if (!strcmp(argv[1], "receive-sideband")) + receive_sideband(); + else + die("invalid argument '%s'", argv[1]); + + return 0; +} diff --git a/t/helper/test-prio-queue.c b/t/helper/test-prio-queue.c index ae58fff359..f4028442e3 100644 --- a/t/helper/test-prio-queue.c +++ b/t/helper/test-prio-queue.c @@ -1,3 +1,4 @@ +#include "test-tool.h" #include "cache.h" #include "prio-queue.h" @@ -16,20 +17,30 @@ static void show(int *v) free(v); } -int cmd_main(int argc, const char **argv) +int cmd__prio_queue(int argc, const char **argv) { struct prio_queue pq = { intcmp }; while (*++argv) { - if (!strcmp(*argv, "get")) - show(prio_queue_get(&pq)); - else if (!strcmp(*argv, "dump")) { - int *v; - while ((v = prio_queue_get(&pq))) - show(v); - } - else { - int *v = malloc(sizeof(*v)); + if (!strcmp(*argv, "get")) { + void *peek = prio_queue_peek(&pq); + void *get = prio_queue_get(&pq); + if (peek != get) + BUG("peek and get results do not match"); + show(get); + } else if (!strcmp(*argv, "dump")) { + void *peek; + void *get; + while ((peek = prio_queue_peek(&pq))) { + get = prio_queue_get(&pq); + if (peek != get) + BUG("peek and get results do not match"); + show(get); + } + } else if (!strcmp(*argv, "stack")) { + pq.compare = NULL; + } else { + int *v = xmalloc(sizeof(*v)); *v = atoi(*argv); prio_queue_put(&pq, v); } diff --git a/t/helper/test-proc-receive.c b/t/helper/test-proc-receive.c new file mode 100644 index 0000000000..cc08506cf0 --- /dev/null +++ b/t/helper/test-proc-receive.c @@ -0,0 +1,202 @@ +#include "cache.h" +#include "connect.h" +#include "parse-options.h" +#include "pkt-line.h" +#include "sigchain.h" +#include "test-tool.h" + +static const char *proc_receive_usage[] = { + "test-tool proc-receive [<options>...]", + NULL +}; + +static int die_read_version; +static int die_write_version; +static int die_read_commands; +static int die_read_push_options; +static int die_write_report; +static int no_push_options; +static int use_atomic; +static int use_push_options; +static int verbose; +static int version = 1; +static struct string_list returns = STRING_LIST_INIT_NODUP; + +struct command { + struct command *next; + const char *error_string; + unsigned int skip_update:1, + did_not_exist:1; + int index; + struct object_id old_oid; + struct object_id new_oid; + char ref_name[FLEX_ARRAY]; /* more */ +}; + +static void proc_receive_verison(struct packet_reader *reader) { + int server_version = 0; + + if (die_read_version) + die("die with the --die-read-version option"); + + for (;;) { + int linelen; + + if (packet_reader_read(reader) != PACKET_READ_NORMAL) + break; + + /* Ignore version negotiation for version 0 */ + if (version == 0) + continue; + + if (reader->pktlen > 8 && starts_with(reader->line, "version=")) { + server_version = atoi(reader->line+8); + if (server_version != 1) + die("bad protocol version: %d", server_version); + linelen = strlen(reader->line); + if (linelen < reader->pktlen) { + const char *feature_list = reader->line + linelen + 1; + if (parse_feature_request(feature_list, "atomic")) + use_atomic= 1; + if (parse_feature_request(feature_list, "push-options")) + use_push_options = 1; + } + } + } + + if (die_write_version) + die("die with the --die-write-version option"); + + if (version != 0) + packet_write_fmt(1, "version=%d%c%s\n", + version, '\0', + use_push_options && !no_push_options ? "push-options": ""); + packet_flush(1); +} + +static void proc_receive_read_commands(struct packet_reader *reader, + struct command **commands) +{ + struct command **tail = commands; + + for (;;) { + struct object_id old_oid, new_oid; + struct command *cmd; + const char *refname; + const char *p; + + if (packet_reader_read(reader) != PACKET_READ_NORMAL) + break; + + if (die_read_commands) + die("die with the --die-read-commands option"); + + if (parse_oid_hex(reader->line, &old_oid, &p) || + *p++ != ' ' || + parse_oid_hex(p, &new_oid, &p) || + *p++ != ' ') + die("protocol error: expected 'old new ref', got '%s'", + reader->line); + refname = p; + FLEX_ALLOC_STR(cmd, ref_name, refname); + oidcpy(&cmd->old_oid, &old_oid); + oidcpy(&cmd->new_oid, &new_oid); + + *tail = cmd; + tail = &cmd->next; + } +} + +static void proc_receive_read_push_options(struct packet_reader *reader, + struct string_list *options) +{ + + if (no_push_options || !use_push_options) + return; + + if (die_read_push_options) + die("die with the --die-read-push-options option"); + + while (1) { + if (packet_reader_read(reader) != PACKET_READ_NORMAL) + break; + + string_list_append(options, reader->line); + } +} + +int cmd__proc_receive(int argc, const char **argv) +{ + int nongit_ok = 0; + struct packet_reader reader; + struct command *commands = NULL; + struct string_list push_options = STRING_LIST_INIT_DUP; + struct string_list_item *item; + struct option options[] = { + OPT_BOOL(0, "no-push-options", &no_push_options, + "disable push options"), + OPT_BOOL(0, "die-read-version", &die_read_version, + "die when reading version"), + OPT_BOOL(0, "die-write-version", &die_write_version, + "die when writing version"), + OPT_BOOL(0, "die-read-commands", &die_read_commands, + "die when reading commands"), + OPT_BOOL(0, "die-read-push-options", &die_read_push_options, + "die when reading push-options"), + OPT_BOOL(0, "die-write-report", &die_write_report, + "die when writing report"), + OPT_STRING_LIST('r', "return", &returns, "old/new/ref/status/msg", + "return of results"), + OPT__VERBOSE(&verbose, "be verbose"), + OPT_INTEGER('V', "version", &version, + "use this protocol version number"), + OPT_END() + }; + + setup_git_directory_gently(&nongit_ok); + + argc = parse_options(argc, argv, "test-tools", options, proc_receive_usage, 0); + if (argc > 0) + usage_msg_opt("Too many arguments.", proc_receive_usage, options); + packet_reader_init(&reader, 0, NULL, 0, + PACKET_READ_CHOMP_NEWLINE | + PACKET_READ_GENTLE_ON_EOF); + + sigchain_push(SIGPIPE, SIG_IGN); + proc_receive_verison(&reader); + proc_receive_read_commands(&reader, &commands); + proc_receive_read_push_options(&reader, &push_options); + + if (verbose) { + struct command *cmd; + + if (use_push_options || use_atomic) + fprintf(stderr, "proc-receive:%s%s\n", + use_atomic? " atomic": "", + use_push_options ? " push_options": ""); + + for (cmd = commands; cmd; cmd = cmd->next) + fprintf(stderr, "proc-receive< %s %s %s\n", + oid_to_hex(&cmd->old_oid), + oid_to_hex(&cmd->new_oid), + cmd->ref_name); + + if (push_options.nr > 0) + for_each_string_list_item(item, &push_options) + fprintf(stderr, "proc-receive< %s\n", item->string); + + if (returns.nr) + for_each_string_list_item(item, &returns) + fprintf(stderr, "proc-receive> %s\n", item->string); + } + + if (die_write_report) + die("die with the --die-write-report option"); + if (returns.nr) + for_each_string_list_item(item, &returns) + packet_write_fmt(1, "%s\n", item->string); + packet_flush(1); + sigchain_pop(SIGPIPE); + + return 0; +} diff --git a/t/helper/test-progress.c b/t/helper/test-progress.c new file mode 100644 index 0000000000..5d05cbe789 --- /dev/null +++ b/t/helper/test-progress.c @@ -0,0 +1,74 @@ +/* + * A test helper to exercise the progress display. + * + * Reads instructions from standard input, one instruction per line: + * + * "progress <items>" - Call display_progress() with the given item count + * as parameter. + * "throughput <bytes> <millis> - Call display_throughput() with the given + * byte count as parameter. The 'millis' + * specify the time elapsed since the + * start_progress() call. + * "update" - Set the 'progress_update' flag. + * + * See 't0500-progress-display.sh' for examples. + */ +#define GIT_TEST_PROGRESS_ONLY +#include "test-tool.h" +#include "gettext.h" +#include "parse-options.h" +#include "progress.h" +#include "strbuf.h" + +int cmd__progress(int argc, const char **argv) +{ + int total = 0; + const char *title; + struct strbuf line = STRBUF_INIT; + struct progress *progress; + + const char *usage[] = { + "test-tool progress [--total=<n>] <progress-title>", + NULL + }; + struct option options[] = { + OPT_INTEGER(0, "total", &total, "total number of items"), + OPT_END(), + }; + + argc = parse_options(argc, argv, NULL, options, usage, 0); + if (argc != 1) + die("need a title for the progress output"); + title = argv[0]; + + progress_testing = 1; + progress = start_progress(title, total); + while (strbuf_getline(&line, stdin) != EOF) { + char *end; + + if (skip_prefix(line.buf, "progress ", (const char **) &end)) { + uint64_t item_count = strtoull(end, &end, 10); + if (*end != '\0') + die("invalid input: '%s'\n", line.buf); + display_progress(progress, item_count); + } else if (skip_prefix(line.buf, "throughput ", + (const char **) &end)) { + uint64_t byte_count, test_ms; + + byte_count = strtoull(end, &end, 10); + if (*end != ' ') + die("invalid input: '%s'\n", line.buf); + test_ms = strtoull(end + 1, &end, 10); + if (*end != '\0') + die("invalid input: '%s'\n", line.buf); + progress_test_ns = test_ms * 1000 * 1000; + display_throughput(progress, byte_count); + } else if (!strcmp(line.buf, "update")) + progress_test_force_update(); + else + die("invalid input: '%s'\n", line.buf); + } + stop_progress(&progress); + + return 0; +} diff --git a/t/helper/test-reach.c b/t/helper/test-reach.c new file mode 100644 index 0000000000..2f65c7f6a5 --- /dev/null +++ b/t/helper/test-reach.c @@ -0,0 +1,170 @@ +#include "test-tool.h" +#include "cache.h" +#include "commit.h" +#include "commit-reach.h" +#include "config.h" +#include "parse-options.h" +#include "ref-filter.h" +#include "string-list.h" +#include "tag.h" + +static void print_sorted_commit_ids(struct commit_list *list) +{ + int i; + struct string_list s = STRING_LIST_INIT_DUP; + + while (list) { + string_list_append(&s, oid_to_hex(&list->item->object.oid)); + list = list->next; + } + + string_list_sort(&s); + + for (i = 0; i < s.nr; i++) + printf("%s\n", s.items[i].string); + + string_list_clear(&s, 0); +} + +int cmd__reach(int ac, const char **av) +{ + struct object_id oid_A, oid_B; + struct commit *A, *B; + struct commit_list *X, *Y; + struct object_array X_obj = OBJECT_ARRAY_INIT; + struct commit **X_array, **Y_array; + int X_nr, X_alloc, Y_nr, Y_alloc; + struct strbuf buf = STRBUF_INIT; + struct repository *r = the_repository; + + setup_git_directory(); + + if (ac < 2) + exit(1); + + A = B = NULL; + X = Y = NULL; + X_nr = Y_nr = 0; + X_alloc = Y_alloc = 16; + ALLOC_ARRAY(X_array, X_alloc); + ALLOC_ARRAY(Y_array, Y_alloc); + + while (strbuf_getline(&buf, stdin) != EOF) { + struct object_id oid; + struct object *orig; + struct object *peeled; + struct commit *c; + if (buf.len < 3) + continue; + + if (get_oid_committish(buf.buf + 2, &oid)) + die("failed to resolve %s", buf.buf + 2); + + orig = parse_object(r, &oid); + peeled = deref_tag_noverify(orig); + + if (!peeled) + die("failed to load commit for input %s resulting in oid %s\n", + buf.buf, oid_to_hex(&oid)); + + c = object_as_type(peeled, OBJ_COMMIT, 0); + + if (!c) + die("failed to load commit for input %s resulting in oid %s\n", + buf.buf, oid_to_hex(&oid)); + + switch (buf.buf[0]) { + case 'A': + oidcpy(&oid_A, &oid); + A = c; + break; + + case 'B': + oidcpy(&oid_B, &oid); + B = c; + break; + + case 'X': + commit_list_insert(c, &X); + ALLOC_GROW(X_array, X_nr + 1, X_alloc); + X_array[X_nr++] = c; + add_object_array(orig, NULL, &X_obj); + break; + + case 'Y': + commit_list_insert(c, &Y); + ALLOC_GROW(Y_array, Y_nr + 1, Y_alloc); + Y_array[Y_nr++] = c; + break; + + default: + die("unexpected start of line: %c", buf.buf[0]); + } + } + strbuf_release(&buf); + + if (!strcmp(av[1], "ref_newer")) + printf("%s(A,B):%d\n", av[1], ref_newer(&oid_A, &oid_B)); + else if (!strcmp(av[1], "in_merge_bases")) + printf("%s(A,B):%d\n", av[1], in_merge_bases(A, B)); + else if (!strcmp(av[1], "in_merge_bases_many")) + printf("%s(A,X):%d\n", av[1], in_merge_bases_many(A, X_nr, X_array)); + else if (!strcmp(av[1], "is_descendant_of")) + printf("%s(A,X):%d\n", av[1], repo_is_descendant_of(r, A, X)); + else if (!strcmp(av[1], "get_merge_bases_many")) { + struct commit_list *list = get_merge_bases_many(A, X_nr, X_array); + printf("%s(A,X):\n", av[1]); + print_sorted_commit_ids(list); + } else if (!strcmp(av[1], "reduce_heads")) { + struct commit_list *list = reduce_heads(X); + printf("%s(X):\n", av[1]); + print_sorted_commit_ids(list); + } else if (!strcmp(av[1], "can_all_from_reach")) { + printf("%s(X,Y):%d\n", av[1], can_all_from_reach(X, Y, 1)); + } else if (!strcmp(av[1], "can_all_from_reach_with_flag")) { + struct commit_list *iter = Y; + + while (iter) { + iter->item->object.flags |= 2; + iter = iter->next; + } + + printf("%s(X,_,_,0,0):%d\n", av[1], can_all_from_reach_with_flag(&X_obj, 2, 4, 0, 0)); + } else if (!strcmp(av[1], "commit_contains")) { + struct ref_filter filter; + struct contains_cache cache; + init_contains_cache(&cache); + + if (ac > 2 && !strcmp(av[2], "--tag")) + filter.with_commit_tag_algo = 1; + else + filter.with_commit_tag_algo = 0; + + printf("%s(_,A,X,_):%d\n", av[1], commit_contains(&filter, A, X, &cache)); + } else if (!strcmp(av[1], "get_reachable_subset")) { + const int reachable_flag = 1; + int i, count = 0; + struct commit_list *current; + struct commit_list *list = get_reachable_subset(X_array, X_nr, + Y_array, Y_nr, + reachable_flag); + printf("get_reachable_subset(X,Y)\n"); + for (current = list; current; current = current->next) { + if (!(list->item->object.flags & reachable_flag)) + die(_("commit %s is not marked reachable"), + oid_to_hex(&list->item->object.oid)); + count++; + } + for (i = 0; i < Y_nr; i++) { + if (Y_array[i]->object.flags & reachable_flag) + count--; + } + + if (count < 0) + die(_("too many commits marked reachable")); + + print_sorted_commit_ids(list); + } + + return 0; +} diff --git a/t/helper/test-read-cache.c b/t/helper/test-read-cache.c index 48255eef31..b52c174acc 100644 --- a/t/helper/test-read-cache.c +++ b/t/helper/test-read-cache.c @@ -1,14 +1,82 @@ +#include "test-tool.h" #include "cache.h" +#include "config.h" +#include "blob.h" +#include "commit.h" +#include "tree.h" +#include "sparse-index.h" -int cmd_main(int argc, const char **argv) +static void print_cache_entry(struct cache_entry *ce) { + const char *type; + printf("%06o ", ce->ce_mode & 0177777); + + if (S_ISSPARSEDIR(ce->ce_mode)) + type = tree_type; + else if (S_ISGITLINK(ce->ce_mode)) + type = commit_type; + else + type = blob_type; + + printf("%s %s\t%s\n", + type, + oid_to_hex(&ce->oid), + ce->name); +} + +static void print_cache(struct index_state *istate) +{ + int i; + for (i = 0; i < istate->cache_nr; i++) + print_cache_entry(istate->cache[i]); +} + +int cmd__read_cache(int argc, const char **argv) +{ + struct repository *r = the_repository; int i, cnt = 1; - if (argc == 2) - cnt = strtol(argv[1], NULL, 0); + const char *name = NULL; + int table = 0, expand = 0; + + initialize_the_repository(); + prepare_repo_settings(r); + r->settings.command_requires_full_index = 0; + + for (++argv, --argc; *argv && starts_with(*argv, "--"); ++argv, --argc) { + if (skip_prefix(*argv, "--print-and-refresh=", &name)) + continue; + if (!strcmp(*argv, "--table")) + table = 1; + else if (!strcmp(*argv, "--expand")) + expand = 1; + } + + if (argc == 1) + cnt = strtol(argv[0], NULL, 0); setup_git_directory(); + git_config(git_default_config, NULL); + for (i = 0; i < cnt; i++) { - read_cache(); - discard_cache(); + repo_read_index(r); + + if (expand) + ensure_full_index(r->index); + + if (name) { + int pos; + + refresh_index(r->index, REFRESH_QUIET, + NULL, NULL, NULL); + pos = index_name_pos(r->index, name, strlen(name)); + if (pos < 0) + die("%s not in index", name); + printf("%s is%s up to date\n", name, + ce_uptodate(r->index->cache[pos]) ? "" : " not"); + write_file(name, "%d\n", i); + } + if (table) + print_cache(r->index); + discard_index(r->index); } return 0; } diff --git a/t/helper/test-read-graph.c b/t/helper/test-read-graph.c new file mode 100644 index 0000000000..75927b2c81 --- /dev/null +++ b/t/helper/test-read-graph.c @@ -0,0 +1,51 @@ +#include "test-tool.h" +#include "cache.h" +#include "commit-graph.h" +#include "repository.h" +#include "object-store.h" + +int cmd__read_graph(int argc, const char **argv) +{ + struct commit_graph *graph = NULL; + struct object_directory *odb; + + setup_git_directory(); + odb = the_repository->objects->odb; + + prepare_repo_settings(the_repository); + + graph = read_commit_graph_one(the_repository, odb); + if (!graph) + return 1; + + printf("header: %08x %d %d %d %d\n", + ntohl(*(uint32_t*)graph->data), + *(unsigned char*)(graph->data + 4), + *(unsigned char*)(graph->data + 5), + *(unsigned char*)(graph->data + 6), + *(unsigned char*)(graph->data + 7)); + printf("num_commits: %u\n", graph->num_commits); + printf("chunks:"); + + if (graph->chunk_oid_fanout) + printf(" oid_fanout"); + if (graph->chunk_oid_lookup) + printf(" oid_lookup"); + if (graph->chunk_commit_data) + printf(" commit_metadata"); + if (graph->chunk_generation_data) + printf(" generation_data"); + if (graph->chunk_generation_data_overflow) + printf(" generation_data_overflow"); + if (graph->chunk_extra_edges) + printf(" extra_edges"); + if (graph->chunk_bloom_indexes) + printf(" bloom_indexes"); + if (graph->chunk_bloom_data) + printf(" bloom_data"); + printf("\n"); + + UNLEAK(graph); + + return 0; +} diff --git a/t/helper/test-read-midx.c b/t/helper/test-read-midx.c new file mode 100644 index 0000000000..7c2eb11a8e --- /dev/null +++ b/t/helper/test-read-midx.c @@ -0,0 +1,71 @@ +#include "test-tool.h" +#include "cache.h" +#include "midx.h" +#include "repository.h" +#include "object-store.h" + +static int read_midx_file(const char *object_dir, int show_objects) +{ + uint32_t i; + struct multi_pack_index *m; + + setup_git_directory(); + m = load_multi_pack_index(object_dir, 1); + + if (!m) + return 1; + + printf("header: %08x %d %d %d %d\n", + m->signature, + m->version, + m->hash_len, + m->num_chunks, + m->num_packs); + + printf("chunks:"); + + if (m->chunk_pack_names) + printf(" pack-names"); + if (m->chunk_oid_fanout) + printf(" oid-fanout"); + if (m->chunk_oid_lookup) + printf(" oid-lookup"); + if (m->chunk_object_offsets) + printf(" object-offsets"); + if (m->chunk_large_offsets) + printf(" large-offsets"); + + printf("\nnum_objects: %d\n", m->num_objects); + + printf("packs:\n"); + for (i = 0; i < m->num_packs; i++) + printf("%s\n", m->pack_names[i]); + + printf("object-dir: %s\n", m->object_dir); + + if (show_objects) { + struct object_id oid; + struct pack_entry e; + + for (i = 0; i < m->num_objects; i++) { + nth_midxed_object_oid(&oid, m, i); + fill_midx_entry(the_repository, &oid, &e, m); + + printf("%s %"PRIu64"\t%s\n", + oid_to_hex(&oid), e.offset, e.p->pack_name); + } + return 0; + } + + return 0; +} + +int cmd__read_midx(int argc, const char **argv) +{ + if (!(argc == 2 || argc == 3)) + usage("read-midx [--show-objects] <object-dir>"); + + if (!strcmp(argv[1], "--show-objects")) + return read_midx_file(argv[2], 1); + return read_midx_file(argv[1], 0); +} diff --git a/t/helper/test-ref-store.c b/t/helper/test-ref-store.c index 05d8c4d8af..b314b81a45 100644 --- a/t/helper/test-ref-store.c +++ b/t/helper/test-ref-store.c @@ -1,6 +1,9 @@ +#include "test-tool.h" #include "cache.h" #include "refs.h" #include "worktree.h" +#include "object-store.h" +#include "repository.h" static const char *notnull(const char *arg, const char *name) { @@ -21,7 +24,7 @@ static const char **get_store(const char **argv, struct ref_store **refs) if (!argv[0]) { die("ref store required"); } else if (!strcmp(argv[0], "main")) { - *refs = get_main_ref_store(); + *refs = get_main_ref_store(the_repository); } else if (skip_prefix(argv[0], "submodule:", &gitdir)) { struct strbuf sb = STRBUF_INIT; int ret; @@ -34,7 +37,7 @@ static const char **get_store(const char **argv, struct ref_store **refs) *refs = get_submodule_ref_store(gitdir); } else if (skip_prefix(argv[0], "worktree:", &gitdir)) { - struct worktree **p, **worktrees = get_worktrees(0); + struct worktree **p, **worktrees = get_worktrees(); for (p = worktrees; *p; p++) { struct worktree *wt = *p; @@ -69,18 +72,6 @@ static int cmd_pack_refs(struct ref_store *refs, const char **argv) return refs_pack_refs(refs, flags); } -static int cmd_peel_ref(struct ref_store *refs, const char **argv) -{ - const char *refname = notnull(*argv++, "refname"); - unsigned char sha1[20]; - int ret; - - ret = refs_peel_ref(refs, refname, sha1); - if (!ret) - puts(sha1_to_hex(sha1)); - return ret; -} - static int cmd_create_symref(struct ref_store *refs, const char **argv) { const char *refname = notnull(*argv++, "refname"); @@ -127,15 +118,15 @@ static int cmd_for_each_ref(struct ref_store *refs, const char **argv) static int cmd_resolve_ref(struct ref_store *refs, const char **argv) { - unsigned char sha1[20]; + struct object_id oid = *null_oid(); const char *refname = notnull(*argv++, "refname"); int resolve_flags = arg_flags(*argv++, "resolve-flags"); int flags; const char *ref; ref = refs_resolve_ref_unsafe(refs, refname, resolve_flags, - sha1, &flags); - printf("%s %s 0x%x\n", sha1_to_hex(sha1), ref, flags); + &oid, &flags); + printf("%s %s 0x%x\n", oid_to_hex(&oid), ref ? ref : "(null)", flags); return ref ? 0 : 1; } @@ -218,30 +209,30 @@ static int cmd_delete_ref(struct ref_store *refs, const char **argv) const char *refname = notnull(*argv++, "refname"); const char *sha1_buf = notnull(*argv++, "old-sha1"); unsigned int flags = arg_flags(*argv++, "flags"); - unsigned char old_sha1[20]; + struct object_id old_oid; - if (get_sha1_hex(sha1_buf, old_sha1)) + if (get_oid_hex(sha1_buf, &old_oid)) die("not sha-1"); - return refs_delete_ref(refs, msg, refname, old_sha1, flags); + return refs_delete_ref(refs, msg, refname, &old_oid, flags); } static int cmd_update_ref(struct ref_store *refs, const char **argv) { const char *msg = notnull(*argv++, "msg"); const char *refname = notnull(*argv++, "refname"); - const char *new_sha1_buf = notnull(*argv++, "old-sha1"); + const char *new_sha1_buf = notnull(*argv++, "new-sha1"); const char *old_sha1_buf = notnull(*argv++, "old-sha1"); unsigned int flags = arg_flags(*argv++, "flags"); - unsigned char old_sha1[20]; - unsigned char new_sha1[20]; + struct object_id old_oid; + struct object_id new_oid; - if (get_sha1_hex(old_sha1_buf, old_sha1) || - get_sha1_hex(new_sha1_buf, new_sha1)) + if (get_oid_hex(old_sha1_buf, &old_oid) || + get_oid_hex(new_sha1_buf, &new_oid)) die("not sha-1"); return refs_update_ref(refs, msg, refname, - new_sha1, old_sha1, + &new_oid, &old_oid, flags, UPDATE_REFS_DIE_ON_ERR); } @@ -252,7 +243,6 @@ struct command { static struct command commands[] = { { "pack-refs", cmd_pack_refs }, - { "peel-ref", cmd_peel_ref }, { "create-symref", cmd_create_symref }, { "delete-refs", cmd_delete_refs }, { "rename-ref", cmd_rename_ref }, @@ -274,7 +264,7 @@ static struct command commands[] = { { NULL, NULL } }; -int cmd_main(int argc, const char **argv) +int cmd__ref_store(int argc, const char **argv) { struct ref_store *refs; const char *func; diff --git a/t/helper/test-regex.c b/t/helper/test-regex.c index b5ea8a97c5..d6f28ca8d1 100644 --- a/t/helper/test-regex.c +++ b/t/helper/test-regex.c @@ -1,4 +1,4 @@ -#include "git-compat-util.h" +#include "test-tool.h" #include "gettext.h" struct reg_flag { @@ -7,12 +7,13 @@ struct reg_flag { }; static struct reg_flag reg_flags[] = { - { "EXTENDED", REG_EXTENDED }, - { "NEWLINE", REG_NEWLINE }, - { "ICASE", REG_ICASE }, - { "NOTBOL", REG_NOTBOL }, + { "EXTENDED", REG_EXTENDED }, + { "NEWLINE", REG_NEWLINE }, + { "ICASE", REG_ICASE }, + { "NOTBOL", REG_NOTBOL }, + { "NOTEOL", REG_NOTEOL }, #ifdef REG_STARTEND - { "STARTEND", REG_STARTEND }, + { "STARTEND", REG_STARTEND }, #endif { NULL, 0 } }; @@ -36,40 +37,78 @@ static int test_regex_bug(void) return 0; } -int cmd_main(int argc, const char **argv) +int cmd__regex(int argc, const char **argv) { const char *pat; const char *str; - int flags = 0; + int ret, silent = 0, flags = 0; regex_t r; regmatch_t m[1]; - - if (argc == 2 && !strcmp(argv[1], "--bug")) - return test_regex_bug(); - else if (argc < 3) - usage("test-regex --bug\n" - "test-regex <pattern> <string> [<options>]"); + char errbuf[64]; argv++; - pat = *argv++; - str = *argv++; - while (*argv) { - struct reg_flag *rf; - for (rf = reg_flags; rf->name; rf++) - if (!strcmp(*argv, rf->name)) { - flags |= rf->flag; - break; - } - if (!rf->name) - die("do not recognize %s", *argv); + argc--; + + if (!argc) + goto usage; + + if (!strcmp(*argv, "--bug")) { + if (argc == 1) + return test_regex_bug(); + else + goto usage; + } + if (!strcmp(*argv, "--silent")) { + silent = 1; argv++; + argc--; + } + if (!argc) + goto usage; + + pat = *argv++; + if (argc == 1) + str = NULL; + else { + str = *argv++; + while (*argv) { + struct reg_flag *rf; + for (rf = reg_flags; rf->name; rf++) + if (!strcmp(*argv, rf->name)) { + flags |= rf->flag; + break; + } + if (!rf->name) + die("do not recognize flag %s", *argv); + argv++; + } } git_setup_gettext(); - if (regcomp(&r, pat, flags)) - die("failed regcomp() for pattern '%s'", pat); - if (regexec(&r, str, 1, m, 0)) - return 1; + ret = regcomp(&r, pat, flags); + if (ret) { + if (silent) + return ret; + + regerror(ret, &r, errbuf, sizeof(errbuf)); + die("failed regcomp() for pattern '%s' (%s)", pat, errbuf); + } + if (!str) + return 0; + + ret = regexec(&r, str, 1, m, 0); + if (ret) { + if (silent || ret == REG_NOMATCH) + return ret; + + regerror(ret, &r, errbuf, sizeof(errbuf)); + die("failed regexec() for subject '%s' (%s)", str, errbuf); + } return 0; +usage: + usage("\ttest-tool regex --bug\n" + "\ttest-tool regex [--silent] <pattern>\n" + "\ttest-tool regex [--silent] <pattern> <string> [<options>]"); + return -1; } diff --git a/t/helper/test-repository.c b/t/helper/test-repository.c new file mode 100644 index 0000000000..56f0e3c1be --- /dev/null +++ b/t/helper/test-repository.c @@ -0,0 +1,100 @@ +#include "test-tool.h" +#include "cache.h" +#include "commit-graph.h" +#include "commit.h" +#include "config.h" +#include "object-store.h" +#include "object.h" +#include "repository.h" +#include "tree.h" + +static void test_parse_commit_in_graph(const char *gitdir, const char *worktree, + const struct object_id *commit_oid) +{ + struct repository r; + struct commit *c; + struct commit_list *parent; + + setup_git_env(gitdir); + + memset(the_repository, 0, sizeof(*the_repository)); + + if (repo_init(&r, gitdir, worktree)) + die("Couldn't init repo"); + + repo_set_hash_algo(the_repository, hash_algo_by_ptr(r.hash_algo)); + + c = lookup_commit(&r, commit_oid); + + if (!parse_commit_in_graph(&r, c)) + die("Couldn't parse commit"); + + printf("%"PRItime, c->date); + for (parent = c->parents; parent; parent = parent->next) + printf(" %s", oid_to_hex(&parent->item->object.oid)); + printf("\n"); + + repo_clear(&r); +} + +static void test_get_commit_tree_in_graph(const char *gitdir, + const char *worktree, + const struct object_id *commit_oid) +{ + struct repository r; + struct commit *c; + struct tree *tree; + + setup_git_env(gitdir); + + memset(the_repository, 0, sizeof(*the_repository)); + + if (repo_init(&r, gitdir, worktree)) + die("Couldn't init repo"); + + repo_set_hash_algo(the_repository, hash_algo_by_ptr(r.hash_algo)); + + c = lookup_commit(&r, commit_oid); + + /* + * get_commit_tree_in_graph does not automatically parse the commit, so + * parse it first. + */ + if (!parse_commit_in_graph(&r, c)) + die("Couldn't parse commit"); + tree = get_commit_tree_in_graph(&r, c); + if (!tree) + die("Couldn't get commit tree"); + + printf("%s\n", oid_to_hex(&tree->object.oid)); + + repo_clear(&r); +} + +int cmd__repository(int argc, const char **argv) +{ + int nongit_ok = 0; + + setup_git_directory_gently(&nongit_ok); + + if (argc < 2) + die("must have at least 2 arguments"); + if (!strcmp(argv[1], "parse_commit_in_graph")) { + struct object_id oid; + if (argc < 5) + die("not enough arguments"); + if (parse_oid_hex(argv[4], &oid, &argv[4])) + die("cannot parse oid '%s'", argv[4]); + test_parse_commit_in_graph(argv[2], argv[3], &oid); + } else if (!strcmp(argv[1], "get_commit_tree_in_graph")) { + struct object_id oid; + if (argc < 5) + die("not enough arguments"); + if (parse_oid_hex(argv[4], &oid, &argv[4])) + die("cannot parse oid '%s'", argv[4]); + test_get_commit_tree_in_graph(argv[2], argv[3], &oid); + } else { + die("unrecognized '%s'", argv[1]); + } + return 0; +} diff --git a/t/helper/test-revision-walking.c b/t/helper/test-revision-walking.c index b8e6fe1d00..625b2dbf82 100644 --- a/t/helper/test-revision-walking.c +++ b/t/helper/test-revision-walking.c @@ -8,6 +8,7 @@ * published by the Free Software Foundation. */ +#include "test-tool.h" #include "cache.h" #include "commit.h" #include "diff.h" @@ -31,7 +32,7 @@ static int run_revision_walk(void) int argc = ARRAY_SIZE(argv) - 1; int got_revision = 0; - init_revisions(&rev, NULL); + repo_init_revisions(the_repository, &rev, NULL); setup_revisions(argc, argv, &rev, NULL); if (prepare_revision_walk(&rev)) die("revision walk setup failed"); @@ -45,7 +46,7 @@ static int run_revision_walk(void) return got_revision; } -int cmd_main(int argc, const char **argv) +int cmd__revision_walking(int argc, const char **argv) { if (argc < 2) return 1; diff --git a/t/helper/test-run-command.c b/t/helper/test-run-command.c index d24d157379..7ae03dc712 100644 --- a/t/helper/test-run-command.c +++ b/t/helper/test-run-command.c @@ -8,12 +8,18 @@ * published by the Free Software Foundation. */ +#include "test-tool.h" #include "git-compat-util.h" +#include "cache.h" #include "run-command.h" -#include "argv-array.h" +#include "strvec.h" #include "strbuf.h" -#include <string.h> -#include <errno.h> +#include "parse-options.h" +#include "string-list.h" +#include "thread-utils.h" +#include "wildmatch.h" +#include "gettext.h" +#include "parse-options.h" static int number_callbacks; static int parallel_next(struct child_process *cp, @@ -25,7 +31,7 @@ static int parallel_next(struct child_process *cp, if (number_callbacks >= 4) return 0; - argv_array_pushv(&cp->args, d->argv); + strvec_pushv(&cp->args, d->argv); strbuf_addstr(err, "preloaded output of a child\n"); number_callbacks++; return 1; @@ -49,11 +55,346 @@ static int task_finished(int result, return 1; } -int cmd_main(int argc, const char **argv) +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; + strvec_pushl(&cp->args, "sh", test, NULL); + if (suite->quiet) + strvec_push(&cp->args, "--quiet"); + if (suite->immediate) + strvec_push(&cp->args, "-i"); + if (suite->verbose) + strvec_push(&cp->args, "-v"); + if (suite->verbose_log) + strvec_push(&cp->args, "-V"); + if (suite->trace) + strvec_push(&cp->args, "-x"); + if (suite->write_junit_xml) + strvec_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; +} + +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, skip = 0, msys2 = 0; + struct strbuf out = STRBUF_INIT; + struct strvec args = STRVEC_INIT; + struct option options[] = { + OPT_INTEGER('n', "trials", &trials, "Number of trials"), + OPT_INTEGER('s', "skip", &skip, "Skip <n> trials"), + OPT_BOOL('m', "msys2", &msys2, "Test quoting for MSYS2's sh"), + OPT_END() + }; + const char * const usage[] = { + "test-tool run-command quote-stress-test <options>", + NULL + }; + + argc = parse_options(argc, argv, NULL, options, usage, 0); + + setenv("MSYS_NO_PATHCONV", "1", 0); + + for (i = 0; i < trials; i++) { + struct child_process cp = CHILD_PROCESS_INIT; + size_t arg_count, arg_offset; + int ret = 0; + + strvec_clear(&args); + if (msys2) + strvec_pushl(&args, "sh", "-c", + "printf %s\\\\0 \"$@\"", "skip", NULL); + else + strvec_pushl(&args, "test-tool", "run-command", + "quote-echo", NULL); + arg_offset = args.nr; + + if (argc > 0) { + trials = 1; + arg_count = argc; + for (j = 0; j < arg_count; j++) + strvec_push(&args, argv[j]); + } else { + arg_count = 1 + (my_random() % 5); + 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'; + + strvec_push(&args, buf); + } + } + + if (i < skip) + continue; + + cp.argv = args.v; + 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.v[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.v[j + arg_offset]); + + strbuf_release(&out); + strvec_clear(&args); + + return ret; + } + + if (i && (i % 100) == 0) + fprintf(stderr, "Trials completed: %d\n", (int)i); + } + + strbuf_release(&out); + strvec_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; +} + +static int inherit_handle(const char *argv0) +{ + struct child_process cp = CHILD_PROCESS_INIT; + char path[PATH_MAX]; + int tmp; + + /* First, open an inheritable handle */ + xsnprintf(path, sizeof(path), "out-XXXXXX"); + tmp = xmkstemp(path); + + strvec_pushl(&cp.args, + "test-tool", argv0, "inherited-handle-child", NULL); + cp.in = -1; + cp.no_stdout = cp.no_stderr = 1; + if (start_command(&cp) < 0) + die("Could not start child process"); + + /* Then close it, and try to delete it. */ + close(tmp); + if (unlink(path)) + die("Could not delete '%s'", path); + + if (close(cp.in) < 0 || finish_command(&cp) < 0) + die("Child did not finish"); + + return 0; +} + +static int inherit_handle_child(void) +{ + struct strbuf buf = STRBUF_INIT; + + if (strbuf_read(&buf, 0, 0) < 0) + die("Could not read stdin"); + printf("Received %s\n", buf.buf); + strbuf_release(&buf); + + return 0; +} + +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 (!strcmp(argv[1], "inherited-handle")) + exit(inherit_handle(argv[0])); + if (!strcmp(argv[1], "inherited-handle-child")) + exit(inherit_handle_child()); + + 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; + while (!strcmp(argv[1], "env")) { + if (!argv[2]) + die("env specifier without a value"); + strvec_push(&proc.env_array, argv[2]); + argv += 2; + argc -= 2; + } if (argc < 3) return 1; proc.argv = (const char **)argv + 2; diff --git a/t/helper/test-scrap-cache-tree.c b/t/helper/test-scrap-cache-tree.c index d2a63bea43..393f1604ff 100644 --- a/t/helper/test-scrap-cache-tree.c +++ b/t/helper/test-scrap-cache-tree.c @@ -1,12 +1,13 @@ +#include "test-tool.h" #include "cache.h" #include "lockfile.h" #include "tree.h" #include "cache-tree.h" -static struct lock_file index_lock; - -int cmd_main(int ac, const char **av) +int cmd__scrap_cache_tree(int ac, const char **av) { + struct lock_file index_lock = LOCK_INIT; + setup_git_directory(); hold_locked_index(&index_lock, LOCK_DIE_ON_ERROR); if (read_cache() < 0) diff --git a/t/helper/test-serve-v2.c b/t/helper/test-serve-v2.c new file mode 100644 index 0000000000..aee35e5aef --- /dev/null +++ b/t/helper/test-serve-v2.c @@ -0,0 +1,31 @@ +#include "test-tool.h" +#include "cache.h" +#include "parse-options.h" +#include "serve.h" + +static char const * const serve_usage[] = { + N_("test-tool serve-v2 [<options>]"), + NULL +}; + +int cmd__serve_v2(int argc, const char **argv) +{ + struct serve_options opts = SERVE_OPTIONS_INIT; + + struct option options[] = { + OPT_BOOL(0, "stateless-rpc", &opts.stateless_rpc, + N_("quit after a single request/response exchange")), + OPT_BOOL(0, "advertise-capabilities", &opts.advertise_capabilities, + N_("exit immediately after advertising capabilities")), + OPT_END() + }; + const char *prefix = setup_git_directory(); + + /* ignore all unknown cmdline switches for now */ + argc = parse_options(argc, argv, prefix, options, serve_usage, + PARSE_OPT_KEEP_DASHDASH | + PARSE_OPT_KEEP_UNKNOWN); + serve(&opts); + + return 0; +} diff --git a/t/helper/test-sha1.c b/t/helper/test-sha1.c index a1c13f54ec..d860c387c3 100644 --- a/t/helper/test-sha1.c +++ b/t/helper/test-sha1.c @@ -1,56 +1,7 @@ +#include "test-tool.h" #include "cache.h" -int cmd_main(int ac, const char **av) +int cmd__sha1(int ac, const char **av) { - git_SHA_CTX ctx; - unsigned char sha1[20]; - unsigned bufsz = 8192; - int binary = 0; - char *buffer; - - if (ac == 2) { - if (!strcmp(av[1], "-b")) - binary = 1; - else - bufsz = strtoul(av[1], NULL, 10) * 1024 * 1024; - } - - if (!bufsz) - bufsz = 8192; - - while ((buffer = malloc(bufsz)) == NULL) { - fprintf(stderr, "bufsz %u is too big, halving...\n", bufsz); - bufsz /= 2; - if (bufsz < 1024) - die("OOPS"); - } - - git_SHA1_Init(&ctx); - - while (1) { - ssize_t sz, this_sz; - char *cp = buffer; - unsigned room = bufsz; - this_sz = 0; - while (room) { - sz = xread(0, cp, room); - if (sz == 0) - break; - if (sz < 0) - die_errno("test-sha1"); - this_sz += sz; - cp += sz; - room -= sz; - } - if (this_sz == 0) - break; - git_SHA1_Update(&ctx, buffer, this_sz); - } - git_SHA1_Final(sha1, &ctx); - - if (binary) - fwrite(sha1, 1, 20, stdout); - else - puts(sha1_to_hex(sha1)); - exit(0); + return cmd_hash_impl(ac, av, GIT_HASH_SHA1); } diff --git a/t/helper/test-sha1.sh b/t/helper/test-sha1.sh index 750b95a0a1..84594885c7 100755 --- a/t/helper/test-sha1.sh +++ b/t/helper/test-sha1.sh @@ -1,7 +1,7 @@ #!/bin/sh dd if=/dev/zero bs=1048576 count=100 2>/dev/null | -/usr/bin/time t/helper/test-sha1 >/dev/null +/usr/bin/time t/helper/test-tool sha1 >/dev/null while read expect cnt pfx do @@ -11,7 +11,7 @@ do test -z "$pfx" || echo "$pfx" dd if=/dev/zero bs=1048576 count=$cnt 2>/dev/null | perl -pe 'y/\000/g/' - } | ./t/helper/test-sha1 $cnt + } | ./t/helper/test-tool sha1 $cnt ) if test "$expect" = "$actual" then diff --git a/t/helper/test-sha256.c b/t/helper/test-sha256.c new file mode 100644 index 0000000000..0ac6a99d5f --- /dev/null +++ b/t/helper/test-sha256.c @@ -0,0 +1,7 @@ +#include "test-tool.h" +#include "cache.h" + +int cmd__sha256(int ac, const char **av) +{ + return cmd_hash_impl(ac, av, GIT_HASH_SHA256); +} diff --git a/t/helper/test-sigchain.c b/t/helper/test-sigchain.c index b71edbd442..d013bccdda 100644 --- a/t/helper/test-sigchain.c +++ b/t/helper/test-sigchain.c @@ -1,3 +1,4 @@ +#include "test-tool.h" #include "cache.h" #include "sigchain.h" @@ -13,7 +14,8 @@ X(two) X(three) #undef X -int cmd_main(int argc, const char **argv) { +int cmd__sigchain(int argc, const char **argv) +{ sigchain_push(SIGTERM, one); sigchain_push(SIGTERM, two); sigchain_push(SIGTERM, three); diff --git a/t/helper/test-simple-ipc.c b/t/helper/test-simple-ipc.c new file mode 100644 index 0000000000..42040ef81b --- /dev/null +++ b/t/helper/test-simple-ipc.c @@ -0,0 +1,787 @@ +/* + * test-simple-ipc.c: verify that the Inter-Process Communication works. + */ + +#include "test-tool.h" +#include "cache.h" +#include "strbuf.h" +#include "simple-ipc.h" +#include "parse-options.h" +#include "thread-utils.h" +#include "strvec.h" + +#ifndef SUPPORTS_SIMPLE_IPC +int cmd__simple_ipc(int argc, const char **argv) +{ + die("simple IPC not available on this platform"); +} +#else + +/* + * The test daemon defines an "application callback" that supports a + * series of commands (see `test_app_cb()`). + * + * Unknown commands are caught here and we send an error message back + * to the client process. + */ +static int app__unhandled_command(const char *command, + ipc_server_reply_cb *reply_cb, + struct ipc_server_reply_data *reply_data) +{ + struct strbuf buf = STRBUF_INIT; + int ret; + + strbuf_addf(&buf, "unhandled command: %s", command); + ret = reply_cb(reply_data, buf.buf, buf.len); + strbuf_release(&buf); + + return ret; +} + +/* + * Reply with a single very large buffer. This is to ensure that + * long response are properly handled -- whether the chunking occurs + * in the kernel or in the (probably pkt-line) layer. + */ +#define BIG_ROWS (10000) +static int app__big_command(ipc_server_reply_cb *reply_cb, + struct ipc_server_reply_data *reply_data) +{ + struct strbuf buf = STRBUF_INIT; + int row; + int ret; + + for (row = 0; row < BIG_ROWS; row++) + strbuf_addf(&buf, "big: %.75d\n", row); + + ret = reply_cb(reply_data, buf.buf, buf.len); + strbuf_release(&buf); + + return ret; +} + +/* + * Reply with a series of lines. This is to ensure that we can incrementally + * compute the response and chunk it to the client. + */ +#define CHUNK_ROWS (10000) +static int app__chunk_command(ipc_server_reply_cb *reply_cb, + struct ipc_server_reply_data *reply_data) +{ + struct strbuf buf = STRBUF_INIT; + int row; + int ret; + + for (row = 0; row < CHUNK_ROWS; row++) { + strbuf_setlen(&buf, 0); + strbuf_addf(&buf, "big: %.75d\n", row); + ret = reply_cb(reply_data, buf.buf, buf.len); + } + + strbuf_release(&buf); + + return ret; +} + +/* + * Slowly reply with a series of lines. This is to model an expensive to + * compute chunked response (which might happen if this callback is running + * in a thread and is fighting for a lock with other threads). + */ +#define SLOW_ROWS (1000) +#define SLOW_DELAY_MS (10) +static int app__slow_command(ipc_server_reply_cb *reply_cb, + struct ipc_server_reply_data *reply_data) +{ + struct strbuf buf = STRBUF_INIT; + int row; + int ret; + + for (row = 0; row < SLOW_ROWS; row++) { + strbuf_setlen(&buf, 0); + strbuf_addf(&buf, "big: %.75d\n", row); + ret = reply_cb(reply_data, buf.buf, buf.len); + sleep_millisec(SLOW_DELAY_MS); + } + + strbuf_release(&buf); + + return ret; +} + +/* + * The client sent a command followed by a (possibly very) large buffer. + */ +static int app__sendbytes_command(const char *received, + ipc_server_reply_cb *reply_cb, + struct ipc_server_reply_data *reply_data) +{ + struct strbuf buf_resp = STRBUF_INIT; + const char *p = "?"; + int len_ballast = 0; + int k; + int errs = 0; + int ret; + + if (skip_prefix(received, "sendbytes ", &p)) + len_ballast = strlen(p); + + /* + * Verify that the ballast is n copies of a single letter. + * And that the multi-threaded IO layer didn't cross the streams. + */ + for (k = 1; k < len_ballast; k++) + if (p[k] != p[0]) + errs++; + + if (errs) + strbuf_addf(&buf_resp, "errs:%d\n", errs); + else + strbuf_addf(&buf_resp, "rcvd:%c%08d\n", p[0], len_ballast); + + ret = reply_cb(reply_data, buf_resp.buf, buf_resp.len); + + strbuf_release(&buf_resp); + + return ret; +} + +/* + * An arbitrary fixed address to verify that the application instance + * data is handled properly. + */ +static int my_app_data = 42; + +static ipc_server_application_cb test_app_cb; + +/* + * This is the "application callback" that sits on top of the + * "ipc-server". It completely defines the set of commands supported + * by this application. + */ +static int test_app_cb(void *application_data, + const char *command, + ipc_server_reply_cb *reply_cb, + struct ipc_server_reply_data *reply_data) +{ + /* + * Verify that we received the application-data that we passed + * when we started the ipc-server. (We have several layers of + * callbacks calling callbacks and it's easy to get things mixed + * up (especially when some are "void*").) + */ + if (application_data != (void*)&my_app_data) + BUG("application_cb: application_data pointer wrong"); + + if (!strcmp(command, "quit")) { + /* + * The client sent a "quit" command. This is an async + * request for the server to shutdown. + * + * We DO NOT send the client a response message + * (because we have nothing to say and the other + * server threads have not yet stopped). + * + * Tell the ipc-server layer to start shutting down. + * This includes: stop listening for new connections + * on the socket/pipe and telling all worker threads + * to finish/drain their outgoing responses to other + * clients. + * + * This DOES NOT force an immediate sync shutdown. + */ + return SIMPLE_IPC_QUIT; + } + + if (!strcmp(command, "ping")) { + const char *answer = "pong"; + return reply_cb(reply_data, answer, strlen(answer)); + } + + if (!strcmp(command, "big")) + return app__big_command(reply_cb, reply_data); + + if (!strcmp(command, "chunk")) + return app__chunk_command(reply_cb, reply_data); + + if (!strcmp(command, "slow")) + return app__slow_command(reply_cb, reply_data); + + if (starts_with(command, "sendbytes ")) + return app__sendbytes_command(command, reply_cb, reply_data); + + return app__unhandled_command(command, reply_cb, reply_data); +} + +struct cl_args +{ + const char *subcommand; + const char *path; + const char *token; + + int nr_threads; + int max_wait_sec; + int bytecount; + int batchsize; + + char bytevalue; +}; + +static struct cl_args cl_args = { + .subcommand = NULL, + .path = "ipc-test", + .token = NULL, + + .nr_threads = 5, + .max_wait_sec = 60, + .bytecount = 1024, + .batchsize = 10, + + .bytevalue = 'x', +}; + +/* + * This process will run as a simple-ipc server and listen for IPC commands + * from client processes. + */ +static int daemon__run_server(void) +{ + int ret; + + struct ipc_server_opts opts = { + .nr_threads = cl_args.nr_threads, + }; + + /* + * Synchronously run the ipc-server. We don't need any application + * instance data, so pass an arbitrary pointer (that we'll later + * verify made the round trip). + */ + ret = ipc_server_run(cl_args.path, &opts, test_app_cb, (void*)&my_app_data); + if (ret == -2) + error(_("socket/pipe already in use: '%s'"), cl_args.path); + else if (ret == -1) + error_errno(_("could not start server on: '%s'"), cl_args.path); + + return ret; +} + +#ifndef GIT_WINDOWS_NATIVE +/* + * This is adapted from `daemonize()`. Use `fork()` to directly create and + * run the daemon in a child process. + */ +static int spawn_server(pid_t *pid) +{ + struct ipc_server_opts opts = { + .nr_threads = cl_args.nr_threads, + }; + + *pid = fork(); + + switch (*pid) { + case 0: + if (setsid() == -1) + error_errno(_("setsid failed")); + close(0); + close(1); + close(2); + sanitize_stdfds(); + + return ipc_server_run(cl_args.path, &opts, test_app_cb, + (void*)&my_app_data); + + case -1: + return error_errno(_("could not spawn daemon in the background")); + + default: + return 0; + } +} +#else +/* + * Conceptually like `daemonize()` but different because Windows does not + * have `fork(2)`. Spawn a normal Windows child process but without the + * limitations of `start_command()` and `finish_command()`. + */ +static int spawn_server(pid_t *pid) +{ + char test_tool_exe[MAX_PATH]; + struct strvec args = STRVEC_INIT; + int in, out; + + GetModuleFileNameA(NULL, test_tool_exe, MAX_PATH); + + in = open("/dev/null", O_RDONLY); + out = open("/dev/null", O_WRONLY); + + strvec_push(&args, test_tool_exe); + strvec_push(&args, "simple-ipc"); + strvec_push(&args, "run-daemon"); + strvec_pushf(&args, "--name=%s", cl_args.path); + strvec_pushf(&args, "--threads=%d", cl_args.nr_threads); + + *pid = mingw_spawnvpe(args.v[0], args.v, NULL, NULL, in, out, out); + close(in); + close(out); + + strvec_clear(&args); + + if (*pid < 0) + return error(_("could not spawn daemon in the background")); + + return 0; +} +#endif + +/* + * This is adapted from `wait_or_whine()`. Watch the child process and + * let it get started and begin listening for requests on the socket + * before reporting our success. + */ +static int wait_for_server_startup(pid_t pid_child) +{ + int status; + pid_t pid_seen; + enum ipc_active_state s; + time_t time_limit, now; + + time(&time_limit); + time_limit += cl_args.max_wait_sec; + + for (;;) { + pid_seen = waitpid(pid_child, &status, WNOHANG); + + if (pid_seen == -1) + return error_errno(_("waitpid failed")); + + else if (pid_seen == 0) { + /* + * The child is still running (this should be + * the normal case). Try to connect to it on + * the socket and see if it is ready for + * business. + * + * If there is another daemon already running, + * our child will fail to start (possibly + * after a timeout on the lock), but we don't + * care (who responds) if the socket is live. + */ + s = ipc_get_active_state(cl_args.path); + if (s == IPC_STATE__LISTENING) + return 0; + + time(&now); + if (now > time_limit) + return error(_("daemon not online yet")); + + continue; + } + + else if (pid_seen == pid_child) { + /* + * The new child daemon process shutdown while + * it was starting up, so it is not listening + * on the socket. + * + * Try to ping the socket in the odd chance + * that another daemon started (or was already + * running) while our child was starting. + * + * Again, we don't care who services the socket. + */ + s = ipc_get_active_state(cl_args.path); + if (s == IPC_STATE__LISTENING) + return 0; + + /* + * We don't care about the WEXITSTATUS() nor + * any of the WIF*(status) values because + * `cmd__simple_ipc()` does the `!!result` + * trick on all function return values. + * + * So it is sufficient to just report the + * early shutdown as an error. + */ + return error(_("daemon failed to start")); + } + + else + return error(_("waitpid is confused")); + } +} + +/* + * This process will start a simple-ipc server in a background process and + * wait for it to become ready. This is like `daemonize()` but gives us + * more control and better error reporting (and makes it easier to write + * unit tests). + */ +static int daemon__start_server(void) +{ + pid_t pid_child; + int ret; + + /* + * Run the actual daemon in a background process. + */ + ret = spawn_server(&pid_child); + if (pid_child <= 0) + return ret; + + /* + * Let the parent wait for the child process to get started + * and begin listening for requests on the socket. + */ + ret = wait_for_server_startup(pid_child); + + return ret; +} + +/* + * This process will run a quick probe to see if a simple-ipc server + * is active on this path. + * + * Returns 0 if the server is alive. + */ +static int client__probe_server(void) +{ + enum ipc_active_state s; + + s = ipc_get_active_state(cl_args.path); + switch (s) { + case IPC_STATE__LISTENING: + return 0; + + case IPC_STATE__NOT_LISTENING: + return error("no server listening at '%s'", cl_args.path); + + case IPC_STATE__PATH_NOT_FOUND: + return error("path not found '%s'", cl_args.path); + + case IPC_STATE__INVALID_PATH: + return error("invalid pipe/socket name '%s'", cl_args.path); + + case IPC_STATE__OTHER_ERROR: + default: + return error("other error for '%s'", cl_args.path); + } +} + +/* + * Send an IPC command token to an already-running server daemon and + * print the response. + * + * This is a simple 1 word command/token that `test_app_cb()` (in the + * daemon process) will understand. + */ +static int client__send_ipc(void) +{ + const char *command = "(no-command)"; + struct strbuf buf = STRBUF_INIT; + struct ipc_client_connect_options options + = IPC_CLIENT_CONNECT_OPTIONS_INIT; + + if (cl_args.token && *cl_args.token) + command = cl_args.token; + + options.wait_if_busy = 1; + options.wait_if_not_found = 0; + + if (!ipc_client_send_command(cl_args.path, &options, command, &buf)) { + if (buf.len) { + printf("%s\n", buf.buf); + fflush(stdout); + } + strbuf_release(&buf); + + return 0; + } + + return error("failed to send '%s' to '%s'", command, cl_args.path); +} + +/* + * Send an IPC command to an already-running server and ask it to + * shutdown. "send quit" is an async request and queues a shutdown + * event in the server, so we spin and wait here for it to actually + * shutdown to make the unit tests a little easier to write. + */ +static int client__stop_server(void) +{ + int ret; + time_t time_limit, now; + enum ipc_active_state s; + + time(&time_limit); + time_limit += cl_args.max_wait_sec; + + cl_args.token = "quit"; + + ret = client__send_ipc(); + if (ret) + return ret; + + for (;;) { + sleep_millisec(100); + + s = ipc_get_active_state(cl_args.path); + + if (s != IPC_STATE__LISTENING) { + /* + * The socket/pipe is gone and/or has stopped + * responding. Lets assume that the daemon + * process has exited too. + */ + return 0; + } + + time(&now); + if (now > time_limit) + return error(_("daemon has not shutdown yet")); + } +} + +/* + * Send an IPC command followed by ballast to confirm that a large + * message can be sent and that the kernel or pkt-line layers will + * properly chunk it and that the daemon receives the entire message. + */ +static int do_sendbytes(int bytecount, char byte, const char *path, + const struct ipc_client_connect_options *options) +{ + struct strbuf buf_send = STRBUF_INIT; + struct strbuf buf_resp = STRBUF_INIT; + + strbuf_addstr(&buf_send, "sendbytes "); + strbuf_addchars(&buf_send, byte, bytecount); + + if (!ipc_client_send_command(path, options, buf_send.buf, &buf_resp)) { + strbuf_rtrim(&buf_resp); + printf("sent:%c%08d %s\n", byte, bytecount, buf_resp.buf); + fflush(stdout); + strbuf_release(&buf_send); + strbuf_release(&buf_resp); + + return 0; + } + + return error("client failed to sendbytes(%d, '%c') to '%s'", + bytecount, byte, path); +} + +/* + * Send an IPC command with ballast to an already-running server daemon. + */ +static int client__sendbytes(void) +{ + struct ipc_client_connect_options options + = IPC_CLIENT_CONNECT_OPTIONS_INIT; + + options.wait_if_busy = 1; + options.wait_if_not_found = 0; + options.uds_disallow_chdir = 0; + + return do_sendbytes(cl_args.bytecount, cl_args.bytevalue, cl_args.path, + &options); +} + +struct multiple_thread_data { + pthread_t pthread_id; + struct multiple_thread_data *next; + const char *path; + int bytecount; + int batchsize; + int sum_errors; + int sum_good; + char letter; +}; + +static void *multiple_thread_proc(void *_multiple_thread_data) +{ + struct multiple_thread_data *d = _multiple_thread_data; + int k; + struct ipc_client_connect_options options + = IPC_CLIENT_CONNECT_OPTIONS_INIT; + + options.wait_if_busy = 1; + options.wait_if_not_found = 0; + /* + * A multi-threaded client should not be randomly calling chdir(). + * The test will pass without this restriction because the test is + * not otherwise accessing the filesystem, but it makes us honest. + */ + options.uds_disallow_chdir = 1; + + trace2_thread_start("multiple"); + + for (k = 0; k < d->batchsize; k++) { + if (do_sendbytes(d->bytecount + k, d->letter, d->path, &options)) + d->sum_errors++; + else + d->sum_good++; + } + + trace2_thread_exit(); + return NULL; +} + +/* + * Start a client-side thread pool. Each thread sends a series of + * IPC requests. Each request is on a new connection to the server. + */ +static int client__multiple(void) +{ + struct multiple_thread_data *list = NULL; + int k; + int sum_join_errors = 0; + int sum_thread_errors = 0; + int sum_good = 0; + + for (k = 0; k < cl_args.nr_threads; k++) { + struct multiple_thread_data *d = xcalloc(1, sizeof(*d)); + d->next = list; + d->path = cl_args.path; + d->bytecount = cl_args.bytecount + cl_args.batchsize*(k/26); + d->batchsize = cl_args.batchsize; + d->sum_errors = 0; + d->sum_good = 0; + d->letter = 'A' + (k % 26); + + if (pthread_create(&d->pthread_id, NULL, multiple_thread_proc, d)) { + warning("failed to create thread[%d] skipping remainder", k); + free(d); + break; + } + + list = d; + } + + while (list) { + struct multiple_thread_data *d = list; + + if (pthread_join(d->pthread_id, NULL)) + sum_join_errors++; + + sum_thread_errors += d->sum_errors; + sum_good += d->sum_good; + + list = d->next; + free(d); + } + + printf("client (good %d) (join %d), (errors %d)\n", + sum_good, sum_join_errors, sum_thread_errors); + + return (sum_join_errors + sum_thread_errors) ? 1 : 0; +} + +int cmd__simple_ipc(int argc, const char **argv) +{ + const char * const simple_ipc_usage[] = { + N_("test-helper simple-ipc is-active [<name>] [<options>]"), + N_("test-helper simple-ipc run-daemon [<name>] [<threads>]"), + N_("test-helper simple-ipc start-daemon [<name>] [<threads>] [<max-wait>]"), + N_("test-helper simple-ipc stop-daemon [<name>] [<max-wait>]"), + N_("test-helper simple-ipc send [<name>] [<token>]"), + N_("test-helper simple-ipc sendbytes [<name>] [<bytecount>] [<byte>]"), + N_("test-helper simple-ipc multiple [<name>] [<threads>] [<bytecount>] [<batchsize>]"), + NULL + }; + + const char *bytevalue = NULL; + + struct option options[] = { +#ifndef GIT_WINDOWS_NATIVE + OPT_STRING(0, "name", &cl_args.path, N_("name"), N_("name or pathname of unix domain socket")), +#else + OPT_STRING(0, "name", &cl_args.path, N_("name"), N_("named-pipe name")), +#endif + OPT_INTEGER(0, "threads", &cl_args.nr_threads, N_("number of threads in server thread pool")), + OPT_INTEGER(0, "max-wait", &cl_args.max_wait_sec, N_("seconds to wait for daemon to start or stop")), + + OPT_INTEGER(0, "bytecount", &cl_args.bytecount, N_("number of bytes")), + OPT_INTEGER(0, "batchsize", &cl_args.batchsize, N_("number of requests per thread")), + + OPT_STRING(0, "byte", &bytevalue, N_("byte"), N_("ballast character")), + OPT_STRING(0, "token", &cl_args.token, N_("token"), N_("command token to send to the server")), + + OPT_END() + }; + + if (argc < 2) + usage_with_options(simple_ipc_usage, options); + + if (argc == 2 && !strcmp(argv[1], "-h")) + usage_with_options(simple_ipc_usage, options); + + if (argc == 2 && !strcmp(argv[1], "SUPPORTS_SIMPLE_IPC")) + return 0; + + cl_args.subcommand = argv[1]; + + argc--; + argv++; + + argc = parse_options(argc, argv, NULL, options, simple_ipc_usage, 0); + + if (cl_args.nr_threads < 1) + cl_args.nr_threads = 1; + if (cl_args.max_wait_sec < 0) + cl_args.max_wait_sec = 0; + if (cl_args.bytecount < 1) + cl_args.bytecount = 1; + if (cl_args.batchsize < 1) + cl_args.batchsize = 1; + + if (bytevalue && *bytevalue) + cl_args.bytevalue = bytevalue[0]; + + /* + * Use '!!' on all dispatch functions to map from `error()` style + * (returns -1) style to `test_must_fail` style (expects 1). This + * makes shell error messages less confusing. + */ + + if (!strcmp(cl_args.subcommand, "is-active")) + return !!client__probe_server(); + + if (!strcmp(cl_args.subcommand, "run-daemon")) + return !!daemon__run_server(); + + if (!strcmp(cl_args.subcommand, "start-daemon")) + return !!daemon__start_server(); + + /* + * Client commands follow. Ensure a server is running before + * sending any data. This might be overkill, but then again + * this is a test harness. + */ + + if (!strcmp(cl_args.subcommand, "stop-daemon")) { + if (client__probe_server()) + return 1; + return !!client__stop_server(); + } + + if (!strcmp(cl_args.subcommand, "send")) { + if (client__probe_server()) + return 1; + return !!client__send_ipc(); + } + + if (!strcmp(cl_args.subcommand, "sendbytes")) { + if (client__probe_server()) + return 1; + return !!client__sendbytes(); + } + + if (!strcmp(cl_args.subcommand, "multiple")) { + if (client__probe_server()) + return 1; + return !!client__multiple(); + } + + die("Unhandled subcommand: '%s'", cl_args.subcommand); +} +#endif diff --git a/t/helper/test-strcmp-offset.c b/t/helper/test-strcmp-offset.c index e159c9a127..44e4a6d143 100644 --- a/t/helper/test-strcmp-offset.c +++ b/t/helper/test-strcmp-offset.c @@ -1,6 +1,7 @@ +#include "test-tool.h" #include "cache.h" -int cmd_main(int argc, const char **argv) +int cmd__strcmp_offset(int argc, const char **argv) { int result; size_t offset; diff --git a/t/helper/test-string-list.c b/t/helper/test-string-list.c index 829ec3d7d2..2123dda85b 100644 --- a/t/helper/test-string-list.c +++ b/t/helper/test-string-list.c @@ -1,3 +1,4 @@ +#include "test-tool.h" #include "cache.h" #include "string-list.h" @@ -41,7 +42,7 @@ static int prefix_cb(struct string_list_item *item, void *cb_data) return starts_with(item->string, prefix); } -int cmd_main(int argc, const char **argv) +int cmd__string_list(int argc, const char **argv) { if (argc == 5 && !strcmp(argv[1], "split")) { struct string_list list = STRING_LIST_INIT_DUP; diff --git a/t/helper/test-submodule-config.c b/t/helper/test-submodule-config.c index f23db3b19a..e2692746df 100644 --- a/t/helper/test-submodule-config.c +++ b/t/helper/test-submodule-config.c @@ -1,3 +1,4 @@ +#include "test-tool.h" #include "cache.h" #include "config.h" #include "submodule-config.h" @@ -10,7 +11,7 @@ static void die_usage(int argc, const char **argv, const char *msg) exit(1); } -int cmd_main(int argc, const char **argv) +int cmd__submodule_config(int argc, const char **argv) { const char **arg = argv; int my_argc = argc; @@ -48,9 +49,11 @@ int cmd_main(int argc, const char **argv) die_usage(argc, argv, "Commit not found."); if (lookup_name) { - submodule = submodule_from_name(&commit_oid, path_or_name); + submodule = submodule_from_name(the_repository, + &commit_oid, path_or_name); } else - submodule = submodule_from_path(&commit_oid, path_or_name); + submodule = submodule_from_path(the_repository, + &commit_oid, path_or_name); if (!submodule) die_usage(argc, argv, "Submodule not found."); @@ -64,7 +67,7 @@ int cmd_main(int argc, const char **argv) arg += 2; } - submodule_free(); + submodule_free(the_repository); return 0; } diff --git a/t/helper/test-submodule-nested-repo-config.c b/t/helper/test-submodule-nested-repo-config.c new file mode 100644 index 0000000000..e3f11ff5a7 --- /dev/null +++ b/t/helper/test-submodule-nested-repo-config.c @@ -0,0 +1,32 @@ +#include "test-tool.h" +#include "submodule-config.h" + +static void die_usage(const char **argv, const char *msg) +{ + fprintf(stderr, "%s\n", msg); + fprintf(stderr, "Usage: %s <submodulepath> <config name>\n", argv[0]); + exit(1); +} + +int cmd__submodule_nested_repo_config(int argc, const char **argv) +{ + struct repository subrepo; + const struct submodule *sub; + + if (argc < 3) + die_usage(argv, "Wrong number of arguments."); + + setup_git_directory(); + + sub = submodule_from_path(the_repository, null_oid(), argv[1]); + if (repo_submodule_init(&subrepo, the_repository, sub)) { + die_usage(argv, "Submodule not found."); + } + + /* Read the config of _child_ submodules. */ + print_config_from_gitmodules(&subrepo, argv[2]); + + submodule_free(the_repository); + + return 0; +} diff --git a/t/helper/test-subprocess.c b/t/helper/test-subprocess.c index 30c5765bfc..92b69de635 100644 --- a/t/helper/test-subprocess.c +++ b/t/helper/test-subprocess.c @@ -1,7 +1,8 @@ +#include "test-tool.h" #include "cache.h" #include "run-command.h" -int cmd_main(int argc, const char **argv) +int cmd__subprocess(int argc, const char **argv) { struct child_process cp = CHILD_PROCESS_INIT; int nogit = 0; diff --git a/t/helper/test-svn-fe.c b/t/helper/test-svn-fe.c deleted file mode 100644 index 7667c0803f..0000000000 --- a/t/helper/test-svn-fe.c +++ /dev/null @@ -1,52 +0,0 @@ -/* - * test-svn-fe: Code to exercise the svn import lib - */ - -#include "git-compat-util.h" -#include "vcs-svn/svndump.h" -#include "vcs-svn/svndiff.h" -#include "vcs-svn/sliding_window.h" -#include "vcs-svn/line_buffer.h" - -static const char test_svnfe_usage[] = - "test-svn-fe (<dumpfile> | [-d] <preimage> <delta> <len>)"; - -static int apply_delta(int argc, const char **argv) -{ - struct line_buffer preimage = LINE_BUFFER_INIT; - struct line_buffer delta = LINE_BUFFER_INIT; - struct sliding_view preimage_view = SLIDING_VIEW_INIT(&preimage, -1); - - if (argc != 5) - usage(test_svnfe_usage); - - if (buffer_init(&preimage, argv[2])) - die_errno("cannot open preimage"); - if (buffer_init(&delta, argv[3])) - die_errno("cannot open delta"); - if (svndiff0_apply(&delta, (off_t) strtoumax(argv[4], NULL, 0), - &preimage_view, stdout)) - return 1; - if (buffer_deinit(&preimage)) - die_errno("cannot close preimage"); - if (buffer_deinit(&delta)) - die_errno("cannot close delta"); - strbuf_release(&preimage_view.buf); - return 0; -} - -int cmd_main(int argc, const char **argv) -{ - if (argc == 2) { - if (svndump_init(argv[1])) - return 1; - svndump_read(NULL, "refs/heads/master", "refs/notes/svn/revs"); - svndump_deinit(); - svndump_reset(); - return 0; - } - - if (argc >= 2 && !strcmp(argv[1], "-d")) - return apply_delta(argc, argv); - usage(test_svnfe_usage); -} diff --git a/t/helper/test-tool.c b/t/helper/test-tool.c new file mode 100644 index 0000000000..b21e8f1519 --- /dev/null +++ b/t/helper/test-tool.c @@ -0,0 +1,130 @@ +#include "git-compat-util.h" +#include "test-tool.h" +#include "trace2.h" +#include "parse-options.h" + +static const char * const test_tool_usage[] = { + "test-tool [-C <directory>] <command [<arguments>...]]", + NULL +}; + +struct test_cmd { + const char *name; + int (*fn)(int argc, const char **argv); +}; + +static struct test_cmd cmds[] = { + { "advise", cmd__advise_if_enabled }, + { "bitmap", cmd__bitmap }, + { "bloom", cmd__bloom }, + { "chmtime", cmd__chmtime }, + { "config", cmd__config }, + { "crontab", cmd__crontab }, + { "ctype", cmd__ctype }, + { "date", cmd__date }, + { "delta", cmd__delta }, + { "dir-iterator", cmd__dir_iterator }, + { "drop-caches", cmd__drop_caches }, + { "dump-cache-tree", cmd__dump_cache_tree }, + { "dump-fsmonitor", cmd__dump_fsmonitor }, + { "dump-split-index", cmd__dump_split_index }, + { "dump-untracked-cache", cmd__dump_untracked_cache }, + { "example-decorate", cmd__example_decorate }, + { "fast-rebase", cmd__fast_rebase }, + { "genrandom", cmd__genrandom }, + { "genzeros", cmd__genzeros }, + { "hashmap", cmd__hashmap }, + { "hash-speed", cmd__hash_speed }, + { "index-version", cmd__index_version }, + { "json-writer", cmd__json_writer }, + { "lazy-init-name-hash", cmd__lazy_init_name_hash }, + { "match-trees", cmd__match_trees }, + { "mergesort", cmd__mergesort }, + { "mktemp", cmd__mktemp }, + { "oid-array", cmd__oid_array }, + { "oidmap", cmd__oidmap }, + { "online-cpus", cmd__online_cpus }, + { "parse-options", cmd__parse_options }, + { "parse-pathspec-file", cmd__parse_pathspec_file }, + { "partial-clone", cmd__partial_clone }, + { "path-utils", cmd__path_utils }, + { "pcre2-config", cmd__pcre2_config }, + { "pkt-line", cmd__pkt_line }, + { "prio-queue", cmd__prio_queue }, + { "proc-receive", cmd__proc_receive}, + { "progress", cmd__progress }, + { "reach", cmd__reach }, + { "read-cache", cmd__read_cache }, + { "read-graph", cmd__read_graph }, + { "read-midx", cmd__read_midx }, + { "ref-store", cmd__ref_store }, + { "regex", cmd__regex }, + { "repository", cmd__repository }, + { "revision-walking", cmd__revision_walking }, + { "run-command", cmd__run_command }, + { "scrap-cache-tree", cmd__scrap_cache_tree }, + { "serve-v2", cmd__serve_v2 }, + { "sha1", cmd__sha1 }, + { "sha256", cmd__sha256 }, + { "sigchain", cmd__sigchain }, + { "simple-ipc", cmd__simple_ipc }, + { "strcmp-offset", cmd__strcmp_offset }, + { "string-list", cmd__string_list }, + { "submodule-config", cmd__submodule_config }, + { "submodule-nested-repo-config", cmd__submodule_nested_repo_config }, + { "subprocess", cmd__subprocess }, + { "trace2", cmd__trace2 }, + { "userdiff", cmd__userdiff }, + { "urlmatch-normalization", cmd__urlmatch_normalization }, + { "xml-encode", cmd__xml_encode }, + { "wildmatch", cmd__wildmatch }, +#ifdef GIT_WINDOWS_NATIVE + { "windows-named-pipe", cmd__windows_named_pipe }, +#endif + { "write-cache", cmd__write_cache }, +}; + +static NORETURN void die_usage(void) +{ + size_t i; + + fprintf(stderr, "usage: test-tool <toolname> [args]\n"); + for (i = 0; i < ARRAY_SIZE(cmds); i++) + fprintf(stderr, " %s\n", cmds[i].name); + exit(128); +} + +int cmd_main(int argc, const char **argv) +{ + int i; + const char *working_directory = NULL; + struct option options[] = { + OPT_STRING('C', NULL, &working_directory, "directory", + "change the working directory"), + OPT_END() + }; + + BUG_exit_code = 99; + argc = parse_options(argc, argv, NULL, options, test_tool_usage, + PARSE_OPT_STOP_AT_NON_OPTION | + PARSE_OPT_KEEP_ARGV0); + + if (argc < 2) + die_usage(); + + if (working_directory && chdir(working_directory) < 0) + die("Could not cd to '%s'", working_directory); + + for (i = 0; i < ARRAY_SIZE(cmds); i++) { + if (!strcmp(cmds[i].name, argv[1])) { + argv++; + argc--; + trace2_cmd_name(cmds[i].name); + trace2_cmd_list_config(); + trace2_cmd_list_env_vars(); + return cmds[i].fn(argc, argv); + } + } + error("there is no tool named '%s'", argv[1]); + die_usage(); +} diff --git a/t/helper/test-tool.h b/t/helper/test-tool.h new file mode 100644 index 0000000000..f845ced4b3 --- /dev/null +++ b/t/helper/test-tool.h @@ -0,0 +1,78 @@ +#ifndef TEST_TOOL_H +#define TEST_TOOL_H + +#define USE_THE_INDEX_COMPATIBILITY_MACROS +#include "git-compat-util.h" + +int cmd__advise_if_enabled(int argc, const char **argv); +int cmd__bitmap(int argc, const char **argv); +int cmd__bloom(int argc, const char **argv); +int cmd__chmtime(int argc, const char **argv); +int cmd__config(int argc, const char **argv); +int cmd__crontab(int argc, const char **argv); +int cmd__ctype(int argc, const char **argv); +int cmd__date(int argc, const char **argv); +int cmd__delta(int argc, const char **argv); +int cmd__dir_iterator(int argc, const char **argv); +int cmd__drop_caches(int argc, const char **argv); +int cmd__dump_cache_tree(int argc, const char **argv); +int cmd__dump_fsmonitor(int argc, const char **argv); +int cmd__dump_split_index(int argc, const char **argv); +int cmd__dump_untracked_cache(int argc, const char **argv); +int cmd__example_decorate(int argc, const char **argv); +int cmd__fast_rebase(int argc, const char **argv); +int cmd__genrandom(int argc, const char **argv); +int cmd__genzeros(int argc, const char **argv); +int cmd__hashmap(int argc, const char **argv); +int cmd__hash_speed(int argc, const char **argv); +int cmd__index_version(int argc, const char **argv); +int cmd__json_writer(int argc, const char **argv); +int cmd__lazy_init_name_hash(int argc, const char **argv); +int cmd__match_trees(int argc, const char **argv); +int cmd__mergesort(int argc, const char **argv); +int cmd__mktemp(int argc, const char **argv); +int cmd__oidmap(int argc, const char **argv); +int cmd__online_cpus(int argc, const char **argv); +int cmd__parse_options(int argc, const char **argv); +int cmd__parse_pathspec_file(int argc, const char** argv); +int cmd__partial_clone(int argc, const char **argv); +int cmd__path_utils(int argc, const char **argv); +int cmd__pcre2_config(int argc, const char **argv); +int cmd__pkt_line(int argc, const char **argv); +int cmd__prio_queue(int argc, const char **argv); +int cmd__proc_receive(int argc, const char **argv); +int cmd__progress(int argc, const char **argv); +int cmd__reach(int argc, const char **argv); +int cmd__read_cache(int argc, const char **argv); +int cmd__read_graph(int argc, const char **argv); +int cmd__read_midx(int argc, const char **argv); +int cmd__ref_store(int argc, const char **argv); +int cmd__regex(int argc, const char **argv); +int cmd__repository(int argc, const char **argv); +int cmd__revision_walking(int argc, const char **argv); +int cmd__run_command(int argc, const char **argv); +int cmd__scrap_cache_tree(int argc, const char **argv); +int cmd__serve_v2(int argc, const char **argv); +int cmd__sha1(int argc, const char **argv); +int cmd__oid_array(int argc, const char **argv); +int cmd__sha256(int argc, const char **argv); +int cmd__sigchain(int argc, const char **argv); +int cmd__simple_ipc(int argc, const char **argv); +int cmd__strcmp_offset(int argc, const char **argv); +int cmd__string_list(int argc, const char **argv); +int cmd__submodule_config(int argc, const char **argv); +int cmd__submodule_nested_repo_config(int argc, const char **argv); +int cmd__subprocess(int argc, const char **argv); +int cmd__trace2(int argc, const char **argv); +int cmd__userdiff(int argc, const char **argv); +int cmd__urlmatch_normalization(int argc, const char **argv); +int cmd__xml_encode(int argc, const char **argv); +int cmd__wildmatch(int argc, const char **argv); +#ifdef GIT_WINDOWS_NATIVE +int cmd__windows_named_pipe(int argc, const char **argv); +#endif +int cmd__write_cache(int argc, const char **argv); + +int cmd_hash_impl(int ac, const char **av, int algo); + +#endif diff --git a/t/helper/test-trace2.c b/t/helper/test-trace2.c new file mode 100644 index 0000000000..f93633f895 --- /dev/null +++ b/t/helper/test-trace2.c @@ -0,0 +1,282 @@ +#include "test-tool.h" +#include "cache.h" +#include "strvec.h" +#include "run-command.h" +#include "exec-cmd.h" +#include "config.h" + +typedef int(fn_unit_test)(int argc, const char **argv); + +struct unit_test { + fn_unit_test *ut_fn; + const char *ut_name; + const char *ut_usage; +}; + +#define MyOk 0 +#define MyError 1 + +static int get_i(int *p_value, const char *data) +{ + char *endptr; + + if (!data || !*data) + return MyError; + + *p_value = strtol(data, &endptr, 10); + if (*endptr || errno == ERANGE) + return MyError; + + return MyOk; +} + +/* + * Cause process to exit with the requested value via "return". + * + * Rely on test-tool.c:cmd_main() to call trace2_cmd_exit() + * with our result. + * + * Test harness can confirm: + * [] the process-exit value. + * [] the "code" field in the "exit" trace2 event. + * [] the "code" field in the "atexit" trace2 event. + * [] the "name" field in the "cmd_name" trace2 event. + * [] "def_param" events for all of the "interesting" pre-defined + * config settings. + */ +static int ut_001return(int argc, const char **argv) +{ + int rc; + + if (get_i(&rc, argv[0])) + die("expect <exit_code>"); + + return rc; +} + +/* + * Cause the process to exit with the requested value via "exit()". + * + * Test harness can confirm: + * [] the "code" field in the "exit" trace2 event. + * [] the "code" field in the "atexit" trace2 event. + * [] the "name" field in the "cmd_name" trace2 event. + * [] "def_param" events for all of the "interesting" pre-defined + * config settings. + */ +static int ut_002exit(int argc, const char **argv) +{ + int rc; + + if (get_i(&rc, argv[0])) + die("expect <exit_code>"); + + exit(rc); +} + +/* + * Send an "error" event with each value in argv. Normally, git only issues + * a single "error" event immediately before issuing an "exit" event (such + * as in die() or BUG()), but multiple "error" events are allowed. + * + * Test harness can confirm: + * [] a trace2 "error" event for each value in argv. + * [] the "name" field in the "cmd_name" trace2 event. + * [] (optional) the file:line in the "exit" event refers to this function. + */ +static int ut_003error(int argc, const char **argv) +{ + int k; + + if (!argv[0] || !*argv[0]) + die("expect <error_message>"); + + for (k = 0; k < argc; k++) + error("%s", argv[k]); + + return 0; +} + +/* + * Run a child process and wait for it to finish and exit with its return code. + * test-tool trace2 004child [<child-command-line>] + * + * For example: + * test-tool trace2 004child git version + * test-tool trace2 004child test-tool trace2 001return 0 + * test-tool trace2 004child test-tool trace2 004child test-tool trace2 004child + * test-tool trace2 004child git -c alias.xyz=version xyz + * + * Test harness can confirm: + * [] the "name" field in the "cmd_name" trace2 event. + * [] that the outer process has a single component SID (or depth "d0" in + * the PERF stream). + * [] that "child_start" and "child_exit" events are generated for the child. + * [] if the child process is an instrumented executable: + * [] that "version", "start", ..., "exit", and "atexit" events are + * generated by the child process. + * [] that the child process events have a multiple component SID (or + * depth "dN+1" in the PERF stream). + * [] that the child exit code is propagated to the parent process "exit" + * and "atexit" events.. + * [] (optional) that the "t_abs" field in the child process "atexit" event + * is less than the "t_rel" field in the "child_exit" event of the parent + * process. + * [] if the child process is like the alias example above, + * [] (optional) the child process attempts to run "git-xyx" as a dashed + * command. + * [] the child process emits an "alias" event with "xyz" => "version" + * [] the child process runs "git version" as a child process. + * [] the child process has a 3 component SID (or depth "d2" in the PERF + * stream). + */ +static int ut_004child(int argc, const char **argv) +{ + int result; + + /* + * Allow empty <child_command_line> so we can do arbitrarily deep + * command nesting and let the last one be null. + */ + if (!argc) + return 0; + + result = run_command_v_opt(argv, 0); + exit(result); +} + +/* + * Exec a git command. This may either create a child process (Windows) + * or replace the existing process. + * test-tool trace2 005exec <git_command_args> + * + * For example: + * test-tool trace2 005exec version + * + * Test harness can confirm (on Windows): + * [] the "name" field in the "cmd_name" trace2 event. + * [] that the outer process has a single component SID (or depth "d0" in + * the PERF stream). + * [] that "exec" and "exec_result" events are generated for the child + * process (since the Windows compatibility layer fakes an exec() with + * a CreateProcess(), WaitForSingleObject(), and exit()). + * [] that the child process has multiple component SID (or depth "dN+1" + * in the PERF stream). + * + * Test harness can confirm (on platforms with a real exec() function): + * [] TODO talk about process replacement and how it affects SID. + */ +static int ut_005exec(int argc, const char **argv) +{ + int result; + + if (!argc) + return 0; + + result = execv_git_cmd(argv); + return result; +} + +static int ut_006data(int argc, const char **argv) +{ + const char *usage_error = + "expect <cat0> <k0> <v0> [<cat1> <k1> <v1> [...]]"; + + if (argc % 3 != 0) + die("%s", usage_error); + + while (argc) { + if (!argv[0] || !*argv[0] || !argv[1] || !*argv[1] || + !argv[2] || !*argv[2]) + die("%s", usage_error); + + trace2_data_string(argv[0], the_repository, argv[1], argv[2]); + argv += 3; + argc -= 3; + } + + return 0; +} + +static int ut_007bug(int argc, const char **argv) +{ + /* + * Exercise BUG() to ensure that the message is printed to trace2. + */ + BUG("the bug message"); +} + +/* + * Usage: + * test-tool trace2 <ut_name_1> <ut_usage_1> + * test-tool trace2 <ut_name_2> <ut_usage_2> + * ... + */ +#define USAGE_PREFIX "test-tool trace2" + +/* clang-format off */ +static struct unit_test ut_table[] = { + { ut_001return, "001return", "<exit_code>" }, + { ut_002exit, "002exit", "<exit_code>" }, + { ut_003error, "003error", "<error_message>+" }, + { ut_004child, "004child", "[<child_command_line>]" }, + { ut_005exec, "005exec", "<git_command_args>" }, + { ut_006data, "006data", "[<category> <key> <value>]+" }, + { ut_007bug, "007bug", "" }, +}; +/* clang-format on */ + +/* clang-format off */ +#define for_each_ut(k, ut_k) \ + for (k = 0, ut_k = &ut_table[k]; \ + k < ARRAY_SIZE(ut_table); \ + k++, ut_k = &ut_table[k]) +/* clang-format on */ + +static int print_usage(void) +{ + int k; + struct unit_test *ut_k; + + fprintf(stderr, "usage:\n"); + for_each_ut (k, ut_k) + fprintf(stderr, "\t%s %s %s\n", USAGE_PREFIX, ut_k->ut_name, + ut_k->ut_usage); + + return 129; +} + +/* + * Issue various trace2 events for testing. + * + * We assume that these trace2 routines has already been called: + * [] trace2_initialize() [common-main.c:main()] + * [] trace2_cmd_start() [common-main.c:main()] + * [] trace2_cmd_name() [test-tool.c:cmd_main()] + * [] tracd2_cmd_list_config() [test-tool.c:cmd_main()] + * So that: + * [] the various trace2 streams are open. + * [] the process SID has been created. + * [] the "version" event has been generated. + * [] the "start" event has been generated. + * [] the "cmd_name" event has been generated. + * [] this writes various "def_param" events for interesting config values. + * + * We further assume that if we return (rather than exit()), trace2_cmd_exit() + * will be called by test-tool.c:cmd_main(). + */ +int cmd__trace2(int argc, const char **argv) +{ + int k; + struct unit_test *ut_k; + + argc--; /* skip over "trace2" arg */ + argv++; + + if (argc) + for_each_ut (k, ut_k) + if (!strcmp(argv[0], ut_k->ut_name)) + return ut_k->ut_fn(argc - 1, argv + 1); + + return print_usage(); +} diff --git a/t/helper/test-urlmatch-normalization.c b/t/helper/test-urlmatch-normalization.c index 49b6e836be..8f4d67e646 100644 --- a/t/helper/test-urlmatch-normalization.c +++ b/t/helper/test-urlmatch-normalization.c @@ -1,9 +1,10 @@ +#include "test-tool.h" #include "git-compat-util.h" #include "urlmatch.h" -int cmd_main(int argc, const char **argv) +int cmd__urlmatch_normalization(int argc, const char **argv) { - const char usage[] = "test-urlmatch-normalization [-p | -l] <url1> | <url1> <url2>"; + const char usage[] = "test-tool urlmatch-normalization [-p | -l] <url1> | <url1> <url2>"; char *url1, *url2; int opt_p = 0, opt_l = 0; diff --git a/t/helper/test-userdiff.c b/t/helper/test-userdiff.c new file mode 100644 index 0000000000..f013f8a31e --- /dev/null +++ b/t/helper/test-userdiff.c @@ -0,0 +1,46 @@ +#include "test-tool.h" +#include "cache.h" +#include "userdiff.h" +#include "config.h" + +static int driver_cb(struct userdiff_driver *driver, + enum userdiff_driver_type type, void *priv) +{ + enum userdiff_driver_type *want_type = priv; + if (type & *want_type && driver->funcname.pattern) + puts(driver->name); + return 0; +} + +static int cmd__userdiff_config(const char *var, const char *value, void *cb) +{ + if (userdiff_config(var, value) < 0) + return -1; + return 0; +} + +int cmd__userdiff(int argc, const char **argv) +{ + enum userdiff_driver_type want = 0; + if (argc != 2) + return 1; + + if (!strcmp(argv[1], "list-drivers")) + want = (USERDIFF_DRIVER_TYPE_BUILTIN | + USERDIFF_DRIVER_TYPE_CUSTOM); + else if (!strcmp(argv[1], "list-builtin-drivers")) + want = USERDIFF_DRIVER_TYPE_BUILTIN; + else if (!strcmp(argv[1], "list-custom-drivers")) + want = USERDIFF_DRIVER_TYPE_CUSTOM; + else + return error("unknown argument %s", argv[1]); + + if (want & USERDIFF_DRIVER_TYPE_CUSTOM) { + setup_git_directory(); + git_config(cmd__userdiff_config, NULL); + } + + for_each_userdiff_driver(driver_cb, &want); + + return 0; +} diff --git a/t/helper/test-wildmatch.c b/t/helper/test-wildmatch.c index 921d7b3e7e..2c103d1824 100644 --- a/t/helper/test-wildmatch.c +++ b/t/helper/test-wildmatch.c @@ -1,6 +1,7 @@ +#include "test-tool.h" #include "cache.h" -int cmd_main(int argc, const char **argv) +int cmd__wildmatch(int argc, const char **argv) { int i; for (i = 2; i < argc; i++) { @@ -16,6 +17,8 @@ int cmd_main(int argc, const char **argv) return !!wildmatch(argv[3], argv[2], WM_PATHNAME | WM_CASEFOLD); else if (!strcmp(argv[1], "pathmatch")) return !!wildmatch(argv[3], argv[2], 0); + else if (!strcmp(argv[1], "ipathmatch")) + return !!wildmatch(argv[3], argv[2], WM_CASEFOLD); else return 1; } diff --git a/t/helper/test-windows-named-pipe.c b/t/helper/test-windows-named-pipe.c new file mode 100644 index 0000000000..ae52183e63 --- /dev/null +++ b/t/helper/test-windows-named-pipe.c @@ -0,0 +1,72 @@ +#include "test-tool.h" +#include "git-compat-util.h" +#include "strbuf.h" + +#ifdef GIT_WINDOWS_NATIVE +static const char *usage_string = "<pipe-filename>"; + +#define TEST_BUFSIZE (4096) + +int cmd__windows_named_pipe(int argc, const char **argv) +{ + const char *filename; + struct strbuf pathname = STRBUF_INIT; + int err; + HANDLE h; + BOOL connected; + char buf[TEST_BUFSIZE + 1]; + + if (argc < 2) + goto print_usage; + filename = argv[1]; + if (strpbrk(filename, "/\\")) + goto print_usage; + strbuf_addf(&pathname, "//./pipe/%s", filename); + + /* + * Create a single instance of the server side of the named pipe. + * This will allow exactly one client instance to connect to it. + */ + h = CreateNamedPipeA( + pathname.buf, + PIPE_ACCESS_INBOUND | FILE_FLAG_FIRST_PIPE_INSTANCE, + PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_WAIT, + PIPE_UNLIMITED_INSTANCES, + TEST_BUFSIZE, TEST_BUFSIZE, 0, NULL); + if (h == INVALID_HANDLE_VALUE) { + err = err_win_to_posix(GetLastError()); + fprintf(stderr, "CreateNamedPipe failed: %s\n", + strerror(err)); + return err; + } + + connected = ConnectNamedPipe(h, NULL) + ? TRUE + : (GetLastError() == ERROR_PIPE_CONNECTED); + if (!connected) { + err = err_win_to_posix(GetLastError()); + fprintf(stderr, "ConnectNamedPipe failed: %s\n", + strerror(err)); + CloseHandle(h); + return err; + } + + while (1) { + DWORD nbr; + BOOL success = ReadFile(h, buf, TEST_BUFSIZE, &nbr, NULL); + if (!success || nbr == 0) + break; + buf[nbr] = 0; + + write(1, buf, nbr); + } + + DisconnectNamedPipe(h); + CloseHandle(h); + return 0; + +print_usage: + fprintf(stderr, "usage: %s %s\n", argv[0], usage_string); + return 1; +} +#endif diff --git a/t/helper/test-write-cache.c b/t/helper/test-write-cache.c index b7ee039669..8837717d36 100644 --- a/t/helper/test-write-cache.c +++ b/t/helper/test-write-cache.c @@ -1,22 +1,19 @@ +#include "test-tool.h" #include "cache.h" #include "lockfile.h" -static struct lock_file index_lock; - -int cmd_main(int argc, const char **argv) +int cmd__write_cache(int argc, const char **argv) { - int i, cnt = 1, lockfd; + struct lock_file index_lock = LOCK_INIT; + int i, cnt = 1; if (argc == 2) cnt = strtol(argv[1], NULL, 0); setup_git_directory(); read_cache(); for (i = 0; i < cnt; i++) { - lockfd = hold_locked_index(&index_lock, LOCK_DIE_ON_ERROR); - if (0 <= lockfd) { - write_locked_index(&the_index, &index_lock, COMMIT_LOCK); - } else { - rollback_lock_file(&index_lock); - } + hold_locked_index(&index_lock, LOCK_DIE_ON_ERROR); + if (write_locked_index(&the_index, &index_lock, COMMIT_LOCK)) + die("unable to write index file"); } return 0; diff --git a/t/helper/test-xml-encode.c b/t/helper/test-xml-encode.c new file mode 100644 index 0000000000..a648bbd961 --- /dev/null +++ b/t/helper/test-xml-encode.c @@ -0,0 +1,80 @@ +#include "test-tool.h" + +static const char *utf8_replace_character = "�"; + +/* + * Encodes (possibly incorrect) UTF-8 on <stdin> to <stdout>, to be embedded + * in an XML file. + */ +int cmd__xml_encode(int argc, const char **argv) +{ + unsigned char buf[1024], tmp[4], *tmp2 = NULL; + ssize_t cur = 0, len = 1, remaining = 0; + unsigned char ch; + + for (;;) { + if (++cur == len) { + len = xread(0, buf, sizeof(buf)); + if (!len) + return 0; + if (len < 0) + die_errno("Could not read <stdin>"); + cur = 0; + } + ch = buf[cur]; + + if (tmp2) { + if ((ch & 0xc0) != 0x80) { + fputs(utf8_replace_character, stdout); + tmp2 = NULL; + cur--; + continue; + } + *tmp2 = ch; + tmp2++; + if (--remaining == 0) { + fwrite(tmp, tmp2 - tmp, 1, stdout); + tmp2 = NULL; + } + continue; + } + + if (!(ch & 0x80)) { + /* 0xxxxxxx */ + if (ch == '&') + fputs("&", stdout); + else if (ch == '\'') + fputs("'", stdout); + else if (ch == '"') + fputs(""", stdout); + else if (ch == '<') + fputs("<", stdout); + else if (ch == '>') + fputs(">", stdout); + else if (ch >= 0x20) + fputc(ch, stdout); + else if (ch == 0x09 || ch == 0x0a || ch == 0x0d) + fprintf(stdout, "&#x%02x;", ch); + else + fputs(utf8_replace_character, stdout); + } else if ((ch & 0xe0) == 0xc0) { + /* 110XXXXx 10xxxxxx */ + tmp[0] = ch; + remaining = 1; + tmp2 = tmp + 1; + } else if ((ch & 0xf0) == 0xe0) { + /* 1110XXXX 10Xxxxxx 10xxxxxx */ + tmp[0] = ch; + remaining = 2; + tmp2 = tmp + 1; + } else if ((ch & 0xf8) == 0xf0) { + /* 11110XXX 10XXxxxx 10xxxxxx 10xxxxxx */ + tmp[0] = ch; + remaining = 3; + tmp2 = tmp + 1; + } else + fputs(utf8_replace_character, stdout); + } + + return 0; +} |