PageRenderTime 45ms CodeModel.GetById 19ms app.highlight 20ms RepoModel.GetById 1ms app.codeStats 0ms

/gdata/test_config.py

http://radioappz.googlecode.com/
Python | 403 lines | 362 code | 8 blank | 33 comment | 31 complexity | 7fbcc3e60209268332732a8255239689 MD5 | raw file
  1#!/usr/bin/env python
  2
  3# Copyright (C) 2009 Google Inc.
  4#
  5# Licensed under the Apache License, Version 2.0 (the "License");
  6# you may not use this file except in compliance with the License.
  7# You may obtain a copy of the License at
  8#
  9#      http://www.apache.org/licenses/LICENSE-2.0
 10#
 11# Unless required by applicable law or agreed to in writing, software
 12# distributed under the License is distributed on an "AS IS" BASIS,
 13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 14# See the License for the specific language governing permissions and
 15# limitations under the License.
 16
 17
 18import sys
 19import unittest
 20import getpass
 21import inspect
 22import atom.mock_http_core
 23import gdata.gauth
 24
 25
 26"""Loads configuration for tests which connect to Google servers.
 27
 28Settings used in tests are stored in a ConfigCollection instance in this
 29module called options. If your test needs to get a test related setting,
 30use
 31
 32import gdata.test_config
 33option_value = gdata.test_config.options.get_value('x')
 34
 35The above will check the command line for an '--x' argument, and if not
 36found will either use the default value for 'x' or prompt the user to enter
 37one. 
 38
 39Your test can override the value specified by the user by performing:
 40
 41gdata.test_config.options.set_value('x', 'y')
 42
 43If your test uses a new option which you would like to allow the user to
 44specify on the command line or via a prompt, you can use the register_option
 45method as follows:
 46
 47gdata.test_config.options.register(
 48    'option_name', 'Prompt shown to the user', secret=False #As for password.
 49    'This is the description of the option, shown when help is requested.',
 50    'default value, provide only if you do not want the user to be prompted')
 51"""
 52
 53
 54class Option(object):
 55
 56  def __init__(self, name, prompt, secret=False, description=None, default=None):
 57    self.name = name
 58    self.prompt = prompt
 59    self.secret = secret
 60    self.description = description
 61    self.default = default
 62
 63  def get(self):
 64    value = self.default
 65    # Check for a command line parameter.
 66    for i in xrange(len(sys.argv)):
 67      if sys.argv[i].startswith('--%s=' % self.name):
 68        value = sys.argv[i].split('=')[1]
 69      elif sys.argv[i] == '--%s' % self.name:
 70        value = sys.argv[i + 1]
 71    # If the param was not on the command line, ask the user to input the
 72    # value.
 73    # In order for this to prompt the user, the default value for the option
 74    # must be None.
 75    if value is None:
 76      prompt = '%s: ' % self.prompt
 77      if self.secret:
 78        value = getpass.getpass(prompt)
 79      else:
 80        print 'You can specify this on the command line using --%s' % self.name
 81        value = raw_input(prompt)
 82    return value
 83
 84
 85class ConfigCollection(object):
 86
 87  def __init__(self, options=None):
 88    self.options = options or {}
 89    self.values = {}
 90
 91  def register_option(self, option):
 92    self.options[option.name] = option
 93
 94  def register(self, *args, **kwargs):
 95    self.register_option(Option(*args, **kwargs))
 96
 97  def get_value(self, option_name):
 98    if option_name in self.values:
 99      return self.values[option_name]
100    value = self.options[option_name].get()
101    if value is not None:
102      self.values[option_name] = value
103    return value
104
105  def set_value(self, option_name, value):
106    self.values[option_name] = value
107
108  def render_usage(self):
109    message_parts = []
110    for opt_name, option in self.options.iteritems():
111      message_parts.append('--%s: %s' % (opt_name, option.description))
112    return '\n'.join(message_parts)
113
114
115options = ConfigCollection()
116
117
118# Register the default options.
119options.register(
120    'username',
121    'Please enter the email address of your test account', 
122    description=('The email address you want to sign in with. '
123                 'Make sure this is a test account as these tests may edit'
124                 ' or delete data.'))
125options.register(
126    'password',
127    'Please enter the password for your test account',
128    secret=True, description='The test account password.')
129options.register(
130    'clearcache',
131    'Delete cached data? (enter true or false)',
132    description=('If set to true, any temporary files which cache test'
133                 ' requests and responses will be deleted.'),
134    default='true')
135options.register(
136    'savecache',
137    'Save requests and responses in a temporary file? (enter true or false)',
138    description=('If set to true, requests to the server and responses will'
139                 ' be saved in temporary files.'),
140    default='false')
141options.register(
142    'runlive',
143    'Run the live tests which contact the server? (enter true or false)',
144    description=('If set to true, the tests will make real HTTP requests to'
145                 ' the servers. This slows down test execution and may'
146                 ' modify the users data, be sure to use a test account.'),
147    default='true')
148options.register(
149    'ssl',
150    'Run the live tests over SSL (enter true or false)',
151    description='If set to true, all tests will be performed over HTTPS (SSL)',
152    default='false')
153options.register(
154    'appsusername',
155    'Please enter the email address of your test Apps domain account', 
156    description=('The email address you want to sign in with. '
157                 'Make sure this is a test account on your Apps domain as '
158                 'these tests may edit or delete data.'))
159options.register(
160    'appspassword',
161    'Please enter the password for your test Apps domain account',
162    secret=True, description='The test Apps account password.')
163
164# Other options which may be used if needed.
165BLOG_ID_OPTION = Option(
166    'blogid',
167    'Please enter the ID of your test blog',
168    description=('The blog ID for the blog which should have test posts added'
169                 ' to it. Example 7682659670455539811'))
170TEST_IMAGE_LOCATION_OPTION = Option(
171    'imgpath',
172    'Please enter the full path to a test image to upload',
173    description=('This test image will be uploaded to a service which'
174                 ' accepts a media file, it must be a jpeg.'))
175SPREADSHEET_ID_OPTION = Option(
176    'spreadsheetid',
177    'Please enter the ID of a spreadsheet to use in these tests',
178    description=('The spreadsheet ID for the spreadsheet which should be'
179                 ' modified by theses tests.'))
180APPS_DOMAIN_OPTION = Option(
181    'appsdomain',
182    'Please enter your Google Apps domain',
183    description=('The domain the Google Apps is hosted on or leave blank'
184                 ' if n/a'))
185SITES_NAME_OPTION = Option(
186    'sitename',
187    'Please enter name of your Google Site',
188    description='The webspace name of the Site found in its URL.')
189PROJECT_NAME_OPTION = Option(
190    'project_name',
191    'Please enter the name of your project hosting project',
192    description=('The name of the project which should have test issues added'
193                 ' to it. Example gdata-python-client'))
194ISSUE_ASSIGNEE_OPTION = Option(
195    'issue_assignee',
196    'Enter the email address of the target owner of the updated issue.',
197    description=('The email address of the user a created issue\'s owner will '
198                 ' become. Example testuser2@gmail.com'))
199GA_TABLE_ID = Option(
200    'table_id',
201    'Enter the Table ID of the Google Analytics profile to test',
202    description=('The Table ID of the Google Analytics profile to test.'
203                 ' Example ga:1174'))
204
205# Functions to inject a cachable HTTP client into a service client.
206def configure_client(client, case_name, service_name, use_apps_auth=False):
207  """Sets up a mock client which will reuse a saved session.
208
209  Should be called during setUp of each unit test.
210
211  Handles authentication to allow the GDClient to make requests which
212  require an auth header.
213
214  Args:
215    client: a gdata.GDClient whose http_client member should be replaced
216            with a atom.mock_http_core.MockHttpClient so that repeated
217            executions can used cached responses instead of contacting
218            the server.
219    case_name: str The name of the test case class. Examples: 'BloggerTest',
220               'ContactsTest'. Used to save a session
221               for the ClientLogin auth token request, so the case_name
222               should be reused if and only if the same username, password,
223               and service are being used.
224    service_name: str The service name as used for ClientLogin to identify
225                  the Google Data API being accessed. Example: 'blogger',
226                  'wise', etc.
227    use_apps_auth: bool (optional) If set to True, use appsusername and
228                   appspassword command-line args instead of username and
229                   password respectively.
230  """
231  # Use a mock HTTP client which will record and replay the HTTP traffic
232  # from these tests.
233  client.http_client = atom.mock_http_core.MockHttpClient()
234  client.http_client.cache_case_name = case_name
235  # Getting the auth token only needs to be done once in the course of test
236  # runs.
237  auth_token_key = '%s_auth_token' % service_name
238  if (auth_token_key not in options.values
239      and options.get_value('runlive') == 'true'):
240    client.http_client.cache_test_name = 'client_login'
241    cache_name = client.http_client.get_cache_file_name()
242    if options.get_value('clearcache') == 'true':
243      client.http_client.delete_session(cache_name)
244    client.http_client.use_cached_session(cache_name)
245    if not use_apps_auth:
246      username = options.get_value('username')
247      password = options.get_value('password')
248    else:
249      username = options.get_value('appsusername')
250      password = options.get_value('appspassword')
251    auth_token = client.request_client_login_token(username, password,
252        case_name, service=service_name)
253    options.values[auth_token_key] = gdata.gauth.token_to_blob(auth_token)
254    client.http_client.close_session()
255  # Allow a config auth_token of False to prevent the client's auth header
256  # from being modified.
257  if auth_token_key in options.values:
258    client.auth_token = gdata.gauth.token_from_blob(
259        options.values[auth_token_key])
260
261
262def configure_cache(client, test_name):
263  """Loads or begins a cached session to record HTTP traffic.
264
265  Should be called at the beginning of each test method.
266
267  Args:
268    client: a gdata.GDClient whose http_client member has been replaced
269            with a atom.mock_http_core.MockHttpClient so that repeated
270            executions can used cached responses instead of contacting
271            the server.
272    test_name: str The name of this test method. Examples: 
273               'TestClass.test_x_works', 'TestClass.test_crud_operations'.
274               This is used to name the recording of the HTTP requests and
275               responses, so it should be unique to each test method in the
276               test case.
277  """
278  # Auth token is obtained in configure_client which is called as part of
279  # setUp.
280  client.http_client.cache_test_name = test_name
281  cache_name = client.http_client.get_cache_file_name()
282  if options.get_value('clearcache') == 'true':
283    client.http_client.delete_session(cache_name)
284  client.http_client.use_cached_session(cache_name)
285
286
287def close_client(client):
288  """Saves the recoded responses to a temp file if the config file allows.
289  
290  This should be called in the unit test's tearDown method.
291
292  Checks to see if the 'savecache' option is set to 'true', to make sure we
293  only save sessions to repeat if the user desires.
294  """
295  if client and options.get_value('savecache') == 'true':
296    # If this was a live request, save the recording.
297    client.http_client.close_session()
298
299
300def configure_service(service, case_name, service_name):
301  """Sets up a mock GDataService v1 client to reuse recorded sessions.
302  
303  Should be called during setUp of each unit test. This is a duplicate of
304  configure_client, modified to handle old v1 service classes.
305  """
306  service.http_client.v2_http_client = atom.mock_http_core.MockHttpClient()
307  service.http_client.v2_http_client.cache_case_name = case_name
308  # Getting the auth token only needs to be done once in the course of test
309  # runs.
310  auth_token_key = 'service_%s_auth_token' % service_name
311  if (auth_token_key not in options.values
312      and options.get_value('runlive') == 'true'):
313    service.http_client.v2_http_client.cache_test_name = 'client_login'
314    cache_name = service.http_client.v2_http_client.get_cache_file_name()
315    if options.get_value('clearcache') == 'true':
316      service.http_client.v2_http_client.delete_session(cache_name)
317    service.http_client.v2_http_client.use_cached_session(cache_name)
318    service.ClientLogin(options.get_value('username'),
319                        options.get_value('password'),
320                        service=service_name, source=case_name)
321    options.values[auth_token_key] = service.GetClientLoginToken()
322    service.http_client.v2_http_client.close_session()
323  if auth_token_key in options.values:
324    service.SetClientLoginToken(options.values[auth_token_key])
325
326
327def configure_service_cache(service, test_name):
328  """Loads or starts a session recording for a v1 Service object.
329  
330  Duplicates the behavior of configure_cache, but the target for this
331  function is a v1 Service object instead of a v2 Client.
332  """
333  service.http_client.v2_http_client.cache_test_name = test_name
334  cache_name = service.http_client.v2_http_client.get_cache_file_name()
335  if options.get_value('clearcache') == 'true':
336    service.http_client.v2_http_client.delete_session(cache_name)
337  service.http_client.v2_http_client.use_cached_session(cache_name)
338
339
340def close_service(service):
341  if service and options.get_value('savecache') == 'true':
342    # If this was a live request, save the recording.
343    service.http_client.v2_http_client.close_session()
344
345
346def build_suite(classes):
347  """Creates a TestSuite for all unit test classes in the list.
348  
349  Assumes that each of the classes in the list has unit test methods which
350  begin with 'test'. Calls unittest.makeSuite.
351
352  Returns: 
353    A new unittest.TestSuite containing a test suite for all classes.   
354  """
355  suites = [unittest.makeSuite(a_class, 'test') for a_class in classes]
356  return unittest.TestSuite(suites)
357
358
359def check_data_classes(test, classes):
360  import inspect
361  for data_class in classes:
362    test.assert_(data_class.__doc__ is not None,
363                 'The class %s should have a docstring' % data_class)
364    if hasattr(data_class, '_qname'):
365      qname_versions = None
366      if isinstance(data_class._qname, tuple):
367        qname_versions = data_class._qname
368      else:
369        qname_versions = (data_class._qname,)
370      for versioned_qname in qname_versions:
371        test.assert_(isinstance(versioned_qname, str),
372                     'The class %s has a non-string _qname' % data_class)
373        test.assert_(not versioned_qname.endswith('}'), 
374                     'The _qname for class %s is only a namespace' % (
375                         data_class))
376
377    for attribute_name, value in data_class.__dict__.iteritems():
378      # Ignore all elements that start with _ (private members)
379      if not attribute_name.startswith('_'):
380        try:
381          if not (isinstance(value, str) or inspect.isfunction(value) 
382              or (isinstance(value, list)
383                  and issubclass(value[0], atom.core.XmlElement))
384              or type(value) == property # Allow properties.
385              or inspect.ismethod(value) # Allow methods. 
386              or issubclass(value, atom.core.XmlElement)):
387            test.fail(
388                'XmlElement member should have an attribute, XML class,'
389                ' or list of XML classes as attributes.')
390
391        except TypeError:
392          test.fail('Element %s in %s was of type %s' % (
393              attribute_name, data_class._qname, type(value)))
394
395
396def check_clients_with_auth(test, classes):
397  for client_class in classes:
398    test.assert_(hasattr(client_class, 'api_version'))
399    test.assert_(isinstance(client_class.auth_service, (str, unicode, int)))
400    test.assert_(hasattr(client_class, 'auth_service'))
401    test.assert_(isinstance(client_class.auth_service, (str, unicode)))
402    test.assert_(hasattr(client_class, 'auth_scopes'))
403    test.assert_(isinstance(client_class.auth_scopes, (list, tuple)))