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]>
---
configure.in | 1 +
tests/Makefile.am | 2 +-
tests/ftests/.gitignore | 5 +
tests/ftests/Makefile.am | 27 ++++
tests/ftests/__init__.py | 0
tests/ftests/cgroup.py | 186 +++++++++++++++++++++++
tests/ftests/config.py | 56 +++++++
tests/ftests/consts.py | 49 +++++++
tests/ftests/container.py | 236 ++++++++++++++++++++++++++++++
tests/ftests/default.conf | 29 ++++
tests/ftests/ftests.py | 301 ++++++++++++++++++++++++++++++++++++++
tests/ftests/log.py | 67 +++++++++
tests/ftests/run.py | 73 +++++++++
13 files changed, 1031 insertions(+), 1 deletion(-)
create mode 100644 tests/ftests/.gitignore
create mode 100644 tests/ftests/Makefile.am
create mode 100644 tests/ftests/__init__.py
create mode 100644 tests/ftests/cgroup.py
create mode 100644 tests/ftests/config.py
create mode 100644 tests/ftests/consts.py
create mode 100644 tests/ftests/container.py
create mode 100644 tests/ftests/default.conf
create mode 100755 tests/ftests/ftests.py
create mode 100644 tests/ftests/log.py
create mode 100644 tests/ftests/run.py
diff --git a/configure.in b/configure.in
index 81949b1..fafd245 100644
--- a/configure.in
+++ b/configure.in
@@ -198,6 +198,7 @@ fi
AC_CONFIG_FILES([Makefile
tests/Makefile
+ tests/ftests/Makefile
tests/gunit/Makefile
tests/tools/testenv.sh
tests/tools/Makefile
diff --git a/tests/Makefile.am b/tests/Makefile.am
index 7f1a071..6d748dc 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -1,4 +1,4 @@
-SUBDIRS = gunit tools
+SUBDIRS = ftests gunit tools
INCLUDES = -I$(top_srcdir)/include
LDADD = $(top_builddir)/src/.libs/libcgroup.la
diff --git a/tests/ftests/.gitignore b/tests/ftests/.gitignore
new file mode 100644
index 0000000..9a54287
--- /dev/null
+++ b/tests/ftests/.gitignore
@@ -0,0 +1,5 @@
+tmp.conf
+*.log
+*.pyc
+*.swp
+*.trs
diff --git a/tests/ftests/Makefile.am b/tests/ftests/Makefile.am
new file mode 100644
index 0000000..ede06b4
--- /dev/null
+++ b/tests/ftests/Makefile.am
@@ -0,0 +1,27 @@
+#
+# libcgroup functional tests Makefile.am
+#
+# 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>.
+#
+
+TESTS =
+
+clean-local: clean-local-check
+.PHONY: clean-local-check
+clean-local-check:
+ -rm -f *.pyc
diff --git a/tests/ftests/__init__.py b/tests/ftests/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/tests/ftests/cgroup.py b/tests/ftests/cgroup.py
new file mode 100644
index 0000000..34f054b
--- /dev/null
+++ b/tests/ftests/cgroup.py
@@ -0,0 +1,186 @@
+#!/usr/bin/env python
+#
+# Cgroup 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>.
+#
+# SPDX-License-Identifier: GPL-2.0
+
+import consts
+import os
+from run import Run
+import types
+
+class Cgroup(object):
+ @staticmethod
+ def build_cmd_path(in_container, cmd):
+ if in_container:
+ return os.path.join('/', consts.LIBCG_MOUNT_POINT,
+ 'src/tools/.libs/%s' % cmd)
+ else:
+ return cmd
+
+ @staticmethod
+ def concatenate_controllers(controller_list):
+ if type(controller_list) is types.StringType:
+ # controller is already a string. return it as is
+ return controller_list
+
+ out_str = ""
+ for controller in controller_list:
+ out_str += "%s," % controller
+
+ # remove the trailing ","
+ out_str = out_str[:-1]
+ return out_str
+
+ # TODO - add support for all of the cgcreate options
+ @staticmethod
+ def create(config, controller_list, cgname, in_container=True):
+ cmd = list()
+ cmd.append(Cgroup.build_cmd_path(in_container, 'cgcreate'))
+
+ controllers_and_path = "%s:%s" % \
+ (Cgroup.concatenate_controllers(controller_list), cgname)
+
+ cmd.append('-g')
+ cmd.append(controllers_and_path)
+
+ if in_container:
+ config.container.run(cmd)
+ else:
+ Run.run(cmd)
+
+ @staticmethod
+ def delete(config, controller_list, cgname, in_container=True,
recursive=False):
+ cmd = list()
+ cmd.append(Cgroup.build_cmd_path(in_container, 'cgdelete'))
+
+ if recursive:
+ cmd.append('-r')
+
+ controllers_and_path = "%s:%s" % \
+ (Cgroup.concatenate_controllers(controller_list), cgname)
+
+ cmd.append('-g')
+ cmd.append(controllers_and_path)
+
+ if in_container:
+ config.container.run(cmd)
+ else:
+ Run.run(cmd)
+
+ @staticmethod
+ def set(config, cgname, setting, value, in_container=True):
+ cmd = list()
+ cmd.append(Cgroup.build_cmd_path(in_container, 'cgset'))
+
+ if type(setting) is types.StringType and \
+ type(value) is types.StringType:
+ cmd.append('-r')
+ cmd.append('%s=%s' % (setting, value))
+ elif type(setting) is types.ListType and \
+ type(value) is types.ListType:
+ if len(setting) != len(value):
+ raise ValueError('Settings list length must equal values list
length')
+
+ for idx, stg in enumerate(setting):
+ cmd.append('-r')
+ cmd.append('%s=%s' % (stg, value[idx]))
+
+ cmd.append(cgname)
+
+ if in_container:
+ config.container.run(cmd)
+ else:
+ Run.run(cmd)
+
+ @staticmethod
+ # valid cpuset commands:
+ # Read one setting:
+ # cgget -r cpuset.cpus tomcpuset
+ # Read two settings:
+ # cgget -r cpuset.cpus -r cpuset.cpu_exclusive tomcpuset
+ # Read one setting from two cgroups:
+ # cgget -r cpuset.cpu_exclusive tomcgroup1 tomcgroup2
+ # Read two settings from two cgroups:
+ # cgget -r cpuset.cpu_exclusive -r cpuset.cpu_exclusive tomcgroup1
tomcgroup2
+ #
+ # Read all of the settings in a cgroup
+ # cgget -g cpuset tomcpuset
+ # Read all of the settings in multiple controllers
+ # cgget -g cpuset -g cpu -g memory tomcgroup
+ # Read all of the settings from a cgroup at a specific path
+ # cgget -g memory:tomcgroup/tomcgroup
+ def get(config, controller=None, cgname=None, setting=None,
+ in_container=True, print_headers=True, values_only=False,
+ all_controllers=False):
+ cmd = list()
+ cmd.append(Cgroup.build_cmd_path(in_container, 'cgget'))
+
+ if not print_headers:
+ cmd.append('-n')
+ if values_only:
+ cmd.append('-v')
+
+ if setting is not None:
+ if type(setting) is types.StringType:
+ # the user provided a simple string. use it as is
+ cmd.append('-r')
+ cmd.append(setting)
+ elif type(setting) is types.ListType:
+ for sttng in setting:
+ cmd.append('-r')
+ cmd.append(sttng)
+ else:
+ raise ValueError('Unsupported setting value')
+
+ if controller is not None:
+ if type(controller) is types.StringType and \
+ ':' in controller:
+ # the user provided a controller:cgroup. use it as is
+ cmd.append('-g')
+ cmd.append(controller)
+ elif type(controller) is types.StringType:
+ # the user provided a controller only. use it as is
+ cmd.append('-g')
+ cmd.append(controller)
+ elif type(controller) is types.ListType:
+ for ctrl in controller:
+ cmd.append('-g')
+ cmd.append(ctrl)
+ else:
+ raise ValueError('Unsupported controller value')
+
+ if all_controllers:
+ cmd.append('-a')
+
+ if cgname is not None:
+ if type(cgname) is types.StringType:
+ # use the string as is
+ cmd.append(cgname)
+ elif type(cgname) is types.ListType:
+ for cg in cgname:
+ cmd.append(cg)
+
+ if in_container:
+ ret = config.container.run(cmd)
+ else:
+ ret = Run.run(cmd)
+
+ return ret
diff --git a/tests/ftests/config.py b/tests/ftests/config.py
new file mode 100644
index 0000000..20157f3
--- /dev/null
+++ b/tests/ftests/config.py
@@ -0,0 +1,56 @@
+#!/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>.
+#
+# SPDX-License-Identifier: GPL-2.0
+
+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
new file mode 100644
index 0000000..6ac2eb3
--- /dev/null
+++ b/tests/ftests/consts.py
@@ -0,0 +1,49 @@
+#!/usr/bin/env python
+#
+# Constants 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>.
+#
+# SPDX-License-Identifier: GPL-2.0
+
+import os
+
+DEFAULT_CONTAINER_NAME = 'test_libcg'
+DEFAULT_CONTAINER_DISTRO = 'oracle'
+DEFAULT_CONTAINER_RELEASE = '7'
+DEFAULT_CONTAINER_ARCH = 'amd64'
+DEFAULT_CONTAINER_STOP_TIMEOUT = 5
+DEFAULT_CONTAINER_CFG_PATH=os.path.join(
+ os.path.dirname(os.path.abspath(__file__)),
+ 'default.conf')
+TEMP_CONTAINER_CFG_FILE='tmp.conf'
+
+DEFAULT_LOG_FILE = 'libcgroup-ftests.log'
+
+LOG_CRITICAL = 1
+LOG_WARNING = 5
+LOG_DEBUG = 8
+DEFAULT_LOG_LEVEL = 5
+
+LIBCG_MOUNT_POINT = 'libcg'
+
+TESTS_RUN_ALL = -1
+TESTS_RUN_ALL_SUITES = "allsuites"
+TEST_PASSED = "passed"
+TEST_FAILED = "failed"
+TEST_SKIPPED = "skipped"
diff --git a/tests/ftests/container.py b/tests/ftests/container.py
new file mode 100644
index 0000000..bc1e6de
--- /dev/null
+++ b/tests/ftests/container.py
@@ -0,0 +1,236 @@
+#!/usr/bin/env python
+#
+# Container 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>.
+#
+# SPDX-License-Identifier: GPL-2.0
+
+import consts
+import getpass
+from log import Log
+import os
+from run import Run
+import types
+
+class Container(object):
+ def __init__(self, name, stop_timeout=None, arch=None, cfg_path=None,
+ distro=None, release=None):
+ self.name = name
+ self.privileged = True
+
+ if stop_timeout:
+ self.stop_timeout = stop_timeout
+ else:
+ self.stop_timeout = consts.DEFAULT_CONTAINER_STOP_TIMEOUT
+
+ if arch:
+ self.arch = arch
+ else:
+ self.arch = consts.DEFAULT_CONTAINER_ARCH
+
+ if cfg_path:
+ self.cfg_path = cfg_path
+ else:
+ self.cfg_path = consts.DEFAULT_CONTAINER_CFG_PATH
+
+ if distro:
+ self.distro = distro
+ else:
+ self.distro = consts.DEFAULT_CONTAINER_DISTRO
+
+ if release:
+ self.release = release
+ else:
+ self.release = consts.DEFAULT_CONTAINER_RELEASE
+
+ ftest_dir = os.path.dirname(os.path.abspath(__file__))
+ tests_dir = os.path.dirname(ftest_dir)
+ libcg_dir = os.path.dirname(tests_dir)
+
+ self.tmp_cfg_path = os.path.join(ftest_dir,
consts.TEMP_CONTAINER_CFG_FILE)
+ try:
+ Run.run(['rm', '-f', self.tmp_cfg_path])
+ except:
+ pass
+
+ Run.run(['cp', self.cfg_path, self.tmp_cfg_path])
+
+ conf_line = 'lxc.arch = %s' % self.arch
+ Run.run(['echo', conf_line, '>>', self.tmp_cfg_path], shell_bool=True)
+
+ conf_line = 'lxc.mount.entry = %s %s none bind,ro 0 0' % \
+ (libcg_dir, consts.LIBCG_MOUNT_POINT)
+ Run.run(['echo', conf_line, '>>', self.tmp_cfg_path], shell_bool=True)
+
+ if not self.privileged:
+ conf_line = 'lxc.idmap = u 0 100000 65536'
+ Run.run(['echo', conf_line, '>>', self.tmp_cfg_path],
shell_bool=True)
+ conf_line = 'lxc.idmap = g 0 100000 65536'
+ Run.run(['echo', conf_line, '>>', self.tmp_cfg_path],
shell_bool=True)
+
+ def __str__(self):
+ out_str = "%s" % self.name
+ out_str += "\n\tdistro = %s" % self.distro
+ out_str += "\n\trelease = %s" % self.release
+ out_str += "\n\tarch = %s" % self.arch
+ out_str += "\n\tcfg_path = %s" % self.cfg_path
+ out_str += "\n\tstop_timeout = %d" % self.stop_timeout
+
+ return out_str
+
+ def create(self):
+ cmd = list()
+
+ if self.privileged:
+ cmd.append('sudo')
+
+ cmd.append('lxc-create')
+ cmd.append('-t')
+ cmd.append( 'download')
+
+ cmd.append('-n')
+ cmd.append(self.name)
+
+ if self.privileged:
+ cmd.append('sudo')
+ cmd.append('-f')
+ cmd.append(self.tmp_cfg_path)
+
+ cmd.append('--')
+
+ cmd.append('-d')
+ cmd.append(self.distro)
+
+ cmd.append('-r')
+ cmd.append(self.release)
+
+ cmd.append('-a')
+ cmd.append(self.arch)
+
+ return Run.run(cmd)
+
+ def destroy(self):
+ cmd = list()
+
+ if self.privileged:
+ cmd.append('sudo')
+
+ cmd.append('lxc-destroy')
+
+ cmd.append('-n')
+ cmd.append(self.name)
+
+ return Run.run(cmd)
+
+ def info(self, cfgname):
+ cmd = list()
+
+ if self.privileged:
+ cmd.append('sudo')
+
+ cmd.append('lxc-info')
+
+ cmd.append('--config=%s' % cfgname)
+
+ cmd.append('-n')
+ cmd.append(self.name)
+
+ return Run.run(cmd)
+
+ def rootfs(self):
+ # try to read lxc.rootfs.path first
+ ret = self.info('lxc.rootfs.path')
+ if len(ret.strip()) > 0:
+ return ret
+
+ # older versions of lxc used lxc.rootfs. Try that.
+ ret = self.info('lxc.rootfs')
+ if len(ret.strip()) == 0:
+ # we failed to get the rootfs
+ raise ContainerError('Failed to get the rootfs')
+ return ret
+
+ def run(self, cntnr_cmd):
+ cmd = list()
+
+ if self.privileged:
+ cmd.append('sudo')
+
+ cmd.append('lxc-attach')
+
+ cmd.append('-n')
+ cmd.append(self.name)
+
+ cmd.append('--')
+
+ # concatenate the lxc-attach command with the command to be run
+ # inside the container
+ if type(cntnr_cmd) is types.StringType:
+ cmd.append(cntnr_cmd)
+ elif type(cntnr_cmd) is types.ListType:
+ cmd = cmd + cntnr_cmd
+ else:
+ raise ContainerError('Unsupported command type')
+
+ return Run.run(cmd)
+
+ def start(self):
+ cmd = list()
+
+ if self.privileged:
+ cmd.append('sudo')
+
+ cmd.append('lxc-start')
+ cmd.append('-d')
+
+ cmd.append('-n')
+ cmd.append(self.name)
+
+ return Run.run(cmd)
+
+ def stop(self, kill=True):
+ cmd = list()
+
+ if self.privileged:
+ cmd.append('sudo')
+
+ cmd.append('lxc-stop')
+
+ cmd.append('-n')
+ cmd.append(self.name)
+
+ if kill:
+ cmd.append('-k')
+ else:
+ cmd.append('-t')
+ cmd.append(str(self.stop_timeout))
+
+ return Run.run(cmd)
+
+ def version(self):
+ cmd = ['lxc-create', '--version']
+ return Run.run(cmd)
+
+class ContainerError(Exception):
+ def __init__(self, message, ret):
+ super(RunError, self).__init__(message)
+
+ def __str__(self):
+ out_str = "ContainerError:\n\tmessage = %s" % (self.message)
+ return out_str
diff --git a/tests/ftests/default.conf b/tests/ftests/default.conf
new file mode 100644
index 0000000..c999f75
--- /dev/null
+++ b/tests/ftests/default.conf
@@ -0,0 +1,29 @@
+# Template used to create this container: /usr/share/lxc/templates/lxc-download
+#
+# Default lxc configuration file for 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>.
+#
+# SPDX-License-Identifier: GPL-2.0
+
+# Distribution configuration
+lxc.include = /usr/share/lxc/config/common.conf
+lxc.include = /usr/share/lxc/config/userns.conf
+
+# Container specific configuration
+lxc.include = /etc/lxc/default.conf
diff --git a/tests/ftests/ftests.py b/tests/ftests/ftests.py
new file mode 100755
index 0000000..92b1245
--- /dev/null
+++ b/tests/ftests/ftests.py
@@ -0,0 +1,301 @@
+#!/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>.
+#
+# SPDX-License-Identifier: GPL-2.0
+
+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))
diff --git a/tests/ftests/log.py b/tests/ftests/log.py
new file mode 100644
index 0000000..104aa20
--- /dev/null
+++ b/tests/ftests/log.py
@@ -0,0 +1,67 @@
+#!/usr/bin/env python
+#
+# Log 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>.
+#
+# SPDX-License-Identifier: GPL-2.0
+
+import consts
+import datetime
+import log
+
+log_level = consts.DEFAULT_LOG_LEVEL
+log_file = consts.DEFAULT_LOG_FILE
+log_fd = None
+
+
+class Log(object):
+
+ @staticmethod
+ def log(msg, msg_level=consts.DEFAULT_LOG_LEVEL):
+ if log_level >= msg_level:
+ if log.log_fd is None:
+ Log.open_logfd(log.log_file)
+
+ timestamp = datetime.datetime.now().strftime('%b %d %H:%M:%S')
+ log_fd.write("%s: %s\n" % (timestamp, msg))
+
+ @staticmethod
+ def open_logfd(log_file):
+ log.log_fd = open(log_file, "a")
+
+ @staticmethod
+ def log_critical(msg):
+ Log.log("CRITICAL: %s" % msg, consts.LOG_CRITICAL)
+
+ @staticmethod
+ def log_warning(msg):
+ Log.log("WARNING: %s" % msg, consts.LOG_WARNING)
+
+ @staticmethod
+ def log_debug(msg):
+ Log.log("DEBUG: %s" % msg, consts.LOG_DEBUG)
+
+
+class LogError(Exception):
+ def __init__(self, message):
+ super(LogError, self).__init__(message)
+
+ def __str__(self):
+ out_str = "LogError:\n\tmessage = %s" % self.message
+ return out_str
diff --git a/tests/ftests/run.py b/tests/ftests/run.py
new file mode 100644
index 0000000..c3ddfc3
--- /dev/null
+++ b/tests/ftests/run.py
@@ -0,0 +1,73 @@
+#!/usr/bin/env python
+#
+# Run 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>.
+#
+# SPDX-License-Identifier: GPL-2.0
+from log import Log
+import subprocess
+import time
+import types
+
+
+class Run(object):
+ @staticmethod
+ def run(command, shell_bool=False):
+ if shell_bool:
+ if type(command) is types.StringType:
+ # nothing to do. command is already formatted as a string
+ pass
+ elif type(command) is types.ListType:
+ command = " ".join(command)
+ else:
+ raise ValueError('Unsupported command type')
+
+ subproc = subprocess.Popen(command, shell=shell_bool,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE)
+ out, err = subproc.communicate()
+ ret = subproc.returncode
+
+ out = out.strip()
+ err = err.strip()
+
+ if shell_bool:
+ Log.log_debug("run:\n\tcommand = %s\n\tret = %d\n\tstdout =
%s\n\tstderr = %s" %
+ (command, ret, out, err))
+ else:
+ Log.log_debug("run:\n\tcommand = %s\n\tret = %d\n\tstdout =
%s\n\tstderr = %s" %
+ (" ".join(command), ret, out, err))
+
+ if ret != 0:
+ raise RunError("Command '%s' failed" % " ".join(command), ret,
out, err)
+
+ return out
+
+class RunError(Exception):
+ def __init__(self, message, ret, stdout, stderr):
+ super(RunError, self).__init__(message)
+
+ self.ret = ret
+ self.stdout = stdout
+ self.stderr = stderr
+
+ def __str__(self):
+ out_str = "RunError:\n\tmessage = %s\n\tret = %d" % (self.message,
self.ret)
+ out_str += "\n\tstdout = %s\n\tstderr = %s" % (self.stdout,
self.stderr)
+ return out_str
--
2.21.0
_______________________________________________
Libcg-devel mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/libcg-devel