提交 0941cdd7 编写于 作者: C Cleber Rosa

avocado.Test: introduce get_data() method

This method allows to look for test data in ways that are more
flexible than the current "datadir" feature currently available.

The current "datadir" is bound to the test filename only, and
can not provide different data for different tests hosted within
a single test file.

The newly introduced get_data() method looks for test data in
various locations, to be able to provide custom data not only
to different tests hosted inside a single test file, but also
to different variants.

Note about this alternative implementation of `get_data()`: it does so
in a much more detached form.  It allows for other test types to
clearly change the way data files are stored and accessed.  It also
introduces the concept of named data sources.

In the previous implementation, the types of "data dirs" were hard
coded, while in this approach, they're defined at the class level and
it's up to the `get_data()` implementation to deal with it.
Consequently, the `get_data()` method gets a second (optional)
parameter that allows the user to specify from which data source the
data file should be retrieved.

This is necessary, for example, when we add configurability to sources
of data files, such as expected `stdout`, `stderr` and `output` files.
Signed-off-by: NCleber Rosa <crosa@redhat.com>
上级 1831c239
......@@ -162,7 +162,122 @@ class TestID(object):
% (self.str_uid, str(self)))
class Test(unittest.TestCase):
class TestData(object):
"""
Class that adds the hability for tests to have access to data files
Writers of new test types can change the completely change the behavior
and still be compatible by providing an :attr:`DATA_SOURCES` attribute
and a meth:`get_data` method.
"""
#: Defines the name of data sources that this implementation makes
#: available. Users may choose to pick data file from a specific
#: source.
DATA_SOURCES = ["variant", "test", "file"]
def __init__(self):
self._data_sources_mapping = {
"variant": [lambda: self.datadir,
lambda: "%s.%s" % (self.__class__.__name__,
self._testMethodName),
lambda: self.name.variant],
"test": [lambda: self.datadir,
lambda: "%s.%s" % (self.__class__.__name__,
self._testMethodName)],
"file": [lambda: self.datadir]
}
def _check_valid_data_source(self, source):
"""
Utility to check if user chose a specific data source
:param source: either None for no specific selection or a source name
:type source: None or str
:raises: ValueError
"""
if source is not None and source not in self.DATA_SOURCES:
msg = 'Data file source requested (%s) is not one of: %s'
msg %= (source, ', '.join(self.DATA_SOURCES))
raise ValueError(msg)
def _get_datadir(self, source):
path_components = self._data_sources_mapping.get(source)
if path_components is None:
return
# evaluate lazily, needed when the class changes its own
# information such as its datadir
path_components = [func() for func in path_components]
if None in path_components:
return
# if path components are absolute paths, let's believe that
# they have already been treated (such as the entries that
# return the self.datadir). If not, let's split the path
# components so that they can be treated in the next loop
split_path_components = []
for path_component in path_components:
if not os.path.isabs(path_component):
split_path_components += path_component.split(os.path.sep)
else:
split_path_components.append(path_component)
# now, make sure each individual path component can be represented
# in the filesystem. again, if it's an absolute path, do nothing
paths = []
for path in split_path_components:
if os.path.isabs(path):
paths.append(path)
else:
paths.append(astring.string_to_safe_path(path))
return os.path.join(*paths)
def get_data(self, filename, source=None, must_exist=True):
"""
Retrieves the path to a given data file.
This implementation looks for data file in one of the sources
defined by the :attr:`DATA_SOURCES` attribute.
:param filename: the name of the data file to be retrieved
:type filename: str
:param source: one of the defined data sources. If not set,
all of the :attr:`DATA_SOURCES` will be attempted
in the order they are defined
:type source: str
:param must_exist: whether the existence of a file is checked for
:type must_exist: bool
:rtype: str or None
"""
log_fmt = 'DATA (filename=%s) => %s (%s)'
if source is None:
sources = self.DATA_SOURCES
else:
self._check_valid_data_source(source)
sources = [source]
for attempt_source in sources:
datadir = self._get_datadir(attempt_source)
if datadir is not None:
path = os.path.join(datadir, filename)
if not must_exist:
self.log.debug(log_fmt, filename, path,
("assumed to be located at %s source "
"dir" % attempt_source))
return path
else:
if os.path.exists(path):
self.log.debug(log_fmt, filename, path,
"found at %s source dir" % attempt_source)
return path
self.log.debug(log_fmt, filename, "NOT FOUND",
"data sources: %s" % ', '.join(sources))
class Test(unittest.TestCase, TestData):
"""
Base implementation for the test class.
......@@ -295,6 +410,7 @@ class Test(unittest.TestCase):
self.log.debug("Test metadata:")
self.log.debug(" filename: %s", self.filename)
unittest.TestCase.__init__(self, methodName=methodName)
TestData.__init__(self)
@property
def name(self):
......
......@@ -179,6 +179,51 @@ If you need to attach several output files, you can also use
``$RESULTS/test-results/$TEST_ID/data`` location and is reserved for
arbitrary test result data.
Accessing test data files
=========================
Some tests can depend on data files, external to the test file itself.
Avocado provides an mechanism an a test API that makes it really easy
to access such files: :meth:`get_data() <avocado.core.test.TestData.get_data>`.
For Avocado tests (that is, ``INSTRUMENTED`` tests)
:meth:`get_data() <avocado.core.test.TestData.get_data>` allows test data files
to be accessed from up to three sources:
* **file** level data directory: a directory named after the test file, but
ending with ``.data``. For a test file ``/home/user/test.py``, the file level
data directory is ``/home/user/test.py.data/``.
* **test** level data directory: a directory named after the test file and the
specific test name. These are useful when different tests part of the
same file need different data files (with the same name or not). Considering
the previous example of ``/home/user/test.py``, and supposing it contains two
tests, ``MyTest.test_foo`` and ``MyTest.test_bar``, the test level data
directories will be, ``/home/user/test.py.data/MyTest.test_foo/`` and
``home/user/test.py.data/MyTest.test_bar/`` respectively.
* **variant** level data directory: if variants are being used during the test
execution, a directory named after the variant will also be considered when
looking for test data files. For test file ``/home/user/test.py``, and test
``MyTest.test_foo``, with variant ``debug-ffff``, the data directory path
will be ``/home/user/test.py.data/MyTest.test_foo/debug-ffff/``.
Avocado looks for data files in the order defined at
:attr:`DATA_SOURCES <avocado.core.test.TestData.DATA_SOURCES>`, which are
from most specific one, to most generic one. That means that, if a variant
is being used, the **variant** directory is used first. Then the **test**
level directory is attempted, and finally the **file** level directory.
.. tip:: When running tests you can use the ``--log-test-data-directories``
command line option log the test data directories that will be used
for that specific test and execution conditions (such as with or
without variants). Look for "Test data directories" in the test logs.
.. note:: An older API, :attr:`avocado.core.test.Test.datadir`, allows access
to the data directory based on the test file location only. This API
is limited, deprecated and will be removed. All new users should rely
on ``get_data()`` instead.
.. _accessing-test-parameters:
Accessing test parameters
......
from __future__ import print_function
from avocado import Test
class GetData(Test):
"""
Example for get_data() API usage
"""
def test_a(self):
"""
This large (on purpose) test, tests get_data() with "file",
"test" and "variant" sources.
Then, it adds other checks that include all sources.
"""
# File-level checks
file_data = self.get_data('file_data')
self.assertIsNotNone(file_data)
self.assertEqual(file_data,
self.get_data('file_data', source='file'))
self.assertEqual(file_data,
self.get_data('file_data', source='file',
must_exist=False))
self.assertEqual(open(file_data).read(), 'get_data.py')
# Test-level checks
test_data = self.get_data('test_data')
self.assertIsNotNone(test_data)
self.assertEqual(test_data,
self.get_data('test_data', source='test'))
self.assertEqual(test_data,
self.get_data('test_data', source='test',
must_exist=False))
self.assertEqual(open(test_data).read(), 'a')
# Variant-level checks
in_variant = self.params.get('in_variant', default=False)
if in_variant:
variant_data = self.get_data('variant_data')
self.assertIsNotNone(variant_data)
self.assertEqual(variant_data,
self.get_data('variant_data', source='variant'))
self.assertEqual(variant_data,
self.get_data('variant_data', source='variant',
must_exist=False))
# A variation of data files that do not exist
self.assertIsNone(self.get_data('does_not_exist'))
self.assertIsNone(self.get_data('file_data', source='test'))
self.assertIsNone(self.get_data('test_data', source='file'))
if in_variant:
self.assertIsNone(self.get_data('variant_data', source='test'))
# All `get_data()` called with `must_exist=False` should
# return a valid location for a (to be created?) data file
self.assertIsNotNone(self.get_data('does_not_exist', must_exist=False))
self.assertIsNotNone(self.get_data('does_not_exist', source='file',
must_exist=False))
self.assertIsNotNone(self.get_data('does_not_exist', source='test',
must_exist=False))
if in_variant:
self.assertIsNotNone(self.get_data('does_not_exist',
source='variant',
must_exist=False))
# Write to stdout with print() to test output check capabilities,
# a feature that uses the data directories (get_data()) itself
print("This is output from test_a")
def test_b(self):
"""
Test contains data files under the test data directory, but
has no associated variants directories.
"""
file_data = self.get_data('file_data')
self.assertIsNotNone(file_data)
self.assertEqual(open(file_data).read(), 'get_data.py')
test_data = self.get_data('test_data')
self.assertIsNotNone(test_data)
self.assertEqual(open(test_data).read(), 'b')
variant_data = self.get_data('variant_data')
self.assertIsNone(variant_data)
print("This is output from test_b")
get_data.py
\ No newline at end of file
!mux
short:
in_variant: True
medium:
in_variant: True
long:
in_variant: True
import os
import shutil
import tempfile
import unittest
from avocado.core import exit_codes
from avocado.utils import process
basedir = os.path.join(os.path.dirname(os.path.abspath(__file__)), '..', '..')
basedir = os.path.abspath(basedir)
AVOCADO = os.environ.get("UNITTEST_AVOCADO_CMD", "./scripts/avocado")
class GetData(unittest.TestCase):
def setUp(self):
os.chdir(basedir)
self.tmpdir = tempfile.mkdtemp(prefix='avocado_' + __name__)
def test(self):
test_path = os.path.join(basedir, "selftests", ".data", "get_data.py")
test_variants_path = os.path.join(basedir, "selftests", ".data",
"get_data.py.data", "get_data.yaml")
cmd_line = "%s run --sysinfo=off --job-results-dir '%s' -m %s -- %s"
cmd_line %= (AVOCADO, self.tmpdir, test_variants_path, test_path)
result = process.run(cmd_line)
self.assertEqual(result.exit_status, exit_codes.AVOCADO_ALL_OK)
def tearDown(self):
shutil.rmtree(self.tmpdir)
if __name__ == '__main__':
unittest.main()
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册