blob: b1281abb11ed43b243974712cbc09e7104e7eb4e [file] [log] [blame]
# -*- coding: utf-8 -*-
# Copyright 2020 The Chromium OS Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Configuration and fixtures for pytest.
See the following doc link for an explanation of conftest.py and how it is used
by pytest:
https://docs.pytest.org/en/latest/fixture.html#conftest-py-sharing-fixture-functions
"""
from __future__ import division
from __future__ import print_function
import multiprocessing
import pytest
import chromite as cr
from chromite.lib import cidb
from chromite.lib import parallel
from chromite.lib import retry_stats
from chromite.lib.parser import package_info
# We use wildcard imports here to make fixtures defined in the test module
# globally visible.
# pylint: disable=unused-wildcard-import, wildcard-import
# Importing from *_fixtures.py files into conftest.py is the only time a
# module should use a wildcard import. *_fixtures.py files should ensure
# that the only items visible to a wildcard import are pytest fixtures,
# usually by declaring __all__ if necessary.
from chromite.test.portage_fixtures import *
@pytest.fixture(scope='class', autouse=True)
def mock_cidb_connection():
"""Ensure that the CIDB connection factory is initialized as a mock.
Unit tests should never connect to any live instances of CIDB and this
initialization ensures that they only ever get a mock connection instance.
Previously cros_test_lib.TestProgram.runTests was responsible for globally
initializing this mock and multiple tests are flaky if this mock connection
is not initialized before any tests are run.
"""
# pylint: disable=protected-access
cidb.CIDBConnectionFactory._ClearCIDBSetup()
cidb.CIDBConnectionFactory.SetupMockCidb()
@pytest.fixture(scope='class', autouse=True)
def assert_no_zombies():
"""Assert that tests have no active child processes after completion.
This assertion runs after class tearDown methods because of the scope='class'
declaration.
"""
yield
children = multiprocessing.active_children()
if children:
pytest.fail('Test has %s active child processes after tearDown: %s' %
(len(children), children))
@pytest.fixture(scope='class', autouse=True)
def clear_retry_stats_manager():
"""Reset the global state of the stats manager before every test.
Without this fixture many tests fail due to this global value being set and
then not cleared. The child manager process may have been killed but this
module level variable is still pointing at it, leading to the test trying to
write to a closed pipe.
"""
# pylint: disable=protected-access
retry_stats._STATS_COLLECTION = None
@pytest.fixture(autouse=True)
def set_testing_environment_variable(monkeypatch):
"""Set an environment marker to relax certain strict checks for test code."""
monkeypatch.setenv('CHROMITE_INSIDE_PYTEST', '1')
@pytest.fixture
def singleton_manager(monkeypatch):
"""Force tests to use a singleton Manager and automatically clean it up."""
m = parallel.Manager()
def our_manager():
return m
monkeypatch.setattr(parallel, 'Manager', our_manager)
yield
m.shutdown()
@pytest.fixture
def legacy_capture_output(request, capfd):
"""Adds the `capfd` fixture to TestCase-style test classes.
This fixture should only be used on cros_test_lib.TestCase test classes, since
it doesn't yield anything and just attaches the built-in pytest `capfd`
fixture to the requesting class. Tests written as standalone functions should
use pytest's built-in `capfd` fixture instead of this. See the documentation
for more information on how to use the `capfd` fixture that this provides:
https://docs.pytest.org/en/latest/reference.html#capfd
See the following documentation for an explanation of why fixtures have to be
provided to TestCase classes in this manner:
https://docs.pytest.org/en/latest/unittest.html#mixing-pytest-fixtures-into-unittest-testcase-subclasses-using-marks
"""
request.cls.capfd = capfd
@pytest.fixture
def testcase_caplog(request, caplog):
"""Adds the `caplog` fixture to TestCase-style test classes.
This fixture should only be used on cros_test_lib.TestCase test classes, since
it doesn't yield anything and just attaches the built-in pytest `caplog`
fixture to the requesting class. Tests written as standalone functions should
use pytest's built-in `caplog` fixture instead of this. See the documentation
for more information on how to use the `caplog` fixture that this provides:
https://docs.pytest.org/en/latest/reference.html#caplog
See the following documentation for an explanation of why fixtures have to be
provided to TestCase classes in this manner:
https://docs.pytest.org/en/latest/unittest.html#mixing-pytest-fixtures-into-unittest-testcase-subclasses-using-marks
"""
request.cls.caplog = caplog
@pytest.fixture
def testcase_monkeypatch(request, monkeypatch):
"""Adds the `monkeypatch` fixture to TestCase-style test classes.
This fixture should only be used on cros_test_lib.TestCase test classes, since
it doesn't yield anything and just attaches the built-in pytest `monkeypatch`
fixture to the requesting class. Tests written as standalone functions should
use pytest's built-in `monkeypatch` fixture instead of this. See the
documentation for more information on how to use the `monkeypatch` fixture
that this provides:
https://docs.pytest.org/en/latest/reference.html#monkeypatch
See the following documentation for an explanation of why fixtures have to be
provided to TestCase classes in this manner:
https://docs.pytest.org/en/latest/unittest.html#mixing-pytest-fixtures-into-unittest-testcase-subclasses-using-marks
"""
request.cls.monkeypatch = monkeypatch
def pytest_assertrepr_compare(op, left, right):
"""Global hook for defining detailed explanations for failed assertions.
https://docs.pytest.org/en/latest/assert.html#defining-your-own-explanation-for-failed-assertions
"""
if isinstance(left, package_info.CPV) and isinstance(
right, cr.test.Overlay) and op == 'in':
package_path = right.path / left.category / left.package
return [
f'{left.pv}.ebuild exists in {right.path}',
'Ebuild does not exist in overlay.',
'Ebuilds found in overlay with same category and package:'
] + sorted('\t' + str(p.relative_to(package_path))
for p in package_path.glob('*.ebuild'))
def pytest_addoption(parser):
"""Adds additional options to the default pytest CLI args."""
parser.addoption(
'--no-chroot',
dest='chroot',
action='store_false',
help='Skip any tests that require a chroot to function.',
)
def pytest_collection_modifyitems(config, items):
"""Modifies the list of test items pytest has collected.
See the following link for full documentation on pytest collection hooks:
https://docs.pytest.org/en/latest/reference.html?highlight=pytest_collection_modifyitems#collection-hooks
"""
if config.option.chroot:
return
skip_inside_only = pytest.mark.skip(reason='Test requires a chroot to run.')
for item in items:
if 'inside_only' in item.keywords:
item.add_marker(skip_inside_only)