fat Architecture

What is fat

'fat' is the Framework for Automated Testing and was created to fill the need for automated reliable testing of software and firmware products on a regular basis.

The goal of this product is to have a fully built and defined set of tools that can be 'dropped' into a software project environment and immediately enhance the reliablity of said product by giving a defined and simplistic interface for that project's engineers to use in creating a full bodied test suite.



How it works

The framework consists of two framework files and as many module files as desired.

The framework files:
The framework itself can be viewed as the following components:

Users view:

Data flow

Main()

The framework when executed directly (considered normal usage) has its main routine called.
The Main routine will allow for several arguments:

-h|-? help, prints the usage/help menu
-i <script|gui|cli> user's input interface to use (script is a non-interactive implementation and is default)
*** default is script***
-o <file='filename'|stdout|echo='filename'> output log type will define which type of log to write to 'file' will write information to the file filename, 'screen' will send it to the stdout, and 'echo' will write to 'filename' and echo to the screen
*** default is screen ***
-t <target> the name of the target as it will go to the TargetInterface object, i.e. -t flex4, or -t hl
*** -t is a required argument ***
-m <module> the name of a specific module to run, overrides the frameworks normal directive to run all tests as returned by get_tests() function in testconfig.py
-D debug, allows printing/logging of debug information for the test
-- args will tell the TestWrapper to take the quoted arguments following the '--' and give them to both the TestModule being run as well as the TestInterface in testconfig.py


A typical usage of fat.py being called directly would be:
     python fat.py -t flex3 -- --flex b --file /tmp/all-current.fm
...or if we wanted to install the all-current.fm file and write to a log file only, we would change our output and specify a module with the -m argument:
     python fat.py -o file=install-log.txt -t flex3 -m flexinstall -- --flex b --file /tmp/all-current.fm

Arguments following the -- argument are removed and put into a different list. This list is than provided to the TestWrapper which in turn supplies it to the Input Interface, and the Target Interface components for use if desired.

This list is useful for specifying certain arguments or flags unique to the particular test implementation without cluttering the framework with argument tests that only apply to a specific product.


Object Initialization



The Input Interface

The input interface is given to the TestWrapper as the 'input' argument of its __init__() method. It will normally be provided to the framework by the -i argument given to fat.py when executed directly.

All input interfaces are derived from the base class UserInterface and have the methods __init__(), run() , and fini().

From the UserInterface base class we have pre-defined several sub-classes that will be most commonly used:

Class diagram:


Input Interface Interaction

The input interface object is given to the TestWrapper as an argument, but once the TestWrapper has the other frame work components in place, the input interface will be given control of the program by the TestWrapper via the 'run()' method. At that time all data is handed to the input interface. At the completion of its run() method the input interface returns (without status code) control to the TestWrapper which calls everyone's fini() method and cleans up.

The Input Interface makes use of some of the output objects hidden methods to allow for more data collection to occur:

Input Interface program flow

Beginning with the TestWrapper's call to our run() method:
  1. We save our arguments:
  2. For each module in the self.testmodules list we do the following:
    1. log a module starting with module name
    2. attempt to import module, report our success or failure, and log the import status
    3. create an instance of module.TestModule class giving its __init__() method our logger, our interface, and our arguments
    4. call the TestModule objects run() method
    5. we don't watch for the TestModules return status and leave it up to the module to define its internal tests using the logging mechanisms teststart() method and passed(), failed(), and fatal() to determine the beginning and outcome of a particular test.
      We do, however, protect against exceptions raised, in particular if the module raises a FatalError type we will take that as a flag to cease all testing and not continue with any further modules.
  3. Once all modules are complete, we call our output object (self.log)'s dump() method to get the entire tests final results.
  4. We exit the run() method returning control to the TestWrapper.

Input Interface Interaction Diagram:


The Output Interface

The output interface is a collection of objects controlled through the logging mechanism's Logger object.
The Logger receives all calls from the input interface and the user created test modules and writes them to the actual output device, i.e., stdout or to file. The Logger also sends information to the Statistics collection object where appropriate.


The componenets of the output/logging mechanism are obscured from the user and are not meant to be directly accessed. They are:

The LogTarget is a base class used in creating a defined interface for objects that are to act as recipients of the Logger objects calls. It is required to have the following methods:

In addition, the writable context that is returned by get_fd() will require the following methods be available, though their implementation is case dependent:

The log target is created by the user and given as an argument to the TestWrapper. In the case of normal usage, the -o argument given to fat will specify one of three predefined output classes:

The FileLog and ScreenLog classes are children of the LogTarget class. FileLog will take a filename argument, create the file and open it with w+ permissions. The file descriptor returned by get_fd is a Python File object with all normal File I/O methods available. The ScreenLog class simply returns sys.stdout as the file descriptor which also has the required, write,flush, and close options, though close is overloaded to be ignored (to avoid the user closing stdout). The EchoLog object is a wrapper object that creates both a FileLog and ScreenLog object. It has write, flush, and close methods available and returns itself from the get_fd() method. It will then forward all output to both of its objects.

The Logger is the wrapper object and is given its log target as an argument by the TestWrapper when it is initialized. It then creates an instance of the StatCollect object to gather information about the types of calls it has recieved. When any of its methods are invoked the Logger will write to the log target and then forward information to the StatCollect object when appropriate. The logger sends all of its writing tasks to a single internal method add_entry() which does all the writing to the Log Target (using write()). The Logger has the following hidden methods available that are only used by the Input Interface:

The following interfaces are provided to the user level module:
The logger also has a fini() method which does a final write to the log and then closes the file descriptor.

The StatCollect class stores a list of dictionarys relating to a given module and tracks import outcome, errors, number of tests, warnings, passes, fatalities, failures, and time (as tracked by its Timer object). The data is stored in the following format:
     {"Test-Module":,"Import":, "RunTime":,"Errors":,"Passes":,"Failures":, "FatalErrors":,"Warnings":,"TotalTests":,"Final":}
The entire list of dictionarys is retrieved by the Logger via the dump() method and written to the log target.

The Timer class is used as a utillity by the StatCollect class to gather elapsed time for the module to run. It captures time when its start() and stop() methods are called and then returns the difference in a various formats as required.

Output Class diagrams


The Target Interface and testconfig.py

The target interface is contained within the testconfig.py module. Its implementation is target specific making it impossible to fully describe. The following items are required to be in testconfig.py:

***The two framework exception classes are stored in the testconfig.py module rather than in fat.py itself because of a limitation discovered in Python that wouldn't allow the class to be caught in fat if it was imported into the test modules namespace from fat.py***


The TestInterface class will need to provide a method for the individual TestModule objects to access the target (firmware/hardware/software).
It will be instantiated by the TestWrapper and the object given to the TestModules as self.ifc (interface) so the writers of those modules will need to know how to use that object


The Test Wrapper

The TestWrapper class brings all the components together and handles final cleanup.
The TestWrapper object is created with the following arguments:

The TestWrapper uses the input argument and creates a Logger object that will be provided to the UserInterface and the TargetInterface, It will than import the testconfig.py module and create an instance of its TargetInterface class giving it the target string and the list of arguments. The global function get_tests in the testconfig.py module is also called to get a list of test modules to import and run. This command is called with the a single argument of our self.args (arguments from the commandline).

When the TestWrapper's run() method is called it in turn calls the run() method of the UserInterface and giving it the Logger object, the list of args, a list of modules to run, and the TargetInterface object.

Once the UserInterface returns control to the TestWrapper we call our own fini() method. Our fini() method in turn calls the fini() for each of the following in order:

  1. TargetInterface object
  2. Logger object
  3. Output object - the actual Log target
  4. Input object - the UserInterface
It than returns to the fat.py Main routine.

TestWrapper relationships


The User Created Test Modules

For details on how to implement a Test Module for use in the framework please see our fat overview
How the module interacts with other modules follows the following flow:
  1. this module is imported by the UserInterface (input) object
  2. our TestModule class is instantiated and the following arguments are given to our constructor:
  3. the TestModule's run() method is called by the UserInterface
  4. For each test done in our run method we start it by calling our logging mechanism's teststart() method.
  5. As desired we call the logging mechanism's error, warn, debug, and info methods to log information about the test.
  6. When test is completed we call one of our logging mechanism's completion calls - passed, fail, or fatal
  7. Once all internal testing is complete, we return control to the UserInterface that called us.
  8. If I've put the system into a condition that precludes any other test from running once I'm done, a FatalError (as imported from testconfig.py) is thrown to flag the framework to stop testing.

How the Module interacts from its view:





End of Document