2

I am using the standard library unittest module (so I run my tests with python -m unittest).

I have defined setUpModule to start a subprocess in the background (with subprocess.Popen) and tearDownModule to close its input and output streams, then call os.killpg on its process ID.

This all works fine if I let a test run its course, but if I stop it early using Ctrl-C, I get a bunch of warnings and my terminal slows to a crawl:

KeyboardInterrupt
sys:1: ResourceWarning: unclosed file <_io.FileIO name=6 mode='rb'>
/.../lib/python3.4/importlib/_bootstrap.py:2150: ImportWarning: sys.meta_path is empty
sys:1: ResourceWarning: unclosed file <_io.FileIO name=7 mode='wb'>
sys:1: ResourceWarning: unclosed file <_io.BufferedWriter name='/dev/null'>

Is there some way I can intercept the KeyboardInterrupt in order to clean up properly? Is there a better way to start and stop an external program for a test module?

user1475412
  • 1,659
  • 2
  • 22
  • 30
  • Looks similar to http://stackoverflow.com/a/13382205/5162063 – mzc Aug 06 '15 at 20:27
  • That appears to register `KeyboardInterrupt` handlers for test method results, not the module itself. It's not clear since there's not much information there. – user1475412 Aug 06 '15 at 20:29

3 Answers3

1

Depending on how your tests are organized, you could also catch the KeyboardInterrupt and call the tearDown method in the except block:

import unittest

class MyTestCase(unittest.TestCase):

  def test_one(self):
      for i in range(1<<20):
         if i % 271 == 0:
            print i

  @classmethod
  def tearDownClass(cls):
      print("\nteardown")

if __name__ == '__main__':
     try:
        unittest.main()
     except KeyboardInterrupt:
        MyTestCase.tearDownClass()
mzc
  • 3,265
  • 1
  • 20
  • 25
1

The solution I found was to override the unittest.TextTestRunner and catch the KeyboardInterrupt in the run() method, and then run the module tear down.

class CustomTestRunner(unittest.TextTestRunner):
    def _makeResult(self):
        self.my_result = self.resultclass(self.stream, self.descriptions, self.verbosity)
        return self.my_result

    def run(self, test):
        try:
            return super().run(test)
        except KeyboardInterrupt:
            self.stream.writeln("Caught KeyboardInterrupt.")
            self.my_result.printErrors()
            test._handleModuleTearDown(self.my_result)  # do cleanup
            return self.my_result
0

For what it's worth, I tried following the instructions in https://stackoverflow.com/a/4205386/1475412 and found a solution.

I registered a handler for SIGINT that kills the subprocess, then calls sys.exit(). I thought I would be able to re-raise KeyboardInterrupt inside the handler but that didn't work.

Community
  • 1
  • 1
user1475412
  • 1,659
  • 2
  • 22
  • 30