diff options
author | Ned Batchelder <ned@nedbatchelder.com> | 2012-11-11 12:22:03 -0500 |
---|---|---|
committer | Ned Batchelder <ned@nedbatchelder.com> | 2012-11-11 12:22:03 -0500 |
commit | de89438a0346e3dca4f7e8afe24efa38810deebe (patch) | |
tree | 82acb47b22bf967a9573651adb6cc18d9bc216a2 | |
parent | 99480be7da89cb82cfff01e5d10a2514546faf39 (diff) | |
download | python-coveragepy-git-de89438a0346e3dca4f7e8afe24efa38810deebe.tar.gz |
Windows now reports file names in their correct case. #89 and #203.
-rw-r--r-- | CHANGES.txt | 5 | ||||
-rw-r--r-- | coverage/__init__.py | 7 | ||||
-rw-r--r-- | coverage/control.py | 1 | ||||
-rw-r--r-- | coverage/files.py | 49 | ||||
-rw-r--r-- | coverage/fullcoverage/encodings.py | 8 | ||||
-rw-r--r-- | test/test_summary.py | 30 |
6 files changed, 96 insertions, 4 deletions
diff --git a/CHANGES.txt b/CHANGES.txt index 752738c4..87a75705 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -27,6 +27,9 @@ Version 3.5.4b1 coverage.xml_report() now all return a float, the total percentage covered measurement. +- On Windows, files are now reported in their correct case. Fixes + `issue 89`_ and `issue 203`_. + - Running an HTML report in Python 3 in the same directory as an old Python 2 HTML report would fail with a UnicodeDecodeError. This issue (`issue 193`_) is now fixed. @@ -43,10 +46,12 @@ Version 3.5.4b1 - The pydoc tool can now show docmentation for the class `coverage.coverage`. Closes `issue 206`_. +.. _issue 89: https://bitbucket.org/ned/coveragepy/issue/89/on-windows-all-packages-are-reported-in .. _issue 139: https://bitbucket.org/ned/coveragepy/issue/139/easy-check-for-a-certain-coverage-in-tests .. _issue 163: https://bitbucket.org/ned/coveragepy/issue/163/problem-with-include-and-omit-filename .. _issue 193: https://bitbucket.org/ned/coveragepy/issue/193/unicodedecodeerror-on-htmlpy .. _issue 201: https://bitbucket.org/ned/coveragepy/issue/201/coverage-using-django-14-with-pydb-on +.. _issue 203: https://bitbucket.org/ned/coveragepy/issue/203/duplicate-filenames-reported-when-filename .. _issue 205: https://bitbucket.org/ned/coveragepy/issue/205/make-pydoc-coverage-more-friendly .. _issue 206: https://bitbucket.org/ned/coveragepy/issue/206/pydoc-coveragecoverage-fails-with-an-error .. _issue 210: https://bitbucket.org/ned/coveragepy/issue/210/if-theres-no-coverage-data-coverage-xml diff --git a/coverage/__init__.py b/coverage/__init__.py index a10b4a67..2653853b 100644 --- a/coverage/__init__.py +++ b/coverage/__init__.py @@ -78,6 +78,12 @@ analysis2 = _singleton_method('analysis2') report = _singleton_method('report') annotate = _singleton_method('annotate') + +# On Windows, we encode and decode deep enough that something goes wrong and +# the encodings.utf_8 module is loaded and then unloaded, I don't know why. +# Adding a reference here prevents it from being unloaded. Yuk. +import encodings.utf_8 + # Because of the "from coverage.control import fooey" lines at the top of the # file, there's an entry for coverage.coverage in sys.modules, mapped to None. # This makes some inspection tools (like pydoc) unable to find the class @@ -88,6 +94,7 @@ try: except KeyError: pass + # COPYRIGHT AND LICENSE # # Copyright 2001 Gareth Rees. All rights reserved. diff --git a/coverage/control.py b/coverage/control.py index 9523defa..0391352d 100644 --- a/coverage/control.py +++ b/coverage/control.py @@ -475,6 +475,7 @@ class coverage(object): # Find files that were never executed at all. for src in self.source: for py_file in find_python_files(src): + py_file = self.file_locator.canonical_filename(py_file) self.data.touch_file(py_file) self._harvested = True diff --git a/coverage/files.py b/coverage/files.py index 84466504..65b1dc01 100644 --- a/coverage/files.py +++ b/coverage/files.py @@ -9,7 +9,7 @@ class FileLocator(object): def __init__(self): # The absolute path to our current directory. - self.relative_dir = abs_file(os.curdir) + os.sep + self.relative_dir = os.path.normcase(abs_file(os.curdir) + os.sep) # Cache of results of calling the canonical_filename() method, to # avoid duplicating work. @@ -22,8 +22,9 @@ class FileLocator(object): `FileLocator` was constructed. """ - if filename.startswith(self.relative_dir): - filename = filename.replace(self.relative_dir, "", 1) + fnorm = os.path.normcase(filename) + if fnorm.startswith(self.relative_dir): + filename = filename[len(self.relative_dir):] return filename def canonical_filename(self, filename): @@ -74,9 +75,49 @@ class FileLocator(object): return None +if sys.platform == 'win32': + import ctypes + + def getLongPathName(path): + """Call Windows' GetLongPathNameW, unicode to unicode.""" + buf = ctypes.create_unicode_buffer(260) + rv = ctypes.windll.kernel32.GetLongPathNameW(path, buf, 260) + if rv == 0 or rv > 260: + return path + else: + return buf.value + + def getShortPathName(path): + """Call Windows' GetShortPathNameW, unicode to unicode.""" + buf = ctypes.create_unicode_buffer(260) + rv = ctypes.windll.kernel32.GetShortPathNameW(path, buf, 260) + if rv == 0 or rv > 260: + return path + else: + return buf.value + + def actual_path(path): + """Get the actual path of `path`, including the correct case.""" + if sys.version_info >= (3,0): + path = path.encode('utf-16') + else: + path = path.decode('utf8') + actual = getLongPathName(getShortPathName(path)) + if sys.version_info >= (3,0): + actual = actual.decode('utf-16') + else: + actual = actual.encode('utf8') + return actual +else: + def actual_filename(filename): + """The actual filename for non-Windows platforms.""" + return filename + def abs_file(filename): """Return the absolute normalized form of `filename`.""" - return os.path.normcase(os.path.abspath(os.path.realpath(filename))) + path = os.path.abspath(os.path.realpath(filename)) + path = actual_filename(path) + return path def prep_patterns(patterns): diff --git a/coverage/fullcoverage/encodings.py b/coverage/fullcoverage/encodings.py index ad350bc0..6a258d67 100644 --- a/coverage/fullcoverage/encodings.py +++ b/coverage/fullcoverage/encodings.py @@ -37,6 +37,14 @@ class FullCoverageTracer(object): sys.settrace(FullCoverageTracer().fullcoverage_trace) +# In coverage/files.py is actual_filename(), which uses glob.glob. I don't +# understand why, but that use of glob borks everything if fullcoverage is in +# effect. So here we make an ugly hail-mary pass to switch off glob.glob over +# there. This means when using fullcoverage, Windows path names will not be +# their actual case. + +#sys.fullcoverage = True + # Finally, remove our own directory from sys.path; remove ourselves from # sys.modules; and re-import "encodings", which will be the real package # this time. Note that the delete from sys.modules dictionary has to diff --git a/test/test_summary.py b/test/test_summary.py index d2be95d9..4ca82656 100644 --- a/test/test_summary.py +++ b/test/test_summary.py @@ -179,6 +179,36 @@ class SummaryTest(CoverageTest): self.assertEqual(self.line_count(report), 2) + def run_TheCode_and_report_it(self): + """A helper for the next few tests.""" + cov = coverage.coverage() + cov.start() + import TheCode + cov.stop() + + repout = StringIO() + cov.report(file=repout, show_missing=False) + report = repout.getvalue().replace('\\', '/') + report = re.sub(r"\s+", " ", report) + return report + + def test_bug_203_mixed_case_listed_twice_with_rc(self): + self.make_file("TheCode.py", "a = 1\n") + self.make_file(".coveragerc", "[run]\nsource = .\n") + + report = self.run_TheCode_and_report_it() + + self.assertIn("TheCode", report) + self.assertNotIn("thecode", report) + + def test_bug_203_mixed_case_listed_twice(self): + self.make_file("TheCode.py", "a = 1\n") + + report = self.run_TheCode_and_report_it() + + self.assertIn("TheCode", report) + self.assertNotIn("thecode", report) + class SummaryTest2(CoverageTest): """Another bunch of summary tests.""" |