Merge lp:~dobey/dirspec/add-exefind into lp:dirspec

Proposed by dobey
Status: Merged
Approved by: Roberto Alsina
Approved revision: 14
Merged at revision: 8
Proposed branch: lp:~dobey/dirspec/add-exefind
Merge into: lp:dirspec
Diff against target: 241 lines (+190/-4)
2 files modified
dirspec/tests/test_utils.py (+118/-2)
dirspec/utils.py (+72/-2)
To merge this branch: bzr merge lp:~dobey/dirspec/add-exefind
Reviewer Review Type Date Requested Status
Roberto Alsina (community) Approve
Mike McCracken (community) Approve
Review via email: mp+114273@code.launchpad.net

Commit message

Add a generic API for discovering paths to program executables

To post a comment you must log in.
Revision history for this message
Mike McCracken (mikemc) wrote :

Thanks for putting this together. It needs just a bit more doc string cleanup:

The doc string for UnfrozenSrcTestCase, and its first two test functions are still from the version that prepended 'python' to the return value.

The doc string for test_darwin_pkgd_raises_on_no_appnames is also from the old version.

and probably the function name 'test_linux_no_src_relative_path_no_constants' and its doc string should be changed, too - since the code doesn't look for a constants module anymore.

review: Needs Fixing
Revision history for this message
Mike McCracken (mikemc) wrote :

Just noticed this - you might also want to tag it as fixing bug #1021833.

lp:~dobey/dirspec/add-exefind updated
14. By dobey

Update tests docstrings from review
Remove a couple of now extraneous/duplicate tests
Rename the ExeFind test case to ProgramPath
Link the bug as LP isn't picking up the sub-commit bugs

Revision history for this message
Mike McCracken (mikemc) :
review: Approve
Revision history for this message
Roberto Alsina (ralsina) :
review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'dirspec/tests/test_utils.py'
2--- dirspec/tests/test_utils.py 2012-06-26 15:25:21 +0000
3+++ dirspec/tests/test_utils.py 2012-07-11 14:57:22 +0000
4@@ -17,8 +17,11 @@
5 import os
6 import sys
7
8-from dirspec import basedir
9-from dirspec.utils import get_env_path, get_special_folders, user_home
10+from twisted.trial.unittest import TestCase
11+
12+from dirspec import basedir, utils as dirutils
13+from dirspec.utils import (get_env_path, get_special_folders,
14+ user_home, get_program_path)
15 from dirspec.tests import BaseTestCase
16
17
18@@ -150,3 +153,116 @@
19 self.assertEqual(get_env_path(fake_env_var, default), default)
20 test_get_env_path_var.skip = 'UnicodeEncodeError: bug #907053'
21 test_get_env_path_no_var.skip = test_get_env_path_var.skip
22+
23+
24+class ProgramPathBaseTestCase(TestCase):
25+ """Base class for testing the executable finder."""
26+
27+ def setUp(self):
28+ """Set up fake modules."""
29+ super(ProgramPathBaseTestCase, self).setUp()
30+ self.patch(os.path, "exists", lambda x: True)
31+
32+
33+class UnfrozenSrcTestCase(ProgramPathBaseTestCase):
34+ """Test non-linux path discovery."""
35+
36+ def setUp(self):
37+ super(UnfrozenSrcTestCase, self).setUp()
38+ self.patch(sys, "platform", "darwin")
39+
40+ def test_unfrozen_dev_toplevel(self):
41+ """Not frozen, return path to bin dir."""
42+ path = get_program_path("foo", fallback_dirs=['/path/to/bin'])
43+ self.assertEquals(path, "/path/to/bin/foo")
44+
45+ def test_unfrozen_dev_toplevel_raises_nopath(self):
46+ """Not frozen, raise OSError when the path doesn't exist."""
47+ self.patch(os.path, "exists", lambda x: False)
48+ self.assertRaises(OSError, get_program_path, "foo")
49+
50+
51+class DarwinPkgdTestCase(ProgramPathBaseTestCase):
52+ """Test cmdline for running packaged on darwin."""
53+
54+ def setUp(self):
55+ """SetUp to mimic frozen darwin."""
56+ super(DarwinPkgdTestCase, self).setUp()
57+ self.patch(sys, "platform", "darwin")
58+ sys.frozen = True
59+
60+ self.darwin_app_names = {"foo": "Foo.app"}
61+
62+ def tearDown(self):
63+ """tearDown, Remove frozen attr"""
64+ del sys.frozen
65+ super(DarwinPkgdTestCase, self).tearDown()
66+
67+ def test_darwin_pkgd(self):
68+ """Return sub-app path on darwin when frozen."""
69+ path = get_program_path("foo", app_names=self.darwin_app_names)
70+ expectedpath = "%s/%s" % (
71+ dirutils.__file__,
72+ "Contents/Resources/Foo.app/Contents/MacOS/foo")
73+ self.assertEquals(path, expectedpath)
74+
75+ def test_darwin_pkgd_raises_on_no_appnames(self):
76+ """Raises TypeError when no app_names dict is in the kwargs."""
77+ self.assertRaises(TypeError, get_program_path, "foo")
78+
79+ def test_darwin_pkgd_raises_nopath(self):
80+ """Frozen, raise OSError when the path doesn't exist."""
81+ self.patch(os.path, "exists", lambda x: False)
82+ self.assertRaises(OSError, get_program_path, "foo",
83+ app_names=self.darwin_app_names)
84+
85+
86+class Win32PkgdTestCase(ProgramPathBaseTestCase):
87+ """Test cmdline for running packaged on windows."""
88+
89+ def setUp(self):
90+ """SetUp to mimic frozen windows."""
91+ super(Win32PkgdTestCase, self).setUp()
92+ self.patch(sys, "platform", "win32")
93+ sys.frozen = True
94+
95+ def tearDown(self):
96+ """tearDown, Remove frozen attr"""
97+ del sys.frozen
98+ super(Win32PkgdTestCase, self).tearDown()
99+
100+ def test_windows_pkgd(self):
101+ """Return sub-app path on windows when frozen."""
102+
103+ self.patch(sys, "executable", os.path.join("C:\\path", "to",
104+ "current.exe"))
105+ # patch abspath to let us run this tests on non-windows:
106+ self.patch(os.path, "abspath", lambda x: x)
107+ path = get_program_path("foo", None)
108+ expectedpath = os.path.join("C:\\path", "to", "foo.exe")
109+ self.assertEquals(path, expectedpath)
110+
111+ def test_windows_pkgd_raises_nopath(self):
112+ """Frozen, raise OSError when the path doesn't exist."""
113+ self.patch(os.path, "exists", lambda x: False)
114+ self.assertRaises(OSError, get_program_path, "foo")
115+
116+
117+class PosixTestCase(ProgramPathBaseTestCase):
118+ """Test cmdline for running on linux."""
119+
120+ def setUp(self):
121+ """SetUp to mimic linux2."""
122+ super(PosixTestCase, self).setUp()
123+ self.patch(sys, "platform", "linux2")
124+
125+ def test_linux_src_relative_path_exists(self):
126+ """linux, return source relative path if it exists."""
127+ path = get_program_path("foo", fallback_dirs=['/path/to/bin'])
128+ expectedpath = "/path/to/bin/foo"
129+ self.assertEquals(path, expectedpath)
130+
131+ def test_linux_no_src_relative_path(self):
132+ """raise if no src rel path."""
133+ self.patch(os.path, "exists", lambda x: False)
134+ self.assertRaises(OSError, get_program_path, "foo")
135
136=== modified file 'dirspec/utils.py'
137--- dirspec/utils.py 2011-12-17 00:46:26 +0000
138+++ dirspec/utils.py 2012-07-11 14:57:22 +0000
139@@ -1,6 +1,6 @@
140 # -*- coding: utf-8 -*-
141 #
142-# Copyright 2011 Canonical Ltd.
143+# Copyright 2011-2012 Canonical Ltd.
144 #
145 # This program is free software: you can redistribute it and/or modify
146 # it under the terms of the GNU Lesser General Public License version 3
147@@ -15,6 +15,7 @@
148 # along with this program. If not, see <http://www.gnu.org/licenses/>.
149 """Utilities for multiplatform support of XDG directory handling."""
150
151+import errno
152 import os
153 import sys
154
155@@ -25,10 +26,79 @@
156 'default_data_home',
157 'default_data_path',
158 'get_env_path',
159+ 'get_program_path',
160 'unicode_path',
161 ]
162
163
164+def _get_exe_path_frozen_win32(exe_name):
165+ """Get path to the helper .exe on packaged windows."""
166+ # all the .exes are in the same place on windows:
167+ cur_exec_path = os.path.abspath(sys.executable)
168+ exe_dir = os.path.dirname(cur_exec_path)
169+ return os.path.join(exe_dir, exe_name + ".exe")
170+
171+
172+def _get_exe_path_frozen_darwin(exe_name, app_names):
173+ """Get path to the sub-app executable on packaged darwin."""
174+
175+ sub_app_name = app_names[exe_name]
176+ main_app_dir = "".join(__file__.partition(".app")[:-1])
177+ main_app_resources_dir = os.path.join(main_app_dir,
178+ "Contents",
179+ "Resources")
180+ exe_bin = os.path.join(main_app_resources_dir,
181+ sub_app_name,
182+ "Contents", "MacOS",
183+ exe_name)
184+ return exe_bin
185+
186+
187+def get_program_path(program_name, *args, **kwargs):
188+ """Given a program name, returns the path to run that program.
189+
190+ Raises OSError if the program is not found.
191+
192+ :param program_name: The name of the program to find. For darwin and win32
193+ platforms, the behavior is changed slightly, when sys.frozen is set,
194+ to look in the packaged program locations for the program.
195+ :param search_dirs: A list of directories to look for the program in. This
196+ is only available as a keyword argument.
197+ :param app_names: A dict of program names mapped to sub-app names. Used
198+ for discovering paths in embedded .app bundles on the darwin platform.
199+ This is only available as a keyword argument.
200+ :return: The path to the discovered program.
201+ """
202+ search_dirs = kwargs.get('fallback_dirs', None)
203+ app_names = kwargs.get('app_names', None)
204+
205+ if getattr(sys, "frozen", None) is not None:
206+ if sys.platform == 'win32':
207+ program_path = _get_exe_path_frozen_win32(program_name)
208+ elif sys.platform == 'darwin':
209+ program_path = _get_exe_path_frozen_darwin(program_name,
210+ app_names)
211+ else:
212+ raise Exception("Unsupported platform for frozen execution: %r" %
213+ sys.platform)
214+ else:
215+ if search_dirs is not None:
216+ for dirname in search_dirs:
217+ program_path = os.path.join(dirname, program_name)
218+ if os.path.exists(program_path):
219+ return program_path
220+ else:
221+ # Check in normal system $PATH, if no fallback dirs specified
222+ from distutils.spawn import find_executable
223+ program_path = find_executable(program_name)
224+
225+ if program_path is None or not os.path.exists(program_path):
226+ raise OSError(errno.ENOENT,
227+ "Could not find executable %r" % program_name)
228+
229+ return program_path
230+
231+
232 def get_env_path(key, default):
233 """Get a UTF-8 encoded path from an environment variable."""
234 if key in os.environ:
235@@ -41,7 +111,7 @@
236
237
238 def unicode_path(utf8path):
239- """Turn an utf8 path into a unicode path."""
240+ """Turn a UTF-8 path into a unicode path."""
241 return utf8path.decode("utf-8")
242
243

Subscribers

People subscribed via source and target branches

to all changes: