To sum up:

- I agree to add a generic command TestLib & a wrapper in PostgresNode,
  instead of having pgbench specific things in the later, then call
  them from pgbench test script.

- I still think that moving the pgbench scripts inside the test script
  is a bad idea (tm).

Here is a v2 along those lines.

I have also separated some basic test which do not need a server running, as done in other tap tests.

--
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..8b03af3 100644
--- a/src/bin/pgbench/t/001_pgbench.pl
+++ b/src/bin/pgbench/t/001_pgbench.pl
@@ -3,23 +3,296 @@ use warnings;
 
 use PostgresNode;
 use TestLib;
-use Test::More tests => 3;
+use Test::More tests => 244;
+
+# start a pgbench specific server
+my $node = get_new_node('main');
+$node->init;
+$node->start;
+
+# invoke pgbench
+sub pgbench($$$$$)
+{
+	my ($opts, $stat, $out, $err, $name) = @_;
+	$node->command_likes([ 'pgbench', split(/\s+/, $opts) ],
+			     $stat, $out, $err, $name);
+}
 
 # 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
+pgbench(
+  '--no-vacuum --client=5 --protocol=prepared --transactions=25' .
+  '  --file=t/scripts/001_01_concurrent_oid_generation.pgbench',
+  0, qr{processed: 125/125}, qr{^$}, 'concurrency OID generation');
+
+# cleanup
+$node->safe_psql('postgres', 'DROP TABLE oid_tbl;');
+
+# Trigger various connection errors
+pgbench(
+  'no-such-database', 1, qr{^$},
+  [ qr{connection to database "no-such-database" failed},
+    qr{FATAL:  database "no-such-database" does not exist} ],
+  'no such database');
+
+pgbench(
+  '-U no-such-user template0', 1, qr{^$},
+  [ qr{connection to database "template0" failed},
+    qr{FATAL:  role "no-such-user" does not exist} ],
+  'no such user');
+
+pgbench(
+  '-h no-such-host.postgresql.org', 1, qr{^$},
+  [ qr{connection to database "postgres" failed},
+    qr{could not translate host name "no-such-host.postgresql.org" to address: Name or service not known} ],
+  'no such host');
+
+pgbench(
+  '-S -t 1',
+  1, qr{^$}, qr{Perhaps you need to do initialization},
+  'run without init');
+
+# Initialize pgbench tables scale 1
+pgbench(
+  '-i',
+  0, qr{^$},
+  [ qr{creating tables}, qr{vacuum}, qr{set primary keys}, qr{done\.} ],
+  'pgbench scale 1 initialization',
+);
+
+# Again, with all possible options
+pgbench(
+  # 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\.} ],
+  'pgbench scale 1 initialization');
+
+# Run all builtins for a few transactions: 20 checks
+pgbench(
+  '--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{^$}, 'pgbench tpcb-like');
+
+pgbench(
+  '--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},
+  'pgbench simple update');
+
+pgbench(
+  '-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} ],
+  'pgbench select only');
+
+# run custom scripts: 8 checks
+pgbench(
+  '-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{^$}, 'pgbench custom script');
+
+pgbench(
+  '-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{^$}, 'pgbench custom script');
+
+pgbench(
+  '-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{^$}, 'pgbench custom script');
+
+# test expressions: 23 checks
+pgbench(
+  '-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},
+  ],
+  'pgbench expressions');
+
+# backslash commands: 4 checks
+pgbench(
+  '-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},
+  'pgbench backslash commands');
+
+# 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;
+  pgbench(
+    "-n -t 1 -Dfoo=bla -M prepared -f t/scripts/001_05_error_$number.pgbench",
+    $status, $status ? qr{^$} : qr{processed: 0/1}, $re,
+    'pgbench script error: ' . $name);
+}
+
+# throttling
+pgbench(
+  '-t 100 -S --rate=100000 --latency-limit=1000000 -c 2 -n -r',
+  0, [ qr{processed: 200/200}, qr{builtin: select only} ], qr{^$},
+  'pgbench throttling');
+
+pgbench(
+  # 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{^$},
+  'pgbench late throttling');
+
+# ... 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
+pgbench(
+  '-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} ],
+  'pgbench progress');
+
+# 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
+pgbench(
+  '-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{^$},
+  'pgbench logs');
+
+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
+pgbench(
+  '-n -b se -t 10 -l --log-prefix=001_pgbench_log_3',
+  0, [ qr{select only}, qr{processed: 10/10} ], qr{^$},
+  'pgbench logs contents');
+
+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/002_basic.pl b/src/bin/pgbench/t/002_basic.pl
new file mode 100644
index 0000000..a4ad18c
--- /dev/null
+++ b/src/bin/pgbench/t/002_basic.pl
@@ -0,0 +1,90 @@
+#
+# pgbench tests which do not need a server
+#
+
+use strict;
+use warnings;
+
+use TestLib;
+use Test::More tests => 116;
+
+# invoke pgbench
+sub pgbench($$$$$)
+{
+	my ($opts, $stat, $out, $err, $name) = @_;
+	print STDERR "opts=$opts, stat=$stat, out=$out, err=$err, name=$name";
+	command_likes([ 'pgbench', split(/\s+/, $opts) ],
+		      $stat, $out, $err, $name);
+}
+
+#
+# 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 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} ],
+  [ '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;
+  pgbench($opts, 1, qr{^$}, $err_checks, 'pgbench option error: ' . $name);
+}
+
+# Help: 7 checks
+pgbench('--help',
+  0,
+  [ qr{benchmarking tool for PostgreSQL}, qr{Usage},
+    qr{Initialization options:}, qr{Common options:}, qr{Report bugs to} ],
+  qr{^$}, 'pgbench help');
+
+# Version
+pgbench(
+  '-V',
+  0, qr{^pgbench .PostgreSQL. }, qr{^$}, 'pgbench version');
+
+# list of builtins
+pgbench(
+  '-b list',
+  0, qr{^$},
+  [ qr{Available builtin scripts:},
+    qr{tpcb-like}, qr{simple-update}, qr{select-only} ],
+  'pgbench buitlin list');
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 51cbec8..9884822 100644
--- a/src/test/perl/PostgresNode.pm
+++ b/src/test/perl/PostgresNode.pm
@@ -1302,6 +1302,25 @@ sub command_like
 
 =pod
 
+=item $node->command_likes(...)
+
+Runs a shell command like TestLib::command_likes, but with PGPORT
+set so that the command will default to connecting to this
+PostgresNode.
+
+=cut
+
+sub command_likes
+{
+	my $self = shift;
+
+	local $ENV{PGPORT} = $self->port;
+
+	TestLib::command_likes(@_);
+}
+
+=pod
+
 =item $node->issues_sql_like(cmd, expected_sql, test_name)
 
 Run a command on the node, then verify that $expected_sql appears in the
diff --git a/src/test/perl/TestLib.pm b/src/test/perl/TestLib.pm
index da65c92..a3f6281 100644
--- a/src/test/perl/TestLib.pm
+++ b/src/test/perl/TestLib.pm
@@ -37,6 +37,7 @@ our @EXPORT = qw(
   program_options_handling_ok
   command_like
   command_fails_like
+  command_likes
 
   $windows_os
 );
@@ -309,4 +310,47 @@ sub command_fails_like
 	like($stderr, $expected_stderr, "$test_name: matches");
 }
 
+sub command_likes
+{
+	my ($cmd, $ret, $out, $err, $test_name) = @_;
+
+	# split command if provided as a string
+	$cmd = [ split /\s+/, $cmd ] unless ref $cmd eq 'ARRAY';
+
+	# run command
+	my ($stdout, $stderr);
+	IPC::Run::run($cmd, '>', \$stdout, '2>', \$stderr);
+	my $status = $? >> 8;
+
+	# check status
+	ok($ret == $status,
+	   "$test_name status (got $status vs expected $ret)");
+
+	# check stdout
+	if (ref $out eq 'ARRAY')
+	{
+		for my $re (@$out)
+		{
+			like($stdout, $re, "$test_name out /$re/");
+		}
+	}
+	else
+	{
+	  like($stdout, $out, "$test_name out /$out/");
+	}
+
+	# check stderr
+	if (ref $err eq 'ARRAY')
+	{
+		for my $re (@$err)
+		{
+		  like($stderr, $re, "$test_name err /$re/");
+		}
+	}
+	else
+	{
+		like($stderr, $err, "$test_name err /$err/");
+	}
+}
+
 1;
-- 
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