Commits

Ronald Oussoren committed 2e3f8a0

For for issue 3585235 on SF.net

The threading additions in the Foundation wrappers didn't work due to a type.

Added unittests and uncovered some more problems because of that.

  • Participants
  • Parent commits 0c4bc85
  • Branches pyobjc-2.4.x

Comments (0)

Files changed (4)

File pyobjc-core/NEWS.txt

 
 An overview of the relevant changes in new, and older, releases.
 
+Version 2.4.1
+-------------
+
+- Fix for issue 3585235 on SourceForge: the threading helper category on
+  NSObject didn't work due to a typo (defined in the Cocoa bindings)
+
+  Fix is based on a patch by "Kentzo" with further updates and tests by
+  Ronald.
+
 Version 2.4
 -----------
 

File pyobjc-framework-Cocoa/Doc/NEWS.txt

 NEWS file for pyobjc-framework-Cocoa
 ====================================
 
+Version 2.4.1
+-------------
+
+* Fix for issue 3585235 on SourceForge: the threading helper category on
+  NSObject didn't work due to a typo.
+
+  Fix is based on a patch by "Kentzo" with further updates and tests by
+  Ronald.
+
 Version 2.4
 -----------
 

File pyobjc-framework-Cocoa/Lib/Foundation/_nsobject.py

 """
 Define a category on NSObject with some useful methods.
-
-FIXME: 
-- add signature information (namedSelector) to all methods
-  (not strictly needed)
-- add docstrings everywhere
-- create unittests
 """
 import objc 
 import sys
     def _pyobjc_performOnThread_(self, callinfo):
         try:
             sel, arg = callinfo
-            m = self.methodForSelector_(sel)
+            # XXX: PyObjC's methodForSelector implementation doesn't work
+            # with Python methods, using getattr instead
+            #m = self.methodForSelector_(sel)
+            m = getattr(self, sel)
             m(arg)
         except:
             import traceback
-            traceback.print_exc(fp=sys.stderr)
+            traceback.print_exc(file=sys.stderr)
 
     @objc.namedSelector(b"_pyobjc_performOnThreadWithResult:")
     def _pyobjc_performOnThreadWithResult_(self, callinfo):
         try:
             sel, arg, result = callinfo
-            m = self.methodForSelector_(sel)
+            #m = self.methodForSelector_(sel)
+            m = getattr(self, sel)
             r = m(arg)
             result.append((True, r))
         except:
             NSRunLoop on the other thread).
             """
             self.performSelector_onThread_withObject_waitUntilDone_(
-                    'pyobjc_performOnThread:', thread, (aSelector, arg), wait)
+                    b'_pyobjc_performOnThread:', thread, (aSelector, arg), wait)
 
         @objc.namedSelector(b"pyobjc_performSelector:onThread:withObject:waitUntilDone:modes:")
         def pyobjc_performSelector_onThread_withObject_waitUntilDone_modes_(
             NSRunLoop on the other thread).
             """
             self.performSelector_onThread_withObject_waitUntilDone_modes_(
-                'pyobjc_performOnThread:', thread, (aSelector, arg), wait, modes)
+                b'_pyobjc_performOnThread:', thread, (aSelector, arg), wait, modes)
 
     @objc.namedSelector(b"pyobjc_performSelector:withObject:afterDelay:")
     def pyobjc_performSelector_withObject_afterDelay_(
         NSRunLoop).
         """
         self.performSelector_withObject_afterDelay_(
-            'pyobjc_performOnThread:', (aSelector, arg), delay)
+            b'_pyobjc_performOnThread:', (aSelector, arg), delay)
 
     @objc.namedSelector(b"pyobjc_performSelector:withObject:afterDelay:inModes:")
     def pyobjc_performSelector_withObject_afterDelay_inModes_(
         NSRunLoop).
         """
         self.performSelector_withObject_afterDelay_inModes_(
-            'pyobjc_performOnThread:', (aSelector, arg), delay, modes)
+            b'_pyobjc_performOnThread:', (aSelector, arg), delay, modes)
 
-    if hasattr(NSObject, "performSelectorInBackground_withObject_waitUntilDone_"):
+    if hasattr(NSObject, "performSelectorInBackground_withObject_"):
         @objc.namedSelector(b"pyobjc_performSelectorInBackground:withObject:")
         def pyobjc_performSelectorInBackground_withObject_(
                 self, aSelector, arg):
             NSRunLoop).
             """
             self.performSelectorInBackground_withObject_(
-                'pyobjc_performOnThread:', (aSelector, arg))
+                b'_pyobjc_performOnThread:', (aSelector, arg))
 
-
-    @objc.namedSelector(b"pyobjc_performSelectorInBackground:withObject:waitUntilDone:")
+    @objc.namedSelector(b"pyobjc_performSelectorOnMainThread:withObject:waitUntilDone:")
     def pyobjc_performSelectorOnMainThread_withObject_waitUntilDone_(
             self, aSelector, arg, wait):
         """
         NSRunLoop in the main thread).
         """
         self.performSelectorOnMainThread_withObject_waitUntilDone_(
-            'pyobjc_performOnThread:', (aSelector, arg), wait)
+            b'_pyobjc_performOnThread:', (aSelector, arg), wait)
 
     @objc.namedSelector(b"pyobjc_performSelectorOnMainThread:withObject:waitUntilDone:modes:")
     def pyobjc_performSelectorOnMainThread_withObject_waitUntilDone_modes_(
         NSRunLoop in the main thread).
         """
         self.performSelectorOnMainThread_withObject_waitUntilDone_modes_(
-            'pyobjc_performOnThread:', (aSelector, arg), wait, modes)
+            b'_pyobjc_performOnThread:', (aSelector, arg), wait, modes)
 
 
     # And some a some versions that return results
         """
         result = []
         self.performSelectorOnMainThread_withObject_waitUntilDone_modes_(
-            'pyobjc_performOnThreadWithResult:', 
+            b'_pyobjc_performOnThreadWithResult:', 
             (aSelector, arg, result), True, modes)
         isOK, result = result[0]
 
             self, aSelector, arg):
         result = []
         self.performSelectorOnMainThread_withObject_waitUntilDone_(
-            'pyobjc_performOnThreadWithResult:', 
+            b'_pyobjc_performOnThreadWithResult:', 
             (aSelector, arg, result), True)
         isOK, result = result[0]
 
                 self, aSelector, thread, arg, modes):
             result = []
             self.performSelector_onThread_withObject_waitUntilDone_modes_(
-                'pyobjc_performOnThreadWithResult:', thread,
+                b'_pyobjc_performOnThreadWithResult:', thread,
                 (aSelector, arg, result), True, modes)
             isOK, result = result[0]
 
                 self, aSelector, thread, arg):
             result = []
             self.performSelector_onThread_withObject_waitUntilDone_(
-                'pyobjc_performOnThreadWithResult:', thread,
+                b'_pyobjc_performOnThreadWithResult:', thread,
                 (aSelector, arg, result), True)
             isOK, result = result[0]
 

File pyobjc-framework-Cocoa/PyObjCTest/test_nsobject_additions.py

+from PyObjCTools.TestSupport import *
+
+import Foundation
+import sys
+
+if sys.version_info[0] == 2:
+    from StringIO import StringIO
+else:
+    from io import StringIO
+
+class TheadingHelperTestHelper (Foundation.NSObject):
+    def init(self):
+        self = super(TheadingHelperTestHelper, self).init()
+        if self is None:
+            return None
+
+        self.calls = []
+        return self
+
+    def performSelector_onThread_withObject_waitUntilDone_(self,
+            selector, thread, object, wait):
+        assert isinstance(selector, bytes)
+        assert isinstance(thread, Foundation.NSThread)
+        assert isinstance(wait, bool)
+
+        self.calls.append((selector, thread, object, wait))
+        getattr(self, selector)(object)
+
+    def performSelector_onThread_withObject_waitUntilDone_modes_(self,
+            selector, thread, object, wait, modes):
+        assert isinstance(selector, bytes)
+        assert isinstance(thread, Foundation.NSThread)
+        assert isinstance(wait, bool)
+
+        self.calls.append((selector, thread, object, wait, modes))
+        getattr(self, selector)(object)
+
+    def performSelector_withObject_afterDelay_(self,
+            selector, object, delay):
+        assert isinstance(selector, bytes)
+
+        self.calls.append((selector, object, delay))
+        getattr(self, selector)(object)
+
+    def performSelector_withObject_afterDelay_inModes_(self,
+            selector, object, delay, modes):
+        assert isinstance(selector, bytes)
+
+        self.calls.append((selector, object, delay, modes))
+        getattr(self, selector)(object)
+
+    def performSelectorInBackground_withObject_(self,
+            selector, object):
+        self.calls.append((selector, object))
+        getattr(self, selector)(object)
+
+    def performSelectorOnMainThread_withObject_waitUntilDone_(self,
+            selector, object, wait):
+        self.calls.append((selector, object, wait))
+        getattr(self, selector)(object)
+
+    def performSelectorOnMainThread_withObject_waitUntilDone_modes_(self,
+            selector, object, wait, modes):
+        self.calls.append((selector, object, wait, modes))
+        getattr(self, selector)(object)
+
+    def sel1_(self, arg):
+        pass
+
+    def sel2_(self, arg):
+        return arg * 2
+
+    def sel3_(self, arg):
+        return 1/arg
+
+class TestThreadingHelpers (TestCase):
+    def testAsyncOnThreadNoResult(self):
+        # pyobjc_performSelector_onThread_withObject_waitUntilDone_
+        obj = TheadingHelperTestHelper.alloc().init()
+        thr = Foundation.NSThread.mainThread()
+
+        obj.pyobjc_performSelector_onThread_withObject_waitUntilDone_(
+                b'sel1:', thr, 1, False),
+        obj.pyobjc_performSelector_onThread_withObject_waitUntilDone_(
+                b'sel2:', thr, 2, True)
+        obj.pyobjc_performSelector_onThread_withObject_waitUntilDone_(
+                b'isEqual:', thr, obj, True)
+
+        self.assertEqual(obj.calls, [
+            (b'_pyobjc_performOnThread:', thr, (b'sel1:', 1), False),
+            (b'_pyobjc_performOnThread:', thr, (b'sel2:', 2), True),
+            (b'_pyobjc_performOnThread:', thr, (b'isEqual:', obj), True),
+        ])
+
+        # Raise an exception
+        orig_stderr = sys.stderr
+        sys.stderr = StringIO()
+        try:
+            obj.calls[:] = []
+            obj.pyobjc_performSelector_onThread_withObject_waitUntilDone_(
+                    b'sel3:', thr, 0, False)
+            self.assertEqual(obj.calls, [
+                (b'_pyobjc_performOnThread:', thr, (b'sel3:', 0), False),
+            ])
+            self.assertIn('Traceback', sys.stderr.getvalue())
+        finally:
+            sys.stderr = orig_stderr
+
+    def testAsyncOnThreadNoResultModes(self):
+        # pyobjc_performSelector_onThread_withObject_waitUntilDone_modes_
+        obj = TheadingHelperTestHelper.alloc().init()
+        thr = Foundation.NSThread.mainThread()
+
+        obj.pyobjc_performSelector_onThread_withObject_waitUntilDone_modes_(
+                b'sel1:', thr, 1, False, 0),
+        obj.pyobjc_performSelector_onThread_withObject_waitUntilDone_modes_(
+                b'sel2:', thr, 2, True, 1)
+        obj.pyobjc_performSelector_onThread_withObject_waitUntilDone_modes_(
+                b'isEqual:', thr, obj, True, 2)
+
+        self.assertEqual(obj.calls, [
+            (b'_pyobjc_performOnThread:', thr, (b'sel1:', 1), False, 0),
+            (b'_pyobjc_performOnThread:', thr, (b'sel2:', 2), True, 1),
+            (b'_pyobjc_performOnThread:', thr, (b'isEqual:', obj), True, 2),
+        ])
+
+        # Raise an exception
+        orig_stderr = sys.stderr
+        sys.stderr = StringIO()
+        try:
+            obj.calls[:] = []
+            obj.pyobjc_performSelector_onThread_withObject_waitUntilDone_modes_(
+                    b'sel3:', thr, 0, False, 4)
+            self.assertEqual(obj.calls, [
+                (b'_pyobjc_performOnThread:', thr, (b'sel3:', 0), False, 4),
+            ])
+            self.assertIn('Traceback', sys.stderr.getvalue())
+        finally:
+            sys.stderr = orig_stderr
+
+    def testAsyncWithDelayNoResult(self):
+        # pyobjc_performSelector_withObject_afterDelay_
+        obj = TheadingHelperTestHelper.alloc().init()
+
+        obj.pyobjc_performSelector_withObject_afterDelay_(b'sel1:', 1, 1.0)
+        obj.pyobjc_performSelector_withObject_afterDelay_(b'sel2:', 2, 4.5)
+        obj.pyobjc_performSelector_withObject_afterDelay_(b'isEqual:', obj, 8.5)
+
+        self.assertEqual(obj.calls, [
+            (b'_pyobjc_performOnThread:', (b'sel1:', 1), 1.0),
+            (b'_pyobjc_performOnThread:', (b'sel2:', 2), 4.5),
+            (b'_pyobjc_performOnThread:', (b'isEqual:', obj), 8.5),
+        ])
+
+        # Raise an exception
+        orig_stderr = sys.stderr
+        sys.stderr = StringIO()
+        try:
+            obj.calls[:] = []
+            obj.pyobjc_performSelector_withObject_afterDelay_(
+                    b'sel3:', 0, 0.5)
+            self.assertEqual(obj.calls, [
+                (b'_pyobjc_performOnThread:', (b'sel3:', 0), 0.5),
+            ])
+            self.assertIn('Traceback', sys.stderr.getvalue())
+        finally:
+            sys.stderr = orig_stderr
+
+    def testAsyncWithDelayNoResultModes(self):
+        # pyobjc_performSelector_withObject_afterDelay_inModes_
+        obj = TheadingHelperTestHelper.alloc().init()
+
+        obj.pyobjc_performSelector_withObject_afterDelay_inModes_(b'sel1:', 1, 1.0, 0)
+        obj.pyobjc_performSelector_withObject_afterDelay_inModes_(b'sel2:', 2, 4.5, 1)
+        obj.pyobjc_performSelector_withObject_afterDelay_inModes_(b'isEqual:', obj, 8.5, 2)
+
+        self.assertEqual(obj.calls, [
+            (b'_pyobjc_performOnThread:', (b'sel1:', 1), 1.0, 0),
+            (b'_pyobjc_performOnThread:', (b'sel2:', 2), 4.5, 1),
+            (b'_pyobjc_performOnThread:', (b'isEqual:', obj), 8.5, 2),
+        ])
+
+        # Raise an exception
+        orig_stderr = sys.stderr
+        sys.stderr = StringIO()
+        try:
+            obj.calls[:] = []
+            obj.pyobjc_performSelector_withObject_afterDelay_inModes_(
+                    b'sel3:', 0, 0.5, 3)
+            self.assertEqual(obj.calls, [
+                (b'_pyobjc_performOnThread:', (b'sel3:', 0), 0.5, 3),
+            ])
+            self.assertIn('Traceback', sys.stderr.getvalue())
+        finally:
+            sys.stderr = orig_stderr
+
+    def testInBGNoResult(self):
+        # pyobjc_performSelectorInBackground_withObject_
+
+        obj = TheadingHelperTestHelper.alloc().init()
+
+        obj.pyobjc_performSelectorInBackground_withObject_(b'sel1:', 1)
+        obj.pyobjc_performSelectorInBackground_withObject_(b'sel2:', 2)
+        obj.pyobjc_performSelectorInBackground_withObject_(b'isEqual:', obj)
+
+        self.assertEqual(obj.calls, [
+            (b'_pyobjc_performOnThread:', (b'sel1:', 1)),
+            (b'_pyobjc_performOnThread:', (b'sel2:', 2)),
+            (b'_pyobjc_performOnThread:', (b'isEqual:', obj)),
+        ])
+
+        # Raise an exception
+        orig_stderr = sys.stderr
+        sys.stderr = StringIO()
+        try:
+            obj.calls[:] = []
+            obj.pyobjc_performSelectorInBackground_withObject_(
+                    b'sel3:', 0)
+            self.assertEqual(obj.calls, [
+                (b'_pyobjc_performOnThread:', (b'sel3:', 0)),
+            ])
+            self.assertIn('Traceback', sys.stderr.getvalue())
+        finally:
+            sys.stderr = orig_stderr
+
+    def testOnMtNoResultWait(self):
+        # pyobjc_performSelectorInBackground_withObject_waitUntilDone_
+
+        obj = TheadingHelperTestHelper.alloc().init()
+
+        obj.pyobjc_performSelectorOnMainThread_withObject_waitUntilDone_(b'sel1:', 1, True)
+        obj.pyobjc_performSelectorOnMainThread_withObject_waitUntilDone_(b'sel2:', 2, False)
+        obj.pyobjc_performSelectorOnMainThread_withObject_waitUntilDone_(b'isEqual:', obj, True)
+
+        self.assertEqual(obj.calls, [
+            (b'_pyobjc_performOnThread:', (b'sel1:', 1), True),
+            (b'_pyobjc_performOnThread:', (b'sel2:', 2), False),
+            (b'_pyobjc_performOnThread:', (b'isEqual:', obj), True),
+        ])
+
+        # Raise an exception
+        orig_stderr = sys.stderr
+        sys.stderr = StringIO()
+        try:
+            obj.calls[:] = []
+            obj.pyobjc_performSelectorOnMainThread_withObject_waitUntilDone_(
+                    b'sel3:', 0, False)
+            self.assertEqual(obj.calls, [
+                (b'_pyobjc_performOnThread:', (b'sel3:', 0), False),
+            ])
+            self.assertIn('Traceback', sys.stderr.getvalue())
+        finally:
+            sys.stderr = orig_stderr
+
+    def testOnMtNoResultWaitModes(self):
+        # pyobjc_performSelectorInBackground_withObject_waitUntilDone_modes_
+
+        obj = TheadingHelperTestHelper.alloc().init()
+
+        obj.pyobjc_performSelectorOnMainThread_withObject_waitUntilDone_modes_(b'sel1:', 1, True, 4)
+        obj.pyobjc_performSelectorOnMainThread_withObject_waitUntilDone_modes_(b'sel2:', 2, False, 5)
+        obj.pyobjc_performSelectorOnMainThread_withObject_waitUntilDone_modes_(b'isEqual:', obj, True, 6)
+
+        self.assertEqual(obj.calls, [
+            (b'_pyobjc_performOnThread:', (b'sel1:', 1), True, 4),
+            (b'_pyobjc_performOnThread:', (b'sel2:', 2), False, 5),
+            (b'_pyobjc_performOnThread:', (b'isEqual:', obj), True, 6),
+        ])
+
+        # Raise an exception
+        orig_stderr = sys.stderr
+        sys.stderr = StringIO()
+        try:
+            obj.calls[:] = []
+            obj.pyobjc_performSelectorOnMainThread_withObject_waitUntilDone_modes_(
+                    b'sel3:', 0, False, 7)
+            self.assertEqual(obj.calls, [
+                (b'_pyobjc_performOnThread:', (b'sel3:', 0), False, 7),
+            ])
+            self.assertIn('Traceback', sys.stderr.getvalue())
+        finally:
+            sys.stderr = orig_stderr
+    
+    def testOnMtWithResult(self):
+        # pyobjc_performSelectorOnMainThread_withObject_
+        obj = TheadingHelperTestHelper.alloc().init()
+
+        r = obj.pyobjc_performSelectorOnMainThread_withObject_('sel2:', 3)
+        self.assertEqual(r, 6)
+        r = obj.pyobjc_performSelectorOnMainThread_withObject_('sel3:', 2.0)
+        self.assertEqual(r, 0.5)
+
+        self.assertEqual(obj.calls, [
+            (b'_pyobjc_performOnThreadWithResult:', (b'sel2:', 3, [(True, 6)]), True),
+            (b'_pyobjc_performOnThreadWithResult:', (b'sel3:', 2.0, [(True, 0.5)]), True),
+        ])
+
+        # Raise an exception
+        orig_stderr = sys.stderr
+        sys.stderr = StringIO()
+        try:
+            obj.calls[:] = []
+
+            self.assertRaises(ZeroDivisionError, obj.pyobjc_performSelectorOnMainThread_withObject_,
+                    b'sel3:', 0)
+
+            self.assertEqual(len(obj.calls), 1)
+            self.assertEqual(obj.calls[0][0], b'_pyobjc_performOnThreadWithResult:')
+            self.assertEqual(obj.calls[0][1][-1][0][0],  False)
+            self.assertNotIn('Traceback', sys.stderr.getvalue())
+        finally:
+            sys.stderr = orig_stderr
+
+    def testOnMtWithResultModes(self):
+        obj = TheadingHelperTestHelper.alloc().init()
+
+        r = obj.pyobjc_performSelectorOnMainThread_withObject_modes_('sel2:', 3, 1)
+        self.assertEqual(r, 6)
+        r = obj.pyobjc_performSelectorOnMainThread_withObject_modes_('sel3:', 2.0, 2)
+        self.assertEqual(r, 0.5)
+
+        self.assertEqual(obj.calls, [
+            (b'_pyobjc_performOnThreadWithResult:', (b'sel2:', 3, [(True, 6)]), True, 1),
+            (b'_pyobjc_performOnThreadWithResult:', (b'sel3:', 2.0, [(True, 0.5)]), True, 2),
+        ])
+
+        # Raise an exception
+        orig_stderr = sys.stderr
+        sys.stderr = StringIO()
+        try:
+            obj.calls[:] = []
+
+            self.assertRaises(ZeroDivisionError, obj.pyobjc_performSelectorOnMainThread_withObject_modes_,
+                    b'sel3:', 0, 3)
+
+            self.assertEqual(len(obj.calls), 1)
+            self.assertEqual(obj.calls[0][0], b'_pyobjc_performOnThreadWithResult:')
+            self.assertEqual(obj.calls[0][1][-1][0][0],  False)
+            self.assertNotIn('Traceback', sys.stderr.getvalue())
+        finally:
+            sys.stderr = orig_stderr
+
+    def testOnThreadWithResult(self):
+        obj = TheadingHelperTestHelper.alloc().init()
+        thr = Foundation.NSThread.mainThread()
+
+        r = obj.pyobjc_performSelector_onThread_withObject_('sel2:', thr, 3)
+        self.assertEqual(r, 6)
+        r = obj.pyobjc_performSelector_onThread_withObject_('sel3:', thr, 2.0)
+        self.assertEqual(r, 0.5)
+
+        self.assertEqual(obj.calls, [
+            (b'_pyobjc_performOnThreadWithResult:', thr, (b'sel2:', 3, [(True, 6)]), True),
+            (b'_pyobjc_performOnThreadWithResult:', thr, (b'sel3:', 2.0, [(True, 0.5)]), True),
+        ])
+
+        # Raise an exception
+        orig_stderr = sys.stderr
+        sys.stderr = StringIO()
+        try:
+            obj.calls[:] = []
+
+            self.assertRaises(ZeroDivisionError, obj.pyobjc_performSelector_onThread_withObject_,
+                    b'sel3:', thr, 0)
+
+            self.assertEqual(len(obj.calls), 1)
+            self.assertEqual(obj.calls[0][0], b'_pyobjc_performOnThreadWithResult:')
+            self.assertEqual(obj.calls[0][2][-1][0][0],  False)
+            self.assertNotIn('Traceback', sys.stderr.getvalue())
+        finally:
+            sys.stderr = orig_stderr
+
+    def testOnThreadWithResultModes(self):
+        obj = TheadingHelperTestHelper.alloc().init()
+        thr = Foundation.NSThread.mainThread()
+
+        r = obj.pyobjc_performSelector_onThread_withObject_modes_('sel2:', thr, 3, 1)
+        self.assertEqual(r, 6)
+        r = obj.pyobjc_performSelector_onThread_withObject_modes_('sel3:', thr, 2.0, 2)
+        self.assertEqual(r, 0.5)
+
+        self.assertEqual(obj.calls, [
+            (b'_pyobjc_performOnThreadWithResult:', thr, (b'sel2:', 3, [(True, 6)]), True, 1),
+            (b'_pyobjc_performOnThreadWithResult:', thr, (b'sel3:', 2.0, [(True, 0.5)]), True, 2),
+        ])
+
+        # Raise an exception
+        orig_stderr = sys.stderr
+        sys.stderr = StringIO()
+        try:
+            obj.calls[:] = []
+
+            self.assertRaises(ZeroDivisionError, obj.pyobjc_performSelector_onThread_withObject_modes_,
+                    b'sel3:', thr, 0, 3)
+
+            self.assertEqual(len(obj.calls), 1)
+            self.assertEqual(obj.calls[0][0], b'_pyobjc_performOnThreadWithResult:')
+            self.assertEqual(obj.calls[0][2][-1][0][0],  False)
+            self.assertNotIn('Traceback', sys.stderr.getvalue())
+        finally:
+            sys.stderr = orig_stderr
+
+if __name__ == "__main__":
+    main()