diff --git a/CHANGELOG b/CHANGELOG index e2f41fcda..7162c68f3 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,22 +1,37 @@ Changes between 1.2.1 and 1.2.0 ===================================== -- add a new option "--funcargs" that shows available funcargs - and their help strings (docstrings on the factory) for a - given test path +- refined usage and options for "py.cleanup": + py.cleanup # remove "*.pyc" and "*$py.class" (jython) files + py.cleanup -e .swp -e .cache # also remove files with these extensions + py.cleanup -s # remove "build" and "dist" directory next to setup.py files + py.cleanup -d # also remove empty directories + py.cleanup -a # synonym for "-s -d -e 'pip-log.txt'" + py.cleanup -n # dry run, only show what would be removed + +- add a new option "py.test --funcargs" which shows available funcargs + and their help strings (docstrings on their respective factory function) + for a given test path + - display a short and concise traceback if a funcarg lookup fails + - early-load "test*/conftest.py" files, i.e. conftest.py files in directories starting with 'test'. allows to conveniently keep and access test-related options without having to put a conftest.py into the package root dir. + - fix issue67: new super-short traceback-printing option: "--tb=line" will print a single line for each failing (python) test indicating its filename, lineno and the failure value + - fix issue78: always call python-level teardown functions even if the according setup failed. This includes refinements for calling setup_module/class functions which will now only be called once instead of the previous behaviour where they'd be called multiple times if they raise an exception (including a Skipped exception). Any exception will be re-corded and associated with all tests in the according module/class scope. + - fix issue63: assume <40 columns to be a bogus terminal width, default to 80 + - update apipkg.py to fix an issue where recursive imports might unnecessarily break importing + - fix plugin links Changes between 1.2 and 1.1.1 diff --git a/py/_cmdline/pycleanup.py b/py/_cmdline/pycleanup.py index 084e59bad..7d35c5c2b 100755 --- a/py/_cmdline/pycleanup.py +++ b/py/_cmdline/pycleanup.py @@ -1,47 +1,86 @@ #!/usr/bin/env python """\ -py.cleanup [PATH] +py.cleanup [PATH] ... + +Delete typical python development related files recursively under the specified PATH (which defaults to the current working directory). Don't follow links and don't recurse into directories with a dot. Optionally remove setup.py related files and empty +directories. -Delete pyc file recursively, starting from PATH (which defaults to the current -working directory). Don't follow links and don't recurse into directories with -a ".". """ import py +import sys, subprocess def main(): parser = py.std.optparse.OptionParser(usage=__doc__) - parser.add_option("-e", "--remove", dest="ext", default=".pyc", action="store", - help="remove files with the given comma-separated list of extensions" - ) + parser.add_option("-e", metavar="ENDING", + dest="endings", default=[".pyc", "$py.class"], action="append", + help=("(multi) recursively remove files with the given ending." + " '.pyc' and '$py.class' are in the default list.")) + parser.add_option("-d", action="store_true", dest="removedir", + help="remove empty directories.") + parser.add_option("-s", action="store_true", dest="setup", + help="remove 'build' and 'dist' directories next to setup.py files") + parser.add_option("-a", action="store_true", dest="all", + help="synonym for '-S -d -e pip-log.txt'") parser.add_option("-n", "--dryrun", dest="dryrun", default=False, action="store_true", - help="display would-be-removed filenames" - ) - parser.add_option("-d", action="store_true", dest="removedir", - help="remove empty directories") + help="don't actually delete but display would-be-removed filenames.") (options, args) = parser.parse_args() - if not args: - args = ["."] - ext = options.ext.split(",") - def shouldremove(p): - return p.ext in ext - - for arg in args: - path = py.path.local(arg) - py.builtin.print_("cleaning path", path, "of extensions", ext) - for x in path.visit(shouldremove, lambda x: x.check(dotfile=0, link=0)): - remove(x, options) - if options.removedir: - for x in path.visit(lambda x: x.check(dir=1), - lambda x: x.check(dotfile=0, link=0)): - if not x.listdir(): - remove(x, options) -def remove(path, options): - if options.dryrun: - py.builtin.print_("would remove", path) - else: - py.builtin.print_("removing", path) - path.remove() - + Cleanup(options, args).main() + +class Cleanup: + def __init__(self, options, args): + if not args: + args = ["."] + self.options = options + self.args = [py.path.local(x) for x in args] + if options.all: + options.setup = True + options.removedir = True + options.endings.append("pip-log.txt") + + def main(self): + if self.options.setup: + for arg in self.args: + self.setupclean(arg) + + for path in self.args: + py.builtin.print_("cleaning path", path, + "of extensions", self.options.endings) + for x in path.visit(self.shouldremove, self.recursedir): + self.remove(x) + if self.options.removedir: + for x in path.visit(lambda x: x.check(dir=1), self.recursedir): + if not x.listdir(): + self.remove(x) + + def shouldremove(self, p): + for ending in self.options.endings: + if p.basename.endswith(ending): + return True + + def recursedir(self, path): + return path.check(dotfile=0, link=0) + + def remove(self, path): + if not path.check(): + return + if self.options.dryrun: + py.builtin.print_("would remove", path) + else: + py.builtin.print_("removing", path) + path.remove() + + def XXXcallsetup(self, setup, *args): + old = setup.dirpath().chdir() + try: + subprocess.call([sys.executable, str(setup)] + list(args)) + finally: + old.chdir() + + def setupclean(self, path): + for x in path.visit("setup.py", self.recursedir): + basepath = x.dirpath() + self.remove(basepath / "build") + self.remove(basepath / "dist") diff --git a/testing/cmdline/test_cmdline.py b/testing/cmdline/test_cmdline.py index 97e723dde..18b055eba 100644 --- a/testing/cmdline/test_cmdline.py +++ b/testing/cmdline/test_cmdline.py @@ -45,8 +45,10 @@ class TestPyCleanup: assert p.check() pyc = p.new(ext='pyc') pyc.ensure() + pyclass = p.new(basename=p.basename + '$py.class') result = testdir.runpybin("py.cleanup", tmpdir) assert not pyc.check() + assert not pyclass.check() def test_dir_remove_simple(self, testdir, tmpdir): subdir = tmpdir.mkdir("subdir") @@ -59,3 +61,43 @@ class TestPyCleanup: result = testdir.runpybin("py.cleanup", tmpdir, '-d') assert result.ret == 0 assert not subdir.check() + + @py.test.mark.multi(opt=["-s"]) + def test_remove_setup_simple(self, testdir, tmpdir, opt): + subdir = tmpdir.mkdir("subdir") + p = subdir.ensure("setup.py") + subdir.mkdir("build").ensure("hello", "world.py") + egg1 = subdir.mkdir("something.egg-info") + egg1.mkdir("whatever") + okbuild = subdir.mkdir("preserved1").mkdir("build") + egg2 = subdir.mkdir("preserved2").mkdir("other.egg-info") + subdir.mkdir("dist") + result = testdir.runpybin("py.cleanup", opt, subdir) + assert result.ret == 0 + assert okbuild.check() + assert egg1.check() + assert egg2.check() + assert subdir.join("preserved1").check() + assert subdir.join("preserved2").check() + assert not subdir.join("build").check() + assert not subdir.join("dist").check() + + def test_remove_all(self, testdir, tmpdir): + tmpdir.ensure("setup.py") + tmpdir.ensure("build", "xyz.py") + tmpdir.ensure("dist", "abc.py") + piplog = tmpdir.ensure("preserved2", "pip-log.txt") + tmpdir.ensure("hello.egg-info") + setup = tmpdir.ensure("setup.py") + tmpdir.ensure("src/a/b") + x = tmpdir.ensure("src/x.py") + x2 = tmpdir.ensure("src/x.pyc") + x3 = tmpdir.ensure("src/x$py.class") + result = testdir.runpybin("py.cleanup", "-a", tmpdir) + assert result.ret == 0 + assert len(tmpdir.listdir()) == 3 + assert setup.check() + assert x.check() + assert not x2.check() + assert not x3.check() + assert not piplog.check()