From: Michael Stapelberg Date: Mon, 9 Apr 2012 12:27:33 +0000 (+0200) Subject: Implement fake-outputs option (cmdline, cfg) for multi-monitor testing X-Git-Tag: 4.2~23 X-Git-Url: https://git.sur5r.net/?a=commitdiff_plain;h=0f10ccdf1238dcd03a2fc52fe80e9c33dc17f133;p=i3%2Fi3 Implement fake-outputs option (cmdline, cfg) for multi-monitor testing This kills the dependency on xdmx and makes the testsuite simpler and more flexible (in the output sizes / configurations). --- diff --git a/include/all.h b/include/all.h index 648b0e0a..e550b08b 100644 --- a/include/all.h +++ b/include/all.h @@ -75,5 +75,6 @@ #include "scratchpad.h" #include "commands.h" #include "commands_parser.h" +#include "fake_outputs.h" #endif diff --git a/include/config.h b/include/config.h index e959a2dc..310f8b02 100644 --- a/include/config.h +++ b/include/config.h @@ -134,6 +134,9 @@ struct Config { * is fetched once and never updated. */ bool force_xinerama; + /** Overwrites output detection (for testing), see src/fake_outputs.c */ + char *fake_outputs; + /** Automatic workspace back and forth switching. If this is set, a * switch to the currently active workspace will switch to the * previously focused one instead, making it possible to fast toggle diff --git a/include/fake_outputs.h b/include/fake_outputs.h new file mode 100644 index 00000000..adb10a0d --- /dev/null +++ b/include/fake_outputs.h @@ -0,0 +1,23 @@ +/* + * vim:ts=4:sw=4:expandtab + * + * i3 - an improved dynamic tiling window manager + * © 2009-2012 Michael Stapelberg and contributors (see also: LICENSE) + * + * Faking outputs is useful in pathological situations (like network X servers + * which don’t support multi-monitor in a useful way) and for our testsuite. + * + */ +#ifndef _FAKE_OUTPUTS_H +#define _FAKE_OUTPUTS_H + +/** + * Creates outputs according to the given specification. + * The specification must be in the format wxh+x+y, for example 1024x768+0+0, + * with multiple outputs separated by commas: + * 1900x1200+0+0,1280x1024+1900+0 + * + */ +void fake_outputs_init(const char *output_spec); + +#endif diff --git a/src/cfgparse.l b/src/cfgparse.l index 81847ed7..161ddfdc 100644 --- a/src/cfgparse.l +++ b/src/cfgparse.l @@ -203,6 +203,8 @@ none { return TOK_NONE; } focus_follows_mouse { return TOKFOCUSFOLLOWSMOUSE; } force_focus_wrapping { return TOK_FORCE_FOCUS_WRAPPING; } force_xinerama { return TOK_FORCE_XINERAMA; } +fake_outputs { WS_STRING; return TOK_FAKE_OUTPUTS; } +fake-outputs { WS_STRING; return TOK_FAKE_OUTPUTS; } workspace_auto_back_and_forth { return TOK_WORKSPACE_AUTO_BAF; } workspace_bar { return TOKWORKSPACEBAR; } popup_during_fullscreen { return TOK_POPUP_DURING_FULLSCREEN; } diff --git a/src/cfgparse.y b/src/cfgparse.y index 7aeb0a7e..ab8be57c 100644 --- a/src/cfgparse.y +++ b/src/cfgparse.y @@ -693,6 +693,7 @@ void parse_file(const char *f) { %token TOKFOCUSFOLLOWSMOUSE "focus_follows_mouse" %token TOK_FORCE_FOCUS_WRAPPING "force_focus_wrapping" %token TOK_FORCE_XINERAMA "force_xinerama" +%token TOK_FAKE_OUTPUTS "fake_outputs" %token TOK_WORKSPACE_AUTO_BAF "workspace_auto_back_and_forth" %token TOKWORKSPACEBAR "workspace_bar" %token TOK_DEFAULT "default" @@ -790,6 +791,7 @@ line: | focus_follows_mouse | force_focus_wrapping | force_xinerama + | fake_outputs | workspace_back_and_forth | workspace_bar | workspace @@ -1451,6 +1453,14 @@ force_xinerama: } ; +fake_outputs: + TOK_FAKE_OUTPUTS STR + { + DLOG("fake outputs = %s\n", $2); + config.fake_outputs = $2; + } + ; + workspace_back_and_forth: TOK_WORKSPACE_AUTO_BAF bool { diff --git a/src/fake_outputs.c b/src/fake_outputs.c new file mode 100644 index 00000000..512a808f --- /dev/null +++ b/src/fake_outputs.c @@ -0,0 +1,75 @@ +/* + * vim:ts=4:sw=4:expandtab + * + * i3 - an improved dynamic tiling window manager + * © 2009-2012 Michael Stapelberg and contributors (see also: LICENSE) + * + * Faking outputs is useful in pathological situations (like network X servers + * which don’t support multi-monitor in a useful way) and for our testsuite. + * + */ +#include "all.h" + +static int num_screens; + +/* + * Looks in outputs for the Output whose start coordinates are x, y + * + */ +static Output *get_screen_at(int x, int y) { + Output *output; + TAILQ_FOREACH(output, &outputs, outputs) + if (output->rect.x == x && output->rect.y == y) + return output; + + return NULL; +} + +/* + * Creates outputs according to the given specification. + * The specification must be in the format wxh+x+y, for example 1024x768+0+0, + * with multiple outputs separated by commas: + * 1900x1200+0+0,1280x1024+1900+0 + * + */ +void fake_outputs_init(const char *output_spec) { + char useless_buffer[1024]; + const char *walk = output_spec; + unsigned int x, y, width, height; + while (sscanf(walk, "%ux%u+%u+%u", &width, &height, &x, &y) == 4) { + DLOG("Parsed output as width = %u, height = %u at (%u, %u)\n", + width, height, x, y); + Output *new_output = get_screen_at(x, y); + if (new_output != NULL) { + DLOG("Re-used old output %p\n", new_output); + /* This screen already exists. We use the littlest screen so that the user + can always see the complete workspace */ + new_output->rect.width = min(new_output->rect.width, width); + new_output->rect.height = min(new_output->rect.height, height); + } else { + new_output = scalloc(sizeof(Output)); + sasprintf(&(new_output->name), "fake-%d", num_screens); + DLOG("Created new fake output %s (%p)\n", new_output->name, new_output); + new_output->active = true; + new_output->rect.x = x; + new_output->rect.y = y; + new_output->rect.width = width; + new_output->rect.height = height; + /* We always treat the screen at 0x0 as the primary screen */ + if (new_output->rect.x == 0 && new_output->rect.y == 0) + TAILQ_INSERT_HEAD(&outputs, new_output, outputs); + else TAILQ_INSERT_TAIL(&outputs, new_output, outputs); + output_init_con(new_output); + init_ws_for_output(new_output, output_get_content(new_output->con)); + num_screens++; + } + + /* Figure out how long the input was to skip it */ + walk += sprintf(useless_buffer, "%ux%u+%u+%u", width, height, x, y) + 1; + } + + if (num_screens == 0) { + ELOG("No screens found. Please fix your setup. i3 will exit now.\n"); + exit(0); + } +} diff --git a/src/main.c b/src/main.c index 0d724607..d7598468 100644 --- a/src/main.c +++ b/src/main.c @@ -252,6 +252,7 @@ int main(int argc, char *argv[]) { char *layout_path = NULL; bool delete_layout_path = false; bool force_xinerama = false; + char *fake_outputs = NULL; bool disable_signalhandler = false; static struct option long_options[] = { {"no-autostart", no_argument, 0, 'a'}, @@ -267,6 +268,8 @@ int main(int argc, char *argv[]) { {"shmlog_size", required_argument, 0, 0}, {"get-socketpath", no_argument, 0, 0}, {"get_socketpath", no_argument, 0, 0}, + {"fake_outputs", required_argument, 0, 0}, + {"fake-outputs", required_argument, 0, 0}, {0, 0, 0, 0} }; int option_index = 0, opt; @@ -368,6 +371,11 @@ int main(int argc, char *argv[]) { layout_path = sstrdup(optarg); delete_layout_path = true; break; + } else if (strcmp(long_options[option_index].name, "fake-outputs") == 0 || + strcmp(long_options[option_index].name, "fake_outputs") == 0) { + LOG("Initializing fake outputs: %s\n", optarg); + fake_outputs = sstrdup(optarg); + break; } /* fall-through */ default: @@ -656,10 +664,18 @@ int main(int argc, char *argv[]) { free(greply); - /* Force Xinerama (for drivers which don't support RandR yet, esp. the - * nVidia binary graphics driver), when specified either in the config - * file or on command-line */ - if (force_xinerama || config.force_xinerama) { + /* Setup fake outputs for testing */ + if (fake_outputs == NULL && config.fake_outputs != NULL) + fake_outputs = config.fake_outputs; + + if (fake_outputs != NULL) { + fake_outputs_init(fake_outputs); + FREE(fake_outputs); + config.fake_outputs = NULL; + } else if (force_xinerama || config.force_xinerama) { + /* Force Xinerama (for drivers which don't support RandR yet, esp. the + * nVidia binary graphics driver), when specified either in the config + * file or on command-line */ xinerama_init(); } else { DLOG("Checking for XRandR...\n"); diff --git a/testcases/complete-run.pl b/testcases/complete-run.pl index 15def35c..020e2f90 100755 --- a/testcases/complete-run.pl +++ b/testcases/complete-run.pl @@ -72,16 +72,9 @@ my @testfiles = @ARGV; my $numtests = scalar @testfiles; -# When the user specifies displays, we don’t run multi-monitor tests at all -# (because we don’t know which displaynumber is the X-Server with multiple -# monitors). -my $multidpy = undef; - # No displays specified, let’s start some Xdummy instances. if (@displays == 0) { - my $dpyref; - ($dpyref, $multidpy) = start_xdummy($parallel, $numtests); - @displays = @$dpyref; + @displays = start_xdummy($parallel, $numtests); } # 1: create an output directory for this test-run @@ -111,16 +104,6 @@ for my $display (@displays) { } } -my @multi_worker; -if (defined($multidpy)) { - my $x = X11::XCB::Connection->new(display => $multidpy); - if ($x->has_error) { - die "Could not connect to multi-monitor display $multidpy\n"; - } else { - push @multi_worker, worker($multidpy, $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'); @@ -149,30 +132,21 @@ my @done; my $num = @testfiles; my $harness = TAP::Harness->new({ }); -my @single_monitor_tests = grep { m,^t/([0-9]+)-, && $1 < 500 } @testfiles; -my @multi_monitor_tests = grep { m,^t/([0-9]+)-, && $1 >= 500 } @testfiles; - my $aggregator = TAP::Parser::Aggregator->new(); $aggregator->start(); -status_init(displays => [ @displays, $multidpy ], tests => $num); +status_init(displays => \@displays, tests => $num); my $single_cv = AE::cv; -my $multi_cv = AE::cv; # We start tests concurrently: For each display, one test gets started. Every # test starts another test after completing. for (@single_worker) { $single_cv->begin; - take_job($_, $single_cv, \@single_monitor_tests); -} -for (@multi_worker) { - $multi_cv->begin; - take_job($_, $multi_cv, \@multi_monitor_tests); + take_job($_, $single_cv, \@testfiles); } $single_cv->recv; -$multi_cv->recv; $aggregator->stop(); diff --git a/testcases/lib/StartXDummy.pm b/testcases/lib/StartXDummy.pm index c854a62e..5c739fca 100644 --- a/testcases/lib/StartXDummy.pm +++ b/testcases/lib/StartXDummy.pm @@ -75,17 +75,12 @@ sub start_xdummy { # If /proc/cpuinfo does not exist, we fall back to 2 cores. $num_cores ||= 2; - # If unset, we use num_cores * 2, plus two extra xdummys to combine to a - # multi-monitor setup using Xdmx. - $parallel ||= ($num_cores * 2) + 2; + # If unset, we use num_cores * 2. + $parallel ||= ($num_cores * 2); # If we are running a small number of tests, don’t over-parallelize. $parallel = $numtests if $numtests < $parallel; - # Ensure we have at least 1 X-Server plus two X-Servers for multi-monitor - # tests. - $parallel = 3 if $parallel < 3; - # First get the last used display number, then increment it by one. # Effectively falls back to 1 if no X server is running. my ($displaynum) = map { /(\d+)$/ } reverse sort glob($x_socketpath . '*'); @@ -107,20 +102,7 @@ sub start_xdummy { wait_for_x(\@sockets_waiting); - # Now combine the last two displays to a multi-monitor display using Xdmx - my $first = pop @displays; - my $second = pop @displays; - - # make sure this display isn’t in use yet - $displaynum++ while -e ($x_socketpath . $displaynum); - say 'starting xdmx on display :' . $displaynum; - - my $multidpy = ":$displaynum"; - my $socket = fork_xserver($displaynum, 'Xdmx', '+xinerama', '-xinput', - 'local', '-display', $first, '-display', $second, '-ac', $multidpy); - wait_for_x([ $socket ]); - - return \@displays, $multidpy; + return @displays; } 1 diff --git a/testcases/lib/i3test.pm b/testcases/lib/i3test.pm index f7be1b66..4c41a7f2 100644 --- a/testcases/lib/i3test.pm +++ b/testcases/lib/i3test.pm @@ -288,7 +288,7 @@ sub fresh_workspace { if (exists($args{output})) { my $i3 = i3(get_socket_path()); my $tree = $i3->get_tree->recv; - my $output = first { $_->{name} eq "xinerama-$args{output}" } + my $output = first { $_->{name} eq "fake-$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. diff --git a/testcases/t/500-multi-monitor.t b/testcases/t/500-multi-monitor.t index 1f42f0bb..5c341d63 100644 --- a/testcases/t/500-multi-monitor.t +++ b/testcases/t/500-multi-monitor.t @@ -4,7 +4,15 @@ # Tests that the provided X-Server to the t/5??-*.t tests is actually providing # multiple monitors. # -use i3test; +use i3test i3_autostart => 0; + +my $config = <get_tree->recv; my @outputs = map { $_->{name} } @{$tree->{nodes}}; -is_deeply(\@outputs, [ '__i3', 'xinerama-0', 'xinerama-1' ], +is_deeply(\@outputs, [ '__i3', 'fake-0', 'fake-1' ], 'multi-monitor outputs ok'); +exit_gracefully($pid); + done_testing; diff --git a/testcases/t/501-scratchpad.t b/testcases/t/501-scratchpad.t index ef57ffad..97a64a4a 100644 --- a/testcases/t/501-scratchpad.t +++ b/testcases/t/501-scratchpad.t @@ -5,7 +5,15 @@ # ticket #596, bug present until up to commit # 89dded044b4fffe78f9d70778748fabb7ac533e9. # -use i3test; +use i3test i3_autostart => 0; + +my $config = < 0); verify_scratchpad_switch($first, $second); +exit_gracefully($pid); + done_testing; diff --git a/testcases/t/502-focus-output.t b/testcases/t/502-focus-output.t index 139b670a..01e98723 100644 --- a/testcases/t/502-focus-output.t +++ b/testcases/t/502-focus-output.t @@ -3,9 +3,17 @@ # # Verifies the 'focus output' command works properly. -use i3test; +use i3test i3_autostart => 0; use List::Util qw(first); +my $config = <{name}; } -is(focused_output, 'xinerama-0', 'focus on first output'); +is(focused_output, 'fake-0', 'focus on first output'); cmd 'focus output right'; -is(focused_output, 'xinerama-1', 'focus on second output'); +is(focused_output, 'fake-1', 'focus on second output'); # focus should wrap when we focus to the right again. cmd 'focus output right'; -is(focused_output, 'xinerama-0', 'focus on first output again'); +is(focused_output, 'fake-0', 'focus on first output again'); cmd 'focus output left'; -is(focused_output, 'xinerama-1', 'focus back on second output'); +is(focused_output, 'fake-1', 'focus back on second output'); cmd 'focus output left'; -is(focused_output, 'xinerama-0', 'focus on first output again'); +is(focused_output, 'fake-0', 'focus on first output again'); cmd 'focus output up'; -is(focused_output, 'xinerama-0', 'focus still on first output'); +is(focused_output, 'fake-0', 'focus still on first output'); cmd 'focus output down'; -is(focused_output, 'xinerama-0', 'focus still on first output'); +is(focused_output, 'fake-0', 'focus still on first output'); + +cmd 'focus output fake-1'; +is(focused_output, 'fake-1', 'focus on second output'); -cmd 'focus output xinerama-1'; -is(focused_output, 'xinerama-1', 'focus on second output'); +cmd 'focus output fake-0'; +is(focused_output, 'fake-0', 'focus on first output'); -cmd 'focus output xinerama-0'; -is(focused_output, 'xinerama-0', 'focus on first output'); +exit_gracefully($pid); done_testing; diff --git a/testcases/t/503-workspace.t b/testcases/t/503-workspace.t index 7122cb34..19d9f95a 100644 --- a/testcases/t/503-workspace.t +++ b/testcases/t/503-workspace.t @@ -4,7 +4,15 @@ # Tests whether 'workspace next_on_output' and the like work correctly. # use List::Util qw(first); -use i3test; +use i3test i3_autostart => 0; + +my $config = < to [output] ' command works # use List::Util qw(first); -use i3test; +use i3test i3_autostart => 0; # TODO: # introduce 'move workspace 3 to output ' with synonym 'move workspace 3 to ' +my $config = <get_tree->recv; my @outputs = @{$tree->{nodes}}; - my $xinerama0 = first { $_->{name} eq 'xinerama-0' } @outputs; - my $xinerama0_content = first { $_->{type} == 2 } @{$xinerama0->{nodes}}; + my $fake0 = first { $_->{name} eq 'fake-0' } @outputs; + my $fake0_content = first { $_->{type} == 2 } @{$fake0->{nodes}}; - my $xinerama1 = first { $_->{name} eq 'xinerama-1' } @outputs; - my $xinerama1_content = first { $_->{type} == 2 } @{$xinerama1->{nodes}}; + my $fake1 = first { $_->{name} eq 'fake-1' } @outputs; + my $fake1_content = first { $_->{type} == 2 } @{$fake1->{nodes}}; - my @xinerama0_workspaces = map { $_->{name} } @{$xinerama0_content->{nodes}}; - my @xinerama1_workspaces = map { $_->{name} } @{$xinerama1_content->{nodes}}; + my @fake0_workspaces = map { $_->{name} } @{$fake0_content->{nodes}}; + my @fake1_workspaces = map { $_->{name} } @{$fake1_content->{nodes}}; - return \@xinerama0_workspaces, \@xinerama1_workspaces; + return \@fake0_workspaces, \@fake1_workspaces; } ($x0, $x1) = workspaces_per_screen(); -ok('5' ~~ @$x1, 'workspace 5 now on xinerama-1'); +ok('5' ~~ @$x1, 'workspace 5 now on fake-1'); ################################################################################ # Verify that a new workspace will be created when moving the last workspace. ################################################################################ -is_deeply($x0, [ '1' ], 'only workspace 1 remaining on xinerama-0'); +is_deeply($x0, [ '1' ], 'only workspace 1 remaining on fake-0'); cmd 'workspace 1'; -cmd 'move workspace to output xinerama-1'; +cmd 'move workspace to output fake-1'; ($x0, $x1) = workspaces_per_screen(); -ok('1' ~~ @$x1, 'workspace 1 now on xinerama-1'); -is_deeply($x0, [ '3' ], 'workspace 2 created on xinerama-0'); +ok('1' ~~ @$x1, 'workspace 1 now on fake-1'); +is_deeply($x0, [ '3' ], 'workspace 2 created on fake-0'); ################################################################################ # Verify that 'move workspace to output ' works @@ -87,7 +95,7 @@ cmd 'workspace 5'; cmd 'move workspace to output left'; ($x0, $x1) = workspaces_per_screen(); -ok('5' ~~ @$x0, 'workspace 5 back on xinerama-0'); +ok('5' ~~ @$x0, 'workspace 5 back on fake-0'); ################################################################################ # Verify that coordinates of floating windows are fixed correctly when moving a @@ -108,4 +116,6 @@ is($old_rect->{y}, $new_rect->{y}, 'y coordinate unchanged'); is($old_rect->{width}, $new_rect->{width}, 'width unchanged'); is($old_rect->{height}, $new_rect->{height}, 'height unchanged'); +exit_gracefully($pid); + done_testing;