All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.elasticsearch.test.AbstractXContentTestCase Maven / Gradle / Ivy

There is a newer version: 8.15.1
Show newest version
/*
 * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
 * or more contributor license agreements. Licensed under the "Elastic License
 * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
 * Public License v 1"; you may not use this file except in compliance with, at
 * your election, the "Elastic License 2.0", the "GNU Affero General Public
 * License v3.0 only", or the "Server Side Public License, v 1".
 */

package org.elasticsearch.test;

import org.elasticsearch.common.CheckedBiConsumer;
import org.elasticsearch.common.CheckedBiFunction;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.bytes.BytesReference;
import org.elasticsearch.common.xcontent.ChunkedToXContent;
import org.elasticsearch.common.xcontent.XContentHelper;
import org.elasticsearch.core.CheckedFunction;
import org.elasticsearch.core.Predicates;
import org.elasticsearch.xcontent.ToXContent;
import org.elasticsearch.xcontent.XContent;
import org.elasticsearch.xcontent.XContentBuilder;
import org.elasticsearch.xcontent.XContentFactory;
import org.elasticsearch.xcontent.XContentParser;
import org.elasticsearch.xcontent.XContentType;

import java.io.IOException;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;

import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertToXContentEquivalent;

public abstract class AbstractXContentTestCase extends ESTestCase {
    public static final int NUMBER_OF_TEST_RUNS = 20;

    public static  XContentTester xContentTester(
        CheckedBiFunction createParser,
        Supplier instanceSupplier,
        CheckedBiConsumer toXContent,
        CheckedFunction fromXContent
    ) {
        return new XContentTester<>(createParser, x -> instanceSupplier.get(), (testInstance, xContentType) -> {
            try (XContentBuilder builder = XContentBuilder.builder(xContentType.xContent())) {
                toXContent.accept(testInstance, builder);
                return BytesReference.bytes(builder);
            }
        }, fromXContent);
    }

    public static  XContentTester xContentTester(
        CheckedBiFunction createParser,
        Supplier instanceSupplier,
        CheckedFunction fromXContent
    ) {
        return xContentTester(createParser, instanceSupplier, ToXContent.EMPTY_PARAMS, fromXContent);
    }

    public static  XContentTester xContentTester(
        CheckedBiFunction createParser,
        Supplier instanceSupplier,
        ToXContent.Params toXContentParams,
        CheckedFunction fromXContent
    ) {
        return new XContentTester<>(
            createParser,
            x -> instanceSupplier.get(),
            (testInstance, xContentType) -> XContentHelper.toXContent(testInstance, xContentType, toXContentParams, false),
            fromXContent
        );
    }

    public static  XContentTester xContentTester(
        CheckedBiFunction createParser,
        Function instanceSupplier,
        ToXContent.Params toXContentParams,
        CheckedFunction fromXContent
    ) {
        return new XContentTester<>(
            createParser,
            instanceSupplier,
            (testInstance, xContentType) -> XContentHelper.toXContent(testInstance, xContentType, toXContentParams, false),
            fromXContent
        );
    }

    public static  XContentTester chunkedXContentTester(
        CheckedBiFunction createParser,
        Function instanceSupplier,
        ToXContent.Params toXContentParams,
        CheckedFunction fromXContent
    ) {
        return new XContentTester<>(createParser, instanceSupplier, (testInstance, xContentType) -> {
            try (XContentBuilder builder = XContentBuilder.builder(xContentType.xContent())) {
                var serialization = testInstance.toXContentChunked(toXContentParams);
                if (testInstance.isFragment()) {
                    builder.startObject();
                }
                while (serialization.hasNext()) {
                    serialization.next().toXContent(builder, toXContentParams);
                }
                if (testInstance.isFragment()) {
                    builder.endObject();
                }
                return BytesReference.bytes(builder);
            }
        }, fromXContent);
    }

    /**
     * Tests converting to and from xcontent.
     */
    public static class XContentTester {
        private final CheckedBiFunction createParser;
        private final Function instanceSupplier;
        private final CheckedBiFunction toXContent;
        private final CheckedFunction fromXContent;

        private int numberOfTestRuns = NUMBER_OF_TEST_RUNS;
        private boolean supportsUnknownFields = false;
        private String[] shuffleFieldsExceptions = Strings.EMPTY_ARRAY;
        private Predicate randomFieldsExcludeFilter = field -> false;
        private BiConsumer assertEqualsConsumer = (expectedInstance, newInstance) -> {
            assertNotSame(newInstance, expectedInstance);
            assertEquals(expectedInstance, newInstance);
            assertEquals(expectedInstance.hashCode(), newInstance.hashCode());
        };
        private boolean assertToXContentEquivalence = true;
        private Consumer dispose = t -> {};

        private XContentTester(
            CheckedBiFunction createParser,
            Function instanceSupplier,
            CheckedBiFunction toXContent,
            CheckedFunction fromXContent
        ) {
            this.createParser = createParser;
            this.instanceSupplier = instanceSupplier;
            this.toXContent = toXContent;
            this.fromXContent = fromXContent;
        }

        public void test() throws IOException {
            for (int runs = 0; runs < numberOfTestRuns; runs++) {
                XContentType xContentType = randomFrom(XContentType.values()).canonical();
                T testInstance = instanceSupplier.apply(xContentType);
                try {
                    BytesReference originalXContent = toXContent.apply(testInstance, xContentType);
                    BytesReference shuffledContent = insertRandomFieldsAndShuffle(
                        originalXContent,
                        xContentType,
                        supportsUnknownFields,
                        shuffleFieldsExceptions,
                        randomFieldsExcludeFilter,
                        createParser
                    );
                    final T parsed;
                    try (XContentParser parser = createParser.apply(XContentFactory.xContent(xContentType), shuffledContent)) {
                        parsed = fromXContent.apply(parser);
                    }
                    try {
                        assertEqualsConsumer.accept(testInstance, parsed);
                        if (assertToXContentEquivalence) {
                            assertToXContentEquivalent(
                                toXContent.apply(testInstance, xContentType),
                                toXContent.apply(parsed, xContentType),
                                xContentType
                            );
                        }
                    } finally {
                        dispose.accept(parsed);
                    }
                } finally {
                    dispose.accept(testInstance);
                }
            }
        }

        public XContentTester numberOfTestRuns(int numberOfTestRuns) {
            this.numberOfTestRuns = numberOfTestRuns;
            return this;
        }

        public XContentTester supportsUnknownFields(boolean supportsUnknownFields) {
            this.supportsUnknownFields = supportsUnknownFields;
            return this;
        }

        public XContentTester shuffleFieldsExceptions(String[] shuffleFieldsExceptions) {
            this.shuffleFieldsExceptions = shuffleFieldsExceptions;
            return this;
        }

        public XContentTester randomFieldsExcludeFilter(Predicate randomFieldsExcludeFilter) {
            this.randomFieldsExcludeFilter = randomFieldsExcludeFilter;
            return this;
        }

        public XContentTester assertEqualsConsumer(BiConsumer assertEqualsConsumer) {
            this.assertEqualsConsumer = assertEqualsConsumer;
            return this;
        }

        public XContentTester assertToXContentEquivalence(boolean assertToXContentEquivalence) {
            this.assertToXContentEquivalence = assertToXContentEquivalence;
            return this;
        }

        public XContentTester dispose(Consumer dispose) {
            this.dispose = dispose;
            return this;
        }
    }

    public static  void testFromXContent(
        int numberOfTestRuns,
        Supplier instanceSupplier,
        boolean supportsUnknownFields,
        String[] shuffleFieldsExceptions,
        Predicate randomFieldsExcludeFilter,
        CheckedBiFunction createParserFunction,
        CheckedFunction fromXContent,
        BiConsumer assertEqualsConsumer,
        boolean assertToXContentEquivalence,
        ToXContent.Params toXContentParams
    ) throws IOException {
        testFromXContent(
            numberOfTestRuns,
            instanceSupplier,
            supportsUnknownFields,
            shuffleFieldsExceptions,
            randomFieldsExcludeFilter,
            createParserFunction,
            fromXContent,
            assertEqualsConsumer,
            assertToXContentEquivalence,
            toXContentParams,
            t -> {}
        );
    }

    public static  void testFromXContent(
        int numberOfTestRuns,
        Supplier instanceSupplier,
        boolean supportsUnknownFields,
        String[] shuffleFieldsExceptions,
        Predicate randomFieldsExcludeFilter,
        CheckedBiFunction createParserFunction,
        CheckedFunction fromXContent,
        BiConsumer assertEqualsConsumer,
        boolean assertToXContentEquivalence,
        ToXContent.Params toXContentParams,
        Consumer dispose
    ) throws IOException {
        xContentTester(createParserFunction, instanceSupplier, toXContentParams, fromXContent).numberOfTestRuns(numberOfTestRuns)
            .supportsUnknownFields(supportsUnknownFields)
            .shuffleFieldsExceptions(shuffleFieldsExceptions)
            .randomFieldsExcludeFilter(randomFieldsExcludeFilter)
            .assertEqualsConsumer(assertEqualsConsumer)
            .assertToXContentEquivalence(assertToXContentEquivalence)
            .dispose(dispose)
            .test();
    }

    /**
     * Generic test that creates new instance from the test instance and checks
     * both for equality and asserts equality on the two queries.
     */
    public final void testFromXContent() throws IOException {
        testFromXContent(
            NUMBER_OF_TEST_RUNS,
            this::createTestInstance,
            supportsUnknownFields(),
            getShuffleFieldsExceptions(),
            getRandomFieldsExcludeFilter(),
            this::createParser,
            this::parseInstance,
            this::assertEqualInstances,
            assertToXContentEquivalence(),
            getToXContentParams(),
            this::dispose
        );
    }

    /**
     * Callback invoked after a test instance is no longer needed that can be overridden to release resources associated with the instance.
     * @param instance test instance that is no longer used
     */
    protected void dispose(T instance) {}

    /**
     * Creates a random test instance to use in the tests. This method will be
     * called multiple times during test execution and should return a different
     * random instance each time it is called.
     */
    protected abstract T createTestInstance();

    private T parseInstance(XContentParser parser) throws IOException {
        T parsedInstance = doParseInstance(parser);
        assertNull(parser.nextToken());
        return parsedInstance;
    }

    /**
     * Parses to a new instance using the provided {@link XContentParser}
     */
    protected abstract T doParseInstance(XContentParser parser) throws IOException;

    protected void assertEqualInstances(T expectedInstance, T newInstance) {
        assertNotSame(newInstance, expectedInstance);
        assertEquals(expectedInstance, newInstance);
        assertEquals(expectedInstance.hashCode(), newInstance.hashCode());
    }

    protected boolean assertToXContentEquivalence() {
        return true;
    }

    /**
     * Indicates whether the parser supports unknown fields or not. In case it does, such behaviour will be tested by
     * inserting random fields before parsing and checking that they don't make parsing fail.
     */
    protected abstract boolean supportsUnknownFields();

    /**
     * Returns a predicate that given the field name indicates whether the field has to be excluded from random fields insertion or not
     */
    protected Predicate getRandomFieldsExcludeFilter() {
        return Predicates.never();
    }

    /**
     * Fields that have to be ignored when shuffling as part of testFromXContent
     */
    protected String[] getShuffleFieldsExceptions() {
        return Strings.EMPTY_ARRAY;
    }

    /**
     * Params that have to be provided when calling {@link ToXContent#toXContent(XContentBuilder, ToXContent.Params)}
     */
    protected ToXContent.Params getToXContentParams() {
        return ToXContent.EMPTY_PARAMS;
    }

    static BytesReference insertRandomFieldsAndShuffle(
        BytesReference xContent,
        XContentType xContentType,
        boolean supportsUnknownFields,
        String[] shuffleFieldsExceptions,
        Predicate randomFieldsExcludeFilter,
        CheckedBiFunction createParserFunction
    ) throws IOException {
        BytesReference withRandomFields;
        if (supportsUnknownFields) {
            // add a few random fields to check that the parser is lenient on new fields
            withRandomFields = XContentTestUtils.insertRandomFields(xContentType, xContent, randomFieldsExcludeFilter, random());
        } else {
            withRandomFields = xContent;
        }
        try (XContentParser parserWithRandomFields = createParserFunction.apply(XContentFactory.xContent(xContentType), withRandomFields)) {
            return BytesReference.bytes(ESTestCase.shuffleXContent(parserWithRandomFields, false, shuffleFieldsExceptions));
        }
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy