1# 2# Licensed to the Apache Software Foundation (ASF) under one 3# or more contributor license agreements. See the NOTICE file 4# distributed with this work for additional information 5# regarding copyright ownership. The ASF licenses this file 6# to you under the Apache License, Version 2.0 (the 7# "License"); you may not use this file except in compliance 8# with the License. You may obtain a copy of the License at 9# 10# http://www.apache.org/licenses/LICENSE-2.0 11# 12# Unless required by applicable law or agreed to in writing, 13# software distributed under the License is distributed on an 14# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15# KIND, either express or implied. See the License for the 16# specific language governing permissions and limitations 17# under the License. 18# 19 20import platform 21import re 22from itertools import product 23 24from .util import merge_dict 25from .test import TestEntry 26 27# Those keys are passed to execution as is. 28# Note that there are keys other than these, namely: 29# delay: After server is started, client start is delayed for the value 30# (seconds). 31# timeout: Test timeout after client is started (seconds). 32# platforms: Supported platforms. Should match platform.system() value. 33# protocols: list of supported protocols 34# transports: list of supported transports 35# sockets: list of supported sockets 36# 37# protocols and transports entries can be colon separated "spec:impl" pair 38# (e.g. binary:accel) where test is run for any matching "spec" while actual 39# argument passed to test executable is "impl". 40# Otherwise "spec" is equivalent to "spec:spec" pair. 41# (e.g. "binary" is equivalent to "binary:binary" in tests.json) 42# 43VALID_JSON_KEYS = [ 44 'name', # name of the library, typically a language name 45 'workdir', # work directory where command is executed 46 'command', # test command 47 'extra_args', # args appended to command after other args are appended 48 'remote_args', # args added to the other side of the program 49 'join_args', # whether args should be passed as single concatenated string 50 'env', # additional environmental variable 51] 52 53DEFAULT_MAX_DELAY = 5 54DEFAULT_SIGNAL = 1 55DEFAULT_TIMEOUT = 5 56 57 58def _collect_testlibs(config, server_match, client_match=[None]): 59 """Collects server/client configurations from library configurations""" 60 def expand_libs(config): 61 for lib in config: 62 sv = lib.pop('server', None) 63 cl = lib.pop('client', None) 64 yield lib, sv, cl 65 66 def yield_testlibs(base_configs, configs, match): 67 for base, conf in zip(base_configs, configs): 68 if conf: 69 if not match or base['name'] in match: 70 platforms = conf.get('platforms') or base.get('platforms') 71 if not platforms or platform.system() in platforms: 72 yield merge_dict(base, conf) 73 74 libs, svs, cls = zip(*expand_libs(config)) 75 servers = list(yield_testlibs(libs, svs, server_match)) 76 clients = list(yield_testlibs(libs, cls, client_match)) 77 return servers, clients 78 79 80def collect_features(config, match): 81 res = list(map(re.compile, match)) 82 return list(filter(lambda c: any(map(lambda r: r.search(c['name']), res)), config)) 83 84 85def _do_collect_tests(servers, clients): 86 def intersection(key, o1, o2): 87 """intersection of two collections. 88 collections are replaced with sets the first time""" 89 def cached_set(o, key): 90 v = o[key] 91 if not isinstance(v, set): 92 v = set(v) 93 o[key] = v 94 return v 95 return cached_set(o1, key) & cached_set(o2, key) 96 97 def intersect_with_spec(key, o1, o2): 98 # store as set of (spec, impl) tuple 99 def cached_set(o): 100 def to_spec_impl_tuples(values): 101 for v in values: 102 spec, _, impl = v.partition(':') 103 yield spec, impl or spec 104 v = o[key] 105 if not isinstance(v, set): 106 v = set(to_spec_impl_tuples(set(v))) 107 o[key] = v 108 return v 109 for spec1, impl1 in cached_set(o1): 110 for spec2, impl2 in cached_set(o2): 111 if spec1 == spec2: 112 name = impl1 if impl1 == impl2 else '%s-%s' % (impl1, impl2) 113 yield name, impl1, impl2 114 115 def maybe_max(key, o1, o2, default): 116 """maximum of two if present, otherwise default value""" 117 v1 = o1.get(key) 118 v2 = o2.get(key) 119 return max(v1, v2) if v1 and v2 else v1 or v2 or default 120 121 def filter_with_validkeys(o): 122 ret = {} 123 for key in VALID_JSON_KEYS: 124 if key in o: 125 ret[key] = o[key] 126 return ret 127 128 def merge_metadata(o, **ret): 129 for key in VALID_JSON_KEYS: 130 if key in o: 131 ret[key] = o[key] 132 return ret 133 134 for sv, cl in product(servers, clients): 135 for proto, proto1, proto2 in intersect_with_spec('protocols', sv, cl): 136 for trans, trans1, trans2 in intersect_with_spec('transports', sv, cl): 137 for sock in intersection('sockets', sv, cl): 138 yield { 139 'server': merge_metadata(sv, **{'protocol': proto1, 'transport': trans1}), 140 'client': merge_metadata(cl, **{'protocol': proto2, 'transport': trans2}), 141 'delay': maybe_max('delay', sv, cl, DEFAULT_MAX_DELAY), 142 'stop_signal': maybe_max('stop_signal', sv, cl, DEFAULT_SIGNAL), 143 'timeout': maybe_max('timeout', sv, cl, DEFAULT_TIMEOUT), 144 'protocol': proto, 145 'transport': trans, 146 'socket': sock 147 } 148 149 150def _filter_entries(tests, regex): 151 if regex: 152 return filter(lambda t: re.search(regex, TestEntry.get_name(**t)), tests) 153 return tests 154 155 156def collect_cross_tests(tests_dict, server_match, client_match, regex): 157 sv, cl = _collect_testlibs(tests_dict, server_match, client_match) 158 return list(_filter_entries(_do_collect_tests(sv, cl), regex)) 159 160 161def collect_feature_tests(tests_dict, features_dict, server_match, feature_match, regex): 162 sv, _ = _collect_testlibs(tests_dict, server_match) 163 ft = collect_features(features_dict, feature_match) 164 return list(_filter_entries(_do_collect_tests(sv, ft), regex)) 165