blob: f2de20dd35ff5531c93fea284a55eed6a760438d [file] [log] [blame]
'''
Some base test cases that do environment handling for you
'''
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import logging
import os
import warnings
import httpbin
import tsqa.endpoint
import tsqa.environment
import tsqa.configs
import tsqa.utils
unittest = tsqa.utils.import_unittest()
class BaseEnvironmentCase(unittest.TestCase):
'''
This class will:
- get a unique environment (using getEnv())
- verify that the env is valid for the test (using verifyEnv())
- create wrappers for ATS configs available in self.configs
- setup the environment (setUpEnv())
- write out the configs
- start the environment (environment.start())
'''
def run(self, result=None):
unittest.TestCase.run(self, result)
# we want to keep track of failures at a class level-- not instance level
self.__class__.__successful &= result.result.wasSuccessful()
@classmethod
def setUpClass(cls):
# call parent constructor
super(BaseEnvironmentCase, cls).setUpClass()
# get a logger
cls.log = logging.getLogger(__name__)
# get an environment
cls.environment = cls.getEnv()
cls.verifyEnv()
# TODO: better... I dont think this output is captured in each test run
logging.info('Environment prefix is {0}'.format(cls.environment.layout.prefix))
cfg_dir = os.path.join(cls.environment.layout.prefix, 'etc', 'trafficserver')
# create a bunch of config objects that people can access/modify
# classes that override our default config naming
config_classes = {'records.config': tsqa.configs.RecordsConfig}
# create a mapping of config-name -> config-obj
cls.configs = {}
for name in os.listdir(cls.environment.layout.sysconfdir):
path = os.path.join(cls.environment.layout.sysconfdir, name)
if os.path.isfile(path):
cls.configs[name] = config_classes.get(name, tsqa.configs.Config)(path)
# call env setup, so people can change configs etc
cls.setUpEnv(cls.environment)
for cfg in cls.configs.itervalues():
cfg.write()
# start ATS
cls.environment.start()
# we assume the tests passed
cls.__successful = True
@classmethod
def verifyEnv(cls):
pass
@classmethod
def getEnv(cls):
raise NotImplementedError()
@classmethod
def setUpEnv(cls, env):
'''
This funciton is responsible for setting up the environment for this fixture
This includes everything pre-daemon start (configs, certs, etc.)
'''
pass
@classmethod
def tearDownClass(cls):
if not cls.environment.running():
raise Exception('ATS died during the test run')
# stop ATS
cls.environment.stop()
# call parent destructor
super(BaseEnvironmentCase, cls).tearDownClass()
# if the test was successful, tear down the env
if cls.__successful:
cls.environment.destroy() # this will tear down any processes that we started
# Some helpful properties
@property
def proxies(self):
'''
Return a dict of schema -> proxy. This is primarily used for requests
'''
# TODO: create a better dict by parsing the config-- to handle http/https ports in the string
return {'http': 'http://127.0.0.1:{0}'.format(self.configs['records.config']['CONFIG']['proxy.config.http.server_ports'])}
class EnvironmentFactoryCase(BaseEnvironmentCase):
'''TestCase that can build its own trafficserver using EnvironmentFactory
'''
# TODO: better naming??
environment_factory = {'configure': None,
'env': None,
}
@classmethod
def getEnv(cls):
'''
This function is responsible for returning an environment. The default
is to build ATS and return a copy of an environment
'''
SOURCE_DIR = os.getenv('TSQA_SRC_DIR', '~/trafficserver')
TMP_DIR = os.getenv('TSQA_TMP_DIR','/tmp/tsqa')
ef = tsqa.environment.EnvironmentFactory(SOURCE_DIR, os.path.join(TMP_DIR, 'base_envs'))
return ef.get_environment(cls.environment_factory['configure'], cls.environment_factory['env'])
# TODO: deprecation warning? The naming for this should really be the alternate
class EnvironmentCase(EnvironmentFactoryCase):
pass
class CloneEnvironmentCase(BaseEnvironmentCase):
# dict of k/v that must exist in the feature list of traffic_layout
feature_requirements = {}
@classmethod
def verifyEnv(cls):
# verify that features requirements are met
env_features = cls.environment.features()
for k, v in cls.feature_requirements.iteritems():
if k not in env_features or env_features[k] != v:
# TODO: better error
raise unittest.SkipTest(e)
@classmethod
def getEnv(cls):
'''Clone an existing environment at `TSQA_ATS_ROOT`
'''
# TODO: better default? Or no default?
# create a layout
layout = tsqa.environment.Layout(
os.path.expanduser(os.getenv('TSQA_ATS_ROOT'))
)
# return an environment cloned from that layout
ret = tsqa.environment.Environment()
ret.clone(layout=layout)
return ret
class DynamicHTTPEndpointCase(unittest.TestCase):
'''
This class will set up a dynamic http endpoint that is local to this class
'''
endpoint_port = 0
@classmethod
def setUpClass(cls):
# get a logger
cls.log = logging.getLogger(__name__)
cls.http_endpoint = tsqa.endpoint.DynamicHTTPEndpoint(port=cls.endpoint_port)
cls.http_endpoint.start()
cls.http_endpoint.ready.wait()
if cls.http_endpoint.error is not None:
raise unittest.SkipTest('Error starting DynamicHTTPEndpoint: {0}'.format(cls.http_endpoint.error))
# Do this last, so we can get our stuff registered
# call parent constructor
super(DynamicHTTPEndpointCase, cls).setUpClass()
def endpoint_url(self, path=''):
'''
Get the url for the local dynamic endpoint given a path
'''
return self.http_endpoint.url(path)
class HTTPBinCase(unittest.TestCase):
'''
This class will set up httpbin which is local to this class
'''
@classmethod
def setUpClass(cls):
# get a logger
cls.log = logging.getLogger(__name__)
cls.http_endpoint = tsqa.endpoint.TrackingWSGIServer(httpbin.app)
cls.http_endpoint.start()
cls.http_endpoint.ready.wait()
# create local requester object
cls.track_requests = tsqa.endpoint.TrackingRequests(cls.http_endpoint)
# Do this last, so we can get our stuff registered
# call parent constructor
super(HTTPBinCase, cls).setUpClass()
def endpoint_url(self, path=''):
'''
Get the url for the local dynamic endpoint given a path
'''
warnings.warn(('"endpoint_url" has been deprecated. The same function is '
'available under "http_endpoint.url"'))
if path and not path.startswith('/'):
path = '/' + path
return 'http://127.0.0.1:{0}{1}'.format(self.http_endpoint.address[1],
path)