kivy/python-for-android

Mock assertion issue with ICU recipe under Python 3.12

s-t-e-v-e-n-k opened this issue · 3 comments

Checklist

  • the issue is indeed a bug and not a support request
  • issue doesn't already exist: https://github.com/kivy/python-for-android/issues
  • I have a short, runnable example that reproduces the issue
  • I reproduced the problem with the latest development version (p4a.branch = develop)
  • I used the grave accent (aka backticks) to format code or logs when appropriated

Versions

  • Python: 3.12
  • OS: openSUSE Tumbleweed
  • Kivy:
  • Cython:
  • OpenJDK:

Description

Python 3.12 shows an issue with ICU recipe, with respect to mock usage:

[   13s] ________________________ TestIcuRecipe.test_build_arch _________________________
[   13s]
[   13s] self = <tests.recipes.test_icu.TestIcuRecipe testMethod=test_build_arch>
[   13s] mock_shutil_which = <MagicMock name='which' id='140502634298352'>
[   13s] mock_ensure_dir = <MagicMock name='ensure_dir' id='140502633992880'>
[   13s] mock_sh_make = <MagicMock name='make' id='140502634286352'>
[   13s] mock_sh_command = <MagicMock name='Command' id='140502637079024'>
[   13s] mock_chdir = <MagicMock name='chdir' id='140502637078016'>
[   13s] mock_makedirs = <MagicMock name='makedirs' id='140502637073360'>
[   13s]
[   13s]     @mock.patch("pythonforandroid.util.makedirs")
[   13s]     @mock.patch("pythonforandroid.util.chdir")
[   13s]     @mock.patch("pythonforandroid.bootstrap.sh.Command")
[   13s]     @mock.patch("pythonforandroid.recipes.icu.sh.make")
[   13s]     @mock.patch("pythonforandroid.build.ensure_dir")
[   13s]     @mock.patch("shutil.which")
[   13s]     def test_build_arch(
[   13s]         self,
[   13s]         mock_shutil_which,
[   13s]         mock_ensure_dir,
[   13s]         mock_sh_make,
[   13s]         mock_sh_command,
[   13s]         mock_chdir,
[   13s]         mock_makedirs,
[   13s]     ):
[   13s]         mock_shutil_which.return_value = os.path.join(
[   13s]             self.ctx._ndk_dir,
[   13s]             f"toolchains/llvm/prebuilt/{self.ctx.ndk.host_tag}/bin/clang",
[   13s]         )
[   13s]         self.ctx.toolchain_version = "4.9"
[   13s]         self.recipe.build_arch(self.arch)
[   13s]
[   13s]         # We expect some calls to `sh.Command`
[   13s]         build_root = self.recipe.get_build_dir(self.arch.arch)
[   13s] >       mock_sh_command.has_calls(
[   13s]             [
[   13s]                 mock.call(
[   13s]                     os.path.join(build_root, "source", "runConfigureICU")
[   13s]                 ),
[   13s]                 mock.call(os.path.join(build_root, "source", "configure")),
[   13s]             ]
[   13s]         )
[   13s]
[   13s] tests/recipes/test_icu.py:55:
[   13s] _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
[   13s]
[   13s] self = <MagicMock name='Command' id='140502637079024'>, name = 'has_calls'
[   13s]
[   13s]     def __getattr__(self, name):
[   13s]         if name in {'_mock_methods', '_mock_unsafe'}:
[   13s]             raise AttributeError(name)
[   13s]         elif self._mock_methods is not None:
[   13s]             if name not in self._mock_methods or name in _all_magics:
[   13s]                 raise AttributeError("Mock object has no attribute %r" % name)
[   13s]         elif _is_magic(name):
[   13s]             raise AttributeError(name)
[   13s]         if not self._mock_unsafe and (not self._mock_methods or name not in self._mock_methods):
[   13s]             if name.startswith(('assert', 'assret', 'asert', 'aseert', 'assrt')) or name in _ATTRIB_DENY_LIST:
[   13s] >               raise AttributeError(
[   13s]                     f"{name!r} is not a valid assertion. Use a spec "
[   13s]                     f"for the mock if {name!r} is meant to be an attribute.")
[   13s] E               AttributeError: 'has_calls' is not a valid assertion. Use a spec for the mock if 'has_calls' is meant to be an attribute.
[   13s]
[   13s] /usr/lib64/python3.12/unittest/mock.py:663: AttributeError

Python interpreters before 3.12 would just blindly call any method they were given -- which also means this hasn't been asserting correctly, either. The correct method name is assert_has_calls.

How did you build python3.12? As p4a does not yet have 3.12 support. like this?:

requirements = python==3.12.2, hostpython==3.12.2

Oh, sorry, I meant running the testsuite using pytest under Python 3.12.

Python interpreters before 3.12 would just blindly call any method they were given -- which also means this hasn't been asserting correctly, either. The correct method name is assert_has_calls

Good catch and I'm so glad they fixed that in Python 3.12! This was definitely a pit fall. I never liked the assertion helpers anyway because I think it better reads to use the play assert (all the assert are aligned, highlighted and easier to catch with the eye when using play assert).
Would you mind writing a fix for that test?