diff --git a/.gitignore b/.gitignore index 780df590..c67ae5ae 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,6 @@ /.yardoc /package/*.tar.* *.pot +# screen captures from the libyui tests +*.out.txt +*.out.esc diff --git a/Dockerfile b/Dockerfile index 2fe681f4..3d914607 100644 --- a/Dockerfile +++ b/Dockerfile @@ -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 diff --git a/package/yast2-ruby-bindings.changes b/package/yast2-ruby-bindings.changes index f6ac855c..73294c87 100644 --- a/package/yast2-ruby-bindings.changes +++ b/package/yast2-ruby-bindings.changes @@ -1,3 +1,9 @@ +------------------------------------------------------------------- +Tue Oct 13 14:42:52 UTC 2020 - Martin Vidner + +- Add automatic TUI (ncurses) tests using tmux (bsc#1165388). +- 4.3.5 + ------------------------------------------------------------------- Thu Sep 24 19:46:00 UTC 2020 - besser82@fedoraproject.org diff --git a/package/yast2-ruby-bindings.spec b/package/yast2-ruby-bindings.spec index 59c6d6b7..faf7d01e 100644 --- a/package/yast2-ruby-bindings.spec +++ b/package/yast2-ruby-bindings.spec @@ -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 @@ -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) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index a64eade2..913d9d7f 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -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() @@ -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) diff --git a/tests/libyui/menu_hotkeys_1177760_spec.rb b/tests/libyui/menu_hotkeys_1177760_spec.rb new file mode 100755 index 00000000..f9e09020 --- /dev/null +++ b/tests/libyui/menu_hotkeys_1177760_spec.rb @@ -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 diff --git a/tests/libyui/rspec_tmux_tui.rb b/tests/libyui/rspec_tmux_tui.rb new file mode 100644 index 00000000..f466cd3f --- /dev/null +++ b/tests/libyui/rspec_tmux_tui.rb @@ -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 diff --git a/tests/libyui/table_sort.rb b/tests/libyui/table_sort.rb new file mode 100755 index 00000000..dcaf8b6a --- /dev/null +++ b/tests/libyui/table_sort.rb @@ -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 diff --git a/tests/libyui/table_sort_spec.rb b/tests/libyui/table_sort_spec.rb new file mode 100755 index 00000000..d10de2b7 --- /dev/null +++ b/tests/libyui/table_sort_spec.rb @@ -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 diff --git a/tests/libyui/yast_ncurses b/tests/libyui/yast_ncurses new file mode 100755 index 00000000..5cc5a596 --- /dev/null +++ b/tests/libyui/yast_ncurses @@ -0,0 +1,5 @@ +#! /usr/bin/env ruby +require_relative "../test_helper" +require "yast" +Yast.ui_component = "ncurses" +load ARGV[0]