2424import json
2525import os
2626import re
27+ import sys
2728import warnings
2829import zipfile
2930from typing import Tuple , Union
3637from elasticsearch ._sync .client .utils import _base64_auth_header
3738from 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
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
131104XPACK_FEATURES = None
132- ES_VERSION = None
133105RUN_ASYNC_REST_API_TESTS = os .environ .get ("PYTHON_CONNECTION_CLASS" ) == "requests"
134106
135107FALSEY_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):
546499try :
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.
646578YAML_TEST_SPECS = sorted (YAML_TEST_SPECS , key = _pytest_param_sort_key )
647579
648-
649580if 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