From a5f6d8f1366f8421cbccb823356847f1ac6c35e2 Mon Sep 17 00:00:00 2001 From: Mariana Ulaieva Date: Mon, 20 Jan 2020 11:07:22 +0100 Subject: [PATCH 1/2] Factor out packaging utility Until now, it was assumed that the module-build command returned only one build, so it was only one build_id. However, it is possible that the module-build command will build more than one builds and therefore a list of build_ids is needed. Also is needed to watch and cancel more than one build. For this reason run, watch, and cancel methods are methods of the PackagingUtility class instead of Build class. Run method returns list of Build objects instead of build_id. And it's also possible to cancel and to watch on all generated module builds. --- tests/integration/conftest.py | 10 +++ tests/integration/example.test.env.yaml | 5 ++ tests/integration/test_failed_build.py | 11 ++- tests/integration/test_no_components.py | 12 +-- tests/integration/test_normal_build.py | 13 ++-- .../test_resume_cancelled_build.py | 16 ++-- .../integration/test_reuse_all_components.py | 15 ++-- tests/integration/test_reuse_components.py | 13 ++-- tests/integration/test_scratch_build.py | 12 +-- tests/integration/utils.py | 77 ++++++++++++------- 10 files changed, 111 insertions(+), 73 deletions(-) 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..92a17080 100644 --- a/tests/integration/example.test.env.yaml +++ b/tests/integration/example.test.env.yaml @@ -64,3 +64,8 @@ testdata: no_components: module: testmodule branch: test-no-components-branch + stream_expansion: + # For this scenario it's necessary to use testmodule2 as a module and it doesn't make sense + # reusing a former build + 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/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""" From fefeafa5725a08fed886474e2619cc7b4d091fd3 Mon Sep 17 00:00:00 2001 From: Mariana Ulaieva Date: Wed, 22 Jan 2020 13:26:48 +0100 Subject: [PATCH 2/2] Implement the Stream Expansion scenario --- tests/integration/example.test.env.yaml | 5 +++-- tests/integration/test_stream_expansion.py | 21 +++++++++++++++++++++ 2 files changed, 24 insertions(+), 2 deletions(-) create mode 100644 tests/integration/test_stream_expansion.py diff --git a/tests/integration/example.test.env.yaml b/tests/integration/example.test.env.yaml index 92a17080..d9733361 100644 --- a/tests/integration/example.test.env.yaml +++ b/tests/integration/example.test.env.yaml @@ -65,7 +65,8 @@ testdata: module: testmodule branch: test-no-components-branch stream_expansion: - # For this scenario it's necessary to use testmodule2 as a module and it doesn't make sense - # reusing a former build + # 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_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)