Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

AssertionError if stderr == subprocess.STDOUT and stdout is a file #143

Open
tbhartman opened this issue Feb 10, 2024 · 1 comment
Open

Comments

@tbhartman
Copy link

tbhartman commented Feb 10, 2024

Description

An AssertionError is raised at fake_popen.py:188when calling with argument stderr=subprocess.STDOUT if stdout is a file (stdout is expected to be None:

>           assert self.stdout is not None
E           AssertionError

pyvenv\Lib\site-packages\pytest_subprocess\fake_popen.py:188: AssertionError

subprocess.Popen handles this correctly; stderr is simply written to the file at stdout.

Example

Below is an example demonstrating the error. I have GIT_TRACE set so that git rev-parse gives a small amount of stdout and stderr:

$ git rev-parse HEAD
06:05:28.502294 exec-cmd.c:244          trace: resolved executable dir: C:/Program Files/Git/mingw64/bin
06:05:28.537291 git.c:463               trace: built-in: git rev-parse HEAD
47c77698dd8e0e35af00da56806fe98fcb9a1056

I define a function that runs the subprocess (so that I can call the same thing, using either the fake or real Popen:

def run_git_revparse():
    import os
    os.environ['GIT_TRACE'] = '1'
    with NamedTemporaryFile("w+b", delete=False) as f:
        path = pathlib.Path(f.name)
        # NOTE: I pass f.file because for some reason:
        #           isinstance(f, io.BufferedWriter) == False
        #       so pytest_subprocess isinstance checks would fail.  I am
        #       hesitant to change those tests, though you could simply check
        #       for a few key methods:
        #           hasattr(f.mode)
        #           isinstance(f.mode, str)
        #           hasattr(f.write)
        #           isinstance(f.write, types.FunctionType)
        p = subprocess.Popen(('git', 'rev-parse', 'HEAD'), stdout=f.file, stderr=subprocess.STDOUT)
        f.close()
        assert p.wait() == 0
        assert path.exists()
        output = path.read_text()

        # print to sys.stdout so I can see with `pytest --capture=no`
        print('-'*50)
        print(output)
        print('-'*50)

        output = output.splitlines()
        assert len(output) == 3

        # account for stderr coming before or after stdout
        if 'exec-cmd.c' in output[1]:
            output[2], output[0], output[1] = output
        # first two lines should be stderr
        assert 'exec-cmd.c' in output[0]
        assert 'git.c' in output[1]
        # last line should be a hex string
        assert len(set(output[2]) - set('0123456789abcdef')) == 0

Then define my test function, which first runs the real git, then the fake "git":

def test_fp(fp):
    print() # for --capture=no
    fp.allow_unregistered(True)
    run_git_revparse()
    fp.register(
        ["git", 'rev-parse', 'HEAD'],
        stdout=["47c77698dd8e0e35af00da56806fe98fcb9a1056"],
        stderr=[
            '05:17:17.982923 exec-cmd.c:244          trace: resolved executable dir: C:/Program Files/Git/mingw64/bin',
            '05:17:18.000760 git.c:463               trace: built-in: git rev-parse HEAD'
            ])
    run_git_revparse()

This raises the assertion error. I can correct this:

--- fake_popen.py   2024-02-10 06:09:01.897909800 -0500
+++ fake_popen.py       2024-02-10 06:08:49.610943500 -0500
@@ -181,22 +181,16 @@
         stdout = kwargs.get("stdout")
         if stdout == subprocess.PIPE:
             self.stdout = self._prepare_buffer(self.__stdout)
-        elif self.__is_io_writer(stdout):
+        elif isinstance(stdout, (io.BufferedWriter, io.TextIOWrapper)):
             self._write_to_buffer(self.__stdout, stdout)
         stderr = kwargs.get("stderr")
         if stderr == subprocess.STDOUT and self.__stderr:
-            if self.__is_io_writer(stdout):
-                self._write_to_buffer(self.__stderr, stdout)
-            else:
-                assert self.stdout is not None
-                self.stdout = self._prepare_buffer(self.__stderr, self.stdout)
+            assert self.stdout is not None
+            self.stdout = self._prepare_buffer(self.__stderr, self.stdout)
         elif stderr == subprocess.PIPE:
             self.stderr = self._prepare_buffer(self.__stderr)
-        elif self.__is_io_writer(stderr):
+        elif isinstance(stderr, (io.BufferedWriter, io.TextIOWrapper)):
             self._write_to_buffer(self.__stderr, stderr)
-
-    def __is_io_writer(self, o):
-        return isinstance(o, (io.BufferedWriter, io.TextIOWrapper, io.BufferedRandom))

     def _prepare_buffer(
         self,

Note:

  • I pulled out the isinstance testing.
  • I added io.BufferedRandom to the group, as that is what I had, and I would expect it should be included...my test still won't work without it.
@aklajnert
Copy link
Owner

Thank you for reporting that problem. Right now I don't really have time to work on that project. Feel free to submit a PR if you need it sooner, otherwise I'll work on it in the future, but can't specify exactly when.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants