diff --git a/tests/integration/conftest.py b/tests/integration/conftest.py index 17478744..a2b81f69 100644 --- a/tests/integration/conftest.py +++ b/tests/integration/conftest.py @@ -28,6 +28,16 @@ def test_env(): return env +@pytest.fixture(scope="session") +def pkg_util(test_env): + """Fixture to interact with the packaging utility + + :return: Packaging utility configured for the tests + :rtype: object of utils.PackagingUtility + """ + return utils.PackagingUtility(test_env["packaging_utility"], test_env["mbs_api"]) + + @pytest.fixture(scope="function") def scenario(request, test_env): """Configuration data for the scenario diff --git a/tests/integration/example.test.env.yaml b/tests/integration/example.test.env.yaml index 60a0fea5..d9733361 100644 --- a/tests/integration/example.test.env.yaml +++ b/tests/integration/example.test.env.yaml @@ -64,3 +64,9 @@ testdata: no_components: module: testmodule branch: test-no-components-branch + stream_expansion: + # testmodule2 buildrequires and requires 2 streams from testmodule. + # These are expected to be built already. + # For this scenario reusing former builds doesn't make sense. + module: testmodule2 + branch: test-stream-expans-branch diff --git a/tests/integration/test_failed_build.py b/tests/integration/test_failed_build.py index 9ee6b391..6d6db213 100644 --- a/tests/integration/test_failed_build.py +++ b/tests/integration/test_failed_build.py @@ -1,10 +1,8 @@ # -*- coding: utf-8 -*- # SPDX-License-Identifier: MIT -import utils - -def test_failed_build(test_env, scenario, repo, koji): +def test_failed_build(pkg_util, scenario, repo, koji): """ Run the build with "rebuild_strategy=all". @@ -13,14 +11,15 @@ def test_failed_build(test_env, scenario, repo, koji): * Check that any other components in the same batch as the failed component are cancelled, if not completed. """ - build = utils.Build(test_env["packaging_utility"], test_env["mbs_api"]) repo.bump() - build.run( + builds = pkg_util.run( "--optional", "rebuild_strategy=all", reuse=scenario.get("build_id"), ) - build.watch() + assert len(builds) == 1 + build = builds[0] + pkg_util.watch(builds) assert build.state_name == "failed" batch = scenario["batch"] diff --git a/tests/integration/test_no_components.py b/tests/integration/test_no_components.py index 36bddf2b..cc088cd8 100644 --- a/tests/integration/test_no_components.py +++ b/tests/integration/test_no_components.py @@ -1,10 +1,8 @@ # -*- coding: utf-8 -*- # SPDX-License-Identifier: MIT -import utils - -def test_no_components(test_env, scenario, repo, koji): +def test_no_components(pkg_util, scenario, repo, koji): """ Submit the testmodule build with `fedpkg module-build` @@ -13,10 +11,12 @@ def test_no_components(test_env, scenario, repo, koji): * Verify that the testmodule build succeeds """ - build = utils.Build(test_env["packaging_utility"], test_env["mbs_api"]) repo.bump() - build.run(reuse=scenario.get("build_id")) - build.watch() + builds = pkg_util.run(reuse=scenario.get("build_id")) + assert len(builds) == 1 + + pkg_util.watch(builds) + build = builds[0] assert build.state_name == "ready" assert not build.data["component_builds"] diff --git a/tests/integration/test_normal_build.py b/tests/integration/test_normal_build.py index 061ffbfb..9a7ffba6 100644 --- a/tests/integration/test_normal_build.py +++ b/tests/integration/test_normal_build.py @@ -1,10 +1,8 @@ # -*- coding: utf-8 -*- # SPDX-License-Identifier: MIT -import utils - -def test_normal_build(test_env, scenario, repo, koji): +def test_normal_build(pkg_util, scenario, repo, koji): """ Run build with `rhpkg-stage module-build --optional rebuild_strategy=all` @@ -17,14 +15,15 @@ def test_normal_build(test_env, scenario, repo, koji): * Check that MBS changed the buildrequired platform to have a suffix of ā€œzā€ if a Platform stream is representing a GA RHEL release. """ - build = utils.Build(test_env["packaging_utility"], test_env["mbs_api"]) repo.bump() - build_id = build.run( + builds = pkg_util.run( "--optional", "rebuild_strategy=all", reuse=scenario.get("build_id"), ) - build.watch() + assert len(builds) == 1 + pkg_util.watch(builds) + build = builds[0] assert sorted(build.component_names()) == sorted(repo.components + ["module-build-macros"]) @@ -36,7 +35,7 @@ def test_normal_build(test_env, scenario, repo, koji): cg_build = koji.get_build(build.nvr()) cg_devel_build = koji.get_build(build.nvr(name_suffix="-devel")) assert cg_build and cg_devel_build - assert cg_devel_build['extra']['typeinfo']['module']['module_build_service_id'] == int(build_id) + assert cg_devel_build['extra']['typeinfo']['module']['module_build_service_id'] == int(build.id) modulemd = koji.get_modulemd(cg_build) actual_platforms = modulemd["data"]["dependencies"][0]["buildrequires"]["platform"] diff --git a/tests/integration/test_resume_cancelled_build.py b/tests/integration/test_resume_cancelled_build.py index c56c5832..3612dd5f 100644 --- a/tests/integration/test_resume_cancelled_build.py +++ b/tests/integration/test_resume_cancelled_build.py @@ -1,11 +1,10 @@ # -*- coding: utf-8 -*- # SPDX-License-Identifier: MIT -import utils import time -def test_resume_cancelled_build(test_env, scenario, repo, koji): +def test_resume_cancelled_build(pkg_util, scenario, repo, koji): """ Run the build with "rebuild_strategy=all". Wait until the module-build-macros build is submitted to Koji. @@ -17,17 +16,20 @@ def test_resume_cancelled_build(test_env, scenario, repo, koji): * Check that the testmodule build succeeded """ - build = utils.Build(test_env["packaging_utility"], test_env["mbs_api"]) repo.bump() - build.run( + builds = pkg_util.run( "--optional", "rebuild_strategy=all", ) + + assert len(builds) == 1 + build = builds[0] build.wait_for_koji_task_id(package="module-build-macros", batch=1) - build.cancel() + pkg_util.cancel(build) # Behave like a human: restarting the build too quickly would lead to an error. time.sleep(10) - build.run("--watch") - + builds = pkg_util.run("--watch") + assert len(builds) == 1 + build = builds[0] assert build.state_name == "ready" assert build.was_cancelled() diff --git a/tests/integration/test_reuse_all_components.py b/tests/integration/test_reuse_all_components.py index da7f27da..175e0ef8 100644 --- a/tests/integration/test_reuse_all_components.py +++ b/tests/integration/test_reuse_all_components.py @@ -1,10 +1,8 @@ # -*- coding: utf-8 -*- # SPDX-License-Identifier: MIT -import utils - -def test_reuse_all_components(test_env, scenario, repo, koji): +def test_reuse_all_components(pkg_util, scenario, repo, koji): """Rebuild the test module again, without changing any of the components with: `fedpkg module-build -w --optional rebuild_strategy=only-changed` @@ -13,23 +11,28 @@ def test_reuse_all_components(test_env, scenario, repo, koji): * Verify that all the components are reused from the first build. * Verify that module-build-macros is not built in the second build. """ - build = utils.Build(test_env["packaging_utility"], test_env["mbs_api"]) repo.bump() - build.run( + builds = pkg_util.run( "--watch", "--optional", "rebuild_strategy=all", reuse=scenario.get("build_id"), ) + assert len(builds) == 1 + + build = builds[0] task_ids = build.component_task_ids() task_ids.pop("module-build-macros") repo.bump() - build.run( + builds = pkg_util.run( "-w", "--optional", "rebuild_strategy=only-changed", reuse=scenario.get("build_id_reused")) + + assert len(builds) == 1 + build = builds[0] reused_task_ids = build.component_task_ids() assert not build.components(package="module-build-macros") diff --git a/tests/integration/test_reuse_components.py b/tests/integration/test_reuse_components.py index d4a49637..d570eacc 100644 --- a/tests/integration/test_reuse_components.py +++ b/tests/integration/test_reuse_components.py @@ -4,7 +4,7 @@ import utils -def test_reuse_components(test_env, scenario, repo, koji): +def test_reuse_components(pkg_util, test_env, scenario, repo, koji): """ Bump the commit of one of the components that MBS uses. Bump the commit of the same testmodule that was mentioned in the preconditions. @@ -16,14 +16,15 @@ def test_reuse_components(test_env, scenario, repo, koji): * Verify that the component with the changed commit was rebuilt. """ repo.bump() - baseline_build = utils.Build(test_env["packaging_utility"], test_env["mbs_api"]) - baseline_build.run( + baseline_builds = pkg_util.run( "--watch", "--optional", "rebuild_strategy=all", reuse=scenario.get("baseline_build_id"), ) + assert len(baseline_builds) == 1 + baseline_build = baseline_builds[0] package = scenario.get("package") component = utils.Component( package, @@ -33,14 +34,14 @@ def test_reuse_components(test_env, scenario, repo, koji): component.bump() repo.bump() - build = utils.Build(test_env["packaging_utility"], test_env["mbs_api"]) - build.run( + builds = pkg_util.run( "--watch", "--optional", "rebuild_strategy=only-changed", reuse=scenario.get("build_id"), ) - + assert len(builds) == 1 + build = builds[0] comp_task_ids_base = baseline_build.component_task_ids() comp_task_ids = build.component_task_ids() comp_task_ids_base.pop('module-build-macros') diff --git a/tests/integration/test_scratch_build.py b/tests/integration/test_scratch_build.py index e33a4199..201358ed 100644 --- a/tests/integration/test_scratch_build.py +++ b/tests/integration/test_scratch_build.py @@ -1,10 +1,8 @@ # -*- coding: utf-8 -*- # SPDX-License-Identifier: MIT -import utils - -def test_scratch_build(test_env, scenario, repo, koji): +def test_scratch_build(pkg_util, scenario, repo, koji): """ Run a scratch build with "rebuild_strategy=all". @@ -14,14 +12,16 @@ def test_scratch_build(test_env, scenario, repo, koji): (as opposed to the "ready" state) * no content generator builds are created in Koji """ - build = utils.Build(test_env["packaging_utility"], test_env["mbs_api"]) - build.run( + builds = pkg_util.run( "--scratch", "--optional", "rebuild_strategy=all", reuse=scenario.get("build_id"), ) - build.watch() + + assert len(builds) == 1 + pkg_util.watch(builds) + build = builds[0] assert build.state_name == "done" assert sorted(build.component_names(state="COMPLETE")) == sorted( diff --git a/tests/integration/test_stream_expansion.py b/tests/integration/test_stream_expansion.py new file mode 100644 index 00000000..284d513d --- /dev/null +++ b/tests/integration/test_stream_expansion.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +# SPDX-License-Identifier: MIT + + +def test_stream_expansion(pkg_util, scenario, repo, koji): + """ + + Submit the testmodule2 build with `rhpkg-stage module-build` + The produced builds can be cancelled after the test cases have been verified to save on + resources and time + + Checks: + * Verify two module builds were generated from this build submission + + """ + repo.bump() + builds = pkg_util.run() + + assert len(builds) == 2 + for build in builds: + pkg_util.cancel(build) diff --git a/tests/integration/utils.py b/tests/integration/utils.py index a59d6368..fd81956c 100644 --- a/tests/integration/utils.py +++ b/tests/integration/utils.py @@ -114,15 +114,12 @@ class Repo: git("push") -class Build: - """Wrapper class to work with module builds +class PackagingUtility: + """Wrapper class to work with the packaging utility configured for the tests :attribute sh.Command _packaging_utility: packaging utility command used to kick off this build :attribute string _mbs_api: URL of the MBS API (including trailing '/') - :attribute string _url: URL of this MBS module build - :attribute string _data: Module build data cache for this build fetched from MBS - :attribute string _module_build_data: Verbose module build data cache for this build """ def __init__(self, packaging_utility, mbs_api): @@ -130,54 +127,76 @@ class Build: _out=sys.stdout, _err=sys.stderr, _tee=True ) self._mbs_api = mbs_api - self._data = None - self._component_data = None - self._build_id = None - self._module_build_data = None def run(self, *args, reuse=None): - """Run a module build + """Run one or more module builds :param args: Options and arguments for the build command - :param int reuse: Optional MBS build id to be reused for this run. - When specified, the corresponding module build will be used, - instead of triggering and waiting for a new one to finish. + :param int reuse: An optional MBS build id or a list of MBS build + ids to be reused for this run. + When specified, the corresponding module build(s) will be used, + instead of triggering and waiting for new one(s) to finish. Intended to be used while developing the tests. - :return: MBS build id of the build created - :rtype: int + :return: list of Build objects for the MBS builds created + :rtype: list of Build objects """ - current_build_id = self._build_id + build_ids = [] + if reuse is not None: - self._build_id = int(reuse) + if isinstance(reuse, list): + build_ids = reuse + else: + build_ids = [reuse] else: stdout = self._packaging_utility("module-build", *args).stdout.decode("utf-8") - self._build_id = int(re.search(self._mbs_api + r"module-builds/(\d+)", stdout).group(1)) - # Clear cached data - if current_build_id != self._build_id: - self._component_data = None - return self._build_id + build_ids = re.findall(self._mbs_api + r"module-builds/(\d+)", stdout) + return [Build(self._mbs_api, int(build_id)) for build_id in build_ids] - def watch(self): - """Watch the build till the finish""" - if self._build_id is None: - raise RuntimeError("Build was not started. Cannot watch.") + def watch(self, builds): + """Watch one or more builds till the finish + :param list builds: list of Build objects of the builds to be watched. + :return: Stdout of the watch command + :rtype: string + """ stdout = self._packaging_utility( - "module-build-watch", str(self._build_id) + "module-build-watch", [str(build.id) for build in builds] ).stdout.decode("utf-8") return stdout - def cancel(self): + def cancel(self, build): """Cancel the module build + :param list build: the Build object of the module build to be cancelled. :return: Standard output of the "module-build-cancel command :rtype: str """ - stdout = self._packaging_utility("module-build-cancel", self._build_id).stdout.decode( + stdout = self._packaging_utility("module-build-cancel", build.id).stdout.decode( "utf-8") return stdout + +class Build: + """Wrapper class to work with module builds + + :attribute string _mbs_api: URL of the MBS API (including trailing '/') + :attribute int _build_id: id of this MBS module build + :attribute string _data: Module build data cache for this build fetched from MBS + :attribute string _module_build_data: Verbose module build data cache for this build + """ + + def __init__(self, mbs_api, build_id): + self._mbs_api = mbs_api + self._data = None + self._component_data = None + self._build_id = build_id + self._module_build_data = None + + @property + def id(self): + return self._build_id + @property def data(self): """Module build data cache for this build fetched from MBS"""