From c438562ad30c88dc5c2fc364a53ce6771b967ef0 Mon Sep 17 00:00:00 2001 From: Alexandre Felipe Date: Thu, 17 Sep 2026 08:43:52 +0100 Subject: [PATCH-v2.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. SYNTAX ====== 1. An unindented line is a syntax error 1.a unindented line before any `test:` Example: ``` 9 # ---------- 10 top 11 # required setup steps ``` Error: line 10: expected "test:", indented test group continuation, got "top" 1.b unindented line after a `test:` Example: ``` 16 test: 16 boolean 18 char ``` Error: line 18: expected "test:", indented test group continuation, got "char" 2. blank lines are OK Example: ``` test: boolean char ``` 3. mixing tests in the same line and next lines is OK Example: ``` test: boolean char ``` 4. empty tests are errors 4.a `test:` followed by empty lines or comments Example: ``` 12 test: test_setup 13 test: 14 # ---------- 15 # The first group of parallel tests 16 # ---------- 17 test: boolean ``` Error: empty test group in schedule file "./parallel_schedule" line 13 Reporting the line where the empty test started not when it was confirmed empty. 4.b empty `test:` at the end of the file Example: ``` 364 test: tablespace 365 test: ``` Error: empty test group in schedule file "./parallel_schedule" line 365 4.c Empty `test:` immediately followed by `test:` Example: ``` 12 test: 13 test: test_setup ``` Error: empty test group in schedule file "./parallel_schedule" line 12 EFFECTIVE CONCURRENCY ===================== This also add for each group a note about the average execution concurrency achieved during the test. # parallel group (2 tests): brin_bloom brin_multi # effective concurrency 1.46 / 2 --- src/test/regress/parallel_schedule | 270 ++++++++++++++++++++++++++--- src/test/regress/pg_regress.c | 112 ++++++++++-- 2 files changed, 346 insertions(+), 36 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..ad516174bf6 100644 --- a/src/test/regress/pg_regress.c +++ b/src/test/regress/pg_regress.c @@ -1709,8 +1709,40 @@ log_child_failure(int exitstatus) diag("(test process exited with unrecognized status %d)", exitstatus); } +/* + * During the test execution the concurrency level varies + * this computes the average concurrency from the moment the + * first test starts to the moment the last test stops. + */ +static void +log_group_concurrency(instr_time *test_start, instr_time *test_stop, int num_tests) +{ + double group_start = INSTR_TIME_GET_NANOSEC(test_start[0]); + double group_stop = INSTR_TIME_GET_NANOSEC(test_stop[0]); + double total = (group_stop - group_start); + for(int i = 1; i < num_tests; i++) + { + double t0 = INSTR_TIME_GET_NANOSEC(test_start[i]); + double tf = INSTR_TIME_GET_NANOSEC(test_stop[i]); + if (i == 0 || (t0 < group_start)) + group_start = t0; + if (i == 0 || (tf > group_stop)) + group_stop = tf; + total += tf - t0; + } + note("effective concurrency %.2f / %d", total / (group_stop - group_start), num_tests); +} + /* * Run all the tests specified in one schedule file + * + * Schedule file syntax: + * Schedule = (Blank | Comment | Group)* + * Group = "test:" (Line | Comment) (Indent Line)* + * Line = token (Space+ token)* Comment? + * Blank = '\n' + * Comment = '#' [^\n]* \n + * Indent = [\t ]+ */ static void run_schedule(const char *schedule, test_start_function startfunc, @@ -1728,45 +1760,84 @@ run_schedule(const char *schedule, test_start_function startfunc, char scbuf[1024]; FILE *scf; int line_num = 0; + int test_start = 0; + int num_tests; + bool in_group; + bool eof = false; + int i; memset(tests, 0, sizeof(tests)); memset(resultfiles, 0, sizeof(resultfiles)); memset(expectfiles, 0, sizeof(expectfiles)); memset(tags, 0, sizeof(tags)); + scf = fopen(schedule, "r"); if (!scf) bail("could not open file \"%s\" for reading: %m", schedule); - while (fgets(scbuf, sizeof(scbuf), scf)) + num_tests = 0; + in_group = false; + + while (!eof) { char *test = NULL; char *c; - int num_tests; bool inword; - int i; + + if (!fgets(scbuf, sizeof(scbuf), scf)) + { + eof = true; + goto run_group; + } line_num++; + /* strip trailing whitespace, especially the newline */ i = strlen(scbuf); while (i > 0 && isspace((unsigned char) scbuf[i - 1])) scbuf[--i] = '\0'; - if (scbuf[0] == '\0' || scbuf[0] == '#') + if (scbuf[0] == '#' || scbuf[0] == '\0') continue; - if (strncmp(scbuf, "test: ", 6) == 0) - test = scbuf + 6; + + if (scbuf[0] == ' ' || scbuf[0] == '\t') + { + if (!in_group) + { + bail("indented test name in schedule file \"%s\" line %d " + "without a preceding \"test:\" line: %s", + schedule, line_num, scbuf); + } + test = scbuf; + } else { - bail("syntax error in schedule file \"%s\" line %d: %s", - schedule, line_num, scbuf); + if (strncmp(scbuf, "test:", 5) != 0) + { + bail("syntax error in schedule file \"%s\" line %d: " + "expected \"test:\", indented test group continuation, got \"%s\"", + schedule, line_num, scbuf); + } + if (in_group) + goto run_group; + + +test_line: + test_start = line_num; + in_group = true; + num_tests = 0; + test = scbuf + 5; } - num_tests = 0; + while (*test == ' ' || *test == '\t') + test++; inword = false; for (c = test;; c++) { + if (*c == '#') + break; if (*c == '\0' || isspace((unsigned char) *c)) { if (inword) @@ -1796,12 +1867,15 @@ run_schedule(const char *schedule, test_start_function startfunc, inword = true; } } + continue; +run_group: + if (!in_group) + bail("pg_regress error while reading schedule file \"%s\" line %d", + schedule, test_start); if (num_tests == 0) - { - bail("syntax error in schedule file \"%s\" line %d: %s", - schedule, line_num, scbuf); - } + bail("empty test group in schedule file \"%s\" line %d", + schedule, test_start); if (num_tests == 1) { @@ -1850,6 +1924,9 @@ run_schedule(const char *schedule, test_start_function startfunc, note_end(); } + if(num_tests > 1) + log_group_concurrency(starttimes, stoptimes, num_tests); + /* Check results for all tests */ for (i = 0; i < num_tests; i++) { @@ -1901,7 +1978,6 @@ run_schedule(const char *schedule, test_start_function startfunc, } } } - for (i = 0; i < num_tests; i++) { pg_free(tests[i]); @@ -1910,11 +1986,19 @@ run_schedule(const char *schedule, test_start_function startfunc, free_stringlist(&expectfiles[i]); free_stringlist(&tags[i]); } + /* return */ + if (strncmp(scbuf, "test:", 5) == 0) + goto test_line; + + in_group = false; + num_tests = 0; } fclose(scf); } + + /* * Run a single test */ -- 2.53.0