Test that rank_eval request parsing is not lenient (#28516)
Parsing of a ranking evaluation request and its subcomponents should throw parsing errors on unknown fields. This change adds tests for this and changes the parser behaviour in cases where it is needed.
This commit is contained in:
parent
a55eda626f
commit
01791277cb
|
@ -164,7 +164,7 @@ public class DiscountedCumulativeGain implements EvaluationMetric {
|
||||||
private static final ParseField K_FIELD = new ParseField("k");
|
private static final ParseField K_FIELD = new ParseField("k");
|
||||||
private static final ParseField NORMALIZE_FIELD = new ParseField("normalize");
|
private static final ParseField NORMALIZE_FIELD = new ParseField("normalize");
|
||||||
private static final ParseField UNKNOWN_DOC_RATING_FIELD = new ParseField("unknown_doc_rating");
|
private static final ParseField UNKNOWN_DOC_RATING_FIELD = new ParseField("unknown_doc_rating");
|
||||||
private static final ConstructingObjectParser<DiscountedCumulativeGain, Void> PARSER = new ConstructingObjectParser<>("dcg_at", true,
|
private static final ConstructingObjectParser<DiscountedCumulativeGain, Void> PARSER = new ConstructingObjectParser<>("dcg_at", false,
|
||||||
args -> {
|
args -> {
|
||||||
Boolean normalized = (Boolean) args[0];
|
Boolean normalized = (Boolean) args[0];
|
||||||
Integer optK = (Integer) args[2];
|
Integer optK = (Integer) args[2];
|
||||||
|
|
|
@ -20,7 +20,7 @@
|
||||||
package org.elasticsearch.index.rankeval;
|
package org.elasticsearch.index.rankeval;
|
||||||
|
|
||||||
import org.elasticsearch.common.io.stream.NamedWriteable;
|
import org.elasticsearch.common.io.stream.NamedWriteable;
|
||||||
import org.elasticsearch.common.xcontent.ToXContent;
|
import org.elasticsearch.common.xcontent.ToXContentObject;
|
||||||
import org.elasticsearch.index.rankeval.RatedDocument.DocumentKey;
|
import org.elasticsearch.index.rankeval.RatedDocument.DocumentKey;
|
||||||
import org.elasticsearch.search.SearchHit;
|
import org.elasticsearch.search.SearchHit;
|
||||||
import org.elasticsearch.search.SearchHits;
|
import org.elasticsearch.search.SearchHits;
|
||||||
|
@ -36,7 +36,7 @@ import java.util.stream.Collectors;
|
||||||
* Implementations of {@link EvaluationMetric} need to provide a way to compute the quality metric for
|
* Implementations of {@link EvaluationMetric} need to provide a way to compute the quality metric for
|
||||||
* a result list returned by some search (@link {@link SearchHits}) and a list of rated documents.
|
* a result list returned by some search (@link {@link SearchHits}) and a list of rated documents.
|
||||||
*/
|
*/
|
||||||
public interface EvaluationMetric extends ToXContent, NamedWriteable {
|
public interface EvaluationMetric extends ToXContentObject, NamedWriteable {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a single metric representing the ranking quality of a set of returned
|
* Returns a single metric representing the ranking quality of a set of returned
|
||||||
|
|
|
@ -213,7 +213,7 @@ public class RatedRequest implements Writeable, ToXContentObject {
|
||||||
private static final ParseField FIELDS_FIELD = new ParseField("summary_fields");
|
private static final ParseField FIELDS_FIELD = new ParseField("summary_fields");
|
||||||
private static final ParseField TEMPLATE_ID_FIELD = new ParseField("template_id");
|
private static final ParseField TEMPLATE_ID_FIELD = new ParseField("template_id");
|
||||||
|
|
||||||
private static final ConstructingObjectParser<RatedRequest, Void> PARSER = new ConstructingObjectParser<>("requests",
|
private static final ConstructingObjectParser<RatedRequest, Void> PARSER = new ConstructingObjectParser<>("request",
|
||||||
a -> new RatedRequest((String) a[0], (List<RatedDocument>) a[1], (SearchSourceBuilder) a[2], (Map<String, Object>) a[3],
|
a -> new RatedRequest((String) a[0], (List<RatedDocument>) a[1], (SearchSourceBuilder) a[2], (Map<String, Object>) a[3],
|
||||||
(String) a[4]));
|
(String) a[4]));
|
||||||
|
|
||||||
|
|
|
@ -19,6 +19,7 @@
|
||||||
|
|
||||||
package org.elasticsearch.index.rankeval;
|
package org.elasticsearch.index.rankeval;
|
||||||
|
|
||||||
|
import org.elasticsearch.common.bytes.BytesReference;
|
||||||
import org.elasticsearch.common.io.stream.NamedWriteableRegistry;
|
import org.elasticsearch.common.io.stream.NamedWriteableRegistry;
|
||||||
import org.elasticsearch.common.text.Text;
|
import org.elasticsearch.common.text.Text;
|
||||||
import org.elasticsearch.common.xcontent.ToXContent;
|
import org.elasticsearch.common.xcontent.ToXContent;
|
||||||
|
@ -39,6 +40,8 @@ import java.util.List;
|
||||||
|
|
||||||
import static org.elasticsearch.index.rankeval.EvaluationMetric.filterUnknownDocuments;
|
import static org.elasticsearch.index.rankeval.EvaluationMetric.filterUnknownDocuments;
|
||||||
import static org.elasticsearch.test.EqualsHashCodeTestUtils.checkEqualsAndHashCode;
|
import static org.elasticsearch.test.EqualsHashCodeTestUtils.checkEqualsAndHashCode;
|
||||||
|
import static org.elasticsearch.test.XContentTestUtils.insertRandomFields;
|
||||||
|
import static org.hamcrest.Matchers.startsWith;
|
||||||
|
|
||||||
public class DiscountedCumulativeGainTests extends ESTestCase {
|
public class DiscountedCumulativeGainTests extends ESTestCase {
|
||||||
|
|
||||||
|
@ -243,6 +246,20 @@ public class DiscountedCumulativeGainTests extends ESTestCase {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void testXContentParsingIsNotLenient() throws IOException {
|
||||||
|
DiscountedCumulativeGain testItem = createTestItem();
|
||||||
|
XContentType xContentType = randomFrom(XContentType.values());
|
||||||
|
BytesReference originalBytes = toShuffledXContent(testItem, xContentType, ToXContent.EMPTY_PARAMS, randomBoolean());
|
||||||
|
BytesReference withRandomFields = insertRandomFields(xContentType, originalBytes, null, random());
|
||||||
|
try (XContentParser parser = createParser(xContentType.xContent(), withRandomFields)) {
|
||||||
|
parser.nextToken();
|
||||||
|
parser.nextToken();
|
||||||
|
IllegalArgumentException exception = expectThrows(IllegalArgumentException.class,
|
||||||
|
() -> DiscountedCumulativeGain.fromXContent(parser));
|
||||||
|
assertThat(exception.getMessage(), startsWith("[dcg_at] unknown field"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void testSerialization() throws IOException {
|
public void testSerialization() throws IOException {
|
||||||
DiscountedCumulativeGain original = createTestItem();
|
DiscountedCumulativeGain original = createTestItem();
|
||||||
DiscountedCumulativeGain deserialized = ESTestCase.copyWriteable(original, new NamedWriteableRegistry(Collections.emptyList()),
|
DiscountedCumulativeGain deserialized = ESTestCase.copyWriteable(original, new NamedWriteableRegistry(Collections.emptyList()),
|
||||||
|
|
|
@ -19,6 +19,7 @@
|
||||||
|
|
||||||
package org.elasticsearch.index.rankeval;
|
package org.elasticsearch.index.rankeval;
|
||||||
|
|
||||||
|
import org.elasticsearch.common.bytes.BytesReference;
|
||||||
import org.elasticsearch.common.io.stream.NamedWriteableRegistry;
|
import org.elasticsearch.common.io.stream.NamedWriteableRegistry;
|
||||||
import org.elasticsearch.common.text.Text;
|
import org.elasticsearch.common.text.Text;
|
||||||
import org.elasticsearch.common.xcontent.ToXContent;
|
import org.elasticsearch.common.xcontent.ToXContent;
|
||||||
|
@ -40,6 +41,8 @@ import java.util.List;
|
||||||
import java.util.Vector;
|
import java.util.Vector;
|
||||||
|
|
||||||
import static org.elasticsearch.test.EqualsHashCodeTestUtils.checkEqualsAndHashCode;
|
import static org.elasticsearch.test.EqualsHashCodeTestUtils.checkEqualsAndHashCode;
|
||||||
|
import static org.elasticsearch.test.XContentTestUtils.insertRandomFields;
|
||||||
|
import static org.hamcrest.Matchers.startsWith;
|
||||||
|
|
||||||
public class MeanReciprocalRankTests extends ESTestCase {
|
public class MeanReciprocalRankTests extends ESTestCase {
|
||||||
|
|
||||||
|
@ -169,6 +172,20 @@ public class MeanReciprocalRankTests extends ESTestCase {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void testXContentParsingIsNotLenient() throws IOException {
|
||||||
|
MeanReciprocalRank testItem = createTestItem();
|
||||||
|
XContentType xContentType = randomFrom(XContentType.values());
|
||||||
|
BytesReference originalBytes = toShuffledXContent(testItem, xContentType, ToXContent.EMPTY_PARAMS, randomBoolean());
|
||||||
|
BytesReference withRandomFields = insertRandomFields(xContentType, originalBytes, null, random());
|
||||||
|
try (XContentParser parser = createParser(xContentType.xContent(), withRandomFields)) {
|
||||||
|
parser.nextToken();
|
||||||
|
parser.nextToken();
|
||||||
|
IllegalArgumentException exception = expectThrows(IllegalArgumentException.class,
|
||||||
|
() -> MeanReciprocalRank.fromXContent(parser));
|
||||||
|
assertThat(exception.getMessage(), startsWith("[reciprocal_rank] unknown field"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create SearchHits for testing, starting from dociId 'from' up to docId 'to'.
|
* Create SearchHits for testing, starting from dociId 'from' up to docId 'to'.
|
||||||
* The search hits index also need to be provided
|
* The search hits index also need to be provided
|
||||||
|
|
|
@ -19,6 +19,7 @@
|
||||||
|
|
||||||
package org.elasticsearch.index.rankeval;
|
package org.elasticsearch.index.rankeval;
|
||||||
|
|
||||||
|
import org.elasticsearch.common.bytes.BytesReference;
|
||||||
import org.elasticsearch.common.io.stream.NamedWriteableRegistry;
|
import org.elasticsearch.common.io.stream.NamedWriteableRegistry;
|
||||||
import org.elasticsearch.common.text.Text;
|
import org.elasticsearch.common.text.Text;
|
||||||
import org.elasticsearch.common.xcontent.ToXContent;
|
import org.elasticsearch.common.xcontent.ToXContent;
|
||||||
|
@ -40,6 +41,8 @@ import java.util.List;
|
||||||
import java.util.Vector;
|
import java.util.Vector;
|
||||||
|
|
||||||
import static org.elasticsearch.test.EqualsHashCodeTestUtils.checkEqualsAndHashCode;
|
import static org.elasticsearch.test.EqualsHashCodeTestUtils.checkEqualsAndHashCode;
|
||||||
|
import static org.elasticsearch.test.XContentTestUtils.insertRandomFields;
|
||||||
|
import static org.hamcrest.Matchers.startsWith;
|
||||||
|
|
||||||
public class PrecisionAtKTests extends ESTestCase {
|
public class PrecisionAtKTests extends ESTestCase {
|
||||||
|
|
||||||
|
@ -182,6 +185,19 @@ public class PrecisionAtKTests extends ESTestCase {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void testXContentParsingIsNotLenient() throws IOException {
|
||||||
|
PrecisionAtK testItem = createTestItem();
|
||||||
|
XContentType xContentType = randomFrom(XContentType.values());
|
||||||
|
BytesReference originalBytes = toShuffledXContent(testItem, xContentType, ToXContent.EMPTY_PARAMS, randomBoolean());
|
||||||
|
BytesReference withRandomFields = insertRandomFields(xContentType, originalBytes, null, random());
|
||||||
|
try (XContentParser parser = createParser(xContentType.xContent(), withRandomFields)) {
|
||||||
|
parser.nextToken();
|
||||||
|
parser.nextToken();
|
||||||
|
IllegalArgumentException exception = expectThrows(IllegalArgumentException.class, () -> PrecisionAtK.fromXContent(parser));
|
||||||
|
assertThat(exception.getMessage(), startsWith("[precision] unknown field"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void testSerialization() throws IOException {
|
public void testSerialization() throws IOException {
|
||||||
PrecisionAtK original = createTestItem();
|
PrecisionAtK original = createTestItem();
|
||||||
PrecisionAtK deserialized = ESTestCase.copyWriteable(original, new NamedWriteableRegistry(Collections.emptyList()),
|
PrecisionAtK deserialized = ESTestCase.copyWriteable(original, new NamedWriteableRegistry(Collections.emptyList()),
|
||||||
|
|
|
@ -19,12 +19,14 @@
|
||||||
|
|
||||||
package org.elasticsearch.index.rankeval;
|
package org.elasticsearch.index.rankeval;
|
||||||
|
|
||||||
|
import org.elasticsearch.common.bytes.BytesReference;
|
||||||
import org.elasticsearch.common.io.stream.NamedWriteableRegistry;
|
import org.elasticsearch.common.io.stream.NamedWriteableRegistry;
|
||||||
import org.elasticsearch.common.xcontent.NamedXContentRegistry;
|
import org.elasticsearch.common.xcontent.NamedXContentRegistry;
|
||||||
import org.elasticsearch.common.xcontent.ToXContent;
|
import org.elasticsearch.common.xcontent.ToXContent;
|
||||||
import org.elasticsearch.common.xcontent.XContentBuilder;
|
import org.elasticsearch.common.xcontent.XContentBuilder;
|
||||||
import org.elasticsearch.common.xcontent.XContentFactory;
|
import org.elasticsearch.common.xcontent.XContentFactory;
|
||||||
import org.elasticsearch.common.xcontent.XContentParser;
|
import org.elasticsearch.common.xcontent.XContentParser;
|
||||||
|
import org.elasticsearch.common.xcontent.XContentType;
|
||||||
import org.elasticsearch.common.xcontent.json.JsonXContent;
|
import org.elasticsearch.common.xcontent.json.JsonXContent;
|
||||||
import org.elasticsearch.index.query.MatchAllQueryBuilder;
|
import org.elasticsearch.index.query.MatchAllQueryBuilder;
|
||||||
import org.elasticsearch.index.query.QueryBuilder;
|
import org.elasticsearch.index.query.QueryBuilder;
|
||||||
|
@ -47,6 +49,8 @@ import java.util.Map.Entry;
|
||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
import static org.elasticsearch.test.EqualsHashCodeTestUtils.checkEqualsAndHashCode;
|
import static org.elasticsearch.test.EqualsHashCodeTestUtils.checkEqualsAndHashCode;
|
||||||
|
import static org.elasticsearch.test.XContentTestUtils.insertRandomFields;
|
||||||
|
import static org.hamcrest.Matchers.startsWith;
|
||||||
|
|
||||||
public class RankEvalSpecTests extends ESTestCase {
|
public class RankEvalSpecTests extends ESTestCase {
|
||||||
|
|
||||||
|
@ -123,6 +127,17 @@ public class RankEvalSpecTests extends ESTestCase {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void testXContentParsingIsNotLenient() throws IOException {
|
||||||
|
RankEvalSpec testItem = createTestItem();
|
||||||
|
XContentType xContentType = randomFrom(XContentType.values());
|
||||||
|
BytesReference originalBytes = toShuffledXContent(testItem, xContentType, ToXContent.EMPTY_PARAMS, randomBoolean());
|
||||||
|
BytesReference withRandomFields = insertRandomFields(xContentType, originalBytes, null, random());
|
||||||
|
try (XContentParser parser = createParser(xContentType.xContent(), withRandomFields)) {
|
||||||
|
Exception exception = expectThrows(Exception.class, () -> RankEvalSpec.parse(parser));
|
||||||
|
assertThat(exception.getMessage(), startsWith("[rank_eval] failed to parse field"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void testSerialization() throws IOException {
|
public void testSerialization() throws IOException {
|
||||||
RankEvalSpec original = createTestItem();
|
RankEvalSpec original = createTestItem();
|
||||||
RankEvalSpec deserialized = copy(original);
|
RankEvalSpec deserialized = copy(original);
|
||||||
|
|
|
@ -19,6 +19,7 @@
|
||||||
|
|
||||||
package org.elasticsearch.index.rankeval;
|
package org.elasticsearch.index.rankeval;
|
||||||
|
|
||||||
|
import org.elasticsearch.common.bytes.BytesReference;
|
||||||
import org.elasticsearch.common.io.stream.NamedWriteableRegistry;
|
import org.elasticsearch.common.io.stream.NamedWriteableRegistry;
|
||||||
import org.elasticsearch.common.xcontent.ToXContent;
|
import org.elasticsearch.common.xcontent.ToXContent;
|
||||||
import org.elasticsearch.common.xcontent.XContentBuilder;
|
import org.elasticsearch.common.xcontent.XContentBuilder;
|
||||||
|
@ -31,6 +32,8 @@ import java.io.IOException;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
|
||||||
import static org.elasticsearch.test.EqualsHashCodeTestUtils.checkEqualsAndHashCode;
|
import static org.elasticsearch.test.EqualsHashCodeTestUtils.checkEqualsAndHashCode;
|
||||||
|
import static org.elasticsearch.test.XContentTestUtils.insertRandomFields;
|
||||||
|
import static org.hamcrest.Matchers.startsWith;
|
||||||
|
|
||||||
public class RatedDocumentTests extends ESTestCase {
|
public class RatedDocumentTests extends ESTestCase {
|
||||||
|
|
||||||
|
@ -50,6 +53,17 @@ public class RatedDocumentTests extends ESTestCase {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void testXContentParsingIsNotLenient() throws IOException {
|
||||||
|
RatedDocument testItem = createRatedDocument();
|
||||||
|
XContentType xContentType = randomFrom(XContentType.values());
|
||||||
|
BytesReference originalBytes = toShuffledXContent(testItem, xContentType, ToXContent.EMPTY_PARAMS, randomBoolean());
|
||||||
|
BytesReference withRandomFields = insertRandomFields(xContentType, originalBytes, null, random());
|
||||||
|
try (XContentParser parser = createParser(xContentType.xContent(), withRandomFields)) {
|
||||||
|
Exception exception = expectThrows(IllegalArgumentException.class, () -> RatedDocument.fromXContent(parser));
|
||||||
|
assertThat(exception.getMessage(), startsWith("[rated_document] unknown field"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void testSerialization() throws IOException {
|
public void testSerialization() throws IOException {
|
||||||
RatedDocument original = createRatedDocument();
|
RatedDocument original = createRatedDocument();
|
||||||
RatedDocument deserialized = ESTestCase.copyWriteable(original, new NamedWriteableRegistry(Collections.emptyList()),
|
RatedDocument deserialized = ESTestCase.copyWriteable(original, new NamedWriteableRegistry(Collections.emptyList()),
|
||||||
|
|
|
@ -19,6 +19,8 @@
|
||||||
|
|
||||||
package org.elasticsearch.index.rankeval;
|
package org.elasticsearch.index.rankeval;
|
||||||
|
|
||||||
|
import org.elasticsearch.common.ParsingException;
|
||||||
|
import org.elasticsearch.common.bytes.BytesReference;
|
||||||
import org.elasticsearch.common.io.stream.NamedWriteableRegistry;
|
import org.elasticsearch.common.io.stream.NamedWriteableRegistry;
|
||||||
import org.elasticsearch.common.settings.Settings;
|
import org.elasticsearch.common.settings.Settings;
|
||||||
import org.elasticsearch.common.xcontent.NamedXContentRegistry;
|
import org.elasticsearch.common.xcontent.NamedXContentRegistry;
|
||||||
|
@ -48,6 +50,8 @@ import java.util.stream.Stream;
|
||||||
import static java.util.Collections.emptyList;
|
import static java.util.Collections.emptyList;
|
||||||
import static java.util.stream.Collectors.toList;
|
import static java.util.stream.Collectors.toList;
|
||||||
import static org.elasticsearch.test.EqualsHashCodeTestUtils.checkEqualsAndHashCode;
|
import static org.elasticsearch.test.EqualsHashCodeTestUtils.checkEqualsAndHashCode;
|
||||||
|
import static org.elasticsearch.test.XContentTestUtils.insertRandomFields;
|
||||||
|
import static org.hamcrest.Matchers.startsWith;
|
||||||
|
|
||||||
public class RatedRequestsTests extends ESTestCase {
|
public class RatedRequestsTests extends ESTestCase {
|
||||||
|
|
||||||
|
@ -123,6 +127,22 @@ public class RatedRequestsTests extends ESTestCase {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void testXContentParsingIsNotLenient() throws IOException {
|
||||||
|
RatedRequest testItem = createTestItem(randomBoolean());
|
||||||
|
XContentType xContentType = randomFrom(XContentType.values());
|
||||||
|
BytesReference originalBytes = toShuffledXContent(testItem, xContentType, ToXContent.EMPTY_PARAMS, randomBoolean());
|
||||||
|
BytesReference withRandomFields = insertRandomFields(xContentType, originalBytes, null, random());
|
||||||
|
try (XContentParser parser = createParser(xContentType.xContent(), withRandomFields)) {
|
||||||
|
Exception exception = expectThrows(Exception.class, () -> RatedRequest.fromXContent(parser));
|
||||||
|
if (exception instanceof IllegalArgumentException) {
|
||||||
|
assertThat(exception.getMessage(), startsWith("[request] unknown field"));
|
||||||
|
}
|
||||||
|
if (exception instanceof ParsingException) {
|
||||||
|
assertThat(exception.getMessage(), startsWith("[request] failed to parse field"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void testSerialization() throws IOException {
|
public void testSerialization() throws IOException {
|
||||||
RatedRequest original = createTestItem(randomBoolean());
|
RatedRequest original = createTestItem(randomBoolean());
|
||||||
RatedRequest deserialized = copy(original);
|
RatedRequest deserialized = copy(original);
|
||||||
|
|
Loading…
Reference in New Issue