Merge lp:~frankban/lpsetup/bug-1034602-handle-target-dir into lp:lpsetup

Proposed by Francesco Banconi
Status: Merged
Approved by: Francesco Banconi
Approved revision: 83
Merged at revision: 77
Proposed branch: lp:~frankban/lpsetup/bug-1034602-handle-target-dir
Merge into: lp:lpsetup
Diff against target: 174 lines (+94/-5)
4 files modified
lpsetup/handlers.py (+27/-2)
lpsetup/tests/subcommands/test_smoke.py (+5/-2)
lpsetup/tests/test_handlers.py (+57/-0)
lpsetup/tests/utils.py (+5/-1)
To merge this branch: bzr merge lp:~frankban/lpsetup/bug-1034602-handle-target-dir
Reviewer Review Type Date Requested Status
Francesco Banconi (community) Approve
Brad Crittenden (community) Approve
Review via email: mp+121198@code.launchpad.net

Commit message

handle_target_dir validates the provided directory.

Description of the change

= Summary =

handle_target_dir (used by update subcommand) should raise an error if the target dir does not exist or is not writable.

== Implementation ==

I decided to also add another validation: handle_target_dir fails if the target dir does not look like a Launchpad branch. This way we can avoid `update` to start, create required directories (eggs, yui etc...), and then crash because `update-sourcecode` can not be found in the target dir.

== Other changes ==

Updated dry run smoke tests to pass a valid target dir to `lp-setup update`.

Added tests for handle_target_dir.

Defined `test_template_dir` at module level in lpsetup.tests.utils: that template directory is now used in 3 different places.

To post a comment you must log in.
Revision history for this message
Brad Crittenden (bac) wrote :

* typo: does not exist

* It is minor but I'd change "the target dir {0} is not a valid Launchpad branch" to say it is not a valid Launchpad tree. It may be a valid branch but not have a tree associated with it, i.e. devel vs sandbox.

* I think it would be cleaner to move the addCleanup in HandleTargetDirTest.setUp to be done right after the mktemp().

* Your test rely on the supposition that mktemp() will put things in /tmp. It might be more self-documenting and future proof if you extracted the actual directory using os.path.dirname and use that instead.

* The test_exists test is a bit fragile in that other processes could create that same directory after you remove it. Highly unlikely. But, you could avoid the appearance of a problem by using a subdirectory inside temporary directory as something you know does not exist. Picky, for sure.

* In test_is_writable your cleanup is either unnecessary because rmtree doesn't care and removes it anyway or it depends on the chmod cleanup being called before the rmtree cleanup. I just verified it is the former, rmtree does not care about write permissions when removing an owned tree, so the chmod cleanup is unnecessary.

This is a great improvement. Thanks.

review: Approve
Revision history for this message
Launchpad QA Bot (lpqabot) wrote :
Download full text (6.3 KiB)

The attempt to merge lp:~frankban/lpsetup/bug-1034602-handle-target-dir into lp:lpsetup failed. Below is the output from the failed tests.

+ set -o errexit
++ grep -v distribute_setup.py
++ find . -name build -prune -o -name '*.py'
+ pyfiles='./setup.py
./lplxcip/tests/utils.py
./lplxcip/tests/__init__.py
./lplxcip/tests/test_lxcip.py
./lplxcip/tests/test_helpers.py
./lplxcip/tests/test_utils.py
./lplxcip/lxcip.py
./lpsetup/utils.py
./lpsetup/__init__.py
./lpsetup/handlers.py
./lpsetup/tests/utils.py
./lpsetup/tests/__init__.py
./lpsetup/tests/test_argparser.py
./lpsetup/tests/test_cli.py
./lpsetup/tests/test_handlers.py
./lpsetup/tests/subcommands/__init__.py
./lpsetup/tests/subcommands/test_version.py
./lpsetup/tests/subcommands/test_initrepo.py
./lpsetup/tests/subcommands/test_init_target.py
./lpsetup/tests/subcommands/test_smoke.py
./lpsetup/tests/integration/common.py
./lpsetup/tests/integration/test_install_lxc.py
./lpsetup/tests/integration/test_init_target.py
./lpsetup/tests/test_utils.py
./lpsetup/subcommands/__init__.py
./lpsetup/subcommands/install_lxc.py
./lpsetup/subcommands/initlxc.py
./lpsetup/subcommands/init_target.py
./lpsetup/subcommands/update.py
./lpsetup/subcommands/initrepo.py
./lpsetup/subcommands/version.py
./lpsetup/exceptions.py
./lpsetup/argparser.py
./lpsetup/settings.py
./lpsetup/cli.py'
+ pocketlint ./setup.py ./lplxcip/tests/utils.py ./lplxcip/tests/__init__.py ./lplxcip/tests/test_lxcip.py ./lplxcip/tests/test_helpers.py ./lplxcip/tests/test_utils.py ./lplxcip/lxcip.py ./lpsetup/utils.py ./lpsetup/__init__.py ./lpsetup/handlers.py ./lpsetup/tests/utils.py ./lpsetup/tests/__init__.py ./lpsetup/tests/test_argparser.py ./lpsetup/tests/test_cli.py ./lpsetup/tests/test_handlers.py ./lpsetup/tests/subcommands/__init__.py ./lpsetup/tests/subcommands/test_version.py ./lpsetup/tests/subcommands/test_initrepo.py ./lpsetup/tests/subcommands/test_init_target.py ./lpsetup/tests/subcommands/test_smoke.py ./lpsetup/tests/integration/common.py ./lpsetup/tests/integration/test_install_lxc.py ./lpsetup/tests/integration/test_init_target.py ./lpsetup/tests/test_utils.py ./lpsetup/subcommands/__init__.py ./lpsetup/subcommands/install_lxc.py ./lpsetup/subcommands/initlxc.py ./lpsetup/subcommands/init_target.py ./lpsetup/subcommands/update.py ./lpsetup/subcommands/initrepo.py ./lpsetup/subcommands/version.py ./lpsetup/exceptions.py ./lpsetup/argparser.py ./lpsetup/settings.py ./lpsetup/cli.py
+ pep8 --exclude=build ./setup.py ./lplxcip/tests/utils.py ./lplxcip/tests/__init__.py ./lplxcip/tests/test_lxcip.py ./lplxcip/tests/test_helpers.py ./lplxcip/tests/test_utils.py ./lplxcip/lxcip.py ./lpsetup/utils.py ./lpsetup/__init__.py ./lpsetup/handlers.py ./lpsetup/tests/utils.py ./lpsetup/tests/__init__.py ./lpsetup/tests/test_argparser.py ./lpsetup/tests/test_cli.py ./lpsetup/tests/test_handlers.py ./lpsetup/tests/subcommands/__init__.py ./lpsetup/tests/subcommands/test_version.py ./lpsetup/tests/subcommands/test_initrepo.py ./lpsetup/tests/subcommands/test_init_target.py ./lpsetup/tests/subcommands/test_smoke.py ./lpsetup/tests/integration/common.py ./lpsetup/tests/integration/test_install_lxc.py ./lpsetup/tests/integration/test_init...

Read more...

83. By Francesco Banconi

Changes per review.

Revision history for this message
Francesco Banconi (frankban) wrote :

Thanks for your review Brad. I fixed the branch following your suggestions with one exception:
I've seen that removing the chmod clean up generates this error:

======================================================================
ERROR: test_is_writable (lpsetup.tests.test_handlers.HandleTargetDirTest)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/usr/lib/python2.7/shutil.py", line 245, in rmtree
    rmtree(fullname, ignore_errors, onerror)
  File "/usr/lib/python2.7/shutil.py", line 254, in rmtree
    onerror(os.rmdir, path, sys.exc_info())
  File "/usr/lib/python2.7/shutil.py", line 252, in rmtree
    os.rmdir(path)
OSError: [Errno 13] Permission denied: '/tmp/tmp6TfXhT/utilities'

So, the write permission missing does not prevent you from deleting the directory iself, but its contents.
The chmod cleanup is called before the rmtree cleanup because the (documented) system used is LIFO.
Maybe a try/finally block could be more explicit?

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lpsetup/handlers.py'
2--- lpsetup/handlers.py 2012-08-10 08:34:16 +0000
3+++ lpsetup/handlers.py 2012-08-24 16:08:23 +0000
4@@ -268,10 +268,35 @@
5 """Handle path to the working directory.
6
7 The namespace must contain *target_dir*.
8+ Raise *ValidationError* if the resulting target dir:
9+
10+ - does not exist
11+ - is not a directory
12+ - is not writable by the current user
13+ - does not seem a valid Launchpad branch
14+
15 It should be invoked when *target_dir* is explicitly passed to a command.
16 """
17- path = namespace.target_dir
18- namespace.target_dir = os.path.abspath(os.path.expanduser(path))
19+ target_dir = os.path.abspath(os.path.expanduser(namespace.target_dir))
20+ # The target dir must be an existing directory.
21+ if not os.path.isdir(target_dir):
22+ raise ValidationError(
23+ 'the target dir {0} does not exist or is not a directory.'.format(
24+ target_dir))
25+ # The target dir must be writable.
26+ if not os.access(target_dir, os.W_OK):
27+ raise ValidationError(
28+ 'the target dir {0} is not writable by the current user.'.format(
29+ target_dir))
30+ # The target dir must look like a Launchpad tree.
31+ # This check is done in a naive way: `utilities/update-sourcecode`
32+ # must be included in the target dir.
33+ path_to_check = os.path.join(target_dir, 'utilities', 'update-sourcecode')
34+ if not os.path.exists(path_to_check):
35+ raise ValidationError(
36+ 'the target dir {0} is not a valid Launchpad tree.'.format(
37+ target_dir))
38+ namespace.target_dir = target_dir
39
40
41 def handle_branch_and_checkout(namespace):
42
43=== modified file 'lpsetup/tests/subcommands/test_smoke.py'
44--- lpsetup/tests/subcommands/test_smoke.py 2012-08-23 19:21:55 +0000
45+++ lpsetup/tests/subcommands/test_smoke.py 2012-08-24 16:08:23 +0000
46@@ -10,7 +10,10 @@
47 SUBCOMMANDS,
48 )
49
50-from lpsetup.tests.utils import capture_output
51+from lpsetup.tests.utils import (
52+ capture_output,
53+ test_template_dir,
54+ )
55
56
57 class SmokeTest(unittest.TestCase):
58@@ -31,7 +34,7 @@
59 'init-lxc': required_args,
60 'init-repo': [],
61 'install-lxc': required_args,
62- 'update': [],
63+ 'update': [test_template_dir],
64 }
65 warning = 'This command will perform the following actions:'
66 for name, args in name_args_map.items():
67
68=== added directory 'lpsetup/tests/test-branch/utilities'
69=== added file 'lpsetup/tests/test-branch/utilities/update-sourcecode'
70=== modified file 'lpsetup/tests/test_handlers.py'
71--- lpsetup/tests/test_handlers.py 2012-08-06 09:37:04 +0000
72+++ lpsetup/tests/test_handlers.py 2012-08-24 16:08:23 +0000
73@@ -25,6 +25,7 @@
74 handle_lpuser_as_username,
75 handle_lpuser_from_lplogin,
76 handle_ssh_keys,
77+ handle_target_dir,
78 handle_target_from_repository,
79 handle_testing,
80 handle_user,
81@@ -33,6 +34,7 @@
82 from lpsetup.tests.utils import (
83 lpuser,
84 skip_if_no_lpuser,
85+ test_template_dir,
86 )
87
88
89@@ -276,6 +278,61 @@
90 handle_ssh_keys(self.namespace)
91
92
93+class HandleTargetDirTest(HandlersTestMixin, unittest.TestCase):
94+
95+ def setUp(self):
96+ # Create a base target dir structure.
97+ self.target_dir = tempfile.mktemp()
98+ self.addCleanup(shutil.rmtree, self.target_dir)
99+ shutil.copytree(test_template_dir, self.target_dir)
100+ self.dirname = os.path.split(self.target_dir)[1]
101+
102+ def test_absolute_path(self):
103+ # Ensure the resulting target dir is an absolute path.
104+ namespace = argparse.Namespace(target_dir=self.dirname)
105+ with cd(tempfile.tempdir):
106+ handle_target_dir(namespace)
107+ self.assertEqual(self.target_dir, namespace.target_dir)
108+
109+ def test_expanduser(self):
110+ # Ensure the user is correctly expanded in the resulting target dir.
111+ namespace = argparse.Namespace(target_dir='~/' + self.dirname)
112+ with environ(HOME=tempfile.tempdir):
113+ handle_target_dir(namespace)
114+ self.assertEqual(self.target_dir, namespace.target_dir)
115+
116+ def test_isdir(self):
117+ # An error is raised if the provided target dir is not a directory.
118+ with tempfile.NamedTemporaryFile() as f:
119+ namespace = argparse.Namespace(target_dir=f.name)
120+ with self.assertNotValid('is not a directory'):
121+ handle_target_dir(namespace)
122+
123+ def test_exists(self):
124+ # An error is raised if the provided target dir does not exist.
125+ target_dir = os.path.join(self.target_dir, '__does_not_exist__')
126+ namespace = argparse.Namespace(target_dir=target_dir)
127+ with self.assertNotValid('does not exist'):
128+ handle_target_dir(namespace)
129+
130+ def test_is_writable(self):
131+ # An error is raised if the target dir can not be written by
132+ # current user.
133+ namespace = argparse.Namespace(target_dir=self.target_dir)
134+ os.chmod(self.target_dir, 0500)
135+ self.addCleanup(os.chmod, self.target_dir, 0700)
136+ with self.assertNotValid('is not writable'):
137+ handle_target_dir(namespace)
138+
139+ def test_is_launchpad_tree(self):
140+ # An error is raised if the target dir is not a valid Launchpad tree.
141+ os.remove(
142+ os.path.join(self.target_dir, 'utilities', 'update-sourcecode'))
143+ namespace = argparse.Namespace(target_dir=self.target_dir)
144+ with self.assertNotValid('is not a valid Launchpad tree'):
145+ handle_target_dir(namespace)
146+
147+
148 class HandleTargetFromRepositoryTest(unittest.TestCase):
149
150 branch_name = 'branch'
151
152=== modified file 'lpsetup/tests/utils.py'
153--- lpsetup/tests/utils.py 2012-08-13 14:27:59 +0000
154+++ lpsetup/tests/utils.py 2012-08-24 16:08:23 +0000
155@@ -97,6 +97,10 @@
156 self.assertEqual(self.message + '\n', stream.getvalue())
157
158
159+# Template directory used to create a Launchpad branch.
160+test_template_dir = os.path.join(os.path.dirname(__file__), 'test-branch')
161+
162+
163 def create_test_branch(template_dir=None):
164 """Create a temporary test branch containing files in *template_dir*.
165
166@@ -106,7 +110,7 @@
167 Return the path of the newly created branch.
168 """
169 if template_dir is None:
170- template_dir = os.path.join(os.path.dirname(__file__), 'test-branch')
171+ template_dir = test_template_dir
172 branch_path = tempfile.mktemp()
173 shutil.copytree(template_dir, branch_path)
174 call('bzr', 'init', '--quiet', branch_path)

Subscribers

People subscribed via source and target branches

to all changes: