/* * 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.search.aggregations.bucket.histogram; import org.elasticsearch.Version; import org.elasticsearch.cluster.metadata.IndexMetaData; import org.elasticsearch.common.bytes.BytesReference; import org.elasticsearch.common.io.stream.BytesStreamOutput; import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.joda.FormatDateTimeFormatter; import org.elasticsearch.common.joda.Joda; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.xcontent.ToXContent; import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentParser; import org.elasticsearch.common.xcontent.json.JsonXContent; import org.elasticsearch.index.IndexSettings; import org.elasticsearch.index.query.QueryShardContext; import org.elasticsearch.search.DocValueFormat; import org.elasticsearch.search.SearchParseException; import org.elasticsearch.search.internal.SearchContext; import org.elasticsearch.test.ESTestCase; import org.joda.time.DateTimeZone; import org.joda.time.Instant; import java.io.IOException; import static java.lang.Math.max; import static java.lang.Math.min; import static org.hamcrest.Matchers.equalTo; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; public class ExtendedBoundsTests extends ESTestCase { /** * Construct a random {@link ExtendedBounds}. */ public static ExtendedBounds randomExtendedBounds() { ExtendedBounds bounds = randomParsedExtendedBounds(); if (randomBoolean()) { bounds = unparsed(bounds); } return bounds; } /** * Construct a random {@link ExtendedBounds} in pre-parsed form. */ public static ExtendedBounds randomParsedExtendedBounds() { if (randomBoolean()) { // Construct with one missing bound if (randomBoolean()) { return new ExtendedBounds(null, randomLong()); } return new ExtendedBounds(randomLong(), null); } long a = randomLong(); long b; do { b = randomLong(); } while (a == b); long min = min(a, b); long max = max(a, b); return new ExtendedBounds(min, max); } /** * Convert an extended bounds in parsed for into one in unparsed form. */ public static ExtendedBounds unparsed(ExtendedBounds template) { // It'd probably be better to randomize the formatter FormatDateTimeFormatter formatter = Joda.forPattern("dateOptionalTime"); String minAsStr = template.getMin() == null ? null : formatter.printer().print(new Instant(template.getMin())); String maxAsStr = template.getMax() == null ? null : formatter.printer().print(new Instant(template.getMax())); return new ExtendedBounds(minAsStr, maxAsStr); } public void testParseAndValidate() { long now = randomLong(); Settings indexSettings = Settings.builder().put(IndexMetaData.SETTING_VERSION_CREATED, Version.CURRENT) .put(IndexMetaData.SETTING_NUMBER_OF_SHARDS, 1).put(IndexMetaData.SETTING_NUMBER_OF_REPLICAS, 1).build(); SearchContext context = mock(SearchContext.class); QueryShardContext qsc = new QueryShardContext(0, new IndexSettings(IndexMetaData.builder("foo").settings(indexSettings).build(), indexSettings), null, null, null, null, null, xContentRegistry(), null, null, () -> now); when(context.getQueryShardContext()).thenReturn(qsc); FormatDateTimeFormatter formatter = Joda.forPattern("dateOptionalTime"); DocValueFormat format = new DocValueFormat.DateTime(formatter, DateTimeZone.UTC); ExtendedBounds expected = randomParsedExtendedBounds(); ExtendedBounds parsed = unparsed(expected).parseAndValidate("test", context, format); // parsed won't *equal* expected because equal includes the String parts assertEquals(expected.getMin(), parsed.getMin()); assertEquals(expected.getMax(), parsed.getMax()); parsed = new ExtendedBounds("now", null).parseAndValidate("test", context, format); assertEquals(now, (long) parsed.getMin()); assertNull(parsed.getMax()); parsed = new ExtendedBounds(null, "now").parseAndValidate("test", context, format); assertNull(parsed.getMin()); assertEquals(now, (long) parsed.getMax()); SearchParseException e = expectThrows(SearchParseException.class, () -> new ExtendedBounds(100L, 90L).parseAndValidate("test", context, format)); assertEquals("[extended_bounds.min][100] cannot be greater than [extended_bounds.max][90] for histogram aggregation [test]", e.getMessage()); e = expectThrows(SearchParseException.class, () -> unparsed(new ExtendedBounds(100L, 90L)).parseAndValidate("test", context, format)); assertEquals("[extended_bounds.min][100] cannot be greater than [extended_bounds.max][90] for histogram aggregation [test]", e.getMessage()); } public void testTransportRoundTrip() throws IOException { ExtendedBounds orig = randomExtendedBounds(); BytesReference origBytes; try (BytesStreamOutput out = new BytesStreamOutput()) { orig.writeTo(out); origBytes = out.bytes(); } ExtendedBounds read; try (StreamInput in = origBytes.streamInput()) { read = new ExtendedBounds(in); assertEquals("read fully", 0, in.available()); } assertEquals(orig, read); BytesReference readBytes; try (BytesStreamOutput out = new BytesStreamOutput()) { read.writeTo(out); readBytes = out.bytes(); } assertEquals(origBytes, readBytes); } public void testXContentRoundTrip() throws Exception { ExtendedBounds orig = randomExtendedBounds(); try (XContentBuilder out = JsonXContent.contentBuilder()) { out.startObject(); orig.toXContent(out, ToXContent.EMPTY_PARAMS); out.endObject(); try (XContentParser in = createParser(JsonXContent.jsonXContent, out.bytes())) { XContentParser.Token token = in.currentToken(); assertNull(token); token = in.nextToken(); assertThat(token, equalTo(XContentParser.Token.START_OBJECT)); token = in.nextToken(); assertThat(token, equalTo(XContentParser.Token.FIELD_NAME)); assertThat(in.currentName(), equalTo(ExtendedBounds.EXTENDED_BOUNDS_FIELD.getPreferredName())); ExtendedBounds read = ExtendedBounds.PARSER.apply(in, null); assertEquals(orig, read); } catch (Exception e) { throw new Exception("Error parsing [" + out.bytes().utf8ToString() + "]", e); } } } }