summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNed Batchelder <ned@nedbatchelder.com>2015-08-01 17:15:27 -0400
committerNed Batchelder <ned@nedbatchelder.com>2015-08-01 17:15:27 -0400
commit5e45baa100135e2ef327feb767e8e027a58d1637 (patch)
tree352e759ace46d27e92a66b35045313abe7121e15
parent3e6e85a71c1ad66cd8ed658a61bedbff38f47dd1 (diff)
downloadpython-coveragepy-git-5e45baa100135e2ef327feb767e8e027a58d1637.tar.gz
Support directories on the 'coverage run' command line. #252
-rw-r--r--AUTHORS.txt1
-rw-r--r--CHANGES.txt4
-rw-r--r--coverage/cmdline.py9
-rw-r--r--coverage/execfile.py23
-rw-r--r--tests/test_execfile.py15
-rw-r--r--tests/test_process.py27
-rw-r--r--tests/with_main/__main__.py2
-rw-r--r--tests/with_main/without/__init__.py1
8 files changed, 58 insertions, 24 deletions
diff --git a/AUTHORS.txt b/AUTHORS.txt
index d484edf1..1508d45d 100644
--- a/AUTHORS.txt
+++ b/AUTHORS.txt
@@ -26,6 +26,7 @@ David Christian
David Stanek
Detlev Offenbach
Devin Jeanpierre
+Dmitry Trofimov
Eduardo Schettino
Edward Loper
Geoff Bache
diff --git a/CHANGES.txt b/CHANGES.txt
index 492e831c..45a264a2 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -27,6 +27,9 @@ Latest
- In parallel mode, ``coverage erase`` will now delete all of the data files,
fixing `issue 262`_.
+- Coverage.py now accepts a directory name for ``coverage run`` and will run a
+ ``__main__.py`` found there, just like Python will. Fixes `issue 252`_.
+
- The XML report now includes a ``missing-branches`` attribute. Thanks, Steve
Peak. This is not a part of the Cobertura DTD, so the XML report no longer
references the DTD.
@@ -60,6 +63,7 @@ Latest
support, finishing up `issue 387`_.
.. _issue 236: https://bitbucket.org/ned/coveragepy/issues/236/pickles-are-bad-and-you-should-feel-bad
+.. _issue 252: https://bitbucket.org/ned/coveragepy/issues/252/coverage-wont-run-a-program-with
.. _issue 262: https://bitbucket.org/ned/coveragepy/issues/262/when-parallel-true-erase-should-erase-all
.. _issue 275: https://bitbucket.org/ned/coveragepy/issues/275/refer-consistently-to-project-as-coverage
.. _issue 313: https://bitbucket.org/ned/coveragepy/issues/313/add-license-file-containing-2-3-or-4
diff --git a/coverage/cmdline.py b/coverage/cmdline.py
index fc40e619..aeb74bd5 100644
--- a/coverage/cmdline.py
+++ b/coverage/cmdline.py
@@ -5,7 +5,6 @@
import glob
import optparse
-import os
import sys
import traceback
@@ -560,19 +559,14 @@ class CoverageScript(object):
if not options.append:
self.coverage.erase()
- # Set the first path element properly.
- old_path0 = sys.path[0]
-
# Run the script.
self.coverage.start()
code_ran = True
try:
if options.module:
- sys.path[0] = ''
self.run_python_module(args[0], args)
else:
filename = args[0]
- sys.path[0] = os.path.abspath(os.path.dirname(filename))
self.run_python_file(filename, args)
except NoSource:
code_ran = False
@@ -584,9 +578,6 @@ class CoverageScript(object):
self.coverage.combine(data_paths=[self.coverage.config.data_file])
self.coverage.save()
- # Restore the old path
- sys.path[0] = old_path0
-
return OK
def do_debug(self, args):
diff --git a/coverage/execfile.py b/coverage/execfile.py
index d1158b51..1845f8d7 100644
--- a/coverage/execfile.py
+++ b/coverage/execfile.py
@@ -108,10 +108,10 @@ def run_python_module(modulename, args):
pathname = os.path.abspath(pathname)
args[0] = pathname
- run_python_file(pathname, args, package=packagename, modulename=modulename)
+ run_python_file(pathname, args, package=packagename, modulename=modulename, path0="")
-def run_python_file(filename, args, package=None, modulename=None):
+def run_python_file(filename, args, package=None, modulename=None, path0=None):
"""Run a Python file as if it were the main program on the command line.
`filename` is the path to the file to execute, it need not be a .py file.
@@ -121,6 +121,9 @@ def run_python_file(filename, args, package=None, modulename=None):
`modulename` is the name of the module the file was run as.
+ `path0` is the value to put into sys.path[0]. If it's None, then this
+ function will decide on a value.
+
"""
if modulename is None and sys.version_info >= (3, 3):
modulename = '__main__'
@@ -142,7 +145,10 @@ def run_python_file(filename, args, package=None, modulename=None):
sys.argv = args
if os.path.isdir(filename):
- # in directory we should look for __main__ module
+ # Running a directory means running the __main__.py file in that
+ # directory.
+ my_path0 = filename
+
for ext in [".py", ".pyc", ".pyo"]:
try_filename = os.path.join(filename, "__main__" + ext)
if os.path.exists(try_filename):
@@ -150,6 +156,12 @@ def run_python_file(filename, args, package=None, modulename=None):
break
else:
raise NoSource("Can't find '__main__' module in '%s'" % filename)
+ else:
+ my_path0 = os.path.abspath(os.path.dirname(filename))
+
+ # Set sys.path correctly.
+ old_path0 = sys.path[0]
+ sys.path[0] = path0 if path0 is not None else my_path0
try:
# Make a code object somehow.
@@ -181,11 +193,10 @@ def run_python_file(filename, args, package=None, modulename=None):
raise ExceptionDuringRun(typ, err, tb.tb_next)
finally:
- # Restore the old __main__
+ # Restore the old __main__, argv, and path.
sys.modules['__main__'] = old_main_mod
-
- # Restore the old argv and path
sys.argv = old_argv
+ sys.path[0] = old_path0
def make_code_from_py(filename):
diff --git a/tests/test_execfile.py b/tests/test_execfile.py
index 2533f81d..a3ea1153 100644
--- a/tests/test_execfile.py
+++ b/tests/test_execfile.py
@@ -86,14 +86,17 @@ class RunFileTest(CoverageTest):
run_python_file("xyzzy.py", [])
def test_directory_with_main(self):
- directory_with_main = os.path.join(HERE, "with_main")
- run_python_file(directory_with_main, [directory_with_main])
- self.assertEqual(self.stdout(), "1\n")
+ self.make_file("with_main/__main__.py", """\
+ print("I am __main__")
+ """)
+ run_python_file("with_main", ["with_main"])
+ self.assertEqual(self.stdout(), "I am __main__\n")
def test_directory_without_main(self):
- with self.assertRaises(NoSource):
- directory_with_main = os.path.join(HERE, "with_main", "without")
- run_python_file(directory_with_main, [directory_with_main])
+ self.make_file("without_main/__init__.py", "")
+ with self.assertRaisesRegex(NoSource, "Can't find '__main__' module in 'without_main'"):
+ run_python_file("without_main", ["without_main"])
+
class RunPycFileTest(CoverageTest):
"""Test cases for `run_python_file`."""
diff --git a/tests/test_process.py b/tests/test_process.py
index 7c8b0c2d..fcd88827 100644
--- a/tests/test_process.py
+++ b/tests/test_process.py
@@ -7,6 +7,7 @@
import glob
import os
import os.path
+import re
import sys
import textwrap
@@ -387,6 +388,26 @@ class ProcessTest(CoverageTest):
out_py = self.run_command("python -m tests.try_execfile")
self.assertMultiLineEqual(out_cov, out_py)
+ def test_coverage_run_dir_is_like_python_dir(self):
+ tryfile = os.path.join(HERE, "try_execfile.py")
+ with open(tryfile) as f:
+ self.make_file("with_main/__main__.py", f.read())
+ out_cov = self.run_command("coverage run with_main")
+ out_py = self.run_command("python with_main")
+
+ # The coverage.py results are not identical to the Python results, and
+ # I don't know why. For now, ignore those failures. If someone finds
+ # a real problem with the discrepancies, we can work on it some more.
+ ignored = r"__file__|__loader__|__package__"
+ # PyPy includes the current directory in the path when running a
+ # directory, while CPython and coverage.py do not. Exclude that from
+ # the comparison also...
+ if env.PYPY:
+ ignored += "|"+re.escape(os.getcwd())
+ out_cov = remove_matching_lines(out_cov, ignored)
+ out_py = remove_matching_lines(out_py, ignored)
+ self.assertMultiLineEqual(out_cov, out_py)
+
def test_coverage_run_dashm_equal_to_doubledashsource(self):
"""regression test for #328
@@ -995,3 +1016,9 @@ class ProcessStartupWithSourceTest(ProcessCoverageMixin, CoverageTest):
def test_script_pkg_sub(self):
self.assert_pth_and_source_work_together('', 'pkg', 'sub')
+
+
+def remove_matching_lines(text, pat):
+ """Return `text` with all lines matching `pat` removed."""
+ lines = [l for l in text.splitlines(True) if not re.search(pat, l)]
+ return "".join(lines)
diff --git a/tests/with_main/__main__.py b/tests/with_main/__main__.py
deleted file mode 100644
index e7a4e4f9..00000000
--- a/tests/with_main/__main__.py
+++ /dev/null
@@ -1,2 +0,0 @@
-x = 1
-print(x) \ No newline at end of file
diff --git a/tests/with_main/without/__init__.py b/tests/with_main/without/__init__.py
deleted file mode 100644
index 595e3818..00000000
--- a/tests/with_main/without/__init__.py
+++ /dev/null
@@ -1 +0,0 @@
-__author__ = 'traff'