diff -Nru trash-cli-0.12.7/bugs.txt trash-cli-0.12.9.14/bugs.txt --- trash-cli-0.12.7/bugs.txt 1970-01-01 00:00:00.000000000 +0000 +++ trash-cli-0.12.9.14/bugs.txt 2014-08-27 03:32:08.000000000 +0000 @@ -0,0 +1,6 @@ +Bug reported on external trackers: + x trash-empty crashed with GetoptError in short_has_arg(): option -2 not + recognized, + url: https://bugs.launchpad.net/ubuntu/+source/trash-cli/+bug/1015877 + status: solved on Thu Jun 21 12:50:03 CEST 2012 + diff -Nru trash-cli-0.12.7/check_release_installation.py trash-cli-0.12.9.14/check_release_installation.py --- trash-cli-0.12.7/check_release_installation.py 1970-01-01 00:00:00.000000000 +0000 +++ trash-cli-0.12.9.14/check_release_installation.py 2014-08-27 03:32:08.000000000 +0000 @@ -0,0 +1,94 @@ +TARGET_HOST = '192.168.56.101' + +import nose +from nose.tools import assert_equals, assert_not_equals +from ssh import Connection + +from trashcli.trash import version + +def main(): + check_connection() + check_installation(normal_installation) + check_installation(easy_install_installation) + +def check_installation(installation_method): + tc = LinuxBox('root@' + TARGET_HOST, installation_method) + print "== Cleaning any prior software installation" + tc.clean_any_prior_installation() + print "== Copying software" + tc.copy_tarball() + print "== Installing software" + tc.install_software() + print "== Checking all program were installed" + tc.check_all_programs_are_installed() + + +class LinuxBox: + def __init__(self, address, installation_method): + self.ssh = Connection(address) + self.executables = [ + 'trash-put', 'trash-list', 'trash-rm', 'trash-empty', + 'restore-trash', 'trash'] + self.tarball="trash-cli-%s.tar.gz" % version + self.installation_method = installation_method + def clean_any_prior_installation(self): + "clean any prior installation" + for executable in self.executables: + self._remove_executable(executable) + self._assert_command_removed(executable) + def _remove_executable(self, executable): + self.ssh.run('rm -f $(which %s)' % executable).assert_succesful() + def _assert_command_removed(self, executable): + result = self.ssh.run('which %s' % executable) + command_not_existent_exit_code_for_which = 1 + assert_equals(result.exit_code, command_not_existent_exit_code_for_which, + 'Which returned: %s\n' % result.exit_code + + 'and reported: %s' % result.stdout + ) + def copy_tarball(self): + self.ssh.put('dist/%s' % self.tarball) + def install_software(self): + def run_checked(command): + result = self.ssh.run(command) + result.assert_succesful() + self.installation_method(self.tarball, run_checked) + def check_all_programs_are_installed(self): + for command in self.executables: + result = self.ssh.run('%(command)s --version' % locals()) + assert_not_equals(127, result.exit_code, + "Exit code was: %s, " % result.exit_code + + "Probably command not found, command: %s" % command) + +def normal_installation(tarball, check_run): + directory = strip_end(tarball, '.tar.gz') + check_run('tar xfvz %s' % tarball) + check_run('cd %s && ' + 'python setup.py install' % directory) + +def easy_install_installation(tarball, check_run): + check_run('easy_install %s' % tarball) + +def strip_end(text, suffix): + if not text.endswith(suffix): + return text + return text[:-len(suffix)] + +def check_connection(): + suite = nose.loader.TestLoader().loadTestsFromTestClass(TestConnection) + nose.run(suite=suite) + +class TestConnection: + def __init__(self): + self.ssh = Connection(TARGET_HOST) + def test_should_report_stdout(self): + result = self.ssh.run('echo', 'foo') + assert_equals('foo\n', result.stdout) + def test_should_report_stderr(self): + result = self.ssh.run('echo bar 1>&2') + assert_equals('bar\n', result.stderr) + def test_should_report_exit_code(self): + result = self.ssh.run("exit 134") + assert_equals(134, result.exit_code) + +if __name__ == '__main__': + main() diff -Nru trash-cli-0.12.7/debian/changelog trash-cli-0.12.9.14/debian/changelog --- trash-cli-0.12.7/debian/changelog 2012-06-23 10:21:54.000000000 +0000 +++ trash-cli-0.12.9.14/debian/changelog 2014-08-31 12:52:54.000000000 +0000 @@ -1,3 +1,24 @@ +trash-cli (0.12.9.14-2) unstable; urgency=medium + + * Fix Vcs URLs + * Fix "FTBFS: tests failed" by allowing output of the + should_output_info_for_multiple_files() be any order (Closes: #759920) + + -- Javi Merino Sat, 30 Aug 2014 21:54:14 -0700 + +trash-cli (0.12.9.14-1) unstable; urgency=medium + + * New upstream release + * Upgrade standards version to 3.9.5 (no change needed) + * Upgrade debhelper to v9 + * Use canonical field for Vcs-Git (thanks lintian) + * add patch Fix_should_output_info_for_multiple_files to fix building + the testsuite + * Run the testsuite when building the package + * Add autopkgtest + + -- Javi Merino Thu, 28 Aug 2014 10:21:38 -0700 + trash-cli (0.12.7-1) unstable; urgency=low * New upstream release. diff -Nru trash-cli-0.12.7/debian/compat trash-cli-0.12.9.14/debian/compat --- trash-cli-0.12.7/debian/compat 2012-06-23 10:21:54.000000000 +0000 +++ trash-cli-0.12.9.14/debian/compat 2014-08-31 12:52:54.000000000 +0000 @@ -1 +1 @@ -8 +9 diff -Nru trash-cli-0.12.7/debian/control trash-cli-0.12.9.14/debian/control --- trash-cli-0.12.7/debian/control 2012-06-23 10:21:54.000000000 +0000 +++ trash-cli-0.12.9.14/debian/control 2014-08-31 12:52:54.000000000 +0000 @@ -3,14 +3,18 @@ Priority: extra Maintainer: Stefano Karapetsas Uploaders: Javi Merino -Build-Depends: debhelper (>= 8), +Build-Depends: debhelper (>= 9), python (>= 2.6.6-3~), - python-setuptools + python-setuptools, + python-nose, + python-mock, X-Python-Version: >= 2.6 -Standards-Version: 3.9.3 +Standards-Version: 3.9.5 Homepage: https://github.com/andreafrancia/trash-cli -Vcs-Git: git://git.debian.org/git/collab-maint/trash-cli.git -Vcs-Browser: http://anonscm.debian.org/gitweb/?p=collab-maint/trash-cli.git;a=summary +Vcs-Git: https://anonscm.debian.org/git/collab-maint/trash-cli.git +Vcs-Browser: https://anonscm.debian.org/cgit/collab-maint/trash-cli.git +# This can go away once dpkg 1.17.11 hits stable +XS-Testsuite: autopkgtest Package: trash-cli Architecture: all diff -Nru trash-cli-0.12.7/debian/.git-dpm trash-cli-0.12.9.14/debian/.git-dpm --- trash-cli-0.12.7/debian/.git-dpm 1970-01-01 00:00:00.000000000 +0000 +++ trash-cli-0.12.9.14/debian/.git-dpm 2014-08-31 12:52:54.000000000 +0000 @@ -0,0 +1,8 @@ +# see git-dpm(1) from git-dpm package +219eebac566ba3e7dc9730dcc240024b4183547c +219eebac566ba3e7dc9730dcc240024b4183547c +a149cb0feca85fabb4e9f321b9eb74f302c9ca39 +a149cb0feca85fabb4e9f321b9eb74f302c9ca39 +trash-cli_0.12.9.14.orig.tar.gz +a8b36acbd6e5772d818bd66f3634e89787a9eeef +62100 diff -Nru trash-cli-0.12.7/debian/patches/0001-Fix-should_output_info_for_multiple_files.patch trash-cli-0.12.9.14/debian/patches/0001-Fix-should_output_info_for_multiple_files.patch --- trash-cli-0.12.7/debian/patches/0001-Fix-should_output_info_for_multiple_files.patch 1970-01-01 00:00:00.000000000 +0000 +++ trash-cli-0.12.9.14/debian/patches/0001-Fix-should_output_info_for_multiple_files.patch 2014-08-31 12:52:54.000000000 +0000 @@ -0,0 +1,61 @@ +From 219eebac566ba3e7dc9730dcc240024b4183547c Mon Sep 17 00:00:00 2001 +From: Javi Merino +Date: Sat, 30 Aug 2014 21:25:48 -0700 +Forwarded: https://github.com/JaviMerino/trash-cli/commit/4f45a37a390d7c844dd9c9b58fff7259a77ffff9 +Subject: Fix should_output_info_for_multiple_files + +Test should_output_info_for_multiple_files fails because the output is +not in the same order as the input. Add assert_equal_any_order() to +the OutputCollector, which sorts the expected and actual lines so that +the output matches even if the order in which they are shown in +trash-list is different. +--- + integration_tests/describe_trash_list.py | 8 +++++--- + integration_tests/output_collector.py | 8 ++++++++ + 2 files changed, 13 insertions(+), 3 deletions(-) + +diff --git a/integration_tests/describe_trash_list.py b/integration_tests/describe_trash_list.py +index 6dd8d30..3489a22 100644 +--- a/integration_tests/describe_trash_list.py ++++ b/integration_tests/describe_trash_list.py +@@ -73,9 +73,9 @@ class describe_trash_list(Setup): + + self.user.run_trash_list() + +- self.user.should_read_output( "2000-01-01 00:00:01 /file1\n" +- "2000-01-01 00:00:02 /file2\n" +- "2000-01-01 00:00:03 /file3\n") ++ self.user.should_read_output_any_order( "2000-01-01 00:00:01 /file1\n" ++ "2000-01-01 00:00:02 /file2\n" ++ "2000-01-01 00:00:03 /file3\n") + + @istest + def should_output_unknown_dates_with_question_marks(self): +@@ -294,6 +294,8 @@ class TrashListUser: + raise ValueError() + def should_read_output(self, expected_value): + self.stdout.assert_equal_to(expected_value) ++ def should_read_output_any_order(self, expected_value): ++ self.stdout.assert_equal_any_order(expected_value) + def should_read_error(self, expected_value): + self.stderr.assert_equal_to(expected_value) + def output(self): +diff --git a/integration_tests/output_collector.py b/integration_tests/output_collector.py +index 06dc002..7f3704f 100644 +--- a/integration_tests/output_collector.py ++++ b/integration_tests/output_collector.py +@@ -9,6 +9,14 @@ class OutputCollector: + self.stream.write(data) + def assert_equal_to(self, expected): + return self.should_be(expected) ++ def assert_equal_any_order(self, expected): ++ actual_sorted = sorted(self.stream.getvalue().splitlines(1)) ++ actual = "".join(actual_sorted) ++ ++ expected_sorted = sorted(expected.splitlines(1)) ++ expected = "".join(expected_sorted) ++ ++ assert_equals_with_unidiff(expected, actual) + def should_be(self, expected): + assert_equals_with_unidiff(expected, self.stream.getvalue()) + def should_match(self, regex): diff -Nru trash-cli-0.12.7/debian/patches/series trash-cli-0.12.9.14/debian/patches/series --- trash-cli-0.12.7/debian/patches/series 1970-01-01 00:00:00.000000000 +0000 +++ trash-cli-0.12.9.14/debian/patches/series 2014-08-31 12:52:54.000000000 +0000 @@ -0,0 +1 @@ +0001-Fix-should_output_info_for_multiple_files.patch diff -Nru trash-cli-0.12.7/debian/rules trash-cli-0.12.9.14/debian/rules --- trash-cli-0.12.7/debian/rules 2012-06-23 10:21:54.000000000 +0000 +++ trash-cli-0.12.9.14/debian/rules 2014-08-31 12:52:54.000000000 +0000 @@ -3,6 +3,10 @@ %: dh $@ --with python2 -clean: - dh clean --with python2 +override_dh_auto_clean: + +override_dh_clean: rm -rf trash_cli.egg-info/ + +override_dh_auto_test: + nosetests diff -Nru trash-cli-0.12.7/debian/tests/control trash-cli-0.12.9.14/debian/tests/control --- trash-cli-0.12.7/debian/tests/control 1970-01-01 00:00:00.000000000 +0000 +++ trash-cli-0.12.9.14/debian/tests/control 2014-08-31 12:52:54.000000000 +0000 @@ -0,0 +1,2 @@ +Tests: trash-cli +Depends: @ diff -Nru trash-cli-0.12.7/debian/tests/trash-cli trash-cli-0.12.9.14/debian/tests/trash-cli --- trash-cli-0.12.7/debian/tests/trash-cli 1970-01-01 00:00:00.000000000 +0000 +++ trash-cli-0.12.9.14/debian/tests/trash-cli 2014-08-31 12:52:54.000000000 +0000 @@ -0,0 +1,58 @@ +#!/bin/sh + +# Basic test to test all the commands in the package: trash-put, +# trash-list, restore-trash, trash-rm and trash-empty. The test: +# - trashes a file +# - checks that it's in the trash +# - restores it +# - trashes it again +# - removes it from the trash +# - empties the trash + +set -e + +HOME=$(mktemp --tmpdir -d trashcli_test_home.XXXXXX) +cd $HOME + +### Create the file +echo foo > foo +ls foo > /dev/null + +### Trash it +trash-put foo +# ls must fail since the file is no longer there +count=$(ls | grep -c foo || true) +[ $count -eq 0 ] + +### Check that it made it to the trash +trash-list | grep -q foo + +### Restore it +# Note: this only works in clean environments that started with an +# empty trash +echo 0 | restore-trash +ls foo > /dev/null + +### Now trash it and remove it from the trash +trash-put foo +trash-rm foo +count=$(ls | grep -c foo || true) +[ $count -eq 0 ] +count=$(trash-list | grep -c foo || true) +[ $count -eq 0 ] + +### Create a bunch of files, trash them and empty the trash +# This is currently disabled as I don't have an adt-virt that allows +# the "breaks-testbed" restriction. If you uncomment it, Add +# "Restrictions: breaks-testbed" to the autopkgtest's control file. +# echo foo > foo +# echo bar > bar +# trash-put foo bar +# count=$(trash-list | wc -l) +# [ $count -eq 2 ] +# trash-empty +# count=$(trash-list | wc -l) +# [ $count -eq 2 ] + +### Cleanup +#rm -r $HOME diff -Nru trash-cli-0.12.7/debian/watch trash-cli-0.12.9.14/debian/watch --- trash-cli-0.12.7/debian/watch 2012-06-23 10:21:54.000000000 +0000 +++ trash-cli-0.12.9.14/debian/watch 2014-08-31 12:52:54.000000000 +0000 @@ -1,2 +1,2 @@ version=3 -http://githubredir.debian.net/github/andreafrancia/trash-cli/ (.*).tar.gz +http://githubredir.debian.net/github/andreafrancia/trash-cli/(.*).tar.gz diff -Nru trash-cli-0.12.7/docs/back-links.txt trash-cli-0.12.9.14/docs/back-links.txt --- trash-cli-0.12.7/docs/back-links.txt 2012-06-22 14:15:56.000000000 +0000 +++ trash-cli-0.12.9.14/docs/back-links.txt 2014-08-27 03:32:08.000000000 +0000 @@ -1,6 +1,10 @@ List of web pages that cite trash-cli, with the date when I discovered the page, sorted by discovery date (newest before): + - 2012-07-19 + http://shuffleos.com/5835/trashcli-command-line-utility-access-trashcan-ubuntu-linux-terminal/ + - 2012-07-18 http://crunchbanglinux.org/forums/topic/20818/trashbin-cli/ + - 2012-07-02 http://www.hecticgeek.com/2012/06/access-trash-command-line-ubuntu-linux-trash-cli/ - 2009-07-24 http://www.emacswiki.org/emacs/SystemTrash - 2009-03-29 2008-12-27 http://blog.goo.ne.jp/xxxfishbonexxx/e/45508969110d67f141226b68eab205ce japanese? blog post about installing? trash-cli - 2009-03-29 2008-10-16 http://linuxgazette.net/156/misc/lg/deleted_file_recovery.html diff -Nru trash-cli-0.12.7/docs/how-to-build-and-upload-a-new-release.txt trash-cli-0.12.9.14/docs/how-to-build-and-upload-a-new-release.txt --- trash-cli-0.12.7/docs/how-to-build-and-upload-a-new-release.txt 2012-06-22 14:15:56.000000000 +0000 +++ trash-cli-0.12.9.14/docs/how-to-build-and-upload-a-new-release.txt 2014-08-27 03:32:08.000000000 +0000 @@ -1,43 +1,27 @@ -To build a release ------------------- +How to build a release +====================== + Update the version number: - vim trashcli/trash.py + vim trashcli/trash.py + version="$(python setup.py --version)" Run all tests: - nosetests - + nosetests Test the installation from tarball: python setup.py sdist - tarball=trash-cli-0.12.7.tar.gz - - scp dist/$tarball root@192.168.56.101: - - ssh root@192.168.56.101 " - tar xvfz $tarball - cd ${tarball%%.tar.gz} - python setup.py install - trash-put --help - " - -Test the installation using easy_install: - - ssh root@192.168.56.101 " - apt-get install python-setuptools - easy_install "$tarball" - trash-put --help - " + python check_release_installation.py Register and upload: - python setup.py register - python setup.py sdist upload + python setup.py register + python setup.py sdist upload Now you can tag the repo status: - git tag $(python setup.py --version) + git tag "${version:?}" -EOF diff -Nru trash-cli-0.12.7/docs/tests-to-do-before-releasing.txt trash-cli-0.12.9.14/docs/tests-to-do-before-releasing.txt --- trash-cli-0.12.7/docs/tests-to-do-before-releasing.txt 2012-06-22 14:15:56.000000000 +0000 +++ trash-cli-0.12.9.14/docs/tests-to-do-before-releasing.txt 1970-01-01 00:00:00.000000000 +0000 @@ -1,37 +0,0 @@ -# Creating and testing a release package -# -------------------------------------- -# Create the dist:: - - python setup.py sdist - -# Unpack for testing:: - - cd dist - tar xvfz trash-cli-*.tar.gz - cd trash-cli-* - -# Prepare the virtualenv - - virtualenv testing - source testing/bin/activate - -# prepare the test scripts so they can tested - - python setup.py develop --script-dir scripts - -# launch tests with python nose - - python setup.py test - -# test the scripts with bashunit - command-test/create-test-volume - command-test/run-tests scripts command-test/test-volume - sudo umount command-test/test-volume - -# test the creation of .rpm - python setup.py bdist_rpm - -# in case of errors: - python setup.py bdist_rpm --install-script=install-rpm.sh - -echo "All done" diff -Nru trash-cli-0.12.7/ez_setup.py trash-cli-0.12.9.14/ez_setup.py --- trash-cli-0.12.7/ez_setup.py 2012-06-22 14:15:56.000000000 +0000 +++ trash-cli-0.12.9.14/ez_setup.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,284 +0,0 @@ -#!python -"""Bootstrap setuptools installation - -If you want to use setuptools in your package's setup.py, just include this -file in the same directory with it, and add this to the top of your setup.py:: - - from ez_setup import use_setuptools - use_setuptools() - -If you want to require a specific version of setuptools, set a download -mirror, or use an alternate download directory, you can do so by supplying -the appropriate options to ``use_setuptools()``. - -This file can also be run as a script to install or upgrade setuptools. -""" -import sys -DEFAULT_VERSION = "0.6c11" -DEFAULT_URL = "http://pypi.python.org/packages/%s/s/setuptools/" % sys.version[:3] - -md5_data = { - 'setuptools-0.6b1-py2.3.egg': '8822caf901250d848b996b7f25c6e6ca', - 'setuptools-0.6b1-py2.4.egg': 'b79a8a403e4502fbb85ee3f1941735cb', - 'setuptools-0.6b2-py2.3.egg': '5657759d8a6d8fc44070a9d07272d99b', - 'setuptools-0.6b2-py2.4.egg': '4996a8d169d2be661fa32a6e52e4f82a', - 'setuptools-0.6b3-py2.3.egg': 'bb31c0fc7399a63579975cad9f5a0618', - 'setuptools-0.6b3-py2.4.egg': '38a8c6b3d6ecd22247f179f7da669fac', - 'setuptools-0.6b4-py2.3.egg': '62045a24ed4e1ebc77fe039aa4e6f7e5', - 'setuptools-0.6b4-py2.4.egg': '4cb2a185d228dacffb2d17f103b3b1c4', - 'setuptools-0.6c1-py2.3.egg': 'b3f2b5539d65cb7f74ad79127f1a908c', - 'setuptools-0.6c1-py2.4.egg': 'b45adeda0667d2d2ffe14009364f2a4b', - 'setuptools-0.6c10-py2.3.egg': 'ce1e2ab5d3a0256456d9fc13800a7090', - 'setuptools-0.6c10-py2.4.egg': '57d6d9d6e9b80772c59a53a8433a5dd4', - 'setuptools-0.6c10-py2.5.egg': 'de46ac8b1c97c895572e5e8596aeb8c7', - 'setuptools-0.6c10-py2.6.egg': '58ea40aef06da02ce641495523a0b7f5', - 'setuptools-0.6c11-py2.3.egg': '2baeac6e13d414a9d28e7ba5b5a596de', - 'setuptools-0.6c11-py2.4.egg': 'bd639f9b0eac4c42497034dec2ec0c2b', - 'setuptools-0.6c11-py2.5.egg': '64c94f3bf7a72a13ec83e0b24f2749b2', - 'setuptools-0.6c11-py2.6.egg': 'bfa92100bd772d5a213eedd356d64086', - 'setuptools-0.6c2-py2.3.egg': 'f0064bf6aa2b7d0f3ba0b43f20817c27', - 'setuptools-0.6c2-py2.4.egg': '616192eec35f47e8ea16cd6a122b7277', - 'setuptools-0.6c3-py2.3.egg': 'f181fa125dfe85a259c9cd6f1d7b78fa', - 'setuptools-0.6c3-py2.4.egg': 'e0ed74682c998bfb73bf803a50e7b71e', - 'setuptools-0.6c3-py2.5.egg': 'abef16fdd61955514841c7c6bd98965e', - 'setuptools-0.6c4-py2.3.egg': 'b0b9131acab32022bfac7f44c5d7971f', - 'setuptools-0.6c4-py2.4.egg': '2a1f9656d4fbf3c97bf946c0a124e6e2', - 'setuptools-0.6c4-py2.5.egg': '8f5a052e32cdb9c72bcf4b5526f28afc', - 'setuptools-0.6c5-py2.3.egg': 'ee9fd80965da04f2f3e6b3576e9d8167', - 'setuptools-0.6c5-py2.4.egg': 'afe2adf1c01701ee841761f5bcd8aa64', - 'setuptools-0.6c5-py2.5.egg': 'a8d3f61494ccaa8714dfed37bccd3d5d', - 'setuptools-0.6c6-py2.3.egg': '35686b78116a668847237b69d549ec20', - 'setuptools-0.6c6-py2.4.egg': '3c56af57be3225019260a644430065ab', - 'setuptools-0.6c6-py2.5.egg': 'b2f8a7520709a5b34f80946de5f02f53', - 'setuptools-0.6c7-py2.3.egg': '209fdf9adc3a615e5115b725658e13e2', - 'setuptools-0.6c7-py2.4.egg': '5a8f954807d46a0fb67cf1f26c55a82e', - 'setuptools-0.6c7-py2.5.egg': '45d2ad28f9750e7434111fde831e8372', - 'setuptools-0.6c8-py2.3.egg': '50759d29b349db8cfd807ba8303f1902', - 'setuptools-0.6c8-py2.4.egg': 'cba38d74f7d483c06e9daa6070cce6de', - 'setuptools-0.6c8-py2.5.egg': '1721747ee329dc150590a58b3e1ac95b', - 'setuptools-0.6c9-py2.3.egg': 'a83c4020414807b496e4cfbe08507c03', - 'setuptools-0.6c9-py2.4.egg': '260a2be2e5388d66bdaee06abec6342a', - 'setuptools-0.6c9-py2.5.egg': 'fe67c3e5a17b12c0e7c541b7ea43a8e6', - 'setuptools-0.6c9-py2.6.egg': 'ca37b1ff16fa2ede6e19383e7b59245a', -} - -import sys, os -try: from hashlib import md5 -except ImportError: from md5 import md5 - -def _validate_md5(egg_name, data): - if egg_name in md5_data: - digest = md5(data).hexdigest() - if digest != md5_data[egg_name]: - print >>sys.stderr, ( - "md5 validation of %s failed! (Possible download problem?)" - % egg_name - ) - sys.exit(2) - return data - -def use_setuptools( - version=DEFAULT_VERSION, download_base=DEFAULT_URL, to_dir=os.curdir, - download_delay=15 -): - """Automatically find/download setuptools and make it available on sys.path - - `version` should be a valid setuptools version number that is available - as an egg for download under the `download_base` URL (which should end with - a '/'). `to_dir` is the directory where setuptools will be downloaded, if - it is not already available. If `download_delay` is specified, it should - be the number of seconds that will be paused before initiating a download, - should one be required. If an older version of setuptools is installed, - this routine will print a message to ``sys.stderr`` and raise SystemExit in - an attempt to abort the calling script. - """ - was_imported = 'pkg_resources' in sys.modules or 'setuptools' in sys.modules - def do_download(): - egg = download_setuptools(version, download_base, to_dir, download_delay) - sys.path.insert(0, egg) - import setuptools; setuptools.bootstrap_install_from = egg - try: - import pkg_resources - except ImportError: - return do_download() - try: - pkg_resources.require("setuptools>="+version); return - except pkg_resources.VersionConflict, e: - if was_imported: - print >>sys.stderr, ( - "The required version of setuptools (>=%s) is not available, and\n" - "can't be installed while this script is running. Please install\n" - " a more recent version first, using 'easy_install -U setuptools'." - "\n\n(Currently using %r)" - ) % (version, e.args[0]) - sys.exit(2) - else: - del pkg_resources, sys.modules['pkg_resources'] # reload ok - return do_download() - except pkg_resources.DistributionNotFound: - return do_download() - -def download_setuptools( - version=DEFAULT_VERSION, download_base=DEFAULT_URL, to_dir=os.curdir, - delay = 15 -): - """Download setuptools from a specified location and return its filename - - `version` should be a valid setuptools version number that is available - as an egg for download under the `download_base` URL (which should end - with a '/'). `to_dir` is the directory where the egg will be downloaded. - `delay` is the number of seconds to pause before an actual download attempt. - """ - import urllib2, shutil - egg_name = "setuptools-%s-py%s.egg" % (version,sys.version[:3]) - url = download_base + egg_name - saveto = os.path.join(to_dir, egg_name) - src = dst = None - if not os.path.exists(saveto): # Avoid repeated downloads - try: - from distutils import log - if delay: - log.warn(""" ---------------------------------------------------------------------------- -This script requires setuptools version %s to run (even to display -help). I will attempt to download it for you (from -%s), but -you may need to enable firewall access for this script first. -I will start the download in %d seconds. - -(Note: if this machine does not have network access, please obtain the file - - %s - -and place it in this directory before rerunning this script.) ----------------------------------------------------------------------------""", - version, download_base, delay, url - ); from time import sleep; sleep(delay) - log.warn("Downloading %s", url) - src = urllib2.urlopen(url) - # Read/write all in one block, so we don't create a corrupt file - # if the download is interrupted. - data = _validate_md5(egg_name, src.read()) - dst = open(saveto,"wb"); dst.write(data) - finally: - if src: src.close() - if dst: dst.close() - return os.path.realpath(saveto) - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -def main(argv, version=DEFAULT_VERSION): - """Install or upgrade setuptools and EasyInstall""" - try: - import setuptools - except ImportError: - egg = None - try: - egg = download_setuptools(version, delay=0) - sys.path.insert(0,egg) - from setuptools.command.easy_install import main - return main(list(argv)+[egg]) # we're done here - finally: - if egg and os.path.exists(egg): - os.unlink(egg) - else: - if setuptools.__version__ == '0.0.1': - print >>sys.stderr, ( - "You have an obsolete version of setuptools installed. Please\n" - "remove it from your system entirely before rerunning this script." - ) - sys.exit(2) - - req = "setuptools>="+version - import pkg_resources - try: - pkg_resources.require(req) - except pkg_resources.VersionConflict: - try: - from setuptools.command.easy_install import main - except ImportError: - from easy_install import main - main(list(argv)+[download_setuptools(delay=0)]) - sys.exit(0) # try to force an exit - else: - if argv: - from setuptools.command.easy_install import main - main(argv) - else: - print "Setuptools version",version,"or greater has been installed." - print '(Run "ez_setup.py -U setuptools" to reinstall or upgrade.)' - -def update_md5(filenames): - """Update our built-in md5 registry""" - - import re - - for name in filenames: - base = os.path.basename(name) - f = open(name,'rb') - md5_data[base] = md5(f.read()).hexdigest() - f.close() - - data = [" %r: %r,\n" % it for it in md5_data.items()] - data.sort() - repl = "".join(data) - - import inspect - srcfile = inspect.getsourcefile(sys.modules[__name__]) - f = open(srcfile, 'rb'); src = f.read(); f.close() - - match = re.search("\nmd5_data = {\n([^}]+)}", src) - if not match: - print >>sys.stderr, "Internal error!" - sys.exit(2) - - src = src[:match.start(1)] + repl + src[match.end(1):] - f = open(srcfile,'w') - f.write(src) - f.close() - - -if __name__=='__main__': - if len(sys.argv)>2 and sys.argv[1]=='--md5update': - update_md5(sys.argv[2:]) - else: - main(sys.argv[1:]) - - - - - - diff -Nru trash-cli-0.12.7/.gitignore trash-cli-0.12.9.14/.gitignore --- trash-cli-0.12.7/.gitignore 2012-06-22 14:15:56.000000000 +0000 +++ trash-cli-0.12.9.14/.gitignore 2014-08-27 03:32:08.000000000 +0000 @@ -5,6 +5,7 @@ .DS_Store .local/ .svn +/bin/ /build/ /command-test/sandbox/ /command-test/test-volume.img @@ -15,8 +16,12 @@ /nose-*.egg/ /sandbox/ /scripts/ -/test-volume.dmg -/test-volume/ +/test-disk.img +/test-disk.dmg +/test-disk/ /topdir/ /trash_cli.egg-info/ /XDG_DATA_HOME/ +/fake-vol +/xdh/ +/MANIFEST diff -Nru trash-cli-0.12.7/HISTORY.txt trash-cli-0.12.9.14/HISTORY.txt --- trash-cli-0.12.7/HISTORY.txt 2012-06-22 14:15:56.000000000 +0000 +++ trash-cli-0.12.9.14/HISTORY.txt 2014-08-27 03:32:08.000000000 +0000 @@ -1,3 +1,9 @@ +0.12.9: + - Switched to distutils.core (instead of setuptools) + - Now `trash-put -v` will warning if it found a unsticky .Trash dir. + - New trash-rm command + - (Internal) Swtiched from realpath to abspath + 0.12.7: - fixed trash-empty crashed with GetoptError in short_has_arg(): option -2 not recognized (see diff -Nru trash-cli-0.12.7/integration_tests/assert_equals_with_unidiff.py trash-cli-0.12.9.14/integration_tests/assert_equals_with_unidiff.py --- trash-cli-0.12.7/integration_tests/assert_equals_with_unidiff.py 2012-06-22 14:15:56.000000000 +0000 +++ trash-cli-0.12.9.14/integration_tests/assert_equals_with_unidiff.py 2014-08-27 03:32:08.000000000 +0000 @@ -6,10 +6,14 @@ expected=expected.splitlines(1) actual=actual.splitlines(1) - diff=difflib.unified_diff(expected, actual, - fromfile='Expected', tofile='Actual', + diff=difflib.unified_diff(expected, actual, + fromfile='Expected', tofile='Actual', lineterm='\n', n=10) return ''.join(diff) from nose.tools import assert_equals - assert_equals(expected, actual, "\n" + unidiff(expected, actual)) + assert_equals(expected, actual, + "\n" + "Expected:%s\n" % repr(expected) + + " Actual:%s\n" % repr(actual) + + unidiff(expected, actual)) diff -Nru trash-cli-0.12.7/integration_tests/describe_trash_list.py trash-cli-0.12.9.14/integration_tests/describe_trash_list.py --- trash-cli-0.12.7/integration_tests/describe_trash_list.py 2012-06-22 14:15:56.000000000 +0000 +++ trash-cli-0.12.9.14/integration_tests/describe_trash_list.py 2014-08-27 03:32:08.000000000 +0000 @@ -3,8 +3,8 @@ import os from trashcli.trash import ListCmd from files import (write_file, require_empty_dir, make_sticky_dir, - ensure_non_sticky_dir, make_unsticky_dir, - make_unreadable_file, make_empty_file, make_parent_for) + make_unsticky_dir, make_unreadable_file, make_empty_file, + make_parent_for) from nose.tools import istest from .output_collector import OutputCollector from trashinfo import ( @@ -140,7 +140,7 @@ self.user.run_trash_list() self.user.should_read_output("2000-01-01 00:00:00 topdir/file1\n") - + @istest def and_should_warn_if_parent_is_not_sticky(self): self.when_dir_exists_unsticky('topdir/.Trash') @@ -211,8 +211,6 @@ self.user.should_read_output("2000-01-01 00:00:00 topdir/file\n") -from nose.tools import assert_raises - @istest class describe_trash_list_with_raw_option: def setup(self): @@ -286,6 +284,7 @@ environ = self.environ, getuid = self.fake_getuid, file_reader = file_reader, + list_volumes = lambda: self.volumes, ).run(*argv) def set_fake_uid(self, uid): self.fake_getuid = lambda: uid diff -Nru trash-cli-0.12.7/integration_tests/files.py trash-cli-0.12.9.14/integration_tests/files.py --- trash-cli-0.12.7/integration_tests/files.py 2012-06-22 14:15:56.000000000 +0000 +++ trash-cli-0.12.9.14/integration_tests/files.py 2014-08-27 03:32:08.000000000 +0000 @@ -20,6 +20,8 @@ def require_empty_dir(path): if os.path.exists(path): shutil.rmtree(path) make_dirs(path) + assert os.path.isdir(path) + assert_equals([], list(os.listdir(path))) def having_empty_dir(path): require_empty_dir(path) diff -Nru trash-cli-0.12.7/integration_tests/output_collector.py trash-cli-0.12.9.14/integration_tests/output_collector.py --- trash-cli-0.12.7/integration_tests/output_collector.py 2012-06-22 14:15:56.000000000 +0000 +++ trash-cli-0.12.9.14/integration_tests/output_collector.py 2014-08-27 03:32:08.000000000 +0000 @@ -1,3 +1,5 @@ +from assert_equals_with_unidiff import assert_equals_with_unidiff + class OutputCollector: def __init__(self): from StringIO import StringIO @@ -8,7 +10,6 @@ def assert_equal_to(self, expected): return self.should_be(expected) def should_be(self, expected): - from assert_equals_with_unidiff import assert_equals_with_unidiff assert_equals_with_unidiff(expected, self.stream.getvalue()) def should_match(self, regex): text = self.stream.getvalue() diff -Nru trash-cli-0.12.7/integration_tests/test_file_descriptions.py trash-cli-0.12.9.14/integration_tests/test_file_descriptions.py --- trash-cli-0.12.7/integration_tests/test_file_descriptions.py 2012-06-22 14:15:56.000000000 +0000 +++ trash-cli-0.12.9.14/integration_tests/test_file_descriptions.py 2014-08-27 03:32:08.000000000 +0000 @@ -1,6 +1,6 @@ # Copyright (C) 2011 Andrea Francia Trivolzio(PV) Italy -from trashcli.trash import describe, write_file +from trashcli.trash import describe from .files import require_empty_dir, having_file from nose.tools import assert_equals import os @@ -46,3 +46,9 @@ assert not os.path.exists('non-existent') assert_equals("non existent", describe('non-existent')) + +def write_file(path, contents): + f = open(path, 'w') + f.write(contents) + f.close() + diff -Nru trash-cli-0.12.7/integration_tests/test_listing_all_trashinfo_in_a_trashdir.py trash-cli-0.12.9.14/integration_tests/test_listing_all_trashinfo_in_a_trashdir.py --- trash-cli-0.12.7/integration_tests/test_listing_all_trashinfo_in_a_trashdir.py 1970-01-01 00:00:00.000000000 +0000 +++ trash-cli-0.12.9.14/integration_tests/test_listing_all_trashinfo_in_a_trashdir.py 2014-08-27 03:32:08.000000000 +0000 @@ -0,0 +1,51 @@ +from trashcli.trash import TrashDirectory + +from files import require_empty_dir +from files import write_file +from nose.tools import assert_equals, assert_items_equal +from mock import Mock + +class TestWhenListingTrashinfo: + def setUp(self): + require_empty_dir('sandbox') + self.trash_dir = TrashDirectory('sandbox', '/') + self.logger = Mock() + self.trash_dir.logger = self.logger + + + def test_should_list_a_trashinfo(self): + write_file('sandbox/info/foo.trashinfo') + + result = self.list_trashinfos() + + assert_equals(['sandbox/info/foo.trashinfo'], result) + + def test_should_list_multiple_trashinfo(self): + write_file('sandbox/info/foo.trashinfo') + write_file('sandbox/info/bar.trashinfo') + write_file('sandbox/info/baz.trashinfo') + + result = self.list_trashinfos() + + assert_items_equal(['sandbox/info/foo.trashinfo', + 'sandbox/info/baz.trashinfo', + 'sandbox/info/bar.trashinfo'], result) + + def test_should_ignore_non_trashinfo(self): + write_file('sandbox/info/not-a-trashinfo') + + result = self.list_trashinfos() + + assert_equals([], result) + + def test_non_trashinfo_should_reported_as_a_warn(self): + write_file('sandbox/info/not-a-trashinfo') + + self.list_trashinfos() + + self.logger.warning.assert_called_with('Non .trashinfo file in info dir') + + def list_trashinfos(self): + return list(self.trash_dir.all_info_files()) + + diff -Nru trash-cli-0.12.7/integration_tests/test_persist.py trash-cli-0.12.9.14/integration_tests/test_persist.py --- trash-cli-0.12.7/integration_tests/test_persist.py 2012-06-22 14:15:56.000000000 +0000 +++ trash-cli-0.12.9.14/integration_tests/test_persist.py 2014-08-27 03:32:08.000000000 +0000 @@ -1,70 +1,75 @@ # Copyright (C) 2008-2011 Andrea Francia Trivolzio(PV) Italy -from unittest import TestCase from trashcli.trash import TrashDirectory from trashcli.trash import TrashInfo from integration_tests.files import require_empty_dir -from datetime import datetime +from nose.tools import assert_equals, assert_true + import os +from datetime import datetime +from textwrap import dedent join = os.path.join -class TestTrashDirectory_persit_trash_info(TestCase) : +class TestTrashDirectory_persit_trash_info: def setUp(self): self.trashdirectory_base_dir = os.path.realpath("./sandbox/testTrashDirectory") require_empty_dir(self.trashdirectory_base_dir) - + self.instance=TrashDirectory(self.trashdirectory_base_dir, "/") - + def test_persist_trash_info_first_time(self): trash_info=TrashInfo("dummy-path", datetime(2007,01,01)) basename=os.path.basename(trash_info.path) content=trash_info.render() - (trash_info_file, - trash_info_id)=self.instance.persist_trash_info(basename,content) - - self.assertEquals('dummy-path', trash_info_id) - self.assertEquals(join(self.trashdirectory_base_dir,'info', 'dummy-path.trashinfo'), trash_info_file) - - self.assertEquals("[Trash Info]\n" - "Path=dummy-path\n" - "DeletionDate=2007-01-01T00:00:00\n", - read(trash_info_file)) + (trash_info_file, trash_info_id) = self.instance.persist_trash_info(basename,content) + assert_equals('dummy-path', trash_info_id) + assert_equals(join(self.trashdirectory_base_dir,'info', 'dummy-path.trashinfo'), trash_info_file) + + assert_equals(dedent("""\ + [Trash Info] + Path=dummy-path + DeletionDate=2007-01-01T00:00:00 + """), read(trash_info_file)) def test_persist_trash_info_first_100_times(self): self.test_persist_trash_info_first_time() - + for i in range(1,100) : trash_info=TrashInfo("dummy-path", datetime(2007,01,01)) - + basename=os.path.basename(trash_info.path) content=trash_info.render() (trash_info_file, trash_info_id)=self.instance.persist_trash_info(basename,content) - - self.assertEquals('dummy-path'+"_" + str(i), trash_info_id) - self.assertEquals("""[Trash Info] -Path=dummy-path -DeletionDate=2007-01-01T00:00:00 -""", read(trash_info_file)) + + assert_equals('dummy-path'+"_" + str(i), trash_info_id) + assert_equals(dedent("""\ + [Trash Info] + Path=dummy-path + DeletionDate=2007-01-01T00:00:00 + """), read(trash_info_file)) def test_persist_trash_info_other_times(self): self.test_persist_trash_info_first_100_times() - + for i in range(101,200) : trash_info=TrashInfo("dummy-path", datetime(2007,01,01)) - + basename=os.path.basename(trash_info.path) content=trash_info.render() (trash_info_file, trash_info_id)=self.instance.persist_trash_info(basename,content) - - self.assertTrue(trash_info_id.startswith("dummy-path_")) - self.assertEquals("""[Trash Info] -Path=dummy-path -DeletionDate=2007-01-01T00:00:00 -""", read(trash_info_file)) + + assert_true(trash_info_id.startswith("dummy-path_")) + assert_equals(dedent("""\ + [Trash Info] + Path=dummy-path + DeletionDate=2007-01-01T00:00:00 + """), read(trash_info_file)) + test_persist_trash_info_first_100_times.stress_test = True + test_persist_trash_info_other_times.stress_test = True def read(path): return file(path).read() diff -Nru trash-cli-0.12.7/integration_tests/test_trash_empty.py trash-cli-0.12.9.14/integration_tests/test_trash_empty.py --- trash-cli-0.12.7/integration_tests/test_trash_empty.py 2012-06-22 14:15:56.000000000 +0000 +++ trash-cli-0.12.9.14/integration_tests/test_trash_empty.py 2014-08-27 03:32:08.000000000 +0000 @@ -12,7 +12,7 @@ from mock import MagicMock @istest -class describe_trash_empty: +class WhenCalledWithoutArguments: def setUp(self): require_empty_dir('XDG_DATA_HOME') @@ -24,7 +24,8 @@ out = StringIO(), err = StringIO(), environ = self.environ, - now = now + now = now, + list_volumes = no_volumes, ) def user_run_trash_empty(self): @@ -111,7 +112,7 @@ assert os.path.exists(complete_path) @istest -class describe_trash_empty_invoked_with_N_days_as_argument: +class When_invoked_with_N_days_as_argument: def setUp(self): require_empty_dir('XDG_DATA_HOME') self.xdg_data_home = 'XDG_DATA_HOME' @@ -121,7 +122,8 @@ out = StringIO(), err = StringIO(), environ = self.environ, - now = self.now + now = self.now, + list_volumes = no_volumes, ) def user_run_trash_empty(self, *args): @@ -209,7 +211,8 @@ err, out = StringIO(), StringIO() cmd = EmptyCmd(err = err, out = out, - environ = {},) + environ = {}, + list_volumes = no_volumes,) cmd.run('trash-empty', '--help') assert_equals(out.getvalue(), dedent("""\ Usage: trash-empty [days] @@ -229,7 +232,8 @@ cmd = EmptyCmd(err = err, out = out, environ = {}, - version = '1.2.3') + version = '1.2.3', + list_volumes = no_volumes,) cmd.run('trash-empty', '--version') assert_equals(out.getvalue(), dedent("""\ trash-empty 1.2.3 @@ -241,7 +245,8 @@ self.cmd = EmptyCmd( err = self.err, out = self.out, - environ = {}) + environ = {}, + list_volumes = no_volumes) def it_should_fail(self): @@ -266,5 +271,6 @@ trash-empty: invalid option -- '3' """)) - +def no_volumes(): + return [] diff -Nru trash-cli-0.12.7/integration_tests/test_trash_put.py trash-cli-0.12.9.14/integration_tests/test_trash_put.py --- trash-cli-0.12.7/integration_tests/test_trash_put.py 2012-06-22 14:15:56.000000000 +0000 +++ trash-cli-0.12.9.14/integration_tests/test_trash_put.py 2014-08-27 03:32:08.000000000 +0000 @@ -1,11 +1,139 @@ # Copyright (C) 2009-2011 Andrea Francia Trivolzio(PV) Italy +from trashcli.trash import TrashPutCmd + +import os +from nose.tools import istest, assert_equals, assert_not_equals +from nose.tools import assert_in -from nose.tools import istest from .files import having_file, require_empty_dir, having_empty_dir -from trashcli.trash import TrashPutCmd +from .files import make_sticky_dir +from trashcli.fstab import FakeFstab + +class TrashPutTest: + + def setUp(self): + self.prepare_fixture() + + def prepare_fixture(self): + require_empty_dir('sandbox') + self.environ = {'XDG_DATA_HOME': 'sandbox/XDG_DATA_HOME' } + + from .output_collector import OutputCollector + self.out = OutputCollector() + self.err = OutputCollector() + self.fstab = FakeFstab() + + self.stderr_should_be = self.err.should_be + self.output_should_be = self.out.should_be + + def run_trashput(self, *argv): + cmd = TrashPutCmd( + stdout = self.out, + stderr = self.err, + environ = self.environ, + fstab = self.fstab + ) + self.exit_code = cmd.run(list(argv)) + self.stderr = self.err.getvalue() + +@istest +class trash_put_stderr(TrashPutTest): + @istest + def should_tell_where_a_file_is_trashed(self): + having_file('foo') + self.run_trashput('trash-put', '-v', 'foo') + + assert_in("trash-put: `foo' trashed in sandbox/XDG_DATA_HOME/Trash", + self.stderr.splitlines()) + + +from textwrap import dedent +@istest +class TestUnsecureTrashDirMessages(TrashPutTest): + def setUp(self): + TrashPutTest.setUp(self) + having_empty_dir('fake-vol') + self.fstab.add_mount('fake-vol') + having_file('fake-vol/foo') + + @istest + def when_is_unsticky(self): + having_empty_dir('fake-vol/.Trash') + + self.run_trashput('trash-put', '-v', 'fake-vol/foo') + + assert_line_in_text( + 'trash-put: found unsecure .Trash dir (should be sticky): ' + 'fake-vol/.Trash', self.stderr) + + @istest + def when_it_is_not_a_dir(self): + having_file('fake-vol/.Trash') + + self.run_trashput('trash-put', '-v', 'fake-vol/foo') + + assert_line_in_text( + 'trash-put: found unusable .Trash dir (should be a dir): ' + 'fake-vol/.Trash', self.stderr) + + @istest + def when_is_a_symlink(self): + make_sticky_dir('fake-vol/link-destination') + os.symlink('link-destination', 'fake-vol/.Trash') + + self.run_trashput('trash-put', '-v', 'fake-vol/foo') + + assert_line_in_text( + 'trash-put: found unsecure .Trash dir (should not be a symlink): ' + 'fake-vol/.Trash', self.stderr) + +def assert_line_in_text(line, text): + assert_in(line, text.splitlines(), dedent('''\ + Line not found in text + Line: + + %s + + Text: + + --- + %s---''') + %(repr(line), text)) + + + +def should_fail(func): + from nose.tools import assert_raises + with assert_raises(AssertionError): + func() + +@istest +class exit_code(TrashPutTest): + @istest + def should_be_zero_on_success(self): + having_file('foo') + self.run_trashput('trash-put', 'foo') + self.exit_code_should_be_successfull() + + @istest + def should_be_non_zero_on_failure(self): + self.run_trashput('trash-put', 'non-existent') + self.exit_code_should_be_not_successfull() + + def exit_code_should_be_successfull(self): + assert_equals(0, self.exit_code) + + def exit_code_should_be_not_successfull(self): + assert_not_equals(0, self.exit_code) @istest -class describe_trash_put_command_when_deleting_a_file: +class when_deleting_a_file(TrashPutTest): + + def setUp(self): + self.prepare_fixture() + + having_file('sandbox/foo') + self.run_trashput('trash-put', 'sandbox/foo') @istest def it_should_remove_the_file(self): @@ -18,26 +146,14 @@ self.output_should_be('') def a_trashinfo_file_should_have_been_created(self): - - file('sandbox/XDG_DATA_HOME/Trash/info/foo.trashinfo').read() - def setUp(self): - require_empty_dir('sandbox') - - having_file('sandbox/foo') - self.run_trashput = TrashPutRunner( - environ = {'XDG_DATA_HOME': 'sandbox/XDG_DATA_HOME' } - ) - - self.stderr_should_be = self.run_trashput.err.should_be - self.output_should_be = self.run_trashput.out.should_be - - self.run_trashput('trash-put', 'sandbox/foo') + file('sandbox/XDG_DATA_HOME/Trash/info/foo.trashinfo').read() -import os -exists = os.path.exists @istest -class describe_trash_put_command_on_dot_arguments: +class when_fed_with_dot_arguments(TrashPutTest): + + def setUp(self): + self.prepare_fixture() def test_dot_argument_is_skipped(self): having_file('other_argument') @@ -95,24 +211,8 @@ assert not exists('other_argument') assert exists('sandbox') - def setUp(self): - self.run_trashput = TrashPutRunner() - self.stderr_should_be = self.run_trashput.err.should_be - -class TrashPutRunner: - def __init__(self, environ = os.environ): - from .output_collector import OutputCollector - self.out = OutputCollector() - self.err = OutputCollector() - self.environ = environ - def __call__(self, *argv): - TrashPutCmd( - stdout = self.out, - stderr = self.err, - environ = self.environ - ).run(list(argv)) - def file_should_have_been_deleted(path): import os assert not os.path.exists('sandbox/foo') +exists = os.path.exists diff -Nru trash-cli-0.12.7/integration_tests/test_trash_rm.py trash-cli-0.12.9.14/integration_tests/test_trash_rm.py --- trash-cli-0.12.7/integration_tests/test_trash_rm.py 1970-01-01 00:00:00.000000000 +0000 +++ trash-cli-0.12.9.14/integration_tests/test_trash_rm.py 2014-08-27 03:32:08.000000000 +0000 @@ -0,0 +1,87 @@ +from StringIO import StringIO +from mock import Mock, ANY +from nose.tools import assert_false, assert_raises + +from files import require_empty_dir, write_file +from trashcli.rm import Main, ListTrashinfos +from trashinfo import a_trashinfo_with_path + + +class TestTrashRm: + def test_integration(self): + trash_rm = Main() + trash_rm.environ = {'XDG_DATA_HOME':'sandbox/xdh'} + trash_rm.list_volumes = lambda:[] + trash_rm.getuid = 123 + trash_rm.stderr = StringIO() + + self.add_trashinfo_for(1, 'to/be/deleted') + self.add_trashinfo_for(2, 'to/be/kept') + + trash_rm.run(['trash-rm', 'delete*']) + + self.assert_trashinfo_has_been_deleted(1) + def setUp(self): + require_empty_dir('sandbox/xdh') + + def add_trashinfo_for(self, index, path): + write_file(self.trashinfo_from_index(index), + a_trashinfo_with_path(path)) + def trashinfo_from_index(self, index): + return 'sandbox/xdh/Trash/info/%s.trashinfo' % index + + def assert_trashinfo_has_been_deleted(self, index): + import os + filename = self.trashinfo_from_index(index) + assert_false(os.path.exists(filename), + 'File "%s" still exists' % filename) + +class TestListing: + def setUp(self): + require_empty_dir('sandbox') + self.out = Mock() + self.listing = ListTrashinfos(self.out) + self.index = 0 + + def test_should_report_original_location(self): + self.add_trashinfo('/foo') + + self.listing.list_from_home_trashdir('sandbox/Trash') + + self.out.assert_called_with('/foo', ANY) + + def test_should_report_trashinfo_path(self): + self.add_trashinfo(trashinfo_path='sandbox/Trash/info/a.trashinfo') + + self.listing.list_from_home_trashdir('sandbox/Trash') + + self.out.assert_called_with(ANY, 'sandbox/Trash/info/a.trashinfo') + + def test_should_handle_volume_trashdir(self): + self.add_trashinfo(trashinfo_path='sandbox/.Trash/123/info/a.trashinfo') + + self.listing.list_from_volume_trashdir('sandbox/.Trash/123', + '/fake/vol') + + self.out.assert_called_with(ANY, 'sandbox/.Trash/123/info/a.trashinfo') + + def test_should_absolutize_relative_path_for_volume_trashdir(self): + self.add_trashinfo(path='foo/bar', trashdir='sandbox/.Trash/501') + + self.listing.list_from_volume_trashdir('sandbox/.Trash/501', + '/fake/vol') + + self.out.assert_called_with('/fake/vol/foo/bar', ANY) + + def add_trashinfo(self, path='unspecified/original/location', + trashinfo_path=None, + trashdir='sandbox/Trash'): + trashinfo_path = trashinfo_path or self._trashinfo_path(trashdir) + write_file(trashinfo_path, a_trashinfo_with_path(path)) + def _trashinfo_path(self, trashdir): + path = '%s/info/%s.trashinfo' % (trashdir, self.index) + self.index +=1 + return path + + + diff -Nru trash-cli-0.12.7/integration_tests/test_trash_rm_script.py trash-cli-0.12.9.14/integration_tests/test_trash_rm_script.py --- trash-cli-0.12.7/integration_tests/test_trash_rm_script.py 1970-01-01 00:00:00.000000000 +0000 +++ trash-cli-0.12.9.14/integration_tests/test_trash_rm_script.py 2014-08-27 03:32:08.000000000 +0000 @@ -0,0 +1,24 @@ +from nose.tools import istest, assert_equals, assert_in +import subprocess +from subprocess import STDOUT, PIPE, check_output, call, Popen +from assert_equals_with_unidiff import assert_equals_with_unidiff as assert_equals +from textwrap import dedent + +from pprint import pprint + +@istest +class WhenNoArgs: + def setUp(self): + process = Popen(['python', 'trashcli/rm.py'], + env={'PYTHONPATH':'.'}, + stdin=None, + stdout=PIPE, + stderr=PIPE) + + (self.stdout, self.stderr) = process.communicate() + process.wait() + self.returncode = process.returncode + + def test_should_print_usage_on_standard_error(self): + assert_in("Usage:", self.stderr.splitlines()) + diff -Nru trash-cli-0.12.7/integration_tests/trashinfo.py trash-cli-0.12.9.14/integration_tests/trashinfo.py --- trash-cli-0.12.7/integration_tests/trashinfo.py 2012-06-22 14:15:56.000000000 +0000 +++ trash-cli-0.12.9.14/integration_tests/trashinfo.py 2014-08-27 03:32:08.000000000 +0000 @@ -20,3 +20,7 @@ def a_trashinfo_with_date(date): return ("[Trash Info]\n" "DeletionDate=%s\n" % date) + +def a_trashinfo_with_path(path): + return ("[Trash Info]\n" + "Path=%s\n" % path) diff -Nru trash-cli-0.12.7/man/man1/trash-rm.1 trash-cli-0.12.9.14/man/man1/trash-rm.1 --- trash-cli-0.12.7/man/man1/trash-rm.1 1970-01-01 00:00:00.000000000 +0000 +++ trash-cli-0.12.9.14/man/man1/trash-rm.1 2014-08-27 03:32:08.000000000 +0000 @@ -0,0 +1,42 @@ +.\" Copyright (C) 2012 Andrea Francia + +.TH "TRASH-RM" "1" + +.SH "NAME" +trash-rm \- Removes files matching a pattern from the trash can + +.SH "SYNOPSIS" +.B trash-rm +.RI [ PATTERN ] + +.SH "DESCRIPTION" +.PP +Remove from the trash can all the files matching the PATTERN. + +.SH "EXAMPLE" +.nf +$ trash-rm foo # Removes all files with name 'foo' in trash can +$ trash-rm '*.c' # Removes all files ending with '.o' in trash can +.fi + +.SH "BUGS" +Report bugs to http://code.google.com/p/trash-cli/issues + +.SH "CREDITS" +Trash was written by Andrea Francia . +The original version of trash-empty was contributed by Einar Orn Olason . +The first version of manual pages was written by Steve Stalcup +and was modified by Massimo Cavalleri . + +.SH "COPYRIGHT" +Both are released under the \s-1GNU\s0 General Public License, version 2. + +.SH "SEE ALSO" +trash-put(1), +trash-list(1), +trash-empty(1), +restore-trash(1), +and the FreeDesktop.org Trash Specification at +http://www.ramendik.ru/docs/trashspec.html. +.br + diff -Nru trash-cli-0.12.7/README.rst trash-cli-0.12.9.14/README.rst --- trash-cli-0.12.7/README.rst 2012-06-22 14:15:56.000000000 +0000 +++ trash-cli-0.12.9.14/README.rst 2014-08-27 03:32:08.000000000 +0000 @@ -52,7 +52,7 @@ Remove only the files that have been deleted before ago:: - $ trash-empy + $ trash-empty Example:: @@ -132,9 +132,14 @@ Running tests:: - nosetests unit_tests # unit tests - nosetests integration_tests # integration tests - nosetests # run all tests + nosetests unit_tests # run only unit tests + nosetests integration_tests # run all integration tests + nosetests -A 'not stress_test' # run all tests but stress tests + nosetests # run all tests + +Check the installation process before release:: + + python check_release_installation.py Profiling unit tests:: diff -Nru trash-cli-0.12.7/setup.py trash-cli-0.12.9.14/setup.py --- trash-cli-0.12.7/setup.py 2012-06-22 14:15:56.000000000 +0000 +++ trash-cli-0.12.9.14/setup.py 2014-08-27 03:32:08.000000000 +0000 @@ -1,36 +1,64 @@ -# Copyright (C) 2007-2011 Andrea Francia Trivolzio(PV) Italy - -import ez_setup; ez_setup.use_setuptools() -from setuptools import setup +# Copyright (C) 2007-2012 Andrea Francia Trivolzio(PV) Italy +from distutils.core import setup import sys -sys.path.append('.') -from trashcli import trash -setup( - name = 'trash-cli', - version = trash.version, - author = 'Andrea Francia', - author_email = 'me@andreafrancia.it', - url = 'https://github.com/andreafrancia/trash-cli', - description = 'Command line interface to FreeDesktop.org Trash.', - license = 'GPL v2', - long_description = file("README.rst").read(), - packages = ['trashcli', 'integration_tests', 'unit_tests'], - test_suite = "nose.collector", - entry_points = { - 'console_scripts' : [ - 'trash-list = trashcli.cmds:list', - 'trash = trashcli.cmds:put', - 'trash-put = trashcli.cmds:put', - 'restore-trash = trashcli.cmds:restore', - 'trash-empty = trashcli.cmds:empty' - ] - }, - data_files = [('share/man/man1', ['man/man1/trash-empty.1', - 'man/man1/trash-list.1', - 'man/man1/restore-trash.1', - 'man/man1/trash-put.1'])], - tests_require = file("requirements-dev.txt").readlines(), -) +def main(): + sys.path.append('.') + from trashcli import trash + bin_dir.add_script('trash-list' , 'trashcli.cmds', 'list') + bin_dir.add_script('trash' , 'trashcli.cmds', 'put') + bin_dir.add_script('trash-put' , 'trashcli.cmds', 'put') + bin_dir.add_script('restore-trash', 'trashcli.cmds', 'restore') + bin_dir.add_script('trash-empty' , 'trashcli.cmds', 'empty') + bin_dir.add_script('trash-rm' , 'trashcli.rm' , 'main') + setup( + name = 'trash-cli' , version = trash.version , + author = 'Andrea Francia' , author_email = 'andrea@andreafrancia.it' , + url = 'https://github.com/andreafrancia/trash-cli', + description = 'Command line interface to FreeDesktop.org Trash.', + long_description = file("README.rst").read(), + license = 'GPL v2', + packages = ['trashcli'], + scripts = bin_dir.created_scripts, + data_files = [('share/man/man1', ['man/man1/trash-empty.1', + 'man/man1/trash-list.1', + 'man/man1/restore-trash.1', + 'man/man1/trash-put.1', + 'man/man1/trash-rm.1'])], + ) + +from textwrap import dedent +class BinDir: + def __init__(self, write_file, make_file_executable, make_dir): + self.write_file = write_file + self.make_file_executable = make_file_executable + self.make_dir = make_dir + self.created_scripts = [] + def add_script(self, name, module, main_function): + script_contents = dedent("""\ + #!/usr/bin/env python + from __future__ import absolute_import + import sys + from %(module)s import %(main_function)s as main + sys.exit(main()) + """) % locals() + self.make_dir('bin') + executable = 'bin/' + name + self.write_file(executable, script_contents) + self.make_file_executable(executable) + self.created_scripts.append(executable) + +import os,stat +def make_file_executable(path): + os.chmod(path, os.stat(path).st_mode | stat.S_IXUSR) +def write_file(name, contents): + file(name, 'w').write(contents) +def make_dir(path): + if not os.path.isdir(path): + os.mkdir(path) + +bin_dir = BinDir(write_file, make_file_executable, make_dir) +if __name__ == '__main__': + main() diff -Nru trash-cli-0.12.7/ssh.py trash-cli-0.12.9.14/ssh.py --- trash-cli-0.12.7/ssh.py 1970-01-01 00:00:00.000000000 +0000 +++ trash-cli-0.12.9.14/ssh.py 2014-08-27 03:32:08.000000000 +0000 @@ -0,0 +1,32 @@ +from nose.tools import assert_equals +import subprocess + +class Connection: + def __init__(self, target_host): + self.target_host = target_host + def run(self, *user_command): + ssh_invocation = ['ssh', self.target_host, '-oVisualHostKey=false'] + command = ssh_invocation + list(user_command) + exit_code, stderr, stdout = self._run_command(command) + return self.ExecutionResult(stdout, stderr, exit_code) + def put(self, source_file): + scp_command = ['scp', source_file, self.target_host + ':'] + exit_code, stderr, stdout = self._run_command(scp_command) + assert 0 == exit_code + def _run_command(self, command): + process = subprocess.Popen(command, stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + stdout,stderr = process.communicate() + exit_code = process.poll() + return exit_code, stderr, stdout + class ExecutionResult: + def __init__(self, stdout, stderr, exit_code): + self.stdout = stdout + self.stderr = stderr + self.exit_code = exit_code + def assert_no_err(self): + assert_equals('', self.stderr) + def assert_succesful(self): + assert self.exit_code == 0 + + diff -Nru trash-cli-0.12.7/tasks/make-disk.osx trash-cli-0.12.9.14/tasks/make-disk.osx --- trash-cli-0.12.7/tasks/make-disk.osx 1970-01-01 00:00:00.000000000 +0000 +++ trash-cli-0.12.9.14/tasks/make-disk.osx 2014-08-27 03:32:08.000000000 +0000 @@ -0,0 +1,5 @@ +mkdir --parent test-disk +hdiutil detach ./test-disk || true +rm -f test-disk.dmg +hdiutil create -size 1M -fs HFS+ test-disk +hdiutil attach test-disk.dmg -mountpoint test-disk diff -Nru trash-cli-0.12.7/tasks/make-test-disk trash-cli-0.12.9.14/tasks/make-test-disk --- trash-cli-0.12.7/tasks/make-test-disk 1970-01-01 00:00:00.000000000 +0000 +++ trash-cli-0.12.9.14/tasks/make-test-disk 2014-08-27 03:32:08.000000000 +0000 @@ -0,0 +1,32 @@ +#!/bin/bash +# Copyright (C) 2007-2009 Andrea Francia Trivolzio(PV) Italy + +set -o errexit + +mount_point="test-disk" +device_image="test-disk.img" + +main() { + clean-up + create-image + mount-the-image + echo "test-volume mounted as '$mount_point'" +} + +clean-up() { + sudo umount "$mount_point" || true + rm -fv "$device_image" +} + +create-image() { + dd if=/dev/zero of="$device_image" bs=$((1024*1024)) count=1 + /sbin/mke2fs -F "$device_image" +} + +mount-the-image() { + mkdir --parents "$mount_point" + sudo mount -t ext2 "$device_image" "$mount_point" -o loop + sudo chmod a+rwx "$mount_point" +} + +main diff -Nru trash-cli-0.12.7/tasks/test-scripts-installation trash-cli-0.12.9.14/tasks/test-scripts-installation --- trash-cli-0.12.7/tasks/test-scripts-installation 1970-01-01 00:00:00.000000000 +0000 +++ trash-cli-0.12.9.14/tasks/test-scripts-installation 2014-08-27 03:32:08.000000000 +0000 @@ -0,0 +1,6 @@ +# Clean up +rm env/bin/trash-rm + +python setup.py develop --script-dir env/bin + +env/bin/trash-rm --help diff -Nru trash-cli-0.12.7/TODO.txt trash-cli-0.12.9.14/TODO.txt --- trash-cli-0.12.7/TODO.txt 2012-06-22 14:15:56.000000000 +0000 +++ trash-cli-0.12.9.14/TODO.txt 2014-08-27 03:32:08.000000000 +0000 @@ -1,3 +1,21 @@ +Feature: trash-rm + +TODO: + + - man pages: + - add test for checking man page installation + - add DONATION to all pages + - make all BUGS to point to github + - unificate "SEE ALSO" for all man pages + - add a test for trash-put and method 1 trashdir + - duplication: + - remove duplication (man page example and actual behaviour) + - remove duplication about BUGS in man page + + - setup.py: + - test is a setuptools keyword, it does not work with distutils + - 'data_files' is a distutils keyword, it does not work with setuptools + Features backlog: - trash-list should handle .trashinfo from home trashdir that contains relative path @@ -16,21 +34,7 @@ - trash-empty --from=/.Trash - trash-rm '*.o' - trash-empty should empty even the mac trash directory - -To be fixed: - - invalidate Google Code download and put a redirect to PyPi - - get rid of logging package - - get rid of the IOError exception raising on file is not in the same volume - - consolidate TrashDirectory abstract and the AvailableTrashDir abstraction - - get rid of TrashDirectory.info_dir and files_dir properties - - remove all the duplication - - all_info_files, trashed_files and for_all_trashed_files - - restore the stress test for persist trashinfo (not sure) - - remove NullReporter if not used - - get rid of TrashInfo abstraction - - get rid of TimeUtils.parse_iso8601 if not used - - rename remove_file in to remove_existing_file - - refactor TrashPutCmd for simplicity + - TRASH_DATE=2012-09-12 trash-put Test to be ported to nosetests: - trash-put: @@ -45,10 +49,3 @@ - should refuse to create the $topdir/.Trash/$uid directory if the $topdir/.Trash is not sticky -Bug reported on external trackers: - x trash-empty crashed with GetoptError in short_has_arg(): option -2 not - recognized, - url: https://bugs.launchpad.net/ubuntu/+source/trash-cli/+bug/1015877 - status: solved on Thu Jun 21 12:50:03 CEST 2012 - - diff -Nru trash-cli-0.12.7/trashcli/cmds.py trash-cli-0.12.9.14/trashcli/cmds.py --- trash-cli-0.12.7/trashcli/cmds.py 2012-06-22 14:15:56.000000000 +0000 +++ trash-cli-0.12.9.14/trashcli/cmds.py 2014-08-27 03:32:08.000000000 +0000 @@ -4,7 +4,7 @@ def put(): from trashcli.trash import TrashPutCmd - TrashPutCmd( + return TrashPutCmd( sys.stdout, sys.stderr ).run(sys.argv) @@ -18,22 +18,27 @@ environ = os.environ, exit = sys.exit, input = raw_input - ).run() + ).run(sys.argv) def empty(): from trashcli.trash import EmptyCmd + from trashcli.list_mount_points import mount_points return EmptyCmd( - out=sys.stdout, - err=sys.stderr, - environ=os.environ, + out = sys.stdout, + err = sys.stderr, + environ = os.environ, + list_volumes = mount_points, ).run(*sys.argv) def list(): from trashcli.trash import ListCmd + from trashcli.list_mount_points import mount_points ListCmd( - out = sys.stdout, - err = sys.stderr, - environ = os.environ, - getuid = os.getuid + out = sys.stdout, + err = sys.stderr, + environ = os.environ, + getuid = os.getuid, + list_volumes = mount_points, ).run(*sys.argv) + diff -Nru trash-cli-0.12.7/trashcli/fs.py trash-cli-0.12.9.14/trashcli/fs.py --- trash-cli-0.12.7/trashcli/fs.py 1970-01-01 00:00:00.000000000 +0000 +++ trash-cli-0.12.9.14/trashcli/fs.py 2014-08-27 03:32:08.000000000 +0000 @@ -0,0 +1,63 @@ +import os, shutil + +class FileSystemListing: + def entries_if_dir_exists(self, path): + if os.path.exists(path): + for entry in os.listdir(path): + yield entry + def exists(self, path): + return os.path.exists(path) + +class FileSystemReader(FileSystemListing): + def is_sticky_dir(self, path): + import os + return os.path.isdir(path) and has_sticky_bit(path) + def is_symlink(self, path): + return os.path.islink(path) + def contents_of(self, path): + return file(path).read() + +class FileRemover: + def remove_file(self, path): + try: + return os.remove(path) + except OSError: + shutil.rmtree(path) + def remove_file_if_exists(self,path): + if os.path.exists(path): self.remove_file(path) + +def contents_of(path): # TODO remove + return FileSystemReader().contents_of(path) +def has_sticky_bit(path): # TODO move to FileSystemReader + import os + import stat + return (os.stat(path).st_mode & stat.S_ISVTX) == stat.S_ISVTX + +def parent_of(path): + return os.path.dirname(path) + +def remove_file(path): + if(os.path.exists(path)): + try: + os.remove(path) + except: + return shutil.rmtree(path) + +def move(path, dest) : + return shutil.move(path, str(dest)) + +def mkdirs_using_mode(path, mode): + if os.path.isdir(path): + os.chmod(path, mode) + return + os.makedirs(path, mode) + +def list_files_in_dir(path): + for entry in os.listdir(path): + result = os.path.join(path, entry) + yield result + +def mkdirs(path): + if os.path.isdir(path): + return + os.makedirs(path) diff -Nru trash-cli-0.12.7/trashcli/fstab.py trash-cli-0.12.9.14/trashcli/fstab.py --- trash-cli-0.12.7/trashcli/fstab.py 1970-01-01 00:00:00.000000000 +0000 +++ trash-cli-0.12.9.14/trashcli/fstab.py 2014-08-27 03:32:08.000000000 +0000 @@ -0,0 +1,70 @@ +import os + +def volume_of(path) : + return Fstab().volume_of(path) + +class AbstractFstab(object): + def __init__(self, ismount): + self.ismount = ismount + def volume_of(self, path): + volume_of = VolumeOf(ismount=self.ismount) + return volume_of(path) + def mount_points(self): + return self.ismount.mount_points() + +class Fstab(AbstractFstab): + def __init__(self): + AbstractFstab.__init__(self, OsIsMount()) + +class FakeFstab: + def __init__(self): + self.ismount = FakeIsMount() + self.volume_of = VolumeOf(ismount = self.ismount) + self.volume_of.abspath = os.path.normpath + + def mount_points(self): + return self.ismount.mount_points() + + def volume_of(self, path): + volume_of = VolumeOf(ismount=self.ismount) + return volume_of(path) + + def add_mount(self, path): + self.ismount.add_mount(path) + +from trashcli.list_mount_points import mount_points as os_mount_points +class OsIsMount: + def __call__(self, path): + return os.path.ismount(path) + def mount_points(self): + return os_mount_points() + +class FakeIsMount: + def __init__(self): + self.fakes = set(['/']) + def add_mount(self, path): + self.fakes.add(path) + def __call__(self, path): + if path == '/': + return True + path = os.path.normpath(path) + if path in self.fakes: + return True + return False + def mount_points(self): + return self.fakes.copy() + +class VolumeOf: + def __init__(self, ismount): + self._ismount = ismount + import os + self.abspath = os.path.abspath + + def __call__(self, path): + path = self.abspath(path) + while path != os.path.dirname(path): + if self._ismount(path): + break + path = os.path.dirname(path) + return path + diff -Nru trash-cli-0.12.7/trashcli/list_mount_points.py trash-cli-0.12.9.14/trashcli/list_mount_points.py --- trash-cli-0.12.7/trashcli/list_mount_points.py 2012-06-22 14:15:56.000000000 +0000 +++ trash-cli-0.12.9.14/trashcli/list_mount_points.py 2014-08-27 03:32:08.000000000 +0000 @@ -4,12 +4,12 @@ try: return list(mount_points_from_getmnt()) except AttributeError: - return mount_points_from_df() + return mount_points_from_df() def mount_points_from_getmnt(): for elem in _mounted_filesystems_from_getmnt(): yield elem.mount_dir - + def mount_points_from_df(): import subprocess df_output = subprocess.Popen(["df", "-P"], stdout=subprocess.PIPE).stdout @@ -20,14 +20,14 @@ df_output.readline() def chomp(string): return string.rstrip('\n') - + skip_header() for line in df_output: - line = chomp(line) - yield line.split(None, 5)[-1] + line = chomp(line) + yield line.split(None, 5)[-1] def _mounted_filesystems_from_getmnt() : - from ctypes import Structure, c_char_p, c_int, c_void_p, cdll, POINTER + from ctypes import Structure, c_char_p, c_int, c_void_p, cdll, POINTER from ctypes.util import find_library import sys class Filesystem: diff -Nru trash-cli-0.12.7/trashcli/rm.py trash-cli-0.12.9.14/trashcli/rm.py --- trash-cli-0.12.7/trashcli/rm.py 1970-01-01 00:00:00.000000000 +0000 +++ trash-cli-0.12.9.14/trashcli/rm.py 2014-08-27 03:32:08.000000000 +0000 @@ -0,0 +1,77 @@ +import fnmatch +import os, sys + +from trashcli.trash import TrashDir, parse_path +from trashcli.trash import TrashDirs +from trashcli.trash import TopTrashDirRules +from trashcli.trash import CleanableTrashcan +from trashcli.fs import FileSystemReader +from trashcli.fs import FileRemover + +class Main: + def run(self, argv): + args = argv[1:] + self.exit_code = 0 + + if not args: + self.stderr.write('Usage:\n' + ' trash-rm PATTERN\n' + '\n' + 'Please specify PATTERN\n') + self.exit_code = 8 + return + + trashcan = CleanableTrashcan(FileRemover()) + cmd = Filter(trashcan.delete_trashinfo_and_backup_copy) + cmd.use_pattern(args[0]) + file_reader = FileSystemReader() + listing = ListTrashinfos(cmd.delete_if_matches) + top_trashdir_rules = TopTrashDirRules(file_reader) + trashdirs = TrashDirs(self.environ, self.getuid, + list_volumes = self.list_volumes, + top_trashdir_rules = top_trashdir_rules) + trashdirs.on_trash_dir_found = listing.list_from_volume_trashdir + + trashdirs.list_trashdirs() + +def main(): + from trashcli.list_mount_points import mount_points + main = Main() + main.environ = os.environ + main.getuid = os.getuid + main.list_volumes = mount_points + main.stderr = sys.stderr + + main.run(sys.argv) + + return main.exit_code + +class Filter: + def __init__(self, trashcan): + self.delete = trashcan + def use_pattern(self, pattern): + self.pattern = pattern + def delete_if_matches(self, original_location, info_file): + basename = os.path.basename(original_location) + if fnmatch.fnmatchcase(basename, self.pattern): + self.delete(info_file) + +class ListTrashinfos: + def __init__(self, out): + self.out = out + def list_from_home_trashdir(self, trashdir_path): + self.list_from_volume_trashdir(trashdir_path, '/') + def list_from_volume_trashdir(self, trashdir_path, volume): + self.volume = volume + self.trashdir = TrashDir(FileSystemReader()) + self.trashdir.open(trashdir_path, volume) + self.trashdir.each_trashinfo(self._report_original_location) + def _report_original_location(self, trashinfo_path): + file_reader = FileSystemReader() + trashinfo = file_reader.contents_of(trashinfo_path) + path = parse_path(trashinfo) + complete_path = os.path.join(self.volume, path) + self.out(complete_path, trashinfo_path) + +if __name__ == '__main__': + sys.exit(main()) diff -Nru trash-cli-0.12.7/trashcli/trash.py trash-cli-0.12.9.14/trashcli/trash.py --- trash-cli-0.12.7/trashcli/trash.py 2012-06-22 14:15:56.000000000 +0000 +++ trash-cli-0.12.9.14/trashcli/trash.py 2014-08-27 03:32:08.000000000 +0000 @@ -1,36 +1,69 @@ # Copyright (C) 2007-2011 Andrea Francia Trivolzio(PV) Italy from __future__ import absolute_import -version='0.12.7' +version='0.12.9.14' import os import logging +from .fstab import Fstab logger=logging.getLogger('trashcli.trash') logger.setLevel(logging.WARNING) logger.addHandler(logging.StreamHandler()) +# Error codes (from os on *nix, hard coded for Windows): +EX_OK = getattr(os, 'EX_OK' , 0) +EX_USAGE = getattr(os, 'EX_USAGE', 64) +EX_IOERR = getattr(os, 'EX_IOERR', 74) + +from .fs import list_files_in_dir class TrashDirectory: - def __init__(self, path, volume) : # TODO: contents_of should be injected + def __init__(self, path, volume): self.path = os.path.normpath(path) self.volume = volume + class all_is_ok_checker: + def valid_to_be_written(self, a, b): pass + def check(self, a):pass + self.checker = all_is_ok_checker() + # events + def warn_non_trashinfo(): + self.logger.warning("Non .trashinfo file in info dir") + self.on_non_trashinfo_found = warn_non_trashinfo + self.logger = logger def __str__(self) : - return str(self.path) + return self.name() + + def __repr__(self): + return 'TrashDirectory(%s,%s)' % (repr(self.path), repr(self.volume)) + + def name(self): + import re + import posixpath + result=self.path + try: + home_dir=os.environ['HOME'] + home_dir = posixpath.normpath(home_dir) + if home_dir != '': + result=re.sub('^'+ re.escape(home_dir)+os.path.sep, '~' + os.path.sep,result) + except KeyError: + pass + return result + + def store_absolute_paths(self): + self.path_for_trash_info = PathForTrashInfo() + self.path_for_trash_info.make_absolutes_paths() + + def store_relative_paths(self): + self.path_for_trash_info = PathForTrashInfo() + self.path_for_trash_info.make_paths_relatives_to(self.volume) def trash(self, path): - from datetime import datetime path = os.path.normpath(path) - self.check() - - if not self.volume == volume_of(parent_of(path)) : - raise IOError("file is not in the same volume of trash directory!\n" - + "self.volume = " + str(self.volume) + ", \n" - + "file.parent.volume = " - + str(volume_of(parent_of(path)))) - trash_info = TrashInfo(self._path_for_trashinfo(path), - datetime.now()) + from datetime import datetime + trash_info_path = self.path_for_trash_info.for_file(path) + trash_info = TrashInfo(trash_info_path, datetime.now()) basename = os.path.basename(trash_info.path) trashinfo_file_content = trash_info.render() @@ -78,20 +111,21 @@ try : for info_file in list_files_in_dir(self.info_dir): if not os.path.basename(info_file).endswith('.trashinfo') : - logger.warning("Non .trashinfo file in info dir") + self.on_non_trashinfo_found() else : yield info_file except OSError: # when directory does not exist pass def trashed_files(self) : + # Only used by restore-trash for info_file in self.all_info_files(): try: yield self._create_trashed_file_from_info_file(info_file) except ValueError: - logger.warning("Non parsable trashinfo file: %s" % info_file) + self.logger.warning("Non parsable trashinfo file: %s" % info_file) except IOError as e: - logger.warning(str(e)) + self.logger.warning(str(e)) def _create_trashed_file_from_info_file(self, info_file): trash_id = self.calc_id(info_file) @@ -116,12 +150,6 @@ actual_path, self) - def _calc_original_location(self, path): - if os.path.isabs(path) : - return path - else : - return os.path.join(self.volume, path) - @staticmethod def calc_id(trash_info_file): return os.path.basename(trash_info_file)[:-len('.trashinfo')] @@ -132,9 +160,6 @@ def _calc_path_for_info_file(self, trash_id) : return os.path.join(self.info_dir, '%s.trashinfo' % trash_id) - def _path_for_trashinfo(self, fileToTrash): - raise NotImplementedError() - """ Create a .trashinfo file in the $trash/info directory. returns the created TrashInfoFile. @@ -166,118 +191,51 @@ 0600) os.write(handle, content) os.close(handle) - logger.debug(".trashinfo created as %s." % dest) + self.logger.debug(".trashinfo created as %s." % dest) return (dest, trash_id) except OSError: - logger.debug("Attempt for creating %s failed." % dest) + self.logger.debug("Attempt for creating %s failed." % dest) index += 1 raise IOError() - def check(self): - """ - Perform a sanity check of this trash directory. - If the check is not passed the directory can be used for trashing, - listing or restoring files. - """ - pass - -class HomeTrashDirectory(TrashDirectory): - def __init__(self, path) : - TrashDirectory.__init__(self, path, volume_of(path)) +class PathForTrashInfo: + def make_paths_relatives_to(self, topdir): + self.topdir = topdir - def __str__(self): - import re - import posixpath - result=TrashDirectory.__str__(self) - try: - home_dir=os.environ['HOME'] - home_dir = posixpath.normpath(home_dir) - if home_dir != '': - result=re.sub('^'+ re.escape(home_dir)+os.path.sep, '~' + os.path.sep,result) - except KeyError: - pass - return result + def make_absolutes_paths(self): + self.topdir = None - def _path_for_trashinfo(self, fileToBeTrashed) : - fileToBeTrashed = os.path.normpath(fileToBeTrashed) + def for_file(self, path): + self.normalized_path = os.path.normpath(path) - # for the HomeTrashDirectory all path are stored as absolute + basename = os.path.basename(self.normalized_path) + parent = self._real_parent() - realpath = os.path.realpath(fileToBeTrashed) - parent = os.path.dirname(realpath) - basename = os.path.basename(fileToBeTrashed) - result = os.path.join(parent,basename) + if self.topdir != None: + if (parent == self.topdir) or parent.startswith(self.topdir+os.path.sep) : + parent = parent[len(self.topdir+os.path.sep):] + result = os.path.join(parent, basename) return result -class VolumeTrashDirectory(TrashDirectory) : - def __init__(self, path, volume) : - TrashDirectory.__init__(self,path, volume) - - def _path_for_trashinfo(self, fileToBeTrashed) : - # for the VolumeTrashDirectory paths are stored as relative - # if possible - fileToBeTrashed = os.path.normpath(fileToBeTrashed) - - # string representing the parent of the fileToBeTrashed - parent = os.path.dirname(fileToBeTrashed) - parent = os.path.realpath(parent) - - topdir=self.volume # e.g. /mnt/disk-1 + def _real_parent(self): + parent = os.path.dirname(self.normalized_path) + return os.path.realpath(parent) + +class NullObject: + def __getattr__(self, name): + return lambda *argl,**args:None - if parent.startswith(topdir+os.path.sep) : - parent = parent[len(topdir+os.path.sep):] - - result = os.path.join(parent, os.path.basename(fileToBeTrashed)) - return result - -class TopDirWithoutStickyBit(IOError): - """ - Raised when $topdir/.Trash doesn't have the sticky bit. - """ - pass - -class TopDirNotPresent(IOError): - """ - Raised when $topdir/.Trash is not a dir. - """ - pass - -class TopDirIsSymLink(IOError): - """ - Raised when $topdir/.Trash is a simbolic link. - """ - pass - -class Method1VolumeTrashDirectory(VolumeTrashDirectory): - def __init__(self, path, volume) : - VolumeTrashDirectory.__init__(self,path,volume) - - def check(self): - if not self.parent_is_dir(): - raise TopDirNotPresent("topdir should be a directory: %s" - % self.path) - if self.parent_is_link(): - raise TopDirIsSymLink("topdir can't be a symbolic link: %s" - % self.path) - if not self.parent_has_sticky_bit(): - raise TopDirWithoutStickyBit("topdir should have the sticky bit: %s" - % self.path) - def parent_is_dir(self): - return os.path.isdir(self.parent()) - def parent_is_link(self): - return os.path.islink(self.parent()) - def parent(self): - return os.path.dirname(self.path) - def parent_has_sticky_bit(self): - return has_sticky_bit(self.parent()) - -def real_list_mount_points(): - from trashcli.list_mount_points import mount_points - for mount_point in mount_points(): - yield mount_point +class HomeTrashCan: + def __init__(self, environ): + self.environ = environ + def path_to(self, out): + if 'XDG_DATA_HOME' in self.environ: + out('%(XDG_DATA_HOME)s/Trash' % self.environ) + elif 'HOME' in self.environ: + out('%(HOME)s/.local/share/Trash' % self.environ) class GlobalTrashCan: """ @@ -289,17 +247,16 @@ def __getattr__(self,name): return lambda *argl,**args:None from datetime import datetime - def __init__(self, - environ = os.environ, - reporter = NullReporter(), - getuid = os.getuid, - list_mount_points = real_list_mount_points, - now = datetime.now): - self.getuid = getuid - self.environ = environ - self.reporter = reporter - self.list_mount_points = list_mount_points - self.now = now + def __init__(self, home_trashcan, + reporter = NullReporter(), + getuid = os.getuid, + fstab = Fstab(), + now = datetime.now): + self.getuid = getuid + self.reporter = reporter + self.fstab = fstab + self.now = now + self.home_trashcan = home_trashcan def trashed_files(self): """Return a generator of all TrashedFile(s).""" @@ -307,7 +264,7 @@ for trashedfile in trash_dir.trashed_files(): yield trashedfile - def trash(self,file) : + def trash(self, file) : """ Trash a file in the appropriate trash directory. If the file belong to the same volume of the trash home directory it @@ -323,58 +280,110 @@ then try to trash in the second trash directory. """ - if self.should_skipped_by_specs(file): + if self._should_skipped_by_specs(file): self.reporter.unable_to_trash_dot_entries(file) return for trash_dir in self._possible_trash_directories_for(file): - if self.file_could_be_trashed_in(file, trash_dir.path): + + try: + class ValidationOutput: + def not_valid_should_be_a_dir(_): + raise TopDirNotPresent("topdir should be a directory: %s" + % trash_dir.path) + def not_valid_parent_should_not_be_a_symlink(_): + raise TopDirIsSymLink("topdir can't be a symbolic link: %s" + % trash_dir.path) + def not_valid_parent_should_be_sticky(_): + raise TopDirWithoutStickyBit("topdir should have the sticky bit: %s" + % trash_dir.path) + def is_valid(self): + pass + output = ValidationOutput() + class FileSystem: + def isdir(self, path): + return os.path.isdir(path) + def islink(self, path): + return os.path.islink(path) + def has_sticky_bit(self, path): + return has_sticky_bit(path) + trash_dir.checker.fs = FileSystem() + trash_dir.checker.valid_to_be_written(trash_dir.path, output) + except TopDirIsSymLink: + self.reporter.found_unsercure_trash_dir_symlink( + os.path.dirname(trash_dir.path)) + except TopDirNotPresent: + self.reporter.found_unusable_trash_dir_not_a_dir( + os.path.dirname(trash_dir.path)) + except TopDirWithoutStickyBit: + self.reporter.found_unsecure_trash_dir_unsticky( + os.path.dirname(trash_dir.path)) + + if self._file_could_be_trashed_in(file, trash_dir.path): try: trashed_file = trash_dir.trash(file) self.reporter.file_has_been_trashed_in_as( file, - trashed_file.trash_directory, + trashed_file.trash_directory.name(), trashed_file.original_file) return except (IOError, OSError), error: - self.reporter.unable_to_trash_file_in_because(file, trash_dir, error) + self.reporter.unable_to_trash_file_in_because( + file, trash_dir.name(), str(error)) self.reporter.unable_to_trash_file(file) - def should_skipped_by_specs(self, file): + def _should_skipped_by_specs(self, file): basename = os.path.basename(file) return (basename == ".") or (basename == "..") - def volume_of_parent(self, file): - return volume_of(parent_of(file)) + def volume_of(self, path): + return self.fstab.volume_of(path) - def file_could_be_trashed_in(self,file_to_be_trashed,trash_dir_path): - return volume_of(trash_dir_path) == self.volume_of_parent(file_to_be_trashed) + def _file_could_be_trashed_in(self,file_to_be_trashed,trash_dir_path): + return self.volume_of(trash_dir_path) == self.volume_of_parent(file_to_be_trashed) def _trash_directories(self) : """Return a generator of all TrashDirectories in the filesystem""" - yield self._home_trash_dir() - for mount_point in self.list_mount_points(): + for td in self._home_trash_dir(): + yield td + for mount_point in self.fstab.mount_points(): volume = mount_point yield self._volume_trash_dir1(volume) yield self._volume_trash_dir2(volume) - def _home_trash_dir(self) : - return HomeTrashDirectory(self._home_trash_dir_path()) - def _possible_trash_directories_for(self,file): - yield self._home_trash_dir() - for td in self.trash_directories_for_volume(self.volume_of_parent(file)): + def _possible_trash_directories_for(self, file): + for td in self._home_trash_dir(): + yield td + for td in self._trash_directories_for_volume(self.volume_of_parent(file)): yield td - def trash_directories_for_volume(self,volume): + def volume_of_parent(self, file): + return self.volume_of(parent_of(file)) + def _trash_directories_for_volume(self, volume): yield self._volume_trash_dir1(volume) yield self._volume_trash_dir2(volume) - def _volume_trash_dir1(self,volume): + def _home_trash_dir(self) : + paths = [] + self.home_trashcan.path_to(paths.append) + + result = [] + for trash_dir_path in paths: + trash_dir = TrashDirectory(trash_dir_path, self.volume_of(trash_dir_path)) + trash_dir.volume_of = self.volume_of + trash_dir.store_absolute_paths() + result.append(trash_dir) + return result + def _volume_trash_dir1(self, volume): """ Return the method (1) volume trash dir ($topdir/.Trash/$uid). """ uid = self.getuid() trash_directory_path = os.path.join(volume, '.Trash', str(uid)) - return Method1VolumeTrashDirectory(trash_directory_path,volume) + trash_dir = TrashDirectory(trash_directory_path,volume) + trash_dir.volume_of = self.volume_of + trash_dir.store_relative_paths() + trash_dir.checker = TopTrashDirRules(None) + return trash_dir def _volume_trash_dir2(self, volume) : """ Return the method (2) volume trash dir ($topdir/.Trash-$uid). @@ -382,17 +391,10 @@ uid = self.getuid() dirname=".Trash-%s" % str(uid) trash_directory_path = os.path.join(volume, dirname) - return VolumeTrashDirectory(trash_directory_path,volume) - def _home_trash_dir_path(self): - result = [] - home_trashcan_if_possible(self.environ, result.append) - return result[0] - - def for_all_trashed_file(self, action): - for trashedfile in self.trashed_files(): - action( - info_path = trashedfile.original_file, - path = trashedfile.info_file) + trash_dir = TrashDirectory(trash_directory_path,volume) + trash_dir.volume_of = self.volume_of + trash_dir.store_relative_paths() + return trash_dir class TrashedFile: """ @@ -420,28 +422,12 @@ if not os.path.isabs(path): raise ValueError("Absolute path required.") - self._path = path - self._deletion_date = deletion_date - self._info_file = info_file - self._actual_path = actual_path - self._trash_directory = trash_directory - - @property - def path(self) : - """ - The path from where the file has been trashed - """ - return self._path - - @property - def actual_path(self): - return self._actual_path - - original_file = actual_path - - @property - def deletion_date(self) : - return self._deletion_date + self.path = path + self.deletion_date = deletion_date + self.info_file = info_file + self.actual_path = actual_path + self.trash_directory = trash_directory + self.original_file = actual_path def restore(self, dest=None) : if dest is not None: @@ -455,82 +441,52 @@ move(self.original_file, self.path) remove_file(self.info_file) - @property - def info_file(self): - return self._info_file - - @property - def trash_directory(self) : - return self._trash_directory - class TrashInfo: def __init__(self, path, deletion_date): - from datetime import datetime - """Create a TrashInfo. + assert isinstance(deletion_date, datetime) + self.path = path + self.deletion_date = deletion_date - Keyword arguments: - path -- the of the .trashinfo file (string or Path) - deletion_date -- the date of deletion, should be a datetime. - """ - if not isinstance(deletion_date, datetime): - raise TypeError("deletion_date should be a datetime") - self._path = path - self._deletion_date = deletion_date + def render(self) : + import urllib + result = "[Trash Info]\n" + result += "Path=" + urllib.quote(self.path,'/') + "\n" + result += "DeletionDate=" + self._format_date(self.deletion_date) + "\n" + return result @staticmethod def _format_date(deletion_date): return deletion_date.strftime("%Y-%m-%dT%H:%M:%S") - @property - def deletion_date(self): - return self._deletion_date - - @property - def path(self): - return self._path - @staticmethod def parse(data): path = parse_path(data) deletion_date = parse_deletion_date(data) return TrashInfo(path, deletion_date) - def render(self) : - import urllib - result = "[Trash Info]\n" - result += "Path=" + urllib.quote(self.path,'/') + "\n" - result += "DeletionDate=" + self._format_date(self.deletion_date) + "\n" - return result - -class TimeUtils: - @staticmethod - def parse_iso8601(text) : - import time - from datetime import datetime - t=time.strptime(text, "%Y-%m-%dT%H:%M:%S") - return datetime(t.tm_year, t.tm_mon, t.tm_mday, - t.tm_hour, t.tm_min, t.tm_sec) -import shutil -def remove_file(path): - if(os.path.exists(path)): - try: - os.remove(path) - except: - return shutil.rmtree(path) +import os +from .fs import remove_file, has_sticky_bit +from .fs import move, mkdirs, mkdirs_using_mode, parent_of def getcwd_as_realpath(): return os.path.realpath(os.curdir) +import sys class RestoreCmd: def __init__(self, stdout, stderr, environ, exit, input, - curdir = getcwd_as_realpath): + curdir = getcwd_as_realpath, version = version): self.out = stdout self.err = stderr - self.environ = environ self.exit = exit self.input = input - self.trashcan = GlobalTrashCan( environ = self.environ) + self.trashcan = GlobalTrashCan( + home_trashcan = HomeTrashCan(environ)) self.curdir = curdir - def run(self): + self.version = version + def run(self, args=sys.argv): + if '--version' in args[1:]: + command = os.path.basename(args[0]) + self.println('%s %s' %(command, self.version)) + return trashed_files = [] self.for_all_trashed_file_in_dir(trashed_files.append, self.curdir()) @@ -570,10 +526,11 @@ return text class TrashPutCmd: - def __init__(self, stdout, stderr, environ = os.environ): + def __init__(self, stdout, stderr, environ = os.environ, fstab = Fstab()): self.stdout = stdout self.stderr = stderr self.environ = environ + self.fstab = fstab def run(self, argv): parser = self.get_option_parser(os.path.basename(argv[0])) @@ -582,13 +539,19 @@ if len(args) <= 0: parser.error("Please specify the files to trash.") - reporter=TrashPutReporter(self.get_logger(options.verbose,argv[0])) + reporter = TrashPutReporter(self.get_logger(options.verbose,argv[0])) self.trashcan = GlobalTrashCan( reporter = reporter, - environ = self.environ) + fstab = self.fstab, + home_trashcan = HomeTrashCan(self.environ)) self.trash_all(args) + if reporter.all_files_have_been_trashed: + return EX_OK + else: + return EX_IOERR + def trash_all(self, args): for arg in args : self.trash(arg) @@ -668,46 +631,35 @@ return MyLogger(self.stderr) class TrashPutReporter: - def __init__(self, logger): + def __init__(self, logger = NullObject()): self.logger = logger + self.all_files_have_been_trashed = True def unable_to_trash_dot_entries(self,file): self.logger.warning("cannot trash %s `%s'" % (describe(file), file)) def unable_to_trash_file(self,f): self.logger.warning("cannot trash %s `%s'" % (describe(f), f)) + self.all_files_have_been_trashed = False def file_has_been_trashed_in_as(self, trashee, trash_directory, destination): - self.logger.info("`%s' trashed in %s " % (trashee, trash_directory)) + self.logger.info("`%s' trashed in %s" % (trashee, trash_directory)) - def unable_to_trash_file_in_because(self, file_to_be_trashed, trash_directory, error): - self.logger.info("Failed to trash %s in %s, because :%s" % (file_to_be_trashed, - trash_directory, error)) - -def mkdirs_using_mode(path, mode): - if os.path.isdir(path): - os.chmod(path, mode) - return - os.makedirs(path, mode) - -def list_files_in_dir(path): - for entry in os.listdir(path): - result = os.path.join(path, entry) - yield result - -def move(path, dest) : - import shutil - return shutil.move(path, str(dest)) - -def mkdirs(path): - if os.path.isdir(path): - return - os.makedirs(path) - -import os + def found_unsercure_trash_dir_symlink(self, trash_dir_path): + self.logger.info("found unsecure .Trash dir (should not be a symlink): %s" + % trash_dir_path) + def found_unusable_trash_dir_not_a_dir(self, trash_dir_path): + self.logger.info("found unusable .Trash dir (should be a dir): %s" + % trash_dir_path) + def found_unsecure_trash_dir_unsticky(self, trash_dir_path): + self.logger.info("found unsecure .Trash dir (should be sticky): %s" + % trash_dir_path) + def unable_to_trash_file_in_because(self, + file_to_be_trashed, + trash_directory, error): + self.logger.info("Failed to trash %s in %s, because :%s" % + (file_to_be_trashed, trash_directory, error)) -def parent_of(path): - return os.path.dirname(path) def describe(path): """ @@ -746,68 +698,22 @@ else: return 'entry' -def volume_of(path) : - path = os.path.realpath(path) - while path != os.path.dirname(path): - if os.path.ismount(path): - break - path = os.path.dirname(path) - return path - -def write_file(path, contents): - f = open(path, 'w') - f.write(contents) - f.close() - -def do_nothing(*argv, **argvk): pass -class FileSystemReader: - def entries_if_dir_exists(self, path): - if os.path.exists(path): - for entry in os.listdir(path): - yield entry - def is_sticky_dir(self, path): - import os - return os.path.isdir(path) and has_sticky_bit(path) - def list_volumes(self): - return mount_points() - def exists(self, path): - return os.path.exists(path) - def is_symlink(self, path): - return os.path.islink(path) - def contents_of(self, path): - return file(path).read() - -def contents_of(path): # TODO remove - return FileSystemReader().contents_of(path) - -class _FileRemover: - def remove_file(self, path): - try: - return os.remove(path) - except OSError: - shutil.rmtree(path) - - def remove_file_if_exists(self,path): - if os.path.exists(path): self.remove_file(path) - -from .list_mount_points import mount_points -from datetime import datetime +from .fs import FileSystemReader, contents_of, FileRemover class ListCmd: - def __init__(self, out, err, environ, - getuid = os.getuid, - list_volumes = mount_points, + def __init__(self, out, err, environ, list_volumes, getuid, file_reader = FileSystemReader(), version = version): self.output = self.Output(out, err) self.err = self.output.err - self.file_reader = file_reader self.contents_of = file_reader.contents_of - self.trashdirs = AvailableTrashDirs(environ, - getuid, - fs = file_reader) self.version = version + top_trashdir_rules = TopTrashDirRules(file_reader) + self.trashdirs = TrashDirs(environ, getuid, + list_volumes = list_volumes, + top_trashdir_rules=top_trashdir_rules) + self.harvester = Harvester(file_reader) def run(self, *argv): parse=Parser() @@ -816,26 +722,27 @@ parse.as_default(self.list_trash) parse(argv) def list_trash(self): - self.trashdirs.list_trashdirs(self.list_contents, self.output) - def list_contents(self, trash_dir, volume_path): - self.output.set_volume_path(volume_path) - trashdir = TrashDir(self.file_reader, trash_dir, volume_path) - class Log: - def print_trashinfo(_, path): - try: - contents = self.contents_of(path) - except IOError as e : - self.output.print_read_error(e) - else: - deletion_date = parse_deletion_date(contents) or unknown_date() - try: - path = parse_path(contents) - except ParseError: - self.output.print_parse_path_error(path) - else: - self.output.print_entry(deletion_date, path) - log = Log() - trashdir.each_trashinfo(log.print_trashinfo) + self.harvester.on_volume = self.output.set_volume_path + self.harvester.on_trashinfo_found = self._print_trashinfo + + self.trashdirs.on_trashdir_skipped_because_parent_not_sticky = self.output.top_trashdir_skipped_because_parent_not_sticky + self.trashdirs.on_trashdir_skipped_because_parent_is_symlink = self.output.top_trashdir_skipped_because_parent_is_symlink + self.trashdirs.on_trash_dir_found = self.harvester._analize_trash_directory + + self.trashdirs.list_trashdirs() + def _print_trashinfo(self, path): + try: + contents = self.contents_of(path) + except IOError as e : + self.output.print_read_error(e) + else: + deletion_date = parse_deletion_date(contents) or unknown_date() + try: + path = parse_path(contents) + except ParseError: + self.output.print_parse_path_error(path) + else: + self.output.print_entry(deletion_date, path) def description(self, program_name, printer): printer.usage('Usage: %s [OPTIONS...]' % program_name) printer.summary('List trashed files') @@ -855,7 +762,6 @@ self.error(str(error)) def print_parse_path_error(self, offending_file): self.error("Parse Error: %s: Unable to parse Path." % (offending_file)) - def top_trashdir_skipped_because_parent_not_sticky(self, trashdir): self.error("TrashDir skipped because parent not sticky: %s" % trashdir) @@ -869,6 +775,7 @@ original_location = os.path.join(self.volume_path, relative_location) self.println("%s %s" %(maybe_deletion_date, original_location)) +def do_nothing(*argv, **argvk): pass class Parser: def __init__(self): self.default_action = do_nothing @@ -922,45 +829,108 @@ def as_default(self, default_action): self.default_action = default_action -# Error codes (from os on *nix, hard coded for Windows): -EX_USAGE = getattr(os, 'EX_USAGE', 64) -EX_OK = getattr(os, 'EX_OK' , 0) +class CleanableTrashcan: + def __init__(self, file_remover): + self._file_remover = file_remover + def delete_orphan(self, path_to_backup_copy): + self._file_remover.remove_file(path_to_backup_copy) + def delete_trashinfo_and_backup_copy(self, trashinfo_path): + backup_copy = self._path_of_backup_copy(trashinfo_path) + self._file_remover.remove_file_if_exists(backup_copy) + self._file_remover.remove_file(trashinfo_path) + def _path_of_backup_copy(self, path_to_trashinfo): + from os.path import dirname as parent_of, join, basename + trash_dir = parent_of(parent_of(path_to_trashinfo)) + return join(trash_dir, 'files', basename(path_to_trashinfo)[:-len('.trashinfo')]) + +class ExpiryDate: + def __init__(self, contents_of, now, trashcan): + self._contents_of = contents_of + self._now = now + self._maybe_delete = self._delete_unconditionally + self._trashcan = trashcan + def set_max_age_in_days(self, arg): + self.max_age_in_days = int(arg) + self._maybe_delete = self._delete_according_date + def delete_if_expired(self, trashinfo_path): + self._maybe_delete(trashinfo_path) + def _delete_according_date(self, trashinfo_path): + contents = self._contents_of(trashinfo_path) + ParseTrashInfo( + on_deletion_date=IfDate( + OlderThan(self.max_age_in_days, self._now), + lambda: self._delete_unconditionally(trashinfo_path) + ), + )(contents) + def _delete_unconditionally(self, trashinfo_path): + self._trashcan.delete_trashinfo_and_backup_copy(trashinfo_path) + +class TrashDirs: + def __init__(self, environ, getuid, list_volumes, top_trashdir_rules): + self.getuid = getuid + self.mount_points = list_volumes + self.top_trashdir_rules = top_trashdir_rules + self.home_trashcan = HomeTrashCan(environ) + # events + self.on_trash_dir_found = lambda trashdir, volume: None + self.on_trashdir_skipped_because_parent_not_sticky = lambda trashdir: None + self.on_trashdir_skipped_because_parent_is_symlink = lambda trashdir: None + def list_trashdirs(self): + self.emit_home_trashcan() + self._for_each_volume_trashcan() + def emit_home_trashcan(self): + def return_result_with_volume(trashcan_path): + self.on_trash_dir_found(trashcan_path, '/') + self.home_trashcan.path_to(return_result_with_volume) + def _for_each_volume_trashcan(self): + for volume in self.mount_points(): + self.emit_trashcans_for(volume) + def emit_trashcans_for(self, volume): + self.emit_trashcan_1_for(volume) + self.emit_trashcan_2_for(volume) + def emit_trashcan_1_for(self,volume): + top_trashdir_path = os.path.join(volume, '.Trash/%s' % self.getuid()) + class IsValidOutput: + def not_valid_parent_should_not_be_a_symlink(_): + self.on_trashdir_skipped_because_parent_is_symlink(top_trashdir_path) + def not_valid_parent_should_be_sticky(_): + self.on_trashdir_skipped_because_parent_not_sticky(top_trashdir_path) + def is_valid(_): + self.on_trash_dir_found(top_trashdir_path, volume) + self.top_trashdir_rules.valid_to_be_read(top_trashdir_path, IsValidOutput()) + def emit_trashcan_2_for(self, volume): + alt_top_trashdir = os.path.join(volume, '.Trash-%s' % self.getuid()) + self.on_trash_dir_found(alt_top_trashdir, volume) -class EmptyCmd(): - def __init__(self, out, err, environ, +from datetime import datetime +class EmptyCmd: + def __init__(self, out, err, environ, list_volumes, now = datetime.now, file_reader = FileSystemReader(), - list_volumes = mount_points, getuid = os.getuid, - file_remover = _FileRemover(), + file_remover = FileRemover(), version = version): self.out = out self.err = err self.file_reader = file_reader - self.contents_of = file_reader.contents_of - class Fs: #TODO remove the need of this class - def __init__(self): - self.list_volumes = list_volumes - self.is_sticky_dir = file_reader.is_sticky_dir - self.exists = file_reader.exists - self.is_symlink = file_reader.is_symlink - self.trashdirs = AvailableTrashDirs(environ, - getuid, - fs = Fs()) - - self.now = now - self.file_remover = file_remover + top_trashdir_rules = TopTrashDirRules(file_reader) + self.trashdirs = TrashDirs(environ, getuid, + list_volumes = list_volumes, + top_trashdir_rules = top_trashdir_rules) + self.harvester = Harvester(file_reader) self.version = version + self._cleaning = CleanableTrashcan(file_remover) + self._expiry_date = ExpiryDate(file_reader.contents_of, now, + self._cleaning) def run(self, *argv): - self._maybe_delete = self._delete_both self.exit_code = EX_OK parse = Parser() parse.on_help(PrintHelp(self.description, self.println)) parse.on_version(PrintVersion(self.println, self.version)) - parse.on_argument(self.set_deletion_date_criteria) + parse.on_argument(self._expiry_date.set_max_age_in_days) parse.as_default(self._empty_all_trashdirs) parse.on_invalid_option(self.report_invalid_option_usage) @@ -973,9 +943,6 @@ "{program_name}: invalid option -- '{option}'\n".format(**locals())) self.exit_code |= EX_USAGE - def set_deletion_date_criteria(self, arg): - self.max_age_in_days = int(arg) - self._maybe_delete = self._delete_according_date def description(self, program_name, printer): printer.usage('Usage: %s [days]' % program_name) printer.summary('Purge trashed files.') @@ -990,30 +957,27 @@ except ValueError: return False def _empty_all_trashdirs(self): - self.trashdirs.list_trashdirs(self._empty_trashdir) - - def _empty_trashdir(self, trash_dir, volume_path): - trashdir = TrashDir(self.file_reader, trash_dir, volume_path) - trashdir.each_trashinfo(self._maybe_delete) - trashdir.each_orphan(self.remove_file) - - def _delete_according_date(self, trashinfo_path): - contents = self.file_reader.contents_of(trashinfo_path) - ParseTrashInfo( - on_deletion_date=IfDate( - OlderThan(self.max_age_in_days, self.now), - lambda: self._delete_both(trashinfo_path) - ), - )(contents) - def remove_file(self, path): - self.file_remover.remove_file(path) - def _delete_both(self, trashinfo_path): - backup_copy = path_of_backup_copy(trashinfo_path) - self.file_remover.remove_file_if_exists(backup_copy) - self.file_remover.remove_file(trashinfo_path) + self.harvester.on_trashinfo_found = self._expiry_date.delete_if_expired + self.harvester.on_orphan_found = self._cleaning.delete_orphan + self.trashdirs.on_trash_dir_found = self.harvester._analize_trash_directory + self.trashdirs.list_trashdirs() def println(self, line): self.out.write(line + '\n') +class Harvester: + def __init__(self, file_reader): + self.file_reader = file_reader + self.trashdir = TrashDir(self.file_reader) + + self.on_orphan_found = do_nothing + self.on_trashinfo_found = do_nothing + self.on_volume = do_nothing + def _analize_trash_directory(self, trash_dir_path, volume_path): + self.on_volume(volume_path) + self.trashdir.open(trash_dir_path, volume_path) + self.trashdir.each_trashinfo(self.on_trashinfo_found) + self.trashdir.each_orphan(self.on_orphan_found) + class IfDate: def __init__(self, date_criteria, then): self.date_criteria = date_criteria @@ -1028,23 +992,6 @@ def __call__(self, deletion_date): return deletion_date < self.limit_date -def path_of_backup_copy(path_to_trashinfo): - from os.path import dirname as parent_of, join, basename - trash_dir = parent_of(parent_of(path_to_trashinfo)) - return join(trash_dir, 'files', basename(path_to_trashinfo)[:-len('.trashinfo')]) - - -class EachTrashInfo: - def __init__(self, list_dir, on_trashinfo): - self.list_dir = list_dir - self.on_trashinfo = on_trashinfo - def trashdir(self, path): - import os - info_dir = os.path.join(path, 'info') - for entry in self.list_dir(path): - if entry.endswith('.trashinfo'): - self.on_trashinfo(os.path.join(info_dir, entry)) - class PrintHelp: def __init__(self, description, println): class Printer: @@ -1076,47 +1023,39 @@ def __call__(self, program_name): self.println("%s %s" % (program_name, self.version)) -class AvailableTrashDirs: - def __init__(self, environ, getuid, fs=None): - self.environ = environ - self.getuid = getuid - self.fs = fs - self.list_volumes = fs.list_volumes - self.is_sticky_dir = fs.is_sticky_dir - self.exists = fs.exists - def do_nothing(trash_dir, volume): pass - class NullLog: - def top_trashdir_skipped_because_parent_not_sticky(self, trashdir): pass - def top_trashdir_skipped_because_parent_is_symlink(self, trashdir): pass - def list_trashdirs(self, out = do_nothing, error_log = NullLog()): - self._for_home_trashcan(out) - self._for_each_volume_trashcan(out, error_log) - def _for_home_trashcan(self, out): - def return_result_with_volume(trashcan_path): - out(trashcan_path, '/') - home_trashcan_if_possible(self.environ, return_result_with_volume) - def _for_each_volume_trashcan(self, action, error_log): - from os.path import join - for volume in self.list_volumes(): - parent_trashdir = join(volume, '.Trash') - top_trashdir = join(parent_trashdir, str(self.getuid())) - alt_top_trashdir = join(volume, '.Trash-%s' % self.getuid()) - if self.exists(top_trashdir): - if self.is_sticky_dir(parent_trashdir): - if not self.fs.is_symlink(parent_trashdir): - action(top_trashdir, volume) - else: - error_log.top_trashdir_skipped_because_parent_is_symlink(top_trashdir) - else: - error_log.top_trashdir_skipped_because_parent_not_sticky(top_trashdir) +class TopTrashDirRules: + def __init__(self, fs): + self.fs = fs + + def valid_to_be_read(self, path, output): + parent_trashdir = os.path.dirname(path) + if not self.fs.exists(path): + return + if not self.fs.is_sticky_dir(parent_trashdir): + output.not_valid_parent_should_be_sticky() + return + if self.fs.is_symlink(parent_trashdir): + output.not_valid_parent_should_not_be_a_symlink() + return + else: + output.is_valid() - action(alt_top_trashdir, volume) + def valid_to_be_written(self, trash_dir_path, output): + parent = os.path.dirname(trash_dir_path) + if not self.fs.isdir(parent): + output.not_valid_should_be_a_dir() + return + if self.fs.islink(parent): + output.not_valid_parent_should_not_be_a_symlink() + return + if not self.fs.has_sticky_bit(parent): + output.not_valid_parent_should_be_sticky() + return + output.is_valid() -def home_trashcan_if_possible(environ, out): - if 'XDG_DATA_HOME' in environ: - out('%(XDG_DATA_HOME)s/Trash' % environ) - elif 'HOME' in environ: - out('%(HOME)s/.local/share/Trash' % environ) +class TopDirWithoutStickyBit(IOError): pass +class TopDirNotPresent(IOError): pass +class TopDirIsSymLink(IOError): pass class Dir: def __init__(self, path, entries_if_dir_exists): @@ -1128,9 +1067,10 @@ return os.path.join(self.path, entry) class TrashDir: - def __init__(self, file_reader, path, volume_path): - self.trash_dir_path = path + def __init__(self, file_reader): self.file_reader = file_reader + def open(self, path, volume_path): + self.trash_dir_path = path self.volume_path = volume_path self.files_dir = Dir(self._files_dir(), self.file_reader.entries_if_dir_exists) @@ -1171,10 +1111,12 @@ return os.path.join(self._info_dir(), file_entry + '.trashinfo') def _files_dir(self): return os.path.join(self.trash_dir_path, 'files') - def _trashinfo_entries(self): + def _trashinfo_entries(self, on_non_trashinfo=do_nothing): for entry in self._entries_if_dir_exists(self._info_dir()): if entry.endswith('.trashinfo'): yield entry + else: + on_non_trashinfo() class ParseError(ValueError): pass @@ -1251,8 +1193,3 @@ return urllib.unquote(line[len('Path='):]) raise ParseError('Unable to parse Path') -def has_sticky_bit(path): # TODO move to FileSystemReader - import os - import stat - return (os.stat(path).st_mode & stat.S_ISVTX) == stat.S_ISVTX - diff -Nru trash-cli-0.12.7/unit_tests/test_available_trash_dirs.py trash-cli-0.12.9.14/unit_tests/test_available_trash_dirs.py --- trash-cli-0.12.7/unit_tests/test_available_trash_dirs.py 2012-06-22 14:15:56.000000000 +0000 +++ trash-cli-0.12.9.14/unit_tests/test_available_trash_dirs.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,125 +0,0 @@ -# Copyright (C) 2011 Andrea Francia Trivolzio(PV) Italy - -from trashcli.trash import AvailableTrashDirs -from nose.tools import istest, assert_in, assert_not_in - -@istest -class Describe_AvailableInfoDirs_on_volume_trashcans: - @istest - def the_method_2_is_always_in(self): - self.having_uid(123) - self.having_volumes('/usb') - - assert_in('/usb/.Trash-123', self.trashdirs()) - - @istest - def the_method_1_is_in_if_it_is_a_sticky_dir(self): - self.having_uid(123) - self.having_volumes('/usb') - self.having_sticky_Trash_dir() - - assert_in('/usb/.Trash/123', self.trashdirs()) - - @istest - def the_method_1_is_not_considered_if_not_sticky_dir(self): - self.having_uid(123) - self.having_volumes('/usb') - self.having_non_sticky_Trash_dir() - - assert_not_in('/usb/.Trash/123', self.trashdirs()) - - @istest - def should_return_home_trashcan_when_XDG_DATA_HOME_is_defined(self): - self.having_XDG_DATA_HOME('~/.local/share') - - assert_in('~/.local/share/Trash', self.trashdirs()) - - def trashdirs(self): - result = collector() - class FileReader: - def list_volumes(_): - return self.volumes - def is_sticky_dir(_, path): - return self.Trash_dir_is_sticky - def exists(_, path): - return True - def is_symlink(_, path): - return False - AvailableTrashDirs( - environ=self.environ, - getuid=lambda:self.uid, - fs = FileReader(), - ).list_trashdirs(result) - return result.trash_dirs - - def setUp(self): - self.uid = -1 - self.volumes = () - self.Trash_dir_is_sticky = not_important_for_now() - self.environ = {} - def having_uid(self, uid): self.uid = uid - def having_volumes(self, *volumes): self.volumes = volumes - def having_sticky_Trash_dir(self): self.Trash_dir_is_sticky = True - def having_non_sticky_Trash_dir(self): self.Trash_dir_is_sticky = False - def having_XDG_DATA_HOME(self, XDG_DATA_HOME): - self.environ['XDG_DATA_HOME'] = XDG_DATA_HOME - -def not_important_for_now(): None - -class collector: - def __init__(self): - self.trash_dirs = [] - def __call__(self, trash_dir, volume): - self.trash_dirs.append(trash_dir) - -from nose.tools import assert_equals -from mock import MagicMock -@istest -class Describe_AvailableTrashDirs_when_parent_is_unsticky: - def setUp(self): - self.error_log = MagicMock() - self.fs = MagicMock() - self.dirs = AvailableTrashDirs(environ = {}, getuid = lambda:123, - fs = self.fs) - self.fs.list_volumes.return_value = ['/topdir'] - self.fs.is_sticky_dir.side_effect = ( - lambda path: {'/topdir/.Trash':False}[path]) - - def test_it_should_report_skipped_dir_non_sticky(self): - self.fs.exists.side_effect = ( - lambda path: {'/topdir/.Trash/123':True}[path]) - - self.dirs.list_trashdirs(error_log = self.error_log) - - (self.error_log.top_trashdir_skipped_because_parent_not_sticky. - assert_called_with('/topdir/.Trash/123')) - - def test_it_shouldnot_care_about_non_existent(self): - self.fs.exists.side_effect = ( - lambda path: {'/topdir/.Trash/123':False}[path]) - - self.dirs.list_trashdirs(error_log = self.error_log) - - assert_equals([], self.error_log. - top_trashdir_skipped_because_parent_not_sticky.mock_calls) - -@istest -class Describe_AvailableTrashDirs_when_parent_is_symlink: - def setUp(self): - self.error_log = MagicMock() - self.fs = MagicMock() - self.dirs = AvailableTrashDirs(environ = {}, getuid = lambda:123, - fs = self.fs) - self.fs.list_volumes.return_value = ['/topdir'] - self.fs.exists.side_effect = (lambda path: {'/topdir/.Trash/123':True}[path]) - - - def test_it_should_skip_symlink(self): - self.fs.is_sticky_dir.return_value = True - self.fs.is_symlink.return_value = True - - self.dirs.list_trashdirs(error_log = self.error_log) - - (self.error_log.top_trashdir_skipped_because_parent_is_symlink. - assert_called_with('/topdir/.Trash/123')) - diff -Nru trash-cli-0.12.7/unit_tests/test_characterization.py trash-cli-0.12.9.14/unit_tests/test_characterization.py --- trash-cli-0.12.7/unit_tests/test_characterization.py 2012-06-22 14:15:56.000000000 +0000 +++ trash-cli-0.12.9.14/unit_tests/test_characterization.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,15 +0,0 @@ -# Copyright (C) 2011 Andrea Francia Trivolzio(PV) Italy - -from nose.tools import istest - -from trashcli.trash import GlobalTrashCan - -@istest -class GlobalTrashCanTest: - - def test_home_dir_path(self): - a=GlobalTrashCan(environ = {'XDG_DATA_HOME': './XDG_DATA_HOME'}) - home_trash = a._home_trash_dir_path() - - assert './XDG_DATA_HOME/Trash' == home_trash - diff -Nru trash-cli-0.12.7/unit_tests/test_fake_fstab.py trash-cli-0.12.9.14/unit_tests/test_fake_fstab.py --- trash-cli-0.12.7/unit_tests/test_fake_fstab.py 1970-01-01 00:00:00.000000000 +0000 +++ trash-cli-0.12.9.14/unit_tests/test_fake_fstab.py 2014-08-27 03:32:08.000000000 +0000 @@ -0,0 +1,39 @@ +from trashcli.fstab import FakeFstab + +from nose.tools import assert_equals +from nose.tools import istest +from nose.tools import assert_items_equal + +class TestFakeFstab: + def setUp(self): + self.fstab = FakeFstab() + + @istest + def on_default(self): + self.assert_mount_points_are('/') + + @istest + def it_should_accept_fake_mount_points(self): + self.fstab.add_mount('/fake') + + self.assert_mount_points_are('/', '/fake') + + @istest + def root_is_not_duplicated(self): + self.fstab.add_mount('/') + + self.assert_mount_points_are('/') + + @istest + def test_something(self): + fstab = FakeFstab() + fstab.add_mount('/fake') + assert_equals('/fake', fstab.volume_of('/fake/foo')) + + def assert_mount_points_are(self, *expected_mounts): + expected_mounts = list(expected_mounts) + actual_mounts = list(self.fstab.mount_points()) + assert_items_equal(expected_mounts, list(self.fstab.mount_points()), + 'Expected: %s\n' + 'Found: %s\n' % (expected_mounts, actual_mounts)) + diff -Nru trash-cli-0.12.7/unit_tests/test_fake_ismount.py trash-cli-0.12.9.14/unit_tests/test_fake_ismount.py --- trash-cli-0.12.7/unit_tests/test_fake_ismount.py 1970-01-01 00:00:00.000000000 +0000 +++ trash-cli-0.12.9.14/unit_tests/test_fake_ismount.py 2014-08-27 03:32:08.000000000 +0000 @@ -0,0 +1,60 @@ +from trashcli.fstab import FakeIsMount + +from nose.tools import istest +from nose.tools import assert_false +from nose.tools import assert_true + +@istest +class OnDefault: + def setUp(self): + self.ismount = FakeIsMount() + + @istest + def by_default_root_is_mount(self): + + assert_true(self.ismount('/')) + + @istest + def while_by_default_any_other_is_not_a_mount_point(self): + + assert_false(self.ismount('/any/other')) + +@istest +class WhenOneFakeVolumeIsDefined: + def setUp(self): + self.ismount = FakeIsMount() + self.ismount.add_mount('/fake-vol') + + @istest + def accept_fake_mount_point(self): + + assert_true(self.ismount('/fake-vol')) + + @istest + def other_still_are_not_mounts(self): + + assert_false(self.ismount('/other')) + + @istest + def dont_get_confused_by_traling_slash(self): + + assert_true(self.ismount('/fake-vol/')) + +@istest +class WhenMultipleFakesMountPoints: + def setUp(self): + self.ismount = FakeIsMount() + self.ismount.add_mount('/vol1') + self.ismount.add_mount('/vol2') + + @istest + def recognize_both(self): + assert_true(self.ismount('/vol1')) + assert_true(self.ismount('/vol2')) + assert_false(self.ismount('/other')) + +@istest +def should_handle_relative_volumes(): + ismount = FakeIsMount() + ismount.add_mount('fake-vol') + assert_true(ismount('fake-vol')) diff -Nru trash-cli-0.12.7/unit_tests/test_global_trashcan.py trash-cli-0.12.9.14/unit_tests/test_global_trashcan.py --- trash-cli-0.12.7/unit_tests/test_global_trashcan.py 1970-01-01 00:00:00.000000000 +0000 +++ trash-cli-0.12.9.14/unit_tests/test_global_trashcan.py 2014-08-27 03:32:08.000000000 +0000 @@ -0,0 +1,21 @@ +from mock import Mock +from nose.tools import istest + +from trashcli.trash import GlobalTrashCan + +class TestGlobalTrashCan: + def setUp(self): + self.reporter = Mock() + + self.trashcan = GlobalTrashCan( + home_trashcan = Mock(), + reporter = self.reporter, + getuid = lambda:123, + now = None) + + @istest + def should_report_when_trash_fail(self): + + self.trashcan.trash('non-existent') + self.reporter.unable_to_trash_file.assert_called_with('non-existent') + diff -Nru trash-cli-0.12.7/unit_tests/test_list_all_trashinfo_contents.py trash-cli-0.12.9.14/unit_tests/test_list_all_trashinfo_contents.py --- trash-cli-0.12.7/unit_tests/test_list_all_trashinfo_contents.py 1970-01-01 00:00:00.000000000 +0000 +++ trash-cli-0.12.9.14/unit_tests/test_list_all_trashinfo_contents.py 2014-08-27 03:32:08.000000000 +0000 @@ -0,0 +1,46 @@ +from mock import Mock, call +from nose.tools import assert_equals, assert_items_equal + +class TestListing: + def setUp(self): + self.trashdir = Mock() + self.trashinfo_reader = Mock() + self.listing = Listing(self.trashdir, self.trashinfo_reader) + + def test_it_should_read_all_trashinfo_from_home_dir(self): + + self.listing.read_home_trashdir('/path/to/trash_dir') + + self.trashdir.list_trashinfos.assert_called_with( + trashdir='/path/to/trash_dir', + list_to=self.trashinfo_reader) + +class TestTrashDirReader: + def test_should_list_all_trashinfo_found(self): + def files(path): yield 'file1'; yield 'file2' + os_listdir = Mock(side_effect=files) + trashdir = TrashDirReader(os_listdir) + out = Mock() + + trashdir.list_trashinfos(trashdir='/path', list_to=out) + + assert_items_equal([call(trashinfo='/path/file1'), + call(trashinfo='/path/file2')], out.mock_calls) + + +class TrashDirReader: + def __init__(self, os_listdir): + self.os_listdir = os_listdir + def list_trashinfos(self, trashdir, list_to): + import os + for entry in self.os_listdir(trashdir): + full_path = os.path.join(trashdir, entry) + list_to(trashinfo=full_path) + +class Listing: + def __init__(self, trashdir, trashinfo_reader): + self.trashdir = trashdir + self.trashinfo_reader = trashinfo_reader + def read_home_trashdir(self, path): + self.trashdir.list_trashinfos(trashdir=path, + list_to=self.trashinfo_reader) diff -Nru trash-cli-0.12.7/unit_tests/test_make_script.py trash-cli-0.12.9.14/unit_tests/test_make_script.py --- trash-cli-0.12.7/unit_tests/test_make_script.py 1970-01-01 00:00:00.000000000 +0000 +++ trash-cli-0.12.9.14/unit_tests/test_make_script.py 2014-08-27 03:32:08.000000000 +0000 @@ -0,0 +1,59 @@ +from textwrap import dedent +from nose.tools import assert_equals +import mock +from mock import Mock +from setup import BinDir + +class TestMakeScript: + def setUp(self): + self.make_file_executable = Mock() + self.write_file = Mock() + self.make_dir = Mock() + def capture(name, contents): + self.name = name + self.contents = contents + self.write_file.side_effect = capture + + bindir = BinDir( + make_file_executable = self.make_file_executable, + write_file = self.write_file, + make_dir = self.make_dir) + bindir.add_script('trash-put', 'trashcli.cmds', 'put') + + def test_should_make_bin_dir(self): + self.make_dir.assert_called_with('bin') + + def test_should_set_executable_permission(self): + self.make_file_executable.assert_called_with('bin/trash-put') + + def test_should_write_the_script(self): + self.write_file.assert_called_with( 'bin/trash-put', mock.ANY) + + def test_the_script_should_call_the_right_function_from_the_right_module(self): + args, kwargs = self.write_file.call_args + (_, contents) = args + expected = dedent("""\ + #!/usr/bin/env python + from __future__ import absolute_import + import sys + from trashcli.cmds import put as main + sys.exit(main()) + """) + assert_equals(expected, contents, + "Expected:\n---\n%s---\n" + "Actual :\n---\n%s---\n" + % (expected, contents)) + +class TestListOfCreatedScripts: + def setUp(self): + self.bindir = BinDir( + make_file_executable = Mock(), + write_file = Mock(), + make_dir = Mock()) + + def test_is_empty_on_start_up(self): + assert_equals(self.bindir.created_scripts, []) + + def test_collect_added_script(self): + self.bindir.add_script('foo-command', 'foo-module', 'main') + assert_equals(self.bindir.created_scripts, ['bin/foo-command']) diff -Nru trash-cli-0.12.7/unit_tests/test_method1_security_check.py trash-cli-0.12.9.14/unit_tests/test_method1_security_check.py --- trash-cli-0.12.7/unit_tests/test_method1_security_check.py 1970-01-01 00:00:00.000000000 +0000 +++ trash-cli-0.12.9.14/unit_tests/test_method1_security_check.py 2014-08-27 03:32:08.000000000 +0000 @@ -0,0 +1,44 @@ +from mock import Mock + +from integration_tests.files import require_empty_dir +from trashcli.trash import TopTrashDirRules + +class TestMethod1VolumeTrashDirectory: + def setUp(self): + require_empty_dir('sandbox') + self.fs = Mock() + self.fs.isdir.return_value = True + self.fs.islink.return_value = False + self.fs.has_sticky_bit.return_value = True + self.checker = TopTrashDirRules(self.fs) + self.out = Mock() + + def test_check_when_no_sticky_bit(self): + self.fs.has_sticky_bit.return_value = False + + self.valid_to_be_written() + + self.out.not_valid_parent_should_be_sticky.assert_called_with() + + def test_check_when_no_dir(self): + self.fs.isdir.return_value = False + + self.valid_to_be_written() + + self.out.not_valid_should_be_a_dir.assert_called_with() + + def test_check_when_is_symlink(self): + self.fs.islink.return_value = True + + self.valid_to_be_written() + + self.out.not_valid_parent_should_not_be_a_symlink.assert_called_with() + + def test_check_pass(self): + + self.valid_to_be_written() + + self.out.is_valid() + + def valid_to_be_written(self): + self.checker.valid_to_be_written('sandbox/trash-dir/123', self.out) diff -Nru trash-cli-0.12.7/unit_tests/test_restore_cmd.py trash-cli-0.12.9.14/unit_tests/test_restore_cmd.py --- trash-cli-0.12.7/unit_tests/test_restore_cmd.py 1970-01-01 00:00:00.000000000 +0000 +++ trash-cli-0.12.9.14/unit_tests/test_restore_cmd.py 2014-08-27 03:32:08.000000000 +0000 @@ -0,0 +1,18 @@ +from trashcli.trash import RestoreCmd +from nose.tools import assert_equals +from StringIO import StringIO + +class TestTrashRestoreCmd: + def test_should_print_version(self): + stdout = StringIO() + cmd = RestoreCmd(stdout=stdout, + stderr=None, + environ=None, + exit = None, + input=None, + version = '1.2.3') + + cmd.run(['trash-restore', '--version']) + + assert_equals('trash-restore 1.2.3\n', stdout.getvalue()) + diff -Nru trash-cli-0.12.7/unit_tests/test_storing_paths.py trash-cli-0.12.9.14/unit_tests/test_storing_paths.py --- trash-cli-0.12.7/unit_tests/test_storing_paths.py 1970-01-01 00:00:00.000000000 +0000 +++ trash-cli-0.12.9.14/unit_tests/test_storing_paths.py 2014-08-27 03:32:08.000000000 +0000 @@ -0,0 +1,27 @@ +from trashcli.trash import TrashDirectory +from nose.tools import assert_equals + +class TestTrashInfoPath: + def test_for_absolute_paths(self): + self.dir = TrashDirectory('/volume/.Trash', '/volume') + self.dir.store_absolute_paths() + + self.assert_path_for_trashinfo_is('/file' , '/file') + self.assert_path_for_trashinfo_is('/file' , '/dir/../file') + self.assert_path_for_trashinfo_is('/outside/file' , '/outside/file') + self.assert_path_for_trashinfo_is('/volume/file' , '/volume/file') + self.assert_path_for_trashinfo_is('/volume/dir/file' , '/volume/dir/file') + + def test_for_relative_paths(self): + self.dir = TrashDirectory('/volume/.Trash', '/volume') + self.dir.store_relative_paths() + + self.assert_path_for_trashinfo_is('/file' , '/file') + self.assert_path_for_trashinfo_is('/file' , '/dir/../file') + self.assert_path_for_trashinfo_is('/outside/file' , '/outside/file') + self.assert_path_for_trashinfo_is('file' , '/volume/file') + self.assert_path_for_trashinfo_is('dir/file' , '/volume/dir/file') + + def assert_path_for_trashinfo_is(self, expected_value, file_to_be_trashed): + result = self.dir.path_for_trash_info.for_file(file_to_be_trashed) + assert_equals(expected_value, result) diff -Nru trash-cli-0.12.7/unit_tests/test_trash_dir_name.py trash-cli-0.12.9.14/unit_tests/test_trash_dir_name.py --- trash-cli-0.12.7/unit_tests/test_trash_dir_name.py 1970-01-01 00:00:00.000000000 +0000 +++ trash-cli-0.12.9.14/unit_tests/test_trash_dir_name.py 2014-08-27 03:32:08.000000000 +0000 @@ -0,0 +1,37 @@ +from unittest import TestCase +from nose.tools import assert_equals +import os +from trashcli.trash import TrashDirectory + +class TestTrashDirectory(TestCase) : + + def test_str_uses_tilde(self): + os.environ['HOME']='/home/user' + self.trash_dir = "/home/user/.local/share/Trash" + self.assert_name_is('~/.local/share/Trash') + + def test_str_dont_uses_tilde(self): + os.environ['HOME']='/home/user' + self.trash_dir = "/not-in-home/Trash" + self.assert_name_is('/not-in-home/Trash') + + def test_str_uses_tilde_with_trailing_slashes(self): + os.environ['HOME']='/home/user/' + self.trash_dir = "/home/user/.local/share/Trash" + self.assert_name_is('~/.local/share/Trash') + + def test_str_uses_tilde_with_trailing_slash(self): + os.environ['HOME']='/home/user////' + self.trash_dir = "/home/user/.local/share/Trash" + self.assert_name_is('~/.local/share/Trash') + + def test_str_with_empty_home(self): + os.environ['HOME']='' + self.trash_dir = "/foo/Trash" + self.assert_name_is('/foo/Trash') + + def assert_name_is(self, expected_name): + trash_dir = TrashDirectory(self.trash_dir, '/') + assert_equals(expected_name, trash_dir.name()) + + diff -Nru trash-cli-0.12.7/unit_tests/test_trashdir.py trash-cli-0.12.9.14/unit_tests/test_trashdir.py --- trash-cli-0.12.7/unit_tests/test_trashdir.py 2012-06-22 14:15:56.000000000 +0000 +++ trash-cli-0.12.9.14/unit_tests/test_trashdir.py 2014-08-27 03:32:08.000000000 +0000 @@ -1,7 +1,13 @@ # Copyright (C) 2011 Andrea Francia Trivolzio(PV) Italy -from nose.tools import assert_equals, istest +from nose.tools import assert_equals from trashcli.trash import TrashDir +from trashcli.trash import TrashDirectory + +class TestTrashDir: + def test_path(self): + trash_dir = TrashDirectory('/Trash-501', '/') + assert_equals('/Trash-501', trash_dir.path) class TestTrashDir_finding_orphans: def test(self): @@ -21,41 +27,12 @@ def setUp(self): self.orphan_found=[] self.fs = FakeFileSystem() - self.trashdir=TrashDir(self.fs, '/', None) + self.trashdir=TrashDir(self.fs) + self.trashdir.open('/', None) def find_orphan(self): self.trashdir.each_orphan(self.orphan_found.append) -from trashcli.trash import EachTrashInfo - -@istest -class describe_EachTrashInfo: - @istest - def it_should_list_trashinfos(self): - self.having_directory('~/.local/share/Trash', - containing = ['foo.trashinfo']) - self.trashinfos_found(in_trashdir='~/.local/share/Trash', - should_be=['~/.local/share/Trash/info/foo.trashinfo']) - - @istest - def it_should_not_list_other_files(self): - self.having_directory('~/.local/share/Trash', - containing = ['foo.non-a-trashinfo']) - self.trashinfos_found(in_trashdir='~/.local/share/Trash', - should_be=[]) - - def having_directory(self, path, containing): - self.list_dir = lambda path: { - path : containing - }[path] - def trashinfos_found(self, in_trashdir, should_be): - result = [] - - finder = EachTrashInfo(self.list_dir, result.append) - finder.trashdir(in_trashdir) - - assert result == list(should_be) - class FakeFileSystem: def __init__(self): self.files={} diff -Nru trash-cli-0.12.7/unit_tests/test_trashdirs_how_to_list_them.py trash-cli-0.12.9.14/unit_tests/test_trashdirs_how_to_list_them.py --- trash-cli-0.12.7/unit_tests/test_trashdirs_how_to_list_them.py 1970-01-01 00:00:00.000000000 +0000 +++ trash-cli-0.12.9.14/unit_tests/test_trashdirs_how_to_list_them.py 2014-08-27 03:32:08.000000000 +0000 @@ -0,0 +1,22 @@ +from trashcli.trash import TrashDirs +from mock import Mock, call +from nose.tools import assert_equals + +class TestListTrashinfo: + def test_howto_list_trashdirs(self): + out = Mock() + environ = {'HOME':'/home/user'} + trashdirs = TrashDirs( + environ = environ, + getuid = lambda:123, + list_volumes = lambda:['/vol', '/vol2'], + top_trashdir_rules = Mock(), + ) + trashdirs.on_trash_dir_found = out + trashdirs.list_trashdirs() + + assert_equals([call('/home/user/.local/share/Trash', '/'), + call('/vol/.Trash-123', '/vol'), + call('/vol2/.Trash-123', '/vol2')], + out.mock_calls) + diff -Nru trash-cli-0.12.7/unit_tests/test_trash_dirs_listing.py trash-cli-0.12.9.14/unit_tests/test_trash_dirs_listing.py --- trash-cli-0.12.7/unit_tests/test_trash_dirs_listing.py 1970-01-01 00:00:00.000000000 +0000 +++ trash-cli-0.12.9.14/unit_tests/test_trash_dirs_listing.py 2014-08-27 03:32:08.000000000 +0000 @@ -0,0 +1,129 @@ +# Copyright (C) 2011 Andrea Francia Trivolzio(PV) Italy + +from trashcli.trash import TrashDirs +from nose.tools import istest, assert_in, assert_not_in +from mock import Mock + +@istest +class TestTrashDirs_listing: + @istest + def the_method_2_is_always_in(self): + self.uid = 123 + self.volumes = ['/usb'] + + assert_in('/usb/.Trash-123', self.trashdirs()) + + @istest + def the_method_1_is_in_if_it_is_a_sticky_dir(self): + self.uid = 123 + self.volumes = ['/usb'] + self.having_sticky_Trash_dir() + + assert_in('/usb/.Trash/123', self.trashdirs()) + + @istest + def the_method_1_is_not_considered_if_not_sticky_dir(self): + self.uid = 123 + self.volumes = ['/usb'] + self.having_non_sticky_Trash_dir() + + assert_not_in('/usb/.Trash/123', self.trashdirs()) + + @istest + def should_return_home_trashcan_when_XDG_DATA_HOME_is_defined(self): + self.environ['XDG_DATA_HOME'] = '~/.local/share' + + assert_in('~/.local/share/Trash', self.trashdirs()) + + def trashdirs(self): + result = [] + def append(trash_dir, volume): + result.append(trash_dir) + class FileReader: + def is_sticky_dir(_, path): + return self.Trash_dir_is_sticky + def exists(_, path): + return True + def is_symlink(_, path): + return False + class FakeTopTrashDirRules: + def valid_to_be_read(_, path, out): + if self.Trash_dir_is_sticky: + out.is_valid() + else: + out.not_valid_parent_should_be_sticky() + trash_dirs = TrashDirs( + environ=self.environ, + getuid=lambda:self.uid, + top_trashdir_rules = FakeTopTrashDirRules(), + list_volumes = lambda: self.volumes, + ) + trash_dirs.on_trash_dir_found = append + trash_dirs.list_trashdirs() + return result + + def setUp(self): + self.uid = -1 + self.volumes = () + self.Trash_dir_is_sticky = not_important_for_now() + self.environ = {} + def having_sticky_Trash_dir(self): self.Trash_dir_is_sticky = True + def having_non_sticky_Trash_dir(self): self.Trash_dir_is_sticky = False + +def not_important_for_now(): None + +from nose.tools import assert_equals +from mock import MagicMock +from trashcli.trash import TopTrashDirRules +@istest +class Describe_AvailableTrashDirs_when_parent_is_unsticky: + def setUp(self): + self.fs = MagicMock() + self.dirs = TrashDirs(environ = {}, + getuid = lambda:123, + top_trashdir_rules = TopTrashDirRules(self.fs), + list_volumes = lambda: ['/topdir'], + ) + self.dirs.on_trashdir_skipped_because_parent_not_sticky = Mock() + self.dirs.on_trashdir_skipped_because_parent_is_symlink = Mock() + self.fs.is_sticky_dir.side_effect = ( + lambda path: {'/topdir/.Trash':False}[path]) + + def test_it_should_report_skipped_dir_non_sticky(self): + self.fs.exists.side_effect = ( + lambda path: {'/topdir/.Trash/123':True}[path]) + + self.dirs.list_trashdirs() + + (self.dirs.on_trashdir_skipped_because_parent_not_sticky. + assert_called_with('/topdir/.Trash/123')) + + def test_it_shouldnot_care_about_non_existent(self): + self.fs.exists.side_effect = ( + lambda path: {'/topdir/.Trash/123':False}[path]) + + self.dirs.list_trashdirs() + + assert_equals([], self.dirs.on_trashdir_skipped_because_parent_not_sticky.mock_calls) + +@istest +class Describe_AvailableTrashDirs_when_parent_is_symlink: + def setUp(self): + self.fs = MagicMock() + self.dirs = TrashDirs(environ = {}, + getuid = lambda:123, + top_trashdir_rules = TopTrashDirRules(self.fs), + list_volumes = lambda: ['/topdir']) + self.fs.exists.side_effect = (lambda path: {'/topdir/.Trash/123':True}[path]) + self.symlink_error = Mock() + self.dirs.on_trashdir_skipped_because_parent_is_symlink = self.symlink_error + + + def test_it_should_skip_symlink(self): + self.fs.is_sticky_dir.return_value = True + self.fs.is_symlink.return_value = True + + self.dirs.list_trashdirs() + + self.symlink_error.assert_called_with('/topdir/.Trash/123') + diff -Nru trash-cli-0.12.7/unit_tests/test_trash_new_tests.py trash-cli-0.12.9.14/unit_tests/test_trash_new_tests.py --- trash-cli-0.12.7/unit_tests/test_trash_new_tests.py 2012-06-22 14:15:56.000000000 +0000 +++ trash-cli-0.12.9.14/unit_tests/test_trash_new_tests.py 2014-08-27 03:32:08.000000000 +0000 @@ -3,23 +3,27 @@ from nose.tools import (assert_true, assert_equals) from unittest import TestCase from trashcli.trash import GlobalTrashCan +from mock import Mock class TestGlobalTrashCan(TestCase): def test_the_attempt_of_deleting_a_dot_directory_should_signaled_as_error(self): - argument="." + argument="." - class StubReporter: - def __init__(self): - self.has_been_called=False + class StubReporter: + def __init__(self): + self.has_been_called=False + + def unable_to_trash_dot_entries(self,file): + self.has_been_called=True + assert_equals(file, argument) + + reporter=StubReporter() + trashcan = GlobalTrashCan( + reporter=reporter, + home_trashcan = Mock(), + ) - def unable_to_trash_dot_entries(self,file): - self.has_been_called=True - assert_equals(file, argument) - - reporter=StubReporter() - trashcan = GlobalTrashCan(reporter=reporter) - - trashcan.trash('.') - assert_true(reporter.has_been_called) + trashcan.trash('.') + assert_true(reporter.has_been_called) diff -Nru trash-cli-0.12.7/unit_tests/test_trash_put.py trash-cli-0.12.9.14/unit_tests/test_trash_put.py --- trash-cli-0.12.7/unit_tests/test_trash_put.py 2012-06-22 14:15:56.000000000 +0000 +++ trash-cli-0.12.9.14/unit_tests/test_trash_put.py 2014-08-27 03:32:08.000000000 +0000 @@ -1,12 +1,42 @@ # Copyright (C) 2011 Andrea Francia Trivolzio(PV) Italy +from trashcli.trash import TrashPutCmd + from nose.tools import istest from StringIO import StringIO -from trashcli.trash import TrashPutCmd from integration_tests.assert_equals_with_unidiff import assert_equals_with_unidiff +class TrashPutTest: + def run(self, *arg): + self.stderr = StringIO() + self.stdout = StringIO() + args = ['trash-put'] + list(arg) + cmd = TrashPutCmd(self.stdout, self.stderr) + self._collect_exit_code(lambda:cmd.run(args)) + + def _collect_exit_code(self, main_function): + self.actual_exit_code = 0 + try: + result=main_function() + if result is not None: + self.actual_exit_code=result + except SystemExit, e: + self.actual_exit_code = e.code + + def stderr_should_be(self, expected_err): + assert_equals_with_unidiff(expected_err, self._actual_stderr()) + + def stdout_should_be(self, expected_out): + assert_equals_with_unidiff(expected_out, self._actual_stdout()) + + def _actual_stderr(self): + return self.stderr.getvalue() + + def _actual_stdout(self): + return self.stdout.getvalue() + @istest -class describe_TrashPutCmd: +class describe_TrashPutCmd(TrashPutTest): @istest def on_help_option_print_help(self): @@ -54,31 +84,4 @@ 'trash-put: error: Please specify the files to trash.\n') self.stdout_should_be('') - def run(self, *arg): - self.stderr=StringIO() - self.stdout=StringIO() - args=['trash-put'] + list(arg) - cmd=TrashPutCmd(self.stdout, self.stderr) - self.detect_and_save_exit_code(lambda:cmd.run(args)) - - def detect_and_save_exit_code(self, main_function): - self.actual_exit_code=0 - try: - result=main_function() - if result is not None: - self.actual_exit_code=result - except SystemExit, e: - self.actual_exit_code=e.code - - def stderr_should_be(self, expected_err): - assert_equals_with_unidiff(expected_err, self.actual_stderr()) - - def stdout_should_be(self, expected_out): - assert_equals_with_unidiff(expected_out, self.actual_stdout()) - - def actual_stderr(self): - return self.stderr.getvalue() - - def actual_stdout(self): - return self.stdout.getvalue() diff -Nru trash-cli-0.12.7/unit_tests/test_trash_put_reporter.py trash-cli-0.12.9.14/unit_tests/test_trash_put_reporter.py --- trash-cli-0.12.7/unit_tests/test_trash_put_reporter.py 1970-01-01 00:00:00.000000000 +0000 +++ trash-cli-0.12.9.14/unit_tests/test_trash_put_reporter.py 2014-08-27 03:32:08.000000000 +0000 @@ -0,0 +1,14 @@ +from nose.tools import assert_equals +from nose.tools import istest +from trashcli.trash import TrashPutReporter + +class TestTrashPutReporter: + @istest + def it_should_record_failures(self): + + reporter = TrashPutReporter() + assert_equals(True, reporter.all_files_have_been_trashed) + + reporter.unable_to_trash_file('a file') + assert_equals(False, reporter.all_files_have_been_trashed) + diff -Nru trash-cli-0.12.7/unit_tests/test_trash.py trash-cli-0.12.9.14/unit_tests/test_trash.py --- trash-cli-0.12.7/unit_tests/test_trash.py 2012-06-22 14:15:56.000000000 +0000 +++ trash-cli-0.12.9.14/unit_tests/test_trash.py 2014-08-27 03:32:08.000000000 +0000 @@ -2,23 +2,9 @@ from __future__ import absolute_import -__author__="Andrea Francia (andrea.francia@users.sourceforge.net)" -__copyright__="Copyright (c) 2007 Andrea Francia" -__license__="GPL" - from trashcli.trash import TrashDirectory from trashcli.trash import TrashedFile from trashcli.trash import TrashInfo -from trashcli.trash import VolumeTrashDirectory -from trashcli.trash import TimeUtils -from trashcli.trash import HomeTrashDirectory -from trashcli.trash import mkdirs, volume_of -from trashcli.trash import TopDirIsSymLink -from trashcli.trash import TopDirNotPresent -from trashcli.trash import TopDirWithoutStickyBit -from trashcli.trash import Method1VolumeTrashDirectory - -from integration_tests.files import require_empty_dir from datetime import datetime import os @@ -28,102 +14,6 @@ abspath = os.path.abspath import shutil -class TestTrashDirectory(TestCase) : - - def test_trash(self) : - #instance - instance=VolumeTrashDirectory( - "sandbox/trash-directory", - volume_of("sandbox")) - - # test - file_to_trash="sandbox/dummy.txt" - touch(file_to_trash) - result = instance.trash(file_to_trash) - self.assertTrue(isinstance(result,TrashedFile)) - self.assertEquals(abspath(file_to_trash), result.path) - self.assertTrue(result.deletion_date is not None) - - def test_get_info_dir(self): - instance=TrashDirectory( "/mnt/disk/.Trash-123", volume_of("/mnt/disk")) - self.assertEquals("/mnt/disk/.Trash-123/info", instance.info_dir) - - def test_get_files_dir(self): - instance=TrashDirectory( "/mnt/disk/.Trash-123", volume_of("/mnt/disk")) - self.assertEquals("/mnt/disk/.Trash-123/files", instance.files_dir) - - def test_calc_id(self): - trash_info_file = "/home/user/.local/share/Trash/info/foo.trashinfo" - self.assertEquals('foo',TrashDirectory.calc_id(trash_info_file)) - - def test_calc_original_location_when_absolute(self) : - instance = TrashDirectory( "/mnt/disk/.Trash-123", volume_of("/mnt/disk")) - - assert_equals("/foo", instance._calc_original_location("/foo")) - - def test_calc_original_location_when_relative(self) : - instance = TrashDirectory( "/mnt/disk/.Trash-123", "/mnt/disk") - - assert_equals("/mnt/disk/foo", instance._calc_original_location("foo")) - -class TestHomeTrashDirectory(TestCase) : - def test_path_for_trashinfo (self) : - instance = HomeTrashDirectory("/home/user/.local/share/Trash") - instance.volume = volume_of("/") - - # path for HomeTrashDirectory are always absolute - fileToBeTrashed="/home/user/test.txt" - result=instance._path_for_trashinfo(fileToBeTrashed) - self.assertEquals("/home/user/test.txt",result) - - # ... even if the file is under /home/user/.local/share - fileToBeTrashed="/home/user/.local/share/test.txt" - result=instance._path_for_trashinfo(fileToBeTrashed) - self.assertEquals(os.path.abspath("/home/user/.local/share/test.txt"),result) - - def test_str_uses_tilde(self): - os.environ['HOME']='/home/user' - self.assertEquals('~/.local/share/Trash', str(HomeTrashDirectory("/home/user/.local/share/Trash"))) - - def test_str_dont_uses_tilde(self): - os.environ['HOME']='/home/user' - self.assertEquals('/not-in-home/Trash', str(HomeTrashDirectory("/not-in-home/Trash"))) - - def test_str_uses_tilde_with_trailing_slashes(self): - os.environ['HOME']='/home/user/' - self.assertEquals('~/.local/share/Trash', str(HomeTrashDirectory("/home/user/.local/share/Trash"))) - - def test_str_uses_tilde_with_trailing_slash(self): - os.environ['HOME']='/home/user////' - self.assertEquals('~/.local/share/Trash', str(HomeTrashDirectory("/home/user/.local/share/Trash"))) - - def test_str_with_empty_home(self): - os.environ['HOME']='' - self.assertEquals('/foo/Trash', str(HomeTrashDirectory("/foo/Trash"))) - -class TestVolumeTrashDirectory(TestCase) : - def test_init(self) : - path = "/mnt/disk/.Trash/123" - volume = volume_of("/mnt/disk") - instance = VolumeTrashDirectory(path, volume) - self.assertEquals(path, instance.path) - self.assertEquals(volume, instance.volume) - - def test_path_for_trashinfo (self) : - path = "/mnt/disk/.Trash-123" - volume = "/mnt/volume" - instance = VolumeTrashDirectory(path, volume) - - # path for VolumeTrashDirectory are relative as possible - fileToBeTrashed=("/mnt/volume/directory/test.txt") - result=instance._path_for_trashinfo(fileToBeTrashed) - self.assertEquals("directory/test.txt", result) - - # path for VolumeTrashDirectory are relative as possible - fileToBeTrashed=("/mnt/other-volume/directory/test.txt") - result=instance._path_for_trashinfo(fileToBeTrashed) - self.assertEquals("/mnt/other-volume/directory/test.txt",result) - class TestTrashInfo(TestCase) : def test_parse(self) : data = """[Trash Info] @@ -134,7 +24,7 @@ self.assert_(isinstance(result.deletion_date,datetime)) self.assertEqual(result.deletion_date, datetime(2007, 7, 23, 23, 45, 07)) - + def test_init(self) : instance = TrashInfo("path", datetime(2007, 7, 23, 23, 45, 07)) self.assertEquals("path", instance.path) @@ -144,7 +34,7 @@ instance = TrashInfo("path", datetime(2007, 7, 23, 23, 45, 07)) self.assertEquals("path", instance.path) self.assertEquals(datetime(2007, 7, 23, 23, 45, 07), instance.deletion_date) - + def test_format_date(self) : date = datetime(2007, 7, 23, 23, 45, 07) self.assertEquals("2007-07-23T23:45:07", TrashInfo._format_date(date)) @@ -154,37 +44,38 @@ __dummy_datetime=datetime(2007, 7, 23, 23, 45, 07) def setUp(self): self.xdg_data_home = ("sandbox/XDG_DATA_HOME") - + def test_init(self) : path = "/foo" deletion_date = datetime(2001,01,01) info_file = "/home/user/.local/share/Trash/info/foo.trashinfo" actual_path = ("/home/user/.local/share/Trash/files/foo") - trash_directory = HomeTrashDirectory('/home/user/.local/share/Trash') - - instance = TrashedFile(path, deletion_date, info_file, actual_path, + trash_directory = TrashDirectory('/home/user/.local/share/Trash', '/') + + instance = TrashedFile(path, deletion_date, info_file, actual_path, trash_directory) - + assert_equals(instance.path, path) assert_equals(instance.deletion_date, deletion_date) assert_equals(instance.info_file, info_file) assert_equals(instance.actual_path, actual_path) assert_equals(trash_directory, trash_directory) - + @raises(ValueError) def test_init_requires_absolute_paths(self): path = "./relative-path" deletion_date = datetime(2001,01,01) info_file = "/home/user/.local/share/Trash/info/foo.trashinfo" actual_path = "/home/user/.local/share/Trash/files/foo" - trash_directory = HomeTrashDirectory('/home/user/.local/share/Trash') - - TrashedFile(path, deletion_date, info_file, actual_path, + trash_directory = TrashDirectory('/home/user/.local/share/Trash', '/') + + TrashedFile(path, deletion_date, info_file, actual_path, trash_directory) - + def test_restore_create_needed_directories(self): - trash_dir = HomeTrashDirectory(self.xdg_data_home) + trash_dir = TrashDirectory(self.xdg_data_home, '/') + trash_dir.store_absolute_paths() os.mkdir("sandbox/foo") touch("sandbox/foo/bar") instance = trash_dir.trash("sandbox/foo/bar") @@ -192,65 +83,5 @@ instance.restore() assert os.path.exists("sandbox/foo/bar") -class TestTimeUtils(TestCase) : - def test_parse_iso8601(self) : - expected=datetime(2008,9,8,12,00,11) - result=TimeUtils.parse_iso8601("2008-09-08T12:00:11") - self.assertEqual(expected,result) - -class Method1VolumeTrashDirectoryTest(TestCase): - def setUp(self): - require_empty_dir('sandbox') - - @raises(TopDirWithoutStickyBit) - def test_check_when_no_sticky_bit(self): - # prepare - import subprocess - topdir = "sandbox/trash-dir" - mkdirs(topdir) - assert subprocess.call(["chmod", "-t", topdir]) == 0 - volume = volume_of("/mnt/disk") - instance = Method1VolumeTrashDirectory(os.path.join(topdir,"123"), volume) - - instance.check() - - @raises(TopDirNotPresent) - def test_check_when_no_dir(self): - # prepare - import subprocess - topdir = "sandbox/trash-dir" - touch(topdir) - assert subprocess.call(["chmod", "+t", topdir]) == 0 - volume = volume_of("/mnt/disk") - instance = Method1VolumeTrashDirectory(os.path.join(topdir,"123"), volume) - - instance.check() - - @raises(TopDirIsSymLink) - def test_check_when_is_symlink(self): - # prepare - import subprocess - topdir = "sandbox/trash-dir" - mkdirs(topdir) - assert subprocess.call(["chmod", "+t", topdir]) == 0 - - topdir_link = "sandbox/trash-dir-link" - os.symlink('./trash-dir', topdir_link) - volume = volume_of("/mnt/disk") - instance = Method1VolumeTrashDirectory(os.path.join(topdir_link,"123"), volume) - - instance.check() - - def test_check_pass(self): - # prepare - import subprocess - topdir = "sandbox/trash-dir" - mkdirs(topdir) - assert subprocess.call(["chmod", "+t", topdir]) == 0 - volume = volume_of("/mnt/disk") - instance = Method1VolumeTrashDirectory(os.path.join(topdir,"123"), volume) - - instance.check() # should pass - def touch(path): open(path, 'a+').close() diff -Nru trash-cli-0.12.7/unit_tests/test_trash_rm.py trash-cli-0.12.9.14/unit_tests/test_trash_rm.py --- trash-cli-0.12.7/unit_tests/test_trash_rm.py 1970-01-01 00:00:00.000000000 +0000 +++ trash-cli-0.12.9.14/unit_tests/test_trash_rm.py 2014-08-27 03:32:08.000000000 +0000 @@ -0,0 +1,47 @@ +from nose.tools import istest, assert_items_equal +from mock import Mock, call + +from trashcli.rm import Filter + +class TestTrashRmCmd: + @istest + def a_star_matches_all(self): + + self.cmd.use_pattern('*') + self.cmd.delete_if_matches('/foo', 'info/foo') + self.cmd.delete_if_matches('/bar', 'info/bar') + + assert_items_equal([ + call('info/foo'), + call('info/bar'), + ], self.delete_trashinfo_and_backup_copy.mock_calls) + + @istest + def basename_matches(self): + + self.cmd.use_pattern('foo') + self.cmd.delete_if_matches('/foo', 'info/foo'), + self.cmd.delete_if_matches('/bar', 'info/bar') + + assert_items_equal([ + call('info/foo'), + ], self.delete_trashinfo_and_backup_copy.mock_calls) + + @istest + def example_with_star_dot_o(self): + + self.cmd.use_pattern('*.o') + self.cmd.delete_if_matches('/foo.h', 'info/foo.h'), + self.cmd.delete_if_matches('/foo.c', 'info/foo.c'), + self.cmd.delete_if_matches('/foo.o', 'info/foo.o'), + self.cmd.delete_if_matches('/bar.o', 'info/bar.o') + + assert_items_equal([ + call('info/foo.o'), + call('info/bar.o'), + ], self.delete_trashinfo_and_backup_copy.mock_calls) + + def setUp(self): + self.delete_trashinfo_and_backup_copy = Mock() + self.cmd = Filter(self.delete_trashinfo_and_backup_copy) + diff -Nru trash-cli-0.12.7/unit_tests/test_volume_of.py trash-cli-0.12.9.14/unit_tests/test_volume_of.py --- trash-cli-0.12.7/unit_tests/test_volume_of.py 1970-01-01 00:00:00.000000000 +0000 +++ trash-cli-0.12.9.14/unit_tests/test_volume_of.py 2014-08-27 03:32:08.000000000 +0000 @@ -0,0 +1,28 @@ +from trashcli.fstab import VolumeOf +from trashcli.fstab import FakeIsMount +from nose.tools import assert_equals, istest +import os + +@istest +class TestVolumeOf: + + def setUp(self): + self.ismount = FakeIsMount() + self.volume_of = VolumeOf(ismount = self.ismount) + self.volume_of.abspath = os.path.normpath + + @istest + def return_the_containing_volume(self): + self.ismount.add_mount('/fake-vol') + assert_equals('/fake-vol', self.volume_of('/fake-vol/foo')) + + @istest + def with_file_that_are_outside(self): + self.ismount.add_mount('/fake-vol') + assert_equals('/', self.volume_of('/foo')) + + @istest + def it_work_also_with_relative_mount_point(self): + self.ismount.add_mount('relative-fake-vol') + assert_equals('relative-fake-vol', self.volume_of('relative-fake-vol/foo')) +