pytest icon indicating copy to clipboard operation
pytest copied to clipboard

fixture function `makepyfile` does not encode files properly

Open d-chris opened this issue 10 months ago • 2 comments

makepyfile is ignoring the encoding parameter, in both pytester and testdir fixtures.

found the issue working on windows with cp1252 encoding and using pytest-doctestplus.

i traced the issue to makepyfile, arguments and kwargs are passed unpacked.

 make_file = testdir.makepyfile(
            f"""
            def f():
                '''
                >>> print('{a}')
                {b}
                '''
                pass
            """.encode(encoding=encoding),
            encoding=encoding, # will be packed into `files` and default utf-8 encoding will be used
        )
$ python --version
Python 3.13.2

$ pip freeze
colorama==0.4.6
iniconfig==2.0.0
packaging==24.2
pluggy==1.5.0
pytest==8.3.3

d-chris avatar Mar 27 '25 19:03 d-chris

I don't see a bug here, as such an argument simply doesn't exist, and neither is it valid to pass bytes in in the first place.

From the docs:

kwargs (str) – Each keyword is the name of a file, while the value of it will be written as contents of the file.

That passing in bytes works is a coincidence, and seems to be a historic artifact from Python 2 times (see e.g. f47ae749817df967e1ba2c07e67eda49ae328077). The encoding= argument for _makefile was apparently added for pytest's selftests: d254c6b0aeb872a791889d5e3497fa3ca679c482, but is not actually used since 4588653b2497ed25976b7aaff225b889fb476756.

I think we should simply drop all the code dealing with bytes from Pytester._makefile and add proper type annotations to Pytester.makepyfile (not sure why everything but that has those!).

As for your tests, you should probably do something like this instead:

(pytester.path / "myfile.py").write_bytes(content)

The-Compiler avatar Mar 27 '25 20:03 The-Compiler

for testing i agree, its no big issue.

basically _makefile was confusing me, it has a parameter encoding but kwargs are not unpacked instead passed on as dict seemed wrong to me.

def makepyfile(self, *args, **kwargs) -> Path:
    return self._makefile(".py", args, kwargs)

def _makefile(
  self,
  ext: str,                        # ".py"
  lines: Sequence[Any | bytes],    # contains args
  files: dict[str, str],           # contains kwargs
  encoding: str = "utf-8",         # useless? move into function as 'const'
) -> Path:
    ...

d-chris avatar Mar 27 '25 21:03 d-chris