Providing plug-in support:

Typically, a test program is written to perform certain basic tests on a test
machine.  For example, one might write an "obmc_boot" program that performs
various boot tests on the Open BMC machine.

Experience has shown that over time, additional testing needs often arise.
Examples of such additional testing needs might include:

- Data base logging of results
- Performance measurements
- Memory leak analysis
- Hardware verification
- Error log (sels) analysis
- SOL_console

The developer could add additional parms to obmc_boot and likewise add
supporting code in obmc_boot each time a need arises.  Users would employ
these new functions as follows:

obmc_boot --perf=1 --mem_leak=1 --db_logging=1 --db_userid=xxxx

However, another option would be to add general-purpose plug-in support to
obmc_boot.  This would allow the user to indicate to obmc_boot which plug-in
packages it ought to run.  Such plug-in packages could be written in any
langauge whatsoever: Robot, python, bash, perl, C++.

An example call to obmc_boot would then look something like this:

obmc_boot --plug_in_dir_paths="Perf:Mem_leak:DB_logging"

Now all the obmc_boot developer needs to do is call the plug-in processing
module (process_plug_in_packages.py) at various call points which are agreed
upon by the obmc_boot developer and the plug-in developers.  Example call
points which can be implemented are:

setup - Called at the start of obmc_boot
pre_boot - Called before each boot test initiated by obmc_boot
post_boot - Called after each boot test initiated by obmc_boot
cleanup - Called at the end of obmc_boot

This allows the choice of options to be passed as data to obmc_boot.  The
advantages of this approach are:
- Much less maintenance of the original test program (obmc_boot).
- Since plug-ins are separate from the main test program, users are free to
have plug-ins that suit their environments.  One user may wish to log results
to a database that is of no interest to the rest of the world.  Such a plug-in
can be written and need never be pushed to gerrit/github.
- One can even write temporary plug-ins designed just to collect data or stop
when a particular defect occurs.

In our current environment, the concept has proven exceedingly useful.  We
have over 40 permanent plug-ins and in our temp plug-in directory, we still
have over 80 plug-ins.

Change-Id: Iee0ea950cffaef202d56da4dae7c044b6366a59c
Signed-off-by: Michael Walsh <micwalsh@us.ibm.com>
diff --git a/lib/gen_arg.py b/lib/gen_arg.py
new file mode 100755
index 0000000..1a57411
--- /dev/null
+++ b/lib/gen_arg.py
@@ -0,0 +1,216 @@
+#!/usr/bin/env python
+
+r"""
+This module provides valuable argument processing functions like
+gen_get_options and sprint_args.
+"""
+
+import sys
+import __builtin__
+import atexit
+import signal
+import argparse
+
+import gen_print as gp
+
+default_string = '  The default value is "%(default)s".'
+
+
+###############################################################################
+def gen_get_options(parser,
+                    stock_list=[]):
+
+    r"""
+    Parse the command line arguments using the parser object passed and return
+    True/False (i.e. pass/fail).  Also set the following built in values:
+
+    __builtin__.quiet      This value is used by the qprint functions.
+    __builtin__.test_mode  This value is used by command processing functions.
+    __builtin__.debug      This value is used by the dprint functions.
+    __builtin__.arg_obj    This value is used by print_program_header, etc.
+    __builtin__.parser     This value is used by print_program_header, etc.
+
+    Description of arguments:
+    parser                          A parser object.  See argparse module
+                                    documentation for details.
+    stock_list                      The caller can use this parameter to
+                                    request certain stock parameters offered
+                                    by this function.  For example, this
+                                    function will define a "quiet" option upon
+                                    request.  This includes stop help text and
+                                    parm checking.  The stock_list is a list
+                                    of tuples each of which consists of an
+                                    arg_name and a default value.  Example:
+                                    stock_list = [("test_mode", 0), ("quiet",
+                                    1), ("debug", 0)]
+    """
+
+    # This is a list of stock parms that we support.
+    master_stock_list = ["quiet", "test_mode", "debug", "loglevel"]
+
+    # Process stock_list.
+    for ix in range(0, len(stock_list)):
+        if len(stock_list[ix]) < 1:
+            gp.print_error_report("Programmer error - stock_list[" + str(ix) +
+                                  "] is supposed to be a tuple containing at" +
+                                  " least one element which is the name of" +
+                                  " the desired stock parameter:\n" +
+                                  gp.sprint_var(stock_list))
+            return False
+        if type(stock_list[ix]) is tuple:
+            arg_name = stock_list[ix][0]
+            default = stock_list[ix][1]
+        else:
+            arg_name = stock_list[ix]
+            default = None
+
+        if arg_name not in master_stock_list:
+            gp.pvar(arg_name)
+            gp.print_error_report("Programmer error - \"" + arg_name +
+                                  "\" not found found in stock list:\n" +
+                                  gp.sprint_var(master_stock_list))
+            return False
+
+        if arg_name == "quiet":
+            if default is None:
+                default = 0
+            parser.add_argument(
+                '--quiet',
+                default=default,
+                type=int,
+                choices=[1, 0],
+                help='If this parameter is set to "1", %(prog)s' +
+                     ' will print only essential information, i.e. it will' +
+                     ' not echo parameters, echo commands, print the total' +
+                     ' run time, etc.' + default_string
+                )
+        elif arg_name == "test_mode":
+            if default is None:
+                default = 0
+            parser.add_argument(
+                '--test_mode',
+                default=default,
+                type=int,
+                choices=[1, 0],
+                help='This means that %(prog)s should go through all the' +
+                     ' motions but not actually do anything substantial.' +
+                     '  This is mainly to be used by the developer of' +
+                     ' %(prog)s.' + default_string
+                )
+        elif arg_name == "debug":
+            if default is None:
+                default = 0
+            parser.add_argument(
+                '--debug',
+                default=default,
+                type=int,
+                choices=[1, 0],
+                help='If this parameter is set to "1", %(prog)s will print' +
+                     ' additional debug information.  This is mainly to be' +
+                     ' used by the developer of %(prog)s.' + default_string
+                )
+        elif arg_name == "loglevel":
+            if default is None:
+                default = "info"
+            parser.add_argument(
+                '--loglevel',
+                default=default,
+                type=str,
+                choices=['DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL',
+                         'debug', 'info', 'warning', 'error', 'critical'],
+                help='If this parameter is set to "1", %(prog)s will print' +
+                     ' additional debug information.  This is mainly to be' +
+                     ' used by the developer of %(prog)s.' + default_string
+                )
+
+    arg_obj = parser.parse_args()
+
+    __builtin__.quiet = 0
+    __builtin__.test_mode = 0
+    __builtin__.debug = 0
+    __builtin__.loglevel = 'WARNING'
+    for ix in range(0, len(stock_list)):
+        if type(stock_list[ix]) is tuple:
+            arg_name = stock_list[ix][0]
+            default = stock_list[ix][1]
+        else:
+            arg_name = stock_list[ix]
+            default = None
+        if arg_name == "quiet":
+            __builtin__.quiet = arg_obj.quiet
+        elif arg_name == "test_mode":
+            __builtin__.test_mode = arg_obj.test_mode
+        elif arg_name == "debug":
+            __builtin__.debug = arg_obj.debug
+        elif arg_name == "loglevel":
+            __builtin__.loglevel = arg_obj.loglevel
+
+    __builtin__.arg_obj = arg_obj
+    __builtin__.parser = parser
+
+    # For each command line parameter, create a corresponding global variable
+    # and assign it the appropriate value.  For example, if the command line
+    # contained "--last_name='Smith', we'll create a global variable named
+    # "last_name" with the value "Smith".
+    module = sys.modules['__main__']
+    for key in arg_obj.__dict__:
+        setattr(module, key, getattr(__builtin__.arg_obj, key))
+
+    return True
+
+###############################################################################
+
+
+# Put this in gen_opt.py or gen_parm.py or gen_arg.py.
+###############################################################################
+def sprint_args(arg_obj,
+                indent=0):
+
+    r"""
+    sprint_var all of the arguments found in arg_obj and return the result as
+    a string.
+
+    Description of arguments:
+    arg_obj                         An argument object such as is returned by
+                                    the argparse parse_args() method.
+    indent                          The number of spaces to indent each line
+                                    of output.
+    """
+
+    buffer = ""
+
+    for key in arg_obj.__dict__:
+        buffer += gp.sprint_varx(key, getattr(arg_obj, key), 0, indent)
+
+    return buffer
+
+###############################################################################
+
+
+###############################################################################
+def gen_post_validation(exit_function=None,
+                        signal_handler=None):
+
+    r"""
+    Do generic post-validation processing.  By "post", we mean that this is to
+    be called from a validation function after the caller has done any
+    validation desired.  If the calling program passes exit_function and
+    signal_handler parms, this function will register them.  In other words,
+    it will make the signal_handler functions get called for SIGINT and
+    SIGTERM and will make the exit_function function run prior to the
+    termination of the program.
+
+    Description of arguments:
+    exit_function                   A function object pointing to the caller's
+                                    exit function.
+    signal_handler                  A function object pointing to the caller's
+                                    signal_handler function.
+    """
+
+    if exit_function is not None:
+        atexit.register(exit_function)
+    if signal_handler is not None:
+        signal.signal(signal.SIGINT, signal_handler)
+        signal.signal(signal.SIGTERM, signal_handler)
+
+###############################################################################