Merge pull request #3510 from orestisf1993/tree.t
Introduce cmp_tree test function
This commit is contained in:
commit
376833db45
|
@ -53,6 +53,7 @@ our @EXPORT = qw(
|
|||
events_for
|
||||
listen_for_binding
|
||||
is_net_wm_state_focused
|
||||
cmp_tree
|
||||
);
|
||||
|
||||
=head1 NAME
|
||||
|
@ -1084,6 +1085,229 @@ sub is_net_wm_state_focused {
|
|||
return 0;
|
||||
}
|
||||
|
||||
=head2 cmp_tree([ $args ])
|
||||
|
||||
Compares the tree layout before and after an operation inside a subtest.
|
||||
|
||||
The following arguments can be passed:
|
||||
|
||||
=over 4
|
||||
|
||||
=item layout_before
|
||||
|
||||
Required argument. The initial layout to be created. For example,
|
||||
'H[ V[ a* S[ b c ] d ] e ]' or 'V[a b] T[c d*]'.
|
||||
The layout will be converted to a JSON file which will be passed to i3's
|
||||
append_layout command.
|
||||
|
||||
The syntax's rules, assertions and limitations are:
|
||||
|
||||
=over 8
|
||||
|
||||
=item 1.
|
||||
|
||||
Upper case letters H, V, S, T mean horizontal, vertical, stacked and tabbed
|
||||
layout respectively. They must be followed by an opening square bracket and must
|
||||
be closed with a closing square bracket.
|
||||
Each of the non-leaf containers is marked with their corresponding letter
|
||||
followed by a number indicating the position of the container relative to other
|
||||
containers of the same type. For example, 'H[V[xxx] V[xxx] H[xxx]]' will mark
|
||||
the non-leaf containers as H1, V1, V2, H2.
|
||||
|
||||
=item 2.
|
||||
|
||||
Spaces are ignored.
|
||||
|
||||
=item 3.
|
||||
|
||||
Other alphanumeric characters mean a new window which uses the provided
|
||||
character for its class and name. Eg 'H[a b]' will open windows with classes 'a'
|
||||
and 'b' inside a horizontal split. Windows use a single character for their
|
||||
class, eg 'H[xxx]' will open 3 windows with class 'x'.
|
||||
|
||||
=item 4.
|
||||
|
||||
Asterisks after a window mean that the window must be focused after the layout
|
||||
is loaded. Currently, focusing non-leaf containers must be done manually, in the
|
||||
callback (C<cb>) function.
|
||||
|
||||
=back
|
||||
|
||||
=item cb
|
||||
|
||||
Subroutine to be called after the layout provided by C<layout_before> is created
|
||||
but before the resulting layout (C<layout_after>) is checked.
|
||||
|
||||
=item layout_after
|
||||
|
||||
Required argument. The final layout in which the tree is expected to be after
|
||||
the callback is called. Uses the same syntax with C<layout_before>.
|
||||
For non-leaf containers, their layout (horizontal, vertical, stacked, tabbed)
|
||||
is compared with the corresponding letter (H, V, S, T).
|
||||
For leaf containers, their name is compared with the provided alphanumeric.
|
||||
|
||||
=item ws
|
||||
|
||||
The workspace in which the layout will be created. Will switch focus to it. If
|
||||
not provided, a new one is created.
|
||||
|
||||
=item msg
|
||||
|
||||
Message to prepend to the subtest's name. If not empty, it will be followed by ': '.
|
||||
|
||||
=item dont_kill
|
||||
|
||||
By default, all windows are killed before the C<layout_before> layout is loaded.
|
||||
Set to 1 to avoid this.
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
sub cmp_tree {
|
||||
local $Test::Builder::Level = $Test::Builder::Level + 1;
|
||||
|
||||
my %args = @_;
|
||||
my $ws = $args{ws};
|
||||
if (defined($ws)) {
|
||||
cmd "workspace $ws";
|
||||
} else {
|
||||
$ws = fresh_workspace;
|
||||
}
|
||||
my $msg = '';
|
||||
if ($args{msg}) {
|
||||
$msg = $args{msg} . ': ';
|
||||
}
|
||||
die unless $args{layout_before};
|
||||
die unless $args{layout_after};
|
||||
|
||||
kill_all_windows unless $args{dont_kill};
|
||||
my @windows = create_layout($args{layout_before});
|
||||
Test::More::subtest $msg . $args{layout_before} . ' -> ' . $args{layout_after} => sub {
|
||||
$args{cb}->(\@windows) if $args{cb};
|
||||
verify_layout($args{layout_after}, $ws);
|
||||
};
|
||||
|
||||
return @windows;
|
||||
}
|
||||
|
||||
sub create_layout {
|
||||
my $layout = shift;
|
||||
|
||||
my $focus;
|
||||
my @windows = ();
|
||||
my $r = '';
|
||||
my $depth = 0;
|
||||
my %layout_counts = (H => 0, V => 0, S => 0, T => 0);
|
||||
|
||||
foreach my $char (split('', $layout)) {
|
||||
if ($char eq 'H') {
|
||||
$r = $r . '{"layout": "splith",';
|
||||
$r = $r . '"marks": ["H' . ++$layout_counts{H} . '"],';
|
||||
} elsif ($char eq 'V') {
|
||||
$r = $r . '{"layout": "splitv",';
|
||||
$r = $r . '"marks": ["V' . ++$layout_counts{V} . '"],';
|
||||
} elsif ($char eq 'S') {
|
||||
$r = $r . '{"layout": "stacked",';
|
||||
$r = $r . '"marks": ["S' . ++$layout_counts{S} . '"],';
|
||||
} elsif ($char eq 'T') {
|
||||
$r = $r . '{"layout": "tabbed",';
|
||||
$r = $r . '"marks": ["T' . ++$layout_counts{T} . '"],';
|
||||
} elsif ($char eq '[') {
|
||||
$depth++;
|
||||
$r = $r . '"nodes": [';
|
||||
} elsif ($char eq ']') {
|
||||
# End of nodes array: delete trailing comma.
|
||||
chop $r;
|
||||
# When we are at depth 0 we need to split using newlines, making
|
||||
# multiple "JSON texts".
|
||||
$depth--;
|
||||
$r = $r . ']}' . ($depth == 0 ? "\n" : ',');
|
||||
} elsif ($char eq ' ') {
|
||||
} elsif ($char eq '*') {
|
||||
$focus = $windows[$#windows];
|
||||
} elsif ($char =~ /[[:alnum:]]/) {
|
||||
push @windows, $char;
|
||||
|
||||
$r = $r . '{"swallows": [{';
|
||||
$r = $r . '"class": "^' . "$char" . '$"';
|
||||
$r = $r . '}]},';
|
||||
} else {
|
||||
die "Could not understand $char";
|
||||
}
|
||||
}
|
||||
|
||||
die "Invalid layout, depth is $depth > 0" unless $depth == 0;
|
||||
|
||||
Test::More::diag($r);
|
||||
my ($fh, $tmpfile) = tempfile("layout-XXXXXX", UNLINK => 1);
|
||||
print $fh "$r\n";
|
||||
close($fh);
|
||||
|
||||
my $return = cmd "append_layout $tmpfile";
|
||||
die 'Could not parse layout json file' unless $return->[0]->{success};
|
||||
|
||||
my @result_windows;
|
||||
push @result_windows, open_window(wm_class => "$_", name => "$_") foreach @windows;
|
||||
cmd '[class=' . $focus . '] focus' if $focus;
|
||||
|
||||
return @result_windows;
|
||||
}
|
||||
|
||||
sub verify_layout {
|
||||
my ($layout, $ws) = @_;
|
||||
|
||||
my $nodes = get_ws_content($ws);
|
||||
my %counters;
|
||||
my $depth = 0;
|
||||
my $node;
|
||||
|
||||
foreach my $char (split('', $layout)) {
|
||||
my $node_name;
|
||||
my $node_layout;
|
||||
if ($char eq 'H') {
|
||||
$node_layout = 'splith';
|
||||
} elsif ($char eq 'V') {
|
||||
$node_layout = 'splitv';
|
||||
} elsif ($char eq 'S') {
|
||||
$node_layout = 'stacked';
|
||||
} elsif ($char eq 'T') {
|
||||
$node_layout = 'tabbed';
|
||||
} elsif ($char eq '[') {
|
||||
$depth++;
|
||||
delete $counters{$depth};
|
||||
} elsif ($char eq ']') {
|
||||
$depth--;
|
||||
} elsif ($char eq ' ') {
|
||||
} elsif ($char eq '*') {
|
||||
$tester->is_eq($node->{focused}, 1, 'Correct node focused');
|
||||
} elsif ($char =~ /[[:alnum:]]/) {
|
||||
$node_name = $char;
|
||||
} else {
|
||||
die "Could not understand $char";
|
||||
}
|
||||
|
||||
if ($node_layout || $node_name) {
|
||||
if (exists($counters{$depth})) {
|
||||
$counters{$depth} = $counters{$depth} + 1;
|
||||
} else {
|
||||
$counters{$depth} = 0;
|
||||
}
|
||||
|
||||
$node = $nodes->[$counters{0}];
|
||||
for my $i (1 .. $depth) {
|
||||
$node = $node->{nodes}->[$counters{$i}];
|
||||
}
|
||||
|
||||
if ($node_layout) {
|
||||
$tester->is_eq($node->{layout}, $node_layout, "Layouts match in depth $depth, node number " . $counters{$depth});
|
||||
} else {
|
||||
$tester->is_eq($node->{name}, $node_name, "Names match in depth $depth, node number " . $counters{$depth});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
=head1 AUTHOR
|
||||
|
||||
|
|
|
@ -0,0 +1,94 @@
|
|||
#!perl
|
||||
# vim:ts=4:sw=4:expandtab
|
||||
#
|
||||
# Please read the following documents before working on tests:
|
||||
# • https://build.i3wm.org/docs/testsuite.html
|
||||
# (or docs/testsuite)
|
||||
#
|
||||
# • https://build.i3wm.org/docs/lib-i3test.html
|
||||
# (alternatively: perldoc ./testcases/lib/i3test.pm)
|
||||
#
|
||||
# • https://build.i3wm.org/docs/ipc.html
|
||||
# (or docs/ipc)
|
||||
#
|
||||
# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
|
||||
# (unless you are already familiar with Perl)
|
||||
#
|
||||
# Contains various tests that use the cmp_tree subroutine.
|
||||
# Ticket: #3503
|
||||
use i3test;
|
||||
|
||||
sub sanity_check {
|
||||
local $Test::Builder::Level = $Test::Builder::Level + 1;
|
||||
|
||||
my ($layout, $focus_idx) = @_;
|
||||
my @windows = cmp_tree(
|
||||
msg => 'Sanity check',
|
||||
layout_before => $layout,
|
||||
layout_after => $layout);
|
||||
is($x->input_focus, $windows[$focus_idx]->id, 'Correct window focused') if $focus_idx >= 0;
|
||||
}
|
||||
|
||||
sanity_check('H[ V[ a* V[ b c ] d ] e ]', 0);
|
||||
sanity_check('H[ a b c d* ]', 3);
|
||||
sanity_check('V[a b] V[c d*]', 3);
|
||||
sanity_check('T[a b] S[c*]', 2);
|
||||
|
||||
cmp_tree(
|
||||
msg => 'Simple focus test',
|
||||
layout_before => 'H[a b] V[c* d]',
|
||||
layout_after => 'H[a* b] V[c d]',
|
||||
cb => sub {
|
||||
cmd '[class=a] focus';
|
||||
});
|
||||
|
||||
cmp_tree(
|
||||
msg => 'Simple move test',
|
||||
layout_before => 'H[a b] V[c* d]',
|
||||
layout_after => 'H[a b] V[d c*]',
|
||||
cb => sub {
|
||||
cmd 'move down';
|
||||
});
|
||||
|
||||
cmp_tree(
|
||||
msg => 'Move from horizontal to vertical',
|
||||
layout_before => 'H[a b] V[c d*]',
|
||||
layout_after => 'H[b] V[c d a*]',
|
||||
cb => sub {
|
||||
cmd '[class=a] focus';
|
||||
cmd 'move right, move right';
|
||||
});
|
||||
|
||||
cmp_tree(
|
||||
msg => 'Move unfocused non-leaf container',
|
||||
layout_before => 'S[a b] V[c d* T[e f g]]',
|
||||
layout_after => 'S[a T[e f g] b] V[c d*]',
|
||||
cb => sub {
|
||||
cmd '[con_mark=T1] move up, move up, move left, move up';
|
||||
});
|
||||
|
||||
cmp_tree(
|
||||
msg => 'Simple swap test',
|
||||
layout_before => 'H[a b] V[c d*]',
|
||||
layout_after => 'H[a d*] V[c b]',
|
||||
cb => sub {
|
||||
cmd '[class=b] swap with id ' . $_[0][3]->{id};
|
||||
});
|
||||
|
||||
cmp_tree(
|
||||
msg => 'Swap non-leaf containers',
|
||||
layout_before => 'S[a b] V[c d*]',
|
||||
layout_after => 'V[c d*] S[a b]',
|
||||
cb => sub {
|
||||
cmd '[con_mark=S1] swap with mark V1';
|
||||
});
|
||||
|
||||
cmp_tree(
|
||||
msg => 'Swap nested non-leaf containers',
|
||||
layout_before => 'S[a b] V[c d* T[e f g]]',
|
||||
layout_after => 'T[e f g] V[c d* S[a b]]',
|
||||
cb => sub {
|
||||
cmd '[con_mark=S1] swap with mark T1';
|
||||
});
|
||||
|
||||
done_testing;
|
Loading…
Reference in New Issue