#!/usr/bin/env perl
# vim:ts=4:sw=4:expandtab
-# © 2010-2011 Michael Stapelberg and contributors
+# © 2010-2012 Michael Stapelberg and contributors
package complete_run;
use strict;
use warnings;
use v5.10;
+use utf8;
# the following are modules which ship with Perl (>= 5.10):
use Pod::Usage;
-use Cwd qw(abs_path);
use File::Temp qw(tempfile tempdir);
use Getopt::Long;
use POSIX ();
use TAP::Parser;
use TAP::Parser::Aggregator;
use Time::HiRes qw(time);
+use IO::Handle;
+
+my $dirname;
+
+BEGIN {
+ use File::Basename;
+ use Cwd qw(abs_path);
+
+ # fileparse()[1] contains the directory portion of the specified path.
+ # See File::Basename(3p) for more details.
+ $dirname = (fileparse(abs_path($0)))[1];
+}
+
# these are shipped with the testsuite
-use lib qw(lib);
-use StartXDummy;
+use lib $dirname . 'lib';
+use StartXServer;
use StatusLine;
use TestWorker;
# the following modules are not shipped with Perl
use X11::XCB::Connection;
use JSON::XS; # AnyEvent::I3 depends on it, too.
+binmode STDOUT, ':utf8';
+binmode STDERR, ':utf8';
+
# Close superfluous file descriptors which were passed by running in a VIM
# subshell or situations like that.
AnyEvent::Util::close_all_fds_except(0, 1, 2);
sub Log { say $log "@_" }
my %timings;
-my $coverage_testing = 0;
-my $valgrind = 0;
-my $strace = 0;
my $help = 0;
-# Number of tests to run in parallel. Important to know how many Xdummy
+# Number of tests to run in parallel. Important to know how many Xephyr
# instances we need to start (unless @displays are given). Defaults to
# num_cores * 2.
my $parallel = undef;
my @displays = ();
+my %options = (
+ valgrind => 0,
+ strace => 0,
+ xtrace => 0,
+ coverage => 0,
+ restart => 0,
+);
+my $keep_xserver_output = 0;
my $result = GetOptions(
- "coverage-testing" => \$coverage_testing,
- "valgrind" => \$valgrind,
- "strace" => \$strace,
+ "coverage-testing" => \$options{coverage},
+ "keep-xserver-output" => \$keep_xserver_output,
+ "valgrind" => \$options{valgrind},
+ "strace" => \$options{strace},
+ "xtrace" => \$options{xtrace},
"display=s" => \@displays,
"parallel=i" => \$parallel,
"help|?" => \$help,
pod2usage(-verbose => 2, -exitcode => 0) if $help;
+chdir $dirname or die "Could not chdir into $dirname";
+
+# Check for missing executables
+my @binaries = qw(
+ ../i3
+ ../i3bar/i3bar
+ ../i3-config-wizard/i3-config-wizard
+ ../i3-dump-log/i3-dump-log
+ ../i3-input/i3-input
+ ../i3-msg/i3-msg
+ ../i3-nagbar/i3-nagbar
+ );
+
+foreach my $binary (@binaries) {
+ die "$binary executable not found, did you run “make”?" unless -e $binary;
+ die "$binary is not an executable" unless -x $binary;
+}
+
+if ($options{coverage}) {
+ qx(command -v lcov &> /dev/null);
+ die "Cannot find lcov needed for coverage testing." if $?;
+ qx(command -v genhtml &> /dev/null);
+ die "Cannot find genhtml needed for coverage testing." if $?;
+
+ # clean out the counters that may be left over from previous tests.
+ qx(lcov -d ../ --zerocounters &> /dev/null);
+}
+
+qx(Xephyr -help 2>&1);
+die "Xephyr was not found in your path. Please install Xephyr (xserver-xephyr on Debian)." if $?;
+
@displays = split(/,/, join(',', @displays));
@displays = map { s/ //g; $_ } @displays;
my $numtests = scalar @testfiles;
-# No displays specified, let’s start some Xdummy instances.
-@displays = start_xdummy($parallel, $numtests) if @displays == 0;
+# No displays specified, let’s start some Xephyr instances.
+if (@displays == 0) {
+ @displays = start_xserver($parallel, $numtests, $keep_xserver_output);
+}
# 1: create an output directory for this test-run
my $outdir = "testsuite-";
$outdir .= `git describe --tags`;
chomp($outdir);
mkdir($outdir) or die "Could not create $outdir";
-unlink("latest") if -e "latest";
+unlink("latest") if -l "latest";
symlink("$outdir", "latest") or die "Could not symlink latest to $outdir";
# connect to all displays for two reasons:
# 1: check if the display actually works
# 2: keep the connection open so that i3 is not the only client. this prevents
-# the X server from exiting (Xdummy will restart it, but not quick enough
-# sometimes)
-my @worker;
+# the X server from exiting
+my @single_worker;
for my $display (@displays) {
my $screen;
my $x = X11::XCB::Connection->new(display => $display);
die "Could not connect to display $display\n";
} else {
# start a TestWorker for each display
- push @worker, worker($display, $x, $outdir);
+ push @single_worker, worker($display, $x, $outdir, \%options);
}
}
# Read previous timing information, if available. We will be able to roughly
# predict the test duration and schedule a good order for the tests.
-my $timingsjson = StartXDummy::slurp('.last_run_timings.json');
+my $timingsjson = StartXServer::slurp('.last_run_timings.json');
%timings = %{decode_json($timingsjson)} if length($timingsjson) > 0;
# Re-order the files so that those which took the longest time in the previous
sort { $b->[1] <=> $a->[1] }
map { [$_, $timings{$_} // 999] } @testfiles;
+# Run 000-load-deps.t first to bail out early when dependencies are missing.
+my $loadtest = "t/000-load-deps.t";
+if ((scalar grep { $_ eq $loadtest } @testfiles) > 0) {
+ @testfiles = ($loadtest, grep { $_ ne $loadtest } @testfiles);
+}
+
printf("\nRough time estimate for this run: %.2f seconds\n\n", $timings{GLOBAL})
if exists($timings{GLOBAL});
my $logfile = "$outdir/complete-run.log";
open $log, '>', $logfile or die "Could not create '$logfile': $!";
+$log->autoflush(1);
say "Writing logfile to '$logfile'...";
# 3: run all tests
status_init(displays => \@displays, tests => $num);
-my $cv = AE::cv;
+my $single_cv = AE::cv;
# We start tests concurrently: For each display, one test gets started. Every
# test starts another test after completing.
-for (@worker) { $cv->begin; take_job($_) }
+for (@single_worker) {
+ $single_cv->begin;
+ take_job($_, $single_cv, \@testfiles);
+}
-$cv->recv;
+$single_cv->recv;
$aggregator->stop();
if ($numtests == 1) {
say '';
say 'Test output:';
- say StartXDummy::slurp($logfile);
+ say StartXServer::slurp($logfile);
}
END { cleanup() }
-exit 0;
+if ($options{coverage}) {
+ print("\nGenerating test coverage report...\n");
+ qx(lcov -d ../ -b ../ --capture -o latest/i3-coverage.info);
+ qx(genhtml -o latest/i3-coverage latest/i3-coverage.info);
+ if ($?) {
+ print("Could not generate test coverage html. Did you compile i3 with test coverage support?\n");
+ } else {
+ print("Test coverage report generated in latest/i3-coverage\n");
+ }
+}
+
+exit ($aggregator->failed > 0);
#
# Takes a test from the beginning of @testfiles and runs it.
# triggered to finish testing.
#
sub take_job {
- my ($worker) = @_;
+ my ($worker, $cv, $tests) = @_;
- my $test = shift @testfiles
+ my $test = shift @$tests
or return $cv->end;
my $display = $worker->{display};
for (1 .. $lines) {
my $result = $parser->next;
- if (defined($result) and $result->is_test) {
+ next unless defined($result);
+ if ($result->is_test) {
$tests_completed++;
status($display, "$test: [$tests_completed/??] ");
+ } elsif ($result->is_bailout) {
+ Log status($display, "$test: BAILOUT");
+ status_completed(scalar @done);
+ say "";
+ say "test $test bailed out: " . $result->explanation;
+ exit 1;
}
}
push @done, [ $test, $output ];
undef $w;
- take_job($worker);
+ take_job($worker, $cv, $tests);
}
);
}
sub cleanup {
+ my $exitcode = $?;
$_->() for our @CLEANUP;
- exit;
+ exit $exitcode;
}
# must be in a begin block because we C<exit 0> above
=head1 EXAMPLE
-To run the whole testsuite on a reasonable number of Xdummy instances (your
+To run the whole testsuite on a reasonable number of Xephyr instances (your
running X11 will not be touched), run:
./complete-run.pl
# Run tests on the second X server
./complete-run.pl -d :1
- # Run four tests in parallel on some Xdummy servers
+ # Run four tests in parallel on some Xephyr servers
./complete-run.pl -d :1,:2,:3,:4
Note that it is not necessary to specify this anymore. If omitted,
-complete-run.pl will start (num_cores * 2) Xdummy instances.
+complete-run.pl will start (num_cores * 2) Xephyr instances.
=item B<--valgrind>
Runs i3 under strace to trace system calls. The output will be available in
C<latest/strace-for-$test.log>.
+=item B<--xtrace>
+
+Runs i3 under xtrace to trace X11 requests/replies. The output will be
+available in C<latest/xtrace-for-$test.log>.
+
=item B<--coverage-testing>
-Exits i3 cleanly (instead of kill -9) to make coverage testing work properly.
+Generates a test coverage report at C<latest/i3-coverage>. Exits i3 cleanly
+during tests (instead of kill -9) to make coverage testing work properly.
=item B<--parallel>
-Number of Xdummy instances to start (if you don’t want to start num_cores * 2
+Number of Xephyr instances to start (if you don't want to start num_cores * 2
instances for some reason).
- # Run all tests on a single Xdummy instance
+ # Run all tests on a single Xephyr instance
./complete-run.pl -p 1
+
+=back