id,summary,reporter,owner,description,type,status,priority,milestone,component,resolution,keywords,cc,branch,branch_author,launchpad_bug
4184,reactor.spawnProcess() handles file-not-found differently on different platforms,TimAllen,,"In #3292, I wrote some RPM-building tests, and added some code to skip them if the `rpmbuild` command was not found:

{{{
def checkForRpmbuild():
    def skipTestIfError(result):
	out, err, code = result
	if code != 0 or not out.startswith(""RPM version""):
	    raise SkipTest(""rpmbuild must be present to test tap2rpm"")

    d = utils.getProcessOutputAndValue(""rpmbuild"", (""--version"",))
    d.addCallback(skipTestIfError)
    return d
}}}

On my Fedora workstation, this code worked fine - it found the installed `rpmbuild` binary, and the tests passed. On the various Ubuntu and Debian buildbots, this code worked fine - `rpmbuild` was not found, the process' return code was non-zero, and the tests were properly skipped. On the various Win32 buildbots, it broke horribly, with this traceback:
{{{
Traceback (most recent call last):
  File ""c:\twistedbot2\WXP32-full2.5-scmikes-select\Twisted\twisted\scripts\test\test_tap2rpm.py"", line 94, in setUp
    return self._checkForRpmbuild()
  File ""c:\twistedbot2\WXP32-full2.5-scmikes-select\Twisted\twisted\scripts\test\test_tap2rpm.py"", line 106, in _checkForRpmbuild
    d = utils.getProcessOutputAndValue(""rpmbuild"", (""--version"",))
  File ""c:\twistedbot2\WXP32-full2.5-scmikes-select\Twisted\twisted\internet\utils.py"", line 169, in getProcessOutputAndValue
    reactor)
  File ""c:\twistedbot2\WXP32-full2.5-scmikes-select\Twisted\twisted\internet\utils.py"", line 25, in _callProtocolWithDeferred
    reactor.spawnProcess(p, executable, (executable,)+tuple(args), env, path)
  File ""c:\twistedbot2\WXP32-full2.5-scmikes-select\Twisted\twisted\internet\posixbase.py"", line 234, in spawnProcess
    return Process(self, processProtocol, executable, args, env, path)
  File ""c:\twistedbot2\WXP32-full2.5-scmikes-select\Twisted\twisted\internet\_dumbwin32proc.py"", line 181, in __init__
    raise OSError(pwte)
exceptions.OSError: (2, 'CreateProcess', 'The system cannot find the file specified.')
}}}

It seems that on POSIX, calling `spawnProcess()` with a bogus command results in the callback being called; on Win32 it results in the errback being called, a behaviour likely to catch many people by surprise.

The `spawnProcess()` docs *do* say ""OSError is raised errno EAGAIN or ENOMEM if there are insufficient system resources to create a new process."" so anyone who calls `spawnProcess()` without handling OSError is technically in the wrong, but in that case the documentation still needs to be updated.

Exarkun has pointed out #3815 which doesn't mention a traceback, but does involve process-creation on Win32 being annoyingly non-POSIX. If someone with a Win32 machine determines this ticket is actually the same problem as that ticket, feel free to mark this as a duplicate.
",defect,new,normal,,core,,,,,,
