package org.jgroups.tests;
import org.jgroups.*;
import org.jgroups.logging.Log;
import org.jgroups.logging.LogFactory;
import org.jgroups.protocols.BasicTCP;
import org.jgroups.protocols.TCPPING;
import org.jgroups.protocols.TP;
import org.jgroups.protocols.UDP;
import org.jgroups.protocols.pbcast.FLUSH;
import org.jgroups.stack.Protocol;
import org.jgroups.stack.ProtocolStack;
import org.jgroups.stack.IpAddress;
import org.jgroups.util.ResourceManager;
import org.jgroups.util.Util;
import org.jgroups.util.StackType;
import org.testng.AssertJUnit;
import org.testng.annotations.AfterClass;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Optional;
import org.testng.annotations.Parameters;
import org.testng.annotations.Test;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.net.InetAddress;
import java.util.*;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* @author Bela Ban
* @author Vladimir Blagojevic
* @author <a href="mailto://brian.stansberry@jboss.com">Brian Stansberry</a>
*/
@Test(groups = "base", sequential = true)
public class ChannelTestBase {
protected String channel_conf = "udp.xml";
protected boolean use_blocking = false;
protected boolean use_flush = false;
private String bind_addr = null;
protected final Log log = LogFactory.getLog(this.getClass());
@BeforeClass
@Parameters(value = { "channel.conf", "use_blocking" })
protected void initializeBase(@Optional("udp.xml") String chconf, @Optional("false") String use_blocking) throws Exception {
Test annotation = this.getClass().getAnnotation(Test.class);
// this should never ever happen!
if (annotation == null)
throw new Exception("Test is not marked with @Test annotation");
StackType type=Util.getIpStackType();
bind_addr=type == StackType.IPv6 ? "::1" : "127.0.0.1";
List<String> groups = Arrays.asList(annotation.groups());
boolean testRequiresFlush = groups.contains(Global.FLUSH);
this.use_blocking = testRequiresFlush || Boolean.parseBoolean(use_blocking);
this.use_flush = testRequiresFlush;
this.channel_conf = chconf;
boolean ignore_systemprops=Util.isBindAddressPropertyIgnored();
bind_addr = Util.getProperty(new String[]{Global.BIND_ADDR, Global.BIND_ADDR_OLD}, null, "bind_addr",
ignore_systemprops, bind_addr);
// bind_addr = Util.getBindAddress(null).getHostAddress();
}
@BeforeMethod
protected static void startTestHeader(java.lang.reflect.Method m) {
System.out.println("\n================ Starting test " + m.getName()
+ " ================\n");
}
@AfterClass(alwaysRun = true)
protected void nullifyInstanceFields() {
for (Class<?> current = this.getClass(); current.getSuperclass() != null; current = current.getSuperclass()) {
Field[] fields = current.getDeclaredFields();
for (Field f : fields) {
try {
if (!Modifier.isStatic(f.getModifiers()) && !f.getDeclaringClass().isPrimitive()) {
f.setAccessible(true);
f.set(this, null);
}
} catch (Exception e) {
}
}
}
}
protected String getBindAddress() {
return bind_addr;
}
protected boolean useBlocking() {
return use_blocking;
}
protected void setUseBlocking(boolean flag) {
use_blocking = flag;
}
protected boolean useFlush() {
return use_flush;
}
protected final static void assertTrue(boolean condition) {
Util.assertTrue(condition);
}
protected final static void assertTrue(String message, boolean condition) {
Util.assertTrue(message, condition);
}
protected final static void assertFalse(boolean condition) {
Util.assertFalse(condition);
}
protected final static void assertFalse(String message, boolean condition) {
Util.assertFalse(message, condition);
}
protected final static void assertEquals(String message, Object val1, Object val2) {
Util.assertEquals(message, val1, val2);
}
protected final static void assertEquals(Object val1, Object val2) {
Util.assertEquals(null, val1, val2);
}
protected final static void assertNotNull(String message, Object val) {
Util.assertNotNull(message, val);
}
protected final static void assertNotNull(Object val) {
Util.assertNotNull(null, val);
}
protected final static void assertNull(String message, Object val) {
Util.assertNull(message, val);
}
protected final static void assertNull(Object val) {
Util.assertNotNull(null, val);
}
/**
* Creates a channel and modifies the configuration such that no other channel will able to join
* this one even if they have the same cluster name (if unique = true). This is done by
* modifying mcast_addr and mcast_port with UDP, and by changing TCP.start_port, TCP.port_range
* and TCPPING.initial_hosts with TCP. Mainly used to run TestNG tests concurrently. Note that
* MuxChannels are not currently supported.
*
* @param num
* The number of channels we will create. Only important (for port_range) with TCP,
* ignored by UDP
* @return
* @throws Exception
*/
protected JChannel createChannel(boolean unique, int num) throws Exception {
return (JChannel) new DefaultChannelTestFactory().createChannel(unique, num);
}
protected JChannel createChannel(boolean unique, int num, String name) throws Exception {
JChannel ch=(JChannel)new DefaultChannelTestFactory().createChannel(unique, num);
ch.setName(name);
return ch;
}
protected JChannel createChannel() throws Exception {
return new DefaultChannelTestFactory().createChannel();
}
protected JChannel createChannel(boolean unique) throws Exception {
return createChannel(unique, 2);
}
protected JChannel createChannel(JChannel ch) throws Exception {
return (JChannel) new DefaultChannelTestFactory().createChannel(ch);
}
protected JChannel createChannel(JChannel ch, String name) throws Exception {
JChannel retval=(JChannel) new DefaultChannelTestFactory().createChannel(ch);
retval.setName(name);
return retval;
}
protected static String getUniqueClusterName() {
return getUniqueClusterName(null);
}
protected static String getUniqueClusterName(String base_name) {
return ResourceManager.getUniqueClusterName(base_name);
}
/**
* Default channel factory used in junit tests
*/
protected class DefaultChannelTestFactory {
public JChannel createChannel() throws Exception {
return createChannel(channel_conf);
}
public Channel createChannel(boolean unique, int num) throws Exception {
JChannel c = createChannel(channel_conf);
if(unique)
makeUnique(c, num);
return c;
}
public Channel createChannel(final JChannel ch) throws Exception {
JChannel retval = new JChannel(ch);
retval.setOpt(Channel.BLOCK, ch.getOpt(Channel.BLOCK));
if(useFlush())
Util.addFlush(retval, new FLUSH());
return retval;
}
private JChannel createChannel(String configFile) throws Exception {
JChannel ch = new JChannel(configFile);
ch.setOpt(Channel.BLOCK, useBlocking());
if(useFlush())
Util.addFlush(ch, new FLUSH());
return ch;
}
protected void makeUnique(Channel channel, int num) throws Exception {
String str = Util.getProperty(new String[]{ Global.UDP_MCAST_ADDR, "jboss.partition.udpGroup" },
null, "mcast_addr", false, null);
makeUnique(channel, num, str);
}
protected void makeUnique(Channel channel, int num, String mcast_address) throws Exception {
ProtocolStack stack = channel.getProtocolStack();
Protocol transport = stack.getTransport();
if (transport instanceof UDP) {
short mcast_port = ResourceManager.getNextMulticastPort(InetAddress.getByName(bind_addr));
((UDP) transport).setMulticastPort(mcast_port);
if (mcast_address != null) {
((UDP) transport).setMulticastAddress(InetAddress.getByName(mcast_address));
} else {
String mcast_addr = ResourceManager.getNextMulticastAddress();
((UDP) transport).setMulticastAddress(InetAddress.getByName(mcast_addr));
}
} else if (transport instanceof BasicTCP) {
List<Short> ports = ResourceManager.getNextTcpPorts(InetAddress.getByName(bind_addr), num);
((TP) transport).setBindPort(ports.get(0));
((TP) transport).setPortRange(num);
Protocol ping = stack.findProtocol(TCPPING.class);
if (ping == null)
throw new IllegalStateException("TCP stack must consist of TCP:TCPPING - other config are not supported");
List<String> initial_hosts = new LinkedList<String>();
for (short port : ports) {
initial_hosts.add(bind_addr + "[" + port + "]");
}
String tmp = Util.printListWithDelimiter(initial_hosts, ",");
List<IpAddress> init_hosts = Util.parseCommaDelimitedHosts(tmp, 0);
((TCPPING) ping).setInitialHosts(init_hosts);
} else {
throw new IllegalStateException("Only UDP and TCP are supported as transport protocols");
}
}
}
interface EventSequence {
List<Object> getEvents();
String getName();
}
/**
* Base class for all aplications using channel
*/
protected abstract class ChannelApplication extends ExtendedReceiverAdapter implements EventSequence, Runnable {
protected Channel channel;
protected Thread thread;
protected Throwable exception;
protected List<Object> events;
public ChannelApplication(String name) throws Exception {
channel = createChannel(true, 4);
init(name);
}
public ChannelApplication(JChannel copySource, String name) throws Exception {
channel = createChannel(copySource);
init(name);
}
protected void init(String name) {
events = Collections.synchronizedList(new LinkedList<Object>());
channel.setName(name);
channel.setReceiver(this);
}
/**
* Method allowing implementation of specific test application level logic
*
* @throws Exception
*/
protected abstract void useChannel() throws Exception;
public void run() {
try {
useChannel();
} catch (Exception e) {
log.error(channel.getName() + ": " + e.getLocalizedMessage(), e);
exception = e; // Save it for the test to check
}
}
public List<Address> getMembers() {
List<Address> result = null;
View v = channel.getView();
if (v != null) {
result = v.getMembers();
}
return result;
}
public Address getLocalAddress() {
return channel.getAddress();
}
public void start() {
thread = new Thread(this, getName());
thread.start();
}
public Channel getChannel() {
return channel;
}
public String getName() {
return channel != null ? channel.getName() : "n/a";
}
public void cleanup() {
if (thread != null && thread.isAlive())
thread.interrupt();
try {
channel.close();
} catch (Throwable t) {
log.warn("Exception while closing channel " + getName(), t);
}
}
public List<Object> getEvents() {
return events;
}
public void block() {
events.add(new BlockEvent());
}
public byte[] getState() {
events.add(new GetStateEvent(null, null));
return null;
}
public void getState(OutputStream ostream) {
events.add(new GetStateEvent(null, null));
}
public byte[] getState(String state_id) {
events.add(new GetStateEvent(null, state_id));
return null;
}
public void getState(String state_id, OutputStream ostream) {
events.add(new GetStateEvent(null, state_id));
}
public void setState(byte[] state) {
events.add(new SetStateEvent(null, null));
}
public void setState(InputStream istream) {
events.add(new SetStateEvent(null, null));
}
public void setState(String state_id, byte[] state) {
events.add(new SetStateEvent(null, null));
}
public void setState(String state_id, InputStream istream) {
events.add(new SetStateEvent(null, null));
}
public void unblock() {
events.add(new UnblockEvent());
}
public void viewAccepted(View new_view) {
events.add(new_view);
log.info(getLocalAddress() + ": view=" + new_view);
}
}
/**
* Channel with semaphore allows application to go through fine-grained synchronous step
* control.
* <p/>
* PushChannelApplicationWithSemaphore application will not proceed to useChannel() until it
* acquires permit from semphore. After useChannel() completes the acquired permit will be
* released. Test driver should control how semaphore tickets are given and acquired.
*/
protected abstract class PushChannelApplicationWithSemaphore extends ChannelApplication {
protected Semaphore semaphore;
public PushChannelApplicationWithSemaphore(String name, Semaphore semaphore)
throws Exception {
super(name);
this.semaphore = semaphore;
}
public PushChannelApplicationWithSemaphore(JChannel copySource, String name,
Semaphore semaphore) throws Exception {
super(copySource, name);
this.semaphore = semaphore;
}
public void run() {
boolean acquired = false;
try {
acquired = semaphore.tryAcquire(60000L, TimeUnit.MILLISECONDS);
if (!acquired)
throw new Exception(channel.getAddress() + ": cannot acquire semaphore");
useChannel();
} catch (Exception e) {
exception = e; // Save it for the test to check
} finally {
if (acquired) {
semaphore.release();
}
}
}
}
protected static void checkEventStateTransferSequence(EventSequence receiver) {
List<Object> events = receiver.getEvents();
assertNotNull(events);
final String validSequence = "([b][vgs]*[u])+";
// translate the eventTrace to an eventString
try {
assertTrue("Invalid event sequence " + events, validateEventString(
translateEventTrace(events), validSequence));
} catch (Exception e) {
AssertJUnit.fail("Invalid event sequence " + events);
}
}
/**
* Method for translating event traces into event strings, where each event in the trace is
* represented by a letter.
*/
protected static String translateEventTrace(List<Object> et) throws Exception {
StringBuilder eventString = new StringBuilder();
for (Iterator<Object> it = et.iterator(); it.hasNext();) {
Object obj = it.next();
if (obj instanceof BlockEvent)
eventString.append("b");
else if (obj instanceof UnblockEvent)
eventString.append("u");
else if (obj instanceof SetStateEvent)
eventString.append("s");
else if (obj instanceof GetStateEvent)
eventString.append("g");
else if (obj instanceof View)
eventString.append("v");
else
throw new Exception("Unrecognized event type in event trace");
}
String s = eventString.toString();
// if it ends with block, strip it out because it will be regarded as error sequence
while (s.endsWith("b")) {
s = s.substring(0, s.length() - 1);
}
return s;
}
/**
* Method for validating event strings against event string specifications, where a
* specification is a regular expression involving event symbols. e.g. [b]([sgv])[u]
*/
protected static boolean validateEventString(String eventString, String spec) {
Pattern pattern = null;
Matcher matcher = null;
// set up the regular expression specification
pattern = Pattern.compile(spec);
// set up the actual event string
matcher = pattern.matcher(eventString);
// check if the actual string satisfies the specification
if (matcher.find()) {
// a match has been found, but we need to check that the whole event string
// matches, and not just a substring
if (!(matcher.start() == 0 && matcher.end() == eventString.length())) {
// match on full eventString not found
System.err
.println("event string invalid (proper substring matched): event string = "
+ eventString
+ ", specification = "
+ spec
+ "matcher.start() "
+ matcher.start()
+ " matcher.end() " + matcher.end());
return false;
}
} else {
return false;
}
return true;
}
}