/* * Hibernate Search, full-text search for your domain model * * License: GNU Lesser General Public License (LGPL), version 2.1 or later * See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>. */ package org.hibernate.search.testsupport; import java.util.Deque; import java.util.concurrent.ConcurrentLinkedDeque; import java.util.concurrent.atomic.AtomicInteger; import org.jboss.byteman.rule.Rule; import org.jboss.byteman.rule.exception.ThrowException; import org.jboss.byteman.rule.helper.Helper; import org.junit.rules.TestRule; import org.junit.runner.Description; import org.junit.runners.model.Statement; import org.hibernate.search.util.logging.impl.Log; import org.hibernate.search.util.logging.impl.LoggerFactory; import static org.junit.Assert.fail; /** * @author Sanne Grinovero (C) 2011 Red Hat Inc. * @author Hardy Ferentschik */ public class BytemanHelper extends Helper { private static final Log log = LoggerFactory.make(); private static final AtomicInteger counter = new AtomicInteger(); private static final Deque<String> concurrentStack = new ConcurrentLinkedDeque<>(); protected BytemanHelper(Rule rule) { super( rule ); } public void sleepASecond() { try { log.info( "Byteman rule triggered: sleeping a second" ); Thread.sleep( 1000 ); } catch (InterruptedException e) { Thread.currentThread().interrupt(); log.error( "unexpected interruption", e ); } } public void assertBooleanValue(boolean actual, boolean expected) { if ( actual != expected ) { fail( "Unexpected boolean value" ); } } public void countInvocation() { log.debug( "Increment call count" ); counter.incrementAndGet(); } /** * Throws an exception of a type that is not accessible in the wrapped method. * <p> * Note that the byteman "throw" keyword only works with exception types * imported by the class whose method is being wrapped. */ public void simulateFailure() { throw new ThrowException( new SimulatedFailureException() ); } /** * Throws an exception of a type that is not accessible in the wrapped method. * <p> * Note that the byteman "throw" keyword only works with exception types * imported by the class whose method is being wrapped. * * @param message The message carried by the exception. */ public void simulateFailure(String message) { throw new ThrowException( new SimulatedFailureException( message ) ); } /** * Adds a label to a concurrent queue. * Useful to "tag" events to verify they are issued in a specific order, * even though they are generated by different threads. * @param message some label to be recorded */ public void pushEvent(String message) { concurrentStack.add( message ); } /** * @return A utility to be referenced in the test as a {@link org.junit.Rule JUnit rule}, that allows * access to information resulting from Byteman rules. */ public static BytemanAccessor createAccessor() { return new BytemanAccessor(); } public static class BytemanAccessor implements TestRule { /* * We use this attribute to check that the accessor is being used * as a JUnit rule (as it should be). */ private boolean runningAsJUnitRule = false; private BytemanAccessor() { // Private constructor, use createAccessor() instead. } public boolean isEventStackEmpty() { ensureRunningAsJUnitRule(); return concurrentStack.isEmpty(); } /** * Gets the first event label from the concurrent queue, * and removes it. * The next invocation will return the next one from the queue. * @return the first */ public String consumeNextRecordedEvent() { ensureRunningAsJUnitRule(); try { return concurrentStack.removeFirst(); } catch (java.util.NoSuchElementException nse) { throw new IllegalStateException( "Attempted to consume an event, while no events are left on the stack." + " If it is the first call to this method, check if the Byteman rules are actually being triggered?" ); } } public int getAndResetInvocationCount() { ensureRunningAsJUnitRule(); return counter.getAndSet( 0 ); } private void ensureRunningAsJUnitRule() { if ( ! runningAsJUnitRule ) { throw new IllegalStateException( "Error in test setup: the byteman accessor obtained" + " through BytemanHelper.createAccessor() must be used as a JUnit Rule (see org.junit.Rule)." ); } } @Override public Statement apply(final Statement base, Description description) { return new Statement() { @Override public void evaluate() throws Throwable { runningAsJUnitRule = true; try { base.evaluate(); } finally { runningAsJUnitRule = false; reset(); } } }; } private void reset() { concurrentStack.clear(); counter.set( 0 ); } } public static class SimulatedFailureException extends RuntimeException { private SimulatedFailureException() { } private SimulatedFailureException(String message) { super( message ); } } }