2014-12-31 16:05:56 +00:00
|
|
|
# Copyright 2014, 2015 Red Hat
|
2014-12-15 13:04:08 +00:00
|
|
|
#
|
|
|
|
# Authors: Chris Dent <chdent@redhat.com>
|
|
|
|
#
|
|
|
|
# Licensed 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.
|
|
|
|
|
|
|
|
"""Generate HTTP tests from YAML files
|
|
|
|
|
|
|
|
Each HTTP request is its own TestCase and can be requested to be run in
|
|
|
|
isolation from other tests. If it is member of a sequence of requests,
|
|
|
|
prior requests will be run.
|
|
|
|
|
|
|
|
A sequence is represented by an ordered list in a single YAML file.
|
|
|
|
|
|
|
|
Each sequence becomes a TestSuite.
|
|
|
|
|
|
|
|
An entire directory of YAML files is a TestSuite of TestSuites.
|
|
|
|
"""
|
2014-10-29 16:05:26 +00:00
|
|
|
|
|
|
|
import glob
|
2014-12-27 18:50:05 +00:00
|
|
|
import inspect
|
2014-10-29 16:05:26 +00:00
|
|
|
import os
|
2014-12-15 13:04:08 +00:00
|
|
|
from unittest import suite
|
2014-12-15 21:55:14 +00:00
|
|
|
import uuid
|
2014-10-29 16:05:26 +00:00
|
|
|
|
2014-12-15 21:55:14 +00:00
|
|
|
import httplib2
|
|
|
|
import wsgi_intercept
|
|
|
|
from wsgi_intercept import httplib2_intercept
|
2014-10-30 15:11:47 +00:00
|
|
|
import yaml
|
2014-10-29 16:05:26 +00:00
|
|
|
|
2014-12-31 16:05:56 +00:00
|
|
|
from .suite import GabbiSuite
|
|
|
|
from .case import HTTPTestCase
|
2014-12-27 23:39:39 +00:00
|
|
|
|
2014-10-30 15:11:47 +00:00
|
|
|
|
2014-12-15 21:55:14 +00:00
|
|
|
# Empty test from which all others inherit
|
|
|
|
BASE_TEST = {
|
|
|
|
'name': '',
|
|
|
|
'desc': '',
|
2014-12-16 19:31:39 +00:00
|
|
|
'ssl': False,
|
2014-12-22 16:27:18 +00:00
|
|
|
'redirects': False,
|
2014-12-15 21:55:14 +00:00
|
|
|
'method': 'GET',
|
|
|
|
'url': '',
|
|
|
|
'status': '200',
|
|
|
|
'request_headers': {},
|
|
|
|
'response_headers': {},
|
2014-12-22 16:16:32 +00:00
|
|
|
'response_strings': None,
|
|
|
|
'response_json_paths': None,
|
2014-12-15 21:55:14 +00:00
|
|
|
'data': '',
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2014-12-15 13:33:00 +00:00
|
|
|
class TestBuilder(type):
|
|
|
|
"""Metaclass to munge a dynamically created test.
|
|
|
|
"""
|
|
|
|
|
|
|
|
required_attributes = {'has_run': False}
|
|
|
|
|
|
|
|
def __new__(mcs, name, bases, attributes):
|
|
|
|
attributes.update(mcs.required_attributes)
|
|
|
|
return type.__new__(mcs, name, bases, attributes)
|
|
|
|
|
|
|
|
|
2014-12-27 18:50:05 +00:00
|
|
|
def build_tests(path, loader, host=None, port=8001, intercept=None,
|
2014-12-27 23:39:39 +00:00
|
|
|
test_file_name=None, fixture_module=None):
|
2014-12-15 13:04:08 +00:00
|
|
|
"""Read YAML files from a directory to create tests.
|
|
|
|
|
|
|
|
Each YAML file represents an ordered sequence of HTTP requests.
|
|
|
|
"""
|
2014-12-12 16:51:27 +00:00
|
|
|
top_suite = suite.TestSuite()
|
2014-12-15 21:55:14 +00:00
|
|
|
http = httplib2.Http()
|
|
|
|
|
2014-12-27 18:50:05 +00:00
|
|
|
if test_file_name is None:
|
|
|
|
test_file_name = inspect.stack()[1]
|
|
|
|
test_file_name = os.path.splitext(os.path.basename(
|
|
|
|
test_file_name[1]))[0]
|
|
|
|
|
2014-12-15 21:55:14 +00:00
|
|
|
# Return an empty suite if we have no host to access, either via
|
|
|
|
# a real host or an intercept
|
|
|
|
if not host and not intercept:
|
|
|
|
return top_suite
|
|
|
|
|
|
|
|
if intercept:
|
|
|
|
host = install_intercept(intercept, port)
|
2014-10-29 16:05:26 +00:00
|
|
|
|
2014-12-27 20:20:32 +00:00
|
|
|
test_directory = path
|
2014-10-29 16:05:26 +00:00
|
|
|
path = '%s/*.yaml' % path
|
|
|
|
|
2014-12-18 13:51:07 +00:00
|
|
|
key_test = set(BASE_TEST.keys())
|
|
|
|
|
2014-10-29 16:05:26 +00:00
|
|
|
for test_file in glob.iglob(path):
|
2014-12-27 23:39:39 +00:00
|
|
|
file_suite = GabbiSuite()
|
2014-12-16 20:09:56 +00:00
|
|
|
test_yaml = load_yaml(test_file)
|
|
|
|
test_data = test_yaml['tests']
|
2014-12-27 23:39:39 +00:00
|
|
|
fixtures = test_yaml.get('fixtures', None)
|
2014-12-12 14:44:33 +00:00
|
|
|
test_base_name = os.path.splitext(os.path.basename(test_file))[0]
|
2014-12-15 13:04:08 +00:00
|
|
|
|
2014-12-22 15:04:21 +00:00
|
|
|
# Set defaults from BASE_TESTS the update those defaults
|
|
|
|
# which any defaults set in the YAML file.
|
|
|
|
base_test_data = dict(BASE_TEST)
|
|
|
|
base_test_data.update(test_yaml.get('defaults', {}))
|
|
|
|
|
2014-12-27 23:39:39 +00:00
|
|
|
fixture_classes = []
|
|
|
|
if fixtures and fixture_module:
|
|
|
|
for fixture_class in fixtures:
|
|
|
|
fixture_classes.append(getattr(fixture_module, fixture_class))
|
|
|
|
|
2014-12-12 14:44:33 +00:00
|
|
|
prior_test = None
|
2014-12-15 21:55:14 +00:00
|
|
|
for test_datum in test_data:
|
2014-12-22 15:04:21 +00:00
|
|
|
test = dict(base_test_data)
|
2014-12-15 21:55:14 +00:00
|
|
|
test.update(test_datum)
|
2014-12-27 18:50:05 +00:00
|
|
|
test_name = '%s_%s_%s' % (test_file_name,
|
|
|
|
test_base_name,
|
|
|
|
test['name'].lower().replace(' ', '_'))
|
2014-12-18 13:51:07 +00:00
|
|
|
if set(test.keys()) != key_test:
|
|
|
|
raise ValueError('Invalid Keys in test %s' % test_name)
|
2014-12-27 23:39:39 +00:00
|
|
|
|
2014-12-15 13:04:08 +00:00
|
|
|
# Use metaclasses to build a class of the necessary type
|
|
|
|
# with relevant arguments.
|
2014-12-15 13:14:39 +00:00
|
|
|
klass = TestBuilder(test_name, (HTTPTestCase,),
|
|
|
|
{'test_data': test,
|
2014-12-27 20:20:32 +00:00
|
|
|
'test_directory': test_directory,
|
2014-12-27 23:39:39 +00:00
|
|
|
'fixtures': fixture_classes,
|
2014-12-15 21:55:14 +00:00
|
|
|
'http': http,
|
|
|
|
'host': host,
|
|
|
|
'port': port,
|
2014-12-15 13:14:39 +00:00
|
|
|
'prior': prior_test})
|
2014-12-15 13:04:08 +00:00
|
|
|
|
2014-12-12 14:44:33 +00:00
|
|
|
tests = loader.loadTestsFromTestCase(klass)
|
|
|
|
this_test = tests._tests[0]
|
|
|
|
file_suite.addTest(this_test)
|
|
|
|
prior_test = this_test
|
2014-12-15 13:04:08 +00:00
|
|
|
|
2014-12-12 14:44:33 +00:00
|
|
|
top_suite.addTest(file_suite)
|
|
|
|
|
|
|
|
return top_suite
|
2014-12-15 13:33:00 +00:00
|
|
|
|
|
|
|
|
2014-12-15 21:55:14 +00:00
|
|
|
def factory(wsgi_app):
|
|
|
|
"""Satisfy a bad API."""
|
|
|
|
return wsgi_app
|
|
|
|
|
|
|
|
|
|
|
|
def install_intercept(wsgi_callable, port):
|
|
|
|
"""Install a wsgi-intercept on a random hostname."""
|
|
|
|
hostname = str(uuid.uuid4())
|
|
|
|
httplib2_intercept.install()
|
|
|
|
wsgi_intercept.add_wsgi_intercept(hostname, port, factory(wsgi_callable))
|
|
|
|
return hostname
|
|
|
|
|
|
|
|
|
2014-12-15 13:33:00 +00:00
|
|
|
def load_yaml(yaml_file):
|
2014-12-15 21:55:14 +00:00
|
|
|
"""Read and parse any YAML file. Let exceptions flow where they may."""
|
2014-12-15 13:33:00 +00:00
|
|
|
with open(yaml_file) as source:
|
|
|
|
return yaml.safe_load(source.read())
|