From 76b1821f19bb1195fd72cd4c79b254f8591b1d07 Mon Sep 17 00:00:00 2001 From: Daniel Mach Date: Tue, 1 Nov 2022 10:04:58 +0100 Subject: [PATCH 01/17] Add commandline.pop_project_package_from_args() --- osc/commandline.py | 75 +++++++++++++++++++ tests/test_commandline.py | 147 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 222 insertions(+) create mode 100644 tests/test_commandline.py diff --git a/osc/commandline.py b/osc/commandline.py index d303cff0..10296ed9 100644 --- a/osc/commandline.py +++ b/osc/commandline.py @@ -48,6 +48,81 @@ def get_parser(): return osc.argparser +def pop_project_package_from_args(args, default_project=None, default_package=None, package_is_optional=False): + """ + Get project and package from given `args`. + + :param args: List of command-line arguments. + WARNING: `args` gets modified in this function call! + :type args: list(str) + :param default_project: Used if project cannot be retrieved from `args`. + Resolved from the current working copy if set to '.'. + :type default_project: str + :param default_package: Used if package cannot be retrieved from `args`. + Resolved from the current working copy if set to '.'. + :type default_package: str + :param package_is_optional: Whether to error out when package name cannot be retrieved. + :type package_is_optional: bool + :returns: Project name and package name. + :rtype: tuple(str) + """ + assert isinstance(args, list) + path = Path.cwd() + + try: + project = args.pop(0) + except IndexError: + if not default_project: + raise oscerr.OscValueError("Please specify a project") + project = default_project + + if not isinstance(project, str): + raise TypeError(f"Project should be 'str', found: {type(project).__name__}") + + package = None + + if project == "/": + # no project name (to support listing all projects via `osc ls /`) + project = None + elif project and "/" in project: + # project/package + if project.count("/") != 1: + raise oscerr.OscValueError(f"Argument doesn't match the '/' pattern: {project}") + project, package = project.split("/") + + if project == ".": + # project name taken from the working copy + store = osc_store.Store(path) + project = store.project + + if package is None: + try: + package = args.pop(0) + except IndexError: + if default_package: + package = default_package + else: + if package_is_optional: + return project, None + raise oscerr.OscValueError("Please specify a package") + + if not isinstance(package, str): + raise TypeError(f"Package should be 'str', found: {type(package).__name__}") + + if package == ".": + # package name taken from the working copy + try: + store = osc_store.Store(path) + store.assert_is_package() + package = store.package + except oscerr.NoWorkingCopy: + if not package_is_optional: + raise + package = None + + return project, package + + class Osc(cmdln.Cmdln): """ openSUSE commander is a command-line interface to the Open Build Service. diff --git a/tests/test_commandline.py b/tests/test_commandline.py new file mode 100644 index 00000000..522801ee --- /dev/null +++ b/tests/test_commandline.py @@ -0,0 +1,147 @@ +import os +import shutil +import tempfile +import unittest + +from osc.commandline import pop_project_package_from_args +from osc.oscerr import NoWorkingCopy +from osc.store import Store + + +class TestPopProjectPackageFromArgs(unittest.TestCase): + def _write_store(self, project=None, package=None): + store = Store(self.tmpdir, check=False) + if project: + store.project = project + store.is_project = True + if package: + store.package = package + store.is_project = False + store.is_package = True + + def setUp(self): + self.tmpdir = tempfile.mkdtemp(prefix="osc_test") + os.chdir(self.tmpdir) + + def tearDown(self): + try: + shutil.rmtree(self.tmpdir) + except OSError: + pass + + def test_explicit_project_and_package(self): + args = ["project", "package", "another-arg"] + project, package = pop_project_package_from_args(args) + self.assertEqual(project, "project") + self.assertEqual(package, "package") + self.assertEqual(args, ["another-arg"]) + + def test_defaults(self): + args = ["project"] + project, package = pop_project_package_from_args(args, default_package="default-package") + self.assertEqual(project, "project") + self.assertEqual(package, "default-package") + self.assertEqual(args, []) + + args = ["project"] + project, package = pop_project_package_from_args( + args, default_package="default-package", package_is_optional=True + ) + self.assertEqual(project, "project") + self.assertEqual(package, "default-package") + self.assertEqual(args, []) + + args = [] + project, package = pop_project_package_from_args( + args, default_project="default-project", default_package="default-package" + ) + self.assertEqual(project, "default-project") + self.assertEqual(package, "default-package") + self.assertEqual(args, []) + + args = [] + project, package = pop_project_package_from_args( + args, default_project="default-project", default_package="default-package", package_is_optional=True + ) + self.assertEqual(project, "default-project") + self.assertEqual(package, "default-package") + self.assertEqual(args, []) + + def test_slash_separator(self): + args = ["project/package", "another-arg"] + project, package = pop_project_package_from_args(args) + self.assertEqual(project, "project") + self.assertEqual(package, "package") + self.assertEqual(args, ["another-arg"]) + + args = ["project/", "another-arg"] + project, package = pop_project_package_from_args(args) + self.assertEqual(project, "project") + self.assertEqual(package, "") + self.assertEqual(args, ["another-arg"]) + + def test_no_working_copy(self): + args = [".", "."] + self.assertRaises(NoWorkingCopy, pop_project_package_from_args, args) + + args = [".", "package"] + self.assertRaises(NoWorkingCopy, pop_project_package_from_args, args) + + args = ["project", "."] + self.assertRaises(NoWorkingCopy, pop_project_package_from_args, args) + + def test_project_and_package_from_project_working_copy(self): + self._write_store("store_project") + + args = [".", ".", "another-arg"] + self.assertRaises(NoWorkingCopy, pop_project_package_from_args, args) + + args = ["."] + project, package = pop_project_package_from_args(args, package_is_optional=True) + self.assertEqual(project, "store_project") + self.assertEqual(package, None) + self.assertEqual(args, []) + + args = [] + self.assertRaises(NoWorkingCopy, pop_project_package_from_args, args, default_project=".", default_package=".") + + args = [] + project, package = pop_project_package_from_args( + args, default_project=".", default_package=".", package_is_optional=True + ) + self.assertEqual(project, "store_project") + self.assertEqual(package, None) + self.assertEqual(args, []) + + def test_project_and_package_from_package_working_copy(self): + self._write_store("store_project", "store_package") + + args = [".", ".", "another-arg"] + project, package = pop_project_package_from_args(args) + self.assertEqual(project, "store_project") + self.assertEqual(package, "store_package") + self.assertEqual(args, ["another-arg"]) + + args = ["."] + project, package = pop_project_package_from_args(args, package_is_optional=True) + self.assertEqual(project, "store_project") + self.assertEqual(package, None) + self.assertEqual(args, []) + + args = [] + project, package = pop_project_package_from_args(args, default_project=".", default_package=".") + self.assertEqual(project, "store_project") + self.assertEqual(package, "store_package") + self.assertEqual(args, []) + + args = [] + project, package = pop_project_package_from_args( + args, default_project=".", default_package=".", package_is_optional=True + ) + self.assertEqual(project, "store_project") + self.assertEqual(package, "store_package") + self.assertEqual(args, []) + + +if __name__ == "__main__": + unittest.main() From d81c6887596747f761d4d96f69e59eae60ef53dd Mon Sep 17 00:00:00 2001 From: Daniel Mach Date: Tue, 1 Nov 2022 14:24:41 +0100 Subject: [PATCH 02/17] Add _private.api.post() function --- osc/_private/api.py | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/osc/_private/api.py b/osc/_private/api.py index 9fe29cc3..801ed37f 100644 --- a/osc/_private/api.py +++ b/osc/_private/api.py @@ -33,6 +33,31 @@ def get(apiurl, path, query=None): return root +def post(apiurl, path, query=None): + """ + Send a POST request to OBS. + + :param apiurl: OBS apiurl. + :type apiurl: str + :param path: URL path segments. + :type path: list(str) + :param query: URL query values. + :type query: dict(str, str) + :returns: Parsed XML root. + :rtype: xml.etree.ElementTree.Element + """ + assert apiurl + assert path + + if not isinstance(path, (list, tuple)): + raise TypeError("Argument `path` expects a list of strings") + + url = osc_core.makeurl(apiurl, path, query) + with osc_connection.http_POST(url) as f: + root = osc_core.ET.parse(f).getroot() + return root + + def find_nodes(root, root_name, node_name): """ Find nodes with given `node_name`. From 78c3cf1c4cecdafd47aae040efb658cc8131d212 Mon Sep 17 00:00:00 2001 From: Daniel Mach Date: Tue, 1 Nov 2022 14:26:28 +0100 Subject: [PATCH 03/17] Add functions for printing to stdout or debug outputs --- osc/_private/__init__.py | 2 ++ osc/_private/common.py | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 37 insertions(+) create mode 100644 osc/_private/common.py diff --git a/osc/_private/__init__.py b/osc/_private/__init__.py index 14976d5e..6c4d4302 100644 --- a/osc/_private/__init__.py +++ b/osc/_private/__init__.py @@ -3,5 +3,7 @@ # # The cherry-picked imports will be the supported API. +from .common import print_msg +from .common import format_msg_project_package_options from .package import ApiPackage from .request import forward_request diff --git a/osc/_private/common.py b/osc/_private/common.py new file mode 100644 index 00000000..30cbe5c4 --- /dev/null +++ b/osc/_private/common.py @@ -0,0 +1,35 @@ +import sys + + +def print_msg(msg, print_to="debug"): + from .. import conf + + if print_to is None: + return + elif print_to == "debug": + if conf.config["debug"]: + print(f"DEBUG: {msg}", file=sys.stderr) + elif print_to == "stdout": + print(msg) + else: + raise ValueError(f"Invalid value of the 'output' option: {output}") + + +def format_msg_project_package_options(msg, project=None, package=None, **options): + """ + Format msg, project, package and options into a meaningful message + that can be printed out directly or as a debug message. + """ + if project: + msg += f" project: '{project}'" + + if package: + msg += f" package: '{package}'" + + msg_options = [key.replace("_", "-") for key, value in options.items() if value] + if msg_options: + msg_options.sort() + msg_options_str = ", ".join(msg_options) + msg += f" options: {msg_options_str}" + + return msg From ad85ff437f9040e5b68febdf3d554042f33bdfc1 Mon Sep 17 00:00:00 2001 From: Daniel Mach Date: Tue, 1 Nov 2022 14:29:23 +0100 Subject: [PATCH 04/17] Move addchannels code from commandline to _private --- .../features/addchannels-pkgcheckout.feature | 32 ++++++++++++++++ .../features/addchannels-prjcheckout.feature | 32 ++++++++++++++++ .../addchannels-project-package.feature | 38 +++++++++++++++++++ behave/features/addchannels-project.feature | 31 +++++++++++++++ osc/_private/__init__.py | 1 + osc/_private/api_source.py | 30 +++++++++++++++ osc/commandline.py | 33 ++++------------ 7 files changed, 172 insertions(+), 25 deletions(-) create mode 100644 behave/features/addchannels-pkgcheckout.feature create mode 100644 behave/features/addchannels-prjcheckout.feature create mode 100644 behave/features/addchannels-project-package.feature create mode 100644 behave/features/addchannels-project.feature create mode 100644 osc/_private/api_source.py diff --git a/behave/features/addchannels-pkgcheckout.feature b/behave/features/addchannels-pkgcheckout.feature new file mode 100644 index 00000000..54013d22 --- /dev/null +++ b/behave/features/addchannels-pkgcheckout.feature @@ -0,0 +1,32 @@ +Feature: `osc addchannels` command + + +# common steps for all scenarios +Background: + Given I set working directory to "{context.osc.temp}" + And I execute osc with args "checkout test:factory test-pkgA" + And I set working directory to "{context.osc.temp}/test:factory/test-pkgA" + + +Scenario: Run `osc addchannels` + When I execute osc with args "addchannels" + Then stdout is + """ + Adding channels to project: 'test:factory' package: 'test-pkgA' + """ + + +Scenario: Run `osc addchannels --enable-all` + When I execute osc with args "addchannels --enable-all" + Then stdout is + """ + Adding channels to project: 'test:factory' package: 'test-pkgA' options: enable-all + """ + + +Scenario: Run `osc addchannels --skip-disabled` + When I execute osc with args "addchannels --skip-disabled" + Then stdout is + """ + Adding channels to project: 'test:factory' package: 'test-pkgA' options: skip-disabled + """ diff --git a/behave/features/addchannels-prjcheckout.feature b/behave/features/addchannels-prjcheckout.feature new file mode 100644 index 00000000..a968e7fb --- /dev/null +++ b/behave/features/addchannels-prjcheckout.feature @@ -0,0 +1,32 @@ +Feature: `osc addchannels` command + + +# common steps for all scenarios +Background: + Given I set working directory to "{context.osc.temp}" + And I execute osc with args "checkout test:factory" + And I set working directory to "{context.osc.temp}/test:factory" + + +Scenario: Run `osc addchannels` + When I execute osc with args "addchannels" + Then stdout is + """ + Adding channels to project: 'test:factory' + """ + + +Scenario: Run `osc addchannels --enable-all` + When I execute osc with args "addchannels --enable-all" + Then stdout is + """ + Adding channels to project: 'test:factory' options: enable-all + """ + + +Scenario: Run `osc addchannels --skip-disabled` + When I execute osc with args "addchannels --skip-disabled" + Then stdout is + """ + Adding channels to project: 'test:factory' options: skip-disabled + """ diff --git a/behave/features/addchannels-project-package.feature b/behave/features/addchannels-project-package.feature new file mode 100644 index 00000000..34c13786 --- /dev/null +++ b/behave/features/addchannels-project-package.feature @@ -0,0 +1,38 @@ +Feature: `osc addchannels` command + + +# common steps for all scenarios +Background: + Given I set working directory to "{context.osc.temp}" + + +Scenario: Run `osc addchannels ` + When I execute osc with args "addchannels test:factory test-pkgA" + Then stdout is + """ + Adding channels to project: 'test:factory' package: 'test-pkgA' + """ + + +Scenario: Run `osc addchannels /` + When I execute osc with args "addchannels test:factory/test-pkgA" + Then stdout is + """ + Adding channels to project: 'test:factory' package: 'test-pkgA' + """ + + +Scenario: Run `osc addchannels --enable-all` + When I execute osc with args "addchannels test:factory test-pkgA --enable-all" + Then stdout is + """ + Adding channels to project: 'test:factory' package: 'test-pkgA' options: enable-all + """ + + +Scenario: Run `osc addchannels --skip-disabled` + When I execute osc with args "addchannels test:factory test-pkgA --skip-disabled" + Then stdout is + """ + Adding channels to project: 'test:factory' package: 'test-pkgA' options: skip-disabled + """ diff --git a/behave/features/addchannels-project.feature b/behave/features/addchannels-project.feature new file mode 100644 index 00000000..2c1a6cf7 --- /dev/null +++ b/behave/features/addchannels-project.feature @@ -0,0 +1,31 @@ +Feature: `osc addchannels` command + + +# common steps for all scenarios +Background: + Given I set working directory to "{context.osc.temp}" + + +Scenario: Run `osc addchannels ` + When I execute osc with args "addchannels test:factory" + Then stdout is + """ + Adding channels to project: 'test:factory' + """ + + +Scenario: Run `osc addchannels --enable-all` + When I execute osc with args "addchannels test:factory --enable-all" + Then stdout is + """ + Adding channels to project: 'test:factory' options: enable-all + """ + + +Scenario: Run `osc addchannels --skip-disabled` + When I execute osc with args "addchannels test:factory --skip-disabled" + Then stdout is + """ + Adding channels to project: 'test:factory' options: skip-disabled + """ + diff --git a/osc/_private/__init__.py b/osc/_private/__init__.py index 6c4d4302..32eaa0cb 100644 --- a/osc/_private/__init__.py +++ b/osc/_private/__init__.py @@ -3,6 +3,7 @@ # # The cherry-picked imports will be the supported API. +from .api_source import add_channels from .common import print_msg from .common import format_msg_project_package_options from .package import ApiPackage diff --git a/osc/_private/api_source.py b/osc/_private/api_source.py new file mode 100644 index 00000000..f1e7abb7 --- /dev/null +++ b/osc/_private/api_source.py @@ -0,0 +1,30 @@ +from . import api +from .common import format_msg_project_package_options +from .common import print_msg +from .. import oscerr + + +def add_channels(apiurl, project, package=None, enable_all=False, skip_disabled=False, print_to="debug"): + if all((enable_all, skip_disabled)): + raise oscerr.OscValueError("Options 'enable_all' and 'skip_disabled' are mutually exclusive") + + msg = format_msg_project_package_options( + "Adding channels to", + project, + package, + enable_all=enable_all, + skip_disabled=skip_disabled, + ) + print_msg(msg, print_to=print_to) + + url_path = ["source", project] + if package: + url_path += [package] + + url_query = {"cmd": "addchannels"} + if enable_all: + url_query["mode"] = "enable_all" + if skip_disabled: + url_query["mode"] = "skip_disabled" + + return api.post(apiurl, url_path, url_query) diff --git a/osc/commandline.py b/osc/commandline.py index 10296ed9..a4d91e66 100644 --- a/osc/commandline.py +++ b/osc/commandline.py @@ -629,36 +629,19 @@ class Osc(cmdln.Cmdln): Examples: osc addchannels [PROJECT [PACKAGE]] """ - - args = slash_split(args) apiurl = self.get_api_url() - localdir = Path.cwd() - channel = None - if not args: - if is_project_dir(localdir) or is_package_dir(localdir): - project = store_read_project(localdir) - elif is_package_dir(localdir): - project = store_read_project(localdir) - channel = store_read_package(localdir) - else: - raise oscerr.WrongArgs('Either specify project [package] or call it from a project/package working copy') - else: - project = self._process_project_name(args[0]) - query = {'cmd': 'addchannels'} + args = list(args) + project, package = pop_project_package_from_args( + args, default_project=".", default_package=".", package_is_optional=True + ) if opts.enable_all and opts.skip_disabled: - raise oscerr.WrongOptions('--enable-all and --skip-disabled options are mutually exclusive') - elif opts.enable_all: - query['mode'] = 'enable_all' - elif opts.skip_disabled: - query['mode'] = 'skip_disabled' + self.argparse_error("Options '--enable-all' and '--skip-disabled' are mutually exclusive") - print("Looking for channels...") - url = makeurl(apiurl, ['source', project], query=query) - if channel: - url = makeurl(apiurl, ['source', project, channel], query=query) - f = http_POST(url) + _private.add_channels( + apiurl, project, package, enable_all=opts.enable_all, skip_disabled=opts.skip_disabled, print_to="stdout" + ) @cmdln.alias('enablechannel') def do_enablechannels(self, subcmd, opts, *args): From 59e7013cf297a4120945ea439fb81bdae48a0262 Mon Sep 17 00:00:00 2001 From: Daniel Mach Date: Tue, 1 Nov 2022 14:50:18 +0100 Subject: [PATCH 05/17] Move enablechannels code from commandline to _private --- .../enablechannels-pkgcheckout.feature | 16 +++++++++ .../enablechannels-prjcheckout.feature | 16 +++++++++ .../enablechannels-project-package.feature | 22 +++++++++++++ .../features/enablechannels-project.feature | 14 ++++++++ osc/_private/__init__.py | 1 + osc/_private/api_source.py | 20 +++++++++++ osc/commandline.py | 33 ++++--------------- 7 files changed, 95 insertions(+), 27 deletions(-) create mode 100644 behave/features/enablechannels-pkgcheckout.feature create mode 100644 behave/features/enablechannels-prjcheckout.feature create mode 100644 behave/features/enablechannels-project-package.feature create mode 100644 behave/features/enablechannels-project.feature diff --git a/behave/features/enablechannels-pkgcheckout.feature b/behave/features/enablechannels-pkgcheckout.feature new file mode 100644 index 00000000..53bbc0f9 --- /dev/null +++ b/behave/features/enablechannels-pkgcheckout.feature @@ -0,0 +1,16 @@ +Feature: `osc enablechannels` command + + +# common steps for all scenarios +Background: + Given I set working directory to "{context.osc.temp}" + And I execute osc with args "checkout test:factory test-pkgA" + And I set working directory to "{context.osc.temp}/test:factory/test-pkgA" + + +Scenario: Run `osc enablechannels` + When I execute osc with args "enablechannels" + Then stdout is + """ + Enabling channels in project: 'test:factory' package: 'test-pkgA' + """ diff --git a/behave/features/enablechannels-prjcheckout.feature b/behave/features/enablechannels-prjcheckout.feature new file mode 100644 index 00000000..df80b6e9 --- /dev/null +++ b/behave/features/enablechannels-prjcheckout.feature @@ -0,0 +1,16 @@ +Feature: `osc enablechannels` command + + +# common steps for all scenarios +Background: + Given I set working directory to "{context.osc.temp}" + And I execute osc with args "checkout test:factory" + And I set working directory to "{context.osc.temp}/test:factory" + + +Scenario: Run `osc enablechannels` + When I execute osc with args "enablechannels" + Then stdout is + """ + Enabling channels in project: 'test:factory' + """ diff --git a/behave/features/enablechannels-project-package.feature b/behave/features/enablechannels-project-package.feature new file mode 100644 index 00000000..ac089cf8 --- /dev/null +++ b/behave/features/enablechannels-project-package.feature @@ -0,0 +1,22 @@ +Feature: `osc enablechannels` command + + +# common steps for all scenarios +Background: + Given I set working directory to "{context.osc.temp}" + + +Scenario: Run `osc enablechannels ` + When I execute osc with args "enablechannels test:factory test-pkgA" + Then stdout is + """ + Enabling channels in project: 'test:factory' package: 'test-pkgA' + """ + + +Scenario: Run `osc enablechannels /` + When I execute osc with args "enablechannels test:factory/test-pkgA" + Then stdout is + """ + Enabling channels in project: 'test:factory' package: 'test-pkgA' + """ diff --git a/behave/features/enablechannels-project.feature b/behave/features/enablechannels-project.feature new file mode 100644 index 00000000..33c85e28 --- /dev/null +++ b/behave/features/enablechannels-project.feature @@ -0,0 +1,14 @@ +Feature: `osc enablechannels` command + + +# common steps for all scenarios +Background: + Given I set working directory to "{context.osc.temp}" + + +Scenario: Run `osc enablechannels ` + When I execute osc with args "enablechannels test:factory" + Then stdout is + """ + Enabling channels in project: 'test:factory' + """ diff --git a/osc/_private/__init__.py b/osc/_private/__init__.py index 32eaa0cb..fbbdf092 100644 --- a/osc/_private/__init__.py +++ b/osc/_private/__init__.py @@ -4,6 +4,7 @@ # The cherry-picked imports will be the supported API. from .api_source import add_channels +from .api_source import enable_channels from .common import print_msg from .common import format_msg_project_package_options from .package import ApiPackage diff --git a/osc/_private/api_source.py b/osc/_private/api_source.py index f1e7abb7..643c46be 100644 --- a/osc/_private/api_source.py +++ b/osc/_private/api_source.py @@ -28,3 +28,23 @@ def add_channels(apiurl, project, package=None, enable_all=False, skip_disabled= url_query["mode"] = "skip_disabled" return api.post(apiurl, url_path, url_query) + + +def enable_channels(apiurl, project, package=None, print_to="debug"): + msg = format_msg_project_package_options( + "Enabling channels in", + project, + package, + ) + print_msg(msg, print_to=print_to) + + url_path = ["source", project] + if package: + url_path += [package] + + if package: + url_query = {"cmd": "enablechannel"} + else: + url_query = {"cmd": "modifychannels", "mode": "enable_all"} + + return api.post(apiurl, url_path, url_query) diff --git a/osc/commandline.py b/osc/commandline.py index a4d91e66..8103191c 100644 --- a/osc/commandline.py +++ b/osc/commandline.py @@ -653,37 +653,16 @@ class Osc(cmdln.Cmdln): The command can be used to enable a specific one or all channels of a project. Examples: - osc enablechannels [PROJECT [CHANNEL_PACKAGE]] + osc enablechannels [PROJECT [PACKAGE]] """ - - args = slash_split(args) apiurl = self.get_api_url() - localdir = Path.cwd() - channel = None - if not args: - if is_project_dir(localdir): - project = store_read_project(localdir) - elif is_package_dir(localdir): - project = store_read_project(localdir) - channel = store_read_package(localdir) - else: - raise oscerr.WrongArgs('Either specify project [package] or call it from a project/package working copy') - else: - project = self._process_project_name(args[0]) - if len(args) > 1: - channel = args[1] - query = {} - if channel: - query['cmd'] = 'enablechannel' - else: - query = {'cmd': 'modifychannels', 'mode': 'enable_all'} + args = list(args) + project, package = pop_project_package_from_args( + args, default_project=".", default_package=".", package_is_optional=True + ) - print("Enable channel(s)...") - url = makeurl(apiurl, ['source', project], query=query) - if channel: - url = makeurl(apiurl, ['source', project, channel], query=query) - f = http_POST(url) + _private.enable_channels(apiurl, project, package, print_to="stdout") @cmdln.option('-f', '--force', action='store_true', help='force generation of new patchinfo file, do not update existing one.') From 3faf01c86279dfb87e1985f1cc61c6e087dbc243 Mon Sep 17 00:00:00 2001 From: Daniel Mach Date: Thu, 3 Nov 2022 09:40:15 +0100 Subject: [PATCH 06/17] behave: Add 'stderr is' step --- behave/features/steps/common.py | 27 ++++++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/behave/features/steps/common.py b/behave/features/steps/common.py index 4f2c7644..abd6c2fc 100644 --- a/behave/features/steps/common.py +++ b/behave/features/steps/common.py @@ -93,8 +93,8 @@ def step_impl(context, text): @behave.step("stdout is") def step_impl(context): - expected = context.text.format(context=context).rstrip().split('\n') - found = context.cmd_stdout.rstrip().split('\n') + expected = context.text.format(context=context).rstrip().split("\n") + found = context.cmd_stdout.rstrip().split("\n") if found == expected: return @@ -104,6 +104,19 @@ def step_impl(context): raise AssertionError(f"Stdout is not:\n{expected_str}\n\nActual stdout:\n{found_str}") +@behave.step("stderr is") +def step_impl(context): + expected = context.text.format(context=context).rstrip().split("\n") + found = context.cmd_stderr.rstrip().split("\n") + + if found == expected: + return + + expected_str = "\n".join(expected) + found_str = "\n".join(found) + raise AssertionError(f"Stderr is not:\n{expected_str}\n\nActual stderr:\n{found_str}") + + @behave.step('I set working directory to "{path}"') def step_impl(context, path): path = path.format(context=context) @@ -183,7 +196,15 @@ def step_impl(context, path, mode): @behave.step("the exit code is {exitcode}") def the_exit_code_is(context, exitcode): if context.cmd_exitcode != int(exitcode): - raise AssertionError(f"Command has exited with code {context.cmd_exitcode}: {context.cmd}") + lines = [ + f"Command has exited with code {context.cmd_exitcode}: {context.cmd}", + "> stdout:", + context.cmd_stdout.strip(), + "", + "> stderr:", + context.cmd_stderr.strip(), + ] + raise AssertionError("\n".join(lines)) context.cmd_exitcode_checked = True From 6394867952c000d065327ddcaff5da5db7e7ce79 Mon Sep 17 00:00:00 2001 From: Daniel Mach Date: Thu, 3 Nov 2022 09:58:08 +0100 Subject: [PATCH 07/17] behave: Remove InsecureRequestWarning that is irrelevant to the tests from stderr --- behave/features/steps/osc.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/behave/features/steps/osc.py b/behave/features/steps/osc.py index 95972b99..71295cc4 100644 --- a/behave/features/steps/osc.py +++ b/behave/features/steps/osc.py @@ -1,4 +1,5 @@ import os +import re import shutil import tempfile import time @@ -54,6 +55,8 @@ def step_impl(context, args): cmd = context.osc.get_cmd() + [args] cmd = " ".join(cmd) run_in_context(context, cmd, can_fail=True) + # remove InsecureRequestWarning that is irrelevant to the tests + context.cmd_stderr = re.sub(r"^.*InsecureRequestWarning.*\n warnings.warn\(\n", "", context.cmd_stderr) @behave.step('I wait for osc results for "{project}" "{package}"') From c6b30e500f28d8f059b63310564b7f785dd8b31c Mon Sep 17 00:00:00 2001 From: Daniel Mach Date: Mon, 7 Nov 2022 16:18:52 +0100 Subject: [PATCH 08/17] Add 'dest_project' and 'dest_package' args to format_msg_project_package_options() --- behave/features/addchannels-pkgcheckout.feature | 6 +++--- behave/features/addchannels-prjcheckout.feature | 6 +++--- .../features/addchannels-project-package.feature | 8 ++++---- behave/features/addchannels-project.feature | 6 +++--- .../features/enablechannels-pkgcheckout.feature | 2 +- .../features/enablechannels-prjcheckout.feature | 2 +- .../enablechannels-project-package.feature | 4 ++-- behave/features/enablechannels-project.feature | 2 +- osc/_private/common.py | 16 ++++++++++------ 9 files changed, 28 insertions(+), 24 deletions(-) diff --git a/behave/features/addchannels-pkgcheckout.feature b/behave/features/addchannels-pkgcheckout.feature index 54013d22..4e4c908a 100644 --- a/behave/features/addchannels-pkgcheckout.feature +++ b/behave/features/addchannels-pkgcheckout.feature @@ -12,7 +12,7 @@ Scenario: Run `osc addchannels` When I execute osc with args "addchannels" Then stdout is """ - Adding channels to project: 'test:factory' package: 'test-pkgA' + Adding channels to package 'test:factory/test-pkgA' """ @@ -20,7 +20,7 @@ Scenario: Run `osc addchannels --enable-all` When I execute osc with args "addchannels --enable-all" Then stdout is """ - Adding channels to project: 'test:factory' package: 'test-pkgA' options: enable-all + Adding channels to package 'test:factory/test-pkgA' options: enable-all """ @@ -28,5 +28,5 @@ Scenario: Run `osc addchannels --skip-disabled` When I execute osc with args "addchannels --skip-disabled" Then stdout is """ - Adding channels to project: 'test:factory' package: 'test-pkgA' options: skip-disabled + Adding channels to package 'test:factory/test-pkgA' options: skip-disabled """ diff --git a/behave/features/addchannels-prjcheckout.feature b/behave/features/addchannels-prjcheckout.feature index a968e7fb..44ecf1fd 100644 --- a/behave/features/addchannels-prjcheckout.feature +++ b/behave/features/addchannels-prjcheckout.feature @@ -12,7 +12,7 @@ Scenario: Run `osc addchannels` When I execute osc with args "addchannels" Then stdout is """ - Adding channels to project: 'test:factory' + Adding channels to project 'test:factory' """ @@ -20,7 +20,7 @@ Scenario: Run `osc addchannels --enable-all` When I execute osc with args "addchannels --enable-all" Then stdout is """ - Adding channels to project: 'test:factory' options: enable-all + Adding channels to project 'test:factory' options: enable-all """ @@ -28,5 +28,5 @@ Scenario: Run `osc addchannels --skip-disabled` When I execute osc with args "addchannels --skip-disabled" Then stdout is """ - Adding channels to project: 'test:factory' options: skip-disabled + Adding channels to project 'test:factory' options: skip-disabled """ diff --git a/behave/features/addchannels-project-package.feature b/behave/features/addchannels-project-package.feature index 34c13786..ccb74008 100644 --- a/behave/features/addchannels-project-package.feature +++ b/behave/features/addchannels-project-package.feature @@ -10,7 +10,7 @@ Scenario: Run `osc addchannels ` When I execute osc with args "addchannels test:factory test-pkgA" Then stdout is """ - Adding channels to project: 'test:factory' package: 'test-pkgA' + Adding channels to package 'test:factory/test-pkgA' """ @@ -18,7 +18,7 @@ Scenario: Run `osc addchannels /` When I execute osc with args "addchannels test:factory/test-pkgA" Then stdout is """ - Adding channels to project: 'test:factory' package: 'test-pkgA' + Adding channels to package 'test:factory/test-pkgA' """ @@ -26,7 +26,7 @@ Scenario: Run `osc addchannels --enable-all` When I execute osc with args "addchannels test:factory test-pkgA --enable-all" Then stdout is """ - Adding channels to project: 'test:factory' package: 'test-pkgA' options: enable-all + Adding channels to package 'test:factory/test-pkgA' options: enable-all """ @@ -34,5 +34,5 @@ Scenario: Run `osc addchannels --skip-disabled` When I execute osc with args "addchannels test:factory test-pkgA --skip-disabled" Then stdout is """ - Adding channels to project: 'test:factory' package: 'test-pkgA' options: skip-disabled + Adding channels to package 'test:factory/test-pkgA' options: skip-disabled """ diff --git a/behave/features/addchannels-project.feature b/behave/features/addchannels-project.feature index 2c1a6cf7..2e9bc83b 100644 --- a/behave/features/addchannels-project.feature +++ b/behave/features/addchannels-project.feature @@ -10,7 +10,7 @@ Scenario: Run `osc addchannels ` When I execute osc with args "addchannels test:factory" Then stdout is """ - Adding channels to project: 'test:factory' + Adding channels to project 'test:factory' """ @@ -18,7 +18,7 @@ Scenario: Run `osc addchannels --enable-all` When I execute osc with args "addchannels test:factory --enable-all" Then stdout is """ - Adding channels to project: 'test:factory' options: enable-all + Adding channels to project 'test:factory' options: enable-all """ @@ -26,6 +26,6 @@ Scenario: Run `osc addchannels --skip-disabled` When I execute osc with args "addchannels test:factory --skip-disabled" Then stdout is """ - Adding channels to project: 'test:factory' options: skip-disabled + Adding channels to project 'test:factory' options: skip-disabled """ diff --git a/behave/features/enablechannels-pkgcheckout.feature b/behave/features/enablechannels-pkgcheckout.feature index 53bbc0f9..d11916b5 100644 --- a/behave/features/enablechannels-pkgcheckout.feature +++ b/behave/features/enablechannels-pkgcheckout.feature @@ -12,5 +12,5 @@ Scenario: Run `osc enablechannels` When I execute osc with args "enablechannels" Then stdout is """ - Enabling channels in project: 'test:factory' package: 'test-pkgA' + Enabling channels in package 'test:factory/test-pkgA' """ diff --git a/behave/features/enablechannels-prjcheckout.feature b/behave/features/enablechannels-prjcheckout.feature index df80b6e9..457b42a6 100644 --- a/behave/features/enablechannels-prjcheckout.feature +++ b/behave/features/enablechannels-prjcheckout.feature @@ -12,5 +12,5 @@ Scenario: Run `osc enablechannels` When I execute osc with args "enablechannels" Then stdout is """ - Enabling channels in project: 'test:factory' + Enabling channels in project 'test:factory' """ diff --git a/behave/features/enablechannels-project-package.feature b/behave/features/enablechannels-project-package.feature index ac089cf8..142b9555 100644 --- a/behave/features/enablechannels-project-package.feature +++ b/behave/features/enablechannels-project-package.feature @@ -10,7 +10,7 @@ Scenario: Run `osc enablechannels ` When I execute osc with args "enablechannels test:factory test-pkgA" Then stdout is """ - Enabling channels in project: 'test:factory' package: 'test-pkgA' + Enabling channels in package 'test:factory/test-pkgA' """ @@ -18,5 +18,5 @@ Scenario: Run `osc enablechannels /` When I execute osc with args "enablechannels test:factory/test-pkgA" Then stdout is """ - Enabling channels in project: 'test:factory' package: 'test-pkgA' + Enabling channels in package 'test:factory/test-pkgA' """ diff --git a/behave/features/enablechannels-project.feature b/behave/features/enablechannels-project.feature index 33c85e28..2df78504 100644 --- a/behave/features/enablechannels-project.feature +++ b/behave/features/enablechannels-project.feature @@ -10,5 +10,5 @@ Scenario: Run `osc enablechannels ` When I execute osc with args "enablechannels test:factory" Then stdout is """ - Enabling channels in project: 'test:factory' + Enabling channels in project 'test:factory' """ diff --git a/osc/_private/common.py b/osc/_private/common.py index 30cbe5c4..80e83d66 100644 --- a/osc/_private/common.py +++ b/osc/_private/common.py @@ -15,16 +15,20 @@ def print_msg(msg, print_to="debug"): raise ValueError(f"Invalid value of the 'output' option: {output}") -def format_msg_project_package_options(msg, project=None, package=None, **options): +def format_msg_project_package_options(msg, project=None, package=None, dest_project=None, dest_package=None, **options): """ - Format msg, project, package and options into a meaningful message + Format msg, project, package, dest_project, dest_package and options into a meaningful message that can be printed out directly or as a debug message. """ - if project: - msg += f" project: '{project}'" + if project and not package: + msg += f" project '{project}'" + else: + msg += f" package '{project}/{package}'" - if package: - msg += f" package: '{package}'" + if dest_project and not dest_package: + msg += f" to project '{dest_project}'" + elif dest_project and dest_package: + msg += f" to package '{dest_project}/{dest_package}'" msg_options = [key.replace("_", "-") for key, value in options.items() if value] if msg_options: From 6dcc45f27201255eb29afadfd0c38191b0d73eec Mon Sep 17 00:00:00 2001 From: Daniel Mach Date: Mon, 7 Nov 2022 10:36:03 +0100 Subject: [PATCH 09/17] Improve argument handling in the 'develproject' command --- .../features/develproject-pkgcheckout.feature | 16 ++++ .../features/develproject-prjcheckout.feature | 17 ++++ .../develproject-project-package.feature | 32 +++++++ behave/features/develproject-project.feature | 15 ++++ .../setdevelproject-pkgcheckout.feature | 57 +++++++++++++ .../setdevelproject-prjcheckout.feature | 17 ++++ .../setdevelproject-project-package.feature | 54 ++++++++++++ osc/commandline.py | 84 +++++++++++-------- osc/core.py | 17 +++- tests/test_commandline.py | 7 +- 10 files changed, 274 insertions(+), 42 deletions(-) create mode 100644 behave/features/develproject-pkgcheckout.feature create mode 100644 behave/features/develproject-prjcheckout.feature create mode 100644 behave/features/develproject-project-package.feature create mode 100644 behave/features/develproject-project.feature create mode 100644 behave/features/setdevelproject-pkgcheckout.feature create mode 100644 behave/features/setdevelproject-prjcheckout.feature create mode 100644 behave/features/setdevelproject-project-package.feature diff --git a/behave/features/develproject-pkgcheckout.feature b/behave/features/develproject-pkgcheckout.feature new file mode 100644 index 00000000..49af6d0e --- /dev/null +++ b/behave/features/develproject-pkgcheckout.feature @@ -0,0 +1,16 @@ +Feature: `osc develproject` command + + +# common steps for all scenarios +Background: + Given I set working directory to "{context.osc.temp}" + And I execute osc with args "checkout test:factory test-pkgA" + And I set working directory to "{context.osc.temp}/test:factory/test-pkgA" + + +Scenario: Run `osc develproject` + When I execute osc with args "develproject" + Then stdout is + """ + test:devel/test-pkgA + """ diff --git a/behave/features/develproject-prjcheckout.feature b/behave/features/develproject-prjcheckout.feature new file mode 100644 index 00000000..17881377 --- /dev/null +++ b/behave/features/develproject-prjcheckout.feature @@ -0,0 +1,17 @@ +Feature: `osc develproject` command + + +# common steps for all scenarios +Background: + Given I set working directory to "{context.osc.temp}" + And I execute osc with args "checkout test:factory" + And I set working directory to "{context.osc.temp}/test:factory" + + +Scenario: Run `osc develproject` + When I execute osc with args "develproject" + Then the exit code is 1 + And stderr is + """ + Directory '{context.osc.temp}/test:factory' is not a working copy of a package + """ diff --git a/behave/features/develproject-project-package.feature b/behave/features/develproject-project-package.feature new file mode 100644 index 00000000..92ec9b03 --- /dev/null +++ b/behave/features/develproject-project-package.feature @@ -0,0 +1,32 @@ +Feature: `osc develproject` command + + +# common steps for all scenarios +Background: + Given I set working directory to "{context.osc.temp}" + + +Scenario: Run `osc develproject` + When I execute osc with args "develproject test:factory test-pkgA" + Then stdout is + """ + test:devel/test-pkgA + """ + + +Scenario: Run `osc develproject` + When I execute osc with args "develproject test:factory test-pkgB" + Then the exit code is 1 + And stderr is + """ + Package test:factory/test-pkgB has no devel project + """ + + +Scenario: Run `osc develproject` + When I execute osc with args "develproject test:factory/test-pkgB" + Then the exit code is 1 + And stderr is + """ + Package test:factory/test-pkgB has no devel project + """ diff --git a/behave/features/develproject-project.feature b/behave/features/develproject-project.feature new file mode 100644 index 00000000..547f9782 --- /dev/null +++ b/behave/features/develproject-project.feature @@ -0,0 +1,15 @@ +Feature: `osc develproject` command + + +# common steps for all scenarios +Background: + Given I set working directory to "{context.osc.temp}" + + +Scenario: Run `osc develproject` + When I execute osc with args "develproject test:factory" + Then the exit code is 1 + And stderr is + """ + *** Error: Please specify a package + """ diff --git a/behave/features/setdevelproject-pkgcheckout.feature b/behave/features/setdevelproject-pkgcheckout.feature new file mode 100644 index 00000000..40efb702 --- /dev/null +++ b/behave/features/setdevelproject-pkgcheckout.feature @@ -0,0 +1,57 @@ +Feature: `osc setdevelproject` command + + +# common steps for all scenarios +Background: + Given I set working directory to "{context.osc.temp}" + And I execute osc with args "checkout test:factory test-pkgA" + And I set working directory to "{context.osc.temp}/test:factory/test-pkgA" + + +@destructive +Scenario: Run `osc setdevelproject ` + When I execute osc with args "setdevelproject test:devel" + Then the exit code is 0 + And stdout is + """ + Setting devel project of package 'test:factory/test-pkgA' to package 'test:devel/test-pkgA' + Sending meta data... + Done. + """ + + +@destructive +Scenario: Run `osc setdevelproject ` + When I execute osc with args "setdevelproject test:devel test-pkgA" + Then the exit code is 0 + And stdout is + """ + Setting devel project of package 'test:factory/test-pkgA' to package 'test:devel/test-pkgA' + Sending meta data... + Done. + """ + + +@destructive +Scenario: Run `osc setdevelproject /` + When I execute osc with args "setdevelproject test:devel/test-pkgA" + Then the exit code is 0 + And stdout is + """ + Setting devel project of package 'test:factory/test-pkgA' to package 'test:devel/test-pkgA' + Sending meta data... + Done. + """ + + +@destructive +Scenario: Run `osc setdevelproject --unset` + Given I execute osc with args "setdevelproject test:devel" + When I execute osc with args "setdevelproject --unset" + Then the exit code is 0 + And stdout is + """ + Unsetting devel project from package 'test:factory/test-pkgA' + Sending meta data... + Done. + """ diff --git a/behave/features/setdevelproject-prjcheckout.feature b/behave/features/setdevelproject-prjcheckout.feature new file mode 100644 index 00000000..40e58eed --- /dev/null +++ b/behave/features/setdevelproject-prjcheckout.feature @@ -0,0 +1,17 @@ +Feature: `osc setdevelproject` command + + +# common steps for all scenarios +Background: + Given I set working directory to "{context.osc.temp}" + And I execute osc with args "checkout test:factory" + And I set working directory to "{context.osc.temp}/test:factory" + + +Scenario: Run `osc setdevelproject ` + When I execute osc with args "setdevelproject devel" + Then the exit code is 1 + And stderr is + """ + Directory '{context.osc.temp}/test:factory' is not a working copy of a package + """ diff --git a/behave/features/setdevelproject-project-package.feature b/behave/features/setdevelproject-project-package.feature new file mode 100644 index 00000000..dd9b4032 --- /dev/null +++ b/behave/features/setdevelproject-project-package.feature @@ -0,0 +1,54 @@ +Feature: `osc setdevelproject` command + + +# common steps for all scenarios +Background: + Given I set working directory to "{context.osc.temp}" + + +@destructive +Scenario: Run `osc setdevelproject ` + When I execute osc with args "setdevelproject test:factory test-pkgA test:devel" + Then the exit code is 0 + And stdout is + """ + Setting devel project of package 'test:factory/test-pkgA' to package 'test:devel/test-pkgA' + Sending meta data... + Done. + """ + + +@destructive +Scenario: Run `osc setdevelproject ` + When I execute osc with args "setdevelproject test:factory test-pkgB test:devel test-pkgA" + Then the exit code is 0 + And stdout is + """ + Setting devel project of package 'test:factory/test-pkgB' to package 'test:devel/test-pkgA' + Sending meta data... + Done. + """ + + +@destructive +Scenario: Run `osc setdevelproject / /` + When I execute osc with args "setdevelproject test:factory/test-pkgB test:devel/test-pkgA" + Then the exit code is 0 + And stdout is + """ + Setting devel project of package 'test:factory/test-pkgB' to package 'test:devel/test-pkgA' + Sending meta data... + Done. + """ + + +@destructive +Scenario: Run `osc setdevelproject --unset` + When I execute osc with args "setdevelproject test:factory test-pkgA --unset" + Then the exit code is 0 + And stdout is + """ + Unsetting devel project from package 'test:factory/test-pkgA' + Sending meta data... + Done. + """ diff --git a/osc/commandline.py b/osc/commandline.py index 8103191c..2016d4cf 100644 --- a/osc/commandline.py +++ b/osc/commandline.py @@ -69,12 +69,14 @@ def pop_project_package_from_args(args, default_project=None, default_package=No assert isinstance(args, list) path = Path.cwd() + used_default_project = False try: project = args.pop(0) except IndexError: if not default_project: raise oscerr.OscValueError("Please specify a project") project = default_project + used_default_project = True if not isinstance(project, str): raise TypeError(f"Project should be 'str', found: {type(project).__name__}") @@ -99,6 +101,10 @@ def pop_project_package_from_args(args, default_project=None, default_package=No try: package = args.pop(0) except IndexError: + if not package_is_optional and not used_default_project: + # package is not optional and it wasn't specified together with the project + raise oscerr.OscValueError("Please specify a package") + if default_package: package = default_package else: @@ -748,28 +754,22 @@ class Osc(cmdln.Cmdln): Print the devel project / package of a package Examples: - osc develproject PRJ PKG - osc develproject + osc develproject [PROJECT PACKAGE] """ - args = slash_split(args) apiurl = self.get_api_url() - if len(args) == 0: - project = store_read_project(Path.cwd()) - package = store_read_package(Path.cwd()) - elif len(args) == 2: - project = self._process_project_name(args[0]) - package = args[1] - else: - raise oscerr.WrongArgs('need Project and Package') + args = list(args) + project, package = pop_project_package_from_args( + args, default_project=".", default_package=".", package_is_optional=False + ) - devprj, devpkg = show_devel_project(apiurl, project, package) - if devprj is None: - print('%s / %s has no devel project' % (project, package)) - elif devpkg and devpkg != package: - print("%s %s" % (devprj, devpkg)) - else: - print(devprj) + devel_project, devel_package = show_devel_project(apiurl, project, package) + + if not devel_project: + print(f"Package {project}/{package} has no devel project", file=sys.stderr) + sys.exit(1) + + print(f"{devel_project}/{devel_package}") @cmdln.alias('ca') def do_cleanassets(self, subcmd, opts, *args): @@ -794,29 +794,41 @@ class Osc(cmdln.Cmdln): """Set the devel project / package of a package Examples: - osc setdevelproject [PRJ PKG] DEVPRJ [DEVPKG] + osc setdevelproject [PROJECT PACKAGE] DEVEL_PROJECT [DEVEL_PACKAGE] """ - args = slash_split(args) apiurl = self.get_api_url() - devprj, devpkg = None, None - if len(args) == 3 or len(args) == 4: - project, package = self._process_project_name(args[0]), args[1] - devprj = self._process_project_name(args[2]) - if len(args) == 4: - devpkg = args[3] - elif len(args) >= 1 and len(args) <= 2: - project, package = store_read_project(Path.cwd()), store_read_package(Path.cwd()) - devprj = self._process_project_name(args[0]) - if len(args) == 2: - devpkg = args[1] + args = list(args) + if opts.unset: + project, package = pop_project_package_from_args( + args, default_project=".", default_package=".", package_is_optional=False + ) + devel_project = None + devel_package = None else: - if opts.unset: - project, package = store_read_project(Path.cwd()), store_read_package(Path.cwd()) - else: - raise oscerr.WrongArgs('need at least DEVPRJ (and possibly DEVPKG)') + args_backup = args.copy() - set_devel_project(apiurl, project, package, devprj, devpkg) + try: + # try this sequence first: project package devel_project [devel_package] + project, package = pop_project_package_from_args(args, package_is_optional=False) + devel_project, devel_package = pop_project_package_from_args( + args, default_package=package, package_is_optional=True + ) + except oscerr.OscValueError: + # then read project and package from working copy and try devel_project [devel_package] + args = args_backup.copy() + project, package = pop_project_package_from_args( + [], default_project=".", default_package=".", package_is_optional=False + ) + devel_project, devel_package = pop_project_package_from_args( + args, default_package=package, package_is_optional=True + ) + + if args: + args_str = ", ".join(args) + self.argparse_error(f"Unknown arguments: {args_str}") + + set_devel_project(apiurl, project, package, devel_project, devel_package, print_to="stdout") def do_showlinked(self, subcmd, opts, *args): """ diff --git a/osc/core.py b/osc/core.py index 1a8479e8..1d6383e0 100644 --- a/osc/core.py +++ b/osc/core.py @@ -43,6 +43,7 @@ except ImportError: distro = None from . import __version__ +from . import _private from . import conf from . import meter from . import oscerr @@ -3688,7 +3689,21 @@ def show_devel_project(apiurl, prj, pac): return node.get('project'), node.get('package', None) -def set_devel_project(apiurl, prj, pac, devprj=None, devpac=None): +def set_devel_project(apiurl, prj, pac, devprj=None, devpac=None, print_to="debug"): + if devprj: + msg = "Setting devel project of" + else: + msg = "Unsetting devel project from" + + msg = _private.format_msg_project_package_options( + msg, + prj, + pac, + devprj, + devpac, + ) + _private.print_msg(msg, print_to=print_to) + meta = show_package_meta(apiurl, prj, pac) root = ET.fromstring(b''.join(meta)) node = root.find('devel') diff --git a/tests/test_commandline.py b/tests/test_commandline.py index 522801ee..fc65b2f7 100644 --- a/tests/test_commandline.py +++ b/tests/test_commandline.py @@ -4,7 +4,7 @@ import tempfile import unittest from osc.commandline import pop_project_package_from_args -from osc.oscerr import NoWorkingCopy +from osc.oscerr import NoWorkingCopy, OscValueError from osc.store import Store @@ -38,10 +38,7 @@ class TestPopProjectPackageFromArgs(unittest.TestCase): def test_defaults(self): args = ["project"] - project, package = pop_project_package_from_args(args, default_package="default-package") - self.assertEqual(project, "project") - self.assertEqual(package, "default-package") - self.assertEqual(args, []) + self.assertRaises(OscValueError, pop_project_package_from_args, args, default_package="default-package") args = ["project"] project, package = pop_project_package_from_args( From 2f70408deaa958f79b5190432ffb632efe420e38 Mon Sep 17 00:00:00 2001 From: Daniel Mach Date: Wed, 9 Nov 2022 08:57:47 +0100 Subject: [PATCH 10/17] Move addcontainers code from commandline to _private --- .../addcontainers-pkgcheckout.feature | 24 +++++++++++++++ .../addcontainers-prjcheckout.feature | 17 +++++++++++ .../addcontainers-project-package.feature | 30 +++++++++++++++++++ osc/_private/__init__.py | 1 + osc/_private/api_source.py | 18 +++++++++++ osc/commandline.py | 26 +++++----------- 6 files changed, 97 insertions(+), 19 deletions(-) create mode 100644 behave/features/addcontainers-pkgcheckout.feature create mode 100644 behave/features/addcontainers-prjcheckout.feature create mode 100644 behave/features/addcontainers-project-package.feature diff --git a/behave/features/addcontainers-pkgcheckout.feature b/behave/features/addcontainers-pkgcheckout.feature new file mode 100644 index 00000000..cdd2830c --- /dev/null +++ b/behave/features/addcontainers-pkgcheckout.feature @@ -0,0 +1,24 @@ +Feature: `osc addcontainers` command + + +# common steps for all scenarios +Background: + Given I set working directory to "{context.osc.temp}" + And I execute osc with args "checkout test:factory test-pkgA" + And I set working directory to "{context.osc.temp}/test:factory/test-pkgA" + + +Scenario: Run `osc addcontainers` + When I execute osc with args "addcontainers" + Then stdout is + """ + Adding containers to package 'test:factory/test-pkgA' + """ + + +Scenario: Run `osc addcontainers --extend-package-names` + When I execute osc with args "addcontainers --extend-package-names" + Then stdout is + """ + Adding containers to package 'test:factory/test-pkgA' options: extend-package-names + """ diff --git a/behave/features/addcontainers-prjcheckout.feature b/behave/features/addcontainers-prjcheckout.feature new file mode 100644 index 00000000..ba8ae44d --- /dev/null +++ b/behave/features/addcontainers-prjcheckout.feature @@ -0,0 +1,17 @@ +Feature: `osc addcontainers` command + + +# common steps for all scenarios +Background: + Given I set working directory to "{context.osc.temp}" + And I execute osc with args "checkout test:factory" + And I set working directory to "{context.osc.temp}/test:factory" + + +Scenario: Run `osc addcontainers` + When I execute osc with args "addcontainers" + Then the exit code is 1 + And stderr is + """ + Directory '{context.osc.temp}/test:factory' is not a working copy of a package + """ diff --git a/behave/features/addcontainers-project-package.feature b/behave/features/addcontainers-project-package.feature new file mode 100644 index 00000000..70a475b4 --- /dev/null +++ b/behave/features/addcontainers-project-package.feature @@ -0,0 +1,30 @@ +Feature: `osc addcontainers` command + + +# common steps for all scenarios +Background: + Given I set working directory to "{context.osc.temp}" + + +Scenario: Run `osc addcontainers ` + When I execute osc with args "addcontainers test:factory test-pkgA" + Then stdout is + """ + Adding containers to package 'test:factory/test-pkgA' + """ + + +Scenario: Run `osc addcontainers /` + When I execute osc with args "addcontainers test:factory/test-pkgA" + Then stdout is + """ + Adding containers to package 'test:factory/test-pkgA' + """ + + +Scenario: Run `osc addcontainers --extend-package-names` + When I execute osc with args "addcontainers test:factory test-pkgA --extend-package-names" + Then stdout is + """ + Adding containers to package 'test:factory/test-pkgA' options: extend-package-names + """ diff --git a/osc/_private/__init__.py b/osc/_private/__init__.py index fbbdf092..21581a7d 100644 --- a/osc/_private/__init__.py +++ b/osc/_private/__init__.py @@ -4,6 +4,7 @@ # The cherry-picked imports will be the supported API. from .api_source import add_channels +from .api_source import add_containers from .api_source import enable_channels from .common import print_msg from .common import format_msg_project_package_options diff --git a/osc/_private/api_source.py b/osc/_private/api_source.py index 643c46be..eaa7a81e 100644 --- a/osc/_private/api_source.py +++ b/osc/_private/api_source.py @@ -30,6 +30,24 @@ def add_channels(apiurl, project, package=None, enable_all=False, skip_disabled= return api.post(apiurl, url_path, url_query) +def add_containers(apiurl, project, package, extend_package_names=False, print_to="debug"): + msg = format_msg_project_package_options( + "Adding containers to", + project, + package, + extend_package_names=extend_package_names, + ) + print_msg(msg, print_to=print_to) + + url_path = ["source", project, package] + + url_query = {"cmd": "addcontainers"} + if extend_package_names: + url_query["extend_package_names"] = "1" + + return api.post(apiurl, url_path, url_query) + + def enable_channels(apiurl, project, package=None, print_to="debug"): msg = format_msg_project_package_options( "Enabling channels in", diff --git a/osc/commandline.py b/osc/commandline.py index 2016d4cf..2427f5dd 100644 --- a/osc/commandline.py +++ b/osc/commandline.py @@ -595,28 +595,16 @@ class Osc(cmdln.Cmdln): osc addcontainers [PROJECT PACKAGE] """ - args = slash_split(args) apiurl = self.get_api_url() - localdir = Path.cwd() - project = package = None - if not args: - if is_package_dir(localdir): - project = store_read_project(localdir) - package = store_read_package(localdir) - elif len(args) == 2: - project = self._process_project_name(args[0]) - package = args[1] - if project is None or package is None: - raise oscerr.WrongArgs('Either specify project and package or call it from a package working copy') + args = list(args) + project, package = pop_project_package_from_args( + args, default_project=".", default_package=".", package_is_optional=False + ) - query = {'cmd': 'addcontainers'} - if opts.extend_package_names: - query['extend_package_names'] = '1' - - print("Add containers...") - url = makeurl(apiurl, ['source', project, package], query=query) - f = http_POST(url) + _private.add_containers( + apiurl, project, package, extend_package_names=opts.extend_package_names, print_to="stdout" + ) @cmdln.option('-s', '--skip-disabled', action='store_true', help='Skip disabled channels. Otherwise the source gets added, but not the repositories.') From 4634690176778bee1a60c991a10493ac4121e8f6 Mon Sep 17 00:00:00 2001 From: Daniel Mach Date: Wed, 9 Nov 2022 10:31:20 +0100 Subject: [PATCH 11/17] Move showlinked code from commandline to _private --- .../features/showlinked-pkgcheckout.feature | 17 +++++++++++++ .../features/showlinked-prjcheckout.feature | 17 +++++++++++++ .../showlinked-project-package.feature | 23 ++++++++++++++++++ osc/_private/__init__.py | 1 + osc/_private/api_source.py | 16 +++++++++++++ osc/commandline.py | 24 +++++++------------ 6 files changed, 82 insertions(+), 16 deletions(-) create mode 100644 behave/features/showlinked-pkgcheckout.feature create mode 100644 behave/features/showlinked-prjcheckout.feature create mode 100644 behave/features/showlinked-project-package.feature diff --git a/behave/features/showlinked-pkgcheckout.feature b/behave/features/showlinked-pkgcheckout.feature new file mode 100644 index 00000000..5fced7af --- /dev/null +++ b/behave/features/showlinked-pkgcheckout.feature @@ -0,0 +1,17 @@ +Feature: `osc showlinked` command + + +# common steps for all scenarios +Background: + Given I set working directory to "{context.osc.temp}" + And I execute osc with args "checkout test:factory/test-pkgA" + And I set working directory to "{context.osc.temp}/test:factory/test-pkgA" + And I execute osc with args "linkpac test:factory/test-pkgA home:Admin --force" + + +Scenario: Run `osc showlinked` + When I execute osc with args "showlinked" + Then stdout is + """ + home:Admin/test-pkgA + """ diff --git a/behave/features/showlinked-prjcheckout.feature b/behave/features/showlinked-prjcheckout.feature new file mode 100644 index 00000000..8753ac77 --- /dev/null +++ b/behave/features/showlinked-prjcheckout.feature @@ -0,0 +1,17 @@ +Feature: `osc showlinked` command + + +# common steps for all scenarios +Background: + Given I set working directory to "{context.osc.temp}" + And I execute osc with args "checkout test:factory" + And I set working directory to "{context.osc.temp}/test:factory" + + +Scenario: Run `osc showlinked` + When I execute osc with args "showlinked" + Then the exit code is 1 + And stderr is + """ + Directory '{context.osc.temp}/test:factory' is not a working copy of a package + """ diff --git a/behave/features/showlinked-project-package.feature b/behave/features/showlinked-project-package.feature new file mode 100644 index 00000000..141206b2 --- /dev/null +++ b/behave/features/showlinked-project-package.feature @@ -0,0 +1,23 @@ +Feature: `osc showlinked` command + + +# common steps for all scenarios +Background: + Given I set working directory to "{context.osc.temp}" + And I execute osc with args "linkpac test:factory/test-pkgA home:Admin --force" + + +Scenario: Run `osc showlinked ` + When I execute osc with args "showlinked test:factory test-pkgA" + Then stdout is + """ + home:Admin/test-pkgA + """ + + +Scenario: Run `osc showlinked /` + When I execute osc with args "showlinked test:factory/test-pkgA" + Then stdout is + """ + home:Admin/test-pkgA + """ diff --git a/osc/_private/__init__.py b/osc/_private/__init__.py index 21581a7d..843b162f 100644 --- a/osc/_private/__init__.py +++ b/osc/_private/__init__.py @@ -6,6 +6,7 @@ from .api_source import add_channels from .api_source import add_containers from .api_source import enable_channels +from .api_source import get_linked_packages from .common import print_msg from .common import format_msg_project_package_options from .package import ApiPackage diff --git a/osc/_private/api_source.py b/osc/_private/api_source.py index eaa7a81e..f49f4c4c 100644 --- a/osc/_private/api_source.py +++ b/osc/_private/api_source.py @@ -66,3 +66,19 @@ def enable_channels(apiurl, project, package=None, print_to="debug"): url_query = {"cmd": "modifychannels", "mode": "enable_all"} return api.post(apiurl, url_path, url_query) + + +def get_linked_packages(apiurl, project, package): + url_path = ["source", project, package] + url_query = {"cmd": "showlinked"} + root = api.post(apiurl, url_path, url_query) + + result = [] + nodes = api.find_nodes(root, "collection", "package") + for node in nodes: + item = { + "project": node.get("project"), + "name": node.get("name"), + } + result.append(item) + return result diff --git a/osc/commandline.py b/osc/commandline.py index 2427f5dd..1cf7bdd0 100644 --- a/osc/commandline.py +++ b/osc/commandline.py @@ -826,24 +826,16 @@ class Osc(cmdln.Cmdln): osc showlinked [PROJECT PACKAGE] """ - args = slash_split(args) apiurl = self.get_api_url() - localdir = Path.cwd() - project = package = None - if len(args) == 2: - project = self._process_project_name(args[0]) - package = args[1] - elif is_package_dir(localdir): - project = store_read_project(localdir) - package = store_read_package(localdir) - else: - raise oscerr.WrongArgs('Either specify project and package or call it from a package working copy') - url = makeurl(apiurl, ['source', project, package], query={'cmd': 'showlinked'}) - f = http_POST(url) - root = ET.parse(f).getroot() - for node in root.findall('package'): - print(node.get('project') + " " + node.get('name')) + args = list(args) + project, package = pop_project_package_from_args( + args, default_project=".", default_package=".", package_is_optional=False + ) + + linked_packages = _private.get_linked_packages(apiurl, project, package) + for pkg in linked_packages: + print(f"{pkg['project']}/{pkg['name']}") @cmdln.option('-c', '--create', action='store_true', help='Create a new token') From 62ba626eb5702e4bc845019d9653c733f797f6c0 Mon Sep 17 00:00:00 2001 From: Daniel Mach Date: Wed, 9 Nov 2022 10:56:56 +0100 Subject: [PATCH 12/17] Migrate 'log' command to pop_project_package_from_args() --- osc/commandline.py | 26 ++++---------------------- osc/core.py | 3 +++ 2 files changed, 7 insertions(+), 22 deletions(-) diff --git a/osc/commandline.py b/osc/commandline.py index 1cf7bdd0..ad2e8616 100644 --- a/osc/commandline.py +++ b/osc/commandline.py @@ -6856,30 +6856,12 @@ Please submit there instead, or use --nodevelproject to force direct submission. osc log (inside working copy) osc log remote_project [remote_package] """ - - args = slash_split(args) apiurl = self.get_api_url() - if len(args) == 0: - wd = Path.cwd() - if is_project_dir(wd) or is_package_dir(wd): - project = store_read_project(wd) - if is_project_dir(wd): - package = "_project" - else: - package = store_read_package(wd) - else: - raise oscerr.NoWorkingCopy("Error: \"%s\" is not an osc working copy." % os.path.abspath(wd)) - elif len(args) < 1: - raise oscerr.WrongArgs('Too few arguments (required none or two)') - elif len(args) > 2: - raise oscerr.WrongArgs('Too many arguments (required none or two)') - elif len(args) == 1: - project = self._process_project_name(args[0]) - package = "_project" - else: - project = self._process_project_name(args[0]) - package = args[1] + args = list(args) + project, package = pop_project_package_from_args( + args, default_project=".", default_package=".", package_is_optional=True + ) rev, rev_upper = parseRevisionOption(opts.revision) if rev and not checkRevision(project, package, rev, apiurl, opts.meta): diff --git a/osc/core.py b/osc/core.py index 1d6383e0..a1d41d1e 100644 --- a/osc/core.py +++ b/osc/core.py @@ -6881,6 +6881,9 @@ def print_jobhistory(apiurl: str, prj: str, current_package: str, repository: st def get_commitlog( apiurl: str, prj: str, package: str, revision, format="text", meta=False, deleted=False, revision_upper=None ): + if package is None: + package = "_project" + query = {} if deleted: query['deleted'] = 1 From 37a34e58d494ee4998cd8c433b2863505fc908c2 Mon Sep 17 00:00:00 2001 From: Daniel Mach Date: Wed, 9 Nov 2022 11:54:14 +0100 Subject: [PATCH 13/17] Migrate 'setlinkrev' command to pop_project_package_from_args() --- .../setlinkrev-project-package.feature | 39 +++++++++++++++++++ behave/features/setlinkrev-project.feature | 33 ++++++++++++++++ osc/commandline.py | 33 +++++++++------- 3 files changed, 92 insertions(+), 13 deletions(-) create mode 100644 behave/features/setlinkrev-project-package.feature create mode 100644 behave/features/setlinkrev-project.feature diff --git a/behave/features/setlinkrev-project-package.feature b/behave/features/setlinkrev-project-package.feature new file mode 100644 index 00000000..6dbcb4a4 --- /dev/null +++ b/behave/features/setlinkrev-project-package.feature @@ -0,0 +1,39 @@ +Feature: `osc setlinkrev` command + + +# common steps for all scenarios +Background: + Given I set working directory to "{context.osc.temp}" + And I execute osc with args "linkpac test:factory/test-pkgA home:Admin --force" + + +Scenario: Run `osc setlinkrev ` + When I execute osc with args "setlinkrev home:Admin test-pkgA" + Then stdout is + """ + Set link revision of package home:Admin/test-pkgA to 3 + """ + + +Scenario: Run `osc setlinkrev /` + When I execute osc with args "setlinkrev home:Admin/test-pkgA" + Then stdout is + """ + Set link revision of package home:Admin/test-pkgA to 3 + """ + + +Scenario: Run `osc setlinkrev --revision` + When I execute osc with args "setlinkrev home:Admin test-pkgA --revision=2" + Then stdout is + """ + Set link revision of package home:Admin/test-pkgA to 2 + """ + + +Scenario: Run `osc setlinkrev --unset` + When I execute osc with args "setlinkrev home:Admin test-pkgA --unset" + Then stdout is + """ + Removed link revision from package home:Admin/test-pkgA + """ diff --git a/behave/features/setlinkrev-project.feature b/behave/features/setlinkrev-project.feature new file mode 100644 index 00000000..dab5dfbd --- /dev/null +++ b/behave/features/setlinkrev-project.feature @@ -0,0 +1,33 @@ +Feature: `osc setlinkrev` command + + +# common steps for all scenarios +Background: + Given I set working directory to "{context.osc.temp}" + And I execute osc with args "linkpac test:factory/test-pkgA home:Admin --force" + + +Scenario: Run `osc setlinkrev ` + When I execute osc with args "setlinkrev home:Admin" + Then stdout is + """ + Set link revision of package home:Admin/test-pkgA to 3 + """ + + +Scenario: Run `osc setlinkrev --revision` + When I execute osc with args "setlinkrev home:Admin --revision=2" + Then the exit code is 2 + + +Scenario: Run `osc setlinkrev --unset` + Given I execute osc with args "setlinkrev home:Admin test-pkgA --revision=2" + And stdout is + """ + Set link revision of package home:Admin/test-pkgA to 2 + """ + When I execute osc with args "setlinkrev home:Admin --unset" + Then stdout is + """ + Removed link revision from package home:Admin/test-pkgA + """ diff --git a/osc/commandline.py b/osc/commandline.py index ad2e8616..b296a45c 100644 --- a/osc/commandline.py +++ b/osc/commandline.py @@ -2773,27 +2773,28 @@ Please submit there instead, or use --nodevelproject to force direct submission. osc setlinkrev PROJECT [PACKAGE] """ - args = slash_split(args) apiurl = self.get_api_url() - package = None + rev = parseRevisionOption(opts.revision)[0] or '' if opts.unset: rev = None + args = list(args) if not args: p = Package(Path.cwd()) project = p.prjname package = p.name - apiurl = p.apiurl + assert apiurl == p.apiurl if not p.islink(): sys.exit('Local directory is no checked out source link package, aborting') - elif len(args) == 2: - project = self._process_project_name(args[0]) - package = args[1] - elif len(args) == 1: - project = self._process_project_name(args[0]) else: - self.argparse_error("Incorrect number of arguments.") + project, package = pop_project_package_from_args( + args, default_project=".", default_package=".", package_is_optional=True + ) + + if opts.revision and not package: + # It is highly unlikely that all links for all packages in a project should be set to the same revision. + self.argparse_error("The --revision option requires to specify a package") if package: packages = [package] @@ -2801,12 +2802,18 @@ Please submit there instead, or use --nodevelproject to force direct submission. packages = meta_get_packagelist(apiurl, project) for p in packages: - rev = set_link_rev(apiurl, project, p, revision=rev, - expand=not opts.use_plain_revision) + try: + rev = set_link_rev(apiurl, project, p, revision=rev, expand=not opts.use_plain_revision) + except HTTPError as e: + if e.code != 404: + raise + print(f"WARNING: Package {project}/{p} has no link", file=sys.stderr) + continue + if rev is None: - print('removed revision from link') + print(f"Removed link revision from package {project}/{p}") else: - print('set revision to %s for package %s' % (rev, p)) + print(f"Set link revision of package {project}/{p} to {rev}") def do_linktobranch(self, subcmd, opts, *args): """ From 56bb193b4e7137b7244a909402402b0ff18e5d82 Mon Sep 17 00:00:00 2001 From: Daniel Mach Date: Thu, 10 Nov 2022 10:22:16 +0100 Subject: [PATCH 14/17] Add 'repository' and 'dest_repository' args to format_msg_project_package_options() --- osc/_private/common.py | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/osc/_private/common.py b/osc/_private/common.py index 80e83d66..6704830d 100644 --- a/osc/_private/common.py +++ b/osc/_private/common.py @@ -15,7 +15,16 @@ def print_msg(msg, print_to="debug"): raise ValueError(f"Invalid value of the 'output' option: {output}") -def format_msg_project_package_options(msg, project=None, package=None, dest_project=None, dest_package=None, **options): +def format_msg_project_package_options( + msg, + project=None, + package=None, + dest_project=None, + dest_package=None, + repository=None, + dest_repository=None, + **options, +): """ Format msg, project, package, dest_project, dest_package and options into a meaningful message that can be printed out directly or as a debug message. @@ -25,10 +34,19 @@ def format_msg_project_package_options(msg, project=None, package=None, dest_pro else: msg += f" package '{project}/{package}'" + if repository: + msg += f" repository '{repository}'" + + if any([dest_project, dest_package, dest_repository]): + msg += " to" + if dest_project and not dest_package: - msg += f" to project '{dest_project}'" + msg += f" project '{dest_project}'" elif dest_project and dest_package: - msg += f" to package '{dest_project}/{dest_package}'" + msg += f" package '{dest_project}/{dest_package}'" + + if dest_repository: + msg += f" repository '{dest_repository}'" msg_options = [key.replace("_", "-") for key, value in options.items() if value] if msg_options: From 3027edc0eb370478c70922ab8c380ead64582dbe Mon Sep 17 00:00:00 2001 From: Daniel Mach Date: Thu, 10 Nov 2022 10:29:14 +0100 Subject: [PATCH 15/17] Move 'release' code from commandline to _private --- osc/_private/__init__.py | 1 + osc/_private/api_source.py | 42 ++++++++++++++++++++++++++++ osc/commandline.py | 57 +++++++++++--------------------------- 3 files changed, 59 insertions(+), 41 deletions(-) diff --git a/osc/_private/__init__.py b/osc/_private/__init__.py index 843b162f..fd1b6337 100644 --- a/osc/_private/__init__.py +++ b/osc/_private/__init__.py @@ -7,6 +7,7 @@ from .api_source import add_channels from .api_source import add_containers from .api_source import enable_channels from .api_source import get_linked_packages +from .api_source import release from .common import print_msg from .common import format_msg_project_package_options from .package import ApiPackage diff --git a/osc/_private/api_source.py b/osc/_private/api_source.py index f49f4c4c..3af47b63 100644 --- a/osc/_private/api_source.py +++ b/osc/_private/api_source.py @@ -82,3 +82,45 @@ def get_linked_packages(apiurl, project, package): } result.append(item) return result + + +def release( + apiurl, + project, + package, + repository, + target_project, + target_repository, + set_release_to=None, + delayed=False, + print_to="debug", +): + msg = format_msg_project_package_options( + "Releasing", + project, + package, + target_project, + target_package=None, + repository=repository, + dest_repository=target_repository, + delayed=delayed, + ) + print_msg(msg, print_to=print_to) + + url_path = ["source", project] + if package: + url_path += [package] + + url_query = {"cmd": "release"} + if repository: + url_query["repository"] = repository + if target_project: + url_query["target_project"] = target_project + if target_repository: + url_query["target_repository"] = target_repository + if set_release_to: + url_query["setrelease"] = set_release_to + if not delayed: + url_query["nodelay"] = "1" + + return api.post(apiurl, url_path, url_query) diff --git a/osc/commandline.py b/osc/commandline.py index b296a45c..4935ed3d 100644 --- a/osc/commandline.py +++ b/osc/commandline.py @@ -3136,51 +3136,26 @@ Please submit there instead, or use --nodevelproject to force direct submission. It requires defined release targets set to trigger="manual". usage: - osc release [ SOURCEPROJECT [ SOURCEPACKAGE ] ] + osc release [PROJECT [PACKAGE]] """ - - args = slash_split(args) apiurl = self.get_api_url() - source_project = source_package = None + args = list(args) + project, package = pop_project_package_from_args( + args, default_project=".", default_package=".", package_is_optional=True + ) - if len(args) > 2: - raise oscerr.WrongArgs('Too many arguments.') - - if len(args) == 0: - if is_package_dir(Path.cwd()): - source_project = store_read_project(Path.cwd()) - source_package = store_read_package(Path.cwd()) - elif is_project_dir(Path.cwd()): - source_project = store_read_project(Path.cwd()) - else: - raise oscerr.WrongArgs('Too few arguments.') - if len(args) > 0: - source_project = self._process_project_name(args[0]) - if len(args) > 1: - source_package = args[1] - - query = {'cmd': 'release'} - if opts.target_project: - query["target_project"] = opts.target_project - if opts.target_repository: - query["target_repository"] = opts.target_repository - if opts.repo: - query["repository"] = opts.repo - if opts.set_release: - query["setrelease"] = opts.set_release - if opts.no_delay: - query["nodelay"] = "1" - baseurl = ['source', source_project] - if source_package: - baseurl.append(source_package) - url = makeurl(apiurl, baseurl, query=query) - f = http_POST(url) - while True: - buf = f.read(16384) - if not buf: - break - sys.stdout.write(decode_it(buf)) + _private.release( + apiurl, + project=project, + package=package, + repository=opts.repo, + target_project=opts.target_project, + target_repository=opts.target_repository, + set_release_to=opts.set_release, + delayed=not opts.no_delay, + print_to="stdout", + ) @cmdln.option('-m', '--message', metavar='TEXT', help='specify message TEXT') From 1839e834c61877c25bac7859c901a1a8574d13a0 Mon Sep 17 00:00:00 2001 From: Daniel Mach Date: Fri, 9 Dec 2022 14:26:35 +0100 Subject: [PATCH 16/17] behave: Print debug information when running commands Run behave with -DDEBUG=1 to enable the debug mode --- behave/features/environment.py | 4 ++-- behave/features/steps/common.py | 17 +++++++++++------ behave/features/steps/osc.py | 3 +++ behave/features/steps/podman.py | 14 ++++++++++++-- 4 files changed, 28 insertions(+), 10 deletions(-) diff --git a/behave/features/environment.py b/behave/features/environment.py index 4650ddf7..f14e9d34 100644 --- a/behave/features/environment.py +++ b/behave/features/environment.py @@ -21,7 +21,7 @@ def after_scenario(context, scenario): if "destructive" in scenario.tags: # start a new container after a destructive test context.podman.kill() - context.podman = podman.Podman() + context.podman = podman.Podman(context) context.osc.clear() common.check_exit_code(context) @@ -46,7 +46,7 @@ def before_all(context): # absolute path to .../behave/fixtures context.fixtures = os.path.join(os.path.dirname(__file__), "..", "fixtures") - context.podman = podman.Podman() + context.podman = podman.Podman(context) context.osc = osc.Osc(context) diff --git a/behave/features/steps/common.py b/behave/features/steps/common.py index abd6c2fc..04bc520d 100644 --- a/behave/features/steps/common.py +++ b/behave/features/steps/common.py @@ -6,6 +6,13 @@ import subprocess import behave +def debug(context, *args): + if not context.config.userdata.get("DEBUG", False): + return + msg = " ".join((str(i).strip() for i in args)) + print(f"DEBUG: {msg}") + + def makedirs(path): try: os.makedirs(path) @@ -62,16 +69,14 @@ def run_in_context(context, cmd, can_fail=False, **run_args): env["PATH"] = path.replace("$PATH", env["PATH"]) run_args["env"] = env - if context.config.userdata.get("DEBUG", False): - print(f"DEBUG: command: {cmd}") + debug(context, "Running command:", cmd) context.cmd_exitcode, context.cmd_stdout, context.cmd_stderr = run(cmd, **run_args) context.cmd_exitcode_checked = False - if context.config.userdata.get("DEBUG", False): - print(f"DEBUG: exit code: {context.cmd_exitcode}") - print(f"DEBUG: stdout: {context.cmd_stdout}") - print(f"DEBUG: stderr: {context.cmd_stderr}") + debug(context, "> return code:", context.cmd_exitcode) + debug(context, "> stdout:", context.cmd_stdout) + debug(context, "> stderr:", context.cmd_stderr) if not can_fail and context.cmd_exitcode != 0: raise AssertionError('Running command "%s" failed: %s' % (cmd, context.cmd_exitcode)) diff --git a/behave/features/steps/osc.py b/behave/features/steps/osc.py index 71295cc4..86a03f47 100644 --- a/behave/features/steps/osc.py +++ b/behave/features/steps/osc.py @@ -6,6 +6,7 @@ import time import behave +from steps.common import debug from steps.common import run_in_context @@ -15,6 +16,7 @@ class Osc: raise RuntimeError("context doesn't have 'podman' object set") self.context = context + debug(self.context, "Osc.__init__()") self.temp = None self.clear() @@ -25,6 +27,7 @@ class Osc: pass def clear(self): + debug(self.context, "Osc.clear()") if self.temp: shutil.rmtree(self.temp) self.temp = tempfile.mkdtemp(prefix="osc_behave_") diff --git a/behave/features/steps/podman.py b/behave/features/steps/podman.py index 4bafaa6a..75d12e10 100644 --- a/behave/features/steps/podman.py +++ b/behave/features/steps/podman.py @@ -1,8 +1,12 @@ import subprocess +from steps.common import debug + class Podman: - def __init__(self): + def __init__(self, context): + self.context = context + debug(context, "Podman.__init__()") self.container_id = None self.run() self.wait_on_systemd() @@ -16,6 +20,7 @@ class Podman: def _run(self, args, check=True): cmd = ["podman"] + args + debug(self.context, "Running command:", cmd) proc = subprocess.run( cmd, stdout=subprocess.PIPE, @@ -23,9 +28,13 @@ class Podman: encoding="utf-8", check=check, ) + debug(self.context, "> return code:", proc.returncode) + debug(self.context, "> stdout:", proc.stdout) + debug(self.context, "> stderr:", proc.stderr) return proc def run(self): + debug(self.context, "Podman.run()") args = [ "run", "--name", "obs-server-behave", @@ -45,6 +54,7 @@ class Podman: def kill(self): if not self.container_id: return + debug(self.context, "Podman.kill()") args = ["kill", self.container_id] self._run(args) self.container_id = None @@ -65,4 +75,4 @@ class Podman: if line.startswith("443/tcp"): # return from: "443/tcp -> 0.0.0.0:" return line.split(":")[-1] - return None + raise RuntimeError(f"Could not determine port of container {self.container_id}") From 44eac57595279335a9493c846cad264aa7e183f4 Mon Sep 17 00:00:00 2001 From: Daniel Mach Date: Fri, 9 Dec 2022 15:09:44 +0100 Subject: [PATCH 17/17] behave: Properly support @destructive tests --- behave/features/environment.py | 5 +++-- behave/features/steps/podman.py | 15 ++++++++++----- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/behave/features/environment.py b/behave/features/environment.py index f14e9d34..93fa77df 100644 --- a/behave/features/environment.py +++ b/behave/features/environment.py @@ -20,8 +20,9 @@ def before_scenario(context, scenario): def after_scenario(context, scenario): if "destructive" in scenario.tags: # start a new container after a destructive test - context.podman.kill() - context.podman = podman.Podman(context) + # we must use an existing podman instance defined in `before_all` due to context attribute life-cycle: + # https://behave.readthedocs.io/en/stable/context_attributes.html + context.podman.restart() context.osc.clear() common.check_exit_code(context) diff --git a/behave/features/steps/podman.py b/behave/features/steps/podman.py index 75d12e10..ae9bc5ff 100644 --- a/behave/features/steps/podman.py +++ b/behave/features/steps/podman.py @@ -8,9 +8,7 @@ class Podman: self.context = context debug(context, "Podman.__init__()") self.container_id = None - self.run() - self.wait_on_systemd() - self.port = self.get_port() + self.start() def __del__(self): try: @@ -33,8 +31,8 @@ class Podman: debug(self.context, "> stderr:", proc.stderr) return proc - def run(self): - debug(self.context, "Podman.run()") + def start(self): + debug(self.context, "Podman.start()") args = [ "run", "--name", "obs-server-behave", @@ -50,6 +48,8 @@ class Podman: proc = self._run(args) lines = proc.stdout.strip().splitlines() self.container_id = lines[-1] + self.wait_on_systemd() + self.port = self.get_port() def kill(self): if not self.container_id: @@ -59,6 +59,11 @@ class Podman: self._run(args) self.container_id = None + def restart(self): + debug(self.context, "Podman.restart()") + self.kill() + self.start() + def wait_on_systemd(self): args = [ "exec",