diff options
Diffstat (limited to 'coverage')
-rw-r--r-- | coverage/files.py | 35 | ||||
-rw-r--r-- | coverage/parser.py | 4 | ||||
-rw-r--r-- | coverage/phystokens.py | 3 | ||||
-rw-r--r-- | coverage/python.py | 7 | ||||
-rw-r--r-- | coverage/summary.py | 54 | ||||
-rw-r--r-- | coverage/xmlreport.py | 6 |
6 files changed, 78 insertions, 31 deletions
diff --git a/coverage/files.py b/coverage/files.py index 0b5651cb..855d8157 100644 --- a/coverage/files.py +++ b/coverage/files.py @@ -13,12 +13,9 @@ import sys from coverage import env from coverage.backward import unicode_class -from coverage.misc import CoverageException, join_regex, isolate_module +from coverage.misc import contract, CoverageException, join_regex, isolate_module -RELATIVE_DIR = None -CANONICAL_FILENAME_CACHE = {} - os = isolate_module(os) @@ -33,10 +30,13 @@ def set_relative_directory(): # avoid duplicating work. CANONICAL_FILENAME_CACHE = {} + def relative_directory(): """Return the directory that `relative_filename` is relative to.""" return RELATIVE_DIR + +@contract(returns='unicode') def relative_filename(filename): """Return the relative form of `filename`. @@ -47,8 +47,10 @@ def relative_filename(filename): fnorm = os.path.normcase(filename) if fnorm.startswith(RELATIVE_DIR): filename = filename[len(RELATIVE_DIR):] - return filename + return unicode_filename(filename) + +@contract(returns='unicode') def canonical_filename(filename): """Return a canonical file name for `filename`. @@ -65,6 +67,8 @@ def canonical_filename(filename): filename = f break cf = abs_file(filename) + if env.PY2 and isinstance(cf, str): + cf = cf.decode(sys.getfilesystemencoding()) CANONICAL_FILENAME_CACHE[filename] = cf return CANONICAL_FILENAME_CACHE[filename] @@ -126,14 +130,35 @@ else: return filename +if env.PY2: + @contract(returns='unicode') + def unicode_filename(filename): + """Return a Unicode version of `filename`.""" + if isinstance(filename, str): + filename = filename.decode(sys.getfilesystemencoding()) + return filename +else: + @contract(filename='unicode', returns='unicode') + def unicode_filename(filename): + """Return a Unicode version of `filename`.""" + return filename + + +@contract(returns='unicode') def abs_file(filename): """Return the absolute normalized form of `filename`.""" path = os.path.expandvars(os.path.expanduser(filename)) path = os.path.abspath(os.path.realpath(path)) path = actual_path(path) + path = unicode_filename(path) return path +RELATIVE_DIR = None +CANONICAL_FILENAME_CACHE = None +set_relative_directory() + + def isabs_anywhere(filename): """Is `filename` an absolute path on any OS?""" return ntpath.isabs(filename) or posixpath.isabs(filename) diff --git a/coverage/parser.py b/coverage/parser.py index 882c972b..a5e96237 100644 --- a/coverage/parser.py +++ b/coverage/parser.py @@ -211,7 +211,7 @@ class PythonParser(object): else: lineno = err.args[1][0] # TokenError raise NotPython( - "Couldn't parse '%s' as Python source: '%s' at line %d" % ( + u"Couldn't parse '%s' as Python source: '%s' at line %d" % ( self.filename, err.args[0], lineno ) ) @@ -338,7 +338,7 @@ class ByteParser(object): self.code = compile_unicode(text, filename, "exec") except SyntaxError as synerr: raise NotPython( - "Couldn't parse '%s' as Python source: '%s' at line %d" % ( + u"Couldn't parse '%s' as Python source: '%s' at line %d" % ( filename, synerr.msg, synerr.lineno ) ) diff --git a/coverage/phystokens.py b/coverage/phystokens.py index 203d41f2..f5bd0bc9 100644 --- a/coverage/phystokens.py +++ b/coverage/phystokens.py @@ -6,6 +6,7 @@ import codecs import keyword import re +import sys import token import tokenize @@ -281,6 +282,8 @@ def compile_unicode(source, filename, mode): """ source = neuter_encoding_declaration(source) + if env.PY2 and isinstance(filename, unicode): + filename = filename.encode(sys.getfilesystemencoding(), "replace") code = compile(source, filename, mode) return code diff --git a/coverage/python.py b/coverage/python.py index 71b50f0c..fe32150a 100644 --- a/coverage/python.py +++ b/coverage/python.py @@ -4,6 +4,7 @@ """Python source expertise for coverage.py""" import os.path +import sys import zipimport from coverage import env, files @@ -95,6 +96,9 @@ class PythonFileReporter(FileReporter): else: filename = morf + if env.PY2 and isinstance(filename, str): + filename = filename.decode(sys.getfilesystemencoding()) + # .pyc files should always refer to a .py instead. if filename.endswith(('.pyc', '.pyo')): filename = filename[:-1] @@ -106,6 +110,8 @@ class PythonFileReporter(FileReporter): if hasattr(morf, '__name__'): name = morf.__name__ name = name.replace(".", os.sep) + ".py" + if isinstance(name, bytes): + name = name.decode(sys.getfilesystemencoding()) else: name = files.relative_filename(filename) self.relname = name @@ -115,6 +121,7 @@ class PythonFileReporter(FileReporter): self._statements = None self._excluded = None + @contract(returns='unicode') def relative_filename(self): return self.relname diff --git a/coverage/summary.py b/coverage/summary.py index 4dcaa735..f797e306 100644 --- a/coverage/summary.py +++ b/coverage/summary.py @@ -5,6 +5,7 @@ import sys +from coverage import env from coverage.report import Reporter from coverage.results import Numbers from coverage.misc import NotPython, CoverageException @@ -20,38 +21,45 @@ class SummaryReporter(Reporter): def report(self, morfs, outfile=None): """Writes a report summarizing coverage statistics per module. - `outfile` is a file object to write the summary to. + `outfile` is a file object to write the summary to. It must be opened + for native strings (bytes on Python 2, Unicode on Python 3). """ self.find_file_reporters(morfs) # Prepare the formatting strings max_name = max([len(fr.relative_filename()) for fr in self.file_reporters] + [5]) - fmt_name = "%%- %ds " % max_name - fmt_err = "%s %s: %s\n" - fmt_skip_covered = "\n%s file%s skipped due to complete coverage.\n" + fmt_name = u"%%- %ds " % max_name + fmt_err = u"%s %s: %s\n" + fmt_skip_covered = u"\n%s file%s skipped due to complete coverage.\n" - header = (fmt_name % "Name") + " Stmts Miss" - fmt_coverage = fmt_name + "%6d %6d" + header = (fmt_name % "Name") + u" Stmts Miss" + fmt_coverage = fmt_name + u"%6d %6d" if self.branches: - header += " Branch BrPart" - fmt_coverage += " %6d %6d" + header += u" Branch BrPart" + fmt_coverage += u" %6d %6d" width100 = Numbers.pc_str_width() - header += "%*s" % (width100+4, "Cover") - fmt_coverage += "%%%ds%%%%" % (width100+3,) + header += u"%*s" % (width100+4, "Cover") + fmt_coverage += u"%%%ds%%%%" % (width100+3,) if self.config.show_missing: - header += " Missing" - fmt_coverage += " %s" - rule = "-" * len(header) + "\n" - header += "\n" - fmt_coverage += "\n" + header += u" Missing" + fmt_coverage += u" %s" + rule = u"-" * len(header) + u"\n" + header += u"\n" + fmt_coverage += u"\n" - if not outfile: + if outfile is None: outfile = sys.stdout + if env.PY2: + encoding = getattr(outfile, "encoding", None) or sys.getfilesystemencoding() + writeout = lambda u: outfile.write(u.encode(encoding)) + else: + writeout = outfile.write + # Write the header - outfile.write(header) - outfile.write(rule) + writeout(header) + writeout(rule) total = Numbers() skipped_count = 0 @@ -83,7 +91,7 @@ class SummaryReporter(Reporter): missing_fmtd += ", " missing_fmtd += branches_fmtd args += (missing_fmtd,) - outfile.write(fmt_coverage % args) + writeout(fmt_coverage % args) except Exception: report_it = not self.config.ignore_errors if report_it: @@ -93,22 +101,22 @@ class SummaryReporter(Reporter): if typ is NotPython and not fr.should_be_python(): report_it = False if report_it: - outfile.write(fmt_err % (fr.relative_filename(), typ.__name__, msg)) + writeout(fmt_err % (fr.relative_filename(), typ.__name__, msg)) if total.n_files > 1: - outfile.write(rule) + writeout(rule) args = ("TOTAL", total.n_statements, total.n_missing) if self.branches: args += (total.n_branches, total.n_partial_branches) args += (total.pc_covered_str,) if self.config.show_missing: args += ("",) - outfile.write(fmt_coverage % args) + writeout(fmt_coverage % args) if not total.n_files and not skipped_count: raise CoverageException("No data to report.") if self.config.skip_covered and skipped_count: - outfile.write(fmt_skip_covered % (skipped_count, 's' if skipped_count > 1 else '')) + writeout(fmt_skip_covered % (skipped_count, 's' if skipped_count > 1 else '')) return total.n_statements and total.pc_covered diff --git a/coverage/xmlreport.py b/coverage/xmlreport.py index b8f8a9e4..d7c2f44a 100644 --- a/coverage/xmlreport.py +++ b/coverage/xmlreport.py @@ -8,6 +8,7 @@ import sys import time import xml.dom.minidom +from coverage import env from coverage import __url__, __version__, files from coverage.misc import isolate_module from coverage.report import Reporter @@ -116,7 +117,10 @@ class XmlReporter(Reporter): xcoverage.setAttribute("branch-rate", branch_rate) # Use the DOM to write the output file. - outfile.write(self.xml_out.toprettyxml()) + out = self.xml_out.toprettyxml() + if env.PY2: + out = out.encode("utf8") + outfile.write(out) # Return the total percentage. denom = lnum_tot + bnum_tot |