aboutsummaryrefslogtreecommitdiffstats
path: root/t
diff options
context:
space:
mode:
Diffstat (limited to 't')
-rw-r--r--t/Makefile4
-rw-r--r--t/README9
-rwxr-xr-xt/chainlint.pl42
-rw-r--r--t/chainlint/arithmetic-expansion.expect2
-rw-r--r--t/chainlint/block.expect8
-rw-r--r--t/chainlint/broken-chain.expect2
-rw-r--r--t/chainlint/case.expect4
-rw-r--r--t/chainlint/chain-break-false.expect2
-rw-r--r--t/chainlint/chained-block.expect2
-rw-r--r--t/chainlint/chained-subshell.expect4
-rw-r--r--t/chainlint/command-substitution.expect2
-rw-r--r--t/chainlint/complex-if-in-cuddled-loop.expect2
-rw-r--r--t/chainlint/cuddled.expect4
-rw-r--r--t/chainlint/for-loop.expect8
-rw-r--r--t/chainlint/function.expect4
-rw-r--r--t/chainlint/here-doc-body-indent.expect2
-rw-r--r--t/chainlint/here-doc-body-pathological.expect4
-rw-r--r--t/chainlint/here-doc-body.expect4
-rw-r--r--t/chainlint/here-doc-double.expect2
-rw-r--r--t/chainlint/here-doc-indent-operator.expect2
-rw-r--r--t/chainlint/here-doc-multi-line-command-subst.expect2
-rw-r--r--t/chainlint/here-doc-multi-line-string.expect2
-rw-r--r--t/chainlint/if-condition-split.expect2
-rw-r--r--t/chainlint/if-in-loop.expect4
-rw-r--r--t/chainlint/if-then-else.expect4
-rw-r--r--t/chainlint/inline-comment.expect2
-rw-r--r--t/chainlint/loop-detect-failure.expect2
-rw-r--r--t/chainlint/loop-in-if.expect8
-rw-r--r--t/chainlint/multi-line-string.expect2
-rw-r--r--t/chainlint/negated-one-liner.expect4
-rw-r--r--t/chainlint/nested-cuddled-subshell.expect6
-rw-r--r--t/chainlint/nested-here-doc.expect2
-rw-r--r--t/chainlint/nested-loop-detect-failure.expect6
-rw-r--r--t/chainlint/nested-subshell-comment.expect2
-rw-r--r--t/chainlint/nested-subshell.expect2
-rw-r--r--t/chainlint/not-heredoc.expect2
-rw-r--r--t/chainlint/one-liner-for-loop.expect2
-rw-r--r--t/chainlint/one-liner.expect6
-rw-r--r--t/chainlint/pipe.expect2
-rw-r--r--t/chainlint/semicolon.expect12
-rw-r--r--t/chainlint/subshell-here-doc.expect2
-rw-r--r--t/chainlint/subshell-one-liner.expect10
-rw-r--r--t/chainlint/token-pasting.expect8
-rw-r--r--t/chainlint/unclosed-here-doc-indent.expect2
-rw-r--r--t/chainlint/unclosed-here-doc.expect2
-rw-r--r--t/chainlint/while-loop.expect8
-rwxr-xr-xt/check-non-portable-shell.pl4
-rw-r--r--t/helper/test-advise.c2
-rw-r--r--t/helper/test-config.c5
-rw-r--r--t/helper/test-example-tap.c37
-rw-r--r--t/helper/test-hashmap.c100
-rw-r--r--t/helper/test-mergesort.c2
-rw-r--r--t/helper/test-oid-array.c49
-rw-r--r--t/helper/test-path-utils.c8
-rw-r--r--t/helper/test-progress.c10
-rw-r--r--t/helper/test-reach.c6
-rw-r--r--t/helper/test-read-midx.c32
-rw-r--r--t/helper/test-ref-store.c2
-rw-r--r--t/helper/test-reftable.c194
-rw-r--r--t/helper/test-repository.c4
-rw-r--r--t/helper/test-submodule-nested-repo-config.c2
-rw-r--r--t/helper/test-tool.c5
-rw-r--r--t/helper/test-tool.h3
-rw-r--r--t/helper/test-urlmatch-normalization.c56
-rw-r--r--t/helper/test-userdiff.c2
-rw-r--r--t/interop/README7
-rwxr-xr-xt/interop/i5500-git-daemon.sh1
-rw-r--r--t/interop/interop-lib.sh8
-rw-r--r--t/lib-bitmap.sh6
-rw-r--r--t/lib-midx.sh28
-rwxr-xr-xt/perf/p1500-graph-walks.sh31
-rwxr-xr-xt/run-test.sh2
-rw-r--r--t/socks4-proxy.pl48
-rwxr-xr-xt/t0001-init.sh145
-rwxr-xr-xt/t0011-hashmap.sh260
-rwxr-xr-xt/t0012-help.sh1
-rwxr-xr-xt/t0021-conversion.sh1
-rwxr-xr-xt/t0032-reftable-unittest.sh16
-rwxr-xr-xt/t0064-oid-array.sh122
-rwxr-xr-xt/t0080-unit-test-output.sh60
-rwxr-xr-xt/t0110-urlmatch-normalization.sh182
-rw-r--r--t/t0110/README9
-rw-r--r--t/t0110/url-11
-rw-r--r--t/t0110/url-101
-rw-r--r--t/t0110/url-111
-rw-r--r--t/t0110/url-21
-rw-r--r--t/t0110/url-31
-rw-r--r--t/t0110/url-41
-rw-r--r--t/t0110/url-51
-rw-r--r--t/t0110/url-61
-rw-r--r--t/t0110/url-71
-rw-r--r--t/t0110/url-81
-rw-r--r--t/t0110/url-91
-rwxr-xr-xt/t0210-trace2-normal.sh2
-rwxr-xr-xt/t0301-credential-cache.sh2
-rwxr-xr-xt/t0302-credential-store.sh2
-rwxr-xr-xt/t0303-credential-external.sh1
-rwxr-xr-xt/t0410-partial-clone.sh2
-rwxr-xr-xt/t0601-reffiles-pack-refs.sh101
-rwxr-xr-xt/t0602-reffiles-fsck.sh92
-rwxr-xr-xt/t0610-reftable-basics.sh79
-rwxr-xr-xt/t1006-cat-file.sh1
-rwxr-xr-xt/t1050-large.sh1
-rwxr-xr-xt/t1092-sparse-checkout-compatibility.sh70
-rwxr-xr-xt/t1305-config-include.sh40
-rwxr-xr-xt/t1414-reflog-walk.sh1
-rwxr-xr-xt/t1419-exclude-refs.sh49
-rwxr-xr-xt/t1450-fsck.sh1
-rwxr-xr-xt/t1502-rev-parse-parseopt.sh2
-rwxr-xr-xt/t1511-rev-parse-caret.sh1
-rwxr-xr-xt/t1601-index-bogus.sh2
-rwxr-xr-xt/t2030-unresolve-info.sh1
-rwxr-xr-xt/t2080-parallel-checkout-basics.sh1
-rwxr-xr-xt/t2082-parallel-checkout-attributes.sh1
-rwxr-xr-xt/t2400-worktree-add.sh1
-rwxr-xr-xt/t2501-cwd-empty.sh1
-rwxr-xr-xt/t3011-common-prefixes-and-directory-traversal.sh1
-rwxr-xr-xt/t3201-branch-contains.sh1
-rwxr-xr-xt/t3202-show-branch.sh1
-rwxr-xr-xt/t3206-range-diff.sh53
-rwxr-xr-xt/t3310-notes-merge-manual-resolve.sh1
-rwxr-xr-xt/t3311-notes-merge-fanout.sh1
-rwxr-xr-xt/t3400-rebase.sh14
-rwxr-xr-xt/t3404-rebase-interactive.sh1
-rwxr-xr-xt/t3413-rebase-hook.sh4
-rwxr-xr-xt/t3420-rebase-autostash.sh40
-rwxr-xr-xt/t3430-rebase-merges.sh3
-rwxr-xr-xt/t3435-rebase-gpg-sign.sh1
-rwxr-xr-xt/t3436-rebase-more-options.sh1
-rwxr-xr-xt/t3507-cherry-pick-conflict.sh1
-rwxr-xr-xt/t3510-cherry-pick-sequence.sh1
-rwxr-xr-xt/t3650-replay-basics.sh1
-rwxr-xr-xt/t3701-add-interactive.sh48
-rwxr-xr-xt/t3705-add-sparse-checkout.sh1
-rwxr-xr-xt/t3903-stash.sh1
-rwxr-xr-xt/t3904-stash-patch.sh2
-rwxr-xr-xt/t3905-stash-include-untracked.sh1
-rwxr-xr-xt/t4008-diff-break-rewrite.sh2
-rwxr-xr-xt/t4013-diff-various.sh1
-rwxr-xr-xt/t4014-format-patch.sh1
-rwxr-xr-xt/t4017-diff-retval.sh8
-rwxr-xr-xt/t4018-diff-funcname.sh1
-rwxr-xr-xt/t4022-diff-rewrite.sh1
-rwxr-xr-xt/t4023-diff-rename-typechange.sh1
-rwxr-xr-xt/t4030-diff-textconv.sh2
-rwxr-xr-xt/t4031-diff-rewrite-binary.sh1
-rwxr-xr-xt/t4034-diff-words.sh2
-rwxr-xr-xt/t4042-diff-textconv-caching.sh2
-rwxr-xr-xt/t4048-diff-combined-binary.sh1
-rwxr-xr-xt/t4056-diff-order.sh1
-rwxr-xr-xt/t4064-diff-oidfind.sh2
-rwxr-xr-xt/t4065-diff-anchored.sh1
-rwxr-xr-xt/t4068-diff-symmetric-merge-base.sh1
-rwxr-xr-xt/t4069-remerge-diff.sh1
-rwxr-xr-xt/t4107-apply-ignore-whitespace.sh4
-rwxr-xr-xt/t4108-apply-threeway.sh41
-rwxr-xr-xt/t4124-apply-ws-rule.sh1
-rwxr-xr-xt/t4125-apply-ws-fuzz.sh1
-rwxr-xr-xt/t4138-apply-ws-expansion.sh1
-rwxr-xr-xt/t4150-am.sh1
-rwxr-xr-xt/t4200-rerere.sh1
-rwxr-xr-xt/t4201-shortlog.sh1
-rwxr-xr-xt/t4203-mailmap.sh42
-rwxr-xr-xt/t4204-patch-id.sh1
-rwxr-xr-xt/t4205-log-pretty-formats.sh2
-rwxr-xr-xt/t4209-log-pickaxe.sh2
-rwxr-xr-xt/t4301-merge-tree-write-tree.sh1
-rwxr-xr-xt/t5000-tar-tree.sh13
-rwxr-xr-xt/t5003-archive-zip.sh1
-rwxr-xr-xt/t5100-mailinfo.sh1
-rwxr-xr-xt/t5303-pack-corruption-resilience.sh7
-rwxr-xr-xt/t5304-prune.sh1
-rwxr-xr-xt/t5310-pack-bitmaps.sh5
-rwxr-xr-xt/t5313-pack-bounds-checks.sh8
-rwxr-xr-xt/t5318-commit-graph.sh2
-rwxr-xr-xt/t5319-multi-pack-index.sh32
-rwxr-xr-xt/t5326-multi-pack-bitmaps.sh6
-rwxr-xr-xt/t5327-multi-pack-bitmaps-rev.sh6
-rwxr-xr-xt/t5329-pack-objects-cruft.sh2
-rwxr-xr-xt/t5332-multi-pack-reuse.sh37
-rwxr-xr-xt/t5333-pseudo-merge-bitmaps.sh56
-rwxr-xr-xt/t5334-incremental-multi-pack-index.sh46
-rwxr-xr-xt/t5400-send-pack.sh1
-rwxr-xr-xt/t5401-update-hooks.sh2
-rwxr-xr-xt/t5408-send-pack-stdin.sh2
-rwxr-xr-xt/t5409-colorize-remote-messages.sh1
-rwxr-xr-xt/t5500-fetch-pack.sh1
-rwxr-xr-xt/t5501-fetch-push-alternates.sh1
-rwxr-xr-xt/t5505-remote.sh1
-rwxr-xr-xt/t5509-fetch-push-namespaces.sh10
-rwxr-xr-xt/t5510-fetch.sh1
-rwxr-xr-xt/t5512-ls-remote.sh2
-rwxr-xr-xt/t5514-fetch-multiple.sh1
-rwxr-xr-xt/t5516-fetch-push.sh1
-rwxr-xr-xt/t5519-push-alternates.sh1
-rwxr-xr-xt/t5520-pull.sh1
-rwxr-xr-xt/t5526-fetch-submodules.sh1
-rwxr-xr-xt/t5528-push-default.sh1
-rwxr-xr-xt/t5531-deep-submodule-push.sh1
-rwxr-xr-xt/t5533-push-cas.sh1
-rwxr-xr-xt/t5534-push-signed.sh1
-rwxr-xr-xt/t5535-fetch-push-symref.sh1
-rwxr-xr-xt/t5536-fetch-conflicts.sh1
-rwxr-xr-xt/t5537-fetch-shallow.sh1
-rwxr-xr-xt/t5538-push-shallow.sh1
-rwxr-xr-xt/t5539-fetch-http-shallow.sh1
-rwxr-xr-xt/t5540-http-push-webdav.sh1
-rwxr-xr-xt/t5541-http-push-smart.sh1
-rwxr-xr-xt/t5542-push-http-shallow.sh1
-rwxr-xr-xt/t5543-atomic-push.sh1
-rwxr-xr-xt/t5548-push-porcelain.sh1
-rwxr-xr-xt/t5549-fetch-push-http.sh1
-rwxr-xr-xt/t5550-http-fetch-dumb.sh1
-rwxr-xr-xt/t5551-http-fetch-smart.sh23
-rwxr-xr-xt/t5552-skipping-fetch-negotiator.sh2
-rwxr-xr-xt/t5553-set-upstream.sh1
-rwxr-xr-xt/t5564-http-proxy.sh55
-rwxr-xr-xt/t5570-git-daemon.sh1
-rwxr-xr-xt/t5572-pull-submodule.sh1
-rwxr-xr-xt/t5574-fetch-output.sh1
-rwxr-xr-xt/t5582-fetch-negative-refspec.sh1
-rwxr-xr-xt/t5616-partial-clone.sh7
-rwxr-xr-xt/t5619-clone-local-ambiguous-transport.sh1
-rwxr-xr-xt/t5700-protocol-v1.sh1
-rwxr-xr-xt/t5703-upload-pack-ref-in-want.sh1
-rwxr-xr-xt/t5801-remote-helpers.sh11
-rwxr-xr-xt/t5802-connect-helper.sh2
-rwxr-xr-xt/t5812-proto-disable-http.sh2
-rwxr-xr-xt/t5814-proto-disable-ext.sh2
-rwxr-xr-xt/t5815-submodule-protos.sh2
-rwxr-xr-xt/t6004-rev-list-path-optim.sh1
-rwxr-xr-xt/t6007-rev-list-cherry-pick-file.sh1
-rwxr-xr-xt/t6010-merge-base.sh1
-rwxr-xr-xt/t6019-rev-list-ancestry-path.sh1
-rwxr-xr-xt/t6050-replace.sh1
-rwxr-xr-xt/t6111-rev-list-treesame.sh1
-rwxr-xr-xt/t6120-describe.sh1
-rwxr-xr-xt/t6132-pathspec-exclude.sh1
-rwxr-xr-xt/t6133-pathspec-rev-dwim.sh2
-rwxr-xr-xt/t6135-pathspec-with-attrs.sh2
-rwxr-xr-xt/t6200-fmt-merge-msg.sh1
-rwxr-xr-xt/t6300-for-each-ref.sh50
-rwxr-xr-xt/t6302-for-each-ref-filter.sh1
-rwxr-xr-xt/t6409-merge-subtree.sh1
-rwxr-xr-xt/t6421-merge-partial-clone.sh1
-rwxr-xr-xt/t6423-merge-rename-directories.sh1
-rwxr-xr-xt/t6428-merge-conflicts-sparse.sh1
-rwxr-xr-xt/t6500-gc.sh46
-rwxr-xr-xt/t6600-test-reach.sh121
-rwxr-xr-xt/t7004-tag.sh1144
-rwxr-xr-xt/t7008-filter-branch-null-sha1.sh1
-rwxr-xr-xt/t7030-verify-tag.sh1
-rwxr-xr-xt/t7061-wtstatus-ignore.sh1
-rwxr-xr-xt/t7064-wtstatus-pv2.sh1
-rwxr-xr-xt/t7400-submodule-basic.sh1
-rwxr-xr-xt/t7406-submodule-update.sh1
-rwxr-xr-xt/t7407-submodule-foreach.sh1
-rwxr-xr-xt/t7408-submodule-reference.sh2
-rwxr-xr-xt/t7411-submodule-config.sh1
-rwxr-xr-xt/t7418-submodule-sparse-gitmodules.sh1
-rwxr-xr-xt/t7420-submodule-set-url.sh1
-rwxr-xr-xt/t7422-submodule-output.sh7
-rwxr-xr-xt/t7424-submodule-mixed-ref-formats.sh144
-rwxr-xr-xt/t7521-ignored-mode.sh1
-rwxr-xr-xt/t7524-commit-summary.sh2
-rwxr-xr-xt/t7601-merge-pull-config.sh1
-rwxr-xr-xt/t7615-diff-algo-with-mergy-operations.sh60
-rw-r--r--t/t7615/base.c17
-rw-r--r--t/t7615/ours.c17
-rw-r--r--t/t7615/theirs.c17
-rwxr-xr-xt/t7700-repack.sh49
-rwxr-xr-xt/t7703-repack-geometric.sh1
-rwxr-xr-xt/t7704-repack-cruft.sh3
-rwxr-xr-xt/t7800-difftool.sh1
-rwxr-xr-xt/t7810-grep.sh1
-rwxr-xr-xt/t7814-grep-recurse-submodules.sh1
-rwxr-xr-xt/t7817-grep-sparse-checkout.sh1
-rwxr-xr-xt/t7900-maintenance.sh105
-rwxr-xr-xt/t9001-send-email.sh244
-rwxr-xr-xt/t9210-scalar.sh25
-rwxr-xr-xt/t9300-fast-import.sh1
-rwxr-xr-xt/t9304-fast-import-marks.sh2
-rwxr-xr-xt/t9351-fast-export-anonymize.sh1
-rwxr-xr-xt/t9700-perl-git.sh3
-rwxr-xr-xt/t9700/test.pl5
-rwxr-xr-xt/t9902-completion.sh1
-rwxr-xr-xt/t9903-bash-prompt.sh1
-rw-r--r--t/test-lib-functions.sh3
-rw-r--r--t/test-lib.sh54
-rw-r--r--t/unit-tests/.gitignore2
-rw-r--r--t/unit-tests/clar-generate.awk50
-rw-r--r--t/unit-tests/clar/.github/workflows/ci.yml23
-rw-r--r--t/unit-tests/clar/COPYING15
-rw-r--r--t/unit-tests/clar/README.md329
-rw-r--r--t/unit-tests/clar/clar.c842
-rw-r--r--t/unit-tests/clar/clar.h173
-rw-r--r--t/unit-tests/clar/clar/fixtures.h50
-rw-r--r--t/unit-tests/clar/clar/fs.h524
-rw-r--r--t/unit-tests/clar/clar/print.h211
-rw-r--r--t/unit-tests/clar/clar/sandbox.h159
-rw-r--r--t/unit-tests/clar/clar/summary.h143
-rwxr-xr-xt/unit-tests/clar/generate.py266
-rw-r--r--t/unit-tests/clar/test/.gitignore4
-rw-r--r--t/unit-tests/clar/test/Makefile39
-rw-r--r--t/unit-tests/clar/test/clar_test.h16
-rw-r--r--t/unit-tests/clar/test/main.c40
-rw-r--r--t/unit-tests/clar/test/main.c.sample27
-rw-r--r--t/unit-tests/clar/test/resources/test/file1
-rw-r--r--t/unit-tests/clar/test/sample.c84
-rw-r--r--t/unit-tests/ctype.c (renamed from t/unit-tests/t-ctype.c)73
-rw-r--r--t/unit-tests/lib-oid.c2
-rw-r--r--t/unit-tests/lib-oid.h8
-rw-r--r--t/unit-tests/lib-reftable.c93
-rw-r--r--t/unit-tests/lib-reftable.h20
-rw-r--r--t/unit-tests/strvec.c (renamed from t/unit-tests/t-strvec.c)161
-rw-r--r--t/unit-tests/t-hash.c2
-rw-r--r--t/unit-tests/t-hashmap.c361
-rw-r--r--t/unit-tests/t-mem-pool.c2
-rw-r--r--t/unit-tests/t-oid-array.c126
-rw-r--r--t/unit-tests/t-prio-queue.c2
-rw-r--r--t/unit-tests/t-reftable-basics.c228
-rw-r--r--t/unit-tests/t-reftable-block.c371
-rw-r--r--t/unit-tests/t-reftable-merged.c465
-rw-r--r--t/unit-tests/t-reftable-pq.c152
-rw-r--r--t/unit-tests/t-reftable-reader.c96
-rw-r--r--t/unit-tests/t-reftable-readwrite.c926
-rw-r--r--t/unit-tests/t-reftable-record.c2
-rw-r--r--t/unit-tests/t-reftable-stack.c1386
-rw-r--r--t/unit-tests/t-reftable-tree.c84
-rw-r--r--t/unit-tests/t-strbuf.c2
-rw-r--r--t/unit-tests/t-strcmp-offset.c2
-rw-r--r--t/unit-tests/t-trailer.c2
-rw-r--r--t/unit-tests/t-urlmatch-normalization.c271
-rw-r--r--t/unit-tests/test-lib.c36
-rw-r--r--t/unit-tests/test-lib.h20
-rw-r--r--t/unit-tests/unit-test.c47
-rw-r--r--t/unit-tests/unit-test.h10
337 files changed, 10893 insertions, 2034 deletions
diff --git a/t/Makefile b/t/Makefile
index 4c30e7c06f..131ffd778f 100644
--- a/t/Makefile
+++ b/t/Makefile
@@ -48,6 +48,7 @@ CHAINLINTTESTS = $(sort $(patsubst chainlint/%.test,%,$(wildcard chainlint/*.tes
CHAINLINT = '$(PERL_PATH_SQ)' chainlint.pl
UNIT_TEST_SOURCES = $(wildcard unit-tests/t-*.c)
UNIT_TEST_PROGRAMS = $(patsubst unit-tests/%.c,unit-tests/bin/%$(X),$(UNIT_TEST_SOURCES))
+UNIT_TEST_PROGRAMS += unit-tests/bin/unit-tests$(X)
UNIT_TESTS = $(sort $(UNIT_TEST_PROGRAMS))
UNIT_TESTS_NO_DIR = $(notdir $(UNIT_TESTS))
@@ -68,7 +69,8 @@ failed:
test -z "$$failed" || $(MAKE) $$failed
prove: pre-clean check-chainlint $(TEST_LINT)
- @echo "*** prove (shell & unit tests) ***"; $(CHAINLINTSUPPRESS) TEST_SHELL_PATH='$(TEST_SHELL_PATH_SQ)' $(PROVE) --exec ./run-test.sh $(GIT_PROVE_OPTS) $(T) $(UNIT_TESTS) :: $(GIT_TEST_OPTS)
+ @echo "*** prove (shell & unit tests) ***"
+ @$(CHAINLINTSUPPRESS) TEST_OPTIONS='$(GIT_TEST_OPTS)' TEST_SHELL_PATH='$(TEST_SHELL_PATH_SQ)' $(PROVE) --exec ./run-test.sh $(GIT_PROVE_OPTS) $(T) $(UNIT_TESTS)
$(MAKE) clean-except-prove-cache
$(T):
diff --git a/t/README b/t/README
index 724ee58195..8dcb778e26 100644
--- a/t/README
+++ b/t/README
@@ -386,6 +386,9 @@ GIT_TEST_PASSING_SANITIZE_LEAK=check when combined with "--immediate"
will run to completion faster, and result in the same failing
tests.
+GIT_TEST_PASSING_SANITIZE_LEAK=check-failing behaves the same as "check",
+but skips all tests which are already marked as leak-free.
+
GIT_TEST_PROTOCOL_VERSION=<n>, when set, makes 'protocol.version'
default to n.
@@ -445,9 +448,9 @@ GIT_TEST_MULTI_PACK_INDEX=<boolean>, when true, forces the multi-pack-
index to be written after every 'git repack' command, and overrides the
'core.multiPackIndex' setting to true.
-GIT_TEST_MULTI_PACK_INDEX_WRITE_BITMAP=<boolean>, when true, sets the
-'--bitmap' option on all invocations of 'git multi-pack-index write',
-and ignores pack-objects' '--write-bitmap-index'.
+GIT_TEST_MULTI_PACK_INDEX_WRITE_INCREMENTAL=<boolean>, when true, sets
+the '--incremental' option on all invocations of 'git multi-pack-index
+write'.
GIT_TEST_SIDEBAND_ALL=<boolean>, when true, overrides the
'uploadpack.allowSidebandAll' setting to true, and when false, forces
diff --git a/t/chainlint.pl b/t/chainlint.pl
index 5361f23b1d..f0598e3934 100755
--- a/t/chainlint.pl
+++ b/t/chainlint.pl
@@ -9,9 +9,9 @@
# Input arguments are pathnames of shell scripts containing test definitions,
# or globs referencing a collection of scripts. For each problem discovered,
# the pathname of the script containing the test is printed along with the test
-# name and the test body with a `?!FOO?!` annotation at the location of each
-# detected problem, where "FOO" is a tag such as "AMP" which indicates a broken
-# &&-chain. Returns zero if no problems are discovered, otherwise non-zero.
+# name and the test body with a `?!LINT: ...?!` annotation at the location of
+# each detected problem, where "..." is an explanation of the problem. Returns
+# zero if no problems are discovered, otherwise non-zero.
use warnings;
use strict;
@@ -181,7 +181,7 @@ sub swallow_heredocs {
$self->{lineno} += () = $body =~ /\n/sg;
next;
}
- push(@{$self->{parser}->{problems}}, ['UNCLOSED-HEREDOC', $tag]);
+ push(@{$self->{parser}->{problems}}, ['HEREDOC', $tag]);
$$b =~ /(?:\G|\n).*\z/gc; # consume rest of input
my $body = substr($$b, $start, pos($$b) - $start);
$self->{lineno} += () = $body =~ /\n/sg;
@@ -238,6 +238,7 @@ sub new {
stop => [],
output => [],
heredocs => {},
+ insubshell => 0,
} => $class;
$self->{lexer} = Lexer->new($self, $s);
return $self;
@@ -296,8 +297,11 @@ sub parse_group {
sub parse_subshell {
my $self = shift @_;
- return ($self->parse(qr/^\)$/),
- $self->expect(')'));
+ $self->{insubshell}++;
+ my @tokens = ($self->parse(qr/^\)$/),
+ $self->expect(')'));
+ $self->{insubshell}--;
+ return @tokens;
}
sub parse_case_pattern {
@@ -528,7 +532,7 @@ sub parse_loop_body {
return @tokens if ends_with(\@tokens, [qr/^\|\|$/, "\n", qr/^echo$/, qr/^.+$/]);
# flag missing "return/exit" handling explicit failure in loop body
my $n = find_non_nl(\@tokens);
- push(@{$self->{problems}}, ['LOOP', $tokens[$n]]);
+ push(@{$self->{problems}}, [$self->{insubshell} ? 'LOOPEXIT' : 'LOOPRETURN', $tokens[$n]]);
return @tokens;
}
@@ -587,6 +591,7 @@ sub new {
my $class = shift @_;
my $self = $class->SUPER::new(@_);
$self->{ntests} = 0;
+ $self->{nerrs} = 0;
return $self;
}
@@ -619,6 +624,15 @@ sub unwrap {
return $s
}
+sub format_problem {
+ local $_ = shift;
+ /^AMP$/ && return "missing '&&'";
+ /^LOOPRETURN$/ && return "missing '|| return 1'";
+ /^LOOPEXIT$/ && return "missing '|| exit 1'";
+ /^HEREDOC$/ && return 'unclosed heredoc';
+ die("unrecognized problem type '$_'\n");
+}
+
sub check_test {
my $self = shift @_;
my $title = unwrap(shift @_);
@@ -634,22 +648,26 @@ sub check_test {
my $parser = TestParser->new(\$body);
my @tokens = $parser->parse();
my $problems = $parser->{problems};
+ $self->{nerrs} += @$problems;
return unless $emit_all || @$problems;
my $c = main::fd_colors(1);
+ my ($erropen, $errclose) = -t 1 ? ("$c->{rev}$c->{red}", $c->{reset}) : ('?!', '?!');
my $start = 0;
my $checked = '';
for (sort {$a->[1]->[2] <=> $b->[1]->[2]} @$problems) {
my ($label, $token) = @$_;
my $pos = $token->[2];
- $checked .= substr($body, $start, $pos - $start) . " ?!$label?! ";
+ my $err = format_problem($label);
+ $checked .= substr($body, $start, $pos - $start);
+ $checked .= ' ' unless $checked =~ /\s$/;
+ $checked .= "${erropen}LINT: $err$errclose";
+ $checked .= ' ' unless $pos >= length($body) ||
+ substr($body, $pos, 1) =~ /^\s/;
$start = $pos;
}
$checked .= substr($body, $start);
$checked =~ s/^/$lineno++ . ' '/mge;
$checked =~ s/^\d+ \n//;
- $checked =~ s/(\s) \?!/$1?!/mg;
- $checked =~ s/\?! (\s)/?!$1/mg;
- $checked =~ s/(\?![^?]+\?!)/$c->{rev}$c->{red}$1$c->{reset}/mg;
$checked =~ s/^\d+/$c->{dim}$&$c->{reset}/mg;
$checked .= "\n" unless $checked =~ /\n$/;
push(@{$self->{output}}, "$c->{blue}# chainlint: $title$c->{reset}\n$checked");
@@ -791,9 +809,9 @@ sub check_script {
my $c = fd_colors(1);
my $s = join('', @{$parser->{output}});
$emit->("$c->{bold}$c->{blue}# chainlint: $path$c->{reset}\n" . $s);
- $nerrs += () = $s =~ /\?![^?]+\?!/g;
}
$ntests += $parser->{ntests};
+ $nerrs += $parser->{nerrs};
}
return [$id, $nscripts, $ntests, $nerrs];
}
diff --git a/t/chainlint/arithmetic-expansion.expect b/t/chainlint/arithmetic-expansion.expect
index 338ecd5861..5677e16cad 100644
--- a/t/chainlint/arithmetic-expansion.expect
+++ b/t/chainlint/arithmetic-expansion.expect
@@ -4,6 +4,6 @@
5 baz
6 ) &&
7 (
-8 bar=$((42 + 1)) ?!AMP?!
+8 bar=$((42 + 1)) ?!LINT: missing '&&'?!
9 baz
10 )
diff --git a/t/chainlint/block.expect b/t/chainlint/block.expect
index b62e3d58c3..3d3f854c0d 100644
--- a/t/chainlint/block.expect
+++ b/t/chainlint/block.expect
@@ -1,20 +1,20 @@
2 (
3 foo &&
4 {
-5 echo a ?!AMP?!
+5 echo a ?!LINT: missing '&&'?!
6 echo b
7 } &&
8 bar &&
9 {
10 echo c
-11 } ?!AMP?!
+11 } ?!LINT: missing '&&'?!
12 baz
13 ) &&
14
15 {
-16 echo a; ?!AMP?! echo b
+16 echo a; ?!LINT: missing '&&'?! echo b
17 } &&
-18 { echo a; ?!AMP?! echo b; } &&
+18 { echo a; ?!LINT: missing '&&'?! echo b; } &&
19
20 {
21 echo "${var}9" &&
diff --git a/t/chainlint/broken-chain.expect b/t/chainlint/broken-chain.expect
index 9a1838736f..b7b1ce8509 100644
--- a/t/chainlint/broken-chain.expect
+++ b/t/chainlint/broken-chain.expect
@@ -1,6 +1,6 @@
2 (
3 foo &&
-4 bar ?!AMP?!
+4 bar ?!LINT: missing '&&'?!
5 baz &&
6 wop
7 )
diff --git a/t/chainlint/case.expect b/t/chainlint/case.expect
index c04c61ff36..0a3b09e470 100644
--- a/t/chainlint/case.expect
+++ b/t/chainlint/case.expect
@@ -9,11 +9,11 @@
10 case "$x" in
11 x) foo ;;
12 *) bar ;;
-13 esac ?!AMP?!
+13 esac ?!LINT: missing '&&'?!
14 foobar
15 ) &&
16 (
17 case "$x" in 1) true;; esac &&
-18 case "$y" in 2) false;; esac ?!AMP?!
+18 case "$y" in 2) false;; esac ?!LINT: missing '&&'?!
19 foobar
20 )
diff --git a/t/chainlint/chain-break-false.expect b/t/chainlint/chain-break-false.expect
index 4f815f8e14..f6a0a301e9 100644
--- a/t/chainlint/chain-break-false.expect
+++ b/t/chainlint/chain-break-false.expect
@@ -4,6 +4,6 @@
5 echo failed!
6 false
7 else
-8 echo it went okay ?!AMP?!
+8 echo it went okay ?!LINT: missing '&&'?!
9 congratulate user
10 fi
diff --git a/t/chainlint/chained-block.expect b/t/chainlint/chained-block.expect
index a546b714a6..f2501bba90 100644
--- a/t/chainlint/chained-block.expect
+++ b/t/chainlint/chained-block.expect
@@ -1,5 +1,5 @@
2 echo nobody home && {
-3 test the doohicky ?!AMP?!
+3 test the doohicky ?!LINT: missing '&&'?!
4 right now
5 } &&
6
diff --git a/t/chainlint/chained-subshell.expect b/t/chainlint/chained-subshell.expect
index f78b268291..93fb1a6578 100644
--- a/t/chainlint/chained-subshell.expect
+++ b/t/chainlint/chained-subshell.expect
@@ -1,10 +1,10 @@
2 mkdir sub && (
3 cd sub &&
-4 foo the bar ?!AMP?!
+4 foo the bar ?!LINT: missing '&&'?!
5 nuff said
6 ) &&
7
8 cut "-d " -f actual | (read s1 s2 s3 &&
-9 test -f $s1 ?!AMP?!
+9 test -f $s1 ?!LINT: missing '&&'?!
10 test $(cat $s2) = tree2path1 &&
11 test $(cat $s3) = tree3path1)
diff --git a/t/chainlint/command-substitution.expect b/t/chainlint/command-substitution.expect
index 5e31b36db6..73809fd585 100644
--- a/t/chainlint/command-substitution.expect
+++ b/t/chainlint/command-substitution.expect
@@ -4,6 +4,6 @@
5 baz
6 ) &&
7 (
-8 bar=$(gobble blocks) ?!AMP?!
+8 bar=$(gobble blocks) ?!LINT: missing '&&'?!
9 baz
10 )
diff --git a/t/chainlint/complex-if-in-cuddled-loop.expect b/t/chainlint/complex-if-in-cuddled-loop.expect
index 3a740103db..e66bb2d5d0 100644
--- a/t/chainlint/complex-if-in-cuddled-loop.expect
+++ b/t/chainlint/complex-if-in-cuddled-loop.expect
@@ -4,6 +4,6 @@
5 :
6 else
7 echo >file
-8 fi ?!LOOP?!
+8 fi ?!LINT: missing '|| exit 1'?!
9 done) &&
10 test ! -f file
diff --git a/t/chainlint/cuddled.expect b/t/chainlint/cuddled.expect
index b06d638311..1864b3fc8b 100644
--- a/t/chainlint/cuddled.expect
+++ b/t/chainlint/cuddled.expect
@@ -2,7 +2,7 @@
3 bar
4 ) &&
5
-6 (cd foo ?!AMP?!
+6 (cd foo ?!LINT: missing '&&'?!
7 bar
8 ) &&
9
@@ -13,5 +13,5 @@
14 (cd foo &&
15 bar) &&
16
-17 (cd foo ?!AMP?!
+17 (cd foo ?!LINT: missing '&&'?!
18 bar)
diff --git a/t/chainlint/for-loop.expect b/t/chainlint/for-loop.expect
index 908aeedf96..5029eacce3 100644
--- a/t/chainlint/for-loop.expect
+++ b/t/chainlint/for-loop.expect
@@ -1,14 +1,14 @@
2 (
3 for i in a b c
4 do
-5 echo $i ?!AMP?!
-6 cat <<-\EOF ?!LOOP?!
+5 echo $i ?!LINT: missing '&&'?!
+6 cat <<-\EOF ?!LINT: missing '|| exit 1'?!
7 bar
8 EOF
-9 done ?!AMP?!
+9 done ?!LINT: missing '&&'?!
10
11 for i in a b c; do
12 echo $i &&
-13 cat $i ?!LOOP?!
+13 cat $i ?!LINT: missing '|| exit 1'?!
14 done
15 )
diff --git a/t/chainlint/function.expect b/t/chainlint/function.expect
index c226246b25..9e46a3554a 100644
--- a/t/chainlint/function.expect
+++ b/t/chainlint/function.expect
@@ -4,8 +4,8 @@
5
6 remove_object() {
7 file=$(sha1_file "$*") &&
-8 test -e "$file" ?!AMP?!
+8 test -e "$file" ?!LINT: missing '&&'?!
9 rm -f "$file"
-10 } ?!AMP?!
+10 } ?!LINT: missing '&&'?!
11
12 sha1_file arg && remove_object arg
diff --git a/t/chainlint/here-doc-body-indent.expect b/t/chainlint/here-doc-body-indent.expect
index 4323acc93d..4306faee86 100644
--- a/t/chainlint/here-doc-body-indent.expect
+++ b/t/chainlint/here-doc-body-indent.expect
@@ -1,2 +1,2 @@
-2 echo "we should find this" ?!AMP?!
+2 echo "we should find this" ?!LINT: missing '&&'?!
3 echo "even though our heredoc has its indent stripped"
diff --git a/t/chainlint/here-doc-body-pathological.expect b/t/chainlint/here-doc-body-pathological.expect
index a93a1fa3aa..2f8ea03a47 100644
--- a/t/chainlint/here-doc-body-pathological.expect
+++ b/t/chainlint/here-doc-body-pathological.expect
@@ -1,7 +1,7 @@
-2 echo "outer here-doc does not allow indented end-tag" ?!AMP?!
+2 echo "outer here-doc does not allow indented end-tag" ?!LINT: missing '&&'?!
3 cat >file <<-\EOF &&
4 but this inner here-doc
5 does allow indented EOF
6 EOF
-7 echo "missing chain after" ?!AMP?!
+7 echo "missing chain after" ?!LINT: missing '&&'?!
8 echo "but this line is OK because it's the end"
diff --git a/t/chainlint/here-doc-body.expect b/t/chainlint/here-doc-body.expect
index ddf1c412af..df8d79bc0a 100644
--- a/t/chainlint/here-doc-body.expect
+++ b/t/chainlint/here-doc-body.expect
@@ -1,7 +1,7 @@
-2 echo "missing chain before" ?!AMP?!
+2 echo "missing chain before" ?!LINT: missing '&&'?!
3 cat >file <<-\EOF &&
4 inside inner here-doc
5 these are not shell commands
6 EOF
-7 echo "missing chain after" ?!AMP?!
+7 echo "missing chain after" ?!LINT: missing '&&'?!
8 echo "but this line is OK because it's the end"
diff --git a/t/chainlint/here-doc-double.expect b/t/chainlint/here-doc-double.expect
index 20dba4b452..e5e981889f 100644
--- a/t/chainlint/here-doc-double.expect
+++ b/t/chainlint/here-doc-double.expect
@@ -1,2 +1,2 @@
-8 echo "actual test commands" ?!AMP?!
+8 echo "actual test commands" ?!LINT: missing '&&'?!
9 echo "that should be checked"
diff --git a/t/chainlint/here-doc-indent-operator.expect b/t/chainlint/here-doc-indent-operator.expect
index 277a11202d..ec0e61505b 100644
--- a/t/chainlint/here-doc-indent-operator.expect
+++ b/t/chainlint/here-doc-indent-operator.expect
@@ -4,7 +4,7 @@
5 chunks: oid_fanout oid_lookup commit_metadata generation_data bloom_indexes bloom_data
6 EOF
7
-8 cat >expect << -EOF ?!AMP?!
+8 cat >expect << -EOF ?!LINT: missing '&&'?!
9 this is not indented
10 -EOF
11
diff --git a/t/chainlint/here-doc-multi-line-command-subst.expect b/t/chainlint/here-doc-multi-line-command-subst.expect
index 41b55f6437..8128f15b92 100644
--- a/t/chainlint/here-doc-multi-line-command-subst.expect
+++ b/t/chainlint/here-doc-multi-line-command-subst.expect
@@ -3,6 +3,6 @@
4 fossil
5 vegetable
6 END
-7 wiffle) ?!AMP?!
+7 wiffle) ?!LINT: missing '&&'?!
8 echo $x
9 )
diff --git a/t/chainlint/here-doc-multi-line-string.expect b/t/chainlint/here-doc-multi-line-string.expect
index c71828589e..a03a04ff3d 100644
--- a/t/chainlint/here-doc-multi-line-string.expect
+++ b/t/chainlint/here-doc-multi-line-string.expect
@@ -1,6 +1,6 @@
2 (
3 cat <<-\TXT && echo "multi-line
-4 string" ?!AMP?!
+4 string" ?!LINT: missing '&&'?!
5 fizzle
6 TXT
7 bap
diff --git a/t/chainlint/if-condition-split.expect b/t/chainlint/if-condition-split.expect
index 9daf3d294a..6d2a03dfdb 100644
--- a/t/chainlint/if-condition-split.expect
+++ b/t/chainlint/if-condition-split.expect
@@ -2,6 +2,6 @@
3 marcia ||
4 kevin
5 then
-6 echo "nomads" ?!AMP?!
+6 echo "nomads" ?!LINT: missing '&&'?!
7 echo "for sure"
8 fi
diff --git a/t/chainlint/if-in-loop.expect b/t/chainlint/if-in-loop.expect
index ff8c60dbdb..7e3ba740de 100644
--- a/t/chainlint/if-in-loop.expect
+++ b/t/chainlint/if-in-loop.expect
@@ -5,8 +5,8 @@
6 then
7 echo "err"
8 exit 1
-9 fi ?!AMP?!
+9 fi ?!LINT: missing '&&'?!
10 foo
-11 done ?!AMP?!
+11 done ?!LINT: missing '&&'?!
12 bar
13 )
diff --git a/t/chainlint/if-then-else.expect b/t/chainlint/if-then-else.expect
index 965d7e41a2..924caa2e4e 100644
--- a/t/chainlint/if-then-else.expect
+++ b/t/chainlint/if-then-else.expect
@@ -1,7 +1,7 @@
2 (
3 if test -n ""
4 then
-5 echo very ?!AMP?!
+5 echo very ?!LINT: missing '&&'?!
6 echo empty
7 elif test -z ""
8 then
@@ -11,7 +11,7 @@
12 cat <<-\EOF
13 bar
14 EOF
-15 fi ?!AMP?!
+15 fi ?!LINT: missing '&&'?!
16 echo poodle
17 ) &&
18 (
diff --git a/t/chainlint/inline-comment.expect b/t/chainlint/inline-comment.expect
index 0285c0b22c..4b4080124e 100644
--- a/t/chainlint/inline-comment.expect
+++ b/t/chainlint/inline-comment.expect
@@ -1,6 +1,6 @@
2 (
3 foobar && # comment 1
-4 barfoo ?!AMP?! # wrong position for &&
+4 barfoo ?!LINT: missing '&&'?! # wrong position for &&
5 flibble "not a # comment"
6 ) &&
7
diff --git a/t/chainlint/loop-detect-failure.expect b/t/chainlint/loop-detect-failure.expect
index 40c06f0d53..7d846b878d 100644
--- a/t/chainlint/loop-detect-failure.expect
+++ b/t/chainlint/loop-detect-failure.expect
@@ -11,5 +11,5 @@
12 do
13 printf "%"$n"s" X > r2/large.$n &&
14 git -C r2 add large.$n &&
-15 git -C r2 commit -m "$n" ?!LOOP?!
+15 git -C r2 commit -m "$n" ?!LINT: missing '|| return 1'?!
16 done
diff --git a/t/chainlint/loop-in-if.expect b/t/chainlint/loop-in-if.expect
index 4e8c67c914..32e076ad1b 100644
--- a/t/chainlint/loop-in-if.expect
+++ b/t/chainlint/loop-in-if.expect
@@ -3,10 +3,10 @@
4 then
5 while true
6 do
-7 echo "pop" ?!AMP?!
-8 echo "glup" ?!LOOP?!
-9 done ?!AMP?!
+7 echo "pop" ?!LINT: missing '&&'?!
+8 echo "glup" ?!LINT: missing '|| exit 1'?!
+9 done ?!LINT: missing '&&'?!
10 foo
-11 fi ?!AMP?!
+11 fi ?!LINT: missing '&&'?!
12 bar
13 )
diff --git a/t/chainlint/multi-line-string.expect b/t/chainlint/multi-line-string.expect
index 62c54e3a5e..9d33297525 100644
--- a/t/chainlint/multi-line-string.expect
+++ b/t/chainlint/multi-line-string.expect
@@ -3,7 +3,7 @@
4 line 2
5 line 3" &&
6 y="line 1
-7 line2" ?!AMP?!
+7 line2" ?!LINT: missing '&&'?!
8 foobar
9 ) &&
10 (
diff --git a/t/chainlint/negated-one-liner.expect b/t/chainlint/negated-one-liner.expect
index a6ce52a1da..0a6f3c29b2 100644
--- a/t/chainlint/negated-one-liner.expect
+++ b/t/chainlint/negated-one-liner.expect
@@ -1,5 +1,5 @@
2 ! (foo && bar) &&
3 ! (foo && bar) >baz &&
4
-5 ! (foo; ?!AMP?! bar) &&
-6 ! (foo; ?!AMP?! bar) >baz
+5 ! (foo; ?!LINT: missing '&&'?! bar) &&
+6 ! (foo; ?!LINT: missing '&&'?! bar) >baz
diff --git a/t/chainlint/nested-cuddled-subshell.expect b/t/chainlint/nested-cuddled-subshell.expect
index 0191c9c294..fec2c74274 100644
--- a/t/chainlint/nested-cuddled-subshell.expect
+++ b/t/chainlint/nested-cuddled-subshell.expect
@@ -5,7 +5,7 @@
6
7 (cd foo &&
8 bar
-9 ) ?!AMP?!
+9 ) ?!LINT: missing '&&'?!
10
11 (
12 cd foo &&
@@ -13,13 +13,13 @@
14
15 (
16 cd foo &&
-17 bar) ?!AMP?!
+17 bar) ?!LINT: missing '&&'?!
18
19 (cd foo &&
20 bar) &&
21
22 (cd foo &&
-23 bar) ?!AMP?!
+23 bar) ?!LINT: missing '&&'?!
24
25 foobar
26 )
diff --git a/t/chainlint/nested-here-doc.expect b/t/chainlint/nested-here-doc.expect
index 70d9b68dc9..571f4c9514 100644
--- a/t/chainlint/nested-here-doc.expect
+++ b/t/chainlint/nested-here-doc.expect
@@ -18,7 +18,7 @@
19 toink
20 INPUT_END
21
-22 cat <<-\EOT ?!AMP?!
+22 cat <<-\EOT ?!LINT: missing '&&'?!
23 text goes here
24 data <<EOF
25 data goes here
diff --git a/t/chainlint/nested-loop-detect-failure.expect b/t/chainlint/nested-loop-detect-failure.expect
index c13c4d2f90..b4aaa621a2 100644
--- a/t/chainlint/nested-loop-detect-failure.expect
+++ b/t/chainlint/nested-loop-detect-failure.expect
@@ -2,8 +2,8 @@
3 do
4 for j in 0 1 2 3 4 5 6 7 8 9;
5 do
-6 echo "$i$j" >"path$i$j" ?!LOOP?!
-7 done ?!LOOP?!
+6 echo "$i$j" >"path$i$j" ?!LINT: missing '|| return 1'?!
+7 done ?!LINT: missing '|| return 1'?!
8 done &&
9
10 for i in 0 1 2 3 4 5 6 7 8 9;
@@ -18,7 +18,7 @@
19 do
20 for j in 0 1 2 3 4 5 6 7 8 9;
21 do
-22 echo "$i$j" >"path$i$j" ?!LOOP?!
+22 echo "$i$j" >"path$i$j" ?!LINT: missing '|| return 1'?!
23 done || return 1
24 done &&
25
diff --git a/t/chainlint/nested-subshell-comment.expect b/t/chainlint/nested-subshell-comment.expect
index f89a8d03a8..078c6f275f 100644
--- a/t/chainlint/nested-subshell-comment.expect
+++ b/t/chainlint/nested-subshell-comment.expect
@@ -6,6 +6,6 @@
7 # minor numbers of cows (or do they?)
8 baz &&
9 snaff
-10 ) ?!AMP?!
+10 ) ?!LINT: missing '&&'?!
11 fuzzy
12 )
diff --git a/t/chainlint/nested-subshell.expect b/t/chainlint/nested-subshell.expect
index 811e8a7912..a8d85d5d5b 100644
--- a/t/chainlint/nested-subshell.expect
+++ b/t/chainlint/nested-subshell.expect
@@ -7,7 +7,7 @@
8
9 cd foo &&
10 (
-11 echo a ?!AMP?!
+11 echo a ?!LINT: missing '&&'?!
12 echo b
13 ) >file
14 )
diff --git a/t/chainlint/not-heredoc.expect b/t/chainlint/not-heredoc.expect
index 611b7b75cb..5d51705a7a 100644
--- a/t/chainlint/not-heredoc.expect
+++ b/t/chainlint/not-heredoc.expect
@@ -9,6 +9,6 @@
10 echo ourside &&
11 echo "=======" &&
12 echo theirside &&
-13 echo ">>>>>>> theirs" ?!AMP?!
+13 echo ">>>>>>> theirs" ?!LINT: missing '&&'?!
14 poodle
15 ) >merged
diff --git a/t/chainlint/one-liner-for-loop.expect b/t/chainlint/one-liner-for-loop.expect
index 49dcf065ef..e1fcbd3639 100644
--- a/t/chainlint/one-liner-for-loop.expect
+++ b/t/chainlint/one-liner-for-loop.expect
@@ -3,7 +3,7 @@
4 cd dir-rename-and-content &&
5 test_write_lines 1 2 3 4 5 >foo &&
6 mkdir olddir &&
-7 for i in a b c; do echo $i >olddir/$i; ?!LOOP?! done ?!AMP?!
+7 for i in a b c; do echo $i >olddir/$i; ?!LINT: missing '|| exit 1'?! done ?!LINT: missing '&&'?!
8 git add foo olddir &&
9 git commit -m "original" &&
10 )
diff --git a/t/chainlint/one-liner.expect b/t/chainlint/one-liner.expect
index 9861811283..5deeb05070 100644
--- a/t/chainlint/one-liner.expect
+++ b/t/chainlint/one-liner.expect
@@ -2,8 +2,8 @@
3 (foo && bar) |
4 (foo && bar) >baz &&
5
-6 (foo; ?!AMP?! bar) &&
-7 (foo; ?!AMP?! bar) |
-8 (foo; ?!AMP?! bar) >baz &&
+6 (foo; ?!LINT: missing '&&'?! bar) &&
+7 (foo; ?!LINT: missing '&&'?! bar) |
+8 (foo; ?!LINT: missing '&&'?! bar) >baz &&
9
10 (foo "bar; baz")
diff --git a/t/chainlint/pipe.expect b/t/chainlint/pipe.expect
index 1bbe5a2ce1..d947c76584 100644
--- a/t/chainlint/pipe.expect
+++ b/t/chainlint/pipe.expect
@@ -4,7 +4,7 @@
5 baz &&
6
7 fish |
-8 cow ?!AMP?!
+8 cow ?!LINT: missing '&&'?!
9
10 sunder
11 )
diff --git a/t/chainlint/semicolon.expect b/t/chainlint/semicolon.expect
index 866438310c..2b499fbe70 100644
--- a/t/chainlint/semicolon.expect
+++ b/t/chainlint/semicolon.expect
@@ -1,19 +1,19 @@
2 (
-3 cat foo ; ?!AMP?! echo bar ?!AMP?!
-4 cat foo ; ?!AMP?! echo bar
+3 cat foo ; ?!LINT: missing '&&'?! echo bar ?!LINT: missing '&&'?!
+4 cat foo ; ?!LINT: missing '&&'?! echo bar
5 ) &&
6 (
-7 cat foo ; ?!AMP?! echo bar &&
-8 cat foo ; ?!AMP?! echo bar
+7 cat foo ; ?!LINT: missing '&&'?! echo bar &&
+8 cat foo ; ?!LINT: missing '&&'?! echo bar
9 ) &&
10 (
11 echo "foo; bar" &&
-12 cat foo; ?!AMP?! echo bar
+12 cat foo; ?!LINT: missing '&&'?! echo bar
13 ) &&
14 (
15 foo;
16 ) &&
17 (cd foo &&
18 for i in a b c; do
-19 echo; ?!LOOP?!
+19 echo; ?!LINT: missing '|| exit 1'?!
20 done)
diff --git a/t/chainlint/subshell-here-doc.expect b/t/chainlint/subshell-here-doc.expect
index 5647500c82..e450caf948 100644
--- a/t/chainlint/subshell-here-doc.expect
+++ b/t/chainlint/subshell-here-doc.expect
@@ -6,7 +6,7 @@
7 nevermore...
8 EOF
9
-10 cat <<EOF >bip ?!AMP?!
+10 cat <<EOF >bip ?!LINT: missing '&&'?!
11 fish fly high
12 EOF
13
diff --git a/t/chainlint/subshell-one-liner.expect b/t/chainlint/subshell-one-liner.expect
index 214316c6a0..265d996a21 100644
--- a/t/chainlint/subshell-one-liner.expect
+++ b/t/chainlint/subshell-one-liner.expect
@@ -3,17 +3,17 @@
4 (foo && bar) |
5 (foo && bar) >baz &&
6
-7 (foo; ?!AMP?! bar) &&
-8 (foo; ?!AMP?! bar) |
-9 (foo; ?!AMP?! bar) >baz &&
+7 (foo; ?!LINT: missing '&&'?! bar) &&
+8 (foo; ?!LINT: missing '&&'?! bar) |
+9 (foo; ?!LINT: missing '&&'?! bar) >baz &&
10
11 (foo || exit 1) &&
12 (foo || exit 1) |
13 (foo || exit 1) >baz &&
14
-15 (foo && bar) ?!AMP?!
+15 (foo && bar) ?!LINT: missing '&&'?!
16
-17 (foo && bar; ?!AMP?! baz) ?!AMP?!
+17 (foo && bar; ?!LINT: missing '&&'?! baz) ?!LINT: missing '&&'?!
18
19 foobar
20 )
diff --git a/t/chainlint/token-pasting.expect b/t/chainlint/token-pasting.expect
index 64f3235d26..387189b6de 100644
--- a/t/chainlint/token-pasting.expect
+++ b/t/chainlint/token-pasting.expect
@@ -2,13 +2,13 @@
3 git config filter.rot13.clean ./rot13.sh &&
4
5 {
-6 echo "*.t filter=rot13" ?!AMP?!
+6 echo "*.t filter=rot13" ?!LINT: missing '&&'?!
7 echo "*.i ident"
8 } >.gitattributes &&
9
10 {
-11 echo a b c d e f g h i j k l m ?!AMP?!
-12 echo n o p q r s t u v w x y z ?!AMP?!
+11 echo a b c d e f g h i j k l m ?!LINT: missing '&&'?!
+12 echo n o p q r s t u v w x y z ?!LINT: missing '&&'?!
13 echo '$Id$'
14 } >test &&
15 cat test >test.t &&
@@ -19,7 +19,7 @@
20 git checkout -- test test.t test.i &&
21
22 echo "content-test2" >test2.o &&
-23 echo "content-test3 - filename with special characters" >"test3 'sq',$x=.o" ?!AMP?!
+23 echo "content-test3 - filename with special characters" >"test3 'sq',$x=.o" ?!LINT: missing '&&'?!
24
25 downstream_url_for_sed=$(
26 printf "%sn" "$downstream_url" |
diff --git a/t/chainlint/unclosed-here-doc-indent.expect b/t/chainlint/unclosed-here-doc-indent.expect
index f78e23cb63..156906c85a 100644
--- a/t/chainlint/unclosed-here-doc-indent.expect
+++ b/t/chainlint/unclosed-here-doc-indent.expect
@@ -1,4 +1,4 @@
2 command_which_is_run &&
-3 cat >expect <<-\EOF ?!UNCLOSED-HEREDOC?! &&
+3 cat >expect <<-\EOF ?!LINT: unclosed heredoc?! &&
4 we forget to end the here-doc
5 command_which_is_gobbled
diff --git a/t/chainlint/unclosed-here-doc.expect b/t/chainlint/unclosed-here-doc.expect
index 51304672cf..752c608862 100644
--- a/t/chainlint/unclosed-here-doc.expect
+++ b/t/chainlint/unclosed-here-doc.expect
@@ -1,5 +1,5 @@
2 command_which_is_run &&
-3 cat >expect <<\EOF ?!UNCLOSED-HEREDOC?! &&
+3 cat >expect <<\EOF ?!LINT: unclosed heredoc?! &&
4 we try to end the here-doc below,
5 but the indentation throws us off
6 since the operator is not "<<-".
diff --git a/t/chainlint/while-loop.expect b/t/chainlint/while-loop.expect
index 5ffabd5a93..2ba5582165 100644
--- a/t/chainlint/while-loop.expect
+++ b/t/chainlint/while-loop.expect
@@ -1,14 +1,14 @@
2 (
3 while true
4 do
-5 echo foo ?!AMP?!
-6 cat <<-\EOF ?!LOOP?!
+5 echo foo ?!LINT: missing '&&'?!
+6 cat <<-\EOF ?!LINT: missing '|| exit 1'?!
7 bar
8 EOF
-9 done ?!AMP?!
+9 done ?!LINT: missing '&&'?!
10
11 while true; do
12 echo foo &&
-13 cat bar ?!LOOP?!
+13 cat bar ?!LINT: missing '|| exit 1'?!
14 done
15 )
diff --git a/t/check-non-portable-shell.pl b/t/check-non-portable-shell.pl
index b2b28c2ced..6ee7700eb4 100755
--- a/t/check-non-portable-shell.pl
+++ b/t/check-non-portable-shell.pl
@@ -49,8 +49,8 @@ while (<>) {
/\bexport\s+[A-Za-z0-9_]*=/ and err '"export FOO=bar" is not portable (use FOO=bar && export FOO)';
/\blocal\s+[A-Za-z0-9_]*=\$([A-Za-z0-9_{]|[(][^(])/ and
err q(quote "$val" in 'local var=$val');
- /^\s*([A-Z0-9_]+=(\w*|(["']).*?\3)\s+)+(\w+)/ and exists($func{$4}) and
- err '"FOO=bar shell_func" assignment extends beyond "shell_func"';
+ /\b([A-Z0-9_]+=(\w*|(["']).*?\3)\s+)+(\w+)/ and !/test_env.+=/ and exists($func{$4}) and
+ err '"FOO=bar shell_func" is not portable (use test_env FOO=bar shell_func)';
$line = '';
# this resets our $. for each file
close ARGV if eof;
diff --git a/t/helper/test-advise.c b/t/helper/test-advise.c
index 8a3fd0009a..6967c8e25c 100644
--- a/t/helper/test-advise.c
+++ b/t/helper/test-advise.c
@@ -1,3 +1,5 @@
+#define USE_THE_REPOSITORY_VARIABLE
+
#include "test-tool.h"
#include "advice.h"
#include "config.h"
diff --git a/t/helper/test-config.c b/t/helper/test-config.c
index ed444ca4c2..33247f0e92 100644
--- a/t/helper/test-config.c
+++ b/t/helper/test-config.c
@@ -1,3 +1,5 @@
+#define USE_THE_REPOSITORY_VARIABLE
+
#include "test-tool.h"
#include "config.h"
#include "setup.h"
@@ -94,7 +96,8 @@ int cmd__config(int argc, const char **argv)
struct config_set cs;
if (argc == 3 && !strcmp(argv[1], "read_early_config")) {
- read_early_config(early_config_cb, (void *)argv[2]);
+ read_early_config(the_repository, early_config_cb,
+ (void *)argv[2]);
return 0;
}
diff --git a/t/helper/test-example-tap.c b/t/helper/test-example-tap.c
index d072ad559f..229d495ecf 100644
--- a/t/helper/test-example-tap.c
+++ b/t/helper/test-example-tap.c
@@ -70,8 +70,10 @@ static void t_empty(void)
; /* empty */
}
-int cmd__example_tap(int argc, const char **argv)
+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");
@@ -92,5 +94,38 @@ int cmd__example_tap(int argc, const char **argv)
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-hashmap.c b/t/helper/test-hashmap.c
index 2912899558..7782ae585e 100644
--- a/t/helper/test-hashmap.c
+++ b/t/helper/test-hashmap.c
@@ -12,11 +12,6 @@ struct test_entry
char key[FLEX_ARRAY];
};
-static const char *get_value(const struct test_entry *e)
-{
- return e->key + strlen(e->key) + 1;
-}
-
static int test_entry_cmp(const void *cmp_data,
const struct hashmap_entry *eptr,
const struct hashmap_entry *entry_or_key,
@@ -141,30 +136,16 @@ static void perf_hashmap(unsigned int method, unsigned int rounds)
/*
* Read stdin line by line and print result of commands to stdout:
*
- * hash key -> strhash(key) memhash(key) strihash(key) memihash(key)
- * put key value -> NULL / old value
- * get key -> NULL / value
- * remove key -> NULL / old value
- * iterate -> key1 value1\nkey2 value2\n...
- * size -> tablesize numentries
- *
* perfhashmap method rounds -> test hashmap.[ch] performance
*/
-int cmd__hashmap(int argc, const char **argv)
+int cmd__hashmap(int argc UNUSED, const char **argv UNUSED)
{
struct string_list parts = STRING_LIST_INIT_NODUP;
struct strbuf line = STRBUF_INIT;
- int icase;
- struct hashmap map = HASHMAP_INIT(test_entry_cmp, &icase);
-
- /* init hash map */
- icase = argc > 1 && !strcmp("ignorecase", argv[1]);
/* process commands from stdin */
while (strbuf_getline(&line, stdin) != EOF) {
char *cmd, *p1, *p2;
- unsigned int hash = 0;
- struct test_entry *entry;
/* break line into command and up to two parameters */
string_list_setlen(&parts, 0);
@@ -180,84 +161,8 @@ int cmd__hashmap(int argc, const char **argv)
cmd = parts.items[0].string;
p1 = parts.nr >= 1 ? parts.items[1].string : NULL;
p2 = parts.nr >= 2 ? parts.items[2].string : NULL;
- if (p1)
- hash = icase ? strihash(p1) : strhash(p1);
-
- if (!strcmp("add", cmd) && p1 && p2) {
-
- /* create entry with key = p1, value = p2 */
- entry = alloc_test_entry(hash, p1, p2);
-
- /* add to hashmap */
- hashmap_add(&map, &entry->ent);
-
- } else if (!strcmp("put", cmd) && p1 && p2) {
-
- /* create entry with key = p1, value = p2 */
- entry = alloc_test_entry(hash, p1, p2);
-
- /* add / replace entry */
- entry = hashmap_put_entry(&map, entry, ent);
-
- /* print and free replaced entry, if any */
- puts(entry ? get_value(entry) : "NULL");
- free(entry);
-
- } else if (!strcmp("get", cmd) && p1) {
- /* lookup entry in hashmap */
- entry = hashmap_get_entry_from_hash(&map, hash, p1,
- struct test_entry, ent);
-
- /* print result */
- if (!entry)
- puts("NULL");
- hashmap_for_each_entry_from(&map, entry, ent)
- puts(get_value(entry));
-
- } else if (!strcmp("remove", cmd) && p1) {
-
- /* setup static key */
- struct hashmap_entry key;
- struct hashmap_entry *rm;
- hashmap_entry_init(&key, hash);
-
- /* remove entry from hashmap */
- rm = hashmap_remove(&map, &key, p1);
- entry = rm ? container_of(rm, struct test_entry, ent)
- : NULL;
-
- /* print result and free entry*/
- puts(entry ? get_value(entry) : "NULL");
- free(entry);
-
- } else if (!strcmp("iterate", cmd)) {
- struct hashmap_iter iter;
-
- hashmap_for_each_entry(&map, &iter, entry,
- ent /* member name */)
- printf("%s %s\n", entry->key, get_value(entry));
-
- } else if (!strcmp("size", cmd)) {
-
- /* print table sizes */
- printf("%u %u\n", map.tablesize,
- hashmap_get_size(&map));
-
- } else if (!strcmp("intern", cmd) && p1) {
-
- /* test that strintern works */
- const char *i1 = strintern(p1);
- const char *i2 = strintern(p1);
- if (strcmp(i1, p1))
- printf("strintern(%s) returns %s\n", p1, i1);
- else if (i1 == p1)
- printf("strintern(%s) returns input pointer\n", p1);
- else if (i1 != i2)
- printf("strintern(%s) != strintern(%s)", i1, i2);
- else
- printf("%s\n", i1);
- } else if (!strcmp("perfhashmap", cmd) && p1 && p2) {
+ if (!strcmp("perfhashmap", cmd) && p1 && p2) {
perf_hashmap(atoi(p1), atoi(p2));
@@ -270,6 +175,5 @@ int cmd__hashmap(int argc, const char **argv)
string_list_clear(&parts, 0);
strbuf_release(&line);
- hashmap_clear_and_free(&map, struct test_entry, ent);
return 0;
}
diff --git a/t/helper/test-mergesort.c b/t/helper/test-mergesort.c
index 42ccc87051..328bfe2977 100644
--- a/t/helper/test-mergesort.c
+++ b/t/helper/test-mergesort.c
@@ -122,7 +122,7 @@ static const struct dist *get_dist_by_name(const char *name)
return NULL;
}
-static void mode_copy(int *arr, int n)
+static void mode_copy(int *arr UNUSED, int n UNUSED)
{
/* nothing */
}
diff --git a/t/helper/test-oid-array.c b/t/helper/test-oid-array.c
deleted file mode 100644
index 076b849cbf..0000000000
--- a/t/helper/test-oid-array.c
+++ /dev/null
@@ -1,49 +0,0 @@
-#define USE_THE_REPOSITORY_VARIABLE
-
-#include "test-tool.h"
-#include "hex.h"
-#include "oid-array.h"
-#include "setup.h"
-#include "strbuf.h"
-
-static int print_oid(const struct object_id *oid, void *data UNUSED)
-{
- puts(oid_to_hex(oid));
- return 0;
-}
-
-int cmd__oid_array(int argc UNUSED, const char **argv UNUSED)
-{
- struct oid_array array = OID_ARRAY_INIT;
- struct strbuf line = STRBUF_INIT;
- int nongit_ok;
-
- setup_git_directory_gently(&nongit_ok);
- if (nongit_ok)
- repo_set_hash_algo(the_repository, GIT_HASH_SHA1);
-
- while (strbuf_getline(&line, stdin) != EOF) {
- const char *arg;
- struct object_id oid;
-
- if (skip_prefix(line.buf, "append ", &arg)) {
- if (get_oid_hex(arg, &oid))
- die("not a hexadecimal oid: %s", arg);
- oid_array_append(&array, &oid);
- } else if (skip_prefix(line.buf, "lookup ", &arg)) {
- if (get_oid_hex(arg, &oid))
- die("not a hexadecimal oid: %s", arg);
- printf("%d\n", oid_array_lookup(&array, &oid));
- } else if (!strcmp(line.buf, "clear"))
- oid_array_clear(&array);
- else if (!strcmp(line.buf, "for_each_unique"))
- oid_array_for_each_unique(&array, print_oid, NULL);
- else
- die("unknown command: %s", line.buf);
- }
-
- strbuf_release(&line);
- oid_array_clear(&array);
-
- return 0;
-}
diff --git a/t/helper/test-path-utils.c b/t/helper/test-path-utils.c
index bf0e23ed50..3129aa28fd 100644
--- a/t/helper/test-path-utils.c
+++ b/t/helper/test-path-utils.c
@@ -1,3 +1,5 @@
+#define USE_THE_REPOSITORY_VARIABLE
+
#include "test-tool.h"
#include "abspath.h"
#include "environment.h"
@@ -38,7 +40,7 @@ static void normalize_argv_string(const char **var, const char *input)
*var = input;
if (*var && (**var == '<' || **var == '('))
- die("Bad value: %s\n", input);
+ die("Bad value: %s", input);
}
struct test_data {
@@ -78,12 +80,12 @@ static int test_function(struct test_data *data, char *(*func)(char *input),
if (!strcmp(to, data[i].to))
continue;
if (!data[i].alternative)
- error("FAIL: %s(%s) => '%s' != '%s'\n",
+ 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'\n",
+ error("FAIL: %s(%s) => '%s' != '%s', '%s'",
funcname, data[i].from, to, data[i].to,
data[i].alternative);
failed = 1;
diff --git a/t/helper/test-progress.c b/t/helper/test-progress.c
index 66acb6a06c..44be2645e9 100644
--- a/t/helper/test-progress.c
+++ b/t/helper/test-progress.c
@@ -62,13 +62,13 @@ int cmd__progress(int argc, const char **argv)
else if (*end == ' ')
title = string_list_insert(&titles, end + 1)->string;
else
- die("invalid input: '%s'\n", line.buf);
+ die("invalid input: '%s'", line.buf);
progress = start_progress(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'\n", line.buf);
+ die("invalid input: '%s'", line.buf);
display_progress(progress, item_count);
} else if (skip_prefix(line.buf, "throughput ",
(const char **) &end)) {
@@ -76,10 +76,10 @@ int cmd__progress(int argc, const char **argv)
byte_count = strtoull(end, &end, 10);
if (*end != ' ')
- die("invalid input: '%s'\n", line.buf);
+ die("invalid input: '%s'", line.buf);
test_ms = strtoull(end + 1, &end, 10);
if (*end != '\0')
- die("invalid input: '%s'\n", line.buf);
+ die("invalid input: '%s'", line.buf);
progress_test_ns = test_ms * 1000 * 1000;
display_throughput(progress, byte_count);
} else if (!strcmp(line.buf, "update")) {
@@ -87,7 +87,7 @@ int cmd__progress(int argc, const char **argv)
} else if (!strcmp(line.buf, "stop")) {
stop_progress(&progress);
} else {
- die("invalid input: '%s'\n", line.buf);
+ die("invalid input: '%s'", line.buf);
}
}
strbuf_release(&line);
diff --git a/t/helper/test-reach.c b/t/helper/test-reach.c
index 5dd374379c..995e382863 100644
--- a/t/helper/test-reach.c
+++ b/t/helper/test-reach.c
@@ -67,13 +67,13 @@ int cmd__reach(int ac, const char **av)
peeled = deref_tag_noverify(the_repository, orig);
if (!peeled)
- die("failed to load commit for input %s resulting in oid %s\n",
+ 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\n",
+ die("failed to load commit for input %s resulting in oid %s",
buf.buf, oid_to_hex(&oid));
switch (buf.buf[0]) {
@@ -116,6 +116,8 @@ int cmd__reach(int ac, const char **av)
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,
diff --git a/t/helper/test-read-midx.c b/t/helper/test-read-midx.c
index 83effc2b5f..438fb9fc61 100644
--- a/t/helper/test-read-midx.c
+++ b/t/helper/test-read-midx.c
@@ -9,8 +9,10 @@
#include "packfile.h"
#include "setup.h"
#include "gettext.h"
+#include "pack-revindex.h"
-static int read_midx_file(const char *object_dir, int show_objects)
+static int read_midx_file(const char *object_dir, const char *checksum,
+ int show_objects)
{
uint32_t i;
struct multi_pack_index *m;
@@ -21,6 +23,13 @@ static int read_midx_file(const char *object_dir, int show_objects)
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,
@@ -54,7 +63,8 @@ static int read_midx_file(const char *object_dir, int show_objects)
struct pack_entry e;
for (i = 0; i < m->num_objects; i++) {
- nth_midxed_object_oid(&oid, m, 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",
@@ -76,6 +86,8 @@ static int read_midx_checksum(const char *object_dir)
if (!m)
return 1;
printf("%s\n", hash_to_hex(get_midx_checksum(m)));
+
+ close_midx(m);
return 0;
}
@@ -92,10 +104,12 @@ static int read_midx_preferred_pack(const char *object_dir)
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;
}
@@ -111,9 +125,11 @@ static int read_midx_bitmapped_packs(const char *object_dir)
if (!midx)
return 1;
- for (i = 0; i < midx->num_packs; i++) {
- if (nth_bitmapped_pack(the_repository, midx, &pack, i) < 0)
+ 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);
@@ -127,16 +143,16 @@ static int read_midx_bitmapped_packs(const char *object_dir)
int cmd__read_midx(int argc, const char **argv)
{
- if (!(argc == 2 || argc == 3))
- usage("read-midx [--show-objects|--checksum|--preferred-pack|--bitmap] <object-dir>");
+ 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], 1);
+ 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], 0);
+ 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
index 637b8b294e..65346dee55 100644
--- a/t/helper/test-ref-store.c
+++ b/t/helper/test-ref-store.c
@@ -156,7 +156,7 @@ static int cmd_rename_ref(struct ref_store *refs, const char **argv)
return refs_rename_ref(refs, oldref, newref, logmsg);
}
-static int each_ref(const char *refname, const struct object_id *oid,
+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);
diff --git a/t/helper/test-reftable.c b/t/helper/test-reftable.c
index aa6538a8da..29d4e9a755 100644
--- a/t/helper/test-reftable.c
+++ b/t/helper/test-reftable.c
@@ -1,20 +1,194 @@
+#include "git-compat-util.h"
+#include "hash.h"
+#include "hex.h"
#include "reftable/system.h"
-#include "reftable/reftable-tests.h"
+#include "reftable/reftable-error.h"
+#include "reftable/reftable-merged.h"
+#include "reftable/reftable-reader.h"
+#include "reftable/reftable-stack.h"
#include "test-tool.h"
-int cmd__reftable(int argc, const char **argv)
+static void print_help(void)
{
- /* test from simple to complex. */
- block_test_main(argc, argv);
- tree_test_main(argc, argv);
- pq_test_main(argc, argv);
- readwrite_test_main(argc, argv);
- merged_test_main(argc, argv);
- stack_test_main(argc, argv);
+ 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_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;
+
+ reftable_merged_table_init_ref_iterator(mt, &it);
+ 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);
+
+ reftable_merged_table_init_log_iterator(mt, &it);
+ 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_reader *r = NULL;
+ int err;
+
+ err = reftable_block_source_from_file(&src, tablename);
+ if (err < 0)
+ goto done;
+
+ err = reftable_reader_new(&r, &src, tablename);
+ if (err < 0)
+ goto done;
+
+ err = reftable_merged_table_new(&mt, &r, 1,
+ reftable_reader_hash_id(r));
+ if (err < 0)
+ goto done;
+
+ err = dump_table(mt);
+
+done:
+ reftable_merged_table_free(mt);
+ reftable_reader_decref(r);
+ return err;
+}
+
int cmd__dump_reftable(int argc, const char **argv)
{
- return reftable_dump_main(argc, (char *const *)argv);
+ int err = 0;
+ int opt_dump_blocks = 0;
+ int opt_dump_table = 0;
+ int opt_dump_stack = 0;
+ uint32_t opt_hash_id = GIT_SHA1_FORMAT_ID;
+ 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 = GIT_SHA256_FORMAT_ID;
+ 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 = reftable_reader_print_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-repository.c b/t/helper/test-repository.c
index c6a074df3d..63c37de33d 100644
--- a/t/helper/test-repository.c
+++ b/t/helper/test-repository.c
@@ -19,7 +19,7 @@ static void test_parse_commit_in_graph(const char *gitdir, const char *worktree,
setup_git_env(gitdir);
- memset(the_repository, 0, sizeof(*the_repository));
+ repo_clear(the_repository);
if (repo_init(&r, gitdir, worktree))
die("Couldn't init repo");
@@ -49,7 +49,7 @@ static void test_get_commit_tree_in_graph(const char *gitdir,
setup_git_env(gitdir);
- memset(the_repository, 0, sizeof(*the_repository));
+ repo_clear(the_repository);
if (repo_init(&r, gitdir, worktree))
die("Couldn't init repo");
diff --git a/t/helper/test-submodule-nested-repo-config.c b/t/helper/test-submodule-nested-repo-config.c
index 6ca069ce63..6dce957153 100644
--- a/t/helper/test-submodule-nested-repo-config.c
+++ b/t/helper/test-submodule-nested-repo-config.c
@@ -29,6 +29,6 @@ int cmd__submodule_nested_repo_config(int argc, const char **argv)
print_config_from_gitmodules(&subrepo, argv[2]);
submodule_free(the_repository);
-
+ repo_clear(&subrepo);
return 0;
}
diff --git a/t/helper/test-tool.c b/t/helper/test-tool.c
index da3e69128a..1ebb69a5dc 100644
--- a/t/helper/test-tool.c
+++ b/t/helper/test-tool.c
@@ -26,6 +26,7 @@ static struct test_cmd cmds[] = {
{ "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 },
@@ -43,7 +44,6 @@ static struct test_cmd cmds[] = {
{ "match-trees", cmd__match_trees },
{ "mergesort", cmd__mergesort },
{ "mktemp", cmd__mktemp },
- { "oid-array", cmd__oid_array },
{ "online-cpus", cmd__online_cpus },
{ "pack-mtimes", cmd__pack_mtimes },
{ "parse-options", cmd__parse_options },
@@ -61,9 +61,7 @@ static struct test_cmd cmds[] = {
{ "read-graph", cmd__read_graph },
{ "read-midx", cmd__read_midx },
{ "ref-store", cmd__ref_store },
- { "reftable", cmd__reftable },
{ "rot13-filter", cmd__rot13_filter },
- { "dump-reftable", cmd__dump_reftable },
{ "regex", cmd__regex },
{ "repository", cmd__repository },
{ "revision-walking", cmd__revision_walking },
@@ -83,7 +81,6 @@ static struct test_cmd cmds[] = {
{ "trace2", cmd__trace2 },
{ "truncate", cmd__truncate },
{ "userdiff", cmd__userdiff },
- { "urlmatch-normalization", cmd__urlmatch_normalization },
{ "xml-encode", cmd__xml_encode },
{ "wildmatch", cmd__wildmatch },
#ifdef GIT_WINDOWS_NATIVE
diff --git a/t/helper/test-tool.h b/t/helper/test-tool.h
index 642a34578c..21802ac27d 100644
--- a/t/helper/test-tool.h
+++ b/t/helper/test-tool.h
@@ -55,7 +55,6 @@ 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__reftable(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);
@@ -64,7 +63,6 @@ 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__oid_array(int argc, const char **argv);
int cmd__sha256(int argc, const char **argv);
int cmd__sigchain(int argc, const char **argv);
int cmd__simple_ipc(int argc, const char **argv);
@@ -76,7 +74,6 @@ 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__urlmatch_normalization(int argc, const char **argv);
int cmd__xml_encode(int argc, const char **argv);
int cmd__wildmatch(int argc, const char **argv);
#ifdef GIT_WINDOWS_NATIVE
diff --git a/t/helper/test-urlmatch-normalization.c b/t/helper/test-urlmatch-normalization.c
deleted file mode 100644
index 86edd454f5..0000000000
--- a/t/helper/test-urlmatch-normalization.c
+++ /dev/null
@@ -1,56 +0,0 @@
-#include "test-tool.h"
-#include "git-compat-util.h"
-#include "urlmatch.h"
-
-int cmd__urlmatch_normalization(int argc, const char **argv)
-{
- const char usage[] = "test-tool urlmatch-normalization [-p | -l] <url1> | <url1> <url2>";
- char *url1 = NULL, *url2 = NULL;
- int opt_p = 0, opt_l = 0;
- int ret = 0;
-
- /*
- * For one url, succeed if url_normalize succeeds on it, fail otherwise.
- * For two urls, succeed only if url_normalize succeeds on both and
- * the results compare equal with strcmp. If -p is given (one url only)
- * and url_normalize succeeds, print the result followed by "\n". If
- * -l is given (one url only) and url_normalize succeeds, print the
- * returned length in decimal followed by "\n".
- */
-
- if (argc > 1 && !strcmp(argv[1], "-p")) {
- opt_p = 1;
- argc--;
- argv++;
- } else if (argc > 1 && !strcmp(argv[1], "-l")) {
- opt_l = 1;
- argc--;
- argv++;
- }
-
- if (argc < 2 || argc > 3)
- die("%s", usage);
-
- if (argc == 2) {
- struct url_info info;
- url1 = url_normalize(argv[1], &info);
- if (!url1)
- return 1;
- if (opt_p)
- printf("%s\n", url1);
- if (opt_l)
- printf("%u\n", (unsigned)info.url_len);
- goto cleanup;
- }
-
- if (opt_p || opt_l)
- die("%s", usage);
-
- url1 = url_normalize(argv[1], NULL);
- url2 = url_normalize(argv[2], NULL);
- ret = (url1 && url2 && !strcmp(url1, url2)) ? 0 : 1;
-cleanup:
- free(url1);
- free(url2);
- return ret;
-}
diff --git a/t/helper/test-userdiff.c b/t/helper/test-userdiff.c
index 0ce31ce59f..94c48ababb 100644
--- a/t/helper/test-userdiff.c
+++ b/t/helper/test-userdiff.c
@@ -1,3 +1,5 @@
+#define USE_THE_REPOSITORY_VARIABLE
+
#include "test-tool.h"
#include "setup.h"
#include "userdiff.h"
diff --git a/t/interop/README b/t/interop/README
index 72d42bd856..4e0608f857 100644
--- a/t/interop/README
+++ b/t/interop/README
@@ -83,3 +83,10 @@ You can then use test_expect_success as usual, with a few differences:
should create one with the appropriate version of git.
At the end of the script, call test_done as usual.
+
+Some older versions may need a few build knobs tweaked (e.g., ancient
+versions of Git no longer build with modern OpenSSL). Your script can
+set MAKE_OPTS_A and MAKE_OPTS_B, which will be passed alongside
+GIT_INTEROP_MAKE_OPTS. Users can override them per-script by setting
+GIT_INTEROP_MAKE_OPTS_{A,B} in the environment, just like they can set
+GIT_TEST_VERSION_{A,B}.
diff --git a/t/interop/i5500-git-daemon.sh b/t/interop/i5500-git-daemon.sh
index 4d22e42f84..88712d1b5d 100755
--- a/t/interop/i5500-git-daemon.sh
+++ b/t/interop/i5500-git-daemon.sh
@@ -2,6 +2,7 @@
VERSION_A=.
VERSION_B=v1.0.0
+MAKE_OPTS_B="NO_OPENSSL=TooOld"
: ${LIB_GIT_DAEMON_PORT:=5500}
LIB_GIT_DAEMON_COMMAND='git.a daemon'
diff --git a/t/interop/interop-lib.sh b/t/interop/interop-lib.sh
index 62f4481b6e..1b5864d2a7 100644
--- a/t/interop/interop-lib.sh
+++ b/t/interop/interop-lib.sh
@@ -45,7 +45,7 @@ build_version () {
(
cd "$dir" &&
- make $GIT_INTEROP_MAKE_OPTS >&2 &&
+ make $2 $GIT_INTEROP_MAKE_OPTS >&2 &&
touch .built
) || return 1
@@ -76,9 +76,11 @@ generate_wrappers () {
VERSION_A=${GIT_TEST_VERSION_A:-$VERSION_A}
VERSION_B=${GIT_TEST_VERSION_B:-$VERSION_B}
+MAKE_OPTS_A=${GIT_INTEROP_MAKE_OPTS_A:-$MAKE_OPTS_A}
+MAKE_OPTS_B=${GIT_INTEROP_MAKE_OPTS_B:-$MAKE_OPTS_B}
-if ! DIR_A=$(build_version "$VERSION_A") ||
- ! DIR_B=$(build_version "$VERSION_B")
+if ! DIR_A=$(build_version "$VERSION_A" "$MAKE_OPTS_A") ||
+ ! DIR_B=$(build_version "$VERSION_B" "$MAKE_OPTS_B")
then
echo >&2 "fatal: unable to build git versions"
exit 1
diff --git a/t/lib-bitmap.sh b/t/lib-bitmap.sh
index f595937094..62aa6744a6 100644
--- a/t/lib-bitmap.sh
+++ b/t/lib-bitmap.sh
@@ -1,6 +1,8 @@
# Helpers for scripts testing bitmap functionality; see t5310 for
# example usage.
+. "$TEST_DIRECTORY"/lib-midx.sh
+
objdir=.git/objects
midx=$objdir/pack/multi-pack-index
@@ -264,10 +266,6 @@ have_delta () {
test_cmp expect actual
}
-midx_checksum () {
- test-tool read-midx --checksum "$1"
-}
-
# midx_pack_source <obj>
midx_pack_source () {
test-tool read-midx --show-objects .git/objects | grep "^$1 " | cut -f2
diff --git a/t/lib-midx.sh b/t/lib-midx.sh
index 1261994744..e38c609604 100644
--- a/t/lib-midx.sh
+++ b/t/lib-midx.sh
@@ -6,3 +6,31 @@ test_midx_consistent () {
test_cmp expect actual &&
git multi-pack-index --object-dir=$1 verify
}
+
+midx_checksum () {
+ test-tool read-midx --checksum "$1"
+}
+
+midx_git_two_modes () {
+ git -c core.multiPackIndex=false $1 >expect &&
+ git -c core.multiPackIndex=true $1 >actual &&
+ if [ "$2" = "sorted" ]
+ then
+ sort <expect >expect.sorted &&
+ mv expect.sorted expect &&
+ sort <actual >actual.sorted &&
+ mv actual.sorted actual
+ fi &&
+ test_cmp expect actual
+}
+
+compare_results_with_midx () {
+ MSG=$1
+ test_expect_success "check normal git operations: $MSG" '
+ midx_git_two_modes "rev-list --objects --all" &&
+ midx_git_two_modes "log --raw" &&
+ midx_git_two_modes "count-objects --verbose" &&
+ midx_git_two_modes "cat-file --batch-all-objects --batch-check" &&
+ midx_git_two_modes "cat-file --batch-all-objects --batch-check --unordered" sorted
+ '
+}
diff --git a/t/perf/p1500-graph-walks.sh b/t/perf/p1500-graph-walks.sh
index e14e7620cc..5b23ce5db9 100755
--- a/t/perf/p1500-graph-walks.sh
+++ b/t/perf/p1500-graph-walks.sh
@@ -20,6 +20,21 @@ test_expect_success 'setup' '
echo tag-$ref ||
return 1
done >tags &&
+
+ echo "A:HEAD" >test-tool-refs &&
+ for line in $(cat refs)
+ do
+ echo "X:$line" >>test-tool-refs || return 1
+ done &&
+ echo "A:HEAD" >test-tool-tags &&
+ for line in $(cat tags)
+ do
+ echo "X:$line" >>test-tool-tags || return 1
+ done &&
+
+ commit=$(git commit-tree $(git rev-parse HEAD^{tree})) &&
+ git update-ref refs/heads/disjoint-base $commit &&
+
git commit-graph write --reachable
'
@@ -47,4 +62,20 @@ test_perf 'contains: git tag --merged' '
xargs git tag --merged=HEAD <tags
'
+test_perf 'is-base check: test-tool reach (refs)' '
+ test-tool reach get_branch_base_for_tip <test-tool-refs
+'
+
+test_perf 'is-base check: test-tool reach (tags)' '
+ test-tool reach get_branch_base_for_tip <test-tool-tags
+'
+
+test_perf 'is-base check: git for-each-ref' '
+ git for-each-ref --format="%(is-base:HEAD)" --stdin <refs
+'
+
+test_perf 'is-base check: git for-each-ref (disjoint-base)' '
+ git for-each-ref --format="%(is-base:refs/heads/disjoint-base)" --stdin <refs
+'
+
test_done
diff --git a/t/run-test.sh b/t/run-test.sh
index 13c353b91b..63328ac630 100755
--- a/t/run-test.sh
+++ b/t/run-test.sh
@@ -10,7 +10,7 @@ case "$1" in
echo >&2 "ERROR: TEST_SHELL_PATH is empty or not set"
exit 1
fi
- exec "${TEST_SHELL_PATH}" "$@"
+ exec "${TEST_SHELL_PATH}" "$@" ${TEST_OPTIONS}
;;
*)
exec "$@"
diff --git a/t/socks4-proxy.pl b/t/socks4-proxy.pl
new file mode 100644
index 0000000000..4c3a35c008
--- /dev/null
+++ b/t/socks4-proxy.pl
@@ -0,0 +1,48 @@
+use strict;
+use IO::Select;
+use IO::Socket::UNIX;
+use IO::Socket::INET;
+
+my $path = shift;
+
+unlink($path);
+my $server = IO::Socket::UNIX->new(Listen => 1, Local => $path)
+ or die "unable to listen on $path: $!";
+
+$| = 1;
+print "ready\n";
+
+while (my $client = $server->accept()) {
+ sysread $client, my $buf, 8;
+ my ($version, $cmd, $port, $ip) = unpack 'CCnN', $buf;
+ next unless $version == 4; # socks4
+ next unless $cmd == 1; # TCP stream connection
+
+ # skip NUL-terminated id
+ while (sysread $client, my $char, 1) {
+ last unless ord($char);
+ }
+
+ # version(0), reply(5a == granted), port (ignored), ip (ignored)
+ syswrite $client, "\x00\x5a\x00\x00\x00\x00\x00\x00";
+
+ my $remote = IO::Socket::INET->new(PeerHost => $ip, PeerPort => $port)
+ or die "unable to connect to $ip/$port: $!";
+
+ my $io = IO::Select->new($client, $remote);
+ while ($io->count) {
+ for my $fh ($io->can_read(0)) {
+ for my $pair ([$client, $remote], [$remote, $client]) {
+ my ($from, $to) = @$pair;
+ next unless $fh == $from;
+
+ my $r = sysread $from, my $buf, 1024;
+ if (!defined $r || $r <= 0) {
+ $io->remove($from);
+ next;
+ }
+ syswrite $to, $buf;
+ }
+ }
+ }
+}
diff --git a/t/t0001-init.sh b/t/t0001-init.sh
index 49e9bf77c6..0178aa62a4 100755
--- a/t/t0001-init.sh
+++ b/t/t0001-init.sh
@@ -500,6 +500,7 @@ test_expect_success 're-init from a linked worktree' '
'
test_expect_success 'init honors GIT_DEFAULT_HASH' '
+ test_when_finished "rm -rf sha1 sha256" &&
GIT_DEFAULT_HASH=sha1 git init sha1 &&
git -C sha1 rev-parse --show-object-format >actual &&
echo sha1 >expected &&
@@ -511,6 +512,7 @@ test_expect_success 'init honors GIT_DEFAULT_HASH' '
'
test_expect_success 'init honors --object-format' '
+ test_when_finished "rm -rf explicit-sha1 explicit-sha256" &&
git init --object-format=sha1 explicit-sha1 &&
git -C explicit-sha1 rev-parse --show-object-format >actual &&
echo sha1 >expected &&
@@ -521,7 +523,58 @@ test_expect_success 'init honors --object-format' '
test_cmp expected actual
'
+test_expect_success 'init honors init.defaultObjectFormat' '
+ test_when_finished "rm -rf sha1 sha256" &&
+
+ test_config_global init.defaultObjectFormat sha1 &&
+ (
+ sane_unset GIT_DEFAULT_HASH &&
+ git init sha1 &&
+ git -C sha1 rev-parse --show-object-format >actual &&
+ echo sha1 >expected &&
+ test_cmp expected actual
+ ) &&
+
+ test_config_global init.defaultObjectFormat sha256 &&
+ (
+ sane_unset GIT_DEFAULT_HASH &&
+ git init sha256 &&
+ git -C sha256 rev-parse --show-object-format >actual &&
+ echo sha256 >expected &&
+ test_cmp expected actual
+ )
+'
+
+test_expect_success 'init warns about invalid init.defaultObjectFormat' '
+ test_when_finished "rm -rf repo" &&
+ test_config_global init.defaultObjectFormat garbage &&
+
+ echo "warning: unknown hash algorithm ${SQ}garbage${SQ}" >expect &&
+ git init repo 2>err &&
+ test_cmp expect err &&
+
+ git -C repo rev-parse --show-object-format >actual &&
+ echo $GIT_DEFAULT_HASH >expected &&
+ test_cmp expected actual
+'
+
+test_expect_success '--object-format overrides GIT_DEFAULT_HASH' '
+ test_when_finished "rm -rf repo" &&
+ GIT_DEFAULT_HASH=sha1 git init --object-format=sha256 repo &&
+ git -C repo rev-parse --show-object-format >actual &&
+ echo sha256 >expected
+'
+
+test_expect_success 'GIT_DEFAULT_HASH overrides init.defaultObjectFormat' '
+ test_when_finished "rm -rf repo" &&
+ test_config_global init.defaultObjectFormat sha1 &&
+ GIT_DEFAULT_HASH=sha256 git init repo &&
+ git -C repo rev-parse --show-object-format >actual &&
+ echo sha256 >expected
+'
+
test_expect_success 'extensions.objectFormat is not allowed with repo version 0' '
+ test_when_finished "rm -rf explicit-v0" &&
git init --object-format=sha256 explicit-v0 &&
git -C explicit-v0 config core.repositoryformatversion 0 &&
test_must_fail git -C explicit-v0 rev-parse --show-object-format
@@ -558,15 +611,6 @@ test_expect_success DEFAULT_REPO_FORMAT 'extensions.refStorage with unknown back
grep "invalid value for ${SQ}extensions.refstorage${SQ}: ${SQ}garbage${SQ}" err
'
-test_expect_success DEFAULT_REPO_FORMAT 'init with GIT_DEFAULT_REF_FORMAT=files' '
- test_when_finished "rm -rf refformat" &&
- GIT_DEFAULT_REF_FORMAT=files git init refformat &&
- echo 0 >expect &&
- git -C refformat config core.repositoryformatversion >actual &&
- test_cmp expect actual &&
- test_must_fail git -C refformat config extensions.refstorage
-'
-
test_expect_success 'init with GIT_DEFAULT_REF_FORMAT=garbage' '
test_when_finished "rm -rf refformat" &&
cat >expect <<-EOF &&
@@ -576,15 +620,90 @@ test_expect_success 'init with GIT_DEFAULT_REF_FORMAT=garbage' '
test_cmp expect err
'
-test_expect_success 'init with --ref-format=files' '
+test_expect_success 'init warns about invalid init.defaultRefFormat' '
+ test_when_finished "rm -rf repo" &&
+ test_config_global init.defaultRefFormat garbage &&
+
+ echo "warning: unknown ref storage format ${SQ}garbage${SQ}" >expect &&
+ git init repo 2>err &&
+ test_cmp expect err &&
+
+ git -C repo rev-parse --show-ref-format >actual &&
+ echo $GIT_DEFAULT_REF_FORMAT >expected &&
+ test_cmp expected actual
+'
+
+backends="files reftable"
+for format in $backends
+do
+ test_expect_success DEFAULT_REPO_FORMAT "init with GIT_DEFAULT_REF_FORMAT=$format" '
+ test_when_finished "rm -rf refformat" &&
+ GIT_DEFAULT_REF_FORMAT=$format git init refformat &&
+
+ if test $format = files
+ then
+ test_must_fail git -C refformat config extensions.refstorage &&
+ echo 0 >expect
+ else
+ git -C refformat config extensions.refstorage &&
+ echo 1 >expect
+ fi &&
+ git -C refformat config core.repositoryformatversion >actual &&
+ test_cmp expect actual &&
+
+ echo $format >expect &&
+ git -C refformat rev-parse --show-ref-format >actual &&
+ test_cmp expect actual
+ '
+
+ test_expect_success "init with --ref-format=$format" '
+ test_when_finished "rm -rf refformat" &&
+ git init --ref-format=$format refformat &&
+ echo $format >expect &&
+ git -C refformat rev-parse --show-ref-format >actual &&
+ test_cmp expect actual
+ '
+
+ test_expect_success "init with init.defaultRefFormat=$format" '
+ test_when_finished "rm -rf refformat" &&
+ test_config_global init.defaultRefFormat $format &&
+ (
+ sane_unset GIT_DEFAULT_REF_FORMAT &&
+ git init refformat
+ ) &&
+
+ echo $format >expect &&
+ git -C refformat rev-parse --show-ref-format >actual &&
+ test_cmp expect actual
+ '
+
+ test_expect_success "--ref-format=$format overrides GIT_DEFAULT_REF_FORMAT" '
+ test_when_finished "rm -rf refformat" &&
+ GIT_DEFAULT_REF_FORMAT=garbage git init --ref-format=$format refformat &&
+ echo $format >expect &&
+ git -C refformat rev-parse --show-ref-format >actual &&
+ test_cmp expect actual
+ '
+done
+
+test_expect_success "--ref-format= overrides GIT_DEFAULT_REF_FORMAT" '
test_when_finished "rm -rf refformat" &&
- git init --ref-format=files refformat &&
- echo files >expect &&
+ GIT_DEFAULT_REF_FORMAT=files git init --ref-format=reftable refformat &&
+ echo reftable >expect &&
+ git -C refformat rev-parse --show-ref-format >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success "GIT_DEFAULT_REF_FORMAT= overrides init.defaultRefFormat" '
+ test_when_finished "rm -rf refformat" &&
+ test_config_global init.defaultRefFormat files &&
+
+ GIT_DEFAULT_REF_FORMAT=reftable git init refformat &&
+ echo reftable >expect &&
git -C refformat rev-parse --show-ref-format >actual &&
test_cmp expect actual
'
-backends="files reftable"
for from_format in $backends
do
test_expect_success "re-init with same format ($from_format)" '
diff --git a/t/t0011-hashmap.sh b/t/t0011-hashmap.sh
deleted file mode 100755
index 46e74ad107..0000000000
--- a/t/t0011-hashmap.sh
+++ /dev/null
@@ -1,260 +0,0 @@
-#!/bin/sh
-
-test_description='test hashmap and string hash functions'
-
-TEST_PASSES_SANITIZE_LEAK=true
-. ./test-lib.sh
-
-test_hashmap() {
- echo "$1" | test-tool hashmap $3 > actual &&
- echo "$2" > expect &&
- test_cmp expect actual
-}
-
-test_expect_success 'put' '
-
-test_hashmap "put key1 value1
-put key2 value2
-put fooBarFrotz value3
-put foobarfrotz value4
-size" "NULL
-NULL
-NULL
-NULL
-64 4"
-
-'
-
-test_expect_success 'put (case insensitive)' '
-
-test_hashmap "put key1 value1
-put key2 value2
-put fooBarFrotz value3
-size" "NULL
-NULL
-NULL
-64 3" ignorecase
-
-'
-
-test_expect_success 'replace' '
-
-test_hashmap "put key1 value1
-put key1 value2
-put fooBarFrotz value3
-put fooBarFrotz value4
-size" "NULL
-value1
-NULL
-value3
-64 2"
-
-'
-
-test_expect_success 'replace (case insensitive)' '
-
-test_hashmap "put key1 value1
-put Key1 value2
-put fooBarFrotz value3
-put foobarfrotz value4
-size" "NULL
-value1
-NULL
-value3
-64 2" ignorecase
-
-'
-
-test_expect_success 'get' '
-
-test_hashmap "put key1 value1
-put key2 value2
-put fooBarFrotz value3
-put foobarfrotz value4
-get key1
-get key2
-get fooBarFrotz
-get notInMap" "NULL
-NULL
-NULL
-NULL
-value1
-value2
-value3
-NULL"
-
-'
-
-test_expect_success 'get (case insensitive)' '
-
-test_hashmap "put key1 value1
-put key2 value2
-put fooBarFrotz value3
-get Key1
-get keY2
-get foobarfrotz
-get notInMap" "NULL
-NULL
-NULL
-value1
-value2
-value3
-NULL" ignorecase
-
-'
-
-test_expect_success 'add' '
-
-test_hashmap "add key1 value1
-add key1 value2
-add fooBarFrotz value3
-add fooBarFrotz value4
-get key1
-get fooBarFrotz
-get notInMap" "value2
-value1
-value4
-value3
-NULL"
-
-'
-
-test_expect_success 'add (case insensitive)' '
-
-test_hashmap "add key1 value1
-add Key1 value2
-add fooBarFrotz value3
-add foobarfrotz value4
-get key1
-get Foobarfrotz
-get notInMap" "value2
-value1
-value4
-value3
-NULL" ignorecase
-
-'
-
-test_expect_success 'remove' '
-
-test_hashmap "put key1 value1
-put key2 value2
-put fooBarFrotz value3
-remove key1
-remove key2
-remove notInMap
-size" "NULL
-NULL
-NULL
-value1
-value2
-NULL
-64 1"
-
-'
-
-test_expect_success 'remove (case insensitive)' '
-
-test_hashmap "put key1 value1
-put key2 value2
-put fooBarFrotz value3
-remove Key1
-remove keY2
-remove notInMap
-size" "NULL
-NULL
-NULL
-value1
-value2
-NULL
-64 1" ignorecase
-
-'
-
-test_expect_success 'iterate' '
- test-tool hashmap >actual.raw <<-\EOF &&
- put key1 value1
- put key2 value2
- put fooBarFrotz value3
- iterate
- EOF
-
- cat >expect <<-\EOF &&
- NULL
- NULL
- NULL
- fooBarFrotz value3
- key1 value1
- key2 value2
- EOF
-
- sort <actual.raw >actual &&
- test_cmp expect actual
-'
-
-test_expect_success 'iterate (case insensitive)' '
- test-tool hashmap ignorecase >actual.raw <<-\EOF &&
- put key1 value1
- put key2 value2
- put fooBarFrotz value3
- iterate
- EOF
-
- cat >expect <<-\EOF &&
- NULL
- NULL
- NULL
- fooBarFrotz value3
- key1 value1
- key2 value2
- EOF
-
- sort <actual.raw >actual &&
- test_cmp expect actual
-'
-
-test_expect_success 'grow / shrink' '
-
- rm -f in &&
- rm -f expect &&
- for n in $(test_seq 51)
- do
- echo put key$n value$n >> in &&
- echo NULL >> expect || return 1
- done &&
- echo size >> in &&
- echo 64 51 >> expect &&
- echo put key52 value52 >> in &&
- echo NULL >> expect &&
- echo size >> in &&
- echo 256 52 >> expect &&
- for n in $(test_seq 12)
- do
- echo remove key$n >> in &&
- echo value$n >> expect || return 1
- done &&
- echo size >> in &&
- echo 256 40 >> expect &&
- echo remove key40 >> in &&
- echo value40 >> expect &&
- echo size >> in &&
- echo 64 39 >> expect &&
- test-tool hashmap <in >out &&
- test_cmp expect out
-
-'
-
-test_expect_success 'string interning' '
-
-test_hashmap "intern value1
-intern Value1
-intern value2
-intern value2
-" "value1
-Value1
-value2
-value2"
-
-'
-
-test_done
diff --git a/t/t0012-help.sh b/t/t0012-help.sh
index 1d273d91c2..9eae0d8356 100755
--- a/t/t0012-help.sh
+++ b/t/t0012-help.sh
@@ -2,6 +2,7 @@
test_description='help'
+TEST_PASSES_SANITIZE_LEAK=true
. ./test-lib.sh
configure_help () {
diff --git a/t/t0021-conversion.sh b/t/t0021-conversion.sh
index 0b4997022b..eeb2714d9d 100755
--- a/t/t0021-conversion.sh
+++ b/t/t0021-conversion.sh
@@ -5,6 +5,7 @@ test_description='blob conversion via gitattributes'
GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
+TEST_PASSES_SANITIZE_LEAK=true
. ./test-lib.sh
. "$TEST_DIRECTORY"/lib-terminal.sh
diff --git a/t/t0032-reftable-unittest.sh b/t/t0032-reftable-unittest.sh
deleted file mode 100755
index 471cb37ac2..0000000000
--- a/t/t0032-reftable-unittest.sh
+++ /dev/null
@@ -1,16 +0,0 @@
-#!/bin/sh
-#
-# Copyright (c) 2020 Google LLC
-#
-
-test_description='reftable unittests'
-
-TEST_PASSES_SANITIZE_LEAK=true
-. ./test-lib.sh
-
-test_expect_success 'unittests' '
- TMPDIR=$(pwd) && export TMPDIR &&
- test-tool reftable
-'
-
-test_done
diff --git a/t/t0064-oid-array.sh b/t/t0064-oid-array.sh
deleted file mode 100755
index de74b692d0..0000000000
--- a/t/t0064-oid-array.sh
+++ /dev/null
@@ -1,122 +0,0 @@
-#!/bin/sh
-
-test_description='basic tests for the oid array implementation'
-
-TEST_PASSES_SANITIZE_LEAK=true
-. ./test-lib.sh
-
-echoid () {
- prefix="${1:+$1 }"
- shift
- while test $# -gt 0
- do
- echo "$prefix$ZERO_OID" | sed -e "s/00/$1/g"
- shift
- done
-}
-
-test_expect_success 'without repository' '
- cat >expect <<-EOF &&
- 4444444444444444444444444444444444444444
- 5555555555555555555555555555555555555555
- 8888888888888888888888888888888888888888
- aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
- EOF
- cat >input <<-EOF &&
- append 4444444444444444444444444444444444444444
- append 5555555555555555555555555555555555555555
- append 8888888888888888888888888888888888888888
- append aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
- for_each_unique
- EOF
- nongit test-tool oid-array <input >actual &&
- test_cmp expect actual
-'
-
-test_expect_success 'ordered enumeration' '
- echoid "" 44 55 88 aa >expect &&
- {
- echoid append 88 44 aa 55 &&
- echo for_each_unique
- } | test-tool oid-array >actual &&
- test_cmp expect actual
-'
-
-test_expect_success 'ordered enumeration with duplicate suppression' '
- echoid "" 44 55 88 aa >expect &&
- {
- echoid append 88 44 aa 55 &&
- echoid append 88 44 aa 55 &&
- echoid append 88 44 aa 55 &&
- echo for_each_unique
- } | test-tool oid-array >actual &&
- test_cmp expect actual
-'
-
-test_expect_success 'lookup' '
- {
- echoid append 88 44 aa 55 &&
- echoid lookup 55
- } | test-tool oid-array >actual &&
- n=$(cat actual) &&
- test "$n" -eq 1
-'
-
-test_expect_success 'lookup non-existing entry' '
- {
- echoid append 88 44 aa 55 &&
- echoid lookup 33
- } | test-tool oid-array >actual &&
- n=$(cat actual) &&
- test "$n" -lt 0
-'
-
-test_expect_success 'lookup with duplicates' '
- {
- echoid append 88 44 aa 55 &&
- echoid append 88 44 aa 55 &&
- echoid append 88 44 aa 55 &&
- echoid lookup 55
- } | test-tool oid-array >actual &&
- n=$(cat actual) &&
- test "$n" -ge 3 &&
- test "$n" -le 5
-'
-
-test_expect_success 'lookup non-existing entry with duplicates' '
- {
- echoid append 88 44 aa 55 &&
- echoid append 88 44 aa 55 &&
- echoid append 88 44 aa 55 &&
- echoid lookup 66
- } | test-tool oid-array >actual &&
- n=$(cat actual) &&
- test "$n" -lt 0
-'
-
-test_expect_success 'lookup with almost duplicate values' '
- # n-1 5s
- root=$(echoid "" 55) &&
- root=${root%5} &&
- {
- id1="${root}5" &&
- id2="${root}f" &&
- echo "append $id1" &&
- echo "append $id2" &&
- echoid lookup 55
- } | test-tool oid-array >actual &&
- n=$(cat actual) &&
- test "$n" -eq 0
-'
-
-test_expect_success 'lookup with single duplicate value' '
- {
- echoid append 55 55 &&
- echoid lookup 55
- } | test-tool oid-array >actual &&
- n=$(cat actual) &&
- test "$n" -ge 0 &&
- test "$n" -le 1
-'
-
-test_done
diff --git a/t/t0080-unit-test-output.sh b/t/t0080-unit-test-output.sh
index 7bbb065d58..3c369c88e2 100755
--- a/t/t0080-unit-test-output.sh
+++ b/t/t0080-unit-test-output.sh
@@ -5,23 +5,24 @@ test_description='Test the output of the unit test framework'
TEST_PASSES_SANITIZE_LEAK=true
. ./test-lib.sh
-test_expect_success 'TAP output from unit tests' '
+test_expect_success 'TAP output from unit tests' - <<\EOT
cat >expect <<-EOF &&
+ # BUG: check outside of test at t/helper/test-example-tap.c:75
ok 1 - passing test
ok 2 - passing test and assertion return 1
- # check "1 == 2" failed at t/helper/test-example-tap.c:77
+ # check "1 == 2" failed at t/helper/test-example-tap.c:79
# left: 1
# right: 2
not ok 3 - failing test
ok 4 - failing test and assertion return 0
not ok 5 - passing TEST_TODO() # TODO
ok 6 - passing TEST_TODO() returns 1
- # todo check ${SQ}check(x)${SQ} succeeded at t/helper/test-example-tap.c:26
+ # todo check 'check(x)' succeeded at t/helper/test-example-tap.c:26
not ok 7 - failing TEST_TODO()
ok 8 - failing TEST_TODO() returns 0
# check "0" failed at t/helper/test-example-tap.c:31
# skipping test - missing prerequisite
- # skipping check ${SQ}1${SQ} at t/helper/test-example-tap.c:33
+ # skipping check '1' at t/helper/test-example-tap.c:33
ok 9 - test_skip() # SKIP
ok 10 - skipped test returns 1
# skipping test - missing prerequisite
@@ -39,21 +40,54 @@ test_expect_success 'TAP output from unit tests' '
# check "!strcmp("NULL", NULL)" failed at t/helper/test-example-tap.c:63
# left: "NULL"
# right: NULL
- # check "${SQ}a${SQ} == ${SQ}\n${SQ}" failed at t/helper/test-example-tap.c:64
- # left: ${SQ}a${SQ}
- # right: ${SQ}\012${SQ}
- # check "${SQ}\\\\${SQ} == ${SQ}\\${SQ}${SQ}" failed at t/helper/test-example-tap.c:65
- # left: ${SQ}\\\\${SQ}
- # right: ${SQ}\\${SQ}${SQ}
+ # check "'a' == '\n'" failed at t/helper/test-example-tap.c:64
+ # left: 'a'
+ # right: '\012'
+ # check "'\\\\' == '\\''" failed at t/helper/test-example-tap.c:65
+ # left: '\\\\'
+ # right: '\\''
not ok 17 - messages from failing string and char comparison
- # BUG: test has no checks at t/helper/test-example-tap.c:92
+ # BUG: test has no checks at t/helper/test-example-tap.c:94
not ok 18 - test with no checks
ok 19 - test with no checks returns 0
- 1..19
+ ok 20 - if_test passing test
+ # check "1 == 2" failed at t/helper/test-example-tap.c:100
+ # left: 1
+ # right: 2
+ not ok 21 - if_test failing test
+ not ok 22 - if_test passing TEST_TODO() # TODO
+ # todo check 'check(1)' succeeded at t/helper/test-example-tap.c:104
+ not ok 23 - if_test failing TEST_TODO()
+ # check "0" failed at t/helper/test-example-tap.c:106
+ # skipping test - missing prerequisite
+ # skipping check '1' at t/helper/test-example-tap.c:108
+ ok 24 - if_test test_skip() # SKIP
+ # skipping test - missing prerequisite
+ ok 25 - if_test test_skip() inside TEST_TODO() # SKIP
+ # check "0" failed at t/helper/test-example-tap.c:113
+ not ok 26 - if_test TEST_TODO() after failing check
+ # check "0" failed at t/helper/test-example-tap.c:119
+ not ok 27 - if_test failing check after TEST_TODO()
+ # check "!strcmp("\thello\\\\", "there\"\n")" failed at t/helper/test-example-tap.c:122
+ # left: "\011hello\\\\"
+ # right: "there\"\012"
+ # check "!strcmp("NULL", NULL)" failed at t/helper/test-example-tap.c:123
+ # left: "NULL"
+ # right: NULL
+ # check "'a' == '\n'" failed at t/helper/test-example-tap.c:124
+ # left: 'a'
+ # right: '\012'
+ # check "'\\\\' == '\\''" failed at t/helper/test-example-tap.c:125
+ # left: '\\\\'
+ # right: '\\''
+ not ok 28 - if_test messages from failing string and char comparison
+ # BUG: test has no checks at t/helper/test-example-tap.c:127
+ not ok 29 - if_test test with no checks
+ 1..29
EOF
! test-tool example-tap >actual &&
test_cmp expect actual
-'
+EOT
test_done
diff --git a/t/t0110-urlmatch-normalization.sh b/t/t0110-urlmatch-normalization.sh
deleted file mode 100755
index 12d817fbd3..0000000000
--- a/t/t0110-urlmatch-normalization.sh
+++ /dev/null
@@ -1,182 +0,0 @@
-#!/bin/sh
-
-test_description='urlmatch URL normalization'
-
-TEST_PASSES_SANITIZE_LEAK=true
-. ./test-lib.sh
-
-# The base name of the test url files
-tu="$TEST_DIRECTORY/t0110/url"
-
-# Note that only file: URLs should be allowed without a host
-
-test_expect_success 'url scheme' '
- ! test-tool urlmatch-normalization "" &&
- ! test-tool urlmatch-normalization "_" &&
- ! test-tool urlmatch-normalization "scheme" &&
- ! test-tool urlmatch-normalization "scheme:" &&
- ! test-tool urlmatch-normalization "scheme:/" &&
- ! test-tool urlmatch-normalization "scheme://" &&
- ! test-tool urlmatch-normalization "file" &&
- ! test-tool urlmatch-normalization "file:" &&
- ! test-tool urlmatch-normalization "file:/" &&
- test-tool urlmatch-normalization "file://" &&
- ! test-tool urlmatch-normalization "://acme.co" &&
- ! test-tool urlmatch-normalization "x_test://acme.co" &&
- ! test-tool urlmatch-normalization "-test://acme.co" &&
- ! test-tool urlmatch-normalization "0test://acme.co" &&
- ! test-tool urlmatch-normalization "+test://acme.co" &&
- ! test-tool urlmatch-normalization ".test://acme.co" &&
- ! test-tool urlmatch-normalization "schem%6e://" &&
- test-tool urlmatch-normalization "x-Test+v1.0://acme.co" &&
- test "$(test-tool urlmatch-normalization -p "AbCdeF://x.Y")" = "abcdef://x.y/"
-'
-
-test_expect_success 'url authority' '
- ! test-tool urlmatch-normalization "scheme://user:pass@" &&
- ! test-tool urlmatch-normalization "scheme://?" &&
- ! test-tool urlmatch-normalization "scheme://#" &&
- ! test-tool urlmatch-normalization "scheme:///" &&
- ! test-tool urlmatch-normalization "scheme://:" &&
- ! test-tool urlmatch-normalization "scheme://:555" &&
- test-tool urlmatch-normalization "file://user:pass@" &&
- test-tool urlmatch-normalization "file://?" &&
- test-tool urlmatch-normalization "file://#" &&
- test-tool urlmatch-normalization "file:///" &&
- test-tool urlmatch-normalization "file://:" &&
- ! test-tool urlmatch-normalization "file://:555" &&
- test-tool urlmatch-normalization "scheme://user:pass@host" &&
- test-tool urlmatch-normalization "scheme://@host" &&
- test-tool urlmatch-normalization "scheme://%00@host" &&
- ! test-tool urlmatch-normalization "scheme://%%@host" &&
- test-tool urlmatch-normalization "scheme://host_" &&
- test-tool urlmatch-normalization "scheme://user:pass@host/" &&
- test-tool urlmatch-normalization "scheme://@host/" &&
- test-tool urlmatch-normalization "scheme://host/" &&
- test-tool urlmatch-normalization "scheme://host?x" &&
- test-tool urlmatch-normalization "scheme://host#x" &&
- test-tool urlmatch-normalization "scheme://host/@" &&
- test-tool urlmatch-normalization "scheme://host?@x" &&
- test-tool urlmatch-normalization "scheme://host#@x" &&
- test-tool urlmatch-normalization "scheme://[::1]" &&
- test-tool urlmatch-normalization "scheme://[::1]/" &&
- ! test-tool urlmatch-normalization "scheme://hos%41/" &&
- test-tool urlmatch-normalization "scheme://[invalid....:/" &&
- test-tool urlmatch-normalization "scheme://invalid....:]/" &&
- ! test-tool urlmatch-normalization "scheme://invalid....:[/" &&
- ! test-tool urlmatch-normalization "scheme://invalid....:["
-'
-
-test_expect_success 'url port checks' '
- test-tool urlmatch-normalization "xyz://q@some.host:" &&
- test-tool urlmatch-normalization "xyz://q@some.host:456/" &&
- ! test-tool urlmatch-normalization "xyz://q@some.host:0" &&
- ! test-tool urlmatch-normalization "xyz://q@some.host:0000000" &&
- test-tool urlmatch-normalization "xyz://q@some.host:0000001?" &&
- test-tool urlmatch-normalization "xyz://q@some.host:065535#" &&
- test-tool urlmatch-normalization "xyz://q@some.host:65535" &&
- ! test-tool urlmatch-normalization "xyz://q@some.host:65536" &&
- ! test-tool urlmatch-normalization "xyz://q@some.host:99999" &&
- ! test-tool urlmatch-normalization "xyz://q@some.host:100000" &&
- ! test-tool urlmatch-normalization "xyz://q@some.host:100001" &&
- test-tool urlmatch-normalization "http://q@some.host:80" &&
- test-tool urlmatch-normalization "https://q@some.host:443" &&
- test-tool urlmatch-normalization "http://q@some.host:80/" &&
- test-tool urlmatch-normalization "https://q@some.host:443?" &&
- ! test-tool urlmatch-normalization "http://q@:8008" &&
- ! test-tool urlmatch-normalization "http://:8080" &&
- ! test-tool urlmatch-normalization "http://:" &&
- test-tool urlmatch-normalization "xyz://q@some.host:456/" &&
- test-tool urlmatch-normalization "xyz://[::1]:456/" &&
- test-tool urlmatch-normalization "xyz://[::1]:/" &&
- ! test-tool urlmatch-normalization "xyz://[::1]:000/" &&
- ! test-tool urlmatch-normalization "xyz://[::1]:0%300/" &&
- ! test-tool urlmatch-normalization "xyz://[::1]:0x80/" &&
- ! test-tool urlmatch-normalization "xyz://[::1]:4294967297/" &&
- ! test-tool urlmatch-normalization "xyz://[::1]:030f/"
-'
-
-test_expect_success 'url port normalization' '
- test "$(test-tool urlmatch-normalization -p "http://x:800")" = "http://x:800/" &&
- test "$(test-tool urlmatch-normalization -p "http://x:0800")" = "http://x:800/" &&
- test "$(test-tool urlmatch-normalization -p "http://x:00000800")" = "http://x:800/" &&
- test "$(test-tool urlmatch-normalization -p "http://x:065535")" = "http://x:65535/" &&
- test "$(test-tool urlmatch-normalization -p "http://x:1")" = "http://x:1/" &&
- test "$(test-tool urlmatch-normalization -p "http://x:80")" = "http://x/" &&
- test "$(test-tool urlmatch-normalization -p "http://x:080")" = "http://x/" &&
- test "$(test-tool urlmatch-normalization -p "http://x:000000080")" = "http://x/" &&
- test "$(test-tool urlmatch-normalization -p "https://x:443")" = "https://x/" &&
- test "$(test-tool urlmatch-normalization -p "https://x:0443")" = "https://x/" &&
- test "$(test-tool urlmatch-normalization -p "https://x:000000443")" = "https://x/"
-'
-
-test_expect_success 'url general escapes' '
- ! test-tool urlmatch-normalization "http://x.y?%fg" &&
- test "$(test-tool urlmatch-normalization -p "X://W/%7e%41^%3a")" = "x://w/~A%5E%3A" &&
- test "$(test-tool urlmatch-normalization -p "X://W/:/?#[]@")" = "x://w/:/?#[]@" &&
- test "$(test-tool urlmatch-normalization -p "X://W/$&()*+,;=")" = "x://w/$&()*+,;=" &&
- test "$(test-tool urlmatch-normalization -p "X://W/'\''")" = "x://w/'\''" &&
- test "$(test-tool urlmatch-normalization -p "X://W?'\!'")" = "x://w/?'\!'"
-'
-
-test_expect_success !MINGW 'url high-bit escapes' '
- test "$(test-tool urlmatch-normalization -p "$(cat "$tu-1")")" = "x://q/%01%02%03%04%05%06%07%08%0E%0F%10%11%12" &&
- test "$(test-tool urlmatch-normalization -p "$(cat "$tu-2")")" = "x://q/%13%14%15%16%17%18%19%1B%1C%1D%1E%1F%7F" &&
- test "$(test-tool urlmatch-normalization -p "$(cat "$tu-3")")" = "x://q/%80%81%82%83%84%85%86%87%88%89%8A%8B%8C%8D%8E%8F" &&
- test "$(test-tool urlmatch-normalization -p "$(cat "$tu-4")")" = "x://q/%90%91%92%93%94%95%96%97%98%99%9A%9B%9C%9D%9E%9F" &&
- test "$(test-tool urlmatch-normalization -p "$(cat "$tu-5")")" = "x://q/%A0%A1%A2%A3%A4%A5%A6%A7%A8%A9%AA%AB%AC%AD%AE%AF" &&
- test "$(test-tool urlmatch-normalization -p "$(cat "$tu-6")")" = "x://q/%B0%B1%B2%B3%B4%B5%B6%B7%B8%B9%BA%BB%BC%BD%BE%BF" &&
- test "$(test-tool urlmatch-normalization -p "$(cat "$tu-7")")" = "x://q/%C0%C1%C2%C3%C4%C5%C6%C7%C8%C9%CA%CB%CC%CD%CE%CF" &&
- test "$(test-tool urlmatch-normalization -p "$(cat "$tu-8")")" = "x://q/%D0%D1%D2%D3%D4%D5%D6%D7%D8%D9%DA%DB%DC%DD%DE%DF" &&
- test "$(test-tool urlmatch-normalization -p "$(cat "$tu-9")")" = "x://q/%E0%E1%E2%E3%E4%E5%E6%E7%E8%E9%EA%EB%EC%ED%EE%EF" &&
- test "$(test-tool urlmatch-normalization -p "$(cat "$tu-10")")" = "x://q/%F0%F1%F2%F3%F4%F5%F6%F7%F8%F9%FA%FB%FC%FD%FE%FF"
-'
-
-test_expect_success 'url utf-8 escapes' '
- test "$(test-tool urlmatch-normalization -p "$(cat "$tu-11")")" = "x://q/%C2%80%DF%BF%E0%A0%80%EF%BF%BD%F0%90%80%80%F0%AF%BF%BD"
-'
-
-test_expect_success 'url username/password escapes' '
- test "$(test-tool urlmatch-normalization -p "x://%41%62(^):%70+d@foo")" = "x://Ab(%5E):p+d@foo/"
-'
-
-test_expect_success 'url normalized lengths' '
- test "$(test-tool urlmatch-normalization -l "Http://%4d%65:%4d^%70@The.Host")" = 25 &&
- test "$(test-tool urlmatch-normalization -l "http://%41:%42@x.y/%61/")" = 17 &&
- test "$(test-tool urlmatch-normalization -l "http://@x.y/^")" = 15
-'
-
-test_expect_success 'url . and .. segments' '
- test "$(test-tool urlmatch-normalization -p "x://y/.")" = "x://y/" &&
- test "$(test-tool urlmatch-normalization -p "x://y/./")" = "x://y/" &&
- test "$(test-tool urlmatch-normalization -p "x://y/a/.")" = "x://y/a" &&
- test "$(test-tool urlmatch-normalization -p "x://y/a/./")" = "x://y/a/" &&
- test "$(test-tool urlmatch-normalization -p "x://y/.?")" = "x://y/?" &&
- test "$(test-tool urlmatch-normalization -p "x://y/./?")" = "x://y/?" &&
- test "$(test-tool urlmatch-normalization -p "x://y/a/.?")" = "x://y/a?" &&
- test "$(test-tool urlmatch-normalization -p "x://y/a/./?")" = "x://y/a/?" &&
- test "$(test-tool urlmatch-normalization -p "x://y/a/./b/.././../c")" = "x://y/c" &&
- test "$(test-tool urlmatch-normalization -p "x://y/a/./b/../.././c/")" = "x://y/c/" &&
- test "$(test-tool urlmatch-normalization -p "x://y/a/./b/.././../c/././.././.")" = "x://y/" &&
- ! test-tool urlmatch-normalization "x://y/a/./b/.././../c/././.././.." &&
- test "$(test-tool urlmatch-normalization -p "x://y/a/./?/././..")" = "x://y/a/?/././.." &&
- test "$(test-tool urlmatch-normalization -p "x://y/%2e/")" = "x://y/" &&
- test "$(test-tool urlmatch-normalization -p "x://y/%2E/")" = "x://y/" &&
- test "$(test-tool urlmatch-normalization -p "x://y/a/%2e./")" = "x://y/" &&
- test "$(test-tool urlmatch-normalization -p "x://y/b/.%2E/")" = "x://y/" &&
- test "$(test-tool urlmatch-normalization -p "x://y/c/%2e%2E/")" = "x://y/"
-'
-
-# http://@foo specifies an empty user name but does not specify a password
-# http://foo specifies neither a user name nor a password
-# So they should not be equivalent
-test_expect_success 'url equivalents' '
- test-tool urlmatch-normalization "httP://x" "Http://X/" &&
- test-tool urlmatch-normalization "Http://%4d%65:%4d^%70@The.Host" "hTTP://Me:%4D^p@the.HOST:80/" &&
- ! test-tool urlmatch-normalization "https://@x.y/^" "httpS://x.y:443/^" &&
- test-tool urlmatch-normalization "https://@x.y/^" "httpS://@x.y:0443/^" &&
- test-tool urlmatch-normalization "https://@x.y/^/../abc" "httpS://@x.y:0443/abc" &&
- test-tool urlmatch-normalization "https://@x.y/^/.." "httpS://@x.y:0443/"
-'
-
-test_done
diff --git a/t/t0110/README b/t/t0110/README
deleted file mode 100644
index ad4a50ecd8..0000000000
--- a/t/t0110/README
+++ /dev/null
@@ -1,9 +0,0 @@
-The url data files in this directory contain URLs with characters
-in the range 0x01-0x1f and 0x7f-0xff to test the proper normalization
-of unprintable characters.
-
-A select few characters in the 0x01-0x1f range are skipped to help
-avoid problems running the test itself.
-
-The urls are in test files in this directory rather than being
-embedded in the test script for portability.
diff --git a/t/t0110/url-1 b/t/t0110/url-1
deleted file mode 100644
index 519019c5ce..0000000000
--- a/t/t0110/url-1
+++ /dev/null
@@ -1 +0,0 @@
-x://q/
diff --git a/t/t0110/url-10 b/t/t0110/url-10
deleted file mode 100644
index b9965de6a5..0000000000
--- a/t/t0110/url-10
+++ /dev/null
@@ -1 +0,0 @@
-x://q/ðñòóôõö÷øùúûüýþÿ
diff --git a/t/t0110/url-11 b/t/t0110/url-11
deleted file mode 100644
index f0a50f1009..0000000000
--- a/t/t0110/url-11
+++ /dev/null
@@ -1 +0,0 @@
-x://q/€߿ࠀ�ð€€ð¯¿½
diff --git a/t/t0110/url-2 b/t/t0110/url-2
deleted file mode 100644
index 43334b05b2..0000000000
--- a/t/t0110/url-2
+++ /dev/null
@@ -1 +0,0 @@
-x://q/
diff --git a/t/t0110/url-3 b/t/t0110/url-3
deleted file mode 100644
index 7378c7bec2..0000000000
--- a/t/t0110/url-3
+++ /dev/null
@@ -1 +0,0 @@
-x://q/€‚ƒ„…†‡ˆ‰Š‹ŒŽ
diff --git a/t/t0110/url-4 b/t/t0110/url-4
deleted file mode 100644
index 220b198c97..0000000000
--- a/t/t0110/url-4
+++ /dev/null
@@ -1 +0,0 @@
-x://q/‘’“”•–—˜™š›œžŸ
diff --git a/t/t0110/url-5 b/t/t0110/url-5
deleted file mode 100644
index 1ccd927779..0000000000
--- a/t/t0110/url-5
+++ /dev/null
@@ -1 +0,0 @@
-x://q/ ¡¢£¤¥¦§¨©ª«¬­®¯
diff --git a/t/t0110/url-6 b/t/t0110/url-6
deleted file mode 100644
index e8283aac6d..0000000000
--- a/t/t0110/url-6
+++ /dev/null
@@ -1 +0,0 @@
-x://q/°±²³´µ¶·¸¹º»¼½¾¿
diff --git a/t/t0110/url-7 b/t/t0110/url-7
deleted file mode 100644
index fa7c10b615..0000000000
--- a/t/t0110/url-7
+++ /dev/null
@@ -1 +0,0 @@
-x://q/ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏ
diff --git a/t/t0110/url-8 b/t/t0110/url-8
deleted file mode 100644
index 79a0ba836f..0000000000
--- a/t/t0110/url-8
+++ /dev/null
@@ -1 +0,0 @@
-x://q/ÐÑÒÓÔÕÖרÙÚÛÜÝÞß
diff --git a/t/t0110/url-9 b/t/t0110/url-9
deleted file mode 100644
index 8b44bec48b..0000000000
--- a/t/t0110/url-9
+++ /dev/null
@@ -1 +0,0 @@
-x://q/àáâãäåæçèéêëìíîï
diff --git a/t/t0210-trace2-normal.sh b/t/t0210-trace2-normal.sh
index c312657a12..b9adc94aab 100755
--- a/t/t0210-trace2-normal.sh
+++ b/t/t0210-trace2-normal.sh
@@ -2,7 +2,7 @@
test_description='test trace2 facility (normal target)'
-TEST_PASSES_SANITIZE_LEAK=false
+TEST_PASSES_SANITIZE_LEAK=true
. ./test-lib.sh
# Turn off any inherited trace2 settings for this test.
diff --git a/t/t0301-credential-cache.sh b/t/t0301-credential-cache.sh
index c10e35905e..5d5b64205f 100755
--- a/t/t0301-credential-cache.sh
+++ b/t/t0301-credential-cache.sh
@@ -1,6 +1,8 @@
#!/bin/sh
test_description='credential-cache tests'
+
+TEST_PASSES_SANITIZE_LEAK=true
. ./test-lib.sh
. "$TEST_DIRECTORY"/lib-credential.sh
diff --git a/t/t0302-credential-store.sh b/t/t0302-credential-store.sh
index 716bf1af9f..f83db659e2 100755
--- a/t/t0302-credential-store.sh
+++ b/t/t0302-credential-store.sh
@@ -1,6 +1,8 @@
#!/bin/sh
test_description='credential-store tests'
+
+TEST_PASSES_SANITIZE_LEAK=true
. ./test-lib.sh
. "$TEST_DIRECTORY"/lib-credential.sh
diff --git a/t/t0303-credential-external.sh b/t/t0303-credential-external.sh
index 72ae405c3e..8aadbe86c4 100755
--- a/t/t0303-credential-external.sh
+++ b/t/t0303-credential-external.sh
@@ -29,6 +29,7 @@ you can set GIT_TEST_CREDENTIAL_HELPER_SETUP to a sequence of shell
commands.
'
+TEST_PASSES_SANITIZE_LEAK=true
. ./test-lib.sh
. "$TEST_DIRECTORY"/lib-credential.sh
diff --git a/t/t0410-partial-clone.sh b/t/t0410-partial-clone.sh
index 2c30c86e7b..34bdb3ab1f 100755
--- a/t/t0410-partial-clone.sh
+++ b/t/t0410-partial-clone.sh
@@ -5,8 +5,6 @@ test_description='partial clone'
. ./test-lib.sh
. "$TEST_DIRECTORY"/lib-terminal.sh
-# missing promisor objects cause repacks which write bitmaps to fail
-GIT_TEST_MULTI_PACK_INDEX_WRITE_BITMAP=0
# When enabled, some commands will write commit-graphs. This causes fsck
# to fail when delete_object() is called because fsck will attempt to
# verify the out-of-sync commit graph.
diff --git a/t/t0601-reffiles-pack-refs.sh b/t/t0601-reffiles-pack-refs.sh
index 60a544b8ee..d8cbd3f202 100755
--- a/t/t0601-reffiles-pack-refs.sh
+++ b/t/t0601-reffiles-pack-refs.sh
@@ -161,13 +161,6 @@ test_expect_success 'test --exclude takes precedence over --include' '
git pack-refs --include "refs/heads/pack*" --exclude "refs/heads/pack*" &&
test -f .git/refs/heads/dont_pack5'
-test_expect_success '--auto packs and prunes refs as usual' '
- git branch auto &&
- test_path_is_file .git/refs/heads/auto &&
- git pack-refs --auto --all &&
- test_path_is_missing .git/refs/heads/auto
-'
-
test_expect_success 'see if up-to-date packed refs are preserved' '
git branch q &&
git pack-refs --all --prune &&
@@ -367,14 +360,90 @@ test_expect_success 'pack-refs does not drop broken refs during deletion' '
test_cmp expect actual
'
-test_expect_success 'maintenance --auto unconditionally packs loose refs' '
- git update-ref refs/heads/something HEAD &&
- test_path_is_file .git/refs/heads/something &&
- git rev-parse refs/heads/something >expect &&
- git maintenance run --task=pack-refs --auto &&
- test_path_is_missing .git/refs/heads/something &&
- git rev-parse refs/heads/something >actual &&
- test_cmp expect actual
-'
+for command in "git pack-refs --all --auto" "git maintenance run --task=pack-refs --auto"
+do
+ test_expect_success "$command does not repack below 16 refs without packed-refs" '
+ test_when_finished "rm -rf repo" &&
+ git init repo &&
+ (
+ cd repo &&
+ git config set maintenance.auto false &&
+ git commit --allow-empty --message "initial" &&
+
+ # Create 14 additional references, which brings us to
+ # 15 together with the default branch.
+ printf "create refs/heads/loose-%d HEAD\n" $(test_seq 14) >stdin &&
+ git update-ref --stdin <stdin &&
+ test_path_is_missing .git/packed-refs &&
+ git pack-refs --auto --all &&
+ test_path_is_missing .git/packed-refs &&
+
+ # Create the 16th reference, which should cause us to repack.
+ git update-ref refs/heads/loose-15 HEAD &&
+ git pack-refs --auto --all &&
+ test_path_is_file .git/packed-refs
+ )
+ '
+
+ test_expect_success "$command does not repack below 16 refs with small packed-refs" '
+ test_when_finished "rm -rf repo" &&
+ git init repo &&
+ (
+ cd repo &&
+ git config set maintenance.auto false &&
+ git commit --allow-empty --message "initial" &&
+
+ git pack-refs --all &&
+ test_line_count = 2 .git/packed-refs &&
+
+ # Create 15 loose references.
+ printf "create refs/heads/loose-%d HEAD\n" $(test_seq 15) >stdin &&
+ git update-ref --stdin <stdin &&
+ git pack-refs --auto --all &&
+ test_line_count = 2 .git/packed-refs &&
+
+ # Create the 16th loose reference, which should cause us to repack.
+ git update-ref refs/heads/loose-17 HEAD &&
+ git pack-refs --auto --all &&
+ test_line_count = 18 .git/packed-refs
+ )
+ '
+
+ test_expect_success "$command scales with size of packed-refs" '
+ test_when_finished "rm -rf repo" &&
+ git init repo &&
+ (
+ cd repo &&
+ git config set maintenance.auto false &&
+ git commit --allow-empty --message "initial" &&
+
+ # Create 99 packed refs. This should cause the heuristic
+ # to require more than the minimum amount of loose refs.
+ test_seq 99 |
+ while read i
+ do
+ printf "create refs/heads/packed-%d HEAD\n" $i || return 1
+ done >stdin &&
+ git update-ref --stdin <stdin &&
+ git pack-refs --all &&
+ test_line_count = 101 .git/packed-refs &&
+
+ # Create 24 loose refs, which should not yet cause us to repack.
+ printf "create refs/heads/loose-%d HEAD\n" $(test_seq 24) >stdin &&
+ git update-ref --stdin <stdin &&
+ git pack-refs --auto --all &&
+ test_line_count = 101 .git/packed-refs &&
+
+ # Create another handful of refs to cross the border.
+ # Note that we explicitly do not check for strict
+ # boundaries here, as this also depends on the size of
+ # the object hash.
+ printf "create refs/heads/addn-%d HEAD\n" $(test_seq 10) >stdin &&
+ git update-ref --stdin <stdin &&
+ git pack-refs --auto --all &&
+ test_line_count = 135 .git/packed-refs
+ )
+ '
+done
test_done
diff --git a/t/t0602-reffiles-fsck.sh b/t/t0602-reffiles-fsck.sh
new file mode 100755
index 0000000000..71a4d1a5ae
--- /dev/null
+++ b/t/t0602-reffiles-fsck.sh
@@ -0,0 +1,92 @@
+#!/bin/sh
+
+test_description='Test reffiles backend consistency check'
+
+GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
+export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
+GIT_TEST_DEFAULT_REF_FORMAT=files
+export GIT_TEST_DEFAULT_REF_FORMAT
+TEST_PASSES_SANITIZE_LEAK=true
+
+. ./test-lib.sh
+
+test_expect_success 'ref name should be checked' '
+ test_when_finished "rm -rf repo" &&
+ git init repo &&
+ branch_dir_prefix=.git/refs/heads &&
+ tag_dir_prefix=.git/refs/tags &&
+ cd repo &&
+
+ git commit --allow-empty -m initial &&
+ git checkout -b branch-1 &&
+ git tag tag-1 &&
+ git commit --allow-empty -m second &&
+ git checkout -b branch-2 &&
+ git tag tag-2 &&
+ git tag multi_hierarchy/tag-2 &&
+
+ cp $branch_dir_prefix/branch-1 $branch_dir_prefix/.branch-1 &&
+ test_must_fail git refs verify 2>err &&
+ cat >expect <<-EOF &&
+ error: refs/heads/.branch-1: badRefName: invalid refname format
+ EOF
+ rm $branch_dir_prefix/.branch-1 &&
+ test_cmp expect err &&
+
+ cp $branch_dir_prefix/branch-1 $branch_dir_prefix/@ &&
+ test_must_fail git refs verify 2>err &&
+ cat >expect <<-EOF &&
+ error: refs/heads/@: badRefName: invalid refname format
+ EOF
+ rm $branch_dir_prefix/@ &&
+ test_cmp expect err &&
+
+ cp $tag_dir_prefix/multi_hierarchy/tag-2 $tag_dir_prefix/multi_hierarchy/@ &&
+ test_must_fail git refs verify 2>err &&
+ cat >expect <<-EOF &&
+ error: refs/tags/multi_hierarchy/@: badRefName: invalid refname format
+ EOF
+ rm $tag_dir_prefix/multi_hierarchy/@ &&
+ test_cmp expect err &&
+
+ cp $tag_dir_prefix/tag-1 $tag_dir_prefix/tag-1.lock &&
+ git refs verify 2>err &&
+ rm $tag_dir_prefix/tag-1.lock &&
+ test_must_be_empty err &&
+
+ cp $tag_dir_prefix/tag-1 $tag_dir_prefix/.lock &&
+ test_must_fail git refs verify 2>err &&
+ cat >expect <<-EOF &&
+ error: refs/tags/.lock: badRefName: invalid refname format
+ EOF
+ rm $tag_dir_prefix/.lock &&
+ test_cmp expect err
+'
+
+test_expect_success 'ref name check should be adapted into fsck messages' '
+ test_when_finished "rm -rf repo" &&
+ git init repo &&
+ branch_dir_prefix=.git/refs/heads &&
+ tag_dir_prefix=.git/refs/tags &&
+ cd repo &&
+ git commit --allow-empty -m initial &&
+ git checkout -b branch-1 &&
+ git tag tag-1 &&
+ git commit --allow-empty -m second &&
+ git checkout -b branch-2 &&
+ git tag tag-2 &&
+
+ cp $branch_dir_prefix/branch-1 $branch_dir_prefix/.branch-1 &&
+ git -c fsck.badRefName=warn refs verify 2>err &&
+ cat >expect <<-EOF &&
+ warning: refs/heads/.branch-1: badRefName: invalid refname format
+ EOF
+ rm $branch_dir_prefix/.branch-1 &&
+ test_cmp expect err &&
+
+ cp $branch_dir_prefix/branch-1 $branch_dir_prefix/@ &&
+ git -c fsck.badRefName=ignore refs verify 2>err &&
+ test_must_be_empty err
+'
+
+test_done
diff --git a/t/t0610-reftable-basics.sh b/t/t0610-reftable-basics.sh
index b06c46999d..2d951c8ceb 100755
--- a/t/t0610-reftable-basics.sh
+++ b/t/t0610-reftable-basics.sh
@@ -423,6 +423,64 @@ test_expect_success 'ref transaction: fails gracefully when auto compaction fail
)
'
+test_expect_success 'ref transaction: timeout acquiring tables.list lock' '
+ test_when_finished "rm -rf repo" &&
+ git init repo &&
+ (
+ cd repo &&
+ test_commit initial &&
+ >.git/reftable/tables.list.lock &&
+ test_must_fail git update-ref refs/heads/branch HEAD 2>err &&
+ test_grep "cannot lock references" err
+ )
+'
+
+test_expect_success 'ref transaction: retry acquiring tables.list lock' '
+ test_when_finished "rm -rf repo" &&
+ git init repo &&
+ (
+ cd repo &&
+ test_commit initial &&
+ LOCK=.git/reftable/tables.list.lock &&
+ >$LOCK &&
+ {
+ ( sleep 1 && rm -f $LOCK ) &
+ } &&
+ git -c reftable.lockTimeout=5000 update-ref refs/heads/branch HEAD
+ )
+'
+
+test_expect_success 'ref transaction: many concurrent writers' '
+ test_when_finished "rm -rf repo" &&
+ git init repo &&
+ (
+ cd repo &&
+ # Set a high timeout such that a busy CI machine will not abort
+ # early. 10 seconds should hopefully be ample of time to make
+ # this non-flaky.
+ git config set reftable.lockTimeout 10000 &&
+ test_commit --no-tag initial &&
+
+ head=$(git rev-parse HEAD) &&
+ for i in $(test_seq 100)
+ do
+ printf "%s commit\trefs/heads/branch-%s\n" "$head" "$i" ||
+ return 1
+ done >expect &&
+ printf "%s commit\trefs/heads/main\n" "$head" >>expect &&
+
+ for i in $(test_seq 100)
+ do
+ { git update-ref refs/heads/branch-$i HEAD& } ||
+ return 1
+ done &&
+
+ wait &&
+ git for-each-ref --sort=v:refname >actual &&
+ test_cmp expect actual
+ )
+'
+
test_expect_success 'pack-refs: compacts tables' '
test_when_finished "rm -rf repo" &&
git init repo &&
@@ -478,19 +536,26 @@ test_expect_success "$command: auto compaction" '
test_oid blob17_2 | git hash-object -w --stdin &&
- # Lock all tables write some refs. Auto-compaction will be
- # unable to compact tables and thus fails gracefully, leaving
- # the stack in a sub-optimal state.
- ls .git/reftable/*.ref |
+ # Lock all tables, write some refs. Auto-compaction will be
+ # unable to compact tables and thus fails gracefully,
+ # compacting only those tables which are not locked.
+ ls .git/reftable/*.ref | sort |
while read table
do
- touch "$table.lock" || exit 1
+ touch "$table.lock" &&
+ basename "$table" >>tables.expect || exit 1
done &&
+ test_line_count = 2 .git/reftable/tables.list &&
git branch B &&
git branch C &&
- rm .git/reftable/*.lock &&
- test_line_count = 4 .git/reftable/tables.list &&
+ # The new tables are auto-compacted, but the locked tables are
+ # left intact.
+ test_line_count = 3 .git/reftable/tables.list &&
+ head -n 2 .git/reftable/tables.list >tables.head &&
+ test_cmp tables.expect tables.head &&
+
+ rm .git/reftable/*.lock &&
git $command --auto &&
test_line_count = 1 .git/reftable/tables.list
)
diff --git a/t/t1006-cat-file.sh b/t/t1006-cat-file.sh
index ff9bf213aa..d36cd7c086 100755
--- a/t/t1006-cat-file.sh
+++ b/t/t1006-cat-file.sh
@@ -2,6 +2,7 @@
test_description='git cat-file'
+TEST_PASSES_SANITIZE_LEAK=true
. ./test-lib.sh
test_cmdmode_usage () {
diff --git a/t/t1050-large.sh b/t/t1050-large.sh
index c71932b024..ed638f6644 100755
--- a/t/t1050-large.sh
+++ b/t/t1050-large.sh
@@ -3,6 +3,7 @@
test_description='adding and checking out large blobs'
+TEST_PASSES_SANITIZE_LEAK=true
. ./test-lib.sh
test_expect_success 'core.bigFileThreshold must be non-negative' '
diff --git a/t/t1092-sparse-checkout-compatibility.sh b/t/t1092-sparse-checkout-compatibility.sh
index a2c0e1b4dc..6e230b5487 100755
--- a/t/t1092-sparse-checkout-compatibility.sh
+++ b/t/t1092-sparse-checkout-compatibility.sh
@@ -179,22 +179,26 @@ init_repos_as_submodules () {
}
run_on_sparse () {
+ cat >run-on-sparse-input &&
+
(
cd sparse-checkout &&
GIT_PROGRESS_DELAY=100000 "$@" >../sparse-checkout-out 2>../sparse-checkout-err
- ) &&
+ ) <run-on-sparse-input &&
(
cd sparse-index &&
GIT_PROGRESS_DELAY=100000 "$@" >../sparse-index-out 2>../sparse-index-err
- )
+ ) <run-on-sparse-input
}
run_on_all () {
+ cat >run-on-all-input &&
+
(
cd full-checkout &&
GIT_PROGRESS_DELAY=100000 "$@" >../full-checkout-out 2>../full-checkout-err
- ) &&
- run_on_sparse "$@"
+ ) <run-on-all-input &&
+ run_on_sparse "$@" <run-on-all-input
}
test_all_match () {
@@ -221,7 +225,7 @@ test_sparse_unstaged () {
done
}
-# Usage: test_sprase_checkout_set "<c1> ... <cN>" "<s1> ... <sM>"
+# Usage: test_sparse_checkout_set "<c1> ... <cN>" "<s1> ... <sM>"
# Verifies that "git sparse-checkout set <c1> ... <cN>" succeeds and
# leaves the sparse index in a state where <s1> ... <sM> are sparse
# directories (and <c1> ... <cN> are not).
@@ -803,6 +807,8 @@ test_expect_success 'update-index --remove outside sparse definition' '
test_sparse_match git diff --cached --name-status &&
test_cmp expect sparse-checkout-out &&
+ test_sparse_match git diff-index --cached HEAD &&
+
# Reset the state
test_all_match git reset --hard &&
@@ -812,6 +818,8 @@ test_expect_success 'update-index --remove outside sparse definition' '
test_sparse_match git diff --cached --name-status &&
test_must_be_empty sparse-checkout-out &&
+ test_sparse_match git diff-index --cached HEAD &&
+
# Reset the state
test_all_match git reset --hard &&
@@ -823,7 +831,9 @@ test_expect_success 'update-index --remove outside sparse definition' '
D folder1/a
EOF
test_sparse_match git diff --cached --name-status &&
- test_cmp expect sparse-checkout-out
+ test_cmp expect sparse-checkout-out &&
+
+ test_sparse_match git diff-index --cached HEAD
'
test_expect_success 'update-index with directories' '
@@ -1551,7 +1561,7 @@ test_expect_success 'sparse-index is not expanded: describe' '
ensure_not_expanded describe
'
-test_expect_success 'sparse index is not expanded: diff' '
+test_expect_success 'sparse index is not expanded: diff and diff-index' '
init_repos &&
write_script edit-contents <<-\EOF &&
@@ -1568,6 +1578,7 @@ test_expect_success 'sparse index is not expanded: diff' '
test_all_match git diff --cached &&
ensure_not_expanded diff &&
ensure_not_expanded diff --cached &&
+ ensure_not_expanded diff-index --cached HEAD &&
# Add file outside cone
test_all_match git reset --hard &&
@@ -1582,6 +1593,7 @@ test_expect_success 'sparse index is not expanded: diff' '
test_all_match git diff --cached &&
ensure_not_expanded diff &&
ensure_not_expanded diff --cached &&
+ ensure_not_expanded diff-index --cached HEAD &&
# Merge conflict outside cone
# The sparse checkout will report a warning that is not in the
@@ -1594,7 +1606,8 @@ test_expect_success 'sparse index is not expanded: diff' '
test_all_match git diff &&
test_all_match git diff --cached &&
ensure_not_expanded diff &&
- ensure_not_expanded diff --cached
+ ensure_not_expanded diff --cached &&
+ ensure_not_expanded diff-index --cached HEAD
'
test_expect_success 'sparse index is not expanded: show and rev-parse' '
@@ -2342,7 +2355,46 @@ test_expect_success 'advice.sparseIndexExpanded' '
mkdir -p sparse-index/deep/deeper2/deepest &&
touch sparse-index/deep/deeper2/deepest/bogus &&
git -C sparse-index status 2>err &&
- grep "The sparse index is expanding to a full index" err
+ grep "The sparse index is expanding to a full index" err &&
+
+ git -C sparse-index sparse-checkout disable 2>err &&
+ test_line_count = 0 err
+'
+
+test_expect_success 'cat-file -p' '
+ init_repos &&
+ echo "new content" >>full-checkout/deep/a &&
+ echo "new content" >>sparse-checkout/deep/a &&
+ echo "new content" >>sparse-index/deep/a &&
+ run_on_all git add deep/a &&
+
+ test_all_match git cat-file -p :deep/a &&
+ ensure_not_expanded cat-file -p :deep/a &&
+ test_all_match git cat-file -p :folder1/a &&
+ ensure_expanded cat-file -p :folder1/a
+'
+
+test_expect_success 'cat-file --batch' '
+ init_repos &&
+ echo "new content" >>full-checkout/deep/a &&
+ echo "new content" >>sparse-checkout/deep/a &&
+ echo "new content" >>sparse-index/deep/a &&
+ run_on_all git add deep/a &&
+
+ echo ":deep/a" >in &&
+ test_all_match git cat-file --batch <in &&
+ ensure_not_expanded cat-file --batch <in &&
+
+ echo ":folder1/a" >in &&
+ test_all_match git cat-file --batch <in &&
+ ensure_expanded cat-file --batch <in &&
+
+ cat >in <<-\EOF &&
+ :deep/a
+ :folder1/a
+ EOF
+ test_all_match git cat-file --batch <in &&
+ ensure_expanded cat-file --batch <in
'
test_done
diff --git a/t/t1305-config-include.sh b/t/t1305-config-include.sh
index 5cde79ef8c..517d6c8693 100755
--- a/t/t1305-config-include.sh
+++ b/t/t1305-config-include.sh
@@ -357,4 +357,44 @@ test_expect_success 'include cycles are detected' '
grep "exceeded maximum include depth" stderr
'
+test_expect_success 'onbranch with unborn branch' '
+ test_when_finished "rm -rf repo" &&
+ git init repo &&
+ (
+ cd repo &&
+ git config set includeIf.onbranch:"*".path config.inc &&
+ git config set -f .git/config.inc foo.bar baz &&
+ git config get foo.bar
+ )
+'
+
+test_expect_success 'onbranch with detached HEAD' '
+ test_when_finished "rm -rf repo" &&
+ git init repo &&
+ (
+ cd repo &&
+ git config set "includeIf.onbranch:*.path" config.inc &&
+ git config set -f .git/config.inc foo.bar baz &&
+ test_commit initial &&
+ git switch --detach HEAD &&
+ test_must_fail git config get foo.bar
+ )
+'
+
+test_expect_success 'onbranch without repository' '
+ test_when_finished "rm -f .gitconfig config.inc" &&
+ git config set -f .gitconfig "includeIf.onbranch:**.path" config.inc &&
+ git config set -f config.inc foo.bar baz &&
+ git config get foo.bar &&
+ test_must_fail nongit git config get foo.bar
+'
+
+test_expect_success 'onbranch without repository but explicit nonexistent Git directory' '
+ test_when_finished "rm -f .gitconfig config.inc" &&
+ git config set -f .gitconfig "includeIf.onbranch:**.path" config.inc &&
+ git config set -f config.inc foo.bar baz &&
+ git config get foo.bar &&
+ test_must_fail nongit git --git-dir=nonexistent config get foo.bar
+'
+
test_done
diff --git a/t/t1414-reflog-walk.sh b/t/t1414-reflog-walk.sh
index be6c3f472c..49d28166da 100755
--- a/t/t1414-reflog-walk.sh
+++ b/t/t1414-reflog-walk.sh
@@ -4,6 +4,7 @@ test_description='various tests of reflog walk (log -g) behavior'
GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
+TEST_PASSES_SANITIZE_LEAK=true
. ./test-lib.sh
test_expect_success 'set up some reflog entries' '
diff --git a/t/t1419-exclude-refs.sh b/t/t1419-exclude-refs.sh
index 1359574419..3256e4462f 100755
--- a/t/t1419-exclude-refs.sh
+++ b/t/t1419-exclude-refs.sh
@@ -8,12 +8,6 @@ export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
TEST_PASSES_SANITIZE_LEAK=true
. ./test-lib.sh
-if test_have_prereq !REFFILES
-then
- skip_all='skipping `git for-each-ref --exclude` tests; need files backend'
- test_done
-fi
-
for_each_ref__exclude () {
GIT_TRACE2_PERF=1 test-tool ref-store main \
for-each-ref--exclude "$@" >actual.raw
@@ -28,7 +22,14 @@ assert_jumps () {
local nr="$1"
local trace="$2"
- grep -q "name:jumps_made value:$nr$" $trace
+ case "$GIT_DEFAULT_REF_FORMAT" in
+ files)
+ grep -q "name:jumps_made value:$nr$" $trace;;
+ reftable)
+ grep -q "name:reseeks_made value:$nr$" $trace;;
+ *)
+ BUG "unhandled ref format $GIT_DEFAULT_REF_FORMAT";;
+ esac
}
assert_no_jumps () {
@@ -89,7 +90,14 @@ test_expect_success 'adjacent, non-overlapping excluded regions' '
for_each_ref refs/heads/foo refs/heads/quux >expect &&
test_cmp expect actual &&
- assert_jumps 1 perf
+ case "$GIT_DEFAULT_REF_FORMAT" in
+ files)
+ assert_jumps 1 perf;;
+ reftable)
+ assert_jumps 2 perf;;
+ *)
+ BUG "unhandled ref format $GIT_DEFAULT_REF_FORMAT";;
+ esac
'
test_expect_success 'overlapping excluded regions' '
@@ -106,7 +114,30 @@ test_expect_success 'several overlapping excluded regions' '
for_each_ref refs/heads/quux >expect &&
test_cmp expect actual &&
- assert_jumps 1 perf
+ case "$GIT_DEFAULT_REF_FORMAT" in
+ files)
+ assert_jumps 1 perf;;
+ reftable)
+ assert_jumps 3 perf;;
+ *)
+ BUG "unhandled ref format $GIT_DEFAULT_REF_FORMAT";;
+ esac
+'
+
+test_expect_success 'unordered excludes' '
+ for_each_ref__exclude refs/heads \
+ refs/heads/foo refs/heads/baz >actual 2>perf &&
+ for_each_ref refs/heads/bar refs/heads/quux >expect &&
+
+ test_cmp expect actual &&
+ case "$GIT_DEFAULT_REF_FORMAT" in
+ files)
+ assert_jumps 1 perf;;
+ reftable)
+ assert_jumps 2 perf;;
+ *)
+ BUG "unhandled ref format $GIT_DEFAULT_REF_FORMAT";;
+ esac
'
test_expect_success 'non-matching excluded section' '
diff --git a/t/t1450-fsck.sh b/t/t1450-fsck.sh
index 8a456b1142..280cbf3e03 100755
--- a/t/t1450-fsck.sh
+++ b/t/t1450-fsck.sh
@@ -6,6 +6,7 @@ test_description='git fsck random collection of tests
* (main) A
'
+TEST_PASSES_SANITIZE_LEAK=true
. ./test-lib.sh
test_expect_success setup '
diff --git a/t/t1502-rev-parse-parseopt.sh b/t/t1502-rev-parse-parseopt.sh
index b754b9fd74..5eaa6428c4 100755
--- a/t/t1502-rev-parse-parseopt.sh
+++ b/t/t1502-rev-parse-parseopt.sh
@@ -1,6 +1,8 @@
#!/bin/sh
test_description='test git rev-parse --parseopt'
+
+TEST_PASSES_SANITIZE_LEAK=true
. ./test-lib.sh
check_invalid_long_option () {
diff --git a/t/t1511-rev-parse-caret.sh b/t/t1511-rev-parse-caret.sh
index 6ecfed86bc..e7e78a4028 100755
--- a/t/t1511-rev-parse-caret.sh
+++ b/t/t1511-rev-parse-caret.sh
@@ -5,6 +5,7 @@ test_description='tests for ref^{stuff}'
GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
+TEST_PASSES_SANITIZE_LEAK=true
. ./test-lib.sh
test_expect_success 'setup' '
diff --git a/t/t1601-index-bogus.sh b/t/t1601-index-bogus.sh
index 4171f1e141..5dcc101882 100755
--- a/t/t1601-index-bogus.sh
+++ b/t/t1601-index-bogus.sh
@@ -1,6 +1,8 @@
#!/bin/sh
test_description='test handling of bogus index entries'
+
+TEST_PASSES_SANITIZE_LEAK=true
. ./test-lib.sh
test_expect_success 'create tree with null sha1' '
diff --git a/t/t2030-unresolve-info.sh b/t/t2030-unresolve-info.sh
index be3fcdde07..b3f6bc97b5 100755
--- a/t/t2030-unresolve-info.sh
+++ b/t/t2030-unresolve-info.sh
@@ -5,6 +5,7 @@ test_description='undoing resolution'
GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
+TEST_PASSES_SANITIZE_LEAK=true
. ./test-lib.sh
check_resolve_undo () {
diff --git a/t/t2080-parallel-checkout-basics.sh b/t/t2080-parallel-checkout-basics.sh
index 5ffe1a41e2..59e5570cb2 100755
--- a/t/t2080-parallel-checkout-basics.sh
+++ b/t/t2080-parallel-checkout-basics.sh
@@ -8,6 +8,7 @@ working tree.
'
TEST_NO_CREATE_REPO=1
+TEST_PASSES_SANITIZE_LEAK=true
. ./test-lib.sh
. "$TEST_DIRECTORY/lib-parallel-checkout.sh"
diff --git a/t/t2082-parallel-checkout-attributes.sh b/t/t2082-parallel-checkout-attributes.sh
index f3511cd43a..aec55496eb 100755
--- a/t/t2082-parallel-checkout-attributes.sh
+++ b/t/t2082-parallel-checkout-attributes.sh
@@ -10,6 +10,7 @@ properly (without access to the index or attribute stack).
'
TEST_NO_CREATE_REPO=1
+TEST_PASSES_SANITIZE_LEAK=true
. ./test-lib.sh
. "$TEST_DIRECTORY/lib-parallel-checkout.sh"
. "$TEST_DIRECTORY/lib-encoding.sh"
diff --git a/t/t2400-worktree-add.sh b/t/t2400-worktree-add.sh
index ba320dc417..cfc4aeb179 100755
--- a/t/t2400-worktree-add.sh
+++ b/t/t2400-worktree-add.sh
@@ -6,6 +6,7 @@ GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
TEST_CREATE_REPO_NO_TEMPLATE=1
+TEST_PASSES_SANITIZE_LEAK=true
. ./test-lib.sh
. "$TEST_DIRECTORY"/lib-rebase.sh
diff --git a/t/t2501-cwd-empty.sh b/t/t2501-cwd-empty.sh
index f6d8d7d03d..8af4e8cfe3 100755
--- a/t/t2501-cwd-empty.sh
+++ b/t/t2501-cwd-empty.sh
@@ -2,6 +2,7 @@
test_description='Test handling of the current working directory becoming empty'
+TEST_PASSES_SANITIZE_LEAK=true
. ./test-lib.sh
test_expect_success setup '
diff --git a/t/t3011-common-prefixes-and-directory-traversal.sh b/t/t3011-common-prefixes-and-directory-traversal.sh
index 3da5b2b6e7..69e44c387f 100755
--- a/t/t3011-common-prefixes-and-directory-traversal.sh
+++ b/t/t3011-common-prefixes-and-directory-traversal.sh
@@ -2,6 +2,7 @@
test_description='directory traversal handling, especially with common prefixes'
+TEST_PASSES_SANITIZE_LEAK=true
. ./test-lib.sh
test_expect_success 'setup' '
diff --git a/t/t3201-branch-contains.sh b/t/t3201-branch-contains.sh
index 800fc33165..6e587d27d7 100755
--- a/t/t3201-branch-contains.sh
+++ b/t/t3201-branch-contains.sh
@@ -2,6 +2,7 @@
test_description='branch --contains <commit>, --no-contains <commit> --merged, and --no-merged'
+TEST_PASSES_SANITIZE_LEAK=true
. ./test-lib.sh
test_expect_success setup '
diff --git a/t/t3202-show-branch.sh b/t/t3202-show-branch.sh
index a1139f79e2..3b6dad0c46 100755
--- a/t/t3202-show-branch.sh
+++ b/t/t3202-show-branch.sh
@@ -2,6 +2,7 @@
test_description='test show-branch'
+TEST_PASSES_SANITIZE_LEAK=true
. ./test-lib.sh
test_expect_success 'error descriptions on empty repository' '
diff --git a/t/t3206-range-diff.sh b/t/t3206-range-diff.sh
index a767c3520e..86010931ab 100755
--- a/t/t3206-range-diff.sh
+++ b/t/t3206-range-diff.sh
@@ -5,6 +5,7 @@ test_description='range-diff tests'
GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
+TEST_PASSES_SANITIZE_LEAK=true
. ./test-lib.sh
# Note that because of the range-diff's heuristics, test_commit does more
@@ -533,9 +534,9 @@ test_expect_success 'dual-coloring' '
for prev in topic main..topic
do
test_expect_success "format-patch --range-diff=$prev" '
+ test_when_finished "rm -f 000?-*" &&
git format-patch --cover-letter --range-diff=$prev \
main..unmodified >actual &&
- test_when_finished "rm 000?-*" &&
test_line_count = 5 actual &&
test_grep "^Range-diff:$" 0000-* &&
grep "= 1: .* s/5/A" 0000-* &&
@@ -560,32 +561,32 @@ test_expect_success "explicit --no-cover-letter defeats implied --cover-letter"
'
test_expect_success 'format-patch --range-diff as commentary' '
+ test_when_finished "rm -f 0001-*" &&
git format-patch --range-diff=HEAD~1 HEAD~1 >actual &&
- test_when_finished "rm 0001-*" &&
test_line_count = 1 actual &&
test_grep "^Range-diff:$" 0001-* &&
grep "> 1: .* new message" 0001-*
'
test_expect_success 'format-patch --range-diff reroll-count with a non-integer' '
+ test_when_finished "rm -f v2.9-0001-*" &&
git format-patch --range-diff=HEAD~1 -v2.9 HEAD~1 >actual &&
- test_when_finished "rm v2.9-0001-*" &&
test_line_count = 1 actual &&
test_grep "^Range-diff:$" v2.9-0001-* &&
grep "> 1: .* new message" v2.9-0001-*
'
test_expect_success 'format-patch --range-diff reroll-count with a integer' '
+ test_when_finished "rm -f v2-0001-*" &&
git format-patch --range-diff=HEAD~1 -v2 HEAD~1 >actual &&
- test_when_finished "rm v2-0001-*" &&
test_line_count = 1 actual &&
test_grep "^Range-diff ..* v1:$" v2-0001-* &&
grep "> 1: .* new message" v2-0001-*
'
test_expect_success 'format-patch --range-diff with v0' '
+ test_when_finished "rm -f v0-0001-*" &&
git format-patch --range-diff=HEAD~1 -v0 HEAD~1 >actual &&
- test_when_finished "rm v0-0001-*" &&
test_line_count = 1 actual &&
test_grep "^Range-diff:$" v0-0001-* &&
grep "> 1: .* new message" v0-0001-*
@@ -606,9 +607,9 @@ test_expect_success 'basic with modified format.pretty without "commit "' '
'
test_expect_success 'range-diff compares notes by default' '
+ test_when_finished "git notes remove topic unmodified || :" &&
git notes add -m "topic note" topic &&
git notes add -m "unmodified note" unmodified &&
- test_when_finished git notes remove topic unmodified &&
git range-diff --no-color main..topic main..unmodified \
>actual &&
sed s/Z/\ /g >expect <<-EOF &&
@@ -630,9 +631,9 @@ test_expect_success 'range-diff compares notes by default' '
'
test_expect_success 'range-diff with --no-notes' '
+ test_when_finished "git notes remove topic unmodified || :" &&
git notes add -m "topic note" topic &&
git notes add -m "unmodified note" unmodified &&
- test_when_finished git notes remove topic unmodified &&
git range-diff --no-color --no-notes main..topic main..unmodified \
>actual &&
cat >expect <<-EOF &&
@@ -645,12 +646,12 @@ test_expect_success 'range-diff with --no-notes' '
'
test_expect_success 'range-diff with multiple --notes' '
+ test_when_finished "git notes --ref=note1 remove topic unmodified || :" &&
git notes --ref=note1 add -m "topic note1" topic &&
git notes --ref=note1 add -m "unmodified note1" unmodified &&
- test_when_finished git notes --ref=note1 remove topic unmodified &&
+ test_when_finished "git notes --ref=note2 remove topic unmodified || :" &&
git notes --ref=note2 add -m "topic note2" topic &&
git notes --ref=note2 add -m "unmodified note2" unmodified &&
- test_when_finished git notes --ref=note2 remove topic unmodified &&
git range-diff --no-color --notes=note1 --notes=note2 main..topic main..unmodified \
>actual &&
sed s/Z/\ /g >expect <<-EOF &&
@@ -678,12 +679,12 @@ test_expect_success 'range-diff with multiple --notes' '
# `range-diff` should act like `log` with regards to notes
test_expect_success 'range-diff with --notes=custom does not show default notes' '
+ test_when_finished "git notes remove topic unmodified || :" &&
git notes add -m "topic note" topic &&
git notes add -m "unmodified note" unmodified &&
+ test_when_finished "git notes --ref=custom remove topic unmodified || :" &&
git notes --ref=custom add -m "topic note" topic &&
git notes --ref=custom add -m "unmodified note" unmodified &&
- test_when_finished git notes remove topic unmodified &&
- test_when_finished git notes --ref=custom remove topic unmodified &&
git range-diff --notes=custom main..topic main..unmodified \
>actual &&
! grep "## Notes ##" actual &&
@@ -691,12 +692,12 @@ test_expect_success 'range-diff with --notes=custom does not show default notes'
'
test_expect_success 'format-patch --range-diff does not compare notes by default' '
+ test_when_finished "git notes remove topic unmodified || :" &&
git notes add -m "topic note" topic &&
git notes add -m "unmodified note" unmodified &&
- test_when_finished git notes remove topic unmodified &&
+ test_when_finished "rm -f 000?-*" &&
git format-patch --cover-letter --range-diff=$prev \
main..unmodified >actual &&
- test_when_finished "rm 000?-*" &&
test_line_count = 5 actual &&
test_grep "^Range-diff:$" 0000-* &&
grep "= 1: .* s/5/A" 0000-* &&
@@ -708,26 +709,26 @@ test_expect_success 'format-patch --range-diff does not compare notes by default
'
test_expect_success 'format-patch --notes=custom --range-diff only compares custom notes' '
+ test_when_finished "git notes remove topic unmodified || :" &&
git notes add -m "topic note" topic &&
- git notes --ref=custom add -m "topic note (custom)" topic &&
git notes add -m "unmodified note" unmodified &&
+ test_when_finished "git notes --ref=custom remove topic unmodified || :" &&
+ git notes --ref=custom add -m "topic note (custom)" topic &&
git notes --ref=custom add -m "unmodified note (custom)" unmodified &&
- test_when_finished git notes remove topic unmodified &&
- test_when_finished git notes --ref=custom remove topic unmodified &&
+ test_when_finished "rm -f 000?-*" &&
git format-patch --notes=custom --cover-letter --range-diff=$prev \
main..unmodified >actual &&
- test_when_finished "rm 000?-*" &&
grep "## Notes (custom) ##" 0000-* &&
! grep "## Notes ##" 0000-*
'
test_expect_success 'format-patch --range-diff with --no-notes' '
+ test_when_finished "git notes remove topic unmodified || :" &&
git notes add -m "topic note" topic &&
git notes add -m "unmodified note" unmodified &&
- test_when_finished git notes remove topic unmodified &&
+ test_when_finished "rm -f 000?-*" &&
git format-patch --no-notes --cover-letter --range-diff=$prev \
main..unmodified >actual &&
- test_when_finished "rm 000?-*" &&
test_line_count = 5 actual &&
test_grep "^Range-diff:$" 0000-* &&
grep "= 1: .* s/5/A" 0000-* &&
@@ -739,12 +740,12 @@ test_expect_success 'format-patch --range-diff with --no-notes' '
'
test_expect_success 'format-patch --range-diff with --notes' '
+ test_when_finished "git notes remove topic unmodified || :" &&
git notes add -m "topic note" topic &&
git notes add -m "unmodified note" unmodified &&
- test_when_finished git notes remove topic unmodified &&
+ test_when_finished "rm -f 000?-*" &&
git format-patch --notes --cover-letter --range-diff=$prev \
main..unmodified >actual &&
- test_when_finished "rm 000?-*" &&
test_line_count = 5 actual &&
test_grep "^Range-diff:$" 0000-* &&
grep "= 1: .* s/5/A" 0000-* &&
@@ -767,13 +768,13 @@ test_expect_success 'format-patch --range-diff with --notes' '
'
test_expect_success 'format-patch --range-diff with format.notes config' '
+ test_when_finished "git notes remove topic unmodified || :" &&
git notes add -m "topic note" topic &&
git notes add -m "unmodified note" unmodified &&
- test_when_finished git notes remove topic unmodified &&
test_config format.notes true &&
+ test_when_finished "rm -f 000?-*" &&
git format-patch --cover-letter --range-diff=$prev \
main..unmodified >actual &&
- test_when_finished "rm 000?-*" &&
test_line_count = 5 actual &&
test_grep "^Range-diff:$" 0000-* &&
grep "= 1: .* s/5/A" 0000-* &&
@@ -796,15 +797,15 @@ test_expect_success 'format-patch --range-diff with format.notes config' '
'
test_expect_success 'format-patch --range-diff with multiple notes' '
+ test_when_finished "git notes --ref=note1 remove topic unmodified || :" &&
git notes --ref=note1 add -m "topic note1" topic &&
git notes --ref=note1 add -m "unmodified note1" unmodified &&
- test_when_finished git notes --ref=note1 remove topic unmodified &&
+ test_when_finished "git notes --ref=note2 remove topic unmodified || :" &&
git notes --ref=note2 add -m "topic note2" topic &&
git notes --ref=note2 add -m "unmodified note2" unmodified &&
- test_when_finished git notes --ref=note2 remove topic unmodified &&
+ test_when_finished "rm -f 000?-*" &&
git format-patch --notes=note1 --notes=note2 --cover-letter --range-diff=$prev \
main..unmodified >actual &&
- test_when_finished "rm 000?-*" &&
test_line_count = 5 actual &&
test_grep "^Range-diff:$" 0000-* &&
grep "= 1: .* s/5/A" 0000-* &&
diff --git a/t/t3310-notes-merge-manual-resolve.sh b/t/t3310-notes-merge-manual-resolve.sh
index 597df5ebc0..04866b89be 100755
--- a/t/t3310-notes-merge-manual-resolve.sh
+++ b/t/t3310-notes-merge-manual-resolve.sh
@@ -5,6 +5,7 @@
test_description='Test notes merging with manual conflict resolution'
+TEST_PASSES_SANITIZE_LEAK=true
. ./test-lib.sh
# Set up a notes merge scenario with different kinds of conflicts
diff --git a/t/t3311-notes-merge-fanout.sh b/t/t3311-notes-merge-fanout.sh
index 5b675417e9..ce4144db0f 100755
--- a/t/t3311-notes-merge-fanout.sh
+++ b/t/t3311-notes-merge-fanout.sh
@@ -5,6 +5,7 @@
test_description='Test notes merging at various fanout levels'
+TEST_PASSES_SANITIZE_LEAK=true
. ./test-lib.sh
verify_notes () {
diff --git a/t/t3400-rebase.sh b/t/t3400-rebase.sh
index ae34bfad60..09f230eefb 100755
--- a/t/t3400-rebase.sh
+++ b/t/t3400-rebase.sh
@@ -145,7 +145,9 @@ test_expect_success 'Show verbose error when HEAD could not be detached' '
test_when_finished "rm -f B" &&
test_must_fail git rebase topic 2>output.err >output.out &&
test_grep "The following untracked working tree files would be overwritten by checkout:" output.err &&
- test_grep B output.err
+ test_grep B output.err &&
+ test_must_fail git rebase --quit 2>err &&
+ test_grep "no rebase in progress" err
'
test_expect_success 'fail when upstream arg is missing and not on branch' '
@@ -235,6 +237,12 @@ test_expect_success 'rebase --merge -q is quiet' '
test_must_be_empty output.out
'
+test_expect_success 'rebase --exec -q is quiet' '
+ git checkout -B quiet topic &&
+ git rebase --exec true -q main >output.out 2>&1 &&
+ test_must_be_empty output.out
+'
+
test_expect_success 'Rebase a commit that sprinkles CRs in' '
(
echo "One" &&
@@ -422,7 +430,9 @@ test_expect_success 'refuse to switch to branch checked out elsewhere' '
git checkout main &&
git worktree add wt &&
test_must_fail git -C wt rebase main main 2>err &&
- test_grep "already used by worktree at" err
+ test_grep "already used by worktree at" err &&
+ test_must_fail git -C wt rebase --quit 2>err &&
+ test_grep "no rebase in progress" err
'
test_expect_success 'rebase when inside worktree subdirectory' '
diff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh
index f92baad138..f171af3061 100755
--- a/t/t3404-rebase-interactive.sh
+++ b/t/t3404-rebase-interactive.sh
@@ -26,6 +26,7 @@ Initial setup:
touch file "conflict".
'
+TEST_PASSES_SANITIZE_LEAK=true
. ./test-lib.sh
. "$TEST_DIRECTORY"/lib-rebase.sh
diff --git a/t/t3413-rebase-hook.sh b/t/t3413-rebase-hook.sh
index e8456831e8..426ff098e1 100755
--- a/t/t3413-rebase-hook.sh
+++ b/t/t3413-rebase-hook.sh
@@ -110,7 +110,9 @@ test_expect_success 'pre-rebase hook stops rebase (1)' '
git reset --hard side &&
test_must_fail git rebase main &&
test "z$(git symbolic-ref HEAD)" = zrefs/heads/test &&
- test 0 = $(git rev-list HEAD...side | wc -l)
+ test 0 = $(git rev-list HEAD...side | wc -l) &&
+ test_must_fail git rebase --quit 2>err &&
+ test_grep "no rebase in progress" err
'
test_expect_success 'pre-rebase hook stops rebase (2)' '
diff --git a/t/t3420-rebase-autostash.sh b/t/t3420-rebase-autostash.sh
index 63e400b89f..b43046b3b0 100755
--- a/t/t3420-rebase-autostash.sh
+++ b/t/t3420-rebase-autostash.sh
@@ -82,6 +82,46 @@ testrebase () {
type=$1
dotest=$2
+ test_expect_success "rebase$type: restore autostash when pre-rebase hook fails" '
+ git checkout -f feature-branch &&
+ test_hook pre-rebase <<-\EOF &&
+ exit 1
+ EOF
+
+ echo changed >file0 &&
+ test_must_fail git rebase $type --autostash -f HEAD^ &&
+ test_must_fail git rebase --quit 2>err &&
+ test_grep "no rebase in progress" err &&
+ echo changed >expect &&
+ test_cmp expect file0
+ '
+
+ test_expect_success "rebase$type: restore autostash when checkout onto fails" '
+ git checkout -f --detach feature-branch &&
+ echo uncommitted-content >file0 &&
+ echo untracked >file4 &&
+ test_when_finished "rm file4" &&
+ test_must_fail git rebase $type --autostash \
+ unrelated-onto-branch &&
+ test_must_fail git rebase --quit 2>err &&
+ test_grep "no rebase in progress" err &&
+ echo uncommitted-content >expect &&
+ test_cmp expect file0
+ '
+
+ test_expect_success "rebase$type: restore autostash when branch checkout fails" '
+ git checkout -f unrelated-onto-branch^ &&
+ echo uncommitted-content >file0 &&
+ echo untracked >file4 &&
+ test_when_finished "rm file4" &&
+ test_must_fail git rebase $type --autostash HEAD \
+ unrelated-onto-branch &&
+ test_must_fail git rebase --quit 2>err &&
+ test_grep "no rebase in progress" err &&
+ echo uncommitted-content >expect &&
+ test_cmp expect file0
+ '
+
test_expect_success "rebase$type: dirty worktree, --no-autostash" '
test_config rebase.autostash true &&
git reset --hard &&
diff --git a/t/t3430-rebase-merges.sh b/t/t3430-rebase-merges.sh
index 36ca126bcd..2aa8593f77 100755
--- a/t/t3430-rebase-merges.sh
+++ b/t/t3430-rebase-merges.sh
@@ -392,8 +392,7 @@ test_expect_success 'refuse to merge ancestors of HEAD' '
test_expect_success 'root commits' '
git checkout --orphan unrelated &&
- (GIT_AUTHOR_NAME="Parsnip" GIT_AUTHOR_EMAIL="root@example.com" \
- test_commit second-root) &&
+ test_commit --author "Parsnip <root@example.com>" second-root &&
test_commit third-root &&
cat >script-from-scratch <<-\EOF &&
pick third-root
diff --git a/t/t3435-rebase-gpg-sign.sh b/t/t3435-rebase-gpg-sign.sh
index 6aa2aeb628..6e329fea7c 100755
--- a/t/t3435-rebase-gpg-sign.sh
+++ b/t/t3435-rebase-gpg-sign.sh
@@ -8,6 +8,7 @@ test_description='test rebase --[no-]gpg-sign'
GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
+TEST_PASSES_SANITIZE_LEAK=true
. ./test-lib.sh
. "$TEST_DIRECTORY/lib-rebase.sh"
. "$TEST_DIRECTORY/lib-gpg.sh"
diff --git a/t/t3436-rebase-more-options.sh b/t/t3436-rebase-more-options.sh
index 94671d3c46..4d9744e5fc 100755
--- a/t/t3436-rebase-more-options.sh
+++ b/t/t3436-rebase-more-options.sh
@@ -5,6 +5,7 @@
test_description='tests to ensure compatibility between am and interactive backends'
+TEST_PASSES_SANITIZE_LEAK=true
. ./test-lib.sh
. "$TEST_DIRECTORY"/lib-rebase.sh
diff --git a/t/t3507-cherry-pick-conflict.sh b/t/t3507-cherry-pick-conflict.sh
index f3947b400a..10e9c91dbb 100755
--- a/t/t3507-cherry-pick-conflict.sh
+++ b/t/t3507-cherry-pick-conflict.sh
@@ -13,6 +13,7 @@ GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
TEST_CREATE_REPO_NO_TEMPLATE=1
+TEST_PASSES_SANITIZE_LEAK=true
. ./test-lib.sh
pristine_detach () {
diff --git a/t/t3510-cherry-pick-sequence.sh b/t/t3510-cherry-pick-sequence.sh
index 7eb52b12ed..93c725bac3 100755
--- a/t/t3510-cherry-pick-sequence.sh
+++ b/t/t3510-cherry-pick-sequence.sh
@@ -12,6 +12,7 @@ test_description='Test cherry-pick continuation features
'
+TEST_PASSES_SANITIZE_LEAK=true
. ./test-lib.sh
# Repeat first match 10 times
diff --git a/t/t3650-replay-basics.sh b/t/t3650-replay-basics.sh
index 389670262e..12bd3db4cb 100755
--- a/t/t3650-replay-basics.sh
+++ b/t/t3650-replay-basics.sh
@@ -5,6 +5,7 @@ test_description='basic git replay tests'
GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
+TEST_PASSES_SANITIZE_LEAK=true
. ./test-lib.sh
GIT_AUTHOR_NAME=author@name
diff --git a/t/t3701-add-interactive.sh b/t/t3701-add-interactive.sh
index 9a48933cec..718438ffc7 100755
--- a/t/t3701-add-interactive.sh
+++ b/t/t3701-add-interactive.sh
@@ -575,6 +575,54 @@ test_expect_success 'navigate to hunk via regex / pattern' '
test_cmp expect actual.trimmed
'
+test_expect_success 'print again the hunk' '
+ test_when_finished "git reset" &&
+ tr _ " " >expect <<-EOF &&
+ +15
+ 20
+ (1/2) Stage this hunk [y,n,q,a,d,j,J,g,/,e,p,?]? @@ -1,2 +1,3 @@
+ 10
+ +15
+ 20
+ (1/2) Stage this hunk [y,n,q,a,d,j,J,g,/,e,p,?]?_
+ EOF
+ test_write_lines s y g 1 p | git add -p >actual &&
+ tail -n 7 <actual >actual.trimmed &&
+ test_cmp expect actual.trimmed
+'
+
+test_expect_success TTY 'print again the hunk (PAGER)' '
+ test_when_finished "git reset" &&
+ cat >expect <<-EOF &&
+ <GREEN>+<RESET><GREEN>15<RESET>
+ 20<RESET>
+ <BOLD;BLUE>(1/2) Stage this hunk [y,n,q,a,d,j,J,g,/,e,p,?]? <RESET>PAGER <CYAN>@@ -1,2 +1,3 @@<RESET>
+ PAGER 10<RESET>
+ PAGER <GREEN>+<RESET><GREEN>15<RESET>
+ PAGER 20<RESET>
+ <BOLD;BLUE>(1/2) Stage this hunk [y,n,q,a,d,j,J,g,/,e,p,?]? <RESET>
+ EOF
+ test_write_lines s y g 1 P |
+ (
+ GIT_PAGER="sed s/^/PAGER\ /" &&
+ export GIT_PAGER &&
+ test_terminal git add -p >actual
+ ) &&
+ tail -n 7 <actual | test_decode_color >actual.trimmed &&
+ test_cmp expect actual.trimmed
+'
+
+test_expect_success TTY 'P handles SIGPIPE when writing to pager' '
+ test_when_finished "rm -f huge_file; git reset" &&
+ printf "\n%2500000s" Y >huge_file &&
+ git add -N huge_file &&
+ test_write_lines P q | (
+ GIT_PAGER="head -n 1" &&
+ export GIT_PAGER &&
+ test_terminal git add -p
+ )
+'
+
test_expect_success 'split hunk "add -p (edit)"' '
# Split, say Edit and do nothing. Then:
#
diff --git a/t/t3705-add-sparse-checkout.sh b/t/t3705-add-sparse-checkout.sh
index 2bade9e804..6ae45a788d 100755
--- a/t/t3705-add-sparse-checkout.sh
+++ b/t/t3705-add-sparse-checkout.sh
@@ -2,6 +2,7 @@
test_description='git add in sparse checked out working trees'
+TEST_PASSES_SANITIZE_LEAK=true
. ./test-lib.sh
SPARSE_ENTRY_BLOB=""
diff --git a/t/t3903-stash.sh b/t/t3903-stash.sh
index 74666ff3e4..c87592ee2f 100755
--- a/t/t3903-stash.sh
+++ b/t/t3903-stash.sh
@@ -8,6 +8,7 @@ test_description='Test git stash'
GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
+TEST_PASSES_SANITIZE_LEAK=true
. ./test-lib.sh
. "$TEST_DIRECTORY"/lib-unique-files.sh
diff --git a/t/t3904-stash-patch.sh b/t/t3904-stash-patch.sh
index 368fc2a6cc..aa5019fd6c 100755
--- a/t/t3904-stash-patch.sh
+++ b/t/t3904-stash-patch.sh
@@ -1,6 +1,8 @@
#!/bin/sh
test_description='stash -p'
+
+TEST_PASSES_SANITIZE_LEAK=true
. ./lib-patch-mode.sh
test_expect_success 'setup' '
diff --git a/t/t3905-stash-include-untracked.sh b/t/t3905-stash-include-untracked.sh
index 1289ae3e07..a1733f45c3 100755
--- a/t/t3905-stash-include-untracked.sh
+++ b/t/t3905-stash-include-untracked.sh
@@ -5,6 +5,7 @@
test_description='Test git stash --include-untracked'
+TEST_PASSES_SANITIZE_LEAK=true
. ./test-lib.sh
test_expect_success 'stash save --include-untracked some dirty working directory' '
diff --git a/t/t4008-diff-break-rewrite.sh b/t/t4008-diff-break-rewrite.sh
index 562aaf3a2a..b0ef0026e0 100755
--- a/t/t4008-diff-break-rewrite.sh
+++ b/t/t4008-diff-break-rewrite.sh
@@ -21,6 +21,8 @@ With -B, this should be detected as two complete rewrites.
Further, with -B and -M together, these should turn into two renames.
'
+
+TEST_PASSES_SANITIZE_LEAK=true
. ./test-lib.sh
. "$TEST_DIRECTORY"/lib-diff.sh ;# test-lib chdir's into trash
diff --git a/t/t4013-diff-various.sh b/t/t4013-diff-various.sh
index 3855d68dbc..87d248d034 100755
--- a/t/t4013-diff-various.sh
+++ b/t/t4013-diff-various.sh
@@ -8,6 +8,7 @@ test_description='Various diff formatting options'
GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=master
export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
+TEST_PASSES_SANITIZE_LEAK=true
. ./test-lib.sh
. "$TEST_DIRECTORY"/lib-diff.sh
diff --git a/t/t4014-format-patch.sh b/t/t4014-format-patch.sh
index 884f83fb8a..1c46e963e4 100755
--- a/t/t4014-format-patch.sh
+++ b/t/t4014-format-patch.sh
@@ -8,6 +8,7 @@ test_description='various format-patch tests'
GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
+TEST_PASSES_SANITIZE_LEAK=true
. ./test-lib.sh
. "$TEST_DIRECTORY"/lib-terminal.sh
diff --git a/t/t4017-diff-retval.sh b/t/t4017-diff-retval.sh
index d644310e22..1cea73ef5a 100755
--- a/t/t4017-diff-retval.sh
+++ b/t/t4017-diff-retval.sh
@@ -145,6 +145,14 @@ test_expect_success 'option errors are not confused by --exit-code' '
for option in --exit-code --quiet
do
+ test_expect_success "git diff $option returns 1 for changed binary file" "
+ test_when_finished 'rm -f .gitattributes' &&
+ git reset --hard &&
+ echo a binary >.gitattributes &&
+ echo 2 >>a &&
+ test_expect_code 1 git diff $option
+ "
+
test_expect_success "git diff $option returns 1 for copied file" "
git reset --hard &&
cp a copy &&
diff --git a/t/t4018-diff-funcname.sh b/t/t4018-diff-funcname.sh
index e026fac1f4..8128c30e7f 100755
--- a/t/t4018-diff-funcname.sh
+++ b/t/t4018-diff-funcname.sh
@@ -5,6 +5,7 @@
test_description='Test custom diff function name patterns'
+TEST_PASSES_SANITIZE_LEAK=true
. ./test-lib.sh
test_expect_success 'setup' '
diff --git a/t/t4022-diff-rewrite.sh b/t/t4022-diff-rewrite.sh
index 6fed993ea0..77bc36d5d8 100755
--- a/t/t4022-diff-rewrite.sh
+++ b/t/t4022-diff-rewrite.sh
@@ -2,6 +2,7 @@
test_description='rewrite diff'
+TEST_PASSES_SANITIZE_LEAK=true
. ./test-lib.sh
. "$TEST_DIRECTORY"/lib-diff-data.sh
diff --git a/t/t4023-diff-rename-typechange.sh b/t/t4023-diff-rename-typechange.sh
index 787605ce3f..e6f4fe441e 100755
--- a/t/t4023-diff-rename-typechange.sh
+++ b/t/t4023-diff-rename-typechange.sh
@@ -2,6 +2,7 @@
test_description='typechange rename detection'
+TEST_PASSES_SANITIZE_LEAK=true
. ./test-lib.sh
. "$TEST_DIRECTORY"/lib-diff.sh
diff --git a/t/t4030-diff-textconv.sh b/t/t4030-diff-textconv.sh
index a39a626664..29f6d610c2 100755
--- a/t/t4030-diff-textconv.sh
+++ b/t/t4030-diff-textconv.sh
@@ -1,6 +1,8 @@
#!/bin/sh
test_description='diff.*.textconv tests'
+
+TEST_PASSES_SANITIZE_LEAK=true
. ./test-lib.sh
find_diff() {
diff --git a/t/t4031-diff-rewrite-binary.sh b/t/t4031-diff-rewrite-binary.sh
index c4394a27b5..1b8cd3e4c9 100755
--- a/t/t4031-diff-rewrite-binary.sh
+++ b/t/t4031-diff-rewrite-binary.sh
@@ -2,6 +2,7 @@
test_description='rewrite diff on binary file'
+TEST_PASSES_SANITIZE_LEAK=true
. ./test-lib.sh
# We must be large enough to meet the MINIMUM_BREAK_SIZE
diff --git a/t/t4034-diff-words.sh b/t/t4034-diff-words.sh
index 74586f3813..4dcd7e9925 100755
--- a/t/t4034-diff-words.sh
+++ b/t/t4034-diff-words.sh
@@ -70,7 +70,7 @@ test_language_driver () {
word_diff --color-words
'
test_expect_success "diff driver '$lang' in Islandic" '
- LANG=is_IS.UTF-8 LANGUAGE=is LC_ALL="$is_IS_locale" \
+ test_env LANG=is_IS.UTF-8 LANGUAGE=is LC_ALL="$is_IS_locale" \
word_diff --color-words
'
}
diff --git a/t/t4042-diff-textconv-caching.sh b/t/t4042-diff-textconv-caching.sh
index 8ebfa3c1be..a179205394 100755
--- a/t/t4042-diff-textconv-caching.sh
+++ b/t/t4042-diff-textconv-caching.sh
@@ -1,6 +1,8 @@
#!/bin/sh
test_description='test textconv caching'
+
+TEST_PASSES_SANITIZE_LEAK=true
. ./test-lib.sh
cat >helper <<'EOF'
diff --git a/t/t4048-diff-combined-binary.sh b/t/t4048-diff-combined-binary.sh
index 0260cf64f5..f399484bce 100755
--- a/t/t4048-diff-combined-binary.sh
+++ b/t/t4048-diff-combined-binary.sh
@@ -4,6 +4,7 @@ test_description='combined and merge diff handle binary files and textconv'
GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
+TEST_PASSES_SANITIZE_LEAK=true
. ./test-lib.sh
test_expect_success 'setup binary merge conflict' '
diff --git a/t/t4056-diff-order.sh b/t/t4056-diff-order.sh
index aec1d9d1b4..32c5fcb9a2 100755
--- a/t/t4056-diff-order.sh
+++ b/t/t4056-diff-order.sh
@@ -5,6 +5,7 @@ test_description='diff order & rotate'
GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
+TEST_PASSES_SANITIZE_LEAK=true
. ./test-lib.sh
create_files () {
diff --git a/t/t4064-diff-oidfind.sh b/t/t4064-diff-oidfind.sh
index 6d8c8986fc..846f285f77 100755
--- a/t/t4064-diff-oidfind.sh
+++ b/t/t4064-diff-oidfind.sh
@@ -1,6 +1,8 @@
#!/bin/sh
test_description='test finding specific blobs in the revision walking'
+
+TEST_PASSES_SANITIZE_LEAK=true
. ./test-lib.sh
test_expect_success 'setup ' '
diff --git a/t/t4065-diff-anchored.sh b/t/t4065-diff-anchored.sh
index b3f510f040..647537c12e 100755
--- a/t/t4065-diff-anchored.sh
+++ b/t/t4065-diff-anchored.sh
@@ -2,6 +2,7 @@
test_description='anchored diff algorithm'
+TEST_PASSES_SANITIZE_LEAK=true
. ./test-lib.sh
test_expect_success '--anchored' '
diff --git a/t/t4068-diff-symmetric-merge-base.sh b/t/t4068-diff-symmetric-merge-base.sh
index eff63c16b0..4d6565e728 100755
--- a/t/t4068-diff-symmetric-merge-base.sh
+++ b/t/t4068-diff-symmetric-merge-base.sh
@@ -5,6 +5,7 @@ test_description='behavior of diff with symmetric-diff setups and --merge-base'
GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
+TEST_PASSES_SANITIZE_LEAK=true
. ./test-lib.sh
# build these situations:
diff --git a/t/t4069-remerge-diff.sh b/t/t4069-remerge-diff.sh
index ca8f999cab..df342850a0 100755
--- a/t/t4069-remerge-diff.sh
+++ b/t/t4069-remerge-diff.sh
@@ -2,6 +2,7 @@
test_description='remerge-diff handling'
+TEST_PASSES_SANITIZE_LEAK=true
. ./test-lib.sh
# This test is ort-specific
diff --git a/t/t4107-apply-ignore-whitespace.sh b/t/t4107-apply-ignore-whitespace.sh
index ac72eeaf27..5e6e203aa5 100755
--- a/t/t4107-apply-ignore-whitespace.sh
+++ b/t/t4107-apply-ignore-whitespace.sh
@@ -3,9 +3,9 @@
# Copyright (c) 2009 Giuseppe Bilotta
#
-test_description='git-apply --ignore-whitespace.
+test_description='git-apply --ignore-whitespace.'
-'
+TEST_PASSES_SANITIZE_LEAK=true
. ./test-lib.sh
# This primes main.c file that indents without using HT at all.
diff --git a/t/t4108-apply-threeway.sh b/t/t4108-apply-threeway.sh
index c558282bc0..c6302163d8 100755
--- a/t/t4108-apply-threeway.sh
+++ b/t/t4108-apply-threeway.sh
@@ -5,6 +5,7 @@ test_description='git apply --3way'
GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
+TEST_PASSES_SANITIZE_LEAK=true
. ./test-lib.sh
print_sanitized_conflicted_diff () {
@@ -81,6 +82,46 @@ test_expect_success 'apply with --3way with merge.conflictStyle = diff3' '
test_apply_with_3way
'
+test_apply_with_3way_favoritism () {
+ apply_arg=$1
+ merge_arg=$2
+
+ # Merging side should be similar to applying this patch
+ git diff ...side >P.diff &&
+
+ # The corresponding conflicted merge
+ git reset --hard &&
+ git checkout main^0 &&
+ git merge --no-commit $merge_arg side &&
+ git ls-files -s >expect.ls &&
+ print_sanitized_conflicted_diff >expect.diff &&
+
+ # should apply successfully
+ git reset --hard &&
+ git checkout main^0 &&
+ git apply --index --3way $apply_arg P.diff &&
+ git ls-files -s >actual.ls &&
+ print_sanitized_conflicted_diff >actual.diff &&
+
+ # The result should resemble the corresponding merge
+ test_cmp expect.ls actual.ls &&
+ test_cmp expect.diff actual.diff
+}
+
+test_expect_success 'apply with --3way --ours' '
+ test_apply_with_3way_favoritism --ours -Xours
+'
+
+test_expect_success 'apply with --3way --theirs' '
+ test_apply_with_3way_favoritism --theirs -Xtheirs
+'
+
+test_expect_success 'apply with --3way --union' '
+ echo "* merge=union" >.gitattributes &&
+ test_apply_with_3way_favoritism --union &&
+ rm .gitattributes
+'
+
test_expect_success 'apply with --3way with rerere enabled' '
test_config rerere.enabled true &&
diff --git a/t/t4124-apply-ws-rule.sh b/t/t4124-apply-ws-rule.sh
index 485c7d2d12..cdffee0273 100755
--- a/t/t4124-apply-ws-rule.sh
+++ b/t/t4124-apply-ws-rule.sh
@@ -2,6 +2,7 @@
test_description='core.whitespace rules and git apply'
+TEST_PASSES_SANITIZE_LEAK=true
. ./test-lib.sh
prepare_test_file () {
diff --git a/t/t4125-apply-ws-fuzz.sh b/t/t4125-apply-ws-fuzz.sh
index 090987c89b..f248cc2a00 100755
--- a/t/t4125-apply-ws-fuzz.sh
+++ b/t/t4125-apply-ws-fuzz.sh
@@ -2,6 +2,7 @@
test_description='applying patch that has broken whitespaces in context'
+TEST_PASSES_SANITIZE_LEAK=true
. ./test-lib.sh
test_expect_success setup '
diff --git a/t/t4138-apply-ws-expansion.sh b/t/t4138-apply-ws-expansion.sh
index 8bbf8260fa..7981931b4e 100755
--- a/t/t4138-apply-ws-expansion.sh
+++ b/t/t4138-apply-ws-expansion.sh
@@ -5,6 +5,7 @@
test_description='git apply test patches with whitespace expansion.'
+TEST_PASSES_SANITIZE_LEAK=true
. ./test-lib.sh
test_expect_success setup '
diff --git a/t/t4150-am.sh b/t/t4150-am.sh
index 5e2b6c80ea..232e1394e8 100755
--- a/t/t4150-am.sh
+++ b/t/t4150-am.sh
@@ -5,6 +5,7 @@ test_description='git am running'
GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
+TEST_PASSES_SANITIZE_LEAK=true
. ./test-lib.sh
test_expect_success 'setup: messages' '
diff --git a/t/t4200-rerere.sh b/t/t4200-rerere.sh
index b0a3e84984..213b36fb96 100755
--- a/t/t4200-rerere.sh
+++ b/t/t4200-rerere.sh
@@ -25,6 +25,7 @@ test_description='git rerere
GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
+TEST_PASSES_SANITIZE_LEAK=true
. ./test-lib.sh
test_expect_success 'setup' '
diff --git a/t/t4201-shortlog.sh b/t/t4201-shortlog.sh
index f698d0c9ad..c20c885724 100755
--- a/t/t4201-shortlog.sh
+++ b/t/t4201-shortlog.sh
@@ -9,6 +9,7 @@ test_description='git shortlog
GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
+TEST_PASSES_SANITIZE_LEAK=true
. ./test-lib.sh
test_expect_success 'setup' '
diff --git a/t/t4203-mailmap.sh b/t/t4203-mailmap.sh
index 79e5f42760..2265ff8872 100755
--- a/t/t4203-mailmap.sh
+++ b/t/t4203-mailmap.sh
@@ -72,12 +72,46 @@ test_expect_success 'check-mailmap --stdin arguments: mapping' '
test_cmp expect actual
'
-test_expect_success 'check-mailmap bogus contact' '
- test_must_fail git check-mailmap bogus
+test_expect_success 'check-mailmap simple address: mapping' '
+ test_when_finished "rm .mailmap" &&
+ cat >.mailmap <<-EOF &&
+ New Name <$GIT_AUTHOR_EMAIL>
+ EOF
+ cat .mailmap >expect &&
+ git check-mailmap "$GIT_AUTHOR_EMAIL" >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'check-mailmap --stdin simple address: mapping' '
+ test_when_finished "rm .mailmap" &&
+ cat >.mailmap <<-EOF &&
+ New Name <$GIT_AUTHOR_EMAIL>
+ EOF
+ cat >stdin <<-EOF &&
+ $GIT_AUTHOR_EMAIL
+ EOF
+ cat .mailmap >expect &&
+ git check-mailmap --stdin <stdin >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'check-mailmap simple address: no mapping' '
+ cat >expect <<-EOF &&
+ <bugs@company.xx>
+ EOF
+ git check-mailmap "bugs@company.xx" >actual &&
+ test_cmp expect actual
'
-test_expect_success 'check-mailmap bogus contact --stdin' '
- test_must_fail git check-mailmap --stdin bogus </dev/null
+test_expect_success 'check-mailmap --stdin simple address: no mapping' '
+ cat >expect <<-EOF &&
+ <bugs@company.xx>
+ EOF
+ cat >stdin <<-EOF &&
+ bugs@company.xx
+ EOF
+ git check-mailmap --stdin <stdin >actual &&
+ test_cmp expect actual
'
test_expect_success 'No mailmap' '
diff --git a/t/t4204-patch-id.sh b/t/t4204-patch-id.sh
index 605faea0c7..8e0f283c2b 100755
--- a/t/t4204-patch-id.sh
+++ b/t/t4204-patch-id.sh
@@ -5,6 +5,7 @@ test_description='git patch-id'
GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
+TEST_PASSES_SANITIZE_LEAK=true
. ./test-lib.sh
test_expect_success 'setup' '
diff --git a/t/t4205-log-pretty-formats.sh b/t/t4205-log-pretty-formats.sh
index 158b49d4b6..eb63ce011f 100755
--- a/t/t4205-log-pretty-formats.sh
+++ b/t/t4205-log-pretty-formats.sh
@@ -5,6 +5,8 @@
#
test_description='Test pretty formats'
+
+TEST_PASSES_SANITIZE_LEAK=true
. ./test-lib.sh
# Tested non-UTF-8 encoding
diff --git a/t/t4209-log-pickaxe.sh b/t/t4209-log-pickaxe.sh
index 64e1623733..b42fdc54fc 100755
--- a/t/t4209-log-pickaxe.sh
+++ b/t/t4209-log-pickaxe.sh
@@ -1,6 +1,8 @@
#!/bin/sh
test_description='log --grep/--author/--regexp-ignore-case/-S/-G'
+
+TEST_PASSES_SANITIZE_LEAK=true
. ./test-lib.sh
test_log () {
diff --git a/t/t4301-merge-tree-write-tree.sh b/t/t4301-merge-tree-write-tree.sh
index eea19907b5..37f1cd7364 100755
--- a/t/t4301-merge-tree-write-tree.sh
+++ b/t/t4301-merge-tree-write-tree.sh
@@ -2,6 +2,7 @@
test_description='git merge-tree --write-tree'
+TEST_PASSES_SANITIZE_LEAK=true
. ./test-lib.sh
# This test is ort-specific
diff --git a/t/t5000-tar-tree.sh b/t/t5000-tar-tree.sh
index 72b8d0ff02..b9fda973f7 100755
--- a/t/t5000-tar-tree.sh
+++ b/t/t5000-tar-tree.sh
@@ -25,6 +25,7 @@ commit id embedding:
'
TEST_CREATE_REPO_NO_TEMPLATE=1
+TEST_PASSES_SANITIZE_LEAK=true
. ./test-lib.sh
SUBSTFORMAT=%H%n
@@ -136,6 +137,8 @@ test_expect_success 'end-of-options is correctly eaten' '
test_expect_success 'populate workdir' '
mkdir a &&
+ echo "a files_named_a" >.gitattributes &&
+ git add .gitattributes &&
echo simple textfile >a/a &&
ten=0123456789 &&
hundred="$ten$ten$ten$ten$ten$ten$ten$ten$ten$ten" &&
@@ -449,6 +452,16 @@ test_expect_success 'allow pathspecs that resolve to the current directory' '
test_cmp expect actual
'
+test_expect_success 'attr pathspec in bare repo' '
+ test_expect_code 0 git --git-dir=bare.git archive -v HEAD \
+ ":(attr:files_named_a)" >/dev/null 2>actual &&
+ cat >expect <<-\EOF &&
+ a/
+ a/a
+ EOF
+ test_cmp expect actual
+'
+
# Pull the size and date of each entry in a tarfile using the system tar.
#
# We'll pull out only the year from the date; that avoids any question of
diff --git a/t/t5003-archive-zip.sh b/t/t5003-archive-zip.sh
index 961c6aac25..01f591c99b 100755
--- a/t/t5003-archive-zip.sh
+++ b/t/t5003-archive-zip.sh
@@ -3,6 +3,7 @@
test_description='git archive --format=zip test'
TEST_CREATE_REPO_NO_TEMPLATE=1
+TEST_PASSES_SANITIZE_LEAK=true
. ./test-lib.sh
SUBSTFORMAT=%H%n
diff --git a/t/t5100-mailinfo.sh b/t/t5100-mailinfo.sh
index c8d0655454..065156c1f3 100755
--- a/t/t5100-mailinfo.sh
+++ b/t/t5100-mailinfo.sh
@@ -5,6 +5,7 @@
test_description='git mailinfo and git mailsplit test'
+TEST_PASSES_SANITIZE_LEAK=true
. ./test-lib.sh
DATA="$TEST_DIRECTORY/t5100"
diff --git a/t/t5303-pack-corruption-resilience.sh b/t/t5303-pack-corruption-resilience.sh
index 61469ef4a6..e6a43ec9ae 100755
--- a/t/t5303-pack-corruption-resilience.sh
+++ b/t/t5303-pack-corruption-resilience.sh
@@ -44,9 +44,14 @@ create_new_pack() {
}
do_repack() {
+ for f in $pack.*
+ do
+ mv $f "$(echo $f | sed -e 's/pack-/pack-corrupt-/')" || return 1
+ done &&
pack=$(printf "$blob_1\n$blob_2\n$blob_3\n" |
git pack-objects $@ .git/objects/pack/pack) &&
- pack=".git/objects/pack/pack-${pack}"
+ pack=".git/objects/pack/pack-${pack}" &&
+ rm -f .git/objects/pack/pack-corrupt-*
}
do_corrupt_object() {
diff --git a/t/t5304-prune.sh b/t/t5304-prune.sh
index 1f1f664871..e641df0116 100755
--- a/t/t5304-prune.sh
+++ b/t/t5304-prune.sh
@@ -7,6 +7,7 @@ test_description='prune'
GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
+TEST_PASSES_SANITIZE_LEAK=true
. ./test-lib.sh
day=$((60*60*24))
diff --git a/t/t5310-pack-bitmaps.sh b/t/t5310-pack-bitmaps.sh
index d7fd71360e..7044c7d7c6 100755
--- a/t/t5310-pack-bitmaps.sh
+++ b/t/t5310-pack-bitmaps.sh
@@ -2,13 +2,10 @@
test_description='exercise basic bitmap functionality'
+TEST_PASSES_SANITIZE_LEAK=true
. ./test-lib.sh
. "$TEST_DIRECTORY"/lib-bitmap.sh
-# t5310 deals only with single-pack bitmaps, so don't write MIDX bitmaps in
-# their place.
-GIT_TEST_MULTI_PACK_INDEX_WRITE_BITMAP=0
-
# Likewise, allow individual tests to control whether or not they use
# the boundary-based traversal.
sane_unset GIT_TEST_PACK_USE_BITMAP_BOUNDARY_TRAVERSAL
diff --git a/t/t5313-pack-bounds-checks.sh b/t/t5313-pack-bounds-checks.sh
index ceaa6700a2..86fc73f9fb 100755
--- a/t/t5313-pack-bounds-checks.sh
+++ b/t/t5313-pack-bounds-checks.sh
@@ -7,11 +7,11 @@ TEST_PASSES_SANITIZE_LEAK=true
clear_base () {
test_when_finished 'restore_base' &&
- rm -f $base
+ rm -r -f $base
}
restore_base () {
- cp base-backup/* .git/objects/pack/
+ cp -r base-backup/* .git/objects/pack/
}
do_pack () {
@@ -64,9 +64,9 @@ test_expect_success 'set up base packfile and variables' '
git commit -m base &&
git repack -ad &&
base=$(echo .git/objects/pack/*) &&
- chmod +w $base &&
+ chmod -R +w $base &&
mkdir base-backup &&
- cp $base base-backup/ &&
+ cp -r $base base-backup/ &&
object=$(git rev-parse HEAD:file)
'
diff --git a/t/t5318-commit-graph.sh b/t/t5318-commit-graph.sh
index a2b4442660..2916c07e3c 100755
--- a/t/t5318-commit-graph.sh
+++ b/t/t5318-commit-graph.sh
@@ -1,6 +1,8 @@
#!/bin/sh
test_description='commit graph'
+
+TEST_PASSES_SANITIZE_LEAK=true
. ./test-lib.sh
. "$TEST_DIRECTORY"/lib-chunk.sh
diff --git a/t/t5319-multi-pack-index.sh b/t/t5319-multi-pack-index.sh
index ace5ac3b61..fbbc218d04 100755
--- a/t/t5319-multi-pack-index.sh
+++ b/t/t5319-multi-pack-index.sh
@@ -1,10 +1,15 @@
#!/bin/sh
test_description='multi-pack-indexes'
+
+TEST_PASSES_SANITIZE_LEAK=true
. ./test-lib.sh
. "$TEST_DIRECTORY"/lib-chunk.sh
+. "$TEST_DIRECTORY"/lib-midx.sh
GIT_TEST_MULTI_PACK_INDEX=0
+GIT_TEST_MULTI_PACK_INDEX_WRITE_BITMAP=0
+GIT_TEST_MULTI_PACK_INDEX_WRITE_INCREMENTAL=0
objdir=.git/objects
HASH_LEN=$(test_oid rawsz)
@@ -107,30 +112,6 @@ test_expect_success 'write midx with one v1 pack' '
midx_read_expect 1 18 4 $objdir
'
-midx_git_two_modes () {
- git -c core.multiPackIndex=false $1 >expect &&
- git -c core.multiPackIndex=true $1 >actual &&
- if [ "$2" = "sorted" ]
- then
- sort <expect >expect.sorted &&
- mv expect.sorted expect &&
- sort <actual >actual.sorted &&
- mv actual.sorted actual
- fi &&
- test_cmp expect actual
-}
-
-compare_results_with_midx () {
- MSG=$1
- test_expect_success "check normal git operations: $MSG" '
- midx_git_two_modes "rev-list --objects --all" &&
- midx_git_two_modes "log --raw" &&
- midx_git_two_modes "count-objects --verbose" &&
- midx_git_two_modes "cat-file --batch-all-objects --batch-check" &&
- midx_git_two_modes "cat-file --batch-all-objects --batch-check --unordered" sorted
- '
-}
-
test_expect_success 'write midx with one v2 pack' '
git pack-objects --index-version=2,0x40 $objdir/pack/test <obj-list &&
git multi-pack-index --object-dir=$objdir write &&
@@ -600,8 +581,7 @@ test_expect_success 'repack preserves multi-pack-index when creating packs' '
compare_results_with_midx "after repack"
test_expect_success 'multi-pack-index and pack-bitmap' '
- GIT_TEST_MULTI_PACK_INDEX_WRITE_BITMAP=0 \
- git -c repack.writeBitmaps=true repack -ad &&
+ git -c repack.writeBitmaps=true repack -ad &&
git multi-pack-index write &&
git rev-list --test-bitmap HEAD
'
diff --git a/t/t5326-multi-pack-bitmaps.sh b/t/t5326-multi-pack-bitmaps.sh
index 916da389b6..6eaa692f33 100755
--- a/t/t5326-multi-pack-bitmaps.sh
+++ b/t/t5326-multi-pack-bitmaps.sh
@@ -1,13 +1,15 @@
#!/bin/sh
test_description='exercise basic multi-pack bitmap functionality'
+
+TEST_PASSES_SANITIZE_LEAK=true
. ./test-lib.sh
. "${TEST_DIRECTORY}/lib-bitmap.sh"
-# We'll be writing our own midx and bitmaps, so avoid getting confused by the
+# We'll be writing our own MIDX, so avoid getting confused by the
# automatic ones.
GIT_TEST_MULTI_PACK_INDEX=0
-GIT_TEST_MULTI_PACK_INDEX_WRITE_BITMAP=0
+GIT_TEST_MULTI_PACK_INDEX_WRITE_INCREMENTAL=0
# This test exercise multi-pack bitmap functionality where the object order is
# stored and read from a special chunk within the MIDX, so use the default
diff --git a/t/t5327-multi-pack-bitmaps-rev.sh b/t/t5327-multi-pack-bitmaps-rev.sh
index e65e311cd7..9cac03a94b 100755
--- a/t/t5327-multi-pack-bitmaps-rev.sh
+++ b/t/t5327-multi-pack-bitmaps-rev.sh
@@ -5,10 +5,10 @@ test_description='exercise basic multi-pack bitmap functionality (.rev files)'
. ./test-lib.sh
. "${TEST_DIRECTORY}/lib-bitmap.sh"
-# We'll be writing our own midx and bitmaps, so avoid getting confused by the
-# automatic ones.
+# We'll be writing our own MIDX, so avoid getting confused by the automatic
+# ones.
GIT_TEST_MULTI_PACK_INDEX=0
-GIT_TEST_MULTI_PACK_INDEX_WRITE_BITMAP=0
+GIT_TEST_MULTI_PACK_INDEX_WRITE_INCREMENTAL=0
# Unlike t5326, this test exercise multi-pack bitmap functionality where the
# object order is stored in a separate .rev file.
diff --git a/t/t5329-pack-objects-cruft.sh b/t/t5329-pack-objects-cruft.sh
index fc5fedbe9b..445739d06c 100755
--- a/t/t5329-pack-objects-cruft.sh
+++ b/t/t5329-pack-objects-cruft.sh
@@ -1,6 +1,8 @@
#!/bin/sh
test_description='cruft pack related pack-objects tests'
+
+TEST_PASSES_SANITIZE_LEAK=true
. ./test-lib.sh
objdir=.git/objects
diff --git a/t/t5332-multi-pack-reuse.sh b/t/t5332-multi-pack-reuse.sh
index ed823f37bc..955ea42769 100755
--- a/t/t5332-multi-pack-reuse.sh
+++ b/t/t5332-multi-pack-reuse.sh
@@ -6,6 +6,8 @@ TEST_PASSES_SANITIZE_LEAK=true
. ./test-lib.sh
. "$TEST_DIRECTORY"/lib-bitmap.sh
+GIT_TEST_MULTI_PACK_INDEX=0
+GIT_TEST_MULTI_PACK_INDEX_WRITE_INCREMENTAL=0
objdir=.git/objects
packdir=$objdir/pack
@@ -29,20 +31,24 @@ test_pack_objects_reused_all () {
: >trace2.txt &&
GIT_TRACE2_EVENT="$PWD/trace2.txt" \
git pack-objects --stdout --revs --all --delta-base-offset \
- >/dev/null &&
+ >got.pack &&
test_pack_reused "$1" <trace2.txt &&
- test_packs_reused "$2" <trace2.txt
+ test_packs_reused "$2" <trace2.txt &&
+
+ git index-pack --strict -o got.idx got.pack
}
# test_pack_objects_reused <pack-reused> <packs-reused>
test_pack_objects_reused () {
: >trace2.txt &&
GIT_TRACE2_EVENT="$PWD/trace2.txt" \
- git pack-objects --stdout --revs >/dev/null &&
+ git pack-objects --stdout --revs >got.pack &&
test_pack_reused "$1" <trace2.txt &&
- test_packs_reused "$2" <trace2.txt
+ test_packs_reused "$2" <trace2.txt &&
+
+ git index-pack --strict -o got.idx got.pack
}
test_expect_success 'preferred pack is reused for single-pack reuse' '
@@ -230,4 +236,27 @@ test_expect_success 'non-omitted delta in MIDX preferred pack' '
test_pack_objects_reused_all $(wc -l <expect) 1
'
+test_expect_success 'duplicate objects' '
+ git init duplicate-objects &&
+ (
+ cd duplicate-objects &&
+
+ git config pack.allowPackReuse multi &&
+
+ test_commit base &&
+
+ git repack -a &&
+
+ git rev-parse HEAD^{tree} >in &&
+ p="$(git pack-objects $packdir/pack <in)" &&
+
+ git multi-pack-index write --bitmap --preferred-pack=pack-$p.idx &&
+
+ objects_nr="$(git rev-list --count --all --objects)" &&
+ packs_nr="$(find $packdir -type f -name "pack-*.pack" | wc -l)" &&
+
+ test_pack_objects_reused_all $objects_nr $packs_nr
+ )
+'
+
test_done
diff --git a/t/t5333-pseudo-merge-bitmaps.sh b/t/t5333-pseudo-merge-bitmaps.sh
index f052f395a7..1dd6284756 100755
--- a/t/t5333-pseudo-merge-bitmaps.sh
+++ b/t/t5333-pseudo-merge-bitmaps.sh
@@ -390,4 +390,60 @@ test_expect_success 'pseudo-merge reuse' '
)
'
+test_expect_success 'empty pseudo-merge group' '
+ git init pseudo-merge-empty-group &&
+ (
+ cd pseudo-merge-empty-group &&
+
+ # Ensure that a pseudo-merge group with no unstable
+ # commits does not generate an empty pseudo-merge
+ # bitmap.
+ git config bitmapPseudoMerge.empty.pattern refs/ &&
+
+ test_commit base &&
+ git repack -adb &&
+
+ test-tool bitmap dump-pseudo-merges >merges &&
+ test_line_count = 1 merges &&
+
+ test 0 -eq "$(grep -c commits=0 <merges)"
+ )
+'
+
+test_expect_success 'pseudo-merge closure' '
+ git init pseudo-merge-closure &&
+ (
+ cd pseudo-merge-closure &&
+
+ test_commit A &&
+ git repack -d &&
+
+ test_commit B &&
+
+ # Note that the contents of A is packed, but B is not. A
+ # (and the objects reachable from it) are thus visible
+ # to the MIDX, but the same is not true for B and its
+ # objects.
+ #
+ # Ensure that we do not attempt to create a pseudo-merge
+ # for B, depsite it matching the below pseudo-merge
+ # group pattern, as doing so would result in a failure
+ # to write a non-closed bitmap.
+ git config bitmapPseudoMerge.test.pattern refs/ &&
+ git config bitmapPseudoMerge.test.threshold now &&
+
+ git multi-pack-index write --bitmap &&
+
+ test-tool bitmap dump-pseudo-merges >pseudo-merges &&
+ test_line_count = 1 pseudo-merges &&
+
+ git rev-parse A >expect &&
+
+ test-tool bitmap list-commits >actual &&
+ test_cmp expect actual &&
+ test-tool bitmap dump-pseudo-merge-commits 0 >actual &&
+ test_cmp expect actual
+ )
+'
+
test_done
diff --git a/t/t5334-incremental-multi-pack-index.sh b/t/t5334-incremental-multi-pack-index.sh
new file mode 100755
index 0000000000..c3b08acc73
--- /dev/null
+++ b/t/t5334-incremental-multi-pack-index.sh
@@ -0,0 +1,46 @@
+#!/bin/sh
+
+test_description='incremental multi-pack-index'
+. ./test-lib.sh
+. "$TEST_DIRECTORY"/lib-midx.sh
+
+GIT_TEST_MULTI_PACK_INDEX=0
+export GIT_TEST_MULTI_PACK_INDEX
+
+objdir=.git/objects
+packdir=$objdir/pack
+midxdir=$packdir/multi-pack-index.d
+midx_chain=$midxdir/multi-pack-index-chain
+
+test_expect_success 'convert non-incremental MIDX to incremental' '
+ test_commit base &&
+ git repack -ad &&
+ git multi-pack-index write &&
+
+ test_path_is_file $packdir/multi-pack-index &&
+ old_hash="$(midx_checksum $objdir)" &&
+
+ test_commit other &&
+ git repack -d &&
+ git multi-pack-index write --incremental &&
+
+ test_path_is_missing $packdir/multi-pack-index &&
+ test_path_is_file $midx_chain &&
+ test_line_count = 2 $midx_chain &&
+ grep $old_hash $midx_chain
+'
+
+compare_results_with_midx 'incremental MIDX'
+
+test_expect_success 'convert incremental to non-incremental' '
+ test_commit squash &&
+ git repack -d &&
+ git multi-pack-index write &&
+
+ test_path_is_file $packdir/multi-pack-index &&
+ test_dir_is_empty $midxdir
+'
+
+compare_results_with_midx 'non-incremental MIDX conversion'
+
+test_done
diff --git a/t/t5400-send-pack.sh b/t/t5400-send-pack.sh
index 3f81f16e13..248c74d8ef 100755
--- a/t/t5400-send-pack.sh
+++ b/t/t5400-send-pack.sh
@@ -9,6 +9,7 @@ test_description='See why rewinding head breaks send-pack
GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
+TEST_PASSES_SANITIZE_LEAK=true
. ./test-lib.sh
cnt=64
diff --git a/t/t5401-update-hooks.sh b/t/t5401-update-hooks.sh
index d8cadeec73..3c1ea6086e 100755
--- a/t/t5401-update-hooks.sh
+++ b/t/t5401-update-hooks.sh
@@ -4,6 +4,8 @@
#
test_description='Test the update hook infrastructure.'
+
+TEST_PASSES_SANITIZE_LEAK=true
. ./test-lib.sh
test_expect_success setup '
diff --git a/t/t5408-send-pack-stdin.sh b/t/t5408-send-pack-stdin.sh
index e8737df6f9..c3695a4d4e 100755
--- a/t/t5408-send-pack-stdin.sh
+++ b/t/t5408-send-pack-stdin.sh
@@ -1,6 +1,8 @@
#!/bin/sh
test_description='send-pack --stdin tests'
+
+TEST_PASSES_SANITIZE_LEAK=true
. ./test-lib.sh
create_ref () {
diff --git a/t/t5409-colorize-remote-messages.sh b/t/t5409-colorize-remote-messages.sh
index fa5de4500a..516b22fd96 100755
--- a/t/t5409-colorize-remote-messages.sh
+++ b/t/t5409-colorize-remote-messages.sh
@@ -2,6 +2,7 @@
test_description='remote messages are colorized on the client'
+TEST_PASSES_SANITIZE_LEAK=true
. ./test-lib.sh
test_expect_success 'setup' '
diff --git a/t/t5500-fetch-pack.sh b/t/t5500-fetch-pack.sh
index 585ea0ee16..605f17240c 100755
--- a/t/t5500-fetch-pack.sh
+++ b/t/t5500-fetch-pack.sh
@@ -8,6 +8,7 @@ test_description='Testing multi_ack pack fetching'
GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
+TEST_PASSES_SANITIZE_LEAK=true
. ./test-lib.sh
# Test fetch-pack/upload-pack pair.
diff --git a/t/t5501-fetch-push-alternates.sh b/t/t5501-fetch-push-alternates.sh
index 66f19a4ef2..0c8668a1b8 100755
--- a/t/t5501-fetch-push-alternates.sh
+++ b/t/t5501-fetch-push-alternates.sh
@@ -4,6 +4,7 @@ test_description='fetch/push involving alternates'
GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
+TEST_PASSES_SANITIZE_LEAK=true
. ./test-lib.sh
count_objects () {
diff --git a/t/t5505-remote.sh b/t/t5505-remote.sh
index 08424e878e..532035933f 100755
--- a/t/t5505-remote.sh
+++ b/t/t5505-remote.sh
@@ -2,6 +2,7 @@
test_description='git remote porcelain-ish'
+TEST_PASSES_SANITIZE_LEAK=true
. ./test-lib.sh
setup_repository () {
diff --git a/t/t5509-fetch-push-namespaces.sh b/t/t5509-fetch-push-namespaces.sh
index 31553b48df..f029ae0d28 100755
--- a/t/t5509-fetch-push-namespaces.sh
+++ b/t/t5509-fetch-push-namespaces.sh
@@ -4,6 +4,7 @@ test_description='fetch/push involving ref namespaces'
GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
+TEST_PASSES_SANITIZE_LEAK=true
. ./test-lib.sh
test_expect_success setup '
@@ -95,6 +96,7 @@ test_expect_success 'hide namespaced refs with transfer.hideRefs' '
'
test_expect_success 'check that transfer.hideRefs does not match unstripped refs' '
+ git -C pushee pack-refs --all &&
GIT_NAMESPACE=namespace \
git -C pushee -c transfer.hideRefs=refs/namespaces/namespace/refs/tags \
ls-remote "ext::git %s ." >actual &&
@@ -122,6 +124,14 @@ test_expect_success 'try to update a ref that is not hidden' '
git -C original push pushee-namespaced main
'
+test_expect_success 'git-receive-pack(1) with transfer.hideRefs does not match unstripped refs during advertisement' '
+ git -C pushee update-ref refs/namespaces/namespace/refs/heads/foo/1 refs/namespaces/namespace/refs/heads/main &&
+ git -C pushee pack-refs --all &&
+ test_config -C pushee transfer.hideRefs refs/namespaces/namespace/refs/heads/foo &&
+ GIT_TRACE_PACKET="$(pwd)/trace" git -C original push pushee-namespaced main &&
+ test_grep refs/heads/foo/1 trace
+'
+
test_expect_success 'try to update a hidden full ref' '
test_config -C pushee transfer.hideRefs "^refs/namespaces/namespace/refs/heads/main" &&
test_must_fail git -C original push pushee-namespaced main
diff --git a/t/t5510-fetch.sh b/t/t5510-fetch.sh
index 3b3991ab86..0890b9f61c 100755
--- a/t/t5510-fetch.sh
+++ b/t/t5510-fetch.sh
@@ -5,6 +5,7 @@ test_description='Per branch config variables affects "git fetch".
'
+TEST_PASSES_SANITIZE_LEAK=true
. ./test-lib.sh
. "$TEST_DIRECTORY"/lib-bundle.sh
diff --git a/t/t5512-ls-remote.sh b/t/t5512-ls-remote.sh
index bc442ec221..64b3491e4e 100755
--- a/t/t5512-ls-remote.sh
+++ b/t/t5512-ls-remote.sh
@@ -5,6 +5,7 @@ test_description='git ls-remote'
GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
+TEST_PASSES_SANITIZE_LEAK=true
. ./test-lib.sh
generate_references () {
@@ -405,6 +406,7 @@ test_expect_success 'v0 clients can handle multiple symrefs' '
test_expect_success 'helper with refspec capability fails gracefully' '
mkdir test-bin &&
write_script test-bin/git-remote-foo <<-EOF &&
+ read capabilities
echo import
echo refspec ${SQ}*:*${SQ}
EOF
diff --git a/t/t5514-fetch-multiple.sh b/t/t5514-fetch-multiple.sh
index 25772c85c5..579872c258 100755
--- a/t/t5514-fetch-multiple.sh
+++ b/t/t5514-fetch-multiple.sh
@@ -5,6 +5,7 @@ test_description='fetch --all works correctly'
GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
+TEST_PASSES_SANITIZE_LEAK=true
. ./test-lib.sh
setup_repository () {
diff --git a/t/t5516-fetch-push.sh b/t/t5516-fetch-push.sh
index 9d693eb57f..331778bd42 100755
--- a/t/t5516-fetch-push.sh
+++ b/t/t5516-fetch-push.sh
@@ -19,6 +19,7 @@ GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
TEST_CREATE_REPO_NO_TEMPLATE=1
+TEST_PASSES_SANITIZE_LEAK=true
. ./test-lib.sh
D=$(pwd)
diff --git a/t/t5519-push-alternates.sh b/t/t5519-push-alternates.sh
index 20ba604dfd..72e97b15fa 100755
--- a/t/t5519-push-alternates.sh
+++ b/t/t5519-push-alternates.sh
@@ -5,6 +5,7 @@ test_description='push to a repository that borrows from elsewhere'
GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
+TEST_PASSES_SANITIZE_LEAK=true
. ./test-lib.sh
test_expect_success setup '
diff --git a/t/t5520-pull.sh b/t/t5520-pull.sh
index 47534f1062..1098cbd0a1 100755
--- a/t/t5520-pull.sh
+++ b/t/t5520-pull.sh
@@ -5,6 +5,7 @@ test_description='pulling into void'
GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
+TEST_PASSES_SANITIZE_LEAK=true
. ./test-lib.sh
modify () {
diff --git a/t/t5526-fetch-submodules.sh b/t/t5526-fetch-submodules.sh
index 5e566205ba..2cfb5bd6bb 100755
--- a/t/t5526-fetch-submodules.sh
+++ b/t/t5526-fetch-submodules.sh
@@ -6,6 +6,7 @@ test_description='Recursive "git fetch" for submodules'
GIT_TEST_FATAL_REGISTER_SUBMODULE_ODB=1
export GIT_TEST_FATAL_REGISTER_SUBMODULE_ODB
+TEST_PASSES_SANITIZE_LEAK=true
. ./test-lib.sh
pwd=$(pwd)
diff --git a/t/t5528-push-default.sh b/t/t5528-push-default.sh
index 14f7eced9a..bc2bada34c 100755
--- a/t/t5528-push-default.sh
+++ b/t/t5528-push-default.sh
@@ -4,6 +4,7 @@ test_description='check various push.default settings'
GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
+TEST_PASSES_SANITIZE_LEAK=true
. ./test-lib.sh
test_expect_success 'setup bare remotes' '
diff --git a/t/t5531-deep-submodule-push.sh b/t/t5531-deep-submodule-push.sh
index f3fff55744..135823630a 100755
--- a/t/t5531-deep-submodule-push.sh
+++ b/t/t5531-deep-submodule-push.sh
@@ -8,6 +8,7 @@ export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
GIT_TEST_FATAL_REGISTER_SUBMODULE_ODB=1
export GIT_TEST_FATAL_REGISTER_SUBMODULE_ODB
+TEST_PASSES_SANITIZE_LEAK=true
. ./test-lib.sh
test_expect_success setup '
diff --git a/t/t5533-push-cas.sh b/t/t5533-push-cas.sh
index cba26a872d..6365d99777 100755
--- a/t/t5533-push-cas.sh
+++ b/t/t5533-push-cas.sh
@@ -5,6 +5,7 @@ test_description='compare & swap push force/delete safety'
GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
+TEST_PASSES_SANITIZE_LEAK=true
. ./test-lib.sh
setup_srcdst_basic () {
diff --git a/t/t5534-push-signed.sh b/t/t5534-push-signed.sh
index c91a62b77a..d43aee0c32 100755
--- a/t/t5534-push-signed.sh
+++ b/t/t5534-push-signed.sh
@@ -5,6 +5,7 @@ test_description='signed push'
GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
+TEST_PASSES_SANITIZE_LEAK=true
. ./test-lib.sh
. "$TEST_DIRECTORY"/lib-gpg.sh
diff --git a/t/t5535-fetch-push-symref.sh b/t/t5535-fetch-push-symref.sh
index e8f6d233ff..7122af7fdb 100755
--- a/t/t5535-fetch-push-symref.sh
+++ b/t/t5535-fetch-push-symref.sh
@@ -2,6 +2,7 @@
test_description='avoiding conflicting update through symref aliasing'
+TEST_PASSES_SANITIZE_LEAK=true
. ./test-lib.sh
test_expect_success 'setup' '
diff --git a/t/t5536-fetch-conflicts.sh b/t/t5536-fetch-conflicts.sh
index 23bf696170..2dcbe79052 100755
--- a/t/t5536-fetch-conflicts.sh
+++ b/t/t5536-fetch-conflicts.sh
@@ -2,6 +2,7 @@
test_description='fetch handles conflicting refspecs correctly'
+TEST_PASSES_SANITIZE_LEAK=true
. ./test-lib.sh
D=$(pwd)
diff --git a/t/t5537-fetch-shallow.sh b/t/t5537-fetch-shallow.sh
index 37f7547a4c..cae4d400f3 100755
--- a/t/t5537-fetch-shallow.sh
+++ b/t/t5537-fetch-shallow.sh
@@ -5,6 +5,7 @@ test_description='fetch/clone from a shallow clone'
GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
+TEST_PASSES_SANITIZE_LEAK=true
. ./test-lib.sh
commit() {
diff --git a/t/t5538-push-shallow.sh b/t/t5538-push-shallow.sh
index e91fcc173e..6adc3a20a4 100755
--- a/t/t5538-push-shallow.sh
+++ b/t/t5538-push-shallow.sh
@@ -5,6 +5,7 @@ test_description='push from/to a shallow clone'
GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
+TEST_PASSES_SANITIZE_LEAK=true
. ./test-lib.sh
commit() {
diff --git a/t/t5539-fetch-http-shallow.sh b/t/t5539-fetch-http-shallow.sh
index 3ea75d34ca..82fe09d0a9 100755
--- a/t/t5539-fetch-http-shallow.sh
+++ b/t/t5539-fetch-http-shallow.sh
@@ -5,6 +5,7 @@ test_description='fetch/clone from a shallow clone over http'
GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
+TEST_PASSES_SANITIZE_LEAK=true
. ./test-lib.sh
. "$TEST_DIRECTORY"/lib-httpd.sh
start_httpd
diff --git a/t/t5540-http-push-webdav.sh b/t/t5540-http-push-webdav.sh
index 37db3dec0c..27389b0908 100755
--- a/t/t5540-http-push-webdav.sh
+++ b/t/t5540-http-push-webdav.sh
@@ -10,6 +10,7 @@ This test runs various sanity checks on http-push.'
GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
+TEST_PASSES_SANITIZE_LEAK=true
. ./test-lib.sh
if git http-push > /dev/null 2>&1 || [ $? -eq 128 ]
diff --git a/t/t5541-http-push-smart.sh b/t/t5541-http-push-smart.sh
index 71428f3d5c..3ad514bbd4 100755
--- a/t/t5541-http-push-smart.sh
+++ b/t/t5541-http-push-smart.sh
@@ -7,6 +7,7 @@ test_description='test smart pushing over http via http-backend'
GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
+TEST_PASSES_SANITIZE_LEAK=true
. ./test-lib.sh
ROOT_PATH="$PWD"
diff --git a/t/t5542-push-http-shallow.sh b/t/t5542-push-http-shallow.sh
index c2cc83182f..07624a1d7f 100755
--- a/t/t5542-push-http-shallow.sh
+++ b/t/t5542-push-http-shallow.sh
@@ -5,6 +5,7 @@ test_description='push from/to a shallow clone over http'
GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
+TEST_PASSES_SANITIZE_LEAK=true
. ./test-lib.sh
. "$TEST_DIRECTORY"/lib-httpd.sh
start_httpd
diff --git a/t/t5543-atomic-push.sh b/t/t5543-atomic-push.sh
index 04b47ad84a..479d103469 100755
--- a/t/t5543-atomic-push.sh
+++ b/t/t5543-atomic-push.sh
@@ -5,6 +5,7 @@ test_description='pushing to a repository using the atomic push option'
GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
+TEST_PASSES_SANITIZE_LEAK=true
. ./test-lib.sh
mk_repo_pair () {
diff --git a/t/t5548-push-porcelain.sh b/t/t5548-push-porcelain.sh
index 6282728eaf..ecb3877aa4 100755
--- a/t/t5548-push-porcelain.sh
+++ b/t/t5548-push-porcelain.sh
@@ -4,6 +4,7 @@
#
test_description='Test git push porcelain output'
+TEST_PASSES_SANITIZE_LEAK=true
. ./test-lib.sh
# Create commits in <repo> and assign each commit's oid to shell variables
diff --git a/t/t5549-fetch-push-http.sh b/t/t5549-fetch-push-http.sh
index 2cdebcb735..6377fb6d99 100755
--- a/t/t5549-fetch-push-http.sh
+++ b/t/t5549-fetch-push-http.sh
@@ -5,6 +5,7 @@ test_description='fetch/push functionality using the HTTP protocol'
GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
+TEST_PASSES_SANITIZE_LEAK=true
. ./test-lib.sh
. "$TEST_DIRECTORY"/lib-httpd.sh
start_httpd
diff --git a/t/t5550-http-fetch-dumb.sh b/t/t5550-http-fetch-dumb.sh
index ea8e48f627..58189c9f7d 100755
--- a/t/t5550-http-fetch-dumb.sh
+++ b/t/t5550-http-fetch-dumb.sh
@@ -4,6 +4,7 @@ test_description='test dumb fetching over http via static file'
GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
+TEST_PASSES_SANITIZE_LEAK=true
. ./test-lib.sh
if test_have_prereq !REFFILES
diff --git a/t/t5551-http-fetch-smart.sh b/t/t5551-http-fetch-smart.sh
index 7b5ab0eae1..2ce88535a6 100755
--- a/t/t5551-http-fetch-smart.sh
+++ b/t/t5551-http-fetch-smart.sh
@@ -5,6 +5,7 @@ test_description="test smart fetching over http via http-backend ($HTTP_PROTO)"
GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
+TEST_PASSES_SANITIZE_LEAK=true
. ./test-lib.sh
. "$TEST_DIRECTORY"/lib-httpd.sh
test "$HTTP_PROTO" = "HTTP/2" && enable_http2
@@ -186,6 +187,28 @@ test_expect_success 'clone from password-protected repository' '
test_cmp expect actual
'
+test_expect_success 'credential.interactive=false skips askpass' '
+ set_askpass bogus nonsense &&
+ (
+ GIT_TRACE2_EVENT="$(pwd)/interactive-true" &&
+ export GIT_TRACE2_EVENT &&
+ test_must_fail git clone --bare "$HTTPD_URL/auth/smart/repo.git" interactive-true-dir &&
+ test_region credential interactive interactive-true &&
+
+ GIT_TRACE2_EVENT="$(pwd)/interactive-false" &&
+ export GIT_TRACE2_EVENT &&
+ test_must_fail git -c credential.interactive=false \
+ clone --bare "$HTTPD_URL/auth/smart/repo.git" interactive-false-dir &&
+ test_region ! credential interactive interactive-false &&
+
+ GIT_TRACE2_EVENT="$(pwd)/interactive-never" &&
+ export GIT_TRACE2_EVENT &&
+ test_must_fail git -c credential.interactive=never \
+ clone --bare "$HTTPD_URL/auth/smart/repo.git" interactive-never-dir &&
+ test_region ! credential interactive interactive-never
+ )
+'
+
test_expect_success 'clone from auth-only-for-push repository' '
echo two >expect &&
set_askpass wrong &&
diff --git a/t/t5552-skipping-fetch-negotiator.sh b/t/t5552-skipping-fetch-negotiator.sh
index b55a9f65e6..4f2e5ae8df 100755
--- a/t/t5552-skipping-fetch-negotiator.sh
+++ b/t/t5552-skipping-fetch-negotiator.sh
@@ -1,6 +1,8 @@
#!/bin/sh
test_description='test skipping fetch negotiator'
+
+TEST_PASSES_SANITIZE_LEAK=true
. ./test-lib.sh
test_expect_success 'fetch.negotiationalgorithm config' '
diff --git a/t/t5553-set-upstream.sh b/t/t5553-set-upstream.sh
index 70e3376d31..33e919a17e 100755
--- a/t/t5553-set-upstream.sh
+++ b/t/t5553-set-upstream.sh
@@ -4,6 +4,7 @@ test_description='"git fetch/pull --set-upstream" basic tests.'
GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
+TEST_PASSES_SANITIZE_LEAK=true
. ./test-lib.sh
check_config () {
diff --git a/t/t5564-http-proxy.sh b/t/t5564-http-proxy.sh
index bb35b87071..4aef99bc28 100755
--- a/t/t5564-http-proxy.sh
+++ b/t/t5564-http-proxy.sh
@@ -39,4 +39,59 @@ test_expect_success 'clone can prompt for proxy password' '
expect_askpass pass proxuser
'
+start_socks() {
+ mkfifo socks_output &&
+ {
+ "$PERL_PATH" "$TEST_DIRECTORY/socks4-proxy.pl" "$1" >socks_output &
+ echo $! > "$TRASH_DIRECTORY/socks.pid"
+ } &&
+ read line <socks_output &&
+ test "$line" = ready
+}
+
+# The %30 tests that the correct amount of percent-encoding is applied to the
+# proxy string passed to curl.
+test_lazy_prereq SOCKS_PROXY '
+ test_have_prereq PERL &&
+ start_socks "$TRASH_DIRECTORY/%30.sock"
+'
+
+test_atexit '
+ test ! -e "$TRASH_DIRECTORY/socks.pid" ||
+ kill "$(cat "$TRASH_DIRECTORY/socks.pid")"
+'
+
+# The below tests morally ought to be gated on a prerequisite that Git is
+# linked with a libcurl that supports Unix socket paths for proxies (7.84 or
+# later), but this is not easy to test right now. Instead, we || the tests with
+# this function.
+old_libcurl_error() {
+ grep -Fx "fatal: libcurl 7.84 or later is required to support paths in proxy URLs" "$1"
+}
+
+test_expect_success SOCKS_PROXY 'clone via Unix socket' '
+ test_when_finished "rm -rf clone" &&
+ test_config_global http.proxy "socks4://localhost$PWD/%2530.sock" && {
+ {
+ GIT_TRACE_CURL=$PWD/trace git clone "$HTTPD_URL/smart/repo.git" clone 2>err &&
+ grep -i "SOCKS4 request granted" trace
+ } ||
+ old_libcurl_error err
+ }
+'
+
+test_expect_success 'Unix socket requires socks*:' - <<\EOT
+ ! git clone -c http.proxy=localhost/path https://example.com/repo.git 2>err && {
+ grep -Fx "fatal: Invalid proxy URL 'localhost/path': only SOCKS proxies support paths" err ||
+ old_libcurl_error err
+ }
+EOT
+
+test_expect_success 'Unix socket requires localhost' - <<\EOT
+ ! git clone -c http.proxy=socks4://127.0.0.1/path https://example.com/repo.git 2>err && {
+ grep -Fx "fatal: Invalid proxy URL 'socks4://127.0.0.1/path': host must be localhost if a path is present" err ||
+ old_libcurl_error err
+ }
+EOT
+
test_done
diff --git a/t/t5570-git-daemon.sh b/t/t5570-git-daemon.sh
index f9a9bf9503..c5f08b6799 100755
--- a/t/t5570-git-daemon.sh
+++ b/t/t5570-git-daemon.sh
@@ -4,6 +4,7 @@ test_description='test fetching over git protocol'
GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
+TEST_PASSES_SANITIZE_LEAK=true
. ./test-lib.sh
. "$TEST_DIRECTORY"/lib-git-daemon.sh
diff --git a/t/t5572-pull-submodule.sh b/t/t5572-pull-submodule.sh
index 51744521f7..916e58c166 100755
--- a/t/t5572-pull-submodule.sh
+++ b/t/t5572-pull-submodule.sh
@@ -5,6 +5,7 @@ test_description='pull can handle submodules'
GIT_TEST_FATAL_REGISTER_SUBMODULE_ODB=1
export GIT_TEST_FATAL_REGISTER_SUBMODULE_ODB
+TEST_PASSES_SANITIZE_LEAK=true
. ./test-lib.sh
. "$TEST_DIRECTORY"/lib-submodule-update.sh
diff --git a/t/t5574-fetch-output.sh b/t/t5574-fetch-output.sh
index 5883839a04..f7707326ea 100755
--- a/t/t5574-fetch-output.sh
+++ b/t/t5574-fetch-output.sh
@@ -5,6 +5,7 @@ test_description='git fetch output format'
GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
+TEST_PASSES_SANITIZE_LEAK=true
. ./test-lib.sh
test_expect_success 'fetch with invalid output format configuration' '
diff --git a/t/t5582-fetch-negative-refspec.sh b/t/t5582-fetch-negative-refspec.sh
index 7a80e47c2b..7fa54a4029 100755
--- a/t/t5582-fetch-negative-refspec.sh
+++ b/t/t5582-fetch-negative-refspec.sh
@@ -8,6 +8,7 @@ test_description='"git fetch" with negative refspecs.
GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
+TEST_PASSES_SANITIZE_LEAK=true
. ./test-lib.sh
test_expect_success setup '
diff --git a/t/t5616-partial-clone.sh b/t/t5616-partial-clone.sh
index 2da7291e37..c53e93be2f 100755
--- a/t/t5616-partial-clone.sh
+++ b/t/t5616-partial-clone.sh
@@ -5,6 +5,7 @@ test_description='git partial clone'
GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
+TEST_PASSES_SANITIZE_LEAK=true
. ./test-lib.sh
# create a normal "src" repo where we can later create new commits.
@@ -229,7 +230,7 @@ test_expect_success 'fetch --refetch triggers repacking' '
GIT_TRACE2_EVENT="$PWD/trace1.event" \
git -C pc1 fetch --refetch origin &&
- test_subcommand git maintenance run --auto --no-quiet <trace1.event &&
+ test_subcommand git maintenance run --auto --no-quiet --detach <trace1.event &&
grep \"param\":\"gc.autopacklimit\",\"value\":\"1\" trace1.event &&
grep \"param\":\"maintenance.incremental-repack.auto\",\"value\":\"-1\" trace1.event &&
@@ -238,7 +239,7 @@ test_expect_success 'fetch --refetch triggers repacking' '
-c gc.autoPackLimit=0 \
-c maintenance.incremental-repack.auto=1234 \
-C pc1 fetch --refetch origin &&
- test_subcommand git maintenance run --auto --no-quiet <trace2.event &&
+ test_subcommand git maintenance run --auto --no-quiet --detach <trace2.event &&
grep \"param\":\"gc.autopacklimit\",\"value\":\"0\" trace2.event &&
grep \"param\":\"maintenance.incremental-repack.auto\",\"value\":\"-1\" trace2.event &&
@@ -247,7 +248,7 @@ test_expect_success 'fetch --refetch triggers repacking' '
-c gc.autoPackLimit=1234 \
-c maintenance.incremental-repack.auto=0 \
-C pc1 fetch --refetch origin &&
- test_subcommand git maintenance run --auto --no-quiet <trace3.event &&
+ test_subcommand git maintenance run --auto --no-quiet --detach <trace3.event &&
grep \"param\":\"gc.autopacklimit\",\"value\":\"1\" trace3.event &&
grep \"param\":\"maintenance.incremental-repack.auto\",\"value\":\"0\" trace3.event
'
diff --git a/t/t5619-clone-local-ambiguous-transport.sh b/t/t5619-clone-local-ambiguous-transport.sh
index cce62bf78d..1d4efe414d 100755
--- a/t/t5619-clone-local-ambiguous-transport.sh
+++ b/t/t5619-clone-local-ambiguous-transport.sh
@@ -2,6 +2,7 @@
test_description='test local clone with ambiguous transport'
+TEST_PASSES_SANITIZE_LEAK=true
. ./test-lib.sh
. "$TEST_DIRECTORY/lib-httpd.sh"
diff --git a/t/t5700-protocol-v1.sh b/t/t5700-protocol-v1.sh
index a73b4d4ff6..985e04d06e 100755
--- a/t/t5700-protocol-v1.sh
+++ b/t/t5700-protocol-v1.sh
@@ -11,6 +11,7 @@ export GIT_TEST_PROTOCOL_VERSION
GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
+TEST_PASSES_SANITIZE_LEAK=true
. ./test-lib.sh
# Test protocol v1 with 'git://' transport
diff --git a/t/t5703-upload-pack-ref-in-want.sh b/t/t5703-upload-pack-ref-in-want.sh
index 191097171b..f75fae52c8 100755
--- a/t/t5703-upload-pack-ref-in-want.sh
+++ b/t/t5703-upload-pack-ref-in-want.sh
@@ -2,6 +2,7 @@
test_description='upload-pack ref-in-want'
+TEST_PASSES_SANITIZE_LEAK=true
. ./test-lib.sh
get_actual_refs () {
diff --git a/t/t5801-remote-helpers.sh b/t/t5801-remote-helpers.sh
index 20f43f7b7d..d21877150e 100755
--- a/t/t5801-remote-helpers.sh
+++ b/t/t5801-remote-helpers.sh
@@ -344,4 +344,15 @@ test_expect_success 'fetch tag' '
compare_refs local v1.0 server v1.0
'
+test_expect_success 'totally broken helper reports failure message' '
+ write_script git-remote-broken <<-\EOF &&
+ read cap_cmd
+ exit 1
+ EOF
+ test_must_fail \
+ env PATH="$PWD:$PATH" \
+ git clone broken://example.com/foo.git 2>stderr &&
+ grep aborted stderr
+'
+
test_done
diff --git a/t/t5802-connect-helper.sh b/t/t5802-connect-helper.sh
index c6c2661878..dd3e6235cd 100755
--- a/t/t5802-connect-helper.sh
+++ b/t/t5802-connect-helper.sh
@@ -1,6 +1,8 @@
#!/bin/sh
test_description='ext::cmd remote "connect" helper'
+
+TEST_PASSES_SANITIZE_LEAK=true
. ./test-lib.sh
test_expect_success setup '
diff --git a/t/t5812-proto-disable-http.sh b/t/t5812-proto-disable-http.sh
index 769c717e88..f69959c64c 100755
--- a/t/t5812-proto-disable-http.sh
+++ b/t/t5812-proto-disable-http.sh
@@ -1,6 +1,8 @@
#!/bin/sh
test_description='test disabling of git-over-http in clone/fetch'
+
+TEST_PASSES_SANITIZE_LEAK=true
. ./test-lib.sh
. "$TEST_DIRECTORY/lib-proto-disable.sh"
. "$TEST_DIRECTORY/lib-httpd.sh"
diff --git a/t/t5814-proto-disable-ext.sh b/t/t5814-proto-disable-ext.sh
index 9d6f7dfa2c..6fe1a98b2a 100755
--- a/t/t5814-proto-disable-ext.sh
+++ b/t/t5814-proto-disable-ext.sh
@@ -1,6 +1,8 @@
#!/bin/sh
test_description='test disabling of remote-helper paths in clone/fetch'
+
+TEST_PASSES_SANITIZE_LEAK=true
. ./test-lib.sh
. "$TEST_DIRECTORY/lib-proto-disable.sh"
diff --git a/t/t5815-submodule-protos.sh b/t/t5815-submodule-protos.sh
index 4d5956cc18..fe899ee82d 100755
--- a/t/t5815-submodule-protos.sh
+++ b/t/t5815-submodule-protos.sh
@@ -1,6 +1,8 @@
#!/bin/sh
test_description='test protocol filtering with submodules'
+
+TEST_PASSES_SANITIZE_LEAK=true
. ./test-lib.sh
. "$TEST_DIRECTORY"/lib-proto-disable.sh
diff --git a/t/t6004-rev-list-path-optim.sh b/t/t6004-rev-list-path-optim.sh
index cd4f420e2a..5416241ede 100755
--- a/t/t6004-rev-list-path-optim.sh
+++ b/t/t6004-rev-list-path-optim.sh
@@ -16,6 +16,7 @@ test_description='git rev-list trivial path optimization test
GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
+TEST_PASSES_SANITIZE_LEAK=true
. ./test-lib.sh
test_expect_success setup '
diff --git a/t/t6007-rev-list-cherry-pick-file.sh b/t/t6007-rev-list-cherry-pick-file.sh
index 6f3e543977..2d337d7287 100755
--- a/t/t6007-rev-list-cherry-pick-file.sh
+++ b/t/t6007-rev-list-cherry-pick-file.sh
@@ -5,6 +5,7 @@ test_description='test git rev-list --cherry-pick -- file'
GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
+TEST_PASSES_SANITIZE_LEAK=true
. ./test-lib.sh
# A---B---D---F
diff --git a/t/t6010-merge-base.sh b/t/t6010-merge-base.sh
index 44c726ea39..f96ea82e78 100755
--- a/t/t6010-merge-base.sh
+++ b/t/t6010-merge-base.sh
@@ -6,6 +6,7 @@
test_description='Merge base and parent list computation.
'
+TEST_PASSES_SANITIZE_LEAK=true
. ./test-lib.sh
M=1130000000
diff --git a/t/t6019-rev-list-ancestry-path.sh b/t/t6019-rev-list-ancestry-path.sh
index 738da23628..1aabab6956 100755
--- a/t/t6019-rev-list-ancestry-path.sh
+++ b/t/t6019-rev-list-ancestry-path.sh
@@ -29,6 +29,7 @@ test_description='--ancestry-path'
GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
+TEST_PASSES_SANITIZE_LEAK=true
. ./test-lib.sh
test_merge () {
diff --git a/t/t6050-replace.sh b/t/t6050-replace.sh
index c6e9b33e44..d7702fc756 100755
--- a/t/t6050-replace.sh
+++ b/t/t6050-replace.sh
@@ -7,6 +7,7 @@ test_description='Tests replace refs functionality'
GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
+TEST_PASSES_SANITIZE_LEAK=true
. ./test-lib.sh
. "$TEST_DIRECTORY/lib-gpg.sh"
diff --git a/t/t6111-rev-list-treesame.sh b/t/t6111-rev-list-treesame.sh
index 90ff141640..f63bc8d3da 100755
--- a/t/t6111-rev-list-treesame.sh
+++ b/t/t6111-rev-list-treesame.sh
@@ -16,6 +16,7 @@ test_description='TREESAME and limiting'
GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
+TEST_PASSES_SANITIZE_LEAK=true
. ./test-lib.sh
note () {
diff --git a/t/t6120-describe.sh b/t/t6120-describe.sh
index 79e0f19deb..05ed2510d9 100755
--- a/t/t6120-describe.sh
+++ b/t/t6120-describe.sh
@@ -14,6 +14,7 @@ test_description='test describe'
GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
+TEST_PASSES_SANITIZE_LEAK=true
. ./test-lib.sh
check_describe () {
diff --git a/t/t6132-pathspec-exclude.sh b/t/t6132-pathspec-exclude.sh
index 9fdafeb1e9..f31c09c056 100755
--- a/t/t6132-pathspec-exclude.sh
+++ b/t/t6132-pathspec-exclude.sh
@@ -2,6 +2,7 @@
test_description='test case exclude pathspec'
+TEST_PASSES_SANITIZE_LEAK=true
. ./test-lib.sh
test_expect_success 'setup' '
diff --git a/t/t6133-pathspec-rev-dwim.sh b/t/t6133-pathspec-rev-dwim.sh
index a290ffca0d..6dd4bbbf9f 100755
--- a/t/t6133-pathspec-rev-dwim.sh
+++ b/t/t6133-pathspec-rev-dwim.sh
@@ -1,6 +1,8 @@
#!/bin/sh
test_description='test dwim of revs versus pathspecs in revision parser'
+
+TEST_PASSES_SANITIZE_LEAK=true
. ./test-lib.sh
test_expect_success 'setup' '
diff --git a/t/t6135-pathspec-with-attrs.sh b/t/t6135-pathspec-with-attrs.sh
index 120dcd74a5..794bc7daf0 100755
--- a/t/t6135-pathspec-with-attrs.sh
+++ b/t/t6135-pathspec-with-attrs.sh
@@ -1,6 +1,8 @@
#!/bin/sh
test_description='test labels in pathspecs'
+
+TEST_PASSES_SANITIZE_LEAK=true
. ./test-lib.sh
test_expect_success 'setup a tree' '
diff --git a/t/t6200-fmt-merge-msg.sh b/t/t6200-fmt-merge-msg.sh
index 5a221f8ef1..ac57b0e4ae 100755
--- a/t/t6200-fmt-merge-msg.sh
+++ b/t/t6200-fmt-merge-msg.sh
@@ -8,6 +8,7 @@ test_description='fmt-merge-msg test'
GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
+TEST_PASSES_SANITIZE_LEAK=true
. ./test-lib.sh
. "$TEST_DIRECTORY/lib-gpg.sh"
diff --git a/t/t6300-for-each-ref.sh b/t/t6300-for-each-ref.sh
index eb6c8204e8..b3163629c5 100755
--- a/t/t6300-for-each-ref.sh
+++ b/t/t6300-for-each-ref.sh
@@ -5,6 +5,7 @@
test_description='for-each-ref test'
+TEST_PASSES_SANITIZE_LEAK=true
. ./test-lib.sh
GNUPGHOME_NOT_USED=$GNUPGHOME
. "$TEST_DIRECTORY"/lib-gpg.sh
@@ -1560,6 +1561,25 @@ test_trailer_option '%(trailers:separator,key_value_separator) changes both sepa
Reviewed-by,A U Thor <author@example.com>,Signed-off-by,A U Thor <author@example.com>
EOF
+test_expect_success 'multiple %(trailers) use their own options' '
+ git tag -F - tag-with-trailers <<-\EOF &&
+ body
+
+ one: foo
+ one: bar
+ two: baz
+ two: qux
+ EOF
+ t1="%(trailers:key=one,key_value_separator=W,separator=X)" &&
+ t2="%(trailers:key=two,key_value_separator=Y,separator=Z)" &&
+ git for-each-ref --format="$t1%0a$t2" refs/tags/tag-with-trailers >actual &&
+ cat >expect <<-\EOF &&
+ oneWfooXoneWbar
+ twoYbazZtwoYqux
+ EOF
+ test_cmp expect actual
+'
+
test_failing_trailer_option () {
title=$1 option=$2
cat >expect
@@ -1835,6 +1855,24 @@ sig_crlf="$(printf "%s" "$sig" | append_cr; echo dummy)"
sig_crlf=${sig_crlf%dummy}
test_atom refs/tags/fake-sig-crlf contents:signature "$sig_crlf"
+test_expect_success 'set up tag with signature and trailers' '
+ git tag -F - fake-sig-trailer <<-\EOF
+ this is the subject
+
+ this is the body
+
+ My-Trailer: foo
+ -----BEGIN PGP SIGNATURE-----
+
+ not a real signature, but we just care about the
+ subject/body/trailer parsing.
+ -----END PGP SIGNATURE-----
+ EOF
+'
+
+# use "separator=" here to suppress the terminating newline
+test_atom refs/tags/fake-sig-trailer trailers:separator= 'My-Trailer: foo'
+
test_expect_success 'git for-each-ref --stdin: empty' '
>in &&
git for-each-ref --format="%(refname)" --stdin <in >actual &&
@@ -1907,6 +1945,15 @@ test_expect_success 'git for-each-ref with nested tags' '
test_cmp expect actual
'
+test_expect_success 'is-base atom with non-commits' '
+ git for-each-ref --format="%(is-base:HEAD) %(refname)" >out 2>err &&
+ grep "(HEAD) refs/heads/main" out &&
+
+ test_line_count = 2 err &&
+ grep "error: object .* is a commit, not a blob" err &&
+ grep "error: bad tag pointer to" err
+'
+
GRADE_FORMAT="%(signature:grade)%0a%(signature:key)%0a%(signature:signer)%0a%(signature:fingerprint)%0a%(signature:primarykeyfingerprint)"
TRUSTLEVEL_FORMAT="%(signature:trustlevel)%0a%(signature:key)%0a%(signature:signer)%0a%(signature:fingerprint)%0a%(signature:primarykeyfingerprint)"
@@ -1994,8 +2041,7 @@ test_expect_success GPG 'show good signature with custom format' '
--format="$GRADE_FORMAT" >actual &&
test_cmp expect actual
'
-test_expect_success GPGSSH 'show good signature with custom format
- with ssh' '
+test_expect_success GPGSSH 'show good signature with custom format with ssh' '
test_config gpg.ssh.allowedSignersFile "${GPGSSH_ALLOWED_SIGNERS}" &&
FINGERPRINT=$(ssh-keygen -lf "${GPGSSH_KEY_PRIMARY}" | awk "{print \$2;}") &&
cat >expect.tmpl <<-\EOF &&
diff --git a/t/t6302-for-each-ref-filter.sh b/t/t6302-for-each-ref-filter.sh
index 163c378cfd..7f44d3c3f2 100755
--- a/t/t6302-for-each-ref-filter.sh
+++ b/t/t6302-for-each-ref-filter.sh
@@ -2,6 +2,7 @@
test_description='test for-each-refs usage of ref-filter APIs'
+TEST_PASSES_SANITIZE_LEAK=true
. ./test-lib.sh
. "$TEST_DIRECTORY"/lib-gpg.sh
diff --git a/t/t6409-merge-subtree.sh b/t/t6409-merge-subtree.sh
index e9ba6f1690..528615b981 100755
--- a/t/t6409-merge-subtree.sh
+++ b/t/t6409-merge-subtree.sh
@@ -5,6 +5,7 @@ test_description='subtree merge strategy'
GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
+TEST_PASSES_SANITIZE_LEAK=true
. ./test-lib.sh
test_expect_success setup '
diff --git a/t/t6421-merge-partial-clone.sh b/t/t6421-merge-partial-clone.sh
index b99f29ef9b..30349a466e 100755
--- a/t/t6421-merge-partial-clone.sh
+++ b/t/t6421-merge-partial-clone.sh
@@ -26,6 +26,7 @@ test_description="limiting blob downloads when merging with partial clones"
# underscore notation is to differentiate different
# files that might be renamed into each other's paths.)
+TEST_PASSES_SANITIZE_LEAK=true
. ./test-lib.sh
. "$TEST_DIRECTORY"/lib-merge.sh
diff --git a/t/t6423-merge-rename-directories.sh b/t/t6423-merge-rename-directories.sh
index 88d1cf2cde..4aaaf38be6 100755
--- a/t/t6423-merge-rename-directories.sh
+++ b/t/t6423-merge-rename-directories.sh
@@ -25,6 +25,7 @@ test_description="recursive merge with directory renames"
# underscore notation is to differentiate different
# files that might be renamed into each other's paths.)
+TEST_PASSES_SANITIZE_LEAK=true
. ./test-lib.sh
. "$TEST_DIRECTORY"/lib-merge.sh
diff --git a/t/t6428-merge-conflicts-sparse.sh b/t/t6428-merge-conflicts-sparse.sh
index 9919c3fa7c..8a79bc2e92 100755
--- a/t/t6428-merge-conflicts-sparse.sh
+++ b/t/t6428-merge-conflicts-sparse.sh
@@ -22,6 +22,7 @@ test_description="merge cases"
# underscore notation is to differentiate different
# files that might be renamed into each other's paths.)
+TEST_PASSES_SANITIZE_LEAK=true
. ./test-lib.sh
. "$TEST_DIRECTORY"/lib-merge.sh
diff --git a/t/t6500-gc.sh b/t/t6500-gc.sh
index 1b5909d1b7..ee074b99b7 100755
--- a/t/t6500-gc.sh
+++ b/t/t6500-gc.sh
@@ -3,6 +3,7 @@
test_description='basic git gc tests
'
+TEST_PASSES_SANITIZE_LEAK=true
. ./test-lib.sh
. "$TEST_DIRECTORY"/lib-terminal.sh
@@ -338,14 +339,14 @@ test_expect_success 'gc.maxCruftSize sets appropriate repack options' '
test_subcommand $cruft_max_size_opts --max-cruft-size=3145728 <trace2.txt
'
-run_and_wait_for_auto_gc () {
+run_and_wait_for_gc () {
# We read stdout from gc for the side effect of waiting until the
# background gc process exits, closing its fd 9. Furthermore, the
# variable assignment from a command substitution preserves the
# exit status of the main gc process.
# Note: this fd trickery doesn't work on Windows, but there is no
# need to, because on Win the auto gc always runs in the foreground.
- doesnt_matter=$(git gc --auto 9>&1)
+ doesnt_matter=$(git gc "$@" 9>&1)
}
test_expect_success 'background auto gc does not run if gc.log is present and recent but does if it is old' '
@@ -361,7 +362,7 @@ test_expect_success 'background auto gc does not run if gc.log is present and re
test-tool chmtime =-345600 .git/gc.log &&
git gc --auto &&
test_config gc.logexpiry 2.days &&
- run_and_wait_for_auto_gc &&
+ run_and_wait_for_gc --auto &&
ls .git/objects/pack/pack-*.pack >packs &&
test_line_count = 1 packs
'
@@ -391,11 +392,48 @@ test_expect_success 'background auto gc respects lock for all operations' '
printf "%d %s" "$shell_pid" "$hostname" >.git/gc.pid &&
# our gc should exit zero without doing anything
- run_and_wait_for_auto_gc &&
+ run_and_wait_for_gc --auto &&
(ls -1 .git/refs/heads .git/reftable >actual || true) &&
test_cmp expect actual
'
+test_expect_success '--detach overrides gc.autoDetach=false' '
+ test_when_finished "rm -rf repo" &&
+ git init repo &&
+ (
+ cd repo &&
+
+ # Prepare the repository such that git-gc(1) ends up repacking.
+ test_commit "$(test_oid blob17_1)" &&
+ test_commit "$(test_oid blob17_2)" &&
+ git config gc.autodetach false &&
+ git config gc.auto 2 &&
+
+ # Note that we cannot use `test_cmp` here to compare stderr
+ # because it may contain output from `set -x`.
+ run_and_wait_for_gc --auto --detach 2>actual &&
+ test_grep "Auto packing the repository in background for optimum performance." actual
+ )
+'
+
+test_expect_success '--no-detach overrides gc.autoDetach=true' '
+ test_when_finished "rm -rf repo" &&
+ git init repo &&
+ (
+ cd repo &&
+
+ # Prepare the repository such that git-gc(1) ends up repacking.
+ test_commit "$(test_oid blob17_1)" &&
+ test_commit "$(test_oid blob17_2)" &&
+ git config gc.autodetach true &&
+ git config gc.auto 2 &&
+
+ GIT_PROGRESS_DELAY=0 git gc --auto --no-detach 2>output &&
+ test_grep "Auto packing the repository for optimum performance." output &&
+ test_grep "Collecting referenced commits: 2, done." output
+ )
+'
+
# DO NOT leave a detached auto gc process running near the end of the
# test script: it can run long enough in the background to racily
# interfere with the cleanup in 'test_done'.
diff --git a/t/t6600-test-reach.sh b/t/t6600-test-reach.sh
index b330945f49..2591f8b8b3 100755
--- a/t/t6600-test-reach.sh
+++ b/t/t6600-test-reach.sh
@@ -612,4 +612,125 @@ test_expect_success 'for-each-ref merged:none' '
--format="%(refname)" --stdin
'
+# For get_branch_base_for_tip, we only care about
+# first-parent history. Here is the test graph with
+# second parents removed:
+#
+# (10,10)
+# /
+# (10,9) (9,10)
+# / /
+# (10,8) (9,9) (8,10)
+# / / /
+# ( continued...)
+# \ / / /
+# (3,1) (2,2) (1,3)
+# \ / /
+# (2,1) (1,2)
+# \ /
+# (1,1)
+#
+# In short, for a commit (i,j), the first-parent history
+# walks all commits (i, k) with k from j to 1, then the
+# commits (l, 1) with l from i to 1.
+
+test_expect_success 'get_branch_base_for_tip: none reach' '
+ # (2,3) branched from the first tip (i,4) in X with i > 2
+ cat >input <<-\EOF &&
+ A:commit-2-3
+ X:commit-1-2
+ X:commit-1-4
+ X:commit-4-4
+ X:commit-8-4
+ X:commit-10-4
+ EOF
+ echo "get_branch_base_for_tip(A,X):2" >expect &&
+ test_all_modes get_branch_base_for_tip
+'
+
+test_expect_success 'get_branch_base_for_tip: equal to tip' '
+ # (2,3) branched from the first tip (i,4) in X with i > 2
+ cat >input <<-\EOF &&
+ A:commit-8-4
+ X:commit-1-2
+ X:commit-1-4
+ X:commit-4-4
+ X:commit-8-4
+ X:commit-10-4
+ EOF
+ echo "get_branch_base_for_tip(A,X):3" >expect &&
+ test_all_modes get_branch_base_for_tip
+'
+
+test_expect_success 'get_branch_base_for_tip: all reach tip' '
+ # (2,3) branched from the first tip (i,4) in X with i > 2
+ cat >input <<-\EOF &&
+ A:commit-4-1
+ X:commit-4-2
+ X:commit-5-1
+ EOF
+ echo "get_branch_base_for_tip(A,X):0" >expect &&
+ test_all_modes get_branch_base_for_tip
+'
+
+test_expect_success 'for-each-ref is-base: none reach' '
+ cat >input <<-\EOF &&
+ refs/heads/commit-1-1
+ refs/heads/commit-4-2
+ refs/heads/commit-4-4
+ refs/heads/commit-8-4
+ EOF
+ cat >expect <<-\EOF &&
+ refs/heads/commit-1-1:
+ refs/heads/commit-4-2:(commit-2-3)
+ refs/heads/commit-4-4:
+ refs/heads/commit-8-4:
+ EOF
+ run_all_modes git for-each-ref \
+ --format="%(refname):%(is-base:commit-2-3)" --stdin
+'
+
+test_expect_success 'for-each-ref is-base: all reach' '
+ cat >input <<-\EOF &&
+ refs/heads/commit-4-2
+ refs/heads/commit-5-1
+ EOF
+ cat >expect <<-\EOF &&
+ refs/heads/commit-4-2:(commit-4-1)
+ refs/heads/commit-5-1:
+ EOF
+ run_all_modes git for-each-ref \
+ --format="%(refname):%(is-base:commit-4-1)" --stdin
+'
+
+test_expect_success 'for-each-ref is-base: equal to tip' '
+ cat >input <<-\EOF &&
+ refs/heads/commit-4-2
+ refs/heads/commit-5-1
+ EOF
+ cat >expect <<-\EOF &&
+ refs/heads/commit-4-2:(commit-4-2)
+ refs/heads/commit-5-1:
+ EOF
+ run_all_modes git for-each-ref \
+ --format="%(refname):%(is-base:commit-4-2)" --stdin
+'
+
+test_expect_success 'for-each-ref is-base:multiple' '
+ cat >input <<-\EOF &&
+ refs/heads/commit-1-1
+ refs/heads/commit-4-2
+ refs/heads/commit-4-4
+ refs/heads/commit-8-4
+ EOF
+ cat >expect <<-\EOF &&
+ refs/heads/commit-1-1[-]
+ refs/heads/commit-4-2[(commit-2-3)-]
+ refs/heads/commit-4-4[-]
+ refs/heads/commit-8-4[-(commit-6-5)]
+ EOF
+ run_all_modes git for-each-ref \
+ --format="%(refname)[%(is-base:commit-2-3)-%(is-base:commit-6-5)]" --stdin
+'
+
test_done
diff --git a/t/t7004-tag.sh b/t/t7004-tag.sh
index fa6336edf9..b1316e62f4 100755
--- a/t/t7004-tag.sh
+++ b/t/t7004-tag.sh
@@ -61,8 +61,9 @@ test_expect_success 'sort tags, ignore case' '
)
'
-test_expect_success 'looking for a tag in an empty tree should fail' \
- '! (tag_exists mytag)'
+test_expect_success 'looking for a tag in an empty tree should fail' '
+ ! (tag_exists mytag)
+'
test_expect_success 'creating a tag in an empty tree should fail' '
test_must_fail git tag mynotag &&
@@ -129,10 +130,10 @@ test_expect_success 'listing all tags if one exists should succeed' '
git tag
'
-cat >expect <<EOF
-mytag
-EOF
test_expect_success 'Multiple -l or --list options are equivalent to one -l option' '
+ cat >expect <<-\EOF &&
+ mytag
+ EOF
git tag -l -l >actual &&
test_cmp expect actual &&
git tag --list --list >actual &&
@@ -148,32 +149,33 @@ test_expect_success 'listing all tags if one exists should output that tag' '
# pattern matching:
-test_expect_success 'listing a tag using a matching pattern should succeed' \
- 'git tag -l mytag'
+test_expect_success 'listing a tag using a matching pattern should succeed' '
+ git tag -l mytag
+'
-test_expect_success 'listing a tag with --ignore-case' \
- 'test $(git tag -l --ignore-case MYTAG) = mytag'
+test_expect_success 'listing a tag with --ignore-case' '
+ test $(git tag -l --ignore-case MYTAG) = mytag
+'
-test_expect_success \
- 'listing a tag using a matching pattern should output that tag' \
- 'test $(git tag -l mytag) = mytag'
+test_expect_success 'listing a tag using a matching pattern should output that tag' '
+ test $(git tag -l mytag) = mytag
+'
-test_expect_success \
- 'listing tags using a non-matching pattern should succeed' \
- 'git tag -l xxx'
+test_expect_success 'listing tags using a non-matching pattern should succeed' '
+ git tag -l xxx
+'
-test_expect_success \
- 'listing tags using a non-matching pattern should output nothing' \
- 'test $(git tag -l xxx | wc -l) -eq 0'
+test_expect_success 'listing tags using a non-matching pattern should output nothing' '
+ test $(git tag -l xxx | wc -l) -eq 0
+'
# special cases for creating tags:
-test_expect_success \
- 'trying to create a tag with the name of one existing should fail' \
- 'test_must_fail git tag mytag'
+test_expect_success 'trying to create a tag with the name of one existing should fail' '
+ test_must_fail git tag mytag
+'
-test_expect_success \
- 'trying to create a tag with a non-valid name should fail' '
+test_expect_success 'trying to create a tag with a non-valid name should fail' '
test $(git tag -l | wc -l) -eq 1 &&
test_must_fail git tag "" &&
test_must_fail git tag .othertag &&
@@ -207,19 +209,19 @@ test_expect_success 'trying to delete an unknown tag should fail' '
test_must_fail git tag -d unknown-tag
'
-cat >expect <<EOF
-myhead
-mytag
-EOF
-test_expect_success \
- 'trying to delete tags without params should succeed and do nothing' '
- git tag -l > actual && test_cmp expect actual &&
+test_expect_success 'trying to delete tags without params should succeed and do nothing' '
+ cat >expect <<-\EOF &&
+ myhead
+ mytag
+ EOF
+ git tag -l >actual &&
+ test_cmp expect actual &&
git tag -d &&
- git tag -l > actual && test_cmp expect actual
+ git tag -l >actual &&
+ test_cmp expect actual
'
-test_expect_success \
- 'deleting two existing tags in one command should succeed' '
+test_expect_success 'deleting two existing tags in one command should succeed' '
tag_exists mytag &&
tag_exists myhead &&
git tag -d mytag myhead &&
@@ -227,15 +229,13 @@ test_expect_success \
! tag_exists myhead
'
-test_expect_success \
- 'creating a tag with the name of another deleted one should succeed' '
+test_expect_success 'creating a tag with the name of another deleted one should succeed' '
! tag_exists mytag &&
git tag mytag &&
tag_exists mytag
'
-test_expect_success \
- 'trying to delete two tags, existing and not, should fail in the 2nd' '
+test_expect_success 'trying to delete two tags, existing and not, should fail in the 2nd' '
tag_exists mytag &&
! tag_exists nonexistingtag &&
test_must_fail git tag -d mytag nonexistingtag &&
@@ -243,23 +243,24 @@ test_expect_success \
! tag_exists nonexistingtag
'
-test_expect_success 'trying to delete an already deleted tag should fail' \
- 'test_must_fail git tag -d mytag'
+test_expect_success 'trying to delete an already deleted tag should fail' '
+ test_must_fail git tag -d mytag
+'
# listing various tags with pattern matching:
-cat >expect <<EOF
-a1
-aa1
-cba
-t210
-t211
-v0.2.1
-v1.0
-v1.0.1
-v1.1.3
-EOF
test_expect_success 'listing all tags should print them ordered' '
+ cat >expect <<-\EOF &&
+ a1
+ aa1
+ cba
+ t210
+ t211
+ v0.2.1
+ v1.0
+ v1.0.1
+ v1.1.3
+ EOF
git tag v1.0.1 &&
git tag t211 &&
git tag aa1 &&
@@ -269,91 +270,89 @@ test_expect_success 'listing all tags should print them ordered' '
git tag a1 &&
git tag v1.0 &&
git tag t210 &&
- git tag -l > actual &&
+ git tag -l >actual &&
test_cmp expect actual &&
- git tag > actual &&
+ git tag >actual &&
test_cmp expect actual
'
-cat >expect <<EOF
-a1
-aa1
-cba
-EOF
-test_expect_success \
- 'listing tags with substring as pattern must print those matching' '
+test_expect_success 'listing tags with substring as pattern must print those matching' '
+ cat >expect <<-\EOF &&
+ a1
+ aa1
+ cba
+ EOF
rm *a* &&
- git tag -l "*a*" > current &&
+ git tag -l "*a*" >current &&
test_cmp expect current
'
-cat >expect <<EOF
-v0.2.1
-v1.0.1
-EOF
-test_expect_success \
- 'listing tags with a suffix as pattern must print those matching' '
- git tag -l "*.1" > actual &&
+test_expect_success 'listing tags with a suffix as pattern must print those matching' '
+ cat >expect <<-\EOF &&
+ v0.2.1
+ v1.0.1
+ EOF
+ git tag -l "*.1" >actual &&
test_cmp expect actual
'
-cat >expect <<EOF
-t210
-t211
-EOF
-test_expect_success \
- 'listing tags with a prefix as pattern must print those matching' '
- git tag -l "t21*" > actual &&
+test_expect_success 'listing tags with a prefix as pattern must print those matching' '
+ cat >expect <<-\EOF &&
+ t210
+ t211
+ EOF
+ git tag -l "t21*" >actual &&
test_cmp expect actual
'
-cat >expect <<EOF
-a1
-EOF
-test_expect_success \
- 'listing tags using a name as pattern must print that one matching' '
- git tag -l a1 > actual &&
+test_expect_success 'listing tags using a name as pattern must print that one matching' '
+ cat >expect <<-\EOF &&
+ a1
+ EOF
+ git tag -l a1 >actual &&
test_cmp expect actual
'
-cat >expect <<EOF
-v1.0
-EOF
-test_expect_success \
- 'listing tags using a name as pattern must print that one matching' '
- git tag -l v1.0 > actual &&
+test_expect_success 'listing tags using a name as pattern must print that one matching' '
+ cat >expect <<-\EOF &&
+ v1.0
+ EOF
+ git tag -l v1.0 >actual &&
test_cmp expect actual
'
-cat >expect <<EOF
-v1.0.1
-v1.1.3
-EOF
-test_expect_success \
- 'listing tags with ? in the pattern should print those matching' '
- git tag -l "v1.?.?" > actual &&
+test_expect_success 'listing tags with ? in the pattern should print those matching' '
+ cat >expect <<-\EOF &&
+ v1.0.1
+ v1.1.3
+ EOF
+ git tag -l "v1.?.?" >actual &&
test_cmp expect actual
'
-test_expect_success \
- 'listing tags using v.* should print nothing because none have v.' '
- git tag -l "v.*" > actual &&
+test_expect_success 'listing tags using v.* should print nothing because none have v.' '
+ git tag -l "v.*" >actual &&
test_must_be_empty actual
'
-cat >expect <<EOF
-v0.2.1
-v1.0
-v1.0.1
-v1.1.3
-EOF
-test_expect_success \
- 'listing tags using v* should print only those having v' '
- git tag -l "v*" > actual &&
+test_expect_success 'listing tags using v* should print only those having v' '
+ cat >expect <<-\EOF &&
+ v0.2.1
+ v1.0
+ v1.0.1
+ v1.1.3
+ EOF
+ git tag -l "v*" >actual &&
test_cmp expect actual
'
test_expect_success 'tag -l can accept multiple patterns' '
+ cat >expect <<-\EOF &&
+ v0.2.1
+ v1.0
+ v1.0.1
+ v1.1.3
+ EOF
git tag -l "v1*" "v0*" >actual &&
test_cmp expect actual
'
@@ -367,16 +366,22 @@ test_expect_success 'tag -l can accept multiple patterns' '
# out if we're going to break this long-documented form of taking
# multiple patterns.
test_expect_success 'tag -l <pattern> -l <pattern> works, as our buggy documentation previously suggested' '
+ cat >expect <<-\EOF &&
+ v0.2.1
+ v1.0
+ v1.0.1
+ v1.1.3
+ EOF
git tag -l "v1*" -l "v0*" >actual &&
test_cmp expect actual
'
test_expect_success 'listing tags in column' '
COLUMNS=41 git tag -l --column=row >actual &&
- cat >expected <<\EOF &&
-a1 aa1 cba t210 t211
-v0.2.1 v1.0 v1.0.1 v1.1.3
-EOF
+ cat >expected <<-\EOF &&
+ a1 aa1 cba t210 t211
+ v0.2.1 v1.0 v1.0.1 v1.1.3
+ EOF
test_cmp expected actual
'
@@ -384,10 +389,10 @@ test_expect_success 'listing tags in column with column.*' '
test_config column.tag row &&
test_config column.ui dense &&
COLUMNS=40 git tag -l >actual &&
- cat >expected <<\EOF &&
-a1 aa1 cba t210 t211
-v0.2.1 v1.0 v1.0.1 v1.1.3
-EOF
+ cat >expected <<-\EOF &&
+ a1 aa1 cba t210 t211
+ v0.2.1 v1.0 v1.0.1 v1.1.3
+ EOF
test_cmp expected actual
'
@@ -398,39 +403,39 @@ test_expect_success 'listing tag with -n --column should fail' '
test_expect_success 'listing tags -n in column with column.ui ignored' '
test_config column.ui "row dense" &&
COLUMNS=40 git tag -l -n >actual &&
- cat >expected <<\EOF &&
-a1 Foo
-aa1 Foo
-cba Foo
-t210 Foo
-t211 Foo
-v0.2.1 Foo
-v1.0 Foo
-v1.0.1 Foo
-v1.1.3 Foo
-EOF
+ cat >expected <<-\EOF &&
+ a1 Foo
+ aa1 Foo
+ cba Foo
+ t210 Foo
+ t211 Foo
+ v0.2.1 Foo
+ v1.0 Foo
+ v1.0.1 Foo
+ v1.1.3 Foo
+ EOF
test_cmp expected actual
'
# creating and verifying lightweight tags:
-test_expect_success \
- 'a non-annotated tag created without parameters should point to HEAD' '
+test_expect_success 'a non-annotated tag created without parameters should point to HEAD' '
git tag non-annotated-tag &&
test $(git cat-file -t non-annotated-tag) = commit &&
test $(git rev-parse non-annotated-tag) = $(git rev-parse HEAD)
'
-test_expect_success 'trying to verify an unknown tag should fail' \
- 'test_must_fail git tag -v unknown-tag'
+test_expect_success 'trying to verify an unknown tag should fail' '
+ test_must_fail git tag -v unknown-tag
+'
-test_expect_success \
- 'trying to verify a non-annotated and non-signed tag should fail' \
- 'test_must_fail git tag -v non-annotated-tag'
+test_expect_success 'trying to verify a non-annotated and non-signed tag should fail' '
+ test_must_fail git tag -v non-annotated-tag
+'
-test_expect_success \
- 'trying to verify many non-annotated or unknown tags, should fail' \
- 'test_must_fail git tag -v unknown-tag1 non-annotated-tag unknown-tag2'
+test_expect_success 'trying to verify many non-annotated or unknown tags, should fail' '
+ test_must_fail git tag -v unknown-tag1 non-annotated-tag unknown-tag2
+'
# creating annotated tags:
@@ -449,83 +454,78 @@ tagger C O Mitter <committer@example.com> $4 -0700
EOF
}
-commit=$(git rev-parse HEAD)
-time=$test_tick
-
-get_tag_header annotated-tag $commit commit $time >expect
-echo "A message" >>expect
-test_expect_success \
- 'creating an annotated tag with -m message should succeed' '
+test_expect_success 'creating an annotated tag with -m message should succeed' '
+ commit=$(git rev-parse HEAD) &&
+ time=$test_tick &&
+ get_tag_header annotated-tag $commit commit $time >expect &&
+ echo "A message" >>expect &&
git tag -m "A message" annotated-tag &&
get_tag_msg annotated-tag >actual &&
test_cmp expect actual
'
-get_tag_header annotated-tag-edit $commit commit $time >expect
-echo "An edited message" >>expect
test_expect_success 'set up editor' '
write_script fakeeditor <<-\EOF
sed -e "s/A message/An edited message/g" <"$1" >"$1-"
mv "$1-" "$1"
EOF
'
-test_expect_success \
- 'creating an annotated tag with -m message --edit should succeed' '
+
+test_expect_success 'creating an annotated tag with -m message --edit should succeed' '
+ get_tag_header annotated-tag-edit $commit commit $time >expect &&
+ echo "An edited message" >>expect &&
GIT_EDITOR=./fakeeditor git tag -m "A message" --edit annotated-tag-edit &&
get_tag_msg annotated-tag-edit >actual &&
test_cmp expect actual
'
-cat >msgfile <<EOF
-Another message
-in a file.
-EOF
-get_tag_header file-annotated-tag $commit commit $time >expect
-cat msgfile >>expect
-test_expect_success \
- 'creating an annotated tag with -F messagefile should succeed' '
+test_expect_success 'creating an annotated tag with -F messagefile should succeed' '
+ cat >msgfile <<-\EOF &&
+ Another message
+ in a file.
+ EOF
+ get_tag_header file-annotated-tag $commit commit $time >expect &&
+ cat msgfile >>expect &&
git tag -F msgfile file-annotated-tag &&
get_tag_msg file-annotated-tag >actual &&
test_cmp expect actual
'
-get_tag_header file-annotated-tag-edit $commit commit $time >expect
-sed -e "s/Another message/Another edited message/g" msgfile >>expect
test_expect_success 'set up editor' '
write_script fakeeditor <<-\EOF
sed -e "s/Another message/Another edited message/g" <"$1" >"$1-"
mv "$1-" "$1"
EOF
'
-test_expect_success \
- 'creating an annotated tag with -F messagefile --edit should succeed' '
+
+test_expect_success 'creating an annotated tag with -F messagefile --edit should succeed' '
+ get_tag_header file-annotated-tag-edit $commit commit $time >expect &&
+ sed -e "s/Another message/Another edited message/g" msgfile >>expect &&
GIT_EDITOR=./fakeeditor git tag -F msgfile --edit file-annotated-tag-edit &&
get_tag_msg file-annotated-tag-edit >actual &&
test_cmp expect actual
'
-cat >inputmsg <<EOF
-A message from the
-standard input
-EOF
-get_tag_header stdin-annotated-tag $commit commit $time >expect
-cat inputmsg >>expect
test_expect_success 'creating an annotated tag with -F - should succeed' '
+ cat >inputmsg <<-\EOF &&
+ A message from the
+ standard input
+ EOF
+ get_tag_header stdin-annotated-tag $commit commit $time >expect &&
+ cat inputmsg >>expect &&
git tag -F - stdin-annotated-tag <inputmsg &&
get_tag_msg stdin-annotated-tag >actual &&
test_cmp expect actual
'
-test_expect_success \
- 'trying to create a tag with a non-existing -F file should fail' '
+test_expect_success 'trying to create a tag with a non-existing -F file should fail' '
! test -f nonexistingfile &&
! tag_exists notag &&
test_must_fail git tag -F nonexistingfile notag &&
! tag_exists notag
'
-test_expect_success \
- 'trying to create tags giving both -m or -F options should fail' '
+test_expect_success 'trying to create tags giving both -m or -F options should fail' '
echo "message file 1" >msgfile1 &&
! tag_exists msgtag &&
test_must_fail git tag -m "message 1" -F msgfile1 msgtag &&
@@ -539,67 +539,61 @@ test_expect_success \
# blank and empty messages:
-get_tag_header empty-annotated-tag $commit commit $time >expect
-test_expect_success \
- 'creating a tag with an empty -m message should succeed' '
+test_expect_success 'creating a tag with an empty -m message should succeed' '
+ get_tag_header empty-annotated-tag $commit commit $time >expect &&
git tag -m "" empty-annotated-tag &&
get_tag_msg empty-annotated-tag >actual &&
test_cmp expect actual
'
->emptyfile
-get_tag_header emptyfile-annotated-tag $commit commit $time >expect
-test_expect_success \
- 'creating a tag with an empty -F messagefile should succeed' '
+test_expect_success 'creating a tag with an empty -F messagefile should succeed' '
+ >emptyfile &&
+ get_tag_header emptyfile-annotated-tag $commit commit $time >expect &&
git tag -F emptyfile emptyfile-annotated-tag &&
get_tag_msg emptyfile-annotated-tag >actual &&
test_cmp expect actual
'
-printf '\n\n \n\t\nLeading blank lines\n' >blanksfile
-printf '\n\t \t \nRepeated blank lines\n' >>blanksfile
-printf '\n\n\nTrailing spaces \t \n' >>blanksfile
-printf '\nTrailing blank lines\n\n\t \n\n' >>blanksfile
-get_tag_header blanks-annotated-tag $commit commit $time >expect
-cat >>expect <<EOF
-Leading blank lines
+test_expect_success 'extra blanks in the message for an annotated tag should be removed' '
+ printf "\n\n \n\t\nLeading blank lines\n" >blanksfile &&
+ printf "\n\t \t \nRepeated blank lines\n" >>blanksfile &&
+ printf "\n\n\nTrailing spaces \t \n" >>blanksfile &&
+ printf "\nTrailing blank lines\n\n\t \n\n" >>blanksfile &&
+ get_tag_header blanks-annotated-tag $commit commit $time >expect &&
+ cat >>expect <<-\EOF &&
+ Leading blank lines
-Repeated blank lines
+ Repeated blank lines
-Trailing spaces
+ Trailing spaces
-Trailing blank lines
-EOF
-test_expect_success \
- 'extra blanks in the message for an annotated tag should be removed' '
+ Trailing blank lines
+ EOF
git tag -F blanksfile blanks-annotated-tag &&
get_tag_msg blanks-annotated-tag >actual &&
test_cmp expect actual
'
-get_tag_header blank-annotated-tag $commit commit $time >expect
-test_expect_success \
- 'creating a tag with blank -m message with spaces should succeed' '
+test_expect_success 'creating a tag with blank -m message with spaces should succeed' '
+ get_tag_header blank-annotated-tag $commit commit $time >expect &&
git tag -m " " blank-annotated-tag &&
get_tag_msg blank-annotated-tag >actual &&
test_cmp expect actual
'
-echo ' ' >blankfile
-echo '' >>blankfile
-echo ' ' >>blankfile
-get_tag_header blankfile-annotated-tag $commit commit $time >expect
-test_expect_success \
- 'creating a tag with blank -F messagefile with spaces should succeed' '
+test_expect_success 'creating a tag with blank -F messagefile with spaces should succeed' '
+ echo " " >blankfile &&
+ echo "" >>blankfile &&
+ echo " " >>blankfile &&
+ get_tag_header blankfile-annotated-tag $commit commit $time >expect &&
git tag -F blankfile blankfile-annotated-tag &&
get_tag_msg blankfile-annotated-tag >actual &&
test_cmp expect actual
'
-printf ' ' >blanknonlfile
-get_tag_header blanknonlfile-annotated-tag $commit commit $time >expect
-test_expect_success \
- 'creating a tag with -F file of spaces and no newline should succeed' '
+test_expect_success 'creating a tag with -F file of spaces and no newline should succeed' '
+ printf " " >blanknonlfile &&
+ get_tag_header blanknonlfile-annotated-tag $commit commit $time >expect &&
git tag -F blanknonlfile blanknonlfile-annotated-tag &&
get_tag_msg blanknonlfile-annotated-tag >actual &&
test_cmp expect actual
@@ -607,62 +601,58 @@ test_expect_success \
# messages with commented lines:
-cat >commentsfile <<EOF
-# A comment
+test_expect_success 'creating a tag using a -F messagefile with #comments should succeed' '
+ cat >commentsfile <<-\EOF &&
+ # A comment
-############
-The message.
-############
-One line.
+ ############
+ The message.
+ ############
+ One line.
-# commented lines
-# commented lines
+ # commented lines
+ # commented lines
-Another line.
-# comments
+ Another line.
+ # comments
-Last line.
-EOF
-get_tag_header comments-annotated-tag $commit commit $time >expect
-cat >>expect <<EOF
-The message.
-One line.
+ Last line.
+ EOF
+ get_tag_header comments-annotated-tag $commit commit $time >expect &&
+ cat >>expect <<-\EOF &&
+ The message.
+ One line.
-Another line.
+ Another line.
-Last line.
-EOF
-test_expect_success \
- 'creating a tag using a -F messagefile with #comments should succeed' '
+ Last line.
+ EOF
git tag -F commentsfile comments-annotated-tag &&
get_tag_msg comments-annotated-tag >actual &&
test_cmp expect actual
'
-get_tag_header comment-annotated-tag $commit commit $time >expect
-test_expect_success \
- 'creating a tag with a #comment in the -m message should succeed' '
+test_expect_success 'creating a tag with a #comment in the -m message should succeed' '
+ get_tag_header comment-annotated-tag $commit commit $time >expect &&
git tag -m "#comment" comment-annotated-tag &&
get_tag_msg comment-annotated-tag >actual &&
test_cmp expect actual
'
-echo '#comment' >commentfile
-echo '' >>commentfile
-echo '####' >>commentfile
-get_tag_header commentfile-annotated-tag $commit commit $time >expect
-test_expect_success \
- 'creating a tag with #comments in the -F messagefile should succeed' '
+test_expect_success 'creating a tag with #comments in the -F messagefile should succeed' '
+ echo "#comment" >commentfile &&
+ echo "" >>commentfile &&
+ echo "####" >>commentfile &&
+ get_tag_header commentfile-annotated-tag $commit commit $time >expect &&
git tag -F commentfile commentfile-annotated-tag &&
get_tag_msg commentfile-annotated-tag >actual &&
test_cmp expect actual
'
-printf '#comment' >commentnonlfile
-get_tag_header commentnonlfile-annotated-tag $commit commit $time >expect
-test_expect_success \
- 'creating a tag with a file of #comment and no newline should succeed' '
+test_expect_success 'creating a tag with a file of #comment and no newline should succeed' '
+ printf "#comment" >commentnonlfile &&
+ get_tag_header commentnonlfile-annotated-tag $commit commit $time >expect &&
git tag -F commentnonlfile commentnonlfile-annotated-tag &&
get_tag_msg commentnonlfile-annotated-tag >actual &&
test_cmp expect actual
@@ -779,8 +769,7 @@ test_expect_success 'bad editor causes panic when only --trailer is given' '
# listing messages for annotated non-signed tags:
-test_expect_success \
- 'listing the one-line message of a non-signed tag should succeed' '
+test_expect_success 'listing the one-line message of a non-signed tag should succeed' '
git tag -m "A msg" tag-one-line &&
echo "tag-one-line" >expect &&
@@ -819,8 +808,7 @@ test_expect_success 'The -n 100 invocation means -n --list 100, not -n100' '
test_cmp expect actual
'
-test_expect_success \
- 'listing the zero-lines message of a non-signed tag should succeed' '
+test_expect_success 'listing the zero-lines message of a non-signed tag should succeed' '
git tag -m "" tag-zero-lines &&
echo "tag-zero-lines" >expect &&
@@ -844,11 +832,10 @@ test_expect_success \
test_cmp expect actual
'
-echo 'tag line one' >annotagmsg
-echo 'tag line two' >>annotagmsg
-echo 'tag line three' >>annotagmsg
-test_expect_success \
- 'listing many message lines of a non-signed tag should succeed' '
+test_expect_success 'listing many message lines of a non-signed tag should succeed' '
+ echo "tag line one" >annotagmsg &&
+ echo "tag line two" >>annotagmsg &&
+ echo "tag line three" >>annotagmsg &&
git tag -F annotagmsg tag-lines &&
echo "tag-lines" >expect &&
@@ -936,40 +923,37 @@ test_expect_success 'git tag --format with ahead-behind' '
# trying to verify annotated non-signed tags:
-test_expect_success GPG \
- 'trying to verify an annotated non-signed tag should fail' '
+test_expect_success GPG 'trying to verify an annotated non-signed tag should fail' '
tag_exists annotated-tag &&
test_must_fail git tag -v annotated-tag
'
-test_expect_success GPG \
- 'trying to verify a file-annotated non-signed tag should fail' '
+test_expect_success GPG 'trying to verify a file-annotated non-signed tag should fail' '
tag_exists file-annotated-tag &&
test_must_fail git tag -v file-annotated-tag
'
-test_expect_success GPG \
- 'trying to verify two annotated non-signed tags should fail' '
+test_expect_success GPG 'trying to verify two annotated non-signed tags should fail' '
tag_exists annotated-tag file-annotated-tag &&
test_must_fail git tag -v annotated-tag file-annotated-tag
'
# creating and verifying signed tags:
-get_tag_header signed-tag $commit commit $time >expect
-echo 'A signed tag message' >>expect
-echo '-----BEGIN PGP SIGNATURE-----' >>expect
test_expect_success GPG 'creating a signed tag with -m message should succeed' '
+ get_tag_header signed-tag $commit commit $time >expect &&
+ echo "A signed tag message" >>expect &&
+ echo "-----BEGIN PGP SIGNATURE-----" >>expect &&
git tag -s -m "A signed tag message" signed-tag &&
get_tag_msg signed-tag >actual &&
test_cmp expect actual
'
-get_tag_header u-signed-tag $commit commit $time >expect
-echo 'Another message' >>expect
-echo '-----BEGIN PGP SIGNATURE-----' >>expect
test_expect_success GPG 'sign with a given key id' '
+ get_tag_header u-signed-tag $commit commit $time >expect &&
+ echo "Another message" >>expect &&
+ echo "-----BEGIN PGP SIGNATURE-----" >>expect &&
git tag -u committer@example.com -m "Another message" u-signed-tag &&
get_tag_msg u-signed-tag >actual &&
test_cmp expect actual
@@ -989,137 +973,128 @@ test_expect_success GPG 'sign with an unknown id (2)' '
'
-cat >fakeeditor <<'EOF'
-#!/bin/sh
-test -n "$1" && exec >"$1"
-echo A signed tag message
-echo from a fake editor.
-EOF
-chmod +x fakeeditor
-
-get_tag_header implied-sign $commit commit $time >expect
-./fakeeditor >>expect
-echo '-----BEGIN PGP SIGNATURE-----' >>expect
test_expect_success GPG '-u implies signed tag' '
+ write_script fakeeditor <<-\EOF &&
+ test -n "$1" && exec >"$1"
+ echo A signed tag message
+ echo from a fake editor.
+ EOF
+
+ get_tag_header implied-sign $commit commit $time >expect &&
+ ./fakeeditor >>expect &&
+ echo "-----BEGIN PGP SIGNATURE-----" >>expect &&
GIT_EDITOR=./fakeeditor git tag -u CDDE430D implied-sign &&
get_tag_msg implied-sign >actual &&
test_cmp expect actual
'
-cat >sigmsgfile <<EOF
-Another signed tag
-message in a file.
-EOF
-get_tag_header file-signed-tag $commit commit $time >expect
-cat sigmsgfile >>expect
-echo '-----BEGIN PGP SIGNATURE-----' >>expect
-test_expect_success GPG \
- 'creating a signed tag with -F messagefile should succeed' '
+test_expect_success GPG 'creating a signed tag with -F messagefile should succeed' '
+ cat >sigmsgfile <<-\EOF &&
+ Another signed tag
+ message in a file.
+ EOF
+ get_tag_header file-signed-tag $commit commit $time >expect &&
+ cat sigmsgfile >>expect &&
+ echo "-----BEGIN PGP SIGNATURE-----" >>expect &&
git tag -s -F sigmsgfile file-signed-tag &&
get_tag_msg file-signed-tag >actual &&
test_cmp expect actual
'
-cat >siginputmsg <<EOF
-A signed tag message from
-the standard input
-EOF
-get_tag_header stdin-signed-tag $commit commit $time >expect
-cat siginputmsg >>expect
-echo '-----BEGIN PGP SIGNATURE-----' >>expect
test_expect_success GPG 'creating a signed tag with -F - should succeed' '
+ cat >siginputmsg <<-\EOF &&
+ A signed tag message from
+ the standard input
+ EOF
+ get_tag_header stdin-signed-tag $commit commit $time >expect &&
+ cat siginputmsg >>expect &&
+ echo "-----BEGIN PGP SIGNATURE-----" >>expect &&
git tag -s -F - stdin-signed-tag <siginputmsg &&
get_tag_msg stdin-signed-tag >actual &&
test_cmp expect actual
'
-get_tag_header implied-annotate $commit commit $time >expect
-./fakeeditor >>expect
-echo '-----BEGIN PGP SIGNATURE-----' >>expect
test_expect_success GPG '-s implies annotated tag' '
+ get_tag_header implied-annotate $commit commit $time >expect &&
+ ./fakeeditor >>expect &&
+ echo "-----BEGIN PGP SIGNATURE-----" >>expect &&
GIT_EDITOR=./fakeeditor git tag -s implied-annotate &&
get_tag_msg implied-annotate >actual &&
test_cmp expect actual
'
-get_tag_header forcesignannotated-implied-sign $commit commit $time >expect
-echo "A message" >>expect
-echo '-----BEGIN PGP SIGNATURE-----' >>expect
-test_expect_success GPG \
- 'git tag -s implied if configured with tag.forcesignannotated' \
- 'test_config tag.forcesignannotated true &&
+test_expect_success GPG 'git tag -s implied if configured with tag.forcesignannotated' '
+ get_tag_header forcesignannotated-implied-sign $commit commit $time >expect &&
+ echo "A message" >>expect &&
+ echo "-----BEGIN PGP SIGNATURE-----" >>expect &&
+ test_config tag.forcesignannotated true &&
git tag -m "A message" forcesignannotated-implied-sign &&
get_tag_msg forcesignannotated-implied-sign >actual &&
test_cmp expect actual
'
-test_expect_success GPG \
- 'lightweight with no message when configured with tag.forcesignannotated' \
- 'test_config tag.forcesignannotated true &&
+test_expect_success GPG 'lightweight with no message when configured with tag.forcesignannotated' '
+ test_config tag.forcesignannotated true &&
git tag forcesignannotated-lightweight &&
tag_exists forcesignannotated-lightweight &&
test_must_fail git tag -v forcesignannotated-no-message
'
-get_tag_header forcesignannotated-annotate $commit commit $time >expect
-echo "A message" >>expect
-test_expect_success GPG \
- 'git tag -a disable configured tag.forcesignannotated' \
- 'test_config tag.forcesignannotated true &&
+test_expect_success GPG 'git tag -a disable configured tag.forcesignannotated' '
+ get_tag_header forcesignannotated-annotate $commit commit $time >expect &&
+ echo "A message" >>expect &&
+ test_config tag.forcesignannotated true &&
git tag -a -m "A message" forcesignannotated-annotate &&
get_tag_msg forcesignannotated-annotate >actual &&
test_cmp expect actual &&
test_must_fail git tag -v forcesignannotated-annotate
'
-get_tag_header forcesignannotated-disabled $commit commit $time >expect
-echo "A message" >>expect
-echo '-----BEGIN PGP SIGNATURE-----' >>expect
-test_expect_success GPG \
- 'git tag --sign enable GPG sign' \
- 'test_config tag.forcesignannotated false &&
+test_expect_success GPG 'git tag --sign enable GPG sign' '
+ get_tag_header forcesignannotated-disabled $commit commit $time >expect &&
+ echo "A message" >>expect &&
+ echo "-----BEGIN PGP SIGNATURE-----" >>expect &&
+ test_config tag.forcesignannotated false &&
git tag --sign -m "A message" forcesignannotated-disabled &&
get_tag_msg forcesignannotated-disabled >actual &&
test_cmp expect actual
'
-get_tag_header gpgsign-enabled $commit commit $time >expect
-echo "A message" >>expect
-echo '-----BEGIN PGP SIGNATURE-----' >>expect
-test_expect_success GPG \
- 'git tag configured tag.gpgsign enables GPG sign' \
- 'test_config tag.gpgsign true &&
+test_expect_success GPG 'git tag configured tag.gpgsign enables GPG sign' '
+ get_tag_header gpgsign-enabled $commit commit $time >expect &&
+ echo "A message" >>expect &&
+ echo "-----BEGIN PGP SIGNATURE-----" >>expect &&
+ test_config tag.gpgsign true &&
git tag -m "A message" gpgsign-enabled &&
get_tag_msg gpgsign-enabled>actual &&
test_cmp expect actual
'
-get_tag_header no-sign $commit commit $time >expect
-echo "A message" >>expect
-test_expect_success GPG \
- 'git tag --no-sign configured tag.gpgsign skip GPG sign' \
- 'test_config tag.gpgsign true &&
+test_expect_success GPG 'git tag --no-sign configured tag.gpgsign skip GPG sign' '
+ get_tag_header no-sign $commit commit $time >expect &&
+ echo "A message" >>expect &&
+ test_config tag.gpgsign true &&
git tag -a --no-sign -m "A message" no-sign &&
get_tag_msg no-sign>actual &&
test_cmp expect actual
'
-test_expect_success GPG \
- 'trying to create a signed tag with non-existing -F file should fail' '
+test_expect_success GPG 'trying to create a signed tag with non-existing -F file should fail' '
! test -f nonexistingfile &&
! tag_exists nosigtag &&
test_must_fail git tag -s -F nonexistingfile nosigtag &&
! tag_exists nosigtag
'
-test_expect_success GPG 'verifying a signed tag should succeed' \
- 'git tag -v signed-tag'
+test_expect_success GPG 'verifying a signed tag should succeed' '
+ git tag -v signed-tag
+'
-test_expect_success GPG 'verifying two signed tags in one command should succeed' \
- 'git tag -v signed-tag file-signed-tag'
+test_expect_success GPG 'verifying two signed tags in one command should succeed' '
+ git tag -v signed-tag file-signed-tag
+'
-test_expect_success GPG \
- 'verifying many signed and non-signed tags should fail' '
+test_expect_success GPG 'verifying many signed and non-signed tags should fail' '
test_must_fail git tag -v signed-tag annotated-tag &&
test_must_fail git tag -v file-annotated-tag file-signed-tag &&
test_must_fail git tag -v annotated-tag \
@@ -1150,78 +1125,72 @@ test_expect_success GPG 'verifying a forged tag with --format should fail silent
# blank and empty messages for signed tags:
-get_tag_header empty-signed-tag $commit commit $time >expect
-echo '-----BEGIN PGP SIGNATURE-----' >>expect
-test_expect_success GPG \
- 'creating a signed tag with an empty -m message should succeed' '
+test_expect_success GPG 'creating a signed tag with an empty -m message should succeed' '
+ get_tag_header empty-signed-tag $commit commit $time >expect &&
+ echo "-----BEGIN PGP SIGNATURE-----" >>expect &&
git tag -s -m "" empty-signed-tag &&
get_tag_msg empty-signed-tag >actual &&
test_cmp expect actual &&
git tag -v empty-signed-tag
'
->sigemptyfile
-get_tag_header emptyfile-signed-tag $commit commit $time >expect
-echo '-----BEGIN PGP SIGNATURE-----' >>expect
-test_expect_success GPG \
- 'creating a signed tag with an empty -F messagefile should succeed' '
+test_expect_success GPG 'creating a signed tag with an empty -F messagefile should succeed' '
+ >sigemptyfile &&
+ get_tag_header emptyfile-signed-tag $commit commit $time >expect &&
+ echo "-----BEGIN PGP SIGNATURE-----" >>expect &&
git tag -s -F sigemptyfile emptyfile-signed-tag &&
get_tag_msg emptyfile-signed-tag >actual &&
test_cmp expect actual &&
git tag -v emptyfile-signed-tag
'
-printf '\n\n \n\t\nLeading blank lines\n' > sigblanksfile
-printf '\n\t \t \nRepeated blank lines\n' >>sigblanksfile
-printf '\n\n\nTrailing spaces \t \n' >>sigblanksfile
-printf '\nTrailing blank lines\n\n\t \n\n' >>sigblanksfile
-get_tag_header blanks-signed-tag $commit commit $time >expect
-cat >>expect <<EOF
-Leading blank lines
+test_expect_success GPG 'extra blanks in the message for a signed tag should be removed' '
+ printf "\n\n \n\t\nLeading blank lines\n" >sigblanksfile &&
+ printf "\n\t \t \nRepeated blank lines\n" >>sigblanksfile &&
+ printf "\n\n\nTrailing spaces \t \n" >>sigblanksfile &&
+ printf "\nTrailing blank lines\n\n\t \n\n" >>sigblanksfile &&
+ get_tag_header blanks-signed-tag $commit commit $time >expect &&
+ cat >>expect <<-\EOF &&
+ Leading blank lines
-Repeated blank lines
+ Repeated blank lines
-Trailing spaces
+ Trailing spaces
-Trailing blank lines
-EOF
-echo '-----BEGIN PGP SIGNATURE-----' >>expect
-test_expect_success GPG \
- 'extra blanks in the message for a signed tag should be removed' '
+ Trailing blank lines
+ EOF
+ echo "-----BEGIN PGP SIGNATURE-----" >>expect &&
git tag -s -F sigblanksfile blanks-signed-tag &&
get_tag_msg blanks-signed-tag >actual &&
test_cmp expect actual &&
git tag -v blanks-signed-tag
'
-get_tag_header blank-signed-tag $commit commit $time >expect
-echo '-----BEGIN PGP SIGNATURE-----' >>expect
-test_expect_success GPG \
- 'creating a signed tag with a blank -m message should succeed' '
+test_expect_success GPG 'creating a signed tag with a blank -m message should succeed' '
+ get_tag_header blank-signed-tag $commit commit $time >expect &&
+ echo "-----BEGIN PGP SIGNATURE-----" >>expect &&
git tag -s -m " " blank-signed-tag &&
get_tag_msg blank-signed-tag >actual &&
test_cmp expect actual &&
git tag -v blank-signed-tag
'
-echo ' ' >sigblankfile
-echo '' >>sigblankfile
-echo ' ' >>sigblankfile
-get_tag_header blankfile-signed-tag $commit commit $time >expect
-echo '-----BEGIN PGP SIGNATURE-----' >>expect
-test_expect_success GPG \
- 'creating a signed tag with blank -F file with spaces should succeed' '
+test_expect_success GPG 'creating a signed tag with blank -F file with spaces should succeed' '
+ echo " " >sigblankfile &&
+ echo "" >>sigblankfile &&
+ echo " " >>sigblankfile &&
+ get_tag_header blankfile-signed-tag $commit commit $time >expect &&
+ echo "-----BEGIN PGP SIGNATURE-----" >>expect &&
git tag -s -F sigblankfile blankfile-signed-tag &&
get_tag_msg blankfile-signed-tag >actual &&
test_cmp expect actual &&
git tag -v blankfile-signed-tag
'
-printf ' ' >sigblanknonlfile
-get_tag_header blanknonlfile-signed-tag $commit commit $time >expect
-echo '-----BEGIN PGP SIGNATURE-----' >>expect
-test_expect_success GPG \
- 'creating a signed tag with spaces and no newline should succeed' '
+test_expect_success GPG 'creating a signed tag with spaces and no newline should succeed' '
+ printf " " >sigblanknonlfile &&
+ get_tag_header blanknonlfile-signed-tag $commit commit $time >expect &&
+ echo "-----BEGIN PGP SIGNATURE-----" >>expect &&
git tag -s -F sigblanknonlfile blanknonlfile-signed-tag &&
get_tag_msg blanknonlfile-signed-tag >actual &&
test_cmp expect actual &&
@@ -1241,69 +1210,65 @@ test_expect_success GPG 'signed tag with embedded PGP message' '
# messages with commented lines for signed tags:
-cat >sigcommentsfile <<EOF
-# A comment
+test_expect_success GPG 'creating a signed tag with a -F file with #comments should succeed' '
+ cat >sigcommentsfile <<-\EOF &&
+ # A comment
-############
-The message.
-############
-One line.
+ ############
+ The message.
+ ############
+ One line.
-# commented lines
-# commented lines
+ # commented lines
+ # commented lines
-Another line.
-# comments
+ Another line.
+ # comments
-Last line.
-EOF
-get_tag_header comments-signed-tag $commit commit $time >expect
-cat >>expect <<EOF
-The message.
-One line.
+ Last line.
+ EOF
+ get_tag_header comments-signed-tag $commit commit $time >expect &&
+ cat >>expect <<-\EOF &&
+ The message.
+ One line.
-Another line.
+ Another line.
-Last line.
-EOF
-echo '-----BEGIN PGP SIGNATURE-----' >>expect
-test_expect_success GPG \
- 'creating a signed tag with a -F file with #comments should succeed' '
+ Last line.
+ EOF
+ echo "-----BEGIN PGP SIGNATURE-----" >>expect &&
git tag -s -F sigcommentsfile comments-signed-tag &&
get_tag_msg comments-signed-tag >actual &&
test_cmp expect actual &&
git tag -v comments-signed-tag
'
-get_tag_header comment-signed-tag $commit commit $time >expect
-echo '-----BEGIN PGP SIGNATURE-----' >>expect
-test_expect_success GPG \
- 'creating a signed tag with #commented -m message should succeed' '
+test_expect_success GPG 'creating a signed tag with #commented -m message should succeed' '
+ get_tag_header comment-signed-tag $commit commit $time >expect &&
+ echo "-----BEGIN PGP SIGNATURE-----" >>expect &&
git tag -s -m "#comment" comment-signed-tag &&
get_tag_msg comment-signed-tag >actual &&
test_cmp expect actual &&
git tag -v comment-signed-tag
'
-echo '#comment' >sigcommentfile
-echo '' >>sigcommentfile
-echo '####' >>sigcommentfile
-get_tag_header commentfile-signed-tag $commit commit $time >expect
-echo '-----BEGIN PGP SIGNATURE-----' >>expect
-test_expect_success GPG \
- 'creating a signed tag with #commented -F messagefile should succeed' '
+test_expect_success GPG 'creating a signed tag with #commented -F messagefile should succeed' '
+ echo "#comment" >sigcommentfile &&
+ echo "" >>sigcommentfile &&
+ echo "####" >>sigcommentfile &&
+ get_tag_header commentfile-signed-tag $commit commit $time >expect &&
+ echo "-----BEGIN PGP SIGNATURE-----" >>expect &&
git tag -s -F sigcommentfile commentfile-signed-tag &&
get_tag_msg commentfile-signed-tag >actual &&
test_cmp expect actual &&
git tag -v commentfile-signed-tag
'
-printf '#comment' >sigcommentnonlfile
-get_tag_header commentnonlfile-signed-tag $commit commit $time >expect
-echo '-----BEGIN PGP SIGNATURE-----' >>expect
-test_expect_success GPG \
- 'creating a signed tag with a #comment and no newline should succeed' '
+test_expect_success GPG 'creating a signed tag with a #comment and no newline should succeed' '
+ printf "#comment" >sigcommentnonlfile &&
+ get_tag_header commentnonlfile-signed-tag $commit commit $time >expect &&
+ echo "-----BEGIN PGP SIGNATURE-----" >>expect &&
git tag -s -F sigcommentnonlfile commentnonlfile-signed-tag &&
get_tag_msg commentnonlfile-signed-tag >actual &&
test_cmp expect actual &&
@@ -1312,8 +1277,7 @@ test_expect_success GPG \
# listing messages for signed tags:
-test_expect_success GPG \
- 'listing the one-line message of a signed tag should succeed' '
+test_expect_success GPG 'listing the one-line message of a signed tag should succeed' '
git tag -s -m "A message line signed" stag-one-line &&
echo "stag-one-line" >expect &&
@@ -1337,8 +1301,7 @@ test_expect_success GPG \
test_cmp expect actual
'
-test_expect_success GPG \
- 'listing the zero-lines message of a signed tag should succeed' '
+test_expect_success GPG 'listing the zero-lines message of a signed tag should succeed' '
git tag -s -m "" stag-zero-lines &&
echo "stag-zero-lines" >expect &&
@@ -1362,11 +1325,10 @@ test_expect_success GPG \
test_cmp expect actual
'
-echo 'stag line one' >sigtagmsg
-echo 'stag line two' >>sigtagmsg
-echo 'stag line three' >>sigtagmsg
-test_expect_success GPG \
- 'listing many message lines of a signed tag should succeed' '
+test_expect_success GPG 'listing many message lines of a signed tag should succeed' '
+ echo "stag line one" >sigtagmsg &&
+ echo "stag line two" >>sigtagmsg &&
+ echo "stag line three" >>sigtagmsg &&
git tag -s -F sigtagmsg stag-lines &&
echo "stag-lines" >expect &&
@@ -1408,74 +1370,64 @@ test_expect_success GPG \
# tags pointing to objects different from commits:
-tree=$(git rev-parse HEAD^{tree})
-blob=$(git rev-parse HEAD:foo)
-tag=$(git rev-parse signed-tag 2>/dev/null)
-
-get_tag_header tree-signed-tag $tree tree $time >expect
-echo "A message for a tree" >>expect
-echo '-----BEGIN PGP SIGNATURE-----' >>expect
-test_expect_success GPG \
- 'creating a signed tag pointing to a tree should succeed' '
+test_expect_success GPG 'creating a signed tag pointing to a tree should succeed' '
+ tree=$(git rev-parse HEAD^{tree}) &&
+ get_tag_header tree-signed-tag $tree tree $time >expect &&
+ echo "A message for a tree" >>expect &&
+ echo "-----BEGIN PGP SIGNATURE-----" >>expect &&
git tag -s -m "A message for a tree" tree-signed-tag HEAD^{tree} &&
get_tag_msg tree-signed-tag >actual &&
test_cmp expect actual
'
-get_tag_header blob-signed-tag $blob blob $time >expect
-echo "A message for a blob" >>expect
-echo '-----BEGIN PGP SIGNATURE-----' >>expect
-test_expect_success GPG \
- 'creating a signed tag pointing to a blob should succeed' '
+test_expect_success GPG 'creating a signed tag pointing to a blob should succeed' '
+ blob=$(git rev-parse HEAD:foo) &&
+ get_tag_header blob-signed-tag $blob blob $time >expect &&
+ echo "A message for a blob" >>expect &&
+ echo "-----BEGIN PGP SIGNATURE-----" >>expect &&
git tag -s -m "A message for a blob" blob-signed-tag HEAD:foo &&
get_tag_msg blob-signed-tag >actual &&
test_cmp expect actual
'
-get_tag_header tag-signed-tag $tag tag $time >expect
-echo "A message for another tag" >>expect
-echo '-----BEGIN PGP SIGNATURE-----' >>expect
-test_expect_success GPG \
- 'creating a signed tag pointing to another tag should succeed' '
+test_expect_success GPG 'creating a signed tag pointing to another tag should succeed' '
+ tag=$(git rev-parse signed-tag 2>/dev/null) &&
+ get_tag_header tag-signed-tag $tag tag $time >expect &&
+ echo "A message for another tag" >>expect &&
+ echo "-----BEGIN PGP SIGNATURE-----" >>expect &&
git tag -s -m "A message for another tag" tag-signed-tag signed-tag &&
get_tag_msg tag-signed-tag >actual &&
test_cmp expect actual
'
# usage with rfc1991 signatures
-get_tag_header rfc1991-signed-tag $commit commit $time >expect
-echo "RFC1991 signed tag" >>expect
-echo '-----BEGIN PGP MESSAGE-----' >>expect
-test_expect_success GPG,RFC1991 \
- 'creating a signed tag with rfc1991' '
+
+test_expect_success GPG,RFC1991 'creating a signed tag with rfc1991' '
+ get_tag_header rfc1991-signed-tag $commit commit $time >expect &&
+ echo "RFC1991 signed tag" >>expect &&
+ echo "-----BEGIN PGP MESSAGE-----" >>expect &&
echo "rfc1991" >gpghome/gpg.conf &&
git tag -s -m "RFC1991 signed tag" rfc1991-signed-tag $commit &&
get_tag_msg rfc1991-signed-tag >actual &&
test_cmp expect actual
'
-cat >fakeeditor <<'EOF'
-#!/bin/sh
-cp "$1" actual
-EOF
-chmod +x fakeeditor
-
-test_expect_success GPG,RFC1991 \
- 'reediting a signed tag body omits signature' '
+test_expect_success GPG,RFC1991 'reediting a signed tag body omits signature' '
+ write_script fakeeditor <<-\EOF &&
+ cp "$1" actual
+ EOF
echo "rfc1991" >gpghome/gpg.conf &&
echo "RFC1991 signed tag" >expect &&
GIT_EDITOR=./fakeeditor git tag -f -s rfc1991-signed-tag $commit &&
test_cmp expect actual
'
-test_expect_success GPG,RFC1991 \
- 'verifying rfc1991 signature' '
+test_expect_success GPG,RFC1991 'verifying rfc1991 signature' '
echo "rfc1991" >gpghome/gpg.conf &&
git tag -v rfc1991-signed-tag
'
-test_expect_success GPG,RFC1991 \
- 'list tag with rfc1991 signature' '
+test_expect_success GPG,RFC1991 'list tag with rfc1991 signature' '
echo "rfc1991" >gpghome/gpg.conf &&
echo "rfc1991-signed-tag RFC1991 signed tag" >expect &&
git tag -l -n1 rfc1991-signed-tag >actual &&
@@ -1486,15 +1438,12 @@ test_expect_success GPG,RFC1991 \
test_cmp expect actual
'
-rm -f gpghome/gpg.conf
-
-test_expect_success GPG,RFC1991 \
- 'verifying rfc1991 signature without --rfc1991' '
+test_expect_success GPG,RFC1991 'verifying rfc1991 signature without --rfc1991' '
+ rm -f gpghome/gpg.conf &&
git tag -v rfc1991-signed-tag
'
-test_expect_success GPG,RFC1991 \
- 'list tag with rfc1991 signature without --rfc1991' '
+test_expect_success GPG,RFC1991 'list tag with rfc1991 signature without --rfc1991' '
echo "rfc1991-signed-tag RFC1991 signed tag" >expect &&
git tag -l -n1 rfc1991-signed-tag >actual &&
test_cmp expect actual &&
@@ -1504,24 +1453,23 @@ test_expect_success GPG,RFC1991 \
test_cmp expect actual
'
-test_expect_success GPG,RFC1991 \
- 'reediting a signed tag body omits signature' '
+test_expect_success GPG,RFC1991 'reediting a signed tag body omits signature' '
echo "RFC1991 signed tag" >expect &&
GIT_EDITOR=./fakeeditor git tag -f -s rfc1991-signed-tag $commit &&
test_cmp expect actual
'
# try to sign with bad user.signingkey
-test_expect_success GPG \
- 'git tag -s fails if gpg is misconfigured (bad key)' \
- 'test_config user.signingkey BobTheMouse &&
- test_must_fail git tag -s -m tail tag-gpg-failure'
+test_expect_success GPG 'git tag -s fails if gpg is misconfigured (bad key)' '
+ test_config user.signingkey BobTheMouse &&
+ test_must_fail git tag -s -m tail tag-gpg-failure
+'
# try to produce invalid signature
-test_expect_success GPG \
- 'git tag -s fails if gpg is misconfigured (bad signature format)' \
- 'test_config gpg.program echo &&
- test_must_fail git tag -s -m tail tag-gpg-failure'
+test_expect_success GPG 'git tag -s fails if gpg is misconfigured (bad signature format)' '
+ test_config gpg.program echo &&
+ test_must_fail git tag -s -m tail tag-gpg-failure
+'
# try to produce invalid signature
test_expect_success GPG 'git verifies tag is valid with double signature' '
@@ -1542,34 +1490,32 @@ test_expect_success GPG 'git verifies tag is valid with double signature' '
'
# try to sign with bad user.signingkey
-test_expect_success GPGSM \
- 'git tag -s fails if gpgsm is misconfigured (bad key)' \
- 'test_config user.signingkey BobTheMouse &&
- test_config gpg.format x509 &&
- test_must_fail git tag -s -m tail tag-gpg-failure'
+test_expect_success GPGSM 'git tag -s fails if gpgsm is misconfigured (bad key)' '
+ test_config user.signingkey BobTheMouse &&
+ test_config gpg.format x509 &&
+ test_must_fail git tag -s -m tail tag-gpg-failure
+'
# try to produce invalid signature
-test_expect_success GPGSM \
- 'git tag -s fails if gpgsm is misconfigured (bad signature format)' \
- 'test_config gpg.x509.program echo &&
- test_config gpg.format x509 &&
- test_must_fail git tag -s -m tail tag-gpg-failure'
+test_expect_success GPGSM 'git tag -s fails if gpgsm is misconfigured (bad signature format)' '
+ test_config gpg.x509.program echo &&
+ test_config gpg.format x509 &&
+ test_must_fail git tag -s -m tail tag-gpg-failure
+'
# try to verify without gpg:
-rm -rf gpghome
-test_expect_success GPG \
- 'verify signed tag fails when public key is not present' \
- 'test_must_fail git tag -v signed-tag'
+test_expect_success GPG 'verify signed tag fails when public key is not present' '
+ rm -rf gpghome &&
+ test_must_fail git tag -v signed-tag
+'
-test_expect_success \
- 'git tag -a fails if tag annotation is empty' '
+test_expect_success 'git tag -a fails if tag annotation is empty' '
! (GIT_EDITOR=cat git tag -a initial-comment)
'
-test_expect_success \
- 'message in editor has initial comment' '
- ! (GIT_EDITOR=cat git tag -a initial-comment > actual)
+test_expect_success 'message in editor has initial comment' '
+ ! (GIT_EDITOR=cat git tag -a initial-comment >actual)
'
test_expect_success 'message in editor has initial comment: first line' '
@@ -1579,17 +1525,15 @@ test_expect_success 'message in editor has initial comment: first line' '
test_cmp first.expect first.actual
'
-test_expect_success \
- 'message in editor has initial comment: remainder' '
+test_expect_success 'message in editor has initial comment: remainder' '
# remove commented lines from the remainder -- should be empty
sed -e 1d -e "/^#/d" <actual >rest.actual &&
test_must_be_empty rest.actual
'
-get_tag_header reuse $commit commit $time >expect
-echo "An annotation to be reused" >> expect
-test_expect_success \
- 'overwriting an annotated tag should use its previous body' '
+test_expect_success 'overwriting an annotated tag should use its previous body' '
+ get_tag_header reuse $commit commit $time >expect &&
+ echo "An annotation to be reused" >>expect &&
git tag -a -m "An annotation to be reused" reuse &&
GIT_EDITOR=true git tag -f -a reuse &&
get_tag_msg reuse >actual &&
@@ -1618,192 +1562,202 @@ test_expect_success 'filename for the message is relative to cwd' '
# create a few more commits to test --contains
-hash1=$(git rev-parse HEAD)
-
test_expect_success 'creating second commit and tag' '
+ hash1=$(git rev-parse HEAD) &&
echo foo-2.0 >foo &&
git add foo &&
git commit -m second &&
git tag v2.0
'
-hash2=$(git rev-parse HEAD)
-
test_expect_success 'creating third commit without tag' '
+ hash2=$(git rev-parse HEAD) &&
echo foo-dev >foo &&
git add foo &&
git commit -m third
'
-hash3=$(git rev-parse HEAD)
-
# simple linear checks of --continue
-cat > expected <<EOF
-v0.2.1
-v1.0
-v1.0.1
-v1.1.3
-v2.0
-EOF
-
-test_expect_success 'checking that first commit is in all tags (hash)' "
+test_expect_success 'checking that first commit is in all tags (hash)' '
+ hash3=$(git rev-parse HEAD) &&
+ cat >expected <<-\EOF &&
+ v0.2.1
+ v1.0
+ v1.0.1
+ v1.1.3
+ v2.0
+ EOF
git tag -l --contains $hash1 v* >actual &&
test_cmp expected actual
-"
+'
# other ways of specifying the commit
-test_expect_success 'checking that first commit is in all tags (tag)' "
+test_expect_success 'checking that first commit is in all tags (tag)' '
+ cat >expected <<-\EOF &&
+ v0.2.1
+ v1.0
+ v1.0.1
+ v1.1.3
+ v2.0
+ EOF
git tag -l --contains v1.0 v* >actual &&
test_cmp expected actual
-"
+'
-test_expect_success 'checking that first commit is in all tags (relative)' "
+test_expect_success 'checking that first commit is in all tags (relative)' '
+ cat >expected <<-\EOF &&
+ v0.2.1
+ v1.0
+ v1.0.1
+ v1.1.3
+ v2.0
+ EOF
git tag -l --contains HEAD~2 v* >actual &&
test_cmp expected actual
-"
+'
# All the --contains tests above, but with --no-contains
-test_expect_success 'checking that first commit is not listed in any tag with --no-contains (hash)' "
+test_expect_success 'checking that first commit is not listed in any tag with --no-contains (hash)' '
git tag -l --no-contains $hash1 v* >actual &&
test_must_be_empty actual
-"
+'
-test_expect_success 'checking that first commit is in all tags (tag)' "
+test_expect_success 'checking that first commit is in all tags (tag)' '
git tag -l --no-contains v1.0 v* >actual &&
test_must_be_empty actual
-"
+'
-test_expect_success 'checking that first commit is in all tags (relative)' "
+test_expect_success 'checking that first commit is in all tags (relative)' '
git tag -l --no-contains HEAD~2 v* >actual &&
test_must_be_empty actual
-"
-
-cat > expected <<EOF
-v2.0
-EOF
+'
-test_expect_success 'checking that second commit only has one tag' "
+test_expect_success 'checking that second commit only has one tag' '
+ cat >expected <<-\EOF &&
+ v2.0
+ EOF
git tag -l --contains $hash2 v* >actual &&
test_cmp expected actual
-"
-
-cat > expected <<EOF
-v0.2.1
-v1.0
-v1.0.1
-v1.1.3
-EOF
+'
-test_expect_success 'inverse of the last test, with --no-contains' "
+test_expect_success 'inverse of the last test, with --no-contains' '
+ cat >expected <<-\EOF &&
+ v0.2.1
+ v1.0
+ v1.0.1
+ v1.1.3
+ EOF
git tag -l --no-contains $hash2 v* >actual &&
test_cmp expected actual
-"
+'
-test_expect_success 'checking that third commit has no tags' "
+test_expect_success 'checking that third commit has no tags' '
git tag -l --contains $hash3 v* >actual &&
test_must_be_empty actual
-"
-
-cat > expected <<EOF
-v0.2.1
-v1.0
-v1.0.1
-v1.1.3
-v2.0
-EOF
+'
-test_expect_success 'conversely --no-contains on the third commit lists all tags' "
+test_expect_success 'conversely --no-contains on the third commit lists all tags' '
+ cat >expected <<-\EOF &&
+ v0.2.1
+ v1.0
+ v1.0.1
+ v1.1.3
+ v2.0
+ EOF
git tag -l --no-contains $hash3 v* >actual &&
test_cmp expected actual
-"
+'
# how about a simple merge?
test_expect_success 'creating simple branch' '
git branch stable v2.0 &&
git checkout stable &&
- echo foo-3.0 > foo &&
+ echo foo-3.0 >foo &&
git commit foo -m fourth &&
git tag v3.0
'
-hash4=$(git rev-parse HEAD)
-
-cat > expected <<EOF
-v3.0
-EOF
-
-test_expect_success 'checking that branch head only has one tag' "
+test_expect_success 'checking that branch head only has one tag' '
+ hash4=$(git rev-parse HEAD) &&
+ cat >expected <<-\EOF &&
+ v3.0
+ EOF
git tag -l --contains $hash4 v* >actual &&
test_cmp expected actual
-"
-
-cat > expected <<EOF
-v0.2.1
-v1.0
-v1.0.1
-v1.1.3
-v2.0
-EOF
+'
-test_expect_success 'checking that branch head with --no-contains lists all but one tag' "
+test_expect_success 'checking that branch head with --no-contains lists all but one tag' '
+ cat >expected <<-\EOF &&
+ v0.2.1
+ v1.0
+ v1.0.1
+ v1.1.3
+ v2.0
+ EOF
git tag -l --no-contains $hash4 v* >actual &&
test_cmp expected actual
-"
+'
test_expect_success 'merging original branch into this branch' '
git merge --strategy=ours main &&
git tag v4.0
'
-cat > expected <<EOF
-v4.0
-EOF
-
-test_expect_success 'checking that original branch head has one tag now' "
+test_expect_success 'checking that original branch head has one tag now' '
+ cat >expected <<-\EOF &&
+ v4.0
+ EOF
git tag -l --contains $hash3 v* >actual &&
test_cmp expected actual
-"
-
-cat > expected <<EOF
-v0.2.1
-v1.0
-v1.0.1
-v1.1.3
-v2.0
-v3.0
-EOF
+'
-test_expect_success 'checking that original branch head with --no-contains lists all but one tag now' "
+test_expect_success 'checking that original branch head with --no-contains lists all but one tag now' '
+ cat >expected <<-\EOF &&
+ v0.2.1
+ v1.0
+ v1.0.1
+ v1.1.3
+ v2.0
+ v3.0
+ EOF
git tag -l --no-contains $hash3 v* >actual &&
test_cmp expected actual
-"
-
-cat > expected <<EOF
-v0.2.1
-v1.0
-v1.0.1
-v1.1.3
-v2.0
-v3.0
-v4.0
-EOF
+'
-test_expect_success 'checking that initial commit is in all tags' "
+test_expect_success 'checking that initial commit is in all tags' '
+ cat >expected <<-\EOF &&
+ v0.2.1
+ v1.0
+ v1.0.1
+ v1.1.3
+ v2.0
+ v3.0
+ v4.0
+ EOF
git tag -l --contains $hash1 v* >actual &&
test_cmp expected actual
-"
+'
test_expect_success 'checking that --contains can be used in non-list mode' '
+ cat >expected <<-\EOF &&
+ v0.2.1
+ v1.0
+ v1.0.1
+ v1.1.3
+ v2.0
+ v3.0
+ v4.0
+ EOF
git tag --contains $hash1 v* >actual &&
test_cmp expected actual
'
-test_expect_success 'checking that initial commit is in all tags with --no-contains' "
+test_expect_success 'checking that initial commit is in all tags with --no-contains' '
git tag -l --no-contains $hash1 v* >actual &&
test_must_be_empty actual
-"
+'
# mixing modes and options:
@@ -1840,16 +1794,16 @@ test_expect_success 'mixing incompatibles modes and options is forbidden' '
for option in --contains --with --no-contains --without --merged --no-merged --points-at
do
- test_expect_success "mixing incompatible modes with $option is forbidden" "
+ test_expect_success "mixing incompatible modes with $option is forbidden" '
test_must_fail git tag -d $option HEAD &&
test_must_fail git tag -d $option HEAD some-tag &&
test_must_fail git tag -v $option HEAD
- "
- test_expect_success "Doing 'git tag --list-like $option <commit> <pattern> is permitted" "
+ '
+ test_expect_success "Doing 'git tag --list-like $option <commit> <pattern> is permitted" '
git tag -n $option HEAD HEAD &&
git tag $option HEAD HEAD &&
git tag $option
- "
+ '
done
# check points-at
@@ -2225,7 +2179,7 @@ test_expect_success 'git tag -l with --format="%(rest)" must fail' '
test_must_fail git tag -l --format="%(rest)" "v1*"
'
-test_expect_success "set up color tests" '
+test_expect_success 'set up color tests' '
echo "<RED>v1.0<RESET>" >expect.color &&
echo "v1.0" >expect.bare &&
color_args="--format=%(color:red)%(refname:short) --list v1.0"
diff --git a/t/t7008-filter-branch-null-sha1.sh b/t/t7008-filter-branch-null-sha1.sh
index 93fbc92b8d..0ce8fd2c89 100755
--- a/t/t7008-filter-branch-null-sha1.sh
+++ b/t/t7008-filter-branch-null-sha1.sh
@@ -2,6 +2,7 @@
test_description='filter-branch removal of trees with null sha1'
+TEST_PASSES_SANITIZE_LEAK=true
. ./test-lib.sh
test_expect_success 'setup: base commits' '
diff --git a/t/t7030-verify-tag.sh b/t/t7030-verify-tag.sh
index 6f526c37c2..effa826744 100755
--- a/t/t7030-verify-tag.sh
+++ b/t/t7030-verify-tag.sh
@@ -4,6 +4,7 @@ test_description='signed tag tests'
GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
+TEST_PASSES_SANITIZE_LEAK=true
. ./test-lib.sh
. "$TEST_DIRECTORY/lib-gpg.sh"
diff --git a/t/t7061-wtstatus-ignore.sh b/t/t7061-wtstatus-ignore.sh
index 2f9bea9793..64145a05b1 100755
--- a/t/t7061-wtstatus-ignore.sh
+++ b/t/t7061-wtstatus-ignore.sh
@@ -2,6 +2,7 @@
test_description='git-status ignored files'
+TEST_PASSES_SANITIZE_LEAK=true
. ./test-lib.sh
cat >expected <<\EOF
diff --git a/t/t7064-wtstatus-pv2.sh b/t/t7064-wtstatus-pv2.sh
index 11884d2fc3..06c1301222 100755
--- a/t/t7064-wtstatus-pv2.sh
+++ b/t/t7064-wtstatus-pv2.sh
@@ -4,6 +4,7 @@ test_description='git status --porcelain=v2
This test exercises porcelain V2 output for git status.'
+TEST_PASSES_SANITIZE_LEAK=true
. ./test-lib.sh
diff --git a/t/t7400-submodule-basic.sh b/t/t7400-submodule-basic.sh
index 981488885f..098d8833b6 100755
--- a/t/t7400-submodule-basic.sh
+++ b/t/t7400-submodule-basic.sh
@@ -12,6 +12,7 @@ subcommands of git submodule.
GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
+TEST_PASSES_SANITIZE_LEAK=true
. ./test-lib.sh
test_expect_success 'setup - enable local submodules' '
diff --git a/t/t7406-submodule-update.sh b/t/t7406-submodule-update.sh
index 297c6c3b5c..0f0c86f9cb 100755
--- a/t/t7406-submodule-update.sh
+++ b/t/t7406-submodule-update.sh
@@ -12,6 +12,7 @@ submodule and "git submodule update --rebase/--merge" does not detach the HEAD.
GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
+TEST_PASSES_SANITIZE_LEAK=true
. ./test-lib.sh
diff --git a/t/t7407-submodule-foreach.sh b/t/t7407-submodule-foreach.sh
index 8d7b234beb..9f68893261 100755
--- a/t/t7407-submodule-foreach.sh
+++ b/t/t7407-submodule-foreach.sh
@@ -12,6 +12,7 @@ that are currently checked out.
GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
+TEST_PASSES_SANITIZE_LEAK=true
. ./test-lib.sh
diff --git a/t/t7408-submodule-reference.sh b/t/t7408-submodule-reference.sh
index d6040e0a33..7e1afa9ce4 100755
--- a/t/t7408-submodule-reference.sh
+++ b/t/t7408-submodule-reference.sh
@@ -4,6 +4,8 @@
#
test_description='test clone --reference'
+
+TEST_PASSES_SANITIZE_LEAK=true
. ./test-lib.sh
base_dir=$(pwd)
diff --git a/t/t7411-submodule-config.sh b/t/t7411-submodule-config.sh
index 31271f8e0a..af0de496e0 100755
--- a/t/t7411-submodule-config.sh
+++ b/t/t7411-submodule-config.sh
@@ -10,6 +10,7 @@ from the database and from the worktree works.
'
TEST_NO_CREATE_REPO=1
+TEST_PASSES_SANITIZE_LEAK=true
. ./test-lib.sh
test_expect_success 'setup' '
diff --git a/t/t7418-submodule-sparse-gitmodules.sh b/t/t7418-submodule-sparse-gitmodules.sh
index dde11ecce8..e1d9bf2ee3 100755
--- a/t/t7418-submodule-sparse-gitmodules.sh
+++ b/t/t7418-submodule-sparse-gitmodules.sh
@@ -15,6 +15,7 @@ also by committing .gitmodules and then just removing it from the filesystem.
GIT_TEST_FATAL_REGISTER_SUBMODULE_ODB=1
export GIT_TEST_FATAL_REGISTER_SUBMODULE_ODB
+TEST_PASSES_SANITIZE_LEAK=true
. ./test-lib.sh
test_expect_success 'setup' '
diff --git a/t/t7420-submodule-set-url.sh b/t/t7420-submodule-set-url.sh
index bf7f15ee79..d7fe910bbe 100755
--- a/t/t7420-submodule-set-url.sh
+++ b/t/t7420-submodule-set-url.sh
@@ -10,6 +10,7 @@ as expected.
'
TEST_NO_CREATE_REPO=1
+TEST_PASSES_SANITIZE_LEAK=true
. ./test-lib.sh
test_expect_success 'setup' '
diff --git a/t/t7422-submodule-output.sh b/t/t7422-submodule-output.sh
index ab946ec940..c1686d6bb5 100755
--- a/t/t7422-submodule-output.sh
+++ b/t/t7422-submodule-output.sh
@@ -167,4 +167,11 @@ do
'
done
+test_expect_success !MINGW 'git submodule status --recursive propagates SIGPIPE' '
+ { git submodule status --recursive 2>err; echo $?>status; } |
+ grep -q X/S &&
+ test_must_be_empty err &&
+ test_match_signal 13 "$(cat status)"
+'
+
test_done
diff --git a/t/t7424-submodule-mixed-ref-formats.sh b/t/t7424-submodule-mixed-ref-formats.sh
new file mode 100755
index 0000000000..b43ef2ba67
--- /dev/null
+++ b/t/t7424-submodule-mixed-ref-formats.sh
@@ -0,0 +1,144 @@
+#!/bin/sh
+
+test_description='submodules handle mixed ref storage formats'
+
+TEST_PASSES_SANITIZE_LEAK=true
+. ./test-lib.sh
+
+test_ref_format () {
+ echo "$2" >expect &&
+ git -C "$1" rev-parse --show-ref-format >actual &&
+ test_cmp expect actual
+}
+
+for OTHER_FORMAT in files reftable
+do
+ if test "$OTHER_FORMAT" = "$GIT_DEFAULT_REF_FORMAT"
+ then
+ continue
+ fi
+
+test_expect_success 'setup' '
+ git config set --global protocol.file.allow always &&
+ # Some tests migrate the ref storage format, which does not work with
+ # reflogs at the time of writing these tests.
+ git config set --global core.logAllRefUpdates false
+'
+
+test_expect_success 'add existing repository with different ref storage format' '
+ test_when_finished "rm -rf parent" &&
+
+ git init parent &&
+ (
+ cd parent &&
+ test_commit parent &&
+ git init --ref-format=$OTHER_FORMAT submodule &&
+ test_commit -C submodule submodule &&
+ git submodule add ./submodule
+ )
+'
+
+test_expect_success 'add submodules with different ref storage format' '
+ test_when_finished "rm -rf submodule upstream" &&
+
+ git init submodule &&
+ test_commit -C submodule submodule-initial &&
+ git init upstream &&
+ test_ref_format upstream "$GIT_DEFAULT_REF_FORMAT" &&
+ git -C upstream submodule add --ref-format="$OTHER_FORMAT" "file://$(pwd)/submodule" &&
+ test_ref_format upstream/submodule "$OTHER_FORMAT"
+'
+
+test_expect_success 'recursive clone propagates ref storage format' '
+ test_when_finished "rm -rf submodule upstream downstream" &&
+
+ git init submodule &&
+ test_commit -C submodule submodule-initial &&
+ git init upstream &&
+ git -C upstream submodule add "file://$(pwd)/submodule" &&
+ git -C upstream commit -am "add submodule" &&
+
+ # The upstream repository and its submodule should be using the default
+ # ref format.
+ test_ref_format upstream "$GIT_DEFAULT_REF_FORMAT" &&
+ test_ref_format upstream/submodule "$GIT_DEFAULT_REF_FORMAT" &&
+
+ # The cloned repositories should use the other ref format that we have
+ # specified via `--ref-format`. The option should propagate to cloned
+ # submodules.
+ git clone --ref-format=$OTHER_FORMAT --recurse-submodules \
+ upstream downstream &&
+ test_ref_format downstream "$OTHER_FORMAT" &&
+ test_ref_format downstream/submodule "$OTHER_FORMAT"
+'
+
+test_expect_success 'clone submodules with different ref storage format' '
+ test_when_finished "rm -rf submodule upstream downstream" &&
+
+ git init submodule &&
+ test_commit -C submodule submodule-initial &&
+ git init upstream &&
+ git -C upstream submodule add "file://$(pwd)/submodule" &&
+ git -C upstream commit -m "upstream submodule" &&
+
+ git clone --no-recurse-submodules "file://$(pwd)/upstream" downstream &&
+ test_ref_format downstream "$GIT_DEFAULT_REF_FORMAT" &&
+ git -C downstream submodule update --init --ref-format=$OTHER_FORMAT &&
+ test_ref_format downstream/submodule "$OTHER_FORMAT"
+'
+
+test_expect_success 'status with mixed submodule ref storages' '
+ test_when_finished "rm -rf submodule main" &&
+
+ git init submodule &&
+ test_commit -C submodule submodule-initial &&
+ git init main &&
+ git -C main submodule add "file://$(pwd)/submodule" &&
+ git -C main commit -m "add submodule" &&
+ git -C main/submodule refs migrate --ref-format=$OTHER_FORMAT &&
+
+ # The main repository should use the default ref format now, whereas
+ # the submodule should use the other format.
+ test_ref_format main "$GIT_DEFAULT_REF_FORMAT" &&
+ test_ref_format main/submodule "$OTHER_FORMAT" &&
+
+ cat >expect <<-EOF &&
+ $(git -C main/submodule rev-parse HEAD) submodule (submodule-initial)
+ EOF
+ git -C main submodule status >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'recursive pull with mixed formats' '
+ test_when_finished "rm -rf submodule upstream downstream" &&
+
+ # Set up the initial structure with an upstream repository that has a
+ # submodule, as well as a downstream clone of the upstream repository.
+ git init submodule &&
+ test_commit -C submodule submodule-initial &&
+ git init upstream &&
+ git -C upstream submodule add "file://$(pwd)/submodule" &&
+ git -C upstream commit -m "upstream submodule" &&
+
+ # Clone the upstream repository such that the main repo and its
+ # submodules have different formats.
+ git clone --no-recurse-submodules "file://$(pwd)/upstream" downstream &&
+ git -C downstream submodule update --init --ref-format=$OTHER_FORMAT &&
+ test_ref_format downstream "$GIT_DEFAULT_REF_FORMAT" &&
+ test_ref_format downstream/submodule "$OTHER_FORMAT" &&
+
+ # Update the upstream submodule as well as the owning repository such
+ # that we can do a recursive pull.
+ test_commit -C submodule submodule-update &&
+ git -C upstream/submodule pull &&
+ git -C upstream commit -am "update the submodule" &&
+
+ git -C downstream pull --recurse-submodules &&
+ git -C upstream/submodule rev-parse HEAD >expect &&
+ git -C downstream/submodule rev-parse HEAD >actual &&
+ test_cmp expect actual
+'
+
+done
+
+test_done
diff --git a/t/t7521-ignored-mode.sh b/t/t7521-ignored-mode.sh
index a88b02b06e..edce10f998 100755
--- a/t/t7521-ignored-mode.sh
+++ b/t/t7521-ignored-mode.sh
@@ -2,6 +2,7 @@
test_description='git status ignored modes'
+TEST_PASSES_SANITIZE_LEAK=true
. ./test-lib.sh
test_expect_success 'setup initial commit and ignore file' '
diff --git a/t/t7524-commit-summary.sh b/t/t7524-commit-summary.sh
index 47b2f1dc22..a8fceb6a47 100755
--- a/t/t7524-commit-summary.sh
+++ b/t/t7524-commit-summary.sh
@@ -1,6 +1,8 @@
#!/bin/sh
test_description='git commit summary'
+
+TEST_PASSES_SANITIZE_LEAK=true
. ./test-lib.sh
test_expect_success 'setup' '
diff --git a/t/t7601-merge-pull-config.sh b/t/t7601-merge-pull-config.sh
index a94387a75f..7fd8c086af 100755
--- a/t/t7601-merge-pull-config.sh
+++ b/t/t7601-merge-pull-config.sh
@@ -4,6 +4,7 @@ test_description='git merge
Testing pull.* configuration parsing and other things.'
+TEST_PASSES_SANITIZE_LEAK=true
. ./test-lib.sh
test_expect_success 'setup' '
diff --git a/t/t7615-diff-algo-with-mergy-operations.sh b/t/t7615-diff-algo-with-mergy-operations.sh
new file mode 100755
index 0000000000..9a83be518c
--- /dev/null
+++ b/t/t7615-diff-algo-with-mergy-operations.sh
@@ -0,0 +1,60 @@
+#!/bin/sh
+
+test_description='git merge and other operations that rely on merge
+
+Testing the influence of the diff algorithm on the merge output.'
+
+TEST_PASSES_SANITIZE_LEAK=true
+. ./test-lib.sh
+
+test_expect_success 'setup' '
+ cp "$TEST_DIRECTORY"/t7615/base.c file.c &&
+ git add file.c &&
+ git commit -m c0 &&
+ git tag c0 &&
+ cp "$TEST_DIRECTORY"/t7615/ours.c file.c &&
+ git add file.c &&
+ git commit -m c1 &&
+ git tag c1 &&
+ git reset --hard c0 &&
+ cp "$TEST_DIRECTORY"/t7615/theirs.c file.c &&
+ git add file.c &&
+ git commit -m c2 &&
+ git tag c2
+'
+
+GIT_TEST_MERGE_ALGORITHM=recursive
+
+test_expect_success 'merge c2 to c1 with recursive merge strategy fails with the current default myers diff algorithm' '
+ git reset --hard c1 &&
+ test_must_fail git merge -s recursive c2
+'
+
+test_expect_success 'merge c2 to c1 with recursive merge strategy succeeds with -Xdiff-algorithm=histogram' '
+ git reset --hard c1 &&
+ git merge --strategy recursive -Xdiff-algorithm=histogram c2
+'
+
+test_expect_success 'merge c2 to c1 with recursive merge strategy succeeds with diff.algorithm = histogram' '
+ git reset --hard c1 &&
+ git config diff.algorithm histogram &&
+ git merge --strategy recursive c2
+'
+
+test_expect_success 'cherry-pick c2 to c1 with recursive merge strategy fails with the current default myers diff algorithm' '
+ git reset --hard c1 &&
+ test_must_fail git cherry-pick -s recursive c2
+'
+
+test_expect_success 'cherry-pick c2 to c1 with recursive merge strategy succeeds with -Xdiff-algorithm=histogram' '
+ git reset --hard c1 &&
+ git cherry-pick --strategy recursive -Xdiff-algorithm=histogram c2
+'
+
+test_expect_success 'cherry-pick c2 to c1 with recursive merge strategy succeeds with diff.algorithm = histogram' '
+ git reset --hard c1 &&
+ git config diff.algorithm histogram &&
+ git cherry-pick --strategy recursive c2
+'
+
+test_done
diff --git a/t/t7615/base.c b/t/t7615/base.c
new file mode 100644
index 0000000000..c64abc5936
--- /dev/null
+++ b/t/t7615/base.c
@@ -0,0 +1,17 @@
+int f(int x, int y)
+{
+ if (x == 0)
+ {
+ return y;
+ }
+ return x;
+}
+
+int g(size_t u)
+{
+ while (u < 30)
+ {
+ u++;
+ }
+ return u;
+}
diff --git a/t/t7615/ours.c b/t/t7615/ours.c
new file mode 100644
index 0000000000..44d8251397
--- /dev/null
+++ b/t/t7615/ours.c
@@ -0,0 +1,17 @@
+int g(size_t u)
+{
+ while (u < 30)
+ {
+ u++;
+ }
+ return u;
+}
+
+int h(int x, int y, int z)
+{
+ if (z == 0)
+ {
+ return x;
+ }
+ return y;
+}
diff --git a/t/t7615/theirs.c b/t/t7615/theirs.c
new file mode 100644
index 0000000000..85f02146fe
--- /dev/null
+++ b/t/t7615/theirs.c
@@ -0,0 +1,17 @@
+int f(int x, int y)
+{
+ if (x == 0)
+ {
+ return y;
+ }
+ return x;
+}
+
+int g(size_t u)
+{
+ while (u > 34)
+ {
+ u--;
+ }
+ return u;
+}
diff --git a/t/t7700-repack.sh b/t/t7700-repack.sh
index 127efe99f8..c4c3d1a15d 100755
--- a/t/t7700-repack.sh
+++ b/t/t7700-repack.sh
@@ -2,11 +2,15 @@
test_description='git repack works correctly'
+TEST_PASSES_SANITIZE_LEAK=true
. ./test-lib.sh
. "${TEST_DIRECTORY}/lib-bitmap.sh"
. "${TEST_DIRECTORY}/lib-midx.sh"
. "${TEST_DIRECTORY}/lib-terminal.sh"
+GIT_TEST_MULTI_PACK_INDEX=0
+GIT_TEST_MULTI_PACK_INDEX_WRITE_INCREMENTAL=0
+
commit_and_pack () {
test_commit "$@" 1>&2 &&
incrpackid=$(git pack-objects --all --unpacked --incremental .git/objects/pack/pack </dev/null) &&
@@ -70,14 +74,13 @@ test_expect_success 'objects in packs marked .keep are not repacked' '
test_expect_success 'writing bitmaps via command-line can duplicate .keep objects' '
# build on $oid, $packid, and .keep state from previous
- GIT_TEST_MULTI_PACK_INDEX_WRITE_BITMAP=0 git repack -Adbl &&
+ git repack -Adbl &&
test_has_duplicate_object true
'
test_expect_success 'writing bitmaps via config can duplicate .keep objects' '
# build on $oid, $packid, and .keep state from previous
- GIT_TEST_MULTI_PACK_INDEX_WRITE_BITMAP=0 \
- git -c repack.writebitmaps=true repack -Adl &&
+ git -c repack.writebitmaps=true repack -Adl &&
test_has_duplicate_object true
'
@@ -118,7 +121,7 @@ test_expect_success '--local disables writing bitmaps when connected to alternat
(
cd member &&
test_commit "object" &&
- GIT_TEST_MULTI_PACK_INDEX=0 git repack -Adl --write-bitmap-index 2>err &&
+ git repack -Adl --write-bitmap-index 2>err &&
cat >expect <<-EOF &&
warning: disabling bitmap writing, as some objects are not being packed
EOF
@@ -284,8 +287,7 @@ test_expect_success 'repacking fails when missing .pack actually means missing o
test_expect_success 'bitmaps are created by default in bare repos' '
git clone --bare .git bare.git &&
rm -f bare.git/objects/pack/*.bitmap &&
- GIT_TEST_MULTI_PACK_INDEX_WRITE_BITMAP=0 \
- git -C bare.git repack -ad &&
+ git -C bare.git repack -ad &&
bitmap=$(ls bare.git/objects/pack/*.bitmap) &&
test_path_is_file "$bitmap"
'
@@ -296,8 +298,7 @@ test_expect_success 'incremental repack does not complain' '
'
test_expect_success 'bitmaps can be disabled on bare repos' '
- GIT_TEST_MULTI_PACK_INDEX_WRITE_BITMAP=0 \
- git -c repack.writeBitmaps=false -C bare.git repack -ad &&
+ git -c repack.writeBitmaps=false -C bare.git repack -ad &&
bitmap=$(ls bare.git/objects/pack/*.bitmap || :) &&
test -z "$bitmap"
'
@@ -308,8 +309,7 @@ test_expect_success 'no bitmaps created if .keep files present' '
keep=${pack%.pack}.keep &&
test_when_finished "rm -f \"\$keep\"" &&
>"$keep" &&
- GIT_TEST_MULTI_PACK_INDEX_WRITE_BITMAP=0 \
- git -C bare.git repack -ad 2>stderr &&
+ git -C bare.git repack -ad 2>stderr &&
test_must_be_empty stderr &&
find bare.git/objects/pack/ -type f -name "*.bitmap" >actual &&
test_must_be_empty actual
@@ -320,8 +320,7 @@ test_expect_success 'auto-bitmaps do not complain if unavailable' '
blob=$(test-tool genrandom big $((1024*1024)) |
git -C bare.git hash-object -w --stdin) &&
git -C bare.git update-ref refs/tags/big $blob &&
- GIT_TEST_MULTI_PACK_INDEX_WRITE_BITMAP=0 \
- git -C bare.git repack -ad 2>stderr &&
+ git -C bare.git repack -ad 2>stderr &&
test_must_be_empty stderr &&
find bare.git/objects/pack -type f -name "*.bitmap" >actual &&
test_must_be_empty actual
@@ -342,9 +341,7 @@ test_expect_success 'repacking with a filter works' '
'
test_expect_success '--filter fails with --write-bitmap-index' '
- test_must_fail \
- env GIT_TEST_MULTI_PACK_INDEX_WRITE_BITMAP=0 \
- git -C bare.git repack -a -d --write-bitmap-index --filter=blob:none
+ test_must_fail git -C bare.git repack -a -d --write-bitmap-index --filter=blob:none
'
test_expect_success 'repacking with two filters works' '
@@ -540,11 +537,11 @@ test_expect_success 'setup for --write-midx tests' '
test_expect_success '--write-midx unchanged' '
(
cd midx &&
- GIT_TEST_MULTI_PACK_INDEX=0 git repack &&
+ git repack &&
test_path_is_missing $midx &&
test_path_is_missing $midx-*.bitmap &&
- GIT_TEST_MULTI_PACK_INDEX=0 git repack --write-midx &&
+ git repack --write-midx &&
test_path_is_file $midx &&
test_path_is_missing $midx-*.bitmap &&
@@ -557,7 +554,7 @@ test_expect_success '--write-midx with a new pack' '
cd midx &&
test_commit loose &&
- GIT_TEST_MULTI_PACK_INDEX=0 git repack --write-midx &&
+ git repack --write-midx &&
test_path_is_file $midx &&
test_path_is_missing $midx-*.bitmap &&
@@ -568,7 +565,7 @@ test_expect_success '--write-midx with a new pack' '
test_expect_success '--write-midx with -b' '
(
cd midx &&
- GIT_TEST_MULTI_PACK_INDEX=0 git repack -mb &&
+ git repack -mb &&
test_path_is_file $midx &&
test_path_is_file $midx-*.bitmap &&
@@ -581,7 +578,7 @@ test_expect_success '--write-midx with -d' '
cd midx &&
test_commit repack &&
- GIT_TEST_MULTI_PACK_INDEX=0 git repack -Ad --write-midx &&
+ git repack -Ad --write-midx &&
test_path_is_file $midx &&
test_path_is_missing $midx-*.bitmap &&
@@ -594,21 +591,21 @@ test_expect_success 'cleans up MIDX when appropriate' '
cd midx &&
test_commit repack-2 &&
- GIT_TEST_MULTI_PACK_INDEX=0 git repack -Adb --write-midx &&
+ git repack -Adb --write-midx &&
checksum=$(midx_checksum $objdir) &&
test_path_is_file $midx &&
test_path_is_file $midx-$checksum.bitmap &&
test_commit repack-3 &&
- GIT_TEST_MULTI_PACK_INDEX=0 git repack -Adb --write-midx &&
+ git repack -Adb --write-midx &&
test_path_is_file $midx &&
test_path_is_missing $midx-$checksum.bitmap &&
test_path_is_file $midx-$(midx_checksum $objdir).bitmap &&
test_commit repack-4 &&
- GIT_TEST_MULTI_PACK_INDEX=0 git repack -Adb &&
+ git repack -Adb &&
find $objdir/pack -type f -name "multi-pack-index*" >files &&
test_must_be_empty files
@@ -629,7 +626,6 @@ test_expect_success '--write-midx with preferred bitmap tips' '
git log --format="create refs/tags/%s/%s %H" HEAD >refs &&
git update-ref --stdin <refs &&
- GIT_TEST_MULTI_PACK_INDEX=0 \
git repack --write-midx --write-bitmap-index &&
test_path_is_file $midx &&
test_path_is_file $midx-$(midx_checksum $objdir).bitmap &&
@@ -721,13 +717,13 @@ test_expect_success '--write-midx removes stale pack-based bitmaps' '
(
cd repo &&
test_commit base &&
- GIT_TEST_MULTI_PACK_INDEX=0 git repack -Ab &&
+ git repack -Ab &&
pack_bitmap=$(ls $objdir/pack/pack-*.bitmap) &&
test_path_is_file "$pack_bitmap" &&
test_commit tip &&
- GIT_TEST_MULTI_PACK_INDEX=0 git repack -bm &&
+ git repack -bm &&
test_path_is_file $midx &&
test_path_is_file $midx-$(midx_checksum $objdir).bitmap &&
@@ -750,7 +746,6 @@ test_expect_success '--write-midx with --pack-kept-objects' '
keep="$objdir/pack/pack-$one.keep" &&
touch "$keep" &&
- GIT_TEST_MULTI_PACK_INDEX=0 \
git repack --write-midx --write-bitmap-index --geometric=2 -d \
--pack-kept-objects &&
diff --git a/t/t7703-repack-geometric.sh b/t/t7703-repack-geometric.sh
index 9fc1626fbf..8877aea98b 100755
--- a/t/t7703-repack-geometric.sh
+++ b/t/t7703-repack-geometric.sh
@@ -2,6 +2,7 @@
test_description='git repack --geometric works correctly'
+TEST_PASSES_SANITIZE_LEAK=true
. ./test-lib.sh
GIT_TEST_MULTI_PACK_INDEX=0
diff --git a/t/t7704-repack-cruft.sh b/t/t7704-repack-cruft.sh
index 71e1ef3a10..5db9f4e10f 100755
--- a/t/t7704-repack-cruft.sh
+++ b/t/t7704-repack-cruft.sh
@@ -2,6 +2,7 @@
test_description='git repack works correctly'
+TEST_PASSES_SANITIZE_LEAK=true
. ./test-lib.sh
objdir=.git/objects
@@ -330,7 +331,7 @@ test_expect_success '--max-cruft-size with pruning' '
# repack (and prune) with a --max-cruft-size to ensure
# that we appropriately split the resulting set of packs
git repack -d --cruft --max-cruft-size=1M \
- --cruft-expiration=10.seconds.ago &&
+ --cruft-expiration=1000.seconds.ago &&
ls $packdir/pack-*.mtimes | sort >cruft.after &&
for cruft in $(cat cruft.after)
diff --git a/t/t7800-difftool.sh b/t/t7800-difftool.sh
index cc917b257e..f67b9345b8 100755
--- a/t/t7800-difftool.sh
+++ b/t/t7800-difftool.sh
@@ -11,6 +11,7 @@ Testing basic diff tool invocation
GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
+TEST_PASSES_SANITIZE_LEAK=true
. ./test-lib.sh
difftool_test_setup ()
diff --git a/t/t7810-grep.sh b/t/t7810-grep.sh
index 875dcfd98f..af2cf2f78a 100755
--- a/t/t7810-grep.sh
+++ b/t/t7810-grep.sh
@@ -31,6 +31,7 @@ int main(int argc, const char **argv)
return 0;
/* char ?? */
}
+
EOF
test_expect_success setup '
diff --git a/t/t7814-grep-recurse-submodules.sh b/t/t7814-grep-recurse-submodules.sh
index 167fe66150..55ed630e77 100755
--- a/t/t7814-grep-recurse-submodules.sh
+++ b/t/t7814-grep-recurse-submodules.sh
@@ -7,6 +7,7 @@ submodules.
'
TEST_CREATE_REPO_NO_TEMPLATE=1
+TEST_PASSES_SANITIZE_LEAK=true
. ./test-lib.sh
GIT_TEST_FATAL_REGISTER_SUBMODULE_ODB=1
diff --git a/t/t7817-grep-sparse-checkout.sh b/t/t7817-grep-sparse-checkout.sh
index eb59564565..0ba7817fb7 100755
--- a/t/t7817-grep-sparse-checkout.sh
+++ b/t/t7817-grep-sparse-checkout.sh
@@ -33,6 +33,7 @@ should leave the following structure in the working tree:
But note that sub2 should have the SKIP_WORKTREE bit set.
'
+TEST_PASSES_SANITIZE_LEAK=true
. ./test-lib.sh
test_expect_success 'setup' '
diff --git a/t/t7900-maintenance.sh b/t/t7900-maintenance.sh
index 8595489ceb..a66d0e089d 100755
--- a/t/t7900-maintenance.sh
+++ b/t/t7900-maintenance.sh
@@ -2,6 +2,7 @@
test_description='git maintenance builtin'
+TEST_PASSES_SANITIZE_LEAK=true
. ./test-lib.sh
GIT_TEST_COMMIT_GRAPH=0
@@ -49,22 +50,47 @@ test_expect_success 'run [--auto|--quiet]' '
git maintenance run --auto 2>/dev/null &&
GIT_TRACE2_EVENT="$(pwd)/run-no-quiet.txt" \
git maintenance run --no-quiet 2>/dev/null &&
- test_subcommand git gc --quiet <run-no-auto.txt &&
- test_subcommand ! git gc --auto --quiet <run-auto.txt &&
- test_subcommand git gc --no-quiet <run-no-quiet.txt
+ test_subcommand git gc --quiet --no-detach <run-no-auto.txt &&
+ test_subcommand ! git gc --auto --quiet --no-detach <run-auto.txt &&
+ test_subcommand git gc --no-quiet --no-detach <run-no-quiet.txt
'
test_expect_success 'maintenance.auto config option' '
GIT_TRACE2_EVENT="$(pwd)/default" git commit --quiet --allow-empty -m 1 &&
- test_subcommand git maintenance run --auto --quiet <default &&
+ test_subcommand git maintenance run --auto --quiet --detach <default &&
GIT_TRACE2_EVENT="$(pwd)/true" \
git -c maintenance.auto=true \
commit --quiet --allow-empty -m 2 &&
- test_subcommand git maintenance run --auto --quiet <true &&
+ test_subcommand git maintenance run --auto --quiet --detach <true &&
GIT_TRACE2_EVENT="$(pwd)/false" \
git -c maintenance.auto=false \
commit --quiet --allow-empty -m 3 &&
- test_subcommand ! git maintenance run --auto --quiet <false
+ test_subcommand ! git maintenance run --auto --quiet --detach <false
+'
+
+for cfg in maintenance.autoDetach gc.autoDetach
+do
+ test_expect_success "$cfg=true config option" '
+ test_when_finished "rm -f trace" &&
+ test_config $cfg true &&
+ GIT_TRACE2_EVENT="$(pwd)/trace" git commit --quiet --allow-empty -m 1 &&
+ test_subcommand git maintenance run --auto --quiet --detach <trace
+ '
+
+ test_expect_success "$cfg=false config option" '
+ test_when_finished "rm -f trace" &&
+ test_config $cfg false &&
+ GIT_TRACE2_EVENT="$(pwd)/trace" git commit --quiet --allow-empty -m 1 &&
+ test_subcommand git maintenance run --auto --quiet --no-detach <trace
+ '
+done
+
+test_expect_success "maintenance.autoDetach overrides gc.autoDetach" '
+ test_when_finished "rm -f trace" &&
+ test_config maintenance.autoDetach false &&
+ test_config gc.autoDetach true &&
+ GIT_TRACE2_EVENT="$(pwd)/trace" git commit --quiet --allow-empty -m 1 &&
+ test_subcommand git maintenance run --auto --quiet --no-detach <trace
'
test_expect_success 'register uses XDG_CONFIG_HOME config if it exists' '
@@ -129,9 +155,9 @@ test_expect_success 'run --task=<task>' '
git maintenance run --task=commit-graph 2>/dev/null &&
GIT_TRACE2_EVENT="$(pwd)/run-both.txt" \
git maintenance run --task=commit-graph --task=gc 2>/dev/null &&
- test_subcommand ! git gc --quiet <run-commit-graph.txt &&
- test_subcommand git gc --quiet <run-gc.txt &&
- test_subcommand git gc --quiet <run-both.txt &&
+ test_subcommand ! git gc --quiet --no-detach <run-commit-graph.txt &&
+ test_subcommand git gc --quiet --no-detach <run-gc.txt &&
+ test_subcommand git gc --quiet --no-detach <run-both.txt &&
test_subcommand git commit-graph write --split --reachable --no-progress <run-commit-graph.txt &&
test_subcommand ! git commit-graph write --split --reachable --no-progress <run-gc.txt &&
test_subcommand git commit-graph write --split --reachable --no-progress <run-both.txt
@@ -800,6 +826,9 @@ test_expect_success 'start and stop Linux/systemd maintenance' '
test_systemd_analyze_verify "systemd/user/git-maintenance@daily.service" &&
test_systemd_analyze_verify "systemd/user/git-maintenance@weekly.service" &&
+ grep "core.askPass=true" "systemd/user/git-maintenance@.service" &&
+ grep "credential.interactive=false" "systemd/user/git-maintenance@.service" &&
+
printf -- "--user enable --now git-maintenance@%s.timer\n" hourly daily weekly >expect &&
test_cmp expect args &&
@@ -908,4 +937,62 @@ test_expect_success 'failed schedule prevents config change' '
done
'
+test_expect_success '--no-detach causes maintenance to not run in background' '
+ test_when_finished "rm -rf repo" &&
+ git init repo &&
+ (
+ cd repo &&
+
+ # Prepare the repository such that git-maintenance(1) ends up
+ # outputting something.
+ test_commit something &&
+ git config set maintenance.gc.enabled false &&
+ git config set maintenance.loose-objects.enabled true &&
+ git config set maintenance.loose-objects.auto 1 &&
+ git config set maintenance.incremental-repack.enabled true &&
+
+ GIT_TRACE2_EVENT="$(pwd)/trace.txt" \
+ git maintenance run --no-detach >out 2>&1 &&
+ ! test_region maintenance detach trace.txt
+ )
+'
+
+test_expect_success '--detach causes maintenance to run in background' '
+ test_when_finished "rm -rf repo" &&
+ git init repo &&
+ (
+ cd repo &&
+
+ test_commit something &&
+ git config set maintenance.gc.enabled false &&
+ git config set maintenance.loose-objects.enabled true &&
+ git config set maintenance.loose-objects.auto 1 &&
+ git config set maintenance.incremental-repack.enabled true &&
+
+ # The extra file descriptor gets inherited to the child
+ # process, and by reading stdout we thus essentially wait for
+ # that descriptor to get closed, which indicates that the child
+ # is done, too.
+ does_not_matter=$(GIT_TRACE2_EVENT="$(pwd)/trace.txt" \
+ git maintenance run --detach 9>&1) &&
+ test_region maintenance detach trace.txt
+ )
+'
+
+test_expect_success 'repacking loose objects is quiet' '
+ test_when_finished "rm -rf repo" &&
+ git init repo &&
+ (
+ cd repo &&
+
+ test_commit something &&
+ git config set maintenance.gc.enabled false &&
+ git config set maintenance.loose-objects.enabled true &&
+ git config set maintenance.loose-objects.auto 1 &&
+
+ git maintenance run --quiet >out 2>&1 &&
+ test_must_be_empty out
+ )
+'
+
test_done
diff --git a/t/t9001-send-email.sh b/t/t9001-send-email.sh
index 64a4ab3736..e2430f7bfa 100755
--- a/t/t9001-send-email.sh
+++ b/t/t9001-send-email.sh
@@ -1324,7 +1324,7 @@ test_expect_success $PREREQ 'cc list is sanitized' '
Reviewed-by: Füñný Nâmé <odd_?=mail@example.com>
Reported-by: bugger on Jira
Reported-by: Douglas Reporter <doug@example.com> [from Jira profile]
- BugID: 12345
+ BugID: 12345should-not-appear
Co-developed-by: "C. O. Developer" <codev@example.com>
Signed-off-by: A. U. Thor <thor.au@example.com>
EOF
@@ -1337,7 +1337,7 @@ test_expect_success $PREREQ 'cc list is sanitized' '
" <odd_?=mail@example.com>" actual-show-all-headers &&
test_grep "^(body) Ignoring Reported-by .* bugger on Jira" actual-show-all-headers &&
test_grep "^(body) Adding cc: Douglas Reporter <doug@example.com>" actual-show-all-headers &&
- test_grep ! "12345" actual-show-all-headers &&
+ test_grep ! "12345should-not-appear" actual-show-all-headers &&
test_grep "^(body) Adding cc: \"C. O. Developer\" <codev@example.com>" actual-show-all-headers &&
test_grep "^(body) Adding cc: \"A. U. Thor\" <thor.au@example.com>" actual-show-all-headers
'
@@ -2084,22 +2084,24 @@ test_dump_aliases '--dump-aliases mailrc format' \
'bob' \
'chloe' \
'eve' <<-\EOF
- alias alice Alice W Land <awol@example.com>
- alias eve Eve <eve@example.com>
- alias bob Robert Bobbyton <bob@example.com>
+ alias alice "Alice W Land <awol@example.com>"
+ alias eve "Eve <eve@example.com>"
+ alias bob "Robert Bobbyton <bob@example.com>"
alias chloe chloe@example.com
EOF
test_dump_aliases '--dump-aliases pine format' \
'pine' \
'alice' \
+ 'bcgrp' \
'bob' \
'chloe' \
'eve' <<-\EOF
- alice Alice W Land <awol@example.com>
- eve Eve <eve@example.com>
- bob Robert Bobbyton <bob@example.com>
+ alice Alice W Land awol@example.com Friend
+ eve Eve eve@example.com
+ bob Robert Bobbyton bob@example.com
chloe chloe@example.com
+ bcgrp (bob, chloe, Other <o@example.com>)
EOF
test_dump_aliases '--dump-aliases gnus format' \
@@ -2118,6 +2120,110 @@ test_expect_success '--dump-aliases must be used alone' '
test_must_fail git send-email --dump-aliases --to=janice@example.com -1 refs/heads/accounting
'
+test_translate_aliases () {
+ msg="$1" && shift &&
+ filetype="$1" && shift &&
+ aliases="$1" && shift &&
+ printf '%s\n' "$@" >expect &&
+ cat >.tmp-email-aliases &&
+ printf '%s\n' "$aliases" >aliases &&
+
+ test_expect_success $PREREQ "$msg" '
+ clean_fake_sendmail && rm -fr outdir &&
+ git config --replace-all sendemail.aliasesfile \
+ "$(pwd)/.tmp-email-aliases" &&
+ git config sendemail.aliasfiletype "$filetype" &&
+ git send-email --translate-aliases <aliases 2>errors >actual &&
+ test_cmp expect actual
+ '
+}
+
+test_translate_aliases '--translate-aliases sendmail format' \
+ 'sendmail' \
+ 'alice bcgrp' \
+ 'Alice W Land <awol@example.com>' \
+ 'Robert Bobbyton <bob@example.com>' \
+ 'chloe@example.com' \
+ 'Other <o@example.com>' <<-\EOF
+ alice: Alice W Land <awol@example.com>
+ bob: Robert Bobbyton <bob@example.com>
+ chloe: chloe@example.com
+ abgroup: alice, bob
+ bcgrp: bob, chloe, Other <o@example.com>
+ EOF
+
+test_translate_aliases '--translate-aliases mutt format' \
+ 'mutt' \
+ 'donald bob' \
+ 'Donald C Carlton <donc@example.com>' \
+ 'Robert Bobbyton <bob@example.com>' <<-\EOF
+ alias alice Alice W Land <awol@example.com>
+ alias donald Donald C Carlton <donc@example.com>
+ alias bob Robert Bobbyton <bob@example.com>
+ alias chloe chloe@example.com
+ EOF
+
+test_translate_aliases '--translate-aliases mailrc format' \
+ 'mailrc' \
+ 'chloe eve alice' \
+ 'chloe@example.com' \
+ 'Eve <eve@example.com>' \
+ 'Alice W Land <awol@example.com>' <<-\EOF
+ alias alice "Alice W Land <awol@example.com>"
+ alias eve "Eve <eve@example.com>"
+ alias bob "Robert Bobbyton <bob@example.com>"
+ alias chloe chloe@example.com
+ EOF
+
+test_translate_aliases '--translate-aliases pine format' \
+ 'pine' \
+ 'eve bob bcgrp' \
+ 'eve@example.com' \
+ 'bob@example.com' \
+ 'bob@example.com' \
+ 'chloe@example.com' \
+ 'Other <o@example.com>' <<-\EOF
+ alice Alice W Land awol@example.com Friend
+ eve Eve eve@example.com
+ bob Robert Bobbyton bob@example.com
+ chloe chloe@example.com
+ bcgrp (bob, chloe, Other <o@example.com>)
+ EOF
+
+test_translate_aliases '--translate-aliases gnus format' \
+ 'gnus' \
+ 'alice chloe eve' \
+ 'awol@example.com' \
+ 'chloe@example.com' \
+ 'eve@example.com' <<-\EOF
+ (define-mail-alias "alice" "awol@example.com")
+ (define-mail-alias "eve" "eve@example.com")
+ (define-mail-alias "bob" "bob@example.com")
+ (define-mail-alias "chloe" "chloe@example.com")
+ EOF
+
+test_expect_success $PREREQ '--translate-aliases passes valid addresses through' '
+ cat >expect <<-\EOF &&
+ Other <o@example.com>
+ EOF
+ cat >aliases <<-\EOF &&
+ Other <o@example.com>
+ EOF
+ git send-email --translate-aliases <aliases >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success $PREREQ '--translate-aliases passes unknown aliases through' '
+ cat >expect <<-\EOF &&
+ blargh
+ EOF
+ cat >aliases <<-\EOF &&
+ blargh
+ EOF
+ git send-email --translate-aliases <aliases >actual &&
+ test_cmp expect actual
+'
+
test_expect_success $PREREQ 'aliases and sendemail.identity' '
test_must_fail git \
-c sendemail.identity=cloud \
@@ -2379,6 +2485,128 @@ test_expect_success $PREREQ 'leading and trailing whitespaces are removed' '
test_cmp expected-list actual-list
'
+test_expect_success $PREREQ 'mailmap support with --to' '
+ clean_fake_sendmail &&
+ test_config mailmap.file "mailmap.test" &&
+ cat >mailmap.test <<-EOF &&
+ Some Body <someone@example.com> <someone@example.org>
+ EOF
+ git format-patch --stdout -1 >a.patch &&
+ git send-email \
+ --from="Example <nobody@example.com>" \
+ --smtp-server="$(pwd)/fake.sendmail" \
+ --to=someone@example.org \
+ --mailmap \
+ a.patch \
+ 2>errors >out &&
+ grep "^!someone@example\.com!$" commandline1
+'
+
+test_expect_success $PREREQ 'sendemail.mailmap configuration' '
+ clean_fake_sendmail &&
+ test_config mailmap.file "mailmap.test" &&
+ test_config sendemail.mailmap "true" &&
+ cat >mailmap.test <<-EOF &&
+ Some Body <someone@example.com> <someone@example.org>
+ EOF
+ git format-patch --stdout -1 >a.patch &&
+ git send-email \
+ --from="Example <nobody@example.com>" \
+ --smtp-server="$(pwd)/fake.sendmail" \
+ --to=someone@example.org \
+ a.patch \
+ 2>errors >out &&
+ grep "^!someone@example\.com!$" commandline1
+'
+
+test_expect_success $PREREQ 'sendemail.mailmap.file configuration' '
+ clean_fake_sendmail &&
+ test_config sendemail.mailmap.file "mailmap.test" &&
+ test_config sendemail.mailmap "true" &&
+ cat >mailmap.test <<-EOF &&
+ Some Body <someone@example.com> <someone@example.org>
+ EOF
+ git format-patch --stdout -1 >a.patch &&
+ git send-email \
+ --from="Example <nobody@example.com>" \
+ --smtp-server="$(pwd)/fake.sendmail" \
+ --to=someone@example.org \
+ a.patch \
+ 2>errors >out &&
+ grep "^!someone@example\.com!$" commandline1
+'
+
+test_expect_success $PREREQ 'sendemail.mailmap identity overrides configuration' '
+ clean_fake_sendmail &&
+ test_config sendemail.cloud.mailmap.file "mailmap.test" &&
+ test_config sendemail.mailmap "false" &&
+ test_config sendemail.cloud.mailmap "true" &&
+ cat >mailmap.test <<-EOF &&
+ Some Body <someone@example.com> <someone@example.org>
+ EOF
+ git format-patch --stdout -1 >a.patch &&
+ git send-email \
+ --from="Example <nobody@example.com>" \
+ --smtp-server="$(pwd)/fake.sendmail" \
+ --identity=cloud \
+ --to=someone@example.org \
+ a.patch \
+ 2>errors >out &&
+ grep "^!someone@example\.com!$" commandline1
+'
+
+test_expect_success $PREREQ '--no-mailmap overrides configuration' '
+ clean_fake_sendmail &&
+ test_config sendemail.cloud.mailmap.file "mailmap.test" &&
+ test_config sendemail.mailmap "false" &&
+ test_config sendemail.cloud.mailmap "true" &&
+ cat >mailmap.test <<-EOF &&
+ Some Body <someone@example.com> <someone@example.org>
+ EOF
+ git format-patch --stdout -1 >a.patch &&
+ git send-email \
+ --from="Example <nobody@example.com>" \
+ --smtp-server="$(pwd)/fake.sendmail" \
+ --identity=cloud \
+ --to=someone@example.org \
+ --no-mailmap \
+ a.patch \
+ 2>errors >out &&
+ grep "^!someone@example\.org!$" commandline1
+'
+
+test_expect_success $PREREQ 'mailmap support in To header' '
+ clean_fake_sendmail &&
+ test_config mailmap.file "mailmap.test" &&
+ cat >mailmap.test <<-EOF &&
+ <someone@example.com> <someone@example.org>
+ EOF
+ git format-patch --stdout -1 --to=someone@example.org >a.patch &&
+ git send-email \
+ --from="Example <nobody@example.com>" \
+ --smtp-server="$(pwd)/fake.sendmail" \
+ --mailmap \
+ a.patch \
+ 2>errors >out &&
+ grep "^!someone@example\.com!$" commandline1
+'
+
+test_expect_success $PREREQ 'mailmap support in Cc header' '
+ clean_fake_sendmail &&
+ test_config mailmap.file "mailmap.test" &&
+ cat >mailmap.test <<-EOF &&
+ <someone@example.com> <someone@example.org>
+ EOF
+ git format-patch --stdout -1 --cc=someone@example.org >a.patch &&
+ git send-email \
+ --from="Example <nobody@example.com>" \
+ --smtp-server="$(pwd)/fake.sendmail" \
+ --mailmap \
+ a.patch \
+ 2>errors >out &&
+ grep "^!someone@example\.com!$" commandline1
+'
+
test_expect_success $PREREQ 'test using command name with --sendmail-cmd' '
clean_fake_sendmail &&
PATH="$PWD:$PATH" \
diff --git a/t/t9210-scalar.sh b/t/t9210-scalar.sh
index a41b4fcc08..027235d61a 100755
--- a/t/t9210-scalar.sh
+++ b/t/t9210-scalar.sh
@@ -169,6 +169,24 @@ test_expect_success 'scalar clone' '
)
'
+test_expect_success 'scalar clone --no-... opts' '
+ # Note: redirect stderr always to avoid having a verbose test
+ # run result in a difference in the --[no-]progress option.
+ GIT_TRACE2_EVENT="$(pwd)/no-opt-trace" scalar clone \
+ --no-tags --no-src \
+ "file://$(pwd)" no-opts --single-branch 2>/dev/null &&
+
+ test_subcommand git fetch --quiet --no-progress \
+ origin --no-tags <no-opt-trace &&
+ (
+ cd no-opts &&
+
+ test_cmp_config --no-tags remote.origin.tagopt &&
+ git for-each-ref --format="%(refname)" refs/tags/ >tags &&
+ test_line_count = 0 tags
+ )
+'
+
test_expect_success 'scalar reconfigure' '
git init one/src &&
scalar register one &&
@@ -176,8 +194,11 @@ test_expect_success 'scalar reconfigure' '
scalar reconfigure one &&
test true = "$(git -C one/src config core.preloadIndex)" &&
git -C one/src config core.preloadIndex false &&
- scalar reconfigure -a &&
- test true = "$(git -C one/src config core.preloadIndex)"
+ rm one/src/cron.txt &&
+ GIT_TRACE2_EVENT="$(pwd)/reconfigure" scalar reconfigure -a &&
+ test_path_is_file one/src/cron.txt &&
+ test true = "$(git -C one/src config core.preloadIndex)" &&
+ test_subcommand git maintenance start <reconfigure
'
test_expect_success 'scalar reconfigure --all with includeIf.onbranch' '
diff --git a/t/t9300-fast-import.sh b/t/t9300-fast-import.sh
index 1e68426852..3b3c371740 100755
--- a/t/t9300-fast-import.sh
+++ b/t/t9300-fast-import.sh
@@ -7,6 +7,7 @@ test_description='test git fast-import utility'
GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
+TEST_PASSES_SANITIZE_LEAK=true
. ./test-lib.sh
. "$TEST_DIRECTORY"/lib-diff.sh ;# test-lib chdir's into trash
diff --git a/t/t9304-fast-import-marks.sh b/t/t9304-fast-import-marks.sh
index 410a871c52..1f776a80f3 100755
--- a/t/t9304-fast-import-marks.sh
+++ b/t/t9304-fast-import-marks.sh
@@ -1,6 +1,8 @@
#!/bin/sh
test_description='test exotic situations with marks'
+
+TEST_PASSES_SANITIZE_LEAK=true
. ./test-lib.sh
test_expect_success 'setup dump of basic history' '
diff --git a/t/t9351-fast-export-anonymize.sh b/t/t9351-fast-export-anonymize.sh
index 156a647484..c0d9d7be75 100755
--- a/t/t9351-fast-export-anonymize.sh
+++ b/t/t9351-fast-export-anonymize.sh
@@ -4,6 +4,7 @@ test_description='basic tests for fast-export --anonymize'
GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
+TEST_PASSES_SANITIZE_LEAK=true
. ./test-lib.sh
test_expect_success 'setup simple repo' '
diff --git a/t/t9700-perl-git.sh b/t/t9700-perl-git.sh
index ccc8212d73..4431697122 100755
--- a/t/t9700-perl-git.sh
+++ b/t/t9700-perl-git.sh
@@ -45,7 +45,8 @@ test_expect_success 'set up test repository' '
'
test_expect_success 'set up bare repository' '
- git init --bare bare.git
+ git init --bare bare.git &&
+ git -C bare.git --work-tree=. commit --allow-empty -m "bare commit"
'
test_expect_success 'use t9700/test.pl to test Git.pm' '
diff --git a/t/t9700/test.pl b/t/t9700/test.pl
index d8e85482ab..2e1d50d4d1 100755
--- a/t/t9700/test.pl
+++ b/t/t9700/test.pl
@@ -147,6 +147,11 @@ close TEMPFILE3;
unlink $tmpfile3;
chdir($abs_repo_dir);
+# open alternate bare repo
+my $r4 = Git->repository(Directory => "$abs_repo_dir/bare.git");
+is($r4->command_oneline(qw(log --format=%s)), "bare commit",
+ "log of bare repo works");
+
# unquoting paths
is(Git::unquote_path('abc'), 'abc', 'unquote unquoted path');
is(Git::unquote_path('"abc def"'), 'abc def', 'unquote simple quoted path');
diff --git a/t/t9902-completion.sh b/t/t9902-completion.sh
index 932d5ad759..cc6aa9f0cd 100755
--- a/t/t9902-completion.sh
+++ b/t/t9902-completion.sh
@@ -16,6 +16,7 @@ test_untraceable=UnfortunatelyYes
GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=master
export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
+TEST_PASSES_SANITIZE_LEAK=true
. ./lib-bash.sh
complete ()
diff --git a/t/t9903-bash-prompt.sh b/t/t9903-bash-prompt.sh
index d667dda654..95e9955bca 100755
--- a/t/t9903-bash-prompt.sh
+++ b/t/t9903-bash-prompt.sh
@@ -8,6 +8,7 @@ test_description='test git-specific bash prompt functions'
GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
+TEST_PASSES_SANITIZE_LEAK=true
. ./lib-bash.sh
. "$GIT_BUILD_DIR/contrib/completion/git-prompt.sh"
diff --git a/t/test-lib-functions.sh b/t/test-lib-functions.sh
index fde9bf54fc..78e054ab50 100644
--- a/t/test-lib-functions.sh
+++ b/t/test-lib-functions.sh
@@ -926,7 +926,8 @@ test_expect_success () {
test_body_or_stdin test_body "$2"
test -n "$test_skip_test_preamble" ||
say >&3 "expecting success of $TEST_NUMBER.$test_count '$1': $test_body"
- if test_run_ "$test_body"
+ if test_run_ "$test_body" &&
+ check_test_results_san_file_empty_
then
test_ok_ "$1"
else
diff --git a/t/test-lib.sh b/t/test-lib.sh
index 54247604cb..b1a8ee5c00 100644
--- a/t/test-lib.sh
+++ b/t/test-lib.sh
@@ -322,7 +322,6 @@ TEST_RESULTS_SAN_FILE_PFX=trace
TEST_RESULTS_SAN_DIR_SFX=leak
TEST_RESULTS_SAN_FILE=
TEST_RESULTS_SAN_DIR="$TEST_RESULTS_DIR/$TEST_NAME.$TEST_RESULTS_SAN_DIR_SFX"
-TEST_RESULTS_SAN_DIR_NR_LEAKS_STARTUP=
TRASH_DIRECTORY="trash directory.$TEST_NAME$TEST_STRESS_JOB_SFX"
test -n "$root" && TRASH_DIRECTORY="$root/$TRASH_DIRECTORY"
case "$TRASH_DIRECTORY" in
@@ -848,6 +847,7 @@ test_failure_ () {
GIT_EXIT_OK=t
exit 0
fi
+ check_test_results_san_file_ "$test_failure"
_error_exit
fi
finalize_test_case_output failure "$failure_label" "$@"
@@ -1215,42 +1215,16 @@ test_atexit_handler () {
teardown_malloc_check
}
-sanitize_leak_log_message_ () {
- local new="$1" &&
- local old="$2" &&
- local file="$3" &&
-
- printf "With SANITIZE=leak at exit we have %d leak logs, but started with %d
-
-This means that we have a blindspot where git is leaking but we're
-losing the exit code somewhere, or not propagating it appropriately
-upwards!
-
-See the logs at \"%s.*\";
-those logs are reproduced below." \
- "$new" "$old" "$file"
+check_test_results_san_file_empty_ () {
+ test -z "$TEST_RESULTS_SAN_FILE" ||
+ test "$(nr_san_dir_leaks_)" = 0
}
check_test_results_san_file_ () {
- if test -z "$TEST_RESULTS_SAN_FILE"
- then
- return
- fi &&
- local old="$TEST_RESULTS_SAN_DIR_NR_LEAKS_STARTUP" &&
- local new="$(nr_san_dir_leaks_)" &&
-
- if test $new -le $old
+ if check_test_results_san_file_empty_
then
return
fi &&
- local out="$(sanitize_leak_log_message_ "$new" "$old" "$TEST_RESULTS_SAN_FILE")" &&
- say_color error "$out" &&
- if test "$old" != 0
- then
- echo &&
- say_color error "The logs include output from past runs to avoid" &&
- say_color error "that remove 'test-results' between runs."
- fi &&
say_color error "$(cat "$TEST_RESULTS_SAN_FILE".*)" &&
if test -n "$passes_sanitize_leak" && test "$test_failure" = 0
@@ -1558,8 +1532,16 @@ then
passes_sanitize_leak=t
fi
- if test "$GIT_TEST_PASSING_SANITIZE_LEAK" = "check"
+ if test "$GIT_TEST_PASSING_SANITIZE_LEAK" = "check" ||
+ test "$GIT_TEST_PASSING_SANITIZE_LEAK" = "check-failing"
then
+ if test "$GIT_TEST_PASSING_SANITIZE_LEAK" = "check-failing" &&
+ test -n "$passes_sanitize_leak"
+ then
+ skip_all="skipping leak-free $this_test under GIT_TEST_PASSING_SANITIZE_LEAK=check-failing"
+ test_done
+ fi
+
sanitize_leak_check=t
if test -n "$invert_exit_code"
then
@@ -1578,16 +1560,13 @@ then
test_done
fi
+ rm -rf "$TEST_RESULTS_SAN_DIR"
if ! mkdir -p "$TEST_RESULTS_SAN_DIR"
then
BAIL_OUT "cannot create $TEST_RESULTS_SAN_DIR"
fi &&
TEST_RESULTS_SAN_FILE="$TEST_RESULTS_SAN_DIR/$TEST_RESULTS_SAN_FILE_PFX"
- # In case "test-results" is left over from a previous
- # run: Only report if new leaks show up.
- TEST_RESULTS_SAN_DIR_NR_LEAKS_STARTUP=$(nr_san_dir_leaks_)
-
# Don't litter *.leak dirs if there was nothing to report
test_atexit "rmdir \"$TEST_RESULTS_SAN_DIR\" 2>/dev/null || :"
@@ -1597,6 +1576,7 @@ then
export LSAN_OPTIONS
elif test "$GIT_TEST_PASSING_SANITIZE_LEAK" = "check" ||
+ test "$GIT_TEST_PASSING_SANITIZE_LEAK" = "check-failing" ||
test_bool_env GIT_TEST_PASSING_SANITIZE_LEAK false
then
BAIL_OUT_ENV_NEEDS_SANITIZE_LEAK "GIT_TEST_PASSING_SANITIZE_LEAK=true"
@@ -1606,7 +1586,7 @@ if test "${GIT_TEST_CHAIN_LINT:-1}" != 0 &&
test "${GIT_TEST_EXT_CHAIN_LINT:-1}" != 0
then
"$PERL_PATH" "$TEST_DIRECTORY/chainlint.pl" "$0" ||
- BUG "lint error (see '?!...!? annotations above)"
+ BUG "lint error (see 'LINT' annotations above)"
fi
# Last-minute variable setup
diff --git a/t/unit-tests/.gitignore b/t/unit-tests/.gitignore
index 5e56e040ec..d0632ec7f9 100644
--- a/t/unit-tests/.gitignore
+++ b/t/unit-tests/.gitignore
@@ -1 +1,3 @@
/bin
+/clar.suite
+/clar-decls.h
diff --git a/t/unit-tests/clar-generate.awk b/t/unit-tests/clar-generate.awk
new file mode 100644
index 0000000000..ab71ce6c9f
--- /dev/null
+++ b/t/unit-tests/clar-generate.awk
@@ -0,0 +1,50 @@
+function add_suite(suite, initialize, cleanup, count) {
+ if (!suite) return
+ suite_count++
+ callback_count += count
+ suites = suites " {\n"
+ suites = suites " \"" suite "\",\n"
+ suites = suites " " initialize ",\n"
+ suites = suites " " cleanup ",\n"
+ suites = suites " _clar_cb_" suite ", " count ", 1\n"
+ suites = suites " },\n"
+}
+
+BEGIN {
+ suites = "static struct clar_suite _clar_suites[] = {\n"
+}
+
+{
+ print
+ name = $3; sub(/\(.*$/, "", name)
+ suite = name; sub(/^test_/, "", suite); sub(/__.*$/, "", suite)
+ short_name = name; sub(/^.*__/, "", short_name)
+ cb = "{ \"" short_name "\", &" name " }"
+ if (suite != prev_suite) {
+ add_suite(prev_suite, initialize, cleanup, count)
+ if (callbacks) callbacks = callbacks "};\n"
+ callbacks = callbacks "static const struct clar_func _clar_cb_" suite "[] = {\n"
+ initialize = "{ NULL, NULL }"
+ cleanup = "{ NULL, NULL }"
+ count = 0
+ prev_suite = suite
+ }
+ if (short_name == "initialize") {
+ initialize = cb
+ } else if (short_name == "cleanup") {
+ cleanup = cb
+ } else {
+ callbacks = callbacks " " cb ",\n"
+ count++
+ }
+}
+
+END {
+ add_suite(suite, initialize, cleanup, count)
+ suites = suites "};"
+ if (callbacks) callbacks = callbacks "};"
+ print callbacks
+ print suites
+ print "static const size_t _clar_suite_count = " suite_count ";"
+ print "static const size_t _clar_callback_count = " callback_count ";"
+}
diff --git a/t/unit-tests/clar/.github/workflows/ci.yml b/t/unit-tests/clar/.github/workflows/ci.yml
new file mode 100644
index 0000000000..b1ac2de460
--- /dev/null
+++ b/t/unit-tests/clar/.github/workflows/ci.yml
@@ -0,0 +1,23 @@
+name: CI
+
+on:
+ push:
+ branches: [ main ]
+ pull_request:
+ branches: [ main ]
+
+jobs:
+ build:
+ strategy:
+ matrix:
+ os: [ ubuntu-latest, macos-latest ]
+
+ runs-on: ${{ matrix.os }}
+
+ steps:
+ - name: Check out
+ uses: actions/checkout@v2
+ - name: Build
+ run: |
+ cd test
+ make
diff --git a/t/unit-tests/clar/COPYING b/t/unit-tests/clar/COPYING
new file mode 100644
index 0000000000..8983817f0c
--- /dev/null
+++ b/t/unit-tests/clar/COPYING
@@ -0,0 +1,15 @@
+ISC License
+
+Copyright (c) 2011-2015 Vicent Marti
+
+Permission to use, copy, modify, and/or distribute this software for any
+purpose with or without fee is hereby granted, provided that the above
+copyright notice and this permission notice appear in all copies.
+
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
diff --git a/t/unit-tests/clar/README.md b/t/unit-tests/clar/README.md
new file mode 100644
index 0000000000..a8961c5f10
--- /dev/null
+++ b/t/unit-tests/clar/README.md
@@ -0,0 +1,329 @@
+Come out and Clar
+=================
+
+In Catalan, "clar" means clear, easy to perceive. Using clar will make it
+easy to test and make clear the quality of your code.
+
+> _Historical note_
+>
+> Originally the clar project was named "clay" because the word "test" has its
+> roots in the latin word *"testum"*, meaning "earthen pot", and *"testa"*,
+> meaning "piece of burned clay"?
+>
+> This is because historically, testing implied melting metal in a pot to
+> check its quality. Clay is what tests are made of.
+
+## Quick Usage Overview
+
+Clar is a minimal C unit testing framework. It's been written to replace the
+old framework in [libgit2][libgit2], but it's both very versatile and
+straightforward to use.
+
+Can you count to funk?
+
+- **Zero: Initialize test directory**
+
+ ~~~~ sh
+ $ mkdir tests
+ $ cp -r $CLAR_ROOT/clar* tests
+ $ cp $CLAR_ROOT/test/clar_test.h tests
+ $ cp $CLAR_ROOT/test/main.c.sample tests/main.c
+ ~~~~
+
+- **One: Write some tests**
+
+ File: tests/adding.c:
+
+ ~~~~ c
+ /* adding.c for the "Adding" suite */
+ #include "clar.h"
+
+ static int *answer;
+
+ void test_adding__initialize(void)
+ {
+ answer = malloc(sizeof(int));
+ cl_assert_(answer != NULL, "No memory left?");
+ *answer = 42;
+ }
+
+ void test_adding__cleanup(void)
+ {
+ free(answer);
+ }
+
+ void test_adding__make_sure_math_still_works(void)
+ {
+ cl_assert_(5 > 3, "Five should probably be greater than three");
+ cl_assert_(-5 < 2, "Negative numbers are small, I think");
+ cl_assert_(*answer == 42, "The universe is doing OK. And the initializer too.");
+ }
+ ~~~~~
+
+- **Two: Build the test executable**
+
+ ~~~~ sh
+ $ cd tests
+ $ $CLAR_PATH/generate.py .
+ Written `clar.suite` (1 suites)
+ $ gcc -I. clar.c main.c adding.c -o testit
+ ~~~~
+
+- **Funk: Funk it.**
+
+ ~~~~ sh
+ $ ./testit
+ ~~~~
+
+## The Clar Test Suite
+
+Writing a test suite is pretty straightforward. Each test suite is a `*.c`
+file with a descriptive name: this encourages modularity.
+
+Each test suite has optional initialize and cleanup methods. These methods
+will be called before and after running **each** test in the suite, even if
+such test fails. As a rule of thumb, if a test needs a different initializer
+or cleanup method than another test in the same module, that means it
+doesn't belong in that module. Keep that in mind when grouping tests
+together.
+
+The `initialize` and `cleanup` methods have the following syntax, with
+`suitename` being the current suite name, e.g. `adding` for the `adding.c`
+suite.
+
+~~~~ c
+void test_suitename__initialize(void)
+{
+ /* init */
+}
+
+void test_suitename__cleanup(void)
+{
+ /* cleanup */
+}
+~~~~
+
+These methods are encouraged to use static, global variables to store the state
+that will be used by all tests inside the suite.
+
+~~~~ c
+static git_repository *_repository;
+
+void test_status__initialize(void)
+{
+ create_tmp_repo(STATUS_REPO);
+ git_repository_open(_repository, STATUS_REPO);
+}
+
+void test_status__cleanup(void)
+{
+ git_repository_close(_repository);
+ git_path_rm(STATUS_REPO);
+}
+
+void test_status__simple_test(void)
+{
+ /* do something with _repository */
+}
+~~~~
+
+Writing the actual tests is just as straightforward. Tests have the
+`void test_suitename__test_name(void)` signature, and they should **not**
+be static. Clar will automatically detect and list them.
+
+Tests are run as they appear on their original suites: they have no return
+value. A test is considered "passed" if it doesn't raise any errors. Check
+the "Clar API" section to see the various helper functions to check and
+raise errors during test execution.
+
+__Caution:__ If you use assertions inside of `test_suitename__initialize`,
+make sure that you do not rely on `__initialize` being completely run
+inside your `test_suitename__cleanup` function. Otherwise you might
+encounter ressource cleanup twice.
+
+## How does Clar work?
+
+To use Clar:
+
+1. copy the Clar boilerplate to your test directory
+2. copy (and probably modify) the sample `main.c` (from
+ `$CLAR_PATH/test/main.c.sample`)
+3. run the Clar mixer (a.k.a. `generate.py`) to scan your test directory and
+ write out the test suite metadata.
+4. compile your test files and the Clar boilerplate into a single test
+ executable
+5. run the executable to test!
+
+The Clar boilerplate gives you a set of useful test assertions and features
+(like accessing or making sandbox copies of fixture data). It consists of
+the `clar.c` and `clar.h` files, plus the code in the `clar/` subdirectory.
+You should not need to edit these files.
+
+The sample `main.c` (i.e. `$CLAR_PATH/test/main.c.sample`) file invokes
+`clar_test(argc, argv)` to run the tests. Usually, you will edit this file
+to perform any framework specific initialization and teardown that you need.
+
+The Clar mixer (`generate.py`) recursively scans your test directory for
+any `.c` files, parses them, and writes the `clar.suite` file with all of
+the metadata about your tests. When you build, the `clar.suite` file is
+included into `clar.c`.
+
+The mixer can be run with **Python 2.5, 2.6, 2.7, 3.0, 3.1, 3.2 and PyPy 1.6**.
+
+Commandline usage of the mixer is as follows:
+
+ $ ./generate.py .
+
+Where `.` is the folder where all the test suites can be found. The mixer
+will automatically locate all the relevant source files and build the
+testing metadata. The metadata will be written to `clar.suite`, in the same
+folder as all the test suites. This file is included by `clar.c` and so
+must be accessible via `#include` when building the test executable.
+
+ $ gcc -I. clar.c main.c suite1.c test2.c -o run_tests
+
+**Note that the Clar mixer only needs to be ran when adding new tests to a
+suite, in order to regenerate the metadata**. As a result, the `clar.suite`
+file can be checked into version control if you wish to be able to build
+your test suite without having to re-run the mixer.
+
+This is handy when e.g. generating tests in a local computer, and then
+building and testing them on an embedded device or a platform where Python
+is not available.
+
+### Fixtures
+
+Clar can create sandboxed fixtures for you to use in your test. You'll need to compile *clar.c* with an additional `CFLAG`, `-DCLAR_FIXTURE_PATH`. This should be an absolute path to your fixtures directory.
+
+Once that's done, you can use the fixture API as defined below.
+
+## The Clar API
+
+Clar makes the following methods available from all functions in a test
+suite.
+
+- `cl_must_pass(call)`, `cl_must_pass_(call, message)`: Verify that the given
+ function call passes, in the POSIX sense (returns a value greater or equal
+ to 0).
+
+- `cl_must_fail(call)`, `cl_must_fail_(call, message)`: Verify that the given
+ function call fails, in the POSIX sense (returns a value less than 0).
+
+- `cl_assert(expr)`, `cl_assert_(expr, message)`: Verify that `expr` is true.
+
+- `cl_check_pass(call)`, `cl_check_pass_(call, message)`: Verify that the
+ given function call passes, in the POSIX sense (returns a value greater or
+ equal to 0). If the function call doesn't succeed, a test failure will be
+ logged but the test's execution will continue.
+
+- `cl_check_fail(call)`, `cl_check_fail_(call, message)`: Verify that the
+ given function call fails, in the POSIX sense (returns a value less than
+ 0). If the function call doesn't fail, a test failure will be logged but
+ the test's execution will continue.
+
+- `cl_check(expr)`: Verify that `expr` is true. If `expr` is not
+ true, a test failure will be logged but the test's execution will continue.
+
+- `cl_fail(message)`: Fail the current test with the given message.
+
+- `cl_warning(message)`: Issue a warning. This warning will be
+ logged as a test failure but the test's execution will continue.
+
+- `cl_set_cleanup(void (*cleanup)(void *), void *opaque)`: Set the cleanup
+ method for a single test. This method will be called with `opaque` as its
+ argument before the test returns (even if the test has failed).
+ If a global cleanup method is also available, the local cleanup will be
+ called first, and then the global.
+
+- `cl_assert_equal_i(int,int)`: Verify that two integer values are equal.
+ The advantage of this over a simple `cl_assert` is that it will format
+ a much nicer error report if the values are not equal.
+
+- `cl_assert_equal_s(const char *,const char *)`: Verify that two strings
+ are equal. The expected value can also be NULL and this will correctly
+ test for that.
+
+- `cl_fixture_sandbox(const char *)`: Sets up a sandbox for a fixture
+ so that you can mutate the file directly.
+
+- `cl_fixture_cleanup(const char *)`: Tears down the previous fixture
+ sandbox.
+
+- `cl_fixture(const char *)`: Gets the full path to a fixture file.
+
+Please do note that these methods are *always* available whilst running a
+test, even when calling auxiliary/static functions inside the same file.
+
+It's strongly encouraged to perform test assertions in auxiliary methods,
+instead of returning error values. This is considered good Clar style.
+
+Style Example:
+
+~~~~ c
+/*
+ * Bad style: auxiliary functions return an error code
+ */
+
+static int check_string(const char *str)
+{
+ const char *aux = process_string(str);
+
+ if (aux == NULL)
+ return -1;
+
+ return strcmp(my_function(aux), str) == 0 ? 0 : -1;
+}
+
+void test_example__a_test_with_auxiliary_methods(void)
+{
+ cl_must_pass_(
+ check_string("foo"),
+ "String differs after processing"
+ );
+
+ cl_must_pass_(
+ check_string("bar"),
+ "String differs after processing"
+ );
+}
+~~~~
+
+~~~~ c
+/*
+ * Good style: auxiliary functions perform assertions
+ */
+
+static void check_string(const char *str)
+{
+ const char *aux = process_string(str);
+
+ cl_assert_(
+ aux != NULL,
+ "String processing failed"
+ );
+
+ cl_assert_(
+ strcmp(my_function(aux), str) == 0,
+ "String differs after processing"
+ );
+}
+
+void test_example__a_test_with_auxiliary_methods(void)
+{
+ check_string("foo");
+ check_string("bar");
+}
+~~~~
+
+About Clar
+==========
+
+Clar has been written from scratch by [Vicent Martí](https://github.com/vmg),
+to replace the old testing framework in [libgit2][libgit2].
+
+Do you know what languages are *in* on the SF startup scene? Node.js *and*
+Latin. Follow [@vmg](https://www.twitter.com/vmg) on Twitter to
+receive more lessons on word etymology. You can be hip too.
+
+
+[libgit2]: https://github.com/libgit2/libgit2
diff --git a/t/unit-tests/clar/clar.c b/t/unit-tests/clar/clar.c
new file mode 100644
index 0000000000..cef0f023c2
--- /dev/null
+++ b/t/unit-tests/clar/clar.c
@@ -0,0 +1,842 @@
+/*
+ * Copyright (c) Vicent Marti. All rights reserved.
+ *
+ * This file is part of clar, distributed under the ISC license.
+ * For full terms see the included COPYING file.
+ */
+#include <assert.h>
+#include <setjmp.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <math.h>
+#include <stdarg.h>
+#include <wchar.h>
+#include <time.h>
+
+/* required for sandboxing */
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#ifdef _WIN32
+# define WIN32_LEAN_AND_MEAN
+# include <windows.h>
+# include <io.h>
+# include <direct.h>
+
+# define _MAIN_CC __cdecl
+
+# ifndef stat
+# define stat(path, st) _stat(path, st)
+# endif
+# ifndef mkdir
+# define mkdir(path, mode) _mkdir(path)
+# endif
+# ifndef chdir
+# define chdir(path) _chdir(path)
+# endif
+# ifndef access
+# define access(path, mode) _access(path, mode)
+# endif
+# ifndef strdup
+# define strdup(str) _strdup(str)
+# endif
+# ifndef strcasecmp
+# define strcasecmp(a,b) _stricmp(a,b)
+# endif
+
+# ifndef __MINGW32__
+# pragma comment(lib, "shell32")
+# ifndef strncpy
+# define strncpy(to, from, to_size) strncpy_s(to, to_size, from, _TRUNCATE)
+# endif
+# ifndef W_OK
+# define W_OK 02
+# endif
+# ifndef S_ISDIR
+# define S_ISDIR(x) ((x & _S_IFDIR) != 0)
+# endif
+# define p_snprintf(buf,sz,fmt,...) _snprintf_s(buf,sz,_TRUNCATE,fmt,__VA_ARGS__)
+# else
+# define p_snprintf snprintf
+# endif
+
+# ifndef PRIuZ
+# define PRIuZ "Iu"
+# endif
+# ifndef PRIxZ
+# define PRIxZ "Ix"
+# endif
+
+# if defined(_MSC_VER) || (defined(__MINGW32__) && !defined(__MINGW64_VERSION_MAJOR))
+ typedef struct stat STAT_T;
+# else
+ typedef struct _stat STAT_T;
+# endif
+#else
+# include <sys/wait.h> /* waitpid(2) */
+# include <unistd.h>
+# define _MAIN_CC
+# define p_snprintf snprintf
+# ifndef PRIuZ
+# define PRIuZ "zu"
+# endif
+# ifndef PRIxZ
+# define PRIxZ "zx"
+# endif
+ typedef struct stat STAT_T;
+#endif
+
+#define MAX(x, y) (((x) > (y)) ? (x) : (y))
+
+#include "clar.h"
+
+static void fs_rm(const char *_source);
+static void fs_copy(const char *_source, const char *dest);
+
+#ifdef CLAR_FIXTURE_PATH
+static const char *
+fixture_path(const char *base, const char *fixture_name);
+#endif
+
+struct clar_error {
+ const char *file;
+ const char *function;
+ size_t line_number;
+ const char *error_msg;
+ char *description;
+
+ struct clar_error *next;
+};
+
+struct clar_explicit {
+ size_t suite_idx;
+ const char *filter;
+
+ struct clar_explicit *next;
+};
+
+struct clar_report {
+ const char *test;
+ int test_number;
+ const char *suite;
+
+ enum cl_test_status status;
+ time_t start;
+ double elapsed;
+
+ struct clar_error *errors;
+ struct clar_error *last_error;
+
+ struct clar_report *next;
+};
+
+struct clar_summary {
+ const char *filename;
+ FILE *fp;
+};
+
+static struct {
+ enum cl_test_status test_status;
+
+ const char *active_test;
+ const char *active_suite;
+
+ int total_skipped;
+ int total_errors;
+
+ int tests_ran;
+ int suites_ran;
+
+ enum cl_output_format output_format;
+
+ int report_errors_only;
+ int exit_on_error;
+ int verbosity;
+
+ int write_summary;
+ char *summary_filename;
+ struct clar_summary *summary;
+
+ struct clar_explicit *explicit;
+ struct clar_explicit *last_explicit;
+
+ struct clar_report *reports;
+ struct clar_report *last_report;
+
+ void (*local_cleanup)(void *);
+ void *local_cleanup_payload;
+
+ jmp_buf trampoline;
+ int trampoline_enabled;
+
+ cl_trace_cb *pfn_trace_cb;
+ void *trace_payload;
+
+} _clar;
+
+struct clar_func {
+ const char *name;
+ void (*ptr)(void);
+};
+
+struct clar_suite {
+ const char *name;
+ struct clar_func initialize;
+ struct clar_func cleanup;
+ const struct clar_func *tests;
+ size_t test_count;
+ int enabled;
+};
+
+/* From clar_print_*.c */
+static void clar_print_init(int test_count, int suite_count, const char *suite_names);
+static void clar_print_shutdown(int test_count, int suite_count, int error_count);
+static void clar_print_error(int num, const struct clar_report *report, const struct clar_error *error);
+static void clar_print_ontest(const char *suite_name, const char *test_name, int test_number, enum cl_test_status failed);
+static void clar_print_onsuite(const char *suite_name, int suite_index);
+static void clar_print_onabort(const char *msg, ...);
+
+/* From clar_sandbox.c */
+static void clar_unsandbox(void);
+static int clar_sandbox(void);
+
+/* From summary.h */
+static struct clar_summary *clar_summary_init(const char *filename);
+static int clar_summary_shutdown(struct clar_summary *fp);
+
+/* Load the declarations for the test suite */
+#include "clar.suite"
+
+
+#define CL_TRACE(ev) \
+ do { \
+ if (_clar.pfn_trace_cb) \
+ _clar.pfn_trace_cb(ev, \
+ _clar.active_suite, \
+ _clar.active_test, \
+ _clar.trace_payload); \
+ } while (0)
+
+void cl_trace_register(cl_trace_cb *cb, void *payload)
+{
+ _clar.pfn_trace_cb = cb;
+ _clar.trace_payload = payload;
+}
+
+
+/* Core test functions */
+static void
+clar_report_errors(struct clar_report *report)
+{
+ struct clar_error *error;
+ int i = 1;
+
+ for (error = report->errors; error; error = error->next)
+ clar_print_error(i++, _clar.last_report, error);
+}
+
+static void
+clar_report_all(void)
+{
+ struct clar_report *report;
+ struct clar_error *error;
+ int i = 1;
+
+ for (report = _clar.reports; report; report = report->next) {
+ if (report->status != CL_TEST_FAILURE)
+ continue;
+
+ for (error = report->errors; error; error = error->next)
+ clar_print_error(i++, report, error);
+ }
+}
+
+#ifdef WIN32
+# define clar_time DWORD
+
+static void clar_time_now(clar_time *out)
+{
+ *out = GetTickCount();
+}
+
+static double clar_time_diff(clar_time *start, clar_time *end)
+{
+ return ((double)*end - (double)*start) / 1000;
+}
+#else
+# include <sys/time.h>
+
+# define clar_time struct timeval
+
+static void clar_time_now(clar_time *out)
+{
+ struct timezone tz;
+
+ gettimeofday(out, &tz);
+}
+
+static double clar_time_diff(clar_time *start, clar_time *end)
+{
+ return ((double)end->tv_sec + (double)end->tv_usec / 1.0E6) -
+ ((double)start->tv_sec + (double)start->tv_usec / 1.0E6);
+}
+#endif
+
+static void
+clar_run_test(
+ const struct clar_suite *suite,
+ const struct clar_func *test,
+ const struct clar_func *initialize,
+ const struct clar_func *cleanup)
+{
+ clar_time start, end;
+
+ _clar.trampoline_enabled = 1;
+
+ CL_TRACE(CL_TRACE__TEST__BEGIN);
+
+ _clar.last_report->start = time(NULL);
+ clar_time_now(&start);
+
+ if (setjmp(_clar.trampoline) == 0) {
+ if (initialize->ptr != NULL)
+ initialize->ptr();
+
+ CL_TRACE(CL_TRACE__TEST__RUN_BEGIN);
+ test->ptr();
+ CL_TRACE(CL_TRACE__TEST__RUN_END);
+ }
+
+ clar_time_now(&end);
+
+ _clar.trampoline_enabled = 0;
+
+ if (_clar.last_report->status == CL_TEST_NOTRUN)
+ _clar.last_report->status = CL_TEST_OK;
+
+ _clar.last_report->elapsed = clar_time_diff(&start, &end);
+
+ if (_clar.local_cleanup != NULL)
+ _clar.local_cleanup(_clar.local_cleanup_payload);
+
+ if (cleanup->ptr != NULL)
+ cleanup->ptr();
+
+ CL_TRACE(CL_TRACE__TEST__END);
+
+ _clar.tests_ran++;
+
+ /* remove any local-set cleanup methods */
+ _clar.local_cleanup = NULL;
+ _clar.local_cleanup_payload = NULL;
+
+ if (_clar.report_errors_only) {
+ clar_report_errors(_clar.last_report);
+ } else {
+ clar_print_ontest(suite->name, test->name, _clar.tests_ran, _clar.last_report->status);
+ }
+}
+
+static void
+clar_run_suite(const struct clar_suite *suite, const char *filter)
+{
+ const struct clar_func *test = suite->tests;
+ size_t i, matchlen;
+ struct clar_report *report;
+ int exact = 0;
+
+ if (!suite->enabled)
+ return;
+
+ if (_clar.exit_on_error && _clar.total_errors)
+ return;
+
+ if (!_clar.report_errors_only)
+ clar_print_onsuite(suite->name, ++_clar.suites_ran);
+
+ _clar.active_suite = suite->name;
+ _clar.active_test = NULL;
+ CL_TRACE(CL_TRACE__SUITE_BEGIN);
+
+ if (filter) {
+ size_t suitelen = strlen(suite->name);
+ matchlen = strlen(filter);
+ if (matchlen <= suitelen) {
+ filter = NULL;
+ } else {
+ filter += suitelen;
+ while (*filter == ':')
+ ++filter;
+ matchlen = strlen(filter);
+
+ if (matchlen && filter[matchlen - 1] == '$') {
+ exact = 1;
+ matchlen--;
+ }
+ }
+ }
+
+ for (i = 0; i < suite->test_count; ++i) {
+ if (filter && strncmp(test[i].name, filter, matchlen))
+ continue;
+
+ if (exact && strlen(test[i].name) != matchlen)
+ continue;
+
+ _clar.active_test = test[i].name;
+
+ report = calloc(1, sizeof(struct clar_report));
+ report->suite = _clar.active_suite;
+ report->test = _clar.active_test;
+ report->test_number = _clar.tests_ran;
+ report->status = CL_TEST_NOTRUN;
+
+ if (_clar.reports == NULL)
+ _clar.reports = report;
+
+ if (_clar.last_report != NULL)
+ _clar.last_report->next = report;
+
+ _clar.last_report = report;
+
+ clar_run_test(suite, &test[i], &suite->initialize, &suite->cleanup);
+
+ if (_clar.exit_on_error && _clar.total_errors)
+ return;
+ }
+
+ _clar.active_test = NULL;
+ CL_TRACE(CL_TRACE__SUITE_END);
+}
+
+static void
+clar_usage(const char *arg)
+{
+ printf("Usage: %s [options]\n\n", arg);
+ printf("Options:\n");
+ printf(" -sname Run only the suite with `name` (can go to individual test name)\n");
+ printf(" -iname Include the suite with `name`\n");
+ printf(" -xname Exclude the suite with `name`\n");
+ printf(" -v Increase verbosity (show suite names)\n");
+ printf(" -q Only report tests that had an error\n");
+ printf(" -Q Quit as soon as a test fails\n");
+ printf(" -t Display results in tap format\n");
+ printf(" -l Print suite names\n");
+ printf(" -r[filename] Write summary file (to the optional filename)\n");
+ exit(-1);
+}
+
+static void
+clar_parse_args(int argc, char **argv)
+{
+ int i;
+
+ /* Verify options before execute */
+ for (i = 1; i < argc; ++i) {
+ char *argument = argv[i];
+
+ if (argument[0] != '-' || argument[1] == '\0'
+ || strchr("sixvqQtlr", argument[1]) == NULL) {
+ clar_usage(argv[0]);
+ }
+ }
+
+ for (i = 1; i < argc; ++i) {
+ char *argument = argv[i];
+
+ switch (argument[1]) {
+ case 's':
+ case 'i':
+ case 'x': { /* given suite name */
+ int offset = (argument[2] == '=') ? 3 : 2, found = 0;
+ char action = argument[1];
+ size_t j, arglen, suitelen, cmplen;
+
+ argument += offset;
+ arglen = strlen(argument);
+
+ if (arglen == 0)
+ clar_usage(argv[0]);
+
+ for (j = 0; j < _clar_suite_count; ++j) {
+ suitelen = strlen(_clar_suites[j].name);
+ cmplen = (arglen < suitelen) ? arglen : suitelen;
+
+ if (strncmp(argument, _clar_suites[j].name, cmplen) == 0) {
+ int exact = (arglen >= suitelen);
+
+ /* Do we have a real suite prefix separated by a
+ * trailing '::' or just a matching substring? */
+ if (arglen > suitelen && (argument[suitelen] != ':'
+ || argument[suitelen + 1] != ':'))
+ continue;
+
+ ++found;
+
+ if (!exact)
+ _clar.verbosity = MAX(_clar.verbosity, 1);
+
+ switch (action) {
+ case 's': {
+ struct clar_explicit *explicit =
+ calloc(1, sizeof(struct clar_explicit));
+ assert(explicit);
+
+ explicit->suite_idx = j;
+ explicit->filter = argument;
+
+ if (_clar.explicit == NULL)
+ _clar.explicit = explicit;
+
+ if (_clar.last_explicit != NULL)
+ _clar.last_explicit->next = explicit;
+
+ _clar_suites[j].enabled = 1;
+ _clar.last_explicit = explicit;
+ break;
+ }
+ case 'i': _clar_suites[j].enabled = 1; break;
+ case 'x': _clar_suites[j].enabled = 0; break;
+ }
+
+ if (exact)
+ break;
+ }
+ }
+
+ if (!found) {
+ clar_print_onabort("No suite matching '%s' found.\n", argument);
+ exit(-1);
+ }
+ break;
+ }
+
+ case 'q':
+ _clar.report_errors_only = 1;
+ break;
+
+ case 'Q':
+ _clar.exit_on_error = 1;
+ break;
+
+ case 't':
+ _clar.output_format = CL_OUTPUT_TAP;
+ break;
+
+ case 'l': {
+ size_t j;
+ printf("Test suites (use -s<name> to run just one):\n");
+ for (j = 0; j < _clar_suite_count; ++j)
+ printf(" %3d: %s\n", (int)j, _clar_suites[j].name);
+
+ exit(0);
+ }
+
+ case 'v':
+ _clar.verbosity++;
+ break;
+
+ case 'r':
+ _clar.write_summary = 1;
+ free(_clar.summary_filename);
+ _clar.summary_filename = *(argument + 2) ? strdup(argument + 2) : NULL;
+ break;
+
+ default:
+ assert(!"Unexpected commandline argument!");
+ }
+ }
+}
+
+void
+clar_test_init(int argc, char **argv)
+{
+ const char *summary_env;
+
+ if (argc > 1)
+ clar_parse_args(argc, argv);
+
+ clar_print_init(
+ (int)_clar_callback_count,
+ (int)_clar_suite_count,
+ ""
+ );
+
+ if (!_clar.summary_filename &&
+ (summary_env = getenv("CLAR_SUMMARY")) != NULL) {
+ _clar.write_summary = 1;
+ _clar.summary_filename = strdup(summary_env);
+ }
+
+ if (_clar.write_summary && !_clar.summary_filename)
+ _clar.summary_filename = strdup("summary.xml");
+
+ if (_clar.write_summary &&
+ !(_clar.summary = clar_summary_init(_clar.summary_filename))) {
+ clar_print_onabort("Failed to open the summary file\n");
+ exit(-1);
+ }
+
+ if (clar_sandbox() < 0) {
+ clar_print_onabort("Failed to sandbox the test runner.\n");
+ exit(-1);
+ }
+}
+
+int
+clar_test_run(void)
+{
+ size_t i;
+ struct clar_explicit *explicit;
+
+ if (_clar.explicit) {
+ for (explicit = _clar.explicit; explicit; explicit = explicit->next)
+ clar_run_suite(&_clar_suites[explicit->suite_idx], explicit->filter);
+ } else {
+ for (i = 0; i < _clar_suite_count; ++i)
+ clar_run_suite(&_clar_suites[i], NULL);
+ }
+
+ return _clar.total_errors;
+}
+
+void
+clar_test_shutdown(void)
+{
+ struct clar_explicit *explicit, *explicit_next;
+ struct clar_report *report, *report_next;
+
+ clar_print_shutdown(
+ _clar.tests_ran,
+ (int)_clar_suite_count,
+ _clar.total_errors
+ );
+
+ clar_unsandbox();
+
+ if (_clar.write_summary && clar_summary_shutdown(_clar.summary) < 0) {
+ clar_print_onabort("Failed to write the summary file\n");
+ exit(-1);
+ }
+
+ for (explicit = _clar.explicit; explicit; explicit = explicit_next) {
+ explicit_next = explicit->next;
+ free(explicit);
+ }
+
+ for (report = _clar.reports; report; report = report_next) {
+ report_next = report->next;
+ free(report);
+ }
+
+ free(_clar.summary_filename);
+}
+
+int
+clar_test(int argc, char **argv)
+{
+ int errors;
+
+ clar_test_init(argc, argv);
+ errors = clar_test_run();
+ clar_test_shutdown();
+
+ return errors;
+}
+
+static void abort_test(void)
+{
+ if (!_clar.trampoline_enabled) {
+ clar_print_onabort(
+ "Fatal error: a cleanup method raised an exception.");
+ clar_report_errors(_clar.last_report);
+ exit(-1);
+ }
+
+ CL_TRACE(CL_TRACE__TEST__LONGJMP);
+ longjmp(_clar.trampoline, -1);
+}
+
+void clar__skip(void)
+{
+ _clar.last_report->status = CL_TEST_SKIP;
+ _clar.total_skipped++;
+ abort_test();
+}
+
+void clar__fail(
+ const char *file,
+ const char *function,
+ size_t line,
+ const char *error_msg,
+ const char *description,
+ int should_abort)
+{
+ struct clar_error *error = calloc(1, sizeof(struct clar_error));
+
+ if (_clar.last_report->errors == NULL)
+ _clar.last_report->errors = error;
+
+ if (_clar.last_report->last_error != NULL)
+ _clar.last_report->last_error->next = error;
+
+ _clar.last_report->last_error = error;
+
+ error->file = file;
+ error->function = function;
+ error->line_number = line;
+ error->error_msg = error_msg;
+
+ if (description != NULL)
+ error->description = strdup(description);
+
+ _clar.total_errors++;
+ _clar.last_report->status = CL_TEST_FAILURE;
+
+ if (should_abort)
+ abort_test();
+}
+
+void clar__assert(
+ int condition,
+ const char *file,
+ const char *function,
+ size_t line,
+ const char *error_msg,
+ const char *description,
+ int should_abort)
+{
+ if (condition)
+ return;
+
+ clar__fail(file, function, line, error_msg, description, should_abort);
+}
+
+void clar__assert_equal(
+ const char *file,
+ const char *function,
+ size_t line,
+ const char *err,
+ int should_abort,
+ const char *fmt,
+ ...)
+{
+ va_list args;
+ char buf[4096];
+ int is_equal = 1;
+
+ va_start(args, fmt);
+
+ if (!strcmp("%s", fmt)) {
+ const char *s1 = va_arg(args, const char *);
+ const char *s2 = va_arg(args, const char *);
+ is_equal = (!s1 || !s2) ? (s1 == s2) : !strcmp(s1, s2);
+
+ if (!is_equal) {
+ if (s1 && s2) {
+ int pos;
+ for (pos = 0; s1[pos] == s2[pos] && s1[pos] && s2[pos]; ++pos)
+ /* find differing byte offset */;
+ p_snprintf(buf, sizeof(buf), "'%s' != '%s' (at byte %d)",
+ s1, s2, pos);
+ } else {
+ p_snprintf(buf, sizeof(buf), "'%s' != '%s'", s1, s2);
+ }
+ }
+ }
+ else if(!strcmp("%.*s", fmt)) {
+ const char *s1 = va_arg(args, const char *);
+ const char *s2 = va_arg(args, const char *);
+ int len = va_arg(args, int);
+ is_equal = (!s1 || !s2) ? (s1 == s2) : !strncmp(s1, s2, len);
+
+ if (!is_equal) {
+ if (s1 && s2) {
+ int pos;
+ for (pos = 0; s1[pos] == s2[pos] && pos < len; ++pos)
+ /* find differing byte offset */;
+ p_snprintf(buf, sizeof(buf), "'%.*s' != '%.*s' (at byte %d)",
+ len, s1, len, s2, pos);
+ } else {
+ p_snprintf(buf, sizeof(buf), "'%.*s' != '%.*s'", len, s1, len, s2);
+ }
+ }
+ }
+ else if (!strcmp("%ls", fmt)) {
+ const wchar_t *wcs1 = va_arg(args, const wchar_t *);
+ const wchar_t *wcs2 = va_arg(args, const wchar_t *);
+ is_equal = (!wcs1 || !wcs2) ? (wcs1 == wcs2) : !wcscmp(wcs1, wcs2);
+
+ if (!is_equal) {
+ if (wcs1 && wcs2) {
+ int pos;
+ for (pos = 0; wcs1[pos] == wcs2[pos] && wcs1[pos] && wcs2[pos]; ++pos)
+ /* find differing byte offset */;
+ p_snprintf(buf, sizeof(buf), "'%ls' != '%ls' (at byte %d)",
+ wcs1, wcs2, pos);
+ } else {
+ p_snprintf(buf, sizeof(buf), "'%ls' != '%ls'", wcs1, wcs2);
+ }
+ }
+ }
+ else if(!strcmp("%.*ls", fmt)) {
+ const wchar_t *wcs1 = va_arg(args, const wchar_t *);
+ const wchar_t *wcs2 = va_arg(args, const wchar_t *);
+ int len = va_arg(args, int);
+ is_equal = (!wcs1 || !wcs2) ? (wcs1 == wcs2) : !wcsncmp(wcs1, wcs2, len);
+
+ if (!is_equal) {
+ if (wcs1 && wcs2) {
+ int pos;
+ for (pos = 0; wcs1[pos] == wcs2[pos] && pos < len; ++pos)
+ /* find differing byte offset */;
+ p_snprintf(buf, sizeof(buf), "'%.*ls' != '%.*ls' (at byte %d)",
+ len, wcs1, len, wcs2, pos);
+ } else {
+ p_snprintf(buf, sizeof(buf), "'%.*ls' != '%.*ls'", len, wcs1, len, wcs2);
+ }
+ }
+ }
+ else if (!strcmp("%"PRIuZ, fmt) || !strcmp("%"PRIxZ, fmt)) {
+ size_t sz1 = va_arg(args, size_t), sz2 = va_arg(args, size_t);
+ is_equal = (sz1 == sz2);
+ if (!is_equal) {
+ int offset = p_snprintf(buf, sizeof(buf), fmt, sz1);
+ strncat(buf, " != ", sizeof(buf) - offset);
+ p_snprintf(buf + offset + 4, sizeof(buf) - offset - 4, fmt, sz2);
+ }
+ }
+ else if (!strcmp("%p", fmt)) {
+ void *p1 = va_arg(args, void *), *p2 = va_arg(args, void *);
+ is_equal = (p1 == p2);
+ if (!is_equal)
+ p_snprintf(buf, sizeof(buf), "%p != %p", p1, p2);
+ }
+ else {
+ int i1 = va_arg(args, int), i2 = va_arg(args, int);
+ is_equal = (i1 == i2);
+ if (!is_equal) {
+ int offset = p_snprintf(buf, sizeof(buf), fmt, i1);
+ strncat(buf, " != ", sizeof(buf) - offset);
+ p_snprintf(buf + offset + 4, sizeof(buf) - offset - 4, fmt, i2);
+ }
+ }
+
+ va_end(args);
+
+ if (!is_equal)
+ clar__fail(file, function, line, err, buf, should_abort);
+}
+
+void cl_set_cleanup(void (*cleanup)(void *), void *opaque)
+{
+ _clar.local_cleanup = cleanup;
+ _clar.local_cleanup_payload = opaque;
+}
+
+#include "clar/sandbox.h"
+#include "clar/fixtures.h"
+#include "clar/fs.h"
+#include "clar/print.h"
+#include "clar/summary.h"
diff --git a/t/unit-tests/clar/clar.h b/t/unit-tests/clar/clar.h
new file mode 100644
index 0000000000..8c22382bd5
--- /dev/null
+++ b/t/unit-tests/clar/clar.h
@@ -0,0 +1,173 @@
+/*
+ * Copyright (c) Vicent Marti. All rights reserved.
+ *
+ * This file is part of clar, distributed under the ISC license.
+ * For full terms see the included COPYING file.
+ */
+#ifndef __CLAR_TEST_H__
+#define __CLAR_TEST_H__
+
+#include <stdlib.h>
+
+enum cl_test_status {
+ CL_TEST_OK,
+ CL_TEST_FAILURE,
+ CL_TEST_SKIP,
+ CL_TEST_NOTRUN,
+};
+
+enum cl_output_format {
+ CL_OUTPUT_CLAP,
+ CL_OUTPUT_TAP,
+};
+
+/** Setup clar environment */
+void clar_test_init(int argc, char *argv[]);
+int clar_test_run(void);
+void clar_test_shutdown(void);
+
+/** One shot setup & run */
+int clar_test(int argc, char *argv[]);
+
+const char *clar_sandbox_path(void);
+
+void cl_set_cleanup(void (*cleanup)(void *), void *opaque);
+void cl_fs_cleanup(void);
+
+/**
+ * cl_trace_* is a hook to provide a simple global tracing
+ * mechanism.
+ *
+ * The goal here is to let main() provide clar-proper
+ * with a callback to optionally write log info for
+ * test operations into the same stream used by their
+ * actual tests. This would let them print test names
+ * and maybe performance data as they choose.
+ *
+ * The goal is NOT to alter the flow of control or to
+ * override test selection/skipping. (So the callback
+ * does not return a value.)
+ *
+ * The goal is NOT to duplicate the existing
+ * pass/fail/skip reporting. (So the callback
+ * does not accept a status/errorcode argument.)
+ *
+ */
+typedef enum cl_trace_event {
+ CL_TRACE__SUITE_BEGIN,
+ CL_TRACE__SUITE_END,
+ CL_TRACE__TEST__BEGIN,
+ CL_TRACE__TEST__END,
+ CL_TRACE__TEST__RUN_BEGIN,
+ CL_TRACE__TEST__RUN_END,
+ CL_TRACE__TEST__LONGJMP,
+} cl_trace_event;
+
+typedef void (cl_trace_cb)(
+ cl_trace_event ev,
+ const char *suite_name,
+ const char *test_name,
+ void *payload);
+
+/**
+ * Register a callback into CLAR to send global trace events.
+ * Pass NULL to disable.
+ */
+void cl_trace_register(cl_trace_cb *cb, void *payload);
+
+
+#ifdef CLAR_FIXTURE_PATH
+const char *cl_fixture(const char *fixture_name);
+void cl_fixture_sandbox(const char *fixture_name);
+void cl_fixture_cleanup(const char *fixture_name);
+const char *cl_fixture_basename(const char *fixture_name);
+#endif
+
+/**
+ * Assertion macros with explicit error message
+ */
+#define cl_must_pass_(expr, desc) clar__assert((expr) >= 0, __FILE__, __func__, __LINE__, "Function call failed: " #expr, desc, 1)
+#define cl_must_fail_(expr, desc) clar__assert((expr) < 0, __FILE__, __func__, __LINE__, "Expected function call to fail: " #expr, desc, 1)
+#define cl_assert_(expr, desc) clar__assert((expr) != 0, __FILE__, __func__, __LINE__, "Expression is not true: " #expr, desc, 1)
+
+/**
+ * Check macros with explicit error message
+ */
+#define cl_check_pass_(expr, desc) clar__assert((expr) >= 0, __FILE__, __func__, __LINE__, "Function call failed: " #expr, desc, 0)
+#define cl_check_fail_(expr, desc) clar__assert((expr) < 0, __FILE__, __func__, __LINE__, "Expected function call to fail: " #expr, desc, 0)
+#define cl_check_(expr, desc) clar__assert((expr) != 0, __FILE__, __func__, __LINE__, "Expression is not true: " #expr, desc, 0)
+
+/**
+ * Assertion macros with no error message
+ */
+#define cl_must_pass(expr) cl_must_pass_(expr, NULL)
+#define cl_must_fail(expr) cl_must_fail_(expr, NULL)
+#define cl_assert(expr) cl_assert_(expr, NULL)
+
+/**
+ * Check macros with no error message
+ */
+#define cl_check_pass(expr) cl_check_pass_(expr, NULL)
+#define cl_check_fail(expr) cl_check_fail_(expr, NULL)
+#define cl_check(expr) cl_check_(expr, NULL)
+
+/**
+ * Forced failure/warning
+ */
+#define cl_fail(desc) clar__fail(__FILE__, __func__, __LINE__, "Test failed.", desc, 1)
+#define cl_warning(desc) clar__fail(__FILE__, __func__, __LINE__, "Warning during test execution:", desc, 0)
+
+#define cl_skip() clar__skip()
+
+/**
+ * Typed assertion macros
+ */
+#define cl_assert_equal_s(s1,s2) clar__assert_equal(__FILE__,__func__,__LINE__,"String mismatch: " #s1 " != " #s2, 1, "%s", (s1), (s2))
+#define cl_assert_equal_s_(s1,s2,note) clar__assert_equal(__FILE__,__func__,__LINE__,"String mismatch: " #s1 " != " #s2 " (" #note ")", 1, "%s", (s1), (s2))
+
+#define cl_assert_equal_wcs(wcs1,wcs2) clar__assert_equal(__FILE__,__func__,__LINE__,"String mismatch: " #wcs1 " != " #wcs2, 1, "%ls", (wcs1), (wcs2))
+#define cl_assert_equal_wcs_(wcs1,wcs2,note) clar__assert_equal(__FILE__,__func__,__LINE__,"String mismatch: " #wcs1 " != " #wcs2 " (" #note ")", 1, "%ls", (wcs1), (wcs2))
+
+#define cl_assert_equal_strn(s1,s2,len) clar__assert_equal(__FILE__,__func__,__LINE__,"String mismatch: " #s1 " != " #s2, 1, "%.*s", (s1), (s2), (int)(len))
+#define cl_assert_equal_strn_(s1,s2,len,note) clar__assert_equal(__FILE__,__func__,__LINE__,"String mismatch: " #s1 " != " #s2 " (" #note ")", 1, "%.*s", (s1), (s2), (int)(len))
+
+#define cl_assert_equal_wcsn(wcs1,wcs2,len) clar__assert_equal(__FILE__,__func__,__LINE__,"String mismatch: " #wcs1 " != " #wcs2, 1, "%.*ls", (wcs1), (wcs2), (int)(len))
+#define cl_assert_equal_wcsn_(wcs1,wcs2,len,note) clar__assert_equal(__FILE__,__func__,__LINE__,"String mismatch: " #wcs1 " != " #wcs2 " (" #note ")", 1, "%.*ls", (wcs1), (wcs2), (int)(len))
+
+#define cl_assert_equal_i(i1,i2) clar__assert_equal(__FILE__,__func__,__LINE__,#i1 " != " #i2, 1, "%d", (int)(i1), (int)(i2))
+#define cl_assert_equal_i_(i1,i2,note) clar__assert_equal(__FILE__,__func__,__LINE__,#i1 " != " #i2 " (" #note ")", 1, "%d", (i1), (i2))
+#define cl_assert_equal_i_fmt(i1,i2,fmt) clar__assert_equal(__FILE__,__func__,__LINE__,#i1 " != " #i2, 1, (fmt), (int)(i1), (int)(i2))
+
+#define cl_assert_equal_b(b1,b2) clar__assert_equal(__FILE__,__func__,__LINE__,#b1 " != " #b2, 1, "%d", (int)((b1) != 0),(int)((b2) != 0))
+
+#define cl_assert_equal_p(p1,p2) clar__assert_equal(__FILE__,__func__,__LINE__,"Pointer mismatch: " #p1 " != " #p2, 1, "%p", (p1), (p2))
+
+void clar__skip(void);
+
+void clar__fail(
+ const char *file,
+ const char *func,
+ size_t line,
+ const char *error,
+ const char *description,
+ int should_abort);
+
+void clar__assert(
+ int condition,
+ const char *file,
+ const char *func,
+ size_t line,
+ const char *error,
+ const char *description,
+ int should_abort);
+
+void clar__assert_equal(
+ const char *file,
+ const char *func,
+ size_t line,
+ const char *err,
+ int should_abort,
+ const char *fmt,
+ ...);
+
+#endif
diff --git a/t/unit-tests/clar/clar/fixtures.h b/t/unit-tests/clar/clar/fixtures.h
new file mode 100644
index 0000000000..6ec6423484
--- /dev/null
+++ b/t/unit-tests/clar/clar/fixtures.h
@@ -0,0 +1,50 @@
+#ifdef CLAR_FIXTURE_PATH
+static const char *
+fixture_path(const char *base, const char *fixture_name)
+{
+ static char _path[4096];
+ size_t root_len;
+
+ root_len = strlen(base);
+ strncpy(_path, base, sizeof(_path));
+
+ if (_path[root_len - 1] != '/')
+ _path[root_len++] = '/';
+
+ if (fixture_name[0] == '/')
+ fixture_name++;
+
+ strncpy(_path + root_len,
+ fixture_name,
+ sizeof(_path) - root_len);
+
+ return _path;
+}
+
+const char *cl_fixture(const char *fixture_name)
+{
+ return fixture_path(CLAR_FIXTURE_PATH, fixture_name);
+}
+
+void cl_fixture_sandbox(const char *fixture_name)
+{
+ fs_copy(cl_fixture(fixture_name), _clar_path);
+}
+
+const char *cl_fixture_basename(const char *fixture_name)
+{
+ const char *p;
+
+ for (p = fixture_name; *p; p++) {
+ if (p[0] == '/' && p[1] && p[1] != '/')
+ fixture_name = p+1;
+ }
+
+ return fixture_name;
+}
+
+void cl_fixture_cleanup(const char *fixture_name)
+{
+ fs_rm(fixture_path(_clar_path, cl_fixture_basename(fixture_name)));
+}
+#endif
diff --git a/t/unit-tests/clar/clar/fs.h b/t/unit-tests/clar/clar/fs.h
new file mode 100644
index 0000000000..8b206179fc
--- /dev/null
+++ b/t/unit-tests/clar/clar/fs.h
@@ -0,0 +1,524 @@
+/*
+ * By default, use a read/write loop to copy files on POSIX systems.
+ * On Linux, use sendfile by default as it's slightly faster. On
+ * macOS, we avoid fcopyfile by default because it's slightly slower.
+ */
+#undef USE_FCOPYFILE
+#define USE_SENDFILE 1
+
+#ifdef _WIN32
+
+#ifdef CLAR_WIN32_LONGPATHS
+# define CLAR_MAX_PATH 4096
+#else
+# define CLAR_MAX_PATH MAX_PATH
+#endif
+
+#define RM_RETRY_COUNT 5
+#define RM_RETRY_DELAY 10
+
+#ifdef __MINGW32__
+
+/* These security-enhanced functions are not available
+ * in MinGW, so just use the vanilla ones */
+#define wcscpy_s(a, b, c) wcscpy((a), (c))
+#define wcscat_s(a, b, c) wcscat((a), (c))
+
+#endif /* __MINGW32__ */
+
+static int
+fs__dotordotdot(WCHAR *_tocheck)
+{
+ return _tocheck[0] == '.' &&
+ (_tocheck[1] == '\0' ||
+ (_tocheck[1] == '.' && _tocheck[2] == '\0'));
+}
+
+static int
+fs_rmdir_rmdir(WCHAR *_wpath)
+{
+ unsigned retries = 1;
+
+ while (!RemoveDirectoryW(_wpath)) {
+ /* Only retry when we have retries remaining, and the
+ * error was ERROR_DIR_NOT_EMPTY. */
+ if (retries++ > RM_RETRY_COUNT ||
+ ERROR_DIR_NOT_EMPTY != GetLastError())
+ return -1;
+
+ /* Give whatever has a handle to a child item some time
+ * to release it before trying again */
+ Sleep(RM_RETRY_DELAY * retries * retries);
+ }
+
+ return 0;
+}
+
+static void translate_path(WCHAR *path, size_t path_size)
+{
+ size_t path_len, i;
+
+ if (wcsncmp(path, L"\\\\?\\", 4) == 0)
+ return;
+
+ path_len = wcslen(path);
+ cl_assert(path_size > path_len + 4);
+
+ for (i = path_len; i > 0; i--) {
+ WCHAR c = path[i - 1];
+
+ if (c == L'/')
+ path[i + 3] = L'\\';
+ else
+ path[i + 3] = path[i - 1];
+ }
+
+ path[0] = L'\\';
+ path[1] = L'\\';
+ path[2] = L'?';
+ path[3] = L'\\';
+ path[path_len + 4] = L'\0';
+}
+
+static void
+fs_rmdir_helper(WCHAR *_wsource)
+{
+ WCHAR buffer[CLAR_MAX_PATH];
+ HANDLE find_handle;
+ WIN32_FIND_DATAW find_data;
+ size_t buffer_prefix_len;
+
+ /* Set up the buffer and capture the length */
+ wcscpy_s(buffer, CLAR_MAX_PATH, _wsource);
+ translate_path(buffer, CLAR_MAX_PATH);
+ wcscat_s(buffer, CLAR_MAX_PATH, L"\\");
+ buffer_prefix_len = wcslen(buffer);
+
+ /* FindFirstFile needs a wildcard to match multiple items */
+ wcscat_s(buffer, CLAR_MAX_PATH, L"*");
+ find_handle = FindFirstFileW(buffer, &find_data);
+ cl_assert(INVALID_HANDLE_VALUE != find_handle);
+
+ do {
+ /* FindFirstFile/FindNextFile gives back . and ..
+ * entries at the beginning */
+ if (fs__dotordotdot(find_data.cFileName))
+ continue;
+
+ wcscpy_s(buffer + buffer_prefix_len, CLAR_MAX_PATH - buffer_prefix_len, find_data.cFileName);
+
+ if (FILE_ATTRIBUTE_DIRECTORY & find_data.dwFileAttributes)
+ fs_rmdir_helper(buffer);
+ else {
+ /* If set, the +R bit must be cleared before deleting */
+ if (FILE_ATTRIBUTE_READONLY & find_data.dwFileAttributes)
+ cl_assert(SetFileAttributesW(buffer, find_data.dwFileAttributes & ~FILE_ATTRIBUTE_READONLY));
+
+ cl_assert(DeleteFileW(buffer));
+ }
+ }
+ while (FindNextFileW(find_handle, &find_data));
+
+ /* Ensure that we successfully completed the enumeration */
+ cl_assert(ERROR_NO_MORE_FILES == GetLastError());
+
+ /* Close the find handle */
+ FindClose(find_handle);
+
+ /* Now that the directory is empty, remove it */
+ cl_assert(0 == fs_rmdir_rmdir(_wsource));
+}
+
+static int
+fs_rm_wait(WCHAR *_wpath)
+{
+ unsigned retries = 1;
+ DWORD last_error;
+
+ do {
+ if (INVALID_FILE_ATTRIBUTES == GetFileAttributesW(_wpath))
+ last_error = GetLastError();
+ else
+ last_error = ERROR_SUCCESS;
+
+ /* Is the item gone? */
+ if (ERROR_FILE_NOT_FOUND == last_error ||
+ ERROR_PATH_NOT_FOUND == last_error)
+ return 0;
+
+ Sleep(RM_RETRY_DELAY * retries * retries);
+ }
+ while (retries++ <= RM_RETRY_COUNT);
+
+ return -1;
+}
+
+static void
+fs_rm(const char *_source)
+{
+ WCHAR wsource[CLAR_MAX_PATH];
+ DWORD attrs;
+
+ /* The input path is UTF-8. Convert it to wide characters
+ * for use with the Windows API */
+ cl_assert(MultiByteToWideChar(CP_UTF8,
+ MB_ERR_INVALID_CHARS,
+ _source,
+ -1, /* Indicates NULL termination */
+ wsource,
+ CLAR_MAX_PATH));
+
+ translate_path(wsource, CLAR_MAX_PATH);
+
+ /* Does the item exist? If not, we have no work to do */
+ attrs = GetFileAttributesW(wsource);
+
+ if (INVALID_FILE_ATTRIBUTES == attrs)
+ return;
+
+ if (FILE_ATTRIBUTE_DIRECTORY & attrs)
+ fs_rmdir_helper(wsource);
+ else {
+ /* The item is a file. Strip the +R bit */
+ if (FILE_ATTRIBUTE_READONLY & attrs)
+ cl_assert(SetFileAttributesW(wsource, attrs & ~FILE_ATTRIBUTE_READONLY));
+
+ cl_assert(DeleteFileW(wsource));
+ }
+
+ /* Wait for the DeleteFile or RemoveDirectory call to complete */
+ cl_assert(0 == fs_rm_wait(wsource));
+}
+
+static void
+fs_copydir_helper(WCHAR *_wsource, WCHAR *_wdest)
+{
+ WCHAR buf_source[CLAR_MAX_PATH], buf_dest[CLAR_MAX_PATH];
+ HANDLE find_handle;
+ WIN32_FIND_DATAW find_data;
+ size_t buf_source_prefix_len, buf_dest_prefix_len;
+
+ wcscpy_s(buf_source, CLAR_MAX_PATH, _wsource);
+ wcscat_s(buf_source, CLAR_MAX_PATH, L"\\");
+ translate_path(buf_source, CLAR_MAX_PATH);
+ buf_source_prefix_len = wcslen(buf_source);
+
+ wcscpy_s(buf_dest, CLAR_MAX_PATH, _wdest);
+ wcscat_s(buf_dest, CLAR_MAX_PATH, L"\\");
+ translate_path(buf_dest, CLAR_MAX_PATH);
+ buf_dest_prefix_len = wcslen(buf_dest);
+
+ /* Get an enumerator for the items in the source. */
+ wcscat_s(buf_source, CLAR_MAX_PATH, L"*");
+ find_handle = FindFirstFileW(buf_source, &find_data);
+ cl_assert(INVALID_HANDLE_VALUE != find_handle);
+
+ /* Create the target directory. */
+ cl_assert(CreateDirectoryW(_wdest, NULL));
+
+ do {
+ /* FindFirstFile/FindNextFile gives back . and ..
+ * entries at the beginning */
+ if (fs__dotordotdot(find_data.cFileName))
+ continue;
+
+ wcscpy_s(buf_source + buf_source_prefix_len, CLAR_MAX_PATH - buf_source_prefix_len, find_data.cFileName);
+ wcscpy_s(buf_dest + buf_dest_prefix_len, CLAR_MAX_PATH - buf_dest_prefix_len, find_data.cFileName);
+
+ if (FILE_ATTRIBUTE_DIRECTORY & find_data.dwFileAttributes)
+ fs_copydir_helper(buf_source, buf_dest);
+ else
+ cl_assert(CopyFileW(buf_source, buf_dest, TRUE));
+ }
+ while (FindNextFileW(find_handle, &find_data));
+
+ /* Ensure that we successfully completed the enumeration */
+ cl_assert(ERROR_NO_MORE_FILES == GetLastError());
+
+ /* Close the find handle */
+ FindClose(find_handle);
+}
+
+static void
+fs_copy(const char *_source, const char *_dest)
+{
+ WCHAR wsource[CLAR_MAX_PATH], wdest[CLAR_MAX_PATH];
+ DWORD source_attrs, dest_attrs;
+ HANDLE find_handle;
+ WIN32_FIND_DATAW find_data;
+
+ /* The input paths are UTF-8. Convert them to wide characters
+ * for use with the Windows API. */
+ cl_assert(MultiByteToWideChar(CP_UTF8,
+ MB_ERR_INVALID_CHARS,
+ _source,
+ -1,
+ wsource,
+ CLAR_MAX_PATH));
+
+ cl_assert(MultiByteToWideChar(CP_UTF8,
+ MB_ERR_INVALID_CHARS,
+ _dest,
+ -1,
+ wdest,
+ CLAR_MAX_PATH));
+
+ translate_path(wsource, CLAR_MAX_PATH);
+ translate_path(wdest, CLAR_MAX_PATH);
+
+ /* Check the source for existence */
+ source_attrs = GetFileAttributesW(wsource);
+ cl_assert(INVALID_FILE_ATTRIBUTES != source_attrs);
+
+ /* Check the target for existence */
+ dest_attrs = GetFileAttributesW(wdest);
+
+ if (INVALID_FILE_ATTRIBUTES != dest_attrs) {
+ /* Target exists; append last path part of source to target.
+ * Use FindFirstFile to parse the path */
+ find_handle = FindFirstFileW(wsource, &find_data);
+ cl_assert(INVALID_HANDLE_VALUE != find_handle);
+ wcscat_s(wdest, CLAR_MAX_PATH, L"\\");
+ wcscat_s(wdest, CLAR_MAX_PATH, find_data.cFileName);
+ FindClose(find_handle);
+
+ /* Check the new target for existence */
+ cl_assert(INVALID_FILE_ATTRIBUTES == GetFileAttributesW(wdest));
+ }
+
+ if (FILE_ATTRIBUTE_DIRECTORY & source_attrs)
+ fs_copydir_helper(wsource, wdest);
+ else
+ cl_assert(CopyFileW(wsource, wdest, TRUE));
+}
+
+void
+cl_fs_cleanup(void)
+{
+#ifdef CLAR_FIXTURE_PATH
+ fs_rm(fixture_path(_clar_path, "*"));
+#else
+ ((void)fs_copy); /* unused */
+#endif
+}
+
+#else
+
+#include <errno.h>
+#include <string.h>
+#include <limits.h>
+#include <dirent.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#if defined(__linux__)
+# include <sys/sendfile.h>
+#endif
+
+#if defined(__APPLE__)
+# include <copyfile.h>
+#endif
+
+static void basename_r(const char **out, int *out_len, const char *in)
+{
+ size_t in_len = strlen(in), start_pos;
+
+ for (in_len = strlen(in); in_len; in_len--) {
+ if (in[in_len - 1] != '/')
+ break;
+ }
+
+ for (start_pos = in_len; start_pos; start_pos--) {
+ if (in[start_pos - 1] == '/')
+ break;
+ }
+
+ cl_assert(in_len - start_pos < INT_MAX);
+
+ if (in_len - start_pos > 0) {
+ *out = &in[start_pos];
+ *out_len = (in_len - start_pos);
+ } else {
+ *out = "/";
+ *out_len = 1;
+ }
+}
+
+static char *joinpath(const char *dir, const char *base, int base_len)
+{
+ char *out;
+ int len;
+
+ if (base_len == -1) {
+ size_t bl = strlen(base);
+
+ cl_assert(bl < INT_MAX);
+ base_len = (int)bl;
+ }
+
+ len = strlen(dir) + base_len + 2;
+ cl_assert(len > 0);
+
+ cl_assert(out = malloc(len));
+ cl_assert(snprintf(out, len, "%s/%.*s", dir, base_len, base) < len);
+
+ return out;
+}
+
+static void
+fs_copydir_helper(const char *source, const char *dest, int dest_mode)
+{
+ DIR *source_dir;
+ struct dirent *d;
+
+ mkdir(dest, dest_mode);
+
+ cl_assert_(source_dir = opendir(source), "Could not open source dir");
+ while ((d = (errno = 0, readdir(source_dir))) != NULL) {
+ char *child;
+
+ if (!strcmp(d->d_name, ".") || !strcmp(d->d_name, ".."))
+ continue;
+
+ child = joinpath(source, d->d_name, -1);
+ fs_copy(child, dest);
+ free(child);
+ }
+
+ cl_assert_(errno == 0, "Failed to iterate source dir");
+
+ closedir(source_dir);
+}
+
+static void
+fs_copyfile_helper(const char *source, size_t source_len, const char *dest, int dest_mode)
+{
+ int in, out;
+
+ cl_must_pass((in = open(source, O_RDONLY)));
+ cl_must_pass((out = open(dest, O_WRONLY|O_CREAT|O_TRUNC, dest_mode)));
+
+#if USE_FCOPYFILE && defined(__APPLE__)
+ ((void)(source_len)); /* unused */
+ cl_must_pass(fcopyfile(in, out, 0, COPYFILE_DATA));
+#elif USE_SENDFILE && defined(__linux__)
+ {
+ ssize_t ret = 0;
+
+ while (source_len && (ret = sendfile(out, in, NULL, source_len)) > 0) {
+ source_len -= (size_t)ret;
+ }
+ cl_assert(ret >= 0);
+ }
+#else
+ {
+ char buf[131072];
+ ssize_t ret;
+
+ ((void)(source_len)); /* unused */
+
+ while ((ret = read(in, buf, sizeof(buf))) > 0) {
+ size_t len = (size_t)ret;
+
+ while (len && (ret = write(out, buf, len)) > 0) {
+ cl_assert(ret <= (ssize_t)len);
+ len -= ret;
+ }
+ cl_assert(ret >= 0);
+ }
+ cl_assert(ret == 0);
+ }
+#endif
+
+ close(in);
+ close(out);
+}
+
+static void
+fs_copy(const char *source, const char *_dest)
+{
+ char *dbuf = NULL;
+ const char *dest = NULL;
+ struct stat source_st, dest_st;
+
+ cl_must_pass_(lstat(source, &source_st), "Failed to stat copy source");
+
+ if (lstat(_dest, &dest_st) == 0) {
+ const char *base;
+ int base_len;
+
+ /* Target exists and is directory; append basename */
+ cl_assert(S_ISDIR(dest_st.st_mode));
+
+ basename_r(&base, &base_len, source);
+ cl_assert(base_len < INT_MAX);
+
+ dbuf = joinpath(_dest, base, base_len);
+ dest = dbuf;
+ } else if (errno != ENOENT) {
+ cl_fail("Cannot copy; cannot stat destination");
+ } else {
+ dest = _dest;
+ }
+
+ if (S_ISDIR(source_st.st_mode)) {
+ fs_copydir_helper(source, dest, source_st.st_mode);
+ } else {
+ fs_copyfile_helper(source, source_st.st_size, dest, source_st.st_mode);
+ }
+
+ free(dbuf);
+}
+
+static void
+fs_rmdir_helper(const char *path)
+{
+ DIR *dir;
+ struct dirent *d;
+
+ cl_assert_(dir = opendir(path), "Could not open dir");
+ while ((d = (errno = 0, readdir(dir))) != NULL) {
+ char *child;
+
+ if (!strcmp(d->d_name, ".") || !strcmp(d->d_name, ".."))
+ continue;
+
+ child = joinpath(path, d->d_name, -1);
+ fs_rm(child);
+ free(child);
+ }
+
+ cl_assert_(errno == 0, "Failed to iterate source dir");
+ closedir(dir);
+
+ cl_must_pass_(rmdir(path), "Could not remove directory");
+}
+
+static void
+fs_rm(const char *path)
+{
+ struct stat st;
+
+ if (lstat(path, &st)) {
+ if (errno == ENOENT)
+ return;
+
+ cl_fail("Cannot copy; cannot stat destination");
+ }
+
+ if (S_ISDIR(st.st_mode)) {
+ fs_rmdir_helper(path);
+ } else {
+ cl_must_pass(unlink(path));
+ }
+}
+
+void
+cl_fs_cleanup(void)
+{
+ clar_unsandbox();
+ clar_sandbox();
+}
+#endif
diff --git a/t/unit-tests/clar/clar/print.h b/t/unit-tests/clar/clar/print.h
new file mode 100644
index 0000000000..c17e2f693b
--- /dev/null
+++ b/t/unit-tests/clar/clar/print.h
@@ -0,0 +1,211 @@
+/* clap: clar protocol, the traditional clar output format */
+
+static void clar_print_clap_init(int test_count, int suite_count, const char *suite_names)
+{
+ (void)test_count;
+ printf("Loaded %d suites: %s\n", (int)suite_count, suite_names);
+ printf("Started (test status codes: OK='.' FAILURE='F' SKIPPED='S')\n");
+}
+
+static void clar_print_clap_shutdown(int test_count, int suite_count, int error_count)
+{
+ (void)test_count;
+ (void)suite_count;
+ (void)error_count;
+
+ printf("\n\n");
+ clar_report_all();
+}
+
+static void clar_print_clap_error(int num, const struct clar_report *report, const struct clar_error *error)
+{
+ printf(" %d) Failure:\n", num);
+
+ printf("%s::%s [%s:%"PRIuZ"]\n",
+ report->suite,
+ report->test,
+ error->file,
+ error->line_number);
+
+ printf(" %s\n", error->error_msg);
+
+ if (error->description != NULL)
+ printf(" %s\n", error->description);
+
+ printf("\n");
+ fflush(stdout);
+}
+
+static void clar_print_clap_ontest(const char *suite_name, const char *test_name, int test_number, enum cl_test_status status)
+{
+ (void)test_name;
+ (void)test_number;
+
+ if (_clar.verbosity > 1) {
+ printf("%s::%s: ", suite_name, test_name);
+
+ switch (status) {
+ case CL_TEST_OK: printf("ok\n"); break;
+ case CL_TEST_FAILURE: printf("fail\n"); break;
+ case CL_TEST_SKIP: printf("skipped"); break;
+ case CL_TEST_NOTRUN: printf("notrun"); break;
+ }
+ } else {
+ switch (status) {
+ case CL_TEST_OK: printf("."); break;
+ case CL_TEST_FAILURE: printf("F"); break;
+ case CL_TEST_SKIP: printf("S"); break;
+ case CL_TEST_NOTRUN: printf("N"); break;
+ }
+
+ fflush(stdout);
+ }
+}
+
+static void clar_print_clap_onsuite(const char *suite_name, int suite_index)
+{
+ if (_clar.verbosity == 1)
+ printf("\n%s", suite_name);
+
+ (void)suite_index;
+}
+
+static void clar_print_clap_onabort(const char *fmt, va_list arg)
+{
+ vfprintf(stderr, fmt, arg);
+}
+
+/* tap: test anywhere protocol format */
+
+static void clar_print_tap_init(int test_count, int suite_count, const char *suite_names)
+{
+ (void)test_count;
+ (void)suite_count;
+ (void)suite_names;
+ printf("TAP version 13\n");
+}
+
+static void clar_print_tap_shutdown(int test_count, int suite_count, int error_count)
+{
+ (void)suite_count;
+ (void)error_count;
+
+ printf("1..%d\n", test_count);
+}
+
+static void clar_print_tap_error(int num, const struct clar_report *report, const struct clar_error *error)
+{
+ (void)num;
+ (void)report;
+ (void)error;
+}
+
+static void print_escaped(const char *str)
+{
+ char *c;
+
+ while ((c = strchr(str, '\'')) != NULL) {
+ printf("%.*s", (int)(c - str), str);
+ printf("''");
+ str = c + 1;
+ }
+
+ printf("%s", str);
+}
+
+static void clar_print_tap_ontest(const char *suite_name, const char *test_name, int test_number, enum cl_test_status status)
+{
+ const struct clar_error *error = _clar.last_report->errors;
+
+ (void)test_name;
+ (void)test_number;
+
+ switch(status) {
+ case CL_TEST_OK:
+ printf("ok %d - %s::%s\n", test_number, suite_name, test_name);
+ break;
+ case CL_TEST_FAILURE:
+ printf("not ok %d - %s::%s\n", test_number, suite_name, test_name);
+
+ printf(" ---\n");
+ printf(" reason: |\n");
+ printf(" %s\n", error->error_msg);
+
+ if (error->description)
+ printf(" %s\n", error->description);
+
+ printf(" at:\n");
+ printf(" file: '"); print_escaped(error->file); printf("'\n");
+ printf(" line: %" PRIuZ "\n", error->line_number);
+ printf(" function: '%s'\n", error->function);
+ printf(" ---\n");
+
+ break;
+ case CL_TEST_SKIP:
+ case CL_TEST_NOTRUN:
+ printf("ok %d - # SKIP %s::%s\n", test_number, suite_name, test_name);
+ break;
+ }
+
+ fflush(stdout);
+}
+
+static void clar_print_tap_onsuite(const char *suite_name, int suite_index)
+{
+ printf("# start of suite %d: %s\n", suite_index, suite_name);
+}
+
+static void clar_print_tap_onabort(const char *fmt, va_list arg)
+{
+ printf("Bail out! ");
+ vprintf(fmt, arg);
+ fflush(stdout);
+}
+
+/* indirection between protocol output selection */
+
+#define PRINT(FN, ...) do { \
+ switch (_clar.output_format) { \
+ case CL_OUTPUT_CLAP: \
+ clar_print_clap_##FN (__VA_ARGS__); \
+ break; \
+ case CL_OUTPUT_TAP: \
+ clar_print_tap_##FN (__VA_ARGS__); \
+ break; \
+ default: \
+ abort(); \
+ } \
+ } while (0)
+
+static void clar_print_init(int test_count, int suite_count, const char *suite_names)
+{
+ PRINT(init, test_count, suite_count, suite_names);
+}
+
+static void clar_print_shutdown(int test_count, int suite_count, int error_count)
+{
+ PRINT(shutdown, test_count, suite_count, error_count);
+}
+
+static void clar_print_error(int num, const struct clar_report *report, const struct clar_error *error)
+{
+ PRINT(error, num, report, error);
+}
+
+static void clar_print_ontest(const char *suite_name, const char *test_name, int test_number, enum cl_test_status status)
+{
+ PRINT(ontest, suite_name, test_name, test_number, status);
+}
+
+static void clar_print_onsuite(const char *suite_name, int suite_index)
+{
+ PRINT(onsuite, suite_name, suite_index);
+}
+
+static void clar_print_onabort(const char *msg, ...)
+{
+ va_list argp;
+ va_start(argp, msg);
+ PRINT(onabort, msg, argp);
+ va_end(argp);
+}
diff --git a/t/unit-tests/clar/clar/sandbox.h b/t/unit-tests/clar/clar/sandbox.h
new file mode 100644
index 0000000000..e25057b7c4
--- /dev/null
+++ b/t/unit-tests/clar/clar/sandbox.h
@@ -0,0 +1,159 @@
+#ifdef __APPLE__
+#include <sys/syslimits.h>
+#endif
+
+static char _clar_path[4096 + 1];
+
+static int
+is_valid_tmp_path(const char *path)
+{
+ STAT_T st;
+
+ if (stat(path, &st) != 0)
+ return 0;
+
+ if (!S_ISDIR(st.st_mode))
+ return 0;
+
+ return (access(path, W_OK) == 0);
+}
+
+static int
+find_tmp_path(char *buffer, size_t length)
+{
+#ifndef _WIN32
+ static const size_t var_count = 5;
+ static const char *env_vars[] = {
+ "CLAR_TMP", "TMPDIR", "TMP", "TEMP", "USERPROFILE"
+ };
+
+ size_t i;
+
+ for (i = 0; i < var_count; ++i) {
+ const char *env = getenv(env_vars[i]);
+ if (!env)
+ continue;
+
+ if (is_valid_tmp_path(env)) {
+#ifdef __APPLE__
+ if (length >= PATH_MAX && realpath(env, buffer) != NULL)
+ return 0;
+#endif
+ strncpy(buffer, env, length - 1);
+ buffer[length - 1] = '\0';
+ return 0;
+ }
+ }
+
+ /* If the environment doesn't say anything, try to use /tmp */
+ if (is_valid_tmp_path("/tmp")) {
+#ifdef __APPLE__
+ if (length >= PATH_MAX && realpath("/tmp", buffer) != NULL)
+ return 0;
+#endif
+ strncpy(buffer, "/tmp", length - 1);
+ buffer[length - 1] = '\0';
+ return 0;
+ }
+
+#else
+ DWORD env_len = GetEnvironmentVariable("CLAR_TMP", buffer, (DWORD)length);
+ if (env_len > 0 && env_len < (DWORD)length)
+ return 0;
+
+ if (GetTempPath((DWORD)length, buffer))
+ return 0;
+#endif
+
+ /* This system doesn't like us, try to use the current directory */
+ if (is_valid_tmp_path(".")) {
+ strncpy(buffer, ".", length - 1);
+ buffer[length - 1] = '\0';
+ return 0;
+ }
+
+ return -1;
+}
+
+static void clar_unsandbox(void)
+{
+ if (_clar_path[0] == '\0')
+ return;
+
+ cl_must_pass(chdir(".."));
+
+ fs_rm(_clar_path);
+}
+
+static int build_sandbox_path(void)
+{
+#ifdef CLAR_TMPDIR
+ const char path_tail[] = CLAR_TMPDIR "_XXXXXX";
+#else
+ const char path_tail[] = "clar_tmp_XXXXXX";
+#endif
+
+ size_t len;
+
+ if (find_tmp_path(_clar_path, sizeof(_clar_path)) < 0)
+ return -1;
+
+ len = strlen(_clar_path);
+
+#ifdef _WIN32
+ { /* normalize path to POSIX forward slashes */
+ size_t i;
+ for (i = 0; i < len; ++i) {
+ if (_clar_path[i] == '\\')
+ _clar_path[i] = '/';
+ }
+ }
+#endif
+
+ if (_clar_path[len - 1] != '/') {
+ _clar_path[len++] = '/';
+ }
+
+ strncpy(_clar_path + len, path_tail, sizeof(_clar_path) - len);
+
+#if defined(__MINGW32__)
+ if (_mktemp(_clar_path) == NULL)
+ return -1;
+
+ if (mkdir(_clar_path, 0700) != 0)
+ return -1;
+#elif defined(__TANDEM)
+ if (mktemp(_clar_path) == NULL)
+ return -1;
+
+ if (mkdir(_clar_path, 0700) != 0)
+ return -1;
+#elif defined(_WIN32)
+ if (_mktemp_s(_clar_path, sizeof(_clar_path)) != 0)
+ return -1;
+
+ if (mkdir(_clar_path, 0700) != 0)
+ return -1;
+#else
+ if (mkdtemp(_clar_path) == NULL)
+ return -1;
+#endif
+
+ return 0;
+}
+
+static int clar_sandbox(void)
+{
+ if (_clar_path[0] == '\0' && build_sandbox_path() < 0)
+ return -1;
+
+ if (chdir(_clar_path) != 0)
+ return -1;
+
+ return 0;
+}
+
+const char *clar_sandbox_path(void)
+{
+ return _clar_path;
+}
diff --git a/t/unit-tests/clar/clar/summary.h b/t/unit-tests/clar/clar/summary.h
new file mode 100644
index 0000000000..4dd352e28b
--- /dev/null
+++ b/t/unit-tests/clar/clar/summary.h
@@ -0,0 +1,143 @@
+
+#include <stdio.h>
+#include <time.h>
+
+static int clar_summary_close_tag(
+ struct clar_summary *summary, const char *tag, int indent)
+{
+ const char *indt;
+
+ if (indent == 0) indt = "";
+ else if (indent == 1) indt = "\t";
+ else indt = "\t\t";
+
+ return fprintf(summary->fp, "%s</%s>\n", indt, tag);
+}
+
+static int clar_summary_testsuites(struct clar_summary *summary)
+{
+ return fprintf(summary->fp, "<testsuites>\n");
+}
+
+static int clar_summary_testsuite(struct clar_summary *summary,
+ int idn, const char *name, time_t timestamp,
+ int test_count, int fail_count, int error_count)
+{
+ struct tm *tm = localtime(&timestamp);
+ char iso_dt[20];
+
+ if (strftime(iso_dt, sizeof(iso_dt), "%Y-%m-%dT%H:%M:%S", tm) == 0)
+ return -1;
+
+ return fprintf(summary->fp, "\t<testsuite"
+ " id=\"%d\""
+ " name=\"%s\""
+ " hostname=\"localhost\""
+ " timestamp=\"%s\""
+ " tests=\"%d\""
+ " failures=\"%d\""
+ " errors=\"%d\">\n",
+ idn, name, iso_dt, test_count, fail_count, error_count);
+}
+
+static int clar_summary_testcase(struct clar_summary *summary,
+ const char *name, const char *classname, double elapsed)
+{
+ return fprintf(summary->fp,
+ "\t\t<testcase name=\"%s\" classname=\"%s\" time=\"%.2f\">\n",
+ name, classname, elapsed);
+}
+
+static int clar_summary_failure(struct clar_summary *summary,
+ const char *type, const char *message, const char *desc)
+{
+ return fprintf(summary->fp,
+ "\t\t\t<failure type=\"%s\"><![CDATA[%s\n%s]]></failure>\n",
+ type, message, desc);
+}
+
+static int clar_summary_skipped(struct clar_summary *summary)
+{
+ return fprintf(summary->fp, "\t\t\t<skipped />\n");
+}
+
+struct clar_summary *clar_summary_init(const char *filename)
+{
+ struct clar_summary *summary;
+ FILE *fp;
+
+ if ((fp = fopen(filename, "w")) == NULL) {
+ perror("fopen");
+ return NULL;
+ }
+
+ if ((summary = malloc(sizeof(struct clar_summary))) == NULL) {
+ perror("malloc");
+ fclose(fp);
+ return NULL;
+ }
+
+ summary->filename = filename;
+ summary->fp = fp;
+
+ return summary;
+}
+
+int clar_summary_shutdown(struct clar_summary *summary)
+{
+ struct clar_report *report;
+ const char *last_suite = NULL;
+
+ if (clar_summary_testsuites(summary) < 0)
+ goto on_error;
+
+ report = _clar.reports;
+ while (report != NULL) {
+ struct clar_error *error = report->errors;
+
+ if (last_suite == NULL || strcmp(last_suite, report->suite) != 0) {
+ if (clar_summary_testsuite(summary, 0, report->suite,
+ report->start, _clar.tests_ran, _clar.total_errors, 0) < 0)
+ goto on_error;
+ }
+
+ last_suite = report->suite;
+
+ clar_summary_testcase(summary, report->test, report->suite, report->elapsed);
+
+ while (error != NULL) {
+ if (clar_summary_failure(summary, "assert",
+ error->error_msg, error->description) < 0)
+ goto on_error;
+
+ error = error->next;
+ }
+
+ if (report->status == CL_TEST_SKIP)
+ clar_summary_skipped(summary);
+
+ if (clar_summary_close_tag(summary, "testcase", 2) < 0)
+ goto on_error;
+
+ report = report->next;
+
+ if (!report || strcmp(last_suite, report->suite) != 0) {
+ if (clar_summary_close_tag(summary, "testsuite", 1) < 0)
+ goto on_error;
+ }
+ }
+
+ if (clar_summary_close_tag(summary, "testsuites", 0) < 0 ||
+ fclose(summary->fp) != 0)
+ goto on_error;
+
+ printf("written summary file to %s\n", summary->filename);
+
+ free(summary);
+ return 0;
+
+on_error:
+ fclose(summary->fp);
+ free(summary);
+ return -1;
+}
diff --git a/t/unit-tests/clar/generate.py b/t/unit-tests/clar/generate.py
new file mode 100755
index 0000000000..80996ac3e7
--- /dev/null
+++ b/t/unit-tests/clar/generate.py
@@ -0,0 +1,266 @@
+#!/usr/bin/env python
+#
+# Copyright (c) Vicent Marti. All rights reserved.
+#
+# This file is part of clar, distributed under the ISC license.
+# For full terms see the included COPYING file.
+#
+
+from __future__ import with_statement
+from string import Template
+import re, fnmatch, os, sys, codecs, pickle
+
+class Module(object):
+ class Template(object):
+ def __init__(self, module):
+ self.module = module
+
+ def _render_callback(self, cb):
+ if not cb:
+ return ' { NULL, NULL }'
+ return ' { "%s", &%s }' % (cb['short_name'], cb['symbol'])
+
+ class DeclarationTemplate(Template):
+ def render(self):
+ out = "\n".join("extern %s;" % cb['declaration'] for cb in self.module.callbacks) + "\n"
+
+ for initializer in self.module.initializers:
+ out += "extern %s;\n" % initializer['declaration']
+
+ if self.module.cleanup:
+ out += "extern %s;\n" % self.module.cleanup['declaration']
+
+ return out
+
+ class CallbacksTemplate(Template):
+ def render(self):
+ out = "static const struct clar_func _clar_cb_%s[] = {\n" % self.module.name
+ out += ",\n".join(self._render_callback(cb) for cb in self.module.callbacks)
+ out += "\n};\n"
+ return out
+
+ class InfoTemplate(Template):
+ def render(self):
+ templates = []
+
+ initializers = self.module.initializers
+ if len(initializers) == 0:
+ initializers = [ None ]
+
+ for initializer in initializers:
+ name = self.module.clean_name()
+ if initializer and initializer['short_name'].startswith('initialize_'):
+ variant = initializer['short_name'][len('initialize_'):]
+ name += " (%s)" % variant.replace('_', ' ')
+
+ template = Template(
+ r"""
+ {
+ "${clean_name}",
+ ${initialize},
+ ${cleanup},
+ ${cb_ptr}, ${cb_count}, ${enabled}
+ }"""
+ ).substitute(
+ clean_name = name,
+ initialize = self._render_callback(initializer),
+ cleanup = self._render_callback(self.module.cleanup),
+ cb_ptr = "_clar_cb_%s" % self.module.name,
+ cb_count = len(self.module.callbacks),
+ enabled = int(self.module.enabled)
+ )
+ templates.append(template)
+
+ return ','.join(templates)
+
+ def __init__(self, name):
+ self.name = name
+
+ self.mtime = None
+ self.enabled = True
+ self.modified = False
+
+ def clean_name(self):
+ return self.name.replace("_", "::")
+
+ def _skip_comments(self, text):
+ SKIP_COMMENTS_REGEX = re.compile(
+ r'//.*?$|/\*.*?\*/|\'(?:\\.|[^\\\'])*\'|"(?:\\.|[^\\"])*"',
+ re.DOTALL | re.MULTILINE)
+
+ def _replacer(match):
+ s = match.group(0)
+ return "" if s.startswith('/') else s
+
+ return re.sub(SKIP_COMMENTS_REGEX, _replacer, text)
+
+ def parse(self, contents):
+ TEST_FUNC_REGEX = r"^(void\s+(test_%s__(\w+))\s*\(\s*void\s*\))\s*\{"
+
+ contents = self._skip_comments(contents)
+ regex = re.compile(TEST_FUNC_REGEX % self.name, re.MULTILINE)
+
+ self.callbacks = []
+ self.initializers = []
+ self.cleanup = None
+
+ for (declaration, symbol, short_name) in regex.findall(contents):
+ data = {
+ "short_name" : short_name,
+ "declaration" : declaration,
+ "symbol" : symbol
+ }
+
+ if short_name.startswith('initialize'):
+ self.initializers.append(data)
+ elif short_name == 'cleanup':
+ self.cleanup = data
+ else:
+ self.callbacks.append(data)
+
+ return self.callbacks != []
+
+ def refresh(self, path):
+ self.modified = False
+
+ try:
+ st = os.stat(path)
+
+ # Not modified
+ if st.st_mtime == self.mtime:
+ return True
+
+ self.modified = True
+ self.mtime = st.st_mtime
+
+ with codecs.open(path, encoding='utf-8') as fp:
+ raw_content = fp.read()
+
+ except IOError:
+ return False
+
+ return self.parse(raw_content)
+
+class TestSuite(object):
+
+ def __init__(self, path, output):
+ self.path = path
+ self.output = output
+
+ def should_generate(self, path):
+ if not os.path.isfile(path):
+ return True
+
+ if any(module.modified for module in self.modules.values()):
+ return True
+
+ return False
+
+ def find_modules(self):
+ modules = []
+ for root, _, files in os.walk(self.path):
+ module_root = root[len(self.path):]
+ module_root = [c for c in module_root.split(os.sep) if c]
+
+ tests_in_module = fnmatch.filter(files, "*.c")
+
+ for test_file in tests_in_module:
+ full_path = os.path.join(root, test_file)
+ module_name = "_".join(module_root + [test_file[:-2]]).replace("-", "_")
+
+ modules.append((full_path, module_name))
+
+ return modules
+
+ def load_cache(self):
+ path = os.path.join(self.output, '.clarcache')
+ cache = {}
+
+ try:
+ fp = open(path, 'rb')
+ cache = pickle.load(fp)
+ fp.close()
+ except (IOError, ValueError):
+ pass
+
+ return cache
+
+ def save_cache(self):
+ path = os.path.join(self.output, '.clarcache')
+ with open(path, 'wb') as cache:
+ pickle.dump(self.modules, cache)
+
+ def load(self, force = False):
+ module_data = self.find_modules()
+ self.modules = {} if force else self.load_cache()
+
+ for path, name in module_data:
+ if name not in self.modules:
+ self.modules[name] = Module(name)
+
+ if not self.modules[name].refresh(path):
+ del self.modules[name]
+
+ def disable(self, excluded):
+ for exclude in excluded:
+ for module in self.modules.values():
+ name = module.clean_name()
+ if name.startswith(exclude):
+ module.enabled = False
+ module.modified = True
+
+ def suite_count(self):
+ return sum(max(1, len(m.initializers)) for m in self.modules.values())
+
+ def callback_count(self):
+ return sum(len(module.callbacks) for module in self.modules.values())
+
+ def write(self):
+ output = os.path.join(self.output, 'clar.suite')
+
+ if not self.should_generate(output):
+ return False
+
+ with open(output, 'w') as data:
+ modules = sorted(self.modules.values(), key=lambda module: module.name)
+
+ for module in modules:
+ t = Module.DeclarationTemplate(module)
+ data.write(t.render())
+
+ for module in modules:
+ t = Module.CallbacksTemplate(module)
+ data.write(t.render())
+
+ suites = "static struct clar_suite _clar_suites[] = {" + ','.join(
+ Module.InfoTemplate(module).render() for module in modules
+ ) + "\n};\n"
+
+ data.write(suites)
+
+ data.write("static const size_t _clar_suite_count = %d;\n" % self.suite_count())
+ data.write("static const size_t _clar_callback_count = %d;\n" % self.callback_count())
+
+ self.save_cache()
+ return True
+
+if __name__ == '__main__':
+ from optparse import OptionParser
+
+ parser = OptionParser()
+ parser.add_option('-f', '--force', action="store_true", dest='force', default=False)
+ parser.add_option('-x', '--exclude', dest='excluded', action='append', default=[])
+ parser.add_option('-o', '--output', dest='output')
+
+ options, args = parser.parse_args()
+ if len(args) > 1:
+ print("More than one path given")
+ sys.exit(1)
+
+ path = args.pop() if args else '.'
+ output = options.output or path
+ suite = TestSuite(path, output)
+ suite.load(options.force)
+ suite.disable(options.excluded)
+ if suite.write():
+ print("Written `clar.suite` (%d tests in %d suites)" % (suite.callback_count(), suite.suite_count()))
diff --git a/t/unit-tests/clar/test/.gitignore b/t/unit-tests/clar/test/.gitignore
new file mode 100644
index 0000000000..a477d0c40c
--- /dev/null
+++ b/t/unit-tests/clar/test/.gitignore
@@ -0,0 +1,4 @@
+clar.suite
+.clarcache
+clar_test
+*.o
diff --git a/t/unit-tests/clar/test/Makefile b/t/unit-tests/clar/test/Makefile
new file mode 100644
index 0000000000..93c6b2ad32
--- /dev/null
+++ b/t/unit-tests/clar/test/Makefile
@@ -0,0 +1,39 @@
+#
+# Copyright (c) Vicent Marti. All rights reserved.
+#
+# This file is part of clar, distributed under the ISC license.
+# For full terms see the included COPYING file.
+#
+
+#
+# Set up the path to the clar sources and to the fixtures directory
+#
+# The fixture path needs to be an absolute path so it can be used
+# even after we have chdir'ed into the test directory while testing.
+#
+CURRENT_MAKEFILE := $(word $(words $(MAKEFILE_LIST)),$(MAKEFILE_LIST))
+TEST_DIRECTORY := $(abspath $(dir $(CURRENT_MAKEFILE)))
+CLAR_PATH := $(dir $(TEST_DIRECTORY))
+CLAR_FIXTURE_PATH := $(TEST_DIRECTORY)/resources/
+
+CFLAGS=-g -I.. -I. -Wall -DCLAR_FIXTURE_PATH=\"$(CLAR_FIXTURE_PATH)\"
+
+.PHONY: clean
+
+# list the objects that go into our test
+objects = main.o sample.o
+
+# build the test executable itself
+clar_test: $(objects) clar_test.h clar.suite $(CLAR_PATH)clar.c
+ $(CC) $(CFLAGS) -o $@ "$(CLAR_PATH)clar.c" $(objects)
+
+# test object files depend on clar macros
+$(objects) : $(CLAR_PATH)clar.h
+
+# build the clar.suite file of test metadata
+clar.suite:
+ python "$(CLAR_PATH)generate.py" .
+
+# remove all generated files
+clean:
+ $(RM) -rf *.o clar.suite .clarcache clar_test clar_test.dSYM
diff --git a/t/unit-tests/clar/test/clar_test.h b/t/unit-tests/clar/test/clar_test.h
new file mode 100644
index 0000000000..0fcaa639aa
--- /dev/null
+++ b/t/unit-tests/clar/test/clar_test.h
@@ -0,0 +1,16 @@
+/*
+ * Copyright (c) Vicent Marti. All rights reserved.
+ *
+ * This file is part of clar, distributed under the ISC license.
+ * For full terms see the included COPYING file.
+ */
+#ifndef __CLAR_TEST__
+#define __CLAR_TEST__
+
+/* Import the standard clar helper functions */
+#include "clar.h"
+
+/* Your custom shared includes / defines here */
+extern int global_test_counter;
+
+#endif
diff --git a/t/unit-tests/clar/test/main.c b/t/unit-tests/clar/test/main.c
new file mode 100644
index 0000000000..59e56ad255
--- /dev/null
+++ b/t/unit-tests/clar/test/main.c
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) Vicent Marti. All rights reserved.
+ *
+ * This file is part of clar, distributed under the ISC license.
+ * For full terms see the included COPYING file.
+ */
+
+#include "clar_test.h"
+
+/*
+ * Sample main() for clar tests.
+ *
+ * You should write your own main routine for clar tests that does specific
+ * setup and teardown as necessary for your application. The only required
+ * line is the call to `clar_test(argc, argv)`, which will execute the test
+ * suite. If you want to check the return value of the test application,
+ * your main() should return the same value returned by clar_test().
+ */
+
+int global_test_counter = 0;
+
+#ifdef _WIN32
+int __cdecl main(int argc, char *argv[])
+#else
+int main(int argc, char *argv[])
+#endif
+{
+ int ret;
+
+ /* Your custom initialization here */
+ global_test_counter = 0;
+
+ /* Run the test suite */
+ ret = clar_test(argc, argv);
+
+ /* Your custom cleanup here */
+ cl_assert_equal_i(8, global_test_counter);
+
+ return ret;
+}
diff --git a/t/unit-tests/clar/test/main.c.sample b/t/unit-tests/clar/test/main.c.sample
new file mode 100644
index 0000000000..a4d91b72fa
--- /dev/null
+++ b/t/unit-tests/clar/test/main.c.sample
@@ -0,0 +1,27 @@
+/*
+ * Copyright (c) Vicent Marti. All rights reserved.
+ *
+ * This file is part of clar, distributed under the ISC license.
+ * For full terms see the included COPYING file.
+ */
+
+#include "clar_test.h"
+
+/*
+ * Minimal main() for clar tests.
+ *
+ * Modify this with any application specific setup or teardown that you need.
+ * The only required line is the call to `clar_test(argc, argv)`, which will
+ * execute the test suite. If you want to check the return value of the test
+ * application, main() should return the same value returned by clar_test().
+ */
+
+#ifdef _WIN32
+int __cdecl main(int argc, char *argv[])
+#else
+int main(int argc, char *argv[])
+#endif
+{
+ /* Run the test suite */
+ return clar_test(argc, argv);
+}
diff --git a/t/unit-tests/clar/test/resources/test/file b/t/unit-tests/clar/test/resources/test/file
new file mode 100644
index 0000000000..220f4aa98a
--- /dev/null
+++ b/t/unit-tests/clar/test/resources/test/file
@@ -0,0 +1 @@
+File
diff --git a/t/unit-tests/clar/test/sample.c b/t/unit-tests/clar/test/sample.c
new file mode 100644
index 0000000000..faa1209262
--- /dev/null
+++ b/t/unit-tests/clar/test/sample.c
@@ -0,0 +1,84 @@
+#include "clar_test.h"
+#include <sys/stat.h>
+
+static int file_size(const char *filename)
+{
+ struct stat st;
+
+ if (stat(filename, &st) == 0)
+ return (int)st.st_size;
+ return -1;
+}
+
+void test_sample__initialize(void)
+{
+ global_test_counter++;
+}
+
+void test_sample__cleanup(void)
+{
+ cl_fixture_cleanup("test");
+
+ cl_assert(file_size("test/file") == -1);
+}
+
+void test_sample__1(void)
+{
+ cl_assert(1);
+ cl_must_pass(0); /* 0 == success */
+ cl_must_fail(-1); /* <0 == failure */
+ cl_must_pass(-1); /* demonstrate a failing call */
+}
+
+void test_sample__2(void)
+{
+ cl_fixture_sandbox("test");
+
+ cl_assert(file_size("test/nonexistent") == -1);
+ cl_assert(file_size("test/file") > 0);
+ cl_assert(100 == 101);
+}
+
+void test_sample__strings(void)
+{
+ const char *actual = "expected";
+ cl_assert_equal_s("expected", actual);
+ cl_assert_equal_s_("expected", actual, "second try with annotation");
+ cl_assert_equal_s_("mismatched", actual, "this one fails");
+}
+
+void test_sample__strings_with_length(void)
+{
+ const char *actual = "expected";
+ cl_assert_equal_strn("expected_", actual, 8);
+ cl_assert_equal_strn("exactly", actual, 2);
+ cl_assert_equal_strn_("expected_", actual, 8, "with annotation");
+ cl_assert_equal_strn_("exactly", actual, 3, "this one fails");
+}
+
+void test_sample__int(void)
+{
+ int value = 100;
+ cl_assert_equal_i(100, value);
+ cl_assert_equal_i_(101, value, "extra note on failing test");
+}
+
+void test_sample__int_fmt(void)
+{
+ int value = 100;
+ cl_assert_equal_i_fmt(022, value, "%04o");
+}
+
+void test_sample__bool(void)
+{
+ int value = 100;
+ cl_assert_equal_b(1, value); /* test equality as booleans */
+ cl_assert_equal_b(0, value);
+}
+
+void test_sample__ptr(void)
+{
+ const char *actual = "expected";
+ cl_assert_equal_p(actual, actual); /* pointers to same object */
+ cl_assert_equal_p(&actual, actual);
+}
diff --git a/t/unit-tests/t-ctype.c b/t/unit-tests/ctype.c
index d6ac1fe678..32e65867cd 100644
--- a/t/unit-tests/t-ctype.c
+++ b/t/unit-tests/ctype.c
@@ -1,18 +1,16 @@
-#include "test-lib.h"
+#include "unit-test.h"
#define TEST_CHAR_CLASS(class, string) do { \
size_t len = ARRAY_SIZE(string) - 1 + \
BUILD_ASSERT_OR_ZERO(ARRAY_SIZE(string) > 0) + \
BUILD_ASSERT_OR_ZERO(sizeof(string[0]) == sizeof(char)); \
- int skip = test__run_begin(); \
- if (!skip) { \
- for (int i = 0; i < 256; i++) { \
- if (!check_int(class(i), ==, !!memchr(string, i, len)))\
- test_msg(" i: 0x%02x", i); \
- } \
- check(!class(EOF)); \
+ for (int i = 0; i < 256; i++) { \
+ int actual = class(i), expect = !!memchr(string, i, len); \
+ if (actual != expect) \
+ cl_failf("0x%02x is classified incorrectly: expected %d, got %d", \
+ i, expect, actual); \
} \
- test__run_end(!skip, TEST_LOCATION(), #class " works"); \
+ cl_assert(!class(EOF)); \
} while (0)
#define DIGIT "0123456789"
@@ -33,21 +31,72 @@
"\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f" \
"\x7f"
-int cmd_main(int argc, const char **argv) {
+void test_ctype__isspace(void)
+{
TEST_CHAR_CLASS(isspace, " \n\r\t");
+}
+
+void test_ctype__isdigit(void)
+{
TEST_CHAR_CLASS(isdigit, DIGIT);
+}
+
+void test_ctype__isalpha(void)
+{
TEST_CHAR_CLASS(isalpha, LOWER UPPER);
+}
+
+void test_ctype__isalnum(void)
+{
TEST_CHAR_CLASS(isalnum, LOWER UPPER DIGIT);
+}
+
+void test_ctype__is_glob_special(void)
+{
TEST_CHAR_CLASS(is_glob_special, "*?[\\");
+}
+
+void test_ctype__is_regex_special(void)
+{
TEST_CHAR_CLASS(is_regex_special, "$()*+.?[\\^{|");
+}
+
+void test_ctype__is_pathspec_magic(void)
+{
TEST_CHAR_CLASS(is_pathspec_magic, "!\"#%&',-/:;<=>@_`~");
+}
+
+void test_ctype__isascii(void)
+{
TEST_CHAR_CLASS(isascii, ASCII);
+}
+
+void test_ctype__islower(void)
+{
TEST_CHAR_CLASS(islower, LOWER);
+}
+
+void test_ctype__isupper(void)
+{
TEST_CHAR_CLASS(isupper, UPPER);
+}
+
+void test_ctype__iscntrl(void)
+{
TEST_CHAR_CLASS(iscntrl, CNTRL);
+}
+
+void test_ctype__ispunct(void)
+{
TEST_CHAR_CLASS(ispunct, PUNCT);
+}
+
+void test_ctype__isxdigit(void)
+{
TEST_CHAR_CLASS(isxdigit, DIGIT "abcdefABCDEF");
- TEST_CHAR_CLASS(isprint, LOWER UPPER DIGIT PUNCT " ");
+}
- return test_done();
+void test_ctype__isprint(void)
+{
+ TEST_CHAR_CLASS(isprint, LOWER UPPER DIGIT PUNCT " ");
}
diff --git a/t/unit-tests/lib-oid.c b/t/unit-tests/lib-oid.c
index 37105f0a8f..8f0ccac532 100644
--- a/t/unit-tests/lib-oid.c
+++ b/t/unit-tests/lib-oid.c
@@ -3,7 +3,7 @@
#include "strbuf.h"
#include "hex.h"
-static int init_hash_algo(void)
+int init_hash_algo(void)
{
static int algo = -1;
diff --git a/t/unit-tests/lib-oid.h b/t/unit-tests/lib-oid.h
index 8d2acca768..4e77c04bd2 100644
--- a/t/unit-tests/lib-oid.h
+++ b/t/unit-tests/lib-oid.h
@@ -13,5 +13,13 @@
* environment variable.
*/
int get_oid_arbitrary_hex(const char *s, struct object_id *oid);
+/*
+ * Returns one of GIT_HASH_{SHA1, SHA256, UNKNOWN} based on the value of
+ * GIT_TEST_DEFAULT_HASH environment variable. The fallback value in the
+ * absence of GIT_TEST_DEFAULT_HASH is GIT_HASH_SHA1. It also uses
+ * check(algo != GIT_HASH_UNKNOWN) before returning to verify if the
+ * GIT_TEST_DEFAULT_HASH's value is valid or not.
+ */
+int init_hash_algo(void);
#endif /* LIB_OID_H */
diff --git a/t/unit-tests/lib-reftable.c b/t/unit-tests/lib-reftable.c
new file mode 100644
index 0000000000..ab1fa44a28
--- /dev/null
+++ b/t/unit-tests/lib-reftable.c
@@ -0,0 +1,93 @@
+#include "lib-reftable.h"
+#include "test-lib.h"
+#include "reftable/constants.h"
+#include "reftable/writer.h"
+
+void t_reftable_set_hash(uint8_t *p, int i, uint32_t id)
+{
+ memset(p, (uint8_t)i, hash_size(id));
+}
+
+static ssize_t strbuf_writer_write(void *b, const void *data, size_t sz)
+{
+ strbuf_add(b, data, sz);
+ return sz;
+}
+
+static int strbuf_writer_flush(void *arg UNUSED)
+{
+ return 0;
+}
+
+struct reftable_writer *t_reftable_strbuf_writer(struct strbuf *buf,
+ struct reftable_write_options *opts)
+{
+ return reftable_new_writer(&strbuf_writer_write,
+ &strbuf_writer_flush,
+ buf, opts);
+}
+
+void t_reftable_write_to_buf(struct strbuf *buf,
+ struct reftable_ref_record *refs,
+ size_t nrefs,
+ struct reftable_log_record *logs,
+ size_t nlogs,
+ struct reftable_write_options *_opts)
+{
+ struct reftable_write_options opts = { 0 };
+ const struct reftable_stats *stats;
+ struct reftable_writer *writer;
+ uint64_t min = 0xffffffff;
+ uint64_t max = 0;
+ int ret;
+
+ if (_opts)
+ opts = *_opts;
+
+ for (size_t i = 0; i < nrefs; i++) {
+ uint64_t ui = refs[i].update_index;
+ if (ui > max)
+ max = ui;
+ if (ui < min)
+ min = ui;
+ }
+ for (size_t i = 0; i < nlogs; i++) {
+ uint64_t ui = logs[i].update_index;
+ if (ui > max)
+ max = ui;
+ if (ui < min)
+ min = ui;
+ }
+
+ writer = t_reftable_strbuf_writer(buf, &opts);
+ reftable_writer_set_limits(writer, min, max);
+
+ if (nrefs) {
+ ret = reftable_writer_add_refs(writer, refs, nrefs);
+ check_int(ret, ==, 0);
+ }
+
+ if (nlogs) {
+ ret = reftable_writer_add_logs(writer, logs, nlogs);
+ check_int(ret, ==, 0);
+ }
+
+ ret = reftable_writer_close(writer);
+ check_int(ret, ==, 0);
+
+ stats = reftable_writer_stats(writer);
+ for (size_t i = 0; i < stats->ref_stats.blocks; i++) {
+ size_t off = i * (opts.block_size ? opts.block_size
+ : DEFAULT_BLOCK_SIZE);
+ if (!off)
+ off = header_size(opts.hash_id == GIT_SHA256_FORMAT_ID ? 2 : 1);
+ check_char(buf->buf[off], ==, 'r');
+ }
+
+ if (nrefs)
+ check_int(stats->ref_stats.blocks, >, 0);
+ if (nlogs)
+ check_int(stats->log_stats.blocks, >, 0);
+
+ reftable_writer_free(writer);
+}
diff --git a/t/unit-tests/lib-reftable.h b/t/unit-tests/lib-reftable.h
new file mode 100644
index 0000000000..d115419084
--- /dev/null
+++ b/t/unit-tests/lib-reftable.h
@@ -0,0 +1,20 @@
+#ifndef LIB_REFTABLE_H
+#define LIB_REFTABLE_H
+
+#include "git-compat-util.h"
+#include "strbuf.h"
+#include "reftable/reftable-writer.h"
+
+void t_reftable_set_hash(uint8_t *p, int i, uint32_t id);
+
+struct reftable_writer *t_reftable_strbuf_writer(struct strbuf *buf,
+ struct reftable_write_options *opts);
+
+void t_reftable_write_to_buf(struct strbuf *buf,
+ struct reftable_ref_record *refs,
+ size_t nrecords,
+ struct reftable_log_record *logs,
+ size_t nlogs,
+ struct reftable_write_options *opts);
+
+#endif
diff --git a/t/unit-tests/t-strvec.c b/t/unit-tests/strvec.c
index d4615ab06d..bf4c0cb172 100644
--- a/t/unit-tests/t-strvec.c
+++ b/t/unit-tests/strvec.c
@@ -1,69 +1,50 @@
-#include "test-lib.h"
+#include "unit-test.h"
#include "strbuf.h"
#include "strvec.h"
#define check_strvec(vec, ...) \
- check_strvec_loc(TEST_LOCATION(), vec, __VA_ARGS__)
-LAST_ARG_MUST_BE_NULL
-static void check_strvec_loc(const char *loc, struct strvec *vec, ...)
-{
- va_list ap;
- size_t nr = 0;
-
- va_start(ap, vec);
- while (1) {
- const char *str = va_arg(ap, const char *);
- if (!str)
- break;
-
- if (!check_uint(vec->nr, >, nr) ||
- !check_uint(vec->alloc, >, nr) ||
- !check_str(vec->v[nr], str)) {
- struct strbuf msg = STRBUF_INIT;
- strbuf_addf(&msg, "strvec index %"PRIuMAX, (uintmax_t) nr);
- test_assert(loc, msg.buf, 0);
- strbuf_release(&msg);
- va_end(ap);
- return;
- }
-
- nr++;
- }
- va_end(ap);
-
- check_uint(vec->nr, ==, nr);
- check_uint(vec->alloc, >=, nr);
- check_pointer_eq(vec->v[nr], NULL);
-}
+ do { \
+ const char *expect[] = { __VA_ARGS__ }; \
+ size_t expect_len = ARRAY_SIZE(expect); \
+ cl_assert(expect_len > 0); \
+ cl_assert_equal_p(expect[expect_len - 1], NULL); \
+ cl_assert_equal_i((vec)->nr, expect_len - 1); \
+ cl_assert((vec)->nr <= (vec)->alloc); \
+ for (size_t i = 0; i < expect_len; i++) \
+ cl_assert_equal_s((vec)->v[i], expect[i]); \
+ } while (0)
-static void t_static_init(void)
+void test_strvec__init(void)
{
struct strvec vec = STRVEC_INIT;
- check_pointer_eq(vec.v, empty_strvec);
- check_uint(vec.nr, ==, 0);
- check_uint(vec.alloc, ==, 0);
+
+ cl_assert_equal_p(vec.v, empty_strvec);
+ cl_assert_equal_i(vec.nr, 0);
+ cl_assert_equal_i(vec.alloc, 0);
}
-static void t_dynamic_init(void)
+void test_strvec__dynamic_init(void)
{
struct strvec vec;
+
strvec_init(&vec);
- check_pointer_eq(vec.v, empty_strvec);
- check_uint(vec.nr, ==, 0);
- check_uint(vec.alloc, ==, 0);
+ cl_assert_equal_p(vec.v, empty_strvec);
+ cl_assert_equal_i(vec.nr, 0);
+ cl_assert_equal_i(vec.alloc, 0);
}
-static void t_clear(void)
+void test_strvec__clear(void)
{
struct strvec vec = STRVEC_INIT;
+
strvec_push(&vec, "foo");
strvec_clear(&vec);
- check_pointer_eq(vec.v, empty_strvec);
- check_uint(vec.nr, ==, 0);
- check_uint(vec.alloc, ==, 0);
+ cl_assert_equal_p(vec.v, empty_strvec);
+ cl_assert_equal_i(vec.nr, 0);
+ cl_assert_equal_i(vec.alloc, 0);
}
-static void t_push(void)
+void test_strvec__push(void)
{
struct strvec vec = STRVEC_INIT;
@@ -76,23 +57,25 @@ static void t_push(void)
strvec_clear(&vec);
}
-static void t_pushf(void)
+void test_strvec__pushf(void)
{
struct strvec vec = STRVEC_INIT;
+
strvec_pushf(&vec, "foo: %d", 1);
check_strvec(&vec, "foo: 1", NULL);
strvec_clear(&vec);
}
-static void t_pushl(void)
+void test_strvec__pushl(void)
{
struct strvec vec = STRVEC_INIT;
+
strvec_pushl(&vec, "foo", "bar", "baz", NULL);
check_strvec(&vec, "foo", "bar", "baz", NULL);
strvec_clear(&vec);
}
-static void t_pushv(void)
+void test_strvec__pushv(void)
{
const char *strings[] = {
"foo", "bar", "baz", NULL,
@@ -105,16 +88,17 @@ static void t_pushv(void)
strvec_clear(&vec);
}
-static void t_replace_at_head(void)
+void test_strvec__replace_at_head(void)
{
struct strvec vec = STRVEC_INIT;
+
strvec_pushl(&vec, "foo", "bar", "baz", NULL);
strvec_replace(&vec, 0, "replaced");
check_strvec(&vec, "replaced", "bar", "baz", NULL);
strvec_clear(&vec);
}
-static void t_replace_at_tail(void)
+void test_strvec__replace_at_tail(void)
{
struct strvec vec = STRVEC_INIT;
strvec_pushl(&vec, "foo", "bar", "baz", NULL);
@@ -123,109 +107,121 @@ static void t_replace_at_tail(void)
strvec_clear(&vec);
}
-static void t_replace_in_between(void)
+void test_strvec__replace_in_between(void)
{
struct strvec vec = STRVEC_INIT;
+
strvec_pushl(&vec, "foo", "bar", "baz", NULL);
strvec_replace(&vec, 1, "replaced");
check_strvec(&vec, "foo", "replaced", "baz", NULL);
strvec_clear(&vec);
}
-static void t_replace_with_substring(void)
+void test_strvec__replace_with_substring(void)
{
struct strvec vec = STRVEC_INIT;
+
strvec_pushl(&vec, "foo", NULL);
strvec_replace(&vec, 0, vec.v[0] + 1);
check_strvec(&vec, "oo", NULL);
strvec_clear(&vec);
}
-static void t_remove_at_head(void)
+void test_strvec__remove_at_head(void)
{
struct strvec vec = STRVEC_INIT;
+
strvec_pushl(&vec, "foo", "bar", "baz", NULL);
strvec_remove(&vec, 0);
check_strvec(&vec, "bar", "baz", NULL);
strvec_clear(&vec);
}
-static void t_remove_at_tail(void)
+void test_strvec__remove_at_tail(void)
{
struct strvec vec = STRVEC_INIT;
+
strvec_pushl(&vec, "foo", "bar", "baz", NULL);
strvec_remove(&vec, 2);
check_strvec(&vec, "foo", "bar", NULL);
strvec_clear(&vec);
}
-static void t_remove_in_between(void)
+void test_strvec__remove_in_between(void)
{
struct strvec vec = STRVEC_INIT;
+
strvec_pushl(&vec, "foo", "bar", "baz", NULL);
strvec_remove(&vec, 1);
check_strvec(&vec, "foo", "baz", NULL);
strvec_clear(&vec);
}
-static void t_pop_empty_array(void)
+void test_strvec__pop_empty_array(void)
{
struct strvec vec = STRVEC_INIT;
+
strvec_pop(&vec);
check_strvec(&vec, NULL);
strvec_clear(&vec);
}
-static void t_pop_non_empty_array(void)
+void test_strvec__pop_non_empty_array(void)
{
struct strvec vec = STRVEC_INIT;
+
strvec_pushl(&vec, "foo", "bar", "baz", NULL);
strvec_pop(&vec);
check_strvec(&vec, "foo", "bar", NULL);
strvec_clear(&vec);
}
-static void t_split_empty_string(void)
+void test_strvec__split_empty_string(void)
{
struct strvec vec = STRVEC_INIT;
+
strvec_split(&vec, "");
check_strvec(&vec, NULL);
strvec_clear(&vec);
}
-static void t_split_single_item(void)
+void test_strvec__split_single_item(void)
{
struct strvec vec = STRVEC_INIT;
+
strvec_split(&vec, "foo");
check_strvec(&vec, "foo", NULL);
strvec_clear(&vec);
}
-static void t_split_multiple_items(void)
+void test_strvec__split_multiple_items(void)
{
struct strvec vec = STRVEC_INIT;
+
strvec_split(&vec, "foo bar baz");
check_strvec(&vec, "foo", "bar", "baz", NULL);
strvec_clear(&vec);
}
-static void t_split_whitespace_only(void)
+void test_strvec__split_whitespace_only(void)
{
struct strvec vec = STRVEC_INIT;
+
strvec_split(&vec, " \t\n");
check_strvec(&vec, NULL);
strvec_clear(&vec);
}
-static void t_split_multiple_consecutive_whitespaces(void)
+void test_strvec__split_multiple_consecutive_whitespaces(void)
{
struct strvec vec = STRVEC_INIT;
+
strvec_split(&vec, "foo\n\t bar");
check_strvec(&vec, "foo", "bar", NULL);
strvec_clear(&vec);
}
-static void t_detach(void)
+void test_strvec__detach(void)
{
struct strvec vec = STRVEC_INIT;
const char **detached;
@@ -233,40 +229,13 @@ static void t_detach(void)
strvec_push(&vec, "foo");
detached = strvec_detach(&vec);
- check_str(detached[0], "foo");
- check_pointer_eq(detached[1], NULL);
+ cl_assert_equal_s(detached[0], "foo");
+ cl_assert_equal_p(detached[1], NULL);
- check_pointer_eq(vec.v, empty_strvec);
- check_uint(vec.nr, ==, 0);
- check_uint(vec.alloc, ==, 0);
+ cl_assert_equal_p(vec.v, empty_strvec);
+ cl_assert_equal_i(vec.nr, 0);
+ cl_assert_equal_i(vec.alloc, 0);
free((char *) detached[0]);
free(detached);
}
-
-int cmd_main(int argc, const char **argv)
-{
- TEST(t_static_init(), "static initialization");
- TEST(t_dynamic_init(), "dynamic initialization");
- TEST(t_clear(), "clear");
- TEST(t_push(), "push");
- TEST(t_pushf(), "pushf");
- TEST(t_pushl(), "pushl");
- TEST(t_pushv(), "pushv");
- TEST(t_replace_at_head(), "replace at head");
- TEST(t_replace_in_between(), "replace in between");
- TEST(t_replace_at_tail(), "replace at tail");
- TEST(t_replace_with_substring(), "replace with substring");
- TEST(t_remove_at_head(), "remove at head");
- TEST(t_remove_in_between(), "remove in between");
- TEST(t_remove_at_tail(), "remove at tail");
- TEST(t_pop_empty_array(), "pop with empty array");
- TEST(t_pop_non_empty_array(), "pop with non-empty array");
- TEST(t_split_empty_string(), "split empty string");
- TEST(t_split_single_item(), "split single item");
- TEST(t_split_multiple_items(), "split multiple items");
- TEST(t_split_whitespace_only(), "split whitespace only");
- TEST(t_split_multiple_consecutive_whitespaces(), "split multiple consecutive whitespaces");
- TEST(t_detach(), "detach");
- return test_done();
-}
diff --git a/t/unit-tests/t-hash.c b/t/unit-tests/t-hash.c
index e9a78bf2c0..e62647019b 100644
--- a/t/unit-tests/t-hash.c
+++ b/t/unit-tests/t-hash.c
@@ -38,7 +38,7 @@ static void check_hash_data(const void *data, size_t data_length,
"SHA1 and SHA256 (%s) works", #literal); \
} while (0)
-int cmd_main(int argc, const char **argv)
+int cmd_main(int argc UNUSED, const char **argv UNUSED)
{
struct strbuf aaaaaaaaaa_100000 = STRBUF_INIT;
struct strbuf alphabet_100000 = STRBUF_INIT;
diff --git a/t/unit-tests/t-hashmap.c b/t/unit-tests/t-hashmap.c
new file mode 100644
index 0000000000..83b79dff39
--- /dev/null
+++ b/t/unit-tests/t-hashmap.c
@@ -0,0 +1,361 @@
+#include "test-lib.h"
+#include "hashmap.h"
+#include "strbuf.h"
+
+struct test_entry {
+ int padding; /* hashmap entry no longer needs to be the first member */
+ struct hashmap_entry ent;
+ /* key and value as two \0-terminated strings */
+ char key[FLEX_ARRAY];
+};
+
+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 unsigned 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 const char *get_value(const struct test_entry *e)
+{
+ return e->key + strlen(e->key) + 1;
+}
+
+static struct test_entry *alloc_test_entry(const char *key, const char *value,
+ unsigned int ignore_case)
+{
+ size_t klen = strlen(key);
+ size_t vlen = strlen(value);
+ unsigned int hash = ignore_case ? strihash(key) : strhash(key);
+ 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;
+}
+
+static struct test_entry *get_test_entry(struct hashmap *map, const char *key,
+ unsigned int ignore_case)
+{
+ return hashmap_get_entry_from_hash(
+ map, ignore_case ? strihash(key) : strhash(key), key,
+ struct test_entry, ent);
+}
+
+static int key_val_contains(const char *key_val[][2], char seen[], size_t n,
+ struct test_entry *entry)
+{
+ for (size_t i = 0; i < n; i++) {
+ if (!strcmp(entry->key, key_val[i][0]) &&
+ !strcmp(get_value(entry), key_val[i][1])) {
+ if (seen[i])
+ return 2;
+ seen[i] = 1;
+ return 0;
+ }
+ }
+ return 1;
+}
+
+static void setup(void (*f)(struct hashmap *map, unsigned int ignore_case),
+ unsigned int ignore_case)
+{
+ struct hashmap map = HASHMAP_INIT(test_entry_cmp, &ignore_case);
+
+ f(&map, ignore_case);
+ hashmap_clear_and_free(&map, struct test_entry, ent);
+}
+
+static void t_replace(struct hashmap *map, unsigned int ignore_case)
+{
+ struct test_entry *entry;
+
+ entry = alloc_test_entry("key1", "value1", ignore_case);
+ check_pointer_eq(hashmap_put_entry(map, entry, ent), NULL);
+
+ entry = alloc_test_entry(ignore_case ? "Key1" : "key1", "value2",
+ ignore_case);
+ entry = hashmap_put_entry(map, entry, ent);
+ if (check(entry != NULL))
+ check_str(get_value(entry), "value1");
+ free(entry);
+
+ entry = alloc_test_entry("fooBarFrotz", "value3", ignore_case);
+ check_pointer_eq(hashmap_put_entry(map, entry, ent), NULL);
+
+ entry = alloc_test_entry(ignore_case ? "FOObarFrotz" : "fooBarFrotz",
+ "value4", ignore_case);
+ entry = hashmap_put_entry(map, entry, ent);
+ if (check(entry != NULL))
+ check_str(get_value(entry), "value3");
+ free(entry);
+}
+
+static void t_get(struct hashmap *map, unsigned int ignore_case)
+{
+ struct test_entry *entry;
+ const char *key_val[][2] = { { "key1", "value1" },
+ { "key2", "value2" },
+ { "fooBarFrotz", "value3" },
+ { ignore_case ? "key4" : "foobarfrotz",
+ "value4" } };
+ const char *query[][2] = {
+ { ignore_case ? "Key1" : "key1", "value1" },
+ { ignore_case ? "keY2" : "key2", "value2" },
+ { ignore_case ? "FOObarFrotz" : "fooBarFrotz", "value3" },
+ { ignore_case ? "FOObarFrotz" : "foobarfrotz",
+ ignore_case ? "value3" : "value4" }
+ };
+
+ for (size_t i = 0; i < ARRAY_SIZE(key_val); i++) {
+ entry = alloc_test_entry(key_val[i][0], key_val[i][1],
+ ignore_case);
+ check_pointer_eq(hashmap_put_entry(map, entry, ent), NULL);
+ }
+
+ for (size_t i = 0; i < ARRAY_SIZE(query); i++) {
+ entry = get_test_entry(map, query[i][0], ignore_case);
+ if (check(entry != NULL))
+ check_str(get_value(entry), query[i][1]);
+ else
+ test_msg("query key: %s", query[i][0]);
+ }
+
+ check_pointer_eq(get_test_entry(map, "notInMap", ignore_case), NULL);
+ check_int(map->tablesize, ==, 64);
+ check_int(hashmap_get_size(map), ==, ARRAY_SIZE(key_val));
+}
+
+static void t_add(struct hashmap *map, unsigned int ignore_case)
+{
+ struct test_entry *entry;
+ const char *key_val[][2] = {
+ { "key1", "value1" },
+ { ignore_case ? "Key1" : "key1", "value2" },
+ { "fooBarFrotz", "value3" },
+ { ignore_case ? "FOObarFrotz" : "fooBarFrotz", "value4" }
+ };
+ const char *query_keys[] = { "key1", ignore_case ? "FOObarFrotz" :
+ "fooBarFrotz" };
+ char seen[ARRAY_SIZE(key_val)] = { 0 };
+
+ for (size_t i = 0; i < ARRAY_SIZE(key_val); i++) {
+ entry = alloc_test_entry(key_val[i][0], key_val[i][1], ignore_case);
+ hashmap_add(map, &entry->ent);
+ }
+
+ for (size_t i = 0; i < ARRAY_SIZE(query_keys); i++) {
+ int count = 0;
+ entry = hashmap_get_entry_from_hash(map,
+ ignore_case ? strihash(query_keys[i]) :
+ strhash(query_keys[i]),
+ query_keys[i], struct test_entry, ent);
+
+ hashmap_for_each_entry_from(map, entry, ent)
+ {
+ int ret;
+ if (!check_int((ret = key_val_contains(
+ key_val, seen,
+ ARRAY_SIZE(key_val), entry)),
+ ==, 0)) {
+ switch (ret) {
+ case 1:
+ test_msg("found entry was not given in the input\n"
+ " key: %s\n value: %s",
+ entry->key, get_value(entry));
+ break;
+ case 2:
+ test_msg("duplicate entry detected\n"
+ " key: %s\n value: %s",
+ entry->key, get_value(entry));
+ break;
+ }
+ } else {
+ count++;
+ }
+ }
+ check_int(count, ==, 2);
+ }
+
+ for (size_t i = 0; i < ARRAY_SIZE(seen); i++) {
+ if (!check_int(seen[i], ==, 1))
+ test_msg("following key-val pair was not iterated over:\n"
+ " key: %s\n value: %s",
+ key_val[i][0], key_val[i][1]);
+ }
+
+ check_int(hashmap_get_size(map), ==, ARRAY_SIZE(key_val));
+ check_pointer_eq(get_test_entry(map, "notInMap", ignore_case), NULL);
+}
+
+static void t_remove(struct hashmap *map, unsigned int ignore_case)
+{
+ struct test_entry *entry, *removed;
+ const char *key_val[][2] = { { "key1", "value1" },
+ { "key2", "value2" },
+ { "fooBarFrotz", "value3" } };
+ const char *remove[][2] = { { ignore_case ? "Key1" : "key1", "value1" },
+ { ignore_case ? "keY2" : "key2", "value2" } };
+
+ for (size_t i = 0; i < ARRAY_SIZE(key_val); i++) {
+ entry = alloc_test_entry(key_val[i][0], key_val[i][1], ignore_case);
+ check_pointer_eq(hashmap_put_entry(map, entry, ent), NULL);
+ }
+
+ for (size_t i = 0; i < ARRAY_SIZE(remove); i++) {
+ entry = alloc_test_entry(remove[i][0], "", ignore_case);
+ removed = hashmap_remove_entry(map, entry, ent, remove[i][0]);
+ if (check(removed != NULL))
+ check_str(get_value(removed), remove[i][1]);
+ free(entry);
+ free(removed);
+ }
+
+ entry = alloc_test_entry("notInMap", "", ignore_case);
+ check_pointer_eq(hashmap_remove_entry(map, entry, ent, "notInMap"), NULL);
+ free(entry);
+
+ check_int(map->tablesize, ==, 64);
+ check_int(hashmap_get_size(map), ==, ARRAY_SIZE(key_val) - ARRAY_SIZE(remove));
+}
+
+static void t_iterate(struct hashmap *map, unsigned int ignore_case)
+{
+ struct test_entry *entry;
+ struct hashmap_iter iter;
+ const char *key_val[][2] = { { "key1", "value1" },
+ { "key2", "value2" },
+ { "fooBarFrotz", "value3" } };
+ char seen[ARRAY_SIZE(key_val)] = { 0 };
+
+ for (size_t i = 0; i < ARRAY_SIZE(key_val); i++) {
+ entry = alloc_test_entry(key_val[i][0], key_val[i][1], ignore_case);
+ check_pointer_eq(hashmap_put_entry(map, entry, ent), NULL);
+ }
+
+ hashmap_for_each_entry(map, &iter, entry, ent /* member name */)
+ {
+ int ret;
+ if (!check_int((ret = key_val_contains(key_val, seen,
+ ARRAY_SIZE(key_val),
+ entry)), ==, 0)) {
+ switch (ret) {
+ case 1:
+ test_msg("found entry was not given in the input\n"
+ " key: %s\n value: %s",
+ entry->key, get_value(entry));
+ break;
+ case 2:
+ test_msg("duplicate entry detected\n"
+ " key: %s\n value: %s",
+ entry->key, get_value(entry));
+ break;
+ }
+ }
+ }
+
+ for (size_t i = 0; i < ARRAY_SIZE(seen); i++) {
+ if (!check_int(seen[i], ==, 1))
+ test_msg("following key-val pair was not iterated over:\n"
+ " key: %s\n value: %s",
+ key_val[i][0], key_val[i][1]);
+ }
+
+ check_int(hashmap_get_size(map), ==, ARRAY_SIZE(key_val));
+}
+
+static void t_alloc(struct hashmap *map, unsigned int ignore_case)
+{
+ struct test_entry *entry, *removed;
+
+ for (int i = 1; i <= 51; i++) {
+ char *key = xstrfmt("key%d", i);
+ char *value = xstrfmt("value%d", i);
+ entry = alloc_test_entry(key, value, ignore_case);
+ check_pointer_eq(hashmap_put_entry(map, entry, ent), NULL);
+ free(key);
+ free(value);
+ }
+ check_int(map->tablesize, ==, 64);
+ check_int(hashmap_get_size(map), ==, 51);
+
+ entry = alloc_test_entry("key52", "value52", ignore_case);
+ check_pointer_eq(hashmap_put_entry(map, entry, ent), NULL);
+ check_int(map->tablesize, ==, 256);
+ check_int(hashmap_get_size(map), ==, 52);
+
+ for (int i = 1; i <= 12; i++) {
+ char *key = xstrfmt("key%d", i);
+ char *value = xstrfmt("value%d", i);
+
+ entry = alloc_test_entry(key, "", ignore_case);
+ removed = hashmap_remove_entry(map, entry, ent, key);
+ if (check(removed != NULL))
+ check_str(value, get_value(removed));
+ free(key);
+ free(value);
+ free(entry);
+ free(removed);
+ }
+ check_int(map->tablesize, ==, 256);
+ check_int(hashmap_get_size(map), ==, 40);
+
+ entry = alloc_test_entry("key40", "", ignore_case);
+ removed = hashmap_remove_entry(map, entry, ent, "key40");
+ if (check(removed != NULL))
+ check_str("value40", get_value(removed));
+ check_int(map->tablesize, ==, 64);
+ check_int(hashmap_get_size(map), ==, 39);
+ free(entry);
+ free(removed);
+}
+
+static void t_intern(void)
+{
+ const char *values[] = { "value1", "Value1", "value2", "value2" };
+
+ for (size_t i = 0; i < ARRAY_SIZE(values); i++) {
+ const char *i1 = strintern(values[i]);
+ const char *i2 = strintern(values[i]);
+
+ if (!check(!strcmp(i1, values[i])))
+ test_msg("strintern(%s) returns %s\n", values[i], i1);
+ else if (!check(i1 != values[i]))
+ test_msg("strintern(%s) returns input pointer\n",
+ values[i]);
+ else if (!check_pointer_eq(i1, i2))
+ test_msg("address('%s') != address('%s'), so strintern('%s') != strintern('%s')",
+ i1, i2, values[i], values[i]);
+ else
+ check_str(i1, values[i]);
+ }
+}
+
+int cmd_main(int argc UNUSED, const char **argv UNUSED)
+{
+ TEST(setup(t_replace, 0), "replace works");
+ TEST(setup(t_replace, 1), "replace (case insensitive) works");
+ TEST(setup(t_get, 0), "get works");
+ TEST(setup(t_get, 1), "get (case insensitive) works");
+ TEST(setup(t_add, 0), "add works");
+ TEST(setup(t_add, 1), "add (case insensitive) works");
+ TEST(setup(t_remove, 0), "remove works");
+ TEST(setup(t_remove, 1), "remove (case insensitive) works");
+ TEST(setup(t_iterate, 0), "iterate works");
+ TEST(setup(t_iterate, 1), "iterate (case insensitive) works");
+ TEST(setup(t_alloc, 0), "grow / shrink works");
+ TEST(t_intern(), "string interning works");
+ return test_done();
+}
diff --git a/t/unit-tests/t-mem-pool.c b/t/unit-tests/t-mem-pool.c
index a0d57df761..fe500c704b 100644
--- a/t/unit-tests/t-mem-pool.c
+++ b/t/unit-tests/t-mem-pool.c
@@ -20,7 +20,7 @@ static void t_calloc_100(struct mem_pool *pool)
check(pool->mp_block->end != NULL);
}
-int cmd_main(int argc, const char **argv)
+int cmd_main(int argc UNUSED, const char **argv UNUSED)
{
TEST(setup_static(t_calloc_100, 1024 * 1024),
"mem_pool_calloc returns 100 zeroed bytes with big block");
diff --git a/t/unit-tests/t-oid-array.c b/t/unit-tests/t-oid-array.c
new file mode 100644
index 0000000000..45b59a2a51
--- /dev/null
+++ b/t/unit-tests/t-oid-array.c
@@ -0,0 +1,126 @@
+#define USE_THE_REPOSITORY_VARIABLE
+
+#include "test-lib.h"
+#include "lib-oid.h"
+#include "oid-array.h"
+#include "hex.h"
+
+static int fill_array(struct oid_array *array, const char *hexes[], size_t n)
+{
+ for (size_t i = 0; i < n; i++) {
+ struct object_id oid;
+
+ if (!check_int(get_oid_arbitrary_hex(hexes[i], &oid), ==, 0))
+ return -1;
+ oid_array_append(array, &oid);
+ }
+ if (!check_uint(array->nr, ==, n))
+ return -1;
+ return 0;
+}
+
+static int add_to_oid_array(const struct object_id *oid, void *data)
+{
+ struct oid_array *array = data;
+
+ oid_array_append(array, oid);
+ return 0;
+}
+
+static void t_enumeration(const char **input_args, size_t input_sz,
+ const char **expect_args, size_t expect_sz)
+{
+ struct oid_array input = OID_ARRAY_INIT, expect = OID_ARRAY_INIT,
+ actual = OID_ARRAY_INIT;
+ size_t i;
+
+ if (fill_array(&input, input_args, input_sz))
+ return;
+ if (fill_array(&expect, expect_args, expect_sz))
+ return;
+
+ oid_array_for_each_unique(&input, add_to_oid_array, &actual);
+ if (!check_uint(actual.nr, ==, expect.nr))
+ return;
+
+ for (i = 0; i < actual.nr; i++) {
+ if (!check(oideq(&actual.oid[i], &expect.oid[i])))
+ test_msg("expected: %s\n got: %s\n index: %" PRIuMAX,
+ oid_to_hex(&expect.oid[i]), oid_to_hex(&actual.oid[i]),
+ (uintmax_t)i);
+ }
+
+ oid_array_clear(&actual);
+ oid_array_clear(&input);
+ oid_array_clear(&expect);
+}
+
+#define TEST_ENUMERATION(input, expect, desc) \
+ TEST(t_enumeration(input, ARRAY_SIZE(input), expect, ARRAY_SIZE(expect)), \
+ desc " works")
+
+static void t_lookup(const char **input_hexes, size_t n, const char *query_hex,
+ int lower_bound, int upper_bound)
+{
+ struct oid_array array = OID_ARRAY_INIT;
+ struct object_id oid_query;
+ int ret;
+
+ if (!check_int(get_oid_arbitrary_hex(query_hex, &oid_query), ==, 0))
+ return;
+ if (fill_array(&array, input_hexes, n))
+ return;
+ ret = oid_array_lookup(&array, &oid_query);
+
+ if (!check_int(ret, <=, upper_bound) ||
+ !check_int(ret, >=, lower_bound))
+ test_msg("oid query for lookup: %s", oid_to_hex(&oid_query));
+
+ oid_array_clear(&array);
+}
+
+#define TEST_LOOKUP(input_hexes, query, lower_bound, upper_bound, desc) \
+ TEST(t_lookup(input_hexes, ARRAY_SIZE(input_hexes), query, \
+ lower_bound, upper_bound), \
+ desc " works")
+
+static void setup(void)
+{
+ /* The hash algo is used by oid_array_lookup() internally */
+ int algo = init_hash_algo();
+ if (check_int(algo, !=, GIT_HASH_UNKNOWN))
+ repo_set_hash_algo(the_repository, algo);
+}
+
+int cmd_main(int argc UNUSED, const char **argv UNUSED)
+{
+ const char *arr_input[] = { "88", "44", "aa", "55" };
+ const char *arr_input_dup[] = { "88", "44", "aa", "55",
+ "88", "44", "aa", "55",
+ "88", "44", "aa", "55" };
+ const char *res_sorted[] = { "44", "55", "88", "aa" };
+ const char *nearly_55;
+
+ if (!TEST(setup(), "setup"))
+ test_skip_all("hash algo initialization failed");
+
+ TEST_ENUMERATION(arr_input, res_sorted, "ordered enumeration");
+ TEST_ENUMERATION(arr_input_dup, res_sorted,
+ "ordered enumeration with duplicate suppression");
+
+ TEST_LOOKUP(arr_input, "55", 1, 1, "lookup");
+ TEST_LOOKUP(arr_input, "33", INT_MIN, -1, "lookup non-existent entry");
+ TEST_LOOKUP(arr_input_dup, "55", 3, 5, "lookup with duplicates");
+ TEST_LOOKUP(arr_input_dup, "66", INT_MIN, -1,
+ "lookup non-existent entry with duplicates");
+
+ nearly_55 = init_hash_algo() == GIT_HASH_SHA1 ?
+ "5500000000000000000000000000000000000001" :
+ "5500000000000000000000000000000000000000000000000000000000000001";
+ TEST_LOOKUP(((const char *[]){ "55", nearly_55 }), "55", 0, 0,
+ "lookup with almost duplicate values");
+ TEST_LOOKUP(((const char *[]){ "55", "55" }), "55", 0, 1,
+ "lookup with single duplicate value");
+
+ return test_done();
+}
diff --git a/t/unit-tests/t-prio-queue.c b/t/unit-tests/t-prio-queue.c
index 7a4e5780e1..fe6ae37935 100644
--- a/t/unit-tests/t-prio-queue.c
+++ b/t/unit-tests/t-prio-queue.c
@@ -69,7 +69,7 @@ static void test_prio_queue(int *input, size_t input_size,
#define TEST_INPUT(input, result) \
test_prio_queue(input, ARRAY_SIZE(input), result, ARRAY_SIZE(result))
-int cmd_main(int argc, const char **argv)
+int cmd_main(int argc UNUSED, const char **argv UNUSED)
{
TEST(TEST_INPUT(((int []){ 2, 6, 3, 10, 9, 5, 7, 4, 5, 8, 1, DUMP }),
((int []){ 1, 2, 3, 4, 5, 5, 6, 7, 8, 9, 10 })),
diff --git a/t/unit-tests/t-reftable-basics.c b/t/unit-tests/t-reftable-basics.c
index 4e80bdf16d..e5556ebf52 100644
--- a/t/unit-tests/t-reftable-basics.c
+++ b/t/unit-tests/t-reftable-basics.c
@@ -20,141 +20,125 @@ static int integer_needle_lesseq(size_t i, void *_args)
return args->needle <= args->haystack[i];
}
-static void test_binsearch(void)
+int cmd_main(int argc UNUSED, const char *argv[] UNUSED)
{
- int haystack[] = { 2, 4, 6, 8, 10 };
- struct {
- int needle;
- size_t expected_idx;
- } testcases[] = {
- {-9000, 0},
- {-1, 0},
- {0, 0},
- {2, 0},
- {3, 1},
- {4, 1},
- {7, 3},
- {9, 4},
- {10, 4},
- {11, 5},
- {9000, 5},
- };
-
- for (size_t i = 0; i < ARRAY_SIZE(testcases); i++) {
- struct integer_needle_lesseq_args args = {
- .haystack = haystack,
- .needle = testcases[i].needle,
+ if_test ("binary search with binsearch works") {
+ int haystack[] = { 2, 4, 6, 8, 10 };
+ struct {
+ int needle;
+ size_t expected_idx;
+ } testcases[] = {
+ {-9000, 0},
+ {-1, 0},
+ {0, 0},
+ {2, 0},
+ {3, 1},
+ {4, 1},
+ {7, 3},
+ {9, 4},
+ {10, 4},
+ {11, 5},
+ {9000, 5},
};
- size_t idx;
- idx = binsearch(ARRAY_SIZE(haystack), &integer_needle_lesseq, &args);
- check_int(idx, ==, testcases[i].expected_idx);
+ for (size_t i = 0; i < ARRAY_SIZE(testcases); i++) {
+ struct integer_needle_lesseq_args args = {
+ .haystack = haystack,
+ .needle = testcases[i].needle,
+ };
+ size_t idx;
+
+ idx = binsearch(ARRAY_SIZE(haystack),
+ &integer_needle_lesseq, &args);
+ check_int(idx, ==, testcases[i].expected_idx);
+ }
}
-}
-
-static void test_names_length(void)
-{
- const char *a[] = { "a", "b", NULL };
- check_int(names_length(a), ==, 2);
-}
-static void test_names_equal(void)
-{
- const char *a[] = { "a", "b", "c", NULL };
- const char *b[] = { "a", "b", "d", NULL };
- const char *c[] = { "a", "b", NULL };
+ if_test ("names_length retuns size of a NULL-terminated string array") {
+ const char *a[] = { "a", "b", NULL };
+ check_int(names_length(a), ==, 2);
+ }
- check(names_equal(a, a));
- check(!names_equal(a, b));
- check(!names_equal(a, c));
-}
+ if_test ("names_equal compares NULL-terminated string arrays") {
+ const char *a[] = { "a", "b", "c", NULL };
+ const char *b[] = { "a", "b", "d", NULL };
+ const char *c[] = { "a", "b", NULL };
-static void test_parse_names_normal(void)
-{
- char in1[] = "line\n";
- char in2[] = "a\nb\nc";
- char **out = NULL;
- parse_names(in1, strlen(in1), &out);
- check_str(out[0], "line");
- check(!out[1]);
- free_names(out);
-
- parse_names(in2, strlen(in2), &out);
- check_str(out[0], "a");
- check_str(out[1], "b");
- check_str(out[2], "c");
- check(!out[3]);
- free_names(out);
-}
+ check(names_equal(a, a));
+ check(!names_equal(a, b));
+ check(!names_equal(a, c));
+ }
-static void test_parse_names_drop_empty(void)
-{
- char in[] = "a\n\nb\n";
- char **out = NULL;
- parse_names(in, strlen(in), &out);
- check_str(out[0], "a");
- /* simply '\n' should be dropped as empty string */
- check_str(out[1], "b");
- check(!out[2]);
- free_names(out);
-}
+ if_test ("parse_names works for basic input") {
+ char in1[] = "line\n";
+ char in2[] = "a\nb\nc";
+ char **out = NULL;
+ parse_names(in1, strlen(in1), &out);
+ check_str(out[0], "line");
+ check(!out[1]);
+ free_names(out);
+
+ parse_names(in2, strlen(in2), &out);
+ check_str(out[0], "a");
+ check_str(out[1], "b");
+ check_str(out[2], "c");
+ check(!out[3]);
+ free_names(out);
+ }
-static void test_common_prefix(void)
-{
- struct strbuf a = STRBUF_INIT;
- struct strbuf b = STRBUF_INIT;
- struct {
- const char *a, *b;
- int want;
- } cases[] = {
- {"abcdef", "abc", 3},
- { "abc", "ab", 2 },
- { "", "abc", 0 },
- { "abc", "abd", 2 },
- { "abc", "pqr", 0 },
- };
-
- for (size_t i = 0; i < ARRAY_SIZE(cases); i++) {
- strbuf_addstr(&a, cases[i].a);
- strbuf_addstr(&b, cases[i].b);
- check_int(common_prefix_size(&a, &b), ==, cases[i].want);
- strbuf_reset(&a);
- strbuf_reset(&b);
+ if_test ("parse_names drops empty string") {
+ char in[] = "a\n\nb\n";
+ char **out = NULL;
+ parse_names(in, strlen(in), &out);
+ check_str(out[0], "a");
+ /* simply '\n' should be dropped as empty string */
+ check_str(out[1], "b");
+ check(!out[2]);
+ free_names(out);
}
- strbuf_release(&a);
- strbuf_release(&b);
-}
-static void test_u24_roundtrip(void)
-{
- uint32_t in = 0x112233;
- uint8_t dest[3];
- uint32_t out;
- put_be24(dest, in);
- out = get_be24(dest);
- check_int(in, ==, out);
-}
+ if_test ("common_prefix_size works") {
+ struct strbuf a = STRBUF_INIT;
+ struct strbuf b = STRBUF_INIT;
+ struct {
+ const char *a, *b;
+ int want;
+ } cases[] = {
+ {"abcdef", "abc", 3},
+ { "abc", "ab", 2 },
+ { "", "abc", 0 },
+ { "abc", "abd", 2 },
+ { "abc", "pqr", 0 },
+ };
-static void test_u16_roundtrip(void)
-{
- uint32_t in = 0xfef1;
- uint8_t dest[3];
- uint32_t out;
- put_be16(dest, in);
- out = get_be16(dest);
- check_int(in, ==, out);
-}
+ for (size_t i = 0; i < ARRAY_SIZE(cases); i++) {
+ strbuf_addstr(&a, cases[i].a);
+ strbuf_addstr(&b, cases[i].b);
+ check_int(common_prefix_size(&a, &b), ==, cases[i].want);
+ strbuf_reset(&a);
+ strbuf_reset(&b);
+ }
+ strbuf_release(&a);
+ strbuf_release(&b);
+ }
-int cmd_main(int argc, const char *argv[])
-{
- TEST(test_common_prefix(), "common_prefix_size works");
- TEST(test_parse_names_normal(), "parse_names works for basic input");
- TEST(test_parse_names_drop_empty(), "parse_names drops empty string");
- TEST(test_binsearch(), "binary search with binsearch works");
- TEST(test_names_length(), "names_length retuns size of a NULL-terminated string array");
- TEST(test_names_equal(), "names_equal compares NULL-terminated string arrays");
- TEST(test_u24_roundtrip(), "put_be24 and get_be24 work");
- TEST(test_u16_roundtrip(), "put_be16 and get_be16 work");
+ if_test ("put_be24 and get_be24 work") {
+ uint32_t in = 0x112233;
+ uint8_t dest[3];
+ uint32_t out;
+ put_be24(dest, in);
+ out = get_be24(dest);
+ check_int(in, ==, out);
+ }
+
+ if_test ("put_be16 and get_be16 work") {
+ uint32_t in = 0xfef1;
+ uint8_t dest[3];
+ uint32_t out;
+ put_be16(dest, in);
+ out = get_be16(dest);
+ check_int(in, ==, out);
+ }
return test_done();
}
diff --git a/t/unit-tests/t-reftable-block.c b/t/unit-tests/t-reftable-block.c
new file mode 100644
index 0000000000..f1a49485e2
--- /dev/null
+++ b/t/unit-tests/t-reftable-block.c
@@ -0,0 +1,371 @@
+/*
+Copyright 2020 Google LLC
+
+Use of this source code is governed by a BSD-style
+license that can be found in the LICENSE file or at
+https://developers.google.com/open-source/licenses/bsd
+*/
+
+#include "test-lib.h"
+#include "reftable/block.h"
+#include "reftable/blocksource.h"
+#include "reftable/constants.h"
+#include "reftable/reftable-error.h"
+
+static void t_ref_block_read_write(void)
+{
+ const int header_off = 21; /* random */
+ struct reftable_record recs[30];
+ const size_t N = ARRAY_SIZE(recs);
+ const size_t block_size = 1024;
+ struct reftable_block block = { 0 };
+ struct block_writer bw = {
+ .last_key = STRBUF_INIT,
+ };
+ struct reftable_record rec = {
+ .type = BLOCK_TYPE_REF,
+ };
+ size_t i = 0;
+ int ret;
+ struct block_reader br = { 0 };
+ struct block_iter it = BLOCK_ITER_INIT;
+ struct strbuf want = STRBUF_INIT, buf = STRBUF_INIT;
+
+ REFTABLE_CALLOC_ARRAY(block.data, block_size);
+ block.len = block_size;
+ block_source_from_strbuf(&block.source ,&buf);
+ block_writer_init(&bw, BLOCK_TYPE_REF, block.data, block_size,
+ header_off, hash_size(GIT_SHA1_FORMAT_ID));
+
+ rec.u.ref.refname = (char *) "";
+ rec.u.ref.value_type = REFTABLE_REF_DELETION;
+ ret = block_writer_add(&bw, &rec);
+ check_int(ret, ==, REFTABLE_API_ERROR);
+
+ for (i = 0; i < N; i++) {
+ rec.u.ref.refname = xstrfmt("branch%02"PRIuMAX, (uintmax_t)i);
+ rec.u.ref.value_type = REFTABLE_REF_VAL1;
+ memset(rec.u.ref.value.val1, i, GIT_SHA1_RAWSZ);
+
+ recs[i] = rec;
+ ret = block_writer_add(&bw, &rec);
+ rec.u.ref.refname = NULL;
+ rec.u.ref.value_type = REFTABLE_REF_DELETION;
+ check_int(ret, ==, 0);
+ }
+
+ ret = block_writer_finish(&bw);
+ check_int(ret, >, 0);
+
+ block_writer_release(&bw);
+
+ block_reader_init(&br, &block, header_off, block_size, GIT_SHA1_RAWSZ);
+
+ block_iter_seek_start(&it, &br);
+
+ for (i = 0; ; i++) {
+ ret = block_iter_next(&it, &rec);
+ check_int(ret, >=, 0);
+ if (ret > 0) {
+ check_int(i, ==, N);
+ break;
+ }
+ check(reftable_record_equal(&recs[i], &rec, GIT_SHA1_RAWSZ));
+ }
+
+ for (i = 0; i < N; i++) {
+ block_iter_reset(&it);
+ reftable_record_key(&recs[i], &want);
+
+ ret = block_iter_seek_key(&it, &br, &want);
+ check_int(ret, ==, 0);
+
+ ret = block_iter_next(&it, &rec);
+ check_int(ret, ==, 0);
+
+ check(reftable_record_equal(&recs[i], &rec, GIT_SHA1_RAWSZ));
+
+ want.len--;
+ ret = block_iter_seek_key(&it, &br, &want);
+ check_int(ret, ==, 0);
+
+ ret = block_iter_next(&it, &rec);
+ check_int(ret, ==, 0);
+ check(reftable_record_equal(&recs[10 * (i / 10)], &rec, GIT_SHA1_RAWSZ));
+ }
+
+ block_reader_release(&br);
+ block_iter_close(&it);
+ reftable_record_release(&rec);
+ reftable_block_done(&br.block);
+ strbuf_release(&want);
+ strbuf_release(&buf);
+ for (i = 0; i < N; i++)
+ reftable_record_release(&recs[i]);
+}
+
+static void t_log_block_read_write(void)
+{
+ const int header_off = 21;
+ struct reftable_record recs[30];
+ const size_t N = ARRAY_SIZE(recs);
+ const size_t block_size = 2048;
+ struct reftable_block block = { 0 };
+ struct block_writer bw = {
+ .last_key = STRBUF_INIT,
+ };
+ struct reftable_record rec = {
+ .type = BLOCK_TYPE_LOG,
+ };
+ size_t i = 0;
+ int ret;
+ struct block_reader br = { 0 };
+ struct block_iter it = BLOCK_ITER_INIT;
+ struct strbuf want = STRBUF_INIT, buf = STRBUF_INIT;
+
+ REFTABLE_CALLOC_ARRAY(block.data, block_size);
+ block.len = block_size;
+ block_source_from_strbuf(&block.source ,&buf);
+ block_writer_init(&bw, BLOCK_TYPE_LOG, block.data, block_size,
+ header_off, hash_size(GIT_SHA1_FORMAT_ID));
+
+ for (i = 0; i < N; i++) {
+ rec.u.log.refname = xstrfmt("branch%02"PRIuMAX , (uintmax_t)i);
+ rec.u.log.update_index = i;
+ rec.u.log.value_type = REFTABLE_LOG_UPDATE;
+
+ recs[i] = rec;
+ ret = block_writer_add(&bw, &rec);
+ rec.u.log.refname = NULL;
+ rec.u.log.value_type = REFTABLE_LOG_DELETION;
+ check_int(ret, ==, 0);
+ }
+
+ ret = block_writer_finish(&bw);
+ check_int(ret, >, 0);
+
+ block_writer_release(&bw);
+
+ block_reader_init(&br, &block, header_off, block_size, GIT_SHA1_RAWSZ);
+
+ block_iter_seek_start(&it, &br);
+
+ for (i = 0; ; i++) {
+ ret = block_iter_next(&it, &rec);
+ check_int(ret, >=, 0);
+ if (ret > 0) {
+ check_int(i, ==, N);
+ break;
+ }
+ check(reftable_record_equal(&recs[i], &rec, GIT_SHA1_RAWSZ));
+ }
+
+ for (i = 0; i < N; i++) {
+ block_iter_reset(&it);
+ strbuf_reset(&want);
+ strbuf_addstr(&want, recs[i].u.log.refname);
+
+ ret = block_iter_seek_key(&it, &br, &want);
+ check_int(ret, ==, 0);
+
+ ret = block_iter_next(&it, &rec);
+ check_int(ret, ==, 0);
+
+ check(reftable_record_equal(&recs[i], &rec, GIT_SHA1_RAWSZ));
+
+ want.len--;
+ ret = block_iter_seek_key(&it, &br, &want);
+ check_int(ret, ==, 0);
+
+ ret = block_iter_next(&it, &rec);
+ check_int(ret, ==, 0);
+ check(reftable_record_equal(&recs[10 * (i / 10)], &rec, GIT_SHA1_RAWSZ));
+ }
+
+ block_reader_release(&br);
+ block_iter_close(&it);
+ reftable_record_release(&rec);
+ reftable_block_done(&br.block);
+ strbuf_release(&want);
+ strbuf_release(&buf);
+ for (i = 0; i < N; i++)
+ reftable_record_release(&recs[i]);
+}
+
+static void t_obj_block_read_write(void)
+{
+ const int header_off = 21;
+ struct reftable_record recs[30];
+ const size_t N = ARRAY_SIZE(recs);
+ const size_t block_size = 1024;
+ struct reftable_block block = { 0 };
+ struct block_writer bw = {
+ .last_key = STRBUF_INIT,
+ };
+ struct reftable_record rec = {
+ .type = BLOCK_TYPE_OBJ,
+ };
+ size_t i = 0;
+ int ret;
+ struct block_reader br = { 0 };
+ struct block_iter it = BLOCK_ITER_INIT;
+ struct strbuf want = STRBUF_INIT, buf = STRBUF_INIT;
+
+ REFTABLE_CALLOC_ARRAY(block.data, block_size);
+ block.len = block_size;
+ block_source_from_strbuf(&block.source, &buf);
+ block_writer_init(&bw, BLOCK_TYPE_OBJ, block.data, block_size,
+ header_off, hash_size(GIT_SHA1_FORMAT_ID));
+
+ for (i = 0; i < N; i++) {
+ uint8_t bytes[] = { i, i + 1, i + 2, i + 3, i + 5 }, *allocated;
+ DUP_ARRAY(allocated, bytes, ARRAY_SIZE(bytes));
+
+ rec.u.obj.hash_prefix = allocated;
+ rec.u.obj.hash_prefix_len = 5;
+
+ recs[i] = rec;
+ ret = block_writer_add(&bw, &rec);
+ rec.u.obj.hash_prefix = NULL;
+ rec.u.obj.hash_prefix_len = 0;
+ check_int(ret, ==, 0);
+ }
+
+ ret = block_writer_finish(&bw);
+ check_int(ret, >, 0);
+
+ block_writer_release(&bw);
+
+ block_reader_init(&br, &block, header_off, block_size, GIT_SHA1_RAWSZ);
+
+ block_iter_seek_start(&it, &br);
+
+ for (i = 0; ; i++) {
+ ret = block_iter_next(&it, &rec);
+ check_int(ret, >=, 0);
+ if (ret > 0) {
+ check_int(i, ==, N);
+ break;
+ }
+ check(reftable_record_equal(&recs[i], &rec, GIT_SHA1_RAWSZ));
+ }
+
+ for (i = 0; i < N; i++) {
+ block_iter_reset(&it);
+ reftable_record_key(&recs[i], &want);
+
+ ret = block_iter_seek_key(&it, &br, &want);
+ check_int(ret, ==, 0);
+
+ ret = block_iter_next(&it, &rec);
+ check_int(ret, ==, 0);
+
+ check(reftable_record_equal(&recs[i], &rec, GIT_SHA1_RAWSZ));
+ }
+
+ block_reader_release(&br);
+ block_iter_close(&it);
+ reftable_record_release(&rec);
+ reftable_block_done(&br.block);
+ strbuf_release(&want);
+ strbuf_release(&buf);
+ for (i = 0; i < N; i++)
+ reftable_record_release(&recs[i]);
+}
+
+static void t_index_block_read_write(void)
+{
+ const int header_off = 21;
+ struct reftable_record recs[30];
+ const size_t N = ARRAY_SIZE(recs);
+ const size_t block_size = 1024;
+ struct reftable_block block = { 0 };
+ struct block_writer bw = {
+ .last_key = STRBUF_INIT,
+ };
+ struct reftable_record rec = {
+ .type = BLOCK_TYPE_INDEX,
+ .u.idx.last_key = STRBUF_INIT,
+ };
+ size_t i = 0;
+ int ret;
+ struct block_reader br = { 0 };
+ struct block_iter it = BLOCK_ITER_INIT;
+ struct strbuf want = STRBUF_INIT, buf = STRBUF_INIT;
+
+ REFTABLE_CALLOC_ARRAY(block.data, block_size);
+ block.len = block_size;
+ block_source_from_strbuf(&block.source, &buf);
+ block_writer_init(&bw, BLOCK_TYPE_INDEX, block.data, block_size,
+ header_off, hash_size(GIT_SHA1_FORMAT_ID));
+
+ for (i = 0; i < N; i++) {
+ strbuf_init(&recs[i].u.idx.last_key, 9);
+
+ recs[i].type = BLOCK_TYPE_INDEX;
+ strbuf_addf(&recs[i].u.idx.last_key, "branch%02"PRIuMAX, (uintmax_t)i);
+ recs[i].u.idx.offset = i;
+
+ ret = block_writer_add(&bw, &recs[i]);
+ check_int(ret, ==, 0);
+ }
+
+ ret = block_writer_finish(&bw);
+ check_int(ret, >, 0);
+
+ block_writer_release(&bw);
+
+ block_reader_init(&br, &block, header_off, block_size, GIT_SHA1_RAWSZ);
+
+ block_iter_seek_start(&it, &br);
+
+ for (i = 0; ; i++) {
+ ret = block_iter_next(&it, &rec);
+ check_int(ret, >=, 0);
+ if (ret > 0) {
+ check_int(i, ==, N);
+ break;
+ }
+ check(reftable_record_equal(&recs[i], &rec, GIT_SHA1_RAWSZ));
+ }
+
+ for (i = 0; i < N; i++) {
+ block_iter_reset(&it);
+ reftable_record_key(&recs[i], &want);
+
+ ret = block_iter_seek_key(&it, &br, &want);
+ check_int(ret, ==, 0);
+
+ ret = block_iter_next(&it, &rec);
+ check_int(ret, ==, 0);
+
+ check(reftable_record_equal(&recs[i], &rec, GIT_SHA1_RAWSZ));
+
+ want.len--;
+ ret = block_iter_seek_key(&it, &br, &want);
+ check_int(ret, ==, 0);
+
+ ret = block_iter_next(&it, &rec);
+ check_int(ret, ==, 0);
+ check(reftable_record_equal(&recs[10 * (i / 10)], &rec, GIT_SHA1_RAWSZ));
+ }
+
+ block_reader_release(&br);
+ block_iter_close(&it);
+ reftable_record_release(&rec);
+ reftable_block_done(&br.block);
+ strbuf_release(&want);
+ strbuf_release(&buf);
+ for (i = 0; i < N; i++)
+ reftable_record_release(&recs[i]);
+}
+
+int cmd_main(int argc UNUSED, const char *argv[] UNUSED)
+{
+ TEST(t_index_block_read_write(), "read-write operations on index blocks work");
+ TEST(t_log_block_read_write(), "read-write operations on log blocks work");
+ TEST(t_obj_block_read_write(), "read-write operations on obj blocks work");
+ TEST(t_ref_block_read_write(), "read-write operations on ref blocks work");
+
+ return test_done();
+}
diff --git a/t/unit-tests/t-reftable-merged.c b/t/unit-tests/t-reftable-merged.c
new file mode 100644
index 0000000000..19e54bdfb8
--- /dev/null
+++ b/t/unit-tests/t-reftable-merged.c
@@ -0,0 +1,465 @@
+/*
+Copyright 2020 Google LLC
+
+Use of this source code is governed by a BSD-style
+license that can be found in the LICENSE file or at
+https://developers.google.com/open-source/licenses/bsd
+*/
+
+#include "test-lib.h"
+#include "lib-reftable.h"
+#include "reftable/blocksource.h"
+#include "reftable/constants.h"
+#include "reftable/merged.h"
+#include "reftable/reader.h"
+#include "reftable/reftable-error.h"
+#include "reftable/reftable-merged.h"
+#include "reftable/reftable-writer.h"
+
+static struct reftable_merged_table *
+merged_table_from_records(struct reftable_ref_record **refs,
+ struct reftable_block_source **source,
+ struct reftable_reader ***readers, const size_t *sizes,
+ struct strbuf *buf, const size_t n)
+{
+ struct reftable_merged_table *mt = NULL;
+ struct reftable_write_options opts = {
+ .block_size = 256,
+ };
+ int err;
+
+ REFTABLE_CALLOC_ARRAY(*readers, n);
+ REFTABLE_CALLOC_ARRAY(*source, n);
+
+ for (size_t i = 0; i < n; i++) {
+ t_reftable_write_to_buf(&buf[i], refs[i], sizes[i], NULL, 0, &opts);
+ block_source_from_strbuf(&(*source)[i], &buf[i]);
+
+ err = reftable_reader_new(&(*readers)[i], &(*source)[i],
+ "name");
+ check(!err);
+ }
+
+ err = reftable_merged_table_new(&mt, *readers, n, GIT_SHA1_FORMAT_ID);
+ check(!err);
+ return mt;
+}
+
+static void readers_destroy(struct reftable_reader **readers, const size_t n)
+{
+ for (size_t i = 0; i < n; i++)
+ reftable_reader_decref(readers[i]);
+ reftable_free(readers);
+}
+
+static void t_merged_single_record(void)
+{
+ struct reftable_ref_record r1[] = { {
+ .refname = (char *) "b",
+ .update_index = 1,
+ .value_type = REFTABLE_REF_VAL1,
+ .value.val1 = { 1, 2, 3, 0 },
+ } };
+ struct reftable_ref_record r2[] = { {
+ .refname = (char *) "a",
+ .update_index = 2,
+ .value_type = REFTABLE_REF_DELETION,
+ } };
+ struct reftable_ref_record r3[] = { {
+ .refname = (char *) "c",
+ .update_index = 3,
+ .value_type = REFTABLE_REF_DELETION,
+ } };
+
+ struct reftable_ref_record *refs[] = { r1, r2, r3 };
+ size_t sizes[] = { ARRAY_SIZE(r1), ARRAY_SIZE(r2), ARRAY_SIZE(r3) };
+ struct strbuf bufs[3] = { STRBUF_INIT, STRBUF_INIT, STRBUF_INIT };
+ struct reftable_block_source *bs = NULL;
+ struct reftable_reader **readers = NULL;
+ struct reftable_merged_table *mt =
+ merged_table_from_records(refs, &bs, &readers, sizes, bufs, 3);
+ struct reftable_ref_record ref = { 0 };
+ struct reftable_iterator it = { 0 };
+ int err;
+
+ merged_table_init_iter(mt, &it, BLOCK_TYPE_REF);
+ err = reftable_iterator_seek_ref(&it, "a");
+ check(!err);
+
+ err = reftable_iterator_next_ref(&it, &ref);
+ check(!err);
+ check(reftable_ref_record_equal(&r2[0], &ref, GIT_SHA1_RAWSZ));
+ reftable_ref_record_release(&ref);
+ reftable_iterator_destroy(&it);
+ readers_destroy(readers, 3);
+ reftable_merged_table_free(mt);
+ for (size_t i = 0; i < ARRAY_SIZE(bufs); i++)
+ strbuf_release(&bufs[i]);
+ reftable_free(bs);
+}
+
+static void t_merged_refs(void)
+{
+ struct reftable_ref_record r1[] = {
+ {
+ .refname = (char *) "a",
+ .update_index = 1,
+ .value_type = REFTABLE_REF_VAL1,
+ .value.val1 = { 1 },
+ },
+ {
+ .refname = (char *) "b",
+ .update_index = 1,
+ .value_type = REFTABLE_REF_VAL1,
+ .value.val1 = { 1 },
+ },
+ {
+ .refname = (char *) "c",
+ .update_index = 1,
+ .value_type = REFTABLE_REF_VAL1,
+ .value.val1 = { 1 },
+ }
+ };
+ struct reftable_ref_record r2[] = { {
+ .refname = (char *) "a",
+ .update_index = 2,
+ .value_type = REFTABLE_REF_DELETION,
+ } };
+ struct reftable_ref_record r3[] = {
+ {
+ .refname = (char *) "c",
+ .update_index = 3,
+ .value_type = REFTABLE_REF_VAL1,
+ .value.val1 = { 2 },
+ },
+ {
+ .refname = (char *) "d",
+ .update_index = 3,
+ .value_type = REFTABLE_REF_VAL1,
+ .value.val1 = { 1 },
+ },
+ };
+
+ struct reftable_ref_record *want[] = {
+ &r2[0],
+ &r1[1],
+ &r3[0],
+ &r3[1],
+ };
+
+ struct reftable_ref_record *refs[] = { r1, r2, r3 };
+ size_t sizes[3] = { ARRAY_SIZE(r1), ARRAY_SIZE(r2), ARRAY_SIZE(r3) };
+ struct strbuf bufs[3] = { STRBUF_INIT, STRBUF_INIT, STRBUF_INIT };
+ struct reftable_block_source *bs = NULL;
+ struct reftable_reader **readers = NULL;
+ struct reftable_merged_table *mt =
+ merged_table_from_records(refs, &bs, &readers, sizes, bufs, 3);
+ struct reftable_iterator it = { 0 };
+ int err;
+ struct reftable_ref_record *out = NULL;
+ size_t len = 0;
+ size_t cap = 0;
+ size_t i;
+
+ merged_table_init_iter(mt, &it, BLOCK_TYPE_REF);
+ err = reftable_iterator_seek_ref(&it, "a");
+ check(!err);
+ check_int(reftable_merged_table_hash_id(mt), ==, GIT_SHA1_FORMAT_ID);
+ check_int(reftable_merged_table_min_update_index(mt), ==, 1);
+ check_int(reftable_merged_table_max_update_index(mt), ==, 3);
+
+ while (len < 100) { /* cap loops/recursion. */
+ struct reftable_ref_record ref = { 0 };
+ int err = reftable_iterator_next_ref(&it, &ref);
+ if (err > 0)
+ break;
+
+ REFTABLE_ALLOC_GROW(out, len + 1, cap);
+ out[len++] = ref;
+ }
+ reftable_iterator_destroy(&it);
+
+ check_int(ARRAY_SIZE(want), ==, len);
+ for (i = 0; i < len; i++)
+ check(reftable_ref_record_equal(want[i], &out[i],
+ GIT_SHA1_RAWSZ));
+ for (i = 0; i < len; i++)
+ reftable_ref_record_release(&out[i]);
+ reftable_free(out);
+
+ for (i = 0; i < 3; i++)
+ strbuf_release(&bufs[i]);
+ readers_destroy(readers, 3);
+ reftable_merged_table_free(mt);
+ reftable_free(bs);
+}
+
+static void t_merged_seek_multiple_times(void)
+{
+ struct reftable_ref_record r1[] = {
+ {
+ .refname = (char *) "a",
+ .update_index = 1,
+ .value_type = REFTABLE_REF_VAL1,
+ .value.val1 = { 1 },
+ },
+ {
+ .refname = (char *) "c",
+ .update_index = 1,
+ .value_type = REFTABLE_REF_VAL1,
+ .value.val1 = { 2 },
+ }
+ };
+ struct reftable_ref_record r2[] = {
+ {
+ .refname = (char *) "b",
+ .update_index = 2,
+ .value_type = REFTABLE_REF_VAL1,
+ .value.val1 = { 3 },
+ },
+ {
+ .refname = (char *) "d",
+ .update_index = 2,
+ .value_type = REFTABLE_REF_VAL1,
+ .value.val1 = { 4 },
+ },
+ };
+ struct reftable_ref_record *refs[] = {
+ r1, r2,
+ };
+ size_t sizes[] = {
+ ARRAY_SIZE(r1), ARRAY_SIZE(r2),
+ };
+ struct strbuf bufs[] = {
+ STRBUF_INIT, STRBUF_INIT,
+ };
+ struct reftable_block_source *sources = NULL;
+ struct reftable_reader **readers = NULL;
+ struct reftable_ref_record rec = { 0 };
+ struct reftable_iterator it = { 0 };
+ struct reftable_merged_table *mt;
+
+ mt = merged_table_from_records(refs, &sources, &readers, sizes, bufs, 2);
+ merged_table_init_iter(mt, &it, BLOCK_TYPE_REF);
+
+ for (size_t i = 0; i < 5; i++) {
+ int err = reftable_iterator_seek_ref(&it, "c");
+ check(!err);
+
+ err = reftable_iterator_next_ref(&it, &rec);
+ check(!err);
+ err = reftable_ref_record_equal(&rec, &r1[1], GIT_SHA1_RAWSZ);
+ check(err == 1);
+
+ err = reftable_iterator_next_ref(&it, &rec);
+ check(!err);
+ err = reftable_ref_record_equal(&rec, &r2[1], GIT_SHA1_RAWSZ);
+ check(err == 1);
+
+ err = reftable_iterator_next_ref(&it, &rec);
+ check(err > 0);
+ }
+
+ for (size_t i = 0; i < ARRAY_SIZE(bufs); i++)
+ strbuf_release(&bufs[i]);
+ readers_destroy(readers, ARRAY_SIZE(refs));
+ reftable_ref_record_release(&rec);
+ reftable_iterator_destroy(&it);
+ reftable_merged_table_free(mt);
+ reftable_free(sources);
+}
+
+static struct reftable_merged_table *
+merged_table_from_log_records(struct reftable_log_record **logs,
+ struct reftable_block_source **source,
+ struct reftable_reader ***readers, const size_t *sizes,
+ struct strbuf *buf, const size_t n)
+{
+ struct reftable_merged_table *mt = NULL;
+ struct reftable_write_options opts = {
+ .block_size = 256,
+ .exact_log_message = 1,
+ };
+ int err;
+
+ REFTABLE_CALLOC_ARRAY(*readers, n);
+ REFTABLE_CALLOC_ARRAY(*source, n);
+
+ for (size_t i = 0; i < n; i++) {
+ t_reftable_write_to_buf(&buf[i], NULL, 0, logs[i], sizes[i], &opts);
+ block_source_from_strbuf(&(*source)[i], &buf[i]);
+
+ err = reftable_reader_new(&(*readers)[i], &(*source)[i],
+ "name");
+ check(!err);
+ }
+
+ err = reftable_merged_table_new(&mt, *readers, n, GIT_SHA1_FORMAT_ID);
+ check(!err);
+ return mt;
+}
+
+static void t_merged_logs(void)
+{
+ struct reftable_log_record r1[] = {
+ {
+ .refname = (char *) "a",
+ .update_index = 2,
+ .value_type = REFTABLE_LOG_UPDATE,
+ .value.update = {
+ .old_hash = { 2 },
+ /* deletion */
+ .name = (char *) "jane doe",
+ .email = (char *) "jane@invalid",
+ .message = (char *) "message2",
+ }
+ },
+ {
+ .refname = (char *) "a",
+ .update_index = 1,
+ .value_type = REFTABLE_LOG_UPDATE,
+ .value.update = {
+ .old_hash = { 1 },
+ .new_hash = { 2 },
+ .name = (char *) "jane doe",
+ .email = (char *) "jane@invalid",
+ .message = (char *) "message1",
+ }
+ },
+ };
+ struct reftable_log_record r2[] = {
+ {
+ .refname = (char *) "a",
+ .update_index = 3,
+ .value_type = REFTABLE_LOG_UPDATE,
+ .value.update = {
+ .new_hash = { 3 },
+ .name = (char *) "jane doe",
+ .email = (char *) "jane@invalid",
+ .message = (char *) "message3",
+ }
+ },
+ };
+ struct reftable_log_record r3[] = {
+ {
+ .refname = (char *) "a",
+ .update_index = 2,
+ .value_type = REFTABLE_LOG_DELETION,
+ },
+ };
+ struct reftable_log_record *want[] = {
+ &r2[0],
+ &r3[0],
+ &r1[1],
+ };
+
+ struct reftable_log_record *logs[] = { r1, r2, r3 };
+ size_t sizes[3] = { ARRAY_SIZE(r1), ARRAY_SIZE(r2), ARRAY_SIZE(r3) };
+ struct strbuf bufs[3] = { STRBUF_INIT, STRBUF_INIT, STRBUF_INIT };
+ struct reftable_block_source *bs = NULL;
+ struct reftable_reader **readers = NULL;
+ struct reftable_merged_table *mt = merged_table_from_log_records(
+ logs, &bs, &readers, sizes, bufs, 3);
+ struct reftable_iterator it = { 0 };
+ int err;
+ struct reftable_log_record *out = NULL;
+ size_t len = 0;
+ size_t cap = 0;
+ size_t i;
+
+ merged_table_init_iter(mt, &it, BLOCK_TYPE_LOG);
+ err = reftable_iterator_seek_log(&it, "a");
+ check(!err);
+ check_int(reftable_merged_table_hash_id(mt), ==, GIT_SHA1_FORMAT_ID);
+ check_int(reftable_merged_table_min_update_index(mt), ==, 1);
+ check_int(reftable_merged_table_max_update_index(mt), ==, 3);
+
+ while (len < 100) { /* cap loops/recursion. */
+ struct reftable_log_record log = { 0 };
+ int err = reftable_iterator_next_log(&it, &log);
+ if (err > 0)
+ break;
+
+ REFTABLE_ALLOC_GROW(out, len + 1, cap);
+ out[len++] = log;
+ }
+ reftable_iterator_destroy(&it);
+
+ check_int(ARRAY_SIZE(want), ==, len);
+ for (i = 0; i < len; i++)
+ check(reftable_log_record_equal(want[i], &out[i],
+ GIT_SHA1_RAWSZ));
+
+ merged_table_init_iter(mt, &it, BLOCK_TYPE_LOG);
+ err = reftable_iterator_seek_log_at(&it, "a", 2);
+ check(!err);
+ reftable_log_record_release(&out[0]);
+ err = reftable_iterator_next_log(&it, &out[0]);
+ check(!err);
+ check(reftable_log_record_equal(&out[0], &r3[0], GIT_SHA1_RAWSZ));
+ reftable_iterator_destroy(&it);
+
+ for (i = 0; i < len; i++)
+ reftable_log_record_release(&out[i]);
+ reftable_free(out);
+
+ for (i = 0; i < 3; i++)
+ strbuf_release(&bufs[i]);
+ readers_destroy(readers, 3);
+ reftable_merged_table_free(mt);
+ reftable_free(bs);
+}
+
+static void t_default_write_opts(void)
+{
+ struct reftable_write_options opts = { 0 };
+ struct strbuf buf = STRBUF_INIT;
+ struct reftable_writer *w = t_reftable_strbuf_writer(&buf, &opts);
+ struct reftable_ref_record rec = {
+ .refname = (char *) "master",
+ .update_index = 1,
+ };
+ int err;
+ struct reftable_block_source source = { 0 };
+ uint32_t hash_id;
+ struct reftable_reader *rd = NULL;
+ struct reftable_merged_table *merged = NULL;
+
+ reftable_writer_set_limits(w, 1, 1);
+
+ err = reftable_writer_add_ref(w, &rec);
+ check(!err);
+
+ err = reftable_writer_close(w);
+ check(!err);
+ reftable_writer_free(w);
+
+ block_source_from_strbuf(&source, &buf);
+
+ err = reftable_reader_new(&rd, &source, "filename");
+ check(!err);
+
+ hash_id = reftable_reader_hash_id(rd);
+ check_int(hash_id, ==, GIT_SHA1_FORMAT_ID);
+
+ err = reftable_merged_table_new(&merged, &rd, 1, GIT_SHA256_FORMAT_ID);
+ check_int(err, ==, REFTABLE_FORMAT_ERROR);
+ err = reftable_merged_table_new(&merged, &rd, 1, GIT_SHA1_FORMAT_ID);
+ check(!err);
+
+ reftable_reader_decref(rd);
+ reftable_merged_table_free(merged);
+ strbuf_release(&buf);
+}
+
+
+int cmd_main(int argc UNUSED, const char *argv[] UNUSED)
+{
+ TEST(t_default_write_opts(), "merged table with default write opts");
+ TEST(t_merged_logs(), "merged table with multiple log updates for same ref");
+ TEST(t_merged_refs(), "merged table with multiple updates to same ref");
+ TEST(t_merged_seek_multiple_times(), "merged table can seek multiple times");
+ TEST(t_merged_single_record(), "ref ocurring in only one record can be fetched");
+
+ return test_done();
+}
diff --git a/t/unit-tests/t-reftable-pq.c b/t/unit-tests/t-reftable-pq.c
new file mode 100644
index 0000000000..ada4c19f18
--- /dev/null
+++ b/t/unit-tests/t-reftable-pq.c
@@ -0,0 +1,152 @@
+/*
+Copyright 2020 Google LLC
+
+Use of this source code is governed by a BSD-style
+license that can be found in the LICENSE file or at
+https://developers.google.com/open-source/licenses/bsd
+*/
+
+#include "test-lib.h"
+#include "reftable/constants.h"
+#include "reftable/pq.h"
+
+static void merged_iter_pqueue_check(const struct merged_iter_pqueue *pq)
+{
+ for (size_t i = 1; i < pq->len; i++) {
+ size_t parent = (i - 1) / 2;
+ check(pq_less(&pq->heap[parent], &pq->heap[i]));
+ }
+}
+
+static int pq_entry_equal(struct pq_entry *a, struct pq_entry *b)
+{
+ return !reftable_record_cmp(a->rec, b->rec) && (a->index == b->index);
+}
+
+static void t_pq_record(void)
+{
+ struct merged_iter_pqueue pq = { 0 };
+ struct reftable_record recs[54];
+ size_t N = ARRAY_SIZE(recs) - 1, i;
+ char *last = NULL;
+
+ for (i = 0; i < N; i++) {
+ reftable_record_init(&recs[i], BLOCK_TYPE_REF);
+ recs[i].u.ref.refname = xstrfmt("%02"PRIuMAX, (uintmax_t)i);
+ }
+
+ i = 1;
+ do {
+ struct pq_entry e = {
+ .rec = &recs[i],
+ };
+
+ merged_iter_pqueue_add(&pq, &e);
+ merged_iter_pqueue_check(&pq);
+ i = (i * 7) % N;
+ } while (i != 1);
+
+ while (!merged_iter_pqueue_is_empty(pq)) {
+ struct pq_entry top = merged_iter_pqueue_top(pq);
+ struct pq_entry e = merged_iter_pqueue_remove(&pq);
+ merged_iter_pqueue_check(&pq);
+
+ check(pq_entry_equal(&top, &e));
+ check(reftable_record_type(e.rec) == BLOCK_TYPE_REF);
+ if (last)
+ check_int(strcmp(last, e.rec->u.ref.refname), <, 0);
+ last = e.rec->u.ref.refname;
+ }
+
+ for (i = 0; i < N; i++)
+ reftable_record_release(&recs[i]);
+ merged_iter_pqueue_release(&pq);
+}
+
+static void t_pq_index(void)
+{
+ struct merged_iter_pqueue pq = { 0 };
+ struct reftable_record recs[13];
+ char *last = NULL;
+ size_t N = ARRAY_SIZE(recs), i;
+
+ for (i = 0; i < N; i++) {
+ reftable_record_init(&recs[i], BLOCK_TYPE_REF);
+ recs[i].u.ref.refname = (char *) "refs/heads/master";
+ }
+
+ i = 1;
+ do {
+ struct pq_entry e = {
+ .rec = &recs[i],
+ .index = i,
+ };
+
+ merged_iter_pqueue_add(&pq, &e);
+ merged_iter_pqueue_check(&pq);
+ i = (i * 7) % N;
+ } while (i != 1);
+
+ for (i = N - 1; i > 0; i--) {
+ struct pq_entry top = merged_iter_pqueue_top(pq);
+ struct pq_entry e = merged_iter_pqueue_remove(&pq);
+ merged_iter_pqueue_check(&pq);
+
+ check(pq_entry_equal(&top, &e));
+ check(reftable_record_type(e.rec) == BLOCK_TYPE_REF);
+ check_int(e.index, ==, i);
+ if (last)
+ check_str(last, e.rec->u.ref.refname);
+ last = e.rec->u.ref.refname;
+ }
+
+ merged_iter_pqueue_release(&pq);
+}
+
+static void t_merged_iter_pqueue_top(void)
+{
+ struct merged_iter_pqueue pq = { 0 };
+ struct reftable_record recs[13];
+ size_t N = ARRAY_SIZE(recs), i;
+
+ for (i = 0; i < N; i++) {
+ reftable_record_init(&recs[i], BLOCK_TYPE_REF);
+ recs[i].u.ref.refname = (char *) "refs/heads/master";
+ }
+
+ i = 1;
+ do {
+ struct pq_entry e = {
+ .rec = &recs[i],
+ .index = i,
+ };
+
+ merged_iter_pqueue_add(&pq, &e);
+ merged_iter_pqueue_check(&pq);
+ i = (i * 7) % N;
+ } while (i != 1);
+
+ for (i = N - 1; i > 0; i--) {
+ struct pq_entry top = merged_iter_pqueue_top(pq);
+ struct pq_entry e = merged_iter_pqueue_remove(&pq);
+
+ merged_iter_pqueue_check(&pq);
+ check(pq_entry_equal(&top, &e));
+ check(reftable_record_equal(top.rec, &recs[i], GIT_SHA1_RAWSZ));
+ for (size_t j = 0; i < pq.len; j++) {
+ check(pq_less(&top, &pq.heap[j]));
+ check_int(top.index, >, j);
+ }
+ }
+
+ merged_iter_pqueue_release(&pq);
+}
+
+int cmd_main(int argc UNUSED, const char *argv[] UNUSED)
+{
+ TEST(t_pq_record(), "pq works with record-based comparison");
+ TEST(t_pq_index(), "pq works with index-based comparison");
+ TEST(t_merged_iter_pqueue_top(), "merged_iter_pqueue_top works");
+
+ return test_done();
+}
diff --git a/t/unit-tests/t-reftable-reader.c b/t/unit-tests/t-reftable-reader.c
new file mode 100644
index 0000000000..eea86966c0
--- /dev/null
+++ b/t/unit-tests/t-reftable-reader.c
@@ -0,0 +1,96 @@
+#include "test-lib.h"
+#include "lib-reftable.h"
+#include "reftable/blocksource.h"
+#include "reftable/reader.h"
+
+static int t_reader_seek_once(void)
+{
+ struct reftable_ref_record records[] = {
+ {
+ .refname = (char *) "refs/heads/main",
+ .value_type = REFTABLE_REF_VAL1,
+ .value.val1 = { 42 },
+ },
+ };
+ struct reftable_block_source source = { 0 };
+ struct reftable_ref_record ref = { 0 };
+ struct reftable_iterator it = { 0 };
+ struct reftable_reader *reader;
+ struct strbuf buf = STRBUF_INIT;
+ int ret;
+
+ t_reftable_write_to_buf(&buf, records, ARRAY_SIZE(records), NULL, 0, NULL);
+ block_source_from_strbuf(&source, &buf);
+
+ ret = reftable_reader_new(&reader, &source, "name");
+ check(!ret);
+
+ reftable_reader_init_ref_iterator(reader, &it);
+ ret = reftable_iterator_seek_ref(&it, "");
+ check(!ret);
+ ret = reftable_iterator_next_ref(&it, &ref);
+ check(!ret);
+
+ ret = reftable_ref_record_equal(&ref, &records[0], GIT_SHA1_RAWSZ);
+ check_int(ret, ==, 1);
+
+ ret = reftable_iterator_next_ref(&it, &ref);
+ check_int(ret, ==, 1);
+
+ reftable_ref_record_release(&ref);
+ reftable_iterator_destroy(&it);
+ reftable_reader_decref(reader);
+ strbuf_release(&buf);
+ return 0;
+}
+
+static int t_reader_reseek(void)
+{
+ struct reftable_ref_record records[] = {
+ {
+ .refname = (char *) "refs/heads/main",
+ .value_type = REFTABLE_REF_VAL1,
+ .value.val1 = { 42 },
+ },
+ };
+ struct reftable_block_source source = { 0 };
+ struct reftable_ref_record ref = { 0 };
+ struct reftable_iterator it = { 0 };
+ struct reftable_reader *reader;
+ struct strbuf buf = STRBUF_INIT;
+ int ret;
+
+ t_reftable_write_to_buf(&buf, records, ARRAY_SIZE(records), NULL, 0, NULL);
+ block_source_from_strbuf(&source, &buf);
+
+ ret = reftable_reader_new(&reader, &source, "name");
+ check(!ret);
+
+ reftable_reader_init_ref_iterator(reader, &it);
+
+ for (size_t i = 0; i < 5; i++) {
+ ret = reftable_iterator_seek_ref(&it, "");
+ check(!ret);
+ ret = reftable_iterator_next_ref(&it, &ref);
+ check(!ret);
+
+ ret = reftable_ref_record_equal(&ref, &records[0], GIT_SHA1_RAWSZ);
+ check_int(ret, ==, 1);
+
+ ret = reftable_iterator_next_ref(&it, &ref);
+ check_int(ret, ==, 1);
+ }
+
+ reftable_ref_record_release(&ref);
+ reftable_iterator_destroy(&it);
+ reftable_reader_decref(reader);
+ strbuf_release(&buf);
+ return 0;
+}
+
+int cmd_main(int argc UNUSED, const char *argv[] UNUSED)
+{
+ TEST(t_reader_seek_once(), "reader can seek once");
+ TEST(t_reader_reseek(), "reader can reseek multiple times");
+ return test_done();
+}
diff --git a/t/unit-tests/t-reftable-readwrite.c b/t/unit-tests/t-reftable-readwrite.c
new file mode 100644
index 0000000000..e1b235a5f1
--- /dev/null
+++ b/t/unit-tests/t-reftable-readwrite.c
@@ -0,0 +1,926 @@
+/*
+Copyright 2020 Google LLC
+
+Use of this source code is governed by a BSD-style
+license that can be found in the LICENSE file or at
+https://developers.google.com/open-source/licenses/bsd
+*/
+
+#include "test-lib.h"
+#include "lib-reftable.h"
+#include "reftable/basics.h"
+#include "reftable/blocksource.h"
+#include "reftable/reader.h"
+#include "reftable/reftable-error.h"
+#include "reftable/reftable-writer.h"
+
+static const int update_index = 5;
+
+static void t_buffer(void)
+{
+ struct strbuf buf = STRBUF_INIT;
+ struct reftable_block_source source = { 0 };
+ struct reftable_block out = { 0 };
+ int n;
+ uint8_t in[] = "hello";
+ strbuf_add(&buf, in, sizeof(in));
+ block_source_from_strbuf(&source, &buf);
+ check_int(block_source_size(&source), ==, 6);
+ n = block_source_read_block(&source, &out, 0, sizeof(in));
+ check_int(n, ==, sizeof(in));
+ check(!memcmp(in, out.data, n));
+ reftable_block_done(&out);
+
+ n = block_source_read_block(&source, &out, 1, 2);
+ check_int(n, ==, 2);
+ check(!memcmp(out.data, "el", 2));
+
+ reftable_block_done(&out);
+ block_source_close(&source);
+ strbuf_release(&buf);
+}
+
+static void write_table(char ***names, struct strbuf *buf, int N,
+ int block_size, uint32_t hash_id)
+{
+ struct reftable_write_options opts = {
+ .block_size = block_size,
+ .hash_id = hash_id,
+ };
+ struct reftable_ref_record *refs;
+ struct reftable_log_record *logs;
+ int i;
+
+ REFTABLE_CALLOC_ARRAY(*names, N + 1);
+ REFTABLE_CALLOC_ARRAY(refs, N);
+ REFTABLE_CALLOC_ARRAY(logs, N);
+
+ for (i = 0; i < N; i++) {
+ refs[i].refname = (*names)[i] = xstrfmt("refs/heads/branch%02d", i);
+ refs[i].update_index = update_index;
+ refs[i].value_type = REFTABLE_REF_VAL1;
+ t_reftable_set_hash(refs[i].value.val1, i, GIT_SHA1_FORMAT_ID);
+ }
+
+ for (i = 0; i < N; i++) {
+ logs[i].refname = (*names)[i];
+ logs[i].update_index = update_index;
+ logs[i].value_type = REFTABLE_LOG_UPDATE;
+ t_reftable_set_hash(logs[i].value.update.new_hash, i,
+ GIT_SHA1_FORMAT_ID);
+ logs[i].value.update.message = (char *) "message";
+ }
+
+ t_reftable_write_to_buf(buf, refs, N, logs, N, &opts);
+
+ free(refs);
+ free(logs);
+}
+
+static void t_log_buffer_size(void)
+{
+ struct strbuf buf = STRBUF_INIT;
+ struct reftable_write_options opts = {
+ .block_size = 4096,
+ };
+ int err;
+ int i;
+ struct reftable_log_record
+ log = { .refname = (char *) "refs/heads/master",
+ .update_index = 0xa,
+ .value_type = REFTABLE_LOG_UPDATE,
+ .value = { .update = {
+ .name = (char *) "Han-Wen Nienhuys",
+ .email = (char *) "hanwen@google.com",
+ .tz_offset = 100,
+ .time = 0x5e430672,
+ .message = (char *) "commit: 9\n",
+ } } };
+ struct reftable_writer *w = t_reftable_strbuf_writer(&buf, &opts);
+
+ /* This tests buffer extension for log compression. Must use a random
+ hash, to ensure that the compressed part is larger than the original.
+ */
+ for (i = 0; i < GIT_SHA1_RAWSZ; i++) {
+ log.value.update.old_hash[i] = (uint8_t)(git_rand() % 256);
+ log.value.update.new_hash[i] = (uint8_t)(git_rand() % 256);
+ }
+ reftable_writer_set_limits(w, update_index, update_index);
+ err = reftable_writer_add_log(w, &log);
+ check(!err);
+ err = reftable_writer_close(w);
+ check(!err);
+ reftable_writer_free(w);
+ strbuf_release(&buf);
+}
+
+static void t_log_overflow(void)
+{
+ struct strbuf buf = STRBUF_INIT;
+ char msg[256] = { 0 };
+ struct reftable_write_options opts = {
+ .block_size = ARRAY_SIZE(msg),
+ };
+ int err;
+ struct reftable_log_record log = {
+ .refname = (char *) "refs/heads/master",
+ .update_index = 0xa,
+ .value_type = REFTABLE_LOG_UPDATE,
+ .value = {
+ .update = {
+ .old_hash = { 1 },
+ .new_hash = { 2 },
+ .name = (char *) "Han-Wen Nienhuys",
+ .email = (char *) "hanwen@google.com",
+ .tz_offset = 100,
+ .time = 0x5e430672,
+ .message = msg,
+ },
+ },
+ };
+ struct reftable_writer *w = t_reftable_strbuf_writer(&buf, &opts);
+
+ memset(msg, 'x', sizeof(msg) - 1);
+ reftable_writer_set_limits(w, update_index, update_index);
+ err = reftable_writer_add_log(w, &log);
+ check_int(err, ==, REFTABLE_ENTRY_TOO_BIG_ERROR);
+ reftable_writer_free(w);
+ strbuf_release(&buf);
+}
+
+static void t_log_write_read(void)
+{
+ int N = 2;
+ char **names = reftable_calloc(N + 1, sizeof(*names));
+ int err;
+ struct reftable_write_options opts = {
+ .block_size = 256,
+ };
+ struct reftable_ref_record ref = { 0 };
+ int i = 0;
+ struct reftable_log_record log = { 0 };
+ int n;
+ struct reftable_iterator it = { 0 };
+ struct reftable_reader *reader;
+ struct reftable_block_source source = { 0 };
+ struct strbuf buf = STRBUF_INIT;
+ struct reftable_writer *w = t_reftable_strbuf_writer(&buf, &opts);
+ const struct reftable_stats *stats = NULL;
+ reftable_writer_set_limits(w, 0, N);
+ for (i = 0; i < N; i++) {
+ char name[256];
+ struct reftable_ref_record ref = { 0 };
+ snprintf(name, sizeof(name), "b%02d%0*d", i, 130, 7);
+ names[i] = xstrdup(name);
+ ref.refname = name;
+ ref.update_index = i;
+
+ err = reftable_writer_add_ref(w, &ref);
+ check(!err);
+ }
+ for (i = 0; i < N; i++) {
+ struct reftable_log_record log = { 0 };
+
+ log.refname = names[i];
+ log.update_index = i;
+ log.value_type = REFTABLE_LOG_UPDATE;
+ t_reftable_set_hash(log.value.update.old_hash, i,
+ GIT_SHA1_FORMAT_ID);
+ t_reftable_set_hash(log.value.update.new_hash, i + 1,
+ GIT_SHA1_FORMAT_ID);
+
+ err = reftable_writer_add_log(w, &log);
+ check(!err);
+ }
+
+ n = reftable_writer_close(w);
+ check_int(n, ==, 0);
+
+ stats = reftable_writer_stats(w);
+ check_int(stats->log_stats.blocks, >, 0);
+ reftable_writer_free(w);
+ w = NULL;
+
+ block_source_from_strbuf(&source, &buf);
+
+ err = reftable_reader_new(&reader, &source, "file.log");
+ check(!err);
+
+ reftable_reader_init_ref_iterator(reader, &it);
+
+ err = reftable_iterator_seek_ref(&it, names[N - 1]);
+ check(!err);
+
+ err = reftable_iterator_next_ref(&it, &ref);
+ check(!err);
+
+ /* end of iteration. */
+ err = reftable_iterator_next_ref(&it, &ref);
+ check_int(err, >, 0);
+
+ reftable_iterator_destroy(&it);
+ reftable_ref_record_release(&ref);
+
+ reftable_reader_init_log_iterator(reader, &it);
+
+ err = reftable_iterator_seek_log(&it, "");
+ check(!err);
+
+ for (i = 0; ; i++) {
+ int err = reftable_iterator_next_log(&it, &log);
+ if (err > 0)
+ break;
+ check(!err);
+ check_str(names[i], log.refname);
+ check_int(i, ==, log.update_index);
+ reftable_log_record_release(&log);
+ }
+
+ check_int(i, ==, N);
+ reftable_iterator_destroy(&it);
+
+ /* cleanup. */
+ strbuf_release(&buf);
+ free_names(names);
+ reftable_reader_decref(reader);
+}
+
+static void t_log_zlib_corruption(void)
+{
+ struct reftable_write_options opts = {
+ .block_size = 256,
+ };
+ struct reftable_iterator it = { 0 };
+ struct reftable_reader *reader;
+ struct reftable_block_source source = { 0 };
+ struct strbuf buf = STRBUF_INIT;
+ struct reftable_writer *w = t_reftable_strbuf_writer(&buf, &opts);
+ const struct reftable_stats *stats = NULL;
+ char message[100] = { 0 };
+ int err, i, n;
+ struct reftable_log_record log = {
+ .refname = (char *) "refname",
+ .value_type = REFTABLE_LOG_UPDATE,
+ .value = {
+ .update = {
+ .new_hash = { 1 },
+ .old_hash = { 2 },
+ .name = (char *) "My Name",
+ .email = (char *) "myname@invalid",
+ .message = message,
+ },
+ },
+ };
+
+ for (i = 0; i < sizeof(message) - 1; i++)
+ message[i] = (uint8_t)(git_rand() % 64 + ' ');
+
+ reftable_writer_set_limits(w, 1, 1);
+
+ err = reftable_writer_add_log(w, &log);
+ check(!err);
+
+ n = reftable_writer_close(w);
+ check_int(n, ==, 0);
+
+ stats = reftable_writer_stats(w);
+ check_int(stats->log_stats.blocks, >, 0);
+ reftable_writer_free(w);
+ w = NULL;
+
+ /* corrupt the data. */
+ buf.buf[50] ^= 0x99;
+
+ block_source_from_strbuf(&source, &buf);
+
+ err = reftable_reader_new(&reader, &source, "file.log");
+ check(!err);
+
+ reftable_reader_init_log_iterator(reader, &it);
+ err = reftable_iterator_seek_log(&it, "refname");
+ check_int(err, ==, REFTABLE_ZLIB_ERROR);
+
+ reftable_iterator_destroy(&it);
+
+ /* cleanup. */
+ reftable_reader_decref(reader);
+ strbuf_release(&buf);
+}
+
+static void t_table_read_write_sequential(void)
+{
+ char **names;
+ struct strbuf buf = STRBUF_INIT;
+ int N = 50;
+ struct reftable_iterator it = { 0 };
+ struct reftable_block_source source = { 0 };
+ struct reftable_reader *reader;
+ int err = 0;
+ int j = 0;
+
+ write_table(&names, &buf, N, 256, GIT_SHA1_FORMAT_ID);
+
+ block_source_from_strbuf(&source, &buf);
+
+ err = reftable_reader_new(&reader, &source, "file.ref");
+ check(!err);
+
+ reftable_reader_init_ref_iterator(reader, &it);
+ err = reftable_iterator_seek_ref(&it, "");
+ check(!err);
+
+ for (j = 0; ; j++) {
+ struct reftable_ref_record ref = { 0 };
+ int r = reftable_iterator_next_ref(&it, &ref);
+ check_int(r, >=, 0);
+ if (r > 0)
+ break;
+ check_str(names[j], ref.refname);
+ check_int(update_index, ==, ref.update_index);
+ reftable_ref_record_release(&ref);
+ }
+ check_int(j, ==, N);
+
+ reftable_iterator_destroy(&it);
+ reftable_reader_decref(reader);
+ strbuf_release(&buf);
+ free_names(names);
+}
+
+static void t_table_write_small_table(void)
+{
+ char **names;
+ struct strbuf buf = STRBUF_INIT;
+ int N = 1;
+ write_table(&names, &buf, N, 4096, GIT_SHA1_FORMAT_ID);
+ check_int(buf.len, <, 200);
+ strbuf_release(&buf);
+ free_names(names);
+}
+
+static void t_table_read_api(void)
+{
+ char **names;
+ struct strbuf buf = STRBUF_INIT;
+ int N = 50;
+ struct reftable_reader *reader;
+ struct reftable_block_source source = { 0 };
+ int err;
+ struct reftable_log_record log = { 0 };
+ struct reftable_iterator it = { 0 };
+
+ write_table(&names, &buf, N, 256, GIT_SHA1_FORMAT_ID);
+
+ block_source_from_strbuf(&source, &buf);
+
+ err = reftable_reader_new(&reader, &source, "file.ref");
+ check(!err);
+
+ reftable_reader_init_ref_iterator(reader, &it);
+ err = reftable_iterator_seek_ref(&it, names[0]);
+ check(!err);
+
+ err = reftable_iterator_next_log(&it, &log);
+ check_int(err, ==, REFTABLE_API_ERROR);
+
+ strbuf_release(&buf);
+ free_names(names);
+ reftable_iterator_destroy(&it);
+ reftable_reader_decref(reader);
+ strbuf_release(&buf);
+}
+
+static void t_table_read_write_seek(int index, int hash_id)
+{
+ char **names;
+ struct strbuf buf = STRBUF_INIT;
+ int N = 50;
+ struct reftable_reader *reader;
+ struct reftable_block_source source = { 0 };
+ int err;
+ int i = 0;
+
+ struct reftable_iterator it = { 0 };
+ struct strbuf pastLast = STRBUF_INIT;
+ struct reftable_ref_record ref = { 0 };
+
+ write_table(&names, &buf, N, 256, hash_id);
+
+ block_source_from_strbuf(&source, &buf);
+
+ err = reftable_reader_new(&reader, &source, "file.ref");
+ check(!err);
+ check_int(hash_id, ==, reftable_reader_hash_id(reader));
+
+ if (!index) {
+ reader->ref_offsets.index_offset = 0;
+ } else {
+ check_int(reader->ref_offsets.index_offset, >, 0);
+ }
+
+ for (i = 1; i < N; i++) {
+ reftable_reader_init_ref_iterator(reader, &it);
+ err = reftable_iterator_seek_ref(&it, names[i]);
+ check(!err);
+ err = reftable_iterator_next_ref(&it, &ref);
+ check(!err);
+ check_str(names[i], ref.refname);
+ check_int(REFTABLE_REF_VAL1, ==, ref.value_type);
+ check_int(i, ==, ref.value.val1[0]);
+
+ reftable_ref_record_release(&ref);
+ reftable_iterator_destroy(&it);
+ }
+
+ strbuf_addstr(&pastLast, names[N - 1]);
+ strbuf_addstr(&pastLast, "/");
+
+ reftable_reader_init_ref_iterator(reader, &it);
+ err = reftable_iterator_seek_ref(&it, pastLast.buf);
+ if (err == 0) {
+ struct reftable_ref_record ref = { 0 };
+ int err = reftable_iterator_next_ref(&it, &ref);
+ check_int(err, >, 0);
+ } else {
+ check_int(err, >, 0);
+ }
+
+ strbuf_release(&pastLast);
+ reftable_iterator_destroy(&it);
+
+ strbuf_release(&buf);
+ free_names(names);
+ reftable_reader_decref(reader);
+}
+
+static void t_table_read_write_seek_linear(void)
+{
+ t_table_read_write_seek(0, GIT_SHA1_FORMAT_ID);
+}
+
+static void t_table_read_write_seek_linear_sha256(void)
+{
+ t_table_read_write_seek(0, GIT_SHA256_FORMAT_ID);
+}
+
+static void t_table_read_write_seek_index(void)
+{
+ t_table_read_write_seek(1, GIT_SHA1_FORMAT_ID);
+}
+
+static void t_table_refs_for(int indexed)
+{
+ int N = 50;
+ char **want_names = reftable_calloc(N + 1, sizeof(*want_names));
+ int want_names_len = 0;
+ uint8_t want_hash[GIT_SHA1_RAWSZ];
+
+ struct reftable_write_options opts = {
+ .block_size = 256,
+ };
+ struct reftable_ref_record ref = { 0 };
+ int i = 0;
+ int n;
+ int err;
+ struct reftable_reader *reader;
+ struct reftable_block_source source = { 0 };
+ struct strbuf buf = STRBUF_INIT;
+ struct reftable_writer *w = t_reftable_strbuf_writer(&buf, &opts);
+ struct reftable_iterator it = { 0 };
+ int j;
+
+ t_reftable_set_hash(want_hash, 4, GIT_SHA1_FORMAT_ID);
+
+ for (i = 0; i < N; i++) {
+ uint8_t hash[GIT_SHA1_RAWSZ];
+ char fill[51] = { 0 };
+ char name[100];
+ struct reftable_ref_record ref = { 0 };
+
+ memset(hash, i, sizeof(hash));
+ memset(fill, 'x', 50);
+ /* Put the variable part in the start */
+ snprintf(name, sizeof(name), "br%02d%s", i, fill);
+ name[40] = 0;
+ ref.refname = name;
+
+ ref.value_type = REFTABLE_REF_VAL2;
+ t_reftable_set_hash(ref.value.val2.value, i / 4,
+ GIT_SHA1_FORMAT_ID);
+ t_reftable_set_hash(ref.value.val2.target_value, 3 + i / 4,
+ GIT_SHA1_FORMAT_ID);
+
+ /* 80 bytes / entry, so 3 entries per block. Yields 17
+ */
+ /* blocks. */
+ n = reftable_writer_add_ref(w, &ref);
+ check_int(n, ==, 0);
+
+ if (!memcmp(ref.value.val2.value, want_hash, GIT_SHA1_RAWSZ) ||
+ !memcmp(ref.value.val2.target_value, want_hash, GIT_SHA1_RAWSZ))
+ want_names[want_names_len++] = xstrdup(name);
+ }
+
+ n = reftable_writer_close(w);
+ check_int(n, ==, 0);
+
+ reftable_writer_free(w);
+ w = NULL;
+
+ block_source_from_strbuf(&source, &buf);
+
+ err = reftable_reader_new(&reader, &source, "file.ref");
+ check(!err);
+ if (!indexed)
+ reader->obj_offsets.is_present = 0;
+
+ reftable_reader_init_ref_iterator(reader, &it);
+ err = reftable_iterator_seek_ref(&it, "");
+ check(!err);
+ reftable_iterator_destroy(&it);
+
+ err = reftable_reader_refs_for(reader, &it, want_hash);
+ check(!err);
+
+ for (j = 0; ; j++) {
+ int err = reftable_iterator_next_ref(&it, &ref);
+ check_int(err, >=, 0);
+ if (err > 0)
+ break;
+ check_int(j, <, want_names_len);
+ check_str(ref.refname, want_names[j]);
+ reftable_ref_record_release(&ref);
+ }
+ check_int(j, ==, want_names_len);
+
+ strbuf_release(&buf);
+ free_names(want_names);
+ reftable_iterator_destroy(&it);
+ reftable_reader_decref(reader);
+}
+
+static void t_table_refs_for_no_index(void)
+{
+ t_table_refs_for(0);
+}
+
+static void t_table_refs_for_obj_index(void)
+{
+ t_table_refs_for(1);
+}
+
+static void t_write_empty_table(void)
+{
+ struct reftable_write_options opts = { 0 };
+ struct strbuf buf = STRBUF_INIT;
+ struct reftable_writer *w = t_reftable_strbuf_writer(&buf, &opts);
+ struct reftable_block_source source = { 0 };
+ struct reftable_reader *rd = NULL;
+ struct reftable_ref_record rec = { 0 };
+ struct reftable_iterator it = { 0 };
+ int err;
+
+ reftable_writer_set_limits(w, 1, 1);
+
+ err = reftable_writer_close(w);
+ check_int(err, ==, REFTABLE_EMPTY_TABLE_ERROR);
+ reftable_writer_free(w);
+
+ check_int(buf.len, ==, header_size(1) + footer_size(1));
+
+ block_source_from_strbuf(&source, &buf);
+
+ err = reftable_reader_new(&rd, &source, "filename");
+ check(!err);
+
+ reftable_reader_init_ref_iterator(rd, &it);
+ err = reftable_iterator_seek_ref(&it, "");
+ check(!err);
+
+ err = reftable_iterator_next_ref(&it, &rec);
+ check_int(err, >, 0);
+
+ reftable_iterator_destroy(&it);
+ reftable_reader_decref(rd);
+ strbuf_release(&buf);
+}
+
+static void t_write_object_id_min_length(void)
+{
+ struct reftable_write_options opts = {
+ .block_size = 75,
+ };
+ struct strbuf buf = STRBUF_INIT;
+ struct reftable_writer *w = t_reftable_strbuf_writer(&buf, &opts);
+ struct reftable_ref_record ref = {
+ .update_index = 1,
+ .value_type = REFTABLE_REF_VAL1,
+ .value.val1 = {42},
+ };
+ int err;
+ int i;
+
+ reftable_writer_set_limits(w, 1, 1);
+
+ /* Write the same hash in many refs. If there is only 1 hash, the
+ * disambiguating prefix is length 0 */
+ for (i = 0; i < 256; i++) {
+ char name[256];
+ snprintf(name, sizeof(name), "ref%05d", i);
+ ref.refname = name;
+ err = reftable_writer_add_ref(w, &ref);
+ check(!err);
+ }
+
+ err = reftable_writer_close(w);
+ check(!err);
+ check_int(reftable_writer_stats(w)->object_id_len, ==, 2);
+ reftable_writer_free(w);
+ strbuf_release(&buf);
+}
+
+static void t_write_object_id_length(void)
+{
+ struct reftable_write_options opts = {
+ .block_size = 75,
+ };
+ struct strbuf buf = STRBUF_INIT;
+ struct reftable_writer *w = t_reftable_strbuf_writer(&buf, &opts);
+ struct reftable_ref_record ref = {
+ .update_index = 1,
+ .value_type = REFTABLE_REF_VAL1,
+ .value.val1 = {42},
+ };
+ int err;
+ int i;
+
+ reftable_writer_set_limits(w, 1, 1);
+
+ /* Write the same hash in many refs. If there is only 1 hash, the
+ * disambiguating prefix is length 0 */
+ for (i = 0; i < 256; i++) {
+ char name[256];
+ snprintf(name, sizeof(name), "ref%05d", i);
+ ref.refname = name;
+ ref.value.val1[15] = i;
+ err = reftable_writer_add_ref(w, &ref);
+ check(!err);
+ }
+
+ err = reftable_writer_close(w);
+ check(!err);
+ check_int(reftable_writer_stats(w)->object_id_len, ==, 16);
+ reftable_writer_free(w);
+ strbuf_release(&buf);
+}
+
+static void t_write_empty_key(void)
+{
+ struct reftable_write_options opts = { 0 };
+ struct strbuf buf = STRBUF_INIT;
+ struct reftable_writer *w = t_reftable_strbuf_writer(&buf, &opts);
+ struct reftable_ref_record ref = {
+ .refname = (char *) "",
+ .update_index = 1,
+ .value_type = REFTABLE_REF_DELETION,
+ };
+ int err;
+
+ reftable_writer_set_limits(w, 1, 1);
+ err = reftable_writer_add_ref(w, &ref);
+ check_int(err, ==, REFTABLE_API_ERROR);
+
+ err = reftable_writer_close(w);
+ check_int(err, ==, REFTABLE_EMPTY_TABLE_ERROR);
+ reftable_writer_free(w);
+ strbuf_release(&buf);
+}
+
+static void t_write_key_order(void)
+{
+ struct reftable_write_options opts = { 0 };
+ struct strbuf buf = STRBUF_INIT;
+ struct reftable_writer *w = t_reftable_strbuf_writer(&buf, &opts);
+ struct reftable_ref_record refs[2] = {
+ {
+ .refname = (char *) "b",
+ .update_index = 1,
+ .value_type = REFTABLE_REF_SYMREF,
+ .value = {
+ .symref = (char *) "target",
+ },
+ }, {
+ .refname = (char *) "a",
+ .update_index = 1,
+ .value_type = REFTABLE_REF_SYMREF,
+ .value = {
+ .symref = (char *) "target",
+ },
+ }
+ };
+ int err;
+
+ reftable_writer_set_limits(w, 1, 1);
+ err = reftable_writer_add_ref(w, &refs[0]);
+ check(!err);
+ err = reftable_writer_add_ref(w, &refs[1]);
+ check_int(err, ==, REFTABLE_API_ERROR);
+
+ refs[0].update_index = 2;
+ err = reftable_writer_add_ref(w, &refs[0]);
+ check_int(err, ==, REFTABLE_API_ERROR);
+
+ reftable_writer_close(w);
+ reftable_writer_free(w);
+ strbuf_release(&buf);
+}
+
+static void t_write_multiple_indices(void)
+{
+ struct reftable_write_options opts = {
+ .block_size = 100,
+ };
+ struct strbuf writer_buf = STRBUF_INIT, buf = STRBUF_INIT;
+ struct reftable_block_source source = { 0 };
+ struct reftable_iterator it = { 0 };
+ const struct reftable_stats *stats;
+ struct reftable_writer *writer;
+ struct reftable_reader *reader;
+ int err, i;
+
+ writer = t_reftable_strbuf_writer(&writer_buf, &opts);
+ reftable_writer_set_limits(writer, 1, 1);
+ for (i = 0; i < 100; i++) {
+ struct reftable_ref_record ref = {
+ .update_index = 1,
+ .value_type = REFTABLE_REF_VAL1,
+ .value.val1 = {i},
+ };
+
+ strbuf_reset(&buf);
+ strbuf_addf(&buf, "refs/heads/%04d", i);
+ ref.refname = buf.buf,
+
+ err = reftable_writer_add_ref(writer, &ref);
+ check(!err);
+ }
+
+ for (i = 0; i < 100; i++) {
+ struct reftable_log_record log = {
+ .update_index = 1,
+ .value_type = REFTABLE_LOG_UPDATE,
+ .value.update = {
+ .old_hash = { i },
+ .new_hash = { i },
+ },
+ };
+
+ strbuf_reset(&buf);
+ strbuf_addf(&buf, "refs/heads/%04d", i);
+ log.refname = buf.buf,
+
+ err = reftable_writer_add_log(writer, &log);
+ check(!err);
+ }
+
+ reftable_writer_close(writer);
+
+ /*
+ * The written data should be sufficiently large to result in indices
+ * for each of the block types.
+ */
+ stats = reftable_writer_stats(writer);
+ check_int(stats->ref_stats.index_offset, >, 0);
+ check_int(stats->obj_stats.index_offset, >, 0);
+ check_int(stats->log_stats.index_offset, >, 0);
+
+ block_source_from_strbuf(&source, &writer_buf);
+ err = reftable_reader_new(&reader, &source, "filename");
+ check(!err);
+
+ /*
+ * Seeking the log uses the log index now. In case there is any
+ * confusion regarding indices we would notice here.
+ */
+ reftable_reader_init_log_iterator(reader, &it);
+ err = reftable_iterator_seek_log(&it, "");
+ check(!err);
+
+ reftable_iterator_destroy(&it);
+ reftable_writer_free(writer);
+ reftable_reader_decref(reader);
+ strbuf_release(&writer_buf);
+ strbuf_release(&buf);
+}
+
+static void t_write_multi_level_index(void)
+{
+ struct reftable_write_options opts = {
+ .block_size = 100,
+ };
+ struct strbuf writer_buf = STRBUF_INIT, buf = STRBUF_INIT;
+ struct reftable_block_source source = { 0 };
+ struct reftable_iterator it = { 0 };
+ const struct reftable_stats *stats;
+ struct reftable_writer *writer;
+ struct reftable_reader *reader;
+ int err;
+
+ writer = t_reftable_strbuf_writer(&writer_buf, &opts);
+ reftable_writer_set_limits(writer, 1, 1);
+ for (size_t i = 0; i < 200; i++) {
+ struct reftable_ref_record ref = {
+ .update_index = 1,
+ .value_type = REFTABLE_REF_VAL1,
+ .value.val1 = {i},
+ };
+
+ strbuf_reset(&buf);
+ strbuf_addf(&buf, "refs/heads/%03" PRIuMAX, (uintmax_t)i);
+ ref.refname = buf.buf,
+
+ err = reftable_writer_add_ref(writer, &ref);
+ check(!err);
+ }
+ reftable_writer_close(writer);
+
+ /*
+ * The written refs should be sufficiently large to result in a
+ * multi-level index.
+ */
+ stats = reftable_writer_stats(writer);
+ check_int(stats->ref_stats.max_index_level, ==, 2);
+
+ block_source_from_strbuf(&source, &writer_buf);
+ err = reftable_reader_new(&reader, &source, "filename");
+ check(!err);
+
+ /*
+ * Seeking the last ref should work as expected.
+ */
+ reftable_reader_init_ref_iterator(reader, &it);
+ err = reftable_iterator_seek_ref(&it, "refs/heads/199");
+ check(!err);
+
+ reftable_iterator_destroy(&it);
+ reftable_writer_free(writer);
+ reftable_reader_decref(reader);
+ strbuf_release(&writer_buf);
+ strbuf_release(&buf);
+}
+
+static void t_corrupt_table_empty(void)
+{
+ struct strbuf buf = STRBUF_INIT;
+ struct reftable_block_source source = { 0 };
+ struct reftable_reader *reader;
+ int err;
+
+ block_source_from_strbuf(&source, &buf);
+ err = reftable_reader_new(&reader, &source, "file.log");
+ check_int(err, ==, REFTABLE_FORMAT_ERROR);
+}
+
+static void t_corrupt_table(void)
+{
+ uint8_t zeros[1024] = { 0 };
+ struct strbuf buf = STRBUF_INIT;
+ struct reftable_block_source source = { 0 };
+ struct reftable_reader *reader;
+ int err;
+ strbuf_add(&buf, zeros, sizeof(zeros));
+
+ block_source_from_strbuf(&source, &buf);
+ err = reftable_reader_new(&reader, &source, "file.log");
+ check_int(err, ==, REFTABLE_FORMAT_ERROR);
+
+ strbuf_release(&buf);
+}
+
+int cmd_main(int argc UNUSED, const char *argv[] UNUSED)
+{
+ TEST(t_buffer(), "strbuf works as blocksource");
+ TEST(t_corrupt_table(), "read-write on corrupted table");
+ TEST(t_corrupt_table_empty(), "read-write on an empty table");
+ TEST(t_log_buffer_size(), "buffer extension for log compression");
+ TEST(t_log_overflow(), "log overflow returns expected error");
+ TEST(t_log_write_read(), "read-write on log records");
+ TEST(t_log_zlib_corruption(), "reading corrupted log record returns expected error");
+ TEST(t_table_read_api(), "read on a table");
+ TEST(t_table_read_write_seek_index(), "read-write on a table with index");
+ TEST(t_table_read_write_seek_linear(), "read-write on a table without index (SHA1)");
+ TEST(t_table_read_write_seek_linear_sha256(), "read-write on a table without index (SHA256)");
+ TEST(t_table_read_write_sequential(), "sequential read-write on a table");
+ TEST(t_table_refs_for_no_index(), "refs-only table with no index");
+ TEST(t_table_refs_for_obj_index(), "refs-only table with index");
+ TEST(t_table_write_small_table(), "write_table works");
+ TEST(t_write_empty_key(), "write on refs with empty keys");
+ TEST(t_write_empty_table(), "read-write on empty tables");
+ TEST(t_write_key_order(), "refs must be written in increasing order");
+ TEST(t_write_multi_level_index(), "table with multi-level index");
+ TEST(t_write_multiple_indices(), "table with indices for multiple block types");
+ TEST(t_write_object_id_length(), "prefix compression on writing refs");
+ TEST(t_write_object_id_min_length(), "prefix compression on writing refs");
+
+ return test_done();
+}
diff --git a/t/unit-tests/t-reftable-record.c b/t/unit-tests/t-reftable-record.c
index cb649ee419..a7f67d4d9f 100644
--- a/t/unit-tests/t-reftable-record.c
+++ b/t/unit-tests/t-reftable-record.c
@@ -532,7 +532,7 @@ static void t_reftable_index_record_roundtrip(void)
strbuf_release(&in.u.idx.last_key);
}
-int cmd_main(int argc, const char *argv[])
+int cmd_main(int argc UNUSED, const char *argv[] UNUSED)
{
TEST(t_reftable_ref_record_comparison(), "comparison operations work on ref record");
TEST(t_reftable_log_record_comparison(), "comparison operations work on log record");
diff --git a/t/unit-tests/t-reftable-stack.c b/t/unit-tests/t-reftable-stack.c
new file mode 100644
index 0000000000..31d563d992
--- /dev/null
+++ b/t/unit-tests/t-reftable-stack.c
@@ -0,0 +1,1386 @@
+/*
+Copyright 2020 Google LLC
+
+Use of this source code is governed by a BSD-style
+license that can be found in the LICENSE file or at
+https://developers.google.com/open-source/licenses/bsd
+*/
+
+#include "test-lib.h"
+#include "lib-reftable.h"
+#include "reftable/merged.h"
+#include "reftable/reader.h"
+#include "reftable/reftable-error.h"
+#include "reftable/stack.h"
+#include <dirent.h>
+
+static void clear_dir(const char *dirname)
+{
+ struct strbuf path = STRBUF_INIT;
+ strbuf_addstr(&path, dirname);
+ remove_dir_recursively(&path, 0);
+ strbuf_release(&path);
+}
+
+static int count_dir_entries(const char *dirname)
+{
+ DIR *dir = opendir(dirname);
+ int len = 0;
+ struct dirent *d;
+ if (!dir)
+ return 0;
+
+ while ((d = readdir(dir))) {
+ /*
+ * Besides skipping over "." and "..", we also need to
+ * skip over other files that have a leading ".". This
+ * is due to behaviour of NFS, which will rename files
+ * to ".nfs*" to emulate delete-on-last-close.
+ *
+ * In any case this should be fine as the reftable
+ * library will never write files with leading dots
+ * anyway.
+ */
+ if (starts_with(d->d_name, "."))
+ continue;
+ len++;
+ }
+ closedir(dir);
+ return len;
+}
+
+/*
+ * Work linenumber into the tempdir, so we can see which tests forget to
+ * cleanup.
+ */
+static char *get_tmp_template(int linenumber)
+{
+ const char *tmp = getenv("TMPDIR");
+ static char template[1024];
+ snprintf(template, sizeof(template) - 1, "%s/stack_test-%d.XXXXXX",
+ tmp ? tmp : "/tmp", linenumber);
+ return template;
+}
+
+static char *get_tmp_dir(int linenumber)
+{
+ char *dir = get_tmp_template(linenumber);
+ check(mkdtemp(dir) != NULL);
+ return dir;
+}
+
+static void t_read_file(void)
+{
+ char *fn = get_tmp_template(__LINE__);
+ struct tempfile *tmp = mks_tempfile(fn);
+ int fd = get_tempfile_fd(tmp);
+ char out[1024] = "line1\n\nline2\nline3";
+ int n, err;
+ char **names = NULL;
+ const char *want[] = { "line1", "line2", "line3" };
+
+ check_int(fd, >, 0);
+ n = write_in_full(fd, out, strlen(out));
+ check_int(n, ==, strlen(out));
+ err = close(fd);
+ check_int(err, >=, 0);
+
+ err = read_lines(fn, &names);
+ check(!err);
+
+ for (size_t i = 0; names[i]; i++)
+ check_str(want[i], names[i]);
+ free_names(names);
+ (void) remove(fn);
+ delete_tempfile(&tmp);
+}
+
+static int write_test_ref(struct reftable_writer *wr, void *arg)
+{
+ struct reftable_ref_record *ref = arg;
+ reftable_writer_set_limits(wr, ref->update_index, ref->update_index);
+ return reftable_writer_add_ref(wr, ref);
+}
+
+static void write_n_ref_tables(struct reftable_stack *st,
+ size_t n)
+{
+ struct strbuf buf = STRBUF_INIT;
+ int disable_auto_compact;
+ int err;
+
+ disable_auto_compact = st->opts.disable_auto_compact;
+ st->opts.disable_auto_compact = 1;
+
+ for (size_t i = 0; i < n; i++) {
+ struct reftable_ref_record ref = {
+ .update_index = reftable_stack_next_update_index(st),
+ .value_type = REFTABLE_REF_VAL1,
+ };
+
+ strbuf_reset(&buf);
+ strbuf_addf(&buf, "refs/heads/branch-%04"PRIuMAX, (uintmax_t)i);
+ ref.refname = buf.buf;
+ t_reftable_set_hash(ref.value.val1, i, GIT_SHA1_FORMAT_ID);
+
+ err = reftable_stack_add(st, &write_test_ref, &ref);
+ check(!err);
+ }
+
+ st->opts.disable_auto_compact = disable_auto_compact;
+ strbuf_release(&buf);
+}
+
+struct write_log_arg {
+ struct reftable_log_record *log;
+ uint64_t update_index;
+};
+
+static int write_test_log(struct reftable_writer *wr, void *arg)
+{
+ struct write_log_arg *wla = arg;
+
+ reftable_writer_set_limits(wr, wla->update_index, wla->update_index);
+ return reftable_writer_add_log(wr, wla->log);
+}
+
+static void t_reftable_stack_add_one(void)
+{
+ char *dir = get_tmp_dir(__LINE__);
+ struct strbuf scratch = STRBUF_INIT;
+ int mask = umask(002);
+ struct reftable_write_options opts = {
+ .default_permissions = 0660,
+ };
+ struct reftable_stack *st = NULL;
+ int err;
+ struct reftable_ref_record ref = {
+ .refname = (char *) "HEAD",
+ .update_index = 1,
+ .value_type = REFTABLE_REF_SYMREF,
+ .value.symref = (char *) "master",
+ };
+ struct reftable_ref_record dest = { 0 };
+ struct stat stat_result = { 0 };
+ err = reftable_new_stack(&st, dir, &opts);
+ check(!err);
+
+ err = reftable_stack_add(st, write_test_ref, &ref);
+ check(!err);
+
+ err = reftable_stack_read_ref(st, ref.refname, &dest);
+ check(!err);
+ check(reftable_ref_record_equal(&ref, &dest, GIT_SHA1_RAWSZ));
+ check_int(st->readers_len, >, 0);
+
+#ifndef GIT_WINDOWS_NATIVE
+ strbuf_addstr(&scratch, dir);
+ strbuf_addstr(&scratch, "/tables.list");
+ err = stat(scratch.buf, &stat_result);
+ check(!err);
+ check_int((stat_result.st_mode & 0777), ==, opts.default_permissions);
+
+ strbuf_reset(&scratch);
+ strbuf_addstr(&scratch, dir);
+ strbuf_addstr(&scratch, "/");
+ /* do not try at home; not an external API for reftable. */
+ strbuf_addstr(&scratch, st->readers[0]->name);
+ err = stat(scratch.buf, &stat_result);
+ check(!err);
+ check_int((stat_result.st_mode & 0777), ==, opts.default_permissions);
+#else
+ (void) stat_result;
+#endif
+
+ reftable_ref_record_release(&dest);
+ reftable_stack_destroy(st);
+ strbuf_release(&scratch);
+ clear_dir(dir);
+ umask(mask);
+}
+
+static void t_reftable_stack_uptodate(void)
+{
+ struct reftable_write_options opts = { 0 };
+ struct reftable_stack *st1 = NULL;
+ struct reftable_stack *st2 = NULL;
+ char *dir = get_tmp_dir(__LINE__);
+
+ int err;
+ struct reftable_ref_record ref1 = {
+ .refname = (char *) "HEAD",
+ .update_index = 1,
+ .value_type = REFTABLE_REF_SYMREF,
+ .value.symref = (char *) "master",
+ };
+ struct reftable_ref_record ref2 = {
+ .refname = (char *) "branch2",
+ .update_index = 2,
+ .value_type = REFTABLE_REF_SYMREF,
+ .value.symref = (char *) "master",
+ };
+
+
+ /* simulate multi-process access to the same stack
+ by creating two stacks for the same directory.
+ */
+ err = reftable_new_stack(&st1, dir, &opts);
+ check(!err);
+
+ err = reftable_new_stack(&st2, dir, &opts);
+ check(!err);
+
+ err = reftable_stack_add(st1, write_test_ref, &ref1);
+ check(!err);
+
+ err = reftable_stack_add(st2, write_test_ref, &ref2);
+ check_int(err, ==, REFTABLE_OUTDATED_ERROR);
+
+ err = reftable_stack_reload(st2);
+ check(!err);
+
+ err = reftable_stack_add(st2, write_test_ref, &ref2);
+ check(!err);
+ reftable_stack_destroy(st1);
+ reftable_stack_destroy(st2);
+ clear_dir(dir);
+}
+
+static void t_reftable_stack_transaction_api(void)
+{
+ char *dir = get_tmp_dir(__LINE__);
+ struct reftable_write_options opts = { 0 };
+ struct reftable_stack *st = NULL;
+ int err;
+ struct reftable_addition *add = NULL;
+
+ struct reftable_ref_record ref = {
+ .refname = (char *) "HEAD",
+ .update_index = 1,
+ .value_type = REFTABLE_REF_SYMREF,
+ .value.symref = (char *) "master",
+ };
+ struct reftable_ref_record dest = { 0 };
+
+ err = reftable_new_stack(&st, dir, &opts);
+ check(!err);
+
+ reftable_addition_destroy(add);
+
+ err = reftable_stack_new_addition(&add, st, 0);
+ check(!err);
+
+ err = reftable_addition_add(add, write_test_ref, &ref);
+ check(!err);
+
+ err = reftable_addition_commit(add);
+ check(!err);
+
+ reftable_addition_destroy(add);
+
+ err = reftable_stack_read_ref(st, ref.refname, &dest);
+ check(!err);
+ check_int(REFTABLE_REF_SYMREF, ==, dest.value_type);
+ check(reftable_ref_record_equal(&ref, &dest, GIT_SHA1_RAWSZ));
+
+ reftable_ref_record_release(&dest);
+ reftable_stack_destroy(st);
+ clear_dir(dir);
+}
+
+static void t_reftable_stack_transaction_with_reload(void)
+{
+ char *dir = get_tmp_dir(__LINE__);
+ struct reftable_stack *st1 = NULL, *st2 = NULL;
+ int err;
+ struct reftable_addition *add = NULL;
+ struct reftable_ref_record refs[2] = {
+ {
+ .refname = (char *) "refs/heads/a",
+ .update_index = 1,
+ .value_type = REFTABLE_REF_VAL1,
+ .value.val1 = { '1' },
+ },
+ {
+ .refname = (char *) "refs/heads/b",
+ .update_index = 2,
+ .value_type = REFTABLE_REF_VAL1,
+ .value.val1 = { '1' },
+ },
+ };
+ struct reftable_ref_record ref = { 0 };
+
+ err = reftable_new_stack(&st1, dir, NULL);
+ check(!err);
+ err = reftable_new_stack(&st2, dir, NULL);
+ check(!err);
+
+ err = reftable_stack_new_addition(&add, st1, 0);
+ check(!err);
+ err = reftable_addition_add(add, write_test_ref, &refs[0]);
+ check(!err);
+ err = reftable_addition_commit(add);
+ check(!err);
+ reftable_addition_destroy(add);
+
+ /*
+ * The second stack is now outdated, which we should notice. We do not
+ * create the addition and lock the stack by default, but allow the
+ * reload to happen when REFTABLE_STACK_NEW_ADDITION_RELOAD is set.
+ */
+ err = reftable_stack_new_addition(&add, st2, 0);
+ check_int(err, ==, REFTABLE_OUTDATED_ERROR);
+ err = reftable_stack_new_addition(&add, st2, REFTABLE_STACK_NEW_ADDITION_RELOAD);
+ check(!err);
+ err = reftable_addition_add(add, write_test_ref, &refs[1]);
+ check(!err);
+ err = reftable_addition_commit(add);
+ check(!err);
+ reftable_addition_destroy(add);
+
+ for (size_t i = 0; i < ARRAY_SIZE(refs); i++) {
+ err = reftable_stack_read_ref(st2, refs[i].refname, &ref);
+ check(!err);
+ check(reftable_ref_record_equal(&refs[i], &ref, GIT_SHA1_RAWSZ));
+ }
+
+ reftable_ref_record_release(&ref);
+ reftable_stack_destroy(st1);
+ reftable_stack_destroy(st2);
+ clear_dir(dir);
+}
+
+static void t_reftable_stack_transaction_api_performs_auto_compaction(void)
+{
+ char *dir = get_tmp_dir(__LINE__);
+ struct reftable_write_options opts = {0};
+ struct reftable_addition *add = NULL;
+ struct reftable_stack *st = NULL;
+ size_t n = 20;
+ int err;
+
+ err = reftable_new_stack(&st, dir, &opts);
+ check(!err);
+
+ for (size_t i = 0; i <= n; i++) {
+ struct reftable_ref_record ref = {
+ .update_index = reftable_stack_next_update_index(st),
+ .value_type = REFTABLE_REF_SYMREF,
+ .value.symref = (char *) "master",
+ };
+ char name[100];
+
+ snprintf(name, sizeof(name), "branch%04"PRIuMAX, (uintmax_t)i);
+ ref.refname = name;
+
+ /*
+ * Disable auto-compaction for all but the last runs. Like this
+ * we can ensure that we indeed honor this setting and have
+ * better control over when exactly auto compaction runs.
+ */
+ st->opts.disable_auto_compact = i != n;
+
+ err = reftable_stack_new_addition(&add, st, 0);
+ check(!err);
+
+ err = reftable_addition_add(add, write_test_ref, &ref);
+ check(!err);
+
+ err = reftable_addition_commit(add);
+ check(!err);
+
+ reftable_addition_destroy(add);
+
+ /*
+ * The stack length should grow continuously for all runs where
+ * auto compaction is disabled. When enabled, we should merge
+ * all tables in the stack.
+ */
+ if (i != n)
+ check_int(st->merged->readers_len, ==, i + 1);
+ else
+ check_int(st->merged->readers_len, ==, 1);
+ }
+
+ reftable_stack_destroy(st);
+ clear_dir(dir);
+}
+
+static void t_reftable_stack_auto_compaction_fails_gracefully(void)
+{
+ struct reftable_ref_record ref = {
+ .refname = (char *) "refs/heads/master",
+ .update_index = 1,
+ .value_type = REFTABLE_REF_VAL1,
+ .value.val1 = {0x01},
+ };
+ struct reftable_write_options opts = { 0 };
+ struct reftable_stack *st;
+ struct strbuf table_path = STRBUF_INIT;
+ char *dir = get_tmp_dir(__LINE__);
+ int err;
+
+ err = reftable_new_stack(&st, dir, &opts);
+ check(!err);
+
+ err = reftable_stack_add(st, write_test_ref, &ref);
+ check(!err);
+ check_int(st->merged->readers_len, ==, 1);
+ check_int(st->stats.attempts, ==, 0);
+ check_int(st->stats.failures, ==, 0);
+
+ /*
+ * Lock the newly written table such that it cannot be compacted.
+ * Adding a new table to the stack should not be impacted by this, even
+ * though auto-compaction will now fail.
+ */
+ strbuf_addf(&table_path, "%s/%s.lock", dir, st->readers[0]->name);
+ write_file_buf(table_path.buf, "", 0);
+
+ ref.update_index = 2;
+ err = reftable_stack_add(st, write_test_ref, &ref);
+ check(!err);
+ check_int(st->merged->readers_len, ==, 2);
+ check_int(st->stats.attempts, ==, 1);
+ check_int(st->stats.failures, ==, 1);
+
+ reftable_stack_destroy(st);
+ strbuf_release(&table_path);
+ clear_dir(dir);
+}
+
+static int write_error(struct reftable_writer *wr UNUSED, void *arg)
+{
+ return *((int *)arg);
+}
+
+static void t_reftable_stack_update_index_check(void)
+{
+ char *dir = get_tmp_dir(__LINE__);
+ struct reftable_write_options opts = { 0 };
+ struct reftable_stack *st = NULL;
+ int err;
+ struct reftable_ref_record ref1 = {
+ .refname = (char *) "name1",
+ .update_index = 1,
+ .value_type = REFTABLE_REF_SYMREF,
+ .value.symref = (char *) "master",
+ };
+ struct reftable_ref_record ref2 = {
+ .refname = (char *) "name2",
+ .update_index = 1,
+ .value_type = REFTABLE_REF_SYMREF,
+ .value.symref = (char *) "master",
+ };
+
+ err = reftable_new_stack(&st, dir, &opts);
+ check(!err);
+
+ err = reftable_stack_add(st, write_test_ref, &ref1);
+ check(!err);
+
+ err = reftable_stack_add(st, write_test_ref, &ref2);
+ check_int(err, ==, REFTABLE_API_ERROR);
+ reftable_stack_destroy(st);
+ clear_dir(dir);
+}
+
+static void t_reftable_stack_lock_failure(void)
+{
+ char *dir = get_tmp_dir(__LINE__);
+ struct reftable_write_options opts = { 0 };
+ struct reftable_stack *st = NULL;
+ int err, i;
+
+ err = reftable_new_stack(&st, dir, &opts);
+ check(!err);
+ for (i = -1; i != REFTABLE_EMPTY_TABLE_ERROR; i--) {
+ err = reftable_stack_add(st, write_error, &i);
+ check_int(err, ==, i);
+ }
+
+ reftable_stack_destroy(st);
+ clear_dir(dir);
+}
+
+static void t_reftable_stack_add(void)
+{
+ int err = 0;
+ struct reftable_write_options opts = {
+ .exact_log_message = 1,
+ .default_permissions = 0660,
+ .disable_auto_compact = 1,
+ };
+ struct reftable_stack *st = NULL;
+ char *dir = get_tmp_dir(__LINE__);
+ struct reftable_ref_record refs[2] = { 0 };
+ struct reftable_log_record logs[2] = { 0 };
+ struct strbuf path = STRBUF_INIT;
+ struct stat stat_result;
+ size_t i, N = ARRAY_SIZE(refs);
+
+ err = reftable_new_stack(&st, dir, &opts);
+ check(!err);
+
+ for (i = 0; i < N; i++) {
+ char buf[256];
+ snprintf(buf, sizeof(buf), "branch%02"PRIuMAX, (uintmax_t)i);
+ refs[i].refname = xstrdup(buf);
+ refs[i].update_index = i + 1;
+ refs[i].value_type = REFTABLE_REF_VAL1;
+ t_reftable_set_hash(refs[i].value.val1, i, GIT_SHA1_FORMAT_ID);
+
+ logs[i].refname = xstrdup(buf);
+ logs[i].update_index = N + i + 1;
+ logs[i].value_type = REFTABLE_LOG_UPDATE;
+ logs[i].value.update.email = xstrdup("identity@invalid");
+ t_reftable_set_hash(logs[i].value.update.new_hash, i, GIT_SHA1_FORMAT_ID);
+ }
+
+ for (i = 0; i < N; i++) {
+ int err = reftable_stack_add(st, write_test_ref, &refs[i]);
+ check(!err);
+ }
+
+ for (i = 0; i < N; i++) {
+ struct write_log_arg arg = {
+ .log = &logs[i],
+ .update_index = reftable_stack_next_update_index(st),
+ };
+ int err = reftable_stack_add(st, write_test_log, &arg);
+ check(!err);
+ }
+
+ err = reftable_stack_compact_all(st, NULL);
+ check(!err);
+
+ for (i = 0; i < N; i++) {
+ struct reftable_ref_record dest = { 0 };
+
+ int err = reftable_stack_read_ref(st, refs[i].refname, &dest);
+ check(!err);
+ check(reftable_ref_record_equal(&dest, refs + i,
+ GIT_SHA1_RAWSZ));
+ reftable_ref_record_release(&dest);
+ }
+
+ for (i = 0; i < N; i++) {
+ struct reftable_log_record dest = { 0 };
+ int err = reftable_stack_read_log(st, refs[i].refname, &dest);
+ check(!err);
+ check(reftable_log_record_equal(&dest, logs + i,
+ GIT_SHA1_RAWSZ));
+ reftable_log_record_release(&dest);
+ }
+
+#ifndef GIT_WINDOWS_NATIVE
+ strbuf_addstr(&path, dir);
+ strbuf_addstr(&path, "/tables.list");
+ err = stat(path.buf, &stat_result);
+ check(!err);
+ check_int((stat_result.st_mode & 0777), ==, opts.default_permissions);
+
+ strbuf_reset(&path);
+ strbuf_addstr(&path, dir);
+ strbuf_addstr(&path, "/");
+ /* do not try at home; not an external API for reftable. */
+ strbuf_addstr(&path, st->readers[0]->name);
+ err = stat(path.buf, &stat_result);
+ check(!err);
+ check_int((stat_result.st_mode & 0777), ==, opts.default_permissions);
+#else
+ (void) stat_result;
+#endif
+
+ /* cleanup */
+ reftable_stack_destroy(st);
+ for (i = 0; i < N; i++) {
+ reftable_ref_record_release(&refs[i]);
+ reftable_log_record_release(&logs[i]);
+ }
+ strbuf_release(&path);
+ clear_dir(dir);
+}
+
+static void t_reftable_stack_iterator(void)
+{
+ struct reftable_write_options opts = { 0 };
+ struct reftable_stack *st = NULL;
+ char *dir = get_tmp_dir(__LINE__);
+ struct reftable_ref_record refs[10] = { 0 };
+ struct reftable_log_record logs[10] = { 0 };
+ struct reftable_iterator it = { 0 };
+ size_t N = ARRAY_SIZE(refs), i;
+ int err;
+
+ err = reftable_new_stack(&st, dir, &opts);
+ check(!err);
+
+ for (i = 0; i < N; i++) {
+ refs[i].refname = xstrfmt("branch%02"PRIuMAX, (uintmax_t)i);
+ refs[i].update_index = i + 1;
+ refs[i].value_type = REFTABLE_REF_VAL1;
+ t_reftable_set_hash(refs[i].value.val1, i, GIT_SHA1_FORMAT_ID);
+
+ logs[i].refname = xstrfmt("branch%02"PRIuMAX, (uintmax_t)i);
+ logs[i].update_index = i + 1;
+ logs[i].value_type = REFTABLE_LOG_UPDATE;
+ logs[i].value.update.email = xstrdup("johndoe@invalid");
+ logs[i].value.update.message = xstrdup("commit\n");
+ t_reftable_set_hash(logs[i].value.update.new_hash, i, GIT_SHA1_FORMAT_ID);
+ }
+
+ for (i = 0; i < N; i++) {
+ err = reftable_stack_add(st, write_test_ref, &refs[i]);
+ check(!err);
+ }
+
+ for (i = 0; i < N; i++) {
+ struct write_log_arg arg = {
+ .log = &logs[i],
+ .update_index = reftable_stack_next_update_index(st),
+ };
+
+ err = reftable_stack_add(st, write_test_log, &arg);
+ check(!err);
+ }
+
+ reftable_stack_init_ref_iterator(st, &it);
+ reftable_iterator_seek_ref(&it, refs[0].refname);
+ for (i = 0; ; i++) {
+ struct reftable_ref_record ref = { 0 };
+
+ err = reftable_iterator_next_ref(&it, &ref);
+ if (err > 0)
+ break;
+ check(!err);
+ check(reftable_ref_record_equal(&ref, &refs[i], GIT_SHA1_RAWSZ));
+ reftable_ref_record_release(&ref);
+ }
+ check_int(i, ==, N);
+
+ reftable_iterator_destroy(&it);
+
+ reftable_stack_init_log_iterator(st, &it);
+ reftable_iterator_seek_log(&it, logs[0].refname);
+ for (i = 0; ; i++) {
+ struct reftable_log_record log = { 0 };
+
+ err = reftable_iterator_next_log(&it, &log);
+ if (err > 0)
+ break;
+ check(!err);
+ check(reftable_log_record_equal(&log, &logs[i], GIT_SHA1_RAWSZ));
+ reftable_log_record_release(&log);
+ }
+ check_int(i, ==, N);
+
+ reftable_stack_destroy(st);
+ reftable_iterator_destroy(&it);
+ for (i = 0; i < N; i++) {
+ reftable_ref_record_release(&refs[i]);
+ reftable_log_record_release(&logs[i]);
+ }
+ clear_dir(dir);
+}
+
+static void t_reftable_stack_log_normalize(void)
+{
+ int err = 0;
+ struct reftable_write_options opts = {
+ 0,
+ };
+ struct reftable_stack *st = NULL;
+ char *dir = get_tmp_dir(__LINE__);
+ struct reftable_log_record input = {
+ .refname = (char *) "branch",
+ .update_index = 1,
+ .value_type = REFTABLE_LOG_UPDATE,
+ .value = {
+ .update = {
+ .new_hash = { 1 },
+ .old_hash = { 2 },
+ },
+ },
+ };
+ struct reftable_log_record dest = {
+ .update_index = 0,
+ };
+ struct write_log_arg arg = {
+ .log = &input,
+ .update_index = 1,
+ };
+
+ err = reftable_new_stack(&st, dir, &opts);
+ check(!err);
+
+ input.value.update.message = (char *) "one\ntwo";
+ err = reftable_stack_add(st, write_test_log, &arg);
+ check_int(err, ==, REFTABLE_API_ERROR);
+
+ input.value.update.message = (char *) "one";
+ err = reftable_stack_add(st, write_test_log, &arg);
+ check(!err);
+
+ err = reftable_stack_read_log(st, input.refname, &dest);
+ check(!err);
+ check_str(dest.value.update.message, "one\n");
+
+ input.value.update.message = (char *) "two\n";
+ arg.update_index = 2;
+ err = reftable_stack_add(st, write_test_log, &arg);
+ check(!err);
+ err = reftable_stack_read_log(st, input.refname, &dest);
+ check(!err);
+ check_str(dest.value.update.message, "two\n");
+
+ /* cleanup */
+ reftable_stack_destroy(st);
+ reftable_log_record_release(&dest);
+ clear_dir(dir);
+}
+
+static void t_reftable_stack_tombstone(void)
+{
+ char *dir = get_tmp_dir(__LINE__);
+ struct reftable_write_options opts = { 0 };
+ struct reftable_stack *st = NULL;
+ int err;
+ struct reftable_ref_record refs[2] = { 0 };
+ struct reftable_log_record logs[2] = { 0 };
+ size_t i, N = ARRAY_SIZE(refs);
+ struct reftable_ref_record dest = { 0 };
+ struct reftable_log_record log_dest = { 0 };
+
+ err = reftable_new_stack(&st, dir, &opts);
+ check(!err);
+
+ /* even entries add the refs, odd entries delete them. */
+ for (i = 0; i < N; i++) {
+ const char *buf = "branch";
+ refs[i].refname = xstrdup(buf);
+ refs[i].update_index = i + 1;
+ if (i % 2 == 0) {
+ refs[i].value_type = REFTABLE_REF_VAL1;
+ t_reftable_set_hash(refs[i].value.val1, i,
+ GIT_SHA1_FORMAT_ID);
+ }
+
+ logs[i].refname = xstrdup(buf);
+ /* update_index is part of the key. */
+ logs[i].update_index = 42;
+ if (i % 2 == 0) {
+ logs[i].value_type = REFTABLE_LOG_UPDATE;
+ t_reftable_set_hash(logs[i].value.update.new_hash, i,
+ GIT_SHA1_FORMAT_ID);
+ logs[i].value.update.email =
+ xstrdup("identity@invalid");
+ }
+ }
+ for (i = 0; i < N; i++) {
+ int err = reftable_stack_add(st, write_test_ref, &refs[i]);
+ check(!err);
+ }
+
+ for (i = 0; i < N; i++) {
+ struct write_log_arg arg = {
+ .log = &logs[i],
+ .update_index = reftable_stack_next_update_index(st),
+ };
+ int err = reftable_stack_add(st, write_test_log, &arg);
+ check(!err);
+ }
+
+ err = reftable_stack_read_ref(st, "branch", &dest);
+ check_int(err, ==, 1);
+ reftable_ref_record_release(&dest);
+
+ err = reftable_stack_read_log(st, "branch", &log_dest);
+ check_int(err, ==, 1);
+ reftable_log_record_release(&log_dest);
+
+ err = reftable_stack_compact_all(st, NULL);
+ check(!err);
+
+ err = reftable_stack_read_ref(st, "branch", &dest);
+ check_int(err, ==, 1);
+
+ err = reftable_stack_read_log(st, "branch", &log_dest);
+ check_int(err, ==, 1);
+ reftable_ref_record_release(&dest);
+ reftable_log_record_release(&log_dest);
+
+ /* cleanup */
+ reftable_stack_destroy(st);
+ for (i = 0; i < N; i++) {
+ reftable_ref_record_release(&refs[i]);
+ reftable_log_record_release(&logs[i]);
+ }
+ clear_dir(dir);
+}
+
+static void t_reftable_stack_hash_id(void)
+{
+ char *dir = get_tmp_dir(__LINE__);
+ struct reftable_write_options opts = { 0 };
+ struct reftable_stack *st = NULL;
+ int err;
+
+ struct reftable_ref_record ref = {
+ .refname = (char *) "master",
+ .value_type = REFTABLE_REF_SYMREF,
+ .value.symref = (char *) "target",
+ .update_index = 1,
+ };
+ struct reftable_write_options opts32 = { .hash_id = GIT_SHA256_FORMAT_ID };
+ struct reftable_stack *st32 = NULL;
+ struct reftable_write_options opts_default = { 0 };
+ struct reftable_stack *st_default = NULL;
+ struct reftable_ref_record dest = { 0 };
+
+ err = reftable_new_stack(&st, dir, &opts);
+ check(!err);
+
+ err = reftable_stack_add(st, write_test_ref, &ref);
+ check(!err);
+
+ /* can't read it with the wrong hash ID. */
+ err = reftable_new_stack(&st32, dir, &opts32);
+ check_int(err, ==, REFTABLE_FORMAT_ERROR);
+
+ /* check that we can read it back with default opts too. */
+ err = reftable_new_stack(&st_default, dir, &opts_default);
+ check(!err);
+
+ err = reftable_stack_read_ref(st_default, "master", &dest);
+ check(!err);
+
+ check(reftable_ref_record_equal(&ref, &dest, GIT_SHA1_RAWSZ));
+ reftable_ref_record_release(&dest);
+ reftable_stack_destroy(st);
+ reftable_stack_destroy(st_default);
+ clear_dir(dir);
+}
+
+static void t_suggest_compaction_segment(void)
+{
+ uint64_t sizes[] = { 512, 64, 17, 16, 9, 9, 9, 16, 2, 16 };
+ struct segment min =
+ suggest_compaction_segment(sizes, ARRAY_SIZE(sizes), 2);
+ check_int(min.start, ==, 1);
+ check_int(min.end, ==, 10);
+}
+
+static void t_suggest_compaction_segment_nothing(void)
+{
+ uint64_t sizes[] = { 64, 32, 16, 8, 4, 2 };
+ struct segment result =
+ suggest_compaction_segment(sizes, ARRAY_SIZE(sizes), 2);
+ check_int(result.start, ==, result.end);
+}
+
+static void t_reflog_expire(void)
+{
+ char *dir = get_tmp_dir(__LINE__);
+ struct reftable_write_options opts = { 0 };
+ struct reftable_stack *st = NULL;
+ struct reftable_log_record logs[20] = { 0 };
+ size_t i, N = ARRAY_SIZE(logs) - 1;
+ int err;
+ struct reftable_log_expiry_config expiry = {
+ .time = 10,
+ };
+ struct reftable_log_record log = { 0 };
+
+ err = reftable_new_stack(&st, dir, &opts);
+ check(!err);
+
+ for (i = 1; i <= N; i++) {
+ char buf[256];
+ snprintf(buf, sizeof(buf), "branch%02"PRIuMAX, (uintmax_t)i);
+
+ logs[i].refname = xstrdup(buf);
+ logs[i].update_index = i;
+ logs[i].value_type = REFTABLE_LOG_UPDATE;
+ logs[i].value.update.time = i;
+ logs[i].value.update.email = xstrdup("identity@invalid");
+ t_reftable_set_hash(logs[i].value.update.new_hash, i,
+ GIT_SHA1_FORMAT_ID);
+ }
+
+ for (i = 1; i <= N; i++) {
+ struct write_log_arg arg = {
+ .log = &logs[i],
+ .update_index = reftable_stack_next_update_index(st),
+ };
+ int err = reftable_stack_add(st, write_test_log, &arg);
+ check(!err);
+ }
+
+ err = reftable_stack_compact_all(st, NULL);
+ check(!err);
+
+ err = reftable_stack_compact_all(st, &expiry);
+ check(!err);
+
+ err = reftable_stack_read_log(st, logs[9].refname, &log);
+ check_int(err, ==, 1);
+
+ err = reftable_stack_read_log(st, logs[11].refname, &log);
+ check(!err);
+
+ expiry.min_update_index = 15;
+ err = reftable_stack_compact_all(st, &expiry);
+ check(!err);
+
+ err = reftable_stack_read_log(st, logs[14].refname, &log);
+ check_int(err, ==, 1);
+
+ err = reftable_stack_read_log(st, logs[16].refname, &log);
+ check(!err);
+
+ /* cleanup */
+ reftable_stack_destroy(st);
+ for (i = 0; i <= N; i++)
+ reftable_log_record_release(&logs[i]);
+ clear_dir(dir);
+ reftable_log_record_release(&log);
+}
+
+static int write_nothing(struct reftable_writer *wr, void *arg UNUSED)
+{
+ reftable_writer_set_limits(wr, 1, 1);
+ return 0;
+}
+
+static void t_empty_add(void)
+{
+ struct reftable_write_options opts = { 0 };
+ struct reftable_stack *st = NULL;
+ int err;
+ char *dir = get_tmp_dir(__LINE__);
+ struct reftable_stack *st2 = NULL;
+
+ err = reftable_new_stack(&st, dir, &opts);
+ check(!err);
+
+ err = reftable_stack_add(st, write_nothing, NULL);
+ check(!err);
+
+ err = reftable_new_stack(&st2, dir, &opts);
+ check(!err);
+ clear_dir(dir);
+ reftable_stack_destroy(st);
+ reftable_stack_destroy(st2);
+}
+
+static int fastlogN(uint64_t sz, uint64_t N)
+{
+ int l = 0;
+ if (sz == 0)
+ return 0;
+ for (; sz; sz /= N)
+ l++;
+ return l - 1;
+}
+
+static void t_reftable_stack_auto_compaction(void)
+{
+ struct reftable_write_options opts = {
+ .disable_auto_compact = 1,
+ };
+ struct reftable_stack *st = NULL;
+ char *dir = get_tmp_dir(__LINE__);
+ int err;
+ size_t i, N = 100;
+
+ err = reftable_new_stack(&st, dir, &opts);
+ check(!err);
+
+ for (i = 0; i < N; i++) {
+ char name[100];
+ struct reftable_ref_record ref = {
+ .refname = name,
+ .update_index = reftable_stack_next_update_index(st),
+ .value_type = REFTABLE_REF_SYMREF,
+ .value.symref = (char *) "master",
+ };
+ snprintf(name, sizeof(name), "branch%04"PRIuMAX, (uintmax_t)i);
+
+ err = reftable_stack_add(st, write_test_ref, &ref);
+ check(!err);
+
+ err = reftable_stack_auto_compact(st);
+ check(!err);
+ check(i < 2 || st->merged->readers_len < 2 * fastlogN(i, 2));
+ }
+
+ check_int(reftable_stack_compaction_stats(st)->entries_written, <,
+ (uint64_t)(N * fastlogN(N, 2)));
+
+ reftable_stack_destroy(st);
+ clear_dir(dir);
+}
+
+static void t_reftable_stack_auto_compaction_factor(void)
+{
+ struct reftable_write_options opts = {
+ .auto_compaction_factor = 5,
+ };
+ struct reftable_stack *st = NULL;
+ char *dir = get_tmp_dir(__LINE__);
+ int err;
+ size_t N = 100;
+
+ err = reftable_new_stack(&st, dir, &opts);
+ check(!err);
+
+ for (size_t i = 0; i < N; i++) {
+ char name[20];
+ struct reftable_ref_record ref = {
+ .refname = name,
+ .update_index = reftable_stack_next_update_index(st),
+ .value_type = REFTABLE_REF_VAL1,
+ };
+ xsnprintf(name, sizeof(name), "branch%04"PRIuMAX, (uintmax_t)i);
+
+ err = reftable_stack_add(st, &write_test_ref, &ref);
+ check(!err);
+
+ check(i < 5 || st->merged->readers_len < 5 * fastlogN(i, 5));
+ }
+
+ reftable_stack_destroy(st);
+ clear_dir(dir);
+}
+
+static void t_reftable_stack_auto_compaction_with_locked_tables(void)
+{
+ struct reftable_write_options opts = {
+ .disable_auto_compact = 1,
+ };
+ struct reftable_stack *st = NULL;
+ struct strbuf buf = STRBUF_INIT;
+ char *dir = get_tmp_dir(__LINE__);
+ int err;
+
+ err = reftable_new_stack(&st, dir, &opts);
+ check(!err);
+
+ write_n_ref_tables(st, 5);
+ check_int(st->merged->readers_len, ==, 5);
+
+ /*
+ * Given that all tables we have written should be roughly the same
+ * size, we expect that auto-compaction will want to compact all of the
+ * tables. Locking any of the tables will keep it from doing so.
+ */
+ strbuf_reset(&buf);
+ strbuf_addf(&buf, "%s/%s.lock", dir, st->readers[2]->name);
+ write_file_buf(buf.buf, "", 0);
+
+ /*
+ * When parts of the stack are locked, then auto-compaction does a best
+ * effort compaction of those tables which aren't locked. So while this
+ * would in theory compact all tables, due to the preexisting lock we
+ * only compact the newest two tables.
+ */
+ err = reftable_stack_auto_compact(st);
+ check(!err);
+ check_int(st->stats.failures, ==, 0);
+ check_int(st->merged->readers_len, ==, 4);
+
+ reftable_stack_destroy(st);
+ strbuf_release(&buf);
+ clear_dir(dir);
+}
+
+static void t_reftable_stack_add_performs_auto_compaction(void)
+{
+ struct reftable_write_options opts = { 0 };
+ struct reftable_stack *st = NULL;
+ struct strbuf refname = STRBUF_INIT;
+ char *dir = get_tmp_dir(__LINE__);
+ int err;
+ size_t i, n = 20;
+
+ err = reftable_new_stack(&st, dir, &opts);
+ check(!err);
+
+ for (i = 0; i <= n; i++) {
+ struct reftable_ref_record ref = {
+ .update_index = reftable_stack_next_update_index(st),
+ .value_type = REFTABLE_REF_SYMREF,
+ .value.symref = (char *) "master",
+ };
+
+ /*
+ * Disable auto-compaction for all but the last runs. Like this
+ * we can ensure that we indeed honor this setting and have
+ * better control over when exactly auto compaction runs.
+ */
+ st->opts.disable_auto_compact = i != n;
+
+ strbuf_reset(&refname);
+ strbuf_addf(&refname, "branch-%04"PRIuMAX, (uintmax_t)i);
+ ref.refname = refname.buf;
+
+ err = reftable_stack_add(st, write_test_ref, &ref);
+ check(!err);
+
+ /*
+ * The stack length should grow continuously for all runs where
+ * auto compaction is disabled. When enabled, we should merge
+ * all tables in the stack.
+ */
+ if (i != n)
+ check_int(st->merged->readers_len, ==, i + 1);
+ else
+ check_int(st->merged->readers_len, ==, 1);
+ }
+
+ reftable_stack_destroy(st);
+ strbuf_release(&refname);
+ clear_dir(dir);
+}
+
+static void t_reftable_stack_compaction_with_locked_tables(void)
+{
+ struct reftable_write_options opts = {
+ .disable_auto_compact = 1,
+ };
+ struct reftable_stack *st = NULL;
+ struct strbuf buf = STRBUF_INIT;
+ char *dir = get_tmp_dir(__LINE__);
+ int err;
+
+ err = reftable_new_stack(&st, dir, &opts);
+ check(!err);
+
+ write_n_ref_tables(st, 3);
+ check_int(st->merged->readers_len, ==, 3);
+
+ /* Lock one of the tables that we're about to compact. */
+ strbuf_reset(&buf);
+ strbuf_addf(&buf, "%s/%s.lock", dir, st->readers[1]->name);
+ write_file_buf(buf.buf, "", 0);
+
+ /*
+ * Compaction is expected to fail given that we were not able to
+ * compact all tables.
+ */
+ err = reftable_stack_compact_all(st, NULL);
+ check_int(err, ==, REFTABLE_LOCK_ERROR);
+ check_int(st->stats.failures, ==, 1);
+ check_int(st->merged->readers_len, ==, 3);
+
+ reftable_stack_destroy(st);
+ strbuf_release(&buf);
+ clear_dir(dir);
+}
+
+static void t_reftable_stack_compaction_concurrent(void)
+{
+ struct reftable_write_options opts = { 0 };
+ struct reftable_stack *st1 = NULL, *st2 = NULL;
+ char *dir = get_tmp_dir(__LINE__);
+ int err;
+
+ err = reftable_new_stack(&st1, dir, &opts);
+ check(!err);
+ write_n_ref_tables(st1, 3);
+
+ err = reftable_new_stack(&st2, dir, &opts);
+ check(!err);
+
+ err = reftable_stack_compact_all(st1, NULL);
+ check(!err);
+
+ reftable_stack_destroy(st1);
+ reftable_stack_destroy(st2);
+
+ check_int(count_dir_entries(dir), ==, 2);
+ clear_dir(dir);
+}
+
+static void unclean_stack_close(struct reftable_stack *st)
+{
+ /* break abstraction boundary to simulate unclean shutdown. */
+ for (size_t i = 0; i < st->readers_len; i++)
+ reftable_reader_decref(st->readers[i]);
+ st->readers_len = 0;
+ FREE_AND_NULL(st->readers);
+}
+
+static void t_reftable_stack_compaction_concurrent_clean(void)
+{
+ struct reftable_write_options opts = { 0 };
+ struct reftable_stack *st1 = NULL, *st2 = NULL, *st3 = NULL;
+ char *dir = get_tmp_dir(__LINE__);
+ int err;
+
+ err = reftable_new_stack(&st1, dir, &opts);
+ check(!err);
+ write_n_ref_tables(st1, 3);
+
+ err = reftable_new_stack(&st2, dir, &opts);
+ check(!err);
+
+ err = reftable_stack_compact_all(st1, NULL);
+ check(!err);
+
+ unclean_stack_close(st1);
+ unclean_stack_close(st2);
+
+ err = reftable_new_stack(&st3, dir, &opts);
+ check(!err);
+
+ err = reftable_stack_clean(st3);
+ check(!err);
+ check_int(count_dir_entries(dir), ==, 2);
+
+ reftable_stack_destroy(st1);
+ reftable_stack_destroy(st2);
+ reftable_stack_destroy(st3);
+
+ clear_dir(dir);
+}
+
+static void t_reftable_stack_read_across_reload(void)
+{
+ struct reftable_write_options opts = { 0 };
+ struct reftable_stack *st1 = NULL, *st2 = NULL;
+ struct reftable_ref_record rec = { 0 };
+ struct reftable_iterator it = { 0 };
+ char *dir = get_tmp_dir(__LINE__);
+ int err;
+
+ /* Create a first stack and set up an iterator for it. */
+ err = reftable_new_stack(&st1, dir, &opts);
+ check(!err);
+ write_n_ref_tables(st1, 2);
+ check_int(st1->merged->readers_len, ==, 2);
+ reftable_stack_init_ref_iterator(st1, &it);
+ err = reftable_iterator_seek_ref(&it, "");
+ check(!err);
+
+ /* Set up a second stack for the same directory and compact it. */
+ err = reftable_new_stack(&st2, dir, &opts);
+ check(!err);
+ check_int(st2->merged->readers_len, ==, 2);
+ err = reftable_stack_compact_all(st2, NULL);
+ check(!err);
+ check_int(st2->merged->readers_len, ==, 1);
+
+ /*
+ * Verify that we can continue to use the old iterator even after we
+ * have reloaded its stack.
+ */
+ err = reftable_stack_reload(st1);
+ check(!err);
+ check_int(st1->merged->readers_len, ==, 1);
+ err = reftable_iterator_next_ref(&it, &rec);
+ check(!err);
+ check_str(rec.refname, "refs/heads/branch-0000");
+ err = reftable_iterator_next_ref(&it, &rec);
+ check(!err);
+ check_str(rec.refname, "refs/heads/branch-0001");
+ err = reftable_iterator_next_ref(&it, &rec);
+ check_int(err, >, 0);
+
+ reftable_ref_record_release(&rec);
+ reftable_iterator_destroy(&it);
+ reftable_stack_destroy(st1);
+ reftable_stack_destroy(st2);
+ clear_dir(dir);
+}
+
+static void t_reftable_stack_reload_with_missing_table(void)
+{
+ struct reftable_write_options opts = { 0 };
+ struct reftable_stack *st = NULL;
+ struct reftable_ref_record rec = { 0 };
+ struct reftable_iterator it = { 0 };
+ struct strbuf table_path = STRBUF_INIT, content = STRBUF_INIT;
+ char *dir = get_tmp_dir(__LINE__);
+ int err;
+
+ /* Create a first stack and set up an iterator for it. */
+ err = reftable_new_stack(&st, dir, &opts);
+ check(!err);
+ write_n_ref_tables(st, 2);
+ check_int(st->merged->readers_len, ==, 2);
+ reftable_stack_init_ref_iterator(st, &it);
+ err = reftable_iterator_seek_ref(&it, "");
+ check(!err);
+
+ /*
+ * Update the tables.list file with some garbage data, while reusing
+ * our old readers. This should trigger a partial reload of the stack,
+ * where we try to reuse our old readers.
+ */
+ strbuf_addf(&content, "%s\n", st->readers[0]->name);
+ strbuf_addf(&content, "%s\n", st->readers[1]->name);
+ strbuf_addstr(&content, "garbage\n");
+ strbuf_addf(&table_path, "%s.lock", st->list_file);
+ write_file_buf(table_path.buf, content.buf, content.len);
+ err = rename(table_path.buf, st->list_file);
+ check(!err);
+
+ err = reftable_stack_reload(st);
+ check_int(err, ==, -4);
+ check_int(st->merged->readers_len, ==, 2);
+
+ /*
+ * Even though the reload has failed, we should be able to continue
+ * using the iterator.
+ */
+ err = reftable_iterator_next_ref(&it, &rec);
+ check(!err);
+ check_str(rec.refname, "refs/heads/branch-0000");
+ err = reftable_iterator_next_ref(&it, &rec);
+ check(!err);
+ check_str(rec.refname, "refs/heads/branch-0001");
+ err = reftable_iterator_next_ref(&it, &rec);
+ check_int(err, >, 0);
+
+ reftable_ref_record_release(&rec);
+ reftable_iterator_destroy(&it);
+ reftable_stack_destroy(st);
+ strbuf_release(&table_path);
+ strbuf_release(&content);
+ clear_dir(dir);
+}
+
+int cmd_main(int argc UNUSED, const char *argv[] UNUSED)
+{
+ TEST(t_empty_add(), "empty addition to stack");
+ TEST(t_read_file(), "read_lines works");
+ TEST(t_reflog_expire(), "expire reflog entries");
+ TEST(t_reftable_stack_add(), "add multiple refs and logs to stack");
+ TEST(t_reftable_stack_add_one(), "add a single ref record to stack");
+ TEST(t_reftable_stack_add_performs_auto_compaction(), "addition to stack triggers auto-compaction");
+ TEST(t_reftable_stack_auto_compaction(), "stack must form geometric sequence after compaction");
+ TEST(t_reftable_stack_auto_compaction_factor(), "auto-compaction with non-default geometric factor");
+ TEST(t_reftable_stack_auto_compaction_fails_gracefully(), "failure on auto-compaction");
+ TEST(t_reftable_stack_auto_compaction_with_locked_tables(), "auto compaction with locked tables");
+ TEST(t_reftable_stack_compaction_concurrent(), "compaction with concurrent stack");
+ TEST(t_reftable_stack_compaction_concurrent_clean(), "compaction with unclean stack shutdown");
+ TEST(t_reftable_stack_compaction_with_locked_tables(), "compaction with locked tables");
+ TEST(t_reftable_stack_hash_id(), "read stack with wrong hash ID");
+ TEST(t_reftable_stack_iterator(), "log and ref iterator for reftable stack");
+ TEST(t_reftable_stack_lock_failure(), "stack addition with lockfile failure");
+ TEST(t_reftable_stack_log_normalize(), "log messages should be normalized");
+ TEST(t_reftable_stack_read_across_reload(), "stack iterators work across reloads");
+ TEST(t_reftable_stack_reload_with_missing_table(), "stack iteration with garbage tables");
+ TEST(t_reftable_stack_tombstone(), "'tombstone' refs in stack");
+ TEST(t_reftable_stack_transaction_api(), "update transaction to stack");
+ TEST(t_reftable_stack_transaction_with_reload(), "transaction with reload");
+ TEST(t_reftable_stack_transaction_api_performs_auto_compaction(), "update transaction triggers auto-compaction");
+ TEST(t_reftable_stack_update_index_check(), "update transactions with equal update indices");
+ TEST(t_reftable_stack_uptodate(), "stack must be reloaded before ref update");
+ TEST(t_suggest_compaction_segment(), "suggest_compaction_segment with basic input");
+ TEST(t_suggest_compaction_segment_nothing(), "suggest_compaction_segment with pre-compacted input");
+
+ return test_done();
+}
diff --git a/t/unit-tests/t-reftable-tree.c b/t/unit-tests/t-reftable-tree.c
new file mode 100644
index 0000000000..700479d34b
--- /dev/null
+++ b/t/unit-tests/t-reftable-tree.c
@@ -0,0 +1,84 @@
+/*
+Copyright 2020 Google LLC
+
+Use of this source code is governed by a BSD-style
+license that can be found in the LICENSE file or at
+https://developers.google.com/open-source/licenses/bsd
+*/
+
+#include "test-lib.h"
+#include "reftable/tree.h"
+
+static int t_compare(const void *a, const void *b)
+{
+ return (char *)a - (char *)b;
+}
+
+struct curry {
+ void **arr;
+ size_t len;
+};
+
+static void store(void *arg, void *key)
+{
+ struct curry *c = arg;
+ c->arr[c->len++] = key;
+}
+
+static void t_tree_search(void)
+{
+ struct tree_node *root = NULL;
+ void *values[11] = { 0 };
+ struct tree_node *nodes[11] = { 0 };
+ size_t i = 1;
+
+ /*
+ * Pseudo-randomly insert the pointers for elements between
+ * values[1] and values[10] (inclusive) in the tree.
+ */
+ do {
+ nodes[i] = tree_search(&values[i], &root, &t_compare, 1);
+ i = (i * 7) % 11;
+ } while (i != 1);
+
+ for (i = 1; i < ARRAY_SIZE(nodes); i++) {
+ check_pointer_eq(&values[i], nodes[i]->key);
+ check_pointer_eq(nodes[i], tree_search(&values[i], &root, &t_compare, 0));
+ }
+
+ check(!tree_search(values, &root, t_compare, 0));
+ tree_free(root);
+}
+
+static void t_infix_walk(void)
+{
+ struct tree_node *root = NULL;
+ void *values[11] = { 0 };
+ void *out[11] = { 0 };
+ struct curry c = {
+ .arr = (void **) &out,
+ };
+ size_t i = 1;
+ size_t count = 0;
+
+ do {
+ tree_search(&values[i], &root, t_compare, 1);
+ i = (i * 7) % 11;
+ count++;
+ } while (i != 1);
+
+ infix_walk(root, &store, &c);
+ for (i = 1; i < ARRAY_SIZE(values); i++)
+ check_pointer_eq(&values[i], out[i - 1]);
+ check(!out[i - 1]);
+ check_int(c.len, ==, count);
+ tree_free(root);
+}
+
+int cmd_main(int argc UNUSED, const char *argv[] UNUSED)
+{
+ TEST(t_tree_search(), "tree_search works");
+ TEST(t_infix_walk(), "infix_walk works");
+
+ return test_done();
+}
diff --git a/t/unit-tests/t-strbuf.c b/t/unit-tests/t-strbuf.c
index 6027dafef7..3f4044d697 100644
--- a/t/unit-tests/t-strbuf.c
+++ b/t/unit-tests/t-strbuf.c
@@ -105,7 +105,7 @@ static void t_addstr(struct strbuf *buf, const void *data)
check_str(buf->buf + orig_len, text);
}
-int cmd_main(int argc, const char **argv)
+int cmd_main(int argc UNUSED, const char **argv UNUSED)
{
if (!TEST(t_static_init(), "static initialization works"))
test_skip_all("STRBUF_INIT is broken");
diff --git a/t/unit-tests/t-strcmp-offset.c b/t/unit-tests/t-strcmp-offset.c
index fe4c2706b1..6880f21161 100644
--- a/t/unit-tests/t-strcmp-offset.c
+++ b/t/unit-tests/t-strcmp-offset.c
@@ -24,7 +24,7 @@ static void check_strcmp_offset(const char *string1, const char *string2,
expect_offset), \
"strcmp_offset(%s, %s) works", #string1, #string2)
-int cmd_main(int argc, const char **argv)
+int cmd_main(int argc UNUSED, const char **argv UNUSED)
{
TEST_STRCMP_OFFSET("abc", "abc", 0, 3);
TEST_STRCMP_OFFSET("abc", "def", -1, 0);
diff --git a/t/unit-tests/t-trailer.c b/t/unit-tests/t-trailer.c
index 2ecca359d9..e1c6ad7461 100644
--- a/t/unit-tests/t-trailer.c
+++ b/t/unit-tests/t-trailer.c
@@ -308,7 +308,7 @@ static void run_t_trailer_iterator(void)
}
}
-int cmd_main(int argc, const char **argv)
+int cmd_main(int argc UNUSED, const char **argv UNUSED)
{
run_t_trailer_iterator();
return test_done();
diff --git a/t/unit-tests/t-urlmatch-normalization.c b/t/unit-tests/t-urlmatch-normalization.c
new file mode 100644
index 0000000000..1769c357b9
--- /dev/null
+++ b/t/unit-tests/t-urlmatch-normalization.c
@@ -0,0 +1,271 @@
+#include "test-lib.h"
+#include "urlmatch.h"
+
+static void check_url_normalizable(const char *url, unsigned int normalizable)
+{
+ char *url_norm = url_normalize(url, NULL);
+
+ if (!check_int(normalizable, ==, url_norm ? 1 : 0))
+ test_msg("input url: %s", url);
+ free(url_norm);
+}
+
+static void check_normalized_url(const char *url, const char *expect)
+{
+ char *url_norm = url_normalize(url, NULL);
+
+ if (!check_str(url_norm, expect))
+ test_msg("input url: %s", url);
+ free(url_norm);
+}
+
+static void compare_normalized_urls(const char *url1, const char *url2,
+ unsigned int equal)
+{
+ char *url1_norm = url_normalize(url1, NULL);
+ char *url2_norm = url_normalize(url2, NULL);
+
+ if (equal) {
+ if (!check_str(url1_norm, url2_norm))
+ test_msg("input url1: %s\n input url2: %s", url1,
+ url2);
+ } else if (!check_int(strcmp(url1_norm, url2_norm), !=, 0)) {
+ test_msg(" normalized url1: %s\n normalized url2: %s\n"
+ " input url1: %s\n input url2: %s",
+ url1_norm, url2_norm, url1, url2);
+ }
+ free(url1_norm);
+ free(url2_norm);
+}
+
+static void check_normalized_url_length(const char *url, size_t len)
+{
+ struct url_info info;
+ char *url_norm = url_normalize(url, &info);
+
+ if (!check_int(info.url_len, ==, len))
+ test_msg(" input url: %s\n normalized url: %s", url,
+ url_norm);
+ free(url_norm);
+}
+
+/* Note that only "file:" URLs should be allowed without a host */
+static void t_url_scheme(void)
+{
+ check_url_normalizable("", 0);
+ check_url_normalizable("_", 0);
+ check_url_normalizable("scheme", 0);
+ check_url_normalizable("scheme:", 0);
+ check_url_normalizable("scheme:/", 0);
+ check_url_normalizable("scheme://", 0);
+ check_url_normalizable("file", 0);
+ check_url_normalizable("file:", 0);
+ check_url_normalizable("file:/", 0);
+ check_url_normalizable("file://", 1);
+ check_url_normalizable("://acme.co", 0);
+ check_url_normalizable("x_test://acme.co", 0);
+ check_url_normalizable("-test://acme.co", 0);
+ check_url_normalizable("0test://acme.co", 0);
+ check_url_normalizable("+test://acme.co", 0);
+ check_url_normalizable(".test://acme.co", 0);
+ check_url_normalizable("schem%6e://", 0);
+ check_url_normalizable("x-Test+v1.0://acme.co", 1);
+ check_normalized_url("AbCdeF://x.Y", "abcdef://x.y/");
+}
+
+static void t_url_authority(void)
+{
+ check_url_normalizable("scheme://user:pass@", 0);
+ check_url_normalizable("scheme://?", 0);
+ check_url_normalizable("scheme://#", 0);
+ check_url_normalizable("scheme:///", 0);
+ check_url_normalizable("scheme://:", 0);
+ check_url_normalizable("scheme://:555", 0);
+ check_url_normalizable("file://user:pass@", 1);
+ check_url_normalizable("file://?", 1);
+ check_url_normalizable("file://#", 1);
+ check_url_normalizable("file:///", 1);
+ check_url_normalizable("file://:", 1);
+ check_url_normalizable("file://:555", 0);
+ check_url_normalizable("scheme://user:pass@host", 1);
+ check_url_normalizable("scheme://@host", 1);
+ check_url_normalizable("scheme://%00@host", 1);
+ check_url_normalizable("scheme://%%@host", 0);
+ check_url_normalizable("scheme://host_", 1);
+ check_url_normalizable("scheme://user:pass@host/", 1);
+ check_url_normalizable("scheme://@host/", 1);
+ check_url_normalizable("scheme://host/", 1);
+ check_url_normalizable("scheme://host?x", 1);
+ check_url_normalizable("scheme://host#x", 1);
+ check_url_normalizable("scheme://host/@", 1);
+ check_url_normalizable("scheme://host?@x", 1);
+ check_url_normalizable("scheme://host#@x", 1);
+ check_url_normalizable("scheme://[::1]", 1);
+ check_url_normalizable("scheme://[::1]/", 1);
+ check_url_normalizable("scheme://hos%41/", 0);
+ check_url_normalizable("scheme://[invalid....:/", 1);
+ check_url_normalizable("scheme://invalid....:]/", 1);
+ check_url_normalizable("scheme://invalid....:[/", 0);
+ check_url_normalizable("scheme://invalid....:[", 0);
+}
+
+static void t_url_port(void)
+{
+ check_url_normalizable("xyz://q@some.host:", 1);
+ check_url_normalizable("xyz://q@some.host:456/", 1);
+ check_url_normalizable("xyz://q@some.host:0", 0);
+ check_url_normalizable("xyz://q@some.host:0000000", 0);
+ check_url_normalizable("xyz://q@some.host:0000001?", 1);
+ check_url_normalizable("xyz://q@some.host:065535#", 1);
+ check_url_normalizable("xyz://q@some.host:65535", 1);
+ check_url_normalizable("xyz://q@some.host:65536", 0);
+ check_url_normalizable("xyz://q@some.host:99999", 0);
+ check_url_normalizable("xyz://q@some.host:100000", 0);
+ check_url_normalizable("xyz://q@some.host:100001", 0);
+ check_url_normalizable("http://q@some.host:80", 1);
+ check_url_normalizable("https://q@some.host:443", 1);
+ check_url_normalizable("http://q@some.host:80/", 1);
+ check_url_normalizable("https://q@some.host:443?", 1);
+ check_url_normalizable("http://q@:8008", 0);
+ check_url_normalizable("http://:8080", 0);
+ check_url_normalizable("http://:", 0);
+ check_url_normalizable("xyz://q@some.host:456/", 1);
+ check_url_normalizable("xyz://[::1]:456/", 1);
+ check_url_normalizable("xyz://[::1]:/", 1);
+ check_url_normalizable("xyz://[::1]:000/", 0);
+ check_url_normalizable("xyz://[::1]:0%300/", 0);
+ check_url_normalizable("xyz://[::1]:0x80/", 0);
+ check_url_normalizable("xyz://[::1]:4294967297/", 0);
+ check_url_normalizable("xyz://[::1]:030f/", 0);
+}
+
+static void t_url_port_normalization(void)
+{
+ check_normalized_url("http://x:800", "http://x:800/");
+ check_normalized_url("http://x:0800", "http://x:800/");
+ check_normalized_url("http://x:00000800", "http://x:800/");
+ check_normalized_url("http://x:065535", "http://x:65535/");
+ check_normalized_url("http://x:1", "http://x:1/");
+ check_normalized_url("http://x:80", "http://x/");
+ check_normalized_url("http://x:080", "http://x/");
+ check_normalized_url("http://x:000000080", "http://x/");
+ check_normalized_url("https://x:443", "https://x/");
+ check_normalized_url("https://x:0443", "https://x/");
+ check_normalized_url("https://x:000000443", "https://x/");
+}
+
+static void t_url_general_escape(void)
+{
+ check_url_normalizable("http://x.y?%fg", 0);
+ check_normalized_url("X://W/%7e%41^%3a", "x://w/~A%5E%3A");
+ check_normalized_url("X://W/:/?#[]@", "x://w/:/?#[]@");
+ check_normalized_url("X://W/$&()*+,;=", "x://w/$&()*+,;=");
+ check_normalized_url("X://W/'", "x://w/'");
+ check_normalized_url("X://W?!", "x://w/?!");
+}
+
+static void t_url_high_bit(void)
+{
+ check_normalized_url(
+ "x://q/\x01\x02\x03\x04\x05\x06\x07\x08\x0e\x0f\x10\x11\x12",
+ "x://q/%01%02%03%04%05%06%07%08%0E%0F%10%11%12");
+ check_normalized_url(
+ "x://q/\x13\x14\x15\x16\x17\x18\x19\x1b\x1c\x1d\x1e\x1f\x7f",
+ "x://q/%13%14%15%16%17%18%19%1B%1C%1D%1E%1F%7F");
+ check_normalized_url(
+ "x://q/\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f",
+ "x://q/%80%81%82%83%84%85%86%87%88%89%8A%8B%8C%8D%8E%8F");
+ check_normalized_url(
+ "x://q/\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f",
+ "x://q/%90%91%92%93%94%95%96%97%98%99%9A%9B%9C%9D%9E%9F");
+ check_normalized_url(
+ "x://q/\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf",
+ "x://q/%A0%A1%A2%A3%A4%A5%A6%A7%A8%A9%AA%AB%AC%AD%AE%AF");
+ check_normalized_url(
+ "x://q/\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf",
+ "x://q/%B0%B1%B2%B3%B4%B5%B6%B7%B8%B9%BA%BB%BC%BD%BE%BF");
+ check_normalized_url(
+ "x://q/\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf",
+ "x://q/%C0%C1%C2%C3%C4%C5%C6%C7%C8%C9%CA%CB%CC%CD%CE%CF");
+ check_normalized_url(
+ "x://q/\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf",
+ "x://q/%D0%D1%D2%D3%D4%D5%D6%D7%D8%D9%DA%DB%DC%DD%DE%DF");
+ check_normalized_url(
+ "x://q/\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef",
+ "x://q/%E0%E1%E2%E3%E4%E5%E6%E7%E8%E9%EA%EB%EC%ED%EE%EF");
+ check_normalized_url(
+ "x://q/\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff",
+ "x://q/%F0%F1%F2%F3%F4%F5%F6%F7%F8%F9%FA%FB%FC%FD%FE%FF");
+}
+
+static void t_url_utf8_escape(void)
+{
+ check_normalized_url(
+ "x://q/\xc2\x80\xdf\xbf\xe0\xa0\x80\xef\xbf\xbd\xf0\x90\x80\x80\xf0\xaf\xbf\xbd",
+ "x://q/%C2%80%DF%BF%E0%A0%80%EF%BF%BD%F0%90%80%80%F0%AF%BF%BD");
+}
+
+static void t_url_username_pass(void)
+{
+ check_normalized_url("x://%41%62(^):%70+d@foo", "x://Ab(%5E):p+d@foo/");
+}
+
+static void t_url_length(void)
+{
+ check_normalized_url_length("Http://%4d%65:%4d^%70@The.Host", 25);
+ check_normalized_url_length("http://%41:%42@x.y/%61/", 17);
+ check_normalized_url_length("http://@x.y/^", 15);
+}
+
+static void t_url_dots(void)
+{
+ check_normalized_url("x://y/.", "x://y/");
+ check_normalized_url("x://y/./", "x://y/");
+ check_normalized_url("x://y/a/.", "x://y/a");
+ check_normalized_url("x://y/a/./", "x://y/a/");
+ check_normalized_url("x://y/.?", "x://y/?");
+ check_normalized_url("x://y/./?", "x://y/?");
+ check_normalized_url("x://y/a/.?", "x://y/a?");
+ check_normalized_url("x://y/a/./?", "x://y/a/?");
+ check_normalized_url("x://y/a/./b/.././../c", "x://y/c");
+ check_normalized_url("x://y/a/./b/../.././c/", "x://y/c/");
+ check_normalized_url("x://y/a/./b/.././../c/././.././.", "x://y/");
+ check_url_normalizable("x://y/a/./b/.././../c/././.././..", 0);
+ check_normalized_url("x://y/a/./?/././..", "x://y/a/?/././..");
+ check_normalized_url("x://y/%2e/", "x://y/");
+ check_normalized_url("x://y/%2E/", "x://y/");
+ check_normalized_url("x://y/a/%2e./", "x://y/");
+ check_normalized_url("x://y/b/.%2E/", "x://y/");
+ check_normalized_url("x://y/c/%2e%2E/", "x://y/");
+}
+
+/*
+ * "http://@foo" specifies an empty user name but does not specify a password.
+ * "http://foo" specifies neither a user name nor a password.
+ * So they should not be equivalent.
+ */
+static void t_url_equivalents(void)
+{
+ compare_normalized_urls("httP://x", "Http://X/", 1);
+ compare_normalized_urls("Http://%4d%65:%4d^%70@The.Host", "hTTP://Me:%4D^p@the.HOST:80/", 1);
+ compare_normalized_urls("https://@x.y/^", "httpS://x.y:443/^", 0);
+ compare_normalized_urls("https://@x.y/^", "httpS://@x.y:0443/^", 1);
+ compare_normalized_urls("https://@x.y/^/../abc", "httpS://@x.y:0443/abc", 1);
+ compare_normalized_urls("https://@x.y/^/..", "httpS://@x.y:0443/", 1);
+}
+
+int cmd_main(int argc UNUSED, const char **argv UNUSED)
+{
+ TEST(t_url_scheme(), "url scheme");
+ TEST(t_url_authority(), "url authority");
+ TEST(t_url_port(), "url port checks");
+ TEST(t_url_port_normalization(), "url port normalization");
+ TEST(t_url_general_escape(), "url general escapes");
+ TEST(t_url_high_bit(), "url high-bit escapes");
+ TEST(t_url_utf8_escape(), "url utf8 escapes");
+ TEST(t_url_username_pass(), "url username/password escapes");
+ TEST(t_url_length(), "url normalized lengths");
+ TEST(t_url_dots(), "url . and .. segments");
+ TEST(t_url_equivalents(), "url equivalents");
+ return test_done();
+}
diff --git a/t/unit-tests/test-lib.c b/t/unit-tests/test-lib.c
index 3c513ce59a..fa1f95965c 100644
--- a/t/unit-tests/test-lib.c
+++ b/t/unit-tests/test-lib.c
@@ -16,6 +16,8 @@ static struct {
unsigned running :1;
unsigned skip_all :1;
unsigned todo :1;
+ char location[100];
+ char description[100];
} ctx = {
.lazy_plan = 1,
.result = RESULT_NONE,
@@ -125,6 +127,8 @@ void test_plan(int count)
int test_done(void)
{
+ if (ctx.running && ctx.location[0] && ctx.description[0])
+ test__run_end(1, ctx.location, "%s", ctx.description);
assert(!ctx.running);
if (ctx.lazy_plan)
@@ -167,13 +171,38 @@ void test_skip_all(const char *format, ...)
va_end(ap);
}
+void test__run_describe(const char *location, const char *format, ...)
+{
+ va_list ap;
+ int len;
+
+ assert(ctx.running);
+ assert(!ctx.location[0]);
+ assert(!ctx.description[0]);
+
+ xsnprintf(ctx.location, sizeof(ctx.location), "%s",
+ make_relative(location));
+
+ va_start(ap, format);
+ len = vsnprintf(ctx.description, sizeof(ctx.description), format, ap);
+ va_end(ap);
+ if (len < 0)
+ die("unable to format message: %s", format);
+ if (len >= sizeof(ctx.description))
+ BUG("ctx.description too small to format %s", format);
+}
+
int test__run_begin(void)
{
+ if (ctx.running && ctx.location[0] && ctx.description[0])
+ test__run_end(1, ctx.location, "%s", ctx.description);
assert(!ctx.running);
ctx.count++;
ctx.result = RESULT_NONE;
ctx.running = 1;
+ ctx.location[0] = '\0';
+ ctx.description[0] = '\0';
return ctx.skip_all;
}
@@ -264,7 +293,12 @@ static void test_todo(void)
int test_assert(const char *location, const char *check, int ok)
{
- assert(ctx.running);
+ if (!ctx.running) {
+ test_msg("BUG: check outside of test at %s",
+ make_relative(location));
+ ctx.failed = 1;
+ return 0;
+ }
if (ctx.result == RESULT_SKIP) {
test_msg("skipping check '%s' at %s", check,
diff --git a/t/unit-tests/test-lib.h b/t/unit-tests/test-lib.h
index c59f646fd9..e4b234697f 100644
--- a/t/unit-tests/test-lib.h
+++ b/t/unit-tests/test-lib.h
@@ -15,6 +15,23 @@
TEST_LOCATION(), __VA_ARGS__)
/*
+ * Run a test unless test_skip_all() has been called. Acts like a
+ * conditional; the test body is expected as a statement or block after
+ * the closing parenthesis. The description for each test should be
+ * unique. E.g.:
+ *
+ * if_test ("something else %d %d", arg1, arg2) {
+ * prepare();
+ * test_something_else(arg1, arg2);
+ * cleanup();
+ * }
+ */
+#define if_test(...) \
+ if (test__run_begin() ? \
+ (test__run_end(0, TEST_LOCATION(), __VA_ARGS__), 0) : \
+ (test__run_describe(TEST_LOCATION(), __VA_ARGS__), 1))
+
+/*
* Print a test plan, should be called before any tests. If the number
* of tests is not known in advance test_done() will automatically
* print a plan at the end of the test program.
@@ -154,6 +171,9 @@ union test__tmp {
extern union test__tmp test__tmp[2];
+__attribute__((format (printf, 2, 3)))
+void test__run_describe(const char *, const char *, ...);
+
int test__run_begin(void);
__attribute__((format (printf, 3, 4)))
int test__run_end(int, const char *, const char *, ...);
diff --git a/t/unit-tests/unit-test.c b/t/unit-tests/unit-test.c
new file mode 100644
index 0000000000..a474cdcfd3
--- /dev/null
+++ b/t/unit-tests/unit-test.c
@@ -0,0 +1,47 @@
+#include "unit-test.h"
+#include "parse-options.h"
+#include "string-list.h"
+#include "strvec.h"
+
+static const char * const unit_test_usage[] = {
+ N_("unit-test [<options>]"),
+ NULL,
+};
+
+int cmd_main(int argc, const char **argv)
+{
+ struct string_list run_args = STRING_LIST_INIT_NODUP;
+ struct string_list exclude_args = STRING_LIST_INIT_NODUP;
+ int immediate = 0;
+ struct option options[] = {
+ OPT_BOOL('i', "immediate", &immediate,
+ N_("immediately exit upon the first failed test")),
+ OPT_STRING_LIST('r', "run", &run_args, N_("suite[::test]"),
+ N_("run only test suite or individual test <suite[::test]>")),
+ OPT_STRING_LIST('x', "exclude", &exclude_args, N_("suite"),
+ N_("exclude test suite <suite>")),
+ OPT_END(),
+ };
+ struct strvec args = STRVEC_INIT;
+ int ret;
+
+ argc = parse_options(argc, argv, NULL, options,
+ unit_test_usage, PARSE_OPT_KEEP_ARGV0);
+ if (argc > 1)
+ usagef(_("extra command line parameter '%s'"), argv[0]);
+
+ strvec_push(&args, argv[0]);
+ strvec_push(&args, "-t");
+ if (immediate)
+ strvec_push(&args, "-Q");
+ for (size_t i = 0; i < run_args.nr; i++)
+ strvec_pushf(&args, "-s%s", run_args.items[i].string);
+ for (size_t i = 0; i < exclude_args.nr; i++)
+ strvec_pushf(&args, "-x%s", exclude_args.items[i].string);
+
+ ret = clar_test(args.nr, (char **) args.v);
+
+ string_list_clear(&run_args, 0);
+ strvec_clear(&args);
+ return ret;
+}
diff --git a/t/unit-tests/unit-test.h b/t/unit-tests/unit-test.h
new file mode 100644
index 0000000000..85e5d6a948
--- /dev/null
+++ b/t/unit-tests/unit-test.h
@@ -0,0 +1,10 @@
+#include "git-compat-util.h"
+#include "clar/clar.h"
+#include "clar-decls.h"
+#include "strbuf.h"
+
+#define cl_failf(fmt, ...) do { \
+ char desc[4096]; \
+ snprintf(desc, sizeof(desc), fmt, __VA_ARGS__); \
+ clar__fail(__FILE__, __func__, __LINE__, "Test failed.", desc, 1); \
+} while (0)