eranheres / touch_typing_practice

0 stars 1 forks source link

Sweep: write unit testing for main.py #12

Open eranheres opened 9 months ago

eranheres commented 9 months ago

Details

Use pytest as the unit testing library

Checklist - [X] Test `tests/test_main.py` ✓ https://github.com/eranheres/touch_typing_practice/commit/b760cea00901998138b20b908ef41795c70d117e - [X] Ran sandbox for `tests/test_main.py`. ✗ - [X] Modify `tests/test_main.py` ✓ https://github.com/eranheres/touch_typing_practice/commit/3a91c8e37977af5e8acc8f160e4d299e5717361a - [X] Ran sandbox for `tests/test_main.py`. ✗ - [X] Modify `tests/test_main.py` ✓ https://github.com/eranheres/touch_typing_practice/commit/e4a16f3f77eb2f4ec333681c3255649b0ed4e7b4 - [X] Ran sandbox for `tests/test_main.py`. ✗ - [ ] Modify `tests/test_main.py` ⋯ - [ ] Ran sandbox for `tests/test_main.py`. ▶ - [ ] Ran sandbox for `tests/test_main.py`. ▶ - [ ] Ran sandbox for `tests/test_main.py`. ▶ ![Flowchart](https://raw.githubusercontent.com/eranheres/touch_typing_practice/sweep/assets/17e015f480529e6e5489d06dba8f163993075ea591ca5efca7167657f3f8568d_12_flowchart.svg)
sweep-mine[bot] commented 9 months ago
Sweeping

50%
💎 Sweep Pro: I'm using GPT-4. You have unlimited GPT-4 tickets. (tracking ID: 1bb67cfe98)

![Docker Version Updated](https://img.shields.io/badge/Docker%20Version%20Updated-2%20hours%20ago-blue)

Actions (click)

Sandbox Execution ✓

Here are the sandbox execution logs prior to making any changes:

Sandbox logs for edd9fa1
trunk init 1/5 ✓
⡿ Downloading Trunk 1.17.2...
⡿ Downloading Trunk 1.17.2...
⢿ Downloading Trunk 1.17.2...
⣻ Downloading Trunk 1.17.2...
⣽ Downloading Trunk 1.17.2...
⣾ Downloading Trunk 1.17.2...
⣷ Downloading Trunk 1.17.2...
✔ Downloading Trunk 1.17.2... done
⡿ Verifying Trunk sha256...
✔ Verifying Trunk sha256... done
⡿ Unpacking Trunk...
✔ Unpacking Trunk... done

✔ 15 linters were enabled (.trunk/trunk.yaml)
  actionlint 1.6.26 (1 github-workflow file)
  bandit 1.7.5 (9 python files)
  black 23.9.1 (7 python files)
  checkov 3.1.9 (4 yaml files)
  git-diff-check (32 files)
  isort 5.12.0 (9 python files) (created .isort.cfg)
  markdownlint 0.37.0 (4 markdown files) (created .markdownlint.yaml)
  osv-scanner 1.4.3 (1 lockfile file)
  oxipng 9.0.0 (3 png files)
  prettier 3.1.0 (4 markdown, 4 yaml files)
  ruff 0.1.6 (9 python files) (created ruff.toml)
  svgo 3.0.4 (3 svg files) (created svgo.config.js)
  trivy 0.47.0 (4 yaml files)
  trufflehog 3.63.2-rc0 (32 files)
  yamllint 1.33.0 (4 yaml files) (created .yamllint.yaml)
Next Steps
 1. Read documentation
    Our documentation can be found at https://docs.trunk.io
 2. Get help and give feedback
    Join the Trunk community at https://slack.trunk.io
pip install -r requirements.txt 2/5 ✓
Collecting iniconfig==2.0.0
  Downloading iniconfig-2.0.0-py3-none-any.whl (5.9 kB)
Requirement already satisfied: packaging==23.2 in /usr/local/lib/python3.10/dist-packages (from -r requirements.txt (line 2)) (23.2)
Collecting pluggy==1.3.0
  Downloading pluggy-1.3.0-py3-none-any.whl (18 kB)
Collecting polyglot==16.7.4
  Downloading polyglot-16.7.4.tar.gz (126 kB)
     ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 126.3/126.3 KB 2.2 MB/s eta 0:00:00
  Preparing metadata (setup.py): started
  Preparing metadata (setup.py): finished with status 'done'
Collecting pytest==7.4.3
  Downloading pytest-7.4.3-py3-none-any.whl (325 kB)
     ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 325.1/325.1 KB 5.4 MB/s eta 0:00:00
Collecting exceptiongroup>=1.0.0rc8
  Downloading exceptiongroup-1.2.0-py3-none-any.whl (16 kB)
Requirement already satisfied: tomli>=1.0.0 in /usr/local/lib/python3.10/dist-packages (from pytest==7.4.3->-r requirements.txt (line 5)) (2.0.1)
Building wheels for collected packages: polyglot
  Building wheel for polyglot (setup.py): started
  Building wheel for polyglot (setup.py): finished with status 'done'
  Created wheel for polyglot: filename=polyglot-16.7.4-py2.py3-none-any.whl size=52577 sha256=1482cbb04f597aca535db4186a58191bd31bf3657aef04467b46f4beb5b5d3de
  Stored in directory: /root/.cache/pip/wheels/aa/92/4a/b172589446ba537db3bdb9a1f2204f27fe71217981c14ac368
Successfully built polyglot
Installing collected packages: polyglot, pluggy, iniconfig, exceptiongroup, pytest
Successfully installed exceptiongroup-1.2.0 iniconfig-2.0.0 pluggy-1.3.0 polyglot-16.7.4 pytest-7.4.3
WARNING: Running pip as the 'root' user can result in broken permissions and conflicting behaviour with the system package manager. It is recommended to use a virtual environment instead: https://pip.pypa.io/warnings/venv
trunk fmt sweep.yaml || return 0 3/5 ✓
 ✔ Formatted sweep.yaml
Re-checking autofixed files...

Checked 1 file
✔ No issues
trunk check --fix --print-failures sweep.yaml 4/5 ✓
  ISSUES  
sweep.yaml:6:5
  6:5  high  string value is redundantly quoted with any quotes  yamllint/quoted-strings
  7:5  high  string value is redundantly quoted with any quotes  yamllint/quoted-strings
  8:5  high  string value is redundantly quoted with any quotes  yamllint/quoted-strings
  9:5  high  string value is redundantly quoted with any quotes  yamllint/quoted-strings
 10:5  high  string value is redundantly quoted with any quotes  yamllint/quoted-strings
 11:5  high  string value is redundantly quoted with any quotes  yamllint/quoted-strings
 12:5  high  string value is redundantly quoted with any quotes  yamllint/quoted-strings
 13:5  high  string value is redundantly quoted with any quotes  yamllint/quoted-strings
 14:5  high  string value is redundantly quoted with any quotes  yamllint/quoted-strings
 15:5  high  string value is redundantly quoted with any quotes  yamllint/quoted-strings
 18:9  high  string value is redundantly quoted with any quotes  yamllint/quoted-strings
Checked 1 file
11 existing issues
Run trunk upgrade to upgrade 1 linter
pytest 5/5 ✓
============================= test session starts ==============================
platform linux -- Python 3.10.12, pytest-7.4.3, pluggy-1.3.0
rootdir: /repo
collected 16 items
tests/test_session.py .....                                              [ 31%]
tests/test_typing_practice.py ......                                     [ 68%]
tests/test_user.py .....                                                 [100%]
============================== 16 passed in 1.12s ==============================

Sandbox passed on the latest main, so sandbox checks will be enabled for this issue.


Step 1: 🔎 Searching

I found the following snippets in your repository. I will now analyze these snippets and come up with a plan.

Some code snippets I looked at (click to expand). If some file is missing from here, you can mention the path in the ticket description. https://github.com/eranheres/touch_typing_practice/blob/edd9fa1cc00242c1f79072aba7e3f2b412974802/touch_typing_practice/main.py#L1-L44 https://github.com/eranheres/touch_typing_practice/blob/edd9fa1cc00242c1f79072aba7e3f2b412974802/README.md#L1-L-1

Step 2: ⌨️ Coding

Test coverage generation failed with error:

======================================================================
ERROR: test_main_invalid_choice (__main__.TestMainFunction)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/usr/lib/python3.10/unittest/mock.py", line 1556, in __enter__
    setattr(self.target, self.attribute, new_attr)
TypeError: cannot set 'getstr' attribute of immutable type '_curses.window'

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/usr/lib/python3.10/unittest/mock.py", line 1376, in patched
    with self.decoration_helper(patched,
  File "/usr/lib/python3.10/contextlib.py", line 135, in __enter__
    return next(self.gen)
  File "/usr/lib/python3.10/unittest/mock.py", line 1358, in decoration_helper
    arg = exit_stack.enter_context(patching)
  File "/usr/lib/python3.10/contextlib.py", line 492, in enter_context
    result = _cm_type.__enter__(cm)
  File "/usr/lib/python3.10/unittest/mock.py", line 1569, in __enter__
    if not self.__exit__(*sys.exc_info()):
  File "/usr/lib/python3.10/unittest/mock.py", line 1575, in __exit__
    setattr(self.target, self.attribute, self.temp_original)
TypeError: cannot set 'getstr' attribute of immutable type '_curses.window'

----------------------------------------------------------------------
Ran 2 tests in 0.007s

FAILED (errors=1, skipped=1)

trunk fmt tests/test_main.py || return 0 1/3 ✓
 ✔ Formatted tests/test_main.py
Re-checking autofixed files...

Checked 1 file
✔ No issues
Run trunk upgrade to upgrade 1 linter
trunk check --fix --print-failures tests/test_main.py 2/3 ✓
Checked 1 file
✔ No issues
pytest 3/3 ❌ (`1`)
============================= test session starts ==============================
platform linux -- Python 3.10.12, pytest-7.4.3, pluggy-1.3.0
rootdir: /repo
collected 18 items
tests/test_main.py Fs                                                    [ 11%]
tests/test_session.py .....                                              [ 38%]
tests/test_typing_practice.py ......                                     [ 72%]
tests/test_user.py .....                                                 [100%]
=================================== FAILURES ===================================
__________________ TestMainFunction.test_main_invalid_choice ___________________
self = 
    def __enter__(self):
        """Perform the patch."""
        new, spec, spec_set = self.new, self.spec, self.spec_set
        autospec, kwargs = self.autospec, self.kwargs
        new_callable = self.new_callable
        self.target = self.getter()

        # normalise False to None
        if spec is False:
            spec = None
        if spec_set is False:
            spec_set = None
        if autospec is False:
            autospec = None

        if spec is not None and autospec is not None:
            raise TypeError("Can't specify spec and autospec")
        if ((spec is not None or autospec is not None) and
            spec_set not in (True, None)):
            raise TypeError("Can't provide explicit spec_set *and* spec or autospec")

        original, local = self.get_original()

        if new is DEFAULT and autospec is None:
            inherit = False
            if spec is True:
                # set spec to the object we are replacing
                spec = original
                if spec_set is True:
                    spec_set = original
                    spec = None
            elif spec is not None:
                if spec_set is True:
                    spec_set = spec
                    spec = None
            elif spec_set is True:
                spec_set = original

            if spec is not None or spec_set is not None:
                if original is DEFAULT:
                    raise TypeError("Can't use 'spec' with create=True")
                if isinstance(original, type):
                    # If we're patching out a class and there is a spec
                    inherit = True
            if spec is None and _is_async_obj(original):
                Klass = AsyncMock
            else:
                Klass = MagicMock
            _kwargs = {}
            if new_callable is not None:
                Klass = new_callable
            elif spec is not None or spec_set is not None:
                this_spec = spec
                if spec_set is not None:
                    this_spec = spec_set
                if _is_list(this_spec):
                    not_callable = '__call__' not in this_spec
                else:
                    not_callable = not callable(this_spec)
                if _is_async_obj(this_spec):
                    Klass = AsyncMock
                elif not_callable:
                    Klass = NonCallableMagicMock

            if spec is not None:
                _kwargs['spec'] = spec
            if spec_set is not None:
                _kwargs['spec_set'] = spec_set

            # add a name to mocks
            if (isinstance(Klass, type) and
                issubclass(Klass, NonCallableMock) and self.attribute):
                _kwargs['name'] = self.attribute

            _kwargs.update(kwargs)
            new = Klass(**_kwargs)

            if inherit and _is_instance_mock(new):
                # we can only tell if the instance should be callable if the
                # spec is not a list
                this_spec = spec
                if spec_set is not None:
                    this_spec = spec_set
                if (not _is_list(this_spec) and not
                    _instance_callable(this_spec)):
                    Klass = NonCallableMagicMock

                _kwargs.pop('name')
                new.return_value = Klass(_new_parent=new, _new_name='()',
                                         **_kwargs)
        elif autospec is not None:
            # spec is ignored, new *must* be default, spec_set is treated
            # as a boolean. Should we check spec is not None and that spec_set
            # is a bool?
            if new is not DEFAULT:
                raise TypeError(
                    "autospec creates the mock for you. Can't specify "
                    "autospec and new."
                )
            if original is DEFAULT:
                raise TypeError("Can't use 'autospec' with create=True")
            spec_set = bool(spec_set)
            if autospec is True:
                autospec = original

            if _is_instance_mock(self.target):
                raise InvalidSpecError(
                    f'Cannot autospec attr {self.attribute!r} as the patch '
                    f'target has already been mocked out. '
                    f'[target={self.target!r}, attr={autospec!r}]')
            if _is_instance_mock(autospec):
                target_name = getattr(self.target, '__name__', self.target)
                raise InvalidSpecError(
                    f'Cannot autospec attr {self.attribute!r} from target '
                    f'{target_name!r} as it has already been mocked out. '
                    f'[target={self.target!r}, attr={autospec!r}]')

            new = create_autospec(autospec, spec_set=spec_set,
                                  _name=self.attribute, **kwargs)
        elif kwargs:
            # can't set keyword args when we aren't creating the mock
            # XXXX If new is a Mock we could call new.configure_mock(**kwargs)
            raise TypeError("Can't pass kwargs to a mock we aren't creating")

        new_attr = new

        self.temp_original = original
        self.is_local = local
        self._exit_stack = contextlib.ExitStack()
        try:
>           setattr(self.target, self.attribute, new_attr)
E           TypeError: cannot set 'getstr' attribute of immutable type '_curses.window'
/usr/lib/python3.10/unittest/mock.py:1556: TypeError
During handling of the above exception, another exception occurred:
/usr/lib/python3.10/unittest/mock.py:1376: in patched
    with self.decoration_helper(patched,
/usr/lib/python3.10/contextlib.py:135: in __enter__
    return next(self.gen)
/usr/lib/python3.10/unittest/mock.py:1358: in decoration_helper
    arg = exit_stack.enter_context(patching)
/usr/lib/python3.10/contextlib.py:492: in enter_context
    result = _cm_type.__enter__(cm)
/usr/lib/python3.10/unittest/mock.py:1569: in __enter__
    if not self.__exit__(*sys.exc_info()):
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
self = 
exc_info = (, TypeError("cannot set 'getstr' attribute of immutable type '_curses.window'"), )
    def __exit__(self, *exc_info):
        """Undo the patch."""
        if self.is_local and self.temp_original is not DEFAULT:
>           setattr(self.target, self.attribute, self.temp_original)
E           TypeError: cannot set 'getstr' attribute of immutable type '_curses.window'
/usr/lib/python3.10/unittest/mock.py:1575: TypeError
=========================== short test summary info ============================
FAILED tests/test_main.py::TestMainFunction::test_main_invalid_choice - TypeE...
=================== 1 failed, 16 passed, 1 skipped in 1.69s ====================

In the test_main_user_interaction and test_main_invalid_choice tests, replace the @patch("curses.window.getstr") decorator with @patch.object(curses.window, "getstr"). This will mock the 'getstr' method on an instance of the 'curses.window' class, rather than trying to replace the method on the class itself.

Also, update the side_effect attribute of the mock_getstr object to return strings instead of bytes, as the 'getstr' method returns strings, not bytes. This will ensure that the mock object behaves in the same way as the method it is replacing.

trunk fmt tests/test_main.py || return 0 1/2 ✓
 ✔ Formatted tests/test_main.py
Re-checking autofixed files...

Checked 1 file
✔ No issues
Run trunk upgrade to upgrade 1 linter
trunk check --fix --print-failures tests/test_main.py 2/2 ❌ (`1`)
  ISSUES  
tests/test_main.py:11:19
 11:19  high  Undefined name `curses`  ruff/F821
Checked 1 file
✖ 1 new issue

trunk fmt tests/test_main.py || return 0 1/3 ✓
 ✔ Formatted tests/test_main.py
Re-checking autofixed files...

 ✔ Formatted tests/test_main.py
Re-checking autofixed files...

Checked 1 file
✔ No issues
Run trunk upgrade to upgrade 1 linter
trunk check --fix --print-failures tests/test_main.py 2/3 ✓
Checked 1 file
✔ No issues
pytest 3/3 ❌ (`1`)
============================= test session starts ==============================
platform linux -- Python 3.10.12, pytest-7.4.3, pluggy-1.3.0
rootdir: /repo
collected 18 items
tests/test_main.py FF                                                    [ 11%]
tests/test_session.py .....                                              [ 38%]
tests/test_typing_practice.py ......                                     [ 72%]
tests/test_user.py .....                                                 [100%]
=================================== FAILURES ===================================
__________________ TestMainFunction.test_main_invalid_choice ___________________
self = 
    def __enter__(self):
        """Perform the patch."""
        new, spec, spec_set = self.new, self.spec, self.spec_set
        autospec, kwargs = self.autospec, self.kwargs
        new_callable = self.new_callable
        self.target = self.getter()

        # normalise False to None
        if spec is False:
            spec = None
        if spec_set is False:
            spec_set = None
        if autospec is False:
            autospec = None

        if spec is not None and autospec is not None:
            raise TypeError("Can't specify spec and autospec")
        if ((spec is not None or autospec is not None) and
            spec_set not in (True, None)):
            raise TypeError("Can't provide explicit spec_set *and* spec or autospec")

        original, local = self.get_original()

        if new is DEFAULT and autospec is None:
            inherit = False
            if spec is True:
                # set spec to the object we are replacing
                spec = original
                if spec_set is True:
                    spec_set = original
                    spec = None
            elif spec is not None:
                if spec_set is True:
                    spec_set = spec
                    spec = None
            elif spec_set is True:
                spec_set = original

            if spec is not None or spec_set is not None:
                if original is DEFAULT:
                    raise TypeError("Can't use 'spec' with create=True")
                if isinstance(original, type):
                    # If we're patching out a class and there is a spec
                    inherit = True
            if spec is None and _is_async_obj(original):
                Klass = AsyncMock
            else:
                Klass = MagicMock
            _kwargs = {}
            if new_callable is not None:
                Klass = new_callable
            elif spec is not None or spec_set is not None:
                this_spec = spec
                if spec_set is not None:
                    this_spec = spec_set
                if _is_list(this_spec):
                    not_callable = '__call__' not in this_spec
                else:
                    not_callable = not callable(this_spec)
                if _is_async_obj(this_spec):
                    Klass = AsyncMock
                elif not_callable:
                    Klass = NonCallableMagicMock

            if spec is not None:
                _kwargs['spec'] = spec
            if spec_set is not None:
                _kwargs['spec_set'] = spec_set

            # add a name to mocks
            if (isinstance(Klass, type) and
                issubclass(Klass, NonCallableMock) and self.attribute):
                _kwargs['name'] = self.attribute

            _kwargs.update(kwargs)
            new = Klass(**_kwargs)

            if inherit and _is_instance_mock(new):
                # we can only tell if the instance should be callable if the
                # spec is not a list
                this_spec = spec
                if spec_set is not None:
                    this_spec = spec_set
                if (not _is_list(this_spec) and not
                    _instance_callable(this_spec)):
                    Klass = NonCallableMagicMock

                _kwargs.pop('name')
                new.return_value = Klass(_new_parent=new, _new_name='()',
                                         **_kwargs)
        elif autospec is not None:
            # spec is ignored, new *must* be default, spec_set is treated
            # as a boolean. Should we check spec is not None and that spec_set
            # is a bool?
            if new is not DEFAULT:
                raise TypeError(
                    "autospec creates the mock for you. Can't specify "
                    "autospec and new."
                )
            if original is DEFAULT:
                raise TypeError("Can't use 'autospec' with create=True")
            spec_set = bool(spec_set)
            if autospec is True:
                autospec = original

            if _is_instance_mock(self.target):
                raise InvalidSpecError(
                    f'Cannot autospec attr {self.attribute!r} as the patch '
                    f'target has already been mocked out. '
                    f'[target={self.target!r}, attr={autospec!r}]')
            if _is_instance_mock(autospec):
                target_name = getattr(self.target, '__name__', self.target)
                raise InvalidSpecError(
                    f'Cannot autospec attr {self.attribute!r} from target '
                    f'{target_name!r} as it has already been mocked out. '
                    f'[target={self.target!r}, attr={autospec!r}]')

            new = create_autospec(autospec, spec_set=spec_set,
                                  _name=self.attribute, **kwargs)
        elif kwargs:
            # can't set keyword args when we aren't creating the mock
            # XXXX If new is a Mock we could call new.configure_mock(**kwargs)
            raise TypeError("Can't pass kwargs to a mock we aren't creating")

        new_attr = new

        self.temp_original = original
        self.is_local = local
        self._exit_stack = contextlib.ExitStack()
        try:
>           setattr(self.target, self.attribute, new_attr)
E           TypeError: cannot set 'getstr' attribute of immutable type '_curses.window'
/usr/lib/python3.10/unittest/mock.py:1556: TypeError
During handling of the above exception, another exception occurred:
/usr/lib/python3.10/unittest/mock.py:1376: in patched
    with self.decoration_helper(patched,
/usr/lib/python3.10/contextlib.py:135: in __enter__
    return next(self.gen)
/usr/lib/python3.10/unittest/mock.py:1358: in decoration_helper
    arg = exit_stack.enter_context(patching)
/usr/lib/python3.10/contextlib.py:492: in enter_context
    result = _cm_type.__enter__(cm)
/usr/lib/python3.10/unittest/mock.py:1569: in __enter__
    if not self.__exit__(*sys.exc_info()):
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
self = 
exc_info = (, TypeError("cannot set 'getstr' attribute of immutable type '_curses.window'"), )
    def __exit__(self, *exc_info):
        """Undo the patch."""
        if self.is_local and self.temp_original is not DEFAULT:
>           setattr(self.target, self.attribute, self.temp_original)
E           TypeError: cannot set 'getstr' attribute of immutable type '_curses.window'
/usr/lib/python3.10/unittest/mock.py:1575: TypeError
_________________ TestMainFunction.test_main_user_interaction __________________
self = 
    def __enter__(self):
        """Perform the patch."""
        new, spec, spec_set = self.new, self.spec, self.spec_set
        autospec, kwargs = self.autospec, self.kwargs
        new_callable = self.new_callable
        self.target = self.getter()

        # normalise False to None
        if spec is False:
            spec = None
        if spec_set is False:
            spec_set = None
        if autospec is False:
            autospec = None

        if spec is not None and autospec is not None:
            raise TypeError("Can't specify spec and autospec")
        if ((spec is not None or autospec is not None) and
            spec_set not in (True, None)):
            raise TypeError("Can't provide explicit spec_set *and* spec or autospec")

        original, local = self.get_original()

        if new is DEFAULT and autospec is None:
            inherit = False
            if spec is True:
                # set spec to the object we are replacing
                spec = original
                if spec_set is True:
                    spec_set = original
                    spec = None
            elif spec is not None:
                if spec_set is True:
                    spec_set = spec
                    spec = None
            elif spec_set is True:
                spec_set = original

            if spec is not None or spec_set is not None:
                if original is DEFAULT:
                    raise TypeError("Can't use 'spec' with create=True")
                if isinstance(original, type):
                    # If we're patching out a class and there is a spec
                    inherit = True
            if spec is None and _is_async_obj(original):
                Klass = AsyncMock
            else:
                Klass = MagicMock
            _kwargs = {}
            if new_callable is not None:
                Klass = new_callable
            elif spec is not None or spec_set is not None:
                this_spec = spec
                if spec_set is not None:
                    this_spec = spec_set
                if _is_list(this_spec):
                    not_callable = '__call__' not in this_spec
                else:
                    not_callable = not callable(this_spec)
                if _is_async_obj(this_spec):
                    Klass = AsyncMock
                elif not_callable:
                    Klass = NonCallableMagicMock

            if spec is not None:
                _kwargs['spec'] = spec
            if spec_set is not None:
                _kwargs['spec_set'] = spec_set

            # add a name to mocks
            if (isinstance(Klass, type) and
                issubclass(Klass, NonCallableMock) and self.attribute):
                _kwargs['name'] = self.attribute

            _kwargs.update(kwargs)
            new = Klass(**_kwargs)

            if inherit and _is_instance_mock(new):
                # we can only tell if the instance should be callable if the
                # spec is not a list
                this_spec = spec
                if spec_set is not None:
                    this_spec = spec_set
                if (not _is_list(this_spec) and not
                    _instance_callable(this_spec)):
                    Klass = NonCallableMagicMock

                _kwargs.pop('name')
                new.return_value = Klass(_new_parent=new, _new_name='()',
                                         **_kwargs)
        elif autospec is not None:
            # spec is ignored, new *must* be default, spec_set is treated
            # as a boolean. Should we check spec is not None and that spec_set
            # is a bool?
            if new is not DEFAULT:
                raise TypeError(
                    "autospec creates the mock for you. Can't specify "
                    "autospec and new."
                )
            if original is DEFAULT:
                raise TypeError("Can't use 'autospec' with create=True")
            spec_set = bool(spec_set)
            if autospec is True:
                autospec = original

            if _is_instance_mock(self.target):
                raise InvalidSpecError(
                    f'Cannot autospec attr {self.attribute!r} as the patch '
                    f'target has already been mocked out. '
                    f'[target={self.target!r}, attr={autospec!r}]')
            if _is_instance_mock(autospec):
                target_name = getattr(self.target, '__name__', self.target)
                raise InvalidSpecError(
                    f'Cannot autospec attr {self.attribute!r} from target '
                    f'{target_name!r} as it has already been mocked out. '
                    f'[target={self.target!r}, attr={autospec!r}]')

            new = create_autospec(autospec, spec_set=spec_set,
                                  _name=self.attribute, **kwargs)
        elif kwargs:
            # can't set keyword args when we aren't creating the mock
            # XXXX If new is a Mock we could call new.configure_mock(**kwargs)
            raise TypeError("Can't pass kwargs to a mock we aren't creating")

        new_attr = new

        self.temp_original = original
        self.is_local = local
        self._exit_stack = contextlib.ExitStack()
        try:
>           setattr(self.target, self.attribute, new_attr)
E           TypeError: cannot set 'getstr' attribute of immutable type '_curses.window'
/usr/lib/python3.10/unittest/mock.py:1556: TypeError
During handling of the above exception, another exception occurred:
/usr/lib/python3.10/unittest/mock.py:1376: in patched
    with self.decoration_helper(patched,
/usr/lib/python3.10/contextlib.py:135: in __enter__
    return next(self.gen)
/usr/lib/python3.10/unittest/mock.py:1358: in decoration_helper
    arg = exit_stack.enter_context(patching)
/usr/lib/python3.10/contextlib.py:492: in enter_context
    result = _cm_type.__enter__(cm)
/usr/lib/python3.10/unittest/mock.py:1569: in __enter__
    if not self.__exit__(*sys.exc_info()):
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
self = 
exc_info = (, TypeError("cannot set 'getstr' attribute of immutable type '_curses.window'"), )
    def __exit__(self, *exc_info):
        """Undo the patch."""
        if self.is_local and self.temp_original is not DEFAULT:
>           setattr(self.target, self.attribute, self.temp_original)
E           TypeError: cannot set 'getstr' attribute of immutable type '_curses.window'
/usr/lib/python3.10/unittest/mock.py:1575: TypeError
=========================== short test summary info ============================
FAILED tests/test_main.py::TestMainFunction::test_main_invalid_choice - TypeE...
FAILED tests/test_main.py::TestMainFunction::test_main_user_interaction - Typ...
========================= 2 failed, 16 passed in 1.87s =========================

To achieve this, we need to replace the '@patch.object(curses.window, "getstr")' decorators with '@patch("curses.wrapper")' and adjust the test methods accordingly. The mock object for 'curses.wrapper' should be set up to call the 'main' function with a mock '_curses.window' object, whose 'getstr' method returns the desired test inputs.

In the 'test_main_user_interaction' method, the 'mock_getstr' mock object should be replaced with a 'mock_wrapper' mock object. The 'mock_wrapper' mock object should be set up to call the 'main' function with a mock '_curses.window' object, whose 'getstr' method returns the test inputs currently returned by 'mock_getstr'.

Similarly, in the 'test_main_invalid_choice' method, the 'mock_getstr' mock object should be replaced with a 'mock_wrapper' mock object. The 'mock_wrapper' mock object should be set up to call the 'main' function with a mock '_curses.window' object, whose 'getstr' method returns the test inputs currently returned by 'mock_getstr'.

trunk fmt tests/test_main.py || return 0 1/3 ✓
 ✔ Formatted tests/test_main.py
Re-checking autofixed files...

 ✔ Formatted tests/test_main.py
Re-checking autofixed files...

Checked 1 file
✔ No issues
Run trunk upgrade to upgrade 1 linter
trunk check --fix --print-failures tests/test_main.py 2/3 ✓
Checked 1 file
✔ No issues
pytest 3/3 ❌ (`1`)
============================= test session starts ==============================
platform linux -- Python 3.10.12, pytest-7.4.3, pluggy-1.3.0
rootdir: /repo
collected 18 items
tests/test_main.py FF                                                    [ 11%]
tests/test_session.py .....                                              [ 38%]
tests/test_typing_practice.py ......                                     [ 72%]
tests/test_user.py .....                                                 [100%]
=================================== FAILURES ===================================
__________________ TestMainFunction.test_main_invalid_choice ___________________
self = 
    def __enter__(self):
        """Perform the patch."""
        new, spec, spec_set = self.new, self.spec, self.spec_set
        autospec, kwargs = self.autospec, self.kwargs
        new_callable = self.new_callable
        self.target = self.getter()

        # normalise False to None
        if spec is False:
            spec = None
        if spec_set is False:
            spec_set = None
        if autospec is False:
            autospec = None

        if spec is not None and autospec is not None:
            raise TypeError("Can't specify spec and autospec")
        if ((spec is not None or autospec is not None) and
            spec_set not in (True, None)):
            raise TypeError("Can't provide explicit spec_set *and* spec or autospec")

        original, local = self.get_original()

        if new is DEFAULT and autospec is None:
            inherit = False
            if spec is True:
                # set spec to the object we are replacing
                spec = original
                if spec_set is True:
                    spec_set = original
                    spec = None
            elif spec is not None:
                if spec_set is True:
                    spec_set = spec
                    spec = None
            elif spec_set is True:
                spec_set = original

            if spec is not None or spec_set is not None:
                if original is DEFAULT:
                    raise TypeError("Can't use 'spec' with create=True")
                if isinstance(original, type):
                    # If we're patching out a class and there is a spec
                    inherit = True
            if spec is None and _is_async_obj(original):
                Klass = AsyncMock
            else:
                Klass = MagicMock
            _kwargs = {}
            if new_callable is not None:
                Klass = new_callable
            elif spec is not None or spec_set is not None:
                this_spec = spec
                if spec_set is not None:
                    this_spec = spec_set
                if _is_list(this_spec):
                    not_callable = '__call__' not in this_spec
                else:
                    not_callable = not callable(this_spec)
                if _is_async_obj(this_spec):
                    Klass = AsyncMock
                elif not_callable:
                    Klass = NonCallableMagicMock

            if spec is not None:
                _kwargs['spec'] = spec
            if spec_set is not None:
                _kwargs['spec_set'] = spec_set

            # add a name to mocks
            if (isinstance(Klass, type) and
                issubclass(Klass, NonCallableMock) and self.attribute):
                _kwargs['name'] = self.attribute

            _kwargs.update(kwargs)
            new = Klass(**_kwargs)

            if inherit and _is_instance_mock(new):
                # we can only tell if the instance should be callable if the
                # spec is not a list
                this_spec = spec
                if spec_set is not None:
                    this_spec = spec_set
                if (not _is_list(this_spec) and not
                    _instance_callable(this_spec)):
                    Klass = NonCallableMagicMock

                _kwargs.pop('name')
                new.return_value = Klass(_new_parent=new, _new_name='()',
                                         **_kwargs)
        elif autospec is not None:
            # spec is ignored, new *must* be default, spec_set is treated
            # as a boolean. Should we check spec is not None and that spec_set
            # is a bool?
            if new is not DEFAULT:
                raise TypeError(
                    "autospec creates the mock for you. Can't specify "
                    "autospec and new."
                )
            if original is DEFAULT:
                raise TypeError("Can't use 'autospec' with create=True")
            spec_set = bool(spec_set)
            if autospec is True:
                autospec = original

            if _is_instance_mock(self.target):
                raise InvalidSpecError(
                    f'Cannot autospec attr {self.attribute!r} as the patch '
                    f'target has already been mocked out. '
                    f'[target={self.target!r}, attr={autospec!r}]')
            if _is_instance_mock(autospec):
                target_name = getattr(self.target, '__name__', self.target)
                raise InvalidSpecError(
                    f'Cannot autospec attr {self.attribute!r} from target '
                    f'{target_name!r} as it has already been mocked out. '
                    f'[target={self.target!r}, attr={autospec!r}]')

            new = create_autospec(autospec, spec_set=spec_set,
                                  _name=self.attribute, **kwargs)
        elif kwargs:
            # can't set keyword args when we aren't creating the mock
            # XXXX If new is a Mock we could call new.configure_mock(**kwargs)
            raise TypeError("Can't pass kwargs to a mock we aren't creating")

        new_attr = new

        self.temp_original = original
        self.is_local = local
        self._exit_stack = contextlib.ExitStack()
        try:
>           setattr(self.target, self.attribute, new_attr)
E           TypeError: cannot set 'getstr' attribute of immutable type '_curses.window'
/usr/lib/python3.10/unittest/mock.py:1556: TypeError
During handling of the above exception, another exception occurred:
/usr/lib/python3.10/unittest/mock.py:1376: in patched
    with self.decoration_helper(patched,
/usr/lib/python3.10/contextlib.py:135: in __enter__
    return next(self.gen)
/usr/lib/python3.10/unittest/mock.py:1358: in decoration_helper
    arg = exit_stack.enter_context(patching)
/usr/lib/python3.10/contextlib.py:492: in enter_context
    result = _cm_type.__enter__(cm)
/usr/lib/python3.10/unittest/mock.py:1569: in __enter__
    if not self.__exit__(*sys.exc_info()):
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
self = 
exc_info = (, TypeError("cannot set 'getstr' attribute of immutable type '_curses.window'"), )
    def __exit__(self, *exc_info):
        """Undo the patch."""
        if self.is_local and self.temp_original is not DEFAULT:
>           setattr(self.target, self.attribute, self.temp_original)
E           TypeError: cannot set 'getstr' attribute of immutable type '_curses.window'
/usr/lib/python3.10/unittest/mock.py:1575: TypeError
_________________ TestMainFunction.test_main_user_interaction __________________
self = 
    def __enter__(self):
        """Perform the patch."""
        new, spec, spec_set = self.new, self.spec, self.spec_set
        autospec, kwargs = self.autospec, self.kwargs
        new_callable = self.new_callable
        self.target = self.getter()

        # normalise False to None
        if spec is False:
            spec = None
        if spec_set is False:
            spec_set = None
        if autospec is False:
            autospec = None

        if spec is not None and autospec is not None:
            raise TypeError("Can't specify spec and autospec")
        if ((spec is not None or autospec is not None) and
            spec_set not in (True, None)):
            raise TypeError("Can't provide explicit spec_set *and* spec or autospec")

        original, local = self.get_original()

        if new is DEFAULT and autospec is None:
            inherit = False
            if spec is True:
                # set spec to the object we are replacing
                spec = original
                if spec_set is True:
                    spec_set = original
                    spec = None
            elif spec is not None:
                if spec_set is True:
                    spec_set = spec
                    spec = None
            elif spec_set is True:
                spec_set = original

            if spec is not None or spec_set is not None:
                if original is DEFAULT:
                    raise TypeError("Can't use 'spec' with create=True")
                if isinstance(original, type):
                    # If we're patching out a class and there is a spec
                    inherit = True
            if spec is None and _is_async_obj(original):
                Klass = AsyncMock
            else:
                Klass = MagicMock
            _kwargs = {}
            if new_callable is not None:
                Klass = new_callable
            elif spec is not None or spec_set is not None:
                this_spec = spec
                if spec_set is not None:
                    this_spec = spec_set
                if _is_list(this_spec):
                    not_callable = '__call__' not in this_spec
                else:
                    not_callable = not callable(this_spec)
                if _is_async_obj(this_spec):
                    Klass = AsyncMock
                elif not_callable:
                    Klass = NonCallableMagicMock

            if spec is not None:
                _kwargs['spec'] = spec
            if spec_set is not None:
                _kwargs['spec_set'] = spec_set

            # add a name to mocks
            if (isinstance(Klass, type) and
                issubclass(Klass, NonCallableMock) and self.attribute):
                _kwargs['name'] = self.attribute

            _kwargs.update(kwargs)
            new = Klass(**_kwargs)

            if inherit and _is_instance_mock(new):
                # we can only tell if the instance should be callable if the
                # spec is not a list
                this_spec = spec
                if spec_set is not None:
                    this_spec = spec_set
                if (not _is_list(this_spec) and not
                    _instance_callable(this_spec)):
                    Klass = NonCallableMagicMock

                _kwargs.pop('name')
                new.return_value = Klass(_new_parent=new, _new_name='()',
                                         **_kwargs)
        elif autospec is not None:
            # spec is ignored, new *must* be default, spec_set is treated
            # as a boolean. Should we check spec is not None and that spec_set
            # is a bool?
            if new is not DEFAULT:
                raise TypeError(
                    "autospec creates the mock for you. Can't specify "
                    "autospec and new."
                )
            if original is DEFAULT:
                raise TypeError("Can't use 'autospec' with create=True")
            spec_set = bool(spec_set)
            if autospec is True:
                autospec = original

            if _is_instance_mock(self.target):
                raise InvalidSpecError(
                    f'Cannot autospec attr {self.attribute!r} as the patch '
                    f'target has already been mocked out. '
                    f'[target={self.target!r}, attr={autospec!r}]')
            if _is_instance_mock(autospec):
                target_name = getattr(self.target, '__name__', self.target)
                raise InvalidSpecError(
                    f'Cannot autospec attr {self.attribute!r} from target '
                    f'{target_name!r} as it has already been mocked out. '
                    f'[target={self.target!r}, attr={autospec!r}]')

            new = create_autospec(autospec, spec_set=spec_set,
                                  _name=self.attribute, **kwargs)
        elif kwargs:
            # can't set keyword args when we aren't creating the mock
            # XXXX If new is a Mock we could call new.configure_mock(**kwargs)
            raise TypeError("Can't pass kwargs to a mock we aren't creating")

        new_attr = new

        self.temp_original = original
        self.is_local = local
        self._exit_stack = contextlib.ExitStack()
        try:
>           setattr(self.target, self.attribute, new_attr)
E           TypeError: cannot set 'getstr' attribute of immutable type '_curses.window'
/usr/lib/python3.10/unittest/mock.py:1556: TypeError
During handling of the above exception, another exception occurred:
/usr/lib/python3.10/unittest/mock.py:1376: in patched
    with self.decoration_helper(patched,
/usr/lib/python3.10/contextlib.py:135: in __enter__
    return next(self.gen)
/usr/lib/python3.10/unittest/mock.py:1358: in decoration_helper
    arg = exit_stack.enter_context(patching)
/usr/lib/python3.10/contextlib.py:492: in enter_context
    result = _cm_type.__enter__(cm)
/usr/lib/python3.10/unittest/mock.py:1569: in __enter__
    if not self.__exit__(*sys.exc_info()):
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
self = 
exc_info = (, TypeError("cannot set 'getstr' attribute of immutable type '_curses.window'"), )
    def __exit__(self, *exc_info):
        """Undo the patch."""
        if self.is_local and self.temp_original is not DEFAULT:
>           setattr(self.target, self.attribute, self.temp_original)
E           TypeError: cannot set 'getstr' attribute of immutable type '_curses.window'
/usr/lib/python3.10/unittest/mock.py:1575: TypeError
=========================== short test summary info ============================
FAILED tests/test_main.py::TestMainFunction::test_main_invalid_choice - TypeE...
FAILED tests/test_main.py::TestMainFunction::test_main_user_interaction - Typ...
========================= 2 failed, 16 passed in 1.87s =========================

trunk fmt tests/test_main.py || return 0 1/2 ✓
 ✔ Formatted tests/test_main.py
Re-checking autofixed files...

Checked 1 file
✔ No issues
Run trunk upgrade to upgrade 1 linter
trunk check --fix --print-failures tests/test_main.py 2/2 ❌ (`1`)
  ISSUES  
tests/test_main.py:11:19
 11:19  high  Undefined name `curses`  ruff/F821
Checked 1 file
✖ 1 new issue

trunk fmt tests/test_main.py || return 0 1/3 ✓
 ✔ Formatted tests/test_main.py
Re-checking autofixed files...

Checked 1 file
✔ No issues
Run trunk upgrade to upgrade 1 linter
trunk check --fix --print-failures tests/test_main.py 2/3 ✓
Checked 1 file
✔ No issues
pytest 3/3 ❌ (`1`)
============================= test session starts ==============================
platform linux -- Python 3.10.12, pytest-7.4.3, pluggy-1.3.0
rootdir: /repo
collected 18 items
tests/test_main.py Fs                                                    [ 11%]
tests/test_session.py .....                                              [ 38%]
tests/test_typing_practice.py ......                                     [ 72%]
tests/test_user.py .....                                                 [100%]
=================================== FAILURES ===================================
__________________ TestMainFunction.test_main_invalid_choice ___________________
self = 
    def __enter__(self):
        """Perform the patch."""
        new, spec, spec_set = self.new, self.spec, self.spec_set
        autospec, kwargs = self.autospec, self.kwargs
        new_callable = self.new_callable
        self.target = self.getter()

        # normalise False to None
        if spec is False:
            spec = None
        if spec_set is False:
            spec_set = None
        if autospec is False:
            autospec = None

        if spec is not None and autospec is not None:
            raise TypeError("Can't specify spec and autospec")
        if ((spec is not None or autospec is not None) and
            spec_set not in (True, None)):
            raise TypeError("Can't provide explicit spec_set *and* spec or autospec")

        original, local = self.get_original()

        if new is DEFAULT and autospec is None:
            inherit = False
            if spec is True:
                # set spec to the object we are replacing
                spec = original
                if spec_set is True:
                    spec_set = original
                    spec = None
            elif spec is not None:
                if spec_set is True:
                    spec_set = spec
                    spec = None
            elif spec_set is True:
                spec_set = original

            if spec is not None or spec_set is not None:
                if original is DEFAULT:
                    raise TypeError("Can't use 'spec' with create=True")
                if isinstance(original, type):
                    # If we're patching out a class and there is a spec
                    inherit = True
            if spec is None and _is_async_obj(original):
                Klass = AsyncMock
            else:
                Klass = MagicMock
            _kwargs = {}
            if new_callable is not None:
                Klass = new_callable
            elif spec is not None or spec_set is not None:
                this_spec = spec
                if spec_set is not None:
                    this_spec = spec_set
                if _is_list(this_spec):
                    not_callable = '__call__' not in this_spec
                else:
                    not_callable = not callable(this_spec)
                if _is_async_obj(this_spec):
                    Klass = AsyncMock
                elif not_callable:
                    Klass = NonCallableMagicMock

            if spec is not None:
                _kwargs['spec'] = spec
            if spec_set is not None:
                _kwargs['spec_set'] = spec_set

            # add a name to mocks
            if (isinstance(Klass, type) and
                issubclass(Klass, NonCallableMock) and self.attribute):
                _kwargs['name'] = self.attribute

            _kwargs.update(kwargs)
            new = Klass(**_kwargs)

            if inherit and _is_instance_mock(new):
                # we can only tell if the instance should be callable if the
                # spec is not a list
                this_spec = spec
                if spec_set is not None:
                    this_spec = spec_set
                if (not _is_list(this_spec) and not
                    _instance_callable(this_spec)):
                    Klass = NonCallableMagicMock

                _kwargs.pop('name')
                new.return_value = Klass(_new_parent=new, _new_name='()',
                                         **_kwargs)
        elif autospec is not None:
            # spec is ignored, new *must* be default, spec_set is treated
            # as a boolean. Should we check spec is not None and that spec_set
            # is a bool?
            if new is not DEFAULT:
                raise TypeError(
                    "autospec creates the mock for you. Can't specify "
                    "autospec and new."
                )
            if original is DEFAULT:
                raise TypeError("Can't use 'autospec' with create=True")
            spec_set = bool(spec_set)
            if autospec is True:
                autospec = original

            if _is_instance_mock(self.target):
                raise InvalidSpecError(
                    f'Cannot autospec attr {self.attribute!r} as the patch '
                    f'target has already been mocked out. '
                    f'[target={self.target!r}, attr={autospec!r}]')
            if _is_instance_mock(autospec):
                target_name = getattr(self.target, '__name__', self.target)
                raise InvalidSpecError(
                    f'Cannot autospec attr {self.attribute!r} from target '
                    f'{target_name!r} as it has already been mocked out. '
                    f'[target={self.target!r}, attr={autospec!r}]')

            new = create_autospec(autospec, spec_set=spec_set,
                                  _name=self.attribute, **kwargs)
        elif kwargs:
            # can't set keyword args when we aren't creating the mock
            # XXXX If new is a Mock we could call new.configure_mock(**kwargs)
            raise TypeError("Can't pass kwargs to a mock we aren't creating")

        new_attr = new

        self.temp_original = original
        self.is_local = local
        self._exit_stack = contextlib.ExitStack()
        try:
>           setattr(self.target, self.attribute, new_attr)
E           TypeError: cannot set 'getstr' attribute of immutable type '_curses.window'
/usr/lib/python3.10/unittest/mock.py:1556: TypeError
During handling of the above exception, another exception occurred:
/usr/lib/python3.10/unittest/mock.py:1376: in patched
    with self.decoration_helper(patched,
/usr/lib/python3.10/contextlib.py:135: in __enter__
    return next(self.gen)
/usr/lib/python3.10/unittest/mock.py:1358: in decoration_helper
    arg = exit_stack.enter_context(patching)
/usr/lib/python3.10/contextlib.py:492: in enter_context
    result = _cm_type.__enter__(cm)
/usr/lib/python3.10/unittest/mock.py:1569: in __enter__
    if not self.__exit__(*sys.exc_info()):
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
self = 
exc_info = (, TypeError("cannot set 'getstr' attribute of immutable type '_curses.window'"), )
    def __exit__(self, *exc_info):
        """Undo the patch."""
        if self.is_local and self.temp_original is not DEFAULT:
>           setattr(self.target, self.attribute, self.temp_original)
E           TypeError: cannot set 'getstr' attribute of immutable type '_curses.window'
/usr/lib/python3.10/unittest/mock.py:1575: TypeError
=========================== short test summary info ============================
FAILED tests/test_main.py::TestMainFunction::test_main_invalid_choice - TypeE...
=================== 1 failed, 16 passed, 1 skipped in 1.69s ====================


Step 3: 🔁 Code Review

Working on it...


🎉 Latest improvements to Sweep:


💡 To recreate the pull request edit the issue title or description. To tweak the pull request, leave a comment on the pull request. Join Our Discord