Skip to content

Commit

Permalink
Merge pull request #251 from yast/libyui-tests
Browse files Browse the repository at this point in the history
automatic tests for TUI (ncurses)
  • Loading branch information
mvidner authored Oct 27, 2020
2 parents 77a53a3 + 387c970 commit 6e5a650
Show file tree
Hide file tree
Showing 10 changed files with 294 additions and 7 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,6 @@
/.yardoc
/package/*.tar.*
*.pot
# screen captures from the libyui tests
*.out.txt
*.out.esc
5 changes: 5 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,9 @@ FROM registry.opensuse.org/yast/head/containers/yast-cpp:latest
# Install tmux to make sure the libyui+YaST integration tests are run
RUN zypper --non-interactive in tmux

# Enable installing docs...
RUN sed -i 's/\(rpm\.install\.excludedocs =\).*/\1 no/' /etc/zypp/zypp.conf
# ... and reinstall the RPM containing the examples we use for tests
RUN zypper --non-interactive in --force yast2-ycp-ui-bindings-devel

COPY . /usr/src/app
6 changes: 6 additions & 0 deletions package/yast2-ruby-bindings.changes
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]

Expand Down
8 changes: 4 additions & 4 deletions package/yast2-ruby-bindings.spec
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@


Name: yast2-ruby-bindings
Version: 4.3.4
Version: 4.3.5
Release: 0
URL: https://github.com/yast/yast-ruby-bindings
BuildRoot: %{_tmppath}/%{name}-%{version}-build
Expand Down Expand Up @@ -46,9 +46,9 @@ BuildRequires: yast2-ycp-ui-bindings-devel >= 4.3.1
# The test suite includes a regression test (std_streams_spec.rb) for a
# libyui-ncurses bug fixed in 2.47.3
BuildRequires: libyui-ncurses >= 2.47.3
# The mentioned test requires to check if tmux is there, because tmux is
# needed to execute the test in headless systems
BuildRequires: which
# The mentioned test requires tmux in order to be executed in headless systems
# Also many other libyui tests to come
BuildRequires: tmux

# only a soft dependency, the Ruby debugger is optional
Suggests: rubygem(%{rb_default_ruby_abi}:byebug)
Expand Down
16 changes: 13 additions & 3 deletions tests/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
#
# CMakeLists.txt for yast2/ruby-bindings/tests/ruby
#
# CMakeLists.txt for yast-ruby-bindings/tests

# use
# make test
# or, for verbose output,
# make test ARGS=-V
# ARGS is passed to ctest; see also
# man ctest

ENABLE_TESTING()

Expand All @@ -12,3 +17,8 @@ endforeach(test)

ADD_TEST("integration" ruby ${CMAKE_CURRENT_SOURCE_DIR}/integration/run.rb)
ADD_TEST("translations" rspec --format doc ${CMAKE_CURRENT_SOURCE_DIR}/integration/translations_spec.rb)

file(GLOB libyui_specs "libyui/*_spec.rb")
foreach(test ${libyui_specs})
ADD_TEST(${test} rspec --format doc ${test})
endforeach(test)
30 changes: 30 additions & 0 deletions tests/libyui/menu_hotkeys_1177760_spec.rb
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}"
@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
118 changes: 118 additions & 0 deletions tests/libyui/rspec_tmux_tui.rb
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)
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
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
62 changes: 62 additions & 0 deletions tests/libyui/table_sort.rb
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
48 changes: 48 additions & 0 deletions tests/libyui/table_sort_spec.rb
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
5 changes: 5 additions & 0 deletions tests/libyui/yast_ncurses
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]

0 comments on commit 6e5a650

Please sign in to comment.