From e5d8f8af448ac47ad63b3a9c893a578801d12963 Mon Sep 17 00:00:00 2001
From: Alexandre Felipe <o.alexandre.felipe@gmail.com>
Date: Thu, 17 Sep 2026 08:43:52 +0100
Subject: [PATCH] pg_regress: multi-line test groups

This support multi-line test groups
instead of requiring
test: all test in a single line
it also support
test:
  tests in
  different
  lines

this will make it more readable, and showing line numbers
having one test per line makes it much easier to see how many
tests are in a group.
---
 src/test/regress/parallel_schedule | 272 ++++++++++++++++++++++++++---
 src/test/regress/pg_regress.c      |  31 +++-
 2 files changed, 278 insertions(+), 25 deletions(-)

diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index 1c84e3011b1..547766e6152 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -14,21 +14,80 @@ test: test_setup
 # ----------
 # The first group of parallel tests
 # ----------
-test: boolean char name varchar text int2 int4 int8 oid float4 float8 bit numeric txid uuid enum money rangetypes pg_lsn regproc
+test:
+    boolean
+	char
+	name
+	varchar
+	text
+	int2
+	int4
+	int8
+	oid
+	float4
+	float8
+	bit
+	numeric
+	txid
+	uuid
+	enum
+	money
+	rangetypes
+	pg_lsn
+	regproc
 
 # ----------
 # The second group of parallel tests
 # multirangetypes depends on rangetypes
 # multirangetypes shouldn't run concurrently with type_sanity
 # ----------
-test: strings md5 numerology point lseg line box path polygon circle date time timetz timestamp timestamptz interval inet macaddr macaddr8 multirangetypes
+test:
+    strings
+	md5
+	numerology
+	point
+	lseg
+	line
+	box
+	path
+	polygon
+	circle
+	date
+	time
+	timetz
+	timestamp
+	timestamptz
+	interval
+	inet
+	macaddr
+	macaddr8
+	multirangetypes
 
 # ----------
 # Another group of parallel tests
 # geometry depends on point, lseg, line, box, path, polygon, circle
 # horology depends on date, time, timetz, timestamp, timestamptz, interval
 # ----------
-test: geometry horology tstypes regex type_sanity opr_sanity misc_sanity comments expressions unicode xid mvcc database stats_import pg_ndistinct pg_dependencies oid8 encoding euc_kr
+test:
+    geometry
+	horology
+	tstypes
+	regex
+	type_sanity
+	opr_sanity
+	misc_sanity
+	comments
+	expressions
+	unicode
+	xid
+	mvcc
+	database
+	stats_import
+	pg_ndistinct
+	pg_dependencies
+	oid8
+	encoding
+	euc_kr
 
 # ----------
 # Load huge amounts of data
@@ -36,20 +95,57 @@ test: geometry horology tstypes regex type_sanity opr_sanity misc_sanity comment
 # execute two copy tests in parallel, to check that copy itself
 # is concurrent safe.
 # ----------
-test: copy copyselect copydml copyencoding insert insert_conflict
+test:
+	copy
+	copyselect
+	copydml
+	copyencoding
+	insert
+	insert_conflict
 
 # ----------
 # More groups of parallel tests
 # Note: many of the tests in later groups depend on create_index
 # ----------
-test: create_function_c create_misc create_operator create_procedure create_table create_type create_schema
-test: create_index create_index_spgist create_view index_including index_including_gist
+test:
+	create_function_c
+	create_misc
+	create_operator
+	create_procedure
+	create_table
+	create_type
+	create_schema
+
+test:
+	create_index
+	create_index_spgist
+	create_view
+	index_including
+	index_including_gist
 
 # ----------
 # Another group of parallel tests
 # (19 tests)
 # ----------
-test: create_aggregate create_function_sql create_cast constraints triggers select inherit typed_table vacuum drop_if_exists updatable_views roleattributes create_am hash_func errors infinite_recurse slope_catalog slope
+test:
+    create_aggregate
+	create_function_sql
+	create_cast
+	constraints
+	triggers
+	select
+	inherit
+	typed_table
+	vacuum
+	drop_if_exists
+	updatable_views
+	roleattributes
+	create_am
+	hash_func
+	errors
+	infinite_recurse
+	slope_catalog
+	slope
 
 # ----------
 # sanity_check does a vacuum, affecting the sort order of SELECT *
@@ -63,29 +159,91 @@ test: sanity_check
 # join depends on create_misc
 # (20 tests)
 # ----------
-test: select_into select_distinct select_distinct_on select_implicit select_having subselect union case join aggregates transactions random portals arrays btree_index hash_index update delete namespace prepared_xacts
+test:
+    select_into
+	select_distinct
+	select_distinct_on
+	select_implicit
+	select_having
+	subselect
+	union
+	case
+	join
+	aggregates
+	transactions
+	random
+	portals
+	arrays
+	btree_index
+	hash_index
+	update
+	delete
+	namespace
+	prepared_xacts
 
 # ----------
 # Another group of parallel tests
 # (20 tests)
 # ----------
-test: brin gin gist spgist privileges init_privs security_label collate matview lock replica_identity rowsecurity object_address tablesample groupingsets drop_operator password identity generated_stored join_hash
+test:
+    brin
+	gin
+	gist
+	spgist
+	privileges
+	init_privs
+	security_label
+	collate
+	matview
+	lock
+	replica_identity
+	rowsecurity
+	object_address
+	tablesample
+	groupingsets
+	drop_operator
+	password
+	identity
+	generated_stored
+	join_hash
 
 # ----------
 # Additional BRIN tests
 # ----------
-test: brin_bloom brin_multi
+test:
+	brin_bloom
+	brin_multi
 
 # ----------
 # Another group of parallel tests
 # (20 tests)
 # ----------
-test: create_table_like alter_generic alter_operator misc async dbsize merge misc_functions nls sysviews tsrf tid tidscan tidrangescan collate.utf8 collate.icu.utf8 incremental_sort create_role without_overlaps generated_virtual
+test:
+    alter_generic alter_operator
+    create_table_like
+    create_role
+    misc misc_functions
+    async
+    dbsize
+    merge
+    nls
+    sysviews
+    tsrf
+    tid tidscan tidrangescan
+    collate.utf8 collate.icu.utf8
+    incremental_sort
+    without_overlaps
+    generated_virtual
 
 # collate.linux.utf8 and collate.icu.utf8 tests cannot be run in parallel with each other
 # psql depends on create_am
 # amutils depends on geometry, create_index_spgist, hash_index, brin
-test: rules psql psql_crosstab psql_pipeline amutils stats_ext collate.linux.utf8 collate.windows.win1252
+test:
+    amutils
+    psql psql_crosstab psql_pipeline
+    collate.linux.utf8 collate.windows.win1252
+    rules
+    stats_ext
 
 # ----------
 # Run these alone so they don't run out of parallel workers
@@ -100,18 +258,46 @@ test: vacuum_parallel
 test: maintain_every
 
 # no relation related tests can be put in this group
-test: publication subscription
+test:
+	publication
+	subscription
 
 # ----------
 # Another group of parallel tests
 # select_views depends on create_view
 # ----------
-test: select_views portals_p2 foreign_key dependency guc bitmapops combocid tsearch tsdicts foreign_data window xmlmap functional_deps advisory_lock indirect_toast equivclass stats_rewrite
+test:
+    select_views
+    portals_p2
+	foreign_key
+	dependency
+	guc
+	bitmapops
+	combocid
+	tsearch
+	tsdicts
+	foreign_data
+	window
+	xmlmap
+	functional_deps
+	advisory_lock
+	indirect_toast
+	equivclass
+	stats_rewrite
 
 # ----------
 # Another group of parallel tests (JSON related)
 # ----------
-test: json jsonb json_encoding jsonpath jsonpath_encoding jsonb_jsonpath sqljson sqljson_queryfuncs sqljson_jsontable
+test:
+	json
+	jsonb
+	json_encoding
+	jsonpath
+	jsonpath_encoding
+	jsonb_jsonpath
+	sqljson
+	sqljson_queryfuncs
+	sqljson_jsontable
 
 # ----------
 # Another group of parallel tests
@@ -119,7 +305,25 @@ test: json jsonb json_encoding jsonpath jsonpath_encoding jsonb_jsonpath sqljson
 # NB: temp.sql does reconnects which transiently uses 2 connections,
 # so keep this parallel group to at most 19 tests
 # ----------
-test: plancache limit plpgsql copy2 temp domain rangefuncs prepare conversion truncate alter_table sequence polymorphism rowtypes returning largeobject with xml
+test:
+	plancache
+	limit
+	plpgsql
+	copy2
+	temp
+	domain
+	rangefuncs
+	prepare
+	conversion
+	truncate
+	alter_table
+	sequence
+	polymorphism
+	rowtypes
+	returning
+	largeobject
+	with
+	xml
 
 # ----------
 # Another group of parallel tests
@@ -127,26 +331,50 @@ test: plancache limit plpgsql copy2 temp domain rangefuncs prepare conversion tr
 # The stats test resets stats, so nothing else needing stats access can be in
 # this group.
 # ----------
-test: partition_join partition_prune reloptions hash_part indexing partition_aggregate partition_info tuplesort explain memoize stats predicate numa eager_aggregate planner_est
+test:
+	partition_join
+	partition_prune
+	reloptions
+	hash_part
+	indexing
+	partition_aggregate
+	partition_info
+	tuplesort
+	explain
+	memoize
+	stats
+	predicate
+	numa
+	eager_aggregate
+	planner_est
 
 # ----------
 # Another group of parallel tests (compression)
 # ----------
-test: compression compression_lz4 compression_pglz cluster
+test:
+	compression
+	compression_lz4
+	compression_pglz
+	cluster
 
 # event_trigger depends on create_am and cannot run concurrently with
 # any test that runs DDL
 # oidjoins is read-only, though, and should run late for best coverage
-test: oidjoins event_trigger
+test:
+	oidjoins
+	event_trigger
 
 
 # event_trigger_login cannot run concurrently with any other tests because
 # on-login event handling could catch connection of a concurrent test.
-test: event_trigger_login
+test:
+	event_trigger_login
 
 # this test also uses event triggers, so likewise run it by itself
-test: fast_default
+test:
+	fast_default
 
 # run tablespace test at the end because it drops the tablespace created during
 # setup that other tests may use.
-test: tablespace
+test:
+	tablespace
diff --git a/src/test/regress/pg_regress.c b/src/test/regress/pg_regress.c
index 13944701bc7..1ff4885d76a 100644
--- a/src/test/regress/pg_regress.c
+++ b/src/test/regress/pg_regress.c
@@ -1744,6 +1744,7 @@ run_schedule(const char *schedule, test_start_function startfunc,
 		char	   *c;
 		int			num_tests;
 		bool		inword;
+		bool		multiline_test;
 		int			i;
 
 		line_num++;
@@ -1755,7 +1756,7 @@ run_schedule(const char *schedule, test_start_function startfunc,
 
 		if (scbuf[0] == '\0' || scbuf[0] == '#')
 			continue;
-		if (strncmp(scbuf, "test: ", 6) == 0)
+		if (strncmp(scbuf, "test:", 5) == 0)
 			test = scbuf + 6;
 		else
 		{
@@ -1764,9 +1765,12 @@ run_schedule(const char *schedule, test_start_function startfunc,
 		}
 
 		num_tests = 0;
+
+test_line: /* avoid move this piece of code for now to keep v1 patch small */
 		inword = false;
 		for (c = test;; c++)
 		{
+			if(*c == '#') break;
 			if (*c == '\0' || isspace((unsigned char) *c))
 			{
 				if (inword)
@@ -1797,10 +1801,31 @@ run_schedule(const char *schedule, test_start_function startfunc,
 			}
 		}
 
+		/*
+		 * Found `test: # no tests` treat it as a multiline test group
+		 */
 		if (num_tests == 0)
 		{
-			bail("syntax error in schedule file \"%s\" line %d: %s",
-				 schedule, line_num, scbuf);
+			multiline_test = true;
+		}
+
+		if(multiline_test)
+		{
+			/* Scan one more line */
+			if(fgets(scbuf, sizeof(scbuf), scf))
+				line_num++;
+			else
+				scbuf[0] = 0;
+			/* if indented parse the test cases*/
+			if(scbuf[0] == ' ' || scbuf[0] == '\t')
+			{
+				test = scbuf + 1;
+				goto test_line;
+			} else if(strncmp(scbuf, "test:", 5) == 0) {
+				bail("%s line %d, expected a new line after a multiline test group", schedule, line_num++);
+			}
+			/* end of indented block, run! */
+			multiline_test = false;
 		}
 
 		if (num_tests == 1)
-- 
2.53.0

