diff --git a/behave/features/addchannels-pkgcheckout.feature b/behave/features/addchannels-pkgcheckout.feature new file mode 100644 index 00000000..4e4c908a --- /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 package 'test:factory/test-pkgA' + """ + + +Scenario: Run `osc addchannels --enable-all` + When I execute osc with args "addchannels --enable-all" + Then stdout is + """ + Adding channels to package 'test:factory/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 package 'test:factory/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..44ecf1fd --- /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..ccb74008 --- /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 package 'test:factory/test-pkgA' + """ + + +Scenario: Run `osc addchannels /` + When I execute osc with args "addchannels test:factory/test-pkgA" + Then stdout is + """ + Adding channels to package 'test:factory/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 package 'test:factory/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 package 'test:factory/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..2e9bc83b --- /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/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/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/enablechannels-pkgcheckout.feature b/behave/features/enablechannels-pkgcheckout.feature new file mode 100644 index 00000000..d11916b5 --- /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 package 'test:factory/test-pkgA' + """ diff --git a/behave/features/enablechannels-prjcheckout.feature b/behave/features/enablechannels-prjcheckout.feature new file mode 100644 index 00000000..457b42a6 --- /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..142b9555 --- /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 package 'test:factory/test-pkgA' + """ + + +Scenario: Run `osc enablechannels /` + When I execute osc with args "enablechannels test:factory/test-pkgA" + Then stdout is + """ + Enabling channels in package 'test:factory/test-pkgA' + """ diff --git a/behave/features/enablechannels-project.feature b/behave/features/enablechannels-project.feature new file mode 100644 index 00000000..2df78504 --- /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/behave/features/environment.py b/behave/features/environment.py index 4650ddf7..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() + # 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) @@ -46,7 +47,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/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/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/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/behave/features/steps/common.py b/behave/features/steps/common.py index 4f2c7644..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)) @@ -93,8 +98,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 +109,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 +201,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 diff --git a/behave/features/steps/osc.py b/behave/features/steps/osc.py index 95972b99..86a03f47 100644 --- a/behave/features/steps/osc.py +++ b/behave/features/steps/osc.py @@ -1,10 +1,12 @@ import os +import re import shutil import tempfile import time import behave +from steps.common import debug from steps.common import run_in_context @@ -14,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() @@ -24,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_") @@ -54,6 +58,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}"') diff --git a/behave/features/steps/podman.py b/behave/features/steps/podman.py index 4bafaa6a..ae9bc5ff 100644 --- a/behave/features/steps/podman.py +++ b/behave/features/steps/podman.py @@ -1,12 +1,14 @@ 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() - self.port = self.get_port() + self.start() def __del__(self): try: @@ -16,6 +18,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 +26,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): + def start(self): + debug(self.context, "Podman.start()") args = [ "run", "--name", "obs-server-behave", @@ -41,14 +48,22 @@ 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: return + debug(self.context, "Podman.kill()") args = ["kill", self.container_id] 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", @@ -65,4 +80,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}") diff --git a/osc/_private/__init__.py b/osc/_private/__init__.py index 14976d5e..fd1b6337 100644 --- a/osc/_private/__init__.py +++ b/osc/_private/__init__.py @@ -3,5 +3,12 @@ # # 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 .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 from .request import forward_request 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`. diff --git a/osc/_private/api_source.py b/osc/_private/api_source.py new file mode 100644 index 00000000..3af47b63 --- /dev/null +++ b/osc/_private/api_source.py @@ -0,0 +1,126 @@ +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) + + +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", + 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) + + +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 + + +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/_private/common.py b/osc/_private/common.py new file mode 100644 index 00000000..6704830d --- /dev/null +++ b/osc/_private/common.py @@ -0,0 +1,57 @@ +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, + 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. + """ + if project and not package: + msg += f" project '{project}'" + 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" project '{dest_project}'" + elif dest_project and 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: + msg_options.sort() + msg_options_str = ", ".join(msg_options) + msg += f" options: {msg_options_str}" + + return msg diff --git a/osc/commandline.py b/osc/commandline.py index fc97cdf9..360d5091 100644 --- a/osc/commandline.py +++ b/osc/commandline.py @@ -48,6 +48,87 @@ 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() + + 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__}") + + 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 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: + 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. @@ -514,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.') @@ -554,36 +623,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): @@ -595,37 +647,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.') @@ -711,28 +742,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): @@ -757,29 +782,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): """ @@ -789,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') @@ -2744,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] @@ -2772,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): """ @@ -3100,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') @@ -6827,30 +6838,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 1a8479e8..a1d41d1e 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') @@ -6866,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 diff --git a/tests/test_commandline.py b/tests/test_commandline.py new file mode 100644 index 00000000..fc65b2f7 --- /dev/null +++ b/tests/test_commandline.py @@ -0,0 +1,144 @@ +import os +import shutil +import tempfile +import unittest + +from osc.commandline import pop_project_package_from_args +from osc.oscerr import NoWorkingCopy, OscValueError +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"] + self.assertRaises(OscValueError, pop_project_package_from_args, args, default_package="default-package") + + 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()