apache-ignite
242 строки · 8.8 Кб
1# Licensed to the Apache Software Foundation (ASF) under one or more
2# contributor license agreements. See the NOTICE file distributed with
3# this work for additional information regarding copyright ownership.
4# The ASF licenses this file to You under the Apache License, Version 2.0
5# (the "License"); you may not use this file except in compliance with
6# the License. You may obtain a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13# See the License for the specific language governing permissions and
14# limitations under the License.
15
16"""
17Module contains useful test decorators.
18"""
19
20import copy21from collections.abc import Iterable22
23from ducktape.cluster.cluster_spec import ClusterSpec24from ducktape.mark._mark import Ignore, Mark, _inject25
26from ignitetest.utils.version import IgniteVersion27from ignitetest.utils.ignite_test import IgniteTestContext28
29
30class IgnoreIf(Ignore):31"""32Ignore test if version or global parameters correspond to condition.
33"""
34def __init__(self, condition, variable_name):35super().__init__()36self.condition = condition37self.variable_name = variable_name38
39def apply(self, seed_context, context_list):40assert len(context_list) > 0, "ignore if decorator is not being applied to any test cases"41
42for ctx in context_list:43if self.variable_name in ctx.injected_args:44version = ctx.injected_args[self.variable_name]45assert isinstance(version, str), "'%s'n injected args must be a string" % (self.variable_name,)46ctx.ignore = ctx.ignore or self.condition(IgniteVersion(version), ctx.globals)47
48return context_list49
50def __eq__(self, other):51return super().__eq__(other) and self.condition == other.condition52
53
54class IgniteVersionParametrize(Mark):55"""56Parametrize function with ignite_version
57"""
58def __init__(self, *args, version_prefix):59"""60:param args: Can be string, tuple of strings or iterable of them.
61:param version_prefix: prefix for variable to inject into test function.
62"""
63self.versions = list(args)64self.version_prefix = version_prefix65
66def apply(self, seed_context, context_list):67if 'ignite_versions' in seed_context.globals:68ver = seed_context.globals['ignite_versions']69if isinstance(ver, str):70self.versions = [ver]71elif isinstance(ver, Iterable):72self.versions = list(ver)73else:74raise AssertionError("Expected string or iterable as parameter in ignite_versions, "75"%s of type %s passed" % (ver, type(ver)))76
77self.versions = self._inject_global_project(self.versions, seed_context.globals.get("project"))78
79new_context_list = []80if context_list:81for ctx in context_list:82for version in self.versions:83if self._check_injected(ctx):84continue85
86new_context_list.insert(0, self._prepare_new_ctx(version, seed_context, ctx))87else:88for version in self.versions:89new_context_list.insert(0, self._prepare_new_ctx(version, seed_context))90
91return new_context_list92
93@staticmethod94def _inject_global_project(version, project):95if isinstance(version, (list, tuple)):96return list(map(lambda v: IgniteVersionParametrize._inject_global_project(v, project), version))97
98if (version.lower() == "dev" or version[0].isdigit()) and project:99version = "%s-%s" % (project, version)100
101return version102
103def _prepare_new_ctx(self, version, seed_context, ctx=None):104injected_args = dict(ctx.injected_args) if ctx and ctx.injected_args else {}105
106if isinstance(version, (list, tuple)) and len(version) >= 2:107for idx, ver in enumerate(version):108injected_args["%s_%s" % (self.version_prefix, idx + 1)] = ver109elif isinstance(version, str):110injected_args[self.version_prefix] = version111else:112raise AssertionError("Expected string or iterable of size greater than 2 as element in ignite_versions,"113"%s of type %s passed" % (version, type(version)))114
115injected_fun = _inject(**injected_args)(seed_context.function)116
117return seed_context.copy(function=injected_fun, injected_args=injected_args)118
119def _check_injected(self, ctx):120if ctx.injected_args:121for arg_name in ctx.injected_args.keys():122if arg_name.startswith(self.version_prefix):123return True124
125return False126
127@property128def name(self):129"""130This function should return "PARAMETRIZE" string in order to ducktape's method parametrization works.
131Should be fixed after apropriate patch in ducktape will be merged.
132"""
133return "PARAMETRIZE"134
135def __eq__(self, other):136return super().__eq__(other) and self.versions == other.versions137
138
139CLUSTER_SPEC_KEYWORD = "cluster_spec"140CLUSTER_SIZE_KEYWORD = "num_nodes"141
142
143class ParametrizableClusterMetadata(Mark):144"""Provide a hint about how a given test will use the cluster."""145
146def __init__(self, **kwargs):147self.metadata = copy.copy(kwargs)148
149@property150def name(self):151return "PARAMETRIZABLE_RESOURCE_HINT_CLUSTER_USE"152
153def apply(self, seed_context, context_list):154assert len(context_list) > 0, "parametrizable cluster use annotation is not being applied to any test cases"155
156cluster_size_param = self._extract_cluster_size(seed_context)157
158for ctx in context_list:159if cluster_size_param:160self._modify_metadata(cluster_size_param)161
162if not ctx.cluster_use_metadata:163ctx.cluster_use_metadata = self.metadata164
165return list(map(lambda _ctx: IgniteTestContext.resolve(_ctx), context_list))166
167@staticmethod168def _extract_cluster_size(seed_context):169cluster_size = seed_context.globals.get('cluster_size')170
171if cluster_size is None:172return None173
174res = int(cluster_size) if isinstance(cluster_size, str) else cluster_size175
176if not isinstance(res, int):177raise ValueError(f"Expected string or int param, {cluster_size} of {type(cluster_size)} passed instead.")178
179if res <= 0:180raise ValueError(f"Expected cluster_size greater than 0, {cluster_size} passed instead.")181
182return res183
184def _modify_metadata(self, new_size):185cluster_spec = self.metadata.get(CLUSTER_SPEC_KEYWORD)186cluster_size = self.metadata.get(CLUSTER_SIZE_KEYWORD)187
188if cluster_spec is not None and not cluster_spec.empty():189node_spec = next(iter(cluster_spec))190self.metadata[CLUSTER_SPEC_KEYWORD] = ClusterSpec.from_nodes([node_spec] * new_size)191elif cluster_size is not None:192self.metadata[CLUSTER_SIZE_KEYWORD] = new_size193
194
195def ignite_versions(*args, version_prefix="ignite_version"):196"""197Decorate test function to inject ignite versions. Versions will be overriden by globals "ignite_versions" param.
198:param args: Can be string, tuple of strings or iterable of them.
199:param version_prefix: prefix for variable to inject into test function.
200"""
201def parametrizer(func):202Mark.mark(func, IgniteVersionParametrize(*args, version_prefix=version_prefix))203
204return func205return parametrizer206
207
208def ignore_if(condition, *, variable_name='ignite_version'):209"""210Mark decorated test method as IGNORE if version or global parameters correspond to condition.
211
212:param condition: function(IgniteVersion, Globals) -> bool
213:param variable_name: version variable name
214"""
215def ignorer(func):216Mark.mark(func, IgnoreIf(condition, variable_name))217return func218
219return ignorer220
221
222def cluster(**kwargs):223"""Test method decorator used to provide hints about how the test will use the given cluster.224
225:Keywords:
226
227- ``num_nodes`` provide hint about how many nodes the test will consume
228- ``cluster_spec`` provide hint about how many nodes of each type the test will consume
229"""
230def cluster_use_metadata_adder(func):231def extended_test(self, *args, **kwargs):232self.test_context.before()233test_result = func(self, *args, **kwargs)234return self.test_context.after(test_result)235
236extended_test.__dict__.update(**func.__dict__)237extended_test.__name__ = func.__name__238
239Mark.mark(extended_test, ParametrizableClusterMetadata(**kwargs))240return extended_test241
242return cluster_use_metadata_adder243