Jekyll2018-08-11T15:43:48+00:00https://blog.ladslezak.cz/The Ladislav’s BlogThis is my personal blog about Linux, programming and related stuff.
Dual head? Dual Seat! Two Users at One Computer at the Same Time!2018-08-11T00:00:00+00:002018-08-11T00:00:00+00:00https://blog.ladslezak.cz/2018/08/11/dual-head-dual-seat-two-users-at-one-computer-at-the-same-time<h2 id="dual-head-or-multi-monitor">Dual Head or Multi Monitor</h2>
<p>If you have two monitors attached to your computer then the setup is called
dual head, the generic term for any number of monitors is <a href="https://en.wikipedia.org/wiki/Multi-monitor">multi-monitor</a>.</p>
<p>This setup useful if a single monitor is not enough for you to see all needed
windows at once. But in this setup both monitors can be used only one person at the
same time.</p>
<h2 id="dual-seat-or-multi-seat">Dual Seat or Multi Seat</h2>
<p>If you have two monitors what about attaching one more keyboard and mouse and
“split” the computer and have independent sessions for each user? That
setup is called dual seat or <a href="https://en.wikipedia.org/wiki/Multiseat_configuration">multi
seat</a> in general.</p>
<p>Linux is a multi user systems from the very beginning, but normally
these users either work remotely or they simply share one seat and need
to cooperate who will use the computer when.</p>
<h2 id="hardware">Hardware</h2>
<p>For this multi seat solution you need a separate graphics adapter for each seat.
Fortunately to save some money you can combine discrete graphics cards with
an integrated one.</p>
<p>If you use an integrated card you might need to enable the multi graphics support in BIOS
because usually when a discrete graphics card is found the integrated one
is automatically disabled.</p>
<p><img src="/assets/images/blog/2018-08-11/bios_gpu_settings.jpg" alt="BIOS settings" /></p>
<p>:information_source: <em>This option is vendor dependant, check your mainboard manual.</em></p>
<h2 id="linux">Linux</h2>
<p>I wanted to configure a multi seat in the past, but it was really
complicated. I would have to tweak the X.org config manually and there were
lots of hacks to make it work.</p>
<p>But actually it turned out that using a modern Linux distribution like
openSUSE Leap 15.0 makes this very easy!</p>
<h2 id="using-the-loginctl-tool">Using the <em>loginctl</em> Tool</h2>
<p>As in almost all modern Linux distributions also in the openSUSE Leap 15.0
the console is managed by the systemd login manager. To interact with it from
the command line you can use a tool called
<a href="https://www.freedesktop.org/software/systemd/man/loginctl.html"><em>loginctl</em></a>.</p>
<p>It can handle sessions, seats and users. Let’s see which seats are defined
by default:</p>
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">#</span> loginctl list-seats
<span class="go">SEAT
seat0
1 seats listed.
</span></code></pre></div></div>
<p>Now we can list all hardware devices assigned to the default seat:</p>
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">#</span> loginctl seat-status seat0
<span class="go">seat0
Sessions: *2
Devices:
├─/sys/devices/LNXSYSTM:00/LNXPWRBN:00/input/input5
│ input:input5 "Power Button"
├─/sys/device…LNXSYSTM:00/LNXSYBUS:00/PNP0C0C:00/input/input4
│ input:input4 "Power Button"
├─/sys/devices/pci0000:00/0000:00:01.3/0000:02:00.0/usb1
│ usb:usb1
├─/sys/devices/pci0000:00/0000:00:01.3/0000:02:00.0/usb2
│ usb:usb2
├─/sys/device…2:00.1/ata2/host1/target1:0:0/1:0:0:0/block/sr0
│ block:sr0
├─/sys/device…ata2/host1/target1:0:0/1:0:0:0/scsi_generic/sg1
│ scsi_generic:sg1
├─/sys/device…1.3/0000:02:00.2/0000:03:04.0/0000:05:00.0/usb4
│ usb:usb4
├─/sys/devices/pci0000:00/0000:00:03.1/0000:09:00.0/drm/card0
│ [MASTER] drm:card0
│ ├─/sys/device…000:00:03.1/0000:09:00.0/drm/card0/card0-DP-1
│ │ [MASTER] drm:card0-DP-1
│ ├─/sys/device…:00:03.1/0000:09:00.0/drm/card0/card0-DVI-D-1
│ │ [MASTER] drm:card0-DVI-D-1
│ └─/sys/device…00:03.1/0000:09:00.0/drm/card0/card0-HDMI-A-1
│ [MASTER] drm:card0-HDMI-A-1
├─/sys/device…000:00/0000:00:03.1/0000:09:00.0/drm/renderD128
│ drm:renderD128
├─/sys/device…i0000:00/0000:00:03.1/0000:09:00.0/graphics/fb0
│ [MASTER] graphics:fb0 "amdgpudrmfb"
</span><span class="c">...
</span></code></pre></div></div>
<p>The list will be very likely long as it contains all devices present in the system.
Obviously all devices are currently assigned to this single seat.</p>
<h2 id="creating-a-new-seat">Creating a New Seat</h2>
<p>Each seat needs a master (main) device. In the list you should see the graphics card
devices marked with the <code class="highlighter-rouge">[MASTER]</code> tag.</p>
<h3 id="adding-output-device">Adding Output Device</h3>
<p>Each graphics card should have two devices, <code class="highlighter-rouge">drm:card<number></code> and <code class="highlighter-rouge">graphics:fb<number></code>.
To create a new seat simply move these two master devices to the new seat. Use the
<code class="highlighter-rouge">loginctl attach seat <path></code> command, copy and paste the full device path from the
previous list command:</p>
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">#</span> loginctl attach seat1 /sys/devices/pci0000:00/0000:00:03.2/0000:0a:00.0/graphics/fb1
<span class="gp">#</span> loginctl attach seat1 /sys/devices/pci0000:00/0000:00:03.2/0000:0a:00.0/drm/renderD129
</code></pre></div></div>
<p>You can check that the new seat is defined properly and contains the specified devices:</p>
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">#</span> loginctl seat-status seat1
<span class="go">seat1
Sessions: *1
Devices:
├─/sys/devices/pci0000:00/0000:00:03.2/0000:0a:00.0/drm/card1
│ [MASTER] drm:card1
│ ├─/sys/device…000:00:03.2/0000:0a:00.0/drm/card1/card1-DP-2
│ │ [MASTER] drm:card1-DP-2
│ ├─/sys/device…:00:03.2/0000:0a:00.0/drm/card1/card1-DVI-D-2
│ │ [MASTER] drm:card1-DVI-D-2
│ └─/sys/device…00:03.2/0000:0a:00.0/drm/card1/card1-HDMI-A-2
│ [MASTER] drm:card1-HDMI-A-2
├─/sys/device…000:00/0000:00:03.2/0000:0a:00.0/drm/renderD129
│ drm:renderD129
└─/sys/device…i0000:00/0000:00:03.2/0000:0a:00.0/graphics/fb1
[MASTER] graphics:fb1 "amdgpudrmfb"
</span></code></pre></div></div>
<p>Now I’d suggest rebooting the system. After reboot you should see the login screen on the
both monitors. (BTW I’m using the default KDE SDDM login manager, I do not know if the other
login managers support multi seat…)</p>
<h3 id="adding-input-devices">Adding Input Devices</h3>
<p>Now we need to assign also the input devices - a keyboard and a mouse. Find the keyboard
and the mouse devices in the <code class="highlighter-rouge">loginctl seat-status seat0</code> output and move them to the
other seat with the <code class="highlighter-rouge">loginctl attach seat1 <path></code> command.</p>
<p>It is easy if you use different keyboard and mouse models for each seat. If you use
the same models then you cannot easily distinguish between the devices. In that case
I suggest using the simple trial-and-error approach, just move a device to the other
seat and test if it is routed to the correct monitor. If not then simply move it back to
<code class="highlighter-rouge">seat0</code> and move the other device to <code class="highlighter-rouge">seat1</code>. You do not need to reboot, you can
test it immediately.</p>
<p>:information_source: <em>Multimedia keyboards might have several devices, do not forget to move them all.</em></p>
<h3 id="and-thats-it">And That’s it!</h3>
<p>And that’s it! Now monitor should behave as an independent login screen with separate
mouse and keyboard devices. Enjoy! :smile:</p>
<h2 id="the-data-persistence">The Data Persistence</h2>
<p>The configuration is persistent, the attached devices are remembered and
automatically set after reboot. You do not need to reassign the devices after
each reboot.</p>
<h2 id="the-drawback">The Drawback</h2>
<p>There is one drawback of enabling the multi seat feature - the multi head
setup does not work anymore.</p>
<p>To make it work back you would have to reconnect the monitors back to the same card
and move the assigned devices back to the original seat. That’s quite annoying
but you could possibly automate the monitor connection by an automatic HDMI
switch if you use HDMI (or DVI) for connecting the monitors and some scripting…</p>
<h2 id="the-use-case-minecraft-video_game">The Use Case: Minecraft! :video_game:</h2>
<p>With this approach you can avoid arguing who will play Minecraft. Since now
you can run two Minecraft instances in separate sessions at once! :tada:</p>
<p><img src="/assets/images/blog/2018-08-11/minecraft_dualseat_small.jpg" alt="Two Minecraft instances at one computer!" /></p>
<p><em>It’s not obvious from the picture, but there is only one computer below the table!</em></p>Dual Head or Multi MonitorHackWeek 17 - Continuing with the REST API2018-07-13T00:00:00+00:002018-07-13T00:00:00+00:00https://blog.ladslezak.cz/2018/07/13/hackweek-17<h1 id="the-libyui-rest-api-project">The Libyui REST API Project</h1>
<p>I spent the 17th HackWeek working on the previous project - libyui REST API
for testing the applications.</p>
<p>You can read some rationale in the <a href="/2017/12/06/hackweek-16-yast-ui-rest-api/">blog post for the previous Hackweek</a>.</p>
<p>In short it allows inspecting the current user interface of a programm via
a REST API. The API can be used from many testing frameworks, there is a
<a href="https://github.com/lslezak/cucumber-yast">simple experimental wrapper</a> for
the Cucumber framework.</p>
<h2 id="examples">Examples</h2>
<p>The REST API can be also used directly from the command line, this is a short
example how it works:</p>
<p>First start an YaST module, specify the port which will be used by the REST API:</p>
<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">YUI_HTTP_PORT</span><span class="o">=</span>14155 /usr/sbin/yast timezone
</code></pre></div></div>
<h3 id="inspecting-with-curl">Inspecting with cURL</h3>
<p>Then you can inspect the current dialog directly from the command line</p>
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">#</span> curl http://localhost:14155/dialog
<span class="go">{
"class" : "YDialog",
"hstretch" : true,
"type" : "main",
"vstretch" : true,
"widgets" :
[
{
"class" : "YVBox",
"help_text" : "...",
"hstretch" : true,
"id" : "WizardDialog",
"vstretch" : true,
"widgets" :
[
{
"class" : "YReplacePoint",
"id" : "topmenu",
</span><span class="c">...
</span></code></pre></div></div>
<p>You can also send some action, like pressing the <code class="highlighter-rouge">Cancel</code> button:</p>
<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>curl <span class="nt">-i</span> <span class="nt">-X</span> POST <span class="s2">"http://localhost:14155/widgets?action=press_button&label=Cancel"</span>
</code></pre></div></div>
<h3 id="using-the-cucumber-wrapper">Using the Cucumber Wrapper</h3>
<p>The <a href="https://github.com/lslezak/cucumber-yast">experimental Cucumber wrapper</a>
allows writing the tests in a native English language. The advantage is that
people not familiar with YaST internals can write the tests easily.
And even non-developers could use it.</p>
<div class="language-cucumber highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">Feature</span><span class="p">:</span> To install the 3rd party packages I must be able to add a new package
repository to the package management.
<span class="kn">Scenario</span><span class="p">:</span> Adding a new repository
<span class="nf">Given </span>I start the <span class="s">"/usr/sbin/yast2 repositories"</span> application
<span class="nf">Then </span>the dialog heading should be <span class="s">"Configured Software Repositories"</span>
<span class="nf">When </span>I click button <span class="s">"Add"</span>
<span class="nf">Then </span>the dialog heading should be <span class="s">"Add On Product"</span>
<span class="nf">When </span>I click button <span class="s">"Next"</span>
<span class="nf">Then </span>the dialog heading should be <span class="s">"Repository URL"</span>
<span class="nf">Then </span>continue...
</code></pre></div></div>
<h2 id="what-has-been-improved">What Has Been Improved</h2>
<p>Here is a short summary what has been improved since the last HackWeek.</p>
<h3 id="more-details-and-more-actions">More Details and More Actions</h3>
<p>The HTTP server now returns more details about the UI like the table content,
the combo box values, etc… Maybe something is still missing but adding more
details for specific widgets should be rather easy.</p>
<p>Also some more actions have been added, now it is possible to set a combo box
value or select the active radio button via the REST API.</p>
<h3 id="processing-the-server-requests">Processing the Server Requests</h3>
<p>Originally the HTTP server was processing the data only when the <code class="highlighter-rouge">UI.UserInput</code>
call was waiting for the input. That was quite limiting as the HTTP requests
might have timed out before before reaching that point.</p>
<p>Now the server is responsive also during the other UI calls and even when there
is no UI call processed at all. So the API is not blocked when YaST is doing
some time consuming action like installing packages. The response now contains
the current progress state as displayed in the UI.</p>
<p>Moreover it even works when there is no UI dialog open yet!</p>
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">#</span> <span class="nb">cat</span> ./test.rb
<span class="go">sleep(30)
</span><span class="gp">#</span> <span class="nv">YUI_HTTP_PORT</span><span class="o">=</span>14155 /usr/sbin/yast2 ./test.rb &
<span class="go">[1] 23578
</span><span class="gp">#</span> curl <span class="nt">-i</span> <span class="s2">"http://localhost:14155/dialog"</span>
<span class="go">HTTP/1.1 404 Not Found
Connection: Keep-Alive
Content-Length: 34
Content-Encoding: application/json
Date: Fri, 13 Jul 2018 05:37:42 GMT
{ "error" : "No dialog is open" }
</span></code></pre></div></div>
<h3 id="text-mode-ncurses-ui-support">Text Mode (Ncurses UI) Support</h3>
<p>This is the most significant change, now the ncurses texmode UI also supports
the embedded REST Server!</p>
<p>So you can see the updated Cucumber test used for testing in the Qt UI from
the last HackWeek running in the text mode!</p>
<p><img src="/assets/images/blog/2018-07-13/cucumber_add_repo_textmode.gif" alt="Adding a new repository in the text mode" /></p>
<p>The only problematic part is that the Cucumber and YaST fight for the terminal
output. That means we cannot run the YaST module directly but use some wrapper.
In this case we run it in a new <code class="highlighter-rouge">xterm</code> session. But for the real automated
tests it would be better to run it in a <code class="highlighter-rouge">screen</code> or <code class="highlighter-rouge">tmux</code> session in background
so it works also without any X server.</p>
<p>Also some more complex scenarios work in the text mode, like a complete
openSUSE Leap 15.0 installation running inside a VirtualBox VM:</p>
<p><img src="/assets/images/blog/2018-07-13/cucumber_leap15_textmode_install.gif" alt="" /></p>
<p>In theory the REST API should work the same way in both graphical and text mode,
from the outside you should not see a difference. Unfortunately in reality
there <strong>are</strong> some differences you need to be aware…</p>
<h3 id="the-text-mode-differences">The Text Mode Differences</h3>
<p>Some differences are caused by the different UI implementation - like the
Ncurses UI does not implement the Wizard widget and the main window has different
properties. For example the main navigation buttons like <code class="highlighter-rouge">Back</code> or <code class="highlighter-rouge">Next</code>
have <code class="highlighter-rouge">YPushButton</code> class in the text mode but in the graphical mode
they have <code class="highlighter-rouge">YQWizardButton</code> class and the dialog content is wrapped in an
additional <code class="highlighter-rouge">YWizard</code> object.</p>
<p>Another difference might be that YaST (or the libyui application in general)
might display a different content in the graphical mode and in the text mode.
For example the YaST time zone selection displays a nice clickable map in the
graphics mode:</p>
<p><img src="/assets/images/blog/2018-07-13/timezone_qt.png" alt="" /></p>
<p>Obviously the world map cannot be displayed in the text mode:</p>
<p><img src="/assets/images/blog/2018-07-13/timezone_ncurses.png" alt="" /></p>
<p>But the bigger difference is that in the graphical mode the time zone is selected
using the <code class="highlighter-rouge">ComboBox</code> widgets while in the text mode there are <code class="highlighter-rouge">SelectionBox</code>
widgets!</p>
<h2 id="what-is-still-working-smiley">What is Still Working :smiley:</h2>
<p>The original support for generic libyui applications is still present. That
means the REST API is not bound to YaST but can be used by any libyui
application.</p>
<p>Here is a test for the libyui <a href="https://github.com/libyui/libyui/blob/c58f4f1fa42f77831560014d1a6d3da1fc05bb54/examples/ManyWidgets.cc">ManyWidgets</a>
example which is a standalone C++ application not using YaST at all:</p>
<p><img src="/assets/images/blog/2018-07-13/cucumber_manywidgets_example_textmode.gif" alt="" /></p>
<h2 id="prepared-testing-packages">Prepared Testing Packages</h2>
<p>If you want to test this libyui feature in openSUSE Leap 15.0 then you can
install the packages from the <a href="https://build.opensuse.org/project/show/home:lslezak:libyui-rest-api">home:lslezak:libyui-rest-api</a>
OBS project. See more details <a href="https://github.com/lslezak/cucumber-yast#installing-libyui">here</a>.</p>
<h2 id="sources">Sources</h2>
<p>The source code is available in the <code class="highlighter-rouge">http_server</code> branches in the <a href="https://github.com/libyui/libyui/tree/http_server">libyui/libyui</a>, <a href="https://github.com/libyui/libyui-ncurses/tree/http_server">libyui/ncurses</a> and <a href="https://github.com/libyui/libyui-qt/tree/http_server">libyui/qt</a> Git repositories.</p>
<h2 id="todo">TODO</h2>
<p>There are lots of missing things, but the project is becoming more usable for
the basic application testing.</p>
<p>The most important missing features:</p>
<ul>
<li>Trigger <code class="highlighter-rouge">notify</code> or <code class="highlighter-rouge">immediate</code> events - if a widget with these properties
is changed via the API these UI events are not created. Which usually
means after changing a widget via the API the dialog is not updated
as when the change is done by user.</li>
<li>Authentication support</li>
<li>Move the web server to a separate plugin so it is an optional feature
which can be added only when needed</li>
<li>SSL (HTTPS) support</li>
<li>IPv6 support</li>
<li>and many more…</li>
</ul>
<p>I will continue with the project at the next HackWeek if possible…</p>The Libyui REST API ProjectDual head? Dual seat! Two users at one computer at the same time!2018-06-30T00:00:00+00:002018-06-30T00:00:00+00:00https://blog.ladslezak.cz/2018/06/30/dual-head-dual-seat-two-users-at-one-computer-at-the-same-time<h2 id="dual-head-multi-monitor">Dual Head, Multi-monitor</h2>
<p>If you have two monitors attached to your computer then the setup is called
dual head, the generic term for any number of monitors is <a href="https://en.wikipedia.org/wiki/Multi-monitor">multi-monitor</a>.</p>
<p>This setup useful if a single monitor is not enough to show all needed
windows at one. I use this setup for development where on the primary
screen I have an editor or IDE running and on the second I have IRC chat,
documentation, running virtual machines, etc… so I do not need to switch
windows to see them.</p>
<p>But in this setup both monitors can used only one person at the same time.</p>
<h2 id="dual-seat-or-multi-seat">Dual Seat or Multi Seat</h2>
<p>If you have two monitors what about attaching one more keyboard and mouse and
“split” the computer in half and have independent sessions for each user? That
setup is called dual seat or <a href="https://en.wikipedia.org/wiki/Multiseat_configuration">multi
seat</a> in general.</p>
<p>Linux is a multi user systems from the very beginning. But normally
these users either work remotely or they simply share one seat and need
to cooperate who will use the computer when.</p>
<h2 id="hardware">Hardware</h2>
<p>For this multi seat solution you need a separate graphics adapter for each seat.
Fortunately to to save some money you can combine discrete graphics cards with
an integrated one. In my case I used an integrated Intel graphics adapter and
a discrete AMD Radeon card.</p>
<p>If you use an integrated card you might need to enable the multi graphics support in BIOS
because usually when a discrete graphics card is found the integrated one
is disabled. In my case I had to enable the <em>Multi-Monitor</em> option in the BIOS:</p>
<p><img src="/assets/images/blog/2018-06-30/bios_gpu_settings.jpg" alt="BIOS settings" /></p>
<h2 id="linux">Linux</h2>
<p>I wanted to configure a multi seat already in the past, but it was really
complicated. I would have to tweak the X.org config manually and there were
lots of hacks.</p>
<p>But actually it turned out that using a modern Linux distribution like
openSUSE Leap 15.0 makes this very easy!</p>
<h2 id="using-the-loginctl-tool">Using the <em>loginctl</em> Tool</h2>
<p>As in almost all modern Linux distributions also in the openSUSE Leap 15.0
the console is managed by the systemd login manager. To interact with it from
the command line you can use a tool called
<a href="https://www.freedesktop.org/software/systemd/man/loginctl.html"><em>loginctl</em></a>.</p>
<p>It can handle the sessions, seats and users. Let’s see which seats are defined
by default:</p>
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">#</span> loginctl list-seats
<span class="go">SEAT
seat0
1 seats listed.
</span></code></pre></div></div>
<p>Now we can list all hardware devices assigned to that seat:</p>
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">#</span> loginctl seat-status seat0
<span class="go">seat0
Sessions: *5
Devices:
├─/sys/devices/LNXSYSTM:00/LNXPWRBN:00/input/input1
│ input:input1 "Power Button"
├─/sys/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A08:00/LNXVIDEO:00/input/input2
│ input:input2 "Video Bus"
├─/sys/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0C0C:00/input/input0
│ input:input0 "Power Button"
├─/sys/devices/pci0000:00/0000:00:01.0/0000:01:00.0/drm/card0
│ [MASTER] drm:card0
│ ├─/sys/devices/pci0000:00/0000:00:01.0/0000:01:00.0/drm/card0/card0-DP-1
│ │ [MASTER] drm:card0-DP-1
│ ├─/sys/devices/pci0000:00/0000:00:01.0/0000:01:00.0/drm/card0/card0-DVI-D-1
│ │ [MASTER] drm:card0-DVI-D-1
│ └─/sys/devices/pci0000:00/0000:00:01.0/0000:01:00.0/drm/card0/card0-HDMI-A-3
│ [MASTER] drm:card0-HDMI-A-3
├─/sys/devices/pci0000:00/0000:00:01.0/0000:01:00.0/drm/renderD128
│ drm:renderD128
├─/sys/devices/pci0000:00/0000:00:01.0/0000:01:00.0/graphics/fb0
│ [MASTER] graphics:fb0 "amdgpudrmfb"
</span><span class="c">...
</span></code></pre></div></div>
<p>Obviously all are devices are assigned to the only seat defined in the system.</p>
<h2 id="creating-a-new-seat">Creating a New Seat</h2>
<h2 id="persistence">Persistence</h2>
<p>The configuration is persistent, the attached devices are remembered and
automatically set after reboot. You do not need to assign the devices after
each reboot.</p>
<h2 id="drawback">Drawback</h2>
<p>There is one drawback of enabling the multi seat feature - the multi head
setup does not work anymore. :worried:</p>
<p>To make it work back you would have to reconnect the monitors back to the same card
and move the assigned devices back to the original seat. That’s quite annoying
but you could possibly automate the monitor connection by and automatic HDMI
switch if you use HDMI (or DVI) for connecting the monitors.</p>
<h2 id="the-use-case-minecraft-smiley">The Use Case: Minecraft! :smiley:</h2>
<p>To avoid arguing who will play Minecraft right now you can simply enable
running two Minecrafts in separate sessions!</p>
<p><img src="/assets/images/blog/2018-06-30/minecraft_dualseat_small.jpg" alt="Two Minecrafts at one computer!" /></p>
<p>It’s not obvious from the picture, but there is only one computer below the table!</p>Dual Head, Multi-monitorThe openSUSE Conference 20182018-05-28T00:00:00+00:002018-05-28T00:00:00+00:00https://blog.ladslezak.cz/2018/05/28/opensuse-conference-2018<h1 id="the-opensuse-conference-2018">The openSUSE Conference 2018</h1>
<p><img src="/assets/images/blog/2018-05-28/small_20180525_132631.jpg" alt="Venue" /></p>
<p>I visited the openSUSE conference this year, it took place in Prague, at the
same place as few years ago. You can check the schedule and the list of the
talks <a href="https://events.opensuse.org/conference/oSC18/schedule">here</a>.</p>
<h2 id="the-interesting-topics">The Interesting Topics</h2>
<p>Here are some interesting sessions I visited, for the complete list see
the schedule link above.</p>
<ul>
<li>
<p><a href="https://events.opensuse.org/conference/oSC18/program/proposal/1900">The openSUSE Leap 15.0 has been released!</a></p>
</li>
<li>
<p>If you want to share a service without a having a public IP address
the tor network might be an option. Using setting up a <a href="https://events.opensuse.org/conference/oSC18/program/proposal/1678">service using
Docker makes it really easy</a>.</p>
</li>
<li>
<p>The <a href="https://events.opensuse.org/conference/oSC18/program/proposal/1864">package dependencies can be improved</a>
using <code class="highlighter-rouge">Supplements</code> dependency this way:</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Supplements: (foo and bar)
</code></pre></div> </div>
<p>This would automatically install the package when <em>both</em> packages are
installed.</p>
</li>
<li>
<p>The openSUSE distribution is also yours, <a href="https://events.opensuse.org/conference/oSC18/program/proposal/1912">openSUSE is what you make it</a>.</p>
</li>
<li>
<p>The <a href="https://events.opensuse.org/conference/oSC18/program/proposal/1909">openSUSE Kubic</a> is a
Tumbleweed flavor designed specially for running container workloads.</p>
</li>
<li>
<p>There is a <a href="https://build.opensuse.org/package/show/openSUSE:Factory/tumbleweed-cli">tumbleweed-cli</a>
package which makes using the <a href="https://events.opensuse.org/conference/oSC18/program/proposal/1828">Tumbleweed snapshots much easier</a>.</p>
</li>
<li>
<p>The <a href="https://events.opensuse.org/conference/oSC18/program/proposal/1906">transactional updates</a>
provide a completely new way how to update the running system.</p>
</li>
<li>
<p>The btrfs filesystem provides a lot of interesting features. But if something
goes wrong it might a bit tricky to fix it. The most important thing is that
the <code class="highlighter-rouge">btrfs check --repair</code> command should used as the very last option
(if at all). There are <a href="https://events.opensuse.org/conference/oSC18/program/proposal/1915">other more safe options</a> to try
first.</p>
</li>
<li>
<p>Using the LetsEncrypt certificates should be much easier in the openSUSE
Leap 15.0 because the automatic <a href="https://events.opensuse.org/conference/oSC18/program/proposal/1801">certificate renewal is supported</a> out of
box.</p>
</li>
<li>
<p>If you miss some packages in SLE which are present in openSUSE then the
<a href="https://events.opensuse.org/conference/oSC18/program/proposal/1957">SUSE PackageHub might help you</a>.</p>
</li>
<li>
<p>The OBS (Open Build Service) can also build container images, that can
<a href="https://events.opensuse.org/conference/oSC18/program/proposal/1966">help with testing and distributing your packages</a>.
If you want to start in an easy way then you can choose from many
<a href="https://build.opensuse.org/image_templates">image templates</a>.</p>
</li>
<li>
<p>If you want to try the <a href="https://kubic.opensuse.org/">openSUSE Kubic</a>
you might be interested in <a href="https://events.opensuse.org/conference/oSC18/program/proposal/1960">some basic concepts behind</a>.</p>
</li>
<li>
<p>Developing the community and the enterprise products separately has many
disadvantages, it is <a href="https://events.opensuse.org/conference/oSC18/program/proposal/1768">much better to develop them together</a>.</p>
</li>
</ul>
<h3 id="portainer">Portainer</h3>
<p>One of the Kubic presentations mentioned the <a href="https://portainer.io/">portainer.io</a> project which looks really interesting.</p>
<p>If you are new to the Docker and the container world and you would like to use
some GUI at the beginning to make the start easier than the <a href="https://portainer.io/">portainer.io</a> project looks very interesting. It provides a nice web UI
for managing the Docker containers. And obviously it is provided as a Docker
image.</p>
<p>What is nice that starting it is basically a matter of running two commands:</p>
<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>docker volume create portainer_data
docker run <span class="nt">-d</span> <span class="nt">-p</span> 9000:9000 <span class="nt">-v</span> /var/run/docker.sock:/var/run/docker.sock <span class="se">\</span>
<span class="nt">-v</span> portainer_data:/data portainer/portainer
</code></pre></div></div>
<p>Then point your web browser to <a href="http://localhost:9000/">http://localhost:9000/</a>
and that’s it!</p>
<p><a href="/assets/images/blog/2018-05-28/portainer.png"><img src="/assets/images/blog/2018-05-28/portainer.png" alt="Portainer" /></a></p>
<h2 id="conclusion">Conclusion</h2>
<p>The conference was really good and I’d like to say thank you to the presenters, the
organization team and the sponsors to make this happen!</p>The openSUSE Conference 2018Hackweek 16 - The YaST Integration Tests II.2017-12-06T00:00:00+00:002017-12-06T00:00:00+00:00https://blog.ladslezak.cz/2017/12/06/hackweek-16-yast-ui-rest-api<h1 id="hackweek-16-0x10">Hackweek 16 (0x10)</h1>
<p>I spent the <a href="/2017/03/01/hackweek-15-yast-cucumber/">previous Hackweek with Cucumber and YaST</a>. It worked quite well
but it turned out to be too big step forward and focused on one specific
testing framework.</p>
<p>This time I decided to make a smaller step, but make the solution more generic.
So instead of having direct Cucumber support in the UI I proposed implementing
a generic REST API. The API can be then used by any framework (Cucumber, RSpec…)
or it can be used directly by any script or tool.</p>
<p>See also the <a href="https://hackweek.suse.com/16/projects/controlling-and-testing-the-yast-ui-remotely-for-integration-tests-openqa">Hackweek project page</a>.</p>
<h2 id="implementation">Implementation</h2>
<p>The current solution is based on these two key libraries:</p>
<ul>
<li><a href="https://www.gnu.org/software/libmicrohttpd/">GNU libmicrohttpd</a> - an embedded
HTTP server</li>
<li><a href="https://github.com/open-source-parsers/jsoncpp">jsoncpp</a> - JSON parser/writer</li>
</ul>
<p>Both libraries are already included in the openSUSE distributions so it is
easy to use them.</p>
<h2 id="installation">Installation</h2>
<p>After compiling and installing new libyui, libyui-qt and recompiling
yast2-ycp-ui-bindings (because of ABI changes) you can run any YaST module with
<code class="highlighter-rouge">YUI_HTTP_SERVER=14155</code> environment variable set. Then you can access the REST
API via the port 14115 using <code class="highlighter-rouge">curl</code> command. (You can use a different port
number.)</p>
<h2 id="using-the-rest-api">Using the REST API</h2>
<p>Run the YaST repository manager (as <em>root</em>):</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">YUI_HTTP_PORT</span><span class="o">=</span>14155 yast2 repositories
</code></pre></div></div>
<p><a href="/assets/images/blog/2017-12-06/yast_repositories.png"><img src="/assets/images/blog/2017-12-06/yast_repositories.png" alt="Embedded help" /></a></p>
<p>This will start the YaST module as usually, there is no visible change. You can
normally use the module, the only change is that the UI now listens on the port
14155 for incoming HTTP requests.</p>
<p>You can access the REST API (<em>root</em> not required) from command line like this:</p>
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">></span> <span class="c"># dump the current dialog as a tree</span>
<span class="gp">></span> curl <span class="s1">'http://localhost:14155/dialog'</span>
<span class="go">{
"class" : "YDialog",
"hstretch" : true,
"type" : "wizard",
"vstretch" : true,
"widgets" :
[
{
"class" : "YWizard",
"debug_label" : "Configured Software Repositories",
"hstretch" : true,
"id" : "wizard",
"vstretch" : true,
"widgets" :
[
</span><span class="c">...
</span><span class="gp">></span> <span class="c"># dump the current dialog as a simple list (easier iteration, search)</span>
<span class="gp">></span> curl <span class="s1">'http://localhost:14155/widgets'</span>
<span class="go">[
{
"class" : "YDialog",
"hstretch" : true,
"type" : "wizard",
"vstretch" : true
},
{
"class" : "YWizard",
"debug_label" : "Configured Software Repositories",
"hstretch" : true,
"id" : "wizard",
"vstretch" : true
},
</span><span class="c">...
</span><span class="gp">></span> <span class="c"># filter widgets by ID</span>
<span class="gp">></span> curl <span class="s1">'http://localhost:14155/widgets?id=enable'</span>
<span class="go">[
{
"class" : "YCheckBox",
"debug_label" : "Enabled",
"id" : "enable",
"label" : "E&nabled",
"notify" : true,
"value" : true
}
]
</span><span class="gp">></span> <span class="c"># filter widgets by label (as displayed, might be translated!)</span>
<span class="gp">></span> curl <span class="s1">'http://localhost:14155/widgets?label=Priority'</span>
<span class="go">[
{
"class" : "YIntField",
"debug_label" : "Priority",
"hstretch" : true,
"id" : "priority",
"label" : "&Priority",
"max_value" : 200,
"min_value" : 0,
"notify" : true,
"value" : 99
}
]
</span><span class="gp">></span> <span class="c"># filter widgets by type (all check boxes in this case)</span>
<span class="gp">></span> curl <span class="s1">'http://localhost:14155/widgets?type=YCheckBox'</span>
<span class="go">[
{
"class" : "YCheckBox",
"debug_label" : "Enabled",
"id" : "enable",
"label" : "E&nabled",
"notify" : true,
"value" : true
},
{
"class" : "YCheckBox",
"debug_label" : "Automatically Refresh",
"id" : "autorefresh",
"label" : "Automatically &Refresh",
"notify" : true,
"value" : true
},
{
"class" : "YCheckBox",
"debug_label" : "Keep Downloaded Packages",
"id" : "keeppackages",
"label" : "&Keep Downloaded Packages",
"notify" : true,
"value" : false
}
]
</span><span class="gp">></span> <span class="c"># and finally do some action - press the [Abort] button and close the module,</span>
<span class="gp">></span> <span class="c"># you can use the same filters as in the queries above</span>
<span class="gp">></span> curl <span class="nt">-X</span> POST <span class="s1">'http://localhost:14155/widgets?id=abort&action=press'</span>
</code></pre></div></div>
<p>To make it cool I have included some inline documentation or help, just open
<code class="highlighter-rouge">http://localhost:14155</code> URL in your browser:</p>
<p><a href="/assets/images/blog/2017-12-06/libyui_http_help.png"><img src="/assets/images/blog/2017-12-06/libyui_http_help.png" alt="Embedded help" /></a></p>
<h3 id="running-a-cucumber-test">Running a Cucumber Test</h3>
<p>I have also written a simple set of step definitions for the <a href="https://cucumber.io/">Cucumber</a> framework. This allows using the REST API in Cucumber
integration tests.</p>
<p>Let’s have this example Cucumber test:</p>
<div class="language-cucumber highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">Feature</span><span class="p">:</span> To install the 3rd party packages I must be able to add a new package
repository to the package management.
<span class="kn">Background</span><span class="p">:</span>
<span class="c"># make sure the tested repository does not already exist</span>
<span class="nf">When </span>I run `zypper repos --uri`
<span class="err">Then the output should not contain "https</span><span class="p">:</span><span class="err">//download.opensuse.org/tumbleweed/repo/oss/"</span>
<span class="kn">Scenario</span><span class="p">:</span> Aborting the repository manager keeps the old settings
<span class="nf">Given </span>I start the <span class="s">"/usr/sbin/yast2 repositories"</span> application
<span class="nf">Then </span>the dialog heading should be <span class="s">"Configured Software Repositories"</span>
<span class="nf">When </span>I click button <span class="s">"Add"</span>
<span class="nf">Then </span>the dialog heading should be <span class="s">"Add On Product"</span>
<span class="nf">When </span>I click button <span class="s">"Next"</span>
<span class="nf">Then </span>the dialog heading should be <span class="s">"Repository URL"</span>
<span class="nf">When </span>I enter <span class="s">"Tumbleweed OSS"</span> into input field <span class="s">"Repository Name"</span>
<span class="err">And I enter "https</span><span class="p">:</span><span class="err">//download.opensuse.org/tumbleweed/repo/oss/"</span> <span class="err">into</span> <span class="err">input</span> <span class="err">field</span> <span class="err">"URL"</span>
<span class="nf">And </span>I click button <span class="s">"Next"</span>
<span class="nf">Then </span>the dialog heading should be <span class="s">"Tumbleweed OSS License Agreement"</span> in 60 seconds
<span class="nf">When </span>I click button <span class="s">"Next"</span>
<span class="nf">Then </span>the dialog heading should be <span class="s">"Configured Software Repositories"</span>
<span class="nf">When </span>I click button <span class="s">"Cancel"</span>
<span class="nf">Then </span>a popup should be displayed
<span class="nf">And </span>a label including <span class="s">"Abort the repository configuration?"</span> should be displayed
<span class="nf">Then </span>I click button <span class="s">"Yes"</span>
<span class="nf">And </span>I wait for the application to finish
<span class="c"># verify that the tested repository was NOT added</span>
<span class="nf">When </span>I run `zypper repos --uri`
<span class="err">Then the output should not contain "https</span><span class="p">:</span><span class="err">//download.opensuse.org/tumbleweed/repo/oss/"</span>
</code></pre></div></div>
<p>If you run it the result would look like this:</p>
<p><a href="/assets/images/blog/2017-12-06/cucumber_add_repo_test.gif"><img src="/assets/images/blog/2017-12-06/cucumber_add_repo_test.gif" alt="Adding a Repository in YaST" /></a></p>
<p>Pretty nice isn’t it?</p>
<h2 id="generic-usage">Generic Usage</h2>
<p>Because the REST API is implemented on the UI (libyui) level it means that any
application using the library can be tested the same way, it is not limited only
to YaST. For example it should be very easy to write tests also for the
<a href="https://madb.mageia.org/package/show/name/manatools">Mageia tools</a> included
in the Megeia distribution.</p>
<h2 id="todo">TODO</h2>
<p>There are lots of missing features, mainly:</p>
<ul>
<li>Return more details in the response, add missing attributes</li>
<li>Support more actions besides clicking a button or filling an <code class="highlighter-rouge">InputField</code></li>
<li>Support more UI calls (e.g. <code class="highlighter-rouge">UI.PollInput</code>)</li>
<li>Support the other UIs (ncurses, maybe GTK)</li>
<li>Support for the package manager widget (implemented in the <code class="highlighter-rouge">libyui-*-pkg</code> subpackages)</li>
<li>Packaging - make the feature optional (if you do not want to include the web
server for security reasons)</li>
<li>Add more step definitions in the Cucumber wrapper</li>
</ul>
<h3 id="optional-features">Optional Features</h3>
<ul>
<li>User authentication (unauthenticated access is a security hole, but it might be
OK when running tests in a sandboxed environment in a trusted network)</li>
<li>SSL support (securely transfer the current values and set the new values, e.g.
the root password)</li>
<li>IPv6 support (it’s the future, isn’t it?)</li>
<li>Unix socket support (another kind of user authorization)</li>
</ul>
<h2 id="conclusion">Conclusion</h2>
<p>The feature is far from being production ready, it is still rather a proof
of concept. But it shows that it works well and we can continue in this
direction in the future.</p>Hackweek 16 (0x10)Time-stamping Output and Profiling2017-04-24T00:00:00+00:002017-04-24T00:00:00+00:00https://blog.ladslezak.cz/2017/04/24/time-stamping-stdout-and-profiling<h2 id="time-stamping">Time-stamping</h2>
<p>Sometimes you need to get the time stamps for each line printed by a programm.
How to do it? Fortuntely there is a tool called <code class="highlighter-rouge">ts</code> (like Time Stamp).</p>
<p>In openSUSE it is not installed by default, but is available in the standard
repositories in the <em>moreutils</em> package:</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo zypper in moreutils
</code></pre></div></div>
<p>When using time-stamping it is a good idea to get rid of buffering which might
prevent from getting the correct time. So the usual usage pattern should be
something like this:</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>unbuffer <command> | ts
</code></pre></div></div>
<p>See <code class="highlighter-rouge">man ts</code> for more details, it has few but very usefull options. It can display
absolute or relative time, define the precision, etc…</p>
<h2 id="profiling">Profiling</h2>
<p>If you can get exact time stamps for each output line then it is easy
to find the line which took too long time.</p>
<p>In this example I was writing a new test for the <a href="https://github.com/yast/yast-s390">s390</a> YaST module. I noticed that one test
took much more time than the others. So I used <code class="highlighter-rouge">ts</code> to get the real numbers,
the full command in this case was <code class="highlighter-rouge">unbuffer rake test:unit | ts -i %.s</code>:</p>
<p><img src="/assets/images/blog/2017-04-24/s390_test_original.png" alt="Original Test" /></p>
<p>As you can see the tests usually take just few miliseconds, 5 ms at most.
Except the <em>Write</em> test which takes more than 500ms. I was wondering why.</p>
<h2 id="debugging">Debugging</h2>
<p>So how to find the place where the test took most of the time? You could use the
<a href="https://ruby-doc.org/stdlib-2.1.0/libdoc/profiler/rdoc/Profiler__.html">Ruby profiler</a>:</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ruby -rprofile -S rspec <test_file>
</code></pre></div></div>
<p>But I found the output too detailed without finding any relevant information,
probably because it measures the CPU time, not the real time…</p>
<p>The alternative approach is simply to add the <code class="highlighter-rouge">puts</code> call somewhere in the tested
code. So you can measure how long it takes to reach this specific point.
Using the usual bisect approach you can find the problematic place quite quickly.</p>
<p>In this case the problematic place was surprisingly calling the <code class="highlighter-rouge">sleep(500)</code>
function <a href="https://github.com/yast/yast-s390/blob/48b302032d55b2961d4645f0e2a2dece597dcdaf/src/modules/DASDController.rb#L641">here</a>
which obviously adds 500ms to the test time. The fix was easy, simply
mock the <code class="highlighter-rouge">sleep</code> call in the test and return immediately.</p>
<p>The result is that the <em>Write</em> test now also takes few miliseconds just like
the others:</p>
<p><img src="/assets/images/blog/2017-04-24/s390_test_improved.png" alt="Fixed Test" /></p>
<p>The test as a whole is now more than 20x faster. :wink:</p>
<h2 id="conclusion">Conclusion</h2>
<p>In this case the real improvement was small, saving half a second is not
a game changing feature.</p>
<p>But I used this approach in the past in the YaST registration module
where the problem was caused by running a SLP network discovery without mocking it.
That took much more time and scanning the local network in tests is at least
not nice.</p>Time-stampingDocker at Travis2017-03-28T00:00:00+00:002017-03-28T00:00:00+00:00https://blog.ladslezak.cz/2017/03/28/docker_at_travis<h1 id="travis-ci">Travis CI</h1>
<p>The <a href="https://travis-ci.org/">Travis CI</a> service offers a free <a href="https://en.wikipedia.org/wiki/Continuous_integration">Continuous Integration
(CI)</a> service for open
source projects hosted at <a href="https://github.com">GitHub</a>.</p>
<h2 id="ubuntu-1204-lts">Ubuntu 12.04 LTS</h2>
<p>Unfortunately it runs Ubuntu 12.04 LTS at the worker nodes. That means if your
software needs some newer tools or libraries you have to find it in some
external repository. You might be lucky with the <a href="https://launchpad.net/">Ubuntu LaunchPad</a>, but in the worst case you will need to backport the
package by yourselves.</p>
<h2 id="other-distributions">Other Distributions?</h2>
<p>But what if you need a newer Ubuntu version for your build? Or a completely
different distribution like openSUSE or Fedora?</p>
<p>Originally that was not possible with Travis, but fortunately they allow using
<a href="https://www.docker.com/">Docker containers</a> at build for some time. That means
you can run even a completely different distribution for building your software.</p>
<h2 id="travis-and-docker-tricks">Travis and Docker Tricks</h2>
<h3 id="building-for-several-distributions-in-paralell">Building for Several Distributions in Paralell</h3>
<p>Travis allows to setup a <a href="https://docs.travis-ci.com/user/customizing-the-build#Build-Matrix">build matrix</a> which can
run the build in different environments. The usual use case is to run the tests
using different versions of compilers or interpreters.</p>
<p>But you can easily use this feature for using different Docker images
with different distributions. You just define different <code class="highlighter-rouge">Dockerfile</code> and
building script for each environment.</p>
<p>We use this feature in <a href="https://github.com/openSUSE/snapper">snapper</a> and the
code is built for five different distributions in parallel!</p>
<p><a href="https://travis-ci.org/openSUSE/snapper"><img src="/assets/images/blog/2017-03-28/travis_snapper.png" alt="Snapper Build" /></a></p>
<p>As you can see the code is built for Debian, Ubuntu, Fedora, openSUSE Leap
and openSUSE Tumbleweed. That means we know that the code still builds on all
these distributions even before merging a change!</p>
<p>See the <code class="highlighter-rouge">Dockerfile.*</code> and <code class="highlighter-rouge">.travis.*</code> <a href="https://github.com/openSUSE/snapper">source files</a> for more details.</p>
<h3 id="building-docker-images-at-docker-hub">Building Docker Images at Docker Hub</h3>
<p>The <a href="https://hub.docker.com/">Docker Hub</a> is a platform for publishing and
sharing the the Docker images.</p>
<p>The snapper example above builds the Docker image locally and then runs it. But
what if you need to build the same image many times? What if your software
changes a lot? Or you have several packages which run in the same environment?</p>
<p>Then it makes sense to build the image only once and then reusing everywhere where
needed. That is exactly the case for YaST, we have about one hundred
repositories which build in the same environment.</p>
<p>Actually we split the environment into two parts - one for Ruby based packages
and one for C++ based packages. The reason is to have a smaller Docker image for
faster downloads.</p>
<p>So at Travis we either download the <a href="https://hub.docker.com/r/yastdevel/ruby/">Ruby
image</a> or the <a href="https://hub.docker.com/r/yastdevel/cpp/">C++
image</a>. You can check the sources for
both Docker images at GitHub (<a href="https://github.com/yast/docker-yast-ruby">Ruby image</a>, <a href="https://github.com/yast/docker-yast-cpp">C++ image</a>).</p>
<h3 id="local-build">Local Build</h3>
<p>Normally you cannot reproduce the Travis builds locally as Travis uses a customized
Ubuntu image which is not available for download. That means even if you build your
software on Ubuntu 12.04 you might still get a slightly different results at
Travis.</p>
<p>With Docker you can download or build the very same Docker image and run it
locally. Just run the same Docker commands as in Travis.</p>
<p>(Technically it still will not be 100% the same as at Travis, e.g. Docker uses
the host system kernel so there still might be some differences but it is
very very close…)</p>
<h2 id="try-it-in-your-projects">Try it in Your Projects!</h2>
<p>So hopefully these hints will be helpful for somebody and will allow you to run
CI also for your project. And if Travis does not fit your needs then there are
similar alternatives available…</p>Travis CIHackweek 15 - the YaST Integration Tests2017-03-01T00:00:00+00:002017-03-01T00:00:00+00:00https://blog.ladslezak.cz/2017/03/01/hackweek-15-yast-cucumber<h1 id="hackweek-15">Hackweek 15</h1>
<p>I decided to spend the last SUSE Hackweek with YaST and find a way which
would allow us to write and run YaST integration tests easily. See the
details in the <a href="https://hackweek.suse.com/projects/yast-integration-tests-using-cucumber">project Hackweek page</a>.</p>
<p>Some time ago I found the <a href="https://github.com/cucumber/cucumber-cpp">cucumber-cpp</a> project. It is a <a href="https://github.com/cucumber/cucumber">cucumber</a> support for the C++ programming language.</p>
<p>The reason is that the YaST UI uses the
<a href="https://github.com/libyui/libyui">libyui</a> library which is written in C++.
If we want to control and check the YaST UI we need to implement it on the
libyui level.</p>
<h2 id="the-hackweek-result">The Hackweek Result</h2>
<p>Here are some Cucumber test examples which I was able to run in a real system.
The source code for the tests and the details how to run them can be found at
the <a href="https://github.com/lslezak/cucumber-yast">lslezak/cucumber-yast</a> GitHub
repository.</p>
<p>Currently only the graphical (Qt) UI is adapted, the textmode (ncurses) will
not work and crash (because of the ABI incompatibility).</p>
<p>The code is available in my GitHub forks of <a href="https://github.com/libyui/libyui/compare/master...lslezak:cucumber?expand=1">libyui</a>,
<a href="https://github.com/libyui/libyui-qt/compare/master...lslezak:cucumber?expand=1">libyui-qt</a>
and small improvement was done for the <a href="https://github.com/cucumber/cucumber-cpp/compare/master...lslezak:improvements?expand=1">cucumber-cpp</a>
library. The experimental RPM packes are available in the <a href="https://build.opensuse.org/project/monitor/home:lslezak:cucumber">home:lslezak:cucumber</a> OBS repository.</p>
<h3 id="running-a-cucumber-test-in-installed-system">Running a Cucumber Test in Installed system</h3>
<p>Here is the output of the <a href="https://github.com/lslezak/cucumber-yast/blob/master/features/adding_new_repo.feature">adding_new_repo.feature</a>
Cucumber test.</p>
<p><img src="/assets/images/blog/2017-03-01/add_repo.gif" alt="Adding a Repository in YaST" /></p>
<h3 id="running-a-cucumber-test-during-installation">Running a Cucumber Test During Installation</h3>
<p>This needs a patched openSUSE Leap 42.2 installer so it is not trivial to reproduce…</p>
<p>In this case the installation is running in a VirtualBox virtual machine
and the test is running outside on my workstation. The test source is <a href="https://github.com/lslezak/cucumber-yast/blob/master/features/installation.feature">here</a>.</p>
<p><img src="/assets/images/blog/2017-03-01/install_leap_42.2.gif" alt="openSUSE Leap 42.2 Installation" /></p>
<h3 id="running-a-cucumber-test-for-a-plain-libyui-application">Running a Cucumber Test for a Plain Libyui Application</h3>
<p>The Cucumber tests can be written actually for any application which uses the
libyui framework, not only for YaST. This might be interesting for the other users
of the libyui framework, for example the <a href="https://wiki.mageia.org/en/Feature:UiAbstraction4mcc">Mageia tools</a>.</p>
<p>Here is a <a href="https://github.com/lslezak/cucumber-yast/blob/master/features/libyui_selectionbox2.feature">test</a>
for the libyui <a href="https://github.com/libyui/libyui/blob/master/examples/SelectionBox2.cc">SelectionBox2.cc</a> example.</p>
<p><img src="/assets/images/blog/2017-03-01/libyui_selectionbox2.gif" alt="Libyui SelectionBox2.cc Example" /></p>
<h2 id="what-i-learnt">What I Learnt</h2>
<ul>
<li>Using boost for implementing a simple TCP server is probably an overkill,
after seeing <a href="http://www.boost.org/doc/libs/1_63_0/doc/html/boost_asio/tutorial.html#boost_asio.tutorial.tutdaytime3">this boost::asio example</a>
:flushed: I decided to use plain and simple <code class="highlighter-rouge">socket()</code>/<code class="highlighter-rouge">bind()</code>/<code class="highlighter-rouge">listen()</code> C functions.</li>
<li>Synchronizing with the application is crucial, you cannot verify the UI
state until it is fully built by the application. In case of YaST it
is when <code class="highlighter-rouge">UI.UserInput</code> (or similar) function is called.</li>
<li>I refreshed my C++ knowledge, I even used a C++ template in the code :wink:</li>
</ul>
<h2 id="technical-details">Technical details</h2>
<ul>
<li>The cucumber-cpp library uses the <a href="https://github.com/cucumber/cucumber/wiki/Wire-Protocol">Cucumber wire protocol</a> which basically sends
JSON messages over a TCP port. The advantage is that it is possible to
test the application running at another machine. This is useful for testing
the YaST installation.</li>
<li>I had to reimplement the server part as the cucumber-cpp library can only test
an application represented by a single C++ object. Because YaST uses a plugin
architecture where the parts are loaded dynamically and they cannot be easily
accessed from outside so the server part must have been implemented directly on
the libyui level.</li>
<li>The advantage of this solution is that the integration tests are available
to any libyui based application, not only YaST.</li>
</ul>
<h2 id="todo">TODO</h2>
<ul>
<li>Add more matchers</li>
<li>Support for more UI calls (e.g. <code class="highlighter-rouge">UI.PollInput</code>)</li>
<li>Support the other UIs (ncurses, GTK)</li>
<li>Support for the packager widget (in the <code class="highlighter-rouge">libyui-*-pkg</code> subpackages)</li>
<li>Increase the Cucumber timeout - when a step takes too much time (e.g. installing
packages) then the test runner times out</li>
<li>Closing the application crashes the test (the TCP port is closed and cucumber
reports broken pipe)</li>
<li>The application needs to be already running, it is difficult to ensure
clean initial state or restart the application for another test scenario</li>
</ul>
<p>The last two issues are quite tricky, it seems we will need to run some wrapper
and communicate with YaST in the tests indirectly. In that case we probably
should use some simpler communication protocol like the
<a href="https://testanything.org/">Test Anything Protocol (TAP)</a>. I was told that
this protocol is already supported by openQA so it should be easy to use it
also there…</p>Hackweek 15Editing a Screencast in Gimp2016-09-08T00:00:00+00:002016-09-08T00:00:00+00:00https://blog.ladslezak.cz/2016/09/08/editing-screencast<h1 id="editing-screencast">Editing Screencast</h1>
<p>In this <a href="/2016/01/18/recording-screencast-in-linux/">older post</a>
I described how to record a screencast in Linux.</p>
<p>However, you will very likely need to do some post processing with the recording,
like removing the unneeded parts at the beginnig or the end, cropping the image
or adding an empty frame at the end so it is obvious when the animation restarts.</p>
<p>And for that we can use <a href="https://www.gimp.org/">Gimp</a> editor which is usually
pre-installed in the major Linux distributions.</p>
<h2 id="gimp">Gimp</h2>
<p>Gimp is designed for editing static images but with some workarounds it can edit
animations as well. Simply open your <code class="highlighter-rouge">*.gif</code> animation file created in the
<a href="/2016/01/18/recording-screencast-in-linux/">recording session</a>
in Gimp.</p>
<p>Every frame in the animation is loaded as a separate image layer. This looks
a bit strange at first sight but I guess this was the only way how to
add animation support into a static image editor.</p>
<p>The animation is played from the bottom layer to the top.</p>
<p><img src="/images/editing_screencast/init.png" alt="" /></p>
<h2 id="the-initial-step">The Initial Step</h2>
<p>The very first step you should do after opening the animation image is to
<em>unoptimize</em> it. To store the animation efficiently the GIF files usually
store only the changes between the frames. Usually only very small part
of the image changes and it does not make sense to repeat the unchanges parts
in every frame and wasting space.</p>
<p>However, this makes editing more difficult as each frame (layer) only contains
the changes, you cannot see what will be actually displayed on the screen
and editing is almost impossible.</p>
<p>Fortunately Gimp can expand the diff frames to full images, simply use the
<em>Filters</em> ➞ <em>Animation</em> ➞ <em>Unoptimize</em> menu. It will create a new image with
full frames, you can close the original image now.</p>
<p><img src="/images/editing_screencast/animation_menu.png" alt="" /></p>
<h2 id="displaying-separate-frames">Displaying Separate Frames</h2>
<p>As already mentioned, each frame is loaded into a separate layer. If you want
to see a frame you need to select only that frame otherwise the later frames
(the layers above) will cover it.</p>
<p>Press <code class="highlighter-rouge">Shift</code> and click the <em>eye</em> icon in the layer window to display only that
frame.</p>
<p><img src="/images/editing_screencast/show_layer.png" alt="" /></p>
<h2 id="cutting-the-animation">Cutting the Animation</h2>
<p>If you need to remove some frames at the begining or the end then simply remove
the not needed layers from the image.</p>
<p>Select the layer and press the trash bin icon in the left bottom corner in the
layer .</p>
<h2 id="changing-the-timing">Changing the Timing</h2>
<p>As you have probably already noticed the layer names contain special tags at
the end like <code class="highlighter-rouge">(180ms)(replace)</code>. These tags specify the animation properties of
the frame.</p>
<p>The first value in the parenthesis defines how long is this frame displayed
before displaying the next frame. The second value defines how the frame should
be displayed.</p>
<h2 id="cropping-the-image-area">Cropping the Image Area</h2>
<p>If you need to crop the image area e.g. because you recorded a bit larger area
you can simply select the area with the rectangle selection tool and use
<em>Image</em> ➞ <em>Crop to Selection</em> to crop all layers.</p>
<h2 id="adding-empty-frame">Adding Empty Frame</h2>
<p>Select the top layer and select <em>New Layer</em> from the mouse context menu
(right-click) or press the button in the left bottom corner of the layer window.</p>
<p>Do not forget to include the animation properties at the end of the layer name.</p>
<p><img src="/images/editing_screencast/new_frame.png" alt="" /></p>
<h2 id="preview">Preview</h2>
<p>You can preview the current state using <em>Filters</em> ➞ <em>Animation</em>
➞ <em>Playback</em> menu. Exporting the final animation is not trivial so you should
check the final animation before exporting it.</p>
<h2 id="exporting-the-final-animation">Exporting the Final Animation</h2>
<p>At first you need to optimize the animation again. Use <em>Filters</em> ➞ <em>Animation</em>
➞ <em>Optimize (GIF)</em> menu to remove the repeating parts of images. This is
basically the opposite process to the initial <em>unoptimize</em> step.</p>
<p>Then we can finally export the animation to a GIF file. Use <em>File</em> ➞
<em>Export as …</em> menu item, then write the target file name. Do not forget
to set the <code class="highlighter-rouge">.gif</code> suffix.</p>
<p><img src="/images/editing_screencast/export.png" alt="" /></p>
<p>The most important step here is check the <em>As animation</em> option which is by
default unchecked. Then press <em>Export</em> and that’s it!</p>
<p>The result could look like this:</p>
<p><img src="/images/editing_screencast/final.gif" alt="" /></p>
<h2 id="final-words">Final Words</h2>
<p>At the very end check the file size before uploading to web. The animation
should be rather small, usually less than 500kB. Do not use several megabytes
large files, that is probably too much (esp. for mobile devices).</p>Editing ScreencastOvercommit? Overcommit!2016-06-06T00:00:00+00:002016-06-06T00:00:00+00:00https://blog.ladslezak.cz/2016/06/06/overcommit<p>Git itself provides support for running <a href="https://git-scm.com/docs/githooks">hook scripts</a> at many events. It would be nice to run
some checks automatically, for example when creating a commit.</p>
<p>The problem is not how to run the checks but how to integrate them,
make them fast and safe, provide a nice output, etc…</p>
<h1 id="overcommit">Overcommit</h1>
<p>By chance I came across <a href="https://github.com/brigade/overcommit">Overcommit</a>,
a nice framework for running Git hooks.</p>
<p>It provides a wide variety of predefined checks which are easy to use. I found
some of them really useful:</p>
<ul>
<li>
<p>Forbid commits to blacklisted branches - e.g. you can forbid direct commits to
<code class="highlighter-rouge">master</code> because you want to use Pull Requests. This can also avoid mistakes,
e.g. once I did a typo in <code class="highlighter-rouge">git checkout -b</code> and overlooked that.
The following <code class="highlighter-rouge">commit</code> and <code class="highlighter-rouge">push</code> went by mistake directly to <code class="highlighter-rouge">master</code>…</p>
</li>
<li>
<p>Run <a href="https://github.com/bbatsov/rubocop">Rubocop</a> automatically to avoid
fix up commits later. I see <em>“make Rubocop happy”</em> commits quite often in
YaST.</p>
</li>
<li>
<p>Run the tests locally before pushing the changes, that makes sure the tests
still pass. Sometimes you forget to run the tests or simply you are too
lazy to run them.</p>
</li>
<li>
<p>And last but not least it can check spelling of the commit messages to avoid
typos.</p>
</li>
</ul>
<p>For more details see the <a href="https://github.com/brigade/overcommit">Overcommit documentation</a>.</p>
<h2 id="speed">Speed?</h2>
<p>A very nice Overcommit feature is that it wants to be quick, your usual git
workflow should not be slowed down because of the hooks.</p>
<p>For example it tries running all hooks in parallel and the Rubocop hook checks
only the changed files, which speeds it up for large repositories a lot.
Running Rubocop over all source files in the YaST registration module takes
about 7 seconds on my machine, but when a single file is checked it takes
just a fraction of a second.</p>
<h2 id="installation">Installation</h2>
<p>I have prepared RPM packages with Overcommit, simply add the <a href="http://download.opensuse.org/repositories/YaST:/Head/">YaST:Head
repository</a> and run</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>zypper in "rubygem(overcommit)"
</code></pre></div></div>
<h2 id="initializing-the-hooks">Initializing the Hooks</h2>
<p>Now go to your Git repository and set up the hooks:</p>
<ul>
<li>Use <code class="highlighter-rouge">overcommit --install</code> to install the hooks.</li>
<li>Add the <code class="highlighter-rouge">.overcommit.yml</code> configuration file.</li>
<li>Run <code class="highlighter-rouge">overcommit --sign</code> to sign the current configuration. You will need to
run it again whenever the config files is changed.
See the <a href="https://github.com/brigade/overcommit#security">Security section</a>
in the documentation for more details.</li>
</ul>
<p>You need to install Overcommit in each Git repository separately, but it is
possible to <a href="https://github.com/brigade/overcommit#automatically-install-overcommit-hooks">automate the setup</a>.</p>
<h2 id="using-the-hooks">Using the Hooks</h2>
<p>Then you use the <code class="highlighter-rouge">git</code> commands as usually, you do not need to change anything
to run the hooks. You will just see the results of the Overcommit hooks
in the output.</p>
<p>If a hook fails the whole <code class="highlighter-rouge">git</code> command fails. So if for example the Rubocop
hook fails when creating a commit then the commit is not created and you can fix
the problem early.</p>
<p>If for some reason you want to temporarily disable the Overcommit checks run
the <code class="highlighter-rouge">git</code> command with <code class="highlighter-rouge">OVERCOMMIT_DISABLE=1</code> environment setting.</p>
<h2 id="example-configuration">Example Configuration</h2>
<p>Here is an example <code class="highlighter-rouge">.overcommit.yml</code> file proposed for YaST:</p>
<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">PreCommit</span><span class="pi">:</span>
<span class="c1"># do not commit directly to these branches, use Pull Requests!</span>
<span class="na">ForbiddenBranches</span><span class="pi">:</span>
<span class="na">enabled</span><span class="pi">:</span> <span class="no">true</span>
<span class="na">branch_patterns</span><span class="pi">:</span>
<span class="pi">-</span> <span class="s">master</span>
<span class="pi">-</span> <span class="s">openSUSE-*</span>
<span class="pi">-</span> <span class="s">SLE-10-*</span>
<span class="pi">-</span> <span class="s">Code-11*</span>
<span class="pi">-</span> <span class="s">SLE-12-*</span>
<span class="na">RuboCop</span><span class="pi">:</span>
<span class="na">enabled</span><span class="pi">:</span> <span class="no">true</span>
<span class="c1"># treat all warnings as failures</span>
<span class="na">on_warn</span><span class="pi">:</span> <span class="s">fail</span>
<span class="na">CommitMsg</span><span class="pi">:</span>
<span class="na">SpellCheck</span><span class="pi">:</span>
<span class="na">enabled</span><span class="pi">:</span> <span class="no">true</span>
<span class="c1"># force using the English dictionary</span>
<span class="na">env</span><span class="pi">:</span>
<span class="na">LC_ALL</span><span class="pi">:</span> <span class="s">en_US.UTF-8</span>
<span class="na">PrePush</span><span class="pi">:</span>
<span class="na">RSpec</span><span class="pi">:</span>
<span class="na">enabled</span><span class="pi">:</span> <span class="no">true</span>
<span class="na">command</span><span class="pi">:</span> <span class="pi">[</span> <span class="s2">"</span><span class="s">rake"</span><span class="pi">,</span> <span class="s2">"</span><span class="s">test:unit"</span> <span class="pi">]</span>
<span class="c1"># do not fail because of translations</span>
<span class="na">env</span><span class="pi">:</span>
<span class="na">LC_ALL</span><span class="pi">:</span> <span class="s">en_US.UTF-8</span>
</code></pre></div></div>
<h2 id="example-session">Example Session</h2>
<p>I recorded an example session to show the mentioned Overcommit features in
action in the registration repository:</p>
<p><img src="/images/overcommit_screencast.gif" alt="Overcommit screencast" /></p>
<p>It looks nice, isn’t it? (If you like the git status in the bash prompt
check this <a href="/2015/02/09/git-status-in-bash-prompt-bash-git/">older post</a>).</p>
<h2 id="conclusion">Conclusion</h2>
<p>It looks very promising and it could catch some obvious mistakes early, I will
give it a try. I will try using it for some time and then we will see if it
could be used in YaST more widely.</p>Git itself provides support for running hook scripts at many events. It would be nice to run some checks automatically, for example when creating a commit.