Architecture

Fuego consists of a continuous integration system, along with some pre-packaged test programs and a shell-based test harness, running in a Docker container.

Here's a diagram with an overview of Fuego elements:

Major elements [edit section]

The major elements in the Fuego architecture are:

Jenkins [edit section]

The main interface for Fuego is provided by the Jenkins continuous integration system.

The basic function of Jenkins is to automatically launch test jobs, usually in response to changes in the software. However, it can launch test jobs based on a variety of triggers, including when a user manually schedules a test to run.

Jenkins is too big a system to describe in detail here, but it has many features and is very popular. It has an ecosystem of plugins for all kinds of extended functionality, such as integration with different source code management systems, results plotting, e-mail notifications of regressions, and more.

Fuego installs several plugins that are used by various aspects of the system.

Jenkins is used to:

Note that the interface between Jenkins and the test programs is provided by a set of scripts (one per test, along with a set of scripts that comprise the core of the system) written in shell script language.

The interface between Jenkins and these core scripts is documented at Core interfaces.

This overall architecture means that when items are added into the system (for example boards, toolchains, or tests), information has to be supplied to both systems (the Jenkins system and the core script system).

Pre-packaged tests [edit section]

Abstraction scripts [edit section]

Fuego uses a set of shell script fragments to support abstractions for

Container [edit section]

By default, Fuego runs inside a Docker container. This provides two benefits:

Hardware configuration [edit section]

Fuego supports testing of embedded Linux by fully supporting a host/target configuration for building, deploying and executing tests.

Many Linux test systems assume that the system-under-test is a full desktop or server system, with sufficient horsepower to build tests and run them locally. Fuego assumes the opposite - that embedded targets will be underpowered and may not have the normal complement of utilities and tools available for performing tests

Jenkins operations [edit section]

How does Jenkins work?

Fuego operations [edit section]

How do the Fuego scripts work?

Test execution [edit section]

expanded script generation [edit section]

fuego test phases [edit section]

A test execution in fuego runs through several phases, some of which are optional, depending on the test.

The test phases are:

Each of these are described below the diagram.

pre_test [edit section]

The pre_test phase consists of making sure the target is alive, and preparing the workspace for the test. In this phase test directories are created, and the firmware (a string describing the software on the target) are collected.

The 'before' syslog is created, and filesystems are synced and buffer caches dropped, in preparation for any filesystem tests.

build [edit section]

During this phase, the test program source is installed on the host (inside the container), and the software for the test is actually built. The toolchain specified by PLATFORM is used to build the software.

This phase is split into multiple parts:

deploy [edit section]

The purpose of this phase is to copy the test programs, and any required supporting files, to the target.

This consists of 3 sub-phases:

run [edit section]

In this phase the test program on the target is actually executed.

This executes the 'test_run' function defined in the base script for the test, which can consist of anything. Usually, however, it runs the test program with any needed parameters (as specified by the test specs and test plans).

The test execution is usually performed with the 'report' command, which will collect the standard out from the command execution on the target, and save that as the testlog for the test. Note that the testlog is saved on the target, but not yet transferred to the host.

get_testlog [edit section]

In this phase, the test log is retrieved from the target and stored on the host. Note that for functional tests, this is a separate step. But for benchmark tests, this operation in included in the processing phase. (This is an internal detail that should not be a consideration for most test developers.)

processing [edit section]

In the processing phase of the test, the results from the test log are evaluated. The test_processing function of the base test script is called.

For functional tests:

Usually, this phases consists of one or more calls to log_compare, to determine if a particular string occurs in the testlog. This phase determines whether the test passed or failed, and the expanded test script indicates this to the Jenkins interface.

For benchmarking tests:

This phase consists of parsing the testlog, using parser.py, and also running dataload.py.

post_test [edit section]

In this phase, cleanup of the test area is performed on the target, and system logs are downloaded from the target. A final analysis is done on the system logs.

phase relation to base script functions [edit section]

Some of the phases are automatically performed by fuego, and some end up calling a routine in the base script (or use data from the base script) to perform their actions. This table shows the relation between the phases and the data and routines that should be defined in the base script.

It also shows the most common command utilized by base script functions for this phase.

phase  ^ relationship to base script  ^ common operations  ^
pre_test (none)

build uses the 'tarfile' definition, calls 'test_build' patch, configure, make
deploy calls 'test_deploy' put
run calls 'test_run' assert_define, report
get_testlog (none)

processing calls 'test_processing' log_compare
post_test (none)

Query

other scripts and programs [edit section]

Data Files [edit section]

There are data files with definitions for several things in the system.

The Jenkins interface needs to know about boards, running test processes (slaves), test definitions, and test results.

The fuego core needs to know about test definitions, boards, platforms (SDKS), test plans, and test specs.

The core executes the test script for a , in sequence, build the test program, bundle the test programs for the target, and build a custom shell script for a particular test run, execute that script.

The custom shell script should:

The custom shell script can handle host/target tests (because it runs on the host).

(That is, tests that involve actions on both the host and target.

to add a new test, the user defines several files and puts them into /home/jenkins/tests

The jenkins front end scans this directory for tests to show in the user interface. However, each test has to have a front-end entry to allow Jenkins to execute it. This front-end entry specifies the test script for the test.


Roles [edit section]

Human roles:

More Details [edit section]

specific questions to answer [edit section]

What happens when you click on the "run test" button:

Boards are defined in Jenkins in:/var/lib/jenkins/config.xml

These two environment variables are passed to the test agent, which is always "java -jar /home/jenkins/slave.jar"

Jenkins calls:

Some Jenkins notes: Jenkins stores its configuration in plain files under JENKINS_HOME You can edit the data in these files using the web interface, or from the command line using manual editing (and have the changes take affect at runtime by selecting "Reload configuration from disk".

By default, Jenkins assumes you are doing a continuous integration action of "build the product, then test the product". It has default support for Java projects.

Fuego seems to use distributed builds (configured in a master/slave fashion).

Jenkins home has (from 2007 docs):

The docker container interfaces to the outside host filesystem via the following links:

What are all the fields in the "configure target" dialog: Specifically:

The fuego-core repository has:

 engine
   overlays - has the base classes for fuego functions
     base - has core shell functions
     boards - has board definition files
     testplans - has json files for parameter specifications
     distribs - has shell functions related to the distro
     test_specs - has json files with parameter specifications
        Benchmark.foo.spec
        Functional.bar.spec
   scripts - has fuego scripts and programs
    (things like overlahs.sh, loggen.py, parser/common.py, ovgen.py, etc.
   slave.jar - ???
   test_run.properties - file containing some Jenkins variables
   tests - has a directory for each test
     Benchmark.foo
     Functional.bar
     LTP
     etc.
 jobs - has information about jobs, including config.xml  (front-end test definition)
 plugins-conf

What is groovy:

What plugins are installed with Jenkins in the JTA configuration?

Which of these did Cogent write?

What scriptler scripts are included in JTA?

What language are scriptler scripts in?

What is the Maven plugin for Jenkins?

Jenkins refers to a "slave" - what does this mean?

How the tests work [edit section]

A simple test that requires no building is Functional.bc

This runs a shell script on target to test the 'bc' program.

Functional.bc has the files:

    bc-script.sh
       declares "tarball=bc-script.tar.gz"
       defines shell functions:
         test_build - calls 'echo' (does nothing)
         test_deploy - calls 'put bc-device.sh'
         test_run - calls 'assert_define', 'report'
           report references bc-device.sh
         test_processing - calls 'log_compare'
           looking for "OK"
       sources $JTA_SCRIPTS_PATH/functional.sh
     bc-script.tar.gz
       bc-script/bc-device.sh

Variables used (in bc-script.sh):

   FUEGO_HOME
   TESTDIR
   FUNCTIONAL_BC_EXPR
   FUNCTIONAL_BC_RESULT


A simple test that requires simple building: Functional.synctest

This test tries to call fsync to write data to a file, but is interupted with a kill command during the fsync(). If the child dies before the fsync() completes, it is considered success.

It requires shared memory (shmget, shmat) and semaphore IPC (semget and semctl) support in the kernel.

Functional synctest has the files:

     synctest.sh
       declares "tarball=synctest.tar.gz"
       defines shell functions:
         test_build - calls 'make'
         test_deploy - calls 'put'
         test_run - calls 'assert_define', hd_test_mount_prepare, and 'report'
         test_processing - calls 'log_compare'
           looking for "PASS : sync interrupted"
       sources $JTA_SCRIPTS_PATH/functional.sh
     synctest.tar.gz
       synctest/synctest.c
       synctest/Makefile
     synctest_p.log
       has "PASS : sync interrupted"

Variables used (by synctest.sh)

   CFLAGS
   LDFLAGS
   CC
   LD
   FUEGO_HOME
   TESTDIR
   FUNCTIONAL_SYNCTEST_MOUNT_BLOCKDEV
   FUNCTIONAL_SYNCTEST_MOUNT_POINT
   FUNCTIONAL_SYNCTEST_LEN
   FUNCTIONAL_SYNCTEST_LOOP

(NOTE: could be improved by checking for CONFIG_SYSVIPC in /proc/config.gz
to verify that the required kernel features are present)

MOUNT_BLOCKDEV and MOUNT_POINT are used by 'hd_test_mount_prepare'
but are prefaced with FUNCTIONAL_SYNCTEST or BENCHMARK_BONNIE


-------
from clicking "Run Test", to executing code on the target...
config.xml has the slave command: /home/jenkins/slave.jar
  -> which is a link to /home/jenkins/jta/engine/slave.jar

overlays.sh has "run_python $OF_OVGEN ..."
where OF_OVGEN is set to "$JTA_SCRIPTS_PATH/ovgen/ovgen.py"

How is overlays.sh called?
  it is sourced by /home/jenkins/scripts/benchmarks.sh and
    /home/jenkins/scripts/functional.sh

functional.sh is sourced by each Funcational.foo script.


For Functional.synctest:
{{{
Functional.synctest/config.xml
  for the attribute <hudson.tasks.Shell> (in <builders>)
    <command>....
      souce $JTA_TESTS_PATH/$JOB_NAME/synctest.sh</command>

synctest.sh
  '. $JTA_SCRIPTS_PATH/functional.sh'
     'source $JTA_SCRIPTS_PATH/overlays.sh'
     'set_overlay_vars'
         (in overlays.sh)
         run_python $OF_OVGEN ($JTA_SCRIPTS_PATH/ovgen/ovgen.py) ...
                $OF_OUTPUT_FILE ($JTA_SCRIPTS_PATH/work/${NODE_NAME}_prolog.sh)
           generate xxx_prolog.sh
         SOURCE xxx_prolog.sh

     functions.sh pre_test()

     functions.sh build()
        ... test_build()

     functions.sh deploy()

     test_run()
       assert_define()
       functions.sh report()

NOTES about ovgen.py [edit section]

What does this program do?

Here is a sample command line from a test console output:

python /home/jenkins/scripts/ovgen/ovgen.py \
  --classdir /home/jenkins/overlays//base \
  --ovfiles /home/jenkins/overlays//distribs/nologger.dist /home/jenkins/overlays//boards/bbb.board \
  --testplan /home/jenkins/overlays//testplans/testplan_default.json \
  --specdir /home/jenkins/overlays//test_specs/ \
  --output /home/jenkins/work/bbb_prolog.sh

So, ovgen.py takes a classdir, a list of ovfiles a testplan and a specdir, and produces a xxx_prolog.sh file, which is then sourced by the main test script

Here is information about ovgen.py source:

Classes:
 OFClass
 OFLayer
 TestSpecs

Functions:
 parseOFVars - parse Overlay Framework variables and definitions
 parseVars - parse variables definitions
 parseFunctionBodyName
 parseFunction
 baseParseFunction
 parseBaseFile
 parseBaseDir
 parseInherit
 parseInclude
 parseLayerVarOverride
 parseLayerFuncOverride
 parseLayerVarDefinition
 parseLayerCapList - look for BOARD.CAP_LIST
 parseOverrideFile
 generateProlog
 generateSpec
 parseGenTestPlan
 parseSpec
 parseSpecDir
 run

Sample generated test script [edit section]

bbb_prolog.sh is 195 lines, and has the following vars and functions:

from class:base-distrib:
  ov_get_firmware()
  ov_rootfs_kill()
  ov_rootfs_drop_caches()
  ov_rootfs_oom()
  ov_rootfs_sync()
  ov_rootfs_reboot()
  ov_rootfs_state()
  ov_logger()
  ov_rootfs_logread()

from class:base-board:
 LTP_OPEN_POSIX_SUBTEST_COUNT_POS
 MMC_DEV
 SRV_IP
 SATA_DEV
 ...
 JTA_HOME
 IPADDR
 PLATFORM=""
 LOGIN
 PASSWORD
 TRANSPORT
 ov_transport_cmd()
 ov_transport_put()
 ov_transport_get()

from class:base-params:
 DEVICE
 PATH
 SSH
 SCP

from class:base-funcs:
 default_target_route_setup()

from testplan:default:
 BENCHMARK_DHRYSTONE_LOOPS
 BENCHMARK_<TESTNAME>_<VARNAME>
 ...
 FUNCTIONAL_<TESTNAME>_<VARNAME>

Logs [edit section]

When a test is executed, several different kinds of logs are generated: devlog, systemlogs, the testlogs, and the console log.

created by Jenkins [edit section]

created by expanded script [edit section]

Core scripts [edit section]

The main scripts that are sourced by a test script are:

These have the same pattern of operations in them:

functions available to test scripts: See Test Script APIs

notes about benchmark tests [edit section]

Benchmark tests must provide a reference.log file. This has a special syntax recognized by parser.py and the common.py library.

It consists of a series of lines, showing a name, test and value The syntax is: [<var-name>|<test>] <value>

An example is: [dhrystones|ge] 1

This says: the test is OK, if the value of 'dhrystones' is greater than or equal to 1.

(NOTE: this is an extremely weird syntax)

A reference.log file can have multiple tests.

Benchmark tests must provide a parser.py file, which extracts the (single value)? from the log data.

It does this by doing the following: import common as plib f = open(plib.CUR_LOG) lines = f.readlines() ((parse the data)) create a dictionary with a key and value, where the key matches the string in the reference.log file

The parser.py program builds a dictionary of values by parsing the log from the test (basically the test output). It then sends the dictionary, and the pattern for matching the reference log test criteria to the routine: common.py:process_data()

It defines ref_section_pat, and passes that to process_data() Here are the different patterns for ref_section_pat: 9 "\[[\w]+.[gle]{2}\]" 1 "\[\w*.[gle]{2}\]" 1 "^\[[\d\w_ .]+.[gle]{2}\]" 1 "^\[[\d\w_.-]+.[gle]{2}\]" 1 "^\[[\w\d&._/()]+.[gle]{2}\]" 4 "^\[[\w\d._]+.[gle]{2}\]" 2 "^\[[\w\d\s_.-]+.[gle]{2}\]" 3 "^\[[\w\d_ ./]+.[gle]{2}\]" 5 "^\[[\w\d_ .]+.[gle]{2}\]" 1 "^\[[\w\d_\- .]+.[gle]{2}\]" 1 "^\[[\w]+.[gle]{2}\]" 1 "^\[[\w_ .]+.[gle]{2}\]" Why are so many different ones needed?? Why couldn't the syntax be: <var-name> <test> <value> on one line?

How is benchmarking graphing done? [edit section]

See Benchmark parser notes

docker tips [edit section]

config.xml usage [edit section]

Here are the inotify events for a manual rename: ./ OPEN config.xml ./ ACCESS config.xml ./ CLOSE_NOWRITE,CLOSE config.xml ./ DELETE config.xml ./ CREATE config.xml