Merge lp:~gagern/bzr/bash_completion-ExecutableFeature into lp:bzr

Proposed by Martin von Gagern on 2010-05-08
Status: Merged
Approved by: Robert Collins on 2010-05-25
Approved revision: 5171
Merged at revision: 5255
Proposed branch: lp:~gagern/bzr/bash_completion-ExecutableFeature
Merge into: lp:bzr
Prerequisite: lp:~gagern/bzr/bug560030-include-bash-completion-plugin
Diff against target: 122 lines (+42/-27)
3 files modified
NEWS (+4/-0)
bzrlib/plugins/bash_completion/tests/test_bashcomp.py (+5/-27)
bzrlib/tests/features.py (+33/-0)
To merge this branch: bzr merge lp:~gagern/bzr/bash_completion-ExecutableFeature
Reviewer Review Type Date Requested Status
Robert Collins (community) Needs Fixing on 2010-05-25
John A Meinel 2010-05-08 Needs Information on 2010-05-18
Review via email: mp+24951@code.launchpad.net

Commit Message

Generalise probing for executables used in selftest.

Description of the Change

If the bash_completion plugin gets merged into the bzr source tree, here is a subsequent testing framework improvement that might benefit tests for other commands and plugins as well. The class ExecutableFeature checks for the availability of a named executable on the PATH.

To post a comment you must log in.
5169. By Martin von Gagern on 2010-05-18

Handle empty PATH elements in ExecutableFeature.

John A Meinel (jameinel) wrote :

Note that I think you could do this using a 'subprocess.Popen()' style feature, rather than iterating over environ['PATH'].

Does that seem reasonable to you?

review: Needs Information
Robert Collins (lifeless) wrote :

This feature seems to be written a little awkwardly to me - just defining _probe and the string should be enough, no ?

review: Needs Information
Martin von Gagern (gagern) wrote :

@jameinel: subprocess.Popen(cmd, shell=True) has three drawbacks:
1. additional shell invocation causes overhead for one more process
2. can't easily test for existence of command without actually executing it
3. might under some circumstances interfere with shell builtins
So iterating over PATH seems to me to be both cleaner and more performant

@lifeless: I want the path property available so I can execute the found executable by full path name without repeating the search, the way the bash_completion plugin tests do.

And I don't want to impose any restriction about in which order on feature check and access to the path property. Therefore both the feature check and access to the path both should essentially do the same: try to locate the executable upon first run, use cached result afterwards. Under these circumstances, _probe seems the wrong approach, as it is based on a different assumption, namely a boolean-only feature with no additional properties.

What might feel awkward as well is the fact that self._path is not initialized, but only set lazily at first access. Basically I have three possible states: _path is either unset, set to None, or set to some string. It would be easy to either have self._path initialized to a special value, denoting "no cached value", or to have a separate member track whether or not the result has been cached already. But as the simple try-except does the job well, and easily represents the three states named above, I went for that solution.

Robert Collins (lifeless) wrote :

>>> @lifeless: I want the path property available so I can execute the found executable by full path name without repeating the search, the way the bash_completion plugin tests do.

But if the feature isn't available, the path is useless. So the
feature *has* to be checked first.

I'm totally fine with the caching - thats standard for features. It
just seems a lot more awkward than simply having probe set the _path.

-Rob

5170. By Martin von Gagern on 2010-05-25

Have ExecutableFeature implement and use _probe.

Setting _path now is a side-effect of _probe.
This makes Robert Collins happy.

Robert Collins (lifeless) wrote :

One last thing, rather than SedFeature, sed_feature please, and put them in features? [we've changed coding style in this regard].

Cheers,
Rob

review: Needs Fixing
5171. By Martin von Gagern on 2010-05-25

Move ExecutableFeature instances to tests.features module.

Robert Collins (lifeless) wrote :

sent to pqm by email

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'NEWS'
2--- NEWS 2010-05-21 07:17:13 +0000
3+++ NEWS 2010-05-25 11:21:39 +0000
4@@ -244,6 +244,10 @@
5 failures on Lucid, FreeBSD and gentoo.
6 (Vincent Ladeuil, #528436)
7
8+* New class ``ExecutableFeature`` for checking the availability of
9+ executables on the ``PATH``. Migrated from bash_completion plugin.
10+ (Martin von Gagern)
11+
12 bzr 2.2b2
13 #########
14
15
16=== modified file 'bzrlib/plugins/bash_completion/tests/test_bashcomp.py'
17--- bzrlib/plugins/bash_completion/tests/test_bashcomp.py 2010-05-04 19:30:44 +0000
18+++ bzrlib/plugins/bash_completion/tests/test_bashcomp.py 2010-05-25 11:21:39 +0000
19@@ -22,35 +22,10 @@
20 import subprocess
21
22
23-class _BashFeature(tests.Feature):
24- """Feature testing whether a bash executable is available."""
25-
26- bash_paths = ['/bin/bash', '/usr/bin/bash']
27-
28- def __init__(self):
29- super(_BashFeature, self).__init__()
30- self.bash_path = None
31-
32- def available(self):
33- if self.bash_path is not None:
34- return self.bash_path is not False
35- for path in self.bash_paths:
36- if os.access(path, os.X_OK):
37- self.bash_path = path
38- return True
39- self.bash_path = False
40- return False
41-
42- def feature_name(self):
43- return 'bash'
44-
45-BashFeature = _BashFeature()
46-
47-
48 class BashCompletionMixin(object):
49 """Component for testing execution of a bash completion script."""
50
51- _test_needs_features = [BashFeature]
52+ _test_needs_features = [tests.features.bash_feature]
53
54 def complete(self, words, cword=-1):
55 """Perform a bash completion.
56@@ -60,7 +35,8 @@
57 """
58 if self.script is None:
59 self.script = self.get_script()
60- proc = subprocess.Popen([BashFeature.bash_path, '--noprofile'],
61+ proc = subprocess.Popen([tests.features.bash_feature.path,
62+ '--noprofile'],
63 stdin=subprocess.PIPE,
64 stdout=subprocess.PIPE,
65 stderr=subprocess.PIPE)
66@@ -187,6 +163,7 @@
67 return s.replace("$(bzr ", "$('%s' " % self.get_bzr_path())
68
69 def test_revspec_tag_all(self):
70+ self.requireFeature(tests.features.sed_feature)
71 wt = self.make_branch_and_tree('.', format='dirstate-tags')
72 wt.branch.tags.set_tag('tag1', 'null:')
73 wt.branch.tags.set_tag('tag2', 'null:')
74@@ -195,6 +172,7 @@
75 self.assertCompletionEquals('tag1', 'tag2', '3tag')
76
77 def test_revspec_tag_prefix(self):
78+ self.requireFeature(tests.features.sed_feature)
79 wt = self.make_branch_and_tree('.', format='dirstate-tags')
80 wt.branch.tags.set_tag('tag1', 'null:')
81 wt.branch.tags.set_tag('tag2', 'null:')
82
83=== modified file 'bzrlib/tests/features.py'
84--- bzrlib/tests/features.py 2010-05-21 06:14:50 +0000
85+++ bzrlib/tests/features.py 2010-05-25 11:21:39 +0000
86@@ -80,3 +80,36 @@
87
88 chown_feature = _ChownFeature()
89
90+
91+class ExecutableFeature(tests.Feature):
92+ """Feature testing whether an executable of a given name is on the PATH."""
93+
94+ def __init__(self, name):
95+ super(ExecutableFeature, self).__init__()
96+ self.name = name
97+ self._path = None
98+
99+ @property
100+ def path(self):
101+ # This is a property, so accessing path ensures _probe was called
102+ self.available()
103+ return self._path
104+
105+ def _probe(self):
106+ path = os.environ.get('PATH')
107+ if path is None:
108+ return False
109+ for d in path.split(os.pathsep):
110+ if d:
111+ f = os.path.join(d, self.name)
112+ if os.access(f, os.X_OK):
113+ self._path = f
114+ return True
115+ return False
116+
117+ def feature_name(self):
118+ return '%s executable' % self.name
119+
120+
121+bash_feature = ExecutableFeature('bash')
122+sed_feature = ExecutableFeature('sed')