Hello,

When developping new features for pgbench, I usually write some tests which are lost when the feature is committed. Given that I have submitted some more features and that part of pgbench code may be considered for sharing with pgsql, I think that improving the abysmal state of tests would be a good move.

The attached patch:

(1) extends the existing perl tap test infrastructure with methods to test pgbench, i.e. "pgbench" which runs a pgbench test and "pgbench_likes" which allows to check for expectations.

(2) reuse this infrastructure for the prior existing test about concurrent inserts.

(3) add a lot of new very small tests so that coverage jumps from very low to over 90% for source files. I think that derived files (exprparse.c, exprscan.c) should be removed from coverage analysis.

Previous coverage status:

 exprparse.y    0.0 %     0 / 77        0.0 %    0 / 8
 exprscan.l     0.0 %     0 / 102       0.0 %    0 / 8
 pgbench.c      28.3 %  485 / 1716      43.1 %  28 / 65

New status:

 exprparse.y    96.1 %    73 / 76       100.0 %          8 / 8
 exprscan.l     92.8 %    90 / 97       100.0 %          8 / 8
 pgbench.c      90.4 %  1542 / 1705      96.9 %         63 / 65

The test runtime is about doubled on my laptop, which is not too bad given the coverage achieved.

(4) fixes a two minor issues. These fixes may be considered for backpatching to 10, although I doubt anyone will complain, so I would not bother. Namely:

 - the -t/-R/-L combination was not working properly, fix that
   by moving client statistics in processXactStats, adjust some
   formula, and add a few comments for details I had to discover.

 - add a check that --progress-timestamp => --progress

I'm unsure of the portability of some of the tests (\shell and \setshell), especially on Windows. If there is an issue, these test will have to be skipped on this platform.

Some of the tests may fail with a very low probability (eg 1/2**84?).

--
Fabien.
diff --git a/src/bin/pgbench/pgbench.c b/src/bin/pgbench/pgbench.c
index ae36247..2128418 100644
--- a/src/bin/pgbench/pgbench.c
+++ b/src/bin/pgbench/pgbench.c
@@ -229,7 +229,7 @@ typedef struct SimpleStats
 typedef struct StatsData
 {
 	time_t		start_time;		/* interval start time, for aggregates */
-	int64		cnt;			/* number of transactions */
+	int64		cnt;			/* number of transactions, including skipped */
 	int64		skipped;		/* number of transactions skipped under --rate
 								 * and --latency-limit */
 	SimpleStats latency;
@@ -329,7 +329,7 @@ typedef struct
 	bool		prepared[MAX_SCRIPTS];	/* whether client prepared the script */
 
 	/* per client collected stats */
-	int64		cnt;			/* transaction count */
+	int64		cnt;			/* client transaction count, for -t */
 	int			ecnt;			/* error count */
 } CState;
 
@@ -2045,7 +2045,9 @@ doCustom(TState *thread, CState *st, StatsData *agg)
 					if (INSTR_TIME_IS_ZERO(now))
 						INSTR_TIME_SET_CURRENT(now);
 					now_us = INSTR_TIME_GET_MICROSEC(now);
-					while (thread->throttle_trigger < now_us - latency_limit)
+					while (thread->throttle_trigger < now_us - latency_limit &&
+						   /* with -t, do not overshoot */
+						   (nxacts <= 0 || st->cnt < nxacts))
 					{
 						processXactStats(thread, st, &now, true, agg);
 						/* next rendez-vous */
@@ -2053,6 +2055,12 @@ doCustom(TState *thread, CState *st, StatsData *agg)
 						thread->throttle_trigger += wait;
 						st->txn_scheduled = thread->throttle_trigger;
 					}
+
+					if (nxacts > 0 && st->cnt >= nxacts)
+					{
+						st->state = CSTATE_FINISHED;
+						break;
+					}
 				}
 
 				st->state = CSTATE_THROTTLE;
@@ -2366,13 +2374,13 @@ doCustom(TState *thread, CState *st, StatsData *agg)
 
 				/*
 				 * transaction finished: calculate latency and log the
-				 * transaction
+				 * transaction.
 				 */
 				if (progress || throttle_delay || latency_limit ||
 					per_script_stats || use_log)
 					processXactStats(thread, st, &now, false, agg);
 				else
-					thread->stats.cnt++;
+					thread->stats.cnt++, st->cnt++;
 
 				if (is_connect)
 				{
@@ -2381,7 +2389,6 @@ doCustom(TState *thread, CState *st, StatsData *agg)
 					INSTR_TIME_SET_ZERO(now);
 				}
 
-				++st->cnt;
 				if ((st->cnt >= nxacts && duration <= 0) || timer_exceeded)
 				{
 					/* exit success */
@@ -2530,6 +2537,7 @@ processXactStats(TState *thread, CState *st, instr_time *now,
 		lag = INSTR_TIME_GET_MICROSEC(st->txn_begin) - st->txn_scheduled;
 	}
 
+	/* thread stats */
 	if (progress || throttle_delay || latency_limit)
 	{
 		accumStats(&thread->stats, skipped, latency, lag);
@@ -2539,8 +2547,12 @@ processXactStats(TState *thread, CState *st, instr_time *now,
 			thread->latency_late++;
 	}
 	else
+		/* just count */
 		thread->stats.cnt++;
 
+	/* client stat is just counting */
+	st->cnt ++;
+
 	if (use_log)
 		doLog(thread, st, agg, skipped, latency, lag);
 
@@ -3118,7 +3130,7 @@ process_backslash_command(PsqlScanState sstate, const char *source)
 	/* Save line */
 	my_command->line = expr_scanner_get_substring(sstate,
 												  start_offset,
-												  end_offset);
+												  end_offset + 1);
 
 	if (pg_strcasecmp(my_command->argv[0], "sleep") == 0)
 	{
@@ -3509,7 +3521,7 @@ printResults(TState *threads, StatsData *total, instr_time total_time,
 	{
 		printf("number of transactions per client: %d\n", nxacts);
 		printf("number of transactions actually processed: " INT64_FORMAT "/%d\n",
-			   total->cnt, nxacts * nclients);
+			   total->cnt - total->skipped, nxacts * nclients);
 	}
 	else
 	{
@@ -3525,12 +3537,12 @@ printResults(TState *threads, StatsData *total, instr_time total_time,
 	if (throttle_delay && latency_limit)
 		printf("number of transactions skipped: " INT64_FORMAT " (%.3f %%)\n",
 			   total->skipped,
-			   100.0 * total->skipped / (total->skipped + total->cnt));
+			   100.0 * total->skipped / total->cnt);
 
 	if (latency_limit)
 		printf("number of transactions above the %.1f ms latency limit: %d (%.3f %%)\n",
 			   latency_limit / 1000.0, latency_late,
-			   100.0 * latency_late / (total->skipped + total->cnt));
+			   100.0 * latency_late / total->cnt);
 
 	if (throttle_delay || progress || latency_limit)
 		printSimpleStats("latency", &total->latency);
@@ -3580,7 +3592,7 @@ printResults(TState *threads, StatsData *total, instr_time total_time,
 				printf(" - number of transactions skipped: " INT64_FORMAT " (%.3f%%)\n",
 					   sql_script[i].stats.skipped,
 					   100.0 * sql_script[i].stats.skipped /
-					(sql_script[i].stats.skipped + sql_script[i].stats.cnt));
+					   sql_script[i].stats.cnt);
 
 			if (num_scripts > 1)
 				printSimpleStats(" - latency", &sql_script[i].stats.latency);
@@ -4106,6 +4118,12 @@ main(int argc, char **argv)
 		exit(1);
 	}
 
+	if (progress_timestamp && progress <= 0)
+	{
+		fprintf(stderr, "--progress-timestamp is allowed only under --progress\n");
+		exit(1);
+	}
+
 	/*
 	 * save main process id in the global variable because process id will be
 	 * changed after fork.
diff --git a/src/bin/pgbench/t/001_pgbench.pl b/src/bin/pgbench/t/001_pgbench.pl
index 34d686e..e519977 100644
--- a/src/bin/pgbench/t/001_pgbench.pl
+++ b/src/bin/pgbench/t/001_pgbench.pl
@@ -3,23 +3,339 @@ use warnings;
 
 use PostgresNode;
 use TestLib;
-use Test::More tests => 3;
+use Test::More tests => 352;
+
+# start a pgbench specific server
+my $node = get_new_node('main');
+$node->init;
+$node->start;
 
 # Test concurrent insertion into table with UNIQUE oid column.  DDL expects
 # GetNewOidWithIndex() to successfully avoid violating uniqueness for indexes
 # like pg_class_oid_index and pg_proc_oid_index.  This indirectly exercises
 # LWLock and spinlock concurrency.  This test makes a 5-MiB table.
-my $node = get_new_node('main');
-$node->init;
-$node->start;
+
 $node->safe_psql('postgres',
 	    'CREATE UNLOGGED TABLE oid_tbl () WITH OIDS; '
 	  . 'ALTER TABLE oid_tbl ADD UNIQUE (oid);');
-my $script = $node->basedir . '/pgbench_script';
-append_to_file($script,
-	'INSERT INTO oid_tbl SELECT FROM generate_series(1,1000);');
-$node->command_like(
-	[   qw(pgbench --no-vacuum --client=5 --protocol=prepared
-		  --transactions=25 --file), $script ],
-	qr{processed: 125/125},
-	'concurrent OID generation');
+
+# 3 checks
+$node->pgbench_likes(
+  'concurrency OID generation',
+  '--no-vacuum --client=5 --protocol=prepared --transactions=25' .
+  '  --file=t/scripts/001_01_concurrent_oid_generation.pgbench',
+  0, qr{processed: 125/125}, qr{^$});
+
+$node->safe_psql('postgres', 'DROP TABLE oid_tbl;');
+
+#
+# Option various errors
+#
+
+my @options = (
+  # name, options, stderr checks
+  [ 'bad option', '-h home -p 5432 -U calvin -d stuff --bad-option',
+    [ qr{unrecognized option}, qr{--help.*more information} ] ],
+  [ 'no database', 'no-such-database',
+    [ qr{connection to database "no-such-database" failed},
+      qr{FATAL:  database "no-such-database" does not exist} ] ],
+  [ 'no file', '-f no-such-file', qr{could not open file "no-such-file":} ],
+  [ 'no builtin', '-b no-such-builtin', qr{no builtin script .* "no-such-builtin"} ],
+  [ 'invalid weight', '--builtin=select-only@one', qr{invalid weight specification: \@one} ],
+  [ 'invalid weight', '-b select-only@-1', qr{weight spec.* out of range .*: -1} ],
+  [ 'too many scripts', '-S ' x 129, qr{at most 128 SQL scripts} ],
+  [ 'bad #clients', '-c three', qr{invalid number of clients: "three"} ],
+  [ 'bad #threads', '-j eleven', qr{invalid number of threads: "eleven"} ],
+  [ 'bad scale', '-i -s two', qr{invalid scaling factor: "two"} ],
+  [ 'invalid #transactions', '-t zil', qr{invalid number of transactions: "zil"} ],
+  [ 'invalid duration', '-T ten', qr{invalid duration: "ten"} ],
+  [ '-t XOR -T', '-N -l --aggregate-interval=5 --log-prefix=notused -t 1000 -T 1',
+    qr{specify either } ],
+  [ '-T XOR -t', '-P 1 --progress-timestamp -l --sampling-rate=0.001 -T 10 -t 1000',
+    qr{specify either } ],
+  [ 'bad variable', '--define foobla', qr{invalid variable definition} ],
+  [ 'invalid fillfactor', '-F 1', qr{invalid fillfactor} ],
+  [ 'invalid query mode', '-M no-such-mode', qr{invalid query mode} ],
+  [ 'invalid progress', '--progress=0', qr{invalid thread progress delay} ],
+  [ 'invalid rate', '--rate=0.0', qr{invalid rate limit} ],
+  [ 'invalid latency', '--latency-limit=0.0', qr{invalid latency limit} ],
+  [ 'invalid sampling rate', '--sampling-rate=0', qr{invalid sampling rate} ],
+  [ 'invalid aggregate interval', '--aggregate-interval=-3', qr{invalid .* seconds for} ],
+  [ 'weight zero', '-b se@0 -b si@0 -b tpcb@0', qr{weight must not be zero} ],
+  [ 'run without init', '-S -t 1', qr{Perhaps you need to do initialization} ],
+  [ 'prepare after script', '-S -M prepared', qr{query mode .* before any} ],
+  [ 'init vs run', '-i -S', qr{cannot be used in initialization} ],
+  [ 'run vs init', '-S -F 90', qr{cannot be used in benchmarking} ],
+  [ 'ambiguous builtin', '-b s', qr{ambiguous} ],
+  [ '--progress-timestamp => --progress', '--progress-timestamp', qr{allowed only under} ],
+  # loging sub-options
+  [ 'sampling => log', '--sampling-rate=0.01', qr{log sampling .* only when} ],
+  [ 'sampling XOR aggregate', '-l --sampling-rate=0.1 --aggregate-interval=3',
+    qr{sampling .* aggregation .* cannot be used at the same time} ],
+  [ 'aggregate => log', '--aggregate-interval=3', qr{aggregation .* only when} ],
+  [ 'log-prefix => log', '--log-prefix=x', qr{prefix .* only when} ],
+  [ 'duration & aggregation', '-l -T 1 --aggregate-interval=3', qr{aggr.* not be higher} ],
+  [ 'duration % aggregation', '-l -T 5 --aggregate-interval=3', qr{multiple} ],
+);
+
+for my $o (@options)
+{
+  my ($name, $opts, $err_checks) = @$o;
+  $node->pgbench_likes('pgbench option error: ' . $name, $opts, 1, qr{^$}, $err_checks);
+}
+
+# Help: 7 checks
+$node->pgbench_likes(
+  'pgbench help', '--help',
+  0,
+  [ qr{benchmarking tool for PostgreSQL}, qr{Usage},
+    qr{Initialization options:}, qr{Common options:}, qr{Report bugs to} ],
+  qr{^$});
+
+# Version
+$node->pgbench_likes(
+  'pgbench version', '-V',
+  0, qr{^pgbench .PostgreSQL. }, qr{^$});
+
+$node->pgbench_likes(
+  'pgbench buitlin list', '-b list',
+  0, qr{^$},
+  [ qr{Available builtin scripts:},
+    qr{tpcb-like}, qr{simple-update}, qr{select-only} ]);
+
+
+# Initialize pgbench tables scale 1
+$node->pgbench_likes(
+  'pgbench scale 1 initialization', '-i',
+  0, qr{^$},
+  [ qr{creating tables}, qr{vacuum}, qr{set primary keys}, qr{done\.} ]);
+
+# Again, with all possible options
+$node->pgbench_likes(
+  'pgbench scale 1 initialization',
+  # unlogged => faster test
+  '--initialize --scale=1 --unlogged --fillfactor=98 --foreign-keys --quiet' .
+  ' --tablespace=pg_default --index-tablespace=pg_default',
+  0, qr{^$},
+  [ qr{creating tables}, qr{vacuum}, qr{set primary keys},
+    qr{set foreign keys}, qr{done\.} ]);
+
+# Run all builtins for a few transactions: 20 checks
+$node->pgbench_likes(
+  'pgbench tpcb-like',
+  '--transactions=5 -Dfoo=bla --client=2 --protocol=simple --builtin=t --connect -n -v -n',
+  0,
+  [ qr{builtin: TPC-B}, qr{clients: 2\b}, qr{processed: 10/10},
+    qr{mode: simple} ],
+  qr{^$});
+
+$node->pgbench_likes(
+  'pgbench simple update',
+  '--transactions=20 --client=5 -M extended --builtin=si -C --no-vacuum -s 1',
+  0,
+  [ qr{builtin: simple update}, qr{clients: 5\b}, qr{threads: 1\b},
+    qr{processed: 100/100}, qr{mode: extended} ],
+  qr{scale option ignored});
+
+$node->pgbench_likes(
+  'pgbench select only',
+  '-t 100 -c 7 -M prepared -b se --debug',
+  0,
+  [ qr{builtin: select only}, qr{clients: 7\b}, qr{threads: 1\b},
+    qr{processed: 700/700}, qr{mode: prepared} ],
+  [ qr{vacuum}, qr{client 0}, qr{client 1}, qr{sending}, qr{receiving}, qr{executing} ]);
+
+# run custom scripts: 8 checks
+$node->pgbench_likes(
+  'pgbench custom script',
+  '-t 100 -c 1 -j 2 -M prepared -f t/scripts/001_02_custom_script.pgbench@1' .
+  '  -f t/scripts/001_02_custom_script.pgbench@2 -n',
+  0,
+  [ qr{type: multiple scripts}, qr{mode: prepared},
+    qr{script 1: t/scripts/001_02_custom_script}, qr{weight: 1 },
+    qr{script 2: t/scripts/001_02_custom_script}, qr{weight: 2 },
+    qr{processed: 100/100} ],
+  qr{^$});
+
+$node->pgbench_likes(
+  'pgbench custom script',
+  '-n -t 10 -c 1 -M simple -f t/scripts/001_02_custom_script.pgbench',
+  0,
+  [ qr{type: t/scripts/001_02_custom_script}, qr{processed: 10/10},
+    qr{mode: simple} ],
+  qr{^$});
+
+$node->pgbench_likes(
+  'pgbench custom script',
+  '-n -t 10 -c 2 -M extended -f t/scripts/001_02_custom_script.pgbench',
+  0,
+  [ qr{type: t/scripts/001_02_custom_script}, qr{processed: 20/20},
+    qr{mode: extended} ],
+  qr{^$});
+
+# test expressions: 23 checks
+$node->pgbench_likes(
+  'pgbench expressions',
+  '-t 1 -Dfoo=-10.1 -Dbla=false -Di=+3 -Dminint=-9223372036854775808' .
+  ' -f t/scripts/001_03_expressions.pgbench',
+  0,
+  [ qr{type: t/scripts/001_03_expressions}, qr{processed: 1/1} ],
+  [ qr{command=4.: int 4\b},
+    qr{command=5.: int 5\b},
+    qr{command=6.: int 6\b},
+    qr{command=7.: int 7\b},
+    qr{command=8.: int 8\b},
+    qr{command=9.: int 9\b},
+    qr{command=10.: int 10\b},
+    qr{command=11.: int 11\b},
+    qr{command=12.: int 12\b},
+    qr{command=13.: double 13\b},
+    qr{command=14.: double 14\b},
+    qr{command=15.: double 15\b},
+    qr{command=16.: double 16\b},
+    qr{command=17.: double 17\b},
+    qr{command=18.: double 18\b},
+    qr{command=19.: double 19\b},
+    qr{command=20.: double 20\b},
+    qr{command=21.: double -?nan\b},
+    qr{command=22.: double inf\b},
+    qr{command=23.: double -inf\b},
+    qr{command=24.: int 9223372036854775807\b},
+  ]);
+
+# backslash commands: 4 checks
+$node->pgbench_likes(
+  'pgbench backslash commands',
+  '-t 1 -f t/scripts/001_04_backslash_commands.pgbench',
+  0,
+  [ qr{type: t/scripts/001_04_backslash_commands}, qr{processed: 1/1},
+    qr{shell-echo-output} ],
+  qr{command=8.: int 2\b} );
+
+# trigger many expression errors
+my @errors = (
+  # [ test name, script number, status, stderr match ]
+  # SQL
+  [ 'sql syntax error', 'sql_1', 0,
+    [ qr{ERROR:  syntax error}, qr{prepared statement .* does not exist} ] ],
+  [ 'too many args', 'sql_2', 1, qr{statement has too many arguments.*\b9\b} ],
+  # SHELL
+  [ 'shell bad command', 'sh_1', 0, qr{meta-command 'shell' failed} ],
+  [ 'shell undefined variable', 'sh_2', 0, qr{undefined variable ":nosuchvariable"} ],
+  [ 'shell missing command', 'sh_3', 1, qr{missing command } ],
+  [ 'shell too many args', 'sh_4', 1, qr{too many arguments in command "shell"} ],
+  # SET
+  [ 'expr syntax error', 'set_1', 1, qr{syntax error in command "set"} ],
+  [ 'no such function', 'set_2', 1, qr{unexpected function name} ],
+  [ 'invalid variable name', 'set_3', 0, qr{invalid variable name} ],
+  [ 'int overflow', 'set_4', 0, qr{double to int overflow for 100} ],
+  [ 'division by zero', 'set_5', 0, qr{division by zero} ],
+  [ 'bigint out of range', 'set_6', 0, qr{bigint out of range} ],
+  [ 'undefined variable', 'set_7', 0, qr{undefined variable "nosuchvariable"} ],
+  [ 'unexpected char', 'set_8', 1, qr{unexpected character .;.} ],
+  [ 'too many args', 'set_9', 0, qr{too many function arguments} ],
+  [ 'empty random range', 'set_A', 0, qr{empty range given to random} ],
+  [ 'random range too large', 'set_B', 0, qr{random range is too large} ],
+  [ 'gaussian param too small', 'set_C', 0, qr{gaussian param.* at least 2} ],
+  [ 'exponential param > 0', 'set_D', 0, qr{exponential parameter must be greater } ],
+  [ 'non numeric value', 'set_E', 0, qr{malformed variable "foo" value: "bla"} ],
+  [ 'no expression', 'set_F', 1, qr{syntax error} ],
+  [ 'missing argument', 'set_G', 1, qr{missing argument} ],
+  # setshell
+  [ 'setshell not an int', 'setsh_1', 0, qr{command must return an integer} ],
+  [ 'setshell missing arg', 'setsh_2', 1, qr{missing argument } ],
+  [ 'setshell no such command', 'setsh_3', 0, qr{could not read result } ],
+  # SLEEP
+  [ 'undefined variable in sleep', 'sleep_1', 0, qr{sleep: undefined variable} ],
+  [ 'sleep too many args', 'sleep_2', 1, qr{too many arguments} ],
+  [ 'sleep missing arg', 'sleep_3', 1, [ qr{missing argument}, qr{\\sleep} ] ],
+  [ 'sleep unknown unit', 'sleep_4', 1, qr{unrecognized time unit} ],
+  # MISC
+  [ 'invalid command', 'misc_1', 1, qr{invalid command .* "nosuchcommand"} ],
+  [ 'empty script', 'misc_2', 1, qr{empty command list for script} ],
+#  [ '', '8', 0, qr{} ],
+);
+
+for my $e (@errors)
+{
+  my ($name, $number, $status, $re) = @$e;
+  $node->pgbench_likes(
+    'pgbench script error: ' . $name,
+    "-n -t 1 -Dfoo=bla -M prepared -f t/scripts/001_05_error_$number.pgbench",
+    $status, $status ? qr{^$} : qr{processed: 0/1}, $re);
+}
+
+# throttling
+$node->pgbench_likes(
+  'pgbench throttling',
+  '-t 100 -S --rate=100000 --latency-limit=1000000 -c 2 -n -r',
+  0, [ qr{processed: 200/200}, qr{builtin: select only} ], qr{^$});
+
+$node->pgbench_likes(
+  'pgbench late throttling',
+  # given the expected rate and the 2 ms tx duration, at most one is executed
+  '-t 10 --rate=100000 --latency-limit=1 -n -r' .
+  ' -f t/scripts/001_06_sleep.pgbench',
+  0,
+  [ qr{processed: [01]/10}, qr{type: t/scripts/001_06_sleep},
+    qr{above the 1.0 ms latency limit: [01] }],
+  qr{^$});
+
+# ... with logs
+sub check_pgbench_logs($$$$$)
+{
+  my ($prefix, $nb, $min, $max, $re) = @_;
+  my @logs = <$prefix.*>;
+  ok(@logs == $nb, "number of log files");
+  ok(grep(/^$prefix\.\d+(\.\d+)?$/, @logs) == $nb, "file name format");
+
+  my $log_number = 0;
+  for my $log (sort @logs)
+  {
+    eval {
+      open LOG, $log or die "$@";
+      my @contents = <LOG>;
+      my $clen = @contents;
+      ok($min <= $clen && $clen <= $max, "transaction count for $log ($clen)");
+      ok(grep($re, @contents) == scalar @contents,
+	 "transaction format for $prefix");
+      close LOG or die "$@";
+    };
+  }
+  ok(unlink @logs, "remove log files");
+}
+
+# note: --progress-timestamp is not tested
+$node->pgbench_likes(
+  'pgbench progress',
+  '-T 2 -P 1 -l --log-prefix=001_pgbench_log_1 --aggregate-interval=1' .
+  ' -S -b se@2 --rate=20 --latency-limit=1000 -j 2 -c 3 -r',
+  0,
+  [ qr{type: multiple}, qr{clients: 3}, qr{threads: 2}, qr{duration: 2 s},
+    qr{script 1: .* select only}, qr{script 2: .* select only},
+    qr{statement latencies in milliseconds}, qr{FROM pgbench_accounts} ],
+  [ qr{vacuum}, qr{progress: 1\b} ]);
+
+# 2 threads 2 seconds, sometimes only one aggregated line is written
+check_pgbench_logs('001_pgbench_log_1', 2, 1, 2,
+		   qr{^\d+ \d{1,2} \d+ \d+ \d+ \d+ \d+ \d+ \d+ \d+ \d+$});
+
+# with sampling rate
+$node->pgbench_likes(
+  'pgbench logs',
+  '-n -S -t 50 -c 2 --log --log-prefix=001_pgbench_log_2 --sampling-rate=0.5',
+  0,
+  [ qr{select only}, qr{processed: 100/100} ], qr{^$});
+
+check_pgbench_logs('001_pgbench_log_2', 1, 8, 92, qr{^0 \d{1,2} \d+ \d \d+ \d+$});
+
+# check log file in some detail
+$node->pgbench_likes(
+  'pgbench logs contents',
+  '-n -b se -t 10 -l --log-prefix=001_pgbench_log_3',
+  0, [ qr{select only}, qr{processed: 10/10} ], qr{^$});
+
+check_pgbench_logs('001_pgbench_log_3', 1, 10, 10, qr{^\d \d{1,2} \d+ \d \d+ \d+$});
+
+# done
+$node->stop;
diff --git a/src/bin/pgbench/t/scripts/001_01_concurrent_oid_generation.pgbench b/src/bin/pgbench/t/scripts/001_01_concurrent_oid_generation.pgbench
new file mode 100644
index 0000000..649039e
--- /dev/null
+++ b/src/bin/pgbench/t/scripts/001_01_concurrent_oid_generation.pgbench
@@ -0,0 +1 @@
+INSERT INTO oid_tbl SELECT FROM generate_series(1,1000);
diff --git a/src/bin/pgbench/t/scripts/001_02_custom_script.pgbench b/src/bin/pgbench/t/scripts/001_02_custom_script.pgbench
new file mode 100644
index 0000000..8554c5a
--- /dev/null
+++ b/src/bin/pgbench/t/scripts/001_02_custom_script.pgbench
@@ -0,0 +1,9 @@
+\set aid random(1, :scale * 100000)
+BEGIN;
+SELECT abalance::INTEGER AS balance
+  FROM pgbench_accounts
+  WHERE aid=:aid;
+\set foo 1
+-- cast are needed for typing under -M prepared
+SELECT :foo::INT + :scale::INT * :client_id::INT AS bla;
+COMMIT;
diff --git a/src/bin/pgbench/t/scripts/001_03_expressions.pgbench b/src/bin/pgbench/t/scripts/001_03_expressions.pgbench
new file mode 100644
index 0000000..1cc0093
--- /dev/null
+++ b/src/bin/pgbench/t/scripts/001_03_expressions.pgbench
@@ -0,0 +1,31 @@
+-- integer functions
+\set i1 debug(random(1, 100))
+\set i2 debug(random_exponential(1, 100, 10.0))
+\set i3 debug(random_gaussian(1, 100, 10.0))
+\set i4 debug(abs(-4))
+\set i5 debug(greatest(5, 4, 3, 2))
+\set i6 debug(11 + least(-5, -4, -3, -2))
+\set i7 debug(int(7.3))
+-- integer operators
+\set i8 debug(17 / 5 + 5)
+\set i9 debug(- (3 * 4 - 3) / -1 + 3 % -1)
+\set ia debug(10 + (0 + 0 * 0 - 0 / 1))
+\set ib debug(:ia + :scale)
+\set ic debug(64 % 13)
+-- double functions
+\set d1 debug(sqrt(3.0) * abs(-0.8E1))
+\set d2 debug(double(1 + 1) * 7)
+\set pi debug(pi() * 4.9)
+\set d4 debug(greatest(4, 2, -1.17) * 4.0)
+\set d5 debug(least(-5.18, .0E0, 1.0/0) * -3.3)
+-- double operators
+\set d6 debug((0.5 * 12.1 - 0.05) * (31.0 / 10))
+\set d7 debug(11.1 + 7.9)
+\set d8 debug(:foo * -2)
+-- special values
+\set nan debug(0.0 / 0.0)
+\set pin debug(1.0 / 0.0)
+\set nin debug(-1.0 / 0.0)
+\set maxint debug(:minint - 1)
+-- reset a variable
+\set i1 0
diff --git a/src/bin/pgbench/t/scripts/001_04_backslash_commands.pgbench b/src/bin/pgbench/t/scripts/001_04_backslash_commands.pgbench
new file mode 100644
index 0000000..f523f34
--- /dev/null
+++ b/src/bin/pgbench/t/scripts/001_04_backslash_commands.pgbench
@@ -0,0 +1,15 @@
+-- run set
+\set zero 0
+\set one 1.0
+-- sleep
+\sleep :one ms
+\sleep 100 us
+\sleep 0 s
+\sleep :zero
+-- setshell and continuation
+\setshell two\
+  expr \
+    1 + :one
+\set n debug(:two)
+-- shell
+\shell echo shell-echo-output
diff --git a/src/bin/pgbench/t/scripts/001_05_error_misc_1.pgbench b/src/bin/pgbench/t/scripts/001_05_error_misc_1.pgbench
new file mode 100644
index 0000000..89aa1da
--- /dev/null
+++ b/src/bin/pgbench/t/scripts/001_05_error_misc_1.pgbench
@@ -0,0 +1 @@
+\nosuchcommand
diff --git a/src/bin/pgbench/t/scripts/001_05_error_misc_2.pgbench b/src/bin/pgbench/t/scripts/001_05_error_misc_2.pgbench
new file mode 100644
index 0000000..e69de29
diff --git a/src/bin/pgbench/t/scripts/001_05_error_set_1.pgbench b/src/bin/pgbench/t/scripts/001_05_error_set_1.pgbench
new file mode 100644
index 0000000..0c155af
--- /dev/null
+++ b/src/bin/pgbench/t/scripts/001_05_error_set_1.pgbench
@@ -0,0 +1 @@
+\set i 1 +
diff --git a/src/bin/pgbench/t/scripts/001_05_error_set_2.pgbench b/src/bin/pgbench/t/scripts/001_05_error_set_2.pgbench
new file mode 100644
index 0000000..36d7939
--- /dev/null
+++ b/src/bin/pgbench/t/scripts/001_05_error_set_2.pgbench
@@ -0,0 +1 @@
+\set i noSuchFunction()
diff --git a/src/bin/pgbench/t/scripts/001_05_error_set_3.pgbench b/src/bin/pgbench/t/scripts/001_05_error_set_3.pgbench
new file mode 100644
index 0000000..213b557
--- /dev/null
+++ b/src/bin/pgbench/t/scripts/001_05_error_set_3.pgbench
@@ -0,0 +1 @@
+\set . 1
diff --git a/src/bin/pgbench/t/scripts/001_05_error_set_4.pgbench b/src/bin/pgbench/t/scripts/001_05_error_set_4.pgbench
new file mode 100644
index 0000000..c7f2457
--- /dev/null
+++ b/src/bin/pgbench/t/scripts/001_05_error_set_4.pgbench
@@ -0,0 +1 @@
+\set i int(1E32)
diff --git a/src/bin/pgbench/t/scripts/001_05_error_set_5.pgbench b/src/bin/pgbench/t/scripts/001_05_error_set_5.pgbench
new file mode 100644
index 0000000..8c3c033
--- /dev/null
+++ b/src/bin/pgbench/t/scripts/001_05_error_set_5.pgbench
@@ -0,0 +1 @@
+\set i 1/0
diff --git a/src/bin/pgbench/t/scripts/001_05_error_set_6.pgbench b/src/bin/pgbench/t/scripts/001_05_error_set_6.pgbench
new file mode 100644
index 0000000..153efde
--- /dev/null
+++ b/src/bin/pgbench/t/scripts/001_05_error_set_6.pgbench
@@ -0,0 +1 @@
+\set i 9223372036854775808 / -1
diff --git a/src/bin/pgbench/t/scripts/001_05_error_set_7.pgbench b/src/bin/pgbench/t/scripts/001_05_error_set_7.pgbench
new file mode 100644
index 0000000..3c2279f
--- /dev/null
+++ b/src/bin/pgbench/t/scripts/001_05_error_set_7.pgbench
@@ -0,0 +1 @@
+\set i :nosuchvariable
diff --git a/src/bin/pgbench/t/scripts/001_05_error_set_8.pgbench b/src/bin/pgbench/t/scripts/001_05_error_set_8.pgbench
new file mode 100644
index 0000000..55f7d09
--- /dev/null
+++ b/src/bin/pgbench/t/scripts/001_05_error_set_8.pgbench
@@ -0,0 +1 @@
+\set i ;
diff --git a/src/bin/pgbench/t/scripts/001_05_error_set_9.pgbench b/src/bin/pgbench/t/scripts/001_05_error_set_9.pgbench
new file mode 100644
index 0000000..eaeebde
--- /dev/null
+++ b/src/bin/pgbench/t/scripts/001_05_error_set_9.pgbench
@@ -0,0 +1 @@
+\set i least(0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16)
diff --git a/src/bin/pgbench/t/scripts/001_05_error_set_A.pgbench b/src/bin/pgbench/t/scripts/001_05_error_set_A.pgbench
new file mode 100644
index 0000000..dff9ff6
--- /dev/null
+++ b/src/bin/pgbench/t/scripts/001_05_error_set_A.pgbench
@@ -0,0 +1,2 @@
+-- empty range
+\set i random(5,3)
diff --git a/src/bin/pgbench/t/scripts/001_05_error_set_B.pgbench b/src/bin/pgbench/t/scripts/001_05_error_set_B.pgbench
new file mode 100644
index 0000000..5a6a701
--- /dev/null
+++ b/src/bin/pgbench/t/scripts/001_05_error_set_B.pgbench
@@ -0,0 +1,2 @@
+-- random range too large
+\set i random(-9223372036854775808, 9223372036854775807)
diff --git a/src/bin/pgbench/t/scripts/001_05_error_set_C.pgbench b/src/bin/pgbench/t/scripts/001_05_error_set_C.pgbench
new file mode 100644
index 0000000..288c2a9
--- /dev/null
+++ b/src/bin/pgbench/t/scripts/001_05_error_set_C.pgbench
@@ -0,0 +1,2 @@
+-- gaussian parameter too small
+\set i random_gaussian(0, 10, 1.0)
diff --git a/src/bin/pgbench/t/scripts/001_05_error_set_D.pgbench b/src/bin/pgbench/t/scripts/001_05_error_set_D.pgbench
new file mode 100644
index 0000000..5790f2d
--- /dev/null
+++ b/src/bin/pgbench/t/scripts/001_05_error_set_D.pgbench
@@ -0,0 +1,2 @@
+-- exponential parameter not > 0
+\set i random_exponential(0, 10, 0.0)
diff --git a/src/bin/pgbench/t/scripts/001_05_error_set_E.pgbench b/src/bin/pgbench/t/scripts/001_05_error_set_E.pgbench
new file mode 100644
index 0000000..6b5c021
--- /dev/null
+++ b/src/bin/pgbench/t/scripts/001_05_error_set_E.pgbench
@@ -0,0 +1,2 @@
+-- non numerical variable
+\set i :foo + 1
diff --git a/src/bin/pgbench/t/scripts/001_05_error_set_F.pgbench b/src/bin/pgbench/t/scripts/001_05_error_set_F.pgbench
new file mode 100644
index 0000000..336b345
--- /dev/null
+++ b/src/bin/pgbench/t/scripts/001_05_error_set_F.pgbench
@@ -0,0 +1 @@
+\set i
diff --git a/src/bin/pgbench/t/scripts/001_05_error_set_G.pgbench b/src/bin/pgbench/t/scripts/001_05_error_set_G.pgbench
new file mode 100644
index 0000000..0c35993
--- /dev/null
+++ b/src/bin/pgbench/t/scripts/001_05_error_set_G.pgbench
@@ -0,0 +1 @@
+\set
diff --git a/src/bin/pgbench/t/scripts/001_05_error_setsh_1.pgbench b/src/bin/pgbench/t/scripts/001_05_error_setsh_1.pgbench
new file mode 100644
index 0000000..13531de
--- /dev/null
+++ b/src/bin/pgbench/t/scripts/001_05_error_setsh_1.pgbench
@@ -0,0 +1,2 @@
+-- not an int assignment
+\setshell i echo -n one
diff --git a/src/bin/pgbench/t/scripts/001_05_error_setsh_2.pgbench b/src/bin/pgbench/t/scripts/001_05_error_setsh_2.pgbench
new file mode 100644
index 0000000..f170474
--- /dev/null
+++ b/src/bin/pgbench/t/scripts/001_05_error_setsh_2.pgbench
@@ -0,0 +1 @@
+\setshell var
diff --git a/src/bin/pgbench/t/scripts/001_05_error_setsh_3.pgbench b/src/bin/pgbench/t/scripts/001_05_error_setsh_3.pgbench
new file mode 100644
index 0000000..73003a7
--- /dev/null
+++ b/src/bin/pgbench/t/scripts/001_05_error_setsh_3.pgbench
@@ -0,0 +1 @@
+\setshell var no-such-command
diff --git a/src/bin/pgbench/t/scripts/001_05_error_sh_1.pgbench b/src/bin/pgbench/t/scripts/001_05_error_sh_1.pgbench
new file mode 100644
index 0000000..43a5c88
--- /dev/null
+++ b/src/bin/pgbench/t/scripts/001_05_error_sh_1.pgbench
@@ -0,0 +1 @@
+\shell no-such-command
diff --git a/src/bin/pgbench/t/scripts/001_05_error_sh_2.pgbench b/src/bin/pgbench/t/scripts/001_05_error_sh_2.pgbench
new file mode 100644
index 0000000..154e528
--- /dev/null
+++ b/src/bin/pgbench/t/scripts/001_05_error_sh_2.pgbench
@@ -0,0 +1,2 @@
+-- undefined variable in shell
+\shell echo ::foo :nosuchvariable
diff --git a/src/bin/pgbench/t/scripts/001_05_error_sh_3.pgbench b/src/bin/pgbench/t/scripts/001_05_error_sh_3.pgbench
new file mode 100644
index 0000000..00c9ed2
--- /dev/null
+++ b/src/bin/pgbench/t/scripts/001_05_error_sh_3.pgbench
@@ -0,0 +1 @@
+\shell
diff --git a/src/bin/pgbench/t/scripts/001_05_error_sh_4.pgbench b/src/bin/pgbench/t/scripts/001_05_error_sh_4.pgbench
new file mode 100644
index 0000000..e835626
--- /dev/null
+++ b/src/bin/pgbench/t/scripts/001_05_error_sh_4.pgbench
@@ -0,0 +1,17 @@
+\shell echo \
+ 0 1 2 3 4 5 6 7 8 9 A B C D E F \
+ 0 1 2 3 4 5 6 7 8 9 A B C D E F \
+ 0 1 2 3 4 5 6 7 8 9 A B C D E F \
+ 0 1 2 3 4 5 6 7 8 9 A B C D E F \
+ 0 1 2 3 4 5 6 7 8 9 A B C D E F \
+ 0 1 2 3 4 5 6 7 8 9 A B C D E F \
+ 0 1 2 3 4 5 6 7 8 9 A B C D E F \
+ 0 1 2 3 4 5 6 7 8 9 A B C D E F \
+ 0 1 2 3 4 5 6 7 8 9 A B C D E F \
+ 0 1 2 3 4 5 6 7 8 9 A B C D E F \
+ 0 1 2 3 4 5 6 7 8 9 A B C D E F \
+ 0 1 2 3 4 5 6 7 8 9 A B C D E F \
+ 0 1 2 3 4 5 6 7 8 9 A B C D E F \
+ 0 1 2 3 4 5 6 7 8 9 A B C D E F \
+ 0 1 2 3 4 5 6 7 8 9 A B C D E F \
+ 0 1 2 3 4 5 6 7 8 9 A B C D E F
diff --git a/src/bin/pgbench/t/scripts/001_05_error_sleep_1.pgbench b/src/bin/pgbench/t/scripts/001_05_error_sleep_1.pgbench
new file mode 100644
index 0000000..40d1231
--- /dev/null
+++ b/src/bin/pgbench/t/scripts/001_05_error_sleep_1.pgbench
@@ -0,0 +1,2 @@
+-- undefined variable in sleep
+\sleep :nosuchvariable
diff --git a/src/bin/pgbench/t/scripts/001_05_error_sleep_2.pgbench b/src/bin/pgbench/t/scripts/001_05_error_sleep_2.pgbench
new file mode 100644
index 0000000..a2aaec3
--- /dev/null
+++ b/src/bin/pgbench/t/scripts/001_05_error_sleep_2.pgbench
@@ -0,0 +1 @@
+\sleep too many args
\ No newline at end of file
diff --git a/src/bin/pgbench/t/scripts/001_05_error_sleep_3.pgbench b/src/bin/pgbench/t/scripts/001_05_error_sleep_3.pgbench
new file mode 100644
index 0000000..e95e100
--- /dev/null
+++ b/src/bin/pgbench/t/scripts/001_05_error_sleep_3.pgbench
@@ -0,0 +1,2 @@
+-- missing argument
+\sleep
\ No newline at end of file
diff --git a/src/bin/pgbench/t/scripts/001_05_error_sleep_4.pgbench b/src/bin/pgbench/t/scripts/001_05_error_sleep_4.pgbench
new file mode 100644
index 0000000..1d283e4
--- /dev/null
+++ b/src/bin/pgbench/t/scripts/001_05_error_sleep_4.pgbench
@@ -0,0 +1 @@
+\sleep 1 week
\ No newline at end of file
diff --git a/src/bin/pgbench/t/scripts/001_05_error_sql_1.pgbench b/src/bin/pgbench/t/scripts/001_05_error_sql_1.pgbench
new file mode 100644
index 0000000..5eabe77
--- /dev/null
+++ b/src/bin/pgbench/t/scripts/001_05_error_sql_1.pgbench
@@ -0,0 +1,2 @@
+  -- SQL syntax error
+    SELECT 1 + ;
diff --git a/src/bin/pgbench/t/scripts/001_05_error_sql_2.pgbench b/src/bin/pgbench/t/scripts/001_05_error_sql_2.pgbench
new file mode 100644
index 0000000..b388190
--- /dev/null
+++ b/src/bin/pgbench/t/scripts/001_05_error_sql_2.pgbench
@@ -0,0 +1,3 @@
+\set i 0
+-- MAX_ARGS=10 for prepared
+SELECT LEAST(:i, :i, :i, :i, :i, :i, :i, :i, :i, :i, :i);
diff --git a/src/bin/pgbench/t/scripts/001_06_sleep.pgbench b/src/bin/pgbench/t/scripts/001_06_sleep.pgbench
new file mode 100644
index 0000000..48963e5
--- /dev/null
+++ b/src/bin/pgbench/t/scripts/001_06_sleep.pgbench
@@ -0,0 +1 @@
+\sleep 2ms
diff --git a/src/test/perl/PostgresNode.pm b/src/test/perl/PostgresNode.pm
index cb84f1f..124e212 100644
--- a/src/test/perl/PostgresNode.pm
+++ b/src/test/perl/PostgresNode.pm
@@ -1189,6 +1189,65 @@ sub psql
 
 =pod
 
+=item $node->pgbench($options)
+
+Run pgbench with options, return status, stdout and stderr.
+
+=cut
+
+sub pgbench
+{
+	my ($self, $opts) = @_;
+	my ($stdout, $stderr);
+	local $ENV{PGPORT} = $self->port;
+	IPC::Run::run([ 'pgbench', split(/\s+/,$opts) ],
+		      '>', \$stdout, '2>', \$stderr);
+	my $ret = $? >> 8;
+	return ($ret, $stdout, $stderr);
+      }
+
+=pod
+
+=item $node->pgbench_likes($options, $ret, $out_re, $err_re)
+
+Run pgbench with options, and do 3 checks:
+return status against expected value,
+stdout and stderr against provide regular expressions.
+
+=cut
+
+sub pgbench_likes
+{
+	my ($self, $name, $opts, $ret_expected, $out_re, $err_re) = @_;
+	my ($ret, $out, $err) = $self->pgbench($opts);
+	ok($ret == $ret_expected,
+	   "$name status (got $ret vs expected $ret_expected)");
+	if (ref $out_re eq 'ARRAY')
+	{
+		for my $re (@$out_re)
+		{
+			like($out, $re, "$name out /$re/");
+		}
+	}
+	else
+	{
+		like($out, $out_re, "$name out /$out_re/");
+	}
+	if (ref $err_re eq 'ARRAY')
+	{
+		for my $re (@$err_re)
+		{
+			like($err, $re, "$name err /$re/");
+		}
+	}
+	else
+	{
+		like($err, $err_re, "$name err /$err_re/");
+	}
+}
+
+=pod
+
 =item $node->poll_query_until(dbname, query)
 
 Run a query once a second, until it returns 't' (i.e. SQL boolean true).
-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

Reply via email to