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

org.elasticsearch.test.AbstractWireTestCase 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.TransportVersion;
import org.elasticsearch.common.io.stream.NamedWriteable;
import org.elasticsearch.common.io.stream.NamedWriteableRegistry;
import org.elasticsearch.common.io.stream.Writeable;
import org.elasticsearch.core.Releasable;
import org.elasticsearch.xcontent.ToXContent;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

import static org.hamcrest.Matchers.emptyString;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.not;

/**
 * Standard test case for testing wire serialization. If the class being tested
 * extends {@link Writeable} then prefer extending {@link AbstractWireSerializingTestCase}.
 */
public abstract class AbstractWireTestCase extends ESTestCase {

    protected static final int NUMBER_OF_TEST_RUNS = 20;

    /**
     * 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();

    /**
     * Returns an instance which is mutated slightly so it should not be equal
     * to the given instance.
     */
    protected abstract T mutateInstance(T instance) throws IOException;

    /**
     * Tests that the equals and hashcode methods are consistent and copied
     * versions of the instance are equal.
     */
    public final void testEqualsAndHashcode() {
        for (int runs = 0; runs < NUMBER_OF_TEST_RUNS; runs++) {
            T testInstance = createTestInstance();
            try {
                EqualsHashCodeTestUtils.checkEqualsAndHashCode(testInstance, this::copyInstance, this::mutateInstance, this::dispose);
            } finally {
                dispose(testInstance);
            }
        }
    }

    /**
     * Dispose of the copy, usually {@link Releasable#close} or a noop.
     */
    protected void dispose(T t) {}

    /**
     * Calls {@link Object#equals} on equal objects on many threads and verifies
     * they all return true. Folks tend to assume this is true about
     * {@link Object#equals} and it generally is. But some
     * equals implementations violate this assumption and it's very surprising.
     * This tries to fail when that assumption is violated.
     */
    public final void testConcurrentEquals() throws IOException, InterruptedException, ExecutionException {
        T testInstance = createTestInstance();
        try {
            T copy = copyInstance(testInstance);
            try {

                /*
                 * 500 rounds seems to consistently reproduce the issue on Nik's
                 * laptop. Larger numbers are going to be slower but more likely
                 * to reproduce the issue.
                 */
                int rounds = scaledRandomIntBetween(300, 5000);
                concurrentTest(() -> {
                    for (int r = 0; r < rounds; r++) {
                        assertEquals(testInstance, copy);
                    }
                });
            } finally {
                dispose(copy);
            }
        } finally {
            dispose(testInstance);
        }
    }

    /**
     * Call some test on many threads in parallel.
     */
    protected void concurrentTest(Runnable r) throws InterruptedException, ExecutionException {
        int threads = 5;
        int tasks = threads * 2;
        ExecutorService exec = Executors.newFixedThreadPool(threads);
        try {
            List> results = new ArrayList<>();
            for (int t = 0; t < tasks; t++) {
                results.add(exec.submit(r));
            }
            for (Future f : results) {
                f.get();
            }
        } finally {
            exec.shutdown();
        }
    }

    /**
     * Calls {@link Object#hashCode} on the same object on many threads and
     * verifies they return the same result. Folks tend to assume this is true
     * about {@link Object#hashCode} and it generally is. But
     * some hashCode implementations violate this assumption and it's very
     * surprising. This tries to fail when that assumption is violated.
     */
    public final void testConcurrentHashCode() throws InterruptedException, ExecutionException {
        T testInstance = createTestInstance();
        try {
            int firstHashCode = testInstance.hashCode();

            /*
             * 500 rounds seems to consistently reproduce the issue on Nik's
             * laptop. Larger numbers are going to be slower but more likely
             * to reproduce the issue.
             */
            int rounds = scaledRandomIntBetween(300, 5000);
            concurrentTest(() -> {
                for (int r = 0; r < rounds; r++) {
                    assertEquals(firstHashCode, testInstance.hashCode());
                }
            });
        } finally {
            dispose(testInstance);
        }
    }

    public void testToString() throws Exception {
        T testInstance = createTestInstance();
        try {
            final String toString = testInstance.toString();
            assertNotNull(toString);
            assertThat(toString, not(emptyString()));
        } finally {
            dispose(testInstance);
        }
    }

    /**
     * Test serialization and deserialization of the test instance.
     */
    public final void testSerialization() throws IOException {
        for (int runs = 0; runs < NUMBER_OF_TEST_RUNS; runs++) {
            T testInstance = createTestInstance();
            try {
                assertSerialization(testInstance);
            } finally {
                dispose(testInstance);
            }
        }
    }

    /**
     * Test serializing the same object on many threads always
     * deserializes to equal instances. Folks tend to assume this is true
     * about serialization and it generally is. But
     * some implementations violate this assumption and it's very
     * surprising. This tries to fail when that assumption is violated.
     * 

* Async search can serialize responses concurrently with other * operations like {@link ToXContent#toXContent}. This doesn't * check that exactly, but it's close. */ public final void testConcurrentSerialization() throws InterruptedException, ExecutionException { T testInstance = createTestInstance(); try { /* * 500 rounds seems to consistently reproduce the issue on Nik's * laptop. Larger numbers are going to be slower but more likely * to reproduce the issue. */ int rounds = scaledRandomIntBetween(300, 2000); concurrentTest(() -> { try { for (int r = 0; r < rounds; r++) { assertSerialization(testInstance); } } catch (IOException e) { throw new AssertionError("error serializing", e); } }); } finally { dispose(testInstance); } } /** * Serialize the given instance and asserts that both are equal. */ protected final void assertSerialization(T testInstance) throws IOException { assertSerialization(testInstance, TransportVersion.current()); } /** * Assert that instances copied at a particular version are equal. The version is useful * for sanity checking the backwards compatibility of the wire. It isn't a substitute for * real backwards compatibility tests but it is *so* much faster. */ protected final void assertSerialization(T testInstance, TransportVersion version) throws IOException { T deserializedInstance = copyInstance(testInstance, version); try { assertEqualInstances(testInstance, deserializedInstance); } finally { dispose(deserializedInstance); } } /** * Assert that two instances are equal. This is intentionally not final so we can override * how equality is checked. */ protected void assertEqualInstances(T expectedInstance, T newInstance) { if (shouldBeSame(newInstance)) { assertSame(newInstance, expectedInstance); } else { assertNotSame(newInstance, expectedInstance); } assertThat(newInstance, equalTo(expectedInstance)); assertThat(newInstance.hashCode(), equalTo(expectedInstance.hashCode())); } /** * Should this copy be the same instance as what we're copying? Defaults to * {@code false} but implementers might override if the serialization returns * a reuse constant. */ protected boolean shouldBeSame(T newInstance) { return false; } protected final T copyInstance(T instance) throws IOException { return copyInstance(instance, TransportVersion.current()); } /** * Copy the instance as by reading and writing using the code specific to the provided version. * The version is useful for sanity checking the backwards compatibility of the wire. It isn't * a substitute for real backwards compatibility tests but it is *so* much faster. */ protected abstract T copyInstance(T instance, TransportVersion version) throws IOException; /** * Get the {@link NamedWriteableRegistry} to use when de-serializing the object. * * Override this method if you need to register {@link NamedWriteable}s for the test object to de-serialize. * * By default this will return a {@link NamedWriteableRegistry} with no registered {@link NamedWriteable}s */ protected NamedWriteableRegistry getNamedWriteableRegistry() { return new NamedWriteableRegistry(Collections.emptyList()); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy