summaryrefslogtreecommitdiffstats
path: root/src/preproc/tbl/tests
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-15 19:44:05 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-15 19:44:05 +0000
commitd318611dd6f23fcfedd50e9b9e24620b102ba96a (patch)
tree8b9eef82ca40fdd5a8deeabf07572074c236095d /src/preproc/tbl/tests
parentInitial commit. (diff)
downloadgroff-d318611dd6f23fcfedd50e9b9e24620b102ba96a.tar.xz
groff-d318611dd6f23fcfedd50e9b9e24620b102ba96a.zip
Adding upstream version 1.23.0.upstream/1.23.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/preproc/tbl/tests')
-rwxr-xr-xsrc/preproc/tbl/tests/boxes-and-vertical-rules.sh174
-rwxr-xr-xsrc/preproc/tbl/tests/check-horizontal-line-length.sh78
-rwxr-xr-xsrc/preproc/tbl/tests/check-line-intersections.sh52
-rwxr-xr-xsrc/preproc/tbl/tests/check-vertical-line-length.sh49
-rwxr-xr-xsrc/preproc/tbl/tests/cooperate-with-nm-request.sh47
-rwxr-xr-xsrc/preproc/tbl/tests/count-continued-input-lines.sh43
-rwxr-xr-xsrc/preproc/tbl/tests/do-not-overdraw-page-top-in-nroff-mode.sh225
-rwxr-xr-xsrc/preproc/tbl/tests/do-not-overlap-bottom-border-in-nroff.sh62
-rwxr-xr-xsrc/preproc/tbl/tests/do-not-segv-on-invalid-vertical-span-entry.sh41
-rwxr-xr-xsrc/preproc/tbl/tests/do-not-segv-when-backslash-R-in-text-block.sh75
-rwxr-xr-xsrc/preproc/tbl/tests/expand-region-option-works.sh173
-rwxr-xr-xsrc/preproc/tbl/tests/format-time-diagnostics-work.sh268
-rwxr-xr-xsrc/preproc/tbl/tests/save-and-restore-hyphenation-parameters.sh58
-rwxr-xr-xsrc/preproc/tbl/tests/save-and-restore-inter-sentence-space.sh79
-rwxr-xr-xsrc/preproc/tbl/tests/save-and-restore-line-numbering.sh85
-rwxr-xr-xsrc/preproc/tbl/tests/save-and-restore-tab-stops.sh84
-rwxr-xr-xsrc/preproc/tbl/tests/warn-on-long-boxed-unkept-table.sh56
-rwxr-xr-xsrc/preproc/tbl/tests/x-column-modifier-works.sh172
18 files changed, 1821 insertions, 0 deletions
diff --git a/src/preproc/tbl/tests/boxes-and-vertical-rules.sh b/src/preproc/tbl/tests/boxes-and-vertical-rules.sh
new file mode 100755
index 0000000..0471188
--- /dev/null
+++ b/src/preproc/tbl/tests/boxes-and-vertical-rules.sh
@@ -0,0 +1,174 @@
+#!/bin/sh
+#
+# Copyright (C) 2023 Free Software Foundation, Inc.
+#
+# This file is part of groff.
+#
+# groff is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free
+# Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# groff is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+groff="${abs_top_builddir:-.}/test-groff"
+
+fail=
+
+wail () {
+ echo ...FAILED >&2
+ fail=YES
+}
+
+# Test behavior of unexpanded tables using boxes and/or vertical rules.
+
+# Case 1: "naked" table
+
+input='.ll 64n
+.nf
+1234567890123456789012345678901234567890123456789012345678901234
+.fi
+.TS
+tab(@);
+L L L L L.
+abcdef@abcdef@abcdef@abcdef@abcdef
+.TE
+.pl \n(nlu'
+
+echo "checking unboxed table without vertical rules" >&2
+output=$(printf "%s\n" "$input" | "$groff" -t -Tascii)
+echo "$output"
+# 3 spaces between table entries
+echo "$output" | sed -n '2p' \
+ | grep -qx 'abcdef abcdef abcdef abcdef abcdef' || wail
+
+# Case 2: left-hand vertical rule
+
+input='.ll 64n
+.nf
+1234567890123456789012345678901234567890123456789012345678901234
+.fi
+.TS
+tab(@);
+| L L L L L.
+abcdef@abcdef@abcdef@abcdef@abcdef
+.TE
+.pl \n(nlu'
+
+echo "checking unboxed table with left-hand rule" >&2
+output=$(printf "%s\n" "$input" | "$groff" -t -Tascii)
+echo "$output"
+# 3 spaces between table entries
+echo "$output" | sed -n '3p' \
+ | grep -qx '| abcdef abcdef abcdef abcdef abcdef' || wail
+
+# Case 3: right-hand vertical rule
+
+input='.ll 64n
+.nf
+1234567890123456789012345678901234567890123456789012345678901234
+.fi
+.TS
+tab(@);
+L L L L L |.
+abcdef@abcdef@abcdef@abcdef@abcdef
+.TE
+.pl \n(nlu'
+
+echo "checking unboxed table with right-hand rule" >&2
+output=$(printf "%s\n" "$input" | "$groff" -t -Tascii)
+echo "$output"
+# 3 spaces between table entries
+echo "$output" | sed -n '3p' \
+ | grep -qx 'abcdef abcdef abcdef abcdef abcdef |' || wail
+
+# Case 4: vertical rule on both ends
+
+input='.ll 64n
+.nf
+1234567890123456789012345678901234567890123456789012345678901234
+.fi
+.TS
+tab(@);
+| L L L L L |.
+abcdef@abcdef@abcdef@abcdef@abcdef
+.TE
+.pl \n(nlu'
+
+echo "checking unboxed table with both rules" >&2
+output=$(printf "%s\n" "$input" | "$groff" -t -Tascii)
+echo "$output"
+# 3 spaces between table entries
+echo "$output" | sed -n '3p' \
+ | grep -qx '| abcdef abcdef abcdef abcdef abcdef |' || wail
+
+# Case 5: vertical rule on both ends and interior rule
+
+input='.ll 64n
+.nf
+1234567890123456789012345678901234567890123456789012345678901234
+.fi
+.TS
+tab(@);
+| L L L | L L |.
+abcdef@abcdef@abcdef@abcdef@abcdef
+.TE
+.pl \n(nlu'
+
+echo "checking unboxed table with both edge and interior rules" >&2
+output=$(printf "%s\n" "$input" | "$groff" -t -Tascii)
+echo "$output"
+# 3 spaces between table entries
+echo "$output" | sed -n '3p' \
+ | grep -qx '| abcdef abcdef abcdef | abcdef abcdef |' || wail
+
+# Case 6: boxed table
+
+input='.ll 64n
+.nf
+1234567890123456789012345678901234567890123456789012345678901234
+.fi
+.TS
+box tab(@);
+L L L L L.
+abcdef@abcdef@abcdef@abcdef@abcdef
+.TE
+.pl \n(nlu'
+
+echo "checking boxed table without interior rules" >&2
+output=$(printf "%s\n" "$input" | "$groff" -t -Tascii)
+echo "$output"
+# 3 spaces between table entries
+echo "$output" | sed -n '3p' \
+ | grep -qx '| abcdef abcdef abcdef abcdef abcdef |' || wail
+
+# Case 7: boxed table with interior vertical rule
+
+input='.ll 64n
+.nf
+1234567890123456789012345678901234567890123456789012345678901234
+.fi
+.TS
+box tab(@);
+L L L | L L.
+abcdef@abcdef@abcdef@abcdef@abcdef
+.TE
+.pl \n(nlu'
+
+echo "checking boxed table with interior rules" >&2
+output=$(printf "%s\n" "$input" | "$groff" -t -Tascii)
+echo "$output"
+# 3 spaces between table entries
+echo "$output" | sed -n '3p' \
+ | grep -qx '| abcdef abcdef abcdef | abcdef abcdef |' || wail
+
+test -z "$fail"
+
+# vim:set ai et sw=4 ts=4 tw=72:
diff --git a/src/preproc/tbl/tests/check-horizontal-line-length.sh b/src/preproc/tbl/tests/check-horizontal-line-length.sh
new file mode 100755
index 0000000..3d5e2a2
--- /dev/null
+++ b/src/preproc/tbl/tests/check-horizontal-line-length.sh
@@ -0,0 +1,78 @@
+#!/bin/sh
+#
+# Copyright (C) 2022 Free Software Foundation, Inc.
+#
+# This file is part of groff.
+#
+# groff is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free
+# Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# groff is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+groff="${abs_top_builddir:-.}/test-groff"
+fail=
+
+wail () {
+ echo "...FAILED" >&2
+ echo "$output"
+ fail=yes
+}
+
+# GNU tbl draws horizontal lines 1n wider than they need to be on nroff
+# devices to enable them to cross a vertical line on the right-hand
+# side.
+
+input='.ll 10n
+.TS
+L.
+_
+1234567890
+.TE
+.pl \n(nlu
+'
+
+echo "checking length of plain horizontal rule" >&2
+output=$(printf "%s" "$input" | "$groff" -Tascii -t)
+echo "$output" | grep -Eqx -- '-{11}' || wail
+
+input='.ll 12n
+.TS
+| L |.
+_
+1234567890
+_
+.TE
+.pl \n(nlu
+'
+
+echo "checking intersection of vertical and horizontal rules" >&2
+output=$(printf "%s" "$input" | "$groff" -Tascii -t)
+echo "$output" | sed -n '1p' | grep -Eqx '\+-{12}\+' || wail
+echo "$output" | sed -n '3p' | grep -Eqx '\+-{12}\+' || wail
+
+input='.ll 12n
+.TS
+box;
+L.
+1234567890
+.TE
+.pl \n(nlu
+'
+
+echo "checking width of boxed table" >&2
+output=$(printf "%s" "$input" | "$groff" -Tascii -t)
+echo "$output" | sed -n '1p' | grep -Eqx '\+-{12}\+' || wail
+echo "$output" | sed -n '3p' | grep -Eqx '\+-{12}\+' || wail
+
+test -z "$fail"
+
+# vim:set ai et sw=4 ts=4 tw=72:
diff --git a/src/preproc/tbl/tests/check-line-intersections.sh b/src/preproc/tbl/tests/check-line-intersections.sh
new file mode 100755
index 0000000..2012beb
--- /dev/null
+++ b/src/preproc/tbl/tests/check-line-intersections.sh
@@ -0,0 +1,52 @@
+#!/bin/sh
+#
+# Copyright (C) 2022 Free Software Foundation, Inc.
+#
+# This file is part of groff.
+#
+# groff is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free
+# Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# groff is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+groff="${abs_top_builddir:-.}/test-groff"
+fail=
+
+wail () {
+ echo "...FAILED" >&2
+ echo "$output"
+ fail=yes
+}
+
+input='.TS
+allbox tab(@);
+L L L.
+a@b@c
+d@e@f
+g@h@i
+.TE
+'
+
+output=$(printf "%s" "$input" | "$groff" -Tascii -t)
+echo "$output"
+
+for l in 1 3 5 7
+do
+ echo "checking intersections on line $l"
+ echo "$output" | sed -n ${l}p | grep -Fqx '+---+---+---+' || wail
+done
+
+# TODO: Check `-Tutf8` output for correct crossing glyph identities.
+
+test -z "$fail"
+
+# vim:set ai et sw=4 ts=4 tw=72:
diff --git a/src/preproc/tbl/tests/check-vertical-line-length.sh b/src/preproc/tbl/tests/check-vertical-line-length.sh
new file mode 100755
index 0000000..1aafd09
--- /dev/null
+++ b/src/preproc/tbl/tests/check-vertical-line-length.sh
@@ -0,0 +1,49 @@
+#!/bin/sh
+#
+# Copyright (C) 2022 Free Software Foundation, Inc.
+#
+# This file is part of groff.
+#
+# groff is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free
+# Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# groff is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+groff="${abs_top_builddir:-.}/test-groff"
+fail=
+
+wail () {
+ echo "...FAILED" >&2
+ echo "$output"
+ fail=yes
+}
+
+# GNU tbl draws vertical lines 1v taller than they need to be on nroff
+# devices to enable them to cross a potential horizontal line in the
+# table.
+
+input='.ll 12n
+.TS
+| L |.
+_
+1234567890
+.TE
+.pl \n(nlu
+'
+
+echo "checking length of plain vertical rule" >&2
+output=$(printf "%s" "$input" | "$groff" -Tascii -t)
+echo "$output" | sed -n '2p' | grep -Fqx -- '| 1234567890 |' || wail
+
+test -z "$fail"
+
+# vim:set ai et sw=4 ts=4 tw=72:
diff --git a/src/preproc/tbl/tests/cooperate-with-nm-request.sh b/src/preproc/tbl/tests/cooperate-with-nm-request.sh
new file mode 100755
index 0000000..cfbd750
--- /dev/null
+++ b/src/preproc/tbl/tests/cooperate-with-nm-request.sh
@@ -0,0 +1,47 @@
+#!/bin/sh
+#
+# Copyright (C) 2021 Free Software Foundation, Inc.
+#
+# This file is part of groff.
+#
+# groff is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free
+# Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# groff is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+groff="${abs_top_builddir:-.}/test-groff"
+
+set -e
+
+# Regression-test Savannah #59812.
+#
+# A nonzero value of \n[ln] should not cause spurious numbering of table
+# rows.
+
+DOC='\
+.nf
+foo
+.nm 1
+bar
+.nm
+baz
+.TS
+l.
+qux
+.TE
+'
+
+OUTPUT=$(printf "%s" "$DOC" | "$groff" -Tascii -t)
+
+echo "$OUTPUT" | grep -Fqx qux
+
+# vim:set ai noet sw=4 ts=4 tw=72:
diff --git a/src/preproc/tbl/tests/count-continued-input-lines.sh b/src/preproc/tbl/tests/count-continued-input-lines.sh
new file mode 100755
index 0000000..4682788
--- /dev/null
+++ b/src/preproc/tbl/tests/count-continued-input-lines.sh
@@ -0,0 +1,43 @@
+#!/bin/sh
+#
+# Copyright (C) 2022 Free Software Foundation, Inc.
+#
+# This file is part of groff.
+#
+# groff is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free
+# Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# groff is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+groff="${abs_top_builddir:-.}/test-groff"
+
+set -e
+
+# Regression-test Savannah #62191.
+#
+# Line continuation in a row of table data should not make the input
+# line counter inaccurate.
+
+input='.this-is-line-1
+.TS
+L.
+foo\
+bar\
+baz\
+qux
+.TE
+.this-is-line-9'
+
+output=$(printf "%s\n" "$input" | "$groff" -Tascii -t -ww -z 2>&1)
+echo "$output" | grep -q '^troff.*:9:.*this-is-line-9'
+
+# vim:set ai et sw=4 ts=4 tw=72:
diff --git a/src/preproc/tbl/tests/do-not-overdraw-page-top-in-nroff-mode.sh b/src/preproc/tbl/tests/do-not-overdraw-page-top-in-nroff-mode.sh
new file mode 100755
index 0000000..c4698f1
--- /dev/null
+++ b/src/preproc/tbl/tests/do-not-overdraw-page-top-in-nroff-mode.sh
@@ -0,0 +1,225 @@
+#!/bin/sh
+#
+# Copyright (C) 2022-2023 Free Software Foundation, Inc.
+#
+# This file is part of groff.
+#
+# groff is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free
+# Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# groff is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+groff="${abs_top_builddir:-.}/test-groff"
+grotty="${abs_top_builddir:-.}/grotty"
+
+fail=
+
+wail () {
+ echo ...FAILED >&2
+ fail=YES
+}
+
+# Regression-test Savannah #63449.
+#
+# In nroff mode, a table at the top of the page (i.e., one starting at
+# the first possible output line, with no vertical margin) that has
+# vertical rules should not overdraw the page top and provoke a warning
+# from grotty about "character(s) above [the] first line [being]
+# discarded".
+
+# Case 1: No horizontal rules; vertical rule at leading column.
+input='.TS
+| L.
+foo
+.TE'
+
+tmp=$(printf "%s\n" "$input" | "$groff" -t -Z -Tascii -P-cbou)
+output=$(printf "%s" "$tmp" | "$grotty" -F ./font 2>/dev/null)
+error=$(printf "%s" "$tmp" | "$grotty" -F ./font 2>&1 >/dev/null)
+echo "$output"
+
+echo "checking that no diagnostic messages are produced by grotty (1)"
+echo "$error" | grep -q 'grotty:' && wail
+
+echo "checking that a lone vertical rule starts the first output line"
+echo "$output" | sed -n '1p' | grep -Fqx '|' || wail
+
+# Case 2: No horizontal rules; vertical rule between columns.
+input='.TS
+tab(@);
+L | L.
+foo@bar
+.TE'
+
+tmp=$(printf "%s\n" "$input" | "$groff" -t -Z -Tascii -P-cbou)
+output=$(printf "%s" "$tmp" | "$grotty" -F ./font 2>/dev/null)
+error=$(printf "%s" "$tmp" | "$grotty" -F ./font 2>&1 >/dev/null)
+echo "$output"
+
+echo "checking that no diagnostic messages are produced by grotty (2)"
+echo "$error" | grep -q 'grotty:' && wail
+
+echo "checking that a lone vertical rule ends the first output line"
+echo "$output" | sed -n '1p' | grep -Eqx ' +\|' || wail
+
+# Case 3: No horizontal rules; vertical rule at trailing column.
+input='.TS
+L |.
+foo
+.TE'
+
+tmp=$(printf "%s\n" "$input" | "$groff" -t -Z -Tascii -P-cbou)
+output=$(printf "%s" "$tmp" | "$grotty" -F ./font 2>/dev/null)
+error=$(printf "%s" "$tmp" | "$grotty" -F ./font 2>&1 >/dev/null)
+echo "$output"
+
+echo "checking that no diagnostic messages are produced by grotty (3)"
+echo "$error" | grep -q 'grotty:' && wail
+
+echo "checking that a lone vertical rule ends the first output line"
+echo "$output" | sed -n '1p' | grep -Eqx ' +\|' || wail
+
+# Case 4: Vertical rule with horizontal rule in row description.
+input='.TS
+_
+L |.
+foo
+.TE'
+
+tmp=$(printf "%s\n" "$input" | "$groff" -t -Z -Tascii -P-cbou)
+output=$(printf "%s" "$tmp" | "$grotty" -F ./font 2>/dev/null)
+error=$(printf "%s" "$tmp" | "$grotty" -F ./font 2>&1 >/dev/null)
+echo "$output"
+
+echo "checking that no diagnostic messages are produced by grotty (4)"
+echo "$error" | grep -q 'grotty:' && wail
+
+echo "checking that intersection is placed on the first output line"
+echo "$output" | sed -n '1p' | grep -q '+' || wail
+
+# Case 5: Vertical rule with horizontal rule as first table datum.
+input='.TS
+L |.
+_
+foo
+.TE'
+
+tmp=$(printf "%s\n" "$input" | "$groff" -t -Z -Tascii -P-cbou)
+output=$(printf "%s" "$tmp" | "$grotty" -F ./font 2>/dev/null)
+error=$(printf "%s" "$tmp" | "$grotty" -F ./font 2>&1 >/dev/null)
+echo "$output"
+
+echo "checking that no diagnostic messages are produced by grotty (5)"
+echo "$error" | grep -q 'grotty:' && wail
+
+echo "checking that intersection is placed on the first output line"
+echo "$output" | sed -n '1p' | grep -q '+' || wail
+
+# Case 6: Horizontal rule as non-first row description with vertical
+# rule.
+input='.TS
+L,_,L |.
+foo
+bar
+.TE'
+
+tmp=$(printf "%s\n" "$input" | "$groff" -t -Z -Tascii -P-cbou)
+output=$(printf "%s" "$tmp" | "$grotty" -F ./font 2>/dev/null)
+error=$(printf "%s" "$tmp" | "$grotty" -F ./font 2>&1 >/dev/null)
+echo "$output"
+
+echo "checking that no diagnostic messages are produced by grotty (6)"
+echo "$error" | grep -q 'grotty:' && wail
+
+echo "checking that table data begin on first output line"
+echo "$output" | sed -n '1p' | grep -q 'foo' || wail
+
+# Also ensure that no collateral damage arises in related cases.
+
+# Case 7: Horizontal rule as first table datum with no vertical rule.
+input='.TS
+L.
+_
+foo
+.TE'
+
+tmp=$(printf "%s\n" "$input" | "$groff" -t -Z -Tascii -P-cbou)
+output=$(printf "%s" "$tmp" | "$grotty" -F ./font 2>/dev/null)
+error=$(printf "%s" "$tmp" | "$grotty" -F ./font 2>&1 >/dev/null)
+echo "$output"
+
+echo "checking that no diagnostic messages are produced by grotty (7)"
+echo "$error" | grep -q 'grotty:' && wail
+
+echo "checking that horizontal rule is placed on the first output line"
+echo "$output" | sed -n '1p' | grep -q '^---' || wail
+
+# Case 8: Horizontal rule as last table datum with no vertical rule.
+input='.TS
+L.
+foo
+_
+.TE
+.ec @
+.pl @n(nlu'
+
+tmp=$(printf "%s\n" "$input" | "$groff" -t -Z -Tascii -P-cbou)
+output=$(printf "%s" "$tmp" | "$grotty" -F ./font 2>/dev/null)
+error=$(printf "%s" "$tmp" | "$grotty" -F ./font 2>&1 >/dev/null)
+echo "$output"
+
+echo "checking that no diagnostic messages are produced by grotty (8)"
+echo "$error" | grep -q 'grotty:' && wail
+
+echo "checking that horizontal rule is placed on the last output line"
+echo "$output" | sed -n '$p' | grep -q '^---' || wail
+
+# Case 9: Horizontal rule in row description with no vertical rule.
+input='.TS
+_
+L.
+foo
+.TE'
+
+tmp=$(printf "%s\n" "$input" | "$groff" -t -Z -Tascii -P-cbou)
+output=$(printf "%s" "$tmp" | "$grotty" -F ./font 2>/dev/null)
+error=$(printf "%s" "$tmp" | "$grotty" -F ./font 2>&1 >/dev/null)
+echo "$output"
+
+echo "checking that no diagnostic messages are produced by grotty (9)"
+echo "$error" | grep -q 'grotty:' && wail
+
+echo "checking that intersection is placed on the first output line"
+echo "$output" | sed -n '1p' | grep -q '^---' || wail
+
+# Case 10: Horizontal rule as non-first row description with no vertical
+# rule.
+input='.TS
+L,_,L.
+foo
+bar
+.TE'
+
+tmp=$(printf "%s\n" "$input" | "$groff" -t -Z -Tascii -P-cbou)
+output=$(printf "%s" "$tmp" | "$grotty" -F ./font 2>/dev/null)
+error=$(printf "%s" "$tmp" | "$grotty" -F ./font 2>&1 >/dev/null)
+echo "$output"
+
+echo "checking that no diagnostic messages are produced by grotty (10)"
+echo "$error" | grep -q 'grotty:' && wail
+
+echo "checking that table data begin on first output line"
+echo "$output" | sed -n '1p' | grep -q 'foo' || wail
+
+test -z "$fail"
+
+# vim:set ai et sw=4 ts=4 tw=72:
diff --git a/src/preproc/tbl/tests/do-not-overlap-bottom-border-in-nroff.sh b/src/preproc/tbl/tests/do-not-overlap-bottom-border-in-nroff.sh
new file mode 100755
index 0000000..4f63eed
--- /dev/null
+++ b/src/preproc/tbl/tests/do-not-overlap-bottom-border-in-nroff.sh
@@ -0,0 +1,62 @@
+#!/bin/sh
+#
+# Copyright (C) 2022 Free Software Foundation, Inc.
+#
+# This file is part of groff.
+#
+# groff is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free
+# Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# groff is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+groff="${abs_top_builddir:-.}/test-groff"
+
+fail=
+
+wail () {
+ echo "...FAILED" >&2
+ fail=yes
+}
+
+# Regression-test Savannah #49390.
+
+input='foo
+.TS
+box;
+L.
+bar
+.TE
+baz
+.pl \n(nlu
+'
+
+echo "checking for post-table text non-overlap of (single) box border"
+output=$(printf "%s" "$input" | "$groff" -t -Tascii)
+echo "$output" | grep -q baz || wail
+
+input='foo
+.TS
+doublebox;
+L.
+bar
+.TE
+baz
+.pl \n(nlu
+'
+
+echo "checking for post-table text non-overlap of double box border"
+output=$(printf "%s" "$input" | "$groff" -t -Tascii)
+echo "$output" | grep -q baz || wail
+
+test -z "$fail"
+
+# vim:set ai et sw=4 ts=4 tw=72:
diff --git a/src/preproc/tbl/tests/do-not-segv-on-invalid-vertical-span-entry.sh b/src/preproc/tbl/tests/do-not-segv-on-invalid-vertical-span-entry.sh
new file mode 100755
index 0000000..1d672ec
--- /dev/null
+++ b/src/preproc/tbl/tests/do-not-segv-on-invalid-vertical-span-entry.sh
@@ -0,0 +1,41 @@
+#!/bin/sh
+#
+# Copyright (C) 2021 Free Software Foundation, Inc.
+#
+# This file is part of groff.
+#
+# groff is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free
+# Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# groff is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+tbl="${abs_top_builddir:-.}/tbl"
+
+# Regression-test Savannah #61417.
+#
+# Don't segfault because we tried to span down from an invalid span that
+# tbl neglected to replace with an empty table entry.
+
+test -f core && exit 77 # skip
+
+input=$(cat <<EOF
+.TS
+l.
+\^
+\^
+.TE
+EOF
+)
+output=$(printf "%s" "$input" | "$tbl")
+! test -f core
+
+# vim:set ai noet sw=4 ts=4 tw=72:
diff --git a/src/preproc/tbl/tests/do-not-segv-when-backslash-R-in-text-block.sh b/src/preproc/tbl/tests/do-not-segv-when-backslash-R-in-text-block.sh
new file mode 100755
index 0000000..30bd9f6
--- /dev/null
+++ b/src/preproc/tbl/tests/do-not-segv-when-backslash-R-in-text-block.sh
@@ -0,0 +1,75 @@
+#!/bin/sh
+#
+# Copyright (C) 2022 Free Software Foundation, Inc.
+#
+# This file is part of groff.
+#
+# groff is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free
+# Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# groff is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+groff="${abs_top_builddir:-.}/test-groff"
+
+fail=
+
+wail () {
+ echo "...FAILED" >&2
+ fail=yes
+}
+
+# Regression-test Savannah #62366.
+#
+# Do not SEGV when a text block begins with a repeating glyph token, and
+# do not malformat the output if it ends with one.
+
+test -f core && exit 77 # skip
+
+input='.TS
+L.
+T{
+\Ra
+T}
+.TE
+.TS
+L.
+T{
+foo
+\Ra
+T}
+.TE
+.TS
+L.
+T{
+foo
+\Ra
+bar
+T}
+.TE'
+
+output=$(printf "%s\n" "$input" | "$groff" -t -Tascii -P-cbou)
+
+echo "checking that tbl doesn't segfault" >&2
+test -f core && wail
+
+echo "checking text block starting with repeating glyph" >&2
+echo "$output" | sed -n 1p | grep -qx 'a' || wail
+
+echo "checking text block ending with repeating glyph" >&2
+echo "$output" | sed -n 2p | grep -qx 'foo a' || wail
+
+echo "checking text block containing repeating glyph" >&2
+echo "$output" | sed -n 3p | grep -qx 'foo a bar' || wail
+
+test -z "$fail"
+
+# vim:set ai et sw=4 ts=4 tw=72:
diff --git a/src/preproc/tbl/tests/expand-region-option-works.sh b/src/preproc/tbl/tests/expand-region-option-works.sh
new file mode 100755
index 0000000..97da39d
--- /dev/null
+++ b/src/preproc/tbl/tests/expand-region-option-works.sh
@@ -0,0 +1,173 @@
+#!/bin/sh
+#
+# Copyright (C) 2023 Free Software Foundation, Inc.
+#
+# This file is part of groff.
+#
+# groff is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free
+# Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# groff is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+groff="${abs_top_builddir:-.}/test-groff"
+
+fail=
+
+wail () {
+ echo ...FAILED >&2
+ fail=YES
+}
+
+# Ensure that the "expand" region option expands to the line length.
+
+# Case 1: "naked" table
+
+input='.ll 64n
+.nf
+1234567890123456789012345678901234567890123456789012345678901234
+.fi
+.TS
+expand tab(@);
+L L L L L.
+abcdef@abcdef@abcdef@abcdef@abcdef
+.TE
+.pl \n(nlu'
+
+echo "checking unboxed table without vertical rules" >&2
+output=$(printf "%s\n" "$input" | "$groff" -t -Tascii)
+echo "$output"
+echo "$output" | sed -n '2p' \
+ | grep -Eqx '(abcdef {8,9}){4}abcdef' || wail
+
+# Case 2: left-hand vertical rule
+
+input='.ll 64n
+.nf
+1234567890123456789012345678901234567890123456789012345678901234
+.fi
+.TS
+expand tab(@);
+| L L L L L.
+abcdef@abcdef@abcdef@abcdef@abcdef
+.TE
+.pl \n(nlu'
+
+echo "checking unboxed table with left-hand rule" >&2
+output=$(printf "%s\n" "$input" | "$groff" -t -Tascii)
+echo "$output"
+echo "$output" | sed -n '3p' \
+ | grep -Eqx '\| abcdef {8}abcdef {7}abcdef {8}abcdef {9}abcdef' \
+ || wail
+
+# Case 3: right-hand vertical rule
+
+input='.ll 64n
+.nf
+1234567890123456789012345678901234567890123456789012345678901234
+.fi
+.TS
+expand tab(@);
+L L L L L |.
+abcdef@abcdef@abcdef@abcdef@abcdef
+.TE
+.pl \n(nlu'
+
+echo "checking unboxed table with right-hand rule" >&2
+output=$(printf "%s\n" "$input" | "$groff" -t -Tascii)
+echo "$output"
+echo "$output" | sed -n '3p' \
+ | grep -Eqx 'abcdef {8}abcdef {7}abcdef {8}abcdef {9}abcdef \|' \
+ || wail
+
+# Case 4: vertical rule on both ends
+
+input='.ll 64n
+.nf
+1234567890123456789012345678901234567890123456789012345678901234
+.fi
+.TS
+expand tab(@);
+| L L L L L |.
+abcdef@abcdef@abcdef@abcdef@abcdef
+.TE
+.pl \n(nlu'
+
+echo "checking unboxed table with both rules" >&2
+output=$(printf "%s\n" "$input" | "$groff" -t -Tascii)
+echo "$output"
+echo "$output" | sed -n '3p' \
+ | grep -Eqx '\| abcdef {7}abcdef {7}abcdef {8}abcdef {8}abcdef \|' \
+ || wail
+
+# Case 5: vertical rule on both ends and interior rule
+
+input='.ll 64n
+.nf
+1234567890123456789012345678901234567890123456789012345678901234
+.fi
+.TS
+expand tab(@);
+| L L L | L L |.
+abcdef@abcdef@abcdef@abcdef@abcdef
+.TE
+.pl \n(nlu'
+
+echo "checking unboxed table with both edge and interior rules" >&2
+output=$(printf "%s\n" "$input" | "$groff" -t -Tascii)
+echo "$output"
+echo "$output" | sed -n '3p' \
+ | grep -Eqx \
+ '\| abcdef {7}abcdef {7}abcdef {4}\| {3}abcdef {8}abcdef \|' || wail
+
+# Case 6: boxed table
+
+input='.ll 64n
+.nf
+1234567890123456789012345678901234567890123456789012345678901234
+.fi
+.TS
+box expand tab(@);
+L L L L L.
+abcdef@abcdef@abcdef@abcdef@abcdef
+.TE
+.pl \n(nlu'
+
+echo "checking boxed table without interior rules" >&2
+output=$(printf "%s\n" "$input" | "$groff" -t -Tascii)
+echo "$output"
+echo "$output" | sed -n '3p' \
+ | grep -Eqx '\| abcdef {7}abcdef {7}abcdef {8}abcdef {8}abcdef \|' \
+ || wail
+
+# Case 7: boxed table with interior vertical rule
+
+input='.ll 64n
+.nf
+1234567890123456789012345678901234567890123456789012345678901234
+.fi
+.TS
+box expand tab(@);
+L L L | L L.
+abcdef@abcdef@abcdef@abcdef@abcdef
+.TE
+.pl \n(nlu'
+
+echo "checking boxed table with interior rule" >&2
+output=$(printf "%s\n" "$input" | "$groff" -t -Tascii)
+echo "$output"
+echo "$output" | sed -n '3p' \
+ | grep -Eqx \
+ '\| abcdef {7}abcdef {7}abcdef {4}\| {3}abcdef {8}abcdef \|' || wail
+
+test -z "$fail"
+
+# vim:set ai et sw=4 ts=4 tw=72:
diff --git a/src/preproc/tbl/tests/format-time-diagnostics-work.sh b/src/preproc/tbl/tests/format-time-diagnostics-work.sh
new file mode 100755
index 0000000..9d422bd
--- /dev/null
+++ b/src/preproc/tbl/tests/format-time-diagnostics-work.sh
@@ -0,0 +1,268 @@
+#!/bin/sh
+#
+# Copyright (C) 2022 Free Software Foundation, Inc.
+#
+# This file is part of groff.
+#
+# groff is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free
+# Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# groff is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+groff="${abs_top_builddir:-.}/test-groff"
+
+set -e
+
+# Ensure we get diagnostics when we expect to, and not when we don't.
+#
+# Do NOT pattern-match the text of the diagnostic messages; those should
+# be left flexible. (Some day they might even be localized.)
+
+# As of this writing, there are 5 distinct format-time diagnostic
+# messages that tbl writes roff code to generate, one of which can be
+# produced two different ways.
+
+# Diagnostic #1: a row overruns the page bottom
+input='.pl 2v
+.TS
+;
+L.
+T{
+.nf
+1
+2
+3
+T}
+.TE
+'
+
+echo "checking for diagnostic when row with text box overruns page" \
+ "bottom"
+output=$(printf "%s" "$input" | "$groff" -Tascii -t 2>&1 >/dev/null)
+nlines=$(echo "$output" | grep . | wc -l)
+test $nlines -eq 1
+
+echo "checking 'nowarn' suppression of diagnostic when row with text" \
+ "box overruns page bottom"
+input_nowarn=$(printf "%s" "$input" | sed 's/;/nowarn;/')
+output=$(printf "%s" "$input_nowarn" \
+ | "$groff" -Tascii -t 2>&1 >/dev/null)
+nlines=$(echo "$output" | grep . | wc -l)
+test $nlines -eq 0
+
+echo "checking 'nokeep' suppression of diagnostic when row with text" \
+ "box overruns page bottom"
+input_nowarn=$(printf "%s" "$input" | sed 's/;/nokeep;/')
+output=$(printf "%s" "$input_nowarn" \
+ | "$groff" -Tascii -t 2>&1 >/dev/null)
+nlines=$(echo "$output" | grep . | wc -l)
+test $nlines -eq 0
+
+# The other way to get "diagnostic #1" is to have a row that is too
+# tall _without_ involving a text block, for instance by having a font
+# or vertical spacing that is too high.
+input='.pl 2v
+.vs 3v
+.TS
+;
+L.
+1
+.TE
+'
+
+echo "checking for diagnostic when row with large vertical spacing" \
+ "overruns page bottom"
+output=$(printf "%s" "$input" | "$groff" -Tascii -t 2>&1 >/dev/null)
+nlines=$(echo "$output" | grep . | wc -l)
+test $nlines -eq 1
+
+echo "checking 'nowarn' suppression of diagnostic when row with large" \
+ "vertical spacing overruns page bottom"
+input_nowarn=$(printf "%s" "$input" | sed 's/;/nowarn;/')
+output=$(printf "%s" "$input_nowarn" \
+ | "$groff" -Tascii -t 2>&1 >/dev/null)
+nlines=$(echo "$output" | grep . | wc -l)
+test $nlines -eq 0
+
+echo "checking 'nokeep' suppression of diagnostic when row with large" \
+ "vertical spacing overruns page bottom"
+input_nowarn=$(printf "%s" "$input" | sed 's/;/nokeep;/')
+output=$(printf "%s" "$input_nowarn" \
+ | "$groff" -Tascii -t 2>&1 >/dev/null)
+nlines=$(echo "$output" | grep . | wc -l)
+test $nlines -eq 0
+
+# Diagnostic #2: a boxed table won't fit on a page
+
+input='.pl 2v
+.vs 3v
+.TS
+box;
+L.
+1
+.TE
+'
+
+echo "checking for diagnostic when boxed table won't fit on page"
+output=$(printf "%s" "$input" | "$groff" -Tascii -t 2>&1 >/dev/null)
+nlines=$(echo "$output" | grep . | wc -l)
+test $nlines -eq 1
+
+# The above is an error, so the "nowarn" region option won't shut it up.
+#
+# However, "nokeep" does--but arguably shouldn't. See
+# <https://savannah.gnu.org/bugs/?61878>. If that gets fixed, we should
+# test that we still get a diagnostic even with the option given.
+
+# Diagnostic #3: unexpanded columns overrun the line length
+#
+# Case 1: no 'x' column modifiers used
+
+input='.pl 2v
+.ll 10n
+.TS
+;
+L.
+12345678901
+.TE
+'
+
+echo "checking for diagnostic when unexpanded columns overrun line" \
+ "length (1)"
+output=$(printf "%s" "$input" | "$groff" -Tascii -t 2>&1 >/dev/null)
+nlines=$(echo "$output" | grep . | wc -l)
+test $nlines -eq 1
+
+echo "checking 'nowarn' suppression of diagnostic when unexpanded" \
+ "columns overrun line length (1)"
+input_nowarn=$(printf "%s" "$input" | sed 's/;/nowarn;/')
+output=$(printf "%s" "$input_nowarn" \
+ | "$groff" -Tascii -t 2>&1 >/dev/null)
+nlines=$(echo "$output" | grep . | wc -l)
+test $nlines -eq 0
+
+# Avoiding keeps won't get you out of this one.
+echo "checking 'nokeep' NON-suppression of diagnostic when unexpanded" \
+ "columns overrun line length (1)"
+input_nowarn=$(printf "%s" "$input" | sed 's/;/nokeep;/')
+output=$(printf "%s" "$input_nowarn" \
+ | "$groff" -Tascii -t 2>&1 >/dev/null)
+nlines=$(echo "$output" | grep . | wc -l)
+test $nlines -eq 1
+
+# Case 2: 'x' column modifier used
+#
+# This worked as a "get out of jail (warning) free" card in groff 1.22.4
+# and earlier; i.e., it incorrectly suppressed the warning. See
+# Savannah #61854.
+
+input='.pl 2v
+.ll 10n
+.TS
+;
+Lx.
+12345678901
+.TE
+'
+
+echo "checking for diagnostic when unexpanded columns overrun line" \
+ "length (2)"
+output=$(printf "%s" "$input" | "$groff" -Tascii -t 2>&1 >/dev/null)
+nlines=$(echo "$output" | grep . | wc -l)
+test $nlines -eq 1
+
+echo "checking 'nowarn' suppression of diagnostic when unexpanded" \
+ "columns overrun line length (2)"
+input_nowarn=$(printf "%s" "$input" | sed 's/;/nowarn;/')
+output=$(printf "%s" "$input_nowarn" \
+ | "$groff" -Tascii -t 2>&1 >/dev/null)
+nlines=$(echo "$output" | grep . | wc -l)
+test $nlines -eq 0
+
+# Avoiding keeps won't get you out of this one.
+echo "checking 'nokeep' NON-suppression of diagnostic when unexpanded" \
+ "columns overrun line length (2)"
+input_nowarn=$(printf "%s" "$input" | sed 's/;/nokeep;/')
+output=$(printf "%s" "$input_nowarn" \
+ | "$groff" -Tascii -t 2>&1 >/dev/null)
+nlines=$(echo "$output" | grep . | wc -l)
+test $nlines -eq 1
+
+# Diagnostic #4: expanded table gets all column separation squashed out
+
+input='.pl 3v
+.ll 10n
+.TS
+tab(;) expand;
+L L.
+abcde;fghij
+.TE
+'
+
+echo "checking for diagnostic when region-expanded table has column" \
+ "separation eliminated"
+output=$(printf "%s" "$input" | "$groff" -Tascii -t 2>&1 >/dev/null)
+nlines=$(echo "$output" | grep . | wc -l)
+test $nlines -eq 1
+
+echo "checking 'nowarn' suppression of diagnostic when" \
+ "region-expanded table has column separation eliminated"
+input_nowarn=$(printf "%s" "$input" | sed 's/;$/ nowarn;/')
+output=$(printf "%s" "$input_nowarn" \
+ | "$groff" -Tascii -t 2>&1 >/dev/null)
+nlines=$(echo "$output" | grep . | wc -l)
+test $nlines -eq 0
+
+# Avoiding keeps won't get you out of this one.
+echo "checking 'nokeep' NON-suppression of diagnostic when" \
+ "region-expanded table has column separation eliminated"
+input_nowarn=$(printf "%s" "$input" | sed 's/;$/ nokeep;/')
+output=$(printf "%s" "$input_nowarn" \
+ | "$groff" -Tascii -t 2>&1 >/dev/null)
+nlines=$(echo "$output" | grep . | wc -l)
+test $nlines -eq 1
+
+# Diagnostic #5: expanded table gets column separation reduced
+
+input='.pl 3v
+.ll 10n
+.TS
+tab(;) expand;
+L L.
+abcd;efgh
+.TE
+'
+
+echo "checking for diagnostic when region-expanded table has column" \
+ "separation reduced"
+output=$(printf "%s" "$input" | "$groff" -Tascii -t 2>&1 >/dev/null)
+nlines=$(echo "$output" | grep . | wc -l)
+test $nlines -eq 1
+
+echo "checking 'nowarn' suppression of diagnostic when" \
+ "region-expanded table has column separation reduced"
+input_nowarn=$(printf "%s" "$input" | sed 's/;$/ nowarn;/')
+output=$(printf "%s" "$input_nowarn" \
+ | "$groff" -Tascii -t 2>&1 >/dev/null)
+nlines=$(echo "$output" | grep . | wc -l)
+test $nlines -eq 0
+
+# Avoiding keeps won't get you out of this one.
+echo "checking 'nokeep' NON-suppression of diagnostic when" \
+ "region-expanded table has column separation reduced"
+input_nowarn=$(printf "%s" "$input" | sed 's/;$/ nokeep;/')
+output=$(printf "%s" "$input_nowarn" \
+ | "$groff" -Tascii -t 2>&1 >/dev/null)
+nlines=$(echo "$output" | grep . | wc -l)
+test $nlines -eq 1
+
+# vim:set ai et sw=4 ts=4 tw=72:
diff --git a/src/preproc/tbl/tests/save-and-restore-hyphenation-parameters.sh b/src/preproc/tbl/tests/save-and-restore-hyphenation-parameters.sh
new file mode 100755
index 0000000..e368b31
--- /dev/null
+++ b/src/preproc/tbl/tests/save-and-restore-hyphenation-parameters.sh
@@ -0,0 +1,58 @@
+#!/bin/sh
+#
+# Copyright (C) 2021 Free Software Foundation, Inc.
+#
+# This file is part of groff.
+#
+# groff is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free
+# Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# groff is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+groff="${abs_top_builddir:-.}/test-groff"
+
+set -e
+
+# Regression-test Savannah #59971.
+#
+# Hyphenation needs to be restored between (and after) text blocks just
+# as adjustment is.
+
+EXAMPLE='.nr LL 78n
+.hw a-bc-def-ghij-klmno-pqrstu-vwxyz
+.LP
+Here is a table with hyphenation disabled in its text block.
+.
+.TS
+l lx.
+foo T{
+.nh
+abcdefghijklmnopqrstuvwxyz
+abcdefghijklmnopqrstuvwxyz
+abcdefghijklmnopqrstuvwxyz
+T}
+.TE
+.
+Let us see if hyphenation is enabled again as it should be.
+abcdefghijklmnopqrstuvwxyz'
+
+OUTPUT=$(printf "%s\n" "$EXAMPLE" | "$groff" -Tascii -P-cbou -t -ms)
+
+echo "$OUTPUT"
+
+echo "testing whether hyphenation disabled in table text block" >&2
+! echo "$OUTPUT" | grep '^foo' | grep -- '-$'
+
+echo "testing whether hyphenation enabled after table" >&2
+echo "$OUTPUT" | grep -qx 'Let us see.*lmno-'
+
+# vim:set ai noet sw=4 ts=4 tw=72:
diff --git a/src/preproc/tbl/tests/save-and-restore-inter-sentence-space.sh b/src/preproc/tbl/tests/save-and-restore-inter-sentence-space.sh
new file mode 100755
index 0000000..e9a06d8
--- /dev/null
+++ b/src/preproc/tbl/tests/save-and-restore-inter-sentence-space.sh
@@ -0,0 +1,79 @@
+#!/bin/sh
+#
+# Copyright (C) 2022 Free Software Foundation, Inc.
+#
+# This file is part of groff.
+#
+# groff is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free
+# Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# groff is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+groff="${abs_top_builddir:-.}/test-groff"
+
+fail=
+
+wail () {
+ echo ...FAILED >&2
+ fail=YES
+}
+
+# Regression-test Savannah #61909.
+#
+# Inter-sentence space should not be applied to the content of ordinary
+# table entries. They are set "rigidly" (tbl(1)), also without filling,
+# adjustment, hyphenation or breaking. If you want those things, use a
+# text block.
+
+input='.ss 12 120
+Before one.
+Before two.
+.TS
+L.
+.\" two spaces
+Foo. Bar.
+.\" four spaces
+Baz. Qux.
+.\" two spaces
+T{
+Ack. Nak.
+T}
+.TE
+After one.
+After two.
+'
+
+output=$(printf "%s\n" "$input" | "$groff" -Tascii -P-cbou -t)
+echo "$output"
+
+echo "checking that inter-sentence space is altered too early"
+echo "$output" \
+ | grep -Fqx 'Before one. Before two.' || wail # 11 spaces
+
+echo "checking that inter-sentence space is not applied to ordinary" \
+ "table entries (1)"
+echo "$output" | grep -Fqx 'Foo. Bar.' || wail # 2 spaces
+
+echo "checking that inter-sentence space is not applied to ordinary" \
+ "table entries (2)"
+echo "$output" | grep -Fqx 'Baz. Qux.' || wail # 4 spaces
+
+echo "checking that inter-sentence space is applied to text blocks"
+echo "$output" | grep -Fqx 'Ack. Nak.' || wail # 11 spaces
+
+echo "checking that inter-sentence space is restored after table"
+echo "$output" \
+ | grep -Fqx 'After one. After two.' || wail # 11 spaces
+
+test -z "$fail"
+
+# vim:set ai et sw=4 ts=4 tw=72:
diff --git a/src/preproc/tbl/tests/save-and-restore-line-numbering.sh b/src/preproc/tbl/tests/save-and-restore-line-numbering.sh
new file mode 100755
index 0000000..592b43a
--- /dev/null
+++ b/src/preproc/tbl/tests/save-and-restore-line-numbering.sh
@@ -0,0 +1,85 @@
+#!/bin/sh
+#
+# Copyright (C) 2022 Free Software Foundation, Inc.
+#
+# This file is part of groff.
+#
+# groff is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free
+# Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# groff is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+groff="${abs_top_builddir:-.}/test-groff"
+
+fail=
+
+wail () {
+ echo ...FAILED >&2
+ fail=YES
+}
+
+# Regression-test Savannah #60140.
+#
+# Line numbering needs to be suspended within a table and restored
+# afterward. Historical implementations handled line numbering in
+# tables badly when text blocks were used.
+
+input='.nm 1
+Here is a line of output.
+Sic transit adispicing meatballs.
+We pad it out with more content to ensure that the line breaks.
+.TS
+L.
+This is my table.
+There are many like it but this one is mine.
+T{
+Ut enim ad minima veniam,
+quis nostrum exercitationem ullam corporis suscipitlaboriosam,
+nisi ut aliquid ex ea commodi consequatur?
+T}
+.TE
+What is the line number now?'
+
+output=$(printf "%s\n" "$input" | "$groff" -Tascii -P-cbou -t)
+echo "$output"
+
+echo "testing that line numbering is suppressed in table" >&2
+echo "$output" | grep -Fqx 'This is my table.' || wail
+
+echo "testing that line numbering is restored after table" >&2
+echo "$output" | grep -Eq '3 +What is the line number now\?' || wail
+
+input='.nf
+.nm 1
+test of line numbering suppression
+five
+four
+.nn 3
+three
+.TS
+L.
+I am a table.
+I have two rows.
+.TE
+two
+one
+numbering returns here'
+
+output=$(printf "%s\n" "$input" | "$groff" -Tascii -P-cbou -t)
+echo "$output"
+
+echo "testing that suppressed numbering is restored correctly" >&2
+echo "$output" | grep -Eq '4 +numbering returns here' || wail
+
+test -z "$fail"
+
+# vim:set ai et sw=4 ts=4 tw=72:
diff --git a/src/preproc/tbl/tests/save-and-restore-tab-stops.sh b/src/preproc/tbl/tests/save-and-restore-tab-stops.sh
new file mode 100755
index 0000000..b98922a
--- /dev/null
+++ b/src/preproc/tbl/tests/save-and-restore-tab-stops.sh
@@ -0,0 +1,84 @@
+#!/bin/sh
+#
+# Copyright (C) 2020 Free Software Foundation, Inc.
+#
+# This file is part of groff.
+#
+# groff is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free
+# Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# groff is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+groff="${abs_top_builddir:-.}/test-groff"
+
+# Regression-test Savannah #42978.
+#
+# When tbl changes the tab stops, it needs to restore them.
+#
+# Based on an example by Bjarni Igni Gislason.
+
+EXAMPLE='.TH tbl\-tabs\-test 1 2020-10-20 "groff test suite"
+.SH Name
+tbl\-tabs\-test \- see if tbl messes up the tab stops
+.SH Description
+Do not use tabs in man pages outside of
+.BR .TS / .TE
+regions.
+.PP
+But if you do.\|.\|.
+.PP
+.TS
+l l l.
+table entries long enough to change the tab stops
+.TE
+.PP
+.EX
+#!/bin/sh
+case $#
+1)
+ if foo
+ then
+ bar
+ else
+ if baz
+ then
+ qux
+ fi
+ fi
+;;
+esac
+.EE'
+
+OUTPUT=$(printf "%s\n" "$EXAMPLE" | "$groff" -Tascii -P-cbou -t -man)
+FAIL=
+
+if ! echo "$OUTPUT" | grep -Eq '^ {12}if foo$'
+then
+ FAIL=yes
+ echo "first tab stop is wrong" >&2
+fi
+
+if ! echo "$OUTPUT" | grep -Eq '^ {17}bar$'
+then
+ FAIL=yes
+ echo "second tab stop is wrong" >&2
+fi
+
+if ! echo "$OUTPUT" | grep -Eq '^ {22}qux$'
+then
+ FAIL=yes
+ echo "third tab stop is wrong" >&2
+fi
+
+test -z "$FAIL"
+
+# vim:set ai noet sw=4 ts=4 tw=72:
diff --git a/src/preproc/tbl/tests/warn-on-long-boxed-unkept-table.sh b/src/preproc/tbl/tests/warn-on-long-boxed-unkept-table.sh
new file mode 100755
index 0000000..621a752
--- /dev/null
+++ b/src/preproc/tbl/tests/warn-on-long-boxed-unkept-table.sh
@@ -0,0 +1,56 @@
+#!/bin/sh
+#
+# Copyright (C) 2022 Free Software Foundation, Inc.
+#
+# This file is part of groff.
+#
+# groff is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free
+# Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# groff is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+groff="${abs_top_builddir:-.}/test-groff"
+
+fail=
+
+wail () {
+ echo ...FAILED >&2
+ fail=YES
+}
+
+# Regression-test Savannah #61878.
+#
+# A boxed, unkept table that overruns the page bottom will produce ugly
+# output; it looks especially bizarre in nroff mode.
+#
+# We set the page length to 2v to force a problem (any boxed table in
+# nroff mode needs 3 vees minimum), and put a page break at the start to
+# catch an incorrectly initialized starting page number for the table.
+
+input='.pl 2v
+.bp
+.TS
+box nokeep;
+L.
+Z
+.TE'
+
+output=$(printf "%s" "$input" | "$groff" -t -Tascii 2>/dev/null)
+error=$(printf "%s" "$input" | "$groff" -t -Tascii 2>&1 >/dev/null)
+echo "$output"
+
+echo "checking that a diagnostic message is produced"
+echo "$error" | grep -q 'warning: boxed.*page 2$' || wail
+
+test -z "$fail"
+
+# vim:set ai et sw=4 ts=4 tw=72:
diff --git a/src/preproc/tbl/tests/x-column-modifier-works.sh b/src/preproc/tbl/tests/x-column-modifier-works.sh
new file mode 100755
index 0000000..da9b890
--- /dev/null
+++ b/src/preproc/tbl/tests/x-column-modifier-works.sh
@@ -0,0 +1,172 @@
+#!/bin/sh
+#
+# Copyright (C) 2023 Free Software Foundation, Inc.
+#
+# This file is part of groff.
+#
+# groff is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free
+# Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# groff is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+groff="${abs_top_builddir:-.}/test-groff"
+
+fail=
+
+wail () {
+ echo ...FAILED >&2
+ fail=YES
+}
+
+# Ensure that the "x" column modifier causes table expansion to the line
+# length.
+
+# Case 1: "naked" table
+
+input='.ll 64n
+.nf
+1234567890123456789012345678901234567890123456789012345678901234
+.fi
+.TS
+tab(@);
+Lx L L L L.
+abcdef@abcdef@abcdef@abcdef@abcdef
+.TE
+.pl \n(nlu'
+
+echo "checking unboxed table without vertical rules" >&2
+output=$(printf "%s\n" "$input" | "$groff" -t -Tascii)
+echo "$output"
+echo "$output" | sed -n '2p' \
+ | grep -Eqx 'abcdef {25}(abcdef {3}){3}abcdef' || wail
+
+# Case 2: left-hand vertical rule
+
+input='.ll 64n
+.nf
+1234567890123456789012345678901234567890123456789012345678901234
+.fi
+.TS
+tab(@);
+| Lx L L L L.
+abcdef@abcdef@abcdef@abcdef@abcdef
+.TE
+.pl \n(nlu'
+
+echo "checking unboxed table with left-hand rule" >&2
+output=$(printf "%s\n" "$input" | "$groff" -t -Tascii)
+echo "$output"
+echo "$output" | sed -n '3p' \
+ | grep -Eqx '\| abcdef {23}(abcdef {3}){3}abcdef' || wail
+
+# Case 3: right-hand vertical rule
+
+input='.ll 64n
+.nf
+1234567890123456789012345678901234567890123456789012345678901234
+.fi
+.TS
+tab(@);
+Lx L L L L |.
+abcdef@abcdef@abcdef@abcdef@abcdef
+.TE
+.pl \n(nlu'
+
+echo "checking unboxed table with right-hand rule" >&2
+output=$(printf "%s\n" "$input" | "$groff" -t -Tascii)
+echo "$output"
+echo "$output" | sed -n '3p' \
+ | grep -Eqx 'abcdef {23}(abcdef {3}){3}abcdef \|' || wail
+
+# Case 4: vertical rule on both ends
+
+input='.ll 64n
+.nf
+1234567890123456789012345678901234567890123456789012345678901234
+.fi
+.TS
+tab(@);
+| Lx L L L L |.
+abcdef@abcdef@abcdef@abcdef@abcdef
+.TE
+.pl \n(nlu'
+
+echo "checking unboxed table with both rules" >&2
+output=$(printf "%s\n" "$input" | "$groff" -t -Tascii)
+echo "$output"
+echo "$output" | sed -n '3p' \
+ | grep -Eqx '\| abcdef {21}(abcdef {3}){3}abcdef \|' || wail
+
+# Case 5: vertical rule on both ends and interior rule
+
+input='.ll 64n
+.nf
+1234567890123456789012345678901234567890123456789012345678901234
+.fi
+.TS
+tab(@);
+| Lx L L | L L |.
+abcdef@abcdef@abcdef@abcdef@abcdef
+.TE
+.pl \n(nlu'
+
+echo "checking unboxed table with both edge and interior rules" >&2
+output=$(printf "%s\n" "$input" | "$groff" -t -Tascii)
+echo "$output"
+echo "$output" | sed -n '3p' \
+ | grep -Eqx \
+ '\| abcdef {21}abcdef {3}abcdef \| abcdef {3}abcdef \|' \
+ || wail
+
+# Case 6: boxed table
+
+input='.ll 64n
+.nf
+1234567890123456789012345678901234567890123456789012345678901234
+.fi
+.TS
+box tab(@);
+Lx L L L L.
+abcdef@abcdef@abcdef@abcdef@abcdef
+.TE
+.pl \n(nlu'
+
+echo "checking boxed table without interior rules" >&2
+output=$(printf "%s\n" "$input" | "$groff" -t -Tascii)
+echo "$output"
+echo "$output" | sed -n '3p' \
+ | grep -Eqx '\| abcdef {21}(abcdef {3}){3}abcdef \|' || wail
+
+# Case 7: boxed table with interior vertical rule
+
+input='.ll 64n
+.nf
+1234567890123456789012345678901234567890123456789012345678901234
+.fi
+.TS
+box tab(@);
+Lx L L | L L.
+abcdef@abcdef@abcdef@abcdef@abcdef
+.TE
+.pl \n(nlu'
+
+echo "checking boxed table with interior rules" >&2
+output=$(printf "%s\n" "$input" | "$groff" -t -Tascii)
+echo "$output"
+echo "$output" | sed -n '3p' \
+ | grep -Eqx \
+ '\| abcdef {21}abcdef {3}abcdef \| abcdef {3}abcdef \|' \
+ || wail
+
+test -z "$fail"
+
+# vim:set ai et sw=4 ts=4 tw=72: