From 36e4ba81cb93f255579c5baa8ce951cebc64eb41 Mon Sep 17 00:00:00 2001
From: Alexandre Felipe <o.alexandre.felipe@gmail.com>
Date: Sat, 21 Feb 2026 23:59:39 +0000
Subject: [PATCH v1] Relaxed matching in regress

Regression failures after changing PostgreSQL blocksize

it was pointed out by that with
./configure --with-blocksize=32
make && make check
many tests fail.

And that by experience it is known that those differences are expected.
They often reflect output variations (like buffer counts, cost estimates,
or physical storage details) rather than functional bugs.

That said, it really needs to be examined case by case.

If some differences are expected it would be useful to have a way to
describe this in the tests, check what is required, ignore what is
variable.

This patch demonstrates one possible approach to that, where the expected
file can tell pg_regress to ignore differences selectively.
---
 src/test/regress/.gitignore              |   5 +-
 src/test/regress/GNUmakefile             |   2 +
 src/test/regress/README.md               | 401 +++++++++++++++++++++
 src/test/regress/pg_regress.c            | 428 +++++++++++++++++------
 src/test/regress/test_readme_examples.sh | 130 +++++++
 5 files changed, 859 insertions(+), 107 deletions(-)
 create mode 100644 src/test/regress/README.md
 create mode 100755 src/test/regress/test_readme_examples.sh

diff --git a/src/test/regress/.gitignore b/src/test/regress/.gitignore
index 89129d7358a..cdab9e35141 100644
--- a/src/test/regress/.gitignore
+++ b/src/test/regress/.gitignore
@@ -6,6 +6,5 @@
 /results/
 /log/
 
-# Note: regression.* are only left behind on a failure; that's why they're not ignored
-#/regression.diffs
-#/regression.out
+# Ignore hidden files and directories
+.*
diff --git a/src/test/regress/GNUmakefile b/src/test/regress/GNUmakefile
index a8ba19e5971..5b18d4abae9 100644
--- a/src/test/regress/GNUmakefile
+++ b/src/test/regress/GNUmakefile
@@ -122,6 +122,8 @@ bigcheck: all | temp-install
 	$(pg_regress_check) $(REGRESS_OPTS) --schedule=$(srcdir)/parallel_schedule $(MAXCONNOPT) numeric_big
 
 
+self-test: pg_regress$(X)
+	./test_readme_examples.sh
 ##
 ## Clean up
 ##
diff --git a/src/test/regress/README.md b/src/test/regress/README.md
new file mode 100644
index 00000000000..244301942f3
--- /dev/null
+++ b/src/test/regress/README.md
@@ -0,0 +1,401 @@
+
+# Running tests
+
+Documentation concerning how to run these regression tests and interpret
+the results can be found in the PostgreSQL manual, in the chapter
+"Regression Tests".
+
+
+# Writing tests
+
+Regression tests in PostgreSQL are used to automatically verify that the
+database server produces the expected results for a wide variety of SQL
+operations and features. Writing good regression tests helps ensure that new
+changes do not break existing functionality.
+
+### 1. Locate or Create SQL and Expected Files
+
+- All regression test SQL files are located in `src/test/regress/sql/`.
+- For each `.sql` test file, there should be a corresponding expected output
+file in `src/test/regress/expected/` with a `.out` extension.
+
+### 2. Write a Test
+
+Each test comprises a `.sql` file in `src/test/regress/sql/`, and a `.out` file
+in `src/test/regress/expected`.
+
+The `.sql` file executes commands that exercise the functionality you want to
+test. Include queries, DDL, DML, and edge cases as appropriate. Add comments in
+your SQL file describing what and why you are testing.
+
+Example (`mytest.sql`):
+```sql
+-- Test basic insert and select
+CREATE TABLE mydemo(a int, b text);
+INSERT INTO mydemo VALUES (1, 'foo'), (2, 'bar');
+SELECT * FROM mydemo ORDER BY a;
+```
+
+Create a `.out` file in `src/tst/regress/expected` that includes both the
+queries of the `.sql` along with the desired output. Try to make test outputs
+stable: avoid things that vary between runs (timestamps, OIDs, etc.) unless
+necessary. Use pattern matching (`--@@IGNORE ...@@` directives) in the output
+if needed for platform-specific or variable content (see below for details) and
+document what you are testing and why. Finally, include both your `.sql` and
+`.out`, along with any schedule changes.
+
+Edit `src/test/regress/parallel_schedule` or `serial_schedule` to add your test
+file. List the base name (without .sql) in the appropriate place, depending on
+whether the test can run in parallel.
+Run all tests with `make check` in the `src/test/regress` directory.
+
+---
+
+By following these steps for each new feature or bug fix, you will help keep
+PostgreSQL reliable and trustworthy for all users.
+
+
+# Pattern Matching for Regression Test Outputs
+
+This document specifies pattern matching options for comparing regression test
+outputs against expected results. These options help tests pass across
+different configurations (e.g., block sizes) where exact output may vary.
+
+## Syntax
+
+Options are specified using SQL comment directives:
+
+```sql
+--@@IGNORE option1, option2@@
+```
+
+To disable options within a section:
+
+```sql
+--@@CHECK option1@@
+```
+
+Options remain active until changed by another directive or end of file.
+
+
+- Directives are processed line-by-line during comparison
+- `CHECK [options]` re-enables strict matching for specified options
+- `CHECK ALL` enables everything
+- `CHECK DEFAULT` ignore space and case
+- Unknown options generate a warning but don't fail the test
+- Options are case-insensitive (`IGNORE` = `ignore` = `Ignore`)
+
+
+
+## Options
+
+### `case` - Ignore Case Differences
+
+Treats uppercase and lowercase letters as equivalent.
+
+**Pattern: case**
+```
+--@@IGNORE: case@@
+SELECT * FROM users;
+```
+
+**Accepts:**
+```
+SELECT * FROM users;
+```
+
+**Accepts:**
+```
+select * from users;
+```
+
+**Accepts:**
+```
+SELECT * FROM USERS;
+```
+
+**Rejects:**
+```
+SELECT * FROM accounts;
+```
+
+---
+
+### `comments` - Ignore SQL Comments
+
+Ignores SQL comments (`--` and `/* */`) when comparing.
+
+**Pattern: comments**
+```
+--@@IGNORE: comments@@
+SELECT id FROM users;
+```
+
+**Accepts:**
+```
+SELECT id FROM users;
+```
+
+**Accepts:**
+```
+SELECT id FROM users; -- fetch all ids
+```
+
+**Accepts:**
+```
+/* query */ SELECT id FROM users;
+```
+
+**Rejects:**
+```
+SELECT name FROM users;
+```
+
+---
+
+### `spaces` - Ignore Whitespace Differences
+
+Treats any whitespace sequence as equivalent (spaces, tabs, multiple spaces).
+
+**Pattern: spaces**
+```
+--@@IGNORE: spaces@@
+Seq Scan on foo  (cost=0.00..1.00)
+```
+
+**Accepts:**
+```
+Seq Scan on foo (cost=0.00..1.00)
+```
+
+**Accepts:**
+```
+Seq  Scan  on  foo   (cost=0.00..1.00)
+```
+
+**Rejects:**
+```
+SeqScan on foo (cost=0.00..1.00)
+```
+
+---
+
+### `numbers` - Ignore Numeric Value Differences
+
+Treats all numeric values (integers, decimals) as wildcards.
+
+**Pattern: numbers**
+```
+--@@IGNORE: numbers@@
+Buffers: shared hit=10, read=5
+(cost=0.00..123.45 rows=1000 width=8)
+```
+
+**Accepts:**
+```
+Buffers: shared hit=10, read=5
+(cost=0.00..123.45 rows=1000 width=8)
+```
+
+**Accepts:**
+```
+Buffers: shared hit=42, read=17
+(cost=0.00..999.99 rows=5000 width=16)
+```
+
+**Rejects:**
+```
+Buffers: shared hit=10
+(cost=0.00..123.45 rows=1000 width=8)
+```
+
+---
+
+### `result-lines` - Ignore Extra Lines in Result
+
+Allows the actual result to contain lines not present in expected output.
+
+**Pattern: result-lines**
+```
+--@@IGNORE: result-lines@@
+BEGIN
+COMMIT
+```
+
+**Accepts:**
+```
+BEGIN
+COMMIT
+```
+
+**Accepts:**
+```
+BEGIN
+INSERT 0 1
+COMMIT
+```
+
+**Accepts:**
+```
+BEGIN
+INSERT 0 1
+UPDATE 5
+COMMIT
+```
+
+**Rejects:**
+```
+BEGIN
+ROLLBACK
+```
+
+---
+
+### `expected-lines` - Ignore Expected Lines Missing from Result
+
+Allows the actual result to omit lines present in expected output.
+
+**Pattern: expected-lines**
+```
+--@@IGNORE: expected-lines@@
+line one
+line two
+line three
+```
+
+**Accepts:**
+```
+line one
+line two
+line three
+```
+
+**Accepts:**
+```
+line one
+line three
+```
+
+**Accepts:**
+```
+line two
+```
+
+**Rejects:**
+```
+line one
+line four
+line three
+```
+
+---
+
+## Combining Options
+
+Multiple options can be combined:
+
+**Pattern: Combined options**
+```
+--@@IGNORE: case, spaces, numbers@@
+Seq Scan on FOO (cost=0.00..100.00 rows=1000)
+```
+
+**Accepts:**
+```
+seq scan on foo  (cost=0.00..50.00 rows=500)
+```
+
+**Accepts:**
+```
+SEQ SCAN ON FOO (cost=0.00..999.99 rows=9999)
+```
+
+**Rejects:**
+```
+Index Scan on foo (cost=0.00..100.00 rows=1000)
+```
+
+---
+
+## Toggling Options
+
+Options can be enabled and disabled within a file:
+
+```sql
+-- Strict matching by default
+SELECT 1;
+ ?column?
+----------
+        1
+(1 row)
+
+--@@IGNORE: numbers@@
+-- Numbers can vary in this section
+EXPLAIN SELECT * FROM foo;
+                      QUERY PLAN
+------------------------------------------------------
+ Seq Scan on foo  (cost=0.00..1.00 rows=100 width=32)
+(1 row)
+
+--@@CHECK: numbers@@
+-- Back to strict matching
+SELECT 2;
+ ?column?
+----------
+        2
+(1 row)
+```
+
+
+
+**Pattern: toggling options**
+```
+--@@IGNORE: numbers@@
+cost=0.00..10.00
+--@@IGNORE: spaces@@
+col1   col2
+--@@CHECK: numbers@@
+rows=50
+--@@IGNORE: case@@
+Select Done
+```
+
+**Accepts:**
+```
+cost=0.00..99.99
+col1 col2
+rows=50
+select done
+```
+
+**Accepts:**
+```
+cost=123.45..999.99
+col1      col2
+rows=50
+SELECT DONE
+```
+
+**Rejects:**
+```
+cost=0.00..99.99
+col1 col2
+rows=999
+select done
+```
+
+**Rejects:**
+```
+const=0.00 .. 99.99
+col1  col2
+rows=50
+Select Done
+```
+
+**Rejects:**
+```
+const=0.00..99.99
+col2  col1
+rows=50
+Select Done
+```
diff --git a/src/test/regress/pg_regress.c b/src/test/regress/pg_regress.c
index b5c0cb647a8..e11d37fd29e 100644
--- a/src/test/regress/pg_regress.c
+++ b/src/test/regress/pg_regress.c
@@ -1279,30 +1279,6 @@ file_size(const char *file)
 	return r;
 }
 
-/*
- * Count lines in file
- */
-static int
-file_line_count(const char *file)
-{
-	int			c;
-	int			l = 0;
-	FILE	   *f = fopen(file, "r");
-
-	if (!f)
-	{
-		diag("could not open file \"%s\" for reading: %m", file);
-		return -1;
-	}
-	while ((c = fgetc(f)) != EOF)
-	{
-		if (c == '\n')
-			l++;
-	}
-	fclose(f);
-	return l;
-}
-
 bool
 file_exists(const char *file)
 {
@@ -1369,32 +1345,307 @@ get_alternative_expectfile(const char *expectfile, int i)
 }
 
 /*
- * Run a "diff" command and also check that it didn't crash
+ * Pattern matching flags for flexible test output comparison.
+ * These allow expected files to specify which differences to ignore.
+ */
+#define IGNORE_NONE           0x00
+#define IGNORE_CASE           0x01
+#define IGNORE_COMMENTS       0x02
+#define IGNORE_SPACES         0x04
+#define IGNORE_WRAPPING       0x08
+#define IGNORE_NUMBERS        0x10
+#define IGNORE_RESULT         0x20	/* ignore extra lines in result */
+#define IGNORE_EXPECTED       0x40	/* ignore expected lines missing from result */
+
+/*
+ * Parse a directive line (--@@IGNORE: ...@@ or --@@CHECK: ...@@).
+ * Returns true if this line is a directive, modifying *flags accordingly.
  */
-static int
-run_diff(const char *cmd, const char *filename)
+static bool
+parse_directive(const char *line, unsigned int *flags)
 {
-	int			r;
+	const char *p;
+	bool		is_ignore;
+	char		optbuf[64];
+	int			optlen;
+	unsigned int flag;
 
-	fflush(NULL);
-	r = system(cmd);
-	if (!WIFEXITED(r) || WEXITSTATUS(r) > 1)
+	p = line;
+	while (*p && isspace((unsigned char) *p))
+		p++;
+
+	if (strncmp(p, "--@@", 4) != 0)
+		return false;
+	p += 4;
+
+	if (strncasecmp(p, "IGNORE", 6) == 0)
 	{
-		bail("diff command failed with status %d: %s", r, cmd);
+		is_ignore = true;
+		p += 6;
 	}
-#ifdef WIN32
+	else if (strncasecmp(p, "CHECK", 5) == 0)
+	{
+		is_ignore = false;
+		p += 5;
+	}
+	else
+		return false;
 
-	/*
-	 * On WIN32, if the 'diff' command cannot be found, system() returns 1,
-	 * but produces nothing to stdout, so we check for that here.
-	 */
-	if (WEXITSTATUS(r) == 1 && file_size(filename) <= 0)
+	while (*p && (*p == ':' || *p == ' ' || *p == '\t'))
+		p++;
+
+	while (*p && strncmp(p, "@@", 2) != 0)
 	{
-		bail("diff command not found: %s", cmd);
+		while (*p && (*p == ',' || *p == ' ' || *p == '\t'))
+			p++;
+		if (strncmp(p, "@@", 2) == 0)
+			break;
+
+		optlen = 0;
+		while (*p && *p != ',' && *p != '@' && !isspace((unsigned char) *p) &&
+			   optlen < (int) sizeof(optbuf) - 1)
+			optbuf[optlen++] = *p++;
+		optbuf[optlen] = '\0';
+
+		if (optlen == 0)
+			continue;
+
+		flag = IGNORE_NONE;
+		if (strcasecmp(optbuf, "case") == 0)
+			flag = IGNORE_CASE;
+		else if (strcasecmp(optbuf, "comments") == 0)
+			flag = IGNORE_COMMENTS;
+		else if (strcasecmp(optbuf, "spaces") == 0)
+			flag = IGNORE_SPACES;
+		else if (strcasecmp(optbuf, "wrapping") == 0)
+			flag = IGNORE_WRAPPING;
+		else if (strcasecmp(optbuf, "numbers") == 0)
+			flag = IGNORE_NUMBERS;
+		else if (strcasecmp(optbuf, "result-lines") == 0)
+			flag = IGNORE_RESULT;
+		else if (strcasecmp(optbuf, "expected-lines") == 0)
+			flag = IGNORE_EXPECTED;
+
+		if (is_ignore)
+			*flags |= flag;
+		else
+			*flags &= ~flag;
 	}
-#endif
+	return true;
+}
+
+/*
+ * Normalize a line according to active ignore flags.
+ * Returns a newly allocated string.
+ */
+static char *
+normalize_line(const char *line, unsigned int flags)
+{
+	char	   *result;
+	char	   *dst;
+	const char *src;
+	bool		in_space = false;
+	bool		in_block_comment = false;
+
+	result = pg_malloc(strlen(line) + 1);
+	dst = result;
+	src = line;
+
+	while (*src)
+	{
+		if ((flags & IGNORE_COMMENTS) && !in_block_comment &&
+			src[0] == '/' && src[1] == '*')
+		{
+			in_block_comment = true;
+			src += 2;
+			continue;
+		}
+		if (in_block_comment)
+		{
+			if (src[0] == '*' && src[1] == '/')
+			{
+				in_block_comment = false;
+				src += 2;
+				while (*src && isspace((unsigned char) *src))
+					src++;
+			}
+			else
+				src++;
+			continue;
+		}
+		if ((flags & IGNORE_COMMENTS) && src[0] == '-' && src[1] == '-')
+			break;
+
+		if ((flags & IGNORE_SPACES) && isspace((unsigned char) *src))
+		{
+			if (!in_space)
+			{
+				*dst++ = ' ';
+				in_space = true;
+			}
+			src++;
+			continue;
+		}
+		in_space = false;
+
+		if ((flags & IGNORE_NUMBERS) &&
+			(isdigit((unsigned char) *src) ||
+			 (*src == '.' && src[1] != '.' && isdigit((unsigned char) src[1]))))
+		{
+			*dst++ = '#';
+			while (*src && (isdigit((unsigned char) *src) ||
+							(*src == '.' && src[1] != '.')))
+				src++;
+			continue;
+		}
+
+		if (flags & IGNORE_CASE)
+			*dst++ = tolower((unsigned char) *src++);
+		else
+			*dst++ = *src++;
+	}
+	*dst = '\0';
+
+	/* Trim trailing whitespace */
+	while (dst > result && isspace((unsigned char) dst[-1]))
+		*--dst = '\0';
+
+	return result;
+}
+
+/*
+ * Compare two files using pattern matching.
+ * Returns true if files match, false otherwise.
+ * If diff_out is not NULL, writes diff-like output there on mismatch.
+ */
+static bool
+compare_with_patterns(const char *expectfile, const char *resultfile,
+					  char **diff_out)
+{
+	FILE	   *exp_fp;
+	FILE	   *res_fp;
+	char		exp_line[8192];
+	char		res_line[8192];
+	char	  **exp_lines = NULL;
+	char	  **res_lines = NULL;
+	unsigned int *exp_flags = NULL;
+	int			exp_count = 0;
+	int			res_count = 0;
+	int			exp_alloc = 0;
+	int			res_alloc = 0;
+	unsigned int flags = IGNORE_NONE;
+	int			ei, ri, i;
+	bool		match = true;
+	StringInfoData diff_buf;
+
+	if (diff_out)
+	{
+		initStringInfo(&diff_buf);
+		*diff_out = NULL;
+	}
+
+	exp_fp = fopen(expectfile, "r");
+	if (!exp_fp)
+		return false;
+
+	res_fp = fopen(resultfile, "r");
+	if (!res_fp)
+	{
+		fclose(exp_fp);
+		return false;
+	}
+
+	/* Read expected file, parse directives, store normalized lines and flags */
+	while (fgets(exp_line, sizeof(exp_line), exp_fp))
+	{
+		exp_line[strcspn(exp_line, "\r\n")] = '\0';
+		if (parse_directive(exp_line, &flags))
+			continue;
+		if (exp_count >= exp_alloc)
+		{
+			exp_alloc = exp_alloc ? exp_alloc * 2 : 64;
+			exp_lines = pg_realloc(exp_lines, exp_alloc * sizeof(char *));
+			exp_flags = pg_realloc(exp_flags, exp_alloc * sizeof(unsigned int));
+		}
+		exp_lines[exp_count] = normalize_line(exp_line, flags);
+		exp_flags[exp_count] = flags;
+		exp_count++;
+	}
+	fclose(exp_fp);
+
+	/* Read result file and store raw lines */
+	while (fgets(res_line, sizeof(res_line), res_fp))
+	{
+		res_line[strcspn(res_line, "\r\n")] = '\0';
+		if (res_count >= res_alloc)
+		{
+			res_alloc = res_alloc ? res_alloc * 2 : 64;
+			res_lines = pg_realloc(res_lines, res_alloc * sizeof(char *));
+		}
+		res_lines[res_count++] = pg_strdup(res_line);
+	}
+	fclose(res_fp);
+
+	/* Compare lines using two-pointer approach */
+	ei = 0;
+	ri = 0;
+	while (ei < exp_count || ri < res_count)
+	{
+		flags = (ei < exp_count) ? exp_flags[ei] : IGNORE_NONE;
+
+		if (ei < exp_count && ri < res_count)
+		{
+			char *norm_res = normalize_line(res_lines[ri], flags);
+			int cmp = strcmp(exp_lines[ei], norm_res);
+			free(norm_res);
+
+			if (cmp == 0)
+			{
+				ei++;
+				ri++;
+				continue;
+			}
+		}
 
-	return WEXITSTATUS(r);
+		/* Mismatch - check ignore flags */
+		if (ri < res_count && (flags & IGNORE_RESULT))
+		{
+			ri++;
+			continue;
+		}
+		if (ei < exp_count && (flags & IGNORE_EXPECTED))
+		{
+			ei++;
+			continue;
+		}
+
+		match = false;
+		if (diff_out)
+		{
+			if (ei < exp_count)
+				appendStringInfo(&diff_buf, "-%s\n", exp_lines[ei++]);
+			if (ri < res_count)
+				appendStringInfo(&diff_buf, "+%s\n", res_lines[ri++]);
+		}
+		else
+			break;
+	}
+
+	for (i = 0; i < exp_count; i++)
+		free(exp_lines[i]);
+	for (i = 0; i < res_count; i++)
+		free(res_lines[i]);
+	if (exp_lines)
+		free(exp_lines);
+	if (exp_flags)
+		free(exp_flags);
+	if (res_lines)
+		free(res_lines);
+
+	if (diff_out)
+		*diff_out = diff_buf.data;
+
+	return match;
 }
 
 /*
@@ -1407,14 +1658,11 @@ static bool
 results_differ(const char *testname, const char *resultsfile, const char *default_expectfile)
 {
 	char		expectfile[MAXPGPATH];
-	char		diff[MAXPGPATH];
-	char		cmd[MAXPGPATH * 3];
 	char		best_expect_file[MAXPGPATH];
 	FILE	   *difffile;
-	int			best_line_count;
 	int			i;
-	int			l;
 	const char *platform_expectfile;
+	char	   *diff_output = NULL;
 
 	/*
 	 * We can pass either the resultsfile or the expectfile, they should have
@@ -1435,25 +1683,13 @@ results_differ(const char *testname, const char *resultsfile, const char *defaul
 			strcpy(++p, platform_expectfile);
 	}
 
-	/* Name to use for temporary diff file */
-	snprintf(diff, sizeof(diff), "%s.diff", resultsfile);
-
-	/* OK, run the diff */
-	snprintf(cmd, sizeof(cmd),
-			 "diff %s \"%s\" \"%s\" > \"%s\"",
-			 basic_diff_opts, expectfile, resultsfile, diff);
-
-	/* Is the diff file empty? */
-	if (run_diff(cmd, diff) == 0)
-	{
-		unlink(diff);
+	/* Try pattern-based comparison with primary expectfile */
+	if (compare_with_patterns(expectfile, resultsfile, NULL))
 		return false;
-	}
 
-	/* There may be secondary comparison files that match better */
-	best_line_count = file_line_count(diff);
 	strcpy(best_expect_file, expectfile);
 
+	/* There may be secondary comparison files that match better */
 	for (i = 0; i <= 9; i++)
 	{
 		char	   *alt_expectfile;
@@ -1468,24 +1704,11 @@ results_differ(const char *testname, const char *resultsfile, const char *defaul
 			continue;
 		}
 
-		snprintf(cmd, sizeof(cmd),
-				 "diff %s \"%s\" \"%s\" > \"%s\"",
-				 basic_diff_opts, alt_expectfile, resultsfile, diff);
-
-		if (run_diff(cmd, diff) == 0)
+		if (compare_with_patterns(alt_expectfile, resultsfile, NULL))
 		{
-			unlink(diff);
 			free(alt_expectfile);
 			return false;
 		}
-
-		l = file_line_count(diff);
-		if (l < best_line_count)
-		{
-			/* This diff was a better match than the last one */
-			best_line_count = l;
-			strlcpy(best_expect_file, alt_expectfile, sizeof(best_expect_file));
-		}
 		free(alt_expectfile);
 	}
 
@@ -1493,51 +1716,31 @@ results_differ(const char *testname, const char *resultsfile, const char *defaul
 	 * fall back on the canonical results file if we haven't tried it yet and
 	 * haven't found a complete match yet.
 	 */
-
 	if (platform_expectfile)
 	{
-		snprintf(cmd, sizeof(cmd),
-				 "diff %s \"%s\" \"%s\" > \"%s\"",
-				 basic_diff_opts, default_expectfile, resultsfile, diff);
-
-		if (run_diff(cmd, diff) == 0)
-		{
-			/* No diff = no changes = good */
-			unlink(diff);
+		if (compare_with_patterns(default_expectfile, resultsfile, NULL))
 			return false;
-		}
-
-		l = file_line_count(diff);
-		if (l < best_line_count)
-		{
-			/* This diff was a better match than the last one */
-			best_line_count = l;
-			strlcpy(best_expect_file, default_expectfile, sizeof(best_expect_file));
-		}
 	}
 
 	/*
-	 * Use the best comparison file to generate the "pretty" diff, which we
-	 * append to the diffs summary file.
+	 * Use the primary comparison file to generate the diff output,
+	 * which we append to the diffs summary file.
 	 */
+	compare_with_patterns(best_expect_file, resultsfile, &diff_output);
 
-	/* Write diff header */
 	difffile = fopen(difffilename, "a");
 	if (difffile)
 	{
-		fprintf(difffile,
-				"diff %s %s %s\n",
-				pretty_diff_opts, best_expect_file, resultsfile);
+		fprintf(difffile, "diff %s %s\n", best_expect_file, resultsfile);
+		if (diff_output)
+			fprintf(difffile, "%s", diff_output);
+		fprintf(difffile, "\n");
 		fclose(difffile);
 	}
 
-	/* Run diff */
-	snprintf(cmd, sizeof(cmd),
-			 "diff %s \"%s\" \"%s\" >> \"%s\"",
-			 pretty_diff_opts, best_expect_file, resultsfile, difffilename);
-	run_diff(cmd, difffilename);
+	if (diff_output)
+		free(diff_output);
 
-	unlink(diff);
 	return true;
 }
 
@@ -2096,6 +2299,7 @@ regression_main(int argc, char *argv[],
 		{"config-auth", required_argument, NULL, 24},
 		{"max-concurrent-tests", required_argument, NULL, 25},
 		{"expecteddir", required_argument, NULL, 26},
+		{"compare", no_argument, NULL, 27},
 		{NULL, 0, NULL, 0}
 	};
 
@@ -2224,6 +2428,22 @@ regression_main(int argc, char *argv[],
 			case 26:
 				expecteddir = pg_strdup(optarg);
 				break;
+			case 27:
+				/* --compare: compare two files and exit */
+				if (argc - optind >= 2)
+				{
+					char	   *diff_output = NULL;
+					bool		match;
+
+					match = compare_with_patterns(argv[optind], argv[optind + 1], &diff_output);
+					if (diff_output && *diff_output)
+						printf("%s", diff_output);
+					if (diff_output)
+						free(diff_output);
+					exit(match ? 0 : 1);
+				}
+				pg_log_error("--compare requires two file arguments");
+				exit(2);
 			default:
 				/* getopt_long already emitted a complaint */
 				pg_log_error_hint("Try \"%s --help\" for more information.",
diff --git a/src/test/regress/test_readme_examples.sh b/src/test/regress/test_readme_examples.sh
new file mode 100755
index 00000000000..65de039793a
--- /dev/null
+++ b/src/test/regress/test_readme_examples.sh
@@ -0,0 +1,130 @@
+#!/bin/bash
+#
+# test_readme_examples.sh - Test pattern matching examples from README.md
+#
+# Extracts Pattern/Accepts/Rejects blocks and tests them against
+# the pattern matching implementation. Saves test files to .test_pattern_files/
+#
+
+SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
+README="$SCRIPT_DIR/README.md"
+PG_REGRESS="$SCRIPT_DIR/pg_regress"
+TESTDIR="$SCRIPT_DIR/.test_pattern_files"
+RED="\033[0;31m"
+GREEN="\033[0;32m"
+RESET="\033[0m"
+# Create test directory
+mkdir -p "$TESTDIR"
+
+# Build pg_regress if needed
+if [ ! -x "$PG_REGRESS" ]; then
+    echo "Building pg_regress..."
+    make -C "$SCRIPT_DIR" pg_regress >/dev/null 2>&1
+fi
+
+echo "=== Testing Pattern Matching Examples from README.md ==="
+echo ""
+
+# State variables
+pattern_label=""
+pattern_line=0
+pattern_num=0
+passed=0
+failed=0
+skipped=0
+
+# Block parsing state
+block_type=""
+block_label=""
+block_content=""
+block_start_line=0
+in_block=false
+
+line_num=0
+while IFS= read -r line || [[ -n "$line" ]]; do
+    ((line_num++))
+
+    # Check for **Pattern/Accepts/Rejects** marker
+    if [[ "$line" =~ ^\*\*(Pattern|Accepts|Rejects):?[[:space:]]*([^*]*)\*\*[[:space:]]*$ ]]; then
+        block_type="${BASH_REMATCH[1]}"
+        block_label="${BASH_REMATCH[2]}"
+        # Trim whitespace
+        block_label="${block_label#"${block_label%%[![:space:]]*}"}"
+        block_label="${block_label%"${block_label##*[![:space:]]}"}"
+        continue
+    fi
+
+    # Check for ``` start after seeing a marker
+    if [[ -n "$block_type" && "$line" == '```' && "$in_block" == false ]]; then
+        in_block=true
+        block_content=""
+        block_start_line=$line_num
+        continue
+    fi
+
+    # Check for ``` end
+    if [[ "$in_block" == true && "$line" == '```' ]]; then
+        in_block=false
+
+        if [[ "$block_type" == "Pattern" ]]; then
+            ((pattern_num++))
+            if [[ -n "$block_label" ]]; then
+                pattern_label="$block_label"
+            else
+                pattern_label="pattern_$pattern_num"
+            fi
+            pattern_line=$block_start_line
+
+            # Save pattern file
+            printf '%s' "$block_content" > "$TESTDIR/pattern-L$pattern_line.out"
+
+        elif [[ -n "$pattern_label" ]]; then
+            test_type=$(echo "$block_type" | tr '[:upper:]' '[:lower:]')
+            location="README.md:$block_start_line"
+            pattern_file="$TESTDIR/pattern-L$pattern_line.out"
+            test_file="$TESTDIR/L$pattern_line-$test_type-L$block_start_line.out"
+            diff_file="$TESTDIR/L$pattern_line-$test_type-L$block_start_line.diff"
+            pattern_loc="README.md:$pattern_line"
+
+            # Save test file
+            printf '%s' "$block_content" > "$test_file"
+
+            # Run comparison
+            if "$PG_REGRESS" --compare "$pattern_file" "$test_file" > "$diff_file" ; then
+                pg_regress_result=Accepts
+            else
+                pg_regress_result=Rejects
+            fi
+
+
+            if [[ "$block_type" == "$pg_regress_result" ]]; then
+                test_status="$GREEN[PASS]$RESET"
+                ((passed++))
+            else
+                test_status="$RED[FAIL]$RESET"
+                ((failed++))
+            fi
+            echo -e "$test_status $pattern_label ($pattern_loc) $test_type ($location)"
+            ((passed++))
+        fi
+
+        block_type=""
+        continue
+    fi
+
+    # Accumulate block content
+    if [[ "$in_block" == true ]]; then
+        if [[ -n "$block_content" ]]; then
+            block_content+=$'\n'
+        fi
+        block_content+="$line"
+    fi
+done < "$README"
+
+echo ""
+echo "=== Results ==="
+echo -e "Passed:  $GREEN$passed$RESET"
+echo -e "Failed:  $RED$failed$RESET"
+echo "Files:   .test_pattern_files/"
+
+[[ $failed -eq 0 ]]
-- 
2.40.0

