2

I want to capture and analyze output for a function which do this:

subprocess.call(args, stdin=sys.stdin, stdout=sys.stdout, stderr=sys.stderr)

When I run this code without any mocks under py.test I get error: ValueError: redirected Stdin is pseudofile, has no fileno()

When I patch sys.stdin (and others) with mock I get the same error.

When I patch sys.stdin with mock.mock_open() I get error: AttributeError: Mock object has no attribute 'write'

When I patch sys.stdin with mock.mock_open()('name', 'r') I get initial error: ValueError: redirected Stdin is pseudofile, has no fileno()

Is any way to pass some kind of mock object as stdin/out for subprocess?

I want something like this:

mocked = (put something here)
subprocess_call('ls', stdin=mocked, stdout=mocked, stderr=mocked)

Which should work when run under py.test.

Thanks.

George Shuklin
  • 6,952
  • 10
  • 39
  • 80
  • Why are you not mocking `subprocess.call`? You are not testing if `subprocess` works, only if the code that *uses* `subprocess` works. – Martijn Pieters Feb 21 '17 at 18:46
  • One could argue that it isn't your job to test that an external command works correctly; you want to mock `subprocess_call` itself. – chepner Feb 21 '17 at 18:47
  • This test is a part of integration tests, and I'm mocking only stdout/err to check command results (because otherwise they go directly to user terminal). I need actual external program output (from 'args') to see that my program and external program both do what they should to do. – George Shuklin Feb 21 '17 at 18:49
  • Then just assign a different object to `sys.stdout` and `sys.stderr`. Do take into account these must objects with file descriptors. – Martijn Pieters Feb 21 '17 at 18:50
  • Thank you. Can I create them with mock? I like the way I can inspect output later, and allocating real file is not very convenient (I need keep my eye for disk space, delete those files, etc). – George Shuklin Feb 21 '17 at 18:52
  • @GeorgeShuklin: yes, you can just mock out the `call()` function and have it return whatever you want. – Martijn Pieters Feb 21 '17 at 19:04
  • You can use `capfd` pytest fixture for that (decided not to write an answer here, since it was already written at https://stackoverflow.com/a/20507769/952234). – Yaroslav Nikitenko Jun 14 '22 at 16:16

1 Answers1

2

After some struggle I found a way to patch and intercept stdout for subprocess.call.

My main problem was that non-mocked version of call function requires actual file descriptor (FD) from operating system. No mock is capable to provide me such thing.

At the same time I really want to avoid creation of any files as it would really complicate setup/teardown for tests. I decided to use os.pipe call to create FDs and use those for stdout capture from subprocess.call

Here simplified version of my test:

def test_exec_results(ssh):
    rfd, wfd = os.pipe()
    w = os.fdopen(wfd, 'w', 0)
    with mock.patch.multiple(app.sys, stdout=w, stderr=w, stdin=None):
        app.myfunction('test message')
    output = os.read(rfd, 1000)
    assert 'test message ' in output
    assert 'some_other_things' in output
charlesreid1
  • 4,360
  • 4
  • 30
  • 52
George Shuklin
  • 6,952
  • 10
  • 39
  • 80