Skip to content

Commit 3b8d3b4

Browse files
authored
test(scaffold): drop is_polyglot gating, add --langs subset assertions (#112)
Post-subset-honoring scaffold composition, exclusion assertions in .gitignore + .gitattributes no longer need single-lang gating. Removed is_polyglot / ga_is_polyglot guards from assert_managed_files so excludes fire for every opted-out lang, polyglot or not. Added regression tests for --langs subset behavior: - TEST14 go,ts: 2-lang subset, py + rs + php excluded - TEST15 php,go: out-of-order, asserts section order = pass order (php block precedes go block in .gitattributes) - TEST16 go,ts,py,rs,php: full 5-lang regression Added assert_dir_not_exists helper for opted-out lang dirs. Verified pre-T-0866 php-missing bug (hardcoded `for lang in go ts py rs` loops) cannot recur: scaffold.sh polyglot composition iterates LANG_ARRAY exclusively; templates/build.sh sole literal loop includes php. Pass count: 533 → 789 (+256 assertions). Co-authored-by: Jad Bitar <jadb@users.noreply.github.com>
1 parent 681b712 commit 3b8d3b4

1 file changed

Lines changed: 173 additions & 29 deletions

File tree

templates/test-scaffold-e2e.sh

Lines changed: 173 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,15 @@ assert_dir_exists() {
5252
fi
5353
}
5454

55+
assert_dir_not_exists() {
56+
local dir="$1" label="${2:-$1}"
57+
if [ -d "$dir" ]; then
58+
fail "$label unexpectedly exists: $dir"
59+
else
60+
pass "$label absent"
61+
fi
62+
}
63+
5564
assert_no_placeholders() {
5665
local dir="$1" label="$2"
5766
local found
@@ -337,54 +346,50 @@ assert_managed_files() {
337346
assert_file_contains "$gi" '^\.data/$' \
338347
"$tag .gitignore has common .data/ entry"
339348

340-
# Per-lang assertions. `scaffold.sh` selects the polyglot
341-
# dist (which composes all langs into `.gitignore`) whenever
342-
# 2+ langs are passed; only single-lang scaffolds emit a
343-
# `.gitignore` that excludes the other langs. So exclusion
344-
# assertions are gated on single-lang mode.
345-
local lang_count
346-
lang_count="$(echo "${lang_csv//[[:space:]]/}" | tr ',' '\n' | grep -c .)"
347-
local is_polyglot=false
348-
[ "${lang_count:-0}" -gt 1 ] && is_polyglot=true
349+
# Per-lang assertions. Post-T-0867, `scaffold.sh` honors the
350+
# `--langs` subset: polyglot mode composes only the opted-in
351+
# lang sections into `.gitignore`. Exclusion assertions fire
352+
# for every lang NOT in the subset, regardless of polyglot vs.
353+
# single-lang mode.
349354

350355
# vendor/ is shared by go + php, so php's discriminator is
351356
# composer.lock.
352357
if [[ "$lang_norm" == *,go,* ]] || [[ "$lang_norm" == *,php,* ]]; then
353358
assert_file_contains "$gi" '^vendor/$' \
354359
"$tag .gitignore has vendor/ (go|php)"
355-
elif ! $is_polyglot; then
360+
else
356361
assert_file_excludes "$gi" '^vendor/$' \
357362
"$tag .gitignore omits vendor/ (no go|php)"
358363
fi
359364

360365
if [[ "$lang_norm" == *,ts,* ]]; then
361366
assert_file_contains "$gi" '^node_modules/$' \
362367
"$tag .gitignore has ts node_modules/"
363-
elif ! $is_polyglot; then
368+
else
364369
assert_file_excludes "$gi" '^node_modules/$' \
365370
"$tag .gitignore omits ts node_modules/"
366371
fi
367372

368373
if [[ "$lang_norm" == *,py,* ]]; then
369374
assert_file_contains "$gi" '^__pycache__/$' \
370375
"$tag .gitignore has py __pycache__/"
371-
elif ! $is_polyglot; then
376+
else
372377
assert_file_excludes "$gi" '^__pycache__/$' \
373378
"$tag .gitignore omits py __pycache__/"
374379
fi
375380

376381
if [[ "$lang_norm" == *,rs,* ]]; then
377382
assert_file_contains "$gi" '^target/$' \
378383
"$tag .gitignore has rs target/"
379-
elif ! $is_polyglot; then
384+
else
380385
assert_file_excludes "$gi" '^target/$' \
381386
"$tag .gitignore omits rs target/"
382387
fi
383388

384389
if [[ "$lang_norm" == *,php,* ]]; then
385390
assert_file_contains "$gi" '^composer\.lock$' \
386391
"$tag .gitignore has php composer.lock"
387-
elif ! $is_polyglot; then
392+
else
388393
assert_file_excludes "$gi" '^composer\.lock$' \
389394
"$tag .gitignore omits php composer.lock"
390395
fi
@@ -418,23 +423,17 @@ assert_managed_files() {
418423
assert_file_contains "$ga" '^\*\.png[[:space:]][[:space:]]*binary$' \
419424
"$tag .gitattributes has common *.png binary rule"
420425

421-
# Per-lang assertions. `scaffold.sh` selects the polyglot
422-
# dist (which composes all langs into `.gitattributes`) whenever
423-
# 2+ langs are passed; only single-lang scaffolds emit a
424-
# `.gitattributes` that excludes the other langs. So exclusion
425-
# assertions are gated on single-lang mode (mirrors the
426-
# `.gitignore` block above).
427-
local ga_lang_count
428-
ga_lang_count="$(echo "${lang_csv//[[:space:]]/}" | tr ',' '\n' | grep -c .)"
429-
local ga_is_polyglot=false
430-
[ "${ga_lang_count:-0}" -gt 1 ] && ga_is_polyglot=true
426+
# Per-lang assertions. Post-T-0867, `scaffold.sh` honors the
427+
# `--langs` subset for polyglot composition. Exclusion
428+
# assertions fire for every lang NOT in the subset (mirrors
429+
# the `.gitignore` block above).
431430

432431
if [[ "$lang_norm" == *,go,* ]]; then
433432
assert_file_contains "$ga" '^\*\.go[[:space:]][[:space:]]*text eol=lf$' \
434433
"$tag .gitattributes has go *.go text rule"
435434
assert_file_contains "$ga" '^go\.sum[[:space:]][[:space:]]*linguist-generated=true$' \
436435
"$tag .gitattributes has go go.sum linguist-generated"
437-
elif ! $ga_is_polyglot; then
436+
else
438437
assert_file_excludes "$ga" 'go\.sum' \
439438
"$tag .gitattributes omits go go.sum"
440439
fi
@@ -444,7 +443,7 @@ assert_managed_files() {
444443
"$tag .gitattributes has ts *.ts text rule"
445444
assert_file_contains "$ga" '^pnpm-lock\.yaml[[:space:]][[:space:]]*linguist-generated=true$' \
446445
"$tag .gitattributes has ts pnpm-lock.yaml linguist-generated"
447-
elif ! $ga_is_polyglot; then
446+
else
448447
assert_file_excludes "$ga" 'pnpm-lock\.yaml' \
449448
"$tag .gitattributes omits ts pnpm-lock.yaml"
450449
fi
@@ -454,7 +453,7 @@ assert_managed_files() {
454453
"$tag .gitattributes has py *.py text rule"
455454
assert_file_contains "$ga" '^\*\*/uv\.lock[[:space:]][[:space:]]*linguist-generated=true$' \
456455
"$tag .gitattributes has py **/uv.lock linguist-generated"
457-
elif ! $ga_is_polyglot; then
456+
else
458457
assert_file_excludes "$ga" '\*\*/uv\.lock' \
459458
"$tag .gitattributes omits py **/uv.lock"
460459
fi
@@ -464,7 +463,7 @@ assert_managed_files() {
464463
"$tag .gitattributes has rs *.rs text rule"
465464
assert_file_contains "$ga" '^Cargo\.lock[[:space:]][[:space:]]*linguist-generated=true$' \
466465
"$tag .gitattributes has rs Cargo.lock linguist-generated"
467-
elif ! $ga_is_polyglot; then
466+
else
468467
assert_file_excludes "$ga" 'Cargo\.lock' \
469468
"$tag .gitattributes omits rs Cargo.lock"
470469
fi
@@ -474,7 +473,7 @@ assert_managed_files() {
474473
"$tag .gitattributes has php *.php text rule"
475474
assert_file_contains "$ga" '^composer\.lock[[:space:]][[:space:]]*linguist-generated=true$' \
476475
"$tag .gitattributes has php composer.lock linguist-generated"
477-
elif ! $ga_is_polyglot; then
476+
else
478477
assert_file_excludes "$ga" 'composer\.lock' \
479478
"$tag .gitattributes omits php composer.lock"
480479
fi
@@ -845,6 +844,151 @@ bash "$SCRIPT_DIR/scaffold.sh" test-app-with-svc \
845844
--forge none --no-push --no-tlc
846845
assert_managed_files "$TEST13C_DIR" "go" "postgres,redis" "test-app-with-svc"
847846

847+
# ======================================================
848+
# Test 14: --langs subset go,ts (2-lang polyglot)
849+
# ======================================================
850+
#
851+
# Post-T-0867 polyglot honors --langs subset. Verify go + ts
852+
# present, py + rs + php excluded from lang dirs and managed
853+
# files. Mirrors TEST2 (go,ts,py) shape.
854+
855+
info "Test 14: --langs subset go,ts"
856+
TEST14_DIR="$TMPDIR_BASE/test14app"
857+
858+
bash "$SCRIPT_DIR/scaffold.sh" test14app \
859+
--output "$TEST14_DIR" \
860+
--lang go,ts \
861+
--description "Test go,ts subset" \
862+
--license apache \
863+
--author "Test Author" \
864+
--email "test@example.com" \
865+
--module-prefix "github.com/testorg" \
866+
--forge none \
867+
--no-push --no-tlc
868+
869+
assert_dir_exists "$TEST14_DIR" "test14 project dir"
870+
assert_dir_exists "$TEST14_DIR/go" "test14 go/ dir"
871+
assert_dir_exists "$TEST14_DIR/ts" "test14 ts/ dir"
872+
assert_dir_not_exists "$TEST14_DIR/py" "test14 py/ dir"
873+
assert_dir_not_exists "$TEST14_DIR/rs" "test14 rs/ dir"
874+
assert_dir_not_exists "$TEST14_DIR/php" "test14 php/ dir"
875+
assert_file_exists "$TEST14_DIR/Makefile" "test14 root Makefile"
876+
assert_file_contains "$TEST14_DIR/Makefile" 'MAKE) -C go' \
877+
"test14 Makefile delegates to go"
878+
assert_file_contains "$TEST14_DIR/Makefile" 'MAKE) -C ts' \
879+
"test14 Makefile delegates to ts"
880+
assert_no_placeholders "$TEST14_DIR" "test14"
881+
assert_managed_files "$TEST14_DIR" "go,ts" "" "test14app"
882+
883+
# ======================================================
884+
# Test 15: --langs subset php,go (out-of-order, section ordering)
885+
# ======================================================
886+
#
887+
# Section-order convention (PR #103, documented in
888+
# templates/shared/README.md): per-lang sections in `.gitignore`
889+
# and `.gitattributes` appear in LANG_ARRAY pass order. Verify
890+
# php block precedes go block when invoked as --lang php,go.
891+
892+
info "Test 15: --langs subset php,go (section order = pass order)"
893+
TEST15_DIR="$TMPDIR_BASE/test15app"
894+
895+
bash "$SCRIPT_DIR/scaffold.sh" test15app \
896+
--output "$TEST15_DIR" \
897+
--lang php,go \
898+
--description "Test php,go ordering" \
899+
--license apache \
900+
--author "Test Author" \
901+
--email "test@example.com" \
902+
--module-prefix "github.com/testorg" \
903+
--forge none \
904+
--no-push --no-tlc
905+
906+
assert_dir_exists "$TEST15_DIR" "test15 project dir"
907+
assert_dir_exists "$TEST15_DIR/php" "test15 php/ dir"
908+
assert_dir_exists "$TEST15_DIR/go" "test15 go/ dir"
909+
assert_dir_not_exists "$TEST15_DIR/ts" "test15 ts/ dir"
910+
assert_dir_not_exists "$TEST15_DIR/py" "test15 py/ dir"
911+
assert_dir_not_exists "$TEST15_DIR/rs" "test15 rs/ dir"
912+
assert_managed_files "$TEST15_DIR" "php,go" "" "test15app"
913+
914+
# Section-ordering check: composer.lock (php discriminator) must
915+
# appear BEFORE go.sum-ish anchors in .gitattributes. For
916+
# .gitignore, php's composer.lock must appear BEFORE the go-only
917+
# discriminator. (vendor/ is shared by go+php so it's not a
918+
# usable anchor.)
919+
TEST15_GI="$TEST15_DIR/.gitignore"
920+
TEST15_GA="$TEST15_DIR/.gitattributes"
921+
922+
if [ -f "$TEST15_GI" ]; then
923+
# composer.lock (php) and Cargo-or-go anchor. Go has no
924+
# unique .gitignore line beyond vendor/ (shared). Use the
925+
# cli-php section marker if present, else fall back to
926+
# composer.lock line-number comparison vs. end-of-file.
927+
php_line="$(grep -n '^composer\.lock$' "$TEST15_GI" | head -1 | cut -d: -f1 || true)"
928+
if [ -n "$php_line" ]; then
929+
pass "test15 .gitignore contains php composer.lock at line $php_line"
930+
else
931+
fail "test15 .gitignore missing php composer.lock line"
932+
fi
933+
fi
934+
935+
if [ -f "$TEST15_GA" ]; then
936+
# composer.lock = php-only, go.sum = go-only. Both lang
937+
# sections must be present and php must precede go.
938+
php_ga_line="$(grep -n '^composer\.lock[[:space:]]' "$TEST15_GA" | head -1 | cut -d: -f1 || true)"
939+
go_ga_line="$(grep -n '^go\.sum[[:space:]]' "$TEST15_GA" | head -1 | cut -d: -f1 || true)"
940+
if [ -n "$php_ga_line" ] && [ -n "$go_ga_line" ]; then
941+
if [ "$php_ga_line" -lt "$go_ga_line" ]; then
942+
pass "test15 .gitattributes section order: php (L$php_ga_line) before go (L$go_ga_line)"
943+
else
944+
fail "test15 .gitattributes section order: php (L$php_ga_line) NOT before go (L$go_ga_line)"
945+
fi
946+
else
947+
fail "test15 .gitattributes missing php composer.lock or go go.sum anchor (php_ga_line='$php_ga_line' go_ga_line='$go_ga_line')"
948+
fi
949+
fi
950+
951+
# ======================================================
952+
# Test 16: --langs full set go,ts,py,rs,php (5-lang regression)
953+
# ======================================================
954+
#
955+
# Regression: full lang set produces all five lang dirs and
956+
# every per-lang managed-file section. Mirrors TEST12 (4-lang)
957+
# extended to include php.
958+
959+
info "Test 16: --langs full set go,ts,py,rs,php"
960+
TEST16_DIR="$TMPDIR_BASE/test16app"
961+
962+
bash "$SCRIPT_DIR/scaffold.sh" test16app \
963+
--output "$TEST16_DIR" \
964+
--lang go,ts,py,rs,php \
965+
--description "Test full 5-lang polyglot" \
966+
--license apache \
967+
--author "Test Author" \
968+
--email "test@example.com" \
969+
--module-prefix "github.com/testorg" \
970+
--forge none \
971+
--no-push --no-tlc
972+
973+
assert_dir_exists "$TEST16_DIR" "test16 project dir"
974+
assert_dir_exists "$TEST16_DIR/go" "test16 go/ dir"
975+
assert_dir_exists "$TEST16_DIR/ts" "test16 ts/ dir"
976+
assert_dir_exists "$TEST16_DIR/py" "test16 py/ dir"
977+
assert_dir_exists "$TEST16_DIR/rs" "test16 rs/ dir"
978+
assert_dir_exists "$TEST16_DIR/php" "test16 php/ dir"
979+
assert_file_exists "$TEST16_DIR/Makefile" "test16 root Makefile"
980+
assert_file_contains "$TEST16_DIR/Makefile" 'MAKE) -C go' \
981+
"test16 Makefile delegates to go"
982+
assert_file_contains "$TEST16_DIR/Makefile" 'MAKE) -C ts' \
983+
"test16 Makefile delegates to ts"
984+
assert_file_contains "$TEST16_DIR/Makefile" 'MAKE) -C py' \
985+
"test16 Makefile delegates to py"
986+
assert_file_contains "$TEST16_DIR/Makefile" 'MAKE) -C rs' \
987+
"test16 Makefile delegates to rs"
988+
assert_file_contains "$TEST16_DIR/Makefile" 'MAKE) -C php' \
989+
"test16 Makefile delegates to php"
990+
assert_managed_files "$TEST16_DIR" "go,ts,py,rs,php" "" "test16app"
991+
848992
# ======================================================
849993
# Summary
850994
# ======================================================

0 commit comments

Comments
 (0)