]> git.sur5r.net Git - i3/i3/blobdiff - testcases/lib/i3test.pm
Refactor the interface of commands.c
[i3/i3] / testcases / lib / i3test.pm
index 8437a534a1971250946119c9d133ccbf50099c54..979c60ac73815ca0629a106c697c9b1d779ffa55 100644 (file)
@@ -16,6 +16,10 @@ use SocketActivation;
 
 use v5.10;
 
+# preload
+use Test::More ();
+use Data::Dumper ();
+
 use Exporter ();
 our @EXPORT = qw(
     get_workspace_names
@@ -56,11 +60,54 @@ BEGIN {
     }
 }
 
+my $i3_pid;
+my $i3_autostart;
+
+END {
+
+    # testcases which start i3 manually should always call exit_gracefully
+    # on their own. Let’s see, whether they really did.
+    if (! $i3_autostart) {
+        return unless $i3_pid;
+
+        $tester->ok(undef, 'testcase called exit_gracefully()');
+    }
+
+    # don't trigger SIGCHLD handler
+    local $SIG{CHLD};
+
+    # From perldoc -v '$?':
+    # Inside an "END" subroutine $? contains the value
+    # that is going to be given to "exit()".
+    #
+    # Since waitpid sets $?, we need to localize it,
+    # otherwise TAP would be misinterpreted our return status
+    local $?;
+
+    # When measuring code coverage, try to exit i3 cleanly (otherwise, .gcda
+    # files are not written)
+    if ($ENV{COVERAGE} || $ENV{VALGRIND}) {
+        exit_gracefully($i3_pid, "/tmp/nested-$ENV{DISPLAY}");
+
+    } else {
+        kill(9, $i3_pid)
+            or $tester->BAIL_OUT("could not kill i3");
+
+        waitpid $i3_pid, 0;
+    }
+}
+
 sub import {
-    my $class = shift;
+    my ($class, %args) = @_;
     my $pkg = caller;
 
-    my $test_more_args = @_ ? "qw(@_)" : "";
+    $i3_autostart = delete($args{i3_autostart}) // 1;
+
+    my $cv = launch_with_config('-default', dont_block => 1)
+        if $i3_autostart;
+
+    my $test_more_args = '';
+    $test_more_args = join(' ', 'qw(', %args, ')') if keys %args;
     local $@;
     eval << "__";
 package $pkg;
@@ -69,12 +116,14 @@ use Data::Dumper;
 use AnyEvent::I3;
 use Time::HiRes qw(sleep);
 __
-    $tester->bail_out("$@") if $@;
+    $tester->BAIL_OUT("$@") if $@;
     feature->import(":5.10");
     strict->import;
     warnings->import;
 
     $x ||= i3test::X11->new;
+    $cv->recv if $i3_autostart;
+
     @_ = ($class);
     goto \&Exporter::import;
 }
@@ -198,7 +247,7 @@ sub open_empty_con {
     my ($i3) = @_;
 
     my $reply = $i3->command('open')->recv;
-    return $reply->{id};
+    return $reply->[0]->{id};
 }
 
 sub get_workspace_names {
@@ -207,6 +256,7 @@ sub get_workspace_names {
     my @outputs = @{$tree->{nodes}};
     my @cons;
     for my $output (@outputs) {
+        next if $output->{name} eq '__i3';
         # get the first CT_CON of each output
         my $content = first { $_->{type} == 2 } @{$output->{nodes}};
         @cons = (@cons, @{$content->{nodes}});
@@ -221,7 +271,34 @@ sub get_unused_workspace {
     $tmp
 }
 
+=head2 fresh_workspace(...)
+
+Switches to an unused workspace and returns the name of that workspace.
+
+Optionally switches to the specified output first.
+
+    my $ws = fresh_workspace;
+
+    # Get a fresh workspace on the second output.
+    my $ws = fresh_workspace(output => 1);
+
+=cut
 sub fresh_workspace {
+    my %args = @_;
+    if (exists($args{output})) {
+        my $i3 = i3(get_socket_path());
+        my $tree = $i3->get_tree->recv;
+        my $output = first { $_->{name} eq "xinerama-$args{output}" }
+                        @{$tree->{nodes}};
+        die "BUG: Could not find output $args{output}" unless defined($output);
+        # Get the focused workspace on that output and switch to it.
+        my $content = first { $_->{type} == 2 } @{$output->{nodes}};
+        my $focused = $content->{focus}->[0];
+        my $workspace = first { $_->{id} == $focused } @{$content->{nodes}};
+        $workspace = $workspace->{name};
+        cmd("workspace $workspace");
+    }
+
     my $unused = get_unused_workspace;
     cmd("workspace $unused");
     $unused
@@ -287,11 +364,11 @@ sub get_dock_clients {
                                 @{$output->{nodes}});
         } elsif ($which eq 'top') {
             my $first = first { $_->{type} == 5 } @{$output->{nodes}};
-            @docked = (@docked, @{$first->{nodes}});
+            @docked = (@docked, @{$first->{nodes}}) if defined($first);
         } elsif ($which eq 'bottom') {
             my @matching = grep { $_->{type} == 5 } @{$output->{nodes}};
             my $last = $matching[-1];
-            @docked = (@docked, @{$last->{nodes}});
+            @docked = (@docked, @{$last->{nodes}}) if defined($last);
         }
     }
     return @docked;
@@ -306,17 +383,19 @@ sub workspace_exists {
     ($name ~~ @{get_workspace_names()})
 }
 
+=head2 focused_ws
+
+Returns the name of the currently focused workspace.
+
+=cut
 sub focused_ws {
     my $i3 = i3(get_socket_path());
     my $tree = $i3->get_tree->recv;
-    my @outputs = @{$tree->{nodes}};
-    my @cons;
-    for my $output (@outputs) {
-        # get the first CT_CON of each output
-        my $content = first { $_->{type} == 2 } @{$output->{nodes}};
-        my $first = first { $_->{fullscreen_mode} == 1 } @{$content->{nodes}};
-        return $first->{name}
-    }
+    my $focused = $tree->{focus}->[0];
+    my $output = first { $_->{id} == $focused } @{$tree->{nodes}};
+    my $content = first { $_->{type} == 2 } @{$output->{nodes}};
+    my $first = first { $_->{fullscreen_mode} == 1 } @{$content->{nodes}};
+    return $first->{name}
 }
 
 #
@@ -394,12 +473,16 @@ sub exit_gracefully {
     };
 
     if (!$exited) {
-        kill(9, $pid) or die "could not kill i3";
+        kill(9, $pid)
+            or $tester->BAIL_OUT("could not kill i3");
     }
 
     if ($socketpath =~ m,^/tmp/i3-test-socket-,) {
         unlink($socketpath);
     }
+
+    waitpid $pid, 0;
+    undef $i3_pid;
 }
 
 # Gets the socket path from the I3_SOCKET_PATH atom stored on the X11 root window
@@ -415,6 +498,9 @@ sub get_socket_path {
     my $cookie = $x->get_property(0, $x->get_root_window(), $atom->id, GET_PROPERTY_TYPE_ANY, 0, 256);
     my $reply = $x->get_property_reply($cookie->{sequence});
     my $socketpath = $reply->{value};
+    if ($socketpath eq "/tmp/nested-$ENV{DISPLAY}") {
+        $socketpath .= '-activation';
+    }
     $_cached_socket_path = $socketpath;
     return $socketpath;
 }
@@ -422,26 +508,31 @@ sub get_socket_path {
 #
 # launches a new i3 process with the given string as configuration file.
 # useful for tests which test specific config file directives.
-#
-# be sure to use !NO_I3_INSTANCE! somewhere in the file to signal
-# complete-run.pl that it should not create an instance of i3
-#
 sub launch_with_config {
-    my ($config, $dont_add_socket_path) = @_;
+    my ($config, %args) = @_;
 
-    $dont_add_socket_path //= 0;
+    $tmp_socket_path = "/tmp/nested-$ENV{DISPLAY}";
 
-    if (!defined($tmp_socket_path)) {
-        $tmp_socket_path = File::Temp::tempnam('/tmp', 'i3-test-socket-');
+    $args{dont_create_temp_dir} //= 0;
+
+    my ($fh, $tmpfile) = tempfile("i3-cfg-for-$ENV{TESTNAME}-XXXXX", UNLINK => 1);
+
+    if ($config ne '-default') {
+        say $fh $config;
+    } else {
+        open(my $conf_fh, '<', './i3-test.config')
+            or $tester->BAIL_OUT("could not open default config: $!");
+        local $/;
+        say $fh scalar <$conf_fh>;
     }
 
-    my ($fh, $tmpfile) = tempfile('/tmp/i3-test-config-XXXXX', UNLINK => 1);
-    say $fh $config;
-    say $fh "ipc-socket $tmp_socket_path" unless $dont_add_socket_path;
+    say $fh "ipc-socket $tmp_socket_path"
+        unless $args{dont_add_socket_path};
+
     close($fh);
 
     my $cv = AnyEvent->condvar;
-    my $pid = activate_i3(
+    $i3_pid = activate_i3(
         unix_socket_path => "$tmp_socket_path-activation",
         display => $ENV{DISPLAY},
         configfile => $tmpfile,
@@ -449,16 +540,21 @@ sub launch_with_config {
         testname => $ENV{TESTNAME},
         valgrind => $ENV{VALGRIND},
         strace => $ENV{STRACE},
+        restart => $ENV{RESTART},
         cv => $cv,
+        dont_create_temp_dir => $args{dont_create_temp_dir},
     );
 
+    # force update of the cached socket path in lib/i3test
+    # as soon as i3 has started
+    $cv->cb(sub { get_socket_path(0) });
+
+    return $cv if $args{dont_block};
+
     # blockingly wait until i3 is ready
     $cv->recv;
 
-    # force update of the cached socket path in lib/i3test
-    get_socket_path(0);
-
-    return $pid;
+    return $i3_pid;
 }
 
 package i3test::X11;