user/dev discussion of public-inbox itself
 help / color / mirror / code / Atom feed
From: Eric Wong <e@80x24.org>
To: meta@public-inbox.org
Subject: [PATCH 12/29] t/common: introduce run_script wrapper for t/cgi.t
Date: Fri, 15 Nov 2019 09:50:43 +0000	[thread overview]
Message-ID: <20191115095100.25633-13-e@80x24.org> (raw)
In-Reply-To: <20191115095100.25633-1-e@80x24.org>

This will give us a consistent interface for running
test scripts in more performant ways while still giving
us a consistent interface to recreate real-world behavior
via spawn() (fork + execve), if needed.

The default run_mode (1) is faster and can run within the test
process with some minor adjustments to our code to avoid global
state.

This avoids the significante overhead of Perl code loading,
parsing and compilation phases.
---
 t/cgi.t       |  16 ++-----
 t/common.perl | 125 +++++++++++++++++++++++++++++++++++++++++++++++++-
 2 files changed, 129 insertions(+), 12 deletions(-)

diff --git a/t/cgi.t b/t/cgi.t
index 1b4b06cb..3c09ecd6 100644
--- a/t/cgi.t
+++ b/t/cgi.t
@@ -7,10 +7,7 @@ use warnings;
 use Test::More;
 use Email::MIME;
 use File::Temp qw/tempdir/;
-eval { require IPC::Run };
-plan skip_all => "missing IPC::Run for t/cgi.t" if $@;
-
-use constant CGI => "blib/script/public-inbox.cgi";
+require './t/common.perl';
 my $tmpdir = tempdir('pi-cgi-XXXXXX', TMPDIR => 1, CLEANUP => 1);
 my $home = "$tmpdir/pi-home";
 my $pi_home = "$home/.public-inbox";
@@ -145,11 +142,6 @@ EOF
 
 done_testing();
 
-sub run_with_env {
-	my ($env, @args) = @_;
-	IPC::Run::run(@args, init => sub { %ENV = (%ENV, %$env) });
-}
-
 sub cgi_run {
 	my %env = (
 		PATH_INFO => $_[0],
@@ -162,7 +154,9 @@ sub cgi_run {
 		HTTP_HOST => 'test.example.com',
 	);
 	my ($in, $out, $err) = ("", "", "");
-	my $rc = run_with_env(\%env, [CGI], \$in, \$out, \$err);
+	my $rdr = { 0 => \$in, 1 => \$out, 2 => \$err };
+	run_script(['.cgi'], \%env, $rdr);
+	die "unexpected error: \$?=$?" if $?;
 	my ($head, $body) = split(/\r\n\r\n/, $out, 2);
-	{ head => $head, body => $body, rc => $rc, err => $err }
+	{ head => $head, body => $body, err => $err }
 }
diff --git a/t/common.perl b/t/common.perl
index d4a0fcd2..c5693080 100644
--- a/t/common.perl
+++ b/t/common.perl
@@ -1,7 +1,7 @@
 # Copyright (C) 2015-2019 all contributors <meta@public-inbox.org>
 # License: AGPL-3.0+ <https://www.gnu.org/licenses/agpl-3.0.txt>
 
-use Fcntl qw(FD_CLOEXEC F_SETFD F_GETFD);
+use Fcntl qw(FD_CLOEXEC F_SETFD F_GETFD :seek);
 use POSIX qw(dup2);
 use strict;
 use warnings;
@@ -68,4 +68,127 @@ sub require_git ($;$) {
 	1;
 }
 
+my %cached_scripts;
+sub key2script ($) {
+	my ($key) = @_;
+	return $key if $key =~ m!\A/!;
+	# n.b. we may have scripts which don't start with "public-inbox" in
+	# the future:
+	$key =~ s/\A([-\.])/public-inbox$1/;
+	'blib/script/'.$key;
+}
+
+sub _prepare_redirects ($) {
+	my ($fhref) = @_;
+	my @x = ([ \*STDIN, '<&' ], [ \*STDOUT, '>&' ], [ \*STDERR, '>&' ]);
+	for (my $fd = 0; $fd <= $#x; $fd++) {
+		my $fh = $fhref->[$fd] or next;
+		my ($oldfh, $mode) = @{$x[$fd]};
+		open $oldfh, $mode, $fh or die "$$oldfh $mode redirect: $!";
+	}
+}
+
+# $opt->{run_mode} (or $ENV{TEST_RUN_MODE}) allows chosing between
+# three ways to spawn our own short-lived Perl scripts for testing:
+#
+# 0 - (fork|vfork) + execve, the most realistic but slowest
+# 1 - preloading and running in a forked subprocess (fast)
+# 2 - preloading and running in current process (slightly faster than 1)
+#
+# 2 is not compatible with scripts which use "exit" (which we'll try to
+# avoid in the future).
+# The default is 2.
+our $run_script_exit_code;
+sub RUN_SCRIPT_EXIT () { "RUN_SCRIPT_EXIT\n" };
+sub run_script_exit (;$) {
+	$run_script_exit_code = $_[0] // 0;
+	die RUN_SCRIPT_EXIT;
+}
+
+sub run_script ($;$$) {
+	my ($cmd, $env, $opt) = @_;
+	my ($key, @argv) = @$cmd;
+	my $run_mode = $ENV{TEST_RUN_MODE} // $opt->{run_mode} // 1;
+	my $sub = $run_mode == 0 ? undef : ($cached_scripts{$key} //= do {
+		my $f = key2script($key);
+		open my $fh, '<', $f or die "open $f: $!";
+		my $str = do { local $/; <$fh> };
+		my ($fc, $rest) = ($key =~ m/([a-z])([a-z0-9]+)\z/);
+		$fc = uc($fc);
+		my $pkg = "PublicInbox::TestScript::$fc$rest";
+		eval <<EOF;
+package $pkg;
+use strict;
+use subs qw(exit);
+
+*exit = *::run_script_exit;
+sub main {
+$str
+	0;
+}
+1;
+EOF
+		$pkg->can('main');
+	}); # do
+
+	my $fhref = [];
+	my $spawn_opt = {};
+	for my $fd (0..2) {
+		my $redir = $opt->{$fd};
+		next unless ref($redir);
+		open my $fh, '+>', undef or die "open: $!";
+		$fhref->[$fd] = $fh;
+		$spawn_opt->{$fd} = fileno($fh);
+		next if $fd > 0;
+		$fh->autoflush(1);
+		print $fh $$redir or die "print: $!";
+		seek($fh, 0, SEEK_SET) or die "seek: $!";
+	}
+	if ($run_mode == 0) {
+		# spawn an independent new process, like real-world use cases:
+		require PublicInbox::Spawn;
+		my $cmd = [ key2script($key), @argv ];
+		my $pid = PublicInbox::Spawn::spawn($cmd, $env, $spawn_opt);
+		defined($pid) or die "spawn: $!";
+		if (defined $pid) {
+			my $r = waitpid($pid, 0);
+			defined($r) or die "waitpid: $!";
+			$r == $pid or die "waitpid: expected $pid, got $r";
+		}
+	} else { # localize and run everything in the same process:
+		local *STDIN = *STDIN;
+		local *STDOUT = *STDOUT;
+		local *STDERR = *STDERR;
+		local %ENV = $env ? (%ENV, %$env) : %ENV;
+		local %SIG = %SIG;
+		_prepare_redirects($fhref);
+		local @ARGV = @argv;
+		$run_script_exit_code = undef;
+		my $exit_code = eval { $sub->(@argv) };
+		if ($@ eq RUN_SCRIPT_EXIT) {
+			$@ = '';
+			$exit_code = $run_script_exit_code;
+			$? = ($exit_code << 8);
+		} elsif (defined($exit_code)) {
+			$? = ($exit_code << 8);
+		} elsif ($@) { # mimic die() behavior when uncaught
+			warn "E: eval-ed $key: $@\n";
+			$? = ($! << 8) if $!;
+			$? = (255 << 8) if $? == 0;
+		} else {
+			die "BUG: eval-ed $key: no exit code or \$@\n";
+		}
+	}
+
+	# slurp the redirects back into user-supplied strings
+	for my $fd (1..2) {
+		my $fh = $fhref->[$fd] or next;
+		seek($fh, 0, SEEK_SET) or die "seek: $!";
+		my $redir = $opt->{$fd};
+		local $/;
+		$$redir = <$fh>;
+	}
+	$? == 0;
+}
+
 1;

  parent reply	other threads:[~2019-11-15  9:51 UTC|newest]

Thread overview: 54+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2019-11-15  9:50 [PATCH 00/29] speed up tests by preloading Eric Wong
2019-11-15  9:50 ` [PATCH 01/29] edit: pass global variables into subs Eric Wong
2019-11-15  9:50 ` [PATCH 02/29] edit: use OO API of File::Temp to shorten lifetime Eric Wong
2019-11-15  9:50 ` [PATCH 03/29] admin: get rid of singleton $CFG var Eric Wong
2019-11-15  9:50 ` [PATCH 04/29] index: pass global variables into subs Eric Wong
2019-11-15  9:50 ` [PATCH 05/29] init: " Eric Wong
2019-11-15  9:50 ` [PATCH 06/29] mda: " Eric Wong
2019-11-15  9:50 ` [PATCH 07/29] learn: " Eric Wong
2019-11-15  9:50 ` [PATCH 08/29] inboxwritable: add ->cleanup method Eric Wong
2019-11-15  9:50 ` [PATCH 09/29] import: only pass Inbox object to SearchIdx->new Eric Wong
2019-11-15  9:50 ` [PATCH 10/29] xapcmd: do not fire END and DESTROY handlers in child Eric Wong
2019-11-15  9:50 ` [PATCH 11/29] spawn: which: allow embedded slash for relative path Eric Wong
2019-11-15  9:50 ` Eric Wong [this message]
2019-11-15  9:50 ` [PATCH 13/29] t/edit: switch to use run_script Eric Wong
2019-11-15  9:50 ` [PATCH 14/29] t/init: convert to using run_script Eric Wong
2019-11-15  9:50 ` [PATCH 15/29] t/purge: convert to run_script Eric Wong
2019-11-15  9:50 ` [PATCH 16/29] t/v2mirror: get rid of IPC::Run dependency Eric Wong
2019-11-15  9:50 ` [PATCH 17/29] t/mda: switch to run_script for testing Eric Wong
2019-11-15  9:50 ` [PATCH 18/29] t/mda_filter_rubylang: drop IPC::Run dependency Eric Wong
2019-11-15  9:50 ` [PATCH 19/29] doc: remove IPC::Run as a dev and test dependency Eric Wong
2019-11-15  9:50 ` [PATCH 20/29] t/v2mirror: switch to default run_mode for speedup Eric Wong
2019-11-15  9:50 ` [PATCH 21/29] t/convert-compact: convert to run_script Eric Wong
2019-11-15  9:50 ` [PATCH 22/29] t/httpd: use run_script for -init Eric Wong
2019-11-15  9:50 ` [PATCH 23/29] t/watch_maildir_v2: " Eric Wong
2019-11-15  9:50 ` [PATCH 24/29] t/nntpd: " Eric Wong
2019-11-15  9:50 ` [PATCH 25/29] t/watch_filter_rubylang: run_script for -init and -index Eric Wong
2019-11-15  9:50 ` [PATCH 26/29] t/v2mda: switch to run_script in many places Eric Wong
2019-11-15  9:50 ` [PATCH 27/29] t/indexlevels-mirror*: switch to run_script Eric Wong
2019-11-15  9:50 ` [PATCH 28/29] t/xcpdb-reshard: use run_script for -xcpdb Eric Wong
2019-11-15  9:51 ` [PATCH 29/29] t/common: start_script replaces spawn_listener Eric Wong
2019-11-16  6:52   ` Eric Wong
2019-11-16 11:43     ` Eric Wong
2019-11-24  0:22       ` [PATCH 00/17] test fixes and cleanups Eric Wong
2019-11-24  0:22         ` [PATCH 01/17] tests: disable daemon workers in a few more places Eric Wong
2019-11-24  0:22         ` [PATCH 02/17] tests: use strict everywhere Eric Wong
2019-11-24  0:22         ` [PATCH 03/17] t/v1-add-remove-add: quiet down "git init" Eric Wong
2019-11-24  0:22         ` [PATCH 04/17] t/xcpdb-reshard: test xcpdb --compact Eric Wong
2019-11-24  0:22         ` [PATCH 05/17] t/httpd-corner: wait for worker process death Eric Wong
2019-11-24  0:22         ` [PATCH 06/17] t/nntpd-tls: sometimes SSL_connect succeeds quickly Eric Wong
2019-11-24  0:22         ` [PATCH 07/17] .gitignore: ignore local prove(1) files Eric Wong
2019-11-24  0:22         ` [PATCH 08/17] daemon: use sigprocmask to block signals at startup Eric Wong
2019-11-24  0:22         ` [PATCH 09/17] daemon: use sigprocmask when respawning workers Eric Wong
2019-11-24  0:22         ` [PATCH 10/17] daemon: avoid race when quitting workers Eric Wong
2019-11-25  8:59           ` Eric Wong
2019-11-27  1:33             ` [PATCH 0/2] fix kqueue support and missed signal wakeups Eric Wong
2019-11-27  1:33               ` [PATCH 1/2] dskqxs: fix missing EV_DISPATCH define Eric Wong
2019-11-27  1:33               ` [PATCH 2/2] httpd|nntpd: avoid missed signal wakeups Eric Wong
2019-11-24  0:22         ` [PATCH 11/17] t/common: start_script replaces spawn_listener Eric Wong
2019-11-24  0:22         ` [PATCH 12/17] t/nntpd-validate: get rid of threads dependency Eric Wong
2019-11-24  0:22         ` [PATCH 13/17] xapcmd: replace Xtmpdirs with File::Temp->newdir Eric Wong
2019-11-24  0:22         ` [PATCH 14/17] tests: use File::Temp->newdir instead of tempdir() Eric Wong
2019-11-24  0:22         ` [PATCH 15/17] tests: quiet down commit graph Eric Wong
2019-11-24  0:22         ` [PATCH 16/17] t/perf-*.t: use $ENV{GIANT_INBOX_DIR} consistently Eric Wong
2019-11-24  0:22         ` [PATCH 17/17] tests: move giant inbox/git dependent tests to xt/ Eric Wong

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

  List information: http://public-inbox.org/README

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20191115095100.25633-13-e@80x24.org \
    --to=e@80x24.org \
    --cc=meta@public-inbox.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
Code repositories for project(s) associated with this public inbox

	https://80x24.org/public-inbox.git

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).