1# Copyright (c) 2013-2014 Rackspace, Inc.
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7#    http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
12# implied.
13# See the License for the specific language governing permissions and
14# limitations under the License.
15import datetime
16import functools
17from os import path
18import time
19import types
20
21import oslotest.base as oslotest
22import six
23import six.moves.urllib.parse as urlparse
24
25
26class BaseTestCase(oslotest.BaseTestCase):
27    def setUp(self):
28        super(BaseTestCase, self).setUp()
29        self.order_id = 'order1234'
30        self.external_project_id = 'keystone1234'
31
32    def tearDown(self):
33        super(BaseTestCase, self).tearDown()
34
35
36def construct_new_test_function(original_func, name, build_params):
37    """Builds a new test function based on parameterized data.
38
39    :param original_func: The original test function that is used as a template
40    :param name: The fullname of the new test function
41    :param build_params: A dictionary or list containing args or kwargs
42        for the new test
43    :return: A new function object
44    """
45    new_func = types.FunctionType(
46        six.get_function_code(original_func),
47        six.get_function_globals(original_func),
48        name=name,
49        argdefs=six.get_function_defaults(original_func)
50    )
51
52    # Support either an arg list or kwarg dict for our data
53    build_args = build_params if isinstance(build_params, list) else []
54    build_kwargs = build_params if isinstance(build_params, dict) else {}
55
56    # Build a test wrapper to execute with our kwargs
57    def test_wrapper(func, test_args, test_kwargs):
58        @functools.wraps(func)
59        def wrapper(self):
60            return func(self, *test_args, **test_kwargs)
61        return wrapper
62
63    return test_wrapper(new_func, build_args, build_kwargs)
64
65
66def process_parameterized_function(name, func_obj, build_data):
67    """Build lists of functions to add and remove to a test case."""
68    to_remove = []
69    to_add = []
70
71    for subtest_name, params in build_data.items():
72        # Build new test function
73        func_name = '{0}_{1}'.format(name, subtest_name)
74        new_func = construct_new_test_function(func_obj, func_name, params)
75
76        # Mark the new function as needed to be added to the class
77        to_add.append((func_name, new_func))
78
79        # Mark key for removal
80        to_remove.append(name)
81
82    return to_remove, to_add
83
84
85def parameterized_test_case(cls):
86    """Class decorator to process parameterized tests
87
88    This allows for parameterization to be used for potentially any
89    unittest compatible runner; including testr and py.test.
90    """
91    tests_to_remove = []
92    tests_to_add = []
93    for key, val in vars(cls).items():
94        # Only process tests with build data on them
95        if key.startswith('test_') and val.__dict__.get('build_data'):
96            to_remove, to_add = process_parameterized_function(
97                name=key,
98                func_obj=val,
99                build_data=val.__dict__.get('build_data')
100            )
101            tests_to_remove.extend(to_remove)
102            tests_to_add.extend(to_add)
103
104    # Add all new test functions
105    [setattr(cls, name, func) for name, func in tests_to_add]
106
107    # Remove all old test function templates (if they still exist)
108    [delattr(cls, key) for key in tests_to_remove if hasattr(cls, key)]
109    return cls
110
111
112def parameterized_dataset(build_data):
113    """Simple decorator to mark a test method for processing."""
114    def decorator(func):
115        func.__dict__['build_data'] = build_data
116        return func
117    return decorator
118
119
120def create_timestamp_w_tz_and_offset(timezone=None, days=0, hours=0, minutes=0,
121                                     seconds=0):
122    """Creates a timestamp with a timezone and offset in days
123
124    :param timezone: Timezone used in creation of timestamp
125    :param days: The offset in days
126    :param hours: The offset in hours
127    :param minutes: The offset in minutes
128
129    :return: a timestamp
130    """
131    if timezone is None:
132        timezone = time.strftime("%z")
133
134    timestamp = '{time}{timezone}'.format(
135        time=(datetime.datetime.today() + datetime.timedelta(days=days,
136                                                             hours=hours,
137                                                             minutes=minutes,
138                                                             seconds=seconds)),
139        timezone=timezone)
140
141    return timestamp
142
143
144def get_limit_and_offset_from_ref(ref):
145    matches = dict(urlparse.parse_qsl(urlparse.urlparse(ref).query))
146    ref_limit = matches['limit']
147    ref_offset = matches['offset']
148
149    return ref_limit, ref_offset
150
151
152def get_tomorrow_timestamp():
153    tomorrow = (datetime.today() + datetime.timedelta(days=1))
154    return tomorrow.isoformat()
155
156
157def string_to_datetime(datetimestring, date_formats=None):
158    date_formats = date_formats or [
159        '%Y-%m-%d %H:%M:%S', '%Y-%m-%d %H:%M:%S.%f', '%Y-%m-%dT%H:%M:%S.%fZ',
160        '%Y-%m-%dT%H:%M:%S.%f', "%Y-%m-%dT%H:%M:%SZ", "%Y-%m-%dT%H:%M:%S"]
161
162    for dateformat in date_formats:
163        try:
164            return datetime.datetime.strptime(datetimestring, dateformat)
165        except ValueError:
166            continue
167    else:
168        raise
169
170
171def get_id_from_ref(ref):
172    """Returns id from reference."""
173    ref_id = None
174    if ref is not None and len(ref) > 0:
175        ref_id = path.split(ref)[1]
176    return ref_id
177