diff options
Diffstat (limited to 't/helper')
84 files changed, 10532 insertions, 0 deletions
diff --git a/t/helper/.gitignore b/t/helper/.gitignore new file mode 100644 index 0000000000..8c2ddcce95 --- /dev/null +++ b/t/helper/.gitignore @@ -0,0 +1,2 @@ +/test-tool +/test-fake-ssh diff --git a/t/helper/meson.build b/t/helper/meson.build new file mode 100644 index 0000000000..675e64c010 --- /dev/null +++ b/t/helper/meson.build @@ -0,0 +1,95 @@ +test_tool_sources = [ + '../unit-tests/test-lib.c', + 'test-advise.c', + 'test-bitmap.c', + 'test-bloom.c', + 'test-bundle-uri.c', + 'test-cache-tree.c', + 'test-chmtime.c', + 'test-config.c', + 'test-crontab.c', + 'test-csprng.c', + 'test-date.c', + 'test-delete-gpgsig.c', + 'test-delta.c', + 'test-dir-iterator.c', + 'test-drop-caches.c', + 'test-dump-cache-tree.c', + 'test-dump-fsmonitor.c', + 'test-dump-split-index.c', + 'test-dump-untracked-cache.c', + 'test-env-helper.c', + 'test-example-tap.c', + 'test-find-pack.c', + 'test-fsmonitor-client.c', + 'test-genrandom.c', + 'test-genzeros.c', + 'test-getcwd.c', + 'test-hash-speed.c', + 'test-hash.c', + 'test-hashmap.c', + 'test-hexdump.c', + 'test-json-writer.c', + 'test-lazy-init-name-hash.c', + 'test-match-trees.c', + 'test-mergesort.c', + 'test-mktemp.c', + 'test-name-hash.c', + 'test-online-cpus.c', + 'test-pack-deltas.c', + 'test-pack-mtimes.c', + 'test-parse-options.c', + 'test-parse-pathspec-file.c', + 'test-partial-clone.c', + 'test-path-utils.c', + 'test-path-walk.c', + 'test-pcre2-config.c', + 'test-pkt-line.c', + 'test-proc-receive.c', + 'test-progress.c', + 'test-reach.c', + 'test-read-cache.c', + 'test-read-graph.c', + 'test-read-midx.c', + 'test-ref-store.c', + 'test-reftable.c', + 'test-regex.c', + 'test-repository.c', + 'test-revision-walking.c', + 'test-rot13-filter.c', + 'test-run-command.c', + 'test-scrap-cache-tree.c', + 'test-serve-v2.c', + 'test-sha1.c', + 'test-sha256.c', + 'test-sigchain.c', + 'test-simple-ipc.c', + 'test-string-list.c', + 'test-submodule-config.c', + 'test-submodule-nested-repo-config.c', + 'test-submodule.c', + 'test-subprocess.c', + 'test-tool.c', + 'test-trace2.c', + 'test-truncate.c', + 'test-userdiff.c', + 'test-wildmatch.c', + 'test-windows-named-pipe.c', + 'test-write-cache.c', + 'test-xml-encode.c', + 'test-zlib.c', +] + +test_tool = executable('test-tool', + sources: test_tool_sources, + dependencies: [libgit_commonmain], +) +bin_wrappers += test_tool +test_dependencies += test_tool + +test_fake_ssh = executable('test-fake-ssh', + sources: 'test-fake-ssh.c', + dependencies: [libgit_commonmain], +) +bin_wrappers += test_fake_ssh +test_dependencies += test_fake_ssh diff --git a/t/helper/test-advise.c b/t/helper/test-advise.c new file mode 100644 index 0000000000..81ed93a05c --- /dev/null +++ b/t/helper/test-advise.c @@ -0,0 +1,25 @@ +#define USE_THE_REPOSITORY_VARIABLE + +#include "test-tool.h" +#include "advice.h" +#include "config.h" +#include "environment.h" +#include "setup.h" + +int cmd__advise_if_enabled(int argc, const char **argv) +{ + if (argc != 2) + die("usage: %s <advice>", argv[0]); + + setup_git_directory(); + repo_config(the_repository, 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, "%s", argv[1]); + + return 0; +} diff --git a/t/helper/test-bitmap.c b/t/helper/test-bitmap.c new file mode 100644 index 0000000000..16a01669e4 --- /dev/null +++ b/t/helper/test-bitmap.c @@ -0,0 +1,63 @@ +#define USE_THE_REPOSITORY_VARIABLE + +#include "test-tool.h" +#include "git-compat-util.h" +#include "pack-bitmap.h" +#include "setup.h" + +static int bitmap_list_commits(void) +{ + return test_bitmap_commits(the_repository); +} + +static int bitmap_list_commits_with_offset(void) +{ + return test_bitmap_commits_with_offset(the_repository); +} + +static int bitmap_dump_hashes(void) +{ + return test_bitmap_hashes(the_repository); +} + +static int bitmap_dump_pseudo_merges(void) +{ + return test_bitmap_pseudo_merges(the_repository); +} + +static int bitmap_dump_pseudo_merge_commits(uint32_t n) +{ + return test_bitmap_pseudo_merge_commits(the_repository, n); +} + +static int bitmap_dump_pseudo_merge_objects(uint32_t n) +{ + return test_bitmap_pseudo_merge_objects(the_repository, n); +} + +int cmd__bitmap(int argc, const char **argv) +{ + setup_git_directory(); + + if (argc == 2 && !strcmp(argv[1], "list-commits")) + return bitmap_list_commits(); + if (argc == 2 && !strcmp(argv[1], "list-commits-with-offset")) + return bitmap_list_commits_with_offset(); + if (argc == 2 && !strcmp(argv[1], "dump-hashes")) + return bitmap_dump_hashes(); + if (argc == 2 && !strcmp(argv[1], "dump-pseudo-merges")) + return bitmap_dump_pseudo_merges(); + if (argc == 3 && !strcmp(argv[1], "dump-pseudo-merge-commits")) + return bitmap_dump_pseudo_merge_commits(atoi(argv[2])); + if (argc == 3 && !strcmp(argv[1], "dump-pseudo-merge-objects")) + return bitmap_dump_pseudo_merge_objects(atoi(argv[2])); + + usage("\ttest-tool bitmap list-commits\n" + "\ttest-tool bitmap list-commits-with-offset\n" + "\ttest-tool bitmap dump-hashes\n" + "\ttest-tool bitmap dump-pseudo-merges\n" + "\ttest-tool bitmap dump-pseudo-merge-commits <n>\n" + "\ttest-tool bitmap dump-pseudo-merge-objects <n>"); + + return -1; +} diff --git a/t/helper/test-bloom.c b/t/helper/test-bloom.c new file mode 100644 index 0000000000..3283544bd3 --- /dev/null +++ b/t/helper/test-bloom.c @@ -0,0 +1,104 @@ +#define USE_THE_REPOSITORY_VARIABLE + +#include "test-tool.h" +#include "bloom.h" +#include "hex.h" +#include "commit.h" +#include "repository.h" +#include "setup.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; + + bloom_key_fill(&key, data, strlen(data), &settings); + printf("Hashes:"); + for (size_t i = 0; i < settings.num_hashes; i++) + printf("0x%08x|", key.hashes[i]); + printf("\n"); + add_key_to_filter(&key, filter, &settings); + bloom_key_clear(&key); +} + +static void print_bloom_filter(struct bloom_filter *filter) { + if (!filter) { + printf("No filter.\n"); + return; + } + printf("Filter_Length:%d\n", (int)filter->len); + printf("Filter_Data:"); + for (size_t 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; + 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 *const bloom_usage = "\n" +" test-tool bloom get_murmur3 <string>\n" +" test-tool bloom get_murmur3_seven_highbit\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 = test_bloom_murmur3_seeded(0, argv[2], strlen(argv[2]), 2); + printf("Murmur3 Hash with seed=0:0x%08x\n", hashed); + } + + if (!strcmp(argv[1], "get_murmur3_seven_highbit")) { + uint32_t hashed; + hashed = test_bloom_murmur3_seeded(0, "\x99\xaa\xbb\xcc\xdd\xee\xff", 7, 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); + free(filter.data); + } + + 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-bundle-uri.c b/t/helper/test-bundle-uri.c new file mode 100644 index 0000000000..0c5fa723d8 --- /dev/null +++ b/t/helper/test-bundle-uri.c @@ -0,0 +1,133 @@ +#include "test-tool.h" +#include "parse-options.h" +#include "bundle-uri.h" +#include "gettext.h" +#include "strbuf.h" +#include "string-list.h" +#include "transport.h" +#include "remote.h" + +enum input_mode { + KEY_VALUE_PAIRS, + CONFIG_FILE, +}; + +static int cmd__bundle_uri_parse(int argc, const char **argv, enum input_mode mode) +{ + const char *key_value_usage[] = { + "test-tool bundle-uri parse-key-values <input>", + NULL + }; + const char *config_usage[] = { + "test-tool bundle-uri parse-config <input>", + NULL + }; + const char **usage = key_value_usage; + struct option options[] = { + OPT_END(), + }; + struct strbuf sb = STRBUF_INIT; + struct bundle_list list; + int err = 0; + FILE *fp; + + if (mode == CONFIG_FILE) + usage = config_usage; + + argc = parse_options(argc, argv, NULL, options, usage, + PARSE_OPT_STOP_AT_NON_OPTION); + + init_bundle_list(&list); + + list.baseURI = xstrdup("<uri>"); + + switch (mode) { + case KEY_VALUE_PAIRS: + if (argc != 1) + goto usage; + fp = fopen(argv[0], "r"); + if (!fp) + die("failed to open '%s'", argv[0]); + while (strbuf_getline(&sb, fp) != EOF) { + if (bundle_uri_parse_line(&list, sb.buf)) + err = error("bad line: '%s'", sb.buf); + } + fclose(fp); + break; + + case CONFIG_FILE: + if (argc != 1) + goto usage; + err = bundle_uri_parse_config_format("<uri>", argv[0], &list); + break; + } + strbuf_release(&sb); + + print_bundle_list(stdout, &list); + + clear_bundle_list(&list); + + return !!err; + +usage: + usage_with_options(usage, options); +} + +static int cmd_ls_remote(int argc, const char **argv) +{ + const char *dest; + struct remote *remote; + struct transport *transport; + int status = 0; + + dest = argc > 1 ? argv[1] : NULL; + + remote = remote_get(dest); + if (!remote) { + if (dest) + die(_("bad repository '%s'"), dest); + die(_("no remote configured to get bundle URIs from")); + } + + transport = transport_get(remote, NULL); + if (transport_get_remote_bundle_uri(transport) < 0) { + error(_("could not get the bundle-uri list")); + status = 1; + goto cleanup; + } + + print_bundle_list(stdout, transport->bundles); + +cleanup: + if (transport_disconnect(transport)) + return 1; + return status; +} + +int cmd__bundle_uri(int argc, const char **argv) +{ + const char *usage[] = { + "test-tool bundle-uri <subcommand> [<options>]", + NULL + }; + struct option options[] = { + OPT_END(), + }; + + argc = parse_options(argc, argv, NULL, options, usage, + PARSE_OPT_STOP_AT_NON_OPTION | + PARSE_OPT_KEEP_ARGV0); + if (argc == 1) + goto usage; + + if (!strcmp(argv[1], "parse-key-values")) + return cmd__bundle_uri_parse(argc - 1, argv + 1, KEY_VALUE_PAIRS); + if (!strcmp(argv[1], "parse-config")) + return cmd__bundle_uri_parse(argc - 1, argv + 1, CONFIG_FILE); + if (!strcmp(argv[1], "ls-remote")) + return cmd_ls_remote(argc - 1, argv + 1); + error("there is no test-tool bundle-uri tool '%s'", argv[1]); + +usage: + usage_with_options(usage, options); +} diff --git a/t/helper/test-cache-tree.c b/t/helper/test-cache-tree.c new file mode 100644 index 0000000000..3ae45cec3b --- /dev/null +++ b/t/helper/test-cache-tree.c @@ -0,0 +1,71 @@ +#define USE_THE_REPOSITORY_VARIABLE +#define DISABLE_SIGN_COMPARE_WARNINGS + +#include "test-tool.h" +#include "gettext.h" +#include "hex.h" +#include "tree.h" +#include "cache-tree.h" +#include "parse-options.h" +#include "read-cache-ll.h" +#include "repository.h" +#include "setup.h" + +static char const * const test_cache_tree_usage[] = { + N_("test-tool cache-tree <options> (control|prime|update)"), + NULL +}; + +int cmd__cache_tree(int argc, const char **argv) +{ + struct object_id oid; + struct tree *tree; + int empty = 0; + int invalidate_qty = 0; + int i; + + struct option options[] = { + OPT_BOOL(0, "empty", &empty, + N_("clear the cache tree before each iteration")), + OPT_INTEGER_F(0, "invalidate", &invalidate_qty, + N_("number of entries in the cache tree to invalidate (default 0)"), + PARSE_OPT_NONEG), + OPT_END() + }; + + setup_git_directory(); + + argc = parse_options(argc, argv, NULL, options, test_cache_tree_usage, 0); + + if (repo_read_index(the_repository) < 0) + die(_("unable to read index file")); + + oidcpy(&oid, &the_repository->index->cache_tree->oid); + tree = parse_tree_indirect(&oid); + if (!tree) + die(_("not a tree object: %s"), oid_to_hex(&oid)); + + if (empty) { + /* clear the cache tree & allocate a new one */ + cache_tree_free(&the_repository->index->cache_tree); + the_repository->index->cache_tree = cache_tree(); + } else if (invalidate_qty) { + /* invalidate the specified number of unique paths */ + float f_interval = (float)the_repository->index->cache_nr / invalidate_qty; + int interval = f_interval < 1.0 ? 1 : (int)f_interval; + for (i = 0; i < invalidate_qty && i * interval < the_repository->index->cache_nr; i++) + cache_tree_invalidate_path(the_repository->index, the_repository->index->cache[i * interval]->name); + } + + if (argc != 1) + usage_with_options(test_cache_tree_usage, options); + else if (!strcmp(argv[0], "prime")) + prime_cache_tree(the_repository, the_repository->index, tree); + else if (!strcmp(argv[0], "update")) + cache_tree_update(the_repository->index, WRITE_TREE_SILENT | WRITE_TREE_REPAIR); + /* use "control" subcommand to specify no-op */ + else if (!!strcmp(argv[0], "control")) + die(_("Unhandled subcommand '%s'"), argv[0]); + + return 0; +} diff --git a/t/helper/test-chmtime.c b/t/helper/test-chmtime.c new file mode 100644 index 0000000000..0e5538833a --- /dev/null +++ b/t/helper/test-chmtime.c @@ -0,0 +1,163 @@ +/* + * This program can either change modification time of the given + * file(s) or just print it. The program does not change atime or + * ctime (their values are explicitly preserved). + * + * The mtime can be changed to an absolute value: + * + * test-tool chmtime =<seconds> file... + * + * Relative to the current time as returned by time(3): + * + * test-tool chmtime =+<seconds> (or =-<seconds>) file... + * + * Or relative to the current mtime of the file: + * + * test-tool chmtime <seconds> file... + * test-tool chmtime +<seconds> (or -<seconds>) file... + * + * Examples: + * + * To print the mtime and the file name use --verbose and set + * the file mtime offset to 0: + * + * 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-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|-g|--get) (+|=|=+|=-|-)<seconds> <file>..."; + +static int timespec_arg(const char *arg, long int *set_time, int *set_eq) +{ + char *test; + const char *timespec = arg; + *set_eq = (*timespec == '=') ? 1 : 0; + if (*set_eq) { + timespec++; + if (*timespec == '+') { + *set_eq = 2; /* relative "in the future" */ + timespec++; + } + } + *set_time = strtol(timespec, &test, 10); + if (*test) { + return 0; + } + if ((*set_eq && *set_time < 0) || *set_eq == 2) { + time_t now = time(NULL); + *set_time += now; + } + return 1; +} + +int cmd__chmtime(int argc, const char **argv) +{ + static int verbose; + static int get; + + int i = 1; + /* no mtime change by default */ + int set_eq = 0; + long int set_time = 0; + + if (argc < 3) + goto usage; + + 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 (i == argc) { + goto usage; + } + + if (timespec_arg(argv[i], &set_time, &set_eq)) { + ++i; + } else { + if (get == 0 && verbose == 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. Skipping\n", + argv[i], strerror(errno)); + continue; + } + +#ifdef GIT_WINDOWS_NATIVE + if (!(sb.st_mode & S_IWUSR) && + chmod(argv[i], sb.st_mode | S_IWUSR)) { + fprintf(stderr, "Could not make user-writable %s: %s", + argv[i], strerror(errno)); + return 1; + } +#endif + + utb.actime = sb.st_atime; + utb.modtime = set_eq ? set_time : sb.st_mtime + set_time; + + mtime = utb.modtime < 0 ? 0: utb.modtime; + if (get) { + printf("%"PRIuMAX"\n", mtime); + } else if (verbose) { + printf("%"PRIuMAX"\t%s\n", mtime, argv[i]); + } + + if (utb.modtime != sb.st_mtime && utime(argv[i], &utb) < 0) { +#ifdef GIT_WINDOWS_NATIVE + if (S_ISDIR(sb.st_mode)) { + /* + * NEEDSWORK: The Windows version of `utime()` + * (aka `mingw_utime()`) does not correctly + * handle directory arguments, since it uses + * `_wopen()`. Ignore it for now since this + * is just a test. + */ + fprintf(stderr, + ("Failed to modify time on directory %s. " + "Skipping\n"), argv[i]); + continue; + } +#endif + fprintf(stderr, "Failed to modify time on %s: %s\n", + argv[i], strerror(errno)); + return 1; + } + } + + return 0; + +usage: + fprintf(stderr, "usage: %s %s\n", argv[0], usage_str); + return 1; +} diff --git a/t/helper/test-config.c b/t/helper/test-config.c new file mode 100644 index 0000000000..9f8cca7c48 --- /dev/null +++ b/t/helper/test-config.c @@ -0,0 +1,241 @@ +#define USE_THE_REPOSITORY_VARIABLE +#define DISABLE_SIGN_COMPARE_WARNINGS + +#include "test-tool.h" +#include "config.h" +#include "setup.h" +#include "string-list.h" + +/* + * This program exposes the C API of the configuration mechanism + * as a set of simple commands in order to facilitate testing. + * + * Reads stdin and prints result of command to stdout: + * + * get_value -> prints the value with highest priority for the entered key + * + * get_value_multi -> prints all values for the entered key in increasing order + * of priority + * + * get -> print return value for the entered key + * + * get_int -> print integer value for the entered key or die + * + * get_bool -> print bool value for the entered key or die + * + * get_string -> print string value for the entered key or die + * + * configset_get_value -> returns value with the highest priority for the entered key + * from a config_set constructed from files entered as arguments. + * + * configset_get_value_multi -> returns value_list for the entered key sorted in + * ascending order of priority from a config_set + * constructed from files entered as arguments. + * + * iterate -> iterate over all values using repo_config(), and print some + * data for each + * + * git_config_int -> iterate over all values using repo_config() and print the + * integer value for the entered key or die + * + * Examples: + * + * To print the value with highest priority for key "foo.bAr Baz.rock": + * test-tool config get_value "foo.bAr Baz.rock" + * + */ + +static int iterate_cb(const char *var, const char *value, + const struct config_context *ctx, + void *data UNUSED) +{ + const struct key_value_info *kvi = ctx->kvi; + static int nr; + + if (nr++) + putchar('\n'); + + printf("key=%s\n", var); + printf("value=%s\n", value ? value : "(null)"); + printf("origin=%s\n", config_origin_type_name(kvi->origin_type)); + printf("name=%s\n", kvi->filename ? kvi->filename : ""); + printf("lno=%d\n", kvi->linenr); + printf("scope=%s\n", config_scope_name(kvi->scope)); + + return 0; +} + +static int parse_int_cb(const char *var, const char *value, + const struct config_context *ctx, void *data) +{ + const char *key_to_match = data; + + if (!strcmp(key_to_match, var)) { + int parsed = git_config_int(value, value, ctx->kvi); + printf("%d\n", parsed); + } + return 0; +} + +static int early_config_cb(const char *var, const char *value, + const struct config_context *ctx UNUSED, + void *vdata) +{ + const char *key = vdata; + + if (!strcmp(key, var)) + printf("%s\n", value); + + return 0; +} + +int cmd__config(int argc, const char **argv) +{ + int i, val; + const char *v; + const struct string_list *strptr; + struct config_set cs; + + if (argc == 3 && !strcmp(argv[1], "read_early_config")) { + read_early_config(the_repository, early_config_cb, + (void *)argv[2]); + return 0; + } + + setup_git_directory(); + + git_configset_init(&cs); + + if (argc < 2) { + fprintf(stderr, "Please, provide a command name on the command-line\n"); + goto exit1; + } else if (argc == 3 && !strcmp(argv[1], "get_value")) { + if (!repo_config_get_value(the_repository, argv[2], &v)) { + if (!v) + printf("(NULL)\n"); + else + printf("%s\n", v); + goto exit0; + } else { + printf("Value not found for \"%s\"\n", argv[2]); + goto exit1; + } + } else if (argc == 3 && !strcmp(argv[1], "get_value_multi")) { + if (!repo_config_get_value_multi(the_repository, argv[2], &strptr)) { + for (i = 0; i < strptr->nr; i++) { + v = strptr->items[i].string; + if (!v) + printf("(NULL)\n"); + else + printf("%s\n", v); + } + goto exit0; + } else { + printf("Value not found for \"%s\"\n", argv[2]); + goto exit1; + } + } else if (argc == 3 && !strcmp(argv[1], "get")) { + int ret; + + if (!(ret = repo_config_get(the_repository, argv[2]))) + goto exit0; + else if (ret == 1) + printf("Value not found for \"%s\"\n", argv[2]); + else if (ret == -CONFIG_INVALID_KEY) + printf("Key \"%s\" is invalid\n", argv[2]); + else if (ret == -CONFIG_NO_SECTION_OR_NAME) + printf("Key \"%s\" has no section\n", argv[2]); + else + /* + * A normal caller should just check "ret < + * 0", but for our own tests let's BUG() if + * our whitelist of git_config_parse_key() + * return values isn't exhaustive. + */ + BUG("Key \"%s\" has unknown return %d", argv[2], ret); + goto exit1; + } else if (argc == 3 && !strcmp(argv[1], "get_int")) { + if (!repo_config_get_int(the_repository, argv[2], &val)) { + printf("%d\n", val); + goto exit0; + } else { + printf("Value not found for \"%s\"\n", argv[2]); + goto exit1; + } + } else if (argc == 3 && !strcmp(argv[1], "get_bool")) { + if (!repo_config_get_bool(the_repository, argv[2], &val)) { + printf("%d\n", val); + goto exit0; + } else { + printf("Value not found for \"%s\"\n", argv[2]); + goto exit1; + } + } else if (argc == 3 && !strcmp(argv[1], "get_string")) { + if (!repo_config_get_string_tmp(the_repository, argv[2], &v)) { + printf("%s\n", v); + goto exit0; + } else { + printf("Value not found for \"%s\"\n", argv[2]); + goto exit1; + } + } else if (!strcmp(argv[1], "configset_get_value")) { + for (i = 3; i < argc; i++) { + int err; + if ((err = git_configset_add_file(&cs, argv[i]))) { + fprintf(stderr, "Error (%d) reading configuration file %s.\n", err, argv[i]); + goto exit2; + } + } + if (!git_configset_get_value(&cs, argv[2], &v, NULL)) { + if (!v) + printf("(NULL)\n"); + else + printf("%s\n", v); + goto exit0; + } else { + printf("Value not found for \"%s\"\n", argv[2]); + goto exit1; + } + } else if (!strcmp(argv[1], "configset_get_value_multi")) { + for (i = 3; i < argc; i++) { + int err; + if ((err = git_configset_add_file(&cs, argv[i]))) { + fprintf(stderr, "Error (%d) reading configuration file %s.\n", err, argv[i]); + goto exit2; + } + } + if (!git_configset_get_value_multi(&cs, argv[2], &strptr)) { + for (i = 0; i < strptr->nr; i++) { + v = strptr->items[i].string; + if (!v) + printf("(NULL)\n"); + else + printf("%s\n", v); + } + goto exit0; + } else { + printf("Value not found for \"%s\"\n", argv[2]); + goto exit1; + } + } else if (!strcmp(argv[1], "iterate")) { + repo_config(the_repository, iterate_cb, NULL); + goto exit0; + } else if (argc == 3 && !strcmp(argv[1], "git_config_int")) { + repo_config(the_repository, parse_int_cb, (void *) argv[2]); + goto exit0; + } + + die("%s: Please check the syntax and the function name", argv[0]); + +exit0: + git_configset_clear(&cs); + return 0; + +exit1: + git_configset_clear(&cs); + return 1; + +exit2: + git_configset_clear(&cs); + return 2; +} diff --git a/t/helper/test-crontab.c b/t/helper/test-crontab.c new file mode 100644 index 0000000000..597027a96e --- /dev/null +++ b/t/helper/test-crontab.c @@ -0,0 +1,35 @@ +#include "test-tool.h" + +/* + * Usage: test-tool crontab <file> -l|<input> + * + * If -l is specified, then write the contents of <file> to stdout. + * Otherwise, copy the contents of <input> into <file>. + */ +int cmd__crontab(int argc, const char **argv) +{ + int a; + FILE *from, *to; + + if (argc != 3) + usage("test-tool crontab <file> -l|<input>"); + + if (!strcmp(argv[2], "-l")) { + from = fopen(argv[1], "r"); + if (!from) + return 0; + to = stdout; + } else { + from = xfopen(argv[2], "r"); + to = xfopen(argv[1], "w"); + } + + while ((a = fgetc(from)) != EOF) + fputc(a, to); + + fclose(from); + if (to != stdout) + fclose(to); + + return 0; +} diff --git a/t/helper/test-csprng.c b/t/helper/test-csprng.c new file mode 100644 index 0000000000..c86dcc4870 --- /dev/null +++ b/t/helper/test-csprng.c @@ -0,0 +1,28 @@ +#include "test-tool.h" +#include "git-compat-util.h" + +int cmd__csprng(int argc, const char **argv) +{ + unsigned long count; + unsigned char buf[1024]; + + if (argc > 2) { + fprintf(stderr, "usage: %s [<size>]\n", argv[0]); + return 2; + } + + count = (argc == 2) ? strtoul(argv[1], NULL, 0) : ULONG_MAX; + + while (count) { + unsigned long chunk = count < sizeof(buf) ? count : sizeof(buf); + if (csprng_bytes(buf, chunk, 0) < 0) { + perror("failed to read"); + return 5; + } + if (fwrite(buf, chunk, 1, stdout) != chunk) + return 1; + count -= chunk; + } + + return 0; +} diff --git a/t/helper/test-date.c b/t/helper/test-date.c new file mode 100644 index 0000000000..87d2ad6fca --- /dev/null +++ b/t/helper/test-date.c @@ -0,0 +1,135 @@ +#include "test-tool.h" +#include "date.h" +#include "trace.h" + +static const char *const usage_msg = "\n" +" 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, &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 = DATE_MODE_INIT; + + parse_date_format(format, &mode); + for (; *argv; argv++) { + char *arg; + timestamp_t t; + int tz; + + /* + * Do not use our normal timestamp parsing here, as the point + * is to test the formatting code in isolation. + */ + t = parse_timestamp(*argv, &arg, 10); + while (*arg == ' ') + arg++; + tz = atoi(arg); + + printf("%s -> %s\n", *argv, show_date(t, tz, mode)); + } + + date_mode_release(&mode); +} + +static void parse_dates(const char **argv) +{ + struct strbuf result = STRBUF_INIT; + + for (; *argv; argv++) { + timestamp_t t; + int tz; + + strbuf_reset(&result); + parse_date(*argv, &result); + if (sscanf(result.buf, "%"PRItime" %d", &t, &tz) == 2) + printf("%s -> %s\n", + *argv, show_date(t, tz, DATE_MODE(ISO8601))); + else + printf("%s -> bad\n", *argv); + } + strbuf_release(&result); +} + +static void parse_approxidate(const char **argv) +{ + for (; *argv; argv++) { + timestamp_t t; + t = approxidate(*argv); + printf("%s -> %s\n", *argv, show_date(t, 0, DATE_MODE(ISO8601))); + } +} + +static void parse_approx_timestamp(const char **argv) +{ + for (; *argv; argv++) { + timestamp_t t; + t = approxidate(*argv); + printf("%s -> %"PRItime"\n", *argv, t); + } +} + +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 UNUSED, const char **argv) +{ + const char *x; + + argv++; + if (!*argv) + usage(usage_msg); + if (!strcmp(*argv, "relative")) + 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); + else if (!strcmp(*argv, "approxidate")) + 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")) + return sizeof(time_t) == 8 ? 0 : 1; + else + usage(usage_msg); + return 0; +} diff --git a/t/helper/test-delete-gpgsig.c b/t/helper/test-delete-gpgsig.c new file mode 100644 index 0000000000..e36831af03 --- /dev/null +++ b/t/helper/test-delete-gpgsig.c @@ -0,0 +1,62 @@ +#include "test-tool.h" +#include "gpg-interface.h" +#include "strbuf.h" + + +int cmd__delete_gpgsig(int argc, const char **argv) +{ + struct strbuf buf = STRBUF_INIT; + const char *pattern = "gpgsig"; + const char *bufptr, *tail, *eol; + int deleting = 0; + size_t plen; + + if (argc >= 2) { + pattern = argv[1]; + argv++; + argc--; + } + + plen = strlen(pattern); + strbuf_read(&buf, 0, 0); + + if (!strcmp(pattern, "trailer")) { + size_t payload_size = parse_signed_buffer(buf.buf, buf.len); + fwrite(buf.buf, 1, payload_size, stdout); + fflush(stdout); + return 0; + } + + bufptr = buf.buf; + tail = bufptr + buf.len; + + while (bufptr < tail) { + /* Find the end of the line */ + eol = memchr(bufptr, '\n', tail - bufptr); + if (!eol) + eol = tail; + + /* Drop continuation lines */ + if (deleting && (bufptr < eol) && (bufptr[0] == ' ')) { + bufptr = eol + 1; + continue; + } + deleting = 0; + + /* Does the line match the prefix? */ + if (((bufptr + plen) < eol) && + !memcmp(bufptr, pattern, plen) && + (bufptr[plen] == ' ')) { + deleting = 1; + bufptr = eol + 1; + continue; + } + + /* Print all other lines */ + fwrite(bufptr, 1, (eol - bufptr) + 1, stdout); + bufptr = eol + 1; + } + fflush(stdout); + + return 0; +} diff --git a/t/helper/test-delta.c b/t/helper/test-delta.c new file mode 100644 index 0000000000..52ea00c937 --- /dev/null +++ b/t/helper/test-delta.c @@ -0,0 +1,56 @@ +/* + * test-delta.c: test code to exercise diff-delta.c and patch-delta.c + * + * (C) 2005 Nicolas Pitre <nico@fluxnic.net> + * + * This code is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include "test-tool.h" +#include "git-compat-util.h" +#include "delta.h" +#include "strbuf.h" + +static const char usage_str[] = + "test-tool delta (-d|-p) <from_file> <data_file> <out_file>"; + +int cmd__delta(int argc, const char **argv) +{ + int fd; + struct strbuf from = STRBUF_INIT, data = STRBUF_INIT; + char *out_buf; + unsigned long out_size; + + if (argc != 5 || (strcmp(argv[1], "-d") && strcmp(argv[1], "-p"))) + usage(usage_str); + + if (strbuf_read_file(&from, argv[2], 0) < 0) + die_errno("unable to read '%s'", argv[2]); + if (strbuf_read_file(&data, argv[3], 0) < 0) + die_errno("unable to read '%s'", argv[3]); + + if (argv[1][1] == 'd') + out_buf = diff_delta(from.buf, from.len, + data.buf, data.len, + &out_size, 0); + else + out_buf = patch_delta(from.buf, from.len, + data.buf, data.len, + &out_size); + if (!out_buf) + die("delta operation failed (returned NULL)"); + + fd = xopen(argv[4], O_WRONLY|O_CREAT|O_TRUNC, 0666); + if (write_in_full(fd, out_buf, out_size) < 0) + die_errno("write(%s)", argv[4]); + if (close(fd) < 0) + die_errno("close(%s)", argv[4]); + + strbuf_release(&from); + strbuf_release(&data); + free(out_buf); + + return 0; +} diff --git a/t/helper/test-dir-iterator.c b/t/helper/test-dir-iterator.c new file mode 100644 index 0000000000..8d46e8ba40 --- /dev/null +++ b/t/helper/test-dir-iterator.c @@ -0,0 +1,64 @@ +#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 [--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, "--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); + } + dir_iterator_free(diter); + + 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..7055d94354 --- /dev/null +++ b/t/helper/test-drop-caches.c @@ -0,0 +1,164 @@ +#define DISABLE_SIGN_COMPARE_WARNINGS + +#include "test-tool.h" +#include "git-compat-util.h" + +#if defined(GIT_WINDOWS_NATIVE) +#include "lazyload.h" +#include <winnt.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, NTAPI, 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 UNUSED, const char **argv UNUSED) +{ + cmd_sync(); + return cmd_dropcaches(); +} diff --git a/t/helper/test-dump-cache-tree.c b/t/helper/test-dump-cache-tree.c new file mode 100644 index 0000000000..3f0c7d0ed0 --- /dev/null +++ b/t/helper/test-dump-cache-tree.c @@ -0,0 +1,79 @@ +#define USE_THE_REPOSITORY_VARIABLE + +#include "test-tool.h" +#include "hash.h" +#include "hex.h" +#include "tree.h" +#include "cache-tree.h" +#include "read-cache-ll.h" +#include "repository.h" +#include "setup.h" + +static void dump_one(struct cache_tree *it, const char *pfx, const char *x) +{ + if (it->entry_count < 0) + printf("%-40s %s%s (%d subtrees)\n", + "invalid", x, pfx, it->subtree_nr); + else + printf("%s %s%s (%d entries, %d subtrees)\n", + oid_to_hex(&it->oid), x, pfx, + it->entry_count, it->subtree_nr); +} + +static int dump_cache_tree(struct cache_tree *it, + struct cache_tree *ref, + const char *pfx) +{ + int i; + int errs = 0; + + if (!it || !ref) + /* missing in either */ + return 0; + + if (it->entry_count < 0) { + /* invalid */ + dump_one(it, pfx, ""); + dump_one(ref, pfx, "#(ref) "); + } + else { + dump_one(it, pfx, ""); + 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 */ + dump_one(ref, pfx, "#(ref) "); + errs = 1; + } + } + + for (i = 0; i < it->subtree_nr; i++) { + char path[PATH_MAX]; + struct cache_tree_sub *down = it->down[i]; + struct cache_tree_sub *rdwn; + + rdwn = cache_tree_sub(ref, down->name); + xsnprintf(path, sizeof(path), "%s%.*s/", pfx, down->namelen, down->name); + if (dump_cache_tree(down->cache_tree, rdwn->cache_tree, path)) + errs = 1; + } + return errs; +} + +int cmd__dump_cache_tree(int ac UNUSED, const char **av UNUSED) +{ + struct index_state istate; + struct cache_tree *another = cache_tree(); + int ret; + + setup_git_directory(); + if (repo_read_index(the_repository) < 0) + die("unable to read index file"); + istate = *the_repository->index; + istate.cache_tree = another; + cache_tree_update(&istate, WRITE_TREE_DRY_RUN); + ret = dump_cache_tree(the_repository->index->cache_tree, another, ""); + cache_tree_free(&another); + + return ret; +} diff --git a/t/helper/test-dump-fsmonitor.c b/t/helper/test-dump-fsmonitor.c new file mode 100644 index 0000000000..efd017ca35 --- /dev/null +++ b/t/helper/test-dump-fsmonitor.c @@ -0,0 +1,25 @@ +#define USE_THE_REPOSITORY_VARIABLE + +#include "test-tool.h" +#include "read-cache-ll.h" +#include "repository.h" +#include "setup.h" + +int cmd__dump_fsmonitor(int ac UNUSED, const char **av UNUSED) +{ + struct index_state *istate = the_repository->index; + + 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 (size_t 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 new file mode 100644 index 0000000000..f855a3862c --- /dev/null +++ b/t/helper/test-dump-split-index.c @@ -0,0 +1,43 @@ +#define USE_THE_REPOSITORY_VARIABLE + +#include "test-tool.h" +#include "hex.h" +#include "read-cache-ll.h" +#include "repository.h" +#include "setup.h" +#include "split-index.h" +#include "ewah/ewok.h" + +static void show_bit(size_t pos, void *data UNUSED) +{ + printf(" %d", (int)pos); +} + +int cmd__dump_split_index(int ac UNUSED, const char **av) +{ + struct split_index *si; + + setup_git_directory(); + + do_read_index(the_repository->index, av[1], 1); + printf("own %s\n", oid_to_hex(&the_repository->index->oid)); + si = the_repository->index->split_index; + if (!si) { + printf("not a split index\n"); + return 0; + } + printf("base %s\n", oid_to_hex(&si->base_oid)); + for (size_t i = 0; i < the_repository->index->cache_nr; i++) { + struct cache_entry *ce = the_repository->index->cache[i]; + printf("%06o %s %d\t%s\n", ce->ce_mode, + oid_to_hex(&ce->oid), ce_stage(ce), ce->name); + } + printf("replacements:"); + if (si->replace_bitmap) + ewah_each_bit(si->replace_bitmap, show_bit, NULL); + printf("\ndeletions:"); + if (si->delete_bitmap) + ewah_each_bit(si->delete_bitmap, show_bit, NULL); + printf("\n"); + return 0; +} diff --git a/t/helper/test-dump-untracked-cache.c b/t/helper/test-dump-untracked-cache.c new file mode 100644 index 0000000000..01a109496b --- /dev/null +++ b/t/helper/test-dump-untracked-cache.c @@ -0,0 +1,74 @@ +#define USE_THE_REPOSITORY_VARIABLE + +#include "test-tool.h" +#include "dir.h" +#include "hex.h" +#include "read-cache-ll.h" +#include "repository.h" +#include "setup.h" + +static int compare_untracked(const void *a_, const void *b_) +{ + const char *const *a = a_; + const char *const *b = b_; + return strcmp(*a, *b); +} + +static int compare_dir(const void *a_, const void *b_) +{ + const struct untracked_cache_dir *const *a = a_; + const struct untracked_cache_dir *const *b = b_; + return strcmp((*a)->name, (*b)->name); +} + +static void dump(struct untracked_cache_dir *ucd, struct strbuf *base) +{ + int len; + QSORT(ucd->untracked, ucd->untracked_nr, compare_untracked); + QSORT(ucd->dirs, ucd->dirs_nr, compare_dir); + len = base->len; + strbuf_addf(base, "%s/", ucd->name); + printf("%s %s", base->buf, + oid_to_hex(&ucd->exclude_oid)); + if (ucd->recurse) + fputs(" recurse", stdout); + if (ucd->check_only) + fputs(" check_only", stdout); + if (ucd->valid) + fputs(" valid", stdout); + printf("\n"); + for (size_t i = 0; i < ucd->untracked_nr; i++) + printf("%s\n", ucd->untracked[i]); + for (size_t i = 0; i < ucd->dirs_nr; i++) + dump(ucd->dirs[i], base); + strbuf_setlen(base, len); +} + +int cmd__dump_untracked_cache(int ac UNUSED, const char **av UNUSED) +{ + struct untracked_cache *uc; + struct strbuf base = STRBUF_INIT; + + /* Set core.untrackedCache=keep before setup_git_directory() */ + xsetenv("GIT_CONFIG_COUNT", "1", 1); + xsetenv("GIT_CONFIG_KEY_0", "core.untrackedCache", 1); + xsetenv("GIT_CONFIG_VALUE_0", "keep", 1); + + setup_git_directory(); + if (repo_read_index(the_repository) < 0) + die("unable to read index file"); + uc = the_repository->index->untracked; + if (!uc) { + printf("no untracked cache\n"); + return 0; + } + 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) + dump(uc->root, &base); + + strbuf_release(&base); + return 0; +} diff --git a/t/helper/test-env-helper.c b/t/helper/test-env-helper.c new file mode 100644 index 0000000000..1c486888a4 --- /dev/null +++ b/t/helper/test-env-helper.c @@ -0,0 +1,100 @@ +#include "test-tool.h" +#include "parse.h" +#include "parse-options.h" + +static char const * const env__helper_usage[] = { + "test-tool env-helper --type=[bool|ulong] <options> <env-var>", + NULL +}; + +enum cmdmode { + ENV_HELPER_TYPE_BOOL = 1, + ENV_HELPER_TYPE_ULONG +}; + +static int option_parse_type(const struct option *opt, const char *arg, + int unset) +{ + enum cmdmode *cmdmode = opt->value; + + BUG_ON_OPT_NEG(unset); + + if (!strcmp(arg, "bool")) + *cmdmode = ENV_HELPER_TYPE_BOOL; + else if (!strcmp(arg, "ulong")) + *cmdmode = ENV_HELPER_TYPE_ULONG; + else + die("unrecognized --type argument, %s", arg); + + return 0; +} + +int cmd__env_helper(int argc, const char **argv) +{ + int exit_code = 0; + const char *env_variable = NULL; + const char *env_default = NULL; + int ret; + int ret_int, default_int; + unsigned long ret_ulong, default_ulong; + enum cmdmode cmdmode = 0; + struct option opts[] = { + OPT_CALLBACK_F(0, "type", &cmdmode, "type", + "value is given this type", PARSE_OPT_NONEG, + option_parse_type), + OPT_STRING(0, "default", &env_default, "value", + "default for git_env_*(...) to fall back on"), + OPT_BOOL(0, "exit-code", &exit_code, + "be quiet only use git_env_*() value as exit code"), + OPT_END(), + }; + + argc = parse_options(argc, argv, NULL, opts, env__helper_usage, + PARSE_OPT_KEEP_UNKNOWN_OPT); + if (env_default && !*env_default) + usage_with_options(env__helper_usage, opts); + if (!cmdmode) + usage_with_options(env__helper_usage, opts); + if (argc != 1) + usage_with_options(env__helper_usage, opts); + env_variable = argv[0]; + + switch (cmdmode) { + case ENV_HELPER_TYPE_BOOL: + if (env_default) { + default_int = git_parse_maybe_bool(env_default); + if (default_int == -1) { + error("option `--default' expects a boolean value with `--type=bool`, not `%s`", + env_default); + usage_with_options(env__helper_usage, opts); + } + } else { + default_int = 0; + } + ret_int = git_env_bool(env_variable, default_int); + if (!exit_code) + puts(ret_int ? "true" : "false"); + ret = ret_int; + break; + case ENV_HELPER_TYPE_ULONG: + if (env_default) { + if (!git_parse_ulong(env_default, &default_ulong)) { + error("option `--default' expects an unsigned long value with `--type=ulong`, not `%s`", + env_default); + usage_with_options(env__helper_usage, opts); + } + } else { + default_ulong = 0; + } + ret_ulong = git_env_ulong(env_variable, default_ulong); + if (!exit_code) + printf("%lu\n", ret_ulong); + ret = ret_ulong; + break; + default: + BUG("unknown <type> value"); + break; + } + + return !ret; +} diff --git a/t/helper/test-example-tap.c b/t/helper/test-example-tap.c new file mode 100644 index 0000000000..229d495ecf --- /dev/null +++ b/t/helper/test-example-tap.c @@ -0,0 +1,131 @@ +#include "test-tool.h" +#include "t/unit-tests/test-lib.h" + +/* + * The purpose of this "unit test" is to verify a few invariants of the unit + * test framework itself, as well as to provide examples of output from actually + * failing tests. As such, it is intended that this test fails, and thus it + * should not be run as part of `make unit-tests`. Instead, we verify it behaves + * as expected in the integration test t0080-unit-test-output.sh + */ + +/* Used to store the return value of check_int(). */ +static int check_res; + +/* Used to store the return value of TEST(). */ +static int test_res; + +static void t_res(int expect) +{ + check_int(check_res, ==, expect); + check_int(test_res, ==, expect); +} + +static void t_todo(int x) +{ + check_res = TEST_TODO(check(x)); +} + +static void t_skip(void) +{ + check(0); + test_skip("missing prerequisite"); + check(1); +} + +static int do_skip(void) +{ + test_skip("missing prerequisite"); + return 1; +} + +static void t_skip_todo(void) +{ + check_res = TEST_TODO(do_skip()); +} + +static void t_todo_after_fail(void) +{ + check(0); + TEST_TODO(check(0)); +} + +static void t_fail_after_todo(void) +{ + check(1); + TEST_TODO(check(0)); + check(0); +} + +static void t_messages(void) +{ + check_str("\thello\\", "there\"\n"); + check_str("NULL", NULL); + check_char('a', ==, '\n'); + check_char('\\', ==, '\''); +} + +static void t_empty(void) +{ + ; /* empty */ +} + +int cmd__example_tap(int argc UNUSED, const char **argv UNUSED) +{ + check(1); + + test_res = TEST(check_res = check_int(1, ==, 1), "passing test"); + TEST(t_res(1), "passing test and assertion return 1"); + test_res = TEST(check_res = check_int(1, ==, 2), "failing test"); + TEST(t_res(0), "failing test and assertion return 0"); + test_res = TEST(t_todo(0), "passing TEST_TODO()"); + TEST(t_res(1), "passing TEST_TODO() returns 1"); + test_res = TEST(t_todo(1), "failing TEST_TODO()"); + TEST(t_res(0), "failing TEST_TODO() returns 0"); + test_res = TEST(t_skip(), "test_skip()"); + TEST(check_int(test_res, ==, 1), "skipped test returns 1"); + test_res = TEST(t_skip_todo(), "test_skip() inside TEST_TODO()"); + TEST(t_res(1), "test_skip() inside TEST_TODO() returns 1"); + test_res = TEST(t_todo_after_fail(), "TEST_TODO() after failing check"); + TEST(check_int(test_res, ==, 0), "TEST_TODO() after failing check returns 0"); + test_res = TEST(t_fail_after_todo(), "failing check after TEST_TODO()"); + TEST(check_int(test_res, ==, 0), "failing check after TEST_TODO() returns 0"); + TEST(t_messages(), "messages from failing string and char comparison"); + test_res = TEST(t_empty(), "test with no checks"); + TEST(check_int(test_res, ==, 0), "test with no checks returns 0"); + + if_test ("if_test passing test") + check_int(1, ==, 1); + if_test ("if_test failing test") + check_int(1, ==, 2); + if_test ("if_test passing TEST_TODO()") + TEST_TODO(check(0)); + if_test ("if_test failing TEST_TODO()") + TEST_TODO(check(1)); + if_test ("if_test test_skip()") { + check(0); + test_skip("missing prerequisite"); + check(1); + } + if_test ("if_test test_skip() inside TEST_TODO()") + TEST_TODO((test_skip("missing prerequisite"), 1)); + if_test ("if_test TEST_TODO() after failing check") { + check(0); + TEST_TODO(check(0)); + } + if_test ("if_test failing check after TEST_TODO()") { + check(1); + TEST_TODO(check(0)); + check(0); + } + if_test ("if_test messages from failing string and char comparison") { + check_str("\thello\\", "there\"\n"); + check_str("NULL", NULL); + check_char('a', ==, '\n'); + check_char('\\', ==, '\''); + } + if_test ("if_test test with no checks") + ; /* nothing */ + + return test_done(); +} diff --git a/t/helper/test-fake-ssh.c b/t/helper/test-fake-ssh.c new file mode 100644 index 0000000000..27323cb367 --- /dev/null +++ b/t/helper/test-fake-ssh.c @@ -0,0 +1,32 @@ +#include "git-compat-util.h" +#include "run-command.h" +#include "strbuf.h" + +int cmd_main(int argc, const char **argv) +{ + const char *trash_directory = getenv("TRASH_DIRECTORY"); + struct strbuf buf = STRBUF_INIT; + FILE *f; + int i; + struct child_process cmd = CHILD_PROCESS_INIT; + + /* First, print all parameters into $TRASH_DIRECTORY/ssh-output */ + if (!trash_directory) + die("Need a TRASH_DIRECTORY!"); + strbuf_addf(&buf, "%s/ssh-output", trash_directory); + f = fopen(buf.buf, "w"); + if (!f) + die("Could not write to %s", buf.buf); + strbuf_release(&buf); + for (i = 0; i < argc; i++) + fprintf(f, "%s%s", i > 0 ? " " : "", i > 0 ? argv[i] : "ssh:"); + fprintf(f, "\n"); + fclose(f); + + /* Now, evaluate the *last* parameter */ + if (argc < 2) + return 0; + cmd.use_shell = 1; + strvec_push(&cmd.args, argv[argc - 1]); + return run_command(&cmd); +} diff --git a/t/helper/test-find-pack.c b/t/helper/test-find-pack.c new file mode 100644 index 0000000000..611a13a326 --- /dev/null +++ b/t/helper/test-find-pack.c @@ -0,0 +1,52 @@ +#define USE_THE_REPOSITORY_VARIABLE + +#include "test-tool.h" +#include "object-name.h" +#include "odb.h" +#include "packfile.h" +#include "parse-options.h" +#include "setup.h" + +/* + * Display the path(s), one per line, of the packfile(s) containing + * the given object. + * + * If '--check-count <n>' is passed, then error out if the number of + * packfiles containing the object is not <n>. + */ + +static const char *const find_pack_usage[] = { + "test-tool find-pack [--check-count <n>] <object>", + NULL +}; + +int cmd__find_pack(int argc, const char **argv) +{ + struct object_id oid; + struct packed_git *p; + int count = -1, actual_count = 0; + const char *prefix = setup_git_directory(); + + struct option options[] = { + OPT_INTEGER('c', "check-count", &count, "expected number of packs"), + OPT_END(), + }; + + argc = parse_options(argc, argv, prefix, options, find_pack_usage, 0); + if (argc != 1) + usage(find_pack_usage[0]); + + if (repo_get_oid(the_repository, argv[0], &oid)) + die("cannot parse %s as an object name", argv[0]); + + for (p = get_all_packs(the_repository); p; p = p->next) + if (find_pack_entry_one(&oid, p)) { + printf("%s\n", p->pack_name); + actual_count++; + } + + if (count > -1 && count != actual_count) + die("bad packfile count %d instead of %d", actual_count, count); + + return 0; +} diff --git a/t/helper/test-fsmonitor-client.c b/t/helper/test-fsmonitor-client.c new file mode 100644 index 0000000000..02bfe92e8d --- /dev/null +++ b/t/helper/test-fsmonitor-client.c @@ -0,0 +1,226 @@ +/* + * test-fsmonitor-client.c: client code to send commands/requests to + * a `git fsmonitor--daemon` daemon. + */ + +#define USE_THE_REPOSITORY_VARIABLE + +#include "test-tool.h" +#include "parse-options.h" +#include "fsmonitor-ipc.h" +#include "read-cache-ll.h" +#include "repository.h" +#include "setup.h" +#include "thread-utils.h" +#include "trace2.h" + +#ifndef HAVE_FSMONITOR_DAEMON_BACKEND +int cmd__fsmonitor_client(int argc UNUSED, const char **argv UNUSED) +{ + die("fsmonitor--daemon not available on this platform"); +} +#else + +/* + * Read the `.git/index` to get the last token written to the + * FSMonitor Index Extension. + */ +static const char *get_token_from_index(void) +{ + struct index_state *istate = the_repository->index; + + if (do_read_index(istate, the_repository->index_file, 0) < 0) + die("unable to read index file"); + if (!istate->fsmonitor_last_update) + die("index file does not have fsmonitor extension"); + + return istate->fsmonitor_last_update; +} + +/* + * Send an IPC query to a `git-fsmonitor--daemon` daemon and + * ask for the changes since the given token or from the last + * token in the index extension. + * + * This will implicitly start a daemon process if necessary. The + * daemon process will persist after we exit. + */ +static int do_send_query(const char *token) +{ + struct strbuf answer = STRBUF_INIT; + int ret; + + if (!token || !*token) + token = get_token_from_index(); + + ret = fsmonitor_ipc__send_query(token, &answer); + if (ret < 0) + die("could not query fsmonitor--daemon"); + + write_in_full(1, answer.buf, answer.len); + strbuf_release(&answer); + + return 0; +} + +/* + * Send a "flush" command to the `git-fsmonitor--daemon` (if running) + * and tell it to flush its cache. + * + * This feature is primarily used by the test suite to simulate a loss of + * sync with the filesystem where we miss kernel events. + */ +static int do_send_flush(void) +{ + struct strbuf answer = STRBUF_INIT; + int ret; + + ret = fsmonitor_ipc__send_command("flush", &answer); + if (ret) + return ret; + + write_in_full(1, answer.buf, answer.len); + strbuf_release(&answer); + + return 0; +} + +struct hammer_thread_data +{ + pthread_t pthread_id; + int thread_nr; + + int nr_requests; + const char *token; + + int sum_successful; + int sum_errors; +}; + +static void *hammer_thread_proc(void *_hammer_thread_data) +{ + struct hammer_thread_data *data = _hammer_thread_data; + struct strbuf answer = STRBUF_INIT; + int k; + int ret; + + trace2_thread_start("hammer"); + + for (k = 0; k < data->nr_requests; k++) { + strbuf_reset(&answer); + + ret = fsmonitor_ipc__send_query(data->token, &answer); + if (ret < 0) + data->sum_errors++; + else + data->sum_successful++; + } + + strbuf_release(&answer); + trace2_thread_exit(); + return NULL; +} + +/* + * Start a pool of client threads that will each send a series of + * commands to the daemon. + * + * The goal is to overload the daemon with a sustained series of + * concurrent requests. + */ +static int do_hammer(const char *token, int nr_threads, int nr_requests) +{ + struct hammer_thread_data *data = NULL; + int k; + int sum_join_errors = 0; + int sum_commands = 0; + int sum_errors = 0; + + if (!token || !*token) + token = get_token_from_index(); + if (nr_threads < 1) + nr_threads = 1; + if (nr_requests < 1) + nr_requests = 1; + + CALLOC_ARRAY(data, nr_threads); + + for (k = 0; k < nr_threads; k++) { + struct hammer_thread_data *p = &data[k]; + p->thread_nr = k; + p->nr_requests = nr_requests; + p->token = token; + + if (pthread_create(&p->pthread_id, NULL, hammer_thread_proc, p)) { + warning("failed to create thread[%d] skipping remainder", k); + nr_threads = k; + break; + } + } + + for (k = 0; k < nr_threads; k++) { + struct hammer_thread_data *p = &data[k]; + + if (pthread_join(p->pthread_id, NULL)) + sum_join_errors++; + sum_commands += p->sum_successful; + sum_errors += p->sum_errors; + } + + fprintf(stderr, "HAMMER: [threads %d][requests %d] [ok %d][err %d][join %d]\n", + nr_threads, nr_requests, sum_commands, sum_errors, sum_join_errors); + + free(data); + + /* + * Return an error if any of the _send_query requests failed. + * We don't care about thread create/join errors. + */ + return sum_errors > 0; +} + +int cmd__fsmonitor_client(int argc, const char **argv) +{ + const char *subcmd; + const char *token = NULL; + int nr_threads = 1; + int nr_requests = 1; + + const char * const fsmonitor_client_usage[] = { + "test-tool fsmonitor-client query [<token>]", + "test-tool fsmonitor-client flush", + "test-tool fsmonitor-client hammer [<token>] [<threads>] [<requests>]", + NULL, + }; + + struct option options[] = { + OPT_STRING(0, "token", &token, "token", + "command token to send to the server"), + + OPT_INTEGER(0, "threads", &nr_threads, "number of client threads"), + OPT_INTEGER(0, "requests", &nr_requests, "number of requests per thread"), + + OPT_END() + }; + + argc = parse_options(argc, argv, NULL, options, fsmonitor_client_usage, 0); + + if (argc != 1) + usage_with_options(fsmonitor_client_usage, options); + + subcmd = argv[0]; + + setup_git_directory(); + + if (!strcmp(subcmd, "query")) + return !!do_send_query(token); + + if (!strcmp(subcmd, "flush")) + return !!do_send_flush(); + + if (!strcmp(subcmd, "hammer")) + return !!do_hammer(token, nr_threads, nr_requests); + + die("Unhandled subcommand: '%s'", subcmd); +} +#endif diff --git a/t/helper/test-genrandom.c b/t/helper/test-genrandom.c new file mode 100644 index 0000000000..51b67f2f87 --- /dev/null +++ b/t/helper/test-genrandom.c @@ -0,0 +1,34 @@ +/* + * Simple random data generator used to create reproducible test files. + * This is inspired from POSIX.1-2001 implementation example for rand(). + * Copyright (C) 2007 by Nicolas Pitre, licensed under the GPL version 2. + */ + +#include "test-tool.h" +#include "git-compat-util.h" + +int cmd__genrandom(int argc, const char **argv) +{ + unsigned long count, next = 0; + unsigned char *c; + + if (argc < 2 || argc > 3) { + fprintf(stderr, "usage: %s <seed_string> [<size>]\n", argv[0]); + return 1; + } + + c = (unsigned char *) argv[1]; + do { + next = next * 11 + *c; + } while (*c++); + + count = (argc == 3) ? strtoul(argv[2], NULL, 0) : ULONG_MAX; + + while (count--) { + next = next * 1103515245 + 12345; + if (putchar((next >> 16) & 0xff) == EOF) + return -1; + } + + return 0; +} diff --git a/t/helper/test-genzeros.c b/t/helper/test-genzeros.c new file mode 100644 index 0000000000..b895436a32 --- /dev/null +++ b/t/helper/test-genzeros.c @@ -0,0 +1,37 @@ +#define DISABLE_SIGN_COMPARE_WARNINGS + +#include "test-tool.h" +#include "git-compat-util.h" + +int cmd__genzeros(int argc, const char **argv) +{ + /* static, so that it is NUL-initialized */ + static const char zeros[256 * 1024]; + intmax_t count; + ssize_t n; + + if (argc > 2) { + fprintf(stderr, "usage: %s [<count>]\n", argv[0]); + return 1; + } + + count = argc > 1 ? strtoimax(argv[1], NULL, 0) : -1; + + /* Writing out individual NUL bytes is slow... */ + while (count < 0) + if (xwrite(1, zeros, ARRAY_SIZE(zeros)) < 0) + die_errno("write error"); + + while (count > 0) { + n = xwrite(1, zeros, + count < ARRAY_SIZE(zeros) + ? count : ARRAY_SIZE(zeros)); + + if (n < 0) + die_errno("write error"); + + count -= n; + } + + return 0; +} diff --git a/t/helper/test-getcwd.c b/t/helper/test-getcwd.c new file mode 100644 index 0000000000..cd4d424079 --- /dev/null +++ b/t/helper/test-getcwd.c @@ -0,0 +1,26 @@ +#include "test-tool.h" +#include "git-compat-util.h" +#include "parse-options.h" + +static const char *const getcwd_usage[] = { + "test-tool getcwd", + NULL +}; + +int cmd__getcwd(int argc, const char **argv) +{ + struct option options[] = { + OPT_END() + }; + char *cwd; + + argc = parse_options(argc, argv, "test-tools", options, getcwd_usage, 0); + if (argc > 0) + usage_with_options(getcwd_usage, options); + + cwd = xgetcwd(); + puts(cwd); + free(cwd); + + return 0; +} diff --git a/t/helper/test-hash-speed.c b/t/helper/test-hash-speed.c new file mode 100644 index 0000000000..fbf67fe6bd --- /dev/null +++ b/t/helper/test-hash-speed.c @@ -0,0 +1,60 @@ +#include "test-tool.h" +#include "hash.h" + +#define NUM_SECONDS 3 + +static inline void compute_hash(const struct git_hash_algo *algo, struct git_hash_ctx *ctx, uint8_t *final, const void *p, size_t len) +{ + algo->init_fn(ctx); + git_hash_update(ctx, p, len); + git_hash_final(final, ctx); +} + +int cmd__hash_speed(int ac, const char **av) +{ + struct git_hash_ctx ctx; + unsigned char hash[GIT_MAX_RAWSZ]; + clock_t initial, start, end; + unsigned bufsizes[] = { 64, 256, 1024, 8192, 16384 }; + void *p; + const struct git_hash_algo *algo = NULL; + + if (ac == 2) { + for (size_t 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 (size_t 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..f0ee61c8b4 --- /dev/null +++ b/t/helper/test-hash.c @@ -0,0 +1,61 @@ +#include "test-tool.h" +#include "hex.h" + +int cmd_hash_impl(int ac, const char **av, int algo, int unsafe) +{ + struct 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 (unsafe) + algop = unsafe_hash_algo(algop); + + 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; + git_hash_update(&ctx, buffer, this_sz); + } + git_hash_final(hash, &ctx); + + if (binary) + fwrite(hash, 1, algop->rawsz, stdout); + else + puts(hash_to_hex_algop(hash, algop)); + free(buffer); + return 0; +} diff --git a/t/helper/test-hashmap.c b/t/helper/test-hashmap.c new file mode 100644 index 0000000000..16a3145c3a --- /dev/null +++ b/t/helper/test-hashmap.c @@ -0,0 +1,184 @@ +#include "test-tool.h" +#include "git-compat-util.h" +#include "hashmap.h" +#include "strbuf.h" +#include "string-list.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]; +}; + +static int test_entry_cmp(const void *cmp_data, + 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, *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(unsigned int hash, + const char *key, + const char *value) +{ + 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; +} + +#define HASH_METHOD_FNV 0 +#define HASH_METHOD_I 1 +#define HASH_METHOD_IDIV10 2 +#define HASH_METHOD_0 3 +#define HASH_METHOD_X2 4 +#define TEST_SPARSE 8 +#define TEST_ADD 16 +#define TEST_SIZE 100000 + +static unsigned int hash(unsigned int method, unsigned int i, const char *key) +{ + unsigned int hash = 0; + switch (method & 3) + { + case HASH_METHOD_FNV: + hash = strhash(key); + break; + case HASH_METHOD_I: + hash = i; + break; + case HASH_METHOD_IDIV10: + hash = i / 10; + break; + case HASH_METHOD_0: + hash = 0; + break; + } + + if (method & HASH_METHOD_X2) + hash = 2 * hash; + return hash; +} + +/* + * Test performance of hashmap.[ch] + * Usage: time echo "perfhashmap method rounds" | test-tool hashmap + */ +static void perf_hashmap(unsigned int method, unsigned int rounds) +{ + struct hashmap map; + char buf[16]; + struct test_entry **entries; + unsigned int *hashes; + unsigned int i, j; + + ALLOC_ARRAY(entries, TEST_SIZE); + ALLOC_ARRAY(hashes, TEST_SIZE); + for (i = 0; i < TEST_SIZE; i++) { + xsnprintf(buf, sizeof(buf), "%i", i); + entries[i] = alloc_test_entry(0, buf, ""); + hashes[i] = hash(method, i, entries[i]->key); + } + + if (method & TEST_ADD) { + /* test adding to the map */ + for (j = 0; j < rounds; j++) { + hashmap_init(&map, test_entry_cmp, NULL, 0); + + /* add entries */ + for (i = 0; i < TEST_SIZE; i++) { + hashmap_entry_init(&entries[i]->ent, hashes[i]); + hashmap_add(&map, &entries[i]->ent); + } + + hashmap_clear(&map); + } + } else { + /* test map lookups */ + hashmap_init(&map, test_entry_cmp, NULL, 0); + + /* 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]->ent, hashes[i]); + hashmap_add(&map, &entries[i]->ent); + } + + for (j = 0; j < rounds; j++) { + for (i = 0; i < TEST_SIZE; i++) { + hashmap_get_from_hash(&map, hashes[i], + entries[i]->key); + } + } + + hashmap_clear(&map); + } +} + +#define DELIM " \t\r\n" + +/* + * Read stdin line by line and print result of commands to stdout: + * + * perfhashmap method rounds -> test hashmap.[ch] performance + * + * NOTE: this is not used by any of our mechanized build & test + * procedure, after 3469a236 (t: port helper/test-hashmap.c to + * unit-tests/t-hashmap.c, 2024-08-03). See the log message of that + * commit for the reason why this is still here. + */ +int cmd__hashmap(int argc UNUSED, const char **argv UNUSED) +{ + struct string_list parts = STRING_LIST_INIT_NODUP; + struct strbuf line = STRBUF_INIT; + + /* process commands from stdin */ + while (strbuf_getline(&line, stdin) != EOF) { + char *cmd, *p1, *p2; + + /* break line into command and up to two parameters */ + string_list_setlen(&parts, 0); + string_list_split_in_place(&parts, line.buf, DELIM, 2); + string_list_remove_empty_items(&parts, 0); + + /* ignore empty lines */ + if (!parts.nr) + continue; + if (!*parts.items[0].string || *parts.items[0].string == '#') + continue; + + cmd = parts.items[0].string; + p1 = parts.nr >= 1 ? parts.items[1].string : NULL; + p2 = parts.nr >= 2 ? parts.items[2].string : NULL; + + if (!strcmp("perfhashmap", cmd) && p1 && p2) { + + perf_hashmap(atoi(p1), atoi(p2)); + + } else { + + printf("Unknown command %s\n", cmd); + + } + } + + string_list_clear(&parts, 0); + strbuf_release(&line); + return 0; +} diff --git a/t/helper/test-hexdump.c b/t/helper/test-hexdump.c new file mode 100644 index 0000000000..05f55eca21 --- /dev/null +++ b/t/helper/test-hexdump.c @@ -0,0 +1,30 @@ +#include "test-tool.h" +#include "git-compat-util.h" + +/* + * Read stdin and print a hexdump to stdout. + */ +int cmd__hexdump(int argc UNUSED, const char **argv UNUSED) +{ + char buf[1024]; + ssize_t i, len; + int have_data = 0; + + for (;;) { + len = xread(0, buf, sizeof(buf)); + if (len < 0) + die_errno("failure reading stdin"); + if (!len) + break; + + have_data = 1; + + for (i = 0; i < len; i++) + printf("%02x ", (unsigned char)buf[i]); + } + + if (have_data) + putchar('\n'); + + return 0; +} diff --git a/t/helper/test-json-writer.c b/t/helper/test-json-writer.c new file mode 100644 index 0000000000..a288069b04 --- /dev/null +++ b/t/helper/test-json-writer.c @@ -0,0 +1,594 @@ +#include "test-tool.h" +#include "json-writer.h" +#include "string-list.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 const 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) +{ + make_obj1(0); + make_arr1(0); + + jw_object_begin(&nest1, pretty); + { + jw_object_sub_jw(&nest1, "obj1", &obj1); + jw_object_sub_jw(&nest1, "arr1", &arr1); + } + jw_end(&nest1); + + jw_release(&obj1); + jw_release(&arr1); +} + +static const char *expect_inline1 = + "{\"obj1\":{\"a\":\"abc\",\"b\":42,\"c\":true},\"arr1\":[\"abc\",42,true]}"; + +static const 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 const char *expect_inline2 = + "[[1,2],[3,4],{\"a\":\"abc\"}]"; + +static const 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); + + jw_release(&obj1); + jw_release(&arr1); +} + +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); jw_release(&v); } while (0) +#define p(v) do { make_##v(1); cmp(#v, &v, pretty_##v); jw_release(&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); + p(mixed1); + + return 0; +} + +struct line { + struct string_list *parts; + size_t consumed_nr; + int nr; +}; + +static void get_s(struct line *line, char **s_in) +{ + if (line->consumed_nr > line->parts->nr) + die("line[%d]: expected: <s>", line->nr); + *s_in = line->parts->items[line->consumed_nr++].string; +} + +static void get_i(struct line *line, intmax_t *s_in) +{ + char *s; + char *endptr; + + get_s(line, &s); + + errno = 0; + *s_in = strtol(s, &endptr, 10); + if (*endptr || errno == ERANGE) + die("line[%d]: invalid integer value", line->nr); +} + +static void get_d(struct line *line, double *s_in) +{ + char *s; + char *endptr; + + get_s(line, &s); + + errno = 0; + *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 string_list parts = STRING_LIST_INIT_NODUP; + 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) { + struct line state = { 0 }; + char *verb; + char *key; + char *s_value; + intmax_t i_value; + double d_value; + + state.parts = &parts; + state.nr = ++line_nr; + + /* break line into command and zero or more tokens */ + string_list_setlen(&parts, 0); + string_list_split_in_place(&parts, line, " ", -1); + string_list_remove_empty_items(&parts, 0); + + /* ignore empty lines */ + if (!parts.nr || !*parts.items[0].string) + continue; + + verb = parts.items[state.consumed_nr++].string; + + if (!strcmp(verb, "end")) { + jw_end(&jw); + } + else if (!strcmp(verb, "object-string")) { + get_s(&state, &key); + get_s(&state, &s_value); + jw_object_string(&jw, key, s_value); + } + else if (!strcmp(verb, "object-int")) { + get_s(&state, &key); + get_i(&state, &i_value); + jw_object_intmax(&jw, key, i_value); + } + else if (!strcmp(verb, "object-double")) { + get_s(&state, &key); + get_i(&state, &i_value); + get_d(&state, &d_value); + jw_object_double(&jw, key, i_value, d_value); + } + else if (!strcmp(verb, "object-true")) { + get_s(&state, &key); + jw_object_true(&jw, key); + } + else if (!strcmp(verb, "object-false")) { + get_s(&state, &key); + jw_object_false(&jw, key); + } + else if (!strcmp(verb, "object-null")) { + get_s(&state, &key); + jw_object_null(&jw, key); + } + else if (!strcmp(verb, "object-object")) { + get_s(&state, &key); + jw_object_inline_begin_object(&jw, key); + } + else if (!strcmp(verb, "object-array")) { + get_s(&state, &key); + jw_object_inline_begin_array(&jw, key); + } + else if (!strcmp(verb, "array-string")) { + get_s(&state, &s_value); + jw_array_string(&jw, s_value); + } + else if (!strcmp(verb, "array-int")) { + get_i(&state, &i_value); + jw_array_intmax(&jw, i_value); + } + else if (!strcmp(verb, "array-double")) { + get_i(&state, &i_value); + get_d(&state, &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); + + jw_release(&jw); + string_list_clear(&parts, 0); + 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 new file mode 100644 index 0000000000..40f5df4412 --- /dev/null +++ b/t/helper/test-lazy-init-name-hash.c @@ -0,0 +1,268 @@ +#define USE_THE_REPOSITORY_VARIABLE + +#include "test-tool.h" +#include "environment.h" +#include "name-hash.h" +#include "parse-options.h" +#include "read-cache-ll.h" +#include "repository.h" +#include "setup.h" +#include "trace.h" + +static int single; +static int multi; +static int count = 1; +static int dump; +static int perf; +static int analyze; +static int analyze_step; + +/* + * Dump the contents of the "dir" and "name" hash tables to stdout. + * If you sort the result, you can compare it with the other type + * mode and verify that both single and multi produce the same set. + */ +static void dump_run(void) +{ + struct hashmap_iter iter_dir; + struct hashmap_iter iter_cache; + + /* Stolen from name-hash.c */ + struct dir_entry { + struct hashmap_entry ent; + struct dir_entry *parent; + int nr; + unsigned int namelen; + char name[FLEX_ARRAY]; + }; + + struct dir_entry *dir; + struct cache_entry *ce; + + repo_read_index(the_repository); + if (single) { + test_lazy_init_name_hash(the_repository->index, 0); + } else { + int nr_threads_used = test_lazy_init_name_hash(the_repository->index, 1); + if (!nr_threads_used) + die("non-threaded code path used"); + } + + hashmap_for_each_entry(&the_repository->index->dir_hash, &iter_dir, dir, + ent /* member name */) + printf("dir %08x %7d %s\n", dir->ent.hash, dir->nr, dir->name); + + hashmap_for_each_entry(&the_repository->index->name_hash, &iter_cache, ce, + ent /* member name */) + printf("name %08x %s\n", ce->ent.hash, ce->name); + + discard_index(the_repository->index); +} + +/* + * Run the single or multi threaded version "count" times and + * report on the time taken. + */ +static uint64_t time_runs(int try_threaded) +{ + uint64_t t0, t1, t2; + uint64_t sum = 0; + uint64_t avg; + int nr_threads_used; + int i; + + for (i = 0; i < count; i++) { + t0 = getnanotime(); + repo_read_index(the_repository); + t1 = getnanotime(); + nr_threads_used = test_lazy_init_name_hash(the_repository->index, try_threaded); + t2 = getnanotime(); + + sum += (t2 - t1); + + if (try_threaded && !nr_threads_used) + die("non-threaded code path used"); + + if (nr_threads_used) + printf("%f %f %d multi %d\n", + ((double)(t1 - t0))/1000000000, + ((double)(t2 - t1))/1000000000, + the_repository->index->cache_nr, + nr_threads_used); + else + printf("%f %f %d single\n", + ((double)(t1 - t0))/1000000000, + ((double)(t2 - t1))/1000000000, + the_repository->index->cache_nr); + fflush(stdout); + + discard_index(the_repository->index); + } + + avg = sum / count; + if (count > 1) + printf("avg %f %s\n", + (double)avg/1000000000, + (try_threaded) ? "multi" : "single"); + + return avg; +} + +/* + * Try a series of runs varying the "istate->cache_nr" and + * try to find a good value for the multi-threaded criteria. + */ +static void analyze_run(void) +{ + uint64_t t1s, t1m, t2s, t2m; + int cache_nr_limit; + int nr_threads_used = 0; + int i; + int nr; + + repo_read_index(the_repository); + cache_nr_limit = the_repository->index->cache_nr; + discard_index(the_repository->index); + + nr = analyze; + while (1) { + uint64_t sum_single = 0; + uint64_t sum_multi = 0; + uint64_t avg_single; + uint64_t avg_multi; + + if (nr > cache_nr_limit) + nr = cache_nr_limit; + + for (i = 0; i < count; i++) { + repo_read_index(the_repository); + the_repository->index->cache_nr = nr; /* cheap truncate of index */ + t1s = getnanotime(); + test_lazy_init_name_hash(the_repository->index, 0); + t2s = getnanotime(); + sum_single += (t2s - t1s); + the_repository->index->cache_nr = cache_nr_limit; + discard_index(the_repository->index); + + repo_read_index(the_repository); + the_repository->index->cache_nr = nr; /* cheap truncate of index */ + t1m = getnanotime(); + nr_threads_used = test_lazy_init_name_hash(the_repository->index, 1); + t2m = getnanotime(); + sum_multi += (t2m - t1m); + the_repository->index->cache_nr = cache_nr_limit; + discard_index(the_repository->index); + + if (!nr_threads_used) + printf(" [size %8d] [single %f] non-threaded code path used\n", + nr, ((double)(t2s - t1s))/1000000000); + else + printf(" [size %8d] [single %f] %c [multi %f %d]\n", + nr, + ((double)(t2s - t1s))/1000000000, + (((t2s - t1s) < (t2m - t1m)) ? '<' : '>'), + ((double)(t2m - t1m))/1000000000, + nr_threads_used); + fflush(stdout); + } + if (count > 1) { + avg_single = sum_single / count; + avg_multi = sum_multi / count; + if (!nr_threads_used) + printf("avg [size %8d] [single %f]\n", + nr, + (double)avg_single/1000000000); + else + printf("avg [size %8d] [single %f] %c [multi %f %d]\n", + nr, + (double)avg_single/1000000000, + (avg_single < avg_multi ? '<' : '>'), + (double)avg_multi/1000000000, + nr_threads_used); + fflush(stdout); + } + + if (nr >= cache_nr_limit) + return; + nr += analyze_step; + } +} + +int cmd__lazy_init_name_hash(int argc, const char **argv) +{ + const char *usage[] = { + "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[] = { + OPT_BOOL('s', "single", &single, "run single-threaded code"), + OPT_BOOL('m', "multi", &multi, "run multi-threaded code"), + OPT_INTEGER('c', "count", &count, "number of passes"), + OPT_BOOL('d', "dump", &dump, "dump hash tables"), + OPT_BOOL('p', "perf", &perf, "compare single vs multi"), + OPT_INTEGER('a', "analyze", &analyze, "analyze different multi sizes"), + OPT_INTEGER(0, "step", &analyze_step, "analyze step factor"), + OPT_END(), + }; + const char *prefix; + uint64_t avg_single, avg_multi; + + prefix = setup_git_directory(); + + argc = parse_options(argc, argv, prefix, options, usage, 0); + + /* + * istate->dir_hash is only created when ignore_case is set. + */ + ignore_case = 1; + + if (dump) { + if (perf || analyze > 0) + die("cannot combine dump, perf, or analyze"); + if (count > 1) + die("count not valid with dump"); + if (single && multi) + die("cannot use both single and multi with dump"); + if (!single && !multi) + die("dump requires either single or multi"); + dump_run(); + return 0; + } + + if (perf) { + if (analyze > 0) + die("cannot combine dump, perf, or analyze"); + if (single || multi) + die("cannot use single or multi with perf"); + avg_single = time_runs(0); + avg_multi = time_runs(1); + if (avg_multi > avg_single) + die("multi is slower"); + return 0; + } + + if (analyze) { + if (analyze < 500) + die("analyze must be at least 500"); + if (!analyze_step) + analyze_step = analyze; + if (single || multi) + die("cannot use single or multi with analyze"); + analyze_run(); + return 0; + } + + if (!single && !multi) + die("require either -s or -m or both"); + + if (single) + time_runs(0); + if (multi) + time_runs(1); + + return 0; +} diff --git a/t/helper/test-match-trees.c b/t/helper/test-match-trees.c new file mode 100644 index 0000000000..e0e2048320 --- /dev/null +++ b/t/helper/test-match-trees.c @@ -0,0 +1,33 @@ +#define USE_THE_REPOSITORY_VARIABLE + +#include "test-tool.h" +#include "hex.h" +#include "match-trees.h" +#include "object-name.h" +#include "repository.h" +#include "setup.h" +#include "tree.h" + +int cmd__match_trees(int ac UNUSED, const char **av) +{ + struct object_id hash1, hash2, shifted; + struct tree *one, *two; + + setup_git_directory(); + + if (repo_get_oid(the_repository, av[1], &hash1)) + die("cannot parse %s as an object name", av[1]); + if (repo_get_oid(the_repository, av[2], &hash2)) + die("cannot parse %s as an object name", av[2]); + one = parse_tree_indirect(&hash1); + if (!one) + die("not a tree-ish %s", av[1]); + two = parse_tree_indirect(&hash2); + if (!two) + die("not a tree-ish %s", av[2]); + + shift_tree(the_repository, &one->object.oid, &two->object.oid, &shifted, -1); + printf("shifted: %s\n", oid_to_hex(&shifted)); + + return 0; +} diff --git a/t/helper/test-mergesort.c b/t/helper/test-mergesort.c new file mode 100644 index 0000000000..791e128793 --- /dev/null +++ b/t/helper/test-mergesort.c @@ -0,0 +1,408 @@ +#define DISABLE_SIGN_COMPARE_WARNINGS + +#include "test-tool.h" +#include "mem-pool.h" +#include "mergesort.h" +#include "strbuf.h" + +static uint32_t minstd_rand(uint32_t *state) +{ + *state = (uint64_t)*state * 48271 % 2147483647; + return *state; +} + +struct line { + char *text; + struct line *next; +}; + +DEFINE_LIST_SORT(static, sort_lines, struct line, next); + +static int compare_strings(const struct line *x, const struct line *y) +{ + return strcmp(x->text, y->text); +} + +static int sort_stdin(void) +{ + struct line *lines; + struct line **tail = &lines; + struct strbuf sb = STRBUF_INIT; + struct mem_pool lines_pool; + char *p; + + strbuf_read(&sb, 0, 0); + + /* + * Split by newline, but don't create an item + * for the empty string after the last separator. + */ + if (sb.len && sb.buf[sb.len - 1] == '\n') + strbuf_setlen(&sb, sb.len - 1); + + mem_pool_init(&lines_pool, 0); + p = sb.buf; + for (;;) { + char *eol = strchr(p, '\n'); + struct line *line = mem_pool_alloc(&lines_pool, sizeof(*line)); + line->text = p; + *tail = line; + tail = &line->next; + if (!eol) + break; + *eol = '\0'; + p = eol + 1; + } + *tail = NULL; + + sort_lines(&lines, compare_strings); + + while (lines) { + puts(lines->text); + lines = lines->next; + } + return 0; +} + +static void dist_sawtooth(int *arr, int n, int m) +{ + int i; + for (i = 0; i < n; i++) + arr[i] = i % m; +} + +static void dist_rand(int *arr, int n, int m) +{ + int i; + uint32_t seed = 1; + for (i = 0; i < n; i++) + arr[i] = minstd_rand(&seed) % m; +} + +static void dist_stagger(int *arr, int n, int m) +{ + int i; + for (i = 0; i < n; i++) + arr[i] = (i * m + i) % n; +} + +static void dist_plateau(int *arr, int n, int m) +{ + int i; + for (i = 0; i < n; i++) + arr[i] = (i < m) ? i : m; +} + +static void dist_shuffle(int *arr, int n, int m) +{ + int i, j, k; + uint32_t seed = 1; + for (i = j = 0, k = 1; i < n; i++) + arr[i] = minstd_rand(&seed) % m ? (j += 2) : (k += 2); +} + +#define DIST(name) { #name, dist_##name } + +static struct dist { + const char *name; + void (*fn)(int *arr, int n, int m); +} dist[] = { + DIST(sawtooth), + DIST(rand), + DIST(stagger), + DIST(plateau), + DIST(shuffle), +}; + +static const struct dist *get_dist_by_name(const char *name) +{ + int i; + for (i = 0; i < ARRAY_SIZE(dist); i++) { + if (!strcmp(dist[i].name, name)) + return &dist[i]; + } + return NULL; +} + +static void mode_copy(int *arr UNUSED, int n UNUSED) +{ + /* nothing */ +} + +static void mode_reverse(int *arr, int n) +{ + int i, j; + for (i = 0, j = n - 1; i < j; i++, j--) + SWAP(arr[i], arr[j]); +} + +static void mode_reverse_1st_half(int *arr, int n) +{ + mode_reverse(arr, n / 2); +} + +static void mode_reverse_2nd_half(int *arr, int n) +{ + int half = n / 2; + mode_reverse(arr + half, n - half); +} + +static int compare_ints(const void *av, const void *bv) +{ + const int *ap = av, *bp = bv; + int a = *ap, b = *bp; + return (a > b) - (a < b); +} + +static void mode_sort(int *arr, int n) +{ + QSORT(arr, n, compare_ints); +} + +static void mode_dither(int *arr, int n) +{ + int i; + for (i = 0; i < n; i++) + arr[i] += i % 5; +} + +static void unriffle(int *arr, int n, int *tmp) +{ + int i, j; + COPY_ARRAY(tmp, arr, n); + for (i = j = 0; i < n; i += 2) + arr[j++] = tmp[i]; + for (i = 1; i < n; i += 2) + arr[j++] = tmp[i]; +} + +static void unriffle_recursively(int *arr, int n, int *tmp) +{ + if (n > 1) { + int half = n / 2; + unriffle(arr, n, tmp); + unriffle_recursively(arr, half, tmp); + unriffle_recursively(arr + half, n - half, tmp); + } +} + +static void mode_unriffle(int *arr, int n) +{ + int *tmp; + ALLOC_ARRAY(tmp, n); + unriffle_recursively(arr, n, tmp); + free(tmp); +} + +static unsigned int prev_pow2(unsigned int n) +{ + unsigned int pow2 = 1; + while (pow2 * 2 < n) + pow2 *= 2; + return pow2; +} + +static void unriffle_recursively_skewed(int *arr, int n, int *tmp) +{ + if (n > 1) { + int pow2 = prev_pow2(n); + int rest = n - pow2; + unriffle(arr + pow2 - rest, rest * 2, tmp); + unriffle_recursively_skewed(arr, pow2, tmp); + unriffle_recursively_skewed(arr + pow2, rest, tmp); + } +} + +static void mode_unriffle_skewed(int *arr, int n) +{ + int *tmp; + ALLOC_ARRAY(tmp, n); + unriffle_recursively_skewed(arr, n, tmp); + free(tmp); +} + +#define MODE(name) { #name, mode_##name } + +static struct mode { + const char *name; + void (*fn)(int *arr, int n); +} mode[] = { + MODE(copy), + MODE(reverse), + MODE(reverse_1st_half), + MODE(reverse_2nd_half), + MODE(sort), + MODE(dither), + MODE(unriffle), + MODE(unriffle_skewed), +}; + +static const struct mode *get_mode_by_name(const char *name) +{ + int i; + for (i = 0; i < ARRAY_SIZE(mode); i++) { + if (!strcmp(mode[i].name, name)) + return &mode[i]; + } + return NULL; +} + +static int generate(int argc, const char **argv) +{ + const struct dist *dist = NULL; + const struct mode *mode = NULL; + int i, n, m, *arr; + + if (argc != 4) + return 1; + + dist = get_dist_by_name(argv[0]); + mode = get_mode_by_name(argv[1]); + n = strtol(argv[2], NULL, 10); + m = strtol(argv[3], NULL, 10); + if (!dist || !mode) + return 1; + + ALLOC_ARRAY(arr, n); + dist->fn(arr, n, m); + mode->fn(arr, n); + for (i = 0; i < n; i++) + printf("%08x\n", arr[i]); + free(arr); + return 0; +} + +static struct stats { + int get_next, set_next, compare; +} stats; + +struct number { + int value, rank; + struct number *next; +}; + +DEFINE_LIST_SORT_DEBUG(static, sort_numbers, struct number, next, + stats.get_next++, stats.set_next++); + +static int compare_numbers(const struct number *an, const struct number *bn) +{ + int a = an->value, b = bn->value; + stats.compare++; + return (a > b) - (a < b); +} + +static void clear_numbers(struct number *list) +{ + while (list) { + struct number *next = list->next; + free(list); + list = next; + } +} + +static int test(const struct dist *dist, const struct mode *mode, int n, int m) +{ + int *arr; + size_t i; + struct number *curr, *list, **tail; + int is_sorted = 1; + int is_stable = 1; + const char *verdict; + int result = -1; + + ALLOC_ARRAY(arr, n); + dist->fn(arr, n, m); + mode->fn(arr, n); + for (i = 0, tail = &list; i < n; i++) { + curr = xmalloc(sizeof(*curr)); + curr->value = arr[i]; + curr->rank = i; + *tail = curr; + tail = &curr->next; + } + *tail = NULL; + + stats.get_next = stats.set_next = stats.compare = 0; + sort_numbers(&list, compare_numbers); + + QSORT(arr, n, compare_ints); + for (i = 0, curr = list; i < n && curr; i++, curr = curr->next) { + if (arr[i] != curr->value) + is_sorted = 0; + if (curr->next && curr->value == curr->next->value && + curr->rank >= curr->next->rank) + is_stable = 0; + } + if (i < n) { + verdict = "too short"; + } else if (curr) { + verdict = "too long"; + } else if (!is_sorted) { + verdict = "not sorted"; + } else if (!is_stable) { + verdict = "unstable"; + } else { + verdict = "OK"; + result = 0; + } + + printf("%-9s %-16s %8d %8d %8d %8d %8d %s\n", + dist->name, mode->name, n, m, stats.get_next, stats.set_next, + stats.compare, verdict); + + clear_numbers(list); + free(arr); + + return result; +} + +/* + * A version of the qsort certification program from "Engineering a Sort + * Function" by Bentley and McIlroy, Software—Practice and Experience, + * Volume 23, Issue 11, 1249–1265 (November 1993). + */ +static int run_tests(int argc, const char **argv) +{ + const char *argv_default[] = { "100", "1023", "1024", "1025" }; + if (!argc) + return run_tests(ARRAY_SIZE(argv_default), argv_default); + printf("%-9s %-16s %8s %8s %8s %8s %8s %s\n", + "distribut", "mode", "n", "m", "get_next", "set_next", + "compare", "verdict"); + while (argc--) { + int i, j, m, n = strtol(*argv++, NULL, 10); + for (i = 0; i < ARRAY_SIZE(dist); i++) { + for (j = 0; j < ARRAY_SIZE(mode); j++) { + for (m = 1; m < 2 * n; m *= 2) { + if (test(&dist[i], &mode[j], n, m)) + return 1; + } + } + } + } + return 0; +} + +int cmd__mergesort(int argc, const char **argv) +{ + int i; + const char *sep; + + if (argc == 6 && !strcmp(argv[1], "generate")) + return generate(argc - 2, argv + 2); + if (argc == 2 && !strcmp(argv[1], "sort")) + return sort_stdin(); + if (argc > 1 && !strcmp(argv[1], "test")) + return run_tests(argc - 2, argv + 2); + fprintf(stderr, "usage: test-tool mergesort generate <distribution> <mode> <n> <m>\n"); + fprintf(stderr, " or: test-tool mergesort sort\n"); + fprintf(stderr, " or: test-tool mergesort test [<n>...]\n"); + fprintf(stderr, "\n"); + for (i = 0, sep = "distributions: "; i < ARRAY_SIZE(dist); i++, sep = ", ") + fprintf(stderr, "%s%s", sep, dist[i].name); + fprintf(stderr, "\n"); + for (i = 0, sep = "modes: "; i < ARRAY_SIZE(mode); i++, sep = ", ") + fprintf(stderr, "%s%s", sep, mode[i].name); + fprintf(stderr, "\n"); + return 129; +} diff --git a/t/helper/test-mktemp.c b/t/helper/test-mktemp.c new file mode 100644 index 0000000000..2290688940 --- /dev/null +++ b/t/helper/test-mktemp.c @@ -0,0 +1,15 @@ +/* + * test-mktemp.c: code to exercise the creation of temporary files + */ +#include "test-tool.h" +#include "git-compat-util.h" + +int cmd__mktemp(int argc, const char **argv) +{ + if (argc != 2) + usage("Expected 1 parameter defining the temporary file template"); + + xmkstemp(xstrdup(argv[1])); + + return 0; +} diff --git a/t/helper/test-name-hash.c b/t/helper/test-name-hash.c new file mode 100644 index 0000000000..af1d52de10 --- /dev/null +++ b/t/helper/test-name-hash.c @@ -0,0 +1,23 @@ +/* + * test-name-hash.c: Read a list of paths over stdin and report on their + * name-hash and full name-hash. + */ + +#include "test-tool.h" +#include "git-compat-util.h" +#include "pack-objects.h" +#include "strbuf.h" + +int cmd__name_hash(int argc UNUSED, const char **argv UNUSED) +{ + struct strbuf line = STRBUF_INIT; + + while (!strbuf_getline(&line, stdin)) { + printf("%10u ", pack_name_hash(line.buf)); + printf("%10u ", pack_name_hash_v2((unsigned const char *)line.buf)); + printf("%s\n", line.buf); + } + + strbuf_release(&line); + return 0; +} diff --git a/t/helper/test-online-cpus.c b/t/helper/test-online-cpus.c new file mode 100644 index 0000000000..47dc211711 --- /dev/null +++ b/t/helper/test-online-cpus.c @@ -0,0 +1,9 @@ +#include "test-tool.h" +#include "git-compat-util.h" +#include "thread-utils.h" + +int cmd__online_cpus(int argc UNUSED, const char **argv UNUSED) +{ + printf("%d\n", online_cpus()); + return 0; +} diff --git a/t/helper/test-pack-deltas.c b/t/helper/test-pack-deltas.c new file mode 100644 index 0000000000..4caa024b1e --- /dev/null +++ b/t/helper/test-pack-deltas.c @@ -0,0 +1,148 @@ +#define USE_THE_REPOSITORY_VARIABLE + +#include "test-tool.h" +#include "git-compat-util.h" +#include "delta.h" +#include "git-zlib.h" +#include "hash.h" +#include "hex.h" +#include "pack.h" +#include "pack-objects.h" +#include "parse-options.h" +#include "setup.h" +#include "strbuf.h" +#include "string-list.h" + +static const char *usage_str[] = { + "test-tool pack-deltas --num-objects <num-objects>", + NULL +}; + +static unsigned long do_compress(void **pptr, unsigned long size) +{ + git_zstream stream; + void *in, *out; + unsigned long maxsize; + + git_deflate_init(&stream, 1); + maxsize = git_deflate_bound(&stream, size); + + in = *pptr; + out = xmalloc(maxsize); + *pptr = out; + + stream.next_in = in; + stream.avail_in = size; + stream.next_out = out; + stream.avail_out = maxsize; + while (git_deflate(&stream, Z_FINISH) == Z_OK) + ; /* nothing */ + git_deflate_end(&stream); + + free(in); + return stream.total_out; +} + +static void write_ref_delta(struct hashfile *f, + struct object_id *oid, + struct object_id *base) +{ + unsigned char header[MAX_PACK_OBJECT_HEADER]; + unsigned long size, base_size, delta_size, compressed_size, hdrlen; + enum object_type type; + void *base_buf, *delta_buf; + void *buf = repo_read_object_file(the_repository, + oid, &type, + &size); + + if (!buf) + die("unable to read %s", oid_to_hex(oid)); + + base_buf = repo_read_object_file(the_repository, + base, &type, + &base_size); + + if (!base_buf) + die("unable to read %s", oid_to_hex(base)); + + delta_buf = diff_delta(base_buf, base_size, + buf, size, &delta_size, 0); + + compressed_size = do_compress(&delta_buf, delta_size); + + hdrlen = encode_in_pack_object_header(header, sizeof(header), + OBJ_REF_DELTA, delta_size); + hashwrite(f, header, hdrlen); + hashwrite(f, base->hash, the_repository->hash_algo->rawsz); + hashwrite(f, delta_buf, compressed_size); + + free(buf); + free(base_buf); + free(delta_buf); +} + +int cmd__pack_deltas(int argc, const char **argv) +{ + int num_objects = -1; + struct hashfile *f; + struct strbuf line = STRBUF_INIT; + struct option options[] = { + OPT_INTEGER('n', "num-objects", &num_objects, N_("the number of objects to write")), + OPT_END() + }; + + argc = parse_options(argc, argv, NULL, + options, usage_str, 0); + + if (argc || num_objects < 0) + usage_with_options(usage_str, options); + + setup_git_directory(); + + f = hashfd(the_repository->hash_algo, 1, "<stdout>"); + write_pack_header(f, num_objects); + + /* Read each line from stdin into 'line' */ + while (strbuf_getline_lf(&line, stdin) != EOF) { + const char *type_str, *content_oid_str, *base_oid_str = NULL; + struct object_id content_oid, base_oid; + struct string_list items = STRING_LIST_INIT_NODUP; + /* + * Tokenize into two or three parts: + * 1. REF_DELTA, OFS_DELTA, or FULL. + * 2. The object ID for the content object. + * 3. The object ID for the base object (optional). + */ + if (string_list_split_in_place(&items, line.buf, " ", 3) < 0) + die("invalid input format: %s", line.buf); + + if (items.nr < 2) + die("invalid input format: %s", line.buf); + + type_str = items.items[0].string; + content_oid_str = items.items[1].string; + + if (get_oid_hex(content_oid_str, &content_oid)) + die("invalid object: %s", content_oid_str); + if (items.nr >= 3) { + base_oid_str = items.items[2].string; + if (get_oid_hex(base_oid_str, &base_oid)) + die("invalid object: %s", base_oid_str); + } + string_list_clear(&items, 0); + + if (!strcmp(type_str, "REF_DELTA")) + write_ref_delta(f, &content_oid, &base_oid); + else if (!strcmp(type_str, "OFS_DELTA")) + die("OFS_DELTA not implemented"); + else if (!strcmp(type_str, "FULL")) + die("FULL not implemented"); + else + die("unknown pack type: %s", type_str); + } + + finalize_hashfile(f, NULL, FSYNC_COMPONENT_PACK, + CSUM_HASH_IN_STREAM | CSUM_FSYNC | CSUM_CLOSE); + strbuf_release(&line); + return 0; +} diff --git a/t/helper/test-pack-mtimes.c b/t/helper/test-pack-mtimes.c new file mode 100644 index 0000000000..d51aaa3dc4 --- /dev/null +++ b/t/helper/test-pack-mtimes.c @@ -0,0 +1,59 @@ +#define USE_THE_REPOSITORY_VARIABLE + +#include "test-tool.h" +#include "hex.h" +#include "strbuf.h" +#include "odb.h" +#include "packfile.h" +#include "pack-mtimes.h" +#include "setup.h" + +static void dump_mtimes(struct packed_git *p) +{ + uint32_t i; + if (load_pack_mtimes(p) < 0) + die("could not load pack .mtimes"); + + for (i = 0; i < p->num_objects; i++) { + struct object_id oid; + if (nth_packed_object_id(&oid, p, i) < 0) + die("could not load object id at position %"PRIu32, i); + + printf("%s %"PRIu32"\n", + oid_to_hex(&oid), nth_packed_mtime(p, i)); + } +} + +static const char *const pack_mtimes_usage = "\n" +" test-tool pack-mtimes <pack-name.mtimes>"; + +int cmd__pack_mtimes(int argc, const char **argv) +{ + struct strbuf buf = STRBUF_INIT; + struct packed_git *p; + + setup_git_directory(); + + if (argc != 2) + usage(pack_mtimes_usage); + + for (p = get_all_packs(the_repository); p; p = p->next) { + strbuf_addstr(&buf, basename(p->pack_name)); + strbuf_strip_suffix(&buf, ".pack"); + strbuf_addstr(&buf, ".mtimes"); + + if (!strcmp(buf.buf, argv[1])) + break; + + strbuf_reset(&buf); + } + + strbuf_release(&buf); + + if (!p) + die("could not find pack '%s'", argv[1]); + + dump_mtimes(p); + + return 0; +} diff --git a/t/helper/test-parse-options.c b/t/helper/test-parse-options.c new file mode 100644 index 0000000000..68579d83f3 --- /dev/null +++ b/t/helper/test-parse-options.c @@ -0,0 +1,381 @@ +#include "test-tool.h" +#include "parse-options.h" +#include "strbuf.h" +#include "string-list.h" +#include "trace2.h" + +static int boolean = 0; +static int integer = 0; +static unsigned long unsigned_integer = 0; +static timestamp_t timestamp; +static int abbrev = 7; +static int verbose = -1; /* unspecified */ +static int dry_run = 0, quiet = 0; +static char *string = NULL; +static char *file = NULL; +static int ambiguous; + +static struct { + int called; + const char *arg; + int unset; +} length_cb; + +static int mode34_callback(const struct option *opt, const char *arg, int unset) +{ + if (unset) + *(int *)opt->value = 0; + else if (!strcmp(arg, "3")) + *(int *)opt->value = 3; + else if (!strcmp(arg, "4")) + *(int *)opt->value = 4; + else + return error("invalid value for '%s': '%s'", "--mode34", arg); + return 0; +} + +static int length_callback(const struct option *opt, const char *arg, int unset) +{ + length_cb.called = 1; + length_cb.arg = arg; + length_cb.unset = unset; + + if (unset) + return 1; /* do not support unset */ + + *(int *)opt->value = strlen(arg); + return 0; +} + +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; +} + +static int collect_expect(const struct option *opt, const char *arg, int unset) +{ + struct string_list *expect; + struct string_list_item *item; + struct strbuf label = STRBUF_INIT; + const char *colon; + + if (!arg || unset) + die("malformed --expect option"); + + expect = (struct string_list *)opt->value; + colon = strchr(arg, ':'); + if (!colon) + die("malformed --expect option, lacking a colon"); + strbuf_add(&label, arg, colon - arg); + item = string_list_insert(expect, strbuf_detach(&label, NULL)); + if (item->util) + die("malformed --expect option, duplicate %s", label.buf); + item->util = (void *)arg; + return 0; +} + +__attribute__((format (printf,3,4))) +static void show(struct string_list *expect, int *status, const char *fmt, ...) +{ + struct string_list_item *item; + struct strbuf buf = STRBUF_INIT; + va_list args; + + va_start(args, fmt); + strbuf_vaddf(&buf, fmt, args); + va_end(args); + + if (!expect->nr) + printf("%s\n", buf.buf); + else { + char *colon = strchr(buf.buf, ':'); + if (!colon) + die("malformed output format, output lacking colon: %s", fmt); + *colon = '\0'; + item = string_list_lookup(expect, buf.buf); + *colon = ':'; + if (!item) + ; /* not among entries being checked */ + else { + if (strcmp((const char *)item->util, buf.buf)) { + printf("-%s\n", (char *)item->util); + printf("+%s\n", buf.buf); + *status = 1; + } + } + } + strbuf_release(&buf); +} + +int cmd__parse_options(int argc, const char **argv) +{ + const char *prefix = "prefix/"; + const char *usage[] = { + "test-tool parse-options <options>", + "", + "A helper function for the parse-options API.", + NULL + }; + struct string_list expect = STRING_LIST_INIT_NODUP; + struct string_list list = STRING_LIST_INIT_NODUP; + uint16_t u16 = 0; + int16_t i16 = 0; + + struct option options[] = { + OPT_BOOL(0, "yes", &boolean, "get a boolean"), + OPT_BOOL('D', "no-doubt", &boolean, "begins with 'no-'"), + { + .type = OPTION_SET_INT, + .short_name = 'B', + .long_name = "no-fear", + .value = &boolean, + .precision = sizeof(boolean), + .help = "be brave", + .flags = PARSE_OPT_NOARG | PARSE_OPT_NONEG, + .defval = 1, + }, + OPT_COUNTUP('b', "boolean", &boolean, "increment by one"), + OPT_BIT('4', "or4", &boolean, + "bitwise-or boolean with ...0100", 4), + OPT_NEGBIT(0, "neg-or4", &boolean, "same as --no-or4", 4), + OPT_GROUP(""), + OPT_INTEGER('i', "integer", &integer, "get a integer"), + OPT_INTEGER(0, "i16", &i16, "get a 16 bit integer"), + OPT_INTEGER('j', NULL, &integer, "get a integer, too"), + OPT_UNSIGNED('u', "unsigned", &unsigned_integer, "get an unsigned integer"), + OPT_UNSIGNED(0, "u16", &u16, "get a 16 bit unsigned integer"), + OPT_SET_INT(0, "set23", &integer, "set integer to 23", 23), + OPT_CMDMODE(0, "mode1", &integer, "set integer to 1 (cmdmode option)", 1), + OPT_CMDMODE(0, "mode2", &integer, "set integer to 2 (cmdmode option)", 2), + { + .type = OPTION_CALLBACK, + .long_name = "mode34", + .value = &integer, + .precision = sizeof(integer), + .argh = "(3|4)", + .help = "set integer to 3 or 4 (cmdmode option)", + .flags = PARSE_OPT_CMDMODE, + .callback = mode34_callback, + }, + OPT_CALLBACK('L', "length", &integer, "str", + "get length of <str>", length_callback), + OPT_FILENAME('F', "file", &file, "set file to <file>"), + OPT_GROUP("String options"), + OPT_STRING('s', "string", &string, "string", "get a string"), + OPT_STRING(0, "string2", &string, "str", "get another string"), + OPT_STRING(0, "st", &string, "st", "get another string (pervert ordering)"), + OPT_STRING('o', NULL, &string, "str", "get another string"), + OPT_NOOP_NOARG(0, "obsolete"), + OPT_SET_INT_F(0, "longhelp", &integer, "help text of this entry\n" + "spans multiple lines", 0, PARSE_OPT_NONEG), + OPT_STRING_LIST(0, "list", &list, "str", "add str to list"), + OPT_GROUP("Magic arguments"), + OPT_NUMBER_CALLBACK(&integer, "set integer to NUM", + number_callback), + { + .type = OPTION_COUNTUP, + .short_name = '+', + .value = &boolean, + .precision = sizeof(boolean), + .help = "same as -b", + .flags = PARSE_OPT_NOARG | PARSE_OPT_NONEG | PARSE_OPT_NODASH, + }, + { + .type = OPTION_COUNTUP, + .long_name = "ambiguous", + .value = &ambiguous, + .precision = sizeof(ambiguous), + .help = "positive ambiguity", + .flags = PARSE_OPT_NOARG | PARSE_OPT_NONEG, + }, + { + .type = OPTION_COUNTUP, + .long_name = "no-ambiguous", + .value = &ambiguous, + .precision = sizeof(ambiguous), + .help = "negative ambiguity", + .flags = PARSE_OPT_NOARG | PARSE_OPT_NONEG, + }, + OPT_GROUP("Standard options"), + OPT__ABBREV(&abbrev), + OPT__VERBOSE(&verbose, "be verbose"), + OPT__DRY_RUN(&dry_run, "dry run"), + OPT__QUIET(&quiet, "be quiet"), + 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 ret = 0; + + trace2_cmd_name("_parse_"); + + argc = parse_options(argc, (const char **)argv, prefix, options, usage, 0); + + if (length_cb.called) { + const char *arg = length_cb.arg; + int unset = length_cb.unset; + show(&expect, &ret, "Callback: \"%s\", %d", + (arg ? arg : "not set"), unset); + } + show(&expect, &ret, "boolean: %d", boolean); + show(&expect, &ret, "integer: %d", integer); + show(&expect, &ret, "i16: %"PRIdMAX, (intmax_t) i16); + show(&expect, &ret, "unsigned: %lu", unsigned_integer); + show(&expect, &ret, "u16: %"PRIuMAX, (uintmax_t) u16); + show(&expect, &ret, "timestamp: %"PRItime, timestamp); + show(&expect, &ret, "string: %s", string ? string : "(not set)"); + show(&expect, &ret, "abbrev: %d", abbrev); + show(&expect, &ret, "verbose: %d", verbose); + show(&expect, &ret, "quiet: %d", quiet); + show(&expect, &ret, "dry run: %s", dry_run ? "yes" : "no"); + show(&expect, &ret, "file: %s", file ? file : "(not set)"); + + for (size_t i = 0; i < list.nr; i++) + show(&expect, &ret, "list: %s", list.items[i].string); + + for (int i = 0; i < argc; i++) + show(&expect, &ret, "arg %02d: %s", i, argv[i]); + + expect.strdup_strings = 1; + string_list_clear(&expect, 0); + string_list_clear(&list, 0); + free(file); + + return ret; +} + +static void print_args(int argc, const char **argv) +{ + int i; + for (i = 0; i < argc; i++) + printf("arg %02d: %s\n", i, argv[i]); +} + +static int parse_options_flags__cmd(int argc, const char **argv, + enum parse_opt_flags test_flags) +{ + const char *usage[] = { + "<...> cmd [options]", + NULL + }; + int opt = 0; + const struct option options[] = { + OPT_INTEGER('o', "opt", &opt, "an integer option"), + OPT_END() + }; + + argc = parse_options(argc, argv, NULL, options, usage, test_flags); + + printf("opt: %d\n", opt); + print_args(argc, argv); + + return 0; +} + +static enum parse_opt_flags test_flags = 0; +static const struct option test_flag_options[] = { + OPT_GROUP("flag-options:"), + OPT_BIT(0, "keep-dashdash", &test_flags, + "pass PARSE_OPT_KEEP_DASHDASH to parse_options()", + PARSE_OPT_KEEP_DASHDASH), + OPT_BIT(0, "stop-at-non-option", &test_flags, + "pass PARSE_OPT_STOP_AT_NON_OPTION to parse_options()", + PARSE_OPT_STOP_AT_NON_OPTION), + OPT_BIT(0, "keep-argv0", &test_flags, + "pass PARSE_OPT_KEEP_ARGV0 to parse_options()", + PARSE_OPT_KEEP_ARGV0), + OPT_BIT(0, "keep-unknown-opt", &test_flags, + "pass PARSE_OPT_KEEP_UNKNOWN_OPT to parse_options()", + PARSE_OPT_KEEP_UNKNOWN_OPT), + OPT_BIT(0, "no-internal-help", &test_flags, + "pass PARSE_OPT_NO_INTERNAL_HELP to parse_options()", + PARSE_OPT_NO_INTERNAL_HELP), + OPT_BIT(0, "subcommand-optional", &test_flags, + "pass PARSE_OPT_SUBCOMMAND_OPTIONAL to parse_options()", + PARSE_OPT_SUBCOMMAND_OPTIONAL), + OPT_END() +}; + +int cmd__parse_options_flags(int argc, const char **argv) +{ + const char *usage[] = { + "test-tool parse-options-flags [flag-options] cmd [options]", + NULL + }; + + argc = parse_options(argc, argv, NULL, test_flag_options, usage, + PARSE_OPT_STOP_AT_NON_OPTION); + + if (!argc || strcmp(argv[0], "cmd")) { + error("'cmd' is mandatory"); + usage_with_options(usage, test_flag_options); + } + + return parse_options_flags__cmd(argc, argv, test_flags); +} + +static int subcmd_one(int argc, const char **argv, const char *prefix UNUSED, + struct repository *repo UNUSED) +{ + printf("fn: subcmd_one\n"); + print_args(argc, argv); + return 0; +} + +static int subcmd_two(int argc, const char **argv, const char *prefix UNUSED, + struct repository *repo UNUSED) +{ + printf("fn: subcmd_two\n"); + print_args(argc, argv); + return 0; +} + +static int parse_subcommand__cmd(int argc, const char **argv, + enum parse_opt_flags test_flags) +{ + const char *usage[] = { + "<...> cmd subcmd-one", + "<...> cmd subcmd-two", + NULL + }; + parse_opt_subcommand_fn *fn = NULL; + int opt = 0; + struct option options[] = { + OPT_SUBCOMMAND("subcmd-one", &fn, subcmd_one), + OPT_SUBCOMMAND("subcmd-two", &fn, subcmd_two), + OPT_INTEGER('o', "opt", &opt, "an integer option"), + OPT_END() + }; + + if (test_flags & PARSE_OPT_SUBCOMMAND_OPTIONAL) + fn = subcmd_one; + argc = parse_options(argc, argv, NULL, options, usage, test_flags); + + printf("opt: %d\n", opt); + + return fn(argc, argv, NULL, NULL); +} + +int cmd__parse_subcommand(int argc, const char **argv) +{ + const char *usage[] = { + "test-tool parse-subcommand [flag-options] cmd <subcommand>", + NULL + }; + + argc = parse_options(argc, argv, NULL, test_flag_options, usage, + PARSE_OPT_STOP_AT_NON_OPTION); + + if (!argc || strcmp(argv[0], "cmd")) { + error("'cmd' is mandatory"); + usage_with_options(usage, test_flag_options); + } + + return parse_subcommand__cmd(argc, argv, test_flags); +} diff --git a/t/helper/test-parse-pathspec-file.c b/t/helper/test-parse-pathspec-file.c new file mode 100644 index 0000000000..89ecefd1cd --- /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" + +int cmd__parse_pathspec_file(int argc, const char **argv) +{ + struct pathspec pathspec; + 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); + free(pathspec_from_file); + return 0; +} diff --git a/t/helper/test-partial-clone.c b/t/helper/test-partial-clone.c new file mode 100644 index 0000000000..d848800749 --- /dev/null +++ b/t/helper/test-partial-clone.c @@ -0,0 +1,46 @@ +#include "test-tool.h" +#include "hex.h" +#include "repository.h" +#include "odb.h" +#include "setup.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_algop(oid_hex, &oid, &p, r.hash_algo)) + die("could not parse oid"); + if (odb_read_object_info_extended(r.objects, &oid, &oi, 0)) + die("could not obtain object info"); + printf("%d\n", (int) size); + + repo_clear(&r); +} + +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 new file mode 100644 index 0000000000..086238c826 --- /dev/null +++ b/t/helper/test-path-utils.c @@ -0,0 +1,542 @@ +#define USE_THE_REPOSITORY_VARIABLE +#define DISABLE_SIGN_COMPARE_WARNINGS + +#include "test-tool.h" +#include "abspath.h" +#include "environment.h" +#include "path.h" +#include "read-cache-ll.h" +#include "setup.h" +#include "string-list.h" +#include "trace.h" +#include "utf8.h" +#include "copy.h" + +/* + * A "string_list_each_func_t" function that normalizes an entry from + * GIT_CEILING_DIRECTORIES. If the path is unusable for some reason, + * die with an explanation. + */ +static int normalize_ceiling_entry(struct string_list_item *item, + void *data UNUSED) +{ + char *ceil = item->string; + + if (!*ceil) + die("Empty path is not supported"); + if (!is_absolute_path(ceil)) + die("Path \"%s\" is not absolute", ceil); + if (normalize_path_copy(ceil, ceil) < 0) + die("Path \"%s\" could not be normalized", ceil); + return 1; +} + +static void normalize_argv_string(const char **var, const char *input) +{ + if (!strcmp(input, "<null>")) + *var = NULL; + else if (!strcmp(input, "<empty>")) + *var = ""; + else + *var = input; + + if (*var && (**var == '<' || **var == '(')) + die("Bad value: %s", input); +} + +struct test_data { + const char *from; /* input: transform from this ... */ + const char *to; /* output: ... to this. */ + const char *alternative; /* output: ... or this. */ +}; + +/* + * Compatibility wrappers for OpenBSD, whose basename(3) and dirname(3) + * have const parameters. + */ +static char *posix_basename(char *path) +{ + return basename(path); +} + +static char *posix_dirname(char *path) +{ + return dirname(path); +} + +static int test_function(struct test_data *data, char *(*func)(char *input), + const char *funcname) +{ + int failed = 0, i; + char buffer[1024]; + char *to; + + for (i = 0; data[i].to; i++) { + if (!data[i].from) + to = func(NULL); + else { + xsnprintf(buffer, sizeof(buffer), "%s", data[i].from); + to = func(buffer); + } + if (!strcmp(to, data[i].to)) + continue; + if (!data[i].alternative) + error("FAIL: %s(%s) => '%s' != '%s'", + funcname, data[i].from, to, data[i].to); + else if (!strcmp(to, data[i].alternative)) + continue; + else + error("FAIL: %s(%s) => '%s' != '%s', '%s'", + funcname, data[i].from, to, data[i].to, + data[i].alternative); + failed = 1; + } + return failed; +} + +static struct test_data basename_data[] = { + /* --- POSIX type paths --- */ + { NULL, "." }, + { "", "." }, + { ".", "." }, + { "..", ".." }, + { "/", "/" }, + { "//", "/", "//" }, + { "///", "/", "//" }, + { "////", "/", "//" }, + { "usr", "usr" }, + { "/usr", "usr" }, + { "/usr/", "usr" }, + { "/usr//", "usr" }, + { "/usr/lib", "lib" }, + { "usr/lib", "lib" }, + { "usr/lib///", "lib" }, + +#if defined(__MINGW32__) || defined(_MSC_VER) + /* --- win32 type paths --- */ + { "\\usr", "usr" }, + { "\\usr\\", "usr" }, + { "\\usr\\\\", "usr" }, + { "\\usr\\lib", "lib" }, + { "usr\\lib", "lib" }, + { "usr\\lib\\\\\\", "lib" }, + { "C:/usr", "usr" }, + { "C:/usr", "usr" }, + { "C:/usr/", "usr" }, + { "C:/usr//", "usr" }, + { "C:/usr/lib", "lib" }, + { "C:usr/lib", "lib" }, + { "C:usr/lib///", "lib" }, + { "C:", "." }, + { "C:a", "a" }, + { "C:/", "/" }, + { "C:///", "/" }, + { "\\", "\\", "/" }, + { "\\\\", "\\", "/" }, + { "\\\\\\", "\\", "/" }, +#endif + { NULL, NULL } +}; + +static struct test_data dirname_data[] = { + /* --- POSIX type paths --- */ + { NULL, "." }, + { "", "." }, + { ".", "." }, + { "..", "." }, + { "/", "/" }, + { "//", "/", "//" }, + { "///", "/", "//" }, + { "////", "/", "//" }, + { "usr", "." }, + { "/usr", "/" }, + { "/usr/", "/" }, + { "/usr//", "/" }, + { "/usr/lib", "/usr" }, + { "usr/lib", "usr" }, + { "usr/lib///", "usr" }, + +#if defined(__MINGW32__) || defined(_MSC_VER) + /* --- win32 type paths --- */ + { "\\", "\\" }, + { "\\\\", "\\\\" }, + { "\\usr", "\\" }, + { "\\usr\\", "\\" }, + { "\\usr\\\\", "\\" }, + { "\\usr\\lib", "\\usr" }, + { "usr\\lib", "usr" }, + { "usr\\lib\\\\\\", "usr" }, + { "C:a", "C:." }, + { "C:/", "C:/" }, + { "C:///", "C:/" }, + { "C:/usr", "C:/" }, + { "C:/usr/", "C:/" }, + { "C:/usr//", "C:/" }, + { "C:/usr/lib", "C:/usr" }, + { "C:usr/lib", "C:usr" }, + { "C:usr/lib///", "C:usr" }, + { "\\\\\\", "\\" }, + { "\\\\\\\\", "\\" }, + { "C:", "C:.", "." }, +#endif + { NULL, NULL } +}; + +static int 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])); + int rv = normalize_path_copy(buf, argv[2]); + puts(rv ? "++failed++" : buf); + free(buf); + return 0; + } + + if (argc >= 2 && !strcmp(argv[1], "real_path")) { + struct strbuf realpath = STRBUF_INIT; + while (argc > 2) { + strbuf_realpath(&realpath, argv[2], 1); + puts(realpath.buf); + argc--; + argv++; + } + strbuf_release(&realpath); + return 0; + } + + if (argc >= 2 && !strcmp(argv[1], "readlink")) { + struct strbuf target = STRBUF_INIT; + while (argc > 2) { + if (strbuf_readlink(&target, argv[2], 0) < 0) + die_errno("cannot read link at '%s'", argv[2]); + puts(target.buf); + argc--; + argv++; + } + strbuf_release(&target); + return 0; + } + + if (argc >= 2 && !strcmp(argv[1], "absolute_path")) { + while (argc > 2) { + puts(absolute_path(argv[2])); + argc--; + argv++; + } + return 0; + } + + if (argc == 4 && !strcmp(argv[1], "longest_ancestor_length")) { + int len; + struct string_list ceiling_dirs = STRING_LIST_INIT_DUP; + char *path = xstrdup(argv[2]); + + /* + * We have to normalize the arguments because under + * Windows, bash mangles arguments that look like + * absolute POSIX paths or colon-separate lists of + * absolute POSIX paths into DOS paths (e.g., + * "/foo:/foo/bar" might be converted to + * "D:\Src\msysgit\foo;D:\Src\msysgit\foo\bar"), + * whereas longest_ancestor_length() requires paths + * that use forward slashes. + */ + if (normalize_path_copy(path, path)) + die("Path \"%s\" could not be normalized", argv[2]); + string_list_split(&ceiling_dirs, argv[3], PATH_SEP, -1); + filter_string_list(&ceiling_dirs, 0, + normalize_ceiling_entry, NULL); + len = longest_ancestor_length(path, &ceiling_dirs); + string_list_clear(&ceiling_dirs, 0); + free(path); + printf("%d\n", len); + return 0; + } + + if (argc >= 4 && !strcmp(argv[1], "prefix_path")) { + const char *prefix = argv[2]; + int prefix_len = strlen(prefix); + int nongit_ok; + setup_git_directory_gently(&nongit_ok); + while (argc > 3) { + char *pfx = prefix_path(prefix, prefix_len, argv[3]); + + puts(pfx); + free(pfx); + argc--; + argv++; + } + return 0; + } + + if (argc == 4 && !strcmp(argv[1], "strip_path_suffix")) { + char *prefix = strip_path_suffix(argv[2], argv[3]); + printf("%s\n", prefix ? prefix : "(null)"); + free(prefix); + return 0; + } + + if (argc == 3 && !strcmp(argv[1], "print_path")) { + puts(argv[2]); + return 0; + } + + if (argc == 4 && !strcmp(argv[1], "relative_path")) { + struct strbuf sb = STRBUF_INIT; + const char *in, *prefix, *rel; + normalize_argv_string(&in, argv[2]); + normalize_argv_string(&prefix, argv[3]); + rel = relative_path(in, prefix, &sb); + if (!rel) + puts("(null)"); + else + puts(strlen(rel) > 0 ? rel : "(empty)"); + strbuf_release(&sb); + return 0; + } + + if (argc == 2 && !strcmp(argv[1], "basename")) + return test_function(basename_data, posix_basename, argv[1]); + + if (argc == 2 && !strcmp(argv[1], "dirname")) + return test_function(dirname_data, posix_dirname, argv[1]); + + if (argc > 2 && !strcmp(argv[1], "is_dotgitmodules")) { + 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; + } + + if (argc > 1 && !strcmp(argv[1], "is_path_owned_by_current_user")) { + int res = 0; + + for (int i = 2; i < argc; i++) { + struct strbuf buf = STRBUF_INIT; + + if (is_path_owned_by_current_user(argv[i], &buf)) + printf("'%s' is owned by current SID\n", argv[i]); + else { + printf("'%s' is not owned by current SID: %s\n", argv[i], buf.buf); + res = 1; + } + + strbuf_release(&buf); + } + + 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-path-walk.c b/t/helper/test-path-walk.c new file mode 100644 index 0000000000..fe63002c2b --- /dev/null +++ b/t/helper/test-path-walk.c @@ -0,0 +1,134 @@ +#define USE_THE_REPOSITORY_VARIABLE + +#include "test-tool.h" +#include "dir.h" +#include "environment.h" +#include "hex.h" +#include "object-name.h" +#include "object.h" +#include "pretty.h" +#include "revision.h" +#include "setup.h" +#include "parse-options.h" +#include "strbuf.h" +#include "path-walk.h" +#include "oid-array.h" + +static const char * const path_walk_usage[] = { + N_("test-tool path-walk <options> -- <revision-options>"), + NULL +}; + +struct path_walk_test_data { + uintmax_t batch_nr; + + uintmax_t commit_nr; + uintmax_t tree_nr; + uintmax_t blob_nr; + uintmax_t tag_nr; +}; + +static int emit_block(const char *path, struct oid_array *oids, + enum object_type type, void *data) +{ + struct path_walk_test_data *tdata = data; + const char *typestr; + + if (type == OBJ_TREE) + tdata->tree_nr += oids->nr; + else if (type == OBJ_BLOB) + tdata->blob_nr += oids->nr; + else if (type == OBJ_COMMIT) + tdata->commit_nr += oids->nr; + else if (type == OBJ_TAG) + tdata->tag_nr += oids->nr; + else + BUG("we do not understand this type"); + + typestr = type_name(type); + + /* This should never be output during tests. */ + if (!oids->nr) + printf("%"PRIuMAX":%s:%s:EMPTY\n", + tdata->batch_nr, typestr, path); + + for (size_t i = 0; i < oids->nr; i++) { + struct object *o = lookup_unknown_object(the_repository, + &oids->oid[i]); + printf("%"PRIuMAX":%s:%s:%s%s\n", + tdata->batch_nr, typestr, path, + oid_to_hex(&oids->oid[i]), + o->flags & UNINTERESTING ? ":UNINTERESTING" : ""); + } + + tdata->batch_nr++; + return 0; +} + +int cmd__path_walk(int argc, const char **argv) +{ + int res, stdin_pl = 0; + struct rev_info revs = REV_INFO_INIT; + struct path_walk_info info = PATH_WALK_INFO_INIT; + struct path_walk_test_data data = { 0 }; + struct option options[] = { + OPT_BOOL(0, "blobs", &info.blobs, + N_("toggle inclusion of blob objects")), + OPT_BOOL(0, "commits", &info.commits, + N_("toggle inclusion of commit objects")), + OPT_BOOL(0, "tags", &info.tags, + N_("toggle inclusion of tag objects")), + OPT_BOOL(0, "trees", &info.trees, + N_("toggle inclusion of tree objects")), + OPT_BOOL(0, "prune", &info.prune_all_uninteresting, + N_("toggle pruning of uninteresting paths")), + OPT_BOOL(0, "edge-aggressive", &info.edge_aggressive, + N_("toggle aggressive edge walk")), + OPT_BOOL(0, "stdin-pl", &stdin_pl, + N_("read a pattern list over stdin")), + OPT_END(), + }; + + setup_git_directory(); + revs.repo = the_repository; + + argc = parse_options(argc, argv, NULL, + options, path_walk_usage, + PARSE_OPT_KEEP_UNKNOWN_OPT | PARSE_OPT_KEEP_ARGV0); + + if (argc > 1) + setup_revisions(argc, argv, &revs, NULL); + else + usage(path_walk_usage[0]); + + info.revs = &revs; + info.path_fn = emit_block; + info.path_fn_data = &data; + + if (stdin_pl) { + struct strbuf in = STRBUF_INIT; + CALLOC_ARRAY(info.pl, 1); + + info.pl->use_cone_patterns = 1; + + strbuf_fread(&in, 2048, stdin); + add_patterns_from_buffer(in.buf, in.len, "", 0, info.pl); + strbuf_release(&in); + } + + res = walk_objects_by_path(&info); + + printf("commits:%" PRIuMAX "\n" + "trees:%" PRIuMAX "\n" + "blobs:%" PRIuMAX "\n" + "tags:%" PRIuMAX "\n", + data.commit_nr, data.tree_nr, data.blob_nr, data.tag_nr); + + if (info.pl) { + clear_pattern_list(info.pl); + free(info.pl); + } + + release_revisions(&revs); + return res; +} diff --git a/t/helper/test-pcre2-config.c b/t/helper/test-pcre2-config.c new file mode 100644 index 0000000000..5d0b2a2e10 --- /dev/null +++ b/t/helper/test-pcre2-config.c @@ -0,0 +1,11 @@ +#include "test-tool.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..4daa82f00f --- /dev/null +++ b/t/helper/test-pkt-line.c @@ -0,0 +1,188 @@ +#include "git-compat-util.h" +#include "test-tool.h" +#include "pkt-line.h" +#include "sideband.h" +#include "write-or-die.h" +#include "parse-options.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 pack_raw_stdin(void) +{ + struct strbuf sb = STRBUF_INIT; + + if (strbuf_read(&sb, 0, 0) < 0) + die_errno("failed to read from stdin"); + packet_write(1, sb.buf, sb.len); + strbuf_release(&sb); +} + +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(int argc, const char **argv) +{ + struct packet_reader reader; + int options = PACKET_READ_GENTLE_ON_EOF; + int chomp_newline = 1; + int reader_use_sideband = 0; + const char *const unpack_sideband_usage[] = { + "test_tool unpack_sideband [options...]", NULL + }; + struct option cmd_options[] = { + OPT_BOOL(0, "reader-use-sideband", &reader_use_sideband, + "set use_sideband bit for packet reader (Default: off)"), + OPT_BOOL(0, "chomp-newline", &chomp_newline, + "chomp newline in packet (Default: on)"), + OPT_END() + }; + + argc = parse_options(argc, argv, "", cmd_options, unpack_sideband_usage, + 0); + if (argc > 0) + usage_msg_opt(_("too many arguments"), unpack_sideband_usage, + cmd_options); + + if (chomp_newline) + options |= PACKET_READ_CHOMP_NEWLINE; + packet_reader_init(&reader, 0, NULL, 0, options); + reader.use_sideband = reader_use_sideband; + + while (packet_reader_read(&reader) != PACKET_READ_EOF) { + int band; + int fd; + + switch (reader.status) { + case PACKET_READ_EOF: + break; + case PACKET_READ_NORMAL: + /* + * When the "use_sideband" field of the reader is turned + * on, sideband packets other than the payload have been + * parsed and consumed in packet_reader_read(), and only + * the payload arrives here. + */ + if (reader.use_sideband) { + write_or_die(1, reader.line, reader.pktlen - 1); + break; + } + + 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 *foo = "Foo.\n"; + const char *bar = "Bar.\n"; + const char *part1 = "Hello,"; + const char *primary = "\001primary: regular output\n"; + const char *part2 = " world!\n"; + + /* Each sideband message has a trailing newline character. */ + send_sideband(1, 2, foo, strlen(foo), LARGE_PACKET_MAX); + send_sideband(1, 2, bar, strlen(bar), LARGE_PACKET_MAX); + + /* + * One sideband message is divided into part1 and part2 + * by the primary message. + */ + 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); + + /* + * We use unpack_sideband() to consume packets. A flush packet + * is required to end parsing. + */ + packet_flush(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], "pack-raw-stdin")) + pack_raw_stdin(); + else if (!strcmp(argv[1], "unpack")) + unpack(); + else if (!strcmp(argv[1], "unpack-sideband")) + unpack_sideband(argc - 1, argv + 1); + 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-proc-receive.c b/t/helper/test-proc-receive.c new file mode 100644 index 0000000000..8eccc34216 --- /dev/null +++ b/t/helper/test-proc-receive.c @@ -0,0 +1,207 @@ +#include "test-tool.h" +#include "connect.h" +#include "hex.h" +#include "parse-options.h" +#include "pkt-line.h" +#include "sigchain.h" +#include "string-list.h" + +static const char *const 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_any(reader->line, &old_oid, &p) == GIT_HASH_UNKNOWN || + *p++ != ' ' || + parse_oid_hex_any(p, &new_oid, &p) == GIT_HASH_UNKNOWN || + *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) +{ + 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() + }; + + 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); + + while (commands) { + struct command *next = commands->next; + free(commands); + commands = next; + } + string_list_clear(&push_options, 0); + + return 0; +} diff --git a/t/helper/test-progress.c b/t/helper/test-progress.c new file mode 100644 index 0000000000..1f75b7bd19 --- /dev/null +++ b/t/helper/test-progress.c @@ -0,0 +1,101 @@ +/* + * A test helper to exercise the progress display. + * + * Reads instructions from standard input, one instruction per line: + * + * "start <total>[ <title>]" - Call start_progress(title, total), + * Uses the default title of "Working hard" + * if the " <title>" is omitted. + * "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. + * "stop" - Call stop_progress(). + * + * See 't0500-progress-display.sh' for examples. + */ + +#define USE_THE_REPOSITORY_VARIABLE +#define GIT_TEST_PROGRESS_ONLY + +#include "test-tool.h" +#include "parse-options.h" +#include "progress.h" +#include "repository.h" +#include "strbuf.h" +#include "string-list.h" + +int cmd__progress(int argc, const char **argv) +{ + const char *const default_title = "Working hard"; + struct string_list titles = STRING_LIST_INIT_DUP; + struct strbuf line = STRBUF_INIT; + struct progress *progress = NULL; + + const char *usage[] = { + "test-tool progress <stdin", + NULL + }; + struct option options[] = { + OPT_END(), + }; + + argc = parse_options(argc, argv, NULL, options, usage, 0); + if (argc) + usage_with_options(usage, options); + + progress_testing = 1; + while (strbuf_getline(&line, stdin) != EOF) { + char *end; + + if (skip_prefix(line.buf, "start ", (const char **) &end)) { + uint64_t total = strtoull(end, &end, 10); + const char *title; + + /* + * We can't use "end + 1" as an argument to + * start_progress(), it doesn't xstrdup() its + * "title" argument. We need to hold onto a + * valid "char *" for it until the end. + */ + if (!*end) + title = default_title; + else if (*end == ' ') + title = string_list_insert(&titles, end + 1)->string; + else + die("invalid input: '%s'", line.buf); + + progress = start_progress(the_repository, title, total); + } else if (skip_prefix(line.buf, "progress ", (const char **) &end)) { + uint64_t item_count = strtoull(end, &end, 10); + if (*end != '\0') + die("invalid input: '%s'", 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'", line.buf); + test_ms = strtoull(end + 1, &end, 10); + if (*end != '\0') + die("invalid input: '%s'", 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 if (!strcmp(line.buf, "stop")) { + stop_progress(&progress); + } else { + die("invalid input: '%s'", line.buf); + } + } + strbuf_release(&line); + string_list_clear(&titles, 0); + + return 0; +} diff --git a/t/helper/test-reach.c b/t/helper/test-reach.c new file mode 100644 index 0000000000..028ec00306 --- /dev/null +++ b/t/helper/test-reach.c @@ -0,0 +1,191 @@ +#define USE_THE_REPOSITORY_VARIABLE + +#include "test-tool.h" +#include "commit.h" +#include "commit-reach.h" +#include "gettext.h" +#include "hex.h" +#include "object-name.h" +#include "ref-filter.h" +#include "setup.h" +#include "string-list.h" +#include "tag.h" + +static void print_sorted_commit_ids(struct commit_list *list) +{ + 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 (size_t 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; + size_t 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 (repo_get_oid_committish(the_repository, buf.buf + 2, &oid)) + die("failed to resolve %s", buf.buf + 2); + + orig = parse_object(r, &oid); + peeled = deref_tag_noverify(the_repository, orig); + + if (!peeled) + die("failed to load commit for input %s resulting in oid %s", + 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", + 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], + repo_in_merge_bases(the_repository, A, B)); + else if (!strcmp(av[1], "in_merge_bases_many")) + printf("%s(A,X):%d\n", av[1], + repo_in_merge_bases_many(the_repository, A, X_nr, X_array, 0)); + 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_branch_base_for_tip")) + printf("%s(A,X):%d\n", av[1], get_branch_base_for_tip(r, A, X_array, X_nr)); + else if (!strcmp(av[1], "get_merge_bases_many")) { + struct commit_list *list = NULL; + if (repo_get_merge_bases_many(the_repository, + A, X_nr, + X_array, + &list) < 0) + exit(128); + printf("%s(A,X):\n", av[1]); + print_sorted_commit_ids(list); + free_commit_list(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); + free_commit_list(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 = REF_FILTER_INIT; + 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)); + clear_contains_cache(&cache); + } else if (!strcmp(av[1], "get_reachable_subset")) { + const int reachable_flag = 1; + int 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 (size_t 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); + free_commit_list(list); + } + + object_array_clear(&X_obj); + strbuf_release(&buf); + free_commit_list(X); + free_commit_list(Y); + free(X_array); + free(Y_array); + return 0; +} diff --git a/t/helper/test-read-cache.c b/t/helper/test-read-cache.c new file mode 100644 index 0000000000..9ae71cefb3 --- /dev/null +++ b/t/helper/test-read-cache.c @@ -0,0 +1,42 @@ +#define USE_THE_REPOSITORY_VARIABLE + +#include "test-tool.h" +#include "config.h" +#include "environment.h" +#include "read-cache-ll.h" +#include "repository.h" +#include "setup.h" + +int cmd__read_cache(int argc, const char **argv) +{ + int i, cnt = 1; + const char *name = NULL; + + if (argc > 1 && skip_prefix(argv[1], "--print-and-refresh=", &name)) { + argc--; + argv++; + } + + if (argc == 2) + cnt = strtol(argv[1], NULL, 0); + setup_git_directory(); + repo_config(the_repository, git_default_config, NULL); + + for (i = 0; i < cnt; i++) { + repo_read_index(the_repository); + if (name) { + int pos; + + refresh_index(the_repository->index, REFRESH_QUIET, + NULL, NULL, NULL); + pos = index_name_pos(the_repository->index, name, strlen(name)); + if (pos < 0) + die("%s not in index", name); + printf("%s is%s up to date\n", name, + ce_uptodate(the_repository->index->cache[pos]) ? "" : " not"); + write_file(name, "%d\n", i); + } + discard_index(the_repository->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..ef5339bbee --- /dev/null +++ b/t/helper/test-read-graph.c @@ -0,0 +1,102 @@ +#define USE_THE_REPOSITORY_VARIABLE + +#include "test-tool.h" +#include "commit-graph.h" +#include "repository.h" +#include "odb.h" +#include "bloom.h" +#include "setup.h" + +static void dump_graph_info(struct commit_graph *graph) +{ + 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"); + + printf("options:"); + if (graph->bloom_filter_settings) + printf(" bloom(%"PRIu32",%"PRIu32",%"PRIu32")", + graph->bloom_filter_settings->hash_version, + graph->bloom_filter_settings->bits_per_entry, + graph->bloom_filter_settings->num_hashes); + if (graph->read_generation_data) + printf(" read_generation_data"); + if (graph->topo_levels) + printf(" topo_levels"); + printf("\n"); +} + +static void dump_graph_bloom_filters(struct commit_graph *graph) +{ + uint32_t i; + + for (i = 0; i < graph->num_commits + graph->num_commits_in_base; i++) { + struct bloom_filter filter = { 0 }; + size_t j; + + if (load_bloom_filter_from_graph(graph, &filter, i) < 0) { + fprintf(stderr, "missing Bloom filter for graph " + "position %"PRIu32"\n", i); + continue; + } + + for (j = 0; j < filter.len; j++) + printf("%02x", filter.data[j]); + if (filter.len) + printf("\n"); + } +} + +int cmd__read_graph(int argc, const char **argv) +{ + struct commit_graph *graph = NULL; + struct odb_source *source; + int ret = 0; + + setup_git_directory(); + source = the_repository->objects->sources; + + prepare_repo_settings(the_repository); + + graph = read_commit_graph_one(the_repository, source); + if (!graph) { + ret = 1; + goto done; + } + + if (argc <= 1) + dump_graph_info(graph); + else if (!strcmp(argv[1], "bloom-filters")) + dump_graph_bloom_filters(graph); + else { + fprintf(stderr, "unknown sub-command: '%s'\n", argv[1]); + ret = 1; + } + +done: + free_commit_graph(graph); + return ret; +} diff --git a/t/helper/test-read-midx.c b/t/helper/test-read-midx.c new file mode 100644 index 0000000000..da2aa036b5 --- /dev/null +++ b/t/helper/test-read-midx.c @@ -0,0 +1,158 @@ +#define USE_THE_REPOSITORY_VARIABLE + +#include "test-tool.h" +#include "hex.h" +#include "midx.h" +#include "repository.h" +#include "odb.h" +#include "pack-bitmap.h" +#include "packfile.h" +#include "setup.h" +#include "gettext.h" +#include "pack-revindex.h" + +static int read_midx_file(const char *object_dir, const char *checksum, + int show_objects) +{ + uint32_t i; + struct multi_pack_index *m; + + setup_git_directory(); + m = load_multi_pack_index(the_repository, object_dir, 1); + + if (!m) + return 1; + + if (checksum) { + while (m && strcmp(hash_to_hex(get_midx_checksum(m)), checksum)) + m = m->base_midx; + 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 + m->num_objects_in_base); + fill_midx_entry(the_repository, &oid, &e, m); + + printf("%s %"PRIu64"\t%s\n", + oid_to_hex(&oid), e.offset, e.p->pack_name); + } + } + + close_midx(m); + + return 0; +} + +static int read_midx_checksum(const char *object_dir) +{ + struct multi_pack_index *m; + + setup_git_directory(); + m = load_multi_pack_index(the_repository, object_dir, 1); + if (!m) + return 1; + printf("%s\n", hash_to_hex(get_midx_checksum(m))); + + close_midx(m); + return 0; +} + +static int read_midx_preferred_pack(const char *object_dir) +{ + struct multi_pack_index *midx = NULL; + uint32_t preferred_pack; + + setup_git_directory(); + + midx = load_multi_pack_index(the_repository, object_dir, 1); + if (!midx) + return 1; + + if (midx_preferred_pack(midx, &preferred_pack) < 0) { + warning(_("could not determine MIDX preferred pack")); + close_midx(midx); + return 1; + } + + printf("%s\n", midx->pack_names[preferred_pack]); + close_midx(midx); + return 0; +} + +static int read_midx_bitmapped_packs(const char *object_dir) +{ + struct multi_pack_index *midx = NULL; + struct bitmapped_pack pack; + uint32_t i; + + setup_git_directory(); + + midx = load_multi_pack_index(the_repository, object_dir, 1); + if (!midx) + return 1; + + for (i = 0; i < midx->num_packs + midx->num_packs_in_base; i++) { + if (nth_bitmapped_pack(the_repository, midx, &pack, i) < 0) { + close_midx(midx); + return 1; + } + + printf("%s\n", pack_basename(pack.p)); + printf(" bitmap_pos: %"PRIuMAX"\n", (uintmax_t)pack.bitmap_pos); + printf(" bitmap_nr: %"PRIuMAX"\n", (uintmax_t)pack.bitmap_nr); + } + + close_midx(midx); + + return 0; +} + +int cmd__read_midx(int argc, const char **argv) +{ + if (!(argc == 2 || argc == 3 || argc == 4)) + usage("read-midx [--show-objects|--checksum|--preferred-pack|--bitmap] <object-dir> <checksum>"); + + if (!strcmp(argv[1], "--show-objects")) + return read_midx_file(argv[2], argv[3], 1); + else if (!strcmp(argv[1], "--checksum")) + return read_midx_checksum(argv[2]); + else if (!strcmp(argv[1], "--preferred-pack")) + return read_midx_preferred_pack(argv[2]); + else if (!strcmp(argv[1], "--bitmap")) + return read_midx_bitmapped_packs(argv[2]); + return read_midx_file(argv[1], argv[2], 0); +} diff --git a/t/helper/test-ref-store.c b/t/helper/test-ref-store.c new file mode 100644 index 0000000000..8d9a271845 --- /dev/null +++ b/t/helper/test-ref-store.c @@ -0,0 +1,351 @@ +#define USE_THE_REPOSITORY_VARIABLE + +#include "test-tool.h" +#include "hex.h" +#include "refs.h" +#include "setup.h" +#include "worktree.h" +#include "odb.h" +#include "path.h" +#include "repository.h" +#include "strbuf.h" +#include "revision.h" + +struct flag_definition { + const char *name; + uint64_t mask; +}; + +#define FLAG_DEF(x) \ + { \ +#x, (x) \ + } + +static unsigned int parse_flags(const char *str, struct flag_definition *defs) +{ + struct string_list masks = STRING_LIST_INIT_DUP; + unsigned int result = 0; + + if (!strcmp(str, "0")) + return 0; + + string_list_split(&masks, str, ',', 64); + for (size_t i = 0; i < masks.nr; i++) { + const char *name = masks.items[i].string; + struct flag_definition *def = defs; + int found = 0; + while (def->name) { + if (!strcmp(def->name, name)) { + result |= def->mask; + found = 1; + break; + } + def++; + } + if (!found) + die("unknown flag \"%s\"", name); + } + + string_list_clear(&masks, 0); + return result; +} + +static struct flag_definition empty_flags[] = { { NULL, 0 } }; + +static const char *notnull(const char *arg, const char *name) +{ + if (!arg) + die("%s required", name); + return arg; +} + +static unsigned int arg_flags(const char *arg, const char *name, + struct flag_definition *defs) +{ + return parse_flags(notnull(arg, name), defs); +} + +static const char **get_store(const char **argv, struct ref_store **refs) +{ + const char *gitdir; + + if (!argv[0]) { + die("ref store required"); + } else if (!strcmp(argv[0], "main")) { + *refs = get_main_ref_store(the_repository); + } else if (skip_prefix(argv[0], "submodule:", &gitdir)) { + struct strbuf sb = STRBUF_INIT; + + if (!repo_submodule_path_append(the_repository, + &sb, gitdir, "objects/")) + die("computing submodule path failed"); + odb_add_to_alternates_memory(the_repository->objects, sb.buf); + strbuf_release(&sb); + + *refs = repo_get_submodule_ref_store(the_repository, gitdir); + } else if (skip_prefix(argv[0], "worktree:", &gitdir)) { + struct worktree **p, **worktrees = get_worktrees(); + + for (p = worktrees; *p; p++) { + struct worktree *wt = *p; + + if (!wt->id) { + /* special case for main worktree */ + if (!strcmp(gitdir, "main")) + break; + } else if (!strcmp(gitdir, wt->id)) + break; + } + if (!*p) + die("no such worktree: %s", gitdir); + + *refs = get_worktree_ref_store(*p); + free_worktrees(worktrees); + } else + die("unknown backend %s", argv[0]); + + if (!*refs) + die("no ref store"); + + /* consume store-specific optional arguments if needed */ + + return argv + 1; +} + +static int cmd_create_symref(struct ref_store *refs, const char **argv) +{ + const char *refname = notnull(*argv++, "refname"); + const char *target = notnull(*argv++, "target"); + const char *logmsg = *argv++; + + return refs_update_symref(refs, refname, target, logmsg); +} + +static struct flag_definition transaction_flags[] = { + FLAG_DEF(REF_NO_DEREF), + FLAG_DEF(REF_FORCE_CREATE_REFLOG), + FLAG_DEF(REF_SKIP_OID_VERIFICATION), + FLAG_DEF(REF_SKIP_REFNAME_VERIFICATION), + FLAG_DEF(REF_SKIP_CREATE_REFLOG), + { NULL, 0 } +}; + +static int cmd_delete_refs(struct ref_store *refs, const char **argv) +{ + unsigned int flags = arg_flags(*argv++, "flags", transaction_flags); + const char *msg = *argv++; + struct string_list refnames = STRING_LIST_INIT_NODUP; + int result; + + while (*argv) + string_list_append(&refnames, *argv++); + + result = refs_delete_refs(refs, msg, &refnames, flags); + string_list_clear(&refnames, 0); + return result; +} + +static int cmd_rename_ref(struct ref_store *refs, const char **argv) +{ + const char *oldref = notnull(*argv++, "oldref"); + const char *newref = notnull(*argv++, "newref"); + const char *logmsg = *argv++; + + return refs_rename_ref(refs, oldref, newref, logmsg); +} + +static int each_ref(const char *refname, const char *referent UNUSED, const struct object_id *oid, + int flags, void *cb_data UNUSED) +{ + printf("%s %s 0x%x\n", oid_to_hex(oid), refname, flags); + return 0; +} + +static int cmd_for_each_ref(struct ref_store *refs, const char **argv) +{ + const char *prefix = notnull(*argv++, "prefix"); + + return refs_for_each_ref_in(refs, prefix, each_ref, NULL); +} + +static int cmd_for_each_ref__exclude(struct ref_store *refs, const char **argv) +{ + const char *prefix = notnull(*argv++, "prefix"); + const char **exclude_patterns = argv; + + return refs_for_each_fullref_in(refs, prefix, exclude_patterns, each_ref, + NULL); +} + +static int cmd_resolve_ref(struct ref_store *refs, const char **argv) +{ + struct object_id oid = *null_oid(the_hash_algo); + const char *refname = notnull(*argv++, "refname"); + int resolve_flags = arg_flags(*argv++, "resolve-flags", empty_flags); + int flags; + const char *ref; + + ref = refs_resolve_ref_unsafe(refs, refname, resolve_flags, + &oid, &flags); + printf("%s %s 0x%x\n", oid_to_hex(&oid), ref ? ref : "(null)", flags); + return ref ? 0 : 1; +} + +static int cmd_verify_ref(struct ref_store *refs, const char **argv) +{ + const char *refname = notnull(*argv++, "refname"); + struct strbuf err = STRBUF_INIT; + int ret; + + ret = refs_verify_refname_available(refs, refname, NULL, NULL, 0, &err); + if (err.len) + puts(err.buf); + return ret; +} + +static int each_reflog(const char *refname, void *cb_data UNUSED) +{ + printf("%s\n", refname); + return 0; +} + +static int cmd_for_each_reflog(struct ref_store *refs, + const char **argv UNUSED) +{ + return refs_for_each_reflog(refs, each_reflog, NULL); +} + +static int each_reflog_ent(struct object_id *old_oid, struct object_id *new_oid, + const char *committer, timestamp_t timestamp, + int tz, const char *msg, void *cb_data UNUSED) +{ + printf("%s %s %s %" PRItime " %+05d%s%s", oid_to_hex(old_oid), + oid_to_hex(new_oid), committer, timestamp, tz, + *msg == '\n' ? "" : "\t", msg); + return 0; +} + +static int cmd_for_each_reflog_ent(struct ref_store *refs, const char **argv) +{ + const char *refname = notnull(*argv++, "refname"); + + return refs_for_each_reflog_ent(refs, refname, each_reflog_ent, refs); +} + +static int cmd_for_each_reflog_ent_reverse(struct ref_store *refs, const char **argv) +{ + const char *refname = notnull(*argv++, "refname"); + + return refs_for_each_reflog_ent_reverse(refs, refname, each_reflog_ent, refs); +} + +static int cmd_reflog_exists(struct ref_store *refs, const char **argv) +{ + const char *refname = notnull(*argv++, "refname"); + + return !refs_reflog_exists(refs, refname); +} + +static int cmd_create_reflog(struct ref_store *refs, const char **argv) +{ + const char *refname = notnull(*argv++, "refname"); + struct strbuf err = STRBUF_INIT; + int ret; + + ret = refs_create_reflog(refs, refname, &err); + if (err.len) + puts(err.buf); + return ret; +} + +static int cmd_delete_reflog(struct ref_store *refs, const char **argv) +{ + const char *refname = notnull(*argv++, "refname"); + + return refs_delete_reflog(refs, refname); +} + +static int cmd_delete_ref(struct ref_store *refs, const char **argv) +{ + const char *msg = notnull(*argv++, "msg"); + const char *refname = notnull(*argv++, "refname"); + const char *sha1_buf = notnull(*argv++, "old-sha1"); + unsigned int flags = arg_flags(*argv++, "flags", transaction_flags); + struct object_id old_oid; + + if (get_oid_hex(sha1_buf, &old_oid)) + die("cannot parse %s as %s", sha1_buf, the_hash_algo->name); + + 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++, "new-sha1"); + const char *old_sha1_buf = notnull(*argv++, "old-sha1"); + unsigned int flags = arg_flags(*argv++, "flags", transaction_flags); + struct object_id old_oid, *old_oid_ptr = NULL; + struct object_id new_oid; + + if (*old_sha1_buf) { + if (get_oid_hex(old_sha1_buf, &old_oid)) + die("cannot parse %s as %s", old_sha1_buf, the_hash_algo->name); + old_oid_ptr = &old_oid; + } + if (get_oid_hex(new_sha1_buf, &new_oid)) + die("cannot parse %s as %s", new_sha1_buf, the_hash_algo->name); + + return refs_update_ref(refs, msg, refname, + &new_oid, old_oid_ptr, + flags, UPDATE_REFS_DIE_ON_ERR); +} + +struct command { + const char *name; + int (*func)(struct ref_store *refs, const char **argv); +}; + +static struct command commands[] = { + { "create-symref", cmd_create_symref }, + { "delete-refs", cmd_delete_refs }, + { "rename-ref", cmd_rename_ref }, + { "for-each-ref", cmd_for_each_ref }, + { "for-each-ref--exclude", cmd_for_each_ref__exclude }, + { "resolve-ref", cmd_resolve_ref }, + { "verify-ref", cmd_verify_ref }, + { "for-each-reflog", cmd_for_each_reflog }, + { "for-each-reflog-ent", cmd_for_each_reflog_ent }, + { "for-each-reflog-ent-reverse", cmd_for_each_reflog_ent_reverse }, + { "reflog-exists", cmd_reflog_exists }, + { "create-reflog", cmd_create_reflog }, + { "delete-reflog", cmd_delete_reflog }, + /* + * backend transaction functions can't be tested separately + */ + { "delete-ref", cmd_delete_ref }, + { "update-ref", cmd_update_ref }, + { NULL, NULL } +}; + +int cmd__ref_store(int argc UNUSED, const char **argv) +{ + struct ref_store *refs; + const char *func; + struct command *cmd; + + setup_git_directory(); + + argv = get_store(argv + 1, &refs); + + func = *argv++; + if (!func) + die("ref function required"); + for (cmd = commands; cmd->name; cmd++) { + if (!strcmp(func, cmd->name)) + return cmd->func(refs, argv); + } + die("unknown function %s", func); + return 0; +} diff --git a/t/helper/test-reftable.c b/t/helper/test-reftable.c new file mode 100644 index 0000000000..b16c0722c8 --- /dev/null +++ b/t/helper/test-reftable.c @@ -0,0 +1,267 @@ +#include "git-compat-util.h" +#include "hash.h" +#include "hex.h" +#include "reftable/system.h" +#include "reftable/reftable-constants.h" +#include "reftable/reftable-error.h" +#include "reftable/reftable-merged.h" +#include "reftable/reftable-stack.h" +#include "reftable/reftable-table.h" +#include "test-tool.h" + +static void print_help(void) +{ + printf("usage: dump [-st] arg\n\n" + "options: \n" + " -b dump blocks\n" + " -t dump table\n" + " -s dump stack\n" + " -6 sha256 hash format\n" + " -h this help\n" + "\n"); +} + +static int dump_blocks(const char *tablename) +{ + struct reftable_table_iterator ti = { 0 }; + struct reftable_block_source src = { 0 }; + struct reftable_table *table = NULL; + uint8_t section_type = 0; + int err; + + err = reftable_block_source_from_file(&src, tablename); + if (err < 0) + goto done; + + err = reftable_table_new(&table, &src, tablename); + if (err < 0) + goto done; + + err = reftable_table_iterator_init(&ti, table); + if (err < 0) + goto done; + + printf("header:\n"); + printf(" block_size: %d\n", table->block_size); + + while (1) { + const struct reftable_block *block; + + err = reftable_table_iterator_next(&ti, &block); + if (err < 0) + goto done; + if (err > 0) + break; + + if (block->block_type != section_type) { + const char *section; + switch (block->block_type) { + case REFTABLE_BLOCK_TYPE_LOG: + section = "log"; + break; + case REFTABLE_BLOCK_TYPE_REF: + section = "ref"; + break; + case REFTABLE_BLOCK_TYPE_OBJ: + section = "obj"; + break; + case REFTABLE_BLOCK_TYPE_INDEX: + section = "idx"; + break; + default: + err = -1; + goto done; + } + + section_type = block->block_type; + printf("%s:\n", section); + } + + printf(" - length: %u\n", block->restart_off); + printf(" restarts: %u\n", block->restart_count); + } + +done: + reftable_table_iterator_release(&ti); + reftable_table_decref(table); + return err; +} + +static int dump_table(struct reftable_merged_table *mt) +{ + struct reftable_iterator it = { NULL }; + struct reftable_ref_record ref = { NULL }; + struct reftable_log_record log = { NULL }; + const struct git_hash_algo *algop; + int err; + + err = reftable_merged_table_init_ref_iterator(mt, &it); + if (err < 0) + return err; + + err = reftable_iterator_seek_ref(&it, ""); + if (err < 0) + return err; + + algop = &hash_algos[hash_algo_by_id(reftable_merged_table_hash_id(mt))]; + + while (1) { + err = reftable_iterator_next_ref(&it, &ref); + if (err > 0) + break; + if (err < 0) + return err; + + printf("ref{%s(%" PRIu64 ") ", ref.refname, ref.update_index); + switch (ref.value_type) { + case REFTABLE_REF_SYMREF: + printf("=> %s", ref.value.symref); + break; + case REFTABLE_REF_VAL2: + printf("val 2 %s", hash_to_hex_algop(ref.value.val2.value, algop)); + printf("(T %s)", hash_to_hex_algop(ref.value.val2.target_value, algop)); + break; + case REFTABLE_REF_VAL1: + printf("val 1 %s", hash_to_hex_algop(ref.value.val1, algop)); + break; + case REFTABLE_REF_DELETION: + printf("delete"); + break; + } + printf("}\n"); + } + reftable_iterator_destroy(&it); + reftable_ref_record_release(&ref); + + err = reftable_merged_table_init_log_iterator(mt, &it); + if (err < 0) + return err; + + err = reftable_iterator_seek_log(&it, ""); + if (err < 0) + return err; + + while (1) { + err = reftable_iterator_next_log(&it, &log); + if (err > 0) + break; + if (err < 0) + return err; + + switch (log.value_type) { + case REFTABLE_LOG_DELETION: + printf("log{%s(%" PRIu64 ") delete\n", log.refname, + log.update_index); + break; + case REFTABLE_LOG_UPDATE: + printf("log{%s(%" PRIu64 ") %s <%s> %" PRIu64 " %04d\n", + log.refname, log.update_index, + log.value.update.name ? log.value.update.name : "", + log.value.update.email ? log.value.update.email : "", + log.value.update.time, + log.value.update.tz_offset); + printf("%s => ", hash_to_hex_algop(log.value.update.old_hash, algop)); + printf("%s\n\n%s\n}\n", hash_to_hex_algop(log.value.update.new_hash, algop), + log.value.update.message ? log.value.update.message : ""); + break; + } + } + reftable_iterator_destroy(&it); + reftable_log_record_release(&log); + return 0; +} + +static int dump_stack(const char *stackdir, uint32_t hash_id) +{ + struct reftable_stack *stack = NULL; + struct reftable_write_options opts = { .hash_id = hash_id }; + struct reftable_merged_table *merged = NULL; + + int err = reftable_new_stack(&stack, stackdir, &opts); + if (err < 0) + goto done; + + merged = reftable_stack_merged_table(stack); + err = dump_table(merged); +done: + if (stack) + reftable_stack_destroy(stack); + return err; +} + +static int dump_reftable(const char *tablename) +{ + struct reftable_block_source src = { 0 }; + struct reftable_merged_table *mt = NULL; + struct reftable_table *table = NULL; + int err; + + err = reftable_block_source_from_file(&src, tablename); + if (err < 0) + goto done; + + err = reftable_table_new(&table, &src, tablename); + if (err < 0) + goto done; + + err = reftable_merged_table_new(&mt, &table, 1, + reftable_table_hash_id(table)); + if (err < 0) + goto done; + + err = dump_table(mt); + +done: + reftable_merged_table_free(mt); + reftable_table_decref(table); + return err; +} + +int cmd__dump_reftable(int argc, const char **argv) +{ + int err = 0; + int opt_dump_blocks = 0; + int opt_dump_table = 0; + int opt_dump_stack = 0; + uint32_t opt_hash_id = REFTABLE_HASH_SHA1; + const char *arg = NULL, *argv0 = argv[0]; + + for (; argc > 1; argv++, argc--) + if (*argv[1] != '-') + break; + else if (!strcmp("-b", argv[1])) + opt_dump_blocks = 1; + else if (!strcmp("-t", argv[1])) + opt_dump_table = 1; + else if (!strcmp("-6", argv[1])) + opt_hash_id = REFTABLE_HASH_SHA256; + else if (!strcmp("-s", argv[1])) + opt_dump_stack = 1; + else if (!strcmp("-?", argv[1]) || !strcmp("-h", argv[1])) { + print_help(); + return 2; + } + + if (argc != 2) { + fprintf(stderr, "need argument\n"); + print_help(); + return 2; + } + + arg = argv[1]; + + if (opt_dump_blocks) { + err = dump_blocks(arg); + } else if (opt_dump_table) { + err = dump_reftable(arg); + } else if (opt_dump_stack) { + err = dump_stack(arg, opt_hash_id); + } + + if (err < 0) { + fprintf(stderr, "%s: %s: %s\n", argv0, arg, + reftable_error_str(err)); + return 1; + } + return 0; +} diff --git a/t/helper/test-regex.c b/t/helper/test-regex.c new file mode 100644 index 0000000000..366bd70976 --- /dev/null +++ b/t/helper/test-regex.c @@ -0,0 +1,117 @@ +#include "test-tool.h" +#include "gettext.h" + +struct reg_flag { + const char *name; + int flag; +}; + +static struct reg_flag reg_flags[] = { + { "EXTENDED", REG_EXTENDED }, + { "NEWLINE", REG_NEWLINE }, + { "ICASE", REG_ICASE }, + { "NOTBOL", REG_NOTBOL }, + { "NOTEOL", REG_NOTEOL }, +#ifdef REG_STARTEND + { "STARTEND", REG_STARTEND }, +#endif + { NULL, 0 } +}; + +static int test_regex_bug(void) +{ + const char *pat = "[^={} \t]+"; + const char *str = "={}\nfred"; + regex_t r; + regmatch_t m[1]; + + if (regcomp(&r, pat, REG_EXTENDED | REG_NEWLINE)) + die("failed regcomp() for pattern '%s'", pat); + if (regexec(&r, str, 1, m, 0)) + die("no match of pattern '%s' to string '%s'", pat, str); + + /* https://sourceware.org/bugzilla/show_bug.cgi?id=3957 */ + if (m[0].rm_so == 3) /* matches '\n' when it should not */ + die("regex bug confirmed: re-build git with NO_REGEX=1"); + + regfree(&r); + return 0; +} + +int cmd__regex(int argc, const char **argv) +{ + const char *pat; + const char *str; + int ret, silent = 0, flags = 0; + regex_t r; + regmatch_t m[1]; + char errbuf[64]; + + 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(); + + 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) + goto cleanup; + + ret = regexec(&r, str, 1, m, 0); + if (ret) { + if (silent || ret == REG_NOMATCH) + goto cleanup; + + regerror(ret, &r, errbuf, sizeof(errbuf)); + die("failed regexec() for subject '%s' (%s)", str, errbuf); + } + +cleanup: + regfree(&r); + return ret; +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..63c37de33d --- /dev/null +++ b/t/helper/test-repository.c @@ -0,0 +1,102 @@ +#define USE_THE_REPOSITORY_VARIABLE + +#include "test-tool.h" +#include "commit-graph.h" +#include "commit.h" +#include "environment.h" +#include "hex.h" +#include "object.h" +#include "repository.h" +#include "setup.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); + + repo_clear(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); + + repo_clear(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 new file mode 100644 index 0000000000..071f5bd1e2 --- /dev/null +++ b/t/helper/test-revision-walking.c @@ -0,0 +1,74 @@ +/* + * test-revision-walking.c: test revision walking API. + * + * (C) 2012 Heiko Voigt <hvoigt@hvoigt.net> + * + * This code is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#define USE_THE_REPOSITORY_VARIABLE + +#include "test-tool.h" +#include "commit.h" +#include "diff.h" +#include "repository.h" +#include "revision.h" +#include "setup.h" + +static void print_commit(struct commit *commit) +{ + struct strbuf sb = STRBUF_INIT; + struct pretty_print_context ctx = {0}; + ctx.date_mode.type = DATE_NORMAL; + repo_format_commit_message(the_repository, commit, " %m %s", &sb, + &ctx); + printf("%s\n", sb.buf); + strbuf_release(&sb); +} + +static int run_revision_walk(void) +{ + struct rev_info rev; + struct commit *commit; + const char *argv[] = {NULL, "--all", NULL}; + int argc = ARRAY_SIZE(argv) - 1; + int got_revision = 0; + + repo_init_revisions(the_repository, &rev, NULL); + setup_revisions(argc, argv, &rev, NULL); + if (prepare_revision_walk(&rev)) + die("revision walk setup failed"); + + while ((commit = get_revision(&rev)) != NULL) { + print_commit(commit); + got_revision = 1; + } + + reset_revision_walk(); + release_revisions(&rev); + return got_revision; +} + +int cmd__revision_walking(int argc, const char **argv) +{ + if (argc < 2) + return 1; + + setup_git_directory(); + + if (!strcmp(argv[1], "run-twice")) { + printf("1st\n"); + if (!run_revision_walk()) + return 1; + printf("2nd\n"); + if (!run_revision_walk()) + return 1; + + return 0; + } + + fprintf(stderr, "check usage\n"); + return 1; +} diff --git a/t/helper/test-rot13-filter.c b/t/helper/test-rot13-filter.c new file mode 100644 index 0000000000..ad37e10034 --- /dev/null +++ b/t/helper/test-rot13-filter.c @@ -0,0 +1,383 @@ +/* + * Example implementation for the Git filter protocol version 2 + * See Documentation/gitattributes.adoc, section "Filter Protocol" + * + * Usage: test-tool rot13-filter [--always-delay] --log=<path> <capabilities> + * + * Log path defines a debug log file that the script writes to. The + * subsequent arguments define a list of supported protocol capabilities + * ("clean", "smudge", etc). + * + * When --always-delay is given all pathnames with the "can-delay" flag + * that don't appear on the list below are delayed with a count of 1 + * (see more below). + * + * This implementation supports special test cases: + * (1) If data with the pathname "clean-write-fail.r" is processed with + * a "clean" operation then the write operation will die. + * (2) If data with the pathname "smudge-write-fail.r" is processed with + * a "smudge" operation then the write operation will die. + * (3) If data with the pathname "error.r" is processed with any + * operation then the filter signals that it cannot or does not want + * to process the file. + * (4) If data with the pathname "abort.r" is processed with any + * operation then the filter signals that it cannot or does not want + * to process the file and any file after that is processed with the + * same command. + * (5) If data with a pathname that is a key in the delay hash is + * requested (e.g. "test-delay10.a") then the filter responds with + * a "delay" status and sets the "requested" field in the delay hash. + * The filter will signal the availability of this object after + * "count" (field in delay hash) "list_available_blobs" commands. + * (6) If data with the pathname "missing-delay.a" is processed that the + * filter will drop the path from the "list_available_blobs" response. + * (7) If data with the pathname "invalid-delay.a" is processed that the + * filter will add the path "unfiltered" which was not delayed before + * to the "list_available_blobs" response. + */ + +#include "test-tool.h" +#include "pkt-line.h" +#include "string-list.h" +#include "strmap.h" +#include "parse-options.h" + +static FILE *logfile; +static int always_delay, has_clean_cap, has_smudge_cap; +static struct strmap delay = STRMAP_INIT; + +static inline const char *str_or_null(const char *str) +{ + return str ? str : "(null)"; +} + +static char *rot13(char *str) +{ + char *c; + for (c = str; *c; c++) + if (isalpha(*c)) + *c += tolower(*c) < 'n' ? 13 : -13; + return str; +} + +static char *get_value(char *buf, const char *key) +{ + const char *orig_buf = buf; + if (!buf || + !skip_prefix((const char *)buf, key, (const char **)&buf) || + !skip_prefix((const char *)buf, "=", (const char **)&buf) || + !*buf) + die("expected key '%s', got '%s'", key, str_or_null(orig_buf)); + return buf; +} + +/* + * Read a text packet, expecting that it is in the form "key=value" for + * the given key. An EOF does not trigger any error and is reported + * back to the caller with NULL. Die if the "key" part of "key=value" does + * not match the given key, or the value part is empty. + */ +static char *packet_key_val_read(const char *key) +{ + char *buf; + if (packet_read_line_gently(0, NULL, &buf) < 0) + return NULL; + return xstrdup(get_value(buf, key)); +} + +static inline void assert_remote_capability(struct strset *caps, const char *cap) +{ + if (!strset_contains(caps, cap)) + die("required '%s' capability not available from remote", cap); +} + +static void read_capabilities(struct strset *remote_caps) +{ + for (;;) { + char *buf = packet_read_line(0, NULL); + if (!buf) + break; + strset_add(remote_caps, get_value(buf, "capability")); + } + + assert_remote_capability(remote_caps, "clean"); + assert_remote_capability(remote_caps, "smudge"); + assert_remote_capability(remote_caps, "delay"); +} + +static void check_and_write_capabilities(struct strset *remote_caps, + const char **caps, int nr_caps) +{ + int i; + for (i = 0; i < nr_caps; i++) { + if (!strset_contains(remote_caps, caps[i])) + die("our capability '%s' is not available from remote", + caps[i]); + packet_write_fmt(1, "capability=%s\n", caps[i]); + } + packet_flush(1); +} + +struct delay_entry { + int requested, count; + char *output; +}; + +static void free_delay_entries(void) +{ + struct hashmap_iter iter; + struct strmap_entry *ent; + + strmap_for_each_entry(&delay, &iter, ent) { + struct delay_entry *delay_entry = ent->value; + free(delay_entry->output); + free(delay_entry); + } + strmap_clear(&delay, 0); +} + +static void add_delay_entry(const char *pathname, int count, int requested) +{ + struct delay_entry *entry = xcalloc(1, sizeof(*entry)); + entry->count = count; + entry->requested = requested; + if (strmap_put(&delay, pathname, entry)) + BUG("adding the same path twice to delay hash?"); +} + +static void reply_list_available_blobs_cmd(void) +{ + struct hashmap_iter iter; + struct strmap_entry *ent; + struct string_list_item *str_item; + struct string_list paths = STRING_LIST_INIT_NODUP; + + /* flush */ + if (packet_read_line(0, NULL)) + die("bad list_available_blobs end"); + + strmap_for_each_entry(&delay, &iter, ent) { + struct delay_entry *delay_entry = ent->value; + if (!delay_entry->requested) + continue; + delay_entry->count--; + if (!strcmp(ent->key, "invalid-delay.a")) { + /* Send Git a pathname that was not delayed earlier */ + packet_write_fmt(1, "pathname=unfiltered"); + } + if (!strcmp(ent->key, "missing-delay.a")) { + /* Do not signal Git that this file is available */ + } else if (!delay_entry->count) { + string_list_append(&paths, ent->key); + packet_write_fmt(1, "pathname=%s", ent->key); + } + } + + /* Print paths in sorted order. */ + string_list_sort(&paths); + for_each_string_list_item(str_item, &paths) + fprintf(logfile, " %s", str_item->string); + string_list_clear(&paths, 0); + + packet_flush(1); + + fprintf(logfile, " [OK]\n"); + packet_write_fmt(1, "status=success"); + packet_flush(1); +} + +static void command_loop(void) +{ + for (;;) { + char *buf; + const char *output; + char *pathname; + struct delay_entry *entry; + struct strbuf input = STRBUF_INIT; + char *command = packet_key_val_read("command"); + + if (!command) { + fprintf(logfile, "STOP\n"); + break; + } + fprintf(logfile, "IN: %s", command); + + if (!strcmp(command, "list_available_blobs")) { + reply_list_available_blobs_cmd(); + free(command); + continue; + } + + pathname = packet_key_val_read("pathname"); + if (!pathname) + die("unexpected EOF while expecting pathname"); + fprintf(logfile, " %s", pathname); + + /* Read until flush */ + while ((buf = packet_read_line(0, NULL))) { + if (!strcmp(buf, "can-delay=1")) { + entry = strmap_get(&delay, pathname); + if (entry && !entry->requested) + entry->requested = 1; + else if (!entry && always_delay) + add_delay_entry(pathname, 1, 1); + } else if (starts_with(buf, "ref=") || + starts_with(buf, "treeish=") || + starts_with(buf, "blob=")) { + fprintf(logfile, " %s", buf); + } else { + /* + * In general, filters need to be graceful about + * new metadata, since it's documented that we + * can pass any key-value pairs, but for tests, + * let's be a little stricter. + */ + die("Unknown message '%s'", buf); + } + } + + read_packetized_to_strbuf(0, &input, 0); + fprintf(logfile, " %"PRIuMAX" [OK] -- ", (uintmax_t)input.len); + + entry = strmap_get(&delay, pathname); + if (entry && entry->output) { + output = entry->output; + } else if (!strcmp(pathname, "error.r") || !strcmp(pathname, "abort.r")) { + output = ""; + } else if (!strcmp(command, "clean") && has_clean_cap) { + output = rot13(input.buf); + } else if (!strcmp(command, "smudge") && has_smudge_cap) { + output = rot13(input.buf); + } else { + die("bad command '%s'", command); + } + + if (!strcmp(pathname, "error.r")) { + fprintf(logfile, "[ERROR]\n"); + packet_write_fmt(1, "status=error"); + packet_flush(1); + } else if (!strcmp(pathname, "abort.r")) { + fprintf(logfile, "[ABORT]\n"); + packet_write_fmt(1, "status=abort"); + packet_flush(1); + } else if (!strcmp(command, "smudge") && + (entry = strmap_get(&delay, pathname)) && + entry->requested == 1) { + fprintf(logfile, "[DELAYED]\n"); + packet_write_fmt(1, "status=delayed"); + packet_flush(1); + entry->requested = 2; + if (entry->output != output) { + free(entry->output); + entry->output = xstrdup(output); + } + } else { + int i, nr_packets = 0; + size_t output_len; + const char *p; + packet_write_fmt(1, "status=success"); + packet_flush(1); + + if (skip_prefix(pathname, command, &p) && + !strcmp(p, "-write-fail.r")) { + fprintf(logfile, "[WRITE FAIL]\n"); + die("%s write error", command); + } + + output_len = strlen(output); + fprintf(logfile, "OUT: %"PRIuMAX" ", (uintmax_t)output_len); + + if (write_packetized_from_buf_no_flush_count(output, + output_len, 1, &nr_packets)) + die("failed to write buffer to stdout"); + packet_flush(1); + + for (i = 0; i < nr_packets; i++) + fprintf(logfile, "."); + fprintf(logfile, " [OK]\n"); + + packet_flush(1); + } + free(pathname); + strbuf_release(&input); + free(command); + } +} + +static void packet_initialize(void) +{ + char *pkt_buf = packet_read_line(0, NULL); + + if (!pkt_buf || strcmp(pkt_buf, "git-filter-client")) + die("bad initialize: '%s'", str_or_null(pkt_buf)); + + pkt_buf = packet_read_line(0, NULL); + if (!pkt_buf || strcmp(pkt_buf, "version=2")) + die("bad version: '%s'", str_or_null(pkt_buf)); + + pkt_buf = packet_read_line(0, NULL); + if (pkt_buf) + die("bad version end: '%s'", pkt_buf); + + packet_write_fmt(1, "git-filter-server"); + packet_write_fmt(1, "version=2"); + packet_flush(1); +} + +static const char *const rot13_usage[] = { + "test-tool rot13-filter [--always-delay] --log=<path> <capabilities>", + NULL +}; + +int cmd__rot13_filter(int argc, const char **argv) +{ + int i, nr_caps; + struct strset remote_caps = STRSET_INIT; + const char *log_path = NULL; + + struct option options[] = { + OPT_BOOL(0, "always-delay", &always_delay, + "delay all paths with the can-delay flag"), + OPT_STRING(0, "log", &log_path, "path", + "path to the debug log file"), + OPT_END() + }; + nr_caps = parse_options(argc, argv, NULL, options, rot13_usage, + PARSE_OPT_STOP_AT_NON_OPTION); + + if (!log_path || !nr_caps) + usage_with_options(rot13_usage, options); + + logfile = fopen(log_path, "a"); + if (!logfile) + die_errno("failed to open log file"); + + for (i = 0; i < nr_caps; i++) { + if (!strcmp(argv[i], "smudge")) + has_smudge_cap = 1; + if (!strcmp(argv[i], "clean")) + has_clean_cap = 1; + } + + add_delay_entry("test-delay10.a", 1, 0); + add_delay_entry("test-delay11.a", 1, 0); + add_delay_entry("test-delay20.a", 2, 0); + add_delay_entry("test-delay10.b", 1, 0); + add_delay_entry("missing-delay.a", 1, 0); + add_delay_entry("invalid-delay.a", 1, 0); + + fprintf(logfile, "START\n"); + packet_initialize(); + + read_capabilities(&remote_caps); + check_and_write_capabilities(&remote_caps, argv, nr_caps); + fprintf(logfile, "init handshake complete\n"); + strset_clear(&remote_caps); + + command_loop(); + + if (fclose(logfile)) + die_errno("error closing logfile"); + free_delay_entries(); + return 0; +} diff --git a/t/helper/test-run-command.c b/t/helper/test-run-command.c new file mode 100644 index 0000000000..3719f23cc2 --- /dev/null +++ b/t/helper/test-run-command.c @@ -0,0 +1,480 @@ +/* + * test-run-command.c: test run command API. + * + * (C) 2009 Ilari Liusvaara <ilari.liusvaara@elisanet.fi> + * + * This code is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#define DISABLE_SIGN_COMPARE_WARNINGS + +#include "test-tool.h" +#include "run-command.h" +#include "strvec.h" +#include "strbuf.h" +#include "parse-options.h" +#include "string-list.h" +#include "thread-utils.h" +#include "wildmatch.h" + +static int number_callbacks; +static int parallel_next(struct child_process *cp, + struct strbuf *err, + void *cb, + void **task_cb UNUSED) +{ + struct child_process *d = cb; + if (number_callbacks >= 4) + return 0; + + strvec_pushv(&cp->args, d->args.v); + if (err) + strbuf_addstr(err, "preloaded output of a child\n"); + else + fprintf(stderr, "preloaded output of a child\n"); + + number_callbacks++; + return 1; +} + +static int no_job(struct child_process *cp UNUSED, + struct strbuf *err, + void *cb UNUSED, + void **task_cb UNUSED) +{ + if (err) + strbuf_addstr(err, "no further jobs available\n"); + else + fprintf(stderr, "no further jobs available\n"); + return 0; +} + +static int task_finished(int result UNUSED, + struct strbuf *err, + void *pp_cb UNUSED, + void *pp_task_cb UNUSED) +{ + if (err) + strbuf_addstr(err, "asking for a quick stop\n"); + else + fprintf(stderr, "asking for a quick stop\n"); + return 1; +} + +struct testsuite { + struct string_list tests, failed; + int next; + int quiet, immediate, verbose, verbose_log, trace, write_junit_xml; + const char *shell_path; +}; +#define TESTSUITE_INIT { \ + .tests = STRING_LIST_INIT_DUP, \ + .failed = STRING_LIST_INIT_DUP, \ +} + +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; + if (suite->shell_path) + strvec_push(&cp->args, suite->shell_path); + strvec_push(&cp->args, test); + 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 = 0; + 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() + }; + struct run_process_parallel_opts opts = { + .get_next_task = next_test, + .start_failure = test_failed, + .task_finished = test_finished, + .data = &suite, + }; + struct strbuf progpath = STRBUF_INIT; + size_t path_prefix_len; + + argc = parse_options(argc, argv, NULL, options, + testsuite_usage, PARSE_OPT_STOP_AT_NON_OPTION); + + if (max_jobs <= 0) + max_jobs = online_cpus(); + + /* + * If we run without a shell, execute the programs directly from CWD. + */ + suite.shell_path = getenv("TEST_SHELL_PATH"); + if (!suite.shell_path) + strbuf_addstr(&progpath, "./"); + path_prefix_len = progpath.len; + + dir = opendir("."); + if (!dir) + die("Could not open the current directory"); + while ((d = readdir(dir))) { + const char *p = d->d_name; + + if (!strcmp(p, ".") || !strcmp(p, "..")) + continue; + + /* No pattern: match all */ + if (!argc) { + strbuf_setlen(&progpath, path_prefix_len); + strbuf_addstr(&progpath, p); + string_list_append(&suite.tests, progpath.buf); + continue; + } + + for (i = 0; i < argc; i++) + if (!wildmatch(argv[i], p, 0)) { + strbuf_setlen(&progpath, path_prefix_len); + strbuf_addstr(&progpath, p); + string_list_append(&suite.tests, progpath.buf); + 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 %"PRIuMAX" tests (%d at a time)\n", + (uintmax_t)suite.tests.nr, max_jobs); + + opts.processes = max_jobs; + run_processes_parallel(&opts); + + if (suite.failed.nr > 0) { + ret = 1; + fprintf(stderr, "%"PRIuMAX" tests failed:\n\n", + (uintmax_t)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); + strbuf_release(&progpath); + + 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; + + strvec_pushv(&cp.args, 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; + int ret; + struct run_process_parallel_opts opts = { + .data = &proc, + }; + + if (argc > 1 && !strcmp(argv[1], "testsuite")) + return testsuite(argc - 1, argv + 1); + if (!strcmp(argv[1], "inherited-handle")) + return inherit_handle(argv[0]); + if (!strcmp(argv[1], "inherited-handle-child")) + return 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, argv[2]); + argv += 2; + argc -= 2; + } + if (argc < 3) { + ret = 1; + goto cleanup; + } + strvec_pushv(&proc.args, (const char **)argv + 2); + + if (!strcmp(argv[1], "start-command-ENOENT")) { + if (start_command(&proc) < 0 && errno == ENOENT) { + ret = 0; + goto cleanup; + } + fprintf(stderr, "FAIL %s\n", argv[1]); + return 1; + } + if (!strcmp(argv[1], "run-command")) { + ret = run_command(&proc); + goto cleanup; + } + + if (!strcmp(argv[1], "--ungroup")) { + argv += 1; + argc -= 1; + opts.ungroup = 1; + } + + jobs = atoi(argv[2]); + strvec_clear(&proc.args); + strvec_pushv(&proc.args, (const char **)argv + 3); + + if (!strcmp(argv[1], "run-command-parallel")) { + opts.get_next_task = parallel_next; + } else if (!strcmp(argv[1], "run-command-abort")) { + opts.get_next_task = parallel_next; + opts.task_finished = task_finished; + } else if (!strcmp(argv[1], "run-command-no-jobs")) { + opts.get_next_task = no_job; + opts.task_finished = task_finished; + } else { + ret = 1; + fprintf(stderr, "check usage\n"); + goto cleanup; + } + opts.processes = jobs; + run_processes_parallel(&opts); + ret = 0; +cleanup: + child_process_clear(&proc); + return ret; +} diff --git a/t/helper/test-scrap-cache-tree.c b/t/helper/test-scrap-cache-tree.c new file mode 100644 index 0000000000..64fff6e9e3 --- /dev/null +++ b/t/helper/test-scrap-cache-tree.c @@ -0,0 +1,24 @@ +#define USE_THE_REPOSITORY_VARIABLE + +#include "test-tool.h" +#include "lockfile.h" +#include "read-cache-ll.h" +#include "repository.h" +#include "setup.h" +#include "tree.h" +#include "cache-tree.h" + +int cmd__scrap_cache_tree(int ac UNUSED, const char **av UNUSED) +{ + struct lock_file index_lock = LOCK_INIT; + + setup_git_directory(); + repo_hold_locked_index(the_repository, &index_lock, LOCK_DIE_ON_ERROR); + if (repo_read_index(the_repository) < 0) + die("unable to read index file"); + cache_tree_free(&the_repository->index->cache_tree); + the_repository->index->cache_tree = NULL; + if (write_locked_index(the_repository->index, &index_lock, COMMIT_LOCK)) + die("unable to write index file"); + return 0; +} diff --git a/t/helper/test-serve-v2.c b/t/helper/test-serve-v2.c new file mode 100644 index 0000000000..63a200b8d4 --- /dev/null +++ b/t/helper/test-serve-v2.c @@ -0,0 +1,39 @@ +#define USE_THE_REPOSITORY_VARIABLE + +#include "test-tool.h" +#include "gettext.h" +#include "parse-options.h" +#include "repository.h" +#include "serve.h" +#include "setup.h" + +static char const * const serve_usage[] = { + N_("test-tool serve-v2 [<options>]"), + NULL +}; + +int cmd__serve_v2(int argc, const char **argv) +{ + int stateless_rpc = 0; + int advertise_capabilities = 0; + struct option options[] = { + OPT_BOOL(0, "stateless-rpc", &stateless_rpc, + N_("quit after a single request/response exchange")), + OPT_BOOL(0, "advertise-capabilities", &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_OPT); + + if (advertise_capabilities) + protocol_v2_advertise_capabilities(the_repository); + else + protocol_v2_serve_loop(the_repository, stateless_rpc); + + return 0; +} diff --git a/t/helper/test-sha1.c b/t/helper/test-sha1.c new file mode 100644 index 0000000000..349540c4df --- /dev/null +++ b/t/helper/test-sha1.c @@ -0,0 +1,20 @@ +#include "test-tool.h" +#include "hash.h" + +int cmd__sha1(int ac, const char **av) +{ + return cmd_hash_impl(ac, av, GIT_HASH_SHA1, 0); +} + +int cmd__sha1_is_sha1dc(int argc UNUSED, const char **argv UNUSED) +{ +#ifdef platform_SHA_IS_SHA1DC + return 0; +#endif + return 1; +} + +int cmd__sha1_unsafe(int ac, const char **av) +{ + return cmd_hash_impl(ac, av, GIT_HASH_SHA1, 1); +} diff --git a/t/helper/test-sha1.sh b/t/helper/test-sha1.sh new file mode 100755 index 0000000000..f03b784ddc --- /dev/null +++ b/t/helper/test-sha1.sh @@ -0,0 +1,89 @@ +#!/bin/sh + +dd if=/dev/zero bs=1048576 count=100 2>/dev/null | +/usr/bin/time t/helper/test-tool sha1 >/dev/null + +dd if=/dev/zero bs=1048576 count=100 2>/dev/null | +/usr/bin/time t/helper/test-tool sha1-unsafe >/dev/null + +while read expect cnt pfx +do + case "$expect" in '#'*) continue ;; esac + for sha1 in sha1 sha1-unsafe + do + actual=$( + { + test -z "$pfx" || echo "$pfx" + dd if=/dev/zero bs=1048576 count=$cnt 2>/dev/null | + tr "\000" "g" + } | ./t/helper/test-tool $sha1 $cnt + ) + if test "$expect" = "$actual" + then + echo "OK ($sha1): $expect $cnt $pfx" + else + echo >&2 "OOPS ($sha1): $cnt" + echo >&2 "expect ($sha1): $expect" + echo >&2 "actual ($sha1): $actual" + exit 1 + fi + done +done <<EOF +da39a3ee5e6b4b0d3255bfef95601890afd80709 0 +3f786850e387550fdab836ed7e6dc881de23001b 0 a +5277cbb45a15902137d332d97e89cf8136545485 0 ab +03cfd743661f07975fa2f1220c5194cbaff48451 0 abc +3330b4373640f9e4604991e73c7e86bfd8da2dc3 0 abcd +ec11312386ad561674f724b8cca7cf1796e26d1d 0 abcde +bdc37c074ec4ee6050d68bc133c6b912f36474df 0 abcdef +69bca99b923859f2dc486b55b87f49689b7358c7 0 abcdefg +e414af7161c9554089f4106d6f1797ef14a73666 0 abcdefgh +0707f2970043f9f7c22029482db27733deaec029 0 abcdefghi +a4dd8aa74a5636728fe52451636e2e17726033aa 1 +9986b45e2f4d7086372533bb6953a8652fa3644a 1 frotz +23d8d4f788e8526b4877548a32577543cbaaf51f 10 +8cd23f822ab44c7f481b8c92d591f6d1fcad431c 10 frotz +f3b5604a4e604899c1233edb3bf1cc0ede4d8c32 512 +b095bd837a371593048136e429e9ac4b476e1bb3 512 frotz +08fa81d6190948de5ccca3966340cc48c10cceac 1200 xyzzy +e33a291f42c30a159733dd98b8b3e4ff34158ca0 4090 4G +#a3bf783bc20caa958f6cb24dd140a7b21984838d 9999 nitfol +EOF + +exit + +# generating test vectors +# inputs are number of megabytes followed by some random string to prefix. + +while read cnt pfx +do + actual=$( + { + test -z "$pfx" || echo "$pfx" + dd if=/dev/zero bs=1048576 count=$cnt 2>/dev/null | + tr "\000" "g" + } | sha1sum | + sed -e 's/ .*//' + ) + echo "$actual $cnt $pfx" +done <<EOF +0 +0 a +0 ab +0 abc +0 abcd +0 abcde +0 abcdef +0 abcdefg +0 abcdefgh +0 abcdefghi +1 +1 frotz +10 +10 frotz +512 +512 frotz +1200 xyzzy +4090 4G +9999 nitfol +EOF diff --git a/t/helper/test-sha256.c b/t/helper/test-sha256.c new file mode 100644 index 0000000000..7fd0aa1fcd --- /dev/null +++ b/t/helper/test-sha256.c @@ -0,0 +1,7 @@ +#include "test-tool.h" +#include "hash.h" + +int cmd__sha256(int ac, const char **av) +{ + return cmd_hash_impl(ac, av, GIT_HASH_SHA256, 0); +} diff --git a/t/helper/test-sigchain.c b/t/helper/test-sigchain.c new file mode 100644 index 0000000000..2d5ecf7383 --- /dev/null +++ b/t/helper/test-sigchain.c @@ -0,0 +1,23 @@ +#include "test-tool.h" +#include "sigchain.h" + +#define X(f) \ +static void f(int sig) { \ + puts(#f); \ + fflush(stdout); \ + sigchain_pop(sig); \ + raise(sig); \ +} +X(one) +X(two) +X(three) +#undef X + +int cmd__sigchain(int argc UNUSED, const char **argv UNUSED) +{ + sigchain_push(SIGTERM, one); + sigchain_push(SIGTERM, two); + sigchain_push(SIGTERM, three); + raise(SIGTERM); + return 0; +} diff --git a/t/helper/test-simple-ipc.c b/t/helper/test-simple-ipc.c new file mode 100644 index 0000000000..03cc5eea2c --- /dev/null +++ b/t/helper/test-simple-ipc.c @@ -0,0 +1,687 @@ +/* + * test-simple-ipc.c: verify that the Inter-Process Communication works. + */ + +#include "test-tool.h" +#include "gettext.h" +#include "simple-ipc.h" +#include "parse-options.h" +#include "thread-utils.h" +#include "strvec.h" +#include "run-command.h" +#include "trace2.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, size_t received_len, + 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; + + /* + * The test is setup to send: + * "sendbytes" SP <n * char> + */ + if (received_len < strlen("sendbytes ")) + BUG("received_len is short in app__sendbytes_command"); + + 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, size_t command_len, + 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 (command_len == 4 && !strncmp(command, "quit", 4)) { + /* + * 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 (command_len == 4 && !strncmp(command, "ping", 4)) { + const char *answer = "pong"; + return reply_cb(reply_data, answer, strlen(answer)); + } + + if (command_len == 3 && !strncmp(command, "big", 3)) + return app__big_command(reply_cb, reply_data); + + if (command_len == 5 && !strncmp(command, "chunk", 5)) + return app__chunk_command(reply_cb, reply_data); + + if (command_len == 4 && !strncmp(command, "slow", 4)) + return app__slow_command(reply_cb, reply_data); + + if (command_len >= 10 && starts_with(command, "sendbytes ")) + return app__sendbytes_command(command, command_len, + 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; +} + +static start_bg_wait_cb bg_wait_cb; + +static int bg_wait_cb(const struct child_process *cp UNUSED, + void *cb_data UNUSED) +{ + int s = ipc_get_active_state(cl_args.path); + + switch (s) { + case IPC_STATE__LISTENING: + /* child is "ready" */ + return 0; + + case IPC_STATE__NOT_LISTENING: + case IPC_STATE__PATH_NOT_FOUND: + /* give child more time */ + return 1; + + default: + case IPC_STATE__INVALID_PATH: + case IPC_STATE__OTHER_ERROR: + /* all the time in world won't help */ + return -1; + } +} + +static int daemon__start_server(void) +{ + struct child_process cp = CHILD_PROCESS_INIT; + enum start_bg_result sbgr; + + strvec_push(&cp.args, "test-tool"); + strvec_push(&cp.args, "simple-ipc"); + strvec_push(&cp.args, "run-daemon"); + strvec_pushf(&cp.args, "--name=%s", cl_args.path); + strvec_pushf(&cp.args, "--threads=%d", cl_args.nr_threads); + + cp.no_stdin = 1; + cp.no_stdout = 1; + cp.no_stderr = 1; + + sbgr = start_bg_command(&cp, bg_wait_cb, NULL, cl_args.max_wait_sec); + + switch (sbgr) { + case SBGR_READY: + return 0; + + default: + case SBGR_ERROR: + case SBGR_CB_ERROR: + return error("daemon failed to start"); + + case SBGR_TIMEOUT: + return error("daemon not online yet"); + + case SBGR_DIED: + return error("daemon terminated"); + } +} + +/* + * 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, strlen(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_send.len, + &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); + + show_usage_with_options_if_asked(argc, argv, + 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-string-list.c b/t/helper/test-string-list.c new file mode 100644 index 0000000000..6be0cdb8e2 --- /dev/null +++ b/t/helper/test-string-list.c @@ -0,0 +1,35 @@ +#include "test-tool.h" +#include "strbuf.h" +#include "string-list.h" + +int cmd__string_list(int argc, const char **argv) +{ + if (argc == 2 && !strcmp(argv[1], "sort")) { + struct string_list list = STRING_LIST_INIT_NODUP; + struct strbuf sb = STRBUF_INIT; + struct string_list_item *item; + + strbuf_read(&sb, 0, 0); + + /* + * Split by newline, but don't create a string_list item + * for the empty string after the last separator. + */ + if (sb.len && sb.buf[sb.len - 1] == '\n') + strbuf_setlen(&sb, sb.len - 1); + string_list_split_in_place(&list, sb.buf, "\n", -1); + + string_list_sort(&list); + + for_each_string_list_item(item, &list) + puts(item->string); + + string_list_clear(&list, 0); + strbuf_release(&sb); + return 0; + } + + 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-submodule-config.c b/t/helper/test-submodule-config.c new file mode 100644 index 0000000000..cbe93f2f9e --- /dev/null +++ b/t/helper/test-submodule-config.c @@ -0,0 +1,71 @@ +#define USE_THE_REPOSITORY_VARIABLE + +#include "test-tool.h" +#include "config.h" +#include "hash.h" +#include "object-name.h" +#include "repository.h" +#include "setup.h" +#include "submodule-config.h" +#include "submodule.h" + +static void die_usage(int argc UNUSED, const char **argv, const char *msg) +{ + fprintf(stderr, "%s\n", msg); + fprintf(stderr, "Usage: %s [<commit> <submodulepath>] ...\n", argv[0]); + exit(1); +} + +int cmd__submodule_config(int argc, const char **argv) +{ + const char **arg = argv; + int my_argc = argc; + int lookup_name = 0; + + arg++; + my_argc--; + while (arg[0] && starts_with(arg[0], "--")) { + if (!strcmp(arg[0], "--name")) + lookup_name = 1; + arg++; + my_argc--; + } + + if (my_argc % 2 != 0) + die_usage(argc, argv, "Wrong number of arguments."); + + setup_git_directory(); + + while (*arg) { + struct object_id commit_oid; + const struct submodule *submodule; + const char *commit; + const char *path_or_name; + + commit = arg[0]; + path_or_name = arg[1]; + + if (commit[0] == '\0') + oidclr(&commit_oid, the_repository->hash_algo); + else if (repo_get_oid(the_repository, commit, &commit_oid) < 0) + die_usage(argc, argv, "Commit not found."); + + if (lookup_name) { + submodule = submodule_from_name(the_repository, + &commit_oid, path_or_name); + } else + submodule = submodule_from_path(the_repository, + &commit_oid, path_or_name); + if (!submodule) + die_usage(argc, argv, "Submodule not found."); + + printf("Submodule name: '%s' for path '%s'\n", submodule->name, + submodule->path); + + arg += 2; + } + + 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..2710341cd5 --- /dev/null +++ b/t/helper/test-submodule-nested-repo-config.c @@ -0,0 +1,34 @@ +#define USE_THE_REPOSITORY_VARIABLE + +#include "test-tool.h" +#include "repository.h" +#include "setup.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; + + if (argc < 3) + die_usage(argv, "Wrong number of arguments."); + + setup_git_directory(); + + if (repo_submodule_init(&subrepo, the_repository, argv[1], null_oid(the_hash_algo))) { + die_usage(argv, "Submodule not found."); + } + + /* Read the config of _child_ submodules. */ + print_config_from_gitmodules(&subrepo, argv[2]); + + submodule_free(the_repository); + repo_clear(&subrepo); + return 0; +} diff --git a/t/helper/test-submodule.c b/t/helper/test-submodule.c new file mode 100644 index 0000000000..0133852e1e --- /dev/null +++ b/t/helper/test-submodule.c @@ -0,0 +1,244 @@ +#define USE_THE_REPOSITORY_VARIABLE + +#include "test-tool.h" +#include "test-tool-utils.h" +#include "parse-options.h" +#include "remote.h" +#include "repository.h" +#include "setup.h" +#include "strbuf.h" +#include "submodule-config.h" +#include "submodule.h" + +#define TEST_TOOL_CHECK_NAME_USAGE \ + "test-tool submodule check-name" +static const char *const submodule_check_name_usage[] = { + TEST_TOOL_CHECK_NAME_USAGE, + NULL +}; + +#define TEST_TOOL_CHECK_URL_USAGE \ + "test-tool submodule check-url" +static const char *const submodule_check_url_usage[] = { + TEST_TOOL_CHECK_URL_USAGE, + NULL +}; + +#define TEST_TOOL_IS_ACTIVE_USAGE \ + "test-tool submodule is-active <name>" +static const char *const submodule_is_active_usage[] = { + TEST_TOOL_IS_ACTIVE_USAGE, + NULL +}; + +#define TEST_TOOL_RESOLVE_RELATIVE_URL_USAGE \ + "test-tool submodule resolve-relative-url <up_path> <remoteurl> <url>" +static const char *const submodule_resolve_relative_url_usage[] = { + TEST_TOOL_RESOLVE_RELATIVE_URL_USAGE, + NULL, +}; + +static const char *const submodule_usage[] = { + TEST_TOOL_CHECK_NAME_USAGE, + TEST_TOOL_CHECK_URL_USAGE, + TEST_TOOL_IS_ACTIVE_USAGE, + TEST_TOOL_RESOLVE_RELATIVE_URL_USAGE, + NULL +}; + +typedef int (*check_fn_t)(const char *); + +/* + * Apply 'check_fn' to each line of stdin, printing values that pass the check + * to stdout. + */ +static int check_submodule(check_fn_t check_fn) +{ + struct strbuf buf = STRBUF_INIT; + while (strbuf_getline(&buf, stdin) != EOF) { + if (!check_fn(buf.buf)) + printf("%s\n", buf.buf); + } + strbuf_release(&buf); + return 0; +} + +static int cmd__submodule_check_name(int argc, const char **argv) +{ + struct option options[] = { + OPT_END() + }; + argc = parse_options(argc, argv, "test-tools", options, + submodule_check_name_usage, 0); + if (argc) + usage_with_options(submodule_check_name_usage, options); + + return check_submodule(check_submodule_name); +} + +static int cmd__submodule_check_url(int argc, const char **argv) +{ + struct option options[] = { + OPT_END() + }; + argc = parse_options(argc, argv, "test-tools", options, + submodule_check_url_usage, 0); + if (argc) + usage_with_options(submodule_check_url_usage, options); + + return check_submodule(check_submodule_url); +} + +static int cmd__submodule_is_active(int argc, const char **argv) +{ + struct option options[] = { + OPT_END() + }; + argc = parse_options(argc, argv, "test-tools", options, + submodule_is_active_usage, 0); + if (argc != 1) + usage_with_options(submodule_is_active_usage, options); + + setup_git_directory(); + + return !is_submodule_active(the_repository, argv[0]); +} + +static int cmd__submodule_resolve_relative_url(int argc, const char **argv) +{ + char *remoteurl, *res; + const char *up_path, *url; + struct option options[] = { + OPT_END() + }; + argc = parse_options(argc, argv, "test-tools", options, + submodule_resolve_relative_url_usage, 0); + if (argc != 3) + usage_with_options(submodule_resolve_relative_url_usage, options); + + up_path = argv[0]; + remoteurl = xstrdup(argv[1]); + url = argv[2]; + + if (!strcmp(up_path, "(null)")) + up_path = NULL; + + res = relative_url(remoteurl, url, up_path); + puts(res); + free(res); + free(remoteurl); + return 0; +} + +static int cmd__submodule_config_list(int argc, const char **argv) +{ + struct option options[] = { + OPT_END() + }; + const char *const usage[] = { + "test-tool submodule config-list <key>", + NULL + }; + argc = parse_options(argc, argv, "test-tools", options, usage, + PARSE_OPT_KEEP_ARGV0); + + setup_git_directory(); + + if (argc == 2) + return print_config_from_gitmodules(the_repository, argv[1]); + usage_with_options(usage, options); +} + +static int cmd__submodule_config_set(int argc, const char **argv) +{ + struct option options[] = { + OPT_END() + }; + const char *const usage[] = { + "test-tool submodule config-set <key> <value>", + NULL + }; + argc = parse_options(argc, argv, "test-tools", options, usage, + PARSE_OPT_KEEP_ARGV0); + + setup_git_directory(); + + /* Equivalent to ACTION_SET in builtin/config.c */ + if (argc == 3) { + if (!is_writing_gitmodules_ok()) + die("please make sure that the .gitmodules file is in the working tree"); + + return config_set_in_gitmodules_file_gently(argv[1], argv[2]); + } + usage_with_options(usage, options); +} + +static int cmd__submodule_config_unset(int argc, const char **argv) +{ + struct option options[] = { + OPT_END() + }; + const char *const usage[] = { + "test-tool submodule config-unset <key>", + NULL + }; + + setup_git_directory(); + + if (argc == 2) { + if (!is_writing_gitmodules_ok()) + die("please make sure that the .gitmodules file is in the working tree"); + return config_set_in_gitmodules_file_gently(argv[1], NULL); + } + usage_with_options(usage, options); +} + +static int cmd__submodule_config_writeable(int argc, const char **argv UNUSED) +{ + struct option options[] = { + OPT_END() + }; + const char *const usage[] = { + "test-tool submodule config-writeable", + NULL + }; + setup_git_directory(); + + if (argc == 1) + return is_writing_gitmodules_ok() ? 0 : -1; + + usage_with_options(usage, options); +} + +static struct test_cmd cmds[] = { + { "check-name", cmd__submodule_check_name }, + { "check-url", cmd__submodule_check_url }, + { "is-active", cmd__submodule_is_active }, + { "resolve-relative-url", cmd__submodule_resolve_relative_url}, + { "config-list", cmd__submodule_config_list }, + { "config-set", cmd__submodule_config_set }, + { "config-unset", cmd__submodule_config_unset }, + { "config-writeable", cmd__submodule_config_writeable }, +}; + +int cmd__submodule(int argc, const char **argv) +{ + struct option options[] = { + OPT_END() + }; + size_t i; + + argc = parse_options(argc, argv, "test-tools", options, submodule_usage, + PARSE_OPT_STOP_AT_NON_OPTION); + if (argc < 1) + usage_with_options(submodule_usage, options); + + for (i = 0; i < ARRAY_SIZE(cmds); i++) + if (!strcmp(cmds[i].name, argv[0])) + return cmds[i].fn(argc, argv); + + usage_msg_optf("unknown subcommand '%s'", submodule_usage, options, + argv[0]); + + return 0; +} diff --git a/t/helper/test-subprocess.c b/t/helper/test-subprocess.c new file mode 100644 index 0000000000..c344f1694d --- /dev/null +++ b/t/helper/test-subprocess.c @@ -0,0 +1,20 @@ +#include "test-tool.h" +#include "run-command.h" +#include "setup.h" + +int cmd__subprocess(int argc, const char **argv) +{ + struct child_process cp = CHILD_PROCESS_INIT; + int nogit = 0; + + setup_git_directory_gently(&nogit); + if (nogit) + die("No git repo found"); + if (argc > 1 && !strcmp(argv[1], "--setup-work-tree")) { + setup_work_tree(); + argv++; + } + cp.git_cmd = 1; + strvec_pushv(&cp.args, (const char **)argv + 1); + return run_command(&cp); +} diff --git a/t/helper/test-tool-utils.h b/t/helper/test-tool-utils.h new file mode 100644 index 0000000000..6a0e5e0074 --- /dev/null +++ b/t/helper/test-tool-utils.h @@ -0,0 +1,9 @@ +#ifndef TEST_TOOL_UTILS_H +#define TEST_TOOL_UTILS_H + +struct test_cmd { + const char *name; + int (*fn)(int argc, const char **argv); +}; + +#endif diff --git a/t/helper/test-tool.c b/t/helper/test-tool.c new file mode 100644 index 0000000000..a7abc618b3 --- /dev/null +++ b/t/helper/test-tool.c @@ -0,0 +1,139 @@ +#include "git-compat-util.h" +#include "test-tool.h" +#include "test-tool-utils.h" +#include "trace2.h" +#include "parse-options.h" + +static const char * const test_tool_usage[] = { + "test-tool [-C <directory>] <command [<arguments>...]]", + NULL +}; + +static struct test_cmd cmds[] = { + { "advise", cmd__advise_if_enabled }, + { "bitmap", cmd__bitmap }, + { "bloom", cmd__bloom }, + { "bundle-uri", cmd__bundle_uri }, + { "cache-tree", cmd__cache_tree }, + { "chmtime", cmd__chmtime }, + { "config", cmd__config }, + { "crontab", cmd__crontab }, + { "csprng", cmd__csprng }, + { "date", cmd__date }, + { "delete-gpgsig", cmd__delete_gpgsig }, + { "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-reftable", cmd__dump_reftable }, + { "dump-split-index", cmd__dump_split_index }, + { "dump-untracked-cache", cmd__dump_untracked_cache }, + { "env-helper", cmd__env_helper }, + { "example-tap", cmd__example_tap }, + { "find-pack", cmd__find_pack }, + { "fsmonitor-client", cmd__fsmonitor_client }, + { "genrandom", cmd__genrandom }, + { "genzeros", cmd__genzeros }, + { "getcwd", cmd__getcwd }, + { "hashmap", cmd__hashmap }, + { "hash-speed", cmd__hash_speed }, + { "hexdump", cmd__hexdump }, + { "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 }, + { "name-hash", cmd__name_hash }, + { "online-cpus", cmd__online_cpus }, + { "pack-deltas", cmd__pack_deltas }, + { "pack-mtimes", cmd__pack_mtimes }, + { "parse-options", cmd__parse_options }, + { "parse-options-flags", cmd__parse_options_flags }, + { "parse-pathspec-file", cmd__parse_pathspec_file }, + { "parse-subcommand", cmd__parse_subcommand }, + { "partial-clone", cmd__partial_clone }, + { "path-utils", cmd__path_utils }, + { "path-walk", cmd__path_walk }, + { "pcre2-config", cmd__pcre2_config }, + { "pkt-line", cmd__pkt_line }, + { "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 }, + { "rot13-filter", cmd__rot13_filter }, + { "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 }, + { "sha1-is-sha1dc", cmd__sha1_is_sha1dc }, + { "sha1-unsafe", cmd__sha1_unsafe }, + { "sha256", cmd__sha256 }, + { "sigchain", cmd__sigchain }, + { "simple-ipc", cmd__simple_ipc }, + { "string-list", cmd__string_list }, + { "submodule", cmd__submodule }, + { "submodule-config", cmd__submodule_config }, + { "submodule-nested-repo-config", cmd__submodule_nested_repo_config }, + { "subprocess", cmd__subprocess }, + { "trace2", cmd__trace2 }, + { "truncate", cmd__truncate }, + { "userdiff", cmd__userdiff }, + { "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 }, + { "zlib", cmd__zlib }, +}; + +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) +{ + 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 (size_t 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..7f150fa1eb --- /dev/null +++ b/t/helper/test-tool.h @@ -0,0 +1,91 @@ +#ifndef TEST_TOOL_H +#define TEST_TOOL_H + +#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__bundle_uri(int argc, const char **argv); +int cmd__cache_tree(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__csprng(int argc, const char **argv); +int cmd__date(int argc, const char **argv); +int cmd__delta(int argc, const char **argv); +int cmd__delete_gpgsig(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__dump_reftable(int argc, const char **argv); +int cmd__env_helper(int argc, const char **argv); +int cmd__example_tap(int argc, const char **argv); +int cmd__find_pack(int argc, const char **argv); +int cmd__fsmonitor_client(int argc, const char **argv); +int cmd__genrandom(int argc, const char **argv); +int cmd__genzeros(int argc, const char **argv); +int cmd__getcwd(int argc, const char **argv); +int cmd__hashmap(int argc, const char **argv); +int cmd__hash_speed(int argc, const char **argv); +int cmd__hexdump(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__name_hash(int argc, const char **argv); +int cmd__online_cpus(int argc, const char **argv); +int cmd__pack_deltas(int argc, const char **argv); +int cmd__pack_mtimes(int argc, const char **argv); +int cmd__parse_options(int argc, const char **argv); +int cmd__parse_options_flags(int argc, const char **argv); +int cmd__parse_pathspec_file(int argc, const char** argv); +int cmd__parse_subcommand(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__path_walk(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__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__rot13_filter(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__sha1_is_sha1dc(int argc, const char **argv); +int cmd__sha1_unsafe(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__string_list(int argc, const char **argv); +int cmd__submodule(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__truncate(int argc, const char **argv); +int cmd__userdiff(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__zlib(int argc, const char **argv); + +int cmd_hash_impl(int ac, const char **av, int algo, int unsafe); + +#endif diff --git a/t/helper/test-trace2.c b/t/helper/test-trace2.c new file mode 100644 index 0000000000..415df078c1 --- /dev/null +++ b/t/helper/test-trace2.c @@ -0,0 +1,557 @@ +#define USE_THE_REPOSITORY_VARIABLE +#define DISABLE_SIGN_COMPARE_WARNINGS + +#include "test-tool.h" +#include "strvec.h" +#include "run-command.h" +#include "exec-cmd.h" +#include "config.h" +#include "repository.h" +#include "trace2.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; + + errno = 0; + *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 UNUSED, 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 UNUSED, 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) +{ + struct child_process cmd = CHILD_PROCESS_INIT; + 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; + + strvec_pushv(&cmd.args, argv); + result = run_command(&cmd); + 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 UNUSED, const char **argv UNUSED) +{ + /* + * Exercise BUG() to ensure that the message is printed to trace2. + */ + BUG("the bug message"); +} + +static int ut_008bug(int argc UNUSED, const char **argv UNUSED) +{ + bug("a bug message"); + bug("another bug message"); + BUG_if_bug("an explicit BUG_if_bug() following bug() call(s) is nice, but not required"); + return 0; +} + +static int ut_009bug_BUG(int argc UNUSED, const char **argv UNUSED) +{ + bug("a bug message"); + bug("another bug message"); + /* The BUG_if_bug(...) isn't here, but we'll spot bug() calls on exit()! */ + return 0; +} + +static int ut_010bug_BUG(int argc UNUSED, const char **argv UNUSED) +{ + bug("a %s message", "bug"); + BUG("a %s message", "BUG"); +} + +/* + * Single-threaded timer test. Create several intervals using the + * TEST1 timer. The test script can verify that an aggregate Trace2 + * "timer" event is emitted indicating that we started+stopped the + * timer the requested number of times. + */ +static int ut_100timer(int argc, const char **argv) +{ + const char *usage_error = + "expect <count> <ms_delay>"; + + int count = 0; + int delay = 0; + int k; + + if (argc != 2) + die("%s", usage_error); + if (get_i(&count, argv[0])) + die("%s", usage_error); + if (get_i(&delay, argv[1])) + die("%s", usage_error); + + for (k = 0; k < count; k++) { + trace2_timer_start(TRACE2_TIMER_ID_TEST1); + sleep_millisec(delay); + trace2_timer_stop(TRACE2_TIMER_ID_TEST1); + } + + return 0; +} + +struct ut_101_data { + int count; + int delay; +}; + +static void *ut_101timer_thread_proc(void *_ut_101_data) +{ + struct ut_101_data *data = _ut_101_data; + int k; + + trace2_thread_start("ut_101"); + + for (k = 0; k < data->count; k++) { + trace2_timer_start(TRACE2_TIMER_ID_TEST2); + sleep_millisec(data->delay); + trace2_timer_stop(TRACE2_TIMER_ID_TEST2); + } + + trace2_thread_exit(); + return NULL; +} + +/* + * Multi-threaded timer test. Create several threads that each create + * several intervals using the TEST2 timer. The test script can verify + * that an individual Trace2 "th_timer" events for each thread and an + * aggregate "timer" event are generated. + */ +static int ut_101timer(int argc, const char **argv) +{ + const char *usage_error = + "expect <count> <ms_delay> <threads>"; + + struct ut_101_data data = { 0, 0 }; + int nr_threads = 0; + int k; + pthread_t *pids = NULL; + + if (argc != 3) + die("%s", usage_error); + if (get_i(&data.count, argv[0])) + die("%s", usage_error); + if (get_i(&data.delay, argv[1])) + die("%s", usage_error); + if (get_i(&nr_threads, argv[2])) + die("%s", usage_error); + + CALLOC_ARRAY(pids, nr_threads); + + for (k = 0; k < nr_threads; k++) { + if (pthread_create(&pids[k], NULL, ut_101timer_thread_proc, &data)) + die("failed to create thread[%d]", k); + } + + for (k = 0; k < nr_threads; k++) { + if (pthread_join(pids[k], NULL)) + die("failed to join thread[%d]", k); + } + + free(pids); + + return 0; +} + +/* + * Single-threaded counter test. Add several values to the TEST1 counter. + * The test script can verify that the final sum is reported in the "counter" + * event. + */ +static int ut_200counter(int argc, const char **argv) +{ + const char *usage_error = + "expect <v1> [<v2> [...]]"; + int value; + int k; + + if (argc < 1) + die("%s", usage_error); + + for (k = 0; k < argc; k++) { + if (get_i(&value, argv[k])) + die("invalid value[%s] -- %s", + argv[k], usage_error); + trace2_counter_add(TRACE2_COUNTER_ID_TEST1, value); + } + + return 0; +} + +/* + * Multi-threaded counter test. Create seveal threads that each increment + * the TEST2 global counter. The test script can verify that an individual + * "th_counter" event is generated with a partial sum for each thread and + * that a final aggregate "counter" event is generated. + */ + +struct ut_201_data { + int v1; + int v2; +}; + +static void *ut_201counter_thread_proc(void *_ut_201_data) +{ + struct ut_201_data *data = _ut_201_data; + + trace2_thread_start("ut_201"); + + trace2_counter_add(TRACE2_COUNTER_ID_TEST2, data->v1); + trace2_counter_add(TRACE2_COUNTER_ID_TEST2, data->v2); + + trace2_thread_exit(); + return NULL; +} + +static int ut_201counter(int argc, const char **argv) +{ + const char *usage_error = + "expect <v1> <v2> <threads>"; + + struct ut_201_data data = { 0, 0 }; + int nr_threads = 0; + int k; + pthread_t *pids = NULL; + + if (argc != 3) + die("%s", usage_error); + if (get_i(&data.v1, argv[0])) + die("%s", usage_error); + if (get_i(&data.v2, argv[1])) + die("%s", usage_error); + if (get_i(&nr_threads, argv[2])) + die("%s", usage_error); + + CALLOC_ARRAY(pids, nr_threads); + + for (k = 0; k < nr_threads; k++) { + if (pthread_create(&pids[k], NULL, ut_201counter_thread_proc, &data)) + die("failed to create thread[%d]", k); + } + + for (k = 0; k < nr_threads; k++) { + if (pthread_join(pids[k], NULL)) + die("failed to join thread[%d]", k); + } + + free(pids); + + return 0; +} + +static int ut_300redact_start(int argc, const char **argv) +{ + if (!argc) + die("expect <argv...>"); + + trace2_cmd_start(argv); + + return 0; +} + +static int ut_301redact_child_start(int argc, const char **argv) +{ + struct child_process cmd = CHILD_PROCESS_INIT; + int k; + + if (!argc) + die("expect <argv...>"); + + for (k = 0; argv[k]; k++) + strvec_push(&cmd.args, argv[k]); + + trace2_child_start(&cmd); + + strvec_clear(&cmd.args); + + return 0; +} + +static int ut_302redact_exec(int argc, const char **argv) +{ + if (!argc) + die("expect <exe> <argv...>"); + + trace2_exec(argv[0], &argv[1]); + + return 0; +} + +static int ut_303redact_def_param(int argc, const char **argv) +{ + struct key_value_info kvi = KVI_INIT; + + if (argc < 2) + die("expect <key> <value>"); + + trace2_def_param(argv[0], argv[1], &kvi); + + return 0; +} + +/* + * 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", "" }, + { ut_008bug, "008bug", "" }, + { ut_009bug_BUG, "009bug_BUG","" }, + { ut_010bug_BUG, "010bug_BUG","" }, + + { ut_100timer, "100timer", "<count> <ms_delay>" }, + { ut_101timer, "101timer", "<count> <ms_delay> <threads>" }, + + { ut_200counter, "200counter", "<v1> [<v2> [<v3> [...]]]" }, + { ut_201counter, "201counter", "<v1> <v2> <threads>" }, + + { ut_300redact_start, "300redact_start", "<argv...>" }, + { ut_301redact_child_start, "301redact_child_start", "<argv...>" }, + { ut_302redact_exec, "302redact_exec", "<exe> <argv...>" }, + { ut_303redact_def_param, "303redact_def_param", "<key> <value>" }, +}; +/* 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 return from here and let test-tool.c::cmd_main() pass the exit + * code to common-main.c::main(), which will use it to call + * trace2_cmd_exit(). + */ +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-truncate.c b/t/helper/test-truncate.c new file mode 100644 index 0000000000..2820cc7ed7 --- /dev/null +++ b/t/helper/test-truncate.c @@ -0,0 +1,28 @@ +#include "test-tool.h" +#include "git-compat-util.h" + +/* + * Truncate a file to the given size. + */ +int cmd__truncate(int argc, const char **argv) +{ + char *p = NULL; + uintmax_t sz = 0; + int fd = -1; + + if (argc != 3) + die("expected filename and size"); + + sz = strtoumax(argv[2], &p, 0); + if (*p) + die("invalid size"); + + fd = xopen(argv[1], O_WRONLY | O_CREAT, 0600); + + if (ftruncate(fd, (off_t) sz) < 0) + die_errno("failed to truncate file"); + + close(fd); + + return 0; +} diff --git a/t/helper/test-userdiff.c b/t/helper/test-userdiff.c new file mode 100644 index 0000000000..aa3a9894d2 --- /dev/null +++ b/t/helper/test-userdiff.c @@ -0,0 +1,50 @@ +#define USE_THE_REPOSITORY_VARIABLE + +#include "test-tool.h" +#include "setup.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, + const struct config_context *ctx UNUSED, + void *cb UNUSED) +{ + 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(); + repo_config(the_repository, 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 new file mode 100644 index 0000000000..b4ff5f986a --- /dev/null +++ b/t/helper/test-wildmatch.c @@ -0,0 +1,24 @@ +#include "test-tool.h" +#include "wildmatch.h" + +int cmd__wildmatch(int argc, const char **argv) +{ + int i; + for (i = 2; i < argc; i++) { + if (argv[i][0] == '/') + die("Forward slash is not allowed at the beginning of the\n" + "pattern because Windows does not like it. Use `XXX/' instead."); + else if (!strncmp(argv[i], "XXX/", 4)) + argv[i] += 3; + } + if (!strcmp(argv[1], "wildmatch")) + return !!wildmatch(argv[3], argv[2], WM_PATHNAME); + else if (!strcmp(argv[1], "iwildmatch")) + 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..bd73784ceb --- /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 *const 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 new file mode 100644 index 0000000000..b37dd2c5d6 --- /dev/null +++ b/t/helper/test-write-cache.c @@ -0,0 +1,25 @@ +#define USE_THE_REPOSITORY_VARIABLE + +#include "test-tool.h" +#include "lockfile.h" +#include "read-cache-ll.h" +#include "repository.h" +#include "setup.h" + +int cmd__write_cache(int argc, const char **argv) +{ + struct lock_file index_lock = LOCK_INIT; + int i, cnt = 1; + if (argc == 2) + cnt = strtol(argv[1], NULL, 0); + setup_git_directory(); + repo_read_index(the_repository); + for (i = 0; i < cnt; i++) { + repo_hold_locked_index(the_repository, &index_lock, + LOCK_DIE_ON_ERROR); + if (write_locked_index(the_repository->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..b2f330d1a4 --- /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 UNUSED, const char **argv UNUSED) +{ + 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; +} diff --git a/t/helper/test-zlib.c b/t/helper/test-zlib.c new file mode 100644 index 0000000000..de7e9edee1 --- /dev/null +++ b/t/helper/test-zlib.c @@ -0,0 +1,62 @@ +#include "test-tool.h" +#include "git-zlib.h" +#include "strbuf.h" + +static const char *zlib_usage = "test-tool zlib [inflate|deflate]"; + +static void do_zlib(struct git_zstream *stream, + int (*zlib_func)(git_zstream *, int), + int fd_in, int fd_out) +{ + struct strbuf buf_in = STRBUF_INIT; + int status = Z_OK; + + if (strbuf_read(&buf_in, fd_in, 0) < 0) + die_errno("read error"); + + stream->next_in = (unsigned char *)buf_in.buf; + stream->avail_in = buf_in.len; + + while (status == Z_OK || + (status == Z_BUF_ERROR && !stream->avail_out)) { + unsigned char buf_out[4096]; + + stream->next_out = buf_out; + stream->avail_out = sizeof(buf_out); + + status = zlib_func(stream, Z_FINISH); + if (write_in_full(fd_out, buf_out, + sizeof(buf_out) - stream->avail_out) < 0) + die_errno("write error"); + } + + if (status != Z_STREAM_END) + die("zlib error %d", status); + + strbuf_release(&buf_in); +} + +int cmd__zlib(int argc, const char **argv) +{ + git_zstream stream; + + if (argc != 2) + usage(zlib_usage); + + memset(&stream, 0, sizeof(stream)); + + if (!strcmp(argv[1], "inflate")) { + git_inflate_init(&stream); + do_zlib(&stream, git_inflate, 0, 1); + git_inflate_end(&stream); + } else if (!strcmp(argv[1], "deflate")) { + git_deflate_init(&stream, Z_DEFAULT_COMPRESSION); + do_zlib(&stream, git_deflate, 0, 1); + git_deflate_end(&stream); + } else { + error("unknown mode: %s", argv[1]); + usage(zlib_usage); + } + + return 0; +} |