summaryrefslogtreecommitdiff
path: root/t/helper
diff options
context:
space:
mode:
Diffstat (limited to 't/helper')
-rw-r--r--t/helper/.gitignore2
-rw-r--r--t/helper/meson.build95
-rw-r--r--t/helper/test-advise.c25
-rw-r--r--t/helper/test-bitmap.c63
-rw-r--r--t/helper/test-bloom.c104
-rw-r--r--t/helper/test-bundle-uri.c133
-rw-r--r--t/helper/test-cache-tree.c71
-rw-r--r--t/helper/test-chmtime.c163
-rw-r--r--t/helper/test-config.c241
-rw-r--r--t/helper/test-crontab.c35
-rw-r--r--t/helper/test-csprng.c28
-rw-r--r--t/helper/test-date.c135
-rw-r--r--t/helper/test-delete-gpgsig.c62
-rw-r--r--t/helper/test-delta.c56
-rw-r--r--t/helper/test-dir-iterator.c64
-rw-r--r--t/helper/test-drop-caches.c164
-rw-r--r--t/helper/test-dump-cache-tree.c79
-rw-r--r--t/helper/test-dump-fsmonitor.c25
-rw-r--r--t/helper/test-dump-split-index.c43
-rw-r--r--t/helper/test-dump-untracked-cache.c74
-rw-r--r--t/helper/test-env-helper.c100
-rw-r--r--t/helper/test-example-tap.c131
-rw-r--r--t/helper/test-fake-ssh.c32
-rw-r--r--t/helper/test-find-pack.c52
-rw-r--r--t/helper/test-fsmonitor-client.c226
-rw-r--r--t/helper/test-genrandom.c34
-rw-r--r--t/helper/test-genzeros.c37
-rw-r--r--t/helper/test-getcwd.c26
-rw-r--r--t/helper/test-hash-speed.c60
-rw-r--r--t/helper/test-hash.c61
-rw-r--r--t/helper/test-hashmap.c184
-rw-r--r--t/helper/test-hexdump.c30
-rw-r--r--t/helper/test-json-writer.c594
-rw-r--r--t/helper/test-lazy-init-name-hash.c268
-rw-r--r--t/helper/test-match-trees.c33
-rw-r--r--t/helper/test-mergesort.c408
-rw-r--r--t/helper/test-mktemp.c15
-rw-r--r--t/helper/test-name-hash.c23
-rw-r--r--t/helper/test-online-cpus.c9
-rw-r--r--t/helper/test-pack-deltas.c148
-rw-r--r--t/helper/test-pack-mtimes.c59
-rw-r--r--t/helper/test-parse-options.c381
-rw-r--r--t/helper/test-parse-pathspec-file.c33
-rw-r--r--t/helper/test-partial-clone.c46
-rw-r--r--t/helper/test-path-utils.c542
-rw-r--r--t/helper/test-path-walk.c134
-rw-r--r--t/helper/test-pcre2-config.c11
-rw-r--r--t/helper/test-pkt-line.c188
-rw-r--r--t/helper/test-proc-receive.c207
-rw-r--r--t/helper/test-progress.c101
-rw-r--r--t/helper/test-reach.c191
-rw-r--r--t/helper/test-read-cache.c42
-rw-r--r--t/helper/test-read-graph.c102
-rw-r--r--t/helper/test-read-midx.c158
-rw-r--r--t/helper/test-ref-store.c351
-rw-r--r--t/helper/test-reftable.c267
-rw-r--r--t/helper/test-regex.c117
-rw-r--r--t/helper/test-repository.c102
-rw-r--r--t/helper/test-revision-walking.c74
-rw-r--r--t/helper/test-rot13-filter.c383
-rw-r--r--t/helper/test-run-command.c480
-rw-r--r--t/helper/test-scrap-cache-tree.c24
-rw-r--r--t/helper/test-serve-v2.c39
-rw-r--r--t/helper/test-sha1.c20
-rwxr-xr-xt/helper/test-sha1.sh89
-rw-r--r--t/helper/test-sha256.c7
-rw-r--r--t/helper/test-sigchain.c23
-rw-r--r--t/helper/test-simple-ipc.c687
-rw-r--r--t/helper/test-string-list.c35
-rw-r--r--t/helper/test-submodule-config.c71
-rw-r--r--t/helper/test-submodule-nested-repo-config.c34
-rw-r--r--t/helper/test-submodule.c244
-rw-r--r--t/helper/test-subprocess.c20
-rw-r--r--t/helper/test-tool-utils.h9
-rw-r--r--t/helper/test-tool.c139
-rw-r--r--t/helper/test-tool.h91
-rw-r--r--t/helper/test-trace2.c557
-rw-r--r--t/helper/test-truncate.c28
-rw-r--r--t/helper/test-userdiff.c50
-rw-r--r--t/helper/test-wildmatch.c24
-rw-r--r--t/helper/test-windows-named-pipe.c72
-rw-r--r--t/helper/test-write-cache.c25
-rw-r--r--t/helper/test-xml-encode.c80
-rw-r--r--t/helper/test-zlib.c62
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 = "&#xfffd;";
+
+/*
+ * 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("&amp;", stdout);
+ else if (ch == '\'')
+ fputs("&apos;", stdout);
+ else if (ch == '"')
+ fputs("&quot;", stdout);
+ else if (ch == '<')
+ fputs("&lt;", stdout);
+ else if (ch == '>')
+ fputs("&gt;", 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;
+}