test-lib: teach test_seq the -f option

The "seq" tool has a "-f" option to produce printf-style formatted lines. Let's teach our test_seq helper the same trick. This lets us get rid of some shell loops in test snippets (which are particularly verbose in our test suite because we have to "|| return 1" to keep the &&-chain going). This converts a few call-sites I found by grepping around the test suite. A few notes on these: - In "seq", the format specifier is a "%g" float. Since test_seq only supports integers, I've kept the more natural "%d" (which is what these call sites were using already). - Like "seq", test_seq automatically adds a newline to the specified format. This is what all callers are doing already except for t0021, but there we do not care about the exact format. We are just trying to printf a large number of bytes to a file. It's not worth complicating other callers or adding an option to avoid the newline in that caller. - Most conversions are just replacing a shell loop (which does get rid of an extra fork, since $() requires a subshell). In t0612 we can replace an awk invocation, which I think makes the end result more readable, as there's less quoting. - In t7422 we can replace one loop, but sadly we have to leave the loop directly above it. This is because that earlier loop wants to include the seq value twice in the output, which test_seq does not support (nor does regular seq). If you run: test_seq -f "foo-%d %d" 10 the second "%d" will always be the empty string. You might naively think that test_seq could add some extra arguments, like: # 3 ought to be enough for anyone... printf "$fmt\n" "$i "$i" $i" but that just triggers printf to format multiple lines, one per extra set of arguments. So we'd have to actually parse the format string, figure out how many "%" placeholders are there, and then feed it that many instances of the sequence number. The complexity isn't worth it. Signed-off-by: Jeff King <peff@peff.net> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Jeff King committed Jun 23, 2025 at 06:56 UTC b32c7ec02f6407bf3445b0fedf6c7294179b7e49
9 files changed +32 -62
t/t0021-conversion.sh
+2 -2
@@ -281,7 +281,7 @@ test_expect_success 'required filter with absent smudge field' '
281 test_expect_success 'filtering large input to small output should use little memory' '
282 test_config filter.devnull.clean "cat >/dev/null" &&
283 test_config filter.devnull.required true &&
284 - for i in $(test_seq 1 30); do printf "%1048576d" 1 || return 1; done >30MB &&
284 + test_seq -f "%1048576d" 1 30 >30MB &&
285 echo "30MB filter=devnull" >.gitattributes &&
286 GIT_MMAP_LIMIT=1m GIT_ALLOC_LIMIT=1m git add 30MB
287 '
@@ -299,7 +299,7 @@ test_expect_success 'filter that does not read is fine' '
299 test_expect_success EXPENSIVE 'filter large file' '
300 test_config filter.largefile.smudge cat &&
301 test_config filter.largefile.clean cat &&
302 - for i in $(test_seq 1 2048); do printf "%1048576d" 1 || return 1; done >2GB &&
302 + test_seq -f "%1048576d" 1 2048 >2GB &&
303 echo "2GB filter=largefile" >.gitattributes &&
304 git add 2GB 2>err &&
305 test_must_be_empty err &&
t/t0610-reftable-basics.sh
+1 -5
@@ -477,11 +477,7 @@ test_expect_success !CYGWIN 'ref transaction: many concurrent writers' '
477 test_commit --no-tag initial &&
478
479 head=$(git rev-parse HEAD) &&
480 - for i in $(test_seq 100)
481 - do
482 - printf "%s commit\trefs/heads/branch-%s\n" "$head" "$i" ||
483 - return 1
484 - done >expect &&
480 + test_seq -f "$head commit\trefs/heads/branch-%d" 100 >expect &&
481 printf "%s commit\trefs/heads/main\n" "$head" >>expect &&
482
483 for i in $(test_seq 100)
t/t0612-reftable-jgit-compatibility.sh
+5 -8
@@ -112,14 +112,11 @@ test_expect_success 'JGit can read multi-level index' '
112 cd repo &&
113
114 test_commit A &&
115 - awk "
116 - BEGIN {
117 - print \"start\";
118 - for (i = 0; i < 10000; i++)
119 - printf \"create refs/heads/branch-%d HEAD\n\", i;
120 - print \"commit\";
121 - }
122 - " >input &&
115 + {
116 + echo start &&
117 + test_seq -f "create refs/heads/branch-%d HEAD" 10000 &&
118 + echo commit
119 + } >input &&
120 git update-ref --stdin <input &&
121
122 test_same_refs &&
t/t0613-reftable-write-options.sh
+4 -20
@@ -66,11 +66,7 @@ test_expect_success 'many refs results in multiple blocks' '
66 (
67 cd repo &&
68 test_commit initial &&
69 - for i in $(test_seq 200)
70 - do
71 - printf "update refs/heads/branch-%d HEAD\n" "$i" ||
72 - return 1
73 - done >input &&
69 + test_seq -f "update refs/heads/branch-%d HEAD" 200 >input &&
70 git update-ref --stdin <input &&
71 git pack-refs &&
72
@@ -180,11 +176,7 @@ test_expect_success 'restart interval at every single record' '
176 (
177 cd repo &&
178 test_commit initial &&
183 - for i in $(test_seq 10)
184 - do
185 - printf "update refs/heads/branch-%d HEAD\n" "$i" ||
186 - return 1
187 - done >input &&
179 + test_seq -f "update refs/heads/branch-%d HEAD" 10 >input &&
180 git update-ref --stdin <input &&
181 git -c reftable.restartInterval=1 pack-refs &&
182
@@ -224,11 +216,7 @@ test_expect_success 'object index gets written by default with ref index' '
216 (
217 cd repo &&
218 test_commit initial &&
227 - for i in $(test_seq 5)
228 - do
229 - printf "update refs/heads/branch-%d HEAD\n" "$i" ||
230 - return 1
231 - done >input &&
219 + test_seq -f "update refs/heads/branch-%d HEAD" 5 >input &&
220 git update-ref --stdin <input &&
221 git -c reftable.blockSize=100 pack-refs &&
222
@@ -263,11 +251,7 @@ test_expect_success 'object index can be disabled' '
251 (
252 cd repo &&
253 test_commit initial &&
266 - for i in $(test_seq 5)
267 - do
268 - printf "update refs/heads/branch-%d HEAD\n" "$i" ||
269 - return 1
270 - done >input &&
254 + test_seq -f "update refs/heads/branch-%d HEAD" 5 >input &&
255 git update-ref --stdin <input &&
256 git -c reftable.blockSize=100 -c reftable.indexObjects=false pack-refs &&
257
t/t1400-update-ref.sh
+2 -8
@@ -1380,10 +1380,7 @@ test_expect_success 'fails with duplicate ref update via symref' '
1380
1381 test_expect_success ULIMIT_FILE_DESCRIPTORS 'large transaction creating branches does not burst open file limit' '
1382 (
1383 - for i in $(test_seq 33)
1384 - do
1385 - echo "create refs/heads/$i HEAD" || exit 1
1386 - done >large_input &&
1383 + test_seq -f "create refs/heads/%d HEAD" 33 >large_input &&
1384 run_with_limited_open_files git update-ref --stdin <large_input &&
1385 git rev-parse --verify -q refs/heads/33
1386 )
@@ -1391,10 +1388,7 @@ test_expect_success ULIMIT_FILE_DESCRIPTORS 'large transaction creating branches
1388
1389 test_expect_success ULIMIT_FILE_DESCRIPTORS 'large transaction deleting branches does not burst open file limit' '
1390 (
1394 - for i in $(test_seq 33)
1395 - do
1396 - echo "delete refs/heads/$i HEAD" || exit 1
1397 - done >large_input &&
1391 + test_seq -f "delete refs/heads/%d HEAD" 33 >large_input &&
1392 run_with_limited_open_files git update-ref --stdin <large_input &&
1393 test_must_fail git rev-parse --verify -q refs/heads/33
1394 )
t/t5004-archive-corner-cases.sh
+1 -4
@@ -176,10 +176,7 @@ test_expect_success EXPENSIVE,UNZIP,UNZIP_ZIP64_SUPPORT \
176 blob=$(echo $s | git hash-object -w --stdin) &&
177
178 # create tree containing 65500 entries of that blob
179 - for i in $(test_seq 1 65500)
180 - do
181 - echo "100644 blob $blob $i" || return 1
182 - done >tree &&
179 + test_seq -f "100644 blob $blob\t%d" 1 65500 >tree &&
180 tree=$(git mktree <tree) &&
181
182 # zip it, creating an archive a bit bigger than 4GB
t/t6422-merge-rename-corner-cases.sh
+2 -8
@@ -1146,10 +1146,7 @@ test_conflicts_with_adds_and_renames() {
1146 cd simple_${sideL}_${sideR} &&
1147
1148 # Create some related files now
1149 - for i in $(test_seq 1 10)
1150 - do
1151 - echo Random base content line $i
1152 - done >file_v1 &&
1149 + test_seq -f "Random base content line %d" 1 10 >file_v1 &&
1150 cp file_v1 file_v2 &&
1151 echo modification >>file_v2 &&
1152
@@ -1293,10 +1290,7 @@ test_setup_nested_conflicts_from_rename_rename () {
1290 cd nested_conflicts_from_rename_rename &&
1291
1292 # Create some related files now
1296 - for i in $(test_seq 1 10)
1297 - do
1298 - echo Random base content line $i
1299 - done >file_v1 &&
1293 + test_seq -f "Random base content line %d" 1 10 >file_v1 &&
1294
1295 cp file_v1 file_v2 &&
1296 cp file_v1 file_v3 &&
t/t7422-submodule-output.sh
+1 -5
@@ -187,11 +187,7 @@ test_expect_success !MINGW 'git submodule status --recursive propagates SIGPIPE'
187 BLOB=$(git hash-object -w --stdin <gitmodules) &&
188
189 printf "100644 blob $BLOB\t.gitmodules\n" >tree &&
190 - for i in $(test_seq 2000)
191 - do
192 - printf "160000 commit $COMMIT\trecursive-submodule-path-%d\n" "$i" ||
193 - return 1
194 - done >>tree &&
190 + test_seq -f "160000 commit $COMMIT\trecursive-submodule-path-%d" 2000 >>tree &&
191 TREE=$(git mktree <tree) &&
192
193 COMMIT=$(git commit-tree "$TREE") &&
t/test-lib-functions.sh
+14 -2
@@ -1451,9 +1451,21 @@ test_cmp_fspath () {
1451 # test_seq 1 5 -- outputs 1 2 3 4 5 one line at a time
1452 #
1453 # or with one argument (end), in which case it starts counting
1454 -# from 1.
1454 +# from 1. In addition to the start/end arguments, you can pass an optional
1455 +# printf format. For example:
1456 +#
1457 +# test_seq -f "line %d" 1 5
1458 +#
1459 +# would print 5 lines, "line 1" through "line 5".
1460
1461 test_seq () {
1462 + local fmt="%d"
1463 + case "$1" in
1464 + -f)
1465 + fmt="$2"
1466 + shift 2
1467 + ;;
1468 + esac
1469 case $# in
1470 1) set 1 "$@" ;;
1471 2) ;;
@@ -1462,7 +1474,7 @@ test_seq () {
1474 test_seq_counter__=$1
1475 while test "$test_seq_counter__" -le "$2"
1476 do
1465 - echo "$test_seq_counter__"
1477 + printf "$fmt\n" "$test_seq_counter__"
1478 test_seq_counter__=$(( $test_seq_counter__ + 1 ))
1479 done
1480 }