Python unittest print logging works on Linux but not on Windows

618 views Asked by At

I'm trying to write a unittest that will redirect stdout and stderr to a file written on a Windows network drive. For some reason, the same script (only diff. is the directory path) works on Linux but not on Windows. When run on Windows, nothing is written to the log file.

Here is the script on Windows and nothing gets written:

import sys
import unittest

LOGDIR = r"\\windows\somenetwork\somedrive"
LOG1 = open(LOGDIR+'\myStdOut.txt', 'w')
LOG2 = open(LOGDIR+'\myStdErr.txt', 'w')

class MyTest(unittest.TestCase):

currentResult = None # holds last result object passed to run method

def setUp(self):
    pass

def tearDown(self):
    ok = self.currentResult.wasSuccessful()
    errors = self.currentResult.errors
    failures = self.currentResult.failures
    print ' All tests passed so far!' if ok else \
            ' %d errors and %d failures so far' % \
            (len(errors), len(failures))

def run(self, result=None):
    self.currentResult = result # remember result for use in tearDown
    unittest.TestCase.run(self, result) # call superclass run method

def test_onePlusOneEqualsTwo(self):
    self.assertTrue(1 + 1 == 2) # succeeds

def test_onePlusOneEqualsThree(self):
    self.assertTrue(1 + 1 == 3) # fails

def test_onePlusNoneIsNone(self):
    self.assertTrue(1 + None is None) # raises TypeError

if __name__ == '__main__':
    sys.stdout = LOG1 
    sys.stderr = LOG2
    unittest.main()
    LOG1.close()
    LOG2.close()

The same script run on Linux works or would at least write to the files:

import sys
import unittest

LOGDIR = r"/tmp"
LOG1 = open(LOGDIR+'/myStdOut.txt', 'w')
LOG2 = open(LOGDIR+'/myStdErr.txt', 'w')

class MyTestLinux(unittest.TestCase):

currentResult = None # holds last result object passed to run method

def setUp(self):
    pass

def tearDown(self):
    ok = self.currentResult.wasSuccessful()
    errors = self.currentResult.errors
    failures = self.currentResult.failures
    print ' All tests passed so far!' if ok else \
            ' %d errors and %d failures so far' % \
            (len(errors), len(failures))

def run(self, result=None):
    self.currentResult = result # remember result for use in tearDown
    unittest.TestCase.run(self, result) # call superclass run method

def test_onePlusOneEqualsTwo(self):
    self.assertTrue(1 + 1 == 2) # succeeds

def test_onePlusOneEqualsThree(self):
    self.assertTrue(1 + 1 == 3) # fails

def test_onePlusNoneIsNone(self):
    self.assertTrue(1 + None is None) # raises TypeError

if __name__ == '__main__':
    sys.stdout = LOG1 
    sys.stderr = LOG2
    unittest.main()
    LOG1.close()
    LOG2.close()
2

There are 2 answers

7
OozeMeister On BEST ANSWER

This may not be the best way to do this, but it works (at least on my machine):

import os
import sys
import unittest

STDOUT_FD = os.dup(sys.stdout.fileno())
STDERR_FD = os.dup(sys.stderr.fileno())

with open('stdout.txt', 'w') as f, open('stderr.txt', 'w') as g:
    os.dup2(f.fileno(), sys.stdout.fileno())
    os.dup2(g.fileno(), sys.stderr.fileno())


class MyTest(unittest.TestCase):
    def test_print(self):
        print 'some output'
        self.assertEqual('', '')

    def test_some_moar(self):
        print 'some other cool output'
        self.assertTrue(True)

if __name__ == '__main__':
    unittest.main()

print 'I am printing to stdout.txt'
print >> sys.stderr, 'I am printing to stderr.txt'

# revert the File Descriptors
os.dup2(STDOUT_FD, sys.stdout.fileno())
os.dup2(STDERR_FD, sys.stderr.fileno())

print "Yay! Back to printing in the console"

Running this produces:

stdout.txt

some output
some other cool output

stderr.txt

..
----------------------------------------------------------------------
Ran 2 tests in 0.000s

OK

os.dup(fd)

The os.dup function creates a copy of the file descriptor and returns the integer of that duplicated file descriptor. So, after the first two dup calls, there are two file descriptors pointing to stdout and two pointing to stderr.

os.dup2(fd, fd2)

The os.dup2 function copies the file descriptor from fd to fd2 and closes the file descriptor of fd2. So, after the dup2 calls, stdout now points to the f file descriptor and likewise stderr now points to the g file descriptor (and because dup2 closes the second file descriptor, there is only a single file descriptor for both stdout and stderr because of the copies made by the call to dup).

Print everything to your heart's content.

At the end, the last two dup2 calls revert the file descriptors using the copied file descriptors (so stdout and stderr point to where you'd expect) which also closes files f and g.

According to the dup2 docs, this works on both Linux and Windows.

[Edit]

If it's not too much work, I would suggest not using prints and use logging instead:

import logging
import unittest


class MyTest(unittest.TestCase):
    def test_print(self):
        logging.info('some output')
        self.assertEqual('', '')

    def test_some_moar(self):
        logging.info('some other cool output')
        self.assertTrue(True)

if __name__ == '__main__':
    logging.basicConfig(level=logging.INFO)
    # get the default logger
    logger = logging.getLogger()
    # add a file handler
    logger.addHandler(logging.FileHandler('stdout.txt', mode='w'))
    # set up a stream for all stderr output
    stderr_file = open('stderr.txt', 'w')
    # attach that stream to the testRunner
    unittest.main(testRunner=unittest.TextTestRunner(stream=stderr_file))
0
Colin D Bennett On

There's an issue where on Windows you can't reassign sys.stdout to redirect output when a script is started via a Windows file association on Windows. You might try the Windows hotfix that should fix it. Or you should be able to explicitly invoke python yourscript.py to work around it.