From 31b501a9e1841bfbf34b0d956256585eff535185 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-v1.1] 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 | 270 ++++++++++++++++++++++++++---
 src/test/regress/pg_regress.c      |  31 +++-
 2 files changed, 276 insertions(+), 25 deletions(-)

diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index 75063f87a4a..6cbcc3375da 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,19 +95,54 @@ 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
 # ----------
-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
+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
 
 # ----------
 # sanity_check does a vacuum, affecting the sort order of SELECT *
@@ -61,27 +155,89 @@ test: sanity_check
 # aggregates depends on create_aggregate
 # join depends on create_misc
 # ----------
-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
 # ----------
-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
 # ----------
-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
@@ -96,18 +252,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
@@ -115,7 +299,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
@@ -123,26 +325,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

