From a1d5c3f57af3106984dbdd0024d04b13323e209d Mon Sep 17 00:00:00 2001
From: Melanie Plageman <melanieplageman@gmail.com>
Date: Wed, 3 Jun 2026 18:16:49 -0400
Subject: [PATCH v15 09/19] Add COPY FROM write combining test

---
 src/test/modules/test_aio/meson.build         |  1 +
 .../modules/test_aio/t/005_write_combining.pl | 94 +++++++++++++++++++
 2 files changed, 95 insertions(+)
 create mode 100644 src/test/modules/test_aio/t/005_write_combining.pl

diff --git a/src/test/modules/test_aio/meson.build b/src/test/modules/test_aio/meson.build
index 909f81d96c1..d3d5c996931 100644
--- a/src/test/modules/test_aio/meson.build
+++ b/src/test/modules/test_aio/meson.build
@@ -34,6 +34,7 @@ tests += {
       't/002_io_workers.pl',
       't/003_initdb.pl',
       't/004_read_stream.pl',
+      't/005_write_combining.pl',
     ],
   },
 }
diff --git a/src/test/modules/test_aio/t/005_write_combining.pl b/src/test/modules/test_aio/t/005_write_combining.pl
new file mode 100644
index 00000000000..d9c66b3cb01
--- /dev/null
+++ b/src/test/modules/test_aio/t/005_write_combining.pl
@@ -0,0 +1,94 @@
+# Copyright (c) 2026, PostgreSQL Global Development Group
+
+use strict;
+use warnings FATAL => 'all';
+
+use PostgreSQL::Test::Cluster;
+use PostgreSQL::Test::Utils;
+use Test::More;
+
+my $node = PostgreSQL::Test::Cluster->new('write-combining');
+$node->init();
+
+$node->append_conf(
+	'postgresql.conf', qq(
+shared_buffers = '128MB'
+max_wal_size = '4GB'
+checkpoint_timeout = '1h'
+checkpoint_completion_target = 0.0
+autovacuum = off
+io_combine_limit = '128kB'
+eager_clean_max_batch_size = 16
+bgwriter_lru_maxpages = 0
+fsync = off
+synchronous_commit = off
+));
+
+$node->start();
+
+my $block_size = $node->safe_psql('postgres',
+	"SELECT current_setting('block_size')::int");
+
+test_copy_from_combines_writes($node, $block_size);
+
+$node->stop();
+
+done_testing();
+
+sub io_stat_writes
+{
+	my ($node, $backend_type, $context) = @_;
+
+	my $result = $node->safe_psql(
+		'postgres', qq(
+SELECT COALESCE(sum(writes), 0)::bigint,
+       COALESCE(sum(write_bytes), 0)::bigint,
+       COALESCE((sum(write_bytes) / NULLIF(sum(writes), 0))::bigint, 0)
+FROM pg_stat_io
+WHERE backend_type = '$backend_type'
+  AND object = 'relation'
+  AND context = '$context';
+));
+
+	return split /\|/, $result;
+}
+
+sub assert_combined_writes
+{
+	local $Test::Builder::Level = $Test::Builder::Level + 1;
+
+	my ($node, $label, $backend_type, $context, $block_size) = @_;
+	my ($writes, $write_bytes, $avg_write_bytes) =
+	  io_stat_writes($node, $backend_type, $context);
+
+	note "$label: writes=$writes write_bytes=$write_bytes avg_write_bytes=$avg_write_bytes";
+	ok($writes > 0, "$label wrote buffers");
+	ok($avg_write_bytes > $block_size, "$label combined writes");
+}
+
+sub test_copy_from_combines_writes
+{
+	my ($node, $block_size) = @_;
+
+	$node->safe_psql(
+		'postgres', qq(
+	CREATE UNLOGGED TABLE wc_copy (id int, payload text);
+	CHECKPOINT;
+	));
+
+	# Feed the rows to "COPY ... FROM STDIN" inline on psql's stdin.
+	my $rows = 200000;
+	my $payload = '0' x 200;
+	my $copy_data = '';
+	$copy_data .= "$_\t$payload\n" for (1 .. $rows);
+
+	$node->safe_psql('postgres', "SELECT pg_stat_reset_shared('io')");
+	$node->safe_psql('postgres',
+		"COPY wc_copy FROM STDIN;\n" . $copy_data . "\\.\n");
+	$node->safe_psql('postgres', 'SELECT pg_stat_force_next_flush()');
+
+	assert_combined_writes($node, 'copy from', 'client backend', 'bulkwrite',
+		$block_size);
+	is($node->safe_psql('postgres', "SELECT count(*) FROM wc_copy"),
+		$rows, 'copy from inserted rows');
+}
-- 
2.43.0

