{
    "file_diffs": [
        {
            "old_file_content": "",
            "new_file_content": "### 🐛 Bug Fixes\n\n- Exit with the original non-0 exit code if underlying functionality, in particular \"datalad run\", returned incomplete result record with a non-0 exit code.  Fixes [#7504](https://github.com/datalad/datalad/issues/7504) via [PR #7641](https://github.com/datalad/datalad/pull/7641) (by [@yarikoptic](https://github.com/yarikoptic))\n",
            "header": {
                "file": {
                    "path": "changelog.d/pr-7641.md"
                },
                "misc_line": "new file mode 100644"
            },
            "index_line": {
                "old_commit_hash": "000000000",
                "new_commit_hash": "92a05d128",
                "mode": ""
            },
            "is_binary_file": false,
            "binary_line": null,
            "minus_file": {
                "path": "/dev/null"
            },
            "plus_file": {
                "path": "b/changelog.d/pr-7641.md"
            },
            "hunks": [
                {
                    "descriptor": {
                        "old_range": {
                            "start": 0,
                            "length": 0
                        },
                        "new_range": {
                            "start": 1,
                            "length": 3
                        },
                        "section": ""
                    },
                    "line_group": {
                        "all_lines": [
                            {
                                "content": "### 🐛 Bug Fixes",
                                "type": "added"
                            },
                            {
                                "content": "",
                                "type": "added"
                            },
                            {
                                "content": "- Exit with the original non-0 exit code if underlying functionality, in particular \"datalad run\", returned incomplete result record with a non-0 exit code.  Fixes [#7504](https://github.com/datalad/datalad/issues/7504) via [PR #7641](https://github.com/datalad/datalad/pull/7641) (by [@yarikoptic](https://github.com/yarikoptic))",
                                "type": "added"
                            }
                        ]
                    },
                    "modified_entities": [],
                    "added_entities": [],
                    "deleted_entities": []
                }
            ]
        },
        {
            "old_file_content": "# emacs: -*- mode: python; py-indent-offset: 4; tab-width: 4; indent-tabs-mode: nil -*-\n# ex: set sts=4 ts=4 sw=4 et:\n# ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ##\n#\n#   See COPYING file distributed along with the datalad package for the\n#   copyright and license terms.\n#\n# ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ##\n\"\"\"This is the main() CLI entryproint\"\"\"\n\n# It should start-up and run as fast as possible for a responsive CLI.\n\n# Imports are done inline and as late as possible to avoid paying for\n# an unconditional commulative overhead that is only actually needed\n# in some special cases.\n\n__docformat__ = 'restructuredtext'\n\nimport logging\n\nlgr = logging.getLogger('datalad.cli')\n\nlgr.log(5, \"Importing cli.main\")\n\nimport os\nimport sys\n\nimport datalad\n\nfrom .parser import setup_parser\n\n# TODO cross-check with unconditional imports in .parser\n# special case imports\n#   from .helpers import _fix_datalad_ri\n#   import platform\n#   from .helpers import _parse_overrides_from_cmdline\n#   from datalad.utils import chpwd\n#   from .utils import setup_exceptionhook\n#   from datalad.support.exceptions import ...\n\n# unconditional imports, no meaningful functionality without them\n#   from .parser import setup_parser\n\n\ndef _on_msys_tainted_paths():\n    \"\"\"This duplicates datalad.utils.on_msys_tainted_paths\n\n    But it does it while minimizing runtime penalties on all irrelevant\n    systems.\n    \"\"\"\n    if os.environ.get('MSYSTEM', '')[:4] not in ('MSYS', 'MING'):\n        return False\n    if 'MSYS_NO_PATHCONV' in os.environ:\n        return False\n    import platform\n    if platform.system().lower() != 'windows':\n        return False\n    return True\n\n\ndef main(args=sys.argv):\n    \"\"\"Main CLI entrypoint\"\"\"\n    lgr.log(5, \"Starting main(%r)\", args)\n    # record that we came in via the cmdline\n    datalad.__api = 'cmdline'\n    completing = \"_ARGCOMPLETE\" in os.environ\n    if completing and 'COMP_LINE' in os.environ:\n        import shlex\n\n        # TODO support posix=False too?\n        args = shlex.split(os.environ['COMP_LINE']) or args\n\n    if _on_msys_tainted_paths():\n        # Possibly present DataLadRIs were stripped of a leading /\n        from .helpers import _fix_datalad_ri\n        args = [_fix_datalad_ri(s) for s in args]\n\n    from datalad.support.entrypoints import load_extensions\n\n    # load extensions requested by configuration\n    # analog to what coreapi is doing for a Python session\n    # importantly, load them prior to parser construction, such\n    # that CLI tuning is also within reach for extensions\n    load_extensions()\n\n    # PYTHON_ARGCOMPLETE_OK\n    # TODO possibly construct a dedicated parser just for autocompletion\n    # rather than lobotomizing the normal one\n    parser = setup_parser(args, completing=completing)\n    try:\n        import argcomplete\n        argcomplete.autocomplete(parser)\n    except ImportError:\n        pass\n\n    # parse cmd args\n    lgr.debug(\"Parsing known args among %r\", args)\n    cmdlineargs, unparsed_args = parser.parse_known_args(args[1:])\n    # did the parser tell us what command to run?\n    has_func = hasattr(cmdlineargs, 'func') and cmdlineargs.func is not None\n    if unparsed_args:\n        if has_func:\n            lgr.error('unknown argument%s: %s',\n                's' if len(unparsed_args) > 1 else '',\n                unparsed_args if len(unparsed_args) > 1 else unparsed_args[0],\n            )\n            cmdlineargs.subparser.print_usage()\n            sys.exit(1)\n        else:\n            # store all unparsed arguments\n            cmdlineargs.datalad_unparsed_args = unparsed_args\n\n    # pull config overrides from cmdline args and put in effect\n    if cmdlineargs.cfg_overrides is not None:\n        from .helpers import _parse_overrides_from_cmdline\n        datalad.cfg.overrides.update(\n            _parse_overrides_from_cmdline(cmdlineargs)\n        )\n        # enable overrides\n        datalad.cfg.reload(force=True)\n        # try loading extensions again, in case the configuration\n        # added new ones to consider\n        load_extensions()\n\n    if 'datalad.runtime.librarymode' in datalad.cfg:\n        datalad.enable_librarymode()\n\n    if cmdlineargs.change_path is not None:\n        from datalad.utils import chpwd\n        for path in cmdlineargs.change_path:\n            chpwd(path)\n\n    # check argparse could determine what commands needs to be executed\n    if not has_func:\n        # just let argparser spit out its error, since there is smth wrong\n        parser.parse_args(args)\n        # if that one didn't puke -- we should\n        parser.print_usage()\n        lgr.error(\"Please specify the command\")\n        # matches exit code for InsufficientArgumentsError\n        sys.exit(2)\n\n    _run(cmdlineargs)\n\n\ndef _run(namespace):\n    \"\"\"Execute a CLI operation\n\n    Depending on CLI debugging options the CLI operation is executed\n    in a debug harness or an exception handler.\n\n    Parameters\n    ----------\n    namespace: Namespace\n      Object returned by `ArgumentParser.parse_args()` with fully\n      populated and validated CLI command and arguments.\n\n    Raises\n    ------\n    SystemExit\n      When the CLI completed without error (exit 0).\n    \"\"\"\n    # execute the command, either with a debugger catching\n    # a crash, or with a simplistic exception handler.\n    # note that result rendering is happening in the\n    # execution handler, when the command-generator is unwound\n    ret = _run_with_debugger(namespace) \\\n        if namespace.common_debug or namespace.common_idebug \\\n        else _run_with_exception_handler(namespace)\n\n    # all good, not strictly needed, but makes internal testing easier\n    sys.exit(0)\n\n\ndef _run_with_debugger(cmdlineargs):\n    \"\"\"Execute the command and drop into debugger if it crashes\"\"\"\n    from .utils import setup_exceptionhook\n\n    # so we could see/stop clearly at the point of failure\n    setup_exceptionhook(ipython=cmdlineargs.common_idebug)\n    return cmdlineargs.func(cmdlineargs)\n\n\ndef _run_with_exception_handler(cmdlineargs):\n    \"\"\"Execute the command and perform some reporting\n    normalization if it crashes, but otherwise just let it go\"\"\"\n    # otherwise - guard and only log the summary. Postmortem is not\n    # as convenient if being caught in this ultimate except\n    try:\n        return cmdlineargs.func(cmdlineargs)\n    # catch BaseException for KeyboardInterrupt\n    except BaseException as exc:\n        from datalad.support.exceptions import (\n            CapturedException,\n            CommandError,\n            IncompleteResultsError,\n            InsufficientArgumentsError,\n        )\n        ce = CapturedException(exc)\n        # we crashed, it has got to be non-zero for starters\n        exit_code = 1\n        if isinstance(exc, InsufficientArgumentsError):\n            # if the func reports inappropriate usage, give help output\n            lgr.error('%s (%s)', ce, exc.__class__.__name__)\n            cmdlineargs.subparser.print_usage(sys.stderr)\n            exit_code = 2\n        elif isinstance(exc, IncompleteResultsError):\n            # in general we do not want to see the error again, but\n            # present in debug output\n            lgr.debug('could not perform all requested actions: %s', ce)\n        elif isinstance(exc, CommandError):\n            exit_code = _communicate_commanderror(exc) or exit_code\n        elif isinstance(exc, KeyboardInterrupt):\n            from datalad.ui import ui\n            ui.error(\"\\nInterrupted by user while doing magic: %s\" % ce)\n            exit_code = 3\n        else:\n            # some unforeseen problem\n            lgr.error('%s', ce.format_with_cause())\n        sys.exit(exit_code)\n\n\ndef _communicate_commanderror(exc):\n    \"\"\"Behave as if the command ran directly\"\"\"\n    exc_msg = exc.to_str(include_output=False)\n    if exc_msg:\n        msg = exc_msg.encode() if isinstance(exc_msg, str) else exc_msg\n        os.write(2, msg + b\"\\n\")\n    # push any captured output to the respective streams\n    for out, stream in ((exc.stdout, 1), (exc.stderr, 2)):\n        if out:\n            os.write(stream,\n                     out.encode() if isinstance(out, str) else out)\n    # pass on exit code\n    return exc.code\n\n\nlgr.log(5, \"Done importing cli.main\")\n",
            "new_file_content": "# emacs: -*- mode: python; py-indent-offset: 4; tab-width: 4; indent-tabs-mode: nil -*-\n# ex: set sts=4 ts=4 sw=4 et:\n# ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ##\n#\n#   See COPYING file distributed along with the datalad package for the\n#   copyright and license terms.\n#\n# ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ##\n\"\"\"This is the main() CLI entryproint\"\"\"\n\n# It should start-up and run as fast as possible for a responsive CLI.\n\n# Imports are done inline and as late as possible to avoid paying for\n# an unconditional commulative overhead that is only actually needed\n# in some special cases.\n\n__docformat__ = 'restructuredtext'\n\nimport logging\n\nlgr = logging.getLogger('datalad.cli')\n\nlgr.log(5, \"Importing cli.main\")\n\nimport os\nimport sys\n\nimport datalad\n\nfrom .parser import setup_parser\n\n# TODO cross-check with unconditional imports in .parser\n# special case imports\n#   from .helpers import _fix_datalad_ri\n#   import platform\n#   from .helpers import _parse_overrides_from_cmdline\n#   from datalad.utils import chpwd\n#   from .utils import setup_exceptionhook\n#   from datalad.support.exceptions import ...\n\n# unconditional imports, no meaningful functionality without them\n#   from .parser import setup_parser\n\n\ndef _on_msys_tainted_paths():\n    \"\"\"This duplicates datalad.utils.on_msys_tainted_paths\n\n    But it does it while minimizing runtime penalties on all irrelevant\n    systems.\n    \"\"\"\n    if os.environ.get('MSYSTEM', '')[:4] not in ('MSYS', 'MING'):\n        return False\n    if 'MSYS_NO_PATHCONV' in os.environ:\n        return False\n    import platform\n    if platform.system().lower() != 'windows':\n        return False\n    return True\n\n\ndef main(args=sys.argv):\n    \"\"\"Main CLI entrypoint\"\"\"\n    lgr.log(5, \"Starting main(%r)\", args)\n    # record that we came in via the cmdline\n    datalad.__api = 'cmdline'\n    completing = \"_ARGCOMPLETE\" in os.environ\n    if completing and 'COMP_LINE' in os.environ:\n        import shlex\n\n        # TODO support posix=False too?\n        args = shlex.split(os.environ['COMP_LINE']) or args\n\n    if _on_msys_tainted_paths():\n        # Possibly present DataLadRIs were stripped of a leading /\n        from .helpers import _fix_datalad_ri\n        args = [_fix_datalad_ri(s) for s in args]\n\n    from datalad.support.entrypoints import load_extensions\n\n    # load extensions requested by configuration\n    # analog to what coreapi is doing for a Python session\n    # importantly, load them prior to parser construction, such\n    # that CLI tuning is also within reach for extensions\n    load_extensions()\n\n    # PYTHON_ARGCOMPLETE_OK\n    # TODO possibly construct a dedicated parser just for autocompletion\n    # rather than lobotomizing the normal one\n    parser = setup_parser(args, completing=completing)\n    try:\n        import argcomplete\n        argcomplete.autocomplete(parser)\n    except ImportError:\n        pass\n\n    # parse cmd args\n    lgr.debug(\"Parsing known args among %r\", args)\n    cmdlineargs, unparsed_args = parser.parse_known_args(args[1:])\n    # did the parser tell us what command to run?\n    has_func = hasattr(cmdlineargs, 'func') and cmdlineargs.func is not None\n    if unparsed_args:\n        if has_func:\n            lgr.error('unknown argument%s: %s',\n                's' if len(unparsed_args) > 1 else '',\n                unparsed_args if len(unparsed_args) > 1 else unparsed_args[0],\n            )\n            cmdlineargs.subparser.print_usage()\n            sys.exit(1)\n        else:\n            # store all unparsed arguments\n            cmdlineargs.datalad_unparsed_args = unparsed_args\n\n    # pull config overrides from cmdline args and put in effect\n    if cmdlineargs.cfg_overrides is not None:\n        from .helpers import _parse_overrides_from_cmdline\n        datalad.cfg.overrides.update(\n            _parse_overrides_from_cmdline(cmdlineargs)\n        )\n        # enable overrides\n        datalad.cfg.reload(force=True)\n        # try loading extensions again, in case the configuration\n        # added new ones to consider\n        load_extensions()\n\n    if 'datalad.runtime.librarymode' in datalad.cfg:\n        datalad.enable_librarymode()\n\n    if cmdlineargs.change_path is not None:\n        from datalad.utils import chpwd\n        for path in cmdlineargs.change_path:\n            chpwd(path)\n\n    # check argparse could determine what commands needs to be executed\n    if not has_func:\n        # just let argparser spit out its error, since there is smth wrong\n        parser.parse_args(args)\n        # if that one didn't puke -- we should\n        parser.print_usage()\n        lgr.error(\"Please specify the command\")\n        # matches exit code for InsufficientArgumentsError\n        sys.exit(2)\n\n    _run(cmdlineargs)\n\n\ndef _run(namespace):\n    \"\"\"Execute a CLI operation\n\n    Depending on CLI debugging options the CLI operation is executed\n    in a debug harness or an exception handler.\n\n    Parameters\n    ----------\n    namespace: Namespace\n      Object returned by `ArgumentParser.parse_args()` with fully\n      populated and validated CLI command and arguments.\n\n    Raises\n    ------\n    SystemExit\n      When the CLI completed without error (exit 0).\n    \"\"\"\n    # execute the command, either with a debugger catching\n    # a crash, or with a simplistic exception handler.\n    # note that result rendering is happening in the\n    # execution handler, when the command-generator is unwound\n    ret = _run_with_debugger(namespace) \\\n        if namespace.common_debug or namespace.common_idebug \\\n        else _run_with_exception_handler(namespace)\n\n    # all good, not strictly needed, but makes internal testing easier\n    sys.exit(0)\n\n\ndef _run_with_debugger(cmdlineargs):\n    \"\"\"Execute the command and drop into debugger if it crashes\"\"\"\n    from .utils import setup_exceptionhook\n\n    # so we could see/stop clearly at the point of failure\n    setup_exceptionhook(ipython=cmdlineargs.common_idebug)\n    return cmdlineargs.func(cmdlineargs)\n\n\ndef _run_with_exception_handler(cmdlineargs):\n    \"\"\"Execute the command and perform some reporting\n    normalization if it crashes, but otherwise just let it go\"\"\"\n    # otherwise - guard and only log the summary. Postmortem is not\n    # as convenient if being caught in this ultimate except\n    try:\n        return cmdlineargs.func(cmdlineargs)\n    # catch BaseException for KeyboardInterrupt\n    except BaseException as exc:\n        from datalad.support.exceptions import (\n            CapturedException,\n            CommandError,\n            IncompleteResultsError,\n            InsufficientArgumentsError,\n        )\n        ce = CapturedException(exc)\n        # we crashed, it has got to be non-zero for starters\n        exit_code = 1\n        if isinstance(exc, InsufficientArgumentsError):\n            # if the func reports inappropriate usage, give help output\n            lgr.error('%s (%s)', ce, exc.__class__.__name__)\n            cmdlineargs.subparser.print_usage(sys.stderr)\n            exit_code = 2\n        elif isinstance(exc, IncompleteResultsError):\n            # in general we do not want to see the error again, but\n            # present in debug output\n            lgr.debug('could not perform all requested actions: %s', ce)\n            # fish for an exit code. If any of the \"failed\" results\n            # is caused by a command error, it will come with an `exit_code`\n            # property.\n            exit_codes = [\n                r['exit_code'] for r in exc.failed if 'exit_code' in r]\n            if exit_codes:\n                # we have exit code(s), take the first non-0 one\n                non0_codes = [e for e in exit_codes if e]\n                if len(non0_codes) != len(exit_codes):\n                    lgr.debug(\n                        \"Among %d incomplete results with exit codes %d had exit code 0\",\n                        len(exit_codes),\n                        len(exit_codes) - len(non0_codes))\n                if non0_codes:\n                    exit_code = non0_codes[0]\n        elif isinstance(exc, CommandError):\n            exit_code = _communicate_commanderror(exc) or exit_code\n        elif isinstance(exc, KeyboardInterrupt):\n            from datalad.ui import ui\n            ui.error(\"\\nInterrupted by user while doing magic: %s\" % ce)\n            exit_code = 3\n        else:\n            # some unforeseen problem\n            lgr.error('%s', ce.format_with_cause())\n        sys.exit(exit_code)\n\n\ndef _communicate_commanderror(exc):\n    \"\"\"Behave as if the command ran directly\"\"\"\n    exc_msg = exc.to_str(include_output=False)\n    if exc_msg:\n        msg = exc_msg.encode() if isinstance(exc_msg, str) else exc_msg\n        os.write(2, msg + b\"\\n\")\n    # push any captured output to the respective streams\n    for out, stream in ((exc.stdout, 1), (exc.stderr, 2)):\n        if out:\n            os.write(stream,\n                     out.encode() if isinstance(out, str) else out)\n    # pass on exit code\n    return exc.code\n\n\nlgr.log(5, \"Done importing cli.main\")\n",
            "header": {
                "file": {
                    "path": "datalad/cli/main.py"
                },
                "misc_line": null
            },
            "index_line": {
                "old_commit_hash": "bb68e46c8",
                "new_commit_hash": "61d3293ad",
                "mode": "100644"
            },
            "is_binary_file": false,
            "binary_line": null,
            "minus_file": {
                "path": "a/datalad/cli/main.py"
            },
            "plus_file": {
                "path": "b/datalad/cli/main.py"
            },
            "hunks": [
                {
                    "descriptor": {
                        "old_range": {
                            "start": 208,
                            "length": 6
                        },
                        "new_range": {
                            "start": 208,
                            "length": 21
                        },
                        "section": "def _run_with_exception_handler(cmdlineargs):"
                    },
                    "line_group": {
                        "all_lines": [
                            {
                                "content": "            # in general we do not want to see the error again, but",
                                "type": "context"
                            },
                            {
                                "content": "            # present in debug output",
                                "type": "context"
                            },
                            {
                                "content": "            lgr.debug('could not perform all requested actions: %s', ce)",
                                "type": "context"
                            },
                            {
                                "content": "            # fish for an exit code. If any of the \"failed\" results",
                                "type": "added"
                            },
                            {
                                "content": "            # is caused by a command error, it will come with an `exit_code`",
                                "type": "added"
                            },
                            {
                                "content": "            # property.",
                                "type": "added"
                            },
                            {
                                "content": "            exit_codes = [",
                                "type": "added"
                            },
                            {
                                "content": "                r['exit_code'] for r in exc.failed if 'exit_code' in r]",
                                "type": "added"
                            },
                            {
                                "content": "            if exit_codes:",
                                "type": "added"
                            },
                            {
                                "content": "                # we have exit code(s), take the first non-0 one",
                                "type": "added"
                            },
                            {
                                "content": "                non0_codes = [e for e in exit_codes if e]",
                                "type": "added"
                            },
                            {
                                "content": "                if len(non0_codes) != len(exit_codes):",
                                "type": "added"
                            },
                            {
                                "content": "                    lgr.debug(",
                                "type": "added"
                            },
                            {
                                "content": "                        \"Among %d incomplete results with exit codes %d had exit code 0\",",
                                "type": "added"
                            },
                            {
                                "content": "                        len(exit_codes),",
                                "type": "added"
                            },
                            {
                                "content": "                        len(exit_codes) - len(non0_codes))",
                                "type": "added"
                            },
                            {
                                "content": "                if non0_codes:",
                                "type": "added"
                            },
                            {
                                "content": "                    exit_code = non0_codes[0]",
                                "type": "added"
                            },
                            {
                                "content": "        elif isinstance(exc, CommandError):",
                                "type": "context"
                            },
                            {
                                "content": "            exit_code = _communicate_commanderror(exc) or exit_code",
                                "type": "context"
                            },
                            {
                                "content": "        elif isinstance(exc, KeyboardInterrupt):",
                                "type": "context"
                            }
                        ]
                    },
                    "modified_entities": [
                        {
                            "file_name": "datalad/cli/main.py",
                            "type": "function",
                            "name": "_run_with_exception_handler",
                            "content": "def _run_with_exception_handler(cmdlineargs):\n    \"\"\"Execute the command and perform some reporting\n    normalization if it crashes, but otherwise just let it go\"\"\"\n    # otherwise - guard and only log the summary. Postmortem is not\n    # as convenient if being caught in this ultimate except\n    try:\n        return cmdlineargs.func(cmdlineargs)\n    # catch BaseException for KeyboardInterrupt\n    except BaseException as exc:\n        from datalad.support.exceptions import (\n            CapturedException,\n            CommandError,\n            IncompleteResultsError,\n            InsufficientArgumentsError,\n        )\n        ce = CapturedException(exc)\n        # we crashed, it has got to be non-zero for starters\n        exit_code = 1\n        if isinstance(exc, InsufficientArgumentsError):\n            # if the func reports inappropriate usage, give help output\n            lgr.error('%s (%s)', ce, exc.__class__.__name__)\n            cmdlineargs.subparser.print_usage(sys.stderr)\n            exit_code = 2\n        elif isinstance(exc, IncompleteResultsError):\n            # in general we do not want to see the error again, but\n            # present in debug output\n            lgr.debug('could not perform all requested actions: %s', ce)\n            # fish for an exit code. If any of the \"failed\" results\n            # is caused by a command error, it will come with an `exit_code`\n            # property.\n            exit_codes = [\n                r['exit_code'] for r in exc.failed if 'exit_code' in r]\n            if exit_codes:\n                # we have exit code(s), take the first non-0 one\n                non0_codes = [e for e in exit_codes if e]\n                if len(non0_codes) != len(exit_codes):\n                    lgr.debug(\n                        \"Among %d incomplete results with exit codes %d had exit code 0\",\n                        len(exit_codes),\n                        len(exit_codes) - len(non0_codes))\n                if non0_codes:\n                    exit_code = non0_codes[0]\n        elif isinstance(exc, CommandError):\n            exit_code = _communicate_commanderror(exc) or exit_code\n        elif isinstance(exc, KeyboardInterrupt):\n            from datalad.ui import ui\n            ui.error(\"\\nInterrupted by user while doing magic: %s\" % ce)\n            exit_code = 3\n        else:\n            # some unforeseen problem\n            lgr.error('%s', ce.format_with_cause())\n        sys.exit(exit_code)",
                            "ast_type_str": "FunctionDef",
                            "start_lineno": 184,
                            "end_lineno": 235,
                            "parent": null
                        }
                    ],
                    "added_entities": [],
                    "deleted_entities": []
                }
            ]
        },
        {
            "old_file_content": "# emacs: -*- mode: python-mode; py-indent-offset: 4; tab-width: 4; indent-tabs-mode: nil -*-\n# ex: set sts=4 ts=4 sw=4 et:\n# ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ##\n#\n#   See COPYING file distributed along with the datalad package for the\n#   copyright and license terms.\n#\n# ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ##\n\"\"\"Test functioning of the datalad main cmdline utility \"\"\"\n\nimport os\nimport re\nfrom io import StringIO\nfrom unittest.mock import patch\n\nimport pytest\n\nimport datalad\nfrom datalad import __version__\nfrom datalad.api import (\n    Dataset,\n    create,\n)\nfrom datalad.cmd import StdOutErrCapture\nfrom datalad.cmd import WitlessRunner as Runner\nfrom datalad.interface.base import get_interface_groups\nfrom datalad.tests.utils_pytest import (\n    SkipTest,\n    assert_equal,\n    assert_in,\n    assert_not_in,\n    assert_raises,\n    assert_re_in,\n    eq_,\n    in_,\n    ok_,\n    ok_startswith,\n    on_windows,\n    skip_if_no_module,\n    slow,\n    with_tempfile,\n)\nfrom datalad.ui.utils import (\n    get_console_width,\n    get_terminal_size,\n)\nfrom datalad.utils import chpwd\n\nfrom ..helpers import get_commands_from_groups\nfrom ..main import main\n\n\ndef run_main(args, exit_code=0, expect_stderr=False):\n    \"\"\"Run main() of the datalad, do basic checks and provide outputs\n\n    Parameters\n    ----------\n    args : list\n        List of string cmdline arguments to pass\n    exit_code : int\n        Expected exit code. Would raise AssertionError if differs\n    expect_stderr : bool or string\n        Whether to expect stderr output. If string -- match\n\n    Returns\n    -------\n    stdout, stderr  strings\n       Output produced\n    \"\"\"\n    was_mode = datalad.__api\n    try:\n        # we need to catch \"stdout\" from multiple places:\n        # sys.stdout but also from the UI, which insists on holding\n        # a dedicated handle\n        fakeout = StringIO()\n        fakeerr = StringIO()\n        with patch('sys.stderr', new=fakeerr) as cmerr, \\\n             patch('sys.stdout', new=fakeout) as cmout, \\\n             patch.object(datalad.ui.ui._ui, 'out', new=fakeout):\n            with assert_raises(SystemExit) as cm:\n                main([\"datalad\"] + list(args))\n            eq_('cmdline', datalad.get_apimode())\n            assert_equal(cm.value.code, exit_code)\n            stdout = cmout.getvalue()\n            stderr = cmerr.getvalue()\n            if expect_stderr is False:\n                assert_equal(stderr, \"\")\n            elif expect_stderr is True:\n                # do nothing -- just return\n                pass\n            else:\n                # must be a string\n                assert_equal(stderr, expect_stderr)\n    finally:\n        # restore what we had\n        datalad.__api = was_mode\n\n    return stdout, stderr\n\n\ndef get_all_commands() -> list:\n    return list(get_commands_from_groups(get_interface_groups()))\n\n\ndef assert_all_commands_present(out):\n    \"\"\"Helper to reuse to assert that all known commands are present in output\n    \"\"\"\n    for cmd in get_all_commands():\n        assert_re_in(fr\"\\b{cmd}\\b\", out, match=False)\n\n\n# TODO: switch to stdout for --version output\ndef test_version():\n    # we just get a version if not asking for a version of some command\n    stdout, stderr = run_main(['--version'], expect_stderr=True)\n    eq_(stdout.rstrip(), \"datalad %s\" % datalad.__version__)\n\n    stdout, stderr = run_main(['clone', '--version'], expect_stderr=True)\n    ok_startswith(stdout, 'datalad %s\\n' % datalad.__version__)\n    # since https://github.com/datalad/datalad/pull/2733 no license in --version\n    assert_not_in(\"Copyright\", stdout)\n    assert_not_in(\"Permission is hereby granted\", stdout)\n\n    try:\n        import datalad_container\n    except ImportError:\n        pass  # not installed, cannot test with extension\n    else:\n        stdout, stderr = run_main(['containers-list', '--version'], expect_stderr=True)\n        eq_(stdout, 'datalad_container %s\\n' % datalad_container.__version__)\n\n\ndef test_help_np():\n    stdout, stderr = run_main(['--help-np'])\n\n    # Let's extract section titles:\n    # enough of bin/datalad and .tox/py27/bin/datalad -- guarantee consistency! ;)\n    ok_startswith(stdout, 'Usage: datalad')\n    # Sections start/end with * if ran under DATALAD_HELP2MAN mode\n    sections = [l[1:-1] for l in filter(re.compile(r'^\\*.*\\*$').match, stdout.split('\\n'))]\n    for s in {'Essential',\n              'Miscellaneous',\n              'General information',\n              'Global options',\n              'Plumbing',\n              }:\n        assert_in(s, sections)\n        # should be present only one time!\n        eq_(stdout.count(f'*{s}*'), 1)\n\n    # check that we have global options actually listed after \"Global options\"\n    # ATM -c is the first such option\n    assert re.search(r\"Global options\\W*-c \", stdout, flags=re.MULTILINE)\n    # and -c should be listed only once - i.e. that we do not duplicate sections\n    # and our USAGE summary has only [global-opts]\n    assert re.match(r\"Usage: .*datalad.* \\[global-opts\\] command \\[command-opts\\]\", stdout)\n    assert stdout.count(' -c ') == 1\n\n    assert_all_commands_present(stdout)\n\n    if not get_terminal_size()[0] or 0:\n        raise SkipTest(\n            \"Could not determine terminal size, skipping the rest of the test\")\n\n    # none of the lines must be longer than 80 chars\n    # TODO: decide on   create-sibling and possibly\n    # rewrite-urls\n    accepted_width = get_console_width()\n\n    long_lines = [\"%d %s\" % (len(l), l) for l in stdout.split('\\n')\n                  if len(l) > accepted_width and\n                  '{' not in l  # on nd70 summary line is unsplit\n                  ]\n    if long_lines:\n        raise AssertionError(\n            \"Following lines in --help output were longer than %s chars:\\n%s\"\n            % (accepted_width, '\\n'.join(long_lines))\n        )\n\n\ndef test_dashh():\n    stdout, stderr = run_main(['-h'])\n    # Note: for -h we do not do ad-hoc tune up of Usage: to guarantee having\n    # datalad instead of python -m nose etc, so we can only verify that we have\n    # options listed\n    assert_re_in(r'^Usage: .*\\[', stdout.splitlines()[0])\n    assert_all_commands_present(stdout)\n    assert_re_in('Use .--help. to get more comprehensive information', stdout.splitlines())\n\n\ndef test_dashh_clone():\n    # test -h on a sample command\n    stdout, stderr = run_main(['clone', '-h'])\n    assert_re_in(r'^Usage: .* clone \\[', stdout.splitlines()[0])\n    assert_re_in('Use .--help. to get more comprehensive information', stdout.splitlines())\n\n\ndef test_usage_on_insufficient_args():\n    stdout, stderr = run_main(['install'], exit_code=2, expect_stderr=True)\n    ok_startswith(stderr, 'usage:')\n\n\ndef test_subcmd_usage_on_unknown_args():\n    stdout, stderr = run_main(['get', '--murks'], exit_code=1, expect_stderr=True)\n    in_('get', stdout)\n\n\ndef test_combined_short_option():\n    stdout, stderr = run_main(['-fjson'], exit_code=2, expect_stderr=True)\n    assert_not_in(\"unrecognized argument\", stderr)\n    assert_in(\"too few arguments\", stderr)\n\n\n# https://github.com/datalad/datalad/issues/6814\n@with_tempfile(mkdir=True)\ndef test_conflicting_short_option(tempdir=None):\n    # datalad -f|--format   requires a value. regression made parser ignore command\n    # and its options\n    with chpwd(tempdir):  # can't just use -C tempdir since we do \"in process\" run_main\n        run_main(['create', '-f'])\n\n\n# apparently a bit different if following a good one so let's do both\nerr_invalid = \"error: (invalid|too few arguments|unrecognized argument)\"\nerr_insufficient = err_invalid  # \"specify\"\n\n\n@pytest.mark.parametrize(\n    \"opts,err_str\",\n    [\n        (('--buga',), err_invalid),\n        (('--dbg', '--buga'), err_invalid),\n        (('--dbg',), err_insufficient),\n        (tuple(), err_insufficient),\n    ]\n)\ndef test_incorrect_option(opts, err_str):\n    # The first line used to be:\n    # stdout, stderr = run_main((sys.argv[0],) + opts, expect_stderr=True, exit_code=2)\n    # But: what do we expect to be in sys.argv[0] here?\n    # It depends on how we invoke the test.\n    # - nosetests -s -v datalad/cmdline/tests/test_main.py would result in:\n    #   sys.argv[0}=='nosetests'\n    # - python -m nose -s -v datalad/cmdline/tests/test_main.py would result in:\n    #   sys.argv[0}=='python -m nose'\n    # - python -c \"import nose; nose.main()\" -s -v datalad/cmdline/tests/test_main.py would result in:\n    #   sys.argv[0]=='-c'\n    # This led to failure in case sys.argv[0] contained an option, that was\n    # defined to be a datalad option too, therefore was a 'known_arg' and was\n    # checked to meet its constraints.\n    # But sys.argv[0] actually isn't used by main at all. It simply doesn't\n    # matter what's in there. The only thing important to pass here is `opts`.\n    stdout, stderr = run_main(opts, expect_stderr=True, exit_code=2)\n    out = stdout + stderr\n    assert_in(\"usage: \", out)\n    assert_re_in(err_str, out, match=False)\n\n\n@pytest.mark.parametrize(\n    \"script\",\n    [\n        'datalad',\n        'git-annex-remote-datalad-archives',\n        'git-annex-remote-datalad',\n    ]\n)\ndef test_script_shims(script):\n    runner = Runner()\n    if not on_windows:\n\n        from shutil import which\n        which(script)\n\n    # and let's check that it is our script\n    out = runner.run([script, '--version'], protocol=StdOutErrCapture)\n    version = out['stdout'].rstrip()\n    mod, version = version.split(' ', 1)\n    assert_equal(mod, 'datalad')\n    # we can get git and non git .dev version... so for now\n    # relax\n    get_numeric_portion = lambda v: [x for x in re.split('[+.]', v) if x.isdigit()]\n    # extract numeric portion\n    assert get_numeric_portion(version), f\"Got no numeric portion from {version}\"\n    assert_equal(get_numeric_portion(__version__),\n                 get_numeric_portion(version))\n\n\n@with_tempfile(mkdir=True)\ndef test_cfg_override(path=None):\n    with chpwd(path):\n        # use 'wtf' to dump the config\n        # should be rewritten to use `configuration`\n        cmd = ['datalad', 'wtf', '-S', 'configuration', '-s', 'some']\n        # control\n        out = Runner().run(cmd, protocol=StdOutErrCapture)['stdout']\n        assert_not_in('datalad.dummy: this', out)\n        # ensure that this is not a dataset's cfg manager\n        assert_not_in('datalad.dataset.id', out)\n        # env var\n        out = Runner(env=dict(os.environ, DATALAD_DUMMY='this')).run(\n            cmd, protocol=StdOutErrCapture)['stdout']\n        assert_in('datalad.dummy: this', out)\n        # cmdline arg\n        out = Runner().run([cmd[0], '-c', 'datalad.dummy=this'] + cmd[1:],\n                           protocol=StdOutErrCapture)['stdout']\n        assert_in('datalad.dummy: this', out)\n\n        # now create a dataset in the path. the wtf plugin will switch to\n        # using the dataset's config manager, which must inherit the overrides\n        create(dataset=path, annex=False)\n        # control\n        out = Runner().run(cmd, protocol=StdOutErrCapture)['stdout']\n        assert_not_in('datalad.dummy: this', out)\n        # ensure that this is a dataset's cfg manager\n        assert_in('datalad.dataset.id', out)\n        # env var\n        out = Runner(env=dict(os.environ, DATALAD_DUMMY='this')).run(\n            cmd, protocol=StdOutErrCapture)['stdout']\n        assert_in('datalad.dummy: this', out)\n        # cmdline arg\n        out = Runner().run([cmd[0], '-c', 'datalad.dummy=this'] + cmd[1:],\n                           protocol=StdOutErrCapture)['stdout']\n        assert_in('datalad.dummy: this', out)\n\n        # set a config\n        run_main([\n            'configuration', '--scope', 'local', 'set', 'mike.item=some'])\n        # verify it is successfully set\n        assert 'some' == run_main([\n            'configuration', 'get', 'mike.item'])[0].strip()\n        # verify that an override can unset the config\n        # we cannot use run_main(), because the \"singleton\" instance of the\n        # dataset we are in is still around in this session, and with it\n        # also its config managers that we will not be able to post-hoc\n        # overwrite with this method. Instead, we'll execute in a subprocess.\n        assert '' == Runner().run([\n            'datalad', '-c', ':mike.item',\n            'configuration', 'get', 'mike.item'],\n            protocol=StdOutErrCapture)['stdout'].strip()\n        # verify the effect is not permanent\n        assert 'some' == Runner().run([\n            'datalad',\n            'configuration', 'get', 'mike.item'],\n            protocol=StdOutErrCapture)['stdout'].strip()\n\n\ndef test_incorrect_cfg_override():\n    run_main(['-c', 'some', 'wtf'], exit_code=3)\n    run_main(['-c', 'some=', 'wtf'], exit_code=3)\n    run_main(['-c', 'some.var', 'wtf'], exit_code=3)\n    run_main(['-c', 'some.var=', 'wtf'], exit_code=3)\n\n\n@with_tempfile\ndef test_librarymode(path=None):\n    Dataset(path).create()\n    was_mode = datalad.__runtime_mode\n    try:\n        # clean --dry-run is just a no-op command that is cheap\n        # to execute. It has no particular role here, other than\n        # to make the code pass the location where library mode\n        # should be turned on via the cmdline API\n        run_main(['-c', 'datalad.runtime.librarymode=yes', 'clean',\n                  '-d', path, '--dry-run'])\n        ok_(datalad.in_librarymode())\n    finally:\n        # restore pre-test behavior\n        datalad.__runtime_mode = was_mode\n        datalad.cfg.overrides.pop('datalad.runtime.librarymode')\n\n\n@with_tempfile\ndef test_completion(out_fn=None):\n    skip_if_no_module('argcomplete')\n\n    from datalad.cmd import WitlessRunner\n    runner = WitlessRunner()\n\n    def get_completions(s: str, expected) -> list:\n        \"\"\"Run 'datalad' external command and collect completions\n\n        Parameters\n        ----------\n        s: str\n          what to append to 'datalad ' invocation\n        expected: iterable of str\n          What entries to expect - would raise AssertionError if any is\n          not present in output\n        exit_code: int, optional\n          If incomplete/malformed we seems to get 2, most frequently used\n          so default\n\n        Returns\n        -------\n        list of str\n          Entries output\n        \"\"\"\n        if os.path.exists(out_fn):  # reuse but ensure it is gone\n            os.unlink(out_fn)\n        comp_line = f'datalad {s}'\n        runner.run(\n            comp_line.split(' '),\n            env=dict(os.environ,\n                     _ARGCOMPLETE='1',\n                     _ARGCOMPLETE_STDOUT_FILENAME=out_fn,\n                     COMP_LINE=comp_line,\n                     # without -1 seems to get \"finished completion\", someone can investigate more\n                     COMP_POINT=str(len(comp_line)-1),  # always at the end ATM\n                     ))\n        with open(out_fn, 'rb') as f:\n            entries = f.read().split(b'\\x0b')\n            entries = [e.decode() for e in entries]\n        diff = set(expected).difference(entries)\n        if diff:\n            raise AssertionError(\n                f\"Entries {sorted(diff)} were expected but not found in the completion output: {entries}\"\n            )\n        return entries  # for extra analyzes if so desired\n\n    all_commands = get_all_commands()\n    get_completions('i', {'install'})\n    get_completions(' ', ['--dbg', '-c'] + all_commands)\n    # if command already matches -- we get only that hit ATM, not others which begin with it\n    get_completions('create', ['create '])\n    get_completions('create -', ['--dataset'])\n    # but for incomplete one we do get all create* commands\n    get_completions('creat', [c for c in all_commands if c.startswith('create')])\n",
            "new_file_content": "# emacs: -*- mode: python-mode; py-indent-offset: 4; tab-width: 4; indent-tabs-mode: nil -*-\n# ex: set sts=4 ts=4 sw=4 et:\n# ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ##\n#\n#   See COPYING file distributed along with the datalad package for the\n#   copyright and license terms.\n#\n# ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ##\n\"\"\"Test functioning of the datalad main cmdline utility \"\"\"\n\nimport os\nimport re\nfrom io import StringIO\nfrom unittest.mock import patch\n\nimport pytest\n\nimport datalad\nfrom datalad import __version__\nfrom datalad.api import (\n    Dataset,\n    create,\n)\nfrom datalad.cmd import StdOutErrCapture\nfrom datalad.cmd import WitlessRunner as Runner\nfrom datalad.interface.base import get_interface_groups\nfrom datalad.tests.utils_pytest import (\n    SkipTest,\n    assert_equal,\n    assert_in,\n    assert_not_in,\n    assert_raises,\n    assert_re_in,\n    eq_,\n    in_,\n    ok_,\n    ok_startswith,\n    on_windows,\n    skip_if_no_module,\n    slow,\n    with_tempfile,\n)\nfrom datalad.ui.utils import (\n    get_console_width,\n    get_terminal_size,\n)\nfrom datalad.utils import chpwd\n\nfrom ..helpers import get_commands_from_groups\nfrom ..main import main\n\n\ndef run_main(args, exit_code=0, expect_stderr=False):\n    \"\"\"Run main() of the datalad, do basic checks and provide outputs\n\n    Parameters\n    ----------\n    args : list\n        List of string cmdline arguments to pass\n    exit_code : int\n        Expected exit code. Would raise AssertionError if differs\n    expect_stderr : bool or string\n        Whether to expect stderr output. If string -- match\n\n    Returns\n    -------\n    stdout, stderr  strings\n       Output produced\n    \"\"\"\n    was_mode = datalad.__api\n    try:\n        # we need to catch \"stdout\" from multiple places:\n        # sys.stdout but also from the UI, which insists on holding\n        # a dedicated handle\n        fakeout = StringIO()\n        fakeerr = StringIO()\n        with patch('sys.stderr', new=fakeerr) as cmerr, \\\n             patch('sys.stdout', new=fakeout) as cmout, \\\n             patch.object(datalad.ui.ui._ui, 'out', new=fakeout):\n            with assert_raises(SystemExit) as cm:\n                main([\"datalad\"] + list(args))\n            eq_('cmdline', datalad.get_apimode())\n            assert_equal(cm.value.code, exit_code)\n            stdout = cmout.getvalue()\n            stderr = cmerr.getvalue()\n            if expect_stderr is False:\n                assert_equal(stderr, \"\")\n            elif expect_stderr is True:\n                # do nothing -- just return\n                pass\n            else:\n                # must be a string\n                assert_equal(stderr, expect_stderr)\n    finally:\n        # restore what we had\n        datalad.__api = was_mode\n\n    return stdout, stderr\n\n\ndef get_all_commands() -> list:\n    return list(get_commands_from_groups(get_interface_groups()))\n\n\ndef assert_all_commands_present(out):\n    \"\"\"Helper to reuse to assert that all known commands are present in output\n    \"\"\"\n    for cmd in get_all_commands():\n        assert_re_in(fr\"\\b{cmd}\\b\", out, match=False)\n\n\n# TODO: switch to stdout for --version output\ndef test_version():\n    # we just get a version if not asking for a version of some command\n    stdout, stderr = run_main(['--version'], expect_stderr=True)\n    eq_(stdout.rstrip(), \"datalad %s\" % datalad.__version__)\n\n    stdout, stderr = run_main(['clone', '--version'], expect_stderr=True)\n    ok_startswith(stdout, 'datalad %s\\n' % datalad.__version__)\n    # since https://github.com/datalad/datalad/pull/2733 no license in --version\n    assert_not_in(\"Copyright\", stdout)\n    assert_not_in(\"Permission is hereby granted\", stdout)\n\n    try:\n        import datalad_container\n    except ImportError:\n        pass  # not installed, cannot test with extension\n    else:\n        stdout, stderr = run_main(['containers-list', '--version'], expect_stderr=True)\n        eq_(stdout, 'datalad_container %s\\n' % datalad_container.__version__)\n\n\ndef test_help_np():\n    stdout, stderr = run_main(['--help-np'])\n\n    # Let's extract section titles:\n    # enough of bin/datalad and .tox/py27/bin/datalad -- guarantee consistency! ;)\n    ok_startswith(stdout, 'Usage: datalad')\n    # Sections start/end with * if ran under DATALAD_HELP2MAN mode\n    sections = [l[1:-1] for l in filter(re.compile(r'^\\*.*\\*$').match, stdout.split('\\n'))]\n    for s in {'Essential',\n              'Miscellaneous',\n              'General information',\n              'Global options',\n              'Plumbing',\n              }:\n        assert_in(s, sections)\n        # should be present only one time!\n        eq_(stdout.count(f'*{s}*'), 1)\n\n    # check that we have global options actually listed after \"Global options\"\n    # ATM -c is the first such option\n    assert re.search(r\"Global options\\W*-c \", stdout, flags=re.MULTILINE)\n    # and -c should be listed only once - i.e. that we do not duplicate sections\n    # and our USAGE summary has only [global-opts]\n    assert re.match(r\"Usage: .*datalad.* \\[global-opts\\] command \\[command-opts\\]\", stdout)\n    assert stdout.count(' -c ') == 1\n\n    assert_all_commands_present(stdout)\n\n    if not get_terminal_size()[0] or 0:\n        raise SkipTest(\n            \"Could not determine terminal size, skipping the rest of the test\")\n\n    # none of the lines must be longer than 80 chars\n    # TODO: decide on   create-sibling and possibly\n    # rewrite-urls\n    accepted_width = get_console_width()\n\n    long_lines = [\"%d %s\" % (len(l), l) for l in stdout.split('\\n')\n                  if len(l) > accepted_width and\n                  '{' not in l  # on nd70 summary line is unsplit\n                  ]\n    if long_lines:\n        raise AssertionError(\n            \"Following lines in --help output were longer than %s chars:\\n%s\"\n            % (accepted_width, '\\n'.join(long_lines))\n        )\n\n\ndef test_dashh():\n    stdout, stderr = run_main(['-h'])\n    # Note: for -h we do not do ad-hoc tune up of Usage: to guarantee having\n    # datalad instead of python -m nose etc, so we can only verify that we have\n    # options listed\n    assert_re_in(r'^Usage: .*\\[', stdout.splitlines()[0])\n    assert_all_commands_present(stdout)\n    assert_re_in('Use .--help. to get more comprehensive information', stdout.splitlines())\n\n\ndef test_dashh_clone():\n    # test -h on a sample command\n    stdout, stderr = run_main(['clone', '-h'])\n    assert_re_in(r'^Usage: .* clone \\[', stdout.splitlines()[0])\n    assert_re_in('Use .--help. to get more comprehensive information', stdout.splitlines())\n\n\ndef test_usage_on_insufficient_args():\n    stdout, stderr = run_main(['install'], exit_code=2, expect_stderr=True)\n    ok_startswith(stderr, 'usage:')\n\n\ndef test_subcmd_usage_on_unknown_args():\n    stdout, stderr = run_main(['get', '--murks'], exit_code=1, expect_stderr=True)\n    in_('get', stdout)\n\n\ndef test_combined_short_option():\n    stdout, stderr = run_main(['-fjson'], exit_code=2, expect_stderr=True)\n    assert_not_in(\"unrecognized argument\", stderr)\n    assert_in(\"too few arguments\", stderr)\n\n\n# https://github.com/datalad/datalad/issues/7504\n@with_tempfile(mkdir=True)\ndef test_run_exit_code(tempdir=None):\n    # datalad run is just an internal command which can readily be used to trigger\n    # desired situation\n    create(dataset=tempdir, annex=False)\n    with chpwd(tempdir):  # can't just use -C tempdir since we do \"in process\" run_main\n        stdout, stderr = run_main(['run', '--explicit', 'exit 3'], exit_code=3) #, expect_stderr=True)\n    print(stdout)\n    print(stderr)\n\n\n# https://github.com/datalad/datalad/issues/6814\n@with_tempfile(mkdir=True)\ndef test_conflicting_short_option(tempdir=None):\n    # datalad -f|--format   requires a value. regression made parser ignore command\n    # and its options\n    with chpwd(tempdir):  # can't just use -C tempdir since we do \"in process\" run_main\n        run_main(['create', '-f'])\n\n\n# apparently a bit different if following a good one so let's do both\nerr_invalid = \"error: (invalid|too few arguments|unrecognized argument)\"\nerr_insufficient = err_invalid  # \"specify\"\n\n\n@pytest.mark.parametrize(\n    \"opts,err_str\",\n    [\n        (('--buga',), err_invalid),\n        (('--dbg', '--buga'), err_invalid),\n        (('--dbg',), err_insufficient),\n        (tuple(), err_insufficient),\n    ]\n)\ndef test_incorrect_option(opts, err_str):\n    # The first line used to be:\n    # stdout, stderr = run_main((sys.argv[0],) + opts, expect_stderr=True, exit_code=2)\n    # But: what do we expect to be in sys.argv[0] here?\n    # It depends on how we invoke the test.\n    # - nosetests -s -v datalad/cmdline/tests/test_main.py would result in:\n    #   sys.argv[0}=='nosetests'\n    # - python -m nose -s -v datalad/cmdline/tests/test_main.py would result in:\n    #   sys.argv[0}=='python -m nose'\n    # - python -c \"import nose; nose.main()\" -s -v datalad/cmdline/tests/test_main.py would result in:\n    #   sys.argv[0]=='-c'\n    # This led to failure in case sys.argv[0] contained an option, that was\n    # defined to be a datalad option too, therefore was a 'known_arg' and was\n    # checked to meet its constraints.\n    # But sys.argv[0] actually isn't used by main at all. It simply doesn't\n    # matter what's in there. The only thing important to pass here is `opts`.\n    stdout, stderr = run_main(opts, expect_stderr=True, exit_code=2)\n    out = stdout + stderr\n    assert_in(\"usage: \", out)\n    assert_re_in(err_str, out, match=False)\n\n\n@pytest.mark.parametrize(\n    \"script\",\n    [\n        'datalad',\n        'git-annex-remote-datalad-archives',\n        'git-annex-remote-datalad',\n    ]\n)\ndef test_script_shims(script):\n    runner = Runner()\n    if not on_windows:\n\n        from shutil import which\n        which(script)\n\n    # and let's check that it is our script\n    out = runner.run([script, '--version'], protocol=StdOutErrCapture)\n    version = out['stdout'].rstrip()\n    mod, version = version.split(' ', 1)\n    assert_equal(mod, 'datalad')\n    # we can get git and non git .dev version... so for now\n    # relax\n    get_numeric_portion = lambda v: [x for x in re.split('[+.]', v) if x.isdigit()]\n    # extract numeric portion\n    assert get_numeric_portion(version), f\"Got no numeric portion from {version}\"\n    assert_equal(get_numeric_portion(__version__),\n                 get_numeric_portion(version))\n\n\n@with_tempfile(mkdir=True)\ndef test_cfg_override(path=None):\n    with chpwd(path):\n        # use 'wtf' to dump the config\n        # should be rewritten to use `configuration`\n        cmd = ['datalad', 'wtf', '-S', 'configuration', '-s', 'some']\n        # control\n        out = Runner().run(cmd, protocol=StdOutErrCapture)['stdout']\n        assert_not_in('datalad.dummy: this', out)\n        # ensure that this is not a dataset's cfg manager\n        assert_not_in('datalad.dataset.id', out)\n        # env var\n        out = Runner(env=dict(os.environ, DATALAD_DUMMY='this')).run(\n            cmd, protocol=StdOutErrCapture)['stdout']\n        assert_in('datalad.dummy: this', out)\n        # cmdline arg\n        out = Runner().run([cmd[0], '-c', 'datalad.dummy=this'] + cmd[1:],\n                           protocol=StdOutErrCapture)['stdout']\n        assert_in('datalad.dummy: this', out)\n\n        # now create a dataset in the path. the wtf plugin will switch to\n        # using the dataset's config manager, which must inherit the overrides\n        create(dataset=path, annex=False)\n        # control\n        out = Runner().run(cmd, protocol=StdOutErrCapture)['stdout']\n        assert_not_in('datalad.dummy: this', out)\n        # ensure that this is a dataset's cfg manager\n        assert_in('datalad.dataset.id', out)\n        # env var\n        out = Runner(env=dict(os.environ, DATALAD_DUMMY='this')).run(\n            cmd, protocol=StdOutErrCapture)['stdout']\n        assert_in('datalad.dummy: this', out)\n        # cmdline arg\n        out = Runner().run([cmd[0], '-c', 'datalad.dummy=this'] + cmd[1:],\n                           protocol=StdOutErrCapture)['stdout']\n        assert_in('datalad.dummy: this', out)\n\n        # set a config\n        run_main([\n            'configuration', '--scope', 'local', 'set', 'mike.item=some'])\n        # verify it is successfully set\n        assert 'some' == run_main([\n            'configuration', 'get', 'mike.item'])[0].strip()\n        # verify that an override can unset the config\n        # we cannot use run_main(), because the \"singleton\" instance of the\n        # dataset we are in is still around in this session, and with it\n        # also its config managers that we will not be able to post-hoc\n        # overwrite with this method. Instead, we'll execute in a subprocess.\n        assert '' == Runner().run([\n            'datalad', '-c', ':mike.item',\n            'configuration', 'get', 'mike.item'],\n            protocol=StdOutErrCapture)['stdout'].strip()\n        # verify the effect is not permanent\n        assert 'some' == Runner().run([\n            'datalad',\n            'configuration', 'get', 'mike.item'],\n            protocol=StdOutErrCapture)['stdout'].strip()\n\n\ndef test_incorrect_cfg_override():\n    run_main(['-c', 'some', 'wtf'], exit_code=3)\n    run_main(['-c', 'some=', 'wtf'], exit_code=3)\n    run_main(['-c', 'some.var', 'wtf'], exit_code=3)\n    run_main(['-c', 'some.var=', 'wtf'], exit_code=3)\n\n\n@with_tempfile\ndef test_librarymode(path=None):\n    Dataset(path).create()\n    was_mode = datalad.__runtime_mode\n    try:\n        # clean --dry-run is just a no-op command that is cheap\n        # to execute. It has no particular role here, other than\n        # to make the code pass the location where library mode\n        # should be turned on via the cmdline API\n        run_main(['-c', 'datalad.runtime.librarymode=yes', 'clean',\n                  '-d', path, '--dry-run'])\n        ok_(datalad.in_librarymode())\n    finally:\n        # restore pre-test behavior\n        datalad.__runtime_mode = was_mode\n        datalad.cfg.overrides.pop('datalad.runtime.librarymode')\n\n\n@with_tempfile\ndef test_completion(out_fn=None):\n    skip_if_no_module('argcomplete')\n\n    from datalad.cmd import WitlessRunner\n    runner = WitlessRunner()\n\n    def get_completions(s: str, expected) -> list:\n        \"\"\"Run 'datalad' external command and collect completions\n\n        Parameters\n        ----------\n        s: str\n          what to append to 'datalad ' invocation\n        expected: iterable of str\n          What entries to expect - would raise AssertionError if any is\n          not present in output\n        exit_code: int, optional\n          If incomplete/malformed we seems to get 2, most frequently used\n          so default\n\n        Returns\n        -------\n        list of str\n          Entries output\n        \"\"\"\n        if os.path.exists(out_fn):  # reuse but ensure it is gone\n            os.unlink(out_fn)\n        comp_line = f'datalad {s}'\n        runner.run(\n            comp_line.split(' '),\n            env=dict(os.environ,\n                     _ARGCOMPLETE='1',\n                     _ARGCOMPLETE_STDOUT_FILENAME=out_fn,\n                     COMP_LINE=comp_line,\n                     # without -1 seems to get \"finished completion\", someone can investigate more\n                     COMP_POINT=str(len(comp_line)-1),  # always at the end ATM\n                     ))\n        with open(out_fn, 'rb') as f:\n            entries = f.read().split(b'\\x0b')\n            entries = [e.decode() for e in entries]\n        diff = set(expected).difference(entries)\n        if diff:\n            raise AssertionError(\n                f\"Entries {sorted(diff)} were expected but not found in the completion output: {entries}\"\n            )\n        return entries  # for extra analyzes if so desired\n\n    all_commands = get_all_commands()\n    get_completions('i', {'install'})\n    get_completions(' ', ['--dbg', '-c'] + all_commands)\n    # if command already matches -- we get only that hit ATM, not others which begin with it\n    get_completions('create', ['create '])\n    get_completions('create -', ['--dataset'])\n    # but for incomplete one we do get all create* commands\n    get_completions('creat', [c for c in all_commands if c.startswith('create')])\n",
            "header": {
                "file": {
                    "path": "datalad/cli/tests/test_main.py"
                },
                "misc_line": null
            },
            "index_line": {
                "old_commit_hash": "18254166e",
                "new_commit_hash": "3a569b18a",
                "mode": "100644"
            },
            "is_binary_file": false,
            "binary_line": null,
            "minus_file": {
                "path": "a/datalad/cli/tests/test_main.py"
            },
            "plus_file": {
                "path": "b/datalad/cli/tests/test_main.py"
            },
            "hunks": [
                {
                    "descriptor": {
                        "old_range": {
                            "start": 211,
                            "length": 6
                        },
                        "new_range": {
                            "start": 211,
                            "length": 18
                        },
                        "section": "def test_combined_short_option():"
                    },
                    "line_group": {
                        "all_lines": [
                            {
                                "content": "    assert_in(\"too few arguments\", stderr)",
                                "type": "context"
                            },
                            {
                                "content": "",
                                "type": "context"
                            },
                            {
                                "content": "",
                                "type": "context"
                            },
                            {
                                "content": "# https://github.com/datalad/datalad/issues/7504",
                                "type": "added"
                            },
                            {
                                "content": "@with_tempfile(mkdir=True)",
                                "type": "added"
                            },
                            {
                                "content": "def test_run_exit_code(tempdir=None):",
                                "type": "added"
                            },
                            {
                                "content": "    # datalad run is just an internal command which can readily be used to trigger",
                                "type": "added"
                            },
                            {
                                "content": "    # desired situation",
                                "type": "added"
                            },
                            {
                                "content": "    create(dataset=tempdir, annex=False)",
                                "type": "added"
                            },
                            {
                                "content": "    with chpwd(tempdir):  # can't just use -C tempdir since we do \"in process\" run_main",
                                "type": "added"
                            },
                            {
                                "content": "        stdout, stderr = run_main(['run', '--explicit', 'exit 3'], exit_code=3) #, expect_stderr=True)",
                                "type": "added"
                            },
                            {
                                "content": "    print(stdout)",
                                "type": "added"
                            },
                            {
                                "content": "    print(stderr)",
                                "type": "added"
                            },
                            {
                                "content": "",
                                "type": "added"
                            },
                            {
                                "content": "",
                                "type": "added"
                            },
                            {
                                "content": "# https://github.com/datalad/datalad/issues/6814",
                                "type": "context"
                            },
                            {
                                "content": "@with_tempfile(mkdir=True)",
                                "type": "context"
                            },
                            {
                                "content": "def test_conflicting_short_option(tempdir=None):",
                                "type": "context"
                            },
                            {
                                "content": "",
                                "type": "context"
                            }
                        ]
                    },
                    "modified_entities": [],
                    "added_entities": [
                        {
                            "file_name": "datalad/cli/tests/test_main.py",
                            "type": "function",
                            "name": "test_run_exit_code",
                            "content": "def test_run_exit_code(tempdir=None):\n    # datalad run is just an internal command which can readily be used to trigger\n    # desired situation\n    create(dataset=tempdir, annex=False)\n    with chpwd(tempdir):  # can't just use -C tempdir since we do \"in process\" run_main\n        stdout, stderr = run_main(['run', '--explicit', 'exit 3'], exit_code=3) #, expect_stderr=True)\n    print(stdout)\n    print(stderr)",
                            "ast_type_str": "FunctionDef",
                            "start_lineno": 216,
                            "end_lineno": 223,
                            "parent": null
                        }
                    ],
                    "deleted_entities": []
                }
            ]
        }
    ],
    "old_commit_hash": "19f5b45096cadaa4677c9b15f18a6d303497eaeb^",
    "new_commit_hash": "19f5b45096cadaa4677c9b15f18a6d303497eaeb",
    "commit_message": "Merge pull request #7641 from yarikoptic/bf-run-exitcode\n\nExit with the original non-0 exit code if underlying functionality, in particular \"datalad run\", returned incomplete result record with a non-0 exit code",
    "commit_date": "2024-08-31T07:15:33-04:00",
    "metadata": {}
}
{
    "repo_name": "datalad",
    "new_commit_hash": "19f5b45096cadaa4677c9b15f18a6d303497eaeb",
    "test_file_codes": [
        "# emacs: -*- mode: python-mode; py-indent-offset: 4; tab-width: 4; indent-tabs-mode: nil -*-\n# ex: set sts=4 ts=4 sw=4 et:\n# ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ##\n#\n#   See COPYING file distributed along with the datalad package for the\n#   copyright and license terms.\n#\n# ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ##\n\"\"\"Test functioning of the datalad main cmdline utility \"\"\"\n\nimport os\nimport re\nfrom io import StringIO\nfrom unittest.mock import patch\n\nimport pytest\n\nimport datalad\nfrom datalad import __version__\nfrom datalad.api import (\n    Dataset,\n    create,\n)\nfrom datalad.cmd import StdOutErrCapture\nfrom datalad.cmd import WitlessRunner as Runner\nfrom datalad.interface.base import get_interface_groups\nfrom datalad.tests.utils_pytest import (\n    SkipTest,\n    assert_equal,\n    assert_in,\n    assert_not_in,\n    assert_raises,\n    assert_re_in,\n    eq_,\n    in_,\n    ok_,\n    ok_startswith,\n    on_windows,\n    skip_if_no_module,\n    slow,\n    with_tempfile,\n)\nfrom datalad.ui.utils import (\n    get_console_width,\n    get_terminal_size,\n)\nfrom datalad.utils import chpwd\n\nfrom datalad.cli.helpers import get_commands_from_groups\nfrom datalad.cli.main import main\n\n\ndef run_main(args, exit_code=0, expect_stderr=False):\n    \"\"\"Run main() of the datalad, do basic checks and provide outputs\n\n    Parameters\n    ----------\n    args : list\n        List of string cmdline arguments to pass\n    exit_code : int\n        Expected exit code. Would raise AssertionError if differs\n    expect_stderr : bool or string\n        Whether to expect stderr output. If string -- match\n\n    Returns\n    -------\n    stdout, stderr  strings\n       Output produced\n    \"\"\"\n    was_mode = datalad.__api\n    try:\n        # we need to catch \"stdout\" from multiple places:\n        # sys.stdout but also from the UI, which insists on holding\n        # a dedicated handle\n        fakeout = StringIO()\n        fakeerr = StringIO()\n        with patch('sys.stderr', new=fakeerr) as cmerr, \\\n             patch('sys.stdout', new=fakeout) as cmout, \\\n             patch.object(datalad.ui.ui._ui, 'out', new=fakeout):\n            with assert_raises(SystemExit) as cm:\n                main([\"datalad\"] + list(args))\n            eq_('cmdline', datalad.get_apimode())\n            assert_equal(cm.value.code, exit_code)\n            stdout = cmout.getvalue()\n            stderr = cmerr.getvalue()\n            if expect_stderr is False:\n                assert_equal(stderr, \"\")\n            elif expect_stderr is True:\n                # do nothing -- just return\n                pass\n            else:\n                # must be a string\n                assert_equal(stderr, expect_stderr)\n    finally:\n        # restore what we had\n        datalad.__api = was_mode\n\n    return stdout, stderr\n\n\ndef get_all_commands() -> list:\n    return list(get_commands_from_groups(get_interface_groups()))\n\n\ndef assert_all_commands_present(out):\n    \"\"\"Helper to reuse to assert that all known commands are present in output\n    \"\"\"\n    for cmd in get_all_commands():\n        assert_re_in(fr\"\\b{cmd}\\b\", out, match=False)\n\n\n# TODO: switch to stdout for --version output\ndef test_version():\n    # we just get a version if not asking for a version of some command\n    stdout, stderr = run_main(['--version'], expect_stderr=True)\n    eq_(stdout.rstrip(), \"datalad %s\" % datalad.__version__)\n\n    stdout, stderr = run_main(['clone', '--version'], expect_stderr=True)\n    ok_startswith(stdout, 'datalad %s\\n' % datalad.__version__)\n    # since https://github.com/datalad/datalad/pull/2733 no license in --version\n    assert_not_in(\"Copyright\", stdout)\n    assert_not_in(\"Permission is hereby granted\", stdout)\n\n    try:\n        import datalad_container\n    except ImportError:\n        pass  # not installed, cannot test with extension\n    else:\n        stdout, stderr = run_main(['containers-list', '--version'], expect_stderr=True)\n        eq_(stdout, 'datalad_container %s\\n' % datalad_container.__version__)\n\n\ndef test_help_np():\n    stdout, stderr = run_main(['--help-np'])\n\n    # Let's extract section titles:\n    # enough of bin/datalad and .tox/py27/bin/datalad -- guarantee consistency! ;)\n    ok_startswith(stdout, 'Usage: datalad')\n    # Sections start/end with * if ran under DATALAD_HELP2MAN mode\n    sections = [l[1:-1] for l in filter(re.compile(r'^\\*.*\\*$').match, stdout.split('\\n'))]\n    for s in {'Essential',\n              'Miscellaneous',\n              'General information',\n              'Global options',\n              'Plumbing',\n              }:\n        assert_in(s, sections)\n        # should be present only one time!\n        eq_(stdout.count(f'*{s}*'), 1)\n\n    # check that we have global options actually listed after \"Global options\"\n    # ATM -c is the first such option\n    assert re.search(r\"Global options\\W*-c \", stdout, flags=re.MULTILINE)\n    # and -c should be listed only once - i.e. that we do not duplicate sections\n    # and our USAGE summary has only [global-opts]\n    assert re.match(r\"Usage: .*datalad.* \\[global-opts\\] command \\[command-opts\\]\", stdout)\n    assert stdout.count(' -c ') == 1\n\n    assert_all_commands_present(stdout)\n\n    if not get_terminal_size()[0] or 0:\n        raise SkipTest(\n            \"Could not determine terminal size, skipping the rest of the test\")\n\n    # none of the lines must be longer than 80 chars\n    # TODO: decide on   create-sibling and possibly\n    # rewrite-urls\n    accepted_width = get_console_width()\n\n    long_lines = [\"%d %s\" % (len(l), l) for l in stdout.split('\\n')\n                  if len(l) > accepted_width and\n                  '{' not in l  # on nd70 summary line is unsplit\n                  ]\n    if long_lines:\n        raise AssertionError(\n            \"Following lines in --help output were longer than %s chars:\\n%s\"\n            % (accepted_width, '\\n'.join(long_lines))\n        )\n\n\ndef test_dashh():\n    stdout, stderr = run_main(['-h'])\n    # Note: for -h we do not do ad-hoc tune up of Usage: to guarantee having\n    # datalad instead of python -m nose etc, so we can only verify that we have\n    # options listed\n    assert_re_in(r'^Usage: .*\\[', stdout.splitlines()[0])\n    assert_all_commands_present(stdout)\n    assert_re_in('Use .--help. to get more comprehensive information', stdout.splitlines())\n\n\ndef test_dashh_clone():\n    # test -h on a sample command\n    stdout, stderr = run_main(['clone', '-h'])\n    assert_re_in(r'^Usage: .* clone \\[', stdout.splitlines()[0])\n    assert_re_in('Use .--help. to get more comprehensive information', stdout.splitlines())\n\n\ndef test_usage_on_insufficient_args():\n    stdout, stderr = run_main(['install'], exit_code=2, expect_stderr=True)\n    ok_startswith(stderr, 'usage:')\n\n\ndef test_subcmd_usage_on_unknown_args():\n    stdout, stderr = run_main(['get', '--murks'], exit_code=1, expect_stderr=True)\n    in_('get', stdout)\n\n\ndef test_combined_short_option():\n    stdout, stderr = run_main(['-fjson'], exit_code=2, expect_stderr=True)\n    assert_not_in(\"unrecognized argument\", stderr)\n    assert_in(\"too few arguments\", stderr)\n\n\n# https://github.com/datalad/datalad/issues/7504\n@with_tempfile(mkdir=True)\ndef test_run_exit_code(tempdir=None):\n    # datalad run is just an internal command which can readily be used to trigger\n    # desired situation\n    create(dataset=tempdir, annex=False)\n    with chpwd(tempdir):  # can't just use -C tempdir since we do \"in process\" run_main\n        stdout, stderr = run_main(['run', '--explicit', 'exit 3'], exit_code=3) #, expect_stderr=True)\n    print(stdout)\n    print(stderr)\n\n\n# https://github.com/datalad/datalad/issues/6814\n@with_tempfile(mkdir=True)\ndef test_conflicting_short_option(tempdir=None):\n    # datalad -f|--format   requires a value. regression made parser ignore command\n    # and its options\n    with chpwd(tempdir):  # can't just use -C tempdir since we do \"in process\" run_main\n        run_main(['create', '-f'])\n\n\n# apparently a bit different if following a good one so let's do both\nerr_invalid = \"error: (invalid|too few arguments|unrecognized argument)\"\nerr_insufficient = err_invalid  # \"specify\"\n\n\n@pytest.mark.parametrize(\n    \"opts,err_str\",\n    [\n        (('--buga',), err_invalid),\n        (('--dbg', '--buga'), err_invalid),\n        (('--dbg',), err_insufficient),\n        (tuple(), err_insufficient),\n    ]\n)\ndef test_incorrect_option(opts, err_str):\n    # The first line used to be:\n    # stdout, stderr = run_main((sys.argv[0],) + opts, expect_stderr=True, exit_code=2)\n    # But: what do we expect to be in sys.argv[0] here?\n    # It depends on how we invoke the test.\n    # - nosetests -s -v datalad/cmdline/tests/test_main.py would result in:\n    #   sys.argv[0}=='nosetests'\n    # - python -m nose -s -v datalad/cmdline/tests/test_main.py would result in:\n    #   sys.argv[0}=='python -m nose'\n    # - python -c \"import nose; nose.main()\" -s -v datalad/cmdline/tests/test_main.py would result in:\n    #   sys.argv[0]=='-c'\n    # This led to failure in case sys.argv[0] contained an option, that was\n    # defined to be a datalad option too, therefore was a 'known_arg' and was\n    # checked to meet its constraints.\n    # But sys.argv[0] actually isn't used by main at all. It simply doesn't\n    # matter what's in there. The only thing important to pass here is `opts`.\n    stdout, stderr = run_main(opts, expect_stderr=True, exit_code=2)\n    out = stdout + stderr\n    assert_in(\"usage: \", out)\n    assert_re_in(err_str, out, match=False)\n\n\n@pytest.mark.parametrize(\n    \"script\",\n    [\n        'datalad',\n        'git-annex-remote-datalad-archives',\n        'git-annex-remote-datalad',\n    ]\n)\ndef test_script_shims(script):\n    runner = Runner()\n    if not on_windows:\n\n        from shutil import which\n        which(script)\n\n    # and let's check that it is our script\n    out = runner.run([script, '--version'], protocol=StdOutErrCapture)\n    version = out['stdout'].rstrip()\n    mod, version = version.split(' ', 1)\n    assert_equal(mod, 'datalad')\n    # we can get git and non git .dev version... so for now\n    # relax\n    get_numeric_portion = lambda v: [x for x in re.split('[+.]', v) if x.isdigit()]\n    # extract numeric portion\n    assert get_numeric_portion(version), f\"Got no numeric portion from {version}\"\n    assert_equal(get_numeric_portion(__version__),\n                 get_numeric_portion(version))\n\n\n@with_tempfile(mkdir=True)\ndef test_cfg_override(path=None):\n    with chpwd(path):\n        # use 'wtf' to dump the config\n        # should be rewritten to use `configuration`\n        cmd = ['datalad', 'wtf', '-S', 'configuration', '-s', 'some']\n        # control\n        out = Runner().run(cmd, protocol=StdOutErrCapture)['stdout']\n        assert_not_in('datalad.dummy: this', out)\n        # ensure that this is not a dataset's cfg manager\n        assert_not_in('datalad.dataset.id', out)\n        # env var\n        out = Runner(env=dict(os.environ, DATALAD_DUMMY='this')).run(\n            cmd, protocol=StdOutErrCapture)['stdout']\n        assert_in('datalad.dummy: this', out)\n        # cmdline arg\n        out = Runner().run([cmd[0], '-c', 'datalad.dummy=this'] + cmd[1:],\n                           protocol=StdOutErrCapture)['stdout']\n        assert_in('datalad.dummy: this', out)\n\n        # now create a dataset in the path. the wtf plugin will switch to\n        # using the dataset's config manager, which must inherit the overrides\n        create(dataset=path, annex=False)\n        # control\n        out = Runner().run(cmd, protocol=StdOutErrCapture)['stdout']\n        assert_not_in('datalad.dummy: this', out)\n        # ensure that this is a dataset's cfg manager\n        assert_in('datalad.dataset.id', out)\n        # env var\n        out = Runner(env=dict(os.environ, DATALAD_DUMMY='this')).run(\n            cmd, protocol=StdOutErrCapture)['stdout']\n        assert_in('datalad.dummy: this', out)\n        # cmdline arg\n        out = Runner().run([cmd[0], '-c', 'datalad.dummy=this'] + cmd[1:],\n                           protocol=StdOutErrCapture)['stdout']\n        assert_in('datalad.dummy: this', out)\n\n        # set a config\n        run_main([\n            'configuration', '--scope', 'local', 'set', 'mike.item=some'])\n        # verify it is successfully set\n        assert 'some' == run_main([\n            'configuration', 'get', 'mike.item'])[0].strip()\n        # verify that an override can unset the config\n        # we cannot use run_main(), because the \"singleton\" instance of the\n        # dataset we are in is still around in this session, and with it\n        # also its config managers that we will not be able to post-hoc\n        # overwrite with this method. Instead, we'll execute in a subprocess.\n        assert '' == Runner().run([\n            'datalad', '-c', ':mike.item',\n            'configuration', 'get', 'mike.item'],\n            protocol=StdOutErrCapture)['stdout'].strip()\n        # verify the effect is not permanent\n        assert 'some' == Runner().run([\n            'datalad',\n            'configuration', 'get', 'mike.item'],\n            protocol=StdOutErrCapture)['stdout'].strip()\n\n\ndef test_incorrect_cfg_override():\n    run_main(['-c', 'some', 'wtf'], exit_code=3)\n    run_main(['-c', 'some=', 'wtf'], exit_code=3)\n    run_main(['-c', 'some.var', 'wtf'], exit_code=3)\n    run_main(['-c', 'some.var=', 'wtf'], exit_code=3)\n\n\n@with_tempfile\ndef test_librarymode(path=None):\n    Dataset(path).create()\n    was_mode = datalad.__runtime_mode\n    try:\n        # clean --dry-run is just a no-op command that is cheap\n        # to execute. It has no particular role here, other than\n        # to make the code pass the location where library mode\n        # should be turned on via the cmdline API\n        run_main(['-c', 'datalad.runtime.librarymode=yes', 'clean',\n                  '-d', path, '--dry-run'])\n        ok_(datalad.in_librarymode())\n    finally:\n        # restore pre-test behavior\n        datalad.__runtime_mode = was_mode\n        datalad.cfg.overrides.pop('datalad.runtime.librarymode')\n\n\n@with_tempfile\ndef test_completion(out_fn=None):\n    skip_if_no_module('argcomplete')\n\n    from datalad.cmd import WitlessRunner\n    runner = WitlessRunner()\n\n    def get_completions(s: str, expected) -> list:\n        \"\"\"Run 'datalad' external command and collect completions\n\n        Parameters\n        ----------\n        s: str\n          what to append to 'datalad ' invocation\n        expected: iterable of str\n          What entries to expect - would raise AssertionError if any is\n          not present in output\n        exit_code: int, optional\n          If incomplete/malformed we seems to get 2, most frequently used\n          so default\n\n        Returns\n        -------\n        list of str\n          Entries output\n        \"\"\"\n        if os.path.exists(out_fn):  # reuse but ensure it is gone\n            os.unlink(out_fn)\n        comp_line = f'datalad {s}'\n        runner.run(\n            comp_line.split(' '),\n            env=dict(os.environ,\n                     _ARGCOMPLETE='1',\n                     _ARGCOMPLETE_STDOUT_FILENAME=out_fn,\n                     COMP_LINE=comp_line,\n                     # without -1 seems to get \"finished completion\", someone can investigate more\n                     COMP_POINT=str(len(comp_line)-1),  # always at the end ATM\n                     ))\n        with open(out_fn, 'rb') as f:\n            entries = f.read().split(b'\\x0b')\n            entries = [e.decode() for e in entries]\n        diff = set(expected).difference(entries)\n        if diff:\n            raise AssertionError(\n                f\"Entries {sorted(diff)} were expected but not found in the completion output: {entries}\"\n            )\n        return entries  # for extra analyzes if so desired\n\n    all_commands = get_all_commands()\n    get_completions('i', {'install'})\n    get_completions(' ', ['--dbg', '-c'] + all_commands)\n    # if command already matches -- we get only that hit ATM, not others which begin with it\n    get_completions('create', ['create '])\n    get_completions('create -', ['--dataset'])\n    # but for incomplete one we do get all create* commands\n    get_completions('creat', [c for c in all_commands if c.startswith('create')])\n",
        "import pytest\nimport tempfile\nimport os\n\n\n@pytest.fixture\ndef path():\n    with tempfile.TemporaryDirectory() as tmpdir:\n        yield tmpdir\n\n\n@pytest.fixture(autouse=True)\ndef setup_git_config():\n    with tempfile.TemporaryDirectory() as home:\n        old_home = os.environ.get(\"HOME\")\n        os.environ[\"HOME\"] = home\n        os.system('git config --global user.name \"DataLad Tester\"')\n        os.system('git config --global user.email \"test@example.com\"')\n        yield\n        if old_home:\n            os.environ[\"HOME\"] = old_home\n"
    ],
    "test_file_names": [
        "test_1.py",
        "conftest.py"
    ],
    "setup_res_code": 0,
    "setup_res_stdout": "Starting Datalad installation attempts...\nVerifying installation...\nDatalad version: 1.1.3+10.g19f5b4509\n✅ Installation successful!\nSuccessfully installed Datalad using Python 3.9\n",
    "setup_res_stderr": "Using CPython 3.9.21\nCreating virtual environment at: .venv\nActivate with: source .venv/bin/activate\nResolved 25 packages in 68ms\nInstalled 25 packages in 3.55s\n + coverage==7.6.10\n + docopt==0.6.2\n + exceptiongroup==1.2.2\n + grabbit==0.2.6\n + importlib-resources==6.4.5\n + iniconfig==2.0.0\n + nibabel==5.3.2\n + num2words==0.5.14\n + numpy==2.0.2\n + packaging==24.2\n + pandas==2.2.3\n + patsy==1.0.1\n + pluggy==1.5.0\n + pybids==0.6.5\n + pytest==8.3.4\n + pytest-cov==6.0.0\n + python-dateutil==2.9.0.post0\n + pytz==2024.2\n + scipy==1.13.1\n + setuptools==75.6.0\n + six==1.17.0\n + tomli==2.2.1\n + typing-extensions==4.12.2\n + tzdata==2024.2\n + zipp==3.21.0\nResolved 69 packages in 5.04s\nPrepared 1 package in 4.80s\nInstalled 57 packages in 1.22s\n + annexremote==1.6.6\n + argcomplete==3.5.3\n + backports-tarfile==1.2.0\n + beautifulsoup4==4.12.3\n + boto3==1.35.90\n + botocore==1.35.90\n + certifi==2024.12.14\n + cffi==1.17.1\n + chardet==5.2.0\n + charset-normalizer==3.4.1\n + citeproc-py==0.6.0\n + cryptography==44.0.0\n + datalad==1.1.3+10.g19f5b4509 (from file:///home/gcpuser/buckets/local_repoeval_bucket/repos/datalad_19f5b45096cadaa4677c9b15f18a6d303497eaeb)\n + distro==1.9.0\n + duecredit==0.10.2\n + fasteners==0.19\n + httpretty==1.1.4\n + humanize==4.11.0\n + idna==3.10\n + importlib-metadata==8.5.0\n + iso8601==2.1.0\n + jaraco-classes==3.4.0\n + jaraco-context==6.0.1\n + jaraco-functools==4.1.0\n + jeepney==0.8.0\n + jmespath==1.0.1\n + keyring==25.6.0\n + keyrings-alt==5.0.2\n + looseversion==1.3.0\n + lxml==5.3.0\n + more-itertools==10.5.0\n + msgpack==1.1.0\n + multidict==6.1.0\n + mypy==1.14.1\n + mypy-extensions==1.0.0\n + patool==1.15.0\n + platformdirs==4.3.6\n + propcache==0.2.1\n + pycparser==2.22\n + pyperclip==1.9.0\n + pytest-fail-slow==0.6.0\n + python-gitlab==5.3.0\n + pyyaml==6.0.2\n + requests==2.32.3\n + requests-ftp==0.3.1\n + requests-toolbelt==1.0.0\n + s3transfer==0.10.4\n + secretstorage==3.3.3\n + soupsieve==2.6\n + tqdm==4.67.1\n + types-python-dateutil==2.9.0.20241206\n + types-requests==2.31.0.6\n + types-urllib3==1.26.25.14\n + urllib3==1.26.20\n + vcrpy==7.0.0\n + wrapt==1.17.0\n + yarl==1.18.3\nResolved 108 packages in 169ms\nInstalled 39 packages in 244ms\n + alabaster==0.7.16\n + asv==0.6.4\n + asv-runner==0.2.1\n + attrs==24.3.0\n + babel==2.16.0\n + build==1.2.2.post1\n + click==8.1.8\n + click-log==0.4.0\n + distlib==0.3.9\n + docutils==0.21.2\n + execnet==2.1.1\n + filelock==3.16.1\n + gprof2dot==2024.6.6\n + imagesize==1.4.1\n + jinja2==3.1.5\n + json5==0.10.0\n + markdown-it-py==3.0.0\n + markupsafe==3.0.2\n + mdurl==0.1.2\n + psutil==6.1.1\n + pygments==2.18.0\n + pympler==1.1\n + pypandoc==1.14\n + pyproject-hooks==1.2.0\n + pytest-xdist==3.6.1\n + scriv==1.5.1\n + snowballstemmer==2.2.0\n + sphinx==7.4.7\n + sphinx-autodoc-typehints==2.3.0\n + sphinx-rtd-theme==3.0.2\n + sphinxcontrib-applehelp==2.0.0\n + sphinxcontrib-devhelp==2.0.0\n + sphinxcontrib-htmlhelp==2.1.0\n + sphinxcontrib-jquery==4.1\n + sphinxcontrib-jsmath==1.0.1\n + sphinxcontrib-qthelp==2.0.0\n + sphinxcontrib-serializinghtml==2.0.0\n + tabulate==0.9.0\n + virtualenv==20.28.0\n",
    "new_commit_res_code": 1,
    "new_commit_res_stdout": "============================= test session starts ==============================\nplatform linux -- Python 3.9.21, pytest-8.3.4, pluggy-1.5.0\nrootdir: /home/gcpuser/buckets/local_repoeval_bucket/repos/datalad_19f5b45096cadaa4677c9b15f18a6d303497eaeb\nconfigfile: tox.ini\nplugins: fail-slow-0.6.0, xdist-3.6.1, cov-6.0.0\ncollected 20 items\n\nr2e_tests/test_1.py .s...........FFFF..F                                 [100%]\n\n=================================== FAILURES ===================================\n__________________________ test_script_shims[datalad] __________________________\n\nscript = 'datalad'\n\n    @pytest.mark.parametrize(\n        \"script\",\n        [\n            'datalad',\n            'git-annex-remote-datalad-archives',\n            'git-annex-remote-datalad',\n        ]\n    )\n    def test_script_shims(script):\n        runner = Runner()\n        if not on_windows:\n    \n            from shutil import which\n            which(script)\n    \n        # and let's check that it is our script\n>       out = runner.run([script, '--version'], protocol=StdOutErrCapture)\n\nr2e_tests/test_1.py:287: \n_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ \ndatalad/runner/runner.py:225: in run\n    results_or_iterator = threaded_runner.run()\ndatalad/runner/nonasyncrunner.py:347: in run\n    return self._locked_run()\ndatalad/runner/nonasyncrunner.py:407: in _locked_run\n    self.process = Popen(self.cmd, **kwargs)         # nosec\n../../../../.local/share/uv/python/cpython-3.9.21-linux-x86_64-gnu/lib/python3.9/subprocess.py:951: in __init__\n    self._execute_child(args, executable, preexec_fn, close_fds,\n_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ \n\nself = <Popen: returncode: 255 args: ['datalad', '--version']>\nargs = ['datalad', '--version'], executable = b'datalad', preexec_fn = None\nclose_fds = True, pass_fds = (), cwd = None, env = None, startupinfo = None\ncreationflags = 0, shell = False, p2cread = -1, p2cwrite = -1, c2pread = 12\nc2pwrite = 13, errread = 14, errwrite = 15, restore_signals = True, gid = None\ngids = None, uid = None, umask = -1, start_new_session = False\n\n    def _execute_child(self, args, executable, preexec_fn, close_fds,\n                       pass_fds, cwd, env,\n                       startupinfo, creationflags, shell,\n                       p2cread, p2cwrite,\n                       c2pread, c2pwrite,\n                       errread, errwrite,\n                       restore_signals,\n                       gid, gids, uid, umask,\n                       start_new_session):\n        \"\"\"Execute program (POSIX version)\"\"\"\n    \n        if isinstance(args, (str, bytes)):\n            args = [args]\n        elif isinstance(args, os.PathLike):\n            if shell:\n                raise TypeError('path-like args is not allowed when '\n                                'shell is true')\n            args = [args]\n        else:\n            args = list(args)\n    \n        if shell:\n            # On Android the default shell is at '/system/bin/sh'.\n            unix_shell = ('/system/bin/sh' if\n                      hasattr(sys, 'getandroidapilevel') else '/bin/sh')\n            args = [unix_shell, \"-c\"] + args\n            if executable:\n                args[0] = executable\n    \n        if executable is None:\n            executable = args[0]\n    \n        sys.audit(\"subprocess.Popen\", executable, args, cwd, env)\n    \n        if (_USE_POSIX_SPAWN\n                and os.path.dirname(executable)\n                and preexec_fn is None\n                and not close_fds\n                and not pass_fds\n                and cwd is None\n                and (p2cread == -1 or p2cread > 2)\n                and (c2pwrite == -1 or c2pwrite > 2)\n                and (errwrite == -1 or errwrite > 2)\n                and not start_new_session\n                and gid is None\n                and gids is None\n                and uid is None\n                and umask < 0):\n            self._posix_spawn(args, executable, env, restore_signals,\n                              p2cread, p2cwrite,\n                              c2pread, c2pwrite,\n                              errread, errwrite)\n            return\n    \n        orig_executable = executable\n    \n        # For transferring possible exec failure from child to parent.\n        # Data format: \"exception name:hex errno:description\"\n        # Pickle is not used; it is complex and involves memory allocation.\n        errpipe_read, errpipe_write = os.pipe()\n        # errpipe_write must not be in the standard io 0, 1, or 2 fd range.\n        low_fds_to_close = []\n        while errpipe_write < 3:\n            low_fds_to_close.append(errpipe_write)\n            errpipe_write = os.dup(errpipe_write)\n        for low_fd in low_fds_to_close:\n            os.close(low_fd)\n        try:\n            try:\n                # We must avoid complex work that could involve\n                # malloc or free in the child process to avoid\n                # potential deadlocks, thus we do all this here.\n                # and pass it to fork_exec()\n    \n                if env is not None:\n                    env_list = []\n                    for k, v in env.items():\n                        k = os.fsencode(k)\n                        if b'=' in k:\n                            raise ValueError(\"illegal environment variable name\")\n                        env_list.append(k + b'=' + os.fsencode(v))\n                else:\n                    env_list = None  # Use execv instead of execve.\n                executable = os.fsencode(executable)\n                if os.path.dirname(executable):\n                    executable_list = (executable,)\n                else:\n                    # This matches the behavior of os._execvpe().\n                    executable_list = tuple(\n                        os.path.join(os.fsencode(dir), executable)\n                        for dir in os.get_exec_path(env))\n                fds_to_keep = set(pass_fds)\n                fds_to_keep.add(errpipe_write)\n                self.pid = _posixsubprocess.fork_exec(\n                        args, executable_list,\n                        close_fds, tuple(sorted(map(int, fds_to_keep))),\n                        cwd, env_list,\n                        p2cread, p2cwrite, c2pread, c2pwrite,\n                        errread, errwrite,\n                        errpipe_read, errpipe_write,\n                        restore_signals, start_new_session,\n                        gid, gids, uid, umask,\n                        preexec_fn)\n                self._child_created = True\n            finally:\n                # be sure the FD is closed no matter what\n                os.close(errpipe_write)\n    \n            self._close_pipe_fds(p2cread, p2cwrite,\n                                 c2pread, c2pwrite,\n                                 errread, errwrite)\n    \n            # Wait for exec to fail or succeed; possibly raising an\n            # exception (limited in size)\n            errpipe_data = bytearray()\n            while True:\n                part = os.read(errpipe_read, 50000)\n                errpipe_data += part\n                if not part or len(errpipe_data) > 50000:\n                    break\n        finally:\n            # be sure the FD is closed no matter what\n            os.close(errpipe_read)\n    \n        if errpipe_data:\n            try:\n                pid, sts = os.waitpid(self.pid, 0)\n                if pid == self.pid:\n                    self._handle_exitstatus(sts)\n                else:\n                    self.returncode = sys.maxsize\n            except ChildProcessError:\n                pass\n    \n            try:\n                exception_name, hex_errno, err_msg = (\n                        errpipe_data.split(b':', 2))\n                # The encoding here should match the encoding\n                # written in by the subprocess implementations\n                # like _posixsubprocess\n                err_msg = err_msg.decode()\n            except ValueError:\n                exception_name = b'SubprocessError'\n                hex_errno = b'0'\n                err_msg = 'Bad exception data from child: {!r}'.format(\n                              bytes(errpipe_data))\n            child_exception_type = getattr(\n                    builtins, exception_name.decode('ascii'),\n                    SubprocessError)\n            if issubclass(child_exception_type, OSError) and hex_errno:\n                errno_num = int(hex_errno, 16)\n                child_exec_never_called = (err_msg == \"noexec\")\n                if child_exec_never_called:\n                    err_msg = \"\"\n                    # The error must be from chdir(cwd).\n                    err_filename = cwd\n                else:\n                    err_filename = orig_executable\n                if errno_num != 0:\n                    err_msg = os.strerror(errno_num)\n>               raise child_exception_type(errno_num, err_msg, err_filename)\nE               FileNotFoundError: [Errno 2] No such file or directory: 'datalad'\n\n../../../../.local/share/uv/python/cpython-3.9.21-linux-x86_64-gnu/lib/python3.9/subprocess.py:1837: FileNotFoundError\n_____________ test_script_shims[git-annex-remote-datalad-archives] _____________\n\nscript = 'git-annex-remote-datalad-archives'\n\n    @pytest.mark.parametrize(\n        \"script\",\n        [\n            'datalad',\n            'git-annex-remote-datalad-archives',\n            'git-annex-remote-datalad',\n        ]\n    )\n    def test_script_shims(script):\n        runner = Runner()\n        if not on_windows:\n    \n            from shutil import which\n            which(script)\n    \n        # and let's check that it is our script\n>       out = runner.run([script, '--version'], protocol=StdOutErrCapture)\n\nr2e_tests/test_1.py:287: \n_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ \ndatalad/runner/runner.py:225: in run\n    results_or_iterator = threaded_runner.run()\ndatalad/runner/nonasyncrunner.py:347: in run\n    return self._locked_run()\ndatalad/runner/nonasyncrunner.py:407: in _locked_run\n    self.process = Popen(self.cmd, **kwargs)         # nosec\n../../../../.local/share/uv/python/cpython-3.9.21-linux-x86_64-gnu/lib/python3.9/subprocess.py:951: in __init__\n    self._execute_child(args, executable, preexec_fn, close_fds,\n_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ \n\nself = <Popen: returncode: 255 args: ['git-annex-remote-datalad-archives', '--versi...>\nargs = ['git-annex-remote-datalad-archives', '--version']\nexecutable = b'git-annex-remote-datalad-archives', preexec_fn = None\nclose_fds = True, pass_fds = (), cwd = None, env = None, startupinfo = None\ncreationflags = 0, shell = False, p2cread = -1, p2cwrite = -1, c2pread = 12\nc2pwrite = 13, errread = 14, errwrite = 15, restore_signals = True, gid = None\ngids = None, uid = None, umask = -1, start_new_session = False\n\n    def _execute_child(self, args, executable, preexec_fn, close_fds,\n                       pass_fds, cwd, env,\n                       startupinfo, creationflags, shell,\n                       p2cread, p2cwrite,\n                       c2pread, c2pwrite,\n                       errread, errwrite,\n                       restore_signals,\n                       gid, gids, uid, umask,\n                       start_new_session):\n        \"\"\"Execute program (POSIX version)\"\"\"\n    \n        if isinstance(args, (str, bytes)):\n            args = [args]\n        elif isinstance(args, os.PathLike):\n            if shell:\n                raise TypeError('path-like args is not allowed when '\n                                'shell is true')\n            args = [args]\n        else:\n            args = list(args)\n    \n        if shell:\n            # On Android the default shell is at '/system/bin/sh'.\n            unix_shell = ('/system/bin/sh' if\n                      hasattr(sys, 'getandroidapilevel') else '/bin/sh')\n            args = [unix_shell, \"-c\"] + args\n            if executable:\n                args[0] = executable\n    \n        if executable is None:\n            executable = args[0]\n    \n        sys.audit(\"subprocess.Popen\", executable, args, cwd, env)\n    \n        if (_USE_POSIX_SPAWN\n                and os.path.dirname(executable)\n                and preexec_fn is None\n                and not close_fds\n                and not pass_fds\n                and cwd is None\n                and (p2cread == -1 or p2cread > 2)\n                and (c2pwrite == -1 or c2pwrite > 2)\n                and (errwrite == -1 or errwrite > 2)\n                and not start_new_session\n                and gid is None\n                and gids is None\n                and uid is None\n                and umask < 0):\n            self._posix_spawn(args, executable, env, restore_signals,\n                              p2cread, p2cwrite,\n                              c2pread, c2pwrite,\n                              errread, errwrite)\n            return\n    \n        orig_executable = executable\n    \n        # For transferring possible exec failure from child to parent.\n        # Data format: \"exception name:hex errno:description\"\n        # Pickle is not used; it is complex and involves memory allocation.\n        errpipe_read, errpipe_write = os.pipe()\n        # errpipe_write must not be in the standard io 0, 1, or 2 fd range.\n        low_fds_to_close = []\n        while errpipe_write < 3:\n            low_fds_to_close.append(errpipe_write)\n            errpipe_write = os.dup(errpipe_write)\n        for low_fd in low_fds_to_close:\n            os.close(low_fd)\n        try:\n            try:\n                # We must avoid complex work that could involve\n                # malloc or free in the child process to avoid\n                # potential deadlocks, thus we do all this here.\n                # and pass it to fork_exec()\n    \n                if env is not None:\n                    env_list = []\n                    for k, v in env.items():\n                        k = os.fsencode(k)\n                        if b'=' in k:\n                            raise ValueError(\"illegal environment variable name\")\n                        env_list.append(k + b'=' + os.fsencode(v))\n                else:\n                    env_list = None  # Use execv instead of execve.\n                executable = os.fsencode(executable)\n                if os.path.dirname(executable):\n                    executable_list = (executable,)\n                else:\n                    # This matches the behavior of os._execvpe().\n                    executable_list = tuple(\n                        os.path.join(os.fsencode(dir), executable)\n                        for dir in os.get_exec_path(env))\n                fds_to_keep = set(pass_fds)\n                fds_to_keep.add(errpipe_write)\n                self.pid = _posixsubprocess.fork_exec(\n                        args, executable_list,\n                        close_fds, tuple(sorted(map(int, fds_to_keep))),\n                        cwd, env_list,\n                        p2cread, p2cwrite, c2pread, c2pwrite,\n                        errread, errwrite,\n                        errpipe_read, errpipe_write,\n                        restore_signals, start_new_session,\n                        gid, gids, uid, umask,\n                        preexec_fn)\n                self._child_created = True\n            finally:\n                # be sure the FD is closed no matter what\n                os.close(errpipe_write)\n    \n            self._close_pipe_fds(p2cread, p2cwrite,\n                                 c2pread, c2pwrite,\n                                 errread, errwrite)\n    \n            # Wait for exec to fail or succeed; possibly raising an\n            # exception (limited in size)\n            errpipe_data = bytearray()\n            while True:\n                part = os.read(errpipe_read, 50000)\n                errpipe_data += part\n                if not part or len(errpipe_data) > 50000:\n                    break\n        finally:\n            # be sure the FD is closed no matter what\n            os.close(errpipe_read)\n    \n        if errpipe_data:\n            try:\n                pid, sts = os.waitpid(self.pid, 0)\n                if pid == self.pid:\n                    self._handle_exitstatus(sts)\n                else:\n                    self.returncode = sys.maxsize\n            except ChildProcessError:\n                pass\n    \n            try:\n                exception_name, hex_errno, err_msg = (\n                        errpipe_data.split(b':', 2))\n                # The encoding here should match the encoding\n                # written in by the subprocess implementations\n                # like _posixsubprocess\n                err_msg = err_msg.decode()\n            except ValueError:\n                exception_name = b'SubprocessError'\n                hex_errno = b'0'\n                err_msg = 'Bad exception data from child: {!r}'.format(\n                              bytes(errpipe_data))\n            child_exception_type = getattr(\n                    builtins, exception_name.decode('ascii'),\n                    SubprocessError)\n            if issubclass(child_exception_type, OSError) and hex_errno:\n                errno_num = int(hex_errno, 16)\n                child_exec_never_called = (err_msg == \"noexec\")\n                if child_exec_never_called:\n                    err_msg = \"\"\n                    # The error must be from chdir(cwd).\n                    err_filename = cwd\n                else:\n                    err_filename = orig_executable\n                if errno_num != 0:\n                    err_msg = os.strerror(errno_num)\n>               raise child_exception_type(errno_num, err_msg, err_filename)\nE               FileNotFoundError: [Errno 2] No such file or directory: 'git-annex-remote-datalad-archives'\n\n../../../../.local/share/uv/python/cpython-3.9.21-linux-x86_64-gnu/lib/python3.9/subprocess.py:1837: FileNotFoundError\n_________________ test_script_shims[git-annex-remote-datalad] __________________\n\nscript = 'git-annex-remote-datalad'\n\n    @pytest.mark.parametrize(\n        \"script\",\n        [\n            'datalad',\n            'git-annex-remote-datalad-archives',\n            'git-annex-remote-datalad',\n        ]\n    )\n    def test_script_shims(script):\n        runner = Runner()\n        if not on_windows:\n    \n            from shutil import which\n            which(script)\n    \n        # and let's check that it is our script\n>       out = runner.run([script, '--version'], protocol=StdOutErrCapture)\n\nr2e_tests/test_1.py:287: \n_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ \ndatalad/runner/runner.py:225: in run\n    results_or_iterator = threaded_runner.run()\ndatalad/runner/nonasyncrunner.py:347: in run\n    return self._locked_run()\ndatalad/runner/nonasyncrunner.py:407: in _locked_run\n    self.process = Popen(self.cmd, **kwargs)         # nosec\n../../../../.local/share/uv/python/cpython-3.9.21-linux-x86_64-gnu/lib/python3.9/subprocess.py:951: in __init__\n    self._execute_child(args, executable, preexec_fn, close_fds,\n_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ \n\nself = <Popen: returncode: 255 args: ['git-annex-remote-datalad', '--version']>\nargs = ['git-annex-remote-datalad', '--version']\nexecutable = b'git-annex-remote-datalad', preexec_fn = None, close_fds = True\npass_fds = (), cwd = None, env = None, startupinfo = None, creationflags = 0\nshell = False, p2cread = -1, p2cwrite = -1, c2pread = 12, c2pwrite = 13\nerrread = 14, errwrite = 15, restore_signals = True, gid = None, gids = None\nuid = None, umask = -1, start_new_session = False\n\n    def _execute_child(self, args, executable, preexec_fn, close_fds,\n                       pass_fds, cwd, env,\n                       startupinfo, creationflags, shell,\n                       p2cread, p2cwrite,\n                       c2pread, c2pwrite,\n                       errread, errwrite,\n                       restore_signals,\n                       gid, gids, uid, umask,\n                       start_new_session):\n        \"\"\"Execute program (POSIX version)\"\"\"\n    \n        if isinstance(args, (str, bytes)):\n            args = [args]\n        elif isinstance(args, os.PathLike):\n            if shell:\n                raise TypeError('path-like args is not allowed when '\n                                'shell is true')\n            args = [args]\n        else:\n            args = list(args)\n    \n        if shell:\n            # On Android the default shell is at '/system/bin/sh'.\n            unix_shell = ('/system/bin/sh' if\n                      hasattr(sys, 'getandroidapilevel') else '/bin/sh')\n            args = [unix_shell, \"-c\"] + args\n            if executable:\n                args[0] = executable\n    \n        if executable is None:\n            executable = args[0]\n    \n        sys.audit(\"subprocess.Popen\", executable, args, cwd, env)\n    \n        if (_USE_POSIX_SPAWN\n                and os.path.dirname(executable)\n                and preexec_fn is None\n                and not close_fds\n                and not pass_fds\n                and cwd is None\n                and (p2cread == -1 or p2cread > 2)\n                and (c2pwrite == -1 or c2pwrite > 2)\n                and (errwrite == -1 or errwrite > 2)\n                and not start_new_session\n                and gid is None\n                and gids is None\n                and uid is None\n                and umask < 0):\n            self._posix_spawn(args, executable, env, restore_signals,\n                              p2cread, p2cwrite,\n                              c2pread, c2pwrite,\n                              errread, errwrite)\n            return\n    \n        orig_executable = executable\n    \n        # For transferring possible exec failure from child to parent.\n        # Data format: \"exception name:hex errno:description\"\n        # Pickle is not used; it is complex and involves memory allocation.\n        errpipe_read, errpipe_write = os.pipe()\n        # errpipe_write must not be in the standard io 0, 1, or 2 fd range.\n        low_fds_to_close = []\n        while errpipe_write < 3:\n            low_fds_to_close.append(errpipe_write)\n            errpipe_write = os.dup(errpipe_write)\n        for low_fd in low_fds_to_close:\n            os.close(low_fd)\n        try:\n            try:\n                # We must avoid complex work that could involve\n                # malloc or free in the child process to avoid\n                # potential deadlocks, thus we do all this here.\n                # and pass it to fork_exec()\n    \n                if env is not None:\n                    env_list = []\n                    for k, v in env.items():\n                        k = os.fsencode(k)\n                        if b'=' in k:\n                            raise ValueError(\"illegal environment variable name\")\n                        env_list.append(k + b'=' + os.fsencode(v))\n                else:\n                    env_list = None  # Use execv instead of execve.\n                executable = os.fsencode(executable)\n                if os.path.dirname(executable):\n                    executable_list = (executable,)\n                else:\n                    # This matches the behavior of os._execvpe().\n                    executable_list = tuple(\n                        os.path.join(os.fsencode(dir), executable)\n                        for dir in os.get_exec_path(env))\n                fds_to_keep = set(pass_fds)\n                fds_to_keep.add(errpipe_write)\n                self.pid = _posixsubprocess.fork_exec(\n                        args, executable_list,\n                        close_fds, tuple(sorted(map(int, fds_to_keep))),\n                        cwd, env_list,\n                        p2cread, p2cwrite, c2pread, c2pwrite,\n                        errread, errwrite,\n                        errpipe_read, errpipe_write,\n                        restore_signals, start_new_session,\n                        gid, gids, uid, umask,\n                        preexec_fn)\n                self._child_created = True\n            finally:\n                # be sure the FD is closed no matter what\n                os.close(errpipe_write)\n    \n            self._close_pipe_fds(p2cread, p2cwrite,\n                                 c2pread, c2pwrite,\n                                 errread, errwrite)\n    \n            # Wait for exec to fail or succeed; possibly raising an\n            # exception (limited in size)\n            errpipe_data = bytearray()\n            while True:\n                part = os.read(errpipe_read, 50000)\n                errpipe_data += part\n                if not part or len(errpipe_data) > 50000:\n                    break\n        finally:\n            # be sure the FD is closed no matter what\n            os.close(errpipe_read)\n    \n        if errpipe_data:\n            try:\n                pid, sts = os.waitpid(self.pid, 0)\n                if pid == self.pid:\n                    self._handle_exitstatus(sts)\n                else:\n                    self.returncode = sys.maxsize\n            except ChildProcessError:\n                pass\n    \n            try:\n                exception_name, hex_errno, err_msg = (\n                        errpipe_data.split(b':', 2))\n                # The encoding here should match the encoding\n                # written in by the subprocess implementations\n                # like _posixsubprocess\n                err_msg = err_msg.decode()\n            except ValueError:\n                exception_name = b'SubprocessError'\n                hex_errno = b'0'\n                err_msg = 'Bad exception data from child: {!r}'.format(\n                              bytes(errpipe_data))\n            child_exception_type = getattr(\n                    builtins, exception_name.decode('ascii'),\n                    SubprocessError)\n            if issubclass(child_exception_type, OSError) and hex_errno:\n                errno_num = int(hex_errno, 16)\n                child_exec_never_called = (err_msg == \"noexec\")\n                if child_exec_never_called:\n                    err_msg = \"\"\n                    # The error must be from chdir(cwd).\n                    err_filename = cwd\n                else:\n                    err_filename = orig_executable\n                if errno_num != 0:\n                    err_msg = os.strerror(errno_num)\n>               raise child_exception_type(errno_num, err_msg, err_filename)\nE               FileNotFoundError: [Errno 2] No such file or directory: 'git-annex-remote-datalad'\n\n../../../../.local/share/uv/python/cpython-3.9.21-linux-x86_64-gnu/lib/python3.9/subprocess.py:1837: FileNotFoundError\n______________________________ test_cfg_override _______________________________\n\npath = '/tmp/datalad_temp_test_cfg_overrides95zyuyi'\n\n    @with_tempfile(mkdir=True)\n    def test_cfg_override(path=None):\n        with chpwd(path):\n            # use 'wtf' to dump the config\n            # should be rewritten to use `configuration`\n            cmd = ['datalad', 'wtf', '-S', 'configuration', '-s', 'some']\n            # control\n>           out = Runner().run(cmd, protocol=StdOutErrCapture)['stdout']\n\nr2e_tests/test_1.py:307: \n_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ \ndatalad/runner/runner.py:225: in run\n    results_or_iterator = threaded_runner.run()\ndatalad/runner/nonasyncrunner.py:347: in run\n    return self._locked_run()\ndatalad/runner/nonasyncrunner.py:407: in _locked_run\n    self.process = Popen(self.cmd, **kwargs)         # nosec\n../../../../.local/share/uv/python/cpython-3.9.21-linux-x86_64-gnu/lib/python3.9/subprocess.py:951: in __init__\n    self._execute_child(args, executable, preexec_fn, close_fds,\n_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ \n\nself = <Popen: returncode: 255 args: ['datalad', 'wtf', '-S', 'configuration', '-s'...>\nargs = ['datalad', 'wtf', '-S', 'configuration', '-s', 'some']\nexecutable = b'datalad', preexec_fn = None, close_fds = True, pass_fds = ()\ncwd = None, env = None, startupinfo = None, creationflags = 0, shell = False\np2cread = -1, p2cwrite = -1, c2pread = 12, c2pwrite = 13, errread = 14\nerrwrite = 15, restore_signals = True, gid = None, gids = None, uid = None\numask = -1, start_new_session = False\n\n    def _execute_child(self, args, executable, preexec_fn, close_fds,\n                       pass_fds, cwd, env,\n                       startupinfo, creationflags, shell,\n                       p2cread, p2cwrite,\n                       c2pread, c2pwrite,\n                       errread, errwrite,\n                       restore_signals,\n                       gid, gids, uid, umask,\n                       start_new_session):\n        \"\"\"Execute program (POSIX version)\"\"\"\n    \n        if isinstance(args, (str, bytes)):\n            args = [args]\n        elif isinstance(args, os.PathLike):\n            if shell:\n                raise TypeError('path-like args is not allowed when '\n                                'shell is true')\n            args = [args]\n        else:\n            args = list(args)\n    \n        if shell:\n            # On Android the default shell is at '/system/bin/sh'.\n            unix_shell = ('/system/bin/sh' if\n                      hasattr(sys, 'getandroidapilevel') else '/bin/sh')\n            args = [unix_shell, \"-c\"] + args\n            if executable:\n                args[0] = executable\n    \n        if executable is None:\n            executable = args[0]\n    \n        sys.audit(\"subprocess.Popen\", executable, args, cwd, env)\n    \n        if (_USE_POSIX_SPAWN\n                and os.path.dirname(executable)\n                and preexec_fn is None\n                and not close_fds\n                and not pass_fds\n                and cwd is None\n                and (p2cread == -1 or p2cread > 2)\n                and (c2pwrite == -1 or c2pwrite > 2)\n                and (errwrite == -1 or errwrite > 2)\n                and not start_new_session\n                and gid is None\n                and gids is None\n                and uid is None\n                and umask < 0):\n            self._posix_spawn(args, executable, env, restore_signals,\n                              p2cread, p2cwrite,\n                              c2pread, c2pwrite,\n                              errread, errwrite)\n            return\n    \n        orig_executable = executable\n    \n        # For transferring possible exec failure from child to parent.\n        # Data format: \"exception name:hex errno:description\"\n        # Pickle is not used; it is complex and involves memory allocation.\n        errpipe_read, errpipe_write = os.pipe()\n        # errpipe_write must not be in the standard io 0, 1, or 2 fd range.\n        low_fds_to_close = []\n        while errpipe_write < 3:\n            low_fds_to_close.append(errpipe_write)\n            errpipe_write = os.dup(errpipe_write)\n        for low_fd in low_fds_to_close:\n            os.close(low_fd)\n        try:\n            try:\n                # We must avoid complex work that could involve\n                # malloc or free in the child process to avoid\n                # potential deadlocks, thus we do all this here.\n                # and pass it to fork_exec()\n    \n                if env is not None:\n                    env_list = []\n                    for k, v in env.items():\n                        k = os.fsencode(k)\n                        if b'=' in k:\n                            raise ValueError(\"illegal environment variable name\")\n                        env_list.append(k + b'=' + os.fsencode(v))\n                else:\n                    env_list = None  # Use execv instead of execve.\n                executable = os.fsencode(executable)\n                if os.path.dirname(executable):\n                    executable_list = (executable,)\n                else:\n                    # This matches the behavior of os._execvpe().\n                    executable_list = tuple(\n                        os.path.join(os.fsencode(dir), executable)\n                        for dir in os.get_exec_path(env))\n                fds_to_keep = set(pass_fds)\n                fds_to_keep.add(errpipe_write)\n                self.pid = _posixsubprocess.fork_exec(\n                        args, executable_list,\n                        close_fds, tuple(sorted(map(int, fds_to_keep))),\n                        cwd, env_list,\n                        p2cread, p2cwrite, c2pread, c2pwrite,\n                        errread, errwrite,\n                        errpipe_read, errpipe_write,\n                        restore_signals, start_new_session,\n                        gid, gids, uid, umask,\n                        preexec_fn)\n                self._child_created = True\n            finally:\n                # be sure the FD is closed no matter what\n                os.close(errpipe_write)\n    \n            self._close_pipe_fds(p2cread, p2cwrite,\n                                 c2pread, c2pwrite,\n                                 errread, errwrite)\n    \n            # Wait for exec to fail or succeed; possibly raising an\n            # exception (limited in size)\n            errpipe_data = bytearray()\n            while True:\n                part = os.read(errpipe_read, 50000)\n                errpipe_data += part\n                if not part or len(errpipe_data) > 50000:\n                    break\n        finally:\n            # be sure the FD is closed no matter what\n            os.close(errpipe_read)\n    \n        if errpipe_data:\n            try:\n                pid, sts = os.waitpid(self.pid, 0)\n                if pid == self.pid:\n                    self._handle_exitstatus(sts)\n                else:\n                    self.returncode = sys.maxsize\n            except ChildProcessError:\n                pass\n    \n            try:\n                exception_name, hex_errno, err_msg = (\n                        errpipe_data.split(b':', 2))\n                # The encoding here should match the encoding\n                # written in by the subprocess implementations\n                # like _posixsubprocess\n                err_msg = err_msg.decode()\n            except ValueError:\n                exception_name = b'SubprocessError'\n                hex_errno = b'0'\n                err_msg = 'Bad exception data from child: {!r}'.format(\n                              bytes(errpipe_data))\n            child_exception_type = getattr(\n                    builtins, exception_name.decode('ascii'),\n                    SubprocessError)\n            if issubclass(child_exception_type, OSError) and hex_errno:\n                errno_num = int(hex_errno, 16)\n                child_exec_never_called = (err_msg == \"noexec\")\n                if child_exec_never_called:\n                    err_msg = \"\"\n                    # The error must be from chdir(cwd).\n                    err_filename = cwd\n                else:\n                    err_filename = orig_executable\n                if errno_num != 0:\n                    err_msg = os.strerror(errno_num)\n>               raise child_exception_type(errno_num, err_msg, err_filename)\nE               FileNotFoundError: [Errno 2] No such file or directory: 'datalad'\n\n../../../../.local/share/uv/python/cpython-3.9.21-linux-x86_64-gnu/lib/python3.9/subprocess.py:1837: FileNotFoundError\n_______________________________ test_completion ________________________________\n\nout_fn = '/tmp/datalad_temp_test_completionoe0s62tr'\n\n    @with_tempfile\n    def test_completion(out_fn=None):\n        skip_if_no_module('argcomplete')\n    \n        from datalad.cmd import WitlessRunner\n        runner = WitlessRunner()\n    \n        def get_completions(s: str, expected) -> list:\n            \"\"\"Run 'datalad' external command and collect completions\n    \n            Parameters\n            ----------\n            s: str\n              what to append to 'datalad ' invocation\n            expected: iterable of str\n              What entries to expect - would raise AssertionError if any is\n              not present in output\n            exit_code: int, optional\n              If incomplete/malformed we seems to get 2, most frequently used\n              so default\n    \n            Returns\n            -------\n            list of str\n              Entries output\n            \"\"\"\n            if os.path.exists(out_fn):  # reuse but ensure it is gone\n                os.unlink(out_fn)\n            comp_line = f'datalad {s}'\n            runner.run(\n                comp_line.split(' '),\n                env=dict(os.environ,\n                         _ARGCOMPLETE='1',\n                         _ARGCOMPLETE_STDOUT_FILENAME=out_fn,\n                         COMP_LINE=comp_line,\n                         # without -1 seems to get \"finished completion\", someone can investigate more\n                         COMP_POINT=str(len(comp_line)-1),  # always at the end ATM\n                         ))\n            with open(out_fn, 'rb') as f:\n                entries = f.read().split(b'\\x0b')\n                entries = [e.decode() for e in entries]\n            diff = set(expected).difference(entries)\n            if diff:\n                raise AssertionError(\n                    f\"Entries {sorted(diff)} were expected but not found in the completion output: {entries}\"\n                )\n            return entries  # for extra analyzes if so desired\n    \n        all_commands = get_all_commands()\n>       get_completions('i', {'install'})\n\nr2e_tests/test_1.py:433: \n_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ \nr2e_tests/test_1.py:413: in get_completions\n    runner.run(\ndatalad/runner/runner.py:225: in run\n    results_or_iterator = threaded_runner.run()\ndatalad/runner/nonasyncrunner.py:347: in run\n    return self._locked_run()\ndatalad/runner/nonasyncrunner.py:407: in _locked_run\n    self.process = Popen(self.cmd, **kwargs)         # nosec\n../../../../.local/share/uv/python/cpython-3.9.21-linux-x86_64-gnu/lib/python3.9/subprocess.py:951: in __init__\n    self._execute_child(args, executable, preexec_fn, close_fds,\n_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ \n\nself = <Popen: returncode: 255 args: ['datalad', 'i']>, args = ['datalad', 'i']\nexecutable = b'datalad', preexec_fn = None, close_fds = True, pass_fds = ()\ncwd = None\nenv = {'ANTHROPIC_API_KEY': 'sk-ant-api03-kztdHBP1RruInQ2uzw8m4wJLbhPHBrb-qHX2mhAKmwoMFvM_dI2oWb5V6JtS45MScuW5CCjPusIrt9Zatx...3b4166bef66ef252935c/server/bin/helpers/browser.sh', 'COHERE_API_KEY': '66TDU3MmN5ELJT1NHWklGG2PJjK2zwVSQbVq42ol', ...}\nstartupinfo = None, creationflags = 0, shell = False, p2cread = -1\np2cwrite = -1, c2pread = -1, c2pwrite = -1, errread = -1, errwrite = -1\nrestore_signals = True, gid = None, gids = None, uid = None, umask = -1\nstart_new_session = False\n\n    def _execute_child(self, args, executable, preexec_fn, close_fds,\n                       pass_fds, cwd, env,\n                       startupinfo, creationflags, shell,\n                       p2cread, p2cwrite,\n                       c2pread, c2pwrite,\n                       errread, errwrite,\n                       restore_signals,\n                       gid, gids, uid, umask,\n                       start_new_session):\n        \"\"\"Execute program (POSIX version)\"\"\"\n    \n        if isinstance(args, (str, bytes)):\n            args = [args]\n        elif isinstance(args, os.PathLike):\n            if shell:\n                raise TypeError('path-like args is not allowed when '\n                                'shell is true')\n            args = [args]\n        else:\n            args = list(args)\n    \n        if shell:\n            # On Android the default shell is at '/system/bin/sh'.\n            unix_shell = ('/system/bin/sh' if\n                      hasattr(sys, 'getandroidapilevel') else '/bin/sh')\n            args = [unix_shell, \"-c\"] + args\n            if executable:\n                args[0] = executable\n    \n        if executable is None:\n            executable = args[0]\n    \n        sys.audit(\"subprocess.Popen\", executable, args, cwd, env)\n    \n        if (_USE_POSIX_SPAWN\n                and os.path.dirname(executable)\n                and preexec_fn is None\n                and not close_fds\n                and not pass_fds\n                and cwd is None\n                and (p2cread == -1 or p2cread > 2)\n                and (c2pwrite == -1 or c2pwrite > 2)\n                and (errwrite == -1 or errwrite > 2)\n                and not start_new_session\n                and gid is None\n                and gids is None\n                and uid is None\n                and umask < 0):\n            self._posix_spawn(args, executable, env, restore_signals,\n                              p2cread, p2cwrite,\n                              c2pread, c2pwrite,\n                              errread, errwrite)\n            return\n    \n        orig_executable = executable\n    \n        # For transferring possible exec failure from child to parent.\n        # Data format: \"exception name:hex errno:description\"\n        # Pickle is not used; it is complex and involves memory allocation.\n        errpipe_read, errpipe_write = os.pipe()\n        # errpipe_write must not be in the standard io 0, 1, or 2 fd range.\n        low_fds_to_close = []\n        while errpipe_write < 3:\n            low_fds_to_close.append(errpipe_write)\n            errpipe_write = os.dup(errpipe_write)\n        for low_fd in low_fds_to_close:\n            os.close(low_fd)\n        try:\n            try:\n                # We must avoid complex work that could involve\n                # malloc or free in the child process to avoid\n                # potential deadlocks, thus we do all this here.\n                # and pass it to fork_exec()\n    \n                if env is not None:\n                    env_list = []\n                    for k, v in env.items():\n                        k = os.fsencode(k)\n                        if b'=' in k:\n                            raise ValueError(\"illegal environment variable name\")\n                        env_list.append(k + b'=' + os.fsencode(v))\n                else:\n                    env_list = None  # Use execv instead of execve.\n                executable = os.fsencode(executable)\n                if os.path.dirname(executable):\n                    executable_list = (executable,)\n                else:\n                    # This matches the behavior of os._execvpe().\n                    executable_list = tuple(\n                        os.path.join(os.fsencode(dir), executable)\n                        for dir in os.get_exec_path(env))\n                fds_to_keep = set(pass_fds)\n                fds_to_keep.add(errpipe_write)\n                self.pid = _posixsubprocess.fork_exec(\n                        args, executable_list,\n                        close_fds, tuple(sorted(map(int, fds_to_keep))),\n                        cwd, env_list,\n                        p2cread, p2cwrite, c2pread, c2pwrite,\n                        errread, errwrite,\n                        errpipe_read, errpipe_write,\n                        restore_signals, start_new_session,\n                        gid, gids, uid, umask,\n                        preexec_fn)\n                self._child_created = True\n            finally:\n                # be sure the FD is closed no matter what\n                os.close(errpipe_write)\n    \n            self._close_pipe_fds(p2cread, p2cwrite,\n                                 c2pread, c2pwrite,\n                                 errread, errwrite)\n    \n            # Wait for exec to fail or succeed; possibly raising an\n            # exception (limited in size)\n            errpipe_data = bytearray()\n            while True:\n                part = os.read(errpipe_read, 50000)\n                errpipe_data += part\n                if not part or len(errpipe_data) > 50000:\n                    break\n        finally:\n            # be sure the FD is closed no matter what\n            os.close(errpipe_read)\n    \n        if errpipe_data:\n            try:\n                pid, sts = os.waitpid(self.pid, 0)\n                if pid == self.pid:\n                    self._handle_exitstatus(sts)\n                else:\n                    self.returncode = sys.maxsize\n            except ChildProcessError:\n                pass\n    \n            try:\n                exception_name, hex_errno, err_msg = (\n                        errpipe_data.split(b':', 2))\n                # The encoding here should match the encoding\n                # written in by the subprocess implementations\n                # like _posixsubprocess\n                err_msg = err_msg.decode()\n            except ValueError:\n                exception_name = b'SubprocessError'\n                hex_errno = b'0'\n                err_msg = 'Bad exception data from child: {!r}'.format(\n                              bytes(errpipe_data))\n            child_exception_type = getattr(\n                    builtins, exception_name.decode('ascii'),\n                    SubprocessError)\n            if issubclass(child_exception_type, OSError) and hex_errno:\n                errno_num = int(hex_errno, 16)\n                child_exec_never_called = (err_msg == \"noexec\")\n                if child_exec_never_called:\n                    err_msg = \"\"\n                    # The error must be from chdir(cwd).\n                    err_filename = cwd\n                else:\n                    err_filename = orig_executable\n                if errno_num != 0:\n                    err_msg = os.strerror(errno_num)\n>               raise child_exception_type(errno_num, err_msg, err_filename)\nE               FileNotFoundError: [Errno 2] No such file or directory: 'datalad'\n\n../../../../.local/share/uv/python/cpython-3.9.21-linux-x86_64-gnu/lib/python3.9/subprocess.py:1837: FileNotFoundError\n==================================== PASSES ====================================\n_______________________ test_usage_on_insufficient_args ________________________\n----------------------------- Captured stderr call -----------------------------\n[ERROR] InsufficientArgumentsError(Please provide at least a source or a path) (InsufficientArgumentsError) \n------------------------------ Captured log call -------------------------------\nERROR    datalad.cli:main.py:204 InsufficientArgumentsError(Please provide at least a source or a path) (InsufficientArgumentsError)\n______________________ test_subcmd_usage_on_unknown_args _______________________\n----------------------------- Captured stderr call -----------------------------\n[ERROR] unknown argument: --murks \n------------------------------ Captured log call -------------------------------\nERROR    datalad.cli:main.py:103 unknown argument: --murks\n______________________________ test_run_exit_code ______________________________\n----------------------------- Captured stdout call -----------------------------\ncreate(ok): . (dataset)\nrun(error): /tmp/datalad_temp_test_run_exit_codeanunky2q (dataset) [exit 3]\n\n\n----------------------------- Captured stderr call -----------------------------\n[INFO] == Command start (output follows) ===== \n[INFO] == Command exit (modification check follows) ===== \n------------------------------ Captured log call -------------------------------\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad.core.local.run:run.py:700 == Command start (output follows) =====\nINFO     datalad.core.local.run:run.py:708 == Command exit (modification check follows) =====\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\n________________________ test_conflicting_short_option _________________________\n----------------------------- Captured stderr call -----------------------------\n[INFO] scanning for unlocked files (this may take some time) \n------------------------------ Captured log call -------------------------------\nINFO     datalad.annex:annexrepo.py:3975 scanning for unlocked files (this may take some time)\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\n_________________________ test_incorrect_cfg_override __________________________\n----------------------------- Captured stderr call -----------------------------\n[ERROR] Configuration override without section/variable or unset marker or value assignment (must be '(:section.variable|section.variable=value)'): ['some'] \n[ERROR] Configuration override without section/variable or unset marker or value assignment (must be '(:section.variable|section.variable=value)'): ['some='] \n[ERROR] Configuration override without section/variable or unset marker or value assignment (must be '(:section.variable|section.variable=value)'): ['some.var'] \n[ERROR] Configuration override without section/variable or unset marker or value assignment (must be '(:section.variable|section.variable=value)'): ['some.var='] \n------------------------------ Captured log call -------------------------------\nERROR    datalad.cli.helpers:helpers.py:266 Configuration override without section/variable or unset marker or value assignment (must be '(:section.variable|section.variable=value)'): ['some']\nERROR    datalad.cli.helpers:helpers.py:266 Configuration override without section/variable or unset marker or value assignment (must be '(:section.variable|section.variable=value)'): ['some=']\nERROR    datalad.cli.helpers:helpers.py:266 Configuration override without section/variable or unset marker or value assignment (must be '(:section.variable|section.variable=value)'): ['some.var']\nERROR    datalad.cli.helpers:helpers.py:266 Configuration override without section/variable or unset marker or value assignment (must be '(:section.variable|section.variable=value)'): ['some.var=']\n_______________________________ test_librarymode _______________________________\n----------------------------- Captured stdout call -----------------------------\ncreate(ok): . (dataset)\n----------------------------- Captured stderr call -----------------------------\n[INFO] scanning for unlocked files (this may take some time) \n------------------------------ Captured log call -------------------------------\nINFO     datalad.annex:annexrepo.py:3975 scanning for unlocked files (this may take some time)\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\n=========================== short test summary info ============================\nPASSED r2e_tests/test_1.py::test_version\nPASSED r2e_tests/test_1.py::test_dashh\nPASSED r2e_tests/test_1.py::test_dashh_clone\nPASSED r2e_tests/test_1.py::test_usage_on_insufficient_args\nPASSED r2e_tests/test_1.py::test_subcmd_usage_on_unknown_args\nPASSED r2e_tests/test_1.py::test_combined_short_option\nPASSED r2e_tests/test_1.py::test_run_exit_code\nPASSED r2e_tests/test_1.py::test_conflicting_short_option\nPASSED r2e_tests/test_1.py::test_incorrect_option[opts0-error: (invalid|too few arguments|unrecognized argument)]\nPASSED r2e_tests/test_1.py::test_incorrect_option[opts1-error: (invalid|too few arguments|unrecognized argument)]\nPASSED r2e_tests/test_1.py::test_incorrect_option[opts2-error: (invalid|too few arguments|unrecognized argument)]\nPASSED r2e_tests/test_1.py::test_incorrect_option[opts3-error: (invalid|too few arguments|unrecognized argument)]\nPASSED r2e_tests/test_1.py::test_incorrect_cfg_override\nPASSED r2e_tests/test_1.py::test_librarymode\nSKIPPED [1] .venv/lib/python3.9/site-packages/_pytest/unittest.py:385: Could not determine terminal size, skipping the rest of the test\nFAILED r2e_tests/test_1.py::test_script_shims[datalad] - FileNotFoundError: [...\nFAILED r2e_tests/test_1.py::test_script_shims[git-annex-remote-datalad-archives]\nFAILED r2e_tests/test_1.py::test_script_shims[git-annex-remote-datalad] - Fil...\nFAILED r2e_tests/test_1.py::test_cfg_override - FileNotFoundError: [Errno 2] ...\nFAILED r2e_tests/test_1.py::test_completion - FileNotFoundError: [Errno 2] No...\n=================== 5 failed, 14 passed, 1 skipped in 4.99s ====================\n",
    "new_commit_res_stderr": "",
    "old_commit_res_code": 1,
    "old_commit_res_stdout": "============================= test session starts ==============================\nplatform linux -- Python 3.9.21, pytest-8.3.4, pluggy-1.5.0\nrootdir: /home/gcpuser/buckets/local_repoeval_bucket/repos/datalad_19f5b45096cadaa4677c9b15f18a6d303497eaeb\nconfigfile: tox.ini\nplugins: fail-slow-0.6.0, xdist-3.6.1, cov-6.0.0\ncollected 20 items\n\nr2e_tests/test_1.py .s.....F.....FFFF..F                                 [100%]\n\n=================================== FAILURES ===================================\n______________________________ test_run_exit_code ______________________________\n\ntempdir = '/tmp/datalad_temp_test_run_exit_coder3riqpa9'\n\n    @with_tempfile(mkdir=True)\n    def test_run_exit_code(tempdir=None):\n        # datalad run is just an internal command which can readily be used to trigger\n        # desired situation\n        create(dataset=tempdir, annex=False)\n        with chpwd(tempdir):  # can't just use -C tempdir since we do \"in process\" run_main\n>           stdout, stderr = run_main(['run', '--explicit', 'exit 3'], exit_code=3) #, expect_stderr=True)\n\nr2e_tests/test_1.py:221: \n_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ \nr2e_tests/test_1.py:83: in run_main\n    assert_equal(cm.value.code, exit_code)\n_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ \n\nfirst = 1, second = 3, msg = None\n\n    def assert_equal(first, second, msg=None):\n        if msg is None:\n>           assert first == second\nE           assert 1 == 3\n\ndatalad/tests/utils_pytest.py:80: AssertionError\n----------------------------- Captured stdout call -----------------------------\ncreate(ok): . (dataset)\n----------------------------- Captured stderr call -----------------------------\n[INFO] == Command start (output follows) ===== \n[INFO] == Command exit (modification check follows) ===== \n------------------------------ Captured log call -------------------------------\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad.core.local.run:run.py:700 == Command start (output follows) =====\nINFO     datalad.core.local.run:run.py:708 == Command exit (modification check follows) =====\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\n__________________________ test_script_shims[datalad] __________________________\n\nscript = 'datalad'\n\n    @pytest.mark.parametrize(\n        \"script\",\n        [\n            'datalad',\n            'git-annex-remote-datalad-archives',\n            'git-annex-remote-datalad',\n        ]\n    )\n    def test_script_shims(script):\n        runner = Runner()\n        if not on_windows:\n    \n            from shutil import which\n            which(script)\n    \n        # and let's check that it is our script\n>       out = runner.run([script, '--version'], protocol=StdOutErrCapture)\n\nr2e_tests/test_1.py:287: \n_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ \ndatalad/runner/runner.py:225: in run\n    results_or_iterator = threaded_runner.run()\ndatalad/runner/nonasyncrunner.py:347: in run\n    return self._locked_run()\ndatalad/runner/nonasyncrunner.py:407: in _locked_run\n    self.process = Popen(self.cmd, **kwargs)         # nosec\n../../../../.local/share/uv/python/cpython-3.9.21-linux-x86_64-gnu/lib/python3.9/subprocess.py:951: in __init__\n    self._execute_child(args, executable, preexec_fn, close_fds,\n_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ \n\nself = <Popen: returncode: 255 args: ['datalad', '--version']>\nargs = ['datalad', '--version'], executable = b'datalad', preexec_fn = None\nclose_fds = True, pass_fds = (), cwd = None, env = None, startupinfo = None\ncreationflags = 0, shell = False, p2cread = -1, p2cwrite = -1, c2pread = 12\nc2pwrite = 13, errread = 14, errwrite = 15, restore_signals = True, gid = None\ngids = None, uid = None, umask = -1, start_new_session = False\n\n    def _execute_child(self, args, executable, preexec_fn, close_fds,\n                       pass_fds, cwd, env,\n                       startupinfo, creationflags, shell,\n                       p2cread, p2cwrite,\n                       c2pread, c2pwrite,\n                       errread, errwrite,\n                       restore_signals,\n                       gid, gids, uid, umask,\n                       start_new_session):\n        \"\"\"Execute program (POSIX version)\"\"\"\n    \n        if isinstance(args, (str, bytes)):\n            args = [args]\n        elif isinstance(args, os.PathLike):\n            if shell:\n                raise TypeError('path-like args is not allowed when '\n                                'shell is true')\n            args = [args]\n        else:\n            args = list(args)\n    \n        if shell:\n            # On Android the default shell is at '/system/bin/sh'.\n            unix_shell = ('/system/bin/sh' if\n                      hasattr(sys, 'getandroidapilevel') else '/bin/sh')\n            args = [unix_shell, \"-c\"] + args\n            if executable:\n                args[0] = executable\n    \n        if executable is None:\n            executable = args[0]\n    \n        sys.audit(\"subprocess.Popen\", executable, args, cwd, env)\n    \n        if (_USE_POSIX_SPAWN\n                and os.path.dirname(executable)\n                and preexec_fn is None\n                and not close_fds\n                and not pass_fds\n                and cwd is None\n                and (p2cread == -1 or p2cread > 2)\n                and (c2pwrite == -1 or c2pwrite > 2)\n                and (errwrite == -1 or errwrite > 2)\n                and not start_new_session\n                and gid is None\n                and gids is None\n                and uid is None\n                and umask < 0):\n            self._posix_spawn(args, executable, env, restore_signals,\n                              p2cread, p2cwrite,\n                              c2pread, c2pwrite,\n                              errread, errwrite)\n            return\n    \n        orig_executable = executable\n    \n        # For transferring possible exec failure from child to parent.\n        # Data format: \"exception name:hex errno:description\"\n        # Pickle is not used; it is complex and involves memory allocation.\n        errpipe_read, errpipe_write = os.pipe()\n        # errpipe_write must not be in the standard io 0, 1, or 2 fd range.\n        low_fds_to_close = []\n        while errpipe_write < 3:\n            low_fds_to_close.append(errpipe_write)\n            errpipe_write = os.dup(errpipe_write)\n        for low_fd in low_fds_to_close:\n            os.close(low_fd)\n        try:\n            try:\n                # We must avoid complex work that could involve\n                # malloc or free in the child process to avoid\n                # potential deadlocks, thus we do all this here.\n                # and pass it to fork_exec()\n    \n                if env is not None:\n                    env_list = []\n                    for k, v in env.items():\n                        k = os.fsencode(k)\n                        if b'=' in k:\n                            raise ValueError(\"illegal environment variable name\")\n                        env_list.append(k + b'=' + os.fsencode(v))\n                else:\n                    env_list = None  # Use execv instead of execve.\n                executable = os.fsencode(executable)\n                if os.path.dirname(executable):\n                    executable_list = (executable,)\n                else:\n                    # This matches the behavior of os._execvpe().\n                    executable_list = tuple(\n                        os.path.join(os.fsencode(dir), executable)\n                        for dir in os.get_exec_path(env))\n                fds_to_keep = set(pass_fds)\n                fds_to_keep.add(errpipe_write)\n                self.pid = _posixsubprocess.fork_exec(\n                        args, executable_list,\n                        close_fds, tuple(sorted(map(int, fds_to_keep))),\n                        cwd, env_list,\n                        p2cread, p2cwrite, c2pread, c2pwrite,\n                        errread, errwrite,\n                        errpipe_read, errpipe_write,\n                        restore_signals, start_new_session,\n                        gid, gids, uid, umask,\n                        preexec_fn)\n                self._child_created = True\n            finally:\n                # be sure the FD is closed no matter what\n                os.close(errpipe_write)\n    \n            self._close_pipe_fds(p2cread, p2cwrite,\n                                 c2pread, c2pwrite,\n                                 errread, errwrite)\n    \n            # Wait for exec to fail or succeed; possibly raising an\n            # exception (limited in size)\n            errpipe_data = bytearray()\n            while True:\n                part = os.read(errpipe_read, 50000)\n                errpipe_data += part\n                if not part or len(errpipe_data) > 50000:\n                    break\n        finally:\n            # be sure the FD is closed no matter what\n            os.close(errpipe_read)\n    \n        if errpipe_data:\n            try:\n                pid, sts = os.waitpid(self.pid, 0)\n                if pid == self.pid:\n                    self._handle_exitstatus(sts)\n                else:\n                    self.returncode = sys.maxsize\n            except ChildProcessError:\n                pass\n    \n            try:\n                exception_name, hex_errno, err_msg = (\n                        errpipe_data.split(b':', 2))\n                # The encoding here should match the encoding\n                # written in by the subprocess implementations\n                # like _posixsubprocess\n                err_msg = err_msg.decode()\n            except ValueError:\n                exception_name = b'SubprocessError'\n                hex_errno = b'0'\n                err_msg = 'Bad exception data from child: {!r}'.format(\n                              bytes(errpipe_data))\n            child_exception_type = getattr(\n                    builtins, exception_name.decode('ascii'),\n                    SubprocessError)\n            if issubclass(child_exception_type, OSError) and hex_errno:\n                errno_num = int(hex_errno, 16)\n                child_exec_never_called = (err_msg == \"noexec\")\n                if child_exec_never_called:\n                    err_msg = \"\"\n                    # The error must be from chdir(cwd).\n                    err_filename = cwd\n                else:\n                    err_filename = orig_executable\n                if errno_num != 0:\n                    err_msg = os.strerror(errno_num)\n>               raise child_exception_type(errno_num, err_msg, err_filename)\nE               FileNotFoundError: [Errno 2] No such file or directory: 'datalad'\n\n../../../../.local/share/uv/python/cpython-3.9.21-linux-x86_64-gnu/lib/python3.9/subprocess.py:1837: FileNotFoundError\n_____________ test_script_shims[git-annex-remote-datalad-archives] _____________\n\nscript = 'git-annex-remote-datalad-archives'\n\n    @pytest.mark.parametrize(\n        \"script\",\n        [\n            'datalad',\n            'git-annex-remote-datalad-archives',\n            'git-annex-remote-datalad',\n        ]\n    )\n    def test_script_shims(script):\n        runner = Runner()\n        if not on_windows:\n    \n            from shutil import which\n            which(script)\n    \n        # and let's check that it is our script\n>       out = runner.run([script, '--version'], protocol=StdOutErrCapture)\n\nr2e_tests/test_1.py:287: \n_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ \ndatalad/runner/runner.py:225: in run\n    results_or_iterator = threaded_runner.run()\ndatalad/runner/nonasyncrunner.py:347: in run\n    return self._locked_run()\ndatalad/runner/nonasyncrunner.py:407: in _locked_run\n    self.process = Popen(self.cmd, **kwargs)         # nosec\n../../../../.local/share/uv/python/cpython-3.9.21-linux-x86_64-gnu/lib/python3.9/subprocess.py:951: in __init__\n    self._execute_child(args, executable, preexec_fn, close_fds,\n_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ \n\nself = <Popen: returncode: 255 args: ['git-annex-remote-datalad-archives', '--versi...>\nargs = ['git-annex-remote-datalad-archives', '--version']\nexecutable = b'git-annex-remote-datalad-archives', preexec_fn = None\nclose_fds = True, pass_fds = (), cwd = None, env = None, startupinfo = None\ncreationflags = 0, shell = False, p2cread = -1, p2cwrite = -1, c2pread = 12\nc2pwrite = 13, errread = 14, errwrite = 15, restore_signals = True, gid = None\ngids = None, uid = None, umask = -1, start_new_session = False\n\n    def _execute_child(self, args, executable, preexec_fn, close_fds,\n                       pass_fds, cwd, env,\n                       startupinfo, creationflags, shell,\n                       p2cread, p2cwrite,\n                       c2pread, c2pwrite,\n                       errread, errwrite,\n                       restore_signals,\n                       gid, gids, uid, umask,\n                       start_new_session):\n        \"\"\"Execute program (POSIX version)\"\"\"\n    \n        if isinstance(args, (str, bytes)):\n            args = [args]\n        elif isinstance(args, os.PathLike):\n            if shell:\n                raise TypeError('path-like args is not allowed when '\n                                'shell is true')\n            args = [args]\n        else:\n            args = list(args)\n    \n        if shell:\n            # On Android the default shell is at '/system/bin/sh'.\n            unix_shell = ('/system/bin/sh' if\n                      hasattr(sys, 'getandroidapilevel') else '/bin/sh')\n            args = [unix_shell, \"-c\"] + args\n            if executable:\n                args[0] = executable\n    \n        if executable is None:\n            executable = args[0]\n    \n        sys.audit(\"subprocess.Popen\", executable, args, cwd, env)\n    \n        if (_USE_POSIX_SPAWN\n                and os.path.dirname(executable)\n                and preexec_fn is None\n                and not close_fds\n                and not pass_fds\n                and cwd is None\n                and (p2cread == -1 or p2cread > 2)\n                and (c2pwrite == -1 or c2pwrite > 2)\n                and (errwrite == -1 or errwrite > 2)\n                and not start_new_session\n                and gid is None\n                and gids is None\n                and uid is None\n                and umask < 0):\n            self._posix_spawn(args, executable, env, restore_signals,\n                              p2cread, p2cwrite,\n                              c2pread, c2pwrite,\n                              errread, errwrite)\n            return\n    \n        orig_executable = executable\n    \n        # For transferring possible exec failure from child to parent.\n        # Data format: \"exception name:hex errno:description\"\n        # Pickle is not used; it is complex and involves memory allocation.\n        errpipe_read, errpipe_write = os.pipe()\n        # errpipe_write must not be in the standard io 0, 1, or 2 fd range.\n        low_fds_to_close = []\n        while errpipe_write < 3:\n            low_fds_to_close.append(errpipe_write)\n            errpipe_write = os.dup(errpipe_write)\n        for low_fd in low_fds_to_close:\n            os.close(low_fd)\n        try:\n            try:\n                # We must avoid complex work that could involve\n                # malloc or free in the child process to avoid\n                # potential deadlocks, thus we do all this here.\n                # and pass it to fork_exec()\n    \n                if env is not None:\n                    env_list = []\n                    for k, v in env.items():\n                        k = os.fsencode(k)\n                        if b'=' in k:\n                            raise ValueError(\"illegal environment variable name\")\n                        env_list.append(k + b'=' + os.fsencode(v))\n                else:\n                    env_list = None  # Use execv instead of execve.\n                executable = os.fsencode(executable)\n                if os.path.dirname(executable):\n                    executable_list = (executable,)\n                else:\n                    # This matches the behavior of os._execvpe().\n                    executable_list = tuple(\n                        os.path.join(os.fsencode(dir), executable)\n                        for dir in os.get_exec_path(env))\n                fds_to_keep = set(pass_fds)\n                fds_to_keep.add(errpipe_write)\n                self.pid = _posixsubprocess.fork_exec(\n                        args, executable_list,\n                        close_fds, tuple(sorted(map(int, fds_to_keep))),\n                        cwd, env_list,\n                        p2cread, p2cwrite, c2pread, c2pwrite,\n                        errread, errwrite,\n                        errpipe_read, errpipe_write,\n                        restore_signals, start_new_session,\n                        gid, gids, uid, umask,\n                        preexec_fn)\n                self._child_created = True\n            finally:\n                # be sure the FD is closed no matter what\n                os.close(errpipe_write)\n    \n            self._close_pipe_fds(p2cread, p2cwrite,\n                                 c2pread, c2pwrite,\n                                 errread, errwrite)\n    \n            # Wait for exec to fail or succeed; possibly raising an\n            # exception (limited in size)\n            errpipe_data = bytearray()\n            while True:\n                part = os.read(errpipe_read, 50000)\n                errpipe_data += part\n                if not part or len(errpipe_data) > 50000:\n                    break\n        finally:\n            # be sure the FD is closed no matter what\n            os.close(errpipe_read)\n    \n        if errpipe_data:\n            try:\n                pid, sts = os.waitpid(self.pid, 0)\n                if pid == self.pid:\n                    self._handle_exitstatus(sts)\n                else:\n                    self.returncode = sys.maxsize\n            except ChildProcessError:\n                pass\n    \n            try:\n                exception_name, hex_errno, err_msg = (\n                        errpipe_data.split(b':', 2))\n                # The encoding here should match the encoding\n                # written in by the subprocess implementations\n                # like _posixsubprocess\n                err_msg = err_msg.decode()\n            except ValueError:\n                exception_name = b'SubprocessError'\n                hex_errno = b'0'\n                err_msg = 'Bad exception data from child: {!r}'.format(\n                              bytes(errpipe_data))\n            child_exception_type = getattr(\n                    builtins, exception_name.decode('ascii'),\n                    SubprocessError)\n            if issubclass(child_exception_type, OSError) and hex_errno:\n                errno_num = int(hex_errno, 16)\n                child_exec_never_called = (err_msg == \"noexec\")\n                if child_exec_never_called:\n                    err_msg = \"\"\n                    # The error must be from chdir(cwd).\n                    err_filename = cwd\n                else:\n                    err_filename = orig_executable\n                if errno_num != 0:\n                    err_msg = os.strerror(errno_num)\n>               raise child_exception_type(errno_num, err_msg, err_filename)\nE               FileNotFoundError: [Errno 2] No such file or directory: 'git-annex-remote-datalad-archives'\n\n../../../../.local/share/uv/python/cpython-3.9.21-linux-x86_64-gnu/lib/python3.9/subprocess.py:1837: FileNotFoundError\n_________________ test_script_shims[git-annex-remote-datalad] __________________\n\nscript = 'git-annex-remote-datalad'\n\n    @pytest.mark.parametrize(\n        \"script\",\n        [\n            'datalad',\n            'git-annex-remote-datalad-archives',\n            'git-annex-remote-datalad',\n        ]\n    )\n    def test_script_shims(script):\n        runner = Runner()\n        if not on_windows:\n    \n            from shutil import which\n            which(script)\n    \n        # and let's check that it is our script\n>       out = runner.run([script, '--version'], protocol=StdOutErrCapture)\n\nr2e_tests/test_1.py:287: \n_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ \ndatalad/runner/runner.py:225: in run\n    results_or_iterator = threaded_runner.run()\ndatalad/runner/nonasyncrunner.py:347: in run\n    return self._locked_run()\ndatalad/runner/nonasyncrunner.py:407: in _locked_run\n    self.process = Popen(self.cmd, **kwargs)         # nosec\n../../../../.local/share/uv/python/cpython-3.9.21-linux-x86_64-gnu/lib/python3.9/subprocess.py:951: in __init__\n    self._execute_child(args, executable, preexec_fn, close_fds,\n_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ \n\nself = <Popen: returncode: 255 args: ['git-annex-remote-datalad', '--version']>\nargs = ['git-annex-remote-datalad', '--version']\nexecutable = b'git-annex-remote-datalad', preexec_fn = None, close_fds = True\npass_fds = (), cwd = None, env = None, startupinfo = None, creationflags = 0\nshell = False, p2cread = -1, p2cwrite = -1, c2pread = 12, c2pwrite = 13\nerrread = 14, errwrite = 15, restore_signals = True, gid = None, gids = None\nuid = None, umask = -1, start_new_session = False\n\n    def _execute_child(self, args, executable, preexec_fn, close_fds,\n                       pass_fds, cwd, env,\n                       startupinfo, creationflags, shell,\n                       p2cread, p2cwrite,\n                       c2pread, c2pwrite,\n                       errread, errwrite,\n                       restore_signals,\n                       gid, gids, uid, umask,\n                       start_new_session):\n        \"\"\"Execute program (POSIX version)\"\"\"\n    \n        if isinstance(args, (str, bytes)):\n            args = [args]\n        elif isinstance(args, os.PathLike):\n            if shell:\n                raise TypeError('path-like args is not allowed when '\n                                'shell is true')\n            args = [args]\n        else:\n            args = list(args)\n    \n        if shell:\n            # On Android the default shell is at '/system/bin/sh'.\n            unix_shell = ('/system/bin/sh' if\n                      hasattr(sys, 'getandroidapilevel') else '/bin/sh')\n            args = [unix_shell, \"-c\"] + args\n            if executable:\n                args[0] = executable\n    \n        if executable is None:\n            executable = args[0]\n    \n        sys.audit(\"subprocess.Popen\", executable, args, cwd, env)\n    \n        if (_USE_POSIX_SPAWN\n                and os.path.dirname(executable)\n                and preexec_fn is None\n                and not close_fds\n                and not pass_fds\n                and cwd is None\n                and (p2cread == -1 or p2cread > 2)\n                and (c2pwrite == -1 or c2pwrite > 2)\n                and (errwrite == -1 or errwrite > 2)\n                and not start_new_session\n                and gid is None\n                and gids is None\n                and uid is None\n                and umask < 0):\n            self._posix_spawn(args, executable, env, restore_signals,\n                              p2cread, p2cwrite,\n                              c2pread, c2pwrite,\n                              errread, errwrite)\n            return\n    \n        orig_executable = executable\n    \n        # For transferring possible exec failure from child to parent.\n        # Data format: \"exception name:hex errno:description\"\n        # Pickle is not used; it is complex and involves memory allocation.\n        errpipe_read, errpipe_write = os.pipe()\n        # errpipe_write must not be in the standard io 0, 1, or 2 fd range.\n        low_fds_to_close = []\n        while errpipe_write < 3:\n            low_fds_to_close.append(errpipe_write)\n            errpipe_write = os.dup(errpipe_write)\n        for low_fd in low_fds_to_close:\n            os.close(low_fd)\n        try:\n            try:\n                # We must avoid complex work that could involve\n                # malloc or free in the child process to avoid\n                # potential deadlocks, thus we do all this here.\n                # and pass it to fork_exec()\n    \n                if env is not None:\n                    env_list = []\n                    for k, v in env.items():\n                        k = os.fsencode(k)\n                        if b'=' in k:\n                            raise ValueError(\"illegal environment variable name\")\n                        env_list.append(k + b'=' + os.fsencode(v))\n                else:\n                    env_list = None  # Use execv instead of execve.\n                executable = os.fsencode(executable)\n                if os.path.dirname(executable):\n                    executable_list = (executable,)\n                else:\n                    # This matches the behavior of os._execvpe().\n                    executable_list = tuple(\n                        os.path.join(os.fsencode(dir), executable)\n                        for dir in os.get_exec_path(env))\n                fds_to_keep = set(pass_fds)\n                fds_to_keep.add(errpipe_write)\n                self.pid = _posixsubprocess.fork_exec(\n                        args, executable_list,\n                        close_fds, tuple(sorted(map(int, fds_to_keep))),\n                        cwd, env_list,\n                        p2cread, p2cwrite, c2pread, c2pwrite,\n                        errread, errwrite,\n                        errpipe_read, errpipe_write,\n                        restore_signals, start_new_session,\n                        gid, gids, uid, umask,\n                        preexec_fn)\n                self._child_created = True\n            finally:\n                # be sure the FD is closed no matter what\n                os.close(errpipe_write)\n    \n            self._close_pipe_fds(p2cread, p2cwrite,\n                                 c2pread, c2pwrite,\n                                 errread, errwrite)\n    \n            # Wait for exec to fail or succeed; possibly raising an\n            # exception (limited in size)\n            errpipe_data = bytearray()\n            while True:\n                part = os.read(errpipe_read, 50000)\n                errpipe_data += part\n                if not part or len(errpipe_data) > 50000:\n                    break\n        finally:\n            # be sure the FD is closed no matter what\n            os.close(errpipe_read)\n    \n        if errpipe_data:\n            try:\n                pid, sts = os.waitpid(self.pid, 0)\n                if pid == self.pid:\n                    self._handle_exitstatus(sts)\n                else:\n                    self.returncode = sys.maxsize\n            except ChildProcessError:\n                pass\n    \n            try:\n                exception_name, hex_errno, err_msg = (\n                        errpipe_data.split(b':', 2))\n                # The encoding here should match the encoding\n                # written in by the subprocess implementations\n                # like _posixsubprocess\n                err_msg = err_msg.decode()\n            except ValueError:\n                exception_name = b'SubprocessError'\n                hex_errno = b'0'\n                err_msg = 'Bad exception data from child: {!r}'.format(\n                              bytes(errpipe_data))\n            child_exception_type = getattr(\n                    builtins, exception_name.decode('ascii'),\n                    SubprocessError)\n            if issubclass(child_exception_type, OSError) and hex_errno:\n                errno_num = int(hex_errno, 16)\n                child_exec_never_called = (err_msg == \"noexec\")\n                if child_exec_never_called:\n                    err_msg = \"\"\n                    # The error must be from chdir(cwd).\n                    err_filename = cwd\n                else:\n                    err_filename = orig_executable\n                if errno_num != 0:\n                    err_msg = os.strerror(errno_num)\n>               raise child_exception_type(errno_num, err_msg, err_filename)\nE               FileNotFoundError: [Errno 2] No such file or directory: 'git-annex-remote-datalad'\n\n../../../../.local/share/uv/python/cpython-3.9.21-linux-x86_64-gnu/lib/python3.9/subprocess.py:1837: FileNotFoundError\n______________________________ test_cfg_override _______________________________\n\npath = '/tmp/datalad_temp_test_cfg_overridezjkmbhoe'\n\n    @with_tempfile(mkdir=True)\n    def test_cfg_override(path=None):\n        with chpwd(path):\n            # use 'wtf' to dump the config\n            # should be rewritten to use `configuration`\n            cmd = ['datalad', 'wtf', '-S', 'configuration', '-s', 'some']\n            # control\n>           out = Runner().run(cmd, protocol=StdOutErrCapture)['stdout']\n\nr2e_tests/test_1.py:307: \n_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ \ndatalad/runner/runner.py:225: in run\n    results_or_iterator = threaded_runner.run()\ndatalad/runner/nonasyncrunner.py:347: in run\n    return self._locked_run()\ndatalad/runner/nonasyncrunner.py:407: in _locked_run\n    self.process = Popen(self.cmd, **kwargs)         # nosec\n../../../../.local/share/uv/python/cpython-3.9.21-linux-x86_64-gnu/lib/python3.9/subprocess.py:951: in __init__\n    self._execute_child(args, executable, preexec_fn, close_fds,\n_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ \n\nself = <Popen: returncode: 255 args: ['datalad', 'wtf', '-S', 'configuration', '-s'...>\nargs = ['datalad', 'wtf', '-S', 'configuration', '-s', 'some']\nexecutable = b'datalad', preexec_fn = None, close_fds = True, pass_fds = ()\ncwd = None, env = None, startupinfo = None, creationflags = 0, shell = False\np2cread = -1, p2cwrite = -1, c2pread = 12, c2pwrite = 13, errread = 14\nerrwrite = 15, restore_signals = True, gid = None, gids = None, uid = None\numask = -1, start_new_session = False\n\n    def _execute_child(self, args, executable, preexec_fn, close_fds,\n                       pass_fds, cwd, env,\n                       startupinfo, creationflags, shell,\n                       p2cread, p2cwrite,\n                       c2pread, c2pwrite,\n                       errread, errwrite,\n                       restore_signals,\n                       gid, gids, uid, umask,\n                       start_new_session):\n        \"\"\"Execute program (POSIX version)\"\"\"\n    \n        if isinstance(args, (str, bytes)):\n            args = [args]\n        elif isinstance(args, os.PathLike):\n            if shell:\n                raise TypeError('path-like args is not allowed when '\n                                'shell is true')\n            args = [args]\n        else:\n            args = list(args)\n    \n        if shell:\n            # On Android the default shell is at '/system/bin/sh'.\n            unix_shell = ('/system/bin/sh' if\n                      hasattr(sys, 'getandroidapilevel') else '/bin/sh')\n            args = [unix_shell, \"-c\"] + args\n            if executable:\n                args[0] = executable\n    \n        if executable is None:\n            executable = args[0]\n    \n        sys.audit(\"subprocess.Popen\", executable, args, cwd, env)\n    \n        if (_USE_POSIX_SPAWN\n                and os.path.dirname(executable)\n                and preexec_fn is None\n                and not close_fds\n                and not pass_fds\n                and cwd is None\n                and (p2cread == -1 or p2cread > 2)\n                and (c2pwrite == -1 or c2pwrite > 2)\n                and (errwrite == -1 or errwrite > 2)\n                and not start_new_session\n                and gid is None\n                and gids is None\n                and uid is None\n                and umask < 0):\n            self._posix_spawn(args, executable, env, restore_signals,\n                              p2cread, p2cwrite,\n                              c2pread, c2pwrite,\n                              errread, errwrite)\n            return\n    \n        orig_executable = executable\n    \n        # For transferring possible exec failure from child to parent.\n        # Data format: \"exception name:hex errno:description\"\n        # Pickle is not used; it is complex and involves memory allocation.\n        errpipe_read, errpipe_write = os.pipe()\n        # errpipe_write must not be in the standard io 0, 1, or 2 fd range.\n        low_fds_to_close = []\n        while errpipe_write < 3:\n            low_fds_to_close.append(errpipe_write)\n            errpipe_write = os.dup(errpipe_write)\n        for low_fd in low_fds_to_close:\n            os.close(low_fd)\n        try:\n            try:\n                # We must avoid complex work that could involve\n                # malloc or free in the child process to avoid\n                # potential deadlocks, thus we do all this here.\n                # and pass it to fork_exec()\n    \n                if env is not None:\n                    env_list = []\n                    for k, v in env.items():\n                        k = os.fsencode(k)\n                        if b'=' in k:\n                            raise ValueError(\"illegal environment variable name\")\n                        env_list.append(k + b'=' + os.fsencode(v))\n                else:\n                    env_list = None  # Use execv instead of execve.\n                executable = os.fsencode(executable)\n                if os.path.dirname(executable):\n                    executable_list = (executable,)\n                else:\n                    # This matches the behavior of os._execvpe().\n                    executable_list = tuple(\n                        os.path.join(os.fsencode(dir), executable)\n                        for dir in os.get_exec_path(env))\n                fds_to_keep = set(pass_fds)\n                fds_to_keep.add(errpipe_write)\n                self.pid = _posixsubprocess.fork_exec(\n                        args, executable_list,\n                        close_fds, tuple(sorted(map(int, fds_to_keep))),\n                        cwd, env_list,\n                        p2cread, p2cwrite, c2pread, c2pwrite,\n                        errread, errwrite,\n                        errpipe_read, errpipe_write,\n                        restore_signals, start_new_session,\n                        gid, gids, uid, umask,\n                        preexec_fn)\n                self._child_created = True\n            finally:\n                # be sure the FD is closed no matter what\n                os.close(errpipe_write)\n    \n            self._close_pipe_fds(p2cread, p2cwrite,\n                                 c2pread, c2pwrite,\n                                 errread, errwrite)\n    \n            # Wait for exec to fail or succeed; possibly raising an\n            # exception (limited in size)\n            errpipe_data = bytearray()\n            while True:\n                part = os.read(errpipe_read, 50000)\n                errpipe_data += part\n                if not part or len(errpipe_data) > 50000:\n                    break\n        finally:\n            # be sure the FD is closed no matter what\n            os.close(errpipe_read)\n    \n        if errpipe_data:\n            try:\n                pid, sts = os.waitpid(self.pid, 0)\n                if pid == self.pid:\n                    self._handle_exitstatus(sts)\n                else:\n                    self.returncode = sys.maxsize\n            except ChildProcessError:\n                pass\n    \n            try:\n                exception_name, hex_errno, err_msg = (\n                        errpipe_data.split(b':', 2))\n                # The encoding here should match the encoding\n                # written in by the subprocess implementations\n                # like _posixsubprocess\n                err_msg = err_msg.decode()\n            except ValueError:\n                exception_name = b'SubprocessError'\n                hex_errno = b'0'\n                err_msg = 'Bad exception data from child: {!r}'.format(\n                              bytes(errpipe_data))\n            child_exception_type = getattr(\n                    builtins, exception_name.decode('ascii'),\n                    SubprocessError)\n            if issubclass(child_exception_type, OSError) and hex_errno:\n                errno_num = int(hex_errno, 16)\n                child_exec_never_called = (err_msg == \"noexec\")\n                if child_exec_never_called:\n                    err_msg = \"\"\n                    # The error must be from chdir(cwd).\n                    err_filename = cwd\n                else:\n                    err_filename = orig_executable\n                if errno_num != 0:\n                    err_msg = os.strerror(errno_num)\n>               raise child_exception_type(errno_num, err_msg, err_filename)\nE               FileNotFoundError: [Errno 2] No such file or directory: 'datalad'\n\n../../../../.local/share/uv/python/cpython-3.9.21-linux-x86_64-gnu/lib/python3.9/subprocess.py:1837: FileNotFoundError\n_______________________________ test_completion ________________________________\n\nout_fn = '/tmp/datalad_temp_test_completionu6y8rrjd'\n\n    @with_tempfile\n    def test_completion(out_fn=None):\n        skip_if_no_module('argcomplete')\n    \n        from datalad.cmd import WitlessRunner\n        runner = WitlessRunner()\n    \n        def get_completions(s: str, expected) -> list:\n            \"\"\"Run 'datalad' external command and collect completions\n    \n            Parameters\n            ----------\n            s: str\n              what to append to 'datalad ' invocation\n            expected: iterable of str\n              What entries to expect - would raise AssertionError if any is\n              not present in output\n            exit_code: int, optional\n              If incomplete/malformed we seems to get 2, most frequently used\n              so default\n    \n            Returns\n            -------\n            list of str\n              Entries output\n            \"\"\"\n            if os.path.exists(out_fn):  # reuse but ensure it is gone\n                os.unlink(out_fn)\n            comp_line = f'datalad {s}'\n            runner.run(\n                comp_line.split(' '),\n                env=dict(os.environ,\n                         _ARGCOMPLETE='1',\n                         _ARGCOMPLETE_STDOUT_FILENAME=out_fn,\n                         COMP_LINE=comp_line,\n                         # without -1 seems to get \"finished completion\", someone can investigate more\n                         COMP_POINT=str(len(comp_line)-1),  # always at the end ATM\n                         ))\n            with open(out_fn, 'rb') as f:\n                entries = f.read().split(b'\\x0b')\n                entries = [e.decode() for e in entries]\n            diff = set(expected).difference(entries)\n            if diff:\n                raise AssertionError(\n                    f\"Entries {sorted(diff)} were expected but not found in the completion output: {entries}\"\n                )\n            return entries  # for extra analyzes if so desired\n    \n        all_commands = get_all_commands()\n>       get_completions('i', {'install'})\n\nr2e_tests/test_1.py:433: \n_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ \nr2e_tests/test_1.py:413: in get_completions\n    runner.run(\ndatalad/runner/runner.py:225: in run\n    results_or_iterator = threaded_runner.run()\ndatalad/runner/nonasyncrunner.py:347: in run\n    return self._locked_run()\ndatalad/runner/nonasyncrunner.py:407: in _locked_run\n    self.process = Popen(self.cmd, **kwargs)         # nosec\n../../../../.local/share/uv/python/cpython-3.9.21-linux-x86_64-gnu/lib/python3.9/subprocess.py:951: in __init__\n    self._execute_child(args, executable, preexec_fn, close_fds,\n_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ \n\nself = <Popen: returncode: 255 args: ['datalad', 'i']>, args = ['datalad', 'i']\nexecutable = b'datalad', preexec_fn = None, close_fds = True, pass_fds = ()\ncwd = None\nenv = {'ANTHROPIC_API_KEY': 'sk-ant-api03-kztdHBP1RruInQ2uzw8m4wJLbhPHBrb-qHX2mhAKmwoMFvM_dI2oWb5V6JtS45MScuW5CCjPusIrt9Zatx...3b4166bef66ef252935c/server/bin/helpers/browser.sh', 'COHERE_API_KEY': '66TDU3MmN5ELJT1NHWklGG2PJjK2zwVSQbVq42ol', ...}\nstartupinfo = None, creationflags = 0, shell = False, p2cread = -1\np2cwrite = -1, c2pread = -1, c2pwrite = -1, errread = -1, errwrite = -1\nrestore_signals = True, gid = None, gids = None, uid = None, umask = -1\nstart_new_session = False\n\n    def _execute_child(self, args, executable, preexec_fn, close_fds,\n                       pass_fds, cwd, env,\n                       startupinfo, creationflags, shell,\n                       p2cread, p2cwrite,\n                       c2pread, c2pwrite,\n                       errread, errwrite,\n                       restore_signals,\n                       gid, gids, uid, umask,\n                       start_new_session):\n        \"\"\"Execute program (POSIX version)\"\"\"\n    \n        if isinstance(args, (str, bytes)):\n            args = [args]\n        elif isinstance(args, os.PathLike):\n            if shell:\n                raise TypeError('path-like args is not allowed when '\n                                'shell is true')\n            args = [args]\n        else:\n            args = list(args)\n    \n        if shell:\n            # On Android the default shell is at '/system/bin/sh'.\n            unix_shell = ('/system/bin/sh' if\n                      hasattr(sys, 'getandroidapilevel') else '/bin/sh')\n            args = [unix_shell, \"-c\"] + args\n            if executable:\n                args[0] = executable\n    \n        if executable is None:\n            executable = args[0]\n    \n        sys.audit(\"subprocess.Popen\", executable, args, cwd, env)\n    \n        if (_USE_POSIX_SPAWN\n                and os.path.dirname(executable)\n                and preexec_fn is None\n                and not close_fds\n                and not pass_fds\n                and cwd is None\n                and (p2cread == -1 or p2cread > 2)\n                and (c2pwrite == -1 or c2pwrite > 2)\n                and (errwrite == -1 or errwrite > 2)\n                and not start_new_session\n                and gid is None\n                and gids is None\n                and uid is None\n                and umask < 0):\n            self._posix_spawn(args, executable, env, restore_signals,\n                              p2cread, p2cwrite,\n                              c2pread, c2pwrite,\n                              errread, errwrite)\n            return\n    \n        orig_executable = executable\n    \n        # For transferring possible exec failure from child to parent.\n        # Data format: \"exception name:hex errno:description\"\n        # Pickle is not used; it is complex and involves memory allocation.\n        errpipe_read, errpipe_write = os.pipe()\n        # errpipe_write must not be in the standard io 0, 1, or 2 fd range.\n        low_fds_to_close = []\n        while errpipe_write < 3:\n            low_fds_to_close.append(errpipe_write)\n            errpipe_write = os.dup(errpipe_write)\n        for low_fd in low_fds_to_close:\n            os.close(low_fd)\n        try:\n            try:\n                # We must avoid complex work that could involve\n                # malloc or free in the child process to avoid\n                # potential deadlocks, thus we do all this here.\n                # and pass it to fork_exec()\n    \n                if env is not None:\n                    env_list = []\n                    for k, v in env.items():\n                        k = os.fsencode(k)\n                        if b'=' in k:\n                            raise ValueError(\"illegal environment variable name\")\n                        env_list.append(k + b'=' + os.fsencode(v))\n                else:\n                    env_list = None  # Use execv instead of execve.\n                executable = os.fsencode(executable)\n                if os.path.dirname(executable):\n                    executable_list = (executable,)\n                else:\n                    # This matches the behavior of os._execvpe().\n                    executable_list = tuple(\n                        os.path.join(os.fsencode(dir), executable)\n                        for dir in os.get_exec_path(env))\n                fds_to_keep = set(pass_fds)\n                fds_to_keep.add(errpipe_write)\n                self.pid = _posixsubprocess.fork_exec(\n                        args, executable_list,\n                        close_fds, tuple(sorted(map(int, fds_to_keep))),\n                        cwd, env_list,\n                        p2cread, p2cwrite, c2pread, c2pwrite,\n                        errread, errwrite,\n                        errpipe_read, errpipe_write,\n                        restore_signals, start_new_session,\n                        gid, gids, uid, umask,\n                        preexec_fn)\n                self._child_created = True\n            finally:\n                # be sure the FD is closed no matter what\n                os.close(errpipe_write)\n    \n            self._close_pipe_fds(p2cread, p2cwrite,\n                                 c2pread, c2pwrite,\n                                 errread, errwrite)\n    \n            # Wait for exec to fail or succeed; possibly raising an\n            # exception (limited in size)\n            errpipe_data = bytearray()\n            while True:\n                part = os.read(errpipe_read, 50000)\n                errpipe_data += part\n                if not part or len(errpipe_data) > 50000:\n                    break\n        finally:\n            # be sure the FD is closed no matter what\n            os.close(errpipe_read)\n    \n        if errpipe_data:\n            try:\n                pid, sts = os.waitpid(self.pid, 0)\n                if pid == self.pid:\n                    self._handle_exitstatus(sts)\n                else:\n                    self.returncode = sys.maxsize\n            except ChildProcessError:\n                pass\n    \n            try:\n                exception_name, hex_errno, err_msg = (\n                        errpipe_data.split(b':', 2))\n                # The encoding here should match the encoding\n                # written in by the subprocess implementations\n                # like _posixsubprocess\n                err_msg = err_msg.decode()\n            except ValueError:\n                exception_name = b'SubprocessError'\n                hex_errno = b'0'\n                err_msg = 'Bad exception data from child: {!r}'.format(\n                              bytes(errpipe_data))\n            child_exception_type = getattr(\n                    builtins, exception_name.decode('ascii'),\n                    SubprocessError)\n            if issubclass(child_exception_type, OSError) and hex_errno:\n                errno_num = int(hex_errno, 16)\n                child_exec_never_called = (err_msg == \"noexec\")\n                if child_exec_never_called:\n                    err_msg = \"\"\n                    # The error must be from chdir(cwd).\n                    err_filename = cwd\n                else:\n                    err_filename = orig_executable\n                if errno_num != 0:\n                    err_msg = os.strerror(errno_num)\n>               raise child_exception_type(errno_num, err_msg, err_filename)\nE               FileNotFoundError: [Errno 2] No such file or directory: 'datalad'\n\n../../../../.local/share/uv/python/cpython-3.9.21-linux-x86_64-gnu/lib/python3.9/subprocess.py:1837: FileNotFoundError\n==================================== PASSES ====================================\n_______________________ test_usage_on_insufficient_args ________________________\n----------------------------- Captured stderr call -----------------------------\n[ERROR] InsufficientArgumentsError(Please provide at least a source or a path) (InsufficientArgumentsError) \n------------------------------ Captured log call -------------------------------\nERROR    datalad.cli:main.py:204 InsufficientArgumentsError(Please provide at least a source or a path) (InsufficientArgumentsError)\n______________________ test_subcmd_usage_on_unknown_args _______________________\n----------------------------- Captured stderr call -----------------------------\n[ERROR] unknown argument: --murks \n------------------------------ Captured log call -------------------------------\nERROR    datalad.cli:main.py:103 unknown argument: --murks\n________________________ test_conflicting_short_option _________________________\n----------------------------- Captured stderr call -----------------------------\n[INFO] scanning for unlocked files (this may take some time) \n------------------------------ Captured log call -------------------------------\nINFO     datalad.annex:annexrepo.py:3975 scanning for unlocked files (this may take some time)\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\n_________________________ test_incorrect_cfg_override __________________________\n----------------------------- Captured stderr call -----------------------------\n[ERROR] Configuration override without section/variable or unset marker or value assignment (must be '(:section.variable|section.variable=value)'): ['some'] \n[ERROR] Configuration override without section/variable or unset marker or value assignment (must be '(:section.variable|section.variable=value)'): ['some='] \n[ERROR] Configuration override without section/variable or unset marker or value assignment (must be '(:section.variable|section.variable=value)'): ['some.var'] \n[ERROR] Configuration override without section/variable or unset marker or value assignment (must be '(:section.variable|section.variable=value)'): ['some.var='] \n------------------------------ Captured log call -------------------------------\nERROR    datalad.cli.helpers:helpers.py:266 Configuration override without section/variable or unset marker or value assignment (must be '(:section.variable|section.variable=value)'): ['some']\nERROR    datalad.cli.helpers:helpers.py:266 Configuration override without section/variable or unset marker or value assignment (must be '(:section.variable|section.variable=value)'): ['some=']\nERROR    datalad.cli.helpers:helpers.py:266 Configuration override without section/variable or unset marker or value assignment (must be '(:section.variable|section.variable=value)'): ['some.var']\nERROR    datalad.cli.helpers:helpers.py:266 Configuration override without section/variable or unset marker or value assignment (must be '(:section.variable|section.variable=value)'): ['some.var=']\n_______________________________ test_librarymode _______________________________\n----------------------------- Captured stdout call -----------------------------\ncreate(ok): . (dataset)\n----------------------------- Captured stderr call -----------------------------\n[INFO] scanning for unlocked files (this may take some time) \n------------------------------ Captured log call -------------------------------\nINFO     datalad.annex:annexrepo.py:3975 scanning for unlocked files (this may take some time)\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\nINFO     datalad:log.py:432 Clear progress bars\nINFO     datalad:log.py:432 Refresh progress bars\n=========================== short test summary info ============================\nPASSED r2e_tests/test_1.py::test_version\nPASSED r2e_tests/test_1.py::test_dashh\nPASSED r2e_tests/test_1.py::test_dashh_clone\nPASSED r2e_tests/test_1.py::test_usage_on_insufficient_args\nPASSED r2e_tests/test_1.py::test_subcmd_usage_on_unknown_args\nPASSED r2e_tests/test_1.py::test_combined_short_option\nPASSED r2e_tests/test_1.py::test_conflicting_short_option\nPASSED r2e_tests/test_1.py::test_incorrect_option[opts0-error: (invalid|too few arguments|unrecognized argument)]\nPASSED r2e_tests/test_1.py::test_incorrect_option[opts1-error: (invalid|too few arguments|unrecognized argument)]\nPASSED r2e_tests/test_1.py::test_incorrect_option[opts2-error: (invalid|too few arguments|unrecognized argument)]\nPASSED r2e_tests/test_1.py::test_incorrect_option[opts3-error: (invalid|too few arguments|unrecognized argument)]\nPASSED r2e_tests/test_1.py::test_incorrect_cfg_override\nPASSED r2e_tests/test_1.py::test_librarymode\nSKIPPED [1] .venv/lib/python3.9/site-packages/_pytest/unittest.py:385: Could not determine terminal size, skipping the rest of the test\nFAILED r2e_tests/test_1.py::test_run_exit_code - assert 1 == 3\nFAILED r2e_tests/test_1.py::test_script_shims[datalad] - FileNotFoundError: [...\nFAILED r2e_tests/test_1.py::test_script_shims[git-annex-remote-datalad-archives]\nFAILED r2e_tests/test_1.py::test_script_shims[git-annex-remote-datalad] - Fil...\nFAILED r2e_tests/test_1.py::test_cfg_override - FileNotFoundError: [Errno 2] ...\nFAILED r2e_tests/test_1.py::test_completion - FileNotFoundError: [Errno 2] No...\n=================== 6 failed, 13 passed, 1 skipped in 2.75s ====================\n",
    "old_commit_res_stderr": ""
}
changelog.d/pr-7641.md datalad/cli/main.py datalad/cli/tests/test_main.py
We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
[ISSUE]
**Title:** `datalad run` Exits with Incorrect Non-Zero Exit Code on Failure

**Description:**
When executing the `datalad run` command with a task that is expected to fail with a specific non-zero exit code, the command does not exit with the intended code. Instead, it exits with a different non-zero exit code, leading to inaccurate error reporting and test failures.

**Example Code:**
```python
from datalad.api import run_main
from datalad.utils import create
from datalad.support.gitrepo import chpwd

def execute_run_command():
    create(dataset='/tmp/test_dataset', annex=False)
    with chpwd('/tmp/test_dataset'):
        run_main(['run', '--explicit', 'exit 3'], exit_code=3)
```

**Expected Behavior:**
The `datalad run` command should exit with the same non-zero exit code (e.g., `3`) that the underlying command returns, accurately reflecting the failure reason.

**Actual Behavior:**
Instead of exiting with the specified exit code `3`, the command exits with a different non-zero exit code (e.g., `1`). This causes tests that rely on the specific exit code to fail, as the expected and actual exit codes do not match.

[/ISSUE]
--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.

--- BEGIN FILE ---
```
### datalad/cli/main.py
# emacs: -*- mode: python; py-indent-offset: 4; tab-width: 4; indent-tabs-mode: nil -*-
# ex: set sts=4 ts=4 sw=4 et:
# ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ##
#
#   See COPYING file distributed along with the datalad package for the
#   copyright and license terms.
#
# ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ##
"""This is the main() CLI entryproint"""

# It should start-up and run as fast as possible for a responsive CLI.

# Imports are done inline and as late as possible to avoid paying for
# an unconditional commulative overhead that is only actually needed
# in some special cases.

__docformat__ = 'restructuredtext'

import logging

lgr = logging.getLogger('datalad.cli')

lgr.log(5, "Importing cli.main")

import os
import sys

import datalad

from .parser import setup_parser

# TODO cross-check with unconditional imports in .parser
# special case imports
#   from .helpers import _fix_datalad_ri
#   import platform
#   from .helpers import _parse_overrides_from_cmdline
#   from datalad.utils import chpwd
#   from .utils import setup_exceptionhook
#   from datalad.support.exceptions import ...

# unconditional imports, no meaningful functionality without them
#   from .parser import setup_parser


def _on_msys_tainted_paths():
    """This duplicates datalad.utils.on_msys_tainted_paths

    But it does it while minimizing runtime penalties on all irrelevant
    systems.
    """
    if os.environ.get('MSYSTEM', '')[:4] not in ('MSYS', 'MING'):
        return False
    if 'MSYS_NO_PATHCONV' in os.environ:
        return False
    import platform
    if platform.system().lower() != 'windows':
        return False
    return True


def main(args=sys.argv):
    """Main CLI entrypoint"""
    lgr.log(5, "Starting main(%r)", args)
    # record that we came in via the cmdline
    datalad.__api = 'cmdline'
    completing = "_ARGCOMPLETE" in os.environ
    if completing and 'COMP_LINE' in os.environ:
        import shlex

        # TODO support posix=False too?
        args = shlex.split(os.environ['COMP_LINE']) or args

    if _on_msys_tainted_paths():
        # Possibly present DataLadRIs were stripped of a leading /
        from .helpers import _fix_datalad_ri
        args = [_fix_datalad_ri(s) for s in args]

    from datalad.support.entrypoints import load_extensions

    # load extensions requested by configuration
    # analog to what coreapi is doing for a Python session
    # importantly, load them prior to parser construction, such
    # that CLI tuning is also within reach for extensions
    load_extensions()

    # PYTHON_ARGCOMPLETE_OK
    # TODO possibly construct a dedicated parser just for autocompletion
    # rather than lobotomizing the normal one
    parser = setup_parser(args, completing=completing)
    try:
        import argcomplete
        argcomplete.autocomplete(parser)
    except ImportError:
        pass

    # parse cmd args
    lgr.debug("Parsing known args among %r", args)
    cmdlineargs, unparsed_args = parser.parse_known_args(args[1:])
    # did the parser tell us what command to run?
    has_func = hasattr(cmdlineargs, 'func') and cmdlineargs.func is not None
    if unparsed_args:
        if has_func:
            lgr.error('unknown argument%s: %s',
                's' if len(unparsed_args) > 1 else '',
                unparsed_args if len(unparsed_args) > 1 else unparsed_args[0],
            )
            cmdlineargs.subparser.print_usage()
            sys.exit(1)
        else:
            # store all unparsed arguments
            cmdlineargs.datalad_unparsed_args = unparsed_args

    # pull config overrides from cmdline args and put in effect
    if cmdlineargs.cfg_overrides is not None:
        from .helpers import _parse_overrides_from_cmdline
        datalad.cfg.overrides.update(
            _parse_overrides_from_cmdline(cmdlineargs)
        )
        # enable overrides
        datalad.cfg.reload(force=True)
        # try loading extensions again, in case the configuration
        # added new ones to consider
        load_extensions()

    if 'datalad.runtime.librarymode' in datalad.cfg:
        datalad.enable_librarymode()

    if cmdlineargs.change_path is not None:
        from datalad.utils import chpwd
        for path in cmdlineargs.change_path:
            chpwd(path)

    # check argparse could determine what commands needs to be executed
    if not has_func:
        # just let argparser spit out its error, since there is smth wrong
        parser.parse_args(args)
        # if that one didn't puke -- we should
        parser.print_usage()
        lgr.error("Please specify the command")
        # matches exit code for InsufficientArgumentsError
        sys.exit(2)

    _run(cmdlineargs)


def _run(namespace):
    """Execute a CLI operation

    Depending on CLI debugging options the CLI operation is executed
    in a debug harness or an exception handler.

    Parameters
    ----------
    namespace: Namespace
      Object returned by `ArgumentParser.parse_args()` with fully
      populated and validated CLI command and arguments.

    Raises
    ------
    SystemExit
      When the CLI completed without error (exit 0).
    """
    # execute the command, either with a debugger catching
    # a crash, or with a simplistic exception handler.
    # note that result rendering is happening in the
    # execution handler, when the command-generator is unwound
    ret = _run_with_debugger(namespace) \
        if namespace.common_debug or namespace.common_idebug \
        else _run_with_exception_handler(namespace)

    # all good, not strictly needed, but makes internal testing easier
    sys.exit(0)


def _run_with_debugger(cmdlineargs):
    """Execute the command and drop into debugger if it crashes"""
    from .utils import setup_exceptionhook

    # so we could see/stop clearly at the point of failure
    setup_exceptionhook(ipython=cmdlineargs.common_idebug)
    return cmdlineargs.func(cmdlineargs)


def _run_with_exception_handler(cmdlineargs):
    """Execute the command and perform some reporting
    normalization if it crashes, but otherwise just let it go"""
    # otherwise - guard and only log the summary. Postmortem is not
    # as convenient if being caught in this ultimate except
    try:
        return cmdlineargs.func(cmdlineargs)
    # catch BaseException for KeyboardInterrupt
    except BaseException as exc:
        from datalad.support.exceptions import (
            CapturedException,
            CommandError,
            IncompleteResultsError,
            InsufficientArgumentsError,
        )
        ce = CapturedException(exc)
        # we crashed, it has got to be non-zero for starters
        exit_code = 1
        if isinstance(exc, InsufficientArgumentsError):
            # if the func reports inappropriate usage, give help output
            lgr.error('%s (%s)', ce, exc.__class__.__name__)
            cmdlineargs.subparser.print_usage(sys.stderr)
            exit_code = 2
        elif isinstance(exc, IncompleteResultsError):
            # in general we do not want to see the error again, but
            # present in debug output
            lgr.debug('could not perform all requested actions: %s', ce)
        elif isinstance(exc, CommandError):
            exit_code = _communicate_commanderror(exc) or exit_code
        elif isinstance(exc, KeyboardInterrupt):
            from datalad.ui import ui
            ui.error("\nInterrupted by user while doing magic: %s" % ce)
            exit_code = 3
        else:
            # some unforeseen problem
            lgr.error('%s', ce.format_with_cause())
        sys.exit(exit_code)


def _communicate_commanderror(exc):
    """Behave as if the command ran directly"""
    exc_msg = exc.to_str(include_output=False)
    if exc_msg:
        msg = exc_msg.encode() if isinstance(exc_msg, str) else exc_msg
        os.write(2, msg + b"\n")
    # push any captured output to the respective streams
    for out, stream in ((exc.stdout, 1), (exc.stderr, 2)):
        if out:
            os.write(stream,
                     out.encode() if isinstance(out, str) else out)
    # pass on exit code
    return exc.code


lgr.log(5, "Done importing cli.main")

```
--- END FILE ---

Please first localize the bug based on the issue statement, and then generate *SEARCH/REPLACE* edits to fix the issue.

Every *SEARCH/REPLACE* edit must use this format:
1. The file path
2. The start of search block: <<<<<<< SEARCH
3. A contiguous chunk of lines to search for in the existing source code
4. The dividing line: =======
5. The lines to replace into the source code
6. The end of the replace block: >>>>>>> REPLACE

Here is an example:

```python
### mathweb/flask/app.py
<<<<<<< SEARCH
from flask import Flask
=======
import math
from flask import Flask
>>>>>>> REPLACE
```
</solution>

Please note that the *SEARCH/REPLACE* edit REQUIRES PROPER INDENTATION. If you would like to add the line '        print(x)', you must fully write that out, with all those spaces before the code!
Wrap each *SEARCH/REPLACE* edit in a code block as shown in the example above. If you have multiple *SEARCH/REPLACE* edits, use a separate code block for each one.
[ISSUE]
**Title:** `datalad run` Exits with Incorrect Non-Zero Exit Code on Failure

**Description:**
When executing the `datalad run` command with a task that is expected to fail with a specific non-zero exit code, the command does not exit with the intended code. Instead, it exits with a different non-zero exit code, leading to inaccurate error reporting and test failures.

**Example Code:**
```python
from datalad.api import run_main
from datalad.utils import create
from datalad.support.gitrepo import chpwd

def execute_run_command():
    create(dataset='/tmp/test_dataset', annex=False)
    with chpwd('/tmp/test_dataset'):
        run_main(['run', '--explicit', 'exit 3'], exit_code=3)
```

**Expected Behavior:**
The `datalad run` command should exit with the same non-zero exit code (e.g., `3`) that the underlying command returns, accurately reflecting the failure reason.

**Actual Behavior:**
Instead of exiting with the specified exit code `3`, the command exits with a different non-zero exit code (e.g., `1`). This causes tests that rely on the specific exit code to fail, as the expected and actual exit codes do not match.

[/ISSUE]
{
    "test_version": "PASSED",
    "test_dashh": "PASSED",
    "test_dashh_clone": "PASSED",
    "test_usage_on_insufficient_args": "PASSED",
    "test_subcmd_usage_on_unknown_args": "PASSED",
    "test_combined_short_option": "PASSED",
    "test_run_exit_code": "PASSED",
    "test_conflicting_short_option": "PASSED",
    "test_incorrect_option[opts0-error: (invalid|too few arguments|unrecognized argument)]": "PASSED",
    "test_incorrect_option[opts1-error: (invalid|too few arguments|unrecognized argument)]": "PASSED",
    "test_incorrect_option[opts2-error: (invalid|too few arguments|unrecognized argument)]": "PASSED",
    "test_incorrect_option[opts3-error: (invalid|too few arguments|unrecognized argument)]": "PASSED",
    "test_script_shims[datalad]": "PASSED",
    "test_script_shims[git-annex-remote-datalad-archives]": "PASSED",
    "test_script_shims[git-annex-remote-datalad]": "PASSED",
    "test_cfg_override": "PASSED",
    "test_incorrect_cfg_override": "PASSED",
    "test_librarymode": "PASSED",
    "test_completion": "PASSED"
}
diff --git a/datalad/cli/main.py b/datalad/cli/main.py
index bb68e46c8..61d3293ad 100644
--- a/datalad/cli/main.py
+++ b/datalad/cli/main.py
@@ -208,6 +208,21 @@ def _run_with_exception_handler(cmdlineargs):
             # in general we do not want to see the error again, but
             # present in debug output
             lgr.debug('could not perform all requested actions: %s', ce)
+            # fish for an exit code. If any of the "failed" results
+            # is caused by a command error, it will come with an `exit_code`
+            # property.
+            exit_codes = [
+                r['exit_code'] for r in exc.failed if 'exit_code' in r]
+            if exit_codes:
+                # we have exit code(s), take the first non-0 one
+                non0_codes = [e for e in exit_codes if e]
+                if len(non0_codes) != len(exit_codes):
+                    lgr.debug(
+                        "Among %d incomplete results with exit codes %d had exit code 0",
+                        len(exit_codes),
+                        len(exit_codes) - len(non0_codes))
+                if non0_codes:
+                    exit_code = non0_codes[0]
         elif isinstance(exc, CommandError):
             exit_code = _communicate_commanderror(exc) or exit_code
         elif isinstance(exc, KeyboardInterrupt):

{"datalad/cli/main.py": "# emacs: -*- mode: python; py-indent-offset: 4; tab-width: 4; indent-tabs-mode: nil -*-\n# ex: set sts=4 ts=4 sw=4 et:\n# ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ##\n#\n#   See COPYING file distributed along with the datalad package for the\n#   copyright and license terms.\n#\n# ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ##\n\"\"\"This is the main() CLI entryproint\"\"\"\n\n# It should start-up and run as fast as possible for a responsive CLI.\n\n# Imports are done inline and as late as possible to avoid paying for\n# an unconditional commulative overhead that is only actually needed\n# in some special cases.\n\n__docformat__ = 'restructuredtext'\n\nimport logging\n\nlgr = logging.getLogger('datalad.cli')\n\nlgr.log(5, \"Importing cli.main\")\n\nimport os\nimport sys\n\nimport datalad\n\nfrom .parser import setup_parser\n\n# TODO cross-check with unconditional imports in .parser\n# special case imports\n#   from .helpers import _fix_datalad_ri\n#   import platform\n#   from .helpers import _parse_overrides_from_cmdline\n#   from datalad.utils import chpwd\n#   from .utils import setup_exceptionhook\n#   from datalad.support.exceptions import ...\n\n# unconditional imports, no meaningful functionality without them\n#   from .parser import setup_parser\n\n\ndef _on_msys_tainted_paths():\n    \"\"\"This duplicates datalad.utils.on_msys_tainted_paths\n\n    But it does it while minimizing runtime penalties on all irrelevant\n    systems.\n    \"\"\"\n    if os.environ.get('MSYSTEM', '')[:4] not in ('MSYS', 'MING'):\n        return False\n    if 'MSYS_NO_PATHCONV' in os.environ:\n        return False\n    import platform\n    if platform.system().lower() != 'windows':\n        return False\n    return True\n\n\ndef main(args=sys.argv):\n    \"\"\"Main CLI entrypoint\"\"\"\n    lgr.log(5, \"Starting main(%r)\", args)\n    # record that we came in via the cmdline\n    datalad.__api = 'cmdline'\n    completing = \"_ARGCOMPLETE\" in os.environ\n    if completing and 'COMP_LINE' in os.environ:\n        import shlex\n\n        # TODO support posix=False too?\n        args = shlex.split(os.environ['COMP_LINE']) or args\n\n    if _on_msys_tainted_paths():\n        # Possibly present DataLadRIs were stripped of a leading /\n        from .helpers import _fix_datalad_ri\n        args = [_fix_datalad_ri(s) for s in args]\n\n    from datalad.support.entrypoints import load_extensions\n\n    # load extensions requested by configuration\n    # analog to what coreapi is doing for a Python session\n    # importantly, load them prior to parser construction, such\n    # that CLI tuning is also within reach for extensions\n    load_extensions()\n\n    # PYTHON_ARGCOMPLETE_OK\n    # TODO possibly construct a dedicated parser just for autocompletion\n    # rather than lobotomizing the normal one\n    parser = setup_parser(args, completing=completing)\n    try:\n        import argcomplete\n        argcomplete.autocomplete(parser)\n    except ImportError:\n        pass\n\n    # parse cmd args\n    lgr.debug(\"Parsing known args among %r\", args)\n    cmdlineargs, unparsed_args = parser.parse_known_args(args[1:])\n    # did the parser tell us what command to run?\n    has_func = hasattr(cmdlineargs, 'func') and cmdlineargs.func is not None\n    if unparsed_args:\n        if has_func:\n            lgr.error('unknown argument%s: %s',\n                's' if len(unparsed_args) > 1 else '',\n                unparsed_args if len(unparsed_args) > 1 else unparsed_args[0],\n            )\n            cmdlineargs.subparser.print_usage()\n            sys.exit(1)\n        else:\n            # store all unparsed arguments\n            cmdlineargs.datalad_unparsed_args = unparsed_args\n\n    # pull config overrides from cmdline args and put in effect\n    if cmdlineargs.cfg_overrides is not None:\n        from .helpers import _parse_overrides_from_cmdline\n        datalad.cfg.overrides.update(\n            _parse_overrides_from_cmdline(cmdlineargs)\n        )\n        # enable overrides\n        datalad.cfg.reload(force=True)\n        # try loading extensions again, in case the configuration\n        # added new ones to consider\n        load_extensions()\n\n    if 'datalad.runtime.librarymode' in datalad.cfg:\n        datalad.enable_librarymode()\n\n    if cmdlineargs.change_path is not None:\n        from datalad.utils import chpwd\n        for path in cmdlineargs.change_path:\n            chpwd(path)\n\n    # check argparse could determine what commands needs to be executed\n    if not has_func:\n        # just let argparser spit out its error, since there is smth wrong\n        parser.parse_args(args)\n        # if that one didn't puke -- we should\n        parser.print_usage()\n        lgr.error(\"Please specify the command\")\n        # matches exit code for InsufficientArgumentsError\n        sys.exit(2)\n\n    _run(cmdlineargs)\n\n\ndef _run(namespace):\n    \"\"\"Execute a CLI operation\n\n    Depending on CLI debugging options the CLI operation is executed\n    in a debug harness or an exception handler.\n\n    Parameters\n    ----------\n    namespace: Namespace\n      Object returned by `ArgumentParser.parse_args()` with fully\n      populated and validated CLI command and arguments.\n\n    Raises\n    ------\n    SystemExit\n      When the CLI completed without error (exit 0).\n    \"\"\"\n    # execute the command, either with a debugger catching\n    # a crash, or with a simplistic exception handler.\n    # note that result rendering is happening in the\n    # execution handler, when the command-generator is unwound\n    ret = _run_with_debugger(namespace) \\\n        if namespace.common_debug or namespace.common_idebug \\\n        else _run_with_exception_handler(namespace)\n\n    # all good, not strictly needed, but makes internal testing easier\n    sys.exit(0)\n\n\ndef _run_with_debugger(cmdlineargs):\n    \"\"\"Execute the command and drop into debugger if it crashes\"\"\"\n    from .utils import setup_exceptionhook\n\n    # so we could see/stop clearly at the point of failure\n    setup_exceptionhook(ipython=cmdlineargs.common_idebug)\n    return cmdlineargs.func(cmdlineargs)\n\n\ndef _run_with_exception_handler(cmdlineargs):\n    \"\"\"Execute the command and perform some reporting\n    normalization if it crashes, but otherwise just let it go\"\"\"\n    # otherwise - guard and only log the summary. Postmortem is not\n    # as convenient if being caught in this ultimate except\n    try:\n        return cmdlineargs.func(cmdlineargs)\n    # catch BaseException for KeyboardInterrupt\n    except BaseException as exc:\n        from datalad.support.exceptions import (\n            CapturedException,\n            CommandError,\n            IncompleteResultsError,\n            InsufficientArgumentsError,\n        )\n        ce = CapturedException(exc)\n        # we crashed, it has got to be non-zero for starters\n        exit_code = 1\n        if isinstance(exc, InsufficientArgumentsError):\n            # if the func reports inappropriate usage, give help output\n            lgr.error('%s (%s)', ce, exc.__class__.__name__)\n            cmdlineargs.subparser.print_usage(sys.stderr)\n            exit_code = 2\n        elif isinstance(exc, IncompleteResultsError):\n            # in general we do not want to see the error again, but\n            # present in debug output\n            lgr.debug('could not perform all requested actions: %s', ce)\n        elif isinstance(exc, CommandError):\n            exit_code = _communicate_commanderror(exc) or exit_code\n        elif isinstance(exc, KeyboardInterrupt):\n            from datalad.ui import ui\n            ui.error(\"\\nInterrupted by user while doing magic: %s\" % ce)\n            exit_code = 3\n        else:\n            # some unforeseen problem\n            lgr.error('%s', ce.format_with_cause())\n        sys.exit(exit_code)\n\n\ndef _communicate_commanderror(exc):\n    \"\"\"Behave as if the command ran directly\"\"\"\n    exc_msg = exc.to_str(include_output=False)\n    if exc_msg:\n        msg = exc_msg.encode() if isinstance(exc_msg, str) else exc_msg\n        os.write(2, msg + b\"\\n\")\n    # push any captured output to the respective streams\n    for out, stream in ((exc.stdout, 1), (exc.stderr, 2)):\n        if out:\n            os.write(stream,\n                     out.encode() if isinstance(out, str) else out)\n    # pass on exit code\n    return exc.code\n\n\nlgr.log(5, \"Done importing cli.main\")\n"}
A user will ask you to solve a task. You should first draft your thinking process (inner monologue). Then, generate the solution.

Your response format must follow the template below:
<think>
Your thoughts or/and draft, like working through an exercise on scratch paper. Be as casual and as long as you want until you are confident to generate a correct solution.
</think>
<solution>
Final solution presented to the user.
</solution>
system
We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
[ISSUE]
**Title:** `datalad run` Exits with Incorrect Non-Zero Exit Code on Failure

**Description:**
When executing the `datalad run` command with a task that is expected to fail with a specific non-zero exit code, the command does not exit with the intended code. Instead, it exits with a different non-zero exit code, leading to inaccurate error reporting and test failures.

**Example Code:**
```python
from datalad.api import run_main
from datalad.utils import create
from datalad.support.gitrepo import chpwd

def execute_run_command():
    create(dataset='/tmp/test_dataset', annex=False)
    with chpwd('/tmp/test_dataset'):
        run_main(['run', '--explicit', 'exit 3'], exit_code=3)
```

**Expected Behavior:**
The `datalad run` command should exit with the same non-zero exit code (e.g., `3`) that the underlying command returns, accurately reflecting the failure reason.

**Actual Behavior:**
Instead of exiting with the specified exit code `3`, the command exits with a different non-zero exit code (e.g., `1`). This causes tests that rely on the specific exit code to fail, as the expected and actual exit codes do not match.

[/ISSUE]
--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.

--- BEGIN FILE ---
```
### datalad/cli/main.py
# emacs: -*- mode: python; py-indent-offset: 4; tab-width: 4; indent-tabs-mode: nil -*-
# ex: set sts=4 ts=4 sw=4 et:
# ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ##
#
#   See COPYING file distributed along with the datalad package for the
#   copyright and license terms.
#
# ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ##
"""This is the main() CLI entryproint"""

# It should start-up and run as fast as possible for a responsive CLI.

# Imports are done inline and as late as possible to avoid paying for
# an unconditional commulative overhead that is only actually needed
# in some special cases.

__docformat__ = 'restructuredtext'

import logging

lgr = logging.getLogger('datalad.cli')

lgr.log(5, "Importing cli.main")

import os
import sys

import datalad

from .parser import setup_parser

# TODO cross-check with unconditional imports in .parser
# special case imports
#   from .helpers import _fix_datalad_ri
#   import platform
#   from .helpers import _parse_overrides_from_cmdline
#   from datalad.utils import chpwd
#   from .utils import setup_exceptionhook
#   from datalad.support.exceptions import ...

# unconditional imports, no meaningful functionality without them
#   from .parser import setup_parser


def _on_msys_tainted_paths():
    """This duplicates datalad.utils.on_msys_tainted_paths

    But it does it while minimizing runtime penalties on all irrelevant
    systems.
    """
    if os.environ.get('MSYSTEM', '')[:4] not in ('MSYS', 'MING'):
        return False
    if 'MSYS_NO_PATHCONV' in os.environ:
        return False
    import platform
    if platform.system().lower() != 'windows':
        return False
    return True


def main(args=sys.argv):
    """Main CLI entrypoint"""
    lgr.log(5, "Starting main(%r)", args)
    # record that we came in via the cmdline
    datalad.__api = 'cmdline'
    completing = "_ARGCOMPLETE" in os.environ
    if completing and 'COMP_LINE' in os.environ:
        import shlex

        # TODO support posix=False too?
        args = shlex.split(os.environ['COMP_LINE']) or args

    if _on_msys_tainted_paths():
        # Possibly present DataLadRIs were stripped of a leading /
        from .helpers import _fix_datalad_ri
        args = [_fix_datalad_ri(s) for s in args]

    from datalad.support.entrypoints import load_extensions

    # load extensions requested by configuration
    # analog to what coreapi is doing for a Python session
    # importantly, load them prior to parser construction, such
    # that CLI tuning is also within reach for extensions
    load_extensions()

    # PYTHON_ARGCOMPLETE_OK
    # TODO possibly construct a dedicated parser just for autocompletion
    # rather than lobotomizing the normal one
    parser = setup_parser(args, completing=completing)
    try:
        import argcomplete
        argcomplete.autocomplete(parser)
    except ImportError:
        pass

    # parse cmd args
    lgr.debug("Parsing known args among %r", args)
    cmdlineargs, unparsed_args = parser.parse_known_args(args[1:])
    # did the parser tell us what command to run?
    has_func = hasattr(cmdlineargs, 'func') and cmdlineargs.func is not None
    if unparsed_args:
        if has_func:
            lgr.error('unknown argument%s: %s',
                's' if len(unparsed_args) > 1 else '',
                unparsed_args if len(unparsed_args) > 1 else unparsed_args[0],
            )
            cmdlineargs.subparser.print_usage()
            sys.exit(1)
        else:
            # store all unparsed arguments
            cmdlineargs.datalad_unparsed_args = unparsed_args

    # pull config overrides from cmdline args and put in effect
    if cmdlineargs.cfg_overrides is not None:
        from .helpers import _parse_overrides_from_cmdline
        datalad.cfg.overrides.update(
            _parse_overrides_from_cmdline(cmdlineargs)
        )
        # enable overrides
        datalad.cfg.reload(force=True)
        # try loading extensions again, in case the configuration
        # added new ones to consider
        load_extensions()

    if 'datalad.runtime.librarymode' in datalad.cfg:
        datalad.enable_librarymode()

    if cmdlineargs.change_path is not None:
        from datalad.utils import chpwd
        for path in cmdlineargs.change_path:
            chpwd(path)

    # check argparse could determine what commands needs to be executed
    if not has_func:
        # just let argparser spit out its error, since there is smth wrong
        parser.parse_args(args)
        # if that one didn't puke -- we should
        parser.print_usage()
        lgr.error("Please specify the command")
        # matches exit code for InsufficientArgumentsError
        sys.exit(2)

    _run(cmdlineargs)


def _run(namespace):
    """Execute a CLI operation

    Depending on CLI debugging options the CLI operation is executed
    in a debug harness or an exception handler.

    Parameters
    ----------
    namespace: Namespace
      Object returned by `ArgumentParser.parse_args()` with fully
      populated and validated CLI command and arguments.

    Raises
    ------
    SystemExit
      When the CLI completed without error (exit 0).
    """
    # execute the command, either with a debugger catching
    # a crash, or with a simplistic exception handler.
    # note that result rendering is happening in the
    # execution handler, when the command-generator is unwound
    ret = _run_with_debugger(namespace) \
        if namespace.common_debug or namespace.common_idebug \
        else _run_with_exception_handler(namespace)

    # all good, not strictly needed, but makes internal testing easier
    sys.exit(0)


def _run_with_debugger(cmdlineargs):
    """Execute the command and drop into debugger if it crashes"""
    from .utils import setup_exceptionhook

    # so we could see/stop clearly at the point of failure
    setup_exceptionhook(ipython=cmdlineargs.common_idebug)
    return cmdlineargs.func(cmdlineargs)


def _run_with_exception_handler(cmdlineargs):
    """Execute the command and perform some reporting
    normalization if it crashes, but otherwise just let it go"""
    # otherwise - guard and only log the summary. Postmortem is not
    # as convenient if being caught in this ultimate except
    try:
        return cmdlineargs.func(cmdlineargs)
    # catch BaseException for KeyboardInterrupt
    except BaseException as exc:
        from datalad.support.exceptions import (
            CapturedException,
            CommandError,
            IncompleteResultsError,
            InsufficientArgumentsError,
        )
        ce = CapturedException(exc)
        # we crashed, it has got to be non-zero for starters
        exit_code = 1
        if isinstance(exc, InsufficientArgumentsError):
            # if the func reports inappropriate usage, give help output
            lgr.error('%s (%s)', ce, exc.__class__.__name__)
            cmdlineargs.subparser.print_usage(sys.stderr)
            exit_code = 2
        elif isinstance(exc, IncompleteResultsError):
            # in general we do not want to see the error again, but
            # present in debug output
            lgr.debug('could not perform all requested actions: %s', ce)
        elif isinstance(exc, CommandError):
            exit_code = _communicate_commanderror(exc) or exit_code
        elif isinstance(exc, KeyboardInterrupt):
            from datalad.ui import ui
            ui.error("\nInterrupted by user while doing magic: %s" % ce)
            exit_code = 3
        else:
            # some unforeseen problem
            lgr.error('%s', ce.format_with_cause())
        sys.exit(exit_code)


def _communicate_commanderror(exc):
    """Behave as if the command ran directly"""
    exc_msg = exc.to_str(include_output=False)
    if exc_msg:
        msg = exc_msg.encode() if isinstance(exc_msg, str) else exc_msg
        os.write(2, msg + b"\n")
    # push any captured output to the respective streams
    for out, stream in ((exc.stdout, 1), (exc.stderr, 2)):
        if out:
            os.write(stream,
                     out.encode() if isinstance(out, str) else out)
    # pass on exit code
    return exc.code


lgr.log(5, "Done importing cli.main")

```
--- END FILE ---

Please first localize the bug based on the issue statement, and then generate *SEARCH/REPLACE* edits to fix the issue.

Every *SEARCH/REPLACE* edit must use this format:
1. The file path
2. The start of search block: <<<<<<< SEARCH
3. A contiguous chunk of lines to search for in the existing source code
4. The dividing line: =======
5. The lines to replace into the source code
6. The end of the replace block: >>>>>>> REPLACE

Here is an example:

```python
### mathweb/flask/app.py
<<<<<<< SEARCH
from flask import Flask
=======
import math
from flask import Flask
>>>>>>> REPLACE
```
</solution>

Please note that the *SEARCH/REPLACE* edit REQUIRES PROPER INDENTATION. If you would like to add the line '        print(x)', you must fully write that out, with all those spaces before the code!
Wrap each *SEARCH/REPLACE* edit in a code block as shown in the example above. If you have multiple *SEARCH/REPLACE* edits, use a separate code block for each one.
user