Commits

Ned Batchelder  committed e61eca1 Merge

Merged weekend work

  • Participants
  • Parent commits 97e829f, 7c1159a

Comments (0)

Files changed (7)

 *.pyc
 *.pyo
 *.pyd
+*.so
 *.bak
 .coverage
 .coverage.*
 - The coverage.py code itself will now not be measured by coverage.py, and no
   coverage modules will be mentioned in the nose --with-cover plugin.
 
+- When running source files, coverage.py now opens them in universal newline
+  mode just like Python does.  This lets it run Windows files on Mac, for
+  example.
+
+- Fixed a bizarre problem involving pyexpat, whereby lines following XML parser
+  invocations could be overlooked.
+
 
 Version 3.0, 13 June 2009
 -------------------------

File coverage/execfile.py

     sys.path[0] = os.path.dirname(filename)
 
     try:
-        source = open(filename).read()
+        source = open(filename, 'rU').read()
         exec compile(source, filename, "exec") in main_mod.__dict__
     finally:
         # Restore the old __main__

File coverage/tracer.c

 #include "structmember.h"
 #include "frameobject.h"
 
-#define DEBUG 1
-
-#if DEBUG
-#define IFDEBUG(x)      x
-#else
-#define IFDEBUG(x)
-#endif
+#undef WHAT_LOG     /* Define to log the WHAT params in the trace function. */
+#undef TRACE_LOG    /* Define to log our bookkeeping. */
 
 /* The Tracer type. */
 
     self->ob_type->tp_free((PyObject*)self);
 }
 
+#if TRACE_LOG
+static const char *
+indent(int n)
+{
+    static const char * spaces = 
+        "                                                                    "
+        "                                                                    "
+        "                                                                    "
+        "                                                                    "
+        ;
+    return spaces + strlen(spaces) - n*2;
+}
+
+static int logging = 0;
+/* Set these constants to be a file substring and line number to start logging. */
+static const char * start_file = "tests/views";
+static int start_line = 27;
+
+static void
+showlog(int depth, int lineno, PyObject * filename, const char * msg)
+{
+    if (logging) {
+        printf("%s%3d ", indent(depth), depth);
+        if (lineno) {
+            printf("%4d", lineno);
+        }
+        else {
+            printf("    ");
+        }
+        if (filename) {
+            printf(" %s", PyString_AS_STRING(filename));
+        }
+        if (msg) {
+            printf(" %s", msg);
+        }
+        printf("\n");
+    }
+}
+
+#define SHOWLOG(a,b,c,d)    showlog(a,b,c,d)
+#else
+#define SHOWLOG(a,b,c,d)
+#endif /* TRACE_LOG */
+
+#if WHAT_LOG
+static const char * what_sym[] = {"CALL", "EXC ", "LINE", "RET "};
+#endif
+
 static int
 Tracer_trace(Tracer *self, PyFrameObject *frame, int what, PyObject *arg)
 {
     PyObject * filename = NULL;
     PyObject * tracename = NULL;
 
-    /* printf("trace: %d @ %d\n", what, frame->f_lineno); */
-    
+    #if WHAT_LOG 
+    if (what <= sizeof(what_sym)/sizeof(const char *)) {
+        printf("trace: %s @ %s %d\n", what_sym[what], PyString_AS_STRING(frame->f_code->co_filename), frame->f_lineno);
+    }
+    #endif 
+
+    #if TRACE_LOG
+    if (strstr(PyString_AS_STRING(frame->f_code->co_filename), start_file) && frame->f_lineno == start_line) {
+        logging = 1;
+    }
+    #endif
+
     switch (what) {
     case PyTrace_CALL:      /* 0 */
         self->depth++;
         /* If tracename is a string, then we're supposed to trace. */
         if (PyString_Check(tracename)) {
             self->tracenames[self->depth] = tracename;
+            SHOWLOG(self->depth, frame->f_lineno, filename, "traced");
         }
         else {
             self->tracenames[self->depth] = NULL;
             Py_DECREF(tracename);
+            SHOWLOG(self->depth, frame->f_lineno, filename, "skipped");
         }
         break;
     
     case PyTrace_RETURN:    /* 3 */
         if (self->depth >= 0) {
+            SHOWLOG(self->depth, frame->f_lineno, frame->f_code->co_filename, "return");
             Py_XDECREF(self->tracenames[self->depth]);
             self->depth--;
         }
     
     case PyTrace_LINE:      /* 2 */
         if (self->depth >= 0) {
+            SHOWLOG(self->depth, frame->f_lineno, frame->f_code->co_filename, "line");
             if (self->tracenames[self->depth]) {
                 PyObject * t = PyTuple_New(2);
                 tracename = self->tracenames[self->depth];
         break;
     }
 
+    /* UGLY HACK: for some reason, pyexpat invokes the systrace function directly.
+       It uses "pyexpat.c" as the filename, which is strange enough, but it calls
+       it incorrectly: when an exception passes through the C code, it calls trace
+       with an EXCEPTION, but never calls RETURN.  This throws off our bookkeeping.
+       To make things right, if this is an EXCEPTION from pyexpat.c, then inject
+       a RETURN event also.  
+       
+       I've reported the problem with pyexpat.c as http://bugs.python.org/issue6359 .
+       If the bug in pyexpat.c gets fixed someday, we'll either have to put a 
+       version check here, or do something more sophisticated to detect the 
+       EXCEPTION-without-RETURN case that has to be fixed up.
+    */
+    if (what == PyTrace_EXCEPTION) {
+        if (strstr(PyString_AS_STRING(frame->f_code->co_filename), "pyexpat.c")) {
+            /* Stupid pyexpat: pretend it gave us the RETURN it should have. */
+            SHOWLOG(self->depth, frame->f_lineno, frame->f_code->co_filename, "wrongexc");
+            if (Tracer_trace(self, frame, PyTrace_RETURN, arg) < 0) {
+                return -1;
+            }
+        }
+    }
+
     return 0;
 }
 

File test/coveragetest.py

         here = os.path.dirname(self.nice_file(coverage.__file__, ".."))
         testmods = self.nice_file(here, 'test/modules')
         zipfile = self.nice_file(here, 'test/zipmods.zip')
-        pypath = os.environ['PYTHONPATH']
+        pypath = os.environ.get('PYTHONPATH', '')
         if pypath:
             pypath += os.pathsep
         pypath += testmods + os.pathsep + zipfile

File test/test_coverage.py

             """,
             [1,2,3,5,7], "")
         
-    def xxtestLongRecursion(self):
+    def testLongRecursion(self):
         # We can't finish a very deep recursion, but we don't crash.
         self.assertRaises(RuntimeError, self.checkCoverage,
             """\
             [1,2,3,5,7], "")
 
 
+class PyexpatTest(CoverageTest):
+    """Pyexpat screws up tracing. Make sure we've counter-defended properly."""
+    def testPyexpat(self):
+        # pyexpat calls the trace function explicitly (inexplicably), and does
+        # it wrong for exceptions.  Parsing a DOCTYPE for some reason throws
+        # an exception internally, and triggers its wrong behavior.  This test
+        # checks that our fake PyTrace_RETURN hack in tracer.c works.  It will
+        # also detect if the pyexpat bug is fixed unbeknownst to us, meaning
+        # we'd see two RETURNs where there should only be one.
+
+        self.makeFile("trydom.py", """\
+            import xml.dom.minidom
+
+            XML = '''\\
+            <!DOCTYPE fooey SYSTEM "http://www.example.com/example.dtd">
+            <root><child/><child/></root>
+            '''
+
+            def foo():
+                dom = xml.dom.minidom.parseString(XML)
+                assert len(dom.getElementsByTagName('child')) == 2
+                print "Parsed"
+
+            foo()
+            """)
+
+        self.makeFile("outer.py", "\n"*100 + "import trydom\nprint 'done'\n")
+
+        cov = coverage.coverage()
+        cov.erase()
+
+        # Import the python file, executing it.
+        cov.start()
+        self.importModule("outer")
+        cov.stop()
+
+        _, statements, missing, _ = cov.analysis("trydom.py")
+        self.assertEqual(statements, [1,3,8,9,10,11,13])
+        self.assertEqual(missing, [])
+    
+        _, statements, missing, _ = cov.analysis("outer.py")
+        self.assertEqual(statements, [101,102])
+        self.assertEqual(missing, [])
+
+
 if __name__ == '__main__':
     print "Testing under Python version: %s" % sys.version
     unittest.main()
 #         in an expression!)
 # TODO: Generator comprehensions? 
 # TODO: Constant if tests ("if 1:").  Py 2.4 doesn't execute them.
-# TODO: There are no tests for analysis2 directly.
+# TODO: There are no tests for analysis2 directly.

File test/test_execfile.py

         self.assertEqual(os.listdir("."), ["xxx"])
         run_python_file("xxx", ["xxx"])
         self.assertEqual(os.listdir("."), ["xxx"])
+
+    def test_universal_newlines(self):
+        # Make sure we can read any sort of line ending.
+        pylines = """# try newlines|print 'Hello, world!'|""".split('|')
+        for nl in ('\n', '\r\n', '\r'):
+            fpy = open('nl.py', 'wb')
+            fpy.write(nl.join(pylines))
+            fpy.close()
+            run_python_file('nl.py', ['nl.py'])
+        self.assertEqual(self.stdout(), "Hello, world!\n"*3)
+