2013-10-25 16:26:16 -07:00
|
|
|
#!/usr/bin/env python3
|
2013-10-15 17:06:26 -07:00
|
|
|
# ------------------------------------------------------------------
|
|
|
|
#
|
|
|
|
# Copyright (C) 2013 Canonical Ltd.
|
|
|
|
# Author: Steve Beattie <steve@nxnw.org>
|
|
|
|
#
|
|
|
|
# This program is free software; you can redistribute it and/or
|
|
|
|
# modify it under the terms of version 2 of the GNU General Public
|
|
|
|
# License published by the Free Software Foundation.
|
|
|
|
#
|
|
|
|
# ------------------------------------------------------------------
|
|
|
|
|
2013-10-15 17:10:12 -07:00
|
|
|
import os
|
|
|
|
import shutil
|
2013-10-15 17:06:26 -07:00
|
|
|
import signal
|
|
|
|
import subprocess
|
2013-10-15 17:10:12 -07:00
|
|
|
import tempfile
|
|
|
|
import time
|
|
|
|
import unittest
|
2013-10-15 17:06:26 -07:00
|
|
|
|
|
|
|
TIMEOUT_ERROR_CODE = 152
|
|
|
|
DEFAULT_PARSER = '../apparmor_parser'
|
|
|
|
|
|
|
|
|
|
|
|
# http://www.chiark.greenend.org.uk/ucgi/~cjwatson/blosxom/2009-07-02-python-sigpipe.html
|
|
|
|
# This is needed so that the subprocesses that produce endless output
|
|
|
|
# actually quit when the reader goes away.
|
|
|
|
def subprocess_setup():
|
|
|
|
# Python installs a SIGPIPE handler by default. This is usually not
|
|
|
|
# what non-Python subprocesses expect.
|
|
|
|
signal.signal(signal.SIGPIPE, signal.SIG_DFL)
|
|
|
|
|
|
|
|
|
2013-10-25 16:26:16 -07:00
|
|
|
class AANoCleanupMetaClass(type):
|
|
|
|
def __new__(cls, name, bases, attrs):
|
|
|
|
|
|
|
|
for attr_name, attr_value in attrs.items():
|
|
|
|
if attr_name.startswith("test_"):
|
|
|
|
attrs[attr_name] = cls.keep_on_fail(attr_value)
|
|
|
|
return super(AANoCleanupMetaClass, cls).__new__(cls, name, bases, attrs)
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def keep_on_fail(cls, unittest_func):
|
2022-08-07 14:57:30 -04:00
|
|
|
"""wrapping function for unittest testcases to detect failure
|
2013-10-25 16:26:16 -07:00
|
|
|
and leave behind test files in tearDown(); to be used as
|
2022-08-07 14:57:30 -04:00
|
|
|
a decorator"""
|
2013-10-25 16:26:16 -07:00
|
|
|
|
|
|
|
def new_unittest_func(self):
|
|
|
|
try:
|
|
|
|
return unittest_func(self)
|
2015-08-18 10:59:21 -05:00
|
|
|
except unittest.SkipTest:
|
|
|
|
raise
|
2013-10-25 16:26:16 -07:00
|
|
|
except Exception:
|
|
|
|
self.do_cleanup = False
|
|
|
|
raise
|
|
|
|
|
|
|
|
return new_unittest_func
|
|
|
|
|
|
|
|
|
|
|
|
class AATestTemplate(unittest.TestCase, metaclass=AANoCleanupMetaClass):
|
2022-08-07 14:57:30 -04:00
|
|
|
"""Stub class for use by test scripts"""
|
2013-10-15 17:10:12 -07:00
|
|
|
debug = False
|
|
|
|
do_cleanup = True
|
2013-10-15 17:06:26 -07:00
|
|
|
|
2013-10-15 17:10:12 -07:00
|
|
|
def run_cmd_check(self, command, input=None, stderr=subprocess.STDOUT, stdout=subprocess.PIPE,
|
|
|
|
stdin=None, timeout=120, expected_rc=0, expected_string=None):
|
2022-08-07 14:57:30 -04:00
|
|
|
"""Wrapper around run_cmd that checks the rc code against
|
2013-10-15 17:10:12 -07:00
|
|
|
expected_rc and for expected strings in the output if
|
|
|
|
passed. The valgrind tests generally don't care what the
|
|
|
|
rc is as long as it's not a specific set of return codes,
|
2022-08-07 14:57:30 -04:00
|
|
|
so can't push the check directly into run_cmd()."""
|
2013-10-15 17:10:12 -07:00
|
|
|
rc, report = self.run_cmd(command, input, stderr, stdout, stdin, timeout)
|
2023-02-19 16:26:14 -05:00
|
|
|
self.assertEqual(rc, expected_rc, "Got return code {}, expected {}\nCommand run: {}\nOutput: {}".format(rc, expected_rc, ' '.join(command), report))
|
2013-10-15 17:10:12 -07:00
|
|
|
if expected_string:
|
2023-02-19 16:26:14 -05:00
|
|
|
self.assertIn(expected_string, report, 'Expected message "{}", got: \n{}'.format(expected_string, report))
|
2013-10-15 17:10:12 -07:00
|
|
|
return report
|
2013-10-15 17:06:26 -07:00
|
|
|
|
2020-05-08 02:02:18 -07:00
|
|
|
def run_cmd(self, command, input=None, stderr=subprocess.PIPE, stdout=subprocess.PIPE,
|
2013-10-15 17:10:12 -07:00
|
|
|
stdin=None, timeout=120):
|
2022-08-07 14:57:30 -04:00
|
|
|
"""Try to execute given command (array) and return its stdout, or
|
|
|
|
return a textual error if it failed."""
|
2013-10-15 17:10:12 -07:00
|
|
|
|
|
|
|
if self.debug:
|
2023-02-19 16:26:14 -05:00
|
|
|
print("\n===> Running command: '{}'".format(' '.join(command)))
|
2013-10-15 17:10:12 -07:00
|
|
|
|
2020-05-08 02:02:18 -07:00
|
|
|
(rc, out, outerr) = self._run_cmd(command, input, stderr, stdout, stdin, timeout)
|
|
|
|
report = out + outerr
|
|
|
|
|
2022-06-18 14:30:49 -04:00
|
|
|
return rc, report
|
2020-05-08 02:02:18 -07:00
|
|
|
|
|
|
|
def _run_cmd(self, command, input=None, stderr=subprocess.PIPE, stdout=subprocess.PIPE,
|
|
|
|
stdin=None, timeout=120):
|
2022-08-07 14:57:30 -04:00
|
|
|
"""Try to execute given command (array) and return its rc, stdout, and stderr as a tuple"""
|
2020-05-08 02:02:18 -07:00
|
|
|
|
2013-10-15 17:10:12 -07:00
|
|
|
try:
|
|
|
|
sp = subprocess.Popen(command, stdin=stdin, stdout=stdout, stderr=stderr,
|
2020-05-08 02:02:18 -07:00
|
|
|
close_fds=True, preexec_fn=subprocess_setup, universal_newlines=True)
|
2013-10-15 17:10:12 -07:00
|
|
|
except OSError as e:
|
2022-06-28 08:30:31 -04:00
|
|
|
return 127, str(e), ''
|
2013-10-15 17:10:12 -07:00
|
|
|
|
|
|
|
out, outerr = (None, None)
|
|
|
|
try:
|
2024-10-15 16:51:17 -07:00
|
|
|
out, outerr = sp.communicate(input, timeout)
|
2013-10-15 17:10:12 -07:00
|
|
|
rc = sp.returncode
|
2024-10-15 16:51:17 -07:00
|
|
|
except subprocess.TimeoutExpired:
|
2013-10-15 17:10:12 -07:00
|
|
|
sp.terminate()
|
2020-05-08 02:02:18 -07:00
|
|
|
outerr = 'test timed out, killed'
|
2013-10-15 17:10:12 -07:00
|
|
|
rc = TIMEOUT_ERROR_CODE
|
|
|
|
|
|
|
|
# Handle redirection of stdout
|
|
|
|
if out is None:
|
2020-05-08 02:02:18 -07:00
|
|
|
out = ''
|
2013-10-15 17:10:12 -07:00
|
|
|
# Handle redirection of stderr
|
|
|
|
if outerr is None:
|
2020-05-08 02:02:18 -07:00
|
|
|
outerr = ''
|
2013-10-15 17:10:12 -07:00
|
|
|
|
2022-06-18 14:30:49 -04:00
|
|
|
return rc, out, outerr
|
2013-10-15 17:06:26 -07:00
|
|
|
|
2013-10-15 17:10:12 -07:00
|
|
|
def filesystem_time_resolution():
|
2022-08-07 14:57:30 -04:00
|
|
|
"""detect whether the filesystem stores subsecond timestamps"""
|
2013-10-15 17:10:12 -07:00
|
|
|
|
|
|
|
default_diff = 0.1
|
|
|
|
result = (True, default_diff)
|
|
|
|
|
|
|
|
tmp_dir = tempfile.mkdtemp(prefix='aa-caching-nanostamp-')
|
|
|
|
try:
|
|
|
|
last_stamp = None
|
|
|
|
for i in range(10):
|
|
|
|
s = None
|
|
|
|
|
2023-02-19 16:26:14 -05:00
|
|
|
with open(os.path.join(tmp_dir, 'test.{}'.format(i)), 'w+') as f:
|
2013-10-15 17:10:12 -07:00
|
|
|
s = os.fstat(f.fileno())
|
|
|
|
|
|
|
|
if (s.st_mtime == last_stamp):
|
2015-08-12 12:24:26 -05:00
|
|
|
print('\n===> WARNING: TMPDIR lacks subsecond timestamp resolution, falling back to slower test')
|
2013-10-15 17:10:12 -07:00
|
|
|
result = (False, 1.0)
|
|
|
|
break
|
|
|
|
|
|
|
|
last_stamp = s.st_mtime
|
|
|
|
time.sleep(default_diff)
|
2022-08-27 17:58:51 +00:00
|
|
|
except Exception:
|
2013-10-15 17:10:12 -07:00
|
|
|
pass
|
|
|
|
finally:
|
|
|
|
if os.path.exists(tmp_dir):
|
|
|
|
shutil.rmtree(tmp_dir)
|
|
|
|
|
|
|
|
return result
|
|
|
|
|
|
|
|
|
|
|
|
def read_features_dir(path):
|
|
|
|
|
|
|
|
result = ''
|
|
|
|
if not os.path.exists(path) or not os.path.isdir(path):
|
|
|
|
return result
|
|
|
|
|
2021-07-16 08:57:57 -05:00
|
|
|
for name in sorted(os.listdir(path)):
|
2013-10-15 17:10:12 -07:00
|
|
|
entry = os.path.join(path, name)
|
2023-02-19 16:26:14 -05:00
|
|
|
result += name + ' {'
|
2013-10-15 17:10:12 -07:00
|
|
|
if os.path.isfile(entry):
|
|
|
|
with open(entry, 'r') as f:
|
|
|
|
# don't need extra '\n' here as features file contains it
|
2023-02-19 16:26:14 -05:00
|
|
|
result += f.read()
|
2013-10-15 17:10:12 -07:00
|
|
|
elif os.path.isdir(entry):
|
2023-02-19 16:26:14 -05:00
|
|
|
result += read_features_dir(entry)
|
2013-10-15 17:10:12 -07:00
|
|
|
result += '}\n'
|
|
|
|
|
|
|
|
return result
|
|
|
|
|
|
|
|
|
|
|
|
def touch(path):
|
|
|
|
return os.utime(path, None)
|
|
|
|
|
|
|
|
|
2013-10-25 16:24:17 -07:00
|
|
|
def write_file(directory, file, contents):
|
2022-08-07 14:57:30 -04:00
|
|
|
"""construct path, write contents to it, and return the constructed path"""
|
2013-10-25 16:24:17 -07:00
|
|
|
path = os.path.join(directory, file)
|
2013-10-15 17:10:12 -07:00
|
|
|
with open(path, 'w+') as f:
|
|
|
|
f.write(contents)
|
2013-10-25 16:24:17 -07:00
|
|
|
return path
|