Skip to content

Commit 6cff128

Browse files
committed
Share rest api test runner between elasticsearch-api and elasticsearch-xpack
1 parent 8681c26 commit 6cff128

File tree

8 files changed

+951
-2
lines changed

8 files changed

+951
-2
lines changed

api-spec-testing/test_file.rb

Lines changed: 224 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,224 @@
1+
# Licensed to Elasticsearch B.V. under one or more contributor
2+
# license agreements. See the NOTICE file distributed with
3+
# this work for additional information regarding copyright
4+
# ownership. Elasticsearch B.V. licenses this file to you under
5+
# the Apache License, Version 2.0 (the "License"); you may
6+
# not use this file except in compliance with the License.
7+
# You may obtain a copy of the License at
8+
#
9+
# http://www.apache.org/licenses/LICENSE-2.0
10+
#
11+
# Unless required by applicable law or agreed to in writing,
12+
# software distributed under the License is distributed on an
13+
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
# KIND, either express or implied. See the License for the
15+
# specific language governing permissions and limitations
16+
# under the License.
17+
18+
require 'support/test_file/action'
19+
require 'support/test_file/test'
20+
require 'support/test_file/task_group'
21+
22+
module Elasticsearch
23+
24+
module RestAPIYAMLTests
25+
26+
# Class representing a single test file, containing a setup, teardown, and multiple tests.
27+
#
28+
# @since 6.2.0
29+
class TestFile
30+
31+
attr_reader :features_to_skip
32+
attr_reader :name
33+
34+
# Initialize a single test file.
35+
#
36+
# @example Create a test file object.
37+
# TestFile.new(file_name)
38+
#
39+
# @param [ String ] file_name The name of the test file.
40+
# @param [ Array<Symbol> ] skip_features The names of features to skip.
41+
#
42+
# @since 6.1.0
43+
def initialize(file_name, features_to_skip = [])
44+
@name = file_name
45+
documents = YAML.load_stream(File.new(file_name))
46+
@test_definitions = documents.reject { |doc| doc['setup'] || doc['teardown'] }
47+
@setup = documents.find { |doc| doc['setup'] }
48+
@teardown = documents.find { |doc| doc['teardown'] }
49+
@features_to_skip = REST_API_YAML_SKIP_FEATURES + features_to_skip
50+
end
51+
52+
# Get a list of tests in the test file.
53+
#
54+
# @example Get the list of tests
55+
# test_file.tests
56+
#
57+
# @return [ Array<Test> ] A list of Test objects.
58+
#
59+
# @since 6.2.0
60+
def tests
61+
@test_definitions.collect do |test_definition|
62+
Test.new(self, test_definition)
63+
end
64+
end
65+
66+
# Run the setup tasks defined for a single test file.
67+
#
68+
# @example Run the setup tasks.
69+
# test_file.setup(client)
70+
#
71+
# @param [ Elasticsearch::Client ] client The client to use to perform the setup tasks.
72+
#
73+
# @return [ self ]
74+
#
75+
# @since 6.2.0
76+
def setup(client)
77+
return unless @setup
78+
actions = @setup['setup'].select { |action| action['do'] }.map { |action| Action.new(action['do']) }
79+
actions.each do |action|
80+
action.execute(client)
81+
end
82+
self
83+
end
84+
85+
# Run the teardown tasks defined for a single test file.
86+
#
87+
# @example Run the teardown tasks.
88+
# test_file.teardown(client)
89+
#
90+
# @param [ Elasticsearch::Client ] client The client to use to perform the teardown tasks.
91+
#
92+
# @return [ self ]
93+
#
94+
# @since 6.2.0
95+
def teardown(client)
96+
return unless @teardown
97+
actions = @teardown['teardown'].select { |action| action['do'] }.map { |action| Action.new(action['do']) }
98+
actions.each { |action| action.execute(client) }
99+
self
100+
end
101+
102+
class << self
103+
104+
# Prepare Elasticsearch for a single test file.
105+
# This method deletes indices, roles, datafeeds, etc.
106+
#
107+
# @since 6.2.0
108+
def clear_data(client)
109+
clear_indices(client)
110+
clear_index_templates(client)
111+
clear_snapshots_and_repositories(client)
112+
end
113+
114+
# Prepare Elasticsearch for a single test file.
115+
# This method deletes indices, roles, datafeeds, etc.
116+
#
117+
# @since 6.2.0
118+
def prepare(client)
119+
clear_indices(client)
120+
clear_roles(client)
121+
clear_users(client)
122+
clear_privileges(client)
123+
clear_datafeeds(client)
124+
clear_ml_jobs(client)
125+
clear_rollup_jobs(client)
126+
clear_tasks(client)
127+
clear_machine_learning_indices(client)
128+
create_x_pack_rest_user(client)
129+
end
130+
131+
private
132+
133+
def create_x_pack_rest_user(client)
134+
client.xpack.security.put_user(username: 'x_pack_rest_user',
135+
body: { password: 'x-pack-test-password', roles: ['superuser'] })
136+
end
137+
138+
def clear_roles(client)
139+
client.xpack.security.get_role.each do |role, _|
140+
begin; client.xpack.security.delete_role(name: role); rescue; end
141+
end
142+
end
143+
144+
def clear_users(client)
145+
client.xpack.security.get_user.each do |user, _|
146+
begin; client.xpack.security.delete_user(username: user); rescue; end
147+
end
148+
end
149+
150+
def clear_privileges(client)
151+
client.xpack.security.get_privileges.each do |privilege, _|
152+
begin; client.xpack.security.delete_privileges(name: privilege); rescue; end
153+
end
154+
end
155+
156+
def clear_datafeeds(client)
157+
client.xpack.ml.stop_datafeed(datafeed_id: '_all', force: true)
158+
client.xpack.ml.get_datafeeds['datafeeds'].each do |d|
159+
client.xpack.ml.delete_datafeed(datafeed_id: d['datafeed_id'])
160+
end
161+
end
162+
163+
def clear_ml_jobs(client)
164+
client.xpack.ml.close_job(job_id: '_all', force: true)
165+
client.xpack.ml.get_jobs['jobs'].each do |d|
166+
client.xpack.ml.delete_job(job_id: d['job_id'])
167+
end
168+
end
169+
170+
def clear_rollup_jobs(client)
171+
client.xpack.rollup.get_jobs(id: '_all')['jobs'].each do |d|
172+
client.xpack.rollup.stop_job(id: d['config']['id'])
173+
client.xpack.rollup.delete_job(id: d['config']['id'])
174+
end
175+
end
176+
177+
def clear_tasks(client)
178+
tasks = client.tasks.get['nodes'].values.first['tasks'].values.select do |d|
179+
d['cancellable']
180+
end.map do |d|
181+
"#{d['node']}:#{d['id']}"
182+
end
183+
tasks.each { |t| client.tasks.cancel task_id: t }
184+
end
185+
186+
def clear_machine_learning_indices(client)
187+
client.indices.delete(index: '.ml-*', ignore: 404)
188+
end
189+
190+
def clear_index_templates(client)
191+
client.indices.delete_template(name: '*')
192+
end
193+
194+
def clear_snapshots_and_repositories(client)
195+
client.snapshot.get_repository(repository: '_all').keys.each do |repository|
196+
client.snapshot.get(repository: repository, snapshot: '_all')['snapshots'].each do |s|
197+
client.snapshot.delete(repository: repository, snapshot: s['snapshot'])
198+
end
199+
client.snapshot.delete_repository(repository: repository)
200+
end
201+
end
202+
203+
def clear_indices(client)
204+
indices = client.indices.get(index: '_all').keys.reject do |i|
205+
i.start_with?('.security') || i.start_with?('.watches')
206+
end
207+
indices.each do |index|
208+
client.indices.delete_alias(index: index, name: '*', ignore: 404)
209+
client.indices.delete(index: index, ignore: 404)
210+
end
211+
# See cat.aliases/10_basic.yml, test_index is not return in client.indices.get(index: '_all')
212+
client.indices.delete(index: 'test_index', ignore: 404)
213+
client.indices.delete(index: 'index1', ignore: 404)
214+
client.indices.delete(index: 'index_closed', ignore: 404)
215+
client.indices.delete(index: 'bar', ignore: 404)
216+
client.indices.delete(index: 'test_close_index', ignore: 404)
217+
client.indices.delete(index: 'test_index_3', ignore: 404)
218+
client.indices.delete(index: 'test_index_2', ignore: 404)
219+
client.indices.delete(index: 'test-xyy', ignore: 404)
220+
end
221+
end
222+
end
223+
end
224+
end
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
# Licensed to Elasticsearch B.V. under one or more contributor
2+
# license agreements. See the NOTICE file distributed with
3+
# this work for additional information regarding copyright
4+
# ownership. Elasticsearch B.V. licenses this file to you under
5+
# the Apache License, Version 2.0 (the "License"); you may
6+
# not use this file except in compliance with the License.
7+
# You may obtain a copy of the License at
8+
#
9+
# http://www.apache.org/licenses/LICENSE-2.0
10+
#
11+
# Unless required by applicable law or agreed to in writing,
12+
# software distributed under the License is distributed on an
13+
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
# KIND, either express or implied. See the License for the
15+
# specific language governing permissions and limitations
16+
# under the License.
17+
18+
module Elasticsearch
19+
20+
module RestAPIYAMLTests
21+
22+
# Class representing a single action. An action is one of the following:
23+
#
24+
# 1. Applying header settings on a client.
25+
# 2. Sending some request to Elasticsearch.
26+
# 3. Sending some request to Elasticsearch, expecting an exception.
27+
#
28+
# @since 6.2.0
29+
class Action
30+
31+
attr_reader :response
32+
33+
# Initialize an Action object.
34+
#
35+
# @example Create an action object:
36+
# Action.new("xpack.watcher.get_watch" => { "id" => "my_watch" })
37+
#
38+
# @param [ Hash ] definition The action definition.
39+
#
40+
# @since 6.2.0
41+
def initialize(definition)
42+
@definition = definition
43+
end
44+
45+
# Execute the action. The method returns the client, in case the action created a new client
46+
# with header settings.
47+
#
48+
# @example Execute the action.
49+
# action.execute(client, test)
50+
#
51+
# @param [ Elasticsearch::Client ] client The client to use to execute the action.
52+
# @param [ Test ] test The test containing this action. Necessary for caching variables.
53+
#
54+
# @return [ Elasticsearch::Client ] The client. It will be a new one, not the one passed in,
55+
# if the action is to set headers.
56+
#
57+
# @since 6.2.0
58+
def execute(client, test = nil)
59+
@definition.each.inject(client) do |client, (method_chain, args)|
60+
chain = method_chain.split('.')
61+
62+
if chain.size > 1
63+
client = chain[0...-1].inject(client) do |_client, _method|
64+
_client.send(_method)
65+
end
66+
end
67+
68+
_method = chain[-1]
69+
case _method
70+
when 'headers'
71+
if ENV['QUIET'] == 'true'
72+
# todo: create a method on Elasticsearch::Client that can clone the client with new options
73+
Elasticsearch::Client.new(host: URL,
74+
transport_options: TRANSPORT_OPTIONS.merge( headers: prepare_arguments(args, test)))
75+
else
76+
Elasticsearch::Client.new(host: URL, tracer: Logger.new($stdout),
77+
transport_options: TRANSPORT_OPTIONS.merge( headers: prepare_arguments(args, test)))
78+
end
79+
when 'catch'
80+
client
81+
else
82+
@response = client.send(_method, prepare_arguments(args, test))
83+
client
84+
end
85+
end
86+
end
87+
88+
def yaml_response?
89+
@definition['headers'] && @definition['headers']['Accept'] == 'application/yaml'
90+
end
91+
92+
private
93+
94+
def prepare_arguments(args, test)
95+
symbolize_keys(args).tap do |args|
96+
if test
97+
if args.is_a?(Hash)
98+
args.each do |key, value|
99+
case value
100+
when Hash
101+
args[key] = prepare_arguments(value, test)
102+
when Array
103+
args[key] = value.collect { |v| prepare_arguments(v, test) }
104+
when String
105+
# Find the cached values where the variable name is contained in the arguments.
106+
if cached_value = test.cached_values.find { |k, v| value =~ /\$\{?#{k}\}?/ }
107+
# The arguments may contain the variable in the form ${variable} or $variable
108+
args[key] = value.gsub(/\$\{?#{cached_value[0]}\}?/, cached_value[1].to_s)
109+
end
110+
end
111+
end
112+
elsif args.is_a?(String)
113+
if cached_value = test.cached_values.find { |k, v| args =~ /\$\{?#{k}\}?/ }
114+
return cached_value[1]
115+
end
116+
end
117+
end
118+
end
119+
end
120+
121+
def symbolize_keys(object)
122+
if object.is_a? Hash
123+
object.reduce({}) { |memo,(k,v)| memo[k.to_s.to_sym] = symbolize_keys(v); memo }
124+
else
125+
object
126+
end
127+
end
128+
end
129+
end
130+
end

0 commit comments

Comments
 (0)