Automation and testing

The YaST rake tasks

During the introduction section of this tutorial we used a rake task to execute our YaST module. This and many other tasks are provided by the yast-rake gem. We already have the gem installed in our system, so adding a very simple Rakefile will immediately supercharge our prototype. To checkout that file, simply run:

git checkout rakefile

As you can see, the new Rakefile contains only the following line.

require "yast/rake"

That's enough to add all the YaST rake tasks to our module. As usual, you can ask rake to list all the available tasks.

rake -T

Now, let's try some of the useful available tasks. But first we need to install "rubygem-gettext".

ruby_version=$(ruby -e "puts RbConfig::CONFIG['ruby_version']")
zypper install -C "rubygem(ruby:$ruby_version:gettext)"

Now we can use this three very simple but handy tasks to check our code.

rake check:license
rake check:pot
rake check:syntax

As you already know, there are also tasks for running the software. One for executing our mockup client and another one for invoking a Ruby interpreter with our code already pre-loaded.

rake run
rake console

You can paste the example code about Journalctl:Query usage at the end of the previous step directly into the interpreter opened by "rake console" and everything will work out of the box.

As mentioned, we will explore other tasks (mostly related to packaging) in an upcoming step, but first we need to finish our prototype.

The RSpec tests

Of course, no Ruby class is complete without the corresponding unit tests. Thus we need unit tests for our Entry and Query classes before proceeding with any further action. As many other Ruby-based projects, YaST uses RSpec (version 2 at the time of writing) for unit testing. In the guidelines section of the YaST home page you will find some recommendations for writing YaST tests.

To checkout some example unit tests for our models run the following command.

git checkout unit_tests

Before running the tests, make sure you have RSpec installed. You can install it using zypper:

ruby_version=$(ruby -e "puts RbConfig::CONFIG['ruby_version']")
zypper install -C "rubygem(ruby:$ruby_version:rspec)"

Under the "test" directory you will find the corresponding RSpec unit tests for Entry and Query. Those tests are just a simple example and are not completely exhaustive. Feel free to implement any additional check. You can run the tests using the following YaST rake task.

rake test:unit

This will execute RSpec with all the files located in the "test" directory with a name ending with "_test.rb" or "_spec.rb". This task is crucial in the development work-flow of any YaST module. It's always executed before building a package and before integrating any pull request into the repositories, so better make sure that your module always contains a comprehensive and updated suite of unit tests. Moreover, the unit tests are considered by some YaST developers as the only authoritative documentation for the programming interface of a class (since they are by definition always up-to-date).

Let's take a look to the implementation of the provided example test suite. To ensure that all the classes, modules and clients defined in our code are available during the tests (in a predictable path), we use the same trick that we used for running the mockup on previous steps - setting the Y2DIR environment variable. Thus, the first line of the spec_helper.rb file included in every test looks like this.

ENV["Y2DIR"] = File.expand_path("../../src", __FILE__)

When writing YaST tests is very important to stub the usage of SCR agents. Otherwise, they will perform their operations on the system running the tests, which can lead to inconsistent results and even big troubles if these operations involve something more dangerous than reading the systemd journal. There are plans to include that functionality into the YaST Ruby bindings in the near future and offer convenient RSpec helpers to deal with SCR. Meanwhile, the proposal in this tutorial shows a simple solution for the particular case of stubbing the calls to journalctl (and other commands) using the ".target.bash_output" path.

# Stub a command execution
def allow_to_execute(cmd)
  path = Yast::Path.new(".target.bash_output")
  allow(Yast::SCR).to receive(:Execute).with(path, cmd)
end

The following code extracted from entry_test.rb shows how the above helper method can then be used (together with other methods defined in spec_helper.rb) to write safe specs that simulate the underlying system.

describe ".all" do
  subject { Journalctl::Entry.all }
  # Stub journalctl call
  before do
    allow_to_execute(/LANG=C journalctl/).and_return(result)
  end

  context "when journalctl reports 'Failed to determine timestamp'" do
    let(:result) {
      journalctl_error("Failed to determine timestamp: Cannot assign")
    }

    it "returns an empty array" do
      expect(subject).to eq([])
    end
  end
end

Almost there

Now we can finally say that we have all the pieces: tested models, a user interface and all the necessary tools. Only one step away from our first fully-functional YaST module (and two steps away from the end of the training).