This commit adds a functional test suite that utilizes lxc
containers to guarantee a non-destructive test environment.
The tests can be invoked individually, as a group of related
tests, or from automake via the standard 'make check'
command.
No tests are included as part of this commit.
Example test invocations:
Run a single test (first cd to tests/ftests):
./001-cgget-basic_cgget.py
or
./ftests.py -N 15 # Run test #015
Run a suite of tests (first cd to tests/ftests):
./ftests.py -s cgget # Run all cgget tests
Run all the tests by hand
./ftests.py
# This may be advantageous over running make check
# because it will try to re-use the same lxc
# container for all of the tests. This should
# provide a significant performance increase
Run the tests from automake
make check
# Then examine the *.trs and *.log files for
# specifics regarding each test result
Example output from a test run:
Test Results:
Run Date: Jun 03 13:41:35
Passed: 1 test
Skipped: 0 tests
Failed: 0 tests
-----------------------------------------------------------------
Timing Results:
Test Time (sec)
---------------------------------------------------------
setup 6.95
001-cgget-basic_cgget.py 0.07
teardown 0.00
---------------------------------------------------------
Total Run Time 7.02
Signed-off-by: Tom Hromatka <[email protected]>
---
tests/ftests/config.py | 55 +++++++++
tests/ftests/consts.py | 6 +
tests/ftests/ftests.py | 300 +++++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 361 insertions(+)
create mode 100644 tests/ftests/config.py
create mode 100755 tests/ftests/ftests.py
diff --git a/tests/ftests/config.py b/tests/ftests/config.py
new file mode 100644
index 0000000..5a8b225
--- /dev/null
+++ b/tests/ftests/config.py
@@ -0,0 +1,55 @@
+#!/usr/bin/env python
+#
+# Config class for the libcgroup functional tests
+#
+# Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
+# Author: Tom Hromatka <[email protected]>
+#
+
+#
+# This library is free software; you can redistribute it and/or modify it
+# under the terms of version 2.1 of the GNU Lesser General Public License as
+# published by the Free Software Foundation.
+#
+# This library is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+# for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this library; if not, see <http://www.gnu.org/licenses>.
+#
+
+import consts
+from container import Container
+import os
+
+class Config(object):
+ def __init__(self, container=None):
+ if container:
+ self.container = container
+ else:
+ # Use the default container settings
+ self.container = Container(consts.DEFAULT_CONTAINER_NAME)
+
+ self.ftest_dir = os.path.dirname(os.path.abspath(__file__))
+ self.libcg_dir = os.path.dirname(self.ftest_dir)
+
+ self.test_suite = consts.TESTS_RUN_ALL_SUITES
+ self.test_num = consts.TESTS_RUN_ALL
+ self.verbose = False
+
+ def __str__(self):
+ out_str = "Configuration"
+ out_str += "\n\tcontainer = %d" % self.container
+
+ return out_str
+
+
+class ConfigError(Exception):
+ def __init__(self, message):
+ super(ConfigError, self).__init__(message)
+
+ def __str__(self):
+ out_str = "ConfigError:\n\tmessage = %s" % self.message
+ return out_str
diff --git a/tests/ftests/consts.py b/tests/ftests/consts.py
index bf2adbe..b410409 100644
--- a/tests/ftests/consts.py
+++ b/tests/ftests/consts.py
@@ -40,3 +40,9 @@ DEFAULT_CONTAINER_CFG_PATH=os.path.join(
os.path.dirname(os.path.abspath(__file__)),
'default.conf')
TEMP_CONTAINER_CFG_FILE='tmp.conf'
+
+TESTS_RUN_ALL = -1
+TESTS_RUN_ALL_SUITES = "allsuites"
+TEST_PASSED = "passed"
+TEST_FAILED = "failed"
+TEST_SKIPPED = "skipped"
diff --git a/tests/ftests/ftests.py b/tests/ftests/ftests.py
new file mode 100755
index 0000000..32e0363
--- /dev/null
+++ b/tests/ftests/ftests.py
@@ -0,0 +1,300 @@
+#!/usr/bin/env python
+#
+# Main entry point for the libcgroup functional tests
+#
+# Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
+# Author: Tom Hromatka <[email protected]>
+#
+
+#
+# This library is free software; you can redistribute it and/or modify it
+# under the terms of version 2.1 of the GNU Lesser General Public License as
+# published by the Free Software Foundation.
+#
+# This library is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+# for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this library; if not, see <http://www.gnu.org/licenses>.
+#
+
+import argparse
+from cgroup import Cgroup
+from config import Config
+import consts
+import container
+import datetime
+import log
+from log import Log
+import os
+from run import Run
+import sys
+import time
+
+setup_time = 0.0
+teardown_time = 0.0
+
+def parse_args():
+ parser = argparse.ArgumentParser("Libcgroup Functional Tests")
+ parser.add_argument('-n', '--name',
+ help='name of the container',
+ required=False, type=str, default=None)
+ parser.add_argument('-f', '--config',
+ help='initial configuration file',
+ required=False, type=str, default=None)
+ parser.add_argument('-d', '--distro',
+ help='linux distribution to use as a template',
+ required=False, type=str, default=None)
+ parser.add_argument('-r', '--release',
+ help='distribution release, e.g.\'trusty\'',
+ required=False, type=str, default=None)
+ parser.add_argument('-a', '--arch',
+ help='processor architecture',
+ required=False, type=str, default=None)
+ parser.add_argument('-t', '--timeout',
+ help='wait timeout (sec) before stopping the
container',
+ required=False, type=int, default=None)
+
+ parser.add_argument('-l', '--loglevel',
+ help='log level',
+ required=False, type=int, default=None)
+ parser.add_argument('-L', '--logfile',
+ help='log file',
+ required=False, type=str, default=None)
+
+ parser.add_argument('-N', '--num',
+ help='Test number to run. If unspecified, all tests
are run',
+ required=False, default=consts.TESTS_RUN_ALL, type=int)
+ parser.add_argument('-s', '--suite',
+ help='Test suite to run, e.g. cpuset', required=False,
+ default=consts.TESTS_RUN_ALL_SUITES, type=str)
+ parser.add_argument('-u', '--unpriv',
+ help='Run the tests in an unprivileged container',
+ required=False, action="store_true")
+ parser.add_argument('-v', '--verbose',
+ help='Print all information about this test run',
+ default=True, required=False, action="store_false")
+
+ args = parser.parse_args()
+
+ config = Config()
+
+ if args.name:
+ config.name = args.name
+ if args.config:
+ config.container.cfg_path = args.config
+ if args.distro:
+ config.container.distro = args.distro
+ if args.release:
+ config.container.release = args.release
+ if args.arch:
+ config.container.arch = args.arch
+ if args.timeout:
+ config.container.stop_timeout = args.timeout
+ if args.loglevel:
+ log.log_level = args.loglevel
+ if args.logfile:
+ log.log_file = args.logfile
+ if args.num:
+ config.test_num = args.num
+ if args.suite:
+ config.test_suite = args.suite
+ if args.unpriv:
+ raise ValueError('Unprivileged containers are not currently supported')
+ config.container.privileged = False
+ config.verbose = args.verbose
+
+ return config
+
+def setup(config, do_teardown=True, record_time=False):
+ global setup_time
+ start_time = time.time()
+ if do_teardown:
+ # belt and suspenders here. In case a previous run wasn't properly
+ # cleaned up, let's try and clean it up here
+ try:
+ teardown(config)
+ except Exception as e:
+ # log but ignore all exceptions
+ Log.log_debug(e)
+
+ config.container.create()
+
+ # make the /libcg directory in the container's rootfs
+ rootfs = config.container.rootfs()
+ container_rootfs_path = rootfs.split('=')[1].strip()
+ Run.run(['sudo', 'mkdir', os.path.join(container_rootfs_path,
+ consts.LIBCG_MOUNT_POINT)])
+
+ config.container.start()
+
+ # add the libcgroup library to the container's ld
+ echo_cmd = ['bash', '-c', 'echo %s >> /etc/ld.so.conf.d/libcgroup.conf' % \
+ os.path.join('/', consts.LIBCG_MOUNT_POINT, 'src/.libs')]
+ config.container.run(echo_cmd)
+ config.container.run('ldconfig')
+ if record_time:
+ setup_time = time.time() - start_time
+
+def run_tests(config):
+ passed_tests = []
+ failed_tests = []
+ skipped_tests = []
+
+ for root, dirs, filenames in os.walk(config.ftest_dir):
+ for filename in filenames:
+ if os.path.splitext(filename)[-1] != ".py":
+ # ignore non-python files
+ continue
+
+ filenum = filename.split('-')[0]
+
+ try:
+ filenum_int = int(filenum)
+ except ValueError:
+ # D'oh. This file must not be a test. Skip it
+ Log.log_debug('Skipping %s. It doesn\'t start with an int' %
+ filename)
+ continue
+
+ try:
+ filesuite = filename.split('-')[1]
+ except IndexError:
+ Log.log_error('Skipping %s. It doesn\'t conform to the
filename format' %
+ filename)
+ continue
+
+ if config.test_suite == consts.TESTS_RUN_ALL_SUITES or \
+ config.test_suite == filesuite:
+ if config.test_num == consts.TESTS_RUN_ALL or \
+ config.test_num == filenum_int:
+ test = __import__(os.path.splitext(filename)[0])
+
+ failure_cause = None
+ start_time = time.time()
+ try:
+ [ret, failure_cause] = test.main(config)
+ except Exception as e:
+ # catch all exceptions. you never know when there's
+ # a crummy test
+ failure_cause = e
+ Log.log_debug(e)
+ ret = consts.TEST_FAILED
+
+ # if the test does cause an exception, it may not have
+ # cleaned up after itself. re-create the container
+ teardown(config)
+ setup(config, do_teardown=False)
+ finally:
+ run_time = time.time() - start_time
+ if ret == consts.TEST_PASSED:
+ passed_tests.append([filename, run_time])
+ elif ret == consts.TEST_FAILED:
+ failed_tests.append([filename, run_time])
+ elif ret == consts.TEST_SKIPPED:
+ skipped_tests.append([filename, run_time])
+ else:
+ raise ValueException('Unexpected ret: %s' % ret)
+
+ passed_cnt = len(passed_tests)
+ failed_cnt = len(failed_tests)
+ skipped_cnt = len(skipped_tests)
+
+ print("-----------------------------------------------------------------")
+ print("Test Results:")
+ date_str = datetime.datetime.now().strftime('%b %d %H:%M:%S')
+ print('\t%s%s' % ('{0: <30}'.format("Run Date:"), '{0:
>15}'.format(date_str)))
+ if passed_cnt == 1:
+ test_str = "1 test"
+ print('\t%s%s' % ('{0: <30}'.format("Passed:"), '{0:
>15}'.format(test_str)))
+ else:
+ test_str = "%d tests" % passed_cnt
+ print('\t%s%s' % ('{0: <30}'.format("Passed:"), '{0:
>15}'.format(test_str)))
+
+ if skipped_cnt == 1:
+ test_str = "1 test"
+ print('\t%s%s' % ('{0: <30}'.format("Skipped:"), '{0:
>15}'.format(test_str)))
+ else:
+ test_str = "%d tests" % skipped_cnt
+ print('\t%s%s' % ('{0: <30}'.format("Skipped:"), '{0:
>15}'.format(test_str)))
+
+ if failed_cnt == 1:
+ test_str = "1 test"
+ print('\t%s%s' % ('{0: <30}'.format("Failed:"), '{0:
>15}'.format(test_str)))
+ else:
+ test_str = "%d tests" % failed_cnt
+ print('\t%s%s' % ('{0: <30}'.format("Failed:"), '{0:
>15}'.format(test_str)))
+ for test in failed_tests:
+ print("\t\tTest:\t\t\t\t%s - %s" % (test[0], str(failure_cause)))
+ print("-----------------------------------------------------------------")
+
+ global setup_time
+ global teardown_time
+ if config.verbose:
+ print("Timing Results:")
+ print('\t%s%s' % ('{0: <30}'.format("Test"), '{0: >15}'.format("Time
(sec)")))
+ print("\t---------------------------------------------------------")
+ time_str = "%2.2f" % setup_time
+ print('\t%s%s' % ('{0: <30}'.format('setup'), '{0:
>15}'.format(time_str)))
+ for test in passed_tests:
+ time_str = "%2.2f" % test[1]
+ print('\t%s%s' % ('{0: <30}'.format(test[0]), '{0:
>15}'.format(time_str)))
+ for test in failed_tests:
+ time_str = "%2.2f" % test[1]
+ print('\t%s%s' % ('{0: <30}'.format(test[0]), '{0:
>15}'.format(time_str)))
+ time_str = "%2.2f" % teardown_time
+ print('\t%s%s' % ('{0: <30}'.format('teardown'), '{0:
>15}'.format(time_str)))
+
+ total_run_time = setup_time + teardown_time
+ for test in passed_tests:
+ total_run_time += test[1]
+ for test in failed_tests:
+ total_run_time += test[1]
+ total_str = "%5.2f" % total_run_time
+ print("\t---------------------------------------------------------")
+ print('\t%s%s' % ('{0: <30}'.format("Total Run Time"), '{0:
>15}'.format(total_str)))
+
+ return [passed_cnt, failed_cnt, skipped_cnt]
+
+def teardown(config, record_time=False):
+ global teardown_time
+ start_time = time.time()
+ try:
+ config.container.stop()
+ except Exception as e:
+ # log but ignore all exceptions
+ Log.log_debug(e)
+ try:
+ config.container.destroy()
+ except Exception as e:
+ # log but ignore all exceptions
+ Log.log_debug(e)
+
+ if record_time:
+ teardown_time = time.time() - start_time
+
+def main(config):
+ AUTOMAKE_SKIPPED = 77
+ AUTOMAKE_HARD_ERROR = 99
+ AUTOMAKE_PASSED = 0
+
+ try:
+ setup(config, record_time=True)
+ [passed_cnt, failed_cnt, skipped_cnt] = run_tests(config)
+ finally:
+ teardown(config, record_time=True)
+
+ if failed_cnt > 0:
+ return failed_cnt
+ if skipped_cnt > 0:
+ return AUTOMAKE_SKIPPED
+ if passed_cnt > 0:
+ return AUTOMAKE_PASSED
+
+ return AUTOMAKE_HARD_ERROR
+
+if __name__ == '__main__':
+ config = parse_args()
+ sys.exit(main(config))
--
1.8.3.1
_______________________________________________
Libcg-devel mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/libcg-devel