Skip to content

Commit

Permalink
Adding tox support for AppVeyor.
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
dhermes committed Sep 2, 2016
1 parent 0fd323e commit 0a7bd23
Show file tree
Hide file tree
Showing 4 changed files with 166 additions and 81 deletions.
8 changes: 7 additions & 1 deletion appveyor.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,32 +19,38 @@ environment:
- PYTHON: "C:\\Python27"
PYTHON_VERSION: "2.7.11"
PYTHON_ARCH: "32"
TOX_ENV: "py27"

- PYTHON: "C:\\Python27-x64"
PYTHON_VERSION: "2.7.11"
PYTHON_ARCH: "64"
TOX_ENV: "py27"

# Python 3.4.4 is the latest Python 3.4 with a Windows installer
# Python 3.4.4 is the overall latest
# https://www.python.org/ftp/python/3.4.4/
- PYTHON: "C:\\Python34"
PYTHON_VERSION: "3.4.4"
PYTHON_ARCH: "32"
TOX_ENV: "py34"

- PYTHON: "C:\\Python34-x64"
PYTHON_VERSION: "3.4.4"
PYTHON_ARCH: "64"
TOX_ENV: "py34"

# Python 3.5.1 is the latest Python 3.5 with a Windows installer
# Python 3.5.1 is the overall latest
# https://www.python.org/ftp/python/3.5.1/
- PYTHON: "C:\\Python35"
PYTHON_VERSION: "3.5.1"
PYTHON_ARCH: "32"
TOX_ENV: "py35"

- PYTHON: "C:\\Python35-x64"
PYTHON_VERSION: "3.5.1"
PYTHON_ARCH: "64"
TOX_ENV: "py35"

install:
- ECHO "Filesystem root:"
Expand Down Expand Up @@ -83,7 +89,7 @@ build_script:
test_script:
- "%CMD_IN_ENV% pip list"
# Run the project tests
- "%CMD_IN_ENV% py.test"
- "%CMD_IN_ENV% tox -e %TOX_ENV%"

after_test:
# If tests are successful, create binary packages for the project.
Expand Down
1 change: 1 addition & 0 deletions appveyor/requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
# target Python version and architecture
wheel
pytest
tox
cryptography
grpcio >= 1.0rc1
grpc-google-pubsub-v1
Expand Down
61 changes: 47 additions & 14 deletions gcloud/_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,16 @@
(?P<nanos>\d{1,9}) # nanoseconds, maybe truncated
Z # Zulu
""", re.VERBOSE)
DEFAULT_CONFIGURATION_PATH = '~/.config/gcloud/configurations/config_default'
# NOTE: Catching this ImportError is a workaround for GAE not supporting the
# "pwd" module which is imported lazily when "expanduser" is called.
try:
_USER_ROOT = os.path.expanduser('~')
except ImportError: # pragma: NO COVER
_USER_ROOT = None
_GCLOUD_CONFIG_FILE = os.path.join(
'gcloud', 'configurations', 'config_default')
_GCLOUD_CONFIG_SECTION = 'core'
_GCLOUD_CONFIG_KEY = 'project'


class _LocalStack(Local):
Expand Down Expand Up @@ -171,10 +180,10 @@ def _app_engine_id():


def _file_project_id():
"""Gets the project id from the credentials file if one is available.
"""Gets the project ID from the credentials file if one is available.
:rtype: str or ``NoneType``
:returns: Project-ID from JSON credentials file if value exists,
:returns: Project ID from JSON credentials file if value exists,
else ``None``.
"""
credentials_file_path = os.getenv(CREDENTIALS)
Expand All @@ -185,9 +194,37 @@ def _file_project_id():
return credentials.get('project_id')


def _get_nix_config_path():
"""Get the ``gcloud`` CLI config path on *nix systems.
:rtype: str
:returns: The filename on a *nix system containing the CLI
config file.
"""
return os.path.join(_USER_ROOT, '.config', _GCLOUD_CONFIG_FILE)


def _get_windows_config_path():
"""Get the ``gcloud`` CLI config path on Windows systems.
:rtype: str
:returns: The filename on a Windows system containing the CLI
config file.
"""
appdata_dir = os.getenv('APPDATA', '')
return os.path.join(appdata_dir, _GCLOUD_CONFIG_FILE)


def _default_service_project_id():
"""Retrieves the project ID from the gcloud command line tool.
This assumes the ``.config`` directory is stored
- in ~/.config on *nix systems
- in the %APPDATA% directory on Windows systems
Additionally, the ${HOME} / "~" directory may not be present on Google
App Engine, so this may be conditionally ignored.
Files that cannot be opened with configparser are silently ignored; this is
designed so that you can specify a list of potential configuration file
locations.
Expand All @@ -196,21 +233,17 @@ def _default_service_project_id():
:returns: Project-ID from default configuration file else ``None``
"""
search_paths = []
# Workaround for GAE not supporting pwd which is used by expanduser.
try:
search_paths.append(os.path.expanduser(DEFAULT_CONFIGURATION_PATH))
except ImportError:
pass
if _USER_ROOT is not None:
search_paths.append(_get_nix_config_path())

if os.name == 'nt':
search_paths.append(_get_windows_config_path())

windows_config_path = os.path.join(os.getenv('APPDATA', ''),
'gcloud', 'configurations',
'config_default')
search_paths.append(windows_config_path)
config = configparser.RawConfigParser()
config.read(search_paths)

if config.has_section('core'):
return config.get('core', 'project')
if config.has_section(_GCLOUD_CONFIG_SECTION):
return config.get(_GCLOUD_CONFIG_SECTION, _GCLOUD_CONFIG_KEY)


def _compute_engine_id():
Expand Down
177 changes: 111 additions & 66 deletions unit_tests/test__helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -149,98 +149,143 @@ def test_value_set(self):
self.assertEqual(dataset_id, APP_ENGINE_ID)


class Test__get_credentials_file_project_id(unittest.TestCase):
class Test__file_project_id(unittest.TestCase):

def _callFUT(self):
from gcloud._helpers import _file_project_id
return _file_project_id()

def setUp(self):
self.old_env = os.environ.get('GOOGLE_APPLICATION_CREDENTIALS')

def tearDown(self):
if (not self.old_env and
'GOOGLE_APPLICATION_CREDENTIALS' in os.environ):
del os.environ['GOOGLE_APPLICATION_CREDENTIALS']

def test_success(self):
from gcloud.environment_vars import CREDENTIALS
from unit_tests._testing import _Monkey
from unit_tests._testing import _NamedTemporaryFile

project_id = 'test-project-id'
payload = '{"%s":"%s"}' % ('project_id', project_id)
with _NamedTemporaryFile() as temp:
with open(temp.name, mode='w') as creds_file:
creds_file.write('{"project_id": "test-project-id"}')
creds_file.seek(0)
os.environ['GOOGLE_APPLICATION_CREDENTIALS'] = creds_file.name
with open(temp.name, 'w') as creds_file:
creds_file.write(payload)

environ = {CREDENTIALS: temp.name}
with _Monkey(os, getenv=environ.get):
result = self._callFUT()

self.assertEqual(result, project_id)

def test_no_environment_variable_set(self):
from unit_tests._testing import _Monkey

environ = {}
with _Monkey(os, getenv=environ.get):
result = self._callFUT()

self.assertIsNone(result)

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

def test_no_environment(self):
self.assertEqual(None, self._callFUT())
class Test__get_nix_config_path(unittest.TestCase):

def _callFUT(self):
from gcloud._helpers import _get_nix_config_path
return _get_nix_config_path()

def test_it(self):
from gcloud import _helpers as MUT
from unit_tests._testing import _Monkey

user_root = 'a'
config_file = 'b'
with _Monkey(MUT, _USER_ROOT=user_root,
_GCLOUD_CONFIG_FILE=config_file):
result = self._callFUT()

class Test__get_default_service_project_id(unittest.TestCase):
config_path = os.path.join('.config', 'gcloud', 'configurations')
config_file = 'config_default'
temp_APPDATA = ''
expected = os.path.join(user_root, '.config', config_file)
self.assertEqual(result, expected)


class Test__get_windows_config_path(unittest.TestCase):

def _callFUT(self):
from gcloud._helpers import _get_windows_config_path
return _get_windows_config_path()

def test_it(self):
from gcloud import _helpers as MUT
from unit_tests._testing import _Monkey

def setUp(self):
import tempfile
appdata_dir = 'a'
environ = {'APPDATA': appdata_dir}
config_file = 'b'
with _Monkey(os, getenv=environ.get):
with _Monkey(MUT, _GCLOUD_CONFIG_FILE=config_file):
result = self._callFUT()

self.temp_config_path = tempfile.mkdtemp()
self.temp_APPDATA = os.getenv('APPDATA')
if self.temp_APPDATA: # pragma: NO COVER Windows
os.environ['APPDATA'] = self.temp_config_path
expected = os.path.join(appdata_dir, config_file)
self.assertEqual(result, expected)

self.config_path = os.path.join(os.getenv('APPDATA', '~/.config'),
'gcloud', 'configurations')
conf_path = os.path.join(self.temp_config_path, self.config_path)
os.makedirs(conf_path)
self.temp_config_file = os.path.join(conf_path, self.config_file)

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

def tearDown(self):
import shutil
if os.path.exists(self.temp_config_path):
shutil.rmtree(self.temp_config_path)
if self.temp_APPDATA: # pragma: NO COVER Windows
os.environ['APPDATA'] = self.temp_APPDATA
CONFIG_TEMPLATE = '[%s]\n%s = %s\n'

def _callFUT(self, project_id=None):
def _callFUT(self):
from gcloud._helpers import _default_service_project_id
return _default_service_project_id()

def test_nix(self):
from gcloud import _helpers as MUT
from unit_tests._testing import _Monkey
from unit_tests._testing import _NamedTemporaryFile

def mock_expanduser(path=None):
if project_id and path:
__import__('pwd') # Simulate actual expanduser imports.
return self.temp_config_file
return ''
project_id = 'test-project-id'
with _NamedTemporaryFile() as temp:
config_value = self.CONFIG_TEMPLATE % (
MUT._GCLOUD_CONFIG_SECTION,
MUT._GCLOUD_CONFIG_KEY, project_id)
with open(temp.name, 'w') as config_file:
config_file.write(config_value)

with _Monkey(os.path, expanduser=mock_expanduser):
return _default_service_project_id()
def mock_get_path():
return temp.name

def test_read_from_cli_info(self):
project_id = self._callFUT('test-project-id')
self.assertEqual('test-project-id', project_id)
with _Monkey(os, name='not-nt'):
with _Monkey(MUT, _get_nix_config_path=mock_get_path,
_USER_ROOT='not-None'):
result = self._callFUT()

def test_gae_without_expanduser(self):
import sys
import shutil
shutil.rmtree(self.temp_config_path)
self.assertEqual(result, project_id)

try:
sys.modules['pwd'] = None # Blocks pwd from being imported.
project_id = self._callFUT('test-project-id')
self.assertEqual(None, project_id)
finally:
del sys.modules['pwd'] # Unblocks importing of pwd.

def test_info_value_not_present(self):
import shutil
shutil.rmtree(self.temp_config_path)
project_id = self._callFUT()
self.assertEqual(None, project_id)
def test_windows(self):
from gcloud import _helpers as MUT
from unit_tests._testing import _Monkey
from unit_tests._testing import _NamedTemporaryFile

project_id = 'test-project-id'
with _NamedTemporaryFile() as temp:
config_value = self.CONFIG_TEMPLATE % (
MUT._GCLOUD_CONFIG_SECTION,
MUT._GCLOUD_CONFIG_KEY, project_id)
with open(temp.name, 'w') as config_file:
config_file.write(config_value)

def mock_get_path():
return temp.name

with _Monkey(os, name='nt'):
with _Monkey(MUT, _get_windows_config_path=mock_get_path,
_USER_ROOT=None):
result = self._callFUT()

self.assertEqual(result, project_id)

def test_gae(self):
from gcloud import _helpers as MUT
from unit_tests._testing import _Monkey

with _Monkey(os, name='not-nt'):
with _Monkey(MUT, _USER_ROOT=None):
result = self._callFUT()

self.assertIsNone(result)


class Test__compute_engine_id(unittest.TestCase):
Expand Down

0 comments on commit 0a7bd23

Please sign in to comment.