Site logo
Stories around the Genode Operating System RSS feed
Martin Stein avatar

Integrating and running automated tests - Part 2


In this article I'd like to give a very practical guide about how you can create, integrate and run your custom test scenarios on Genode. This is the second of two parts. If you have missed the first part, you may want to read it first.

Step 2: The source archives

Now that I have written my new my_expat test component, I could hastily try to run the Depot-Autopilot run script to evaluate it. But the script would reasonably complain as follows:

 Error: unable to guess version of 'test-my_expat' archive

This is because the Genode depot system wouldn't find any recipe to create an archive named test-my_expat. This archive is more precisely speaking a package that should contain the top level description of my test scenario - which ingredients are needed, how to integrate them, and how to interpret the test results. But actually I am missing even more, because the above mentioned test ingredients themselves must also be available as archives. Besides the my_expat component, I want, for example, also an Init component to act as runtime environment. And an archive for the my_expat component creates further dependencies. Because the component makes use of shared libraries like expat and I wouldn't want to mix a shared library in an archive of a component that uses it but rather have it in a dedicated archive. So, the link between my component and a ready-to-use test scenario can be depicted as a tree where the leafes are the actual content (e.g. source code):

 recipes/pkg/test-my_expat
   |
   +-> recipes/src/test-my_expat --+-> src/test/my_expat
   |                               |
   +-> recipes/src/expat           +-> recipes/api/expat -> ...
   |                               |
   +-> recipes/src/init            +-> recipes/api/libc -> ...
   |
   +-> ...

Fortunately, all archives that my test requires, except pkg/test-my_expat and src/test-my_expat, already exist. So, I start with the src/test-my_expat archive:

 cd <GENODE_DIR>
 mkdir -p repos/libports/recipes/src/test-my_expat
 vim repos/libports/recipes/src/test-my_expat/content.mk

The archive shall trigger the creation of the test-my_expat binary. In order to achive this, it needs, first of all, the corresponding target.mk file:

 # file repos/libports/recipes/src/test-my_expat/content.mk
 content:
   mkdir -p src/test/my_expat
   cp $(REP_DIR)/src/test/my_expat/target.mk src/test/my_expat/

Then I add a bogus initial version and hash:

 echo 1970-01-01 0 > repos/libports/recipes/src/test-my_expat/hash

And give the recipe a first try:

 tool/depot/create genodelabs/bin/x86_64/test-my_expat \
   CROSS_DEV_PREFIX=genode-x86- -j4 UPDATE_VERSIONS=1 FORCE=1

This silently fixes my bogus hash file and then complains about some missing library makefiles. Sidenote: If, at some point, the create tool doesn't act as described in this article, you may try to reset your depot directory and try again (be aware that this deletes all your previously created archives, so, you might want to do this in a temporary copy of your genode directory):

 rm -rf depot && git checkout HEAD depot

Now, back to the main track: I can see now that the source archive was created as expected but no binary could be compiled from this:

 ls depot/genodelabs/src/test-my_expat/<LATEST_VERSION>/src/test/expat
   target.mk
 ls depot/genodelabs/bin/x86_64/test-my_expat/<LATEST_VERSION>

I can solve the missing libraries by simply referencing the - as mentioned earlier - already existing API archives:

 export USED_APIS=libports/recipes/src/test-my_expat/used_apis
 echo base  >  $USED_APIS
 echo expat >> $USED_APIS
 echo posix >> $USED_APIS
 echo libc  >> $USED_APIS

When running the create command again, I get a bit further:

  ...
  Library ldso-startup
  Library expat
  Library ld
  Library libc
  Library posix
  Library libm
  Library base
  Program test/expat/test-my_expat
    LINK     test-my_expat
  genode-x86-g++: error: main.o: No such file or directory

Of course, I didn't copy the corresponding C++ file from test/my_expat to the source archive (I did this to ensure that I don't copy stuff to the source archive that isn't needed). Let's enhance the content.mk file accordingly:

 # file repos/libports/recipes/src/test-my_expat/content.mk
 content:
   mkdir -p src/test/my_expat
   cp $(REP_DIR)/src/test/my_expat/target.mk src/test/my_expat/
   cp $(REP_DIR)/src/test/my_expat/main.cc   src/test/my_expat/

Now, the create command produces the desired result:

 ls depot/genodelabs/bin/x86_64/test-my_expat/<LATEST_VERSION>
   test-my_expat

If you want to know more about archives and the depot, you may read this online documentation or section 5.5 in the Genode Foundations book.

Step 3: The package archives

Let's integrate the test into a package archive. First, I create the package skeleton:

 mkdir -p libports/recipes/pkg/test-my_expat
 echo 1970-01-01 0 > repos/libports/recipes/pkg/test-my_expat/hash
 echo "my expat test" > repos/libports/recipes/pkg/test-my_expat/README

Then, I declare which archives must be build for my test package:

 export ARCHIVES=repos/libports/recipes/pkg/test-my_expat/archives
 echo "_/src/init"          >  $ARCHIVES
 echo "_/src/test-my_expat" >> $ARCHIVES
 echo "_/src/expat"         >> $ARCHIVES
 echo "_/src/libc"          >> $ARCHIVES
 echo "_/src/vfs"           >> $ARCHIVES
 echo "_/src/posix"         >> $ARCHIVES

Finally, I have to describe the runtime of my test. This is done through the runtime XML file. The top level of this file describes the runtime component itself (Init in my case):

 <!-- excerpt from repos/libports/recipes/pkg/test-my_expat/runtime -->
 <runtime ram="32M" caps="1000" binary="init">
   ...
 </runtime>

Then, I add four sub-nodes:

 <!-- excerpt from repos/libports/recipes/pkg/test-my_expat/runtime -->
 <runtime ram="32M" caps="1000" binary="init">
   <config>   ... </config>
   <content>  ... </content>
   <requires> ... </requires>
   <events>   ... </events>
 </runtime>

Into the <config> sub-node, I put the Init configuration that causes the Init to start and connect the components for my test properly:

 <!-- excerpt from repos/libports/recipes/pkg/test-my_expat/runtime -->
 ...
 <config>
   <parent-provides>
     <service name="ROM"/>
     <service name="PD"/>
     <service name="RM"/>
     <service name="CPU"/>
     <service name="LOG"/>
     <service name="IO_PORT"/>
     <service name="IRQ"/>
     <service name="Timer"/>
   </parent-provides>
   <default-route>
     <any-service> <parent/> <any-child/> </any-service>
   </default-route>
   <default caps="200"/>
   <start name="test-my_expat">
     <resource name="RAM" quantum="2M"/>
     <config>
       <vfs>
         <inline name="config"><config>
             <test_tag test_attribute="test_value" />
           </config>
         </inline>
         <dir name="dev"> <log/> </dir>
       </vfs>
       <libc stdout="/dev/log"/>
     </config>
   </start>
 </config>

While creating this configuration, I already recognize, that later the Depot Autopilot must make some files - here the expat program and library binaries - availabe to the runtime. This is what the <content> node is for:

 <!-- excerpt from repos/libports/recipes/pkg/test-my_expat/runtime -->
 ...
 <content>
   <rom label="ld.lib.so"/>
   <rom label="libc.lib.so"/>
   <rom label="libm.lib.so"/>
   <rom label="posix.lib.so"/>
   <rom label="vfs.lib.so"/>
   <rom label="expat.lib.so"/>
   <rom label="test-my_expat"/>
 </content>

As you may have noticed in the configuration, instead of spawning a timer driver, I've routed the Timer service to the parent. As it would be cumbersome to start required drivers for each test anew, the Depot-Autopilot run-script starts drivers outside the tests and hands-in the required sessions. Thus, I have to tell the outer world that I need a Timer via the <requires> tag:

 <!-- excerpt from repos/libports/recipes/pkg/test-my_expat/runtime -->
 ...
 <requires> <timer/> </requires>

Last but not least, I want to tell the Depot-Autopilot component how to evaluate the result of my test. I can tell it to inspect the LOG sessions for bad or good string patterns and install a timeout to make sure that my test doesn't block the whole system. All this goes to the <events> tag:

 <!-- excerpt from repos/libports/recipes/pkg/test-my_expat/runtime -->
 ...
 <events>
   <log     meaning="succeeded">
     [init -> test-my_expat]  start of element: config*
     [init -> test-my_expat]  start of element: test_tag*
     [init -> test-my_expat]  attribute: name='test_attribute', value='test_value'*
     [init -> test-my_expat]  end of element: test_tag*
     [init -> test-my_expat]  end of element: config*
     "test-my_expat" exited with exit value 0
   </log>
   <log     meaning="failed">Error: </log>
   <timeout meaning="failed" sec="10" />
 </events>

This is all. I can successfully create my package now:

 tool/depot/create genodelabs/pkg/x86_64/test-my_expat \
   CROSS_DEV_PREFIX=genode-x86- -j4 UPDATE_VERSIONS=1 FORCE=1
 ls depot/genodelabs/pkg/test-my_expat/<LATEST_VERSION>
   archives  README  runtime

Step 4: Running the Depot Autopilot

First, I'd like to give my test a try without being disturbed by the whole battery of available tests:

 cd <BUILD_DIRECTORY>
 KERNEL=nova TEST_PKGS=test-my_expat TEST_SRCS= make run/depot_autopilot

Most propably, this will complain about some missing archives and provide a depot/create command line that will fix the problem. I do as advised (for more convenience, I also add UPDATE_VERSIONS=1 to the command line) and as soon as all archives are built, I retry to run the Depot-Autopilot command. The scenario starts and through the serial output the Depot Autopilot tells me something like:

 --- Run "test-my_expat" (max 10 sec) ---

 4.340 [init -> test-my_expat] Warning: rtc not configured, returning 0
 4.396 [init -> test-my_expat]  start of element: config
 4.406 [init -> test-my_expat]  start of element: test_tag
 4.412 [init -> test-my_expat]  attribute: name='test_attribute', value='test_value'
 4.420 [init -> test-my_expat]  end of element: test_tag
 4.429 [init -> test-my_expat]  end of element: config
 4.644 [init] child "test-my_expat" exited with exit value 0

  test-my_expat                   ok         4.644  log "[init -> test-my_expat]  start o ..."

 --- Finished after 6.358 sec ---

  test-my_expat                   ok         4.644  log "[init -> test-my_expat]  start o ..."

 succeeded: 1 failed: 0 skipped: 0

I might want to debug the test. In this case, the additional environment variables considered by the run script become helpful:

 KERNEL=nova \
   TEST_PKGS=test-my_expat \
   TEST_SRCS= \
   TEST_BUILDS="test/expat init" \
   TEST_MODULES="test-expat init expat.lib.so" \
   TEST_REPEAT=until_failed make
   run/depot_autopilot

You can read about all this in more detail in the README file of the Depot Autopilot:

 <GENODE_DIR>/repos/gems/src/app/depot_autopilot/README

In the end, I can add my test permanently to the Depot-Autopilot run-script by enhancing the default packages list:

 # excerpt from repos/gems/run/depot_autopilot.run
 ...
 set default_test_pkgs {
   test-my_expat
   ...
 }