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