Fail yaml tests and docs snippets that get unexpected warnings

Adds `warnings` syntax to the yaml test that allows you to expect
a `Warning` header that looks like:
```
    - do:
        warnings:
            - '[index] is deprecated'
            - quotes are not required because yaml
            - but this argument is always a list, never a single string
            - no matter how many warnings you expect
        get:
            index:    test
            type:    test
            id:        1
```

These are accessible from the docs with:
```
// TEST[warning:some warning]
```

This should help to force you to update the docs if you deprecate
something. You *must* add the warnings marker to the docs or the build
will fail. While you are there you *should* update the docs to add
deprecation warnings visible in the rendered results.
This commit is contained in:
Nik Everett 2016-08-02 17:35:31 -04:00
parent 4598c36027
commit 1e587406d8
26 changed files with 347 additions and 57 deletions

View File

@ -119,6 +119,7 @@ public class RestTestsFromSnippetsTask extends SnippetsTask {
current.println(" reason: $test.skipTest")
}
if (test.setup != null) {
// Insert a setup defined outside of the docs
String setup = setups[test.setup]
if (setup == null) {
throw new InvalidUserDataException("Couldn't find setup "
@ -136,13 +137,23 @@ public class RestTestsFromSnippetsTask extends SnippetsTask {
response.contents.eachLine { current.println(" $it") }
}
void emitDo(String method, String pathAndQuery,
String body, String catchPart, boolean inSetup) {
void emitDo(String method, String pathAndQuery, String body,
String catchPart, List warnings, boolean inSetup) {
def (String path, String query) = pathAndQuery.tokenize('?')
current.println(" - do:")
if (catchPart != null) {
current.println(" catch: $catchPart")
}
if (false == warnings.isEmpty()) {
current.println(" warnings:")
for (String warning in warnings) {
// Escape " because we're going to quote the warning
String escaped = warning.replaceAll('"', '\\\\"')
/* Quote the warning in case it starts with [ which makes
* it look too much like an array. */
current.println(" - \"$escaped\"")
}
}
current.println(" raw:")
current.println(" method: $method")
current.println(" path: \"$path\"")
@ -200,7 +211,8 @@ public class RestTestsFromSnippetsTask extends SnippetsTask {
// Leading '/'s break the generated paths
pathAndQuery = pathAndQuery.substring(1)
}
emitDo(method, pathAndQuery, body, catchPart, inSetup)
emitDo(method, pathAndQuery, body, catchPart, snippet.warnings,
inSetup)
}
}

View File

@ -37,8 +37,9 @@ public class SnippetsTask extends DefaultTask {
private static final String CATCH = /catch:\s*((?:\/[^\/]+\/)|[^ \]]+)/
private static final String SKIP = /skip:([^\]]+)/
private static final String SETUP = /setup:([^ \]]+)/
private static final String WARNING = /warning:(.+)/
private static final String TEST_SYNTAX =
/(?:$CATCH|$SUBSTITUTION|$SKIP|(continued)|$SETUP) ?/
/(?:$CATCH|$SUBSTITUTION|$SKIP|(continued)|$SETUP|$WARNING) ?/
/**
* Action to take on each snippet. Called with a single parameter, an
@ -158,6 +159,10 @@ public class SnippetsTask extends DefaultTask {
snippet.setup = it.group(6)
return
}
if (it.group(7) != null) {
snippet.warnings.add(it.group(7))
return
}
throw new InvalidUserDataException(
"Invalid test marker: $line")
}
@ -230,6 +235,7 @@ public class SnippetsTask extends DefaultTask {
String language = null
String catchPart = null
String setup = null
List warnings = new ArrayList()
@Override
public String toString() {
@ -254,6 +260,9 @@ public class SnippetsTask extends DefaultTask {
if (setup) {
result += "[setup:$setup]"
}
for (String warning in warnings) {
result += "[warning:$warning]"
}
}
if (testResponse) {
result += '// TESTRESPONSE'

View File

@ -28,6 +28,10 @@ are tests even if they don't have `// CONSOLE`.
* `// TEST[setup:name]`: Run some setup code before running the snippet. This
is useful for creating and populating indexes used in the snippet. The setup
code is defined in `docs/build.gradle`.
* `// TEST[warning:some warning]`: Expect the response to include a `Warning`
header. If the response doesn't include a `Warning` header with the exact
text then the test fails. If the response includes `Warning` headers that
aren't expected then the test fails.
* `// TESTRESPONSE`: Matches this snippet against the body of the response of
the last test. If the response is JSON then order is ignored. With
`// TEST[continued]` you can make tests that contain multiple command snippets

View File

@ -196,14 +196,14 @@ PUT /test
"file" : {
"type" : "attachment",
"fields" : {
"content" : {"index" : "no"},
"title" : {"store" : "yes"},
"date" : {"store" : "yes"},
"content" : {"index" : true},
"title" : {"store" : true},
"date" : {"store" : true},
"author" : {"analyzer" : "my_analyzer"},
"keywords" : {"store" : "yes"},
"content_type" : {"store" : "yes"},
"content_length" : {"store" : "yes"},
"language" : {"store" : "yes"}
"keywords" : {"store" : true},
"content_type" : {"store" : true},
"content_length" : {"store" : true},
"language" : {"store" : true}
}
}
}

View File

@ -127,7 +127,7 @@ experimental[The format of the additional detail information is experimental and
GET _analyze
{
"tokenizer" : "standard",
"token_filter" : ["snowball"],
"filter" : ["snowball"],
"text" : "detailed output",
"explain" : true,
"attributes" : ["keyword"] <1>

View File

@ -1,6 +1,9 @@
[[lat-lon]]
=== `lat_lon`
deprecated[5.0.0, ????????]
// https://github.com/elastic/elasticsearch/issues/19792
<<geo-queries,Geo-queries>> are usually performed by plugging the value of
each <<geo-point,`geo_point`>> field into a formula to determine whether it
falls into the required area or not. Unlike most queries, the inverted index
@ -10,7 +13,7 @@ Setting `lat_lon` to `true` causes the latitude and longitude values to be
indexed as numeric fields (called `.lat` and `.lon`). These fields can be used
by the <<query-dsl-geo-bounding-box-query,`geo_bounding_box`>> and
<<query-dsl-geo-distance-query,`geo_distance`>> queries instead of
performing in-memory calculations.
performing in-memory calculations. So this mapping:
[source,js]
--------------------------------------------------
@ -27,8 +30,15 @@ PUT my_index
}
}
}
--------------------------------------------------
// TEST[warning:geo_point lat_lon parameter is deprecated and will be removed in the next major release]
<1> Setting `lat_lon` to true indexes the geo-point in the `location.lat` and `location.lon` fields.
PUT my_index/my_type/1
Allows these actions:
[source,js]
--------------------------------------------------
PUT my_index/my_type/1?refresh
{
"location": {
"lat": 41.12,
@ -46,18 +56,17 @@ GET my_index/_search
"lon": -71
},
"distance": "50km",
"optimize_bbox": "indexed" <2>
"optimize_bbox": "indexed" <1>
}
}
}
--------------------------------------------------
// CONSOLE
<1> Setting `lat_lon` to true indexes the geo-point in the `location.lat` and `location.lon` fields.
<2> The `indexed` option tells the geo-distance query to use the inverted index instead of the in-memory calculation.
// TEST[continued]
<1> The `indexed` option tells the geo-distance query to use the inverted index instead of the in-memory calculation.
Whether the in-memory or indexed operation performs better depends both on
your dataset and on the types of queries that you are running.
NOTE: The `lat_lon` option only makes sense for single-value `geo_point`
fields. It will not work with arrays of geo-points.

View File

@ -18,7 +18,7 @@ GET /_search
{
"query": {
"function_score": {
"query": {},
"query": { "match_all": {} },
"boost": "5",
"random_score": {}, <1>
"boost_mode":"multiply"
@ -40,17 +40,17 @@ GET /_search
{
"query": {
"function_score": {
"query": {},
"query": { "match_all": {} },
"boost": "5", <1>
"functions": [
{
"filter": {},
"filter": { "match": { "test": "bar" } },
"random_score": {}, <2>
"weight": 23
},
{
"filter": {},
"weight": 42
"filter": { "match": { "test": "cat" } },
"weight": 42
}
],
"max_boost": 42,
@ -170,7 +170,7 @@ you wish to inhibit this, set `"boost_mode": "replace"`
The `weight` score allows you to multiply the score by the provided
`weight`. This can sometimes be desired since boost value set on
specific queries gets normalized, while for this score function it does
not. The number value is of type float.
not. The number value is of type float.
[source,js]
--------------------------------------------------

View File

@ -1,6 +1,8 @@
[[query-dsl-indices-query]]
=== Indices Query
deprecated[5.0.0, Search on the '_index' field instead]
The `indices` query is useful in cases where a search is executed across
multiple indices. It allows to specify a list of index names and an inner
query that is only executed for indices matching names on that list.
@ -20,7 +22,8 @@ GET /_search
}
}
--------------------------------------------------
// CONSOLE
// CONSOLE
// TEST[warning:indices query is deprecated. Instead search on the '_index' field]
You can use the `index` field to provide a single index.

View File

@ -6,7 +6,7 @@ set of documents. In order to do so, MLT selects a set of representative terms
of these input documents, forms a query using these terms, executes the query
and returns the results. The user controls the input documents, how the terms
should be selected and how the query is formed. `more_like_this` can be
shortened to `mlt` deprecated[5.0.0,use `more_like_this` instead).
shortened to `mlt` deprecated[5.0.0,use `more_like_this` instead].
The simplest use case consists of asking for documents that are similar to a
provided piece of text. Here, we are asking for all movies that have some text
@ -175,7 +175,7 @@ follows a similar syntax to the `per_field_analyzer` parameter of the
Additionally, to provide documents not necessarily present in the index,
<<docs-termvectors-artificial-doc,artificial documents>> are also supported.
`unlike`::
`unlike`::
The `unlike` parameter is used in conjunction with `like` in order not to
select terms found in a chosen set of documents. In other words, we could ask
for documents `like: "Apple"`, but `unlike: "cake crumble tree"`. The syntax

View File

@ -57,7 +57,7 @@ GET /my_index/_search
{
"query": {
"has_parent": {
"type": "blog_post",
"parent_type": "blog_post",
"query": {
"term": {
"_id": "1"

View File

@ -19,7 +19,7 @@ PUT /my-index
"doctype": {
"properties": {
"message": {
"type": "string"
"type": "keyword"
}
}
},

View File

@ -28,7 +28,7 @@ GET /_search
--------------------------------------------------
// CONSOLE
Or :
Or with the `prefix` deprecated[5.0.0, Use `value`] syntax:
[source,js]
--------------------------------------------------
@ -39,6 +39,7 @@ GET /_search
}
--------------------------------------------------
// CONSOLE
// TEST[warning:Deprecated field [prefix] used, expected [value] instead]
This multi term query allows you to control how it gets rewritten using the
<<query-dsl-multi-term-rewrite,rewrite>>

View File

@ -1,6 +1,8 @@
[[query-dsl-template-query]]
=== Template Query
deprecated[5.0.0, Use the <<search-template>> API]
A query that accepts a query template and a map of key/value pairs to fill in
template parameters. Templating is based on Mustache. For simple token substitution all you provide
is a query containing some variable that you want to substitute and the actual
@ -21,6 +23,7 @@ GET /_search
}
------------------------------------------
// CONSOLE
// TEST[warning:[template] query is deprecated, use search template api instead]
The above request is translated into:
@ -54,6 +57,7 @@ GET /_search
}
------------------------------------------
// CONSOLE
// TEST[warning:[template] query is deprecated, use search template api instead]
<1> New line characters (`\n`) should be escaped as `\\n` or removed,
and quotes (`"`) should be escaped as `\\"`.
@ -80,6 +84,7 @@ GET /_search
}
------------------------------------------
// CONSOLE
// TEST[warning:[template] query is deprecated, use search template api instead]
<1> Name of the query template in `config/scripts/`, i.e., `my_template.mustache`.
@ -113,11 +118,10 @@ GET /_search
------------------------------------------
// CONSOLE
// TEST[continued]
// TEST[warning:[template] query is deprecated, use search template api instead]
<1> Name of the query template in `config/scripts/`, i.e., `my_template.mustache`.
There is also a dedicated `template` endpoint, allows you to template an entire search request.
Please see <<search-template>> for more details.

View File

@ -52,9 +52,9 @@ It tries hard to reflect the query matching logic in terms of understanding word
[WARNING]
If you want to highlight a lot of fields in a lot of documents with complex queries this highlighter will not be fast.
In its efforts to accurately reflect query logic it creates a tiny in-memory index and re-runs the original query criteria through
Lucene's query execution planner to get access to low-level match information on the current document.
This is repeated for every field and every document that needs highlighting. If this presents a performance issue in your system consider using an alternative highlighter.
In its efforts to accurately reflect query logic it creates a tiny in-memory index and re-runs the original query criteria through
Lucene's query execution planner to get access to low-level match information on the current document.
This is repeated for every field and every document that needs highlighting. If this presents a performance issue in your system consider using an alternative highlighter.
[[postings-highlighter]]
==== Postings highlighter
@ -387,7 +387,7 @@ GET /_search
"match_phrase": {
"content": {
"query": "foo bar",
"phrase_slop": 1
"slop": 1
}
}
},
@ -413,7 +413,7 @@ GET /_search
"match_phrase": {
"content": {
"query": "foo bar",
"phrase_slop": 1,
"slop": 1,
"boost": 10.0
}
}

View File

@ -53,15 +53,16 @@ GET /_search
--------------------------------------------------
// CONSOLE
Finally, for complete control, you can specify both include and exclude patterns:
Finally, for complete control, you can specify both `includes` and `excludes`
patterns:
[source,js]
--------------------------------------------------
GET /_search
{
"_source": {
"include": [ "obj1.*", "obj2.*" ],
"exclude": [ "*.description" ]
"includes": [ "obj1.*", "obj2.*" ],
"excludes": [ "*.description" ]
},
"query" : {
"term" : { "user" : "kimchy" }

View File

@ -1,5 +1,7 @@
---
"Template query":
- skip:
features: warnings
- do:
index:
@ -23,54 +25,72 @@
- match: { acknowledged: true }
- do:
warnings:
- '[template] query is deprecated, use search template api instead'
search:
body: { "query": { "template": { "inline": { "term": { "text": { "value": "{{template}}" } } }, "params": { "template": "value1" } } } }
- match: { hits.total: 1 }
- do:
warnings:
- '[template] query is deprecated, use search template api instead'
search:
body: { "query": { "template": { "file": "file_query_template", "params": { "my_value": "value1" } } } }
- match: { hits.total: 1 }
- do:
warnings:
- '[template] query is deprecated, use search template api instead'
search:
body: { "query": { "template": { "id": "1", "params": { "my_value": "value1" } } } }
- match: { hits.total: 1 }
- do:
warnings:
- '[template] query is deprecated, use search template api instead'
search:
body: { "query": { "template": { "id": "/mustache/1", "params": { "my_value": "value1" } } } }
- match: { hits.total: 1 }
- do:
search:
warnings:
- '[template] query is deprecated, use search template api instead'
search:
body: { "query": { "template": { "inline": {"match_{{template}}": {}}, "params" : { "template" : "all" } } } }
- match: { hits.total: 2 }
- do:
warnings:
- '[template] query is deprecated, use search template api instead'
search:
body: { "query": { "template": { "inline": "{ \"term\": { \"text\": { \"value\": \"{{template}}\" } } }", "params": { "template": "value1" } } } }
- match: { hits.total: 1 }
- do:
warnings:
- '[template] query is deprecated, use search template api instead'
search:
body: { "query": { "template": { "inline": "{\"match_{{template}}\": {}}", "params" : { "template" : "all" } } } }
- match: { hits.total: 2 }
- do:
warnings:
- '[template] query is deprecated, use search template api instead'
search:
body: { "query": { "template": { "inline": "{\"match_all\": {}}", "params" : {} } } }
- match: { hits.total: 2 }
- do:
warnings:
- '[template] query is deprecated, use search template api instead'
search:
body: { "query": { "template": { "inline": "{\"query_string\": { \"query\" : \"{{query}}\" }}", "params" : { "query" : "text:\"value2 value3\"" } } } }
- match: { hits.total: 1 }

View File

@ -173,6 +173,25 @@ The argument to `catch` can be any of:
If `catch` is specified, then the `response` var must be cleared, and the test
should fail if no error is thrown.
If the arguments to `do` include `warnings` then we are expecting a `Warning`
header to come back from the request. If the arguments *don't* include a
`warnings` argument then we *don't* expect the response to include a `Warning`
header. The warnings must match exactly. Using it looks like this:
....
- do:
warnings:
- '[index] is deprecated'
- quotes are not required because yaml
- but this argument is always a list, never a single string
- no matter how many warnings you expect
get:
index: test
type: test
id: 1
....
=== `set`
For some tests, it is necessary to extract a value from the previous `response`, in
@ -284,4 +303,3 @@ This depends on the datatype of the value being examined, eg:
- length: { _tokens: 3 } # the `_tokens` array has 3 elements
- length: { _source: 5 } # the `_source` hash has 5 keys
....

View File

@ -41,7 +41,7 @@
body:
query:
more_like_this:
docs:
like:
-
_index: test_1
_type: test
@ -51,8 +51,8 @@
_index: test_1
_type: test
_id: 2
ids:
- 3
-
_id: 3
include: true
min_doc_freq: 0
min_term_freq: 0

View File

@ -72,7 +72,7 @@ setup:
search:
body:
_source:
include: [ include.field1, include.field2 ]
includes: [ include.field1, include.field2 ]
query: { match_all: {} }
- match: { hits.hits.0._source.include.field1: v1 }
- match: { hits.hits.0._source.include.field2: v2 }

View File

@ -18,6 +18,7 @@
*/
package org.elasticsearch.test.rest.yaml;
import org.apache.http.Header;
import org.apache.http.client.methods.HttpHead;
import org.apache.http.util.EntityUtils;
import org.elasticsearch.client.Response;
@ -25,6 +26,8 @@ import org.elasticsearch.common.xcontent.XContentType;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
/**
* Response obtained from a REST call, eagerly reads the response body into a string for later optional parsing.
@ -70,6 +73,19 @@ public class ClientYamlTestResponse {
return response.getStatusLine().getReasonPhrase();
}
/**
* Get a list of all of the values of all warning headers returned in the response.
*/
public List<String> getWarningHeaders() {
List<String> warningHeaders = new ArrayList<>();
for (Header header : response.getHeaders()) {
if (header.getName().equals("Warning")) {
warningHeaders.add(header.getValue());
}
}
return warningHeaders;
}
/**
* Returns the body properly parsed depending on the content type.
* Might be a string or a json object parsed as a map.

View File

@ -41,6 +41,7 @@ public final class Features {
"groovy_scripting",
"headers",
"stash_in_path",
"warnings",
"yaml"));
private Features() {

View File

@ -18,6 +18,8 @@
*/
package org.elasticsearch.test.rest.yaml.parser;
import org.elasticsearch.common.ParseFieldMatcher;
import org.elasticsearch.common.ParseFieldMatcherSupplier;
import org.elasticsearch.common.collect.Tuple;
import org.elasticsearch.common.xcontent.XContentLocation;
import org.elasticsearch.common.xcontent.XContentParser;
@ -36,7 +38,7 @@ import java.util.Map;
* Context shared across the whole tests parse phase.
* Provides shared parse methods and holds information needed to parse the test sections (e.g. es version)
*/
public class ClientYamlTestSuiteParseContext {
public class ClientYamlTestSuiteParseContext implements ParseFieldMatcherSupplier {
private static final SetupSectionParser SETUP_SECTION_PARSER = new SetupSectionParser();
private static final TeardownSectionParser TEARDOWN_SECTION_PARSER = new TeardownSectionParser();
@ -185,4 +187,9 @@ public class ClientYamlTestSuiteParseContext {
Map.Entry<String, Object> entry = map.entrySet().iterator().next();
return Tuple.tuple(entry.getKey(), entry.getValue());
}
@Override
public ParseFieldMatcher getParseFieldMatcher() {
return ParseFieldMatcher.STRICT;
}
}

View File

@ -18,6 +18,7 @@
*/
package org.elasticsearch.test.rest.yaml.parser;
import org.elasticsearch.common.ParsingException;
import org.elasticsearch.common.xcontent.XContentFactory;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.common.xcontent.XContentType;
@ -25,9 +26,13 @@ import org.elasticsearch.test.rest.yaml.section.ApiCallSection;
import org.elasticsearch.test.rest.yaml.section.DoSection;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static java.util.Collections.unmodifiableList;
/**
* Parser for do sections
*/
@ -44,6 +49,7 @@ public class DoSectionParser implements ClientYamlTestFragmentParser<DoSection>
DoSection doSection = new DoSection(parseContext.parser().getTokenLocation());
ApiCallSection apiCallSection = null;
Map<String, String> headers = new HashMap<>();
List<String> expectedWarnings = new ArrayList<>();
while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
if (token == XContentParser.Token.FIELD_NAME) {
@ -52,6 +58,17 @@ public class DoSectionParser implements ClientYamlTestFragmentParser<DoSection>
if ("catch".equals(currentFieldName)) {
doSection.setCatch(parser.text());
}
} else if (token == XContentParser.Token.START_ARRAY) {
if ("warnings".equals(currentFieldName)) {
while ((token = parser.nextToken()) == XContentParser.Token.VALUE_STRING) {
expectedWarnings.add(parser.text());
}
if (token != XContentParser.Token.END_ARRAY) {
throw new ParsingException(parser.getTokenLocation(), "[warnings] must be a string array but saw [" + token + "]");
}
} else {
throw new ParsingException(parser.getTokenLocation(), "unknown array [" + currentFieldName + "]");
}
} else if (token == XContentParser.Token.START_OBJECT) {
if ("headers".equals(currentFieldName)) {
String headerName = null;
@ -97,6 +114,7 @@ public class DoSectionParser implements ClientYamlTestFragmentParser<DoSection>
apiCallSection.addHeaders(headers);
}
doSection.setApiCallSection(apiCallSection);
doSection.setExpectedWarningHeaders(unmodifiableList(expectedWarnings));
} finally {
parser.nextToken();
}

View File

@ -29,8 +29,12 @@ import org.elasticsearch.test.rest.yaml.ClientYamlTestResponseException;
import java.io.IOException;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import static java.util.Collections.emptyList;
import static org.elasticsearch.common.collect.Tuple.tuple;
import static org.elasticsearch.test.hamcrest.RegexMatcher.matches;
import static org.hamcrest.Matchers.allOf;
@ -49,6 +53,10 @@ import static org.junit.Assert.fail;
* headers:
* Authorization: Basic user:pass
* Content-Type: application/json
* warnings:
* - Stuff is deprecated, yo
* - Don't use deprecated stuff
* - Please, stop. It hurts.
* update:
* index: test_1
* type: test
@ -63,6 +71,7 @@ public class DoSection implements ExecutableSection {
private final XContentLocation location;
private String catchParam;
private ApiCallSection apiCallSection;
private List<String> expectedWarningHeaders = emptyList();
public DoSection(XContentLocation location) {
this.location = location;
@ -84,6 +93,22 @@ public class DoSection implements ExecutableSection {
this.apiCallSection = apiCallSection;
}
/**
* Warning headers that we expect from this response. If the headers don't match exactly this request is considered to have failed.
* Defaults to emptyList.
*/
public List<String> getExpectedWarningHeaders() {
return expectedWarningHeaders;
}
/**
* Set the warning headers that we expect from this response. If the headers don't match exactly this request is considered to have
* failed. Defaults to emptyList.
*/
public void setExpectedWarningHeaders(List<String> expectedWarningHeaders) {
this.expectedWarningHeaders = expectedWarningHeaders;
}
@Override
public XContentLocation getLocation() {
return location;
@ -100,7 +125,7 @@ public class DoSection implements ExecutableSection {
}
try {
ClientYamlTestResponse restTestResponse = executionContext.callApi(apiCallSection.getApi(), apiCallSection.getParams(),
ClientYamlTestResponse response = executionContext.callApi(apiCallSection.getApi(), apiCallSection.getParams(),
apiCallSection.getBodies(), apiCallSection.getHeaders());
if (Strings.hasLength(catchParam)) {
String catchStatusCode;
@ -111,8 +136,9 @@ public class DoSection implements ExecutableSection {
} else {
throw new UnsupportedOperationException("catch value [" + catchParam + "] not supported");
}
fail(formatStatusCodeMessage(restTestResponse, catchStatusCode));
fail(formatStatusCodeMessage(response, catchStatusCode));
}
checkWarningHeaders(response.getWarningHeaders());
} catch(ClientYamlTestResponseException e) {
ClientYamlTestResponse restTestResponse = e.getRestTestResponse();
if (!Strings.hasLength(catchParam)) {
@ -135,6 +161,39 @@ public class DoSection implements ExecutableSection {
}
}
/**
* Check that the response contains only the warning headers that we expect.
*/
void checkWarningHeaders(List<String> warningHeaders) {
StringBuilder failureMessage = null;
// LinkedHashSet so that missing expected warnings come back in a predictable order which is nice for testing
Set<String> expected = new LinkedHashSet<>(expectedWarningHeaders);
for (String header : warningHeaders) {
if (expected.remove(header)) {
// Was expected, all good.
continue;
}
if (failureMessage == null) {
failureMessage = new StringBuilder("got unexpected warning headers [");
}
failureMessage.append('\n').append(header);
}
if (false == expected.isEmpty()) {
if (failureMessage == null) {
failureMessage = new StringBuilder();
} else {
failureMessage.append("\n] ");
}
failureMessage.append("didn't get expected warning headers [");
for (String header : expected) {
failureMessage.append('\n').append(header);
}
}
if (failureMessage != null) {
fail(failureMessage + "\n]");
}
}
private void assertStatusCode(ClientYamlTestResponse restTestResponse) {
Tuple<String, org.hamcrest.Matcher<Integer>> stringMatcherTuple = catches.get(catchParam);
assertThat(formatStatusCodeMessage(restTestResponse, stringMatcherTuple.v1()),

View File

@ -29,8 +29,10 @@ import org.elasticsearch.test.rest.yaml.section.DoSection;
import org.hamcrest.MatcherAssert;
import java.io.IOException;
import java.util.Arrays;
import java.util.Map;
import static java.util.Collections.singletonList;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.notNullValue;
@ -344,11 +346,11 @@ public class DoSectionParserTests extends AbstractParserTestCase {
public void testParseDoSectionWithHeaders() throws Exception {
parser = YamlXContent.yamlXContent.createParser(
"headers:\n" +
" Authorization: \"thing one\"\n" +
" Content-Type: \"application/json\"\n" +
"indices.get_warmer:\n" +
" index: test_index\n" +
" name: test_warmer"
" Authorization: \"thing one\"\n" +
" Content-Type: \"application/json\"\n" +
"indices.get_warmer:\n" +
" index: test_index\n" +
" name: test_warmer"
);
DoSectionParser doSectionParser = new DoSectionParser();
@ -381,9 +383,9 @@ public class DoSectionParserTests extends AbstractParserTestCase {
public void testParseDoSectionMultivaluedField() throws Exception {
parser = YamlXContent.yamlXContent.createParser(
"indices.get_field_mapping:\n" +
" index: test_index\n" +
" type: test_type\n" +
" field: [ text , text1 ]"
" index: test_index\n" +
" type: test_type\n" +
" field: [ text , text1 ]"
);
DoSectionParser doSectionParser = new DoSectionParser();
@ -400,6 +402,46 @@ public class DoSectionParserTests extends AbstractParserTestCase {
assertThat(doSection.getApiCallSection().getBodies().size(), equalTo(0));
}
public void testParseDoSectionExpectedWarnings() throws Exception {
parser = YamlXContent.yamlXContent.createParser(
"indices.get_field_mapping:\n" +
" index: test_index\n" +
" type: test_type\n" +
"warnings:\n" +
" - some test warning they are typically pretty long\n" +
" - some other test warning somtimes they have [in] them"
);
DoSectionParser doSectionParser = new DoSectionParser();
DoSection doSection = doSectionParser.parse(new ClientYamlTestSuiteParseContext("api", "suite", parser));
assertThat(doSection.getCatch(), nullValue());
assertThat(doSection.getApiCallSection(), notNullValue());
assertThat(doSection.getApiCallSection().getApi(), equalTo("indices.get_field_mapping"));
assertThat(doSection.getApiCallSection().getParams().size(), equalTo(2));
assertThat(doSection.getApiCallSection().getParams().get("index"), equalTo("test_index"));
assertThat(doSection.getApiCallSection().getParams().get("type"), equalTo("test_type"));
assertThat(doSection.getApiCallSection().hasBody(), equalTo(false));
assertThat(doSection.getApiCallSection().getBodies().size(), equalTo(0));
assertThat(doSection.getExpectedWarningHeaders(), equalTo(Arrays.asList(
"some test warning they are typically pretty long",
"some other test warning somtimes they have [in] them")));
parser = YamlXContent.yamlXContent.createParser(
"indices.get_field_mapping:\n" +
" index: test_index\n" +
"warnings:\n" +
" - just one entry this time"
);
doSection = doSectionParser.parse(new ClientYamlTestSuiteParseContext("api", "suite", parser));
assertThat(doSection.getCatch(), nullValue());
assertThat(doSection.getApiCallSection(), notNullValue());
assertThat(doSection.getExpectedWarningHeaders(), equalTo(singletonList(
"just one entry this time")));
}
private static void assertJsonEquals(Map<String, Object> actual, String expected) throws IOException {
Map<String,Object> expectedMap;
try (XContentParser parser = JsonXContent.jsonXContent.createParser(expected)) {

View File

@ -0,0 +1,66 @@
/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.elasticsearch.test.rest.yaml.section;
import org.elasticsearch.common.xcontent.XContentLocation;
import org.elasticsearch.test.ESTestCase;
import java.io.IOException;
import java.util.Arrays;
import static java.util.Collections.emptyList;
import static java.util.Collections.singletonList;
public class DoSectionTests extends ESTestCase {
public void testWarningHeaders() throws IOException {
DoSection section = new DoSection(new XContentLocation(1, 1));
// No warning headers doesn't throw an exception
section.checkWarningHeaders(emptyList());
// Any warning headers fail
AssertionError e = expectThrows(AssertionError.class, () -> section.checkWarningHeaders(singletonList("test")));
assertEquals("got unexpected warning headers [\ntest\n]", e.getMessage());
e = expectThrows(AssertionError.class, () -> section.checkWarningHeaders(Arrays.asList("test", "another", "some more")));
assertEquals("got unexpected warning headers [\ntest\nanother\nsome more\n]", e.getMessage());
// But not when we expect them
section.setExpectedWarningHeaders(singletonList("test"));
section.checkWarningHeaders(singletonList("test"));
section.setExpectedWarningHeaders(Arrays.asList("test", "another", "some more"));
section.checkWarningHeaders(Arrays.asList("test", "another", "some more"));
// But if you don't get some that you did expect, that is an error
section.setExpectedWarningHeaders(singletonList("test"));
e = expectThrows(AssertionError.class, () -> section.checkWarningHeaders(emptyList()));
assertEquals("didn't get expected warning headers [\ntest\n]", e.getMessage());
section.setExpectedWarningHeaders(Arrays.asList("test", "another", "some more"));
e = expectThrows(AssertionError.class, () -> section.checkWarningHeaders(emptyList()));
assertEquals("didn't get expected warning headers [\ntest\nanother\nsome more\n]", e.getMessage());
e = expectThrows(AssertionError.class, () -> section.checkWarningHeaders(Arrays.asList("test", "some more")));
assertEquals("didn't get expected warning headers [\nanother\n]", e.getMessage());
// It is also an error if you get some warning you want and some you don't want
section.setExpectedWarningHeaders(Arrays.asList("test", "another", "some more"));
e = expectThrows(AssertionError.class, () -> section.checkWarningHeaders(Arrays.asList("test", "cat")));
assertEquals("got unexpected warning headers [\ncat\n] didn't get expected warning headers [\nanother\nsome more\n]",
e.getMessage());
}
}