Skip to content

Commit 6536994

Browse files
committed
Adding tox support for AppVeyor.
In the process, fixing some PATH issues in "gcloud._helpers" (path separator was explicitly provided, rather than using "os.join"). Also the the associated tests were refactored with more mocks so that they depend less on the OS.
1 parent 0fd323e commit 6536994

File tree

4 files changed

+173
-81
lines changed

4 files changed

+173
-81
lines changed

appveyor.yml

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,32 +19,38 @@ environment:
1919
- PYTHON: "C:\\Python27"
2020
PYTHON_VERSION: "2.7.11"
2121
PYTHON_ARCH: "32"
22+
TOX_ENV: "py27"
2223

2324
- PYTHON: "C:\\Python27-x64"
2425
PYTHON_VERSION: "2.7.11"
2526
PYTHON_ARCH: "64"
27+
TOX_ENV: "py27"
2628

2729
# Python 3.4.4 is the latest Python 3.4 with a Windows installer
2830
# Python 3.4.4 is the overall latest
2931
# https://www.python.org/ftp/python/3.4.4/
3032
- PYTHON: "C:\\Python34"
3133
PYTHON_VERSION: "3.4.4"
3234
PYTHON_ARCH: "32"
35+
TOX_ENV: "py34"
3336

3437
- PYTHON: "C:\\Python34-x64"
3538
PYTHON_VERSION: "3.4.4"
3639
PYTHON_ARCH: "64"
40+
TOX_ENV: "py34"
3741

3842
# Python 3.5.1 is the latest Python 3.5 with a Windows installer
3943
# Python 3.5.1 is the overall latest
4044
# https://www.python.org/ftp/python/3.5.1/
4145
- PYTHON: "C:\\Python35"
4246
PYTHON_VERSION: "3.5.1"
4347
PYTHON_ARCH: "32"
48+
TOX_ENV: "py35"
4449

4550
- PYTHON: "C:\\Python35-x64"
4651
PYTHON_VERSION: "3.5.1"
4752
PYTHON_ARCH: "64"
53+
TOX_ENV: "py35"
4854

4955
install:
5056
- ECHO "Filesystem root:"
@@ -83,7 +89,7 @@ build_script:
8389
test_script:
8490
- "%CMD_IN_ENV% pip list"
8591
# Run the project tests
86-
- "%CMD_IN_ENV% py.test"
92+
- "%CMD_IN_ENV% tox -e %TOX_ENV%"
8793

8894
after_test:
8995
# If tests are successful, create binary packages for the project.

appveyor/requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
# target Python version and architecture
55
wheel
66
pytest
7+
tox
78
cryptography
89
grpcio >= 1.0rc1
910
grpc-google-pubsub-v1

gcloud/_helpers.py

Lines changed: 47 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,16 @@
5757
(?P<nanos>\d{1,9}) # nanoseconds, maybe truncated
5858
Z # Zulu
5959
""", re.VERBOSE)
60-
DEFAULT_CONFIGURATION_PATH = '~/.config/gcloud/configurations/config_default'
60+
# NOTE: Catching this ImportError is a workaround for GAE not supporting the
61+
# "pwd" module which is imported lazily when "expanduser" is called.
62+
try:
63+
_USER_ROOT = os.path.expanduser('~')
64+
except ImportError: # pragma: NO COVER
65+
_USER_ROOT = None
66+
_GCLOUD_CONFIG_FILE = os.path.join(
67+
'gcloud', 'configurations', 'config_default')
68+
_GCLOUD_CONFIG_SECTION = 'core'
69+
_GCLOUD_CONFIG_KEY = 'project'
6170

6271

6372
class _LocalStack(Local):
@@ -171,10 +180,10 @@ def _app_engine_id():
171180

172181

173182
def _file_project_id():
174-
"""Gets the project id from the credentials file if one is available.
183+
"""Gets the project ID from the credentials file if one is available.
175184
176185
:rtype: str or ``NoneType``
177-
:returns: Project-ID from JSON credentials file if value exists,
186+
:returns: Project ID from JSON credentials file if value exists,
178187
else ``None``.
179188
"""
180189
credentials_file_path = os.getenv(CREDENTIALS)
@@ -185,9 +194,37 @@ def _file_project_id():
185194
return credentials.get('project_id')
186195

187196

197+
def _get_nix_config_path():
198+
"""Get the ``gcloud`` CLI config path on *nix systems.
199+
200+
:rtype: str
201+
:returns: The filename on a *nix system containing the CLI
202+
config file.
203+
"""
204+
return os.path.join(_USER_ROOT, '.config', _GCLOUD_CONFIG_FILE)
205+
206+
207+
def _get_windows_config_path():
208+
"""Get the ``gcloud`` CLI config path on Windows systems.
209+
210+
:rtype: str
211+
:returns: The filename on a Windows system containing the CLI
212+
config file.
213+
"""
214+
appdata_dir = os.getenv('APPDATA', '')
215+
return os.path.join(appdata_dir, _GCLOUD_CONFIG_FILE)
216+
217+
188218
def _default_service_project_id():
189219
"""Retrieves the project ID from the gcloud command line tool.
190220
221+
This assumes the ``.config`` directory is stored
222+
- in ~/.config on *nix systems
223+
- in the %APPDATA% directory on Windows systems
224+
225+
Additionally, the ${HOME} / "~" directory may not be present on Google
226+
App Engine, so this may be conditionally ignored.
227+
191228
Files that cannot be opened with configparser are silently ignored; this is
192229
designed so that you can specify a list of potential configuration file
193230
locations.
@@ -196,21 +233,17 @@ def _default_service_project_id():
196233
:returns: Project-ID from default configuration file else ``None``
197234
"""
198235
search_paths = []
199-
# Workaround for GAE not supporting pwd which is used by expanduser.
200-
try:
201-
search_paths.append(os.path.expanduser(DEFAULT_CONFIGURATION_PATH))
202-
except ImportError:
203-
pass
236+
if _USER_ROOT is not None:
237+
search_paths.append(_get_nix_config_path())
238+
239+
if os.name == 'nt':
240+
search_paths.append(_get_windows_config_path())
204241

205-
windows_config_path = os.path.join(os.getenv('APPDATA', ''),
206-
'gcloud', 'configurations',
207-
'config_default')
208-
search_paths.append(windows_config_path)
209242
config = configparser.RawConfigParser()
210243
config.read(search_paths)
211244

212-
if config.has_section('core'):
213-
return config.get('core', 'project')
245+
if config.has_section(_GCLOUD_CONFIG_SECTION):
246+
return config.get(_GCLOUD_CONFIG_SECTION, _GCLOUD_CONFIG_KEY)
214247

215248

216249
def _compute_engine_id():

unit_tests/test__helpers.py

Lines changed: 118 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -149,98 +149,150 @@ def test_value_set(self):
149149
self.assertEqual(dataset_id, APP_ENGINE_ID)
150150

151151

152-
class Test__get_credentials_file_project_id(unittest.TestCase):
152+
class Test__file_project_id(unittest.TestCase):
153153

154154
def _callFUT(self):
155155
from gcloud._helpers import _file_project_id
156156
return _file_project_id()
157157

158-
def setUp(self):
159-
self.old_env = os.environ.get('GOOGLE_APPLICATION_CREDENTIALS')
160-
161-
def tearDown(self):
162-
if (not self.old_env and
163-
'GOOGLE_APPLICATION_CREDENTIALS' in os.environ):
164-
del os.environ['GOOGLE_APPLICATION_CREDENTIALS']
165-
166158
def test_success(self):
159+
import os
160+
from gcloud.environment_vars import CREDENTIALS
161+
from unit_tests._testing import _Monkey
167162
from unit_tests._testing import _NamedTemporaryFile
168163

164+
project_id = 'test-project-id'
165+
payload = '{"%s":"%s"}' % ('project_id', project_id)
169166
with _NamedTemporaryFile() as temp:
170-
with open(temp.name, mode='w') as creds_file:
171-
creds_file.write('{"project_id": "test-project-id"}')
172-
creds_file.seek(0)
173-
os.environ['GOOGLE_APPLICATION_CREDENTIALS'] = creds_file.name
167+
with open(temp.name, 'w') as creds_file:
168+
creds_file.write(payload)
169+
170+
environ = {CREDENTIALS: temp.name}
171+
with _Monkey(os, getenv=environ.get):
172+
result = self._callFUT()
173+
174+
self.assertEqual(result, project_id)
175+
176+
def test_no_environment_variable_set(self):
177+
import os
178+
from unit_tests._testing import _Monkey
179+
180+
environ = {}
181+
with _Monkey(os, getenv=environ.get):
182+
result = self._callFUT()
183+
184+
self.assertIsNone(result)
174185

175-
self.assertEqual('test-project-id', self._callFUT())
176186

177-
def test_no_environment(self):
178-
self.assertEqual(None, self._callFUT())
187+
class Test__get_nix_config_path(unittest.TestCase):
179188

189+
def _callFUT(self):
190+
from gcloud._helpers import _get_nix_config_path
191+
return _get_nix_config_path()
192+
193+
def test_it(self):
194+
import os
195+
from gcloud import _helpers as MUT
196+
from unit_tests._testing import _Monkey
197+
198+
user_root = 'a'
199+
config_file = 'b'
200+
with _Monkey(MUT, _USER_ROOT=user_root,
201+
_GCLOUD_CONFIG_FILE=config_file):
202+
result = self._callFUT()
180203

181-
class Test__get_default_service_project_id(unittest.TestCase):
182-
config_path = os.path.join('.config', 'gcloud', 'configurations')
183-
config_file = 'config_default'
184-
temp_APPDATA = ''
204+
expected = os.path.join(user_root, '.config', config_file)
205+
self.assertEqual(result, expected)
206+
207+
208+
class Test__get_windows_config_path(unittest.TestCase):
209+
210+
def _callFUT(self):
211+
from gcloud._helpers import _get_windows_config_path
212+
return _get_windows_config_path()
213+
214+
def test_it(self):
215+
import os
216+
from gcloud import _helpers as MUT
217+
from unit_tests._testing import _Monkey
185218

186-
def setUp(self):
187-
import tempfile
219+
appdata_dir = 'a'
220+
environ = {'APPDATA': appdata_dir}
221+
config_file = 'b'
222+
with _Monkey(os, getenv=environ.get):
223+
with _Monkey(MUT, _GCLOUD_CONFIG_FILE=config_file):
224+
result = self._callFUT()
188225

189-
self.temp_config_path = tempfile.mkdtemp()
190-
self.temp_APPDATA = os.getenv('APPDATA')
191-
if self.temp_APPDATA: # pragma: NO COVER Windows
192-
os.environ['APPDATA'] = self.temp_config_path
226+
expected = os.path.join(appdata_dir, config_file)
227+
self.assertEqual(result, expected)
193228

194-
self.config_path = os.path.join(os.getenv('APPDATA', '~/.config'),
195-
'gcloud', 'configurations')
196-
conf_path = os.path.join(self.temp_config_path, self.config_path)
197-
os.makedirs(conf_path)
198-
self.temp_config_file = os.path.join(conf_path, self.config_file)
199229

200-
with open(self.temp_config_file, 'w') as conf_file:
201-
conf_file.write('[core]\nproject = test-project-id')
230+
class Test__default_service_project_id(unittest.TestCase):
202231

203-
def tearDown(self):
204-
import shutil
205-
if os.path.exists(self.temp_config_path):
206-
shutil.rmtree(self.temp_config_path)
207-
if self.temp_APPDATA: # pragma: NO COVER Windows
208-
os.environ['APPDATA'] = self.temp_APPDATA
232+
CONFIG_TEMPLATE = '[%s]\n%s = %s\n'
209233

210-
def _callFUT(self, project_id=None):
234+
def _callFUT(self):
211235
from gcloud._helpers import _default_service_project_id
236+
return _default_service_project_id()
237+
238+
def test_nix(self):
239+
import os
240+
from gcloud import _helpers as MUT
212241
from unit_tests._testing import _Monkey
242+
from unit_tests._testing import _NamedTemporaryFile
213243

214-
def mock_expanduser(path=None):
215-
if project_id and path:
216-
__import__('pwd') # Simulate actual expanduser imports.
217-
return self.temp_config_file
218-
return ''
244+
project_id = 'test-project-id'
245+
with _NamedTemporaryFile() as temp:
246+
config_value = self.CONFIG_TEMPLATE % (
247+
MUT._GCLOUD_CONFIG_SECTION,
248+
MUT._GCLOUD_CONFIG_KEY, project_id)
249+
with open(temp.name, 'w') as config_file:
250+
config_file.write(config_value)
219251

220-
with _Monkey(os.path, expanduser=mock_expanduser):
221-
return _default_service_project_id()
252+
def mock_get_path():
253+
return temp.name
222254

223-
def test_read_from_cli_info(self):
224-
project_id = self._callFUT('test-project-id')
225-
self.assertEqual('test-project-id', project_id)
255+
with _Monkey(os, name='not-nt'):
256+
with _Monkey(MUT, _get_nix_config_path=mock_get_path,
257+
_USER_ROOT='not-None'):
258+
result = self._callFUT()
226259

227-
def test_gae_without_expanduser(self):
228-
import sys
229-
import shutil
230-
shutil.rmtree(self.temp_config_path)
260+
self.assertEqual(result, project_id)
231261

232-
try:
233-
sys.modules['pwd'] = None # Blocks pwd from being imported.
234-
project_id = self._callFUT('test-project-id')
235-
self.assertEqual(None, project_id)
236-
finally:
237-
del sys.modules['pwd'] # Unblocks importing of pwd.
238-
239-
def test_info_value_not_present(self):
240-
import shutil
241-
shutil.rmtree(self.temp_config_path)
242-
project_id = self._callFUT()
243-
self.assertEqual(None, project_id)
262+
def test_windows(self):
263+
import os
264+
from gcloud import _helpers as MUT
265+
from unit_tests._testing import _Monkey
266+
from unit_tests._testing import _NamedTemporaryFile
267+
268+
project_id = 'test-project-id'
269+
with _NamedTemporaryFile() as temp:
270+
config_value = self.CONFIG_TEMPLATE % (
271+
MUT._GCLOUD_CONFIG_SECTION,
272+
MUT._GCLOUD_CONFIG_KEY, project_id)
273+
with open(temp.name, 'w') as config_file:
274+
config_file.write(config_value)
275+
276+
def mock_get_path():
277+
return temp.name
278+
279+
with _Monkey(os, name='nt'):
280+
with _Monkey(MUT, _get_windows_config_path=mock_get_path,
281+
_USER_ROOT=None):
282+
result = self._callFUT()
283+
284+
self.assertEqual(result, project_id)
285+
286+
def test_gae(self):
287+
import os
288+
from gcloud import _helpers as MUT
289+
from unit_tests._testing import _Monkey
290+
291+
with _Monkey(os, name='not-nt'):
292+
with _Monkey(MUT, _USER_ROOT=None):
293+
result = self._callFUT()
294+
295+
self.assertIsNone(result)
244296

245297

246298
class Test__compute_engine_id(unittest.TestCase):

0 commit comments

Comments
 (0)