Skip to content

Commit 8eea614

Browse files
committed
Switch from Elasticsearch YAML tests to client tests
1 parent e22de7e commit 8eea614

File tree

6 files changed

+143
-182
lines changed

6 files changed

+143
-182
lines changed

.buildkite/pipeline.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ steps:
55
env:
66
PYTHON_VERSION: "{{ matrix.python }}"
77
TEST_SUITE: "platinum"
8-
STACK_VERSION: "8.11.0-SNAPSHOT"
8+
STACK_VERSION: "8.16.0-SNAPSHOT"
99
PYTHON_CONNECTION_CLASS: "{{ matrix.connection }}"
1010
NOX_SESSION: "{{ matrix.nox_session }}"
1111
matrix:

noxfile.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ def pytest_argv():
4949
def test(session):
5050
session.install(".[dev]", env=INSTALL_ENV, silent=False)
5151

52-
session.run(*pytest_argv())
52+
session.run(*pytest_argv(), *session.posargs)
5353

5454

5555
@nox.session(python=["3.8", "3.12"])

test_elasticsearch/test_async/test_server/conftest.py

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626

2727

2828
@pytest_asyncio.fixture(scope="function")
29-
async def async_client(elasticsearch_url):
29+
async def async_client_factory(elasticsearch_url):
3030

3131
if not hasattr(elasticsearch, "AsyncElasticsearch"):
3232
pytest.skip("test requires 'AsyncElasticsearch' and aiohttp to be installed")
@@ -37,10 +37,18 @@ async def async_client(elasticsearch_url):
3737
client = None
3838
try:
3939
client = elasticsearch.AsyncElasticsearch(
40-
elasticsearch_url, request_timeout=3, ca_certs=CA_CERTS
40+
elasticsearch_url, ca_certs=CA_CERTS
4141
)
4242
yield client
4343
finally:
4444
if client:
45-
wipe_cluster(client)
4645
await client.close()
46+
47+
48+
@pytest.fixture(scope="function")
49+
def async_client(async_client_factory, elasticsearch_api_key):
50+
try:
51+
yield async_client_factory
52+
finally:
53+
# Wipe the cluster clean after every test execution.
54+
wipe_cluster(async_client_factory, elasticsearch_api_key)

test_elasticsearch/test_async/test_server/test_rest_api_spec.py

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,9 @@ async def run_do(self, action):
130130
headers.pop("Authorization")
131131

132132
method, args = list(action.items())[0]
133-
args["headers"] = headers
133+
134+
if headers:
135+
args["headers"] = headers
134136

135137
# locate api endpoint
136138
for m in method.split("."):
@@ -239,15 +241,17 @@ async def _feature_enabled(self, name):
239241

240242

241243
@pytest_asyncio.fixture(scope="function")
242-
def async_runner(async_client):
243-
return AsyncYamlRunner(async_client)
244+
def async_runner(async_client_factory):
245+
return AsyncYamlRunner(async_client_factory)
244246

245247

246248
if RUN_ASYNC_REST_API_TESTS:
247249

248250
@pytest.mark.parametrize("test_spec", YAML_TEST_SPECS)
249251
async def test_rest_api_spec(test_spec, async_runner):
250-
if test_spec.get("skip", False):
251-
pytest.skip("Manually skipped in 'SKIP_TESTS'")
252+
if test_spec.get("fail", False):
253+
pytest.xfail("Manually marked as failing in 'FAILING_TESTS'")
254+
elif test_spec.get("skip", False):
255+
pytest.xfail("Manually skipped")
252256
async_runner.use_spec(test_spec)
253257
await async_runner.run()

test_elasticsearch/test_server/test_rest_api_spec.py

Lines changed: 63 additions & 130 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import json
2525
import os
2626
import re
27+
import sys
2728
import warnings
2829
import zipfile
2930
from typing import Tuple, Union
@@ -36,7 +37,7 @@
3637
from elasticsearch._sync.client.utils import _base64_auth_header
3738
from elasticsearch.compat import string_types
3839

39-
from ..utils import CA_CERTS, es_url, parse_version
40+
from ..utils import es_url
4041

4142
# some params had to be changed in python, keep track of them so we can rename
4243
# those in the tests accordingly
@@ -70,66 +71,37 @@
7071
}
7172

7273
# broken YAML tests on some releases
73-
SKIP_TESTS = {
74-
# Warning about date_histogram.interval deprecation is raised randomly
75-
"search/aggregation/250_moving_fn[1]",
76-
# body: null
77-
"indices/simulate_index_template/10_basic[2]",
78-
# No ML node with sufficient capacity / random ML failing
79-
"ml/start_stop_datafeed",
80-
"ml/post_data",
81-
"ml/jobs_crud",
82-
"ml/datafeeds_crud",
83-
"ml/set_upgrade_mode",
84-
"ml/reset_job[2]",
85-
"ml/jobs_get_stats",
86-
"ml/get_datafeed_stats",
87-
"ml/get_trained_model_stats",
88-
"ml/delete_job_force",
89-
"ml/jobs_get_result_overall_buckets",
90-
"ml/bucket_correlation_agg[0]",
91-
"ml/job_groups",
92-
"transform/transforms_stats_continuous[0]",
93-
# Fails bad request instead of 404?
94-
"ml/inference_crud",
95-
# rollup/security_tests time out?
96-
"rollup/security_tests",
97-
# Our TLS certs are custom
98-
"ssl/10_basic[0]",
99-
# Our user is custom
100-
"users/10_basic[3]",
101-
# License warning not sent?
102-
"license/30_enterprise_license[0]",
103-
# Shards/snapshots aren't right?
104-
"searchable_snapshots/10_usage[1]",
105-
# flaky data streams?
106-
"data_stream/10_basic[1]",
107-
"data_stream/80_resolve_index_data_streams[1]",
108-
# bad formatting?
109-
"cat/allocation/10_basic",
110-
"runtime_fields/10_keyword[8]",
111-
# service account number not right?
112-
"service_accounts/10_basic[1]",
113-
# doesn't use 'contains' properly?
114-
"xpack/10_basic[0]",
115-
"privileges/40_get_user_privs[0]",
116-
"privileges/40_get_user_privs[1]",
117-
"features/get_features/10_basic[0]",
118-
"features/reset_features/10_basic[0]",
119-
# bad use of 'is_false'?
120-
"indices/get_alias/10_basic[22]",
121-
# unique usage of 'set'
122-
"indices/stats/50_disk_usage[0]",
123-
"indices/stats/60_field_usage[0]",
124-
# actual Elasticsearch failure?
125-
"transform/transforms_stats",
126-
"transform/transforms_cat_apis",
127-
"transform/transforms_update",
74+
FAILING_TESTS = {
75+
# ping has a custom implementation in Python and returns a boolean
76+
"ping/ping",
77+
# Not investigated yet
78+
"cat/aliases",
79+
"cat/fielddata",
80+
"cluster/delete_voting_config_exclusions",
81+
"cluster/voting_config_exclusions",
82+
"entsearch/10_basic",
83+
"indices/clone",
84+
"indices/resolve_cluster",
85+
"indices/settings",
86+
"indices/split",
87+
"indices/simulate_template_stack",
88+
"logstash/10_basic",
89+
"machine_learning/30_trained_model_stack",
90+
"machine_learning/jobs_crud",
91+
"scroll/10_basic",
92+
"security/10_api_key_basic",
93+
"transform/10_basic",
94+
}
95+
SKIPPED_TESTS = {
96+
# Timeouts
97+
# https://github.com/elastic/elasticsearch-serverless-python/issues/63
98+
"cluster/cluster_info[0]",
99+
"inference/10_basic[0]",
100+
"machine_learning/20_trained_model[0]",
128101
}
129102

130103

131104
XPACK_FEATURES = None
132-
ES_VERSION = None
133105
RUN_ASYNC_REST_API_TESTS = os.environ.get("PYTHON_CONNECTION_CLASS") == "requests"
134106

135107
FALSEY_VALUES = ("", None, False, 0, 0.0)
@@ -173,16 +145,6 @@ def teardown(self):
173145
self.section("teardown")
174146
self.run_code(self._teardown_code)
175147

176-
def es_version(self):
177-
global ES_VERSION
178-
if ES_VERSION is None:
179-
version_string = (self.client.info())["version"]["number"]
180-
if "." not in version_string:
181-
return ()
182-
version = version_string.strip().split(".")
183-
ES_VERSION = tuple(int(v) if v.isdigit() else 999 for v in version)
184-
return ES_VERSION
185-
186148
def section(self, name):
187149
print(("=" * 10) + " " + name + " " + ("=" * 10))
188150

@@ -331,16 +293,6 @@ def run_skip(self, skip):
331293
continue
332294
pytest.skip(f"feature '{feature}' is not supported")
333295

334-
if "version" in skip:
335-
version, reason = skip["version"], skip["reason"]
336-
if version == "all":
337-
pytest.skip(reason)
338-
min_version, _, max_version = version.partition("-")
339-
min_version = parse_version(min_version.strip()) or (0,)
340-
max_version = parse_version(max_version.strip()) or (999,)
341-
if min_version <= (self.es_version()) <= max_version:
342-
pytest.skip(reason)
343-
344296
def run_gt(self, action):
345297
for key, value in action.items():
346298
value = self._resolve(value)
@@ -516,8 +468,9 @@ def _skip_intentional_type_errors(self, e: Exception):
516468

517469

518470
@pytest.fixture(scope="function")
519-
def sync_runner(sync_client):
520-
return YamlRunner(sync_client)
471+
def sync_runner(sync_client_factory):
472+
# sync_client_factory does not wipe the cluster between tests
473+
return YamlRunner(sync_client_factory)
521474

522475

523476
# Source: https://stackoverflow.com/a/37958106/5763213
@@ -546,77 +499,54 @@ def remove_implicit_resolver(cls, tag_to_remove):
546499
try:
547500
# Construct the HTTP and Elasticsearch client
548501
http = urllib3.PoolManager(retries=10)
549-
client = Elasticsearch(es_url(), request_timeout=3, ca_certs=CA_CERTS)
550-
551-
# Make a request to Elasticsearch for the build hash, we'll be looking for
552-
# an artifact with this same hash to download test specs for.
553-
client_info = client.info()
554-
version_number = client_info["version"]["number"]
555-
build_hash = client_info["version"]["build_hash"]
556-
557-
# Now talk to the artifacts API with the 'STACK_VERSION' environment variable
558-
resp = http.request(
559-
"GET",
560-
f"https://artifacts-api.elastic.co/v1/versions/{version_number}",
502+
503+
yaml_tests_url = (
504+
"https://api.github.com/repos/elastic/elasticsearch-clients-tests/zipball/main"
561505
)
562-
resp = json.loads(resp.data.decode("utf-8"))
563-
564-
# Look through every build and see if one matches the commit hash
565-
# we're looking for. If not it's okay, we'll just use the latest and
566-
# hope for the best!
567-
builds = resp["version"]["builds"]
568-
for build in builds:
569-
if build["projects"]["elasticsearch"]["commit_hash"] == build_hash:
570-
break
571-
else:
572-
build = builds[0] # Use the latest
573-
574-
# Now we're looking for the 'rest-api-spec-<VERSION>-sources.jar' file
575-
# to download and extract in-memory.
576-
packages = build["projects"]["elasticsearch"]["packages"]
577-
for package in packages:
578-
if re.match(r"rest-resources-zip-.*\.zip", package):
579-
package_url = packages[package]["url"]
580-
break
581-
else:
582-
raise RuntimeError(
583-
f"Could not find the package 'rest-resources-zip-*.zip' in build {build!r}"
584-
)
585506

586507
# Download the zip and start reading YAML from the files in memory
587-
package_zip = zipfile.ZipFile(io.BytesIO(http.request("GET", package_url).data))
508+
package_zip = zipfile.ZipFile(io.BytesIO(http.request("GET", yaml_tests_url).data))
509+
588510
for yaml_file in package_zip.namelist():
589-
if not re.match(r"^rest-api-spec/test/.*\.ya?ml$", yaml_file):
511+
if not re.match(r"^.*\/tests\/.*\.ya?ml$", yaml_file):
590512
continue
591513
yaml_tests = list(
592514
yaml.load_all(package_zip.read(yaml_file), Loader=NoDatesSafeLoader)
593515
)
594516

595-
# Each file may have a "test" named 'setup' or 'teardown',
596-
# these sets of steps should be run at the beginning and end
597-
# of every other test within the file so we do one pass to capture those.
598-
setup_steps = teardown_steps = None
517+
# Each file has a `requires` section with `serverless` and `stack`
518+
# boolean entries indicating whether the test should run with
519+
# serverless, stack or both. Additionally, each file may have a section
520+
# named 'setup' or 'teardown', these sets of steps should be run at the
521+
# beginning and end of every other test within the file so we do one
522+
# pass to capture those.
523+
requires = setup_steps = teardown_steps = None
599524
test_numbers_and_steps = []
600525
test_number = 0
601526

602527
for yaml_test in yaml_tests:
603528
test_name, test_step = yaml_test.popitem()
604-
if test_name == "setup":
529+
if test_name == "requires":
530+
requires = test_step
531+
elif test_name == "setup":
605532
setup_steps = test_step
606533
elif test_name == "teardown":
607534
teardown_steps = test_step
608535
else:
609536
test_numbers_and_steps.append((test_number, test_step))
610537
test_number += 1
611538

539+
if not requires["stack"]:
540+
continue
541+
612542
# Now we combine setup, teardown, and test_steps into
613543
# a set of pytest.param() instances
614544
for test_number, test_step in test_numbers_and_steps:
615-
# Build the id from the name of the YAML file and
616-
# the number within that file. Most important step
617-
# is to remove most of the file path prefixes and
618-
# the .yml suffix.
619-
pytest_test_name = yaml_file.rpartition(".")[0].replace(".", "/")
545+
# Build the id from the name of the YAML file and the number within
546+
# that file. Most important step is to remove most of the file path
547+
# prefixes and the .yml suffix.
548+
test_path = "/".join(yaml_file.split("/")[2:])
549+
pytest_test_name = test_path.rpartition(".")[0].replace(".", "/")
620550
for prefix in ("rest-api-spec/", "test/", "free/", "platinum/"):
621551
if pytest_test_name.startswith(prefix):
622552
pytest_test_name = pytest_test_name[len(prefix) :]
@@ -628,7 +558,9 @@ def remove_implicit_resolver(cls, tag_to_remove):
628558
"teardown": teardown_steps,
629559
}
630560
# Skip either 'test_name' or 'test_name[x]'
631-
if pytest_test_name in SKIP_TESTS or pytest_param_id in SKIP_TESTS:
561+
if pytest_test_name in FAILING_TESTS or pytest_param_id in FAILING_TESTS:
562+
pytest_param["fail"] = True
563+
elif pytest_test_name in SKIPPED_TESTS or pytest_param_id in SKIPPED_TESTS:
632564
pytest_param["skip"] = True
633565

634566
YAML_TEST_SPECS.append(pytest.param(pytest_param, id=pytest_param_id))
@@ -645,12 +577,13 @@ def _pytest_param_sort_key(param: pytest.param) -> Tuple[Union[str, int], ...]:
645577
# Sort the tests by ID so they're grouped together nicely.
646578
YAML_TEST_SPECS = sorted(YAML_TEST_SPECS, key=_pytest_param_sort_key)
647579

648-
649580
if not RUN_ASYNC_REST_API_TESTS:
650581

651582
@pytest.mark.parametrize("test_spec", YAML_TEST_SPECS)
652583
def test_rest_api_spec(test_spec, sync_runner):
653-
if test_spec.get("skip", False):
654-
pytest.skip("Manually skipped in 'SKIP_TESTS'")
584+
if test_spec.get("fail", False):
585+
pytest.xfail("Manually marked as failing in 'FAILING_TESTS'")
586+
elif test_spec.get("skip", False):
587+
pytest.skip("Manually marked as skipped")
655588
sync_runner.use_spec(test_spec)
656589
sync_runner.run()

0 commit comments

Comments
 (0)