/* * Licensed to Crate under one or more contributor license agreements. * See the NOTICE file distributed with this work for additional * information regarding copyright ownership. Crate 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. * * However, if you have executed another commercial license agreement * with Crate these terms will supersede the license and you may use the * software solely pursuant to the terms of the relevant commercial * agreement. */ package io.crate.test; import com.google.common.collect.ImmutableSet; import org.apache.lucene.util.LuceneTestCase; import org.codehaus.groovy.reflection.ClassInfo; import java.lang.ref.SoftReference; import java.lang.reflect.Field; import java.lang.reflect.Modifier; import java.util.Set; /** * COPIED FROM https://github.com/elastic/elasticsearch-groovy * <p> * <p> * {@code GroovyTestSanitizer} sanitizes the existing test infrastructure to work well with the Groovy runtime. * <ul> * <li>Modifies {@link LuceneTestCase} to add Groovy's {@link ClassInfo} type to the list (literally it's a * {@code Set}) of types that are ignored during the cleanup process.</li> * <li>This also adds the {@code SoftReference} to the list of types are ignored so that we can continue to support * the Grails release.</li> * </ul> * This should be invoked in a {@code static} block of every abstract test that uses the Elasticsearch test framework. * <pre> * static { * assert GroovyTestSanitizer.groovySanitized * } * </pre> */ public class GroovyTestSanitizer { /** * This will thread safely trigger any necessary action to appropriately ignore {@code static} Groovy baggage * during test cleanup. This is necessary to avoid test failures due to automatic static field monitoring for * improper test code cleanup (and therefore JVM waste and theoretically slower tests). * <pre> * static {* assert GroovyTestSanitizer.groovySanitized * } * </pre> * * @return {@code true} to indicate that tests can be safely executed. Expected to always be {@code true}. */ public static boolean isGroovySanitized() { // This forces the class loader to load the inner type, thereby triggering its static block. // The JVM guarantees that this will only happen once, so we can lock-free do this as many times as we want // and the static block will only ever be executed once. return GroovyTestSanitizerSingleton.load(); } /** * An inner class is used to force the JVM to perform its Classloader magic. This allows us to use it as though * it's locked without ever using locks ourselves. * <p> * It can be used as many times as necessary, but it will only be invoked once. */ private static class GroovyTestSanitizerSingleton { /** * Currently, this modifies {@link LuceneTestCase} to add {@link ClassInfo} to the set of known * static leak types to be ignored. */ static { // Types of static fields that are added by Groovy at runtime // - SoftReferences are the $callSiteArrays and they should be removed from the safe list once we only // support running with invokedynamic support ImmutableSet<String> safeGroovyTypes = ImmutableSet.of(ClassInfo.class.getName(), SoftReference.class.getName()); try { // this corresponds to a Set<String> Field field = LuceneTestCase.class.getDeclaredField("STATIC_LEAK_IGNORED_TYPES"); // the field is private static, so this allows us to mess with it field.setAccessible(true); // the field is also final, so we need to remove that Field modifiersField = Field.class.getDeclaredField("modifiers"); modifiersField.setAccessible(true); modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL); // read the field Set<String> staticLeakIgnoreTypes = (Set<String>) field.get(null); // replace the field: add our own class to it, then replace it field.set(null, ImmutableSet.builder().addAll(staticLeakIgnoreTypes).addAll(safeGroovyTypes).build()); // reset it as final modifiersField.setInt(field, field.getModifiers() | Modifier.FINAL); modifiersField.setAccessible(false); // reset it as private field.setAccessible(false); } catch (Exception e) { e.printStackTrace(); } } /** * An arbitrary method to allow the outer class to easily trigger the {@code static} block. */ static boolean load() { return true; } } }