cloud-init fails to build with pytest 8. =================================== FAILURES =================================== _______ TestCLI.test_status_wrapper_init_local_writes_fresh_status_info ________ self = <tests.unittests.test_cli.TestCLI object at 0x7f42d248cbf0> m_json = <MagicMock name='write_json' id='139923590366032'> tmpdir = local('/tmp/pytest-of-mockbuild/pytest-0/test_status_wrapper_init_local0') @mock.patch("cloudinit.cmd.main.atomic_helper.write_json") def test_status_wrapper_init_local_writes_fresh_status_info( self, m_json, tmpdir, ): """When running in init-local mode, status_wrapper writes status.json. Old status and results artifacts are also removed. """ data_d = tmpdir.join("data") link_d = tmpdir.join("link") # Write old artifacts which will be removed or updated. for _dir in data_d, link_d: test_helpers.populate_dir( str(_dir), {"status.json": "old", "result.json": "old"} ) FakeArgs = namedtuple("FakeArgs", ["action", "local", "mode"]) def myaction(name, args): # Return an error to watch status capture them return "SomeDatasource", ["an error"] myargs = FakeArgs(("ignored_name", myaction), True, "bogusmode") > cli.status_wrapper("init", myargs, data_d, link_d) tests/unittests/test_cli.py:90: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ name = 'init' args = FakeArgs(action=('ignored_name', <function TestCLI.test_status_wrapper_init_local_writes_fresh_status_info.<locals>.myaction at 0x7f427fbb8b80>), local=True, mode='bogusmode') data_d = local('/tmp/pytest-of-mockbuild/pytest-0/test_status_wrapper_init_local0/data') link_d = local('/tmp/pytest-of-mockbuild/pytest-0/test_status_wrapper_init_local0/link') def status_wrapper(name, args, data_d=None, link_d=None): if data_d is None: paths = read_cfg_paths() data_d = paths.get_cpath("data") if link_d is None: link_d = os.path.normpath("/run/cloud-init") status_path = os.path.join(data_d, "status.json") status_link = os.path.join(link_d, "status.json") result_path = os.path.join(data_d, "result.json") result_link = os.path.join(link_d, "result.json") root_logger = logging.getLogger() util.ensure_dirs( ( data_d, link_d, ) ) (_name, functor) = args.action if name == "init": if args.local: mode = "init-local" else: mode = "init" elif name == "modules": mode = "modules-%s" % args.mode else: raise ValueError("unknown name: %s" % name) modes = ( "init", "init-local", "modules-config", "modules-final", ) if mode not in modes: raise ValueError( "Invalid cloud init mode specified '{0}'".format(mode) ) status = None if mode == "init-local": for f in (status_link, result_link, status_path, result_path): util.del_file(f) else: try: status = json.loads(util.load_file(status_path)) except Exception: pass nullstatus = { "errors": [], "start": None, "finished": None, } if status is None: status = {"v1": {}} status["v1"]["datasource"] = None for m in modes: if m not in status["v1"]: status["v1"][m] = nullstatus.copy() v1 = status["v1"] v1["stage"] = mode v1[mode]["start"] = time.time() > v1[mode]["recoverable_errors"] = next( filter(lambda h: isinstance(h, LogExporter), root_logger.handlers) ).export_logs() E StopIteration cloudinit/cmd/main.py:770: StopIteration The above exception was the direct cause of the following exception: cls = <class '_pytest.runner.CallInfo'> func = <function call_and_report.<locals>.<lambda> at 0x7f42824298a0> when = 'call' reraise = (<class '_pytest.outcomes.Exit'>, <class 'KeyboardInterrupt'>) @classmethod def from_call( cls, func: Callable[[], TResult], when: Literal["collect", "setup", "call", "teardown"], reraise: Optional[ Union[Type[BaseException], Tuple[Type[BaseException], ...]] ] = None, ) -> "CallInfo[TResult]": """Call func, wrapping the result in a CallInfo. :param func: The function to call. Called without arguments. :param when: The phase in which the function is called. :param reraise: Exception or exceptions that shall propagate if raised by the function, instead of being wrapped in the CallInfo. """ excinfo = None start = timing.time() precise_start = timing.perf_counter() try: > result: Optional[TResult] = func() /usr/lib/python3.12/site-packages/_pytest/runner.py:340: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ /usr/lib/python3.12/site-packages/_pytest/runner.py:240: in <lambda> lambda: runtest_hook(item=item, **kwds), when=when, reraise=reraise /usr/lib/python3.12/site-packages/pluggy/_hooks.py:501: in __call__ return self._hookexec(self.name, self._hookimpls.copy(), kwargs, firstresult) /usr/lib/python3.12/site-packages/pluggy/_manager.py:119: in _hookexec return self._inner_hookexec(hook_name, methods, kwargs, firstresult) /usr/lib/python3.12/site-packages/_pytest/threadexception.py:87: in pytest_runtest_call yield from thread_exception_runtest_hook() /usr/lib/python3.12/site-packages/_pytest/threadexception.py:63: in thread_exception_runtest_hook yield /usr/lib/python3.12/site-packages/_pytest/unraisableexception.py:90: in pytest_runtest_call yield from unraisable_exception_runtest_hook() /usr/lib/python3.12/site-packages/_pytest/unraisableexception.py:65: in unraisable_exception_runtest_hook yield /usr/lib/python3.12/site-packages/_pytest/logging.py:849: in pytest_runtest_call yield from self._runtest_for(item, "call") /usr/lib/python3.12/site-packages/_pytest/logging.py:832: in _runtest_for yield _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = <CaptureManager _method='fd' _global_capturing=<MultiCapture out=<FDCapture 1 oldfd=5 _state='suspended' tmpfile=<_io....xtIOWrapper name='/dev/null' mode='r' encoding='utf-8'>> _state='suspended' _in_suspended=False> _capture_fixture=None> item = <Function test_status_wrapper_init_local_writes_fresh_status_info> @hookimpl(wrapper=True) def pytest_runtest_call(self, item: Item) -> Generator[None, None, None]: with self.item_capture("call", item): > return (yield) E RuntimeError: generator raised StopIteration /usr/lib/python3.12/site-packages/_pytest/capture.py:883: RuntimeError ------------------------------ Captured log call ------------------------------- 2024-04-02 16:25:11 DEBUG cloudinit.util:util.py:2052 Attempting to remove /tmp/pytest-of-mockbuild/pytest-0/test_status_wrapper_init_local0/link/status.json 2024-04-02 16:25:11 DEBUG cloudinit.util:util.py:2052 Attempting to remove /tmp/pytest-of-mockbuild/pytest-0/test_status_wrapper_init_local0/link/result.json 2024-04-02 16:25:11 DEBUG cloudinit.util:util.py:2052 Attempting to remove /tmp/pytest-of-mockbuild/pytest-0/test_status_wrapper_init_local0/data/status.json 2024-04-02 16:25:11 DEBUG cloudinit.util:util.py:2052 Attempting to remove /tmp/pytest-of-mockbuild/pytest-0/test_status_wrapper_init_local0/data/result.json ____________ TestCLI.test_status_wrapper_init_local_honor_cloud_dir ____________ self = <tests.unittests.test_cli.TestCLI object at 0x7f42d248c320> m_json = <MagicMock name='write_json' id='139923589432432'> mocker = <pytest_mock.plugin.MockerFixture object at 0x7f427fe64440> tmpdir = local('/tmp/pytest-of-mockbuild/pytest-0/test_status_wrapper_init_local1') @mock.patch("cloudinit.cmd.main.atomic_helper.write_json") def test_status_wrapper_init_local_honor_cloud_dir( self, m_json, mocker, tmpdir ): """When running in init-local mode, status_wrapper honors cloud_dir.""" cloud_dir = tmpdir.join("cloud") paths = helpers.Paths({"cloud_dir": str(cloud_dir)}) mocker.patch(M_PATH + "read_cfg_paths", return_value=paths) data_d = cloud_dir.join("data") link_d = tmpdir.join("link") FakeArgs = namedtuple("FakeArgs", ["action", "local", "mode"]) def myaction(name, args): # Return an error to watch status capture them return "SomeDatasource", ["an_error"] myargs = FakeArgs(("ignored_name", myaction), True, "bogusmode") > cli.status_wrapper("init", myargs, link_d=link_d) # No explicit data_d tests/unittests/test_cli.py:128: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ name = 'init' args = FakeArgs(action=('ignored_name', <function TestCLI.test_status_wrapper_init_local_honor_cloud_dir.<locals>.myaction at 0x7f4282724220>), local=True, mode='bogusmode') data_d = '/tmp/pytest-of-mockbuild/pytest-0/test_status_wrapper_init_local1/cloud/data' link_d = local('/tmp/pytest-of-mockbuild/pytest-0/test_status_wrapper_init_local1/link') def status_wrapper(name, args, data_d=None, link_d=None): if data_d is None: paths = read_cfg_paths() data_d = paths.get_cpath("data") if link_d is None: link_d = os.path.normpath("/run/cloud-init") status_path = os.path.join(data_d, "status.json") status_link = os.path.join(link_d, "status.json") result_path = os.path.join(data_d, "result.json") result_link = os.path.join(link_d, "result.json") root_logger = logging.getLogger() util.ensure_dirs( ( data_d, link_d, ) ) (_name, functor) = args.action if name == "init": if args.local: mode = "init-local" else: mode = "init" elif name == "modules": mode = "modules-%s" % args.mode else: raise ValueError("unknown name: %s" % name) modes = ( "init", "init-local", "modules-config", "modules-final", ) if mode not in modes: raise ValueError( "Invalid cloud init mode specified '{0}'".format(mode) ) status = None if mode == "init-local": for f in (status_link, result_link, status_path, result_path): util.del_file(f) else: try: status = json.loads(util.load_file(status_path)) except Exception: pass nullstatus = { "errors": [], "start": None, "finished": None, } if status is None: status = {"v1": {}} status["v1"]["datasource"] = None for m in modes: if m not in status["v1"]: status["v1"][m] = nullstatus.copy() v1 = status["v1"] v1["stage"] = mode v1[mode]["start"] = time.time() > v1[mode]["recoverable_errors"] = next( filter(lambda h: isinstance(h, LogExporter), root_logger.handlers) ).export_logs() E StopIteration cloudinit/cmd/main.py:770: StopIteration The above exception was the direct cause of the following exception: cls = <class '_pytest.runner.CallInfo'> func = <function call_and_report.<locals>.<lambda> at 0x7f427fbb9120> when = 'call' reraise = (<class '_pytest.outcomes.Exit'>, <class 'KeyboardInterrupt'>) @classmethod def from_call( cls, func: Callable[[], TResult], when: Literal["collect", "setup", "call", "teardown"], reraise: Optional[ Union[Type[BaseException], Tuple[Type[BaseException], ...]] ] = None, ) -> "CallInfo[TResult]": """Call func, wrapping the result in a CallInfo. :param func: The function to call. Called without arguments. :param when: The phase in which the function is called. :param reraise: Exception or exceptions that shall propagate if raised by the function, instead of being wrapped in the CallInfo. """ excinfo = None start = timing.time() precise_start = timing.perf_counter() try: > result: Optional[TResult] = func() /usr/lib/python3.12/site-packages/_pytest/runner.py:340: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ /usr/lib/python3.12/site-packages/_pytest/runner.py:240: in <lambda> lambda: runtest_hook(item=item, **kwds), when=when, reraise=reraise /usr/lib/python3.12/site-packages/pluggy/_hooks.py:501: in __call__ return self._hookexec(self.name, self._hookimpls.copy(), kwargs, firstresult) /usr/lib/python3.12/site-packages/pluggy/_manager.py:119: in _hookexec return self._inner_hookexec(hook_name, methods, kwargs, firstresult) /usr/lib/python3.12/site-packages/_pytest/threadexception.py:87: in pytest_runtest_call yield from thread_exception_runtest_hook() /usr/lib/python3.12/site-packages/_pytest/threadexception.py:63: in thread_exception_runtest_hook yield /usr/lib/python3.12/site-packages/_pytest/unraisableexception.py:90: in pytest_runtest_call yield from unraisable_exception_runtest_hook() /usr/lib/python3.12/site-packages/_pytest/unraisableexception.py:65: in unraisable_exception_runtest_hook yield /usr/lib/python3.12/site-packages/_pytest/logging.py:849: in pytest_runtest_call yield from self._runtest_for(item, "call") /usr/lib/python3.12/site-packages/_pytest/logging.py:832: in _runtest_for yield _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = <CaptureManager _method='fd' _global_capturing=<MultiCapture out=<FDCapture 1 oldfd=5 _state='suspended' tmpfile=<_io....xtIOWrapper name='/dev/null' mode='r' encoding='utf-8'>> _state='suspended' _in_suspended=False> _capture_fixture=None> item = <Function test_status_wrapper_init_local_honor_cloud_dir> @hookimpl(wrapper=True) def pytest_runtest_call(self, item: Item) -> Generator[None, None, None]: with self.item_capture("call", item): > return (yield) E RuntimeError: generator raised StopIteration /usr/lib/python3.12/site-packages/_pytest/capture.py:883: RuntimeError ------------------------------ Captured log call ------------------------------- 2024-04-02 16:25:11 DEBUG cloudinit.util:util.py:2052 Attempting to remove /tmp/pytest-of-mockbuild/pytest-0/test_status_wrapper_init_local1/link/status.json 2024-04-02 16:25:11 DEBUG cloudinit.util:util.py:2052 Attempting to remove /tmp/pytest-of-mockbuild/pytest-0/test_status_wrapper_init_local1/link/result.json 2024-04-02 16:25:11 DEBUG cloudinit.util:util.py:2052 Attempting to remove /tmp/pytest-of-mockbuild/pytest-0/test_status_wrapper_init_local1/cloud/data/status.json 2024-04-02 16:25:11 DEBUG cloudinit.util:util.py:2052 Attempting to remove /tmp/pytest-of-mockbuild/pytest-0/test_status_wrapper_init_local1/cloud/data/result.json _______________ TestInit.test_apply_network_on_same_instance_id ________________ self = <tests.unittests.test_stages.TestInit object at 0x7f42d260e480> m_ubuntu = <MagicMock name='Distro' id='139923557571504'> caplog = <_pytest.logging.LogCaptureFixture object at 0x7f427df1ffb0> @mock.patch("cloudinit.distros.ubuntu.Distro") def test_apply_network_on_same_instance_id(self, m_ubuntu, caplog): """Only call distro.networking.apply_network_config_names on same instance id.""" self.init.is_new_instance = self._real_is_new_instance old_instance_id = os.path.join( self.init.paths.get_cpath("data"), "instance-id" ) write_file(old_instance_id, TEST_INSTANCE_ID) net_cfg = { "version": 1, "config": [ { "subnets": [{"type": "dhcp"}], "type": "physical", "name": "eth9", "mac_address": "42:42:42:42:42:42", } ], } def fake_network_config(): return net_cfg, NetworkConfigSource.FALLBACK self.init._find_networking_config = fake_network_config > self.init.apply_network_config(True) tests/unittests/test_stages.py:469: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ cloudinit/stages.py:1007: in apply_network_config and not should_run_on_boot_event() cloudinit/stages.py:1001: in should_run_on_boot_event and event_enabled_and_metadata_updated(EventType.BOOT) cloudinit/stages.py:996: in event_enabled_and_metadata_updated ) and self.datasource.update_metadata_if_supported([event_type]) cloudinit/sources/__init__.py:903: in update_metadata_if_supported result = self.get_data() cloudinit/sources/__init__.py:438: in get_data return_value = self._check_and_get_data() cloudinit/sources/__init__.py:363: in _check_and_get_data if self.override_ds_detect(): cloudinit/sources/__init__.py:347: in override_ds_detect if self.dsname.lower() == parse_cmdline().lower(): cloudinit/sources/__init__.py:1187: in parse_cmdline return parse_cmdline_or_dmi(util.get_cmdline()) cloudinit/util.py:1622: in get_cmdline return _get_cmdline() cloudinit/util.py:1601: in _get_cmdline if is_container(): cloudinit/util.py:2386: in is_container if helper(): cloudinit/util.py:2357: in _is_container_systemd return _cmd_exits_zero(["systemd-detect-virt", "--quiet", "--container"]) cloudinit/util.py:2350: in _cmd_exits_zero subp.subp(cmd) <string>:3: in subp ??? /usr/lib64/python3.12/unittest/mock.py:1134: in __call__ return self._mock_call(*args, **kwargs) /usr/lib64/python3.12/unittest/mock.py:1138: in _mock_call return self._execute_mock_call(*args, **kwargs) /usr/lib64/python3.12/unittest/mock.py:1199: in _execute_mock_call result = effect(*args, **kwargs) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ args = ['systemd-detect-virt', '--quiet', '--container'], other_args = () kwargs = {} def side_effect(args, *other_args, **kwargs): > raise AssertionError("Unexpectedly used subp.subp") E AssertionError: Unexpectedly used subp.subp conftest.py:145: AssertionError https://docs.pytest.org/en/stable/changelog.html For the build logs, see: https://copr-be.cloud.fedoraproject.org/results/thrnciar/pytest/fedora-rawhide-x86_64/07247517-cloud-init/ For all our attempts to build cloud-init with pytest 8, see: https://copr.fedorainfracloud.org/coprs/thrnciar/pytest/package/cloud-init/ Let us know here if you have any questions. Pytest 8 is planned to be included in Fedora 41. And this bugzilla is a heads up before we merge new pytest into rawhide. For more info see a Fedora Change proposal https://fedoraproject.org/wiki/Changes/Pytest_8 We'd appreciate help from the people who know this package best, but if you don't want to work on this now, let us know so we can try to work around it on our side.
I see there is a successfull build with pytest 8 in copr. I am closing this as NOTABUG, sorry for the noise. https://copr.fedorainfracloud.org/coprs/thrnciar/pytest/build/7321288/