[7.x] Switch to Pytest as default runner

This commit is contained in:
Seth Michael Larson
2020-05-19 14:18:53 -05:00
committed by Seth Michael Larson
parent f8b005f62e
commit eaff4af910
12 changed files with 274 additions and 233 deletions
-1
View File
@@ -3,7 +3,6 @@ pytest
pytest-cov pytest-cov
coverage coverage
mock mock
nosexcover
sphinx<1.7 sphinx<1.7
sphinx_rtd_theme sphinx_rtd_theme
jinja2 jinja2
+3 -7
View File
@@ -49,16 +49,13 @@ class ElasticsearchTestCase(TestCase):
return get_test_client() return get_test_client()
@classmethod @classmethod
def setUpClass(cls): def setup_class(cls):
super(ElasticsearchTestCase, cls).setUpClass()
cls.client = cls._get_client() cls.client = cls._get_client()
def tearDown(self): def teardown_method(self, _):
super(ElasticsearchTestCase, self).tearDown()
# Hidden indices expanded in wildcards in ES 7.7 # Hidden indices expanded in wildcards in ES 7.7
expand_wildcards = ["open", "closed"] expand_wildcards = ["open", "closed"]
if self.es_version >= (7, 7): if self.es_version() >= (7, 7):
expand_wildcards.append("hidden") expand_wildcards.append("hidden")
self.client.indices.delete( self.client.indices.delete(
@@ -66,7 +63,6 @@ class ElasticsearchTestCase(TestCase):
) )
self.client.indices.delete_template(name="*", ignore=404) self.client.indices.delete_template(name="*", ignore=404)
@property
def es_version(self): def es_version(self):
if not hasattr(self, "_es_version"): if not hasattr(self, "_es_version"):
version_string = self.client.info()["version"]["number"] version_string = self.client.info()["version"]["number"]
+6 -3
View File
@@ -13,14 +13,17 @@ __versionstr__ = "7.9.0a1"
with open(join(dirname(__file__), "README")) as f: with open(join(dirname(__file__), "README")) as f:
long_description = f.read().strip() long_description = f.read().strip()
install_requires = ["urllib3>=1.21.1", "certifi"] install_requires = [
"urllib3>=1.21.1",
"certifi",
]
tests_require = [ tests_require = [
"requests>=2.0.0, <3.0.0", "requests>=2.0.0, <3.0.0",
"nose",
"coverage", "coverage",
"mock", "mock",
"pyyaml", "pyyaml",
"nosexcover", "pytest",
"pytest-cov",
] ]
docs_require = ["sphinx<1.7", "sphinx_rtd_theme"] docs_require = ["sphinx<1.7", "sphinx_rtd_theme"]
+1 -2
View File
@@ -34,8 +34,7 @@ The behavior is driven by environmental variables:
Alternatively, if you wish to control what you are doing you have several additional options: Alternatively, if you wish to control what you are doing you have several additional options:
* you can just run your favorite runner in the ``test_elasticsearch`` directory * ``run_tests.py`` will pass any parameters specified to ``pytest``
(verified to work with nose and py.test) and bypass the fetch logic entirely.
* to run a specific test, you can use ``python3 setup.py test -s <test_name>``, for example * to run a specific test, you can use ``python3 setup.py test -s <test_name>``, for example
``python3 setup.py test -s test_elasticsearch.test_helpers.TestParallelBulk.test_all_chunks_sent`` ``python3 setup.py test -s test_elasticsearch.test_helpers.TestParallelBulk.test_all_chunks_sent``
+1 -1
View File
@@ -12,7 +12,7 @@ from ..test_cases import TestCase, SkipTest
class TestQueryParams(TestCase): class TestQueryParams(TestCase):
def setUp(self): def setup_method(self, _):
self.calls = [] self.calls = []
@query_params("simple_param") @query_params("simple_param")
+3 -4
View File
@@ -6,7 +6,7 @@
import mock import mock
import time import time
import threading import threading
from nose.plugins.skip import SkipTest import pytest
from elasticsearch import helpers, Elasticsearch from elasticsearch import helpers, Elasticsearch
from elasticsearch.serializer import JSONSerializer from elasticsearch.serializer import JSONSerializer
@@ -41,7 +41,7 @@ class TestParallelBulk(TestCase):
self.assertEqual(50, mock_process_bulk_chunk.call_count) self.assertEqual(50, mock_process_bulk_chunk.call_count)
@SkipTest @pytest.mark.skip
@mock.patch( @mock.patch(
"elasticsearch.helpers.actions._process_bulk_chunk", "elasticsearch.helpers.actions._process_bulk_chunk",
# make sure we spend some time in the thread # make sure we spend some time in the thread
@@ -60,8 +60,7 @@ class TestParallelBulk(TestCase):
class TestChunkActions(TestCase): class TestChunkActions(TestCase):
def setUp(self): def setup_method(self, _):
super(TestChunkActions, self).setUp()
self.actions = [({"index": {}}, {"some": u"datá", "i": i}) for i in range(100)] self.actions = [({"index": {}}, {"some": u"datá", "i": i}) for i in range(100)]
def test_chunks_are_chopped_by_byte_size(self): def test_chunks_are_chopped_by_byte_size(self):
+1 -2
View File
@@ -144,8 +144,7 @@ class TestTextSerializer(TestCase):
class TestDeserializer(TestCase): class TestDeserializer(TestCase):
def setUp(self): def setup_method(self, _):
super(TestDeserializer, self).setUp()
self.de = Deserializer(DEFAULT_SERIALIZERS) self.de = Deserializer(DEFAULT_SERIALIZERS)
def test_deserializes_json_by_default(self): def test_deserializes_json_by_default(self):
+1 -1
View File
@@ -35,7 +35,7 @@ def get_client(**kwargs):
return new_client return new_client
def setup(): def setup_module():
get_client() get_client()
@@ -0,0 +1,59 @@
# Licensed to Elasticsearch B.V under one or more agreements.
# Elasticsearch B.V licenses this file to you under the Apache 2.0 License.
# See the LICENSE file in the project root for more information
import os
import time
import pytest
import elasticsearch
@pytest.fixture(scope="function")
def sync_client():
client = None
try:
kw = {
"timeout": 30,
"ca_certs": ".ci/certs/ca.pem",
"connection_class": getattr(
elasticsearch,
os.environ.get("PYTHON_CONNECTION_CLASS", "Urllib3HttpConnection"),
),
}
client = elasticsearch.Elasticsearch(
[os.environ.get("ELASTICSEARCH_HOST", {})], **kw
)
# wait for yellow status
for _ in range(100):
try:
client.cluster.health(wait_for_status="yellow")
break
except ConnectionError:
time.sleep(0.1)
else:
# timeout
pytest.skip("Elasticsearch failed to start.")
yield client
finally:
if client:
version = tuple(
[
int(x) if x.isdigit() else 999
for x in (client.info())["version"]["number"].split(".")
]
)
expand_wildcards = ["open", "closed"]
if version >= (7, 7):
expand_wildcards.append("hidden")
client.indices.delete(
index="*", ignore=404, expand_wildcards=expand_wildcards
)
client.indices.delete_template(name="*", ignore=404)
client.indices.delete_index_template(name="*", ignore=404)
client.transport.close()
@@ -70,7 +70,7 @@ class TestStreamingBulk(ElasticsearchTestCase):
assert False, "exception should have been raised" assert False, "exception should have been raised"
def test_different_op_types(self): def test_different_op_types(self):
if self.es_version < (0, 90, 1): if self.es_version() < (0, 90, 1):
raise SkipTest("update supported since 0.90.1") raise SkipTest("update supported since 0.90.1")
self.client.index(index="i", id=45, body={}) self.client.index(index="i", id=45, body={})
self.client.index(index="i", id=42, body={}) self.client.index(index="i", id=42, body={})
@@ -325,10 +325,9 @@ class TestScan(ElasticsearchTestCase):
}, },
] ]
@classmethod def teardown_method(self, m):
def tearDownClass(cls): self.client.transport.perform_request("DELETE", "/_search/scroll/_all")
cls.client.transport.perform_request("DELETE", "/_search/scroll/_all") super(TestScan, self).teardown_method(m)
super(TestScan, cls).tearDownClass()
def test_order_can_be_preserved(self): def test_order_can_be_preserved(self):
bulk = [] bulk = []
@@ -498,8 +497,7 @@ class TestScan(ElasticsearchTestCase):
class TestReindex(ElasticsearchTestCase): class TestReindex(ElasticsearchTestCase):
def setUp(self): def setup_method(self, _):
super(TestReindex, self).setUp()
bulk = [] bulk = []
for x in range(100): for x in range(100):
bulk.append({"index": {"_index": "test_index", "_type": "_doc", "_id": x}}) bulk.append({"index": {"_index": "test_index", "_type": "_doc", "_id": x}})
@@ -569,8 +567,7 @@ class TestReindex(ElasticsearchTestCase):
class TestParentChildReindex(ElasticsearchTestCase): class TestParentChildReindex(ElasticsearchTestCase):
def setUp(self): def setup_method(self, _):
super(TestParentChildReindex, self).setUp()
body = { body = {
"settings": {"number_of_shards": 1, "number_of_replicas": 0}, "settings": {"number_of_shards": 1, "number_of_replicas": 0},
"mappings": { "mappings": {
@@ -3,25 +3,22 @@
# See the LICENSE file in the project root for more information # See the LICENSE file in the project root for more information
""" """
Dynamically generated set of TestCases based on set of yaml files decribing Dynamically generated set of TestCases based on set of yaml files describing
some integration tests. These files are shared among all official Elasticsearch some integration tests. These files are shared among all official Elasticsearch
clients. clients.
""" """
import sys
import re import re
from os import walk, environ from os import walk, environ
from os.path import exists, join, dirname, pardir from os.path import exists, join, dirname, pardir, relpath
import yaml import yaml
from shutil import rmtree from shutil import rmtree
import warnings import warnings
import pytest
from elasticsearch import TransportError, RequestError, ElasticsearchDeprecationWarning from elasticsearch import TransportError, RequestError, ElasticsearchDeprecationWarning
from elasticsearch.compat import string_types from elasticsearch.compat import string_types
from elasticsearch.helpers.test import _get_version from elasticsearch.helpers.test import _get_version
from ..test_cases import SkipTest
from . import ElasticsearchTestCase
# some params had to be changed in python, keep track of them so we can rename # some params had to be changed in python, keep track of them so we can rename
# those in the tests accordingly # those in the tests accordingly
PARAMS_RENAMES = {"type": "doc_type", "from": "from_"} PARAMS_RENAMES = {"type": "doc_type", "from": "from_"}
@@ -41,41 +38,57 @@ IMPLEMENTED_FEATURES = {
# broken YAML tests on some releases # broken YAML tests on some releases
SKIP_TESTS = { SKIP_TESTS = {
"*": { # can't figure out the expand_wildcards=open issue?
# Can't figure out the get_alias(expand_wildcards=open) failure. "indices/get_alias/10_basic[23]",
"TestIndicesGetAlias10Basic", # [interval] on [date_histogram] is deprecated, use [fixed_interval] or [calendar_interval] in the future.
# Disallowing expensive queries is 7.7+ "search/aggregation/230_composite[6]",
"TestSearch320DisallowQueries", "search/aggregation/250_moving_fn[1]",
# Order of overlapped templates isn't consistent # fails by not returning 'search'?
"TestIndicesSimulateIndexTemplate10Basic", "search/320_disallow_queries[2]",
"search/40_indices_boost[1]",
# ?q= fails
"explain/30_query_string[0]",
"count/20_query_string[0]",
# index template issues
"indices/put_template/10_basic[0]",
"indices/put_template/10_basic[1]",
"indices/put_template/10_basic[2]",
"indices/put_template/10_basic[3]",
"indices/put_template/10_basic[4]",
# depends on order of response JSON which is random
"indices/simulate_index_template/10_basic[1]",
} }
}
# Test is inconsistent due to dictionaries not being ordered.
if sys.version_info < (3, 6):
SKIP_TESTS["*"].add("TestSearchAggregation250MovingFn")
XPACK_FEATURES = None XPACK_FEATURES = None
ES_VERSION = None
class InvalidActionType(Exception): class YamlRunner:
pass def __init__(self, client):
self.client = client
class YamlTestCase(ElasticsearchTestCase):
def setUp(self):
super(YamlTestCase, self).setUp()
if hasattr(self, "_setup_code"):
self.run_code(self._setup_code)
self.last_response = None self.last_response = None
self._run_code = None
self._setup_code = None
self._teardown_code = None
self._state = {} self._state = {}
def tearDown(self): def use_spec(self, test_spec):
if hasattr(self, "_teardown_code"): self._setup_code = test_spec.pop("setup", None)
self._run_code = test_spec.pop("run", None)
self._teardown_code = test_spec.pop("teardown")
def setup(self):
if self._setup_code:
self.run_code(self._setup_code)
def teardown(self):
if self._teardown_code:
self.run_code(self._teardown_code) self.run_code(self._teardown_code)
for repo, definition in self.client.snapshot.get_repository(
repository="_all" for repo, definition in (
self.client.snapshot.get_repository(repository="_all")
).items(): ).items():
self.client.snapshot.delete_repository(repository=repo) self.client.snapshot.delete_repository(repository=repo)
if definition["type"] == "fs": if definition["type"] == "fs":
@@ -86,86 +99,45 @@ class YamlTestCase(ElasticsearchTestCase):
# stop and remove all ML stuff # stop and remove all ML stuff
if self._feature_enabled("ml"): if self._feature_enabled("ml"):
self.client.ml.stop_datafeed(datafeed_id="*", force=True) self.client.ml.stop_datafeed(datafeed_id="*", force=True)
for feed in self.client.ml.get_datafeeds(datafeed_id="*")["datafeeds"]: for feed in (self.client.ml.get_datafeeds(datafeed_id="*"))["datafeeds"]:
self.client.ml.delete_datafeed(datafeed_id=feed["datafeed_id"]) self.client.ml.delete_datafeed(datafeed_id=feed["datafeed_id"])
self.client.ml.close_job(job_id="*", force=True) self.client.ml.close_job(job_id="*", force=True)
for job in self.client.ml.get_jobs(job_id="*")["jobs"]: for job in (self.client.ml.get_jobs(job_id="*"))["jobs"]:
self.client.ml.delete_job( self.client.ml.delete_job(
job_id=job["job_id"], wait_for_completion=True, force=True job_id=job["job_id"], wait_for_completion=True, force=True
) )
# stop and remove all Rollup jobs # stop and remove all Rollup jobs
if self._feature_enabled("rollup"): if self._feature_enabled("rollup"):
for rollup in self.client.rollup.get_jobs(id="*")["jobs"]: for rollup in (self.client.rollup.get_jobs(id="*"))["jobs"]:
self.client.rollup.stop_job( self.client.rollup.stop_job(
id=rollup["config"]["id"], wait_for_completion=True id=rollup["config"]["id"], wait_for_completion=True
) )
self.client.rollup.delete_job(id=rollup["config"]["id"]) self.client.rollup.delete_job(id=rollup["config"]["id"])
super(YamlTestCase, self).tearDown() def es_version(self):
global ES_VERSION
if ES_VERSION is None:
version_string = (self.client.info())["version"]["number"]
if "." not in version_string:
return ()
version = version_string.strip().split(".")
ES_VERSION = tuple(int(v) if v.isdigit() else 999 for v in version)
return ES_VERSION
def _feature_enabled(self, name): def run(self):
global XPACK_FEATURES, IMPLEMENTED_FEATURES
if XPACK_FEATURES is None:
try: try:
xinfo = self.client.xpack.info() self.setup()
XPACK_FEATURES = set( self.run_code(self._run_code)
f for f in xinfo["features"] if xinfo["features"][f]["enabled"] finally:
) self.teardown()
IMPLEMENTED_FEATURES.add("xpack")
except RequestError as e:
# We receive a 'RequestError' here when using 'oss' because the request
# for xpack.info() looks like this: 'GET /_xpack' which errors on the
# oss container due to having a similar route to 'GET /<index>'.
if "invalid_index_name_exception" not in e.error:
raise
XPACK_FEATURES = set()
IMPLEMENTED_FEATURES.add("no_xpack")
return name in XPACK_FEATURES
def _resolve(self, value):
# resolve variables
if isinstance(value, string_types) and value.startswith("$"):
value = value[1:]
self.assertIn(value, self._state)
value = self._state[value]
if isinstance(value, string_types):
value = value.strip()
elif isinstance(value, dict):
value = dict((k, self._resolve(v)) for (k, v) in value.items())
elif isinstance(value, list):
value = list(map(self._resolve, value))
return value
def _lookup(self, path):
# fetch the possibly nested value from last_response
value = self.last_response
if path == "$body":
return value
path = path.replace(r"\.", "\1")
for step in path.split("."):
if not step:
continue
step = step.replace("\1", ".")
step = self._resolve(step)
if step.isdigit() and step not in value:
step = int(step)
self.assertIsInstance(value, list)
self.assertGreater(len(value), step)
else:
self.assertIn(step, value)
value = value[step]
return value
def run_code(self, test): def run_code(self, test):
""" Execute an instruction based on it's type. """ """ Execute an instruction based on it's type. """
print(test) print(test)
for action in test: for action in test:
self.assertEqual(1, len(action)) assert len(action) == 1
action_type, action = list(action.items())[0] action_type, action = list(action.items())[0]
if hasattr(self, "run_" + action_type): if hasattr(self, "run_" + action_type):
@@ -174,19 +146,18 @@ class YamlTestCase(ElasticsearchTestCase):
raise InvalidActionType(action_type) raise InvalidActionType(action_type)
def run_do(self, action): def run_do(self, action):
""" Perform an api call with given parameters. """
api = self.client api = self.client
headers = action.pop("headers", None) headers = action.pop("headers", None)
catch = action.pop("catch", None) catch = action.pop("catch", None)
warn = action.pop("warnings", None) warn = action.pop("warnings", ())
self.assertEqual(1, len(action)) assert len(action) == 1
method, args = list(action.items())[0] method, args = list(action.items())[0]
args["headers"] = headers args["headers"] = headers
# locate api endpoint # locate api endpoint
for m in method.split("."): for m in method.split("."):
self.assertTrue(hasattr(api, m)) assert hasattr(api, m)
api = getattr(api, m) api = getattr(api, m)
# some parameters had to be renamed to not clash with python builtins, # some parameters had to be renamed to not clash with python builtins,
@@ -228,32 +199,24 @@ class YamlTestCase(ElasticsearchTestCase):
% (warn, caught_warnings) % (warn, caught_warnings)
) )
def _get_nodes(self): def run_catch(self, catch, exception):
if not hasattr(self, "_node_info"): if catch == "param":
self._node_info = list( assert isinstance(exception, TypeError)
self.client.nodes.info(node_id="_all", metric="clear")["nodes"].values() return
)
return self._node_info
def _get_data_nodes(self): assert isinstance(exception, TransportError)
return len( if catch in CATCH_CODES:
[ assert CATCH_CODES[catch] == exception.status_code
info elif catch[0] == "/" and catch[-1] == "/":
for info in self._get_nodes() assert (
if info.get("attributes", {}).get("data", "true") == "true" re.search(catch[1:-1], exception.error + " " + repr(exception.info)),
] "%s not in %r" % (catch, exception.info),
) ) is not None
self.last_response = exception.info
def _get_benchmark_nodes(self):
return len(
[
info
for info in self._get_nodes()
if info.get("attributes", {}).get("bench", "false") == "true"
]
)
def run_skip(self, skip): def run_skip(self, skip):
global IMPLEMENTED_FEATURES
if "features" in skip: if "features" in skip:
features = skip["features"] features = skip["features"]
if not isinstance(features, (tuple, list)): if not isinstance(features, (tuple, list)):
@@ -261,58 +224,37 @@ class YamlTestCase(ElasticsearchTestCase):
for feature in features: for feature in features:
if feature in IMPLEMENTED_FEATURES: if feature in IMPLEMENTED_FEATURES:
continue continue
elif feature == "requires_replica": pytest.skip("feature '%s' is not supported" % feature)
if self._get_data_nodes() > 1:
continue
elif feature == "benchmark":
if self._get_benchmark_nodes():
continue
raise SkipTest("Feature %s is not supported" % feature)
if "version" in skip: if "version" in skip:
version, reason = skip["version"], skip["reason"] version, reason = skip["version"], skip["reason"]
if version == "all": if version == "all":
raise SkipTest(reason) pytest.skip(reason)
min_version, max_version = version.split("-") min_version, max_version = version.split("-")
min_version = _get_version(min_version) or (0,) min_version = _get_version(min_version) or (0,)
max_version = _get_version(max_version) or (999,) max_version = _get_version(max_version) or (999,)
if min_version <= self.es_version <= max_version: if min_version <= (self.es_version()) <= max_version:
raise SkipTest(reason) pytest.skip(reason)
def run_catch(self, catch, exception):
if catch == "param":
self.assertIsInstance(exception, TypeError)
return
self.assertIsInstance(exception, TransportError)
if catch in CATCH_CODES:
self.assertEqual(CATCH_CODES[catch], exception.status_code)
elif catch[0] == "/" and catch[-1] == "/":
self.assertTrue(
re.search(catch[1:-1], exception.error + " " + repr(exception.info)),
"%s not in %r" % (catch, exception.info),
)
self.last_response = exception.info
def run_gt(self, action): def run_gt(self, action):
for key, value in action.items(): for key, value in action.items():
value = self._resolve(value) value = self._resolve(value)
self.assertGreater(self._lookup(key), value) assert self._lookup(key) > value
def run_gte(self, action): def run_gte(self, action):
for key, value in action.items(): for key, value in action.items():
value = self._resolve(value) value = self._resolve(value)
self.assertGreaterEqual(self._lookup(key), value) assert self._lookup(key) >= value
def run_lt(self, action): def run_lt(self, action):
for key, value in action.items(): for key, value in action.items():
value = self._resolve(value) value = self._resolve(value)
self.assertLess(self._lookup(key), value) assert self._lookup(key) < value
def run_lte(self, action): def run_lte(self, action):
for key, value in action.items(): for key, value in action.items():
value = self._resolve(value) value = self._resolve(value)
self.assertLessEqual(self._lookup(key), value) assert self._lookup(key) <= value
def run_set(self, action): def run_set(self, action):
for key, value in action.items(): for key, value in action.items():
@@ -325,17 +267,17 @@ class YamlTestCase(ElasticsearchTestCase):
except AssertionError: except AssertionError:
pass pass
else: else:
self.assertIn(value, ("", None, False, 0)) assert value in ("", None, False, 0)
def run_is_true(self, action): def run_is_true(self, action):
value = self._lookup(action) value = self._lookup(action)
self.assertNotIn(value, ("", None, False, 0)) assert value not in ("", None, False, 0)
def run_length(self, action): def run_length(self, action):
for path, expected in action.items(): for path, expected in action.items():
value = self._lookup(path) value = self._lookup(path)
expected = self._resolve(expected) expected = self._resolve(expected)
self.assertEqual(expected, len(value)) assert expected == len(value)
def run_match(self, action): def run_match(self, action):
for path, expected in action.items(): for path, expected in action.items():
@@ -347,51 +289,65 @@ class YamlTestCase(ElasticsearchTestCase):
and expected.startswith("/") and expected.startswith("/")
and expected.endswith("/") and expected.endswith("/")
): ):
expected = re.compile(expected[1:-1], re.VERBOSE) expected = re.compile(expected[1:-1], re.VERBOSE | re.MULTILINE)
self.assertTrue(expected.search(value)) assert expected.search(value), "%r does not match %r" % (
else: value,
self.assertEqual(expected, value) expected,
def construct_case(filename, name):
"""
Parse a definition of a test case from a yaml file and construct the
TestCase subclass dynamically.
"""
def make_test(test_name, definition, i):
def m(self):
if name in SKIP_TESTS.get(self.es_version, ()) or name in SKIP_TESTS.get(
"*", ()
):
raise SkipTest()
self.run_code(definition)
m.__doc__ = "%s:%s.test_from_yaml_%d (%s): %s" % (
__name__,
name,
i,
"/".join(filename.split("/")[-2:]),
test_name,
) )
m.__name__ = "test_from_yaml_%d" % i else:
return m assert expected == value, "%r does not match %r" % (value, expected)
with open(filename) as f: def _resolve(self, value):
tests = list(yaml.load_all(f)) # resolve variables
if isinstance(value, string_types) and value.startswith("$"):
value = value[1:]
assert value in self._state
value = self._state[value]
if isinstance(value, string_types):
value = value.strip()
elif isinstance(value, dict):
value = dict((k, self._resolve(v)) for (k, v) in value.items())
elif isinstance(value, list):
value = list(map(self._resolve, value))
return value
attrs = {"_yaml_file": filename} def _lookup(self, path):
i = 0 # fetch the possibly nested value from last_response
for test in tests: value = self.last_response
for test_name, definition in test.items(): if path == "$body":
if test_name in ("setup", "teardown"): return value
attrs["_%s_code" % test_name] = definition path = path.replace(r"\.", "\1")
for step in path.split("."):
if not step:
continue continue
step = step.replace("\1", ".")
step = self._resolve(step)
if step.isdigit() and step not in value:
step = int(step)
assert isinstance(value, list)
assert len(value) > step
else:
assert step in value
value = value[step]
return value
attrs["test_from_yaml_%d" % i] = make_test(test_name, definition, i) def _feature_enabled(self, name):
i += 1 global XPACK_FEATURES, IMPLEMENTED_FEATURES
if XPACK_FEATURES is None:
try:
xinfo = self.client.xpack.info()
XPACK_FEATURES = set(
f for f in xinfo["features"] if xinfo["features"][f]["enabled"]
)
IMPLEMENTED_FEATURES.add("xpack")
except RequestError:
XPACK_FEATURES = set()
IMPLEMENTED_FEATURES.add("no_xpack")
return name in XPACK_FEATURES
return type(name, (YamlTestCase,), attrs)
class InvalidActionType(Exception):
pass
YAML_DIR = environ.get( YAML_DIR = environ.get(
@@ -412,21 +368,53 @@ YAML_DIR = environ.get(
) )
YAML_TEST_SPECS = []
if exists(YAML_DIR): if exists(YAML_DIR):
# find all the test definitions in yaml files ... # find all the test definitions in yaml files ...
for (path, dirs, files) in walk(YAML_DIR): for path, _, files in walk(YAML_DIR):
for filename in files: for filename in files:
if not filename.endswith((".yaml", ".yml")): if not filename.endswith((".yaml", ".yml")):
continue continue
# ... parse them
name = ( filepath = join(path, filename)
( with open(filepath) as f:
"Test" tests = list(yaml.load_all(f, Loader=yaml.SafeLoader))
+ "".join(s.title() for s in path[len(YAML_DIR) + 1 :].split("/"))
+ filename.rsplit(".", 1)[0].title() setup_code = None
) teardown_code = None
.replace("_", "") run_codes = []
.replace(".", "") for i, test in enumerate(tests):
) for test_name, definition in test.items():
# and insert them into locals for test runner to find them if test_name == "setup":
locals()[name] = construct_case(join(path, filename), name) setup_code = definition
elif test_name == "teardown":
teardown_code = definition
else:
run_codes.append((i, definition))
for i, run_code in run_codes:
src = {"setup": setup_code, "run": run_code, "teardown": teardown_code}
# Pytest already replaces '.' and '_' with '/' so we do
# it ourselves so UI and 'SKIP_TESTS' match.
pytest_param_id = (
"%s[%d]" % (relpath(filepath, YAML_DIR).rpartition(".")[0], i)
).replace(".", "/")
if pytest_param_id in SKIP_TESTS:
src["skip"] = True
YAML_TEST_SPECS.append(pytest.param(src, id=pytest_param_id))
@pytest.fixture(scope="function")
def sync_runner(sync_client):
return YamlRunner(sync_client)
@pytest.mark.parametrize("test_spec", YAML_TEST_SPECS)
def test_rest_api_spec(test_spec, sync_runner):
if test_spec.get("skip", False):
pytest.skip("Manually skipped in 'SKIP_TESTS'")
sync_runner.use_spec(test_spec)
sync_runner.run()
+3 -1
View File
@@ -2,6 +2,8 @@
envlist = pypy,py27,py34,py35,py36,py37,py38,lint,docs envlist = pypy,py27,py34,py35,py36,py37,py38,lint,docs
[testenv] [testenv]
whitelist_externals = git whitelist_externals = git
deps =
-r dev-requirements.txt
commands = commands =
python setup.py test python setup.py test
@@ -44,7 +46,7 @@ commands =
[testenv:docs] [testenv:docs]
deps = deps =
sphinx
sphinx-rtd-theme sphinx-rtd-theme
-r dev-requirements.txt
commands = commands =
sphinx-build docs/ docs/_build -b html sphinx-build docs/ docs/_build -b html