Skip to content

Commit 0a7bd23

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 0a7bd23

File tree

4 files changed

+166
-81
lines changed

4 files changed

+166
-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: 111 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -149,98 +149,143 @@ 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+
from gcloud.environment_vars import CREDENTIALS
160+
from unit_tests._testing import _Monkey
167161
from unit_tests._testing import _NamedTemporaryFile
168162

163+
project_id = 'test-project-id'
164+
payload = '{"%s":"%s"}' % ('project_id', project_id)
169165
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
166+
with open(temp.name, 'w') as creds_file:
167+
creds_file.write(payload)
168+
169+
environ = {CREDENTIALS: temp.name}
170+
with _Monkey(os, getenv=environ.get):
171+
result = self._callFUT()
172+
173+
self.assertEqual(result, project_id)
174+
175+
def test_no_environment_variable_set(self):
176+
from unit_tests._testing import _Monkey
177+
178+
environ = {}
179+
with _Monkey(os, getenv=environ.get):
180+
result = self._callFUT()
181+
182+
self.assertIsNone(result)
174183

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

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

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

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 = ''
201+
expected = os.path.join(user_root, '.config', config_file)
202+
self.assertEqual(result, expected)
203+
204+
205+
class Test__get_windows_config_path(unittest.TestCase):
206+
207+
def _callFUT(self):
208+
from gcloud._helpers import _get_windows_config_path
209+
return _get_windows_config_path()
210+
211+
def test_it(self):
212+
from gcloud import _helpers as MUT
213+
from unit_tests._testing import _Monkey
185214

186-
def setUp(self):
187-
import tempfile
215+
appdata_dir = 'a'
216+
environ = {'APPDATA': appdata_dir}
217+
config_file = 'b'
218+
with _Monkey(os, getenv=environ.get):
219+
with _Monkey(MUT, _GCLOUD_CONFIG_FILE=config_file):
220+
result = self._callFUT()
188221

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
222+
expected = os.path.join(appdata_dir, config_file)
223+
self.assertEqual(result, expected)
193224

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)
199225

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

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
228+
CONFIG_TEMPLATE = '[%s]\n%s = %s\n'
209229

210-
def _callFUT(self, project_id=None):
230+
def _callFUT(self):
211231
from gcloud._helpers import _default_service_project_id
232+
return _default_service_project_id()
233+
234+
def test_nix(self):
235+
from gcloud import _helpers as MUT
212236
from unit_tests._testing import _Monkey
237+
from unit_tests._testing import _NamedTemporaryFile
213238

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 ''
239+
project_id = 'test-project-id'
240+
with _NamedTemporaryFile() as temp:
241+
config_value = self.CONFIG_TEMPLATE % (
242+
MUT._GCLOUD_CONFIG_SECTION,
243+
MUT._GCLOUD_CONFIG_KEY, project_id)
244+
with open(temp.name, 'w') as config_file:
245+
config_file.write(config_value)
219246

220-
with _Monkey(os.path, expanduser=mock_expanduser):
221-
return _default_service_project_id()
247+
def mock_get_path():
248+
return temp.name
222249

223-
def test_read_from_cli_info(self):
224-
project_id = self._callFUT('test-project-id')
225-
self.assertEqual('test-project-id', project_id)
250+
with _Monkey(os, name='not-nt'):
251+
with _Monkey(MUT, _get_nix_config_path=mock_get_path,
252+
_USER_ROOT='not-None'):
253+
result = self._callFUT()
226254

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

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)
257+
def test_windows(self):
258+
from gcloud import _helpers as MUT
259+
from unit_tests._testing import _Monkey
260+
from unit_tests._testing import _NamedTemporaryFile
261+
262+
project_id = 'test-project-id'
263+
with _NamedTemporaryFile() as temp:
264+
config_value = self.CONFIG_TEMPLATE % (
265+
MUT._GCLOUD_CONFIG_SECTION,
266+
MUT._GCLOUD_CONFIG_KEY, project_id)
267+
with open(temp.name, 'w') as config_file:
268+
config_file.write(config_value)
269+
270+
def mock_get_path():
271+
return temp.name
272+
273+
with _Monkey(os, name='nt'):
274+
with _Monkey(MUT, _get_windows_config_path=mock_get_path,
275+
_USER_ROOT=None):
276+
result = self._callFUT()
277+
278+
self.assertEqual(result, project_id)
279+
280+
def test_gae(self):
281+
from gcloud import _helpers as MUT
282+
from unit_tests._testing import _Monkey
283+
284+
with _Monkey(os, name='not-nt'):
285+
with _Monkey(MUT, _USER_ROOT=None):
286+
result = self._callFUT()
287+
288+
self.assertIsNone(result)
244289

245290

246291
class Test__compute_engine_id(unittest.TestCase):

0 commit comments

Comments
 (0)