-
Notifications
You must be signed in to change notification settings - Fork 14
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
automatic tests for TUI (ncurses) #251
Changes from all commits
a2d8479
12c144d
eee00b3
1b27612
1f99889
79c25ff
55a3d2b
b60f2bb
e2fa1a5
89b2eaf
7c7d193
50ba537
c660d1a
12eb453
d3f08eb
387c970
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,3 +3,6 @@ | |
/.yardoc | ||
/package/*.tar.* | ||
*.pot | ||
# screen captures from the libyui tests | ||
*.out.txt | ||
*.out.esc |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,9 @@ | ||
------------------------------------------------------------------- | ||
Tue Oct 13 14:42:52 UTC 2020 - Martin Vidner <[email protected]> | ||
|
||
- Add automatic TUI (ncurses) tests using tmux (bsc#1165388). | ||
- 4.3.5 | ||
|
||
------------------------------------------------------------------- | ||
Thu Sep 24 19:46:00 UTC 2020 - [email protected] | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
require_relative "rspec_tmux_tui" | ||
|
||
describe "Menu Item" do | ||
bug = "1177760" # https://bugzilla.suse.com/show_bug.cgi?id=1177760 | ||
around(:each) do |ex| | ||
@base = "menu_hotkeys_#{bug}" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. where is it used? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. in the screenshot filenames: |
||
@tui = YastTui.new | ||
@tui.example("MenuBar1") do | ||
ex.run | ||
end | ||
end | ||
|
||
it "has hotkeys in menu items, boo##{bug}" do | ||
@tui.await(/File.*Edit.*View/) | ||
@tui.capture_pane_to("#{@base}-1-initial") | ||
|
||
@tui.send_keys "M-V" # &View | ||
@tui.capture_pane_to("#{@base}-2-view-menu-activated") | ||
|
||
@tui.send_keys "M-N" # &Normal | ||
@tui.capture_pane_to("#{@base}-3-normal-menu-item-activated") | ||
|
||
# the label | ||
expect(@tui.capture_pane).to include("Last Event") | ||
# the output | ||
expect(@tui.capture_pane).to include("view_normal") | ||
|
||
@tui.send_keys "M-Q" # &Quit | ||
end | ||
end |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,118 @@ | ||
require "shellwords" | ||
|
||
# Drive interactive TUI (textual user interface) with tmux. | ||
# https://github.com/tmux/tmux | ||
class TmuxTui | ||
class Error < RuntimeError | ||
end | ||
|
||
def self.new_session(*args) | ||
new(*args) | ||
end | ||
|
||
attr_reader :session_name | ||
|
||
# @param session_name [String] | ||
def initialize(session_name: nil) | ||
@session_name = session_name || new_session_name | ||
end | ||
|
||
# @param shell_command [String] | ||
# @param xy [(Integer, Integer)] | ||
# @param detach [Boolean] | ||
# @param remain_on_exit [Boolean] useful if shell_command may unexpectedly | ||
# fail quickly. In that case we can still capture the pane | ||
# and read the error messages. | ||
def new_session(shell_command, | ||
xy: [80, 24], detach: true, remain_on_exit: true) | ||
|
||
@shell_command = shell_command | ||
@x, @y = xy | ||
@detach = detach | ||
|
||
detach_args = @detach ? ["-d"] : [] | ||
remain_on_exit_args = if remain_on_exit | ||
["set-hook", "-g", "session-created", "set remain-on-exit on", ";"] | ||
else | ||
[] | ||
end | ||
|
||
tmux_ret = system "tmux", | ||
* remain_on_exit_args, | ||
"new-session", | ||
"-s", @session_name, | ||
"-x", @x.to_s, | ||
"-y", @y.to_s, | ||
* detach_args, | ||
"sh", "-c", shell_command | ||
|
||
return tmux_ret unless block_given? | ||
|
||
yield | ||
ensure_no_session | ||
end | ||
|
||
def new_session_name | ||
"tmux-tui-#{rand 10000}" | ||
end | ||
|
||
# @return [String] | ||
def capture_pane(color: false) | ||
esc = color ? "-e" : "" | ||
# FIXME: failure of the command? | ||
`tmux capture-pane -t #{session_name.shellescape} -p #{esc}` | ||
end | ||
|
||
def capture_pane_to(filename) | ||
txt = capture_pane(color: false) | ||
esc = capture_pane(color: true) | ||
File.write("#{filename}.out.txt", txt) | ||
File.write("#{filename}.out.esc", esc) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd prefer There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Unfortunately, as I understand it, there is no such thing as a raw terminal dump, unless we're talking about the linux console devices ( |
||
end | ||
|
||
def await(pattern) | ||
sleeps = [0.1, 0.2, 0.2, 0.5, 1, 2, 2, 5] | ||
txt = "" | ||
sleeps.each do |sl| | ||
txt = capture_pane | ||
case txt | ||
when pattern | ||
sleep 0.1 # draw the rest of the screen | ||
return nil | ||
else | ||
sleep sl | ||
end | ||
end | ||
raise Error, "Timed out waiting for #{pattern.inspect}. Seen:\n#{txt}" | ||
end | ||
|
||
# @param keys [String] "C-X" for Ctrl-X, "M-X" for Alt-X, think "Meta"; | ||
# for details see: | ||
# man tmux | less +/"^KEY BINDINGS" | ||
def send_keys(keys) | ||
system "tmux", "send-keys", "-t", session_name, keys | ||
end | ||
|
||
def has_session? # rubocop:disable Style/PredicateName | ||
# the method name mimics the tmux command | ||
system "tmux", "has-session", "-t", session_name | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Um, because of that |
||
end | ||
|
||
def kill_session | ||
system "tmux", "kill-session", "-t", session_name | ||
end | ||
|
||
def ensure_no_session | ||
kill_session if has_session? | ||
end | ||
end | ||
|
||
class YastTui < TmuxTui | ||
def example(basename, &block) | ||
basename += ".rb" unless basename.end_with? ".rb" | ||
yast_ncurses = "#{__dir__}/yast_ncurses" | ||
example_dir = "/usr/share/doc/packages/yast2-ycp-ui-bindings/examples" | ||
|
||
new_session("#{yast_ncurses} #{example_dir}/#{basename}", &block) | ||
end | ||
end |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
#! /usr/bin/env ruby | ||
|
||
require_relative "../test_helper" | ||
require "yast" | ||
|
||
if Yast.ui_component == "" | ||
Yast.ui_component = ARGV[0] || "ncurses" | ||
end | ||
|
||
module Yast | ||
class TableCellClient < Client | ||
def main | ||
Yast.import "UI" | ||
|
||
# notice that neither the ids nor the values are sorted here | ||
contents = [ | ||
Item(Id("id-zzz-1-bbb"), "name-bbb", "value-bbb"), | ||
Item(Id("id-yyy-2-ccc"), "name-ccc", "value-ccc"), | ||
Item(Id("id-xxx-3-aaa"), "name-aaa", "value-aaa"), | ||
] | ||
keep_sorting = WFM.Args()[0] == "no-sort" | ||
opts = keep_sorting ? Opt(:keepSorting, :notify) : Opt(:notify) | ||
UI.OpenDialog( | ||
VBox( | ||
Label("Table sorting test"), | ||
MinSize( | ||
25, 8, | ||
Table(Id(:table), opts, Header("Name", "Value"), contents) | ||
), | ||
Label("Enter/Double-click any item to uppercase the value"), | ||
HBox( | ||
HSquash(Label("Current Item: ")), | ||
Label(Id(:current_item), Opt(:outputField, :hstretch), "...") | ||
), | ||
PushButton(Id(:cancel), "&Close") | ||
) | ||
) | ||
|
||
if WFM.Args()[0] == "change-current-item" | ||
# test boo#1177145, wrong item is selected | ||
UI.ChangeWidget(Id(:table), :CurrentItem, "id-yyy-2-ccc") | ||
current_item_id = UI.QueryWidget(Id(:table), :CurrentItem) | ||
UI.ChangeWidget(Id(:current_item), :Value, current_item_id.inspect) | ||
end | ||
|
||
while UI.UserInput != :cancel | ||
current_item_id = UI.QueryWidget(Id(:table), :CurrentItem) | ||
UI.ChangeWidget(Id(:current_item), :Value, current_item_id.inspect) | ||
|
||
value = UI.QueryWidget(:table, Cell(current_item_id, 1)) | ||
UI.ChangeWidget(Id(:table), Cell(current_item_id, 1), value.upcase) | ||
end | ||
items = UI.QueryWidget(:table, :Items) | ||
Builtins.y2milestone("Items: %1", items) | ||
|
||
UI.CloseDialog | ||
nil | ||
end | ||
end | ||
end | ||
|
||
Yast::TableCellClient.new.main |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
require_relative "rspec_tmux_tui" | ||
|
||
describe "Table" do | ||
context "when it sorts the items," do | ||
around(:each) do |ex| | ||
yast_ncurses = "#{__dir__}/yast_ncurses" | ||
@base = "table_sort" | ||
@tui = TmuxTui.new | ||
@tui.new_session "#{yast_ncurses} #{__dir__}/#{@base}.rb change-current-item" do | ||
ex.run | ||
end | ||
end | ||
|
||
bug = "1165388" # https://bugzilla.suse.com/show_bug.cgi?id=1165388 | ||
it "ChangeWidget(_, Cell(row, col)) changes the correct cell, boo##{bug}" do | ||
base = @base + "_cell" | ||
@tui.await(/Table sorting test/) | ||
@tui.capture_pane_to("#{base}-1-initial") | ||
|
||
@tui.send_keys "Home" # go to first table row | ||
@tui.capture_pane_to("#{base}-2-first-row-selected") | ||
|
||
@tui.send_keys "Enter" # activate first table row | ||
@tui.capture_pane_to("#{base}-3-first-row-activated") | ||
|
||
expect(@tui.capture_pane).to match(/name-aaa.VALUE-AAA/) | ||
|
||
@tui.send_keys "M-C" # &Close | ||
end | ||
|
||
bug = "1177145" # https://bugzilla.suse.com/show_bug.cgi?id=1177145 | ||
it "ChangeWidget(_, :CurrentItem) activates the correct line, boo##{bug}" do | ||
pending "not fixed yet" | ||
|
||
base = @base + "_current_item" | ||
@tui.await(/Table sorting test/) | ||
@tui.capture_pane_to("#{base}-1-ccc-selected") | ||
# the UI code performs a | ||
# UI.ChangeWidget(Id(:table), :CurrentItem, "id-yyy-2-ccc") | ||
# then | ||
# UI.QueryWidget(Id(:table), :CurrentItem) | ||
@tui.send_keys "Enter" # activate the current item to produce an event | ||
expect(@tui.capture_pane).to match(/Current Item: "id-yyy-2-ccc"/) | ||
|
||
@tui.send_keys "M-C" # &Close | ||
end | ||
end | ||
end |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
#! /usr/bin/env ruby | ||
require_relative "../test_helper" | ||
require "yast" | ||
Yast.ui_component = "ncurses" | ||
load ARGV[0] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
NP: It would be nice to have a full bug URL here, just having a bug number might not be enough for people not familiar with libyui or SUSE.