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