

package org.jgroups.tests;

import org.testng.annotations.Test ;
import org.testng.annotations.BeforeMethod ;
import org.testng.annotations.AfterMethod ;
import org.testng.Assert;

import org.jgroups.*;
import org.jgroups.debug.Simulator;
import org.jgroups.protocols.DELAY;
import org.jgroups.stack.IpAddress;
import org.jgroups.stack.Protocol;

import java.util.Properties;
import java.util.Vector;

/**
 * Tests for the fault-injection features of Simulator.
 * 
 * @author Richard Achmatowicz 
 */
@Test(groups=Global.FUNCTIONAL, sequential=true)
public class SimulatorTest {

	final static int NUM_PEERS = 3; 
	final static int NUM_MSGS = 5; 
	final static int WAIT_TIMEOUT = 5; // secs
	final static int MSGS_PER_STATUS_LINE = 1; 

	// convey assertion failure from thread to main framework
	static boolean allMsgsReceived = false ;

	IpAddress[] addresses = new IpAddress[NUM_PEERS] ;
	Vector<Address> members = null ;
	View view = null ;
	Simulator[] simulators = new Simulator[NUM_PEERS] ;
	DELAY[] layers = new DELAY[NUM_PEERS] ;
	Protocol[][] stacks = new Protocol[NUM_PEERS][] ;
	Thread[] threads = new Thread[NUM_PEERS] ;

	//define senders and receivers
	boolean[] isSender = new boolean[NUM_PEERS] ;

	// used to wait for signal that all messages received
	static Object all_msgs_recd = new Object() ;


	/**
	 * Set up a number of simulator instances wrapping NAKACK 
	 */
	@BeforeMethod(alwaysRun=true)
	public void setUp() throws Exception {

		System.out.println("calling setUp()") ;

		// define the senders and the receivers
		isSender[0] = false ;
		isSender[1] = true ;
		isSender[2] = true ;

		// dummy IP addresses and ports 
		addresses[0] = new IpAddress(1111);
		addresses[1] = new IpAddress(2222);
		addresses[2] = new IpAddress(3333);

		// dummy set of members which works for all three simulators
		members = new Vector<Address>();
		for (int i = 0 ; i < NUM_PEERS; i++) {
			members.add(addresses[i]);
		}

		// create a dummy View(creator, timestamp, member set) 
		view = new View(addresses[0], 1, members);

		// create new simulator instances
		for (int i = 0; i < NUM_PEERS; i++) {
			createSimulator(simulators, view, addresses, layers, stacks, i) ;
		}

		// describe the configuration of the three simulators
		for (int i = 0; i < NUM_PEERS; i++) {
			for (int j = 0; j < NUM_PEERS; j++) {
				if (i == j) 
					simulators[i].addMember(addresses[j]) ;
				else 
					simulators[i].addMember(addresses[j], simulators[j]) ;
			}
		}

		// set up the receiver callbacks for each simulator
		Simulator.Receiver[] receivers = new Simulator.Receiver[NUM_PEERS] ;

		// set up the sender and the receiver callbacks, according to whether
		// the peer is a sender or a receiver
		for (int i = 0; i < NUM_PEERS; i++) {

			if (isSender[i]) 
				receivers[i] = new SenderPeer(simulators[i]) ;
			else 
				receivers[i] = new ReceiverPeer(simulators[i]) ;

			simulators[i].setReceiver(receivers[i]);
		}

		// start the simulators
		for (int i = 0; i < NUM_PEERS; i++) 
			simulators[i].start();

		System.out.println("Ending setUp()") ;
	}

	@AfterMethod(alwaysRun=true)
	public void tearDown() throws Exception {

		System.out.println("Calling tearDown()") ;
		// reset messages received flag
		allMsgsReceived = false ;
		
		// stop the simulators
		for (int i = 0; i < NUM_PEERS; i++)
			simulators[i].stop();
		System.out.println("Ending tearDown()") ;
	}

	private void createSimulator(Simulator[] simulators, View view, Address[] addresses, DELAY[] layers, Protocol[][] stacks, int i) {

		// create the simulator instance
		simulators[i] = new Simulator();
		simulators[i].setLocalAddress(addresses[i]);
		simulators[i].setView(view);

		// set up a dummy passthrough layer using DELAY
		layers[i] = new DELAY();

		// set up its properties
		layers[i].setInDelay(0);
		layers[i].setOutDelay(0);

		// our protocol stack under test consists of one protocol
		stacks[i] = new Protocol[]{layers[i]};
		simulators[i].setProtocolStack(stacks[i]);
	}


	/*
	 * Drop messages from our address to destination address a
	 */
	class MyDropMessage implements Simulator.DropMessage {
		Address address = null ;

		MyDropMessage(Address a) {
			this.address = a ;
		}
		public boolean drop(Message msg, Address dest) {

			// multicast messages have their source address set, and drop
			// gets called |view| times, each with a different dest parameter
			
			// here we drop that part of a multicast which is headed for dest
			if (msg.getDest() == null && dest.equals(address)) {
				return true ;
			}

			// drop when sending specifically to address
			if (msg.getDest() != null && msg.getDest().equals(address)) {
				return true ;
			}
			return false ;
		}
	}


	/**
	 * Test dropped messages modelling.
	 */
	public void testDroppedMessages() {

		System.out.println("Starting testDroppedMessages") ;

		// set up a drop function which drops all messages from 2 to 0
		// (even those message instances which are part of a multicast) 
		Simulator.DropMessage d = new MyDropMessage(addresses[0]) ;
		simulators[2].registerDropMessage(d) ;

		// start the NAKACK peers and let them exchange messages
		for (int i = 0; i < NUM_PEERS; i++) {
			threads[i] = new MyPeer(simulators[i], isSender[i]) ;
			threads[i].start() ;
		}

		// wait for the receiver peer to signal that it has received messages, or timeout
		synchronized(all_msgs_recd) {
			try {
				all_msgs_recd.wait(WAIT_TIMEOUT * 1000) ;
			}
			catch(InterruptedException e) {
				System.out.println("main thread interrupted") ;
			}
		}

		// wait for the threads to terminate
		try {
			for (int i = 0; i < NUM_PEERS; i++) {
				threads[i].join() ;
			}
		}
		catch(InterruptedException e) {
		}


		int receiver = ((ReceiverPeer)simulators[0].getReceiver()).getNumberOfReceivedMessages() ;
		int sender1 = ((SenderPeer)simulators[1].getReceiver()).getNumberOfReceivedMessages() ;
		int sender2 = ((SenderPeer)simulators[2].getReceiver()).getNumberOfReceivedMessages() ;

		Assert.assertFalse(allMsgsReceived, "receiver received all messages from both peers") ;  
		Assert.assertTrue(receiver == NUM_MSGS, "receiver did not receive all messages from single peer: received " + receiver) ;  
		Assert.assertTrue(sender1 == 2 * NUM_MSGS, "sender1 did not receive messages from itself and other sender: received " + sender1) ;  
		Assert.assertTrue(sender2 == 2 * NUM_MSGS, "sender2 did not receive messages from itself and other sender: received " + sender2) ;  
	}

	/**
	 * Test crash failure modelling.
	 */
	public void testCrashFailure() {

		// simulate crash failure on sender
		System.out.println("Starting testCrashfailure") ;
		simulators[1].simulateCrashFailure() ;

		// start the NAKACK peers and let them exchange messages
		for (int i = 0; i < NUM_PEERS; i++) {
			threads[i] = new MyPeer(simulators[i], isSender[i]) ;
			threads[i].start() ;
		}

		// wait for the receiver peer to signal that it has received messages, or timeout
		synchronized(all_msgs_recd) {
			try {
				all_msgs_recd.wait(WAIT_TIMEOUT * 1000) ;
			}
			catch(InterruptedException e) {
				System.out.println("main thread interrupted") ;
			}
		}

		// wait for the threads to terminate
		try {
			for (int i = 0; i < NUM_PEERS; i++) {
				threads[i].join() ;
			}
		}
		catch(InterruptedException e) {
		}

		int receiver = ((ReceiverPeer)simulators[0].getReceiver()).getNumberOfReceivedMessages() ;
		int sender1 = ((SenderPeer)simulators[1].getReceiver()).getNumberOfReceivedMessages() ;
		int sender2 = ((SenderPeer)simulators[2].getReceiver()).getNumberOfReceivedMessages() ;

		Assert.assertFalse(allMsgsReceived, "receiver received all messages from both peers") ;  
		Assert.assertTrue(receiver == NUM_MSGS, "receiver did not receive all messages from single peer") ;  
		Assert.assertTrue(sender1 == 0, "sender1 received messages") ;  
		Assert.assertTrue(sender2 == NUM_MSGS, "sender2 did not receive messages only from itself") ;  
	}

	/**
	 * Test network partition modelling.
	 */
	public void testNetworkPartition() {

		System.out.println("Starting testNetworkPartition") ;

		Address[] part1 = {addresses[0], addresses[1]} ;
		Address[] part2 = {addresses[2]} ;

		simulators[0].simulatePartition(part1) ;
		simulators[1].simulatePartition(part1) ;
		simulators[2].simulatePartition(part2) ;

		// start the NAKACK peers and let them exchange messages
		for (int i = 0; i < NUM_PEERS; i++) {
			threads[i] = new MyPeer(simulators[i], isSender[i]) ;
			threads[i].start() ;
		}

		// wait for the receiver peer to signal that it has received messages, or timeout
		synchronized(all_msgs_recd) {
			try {
				all_msgs_recd.wait(WAIT_TIMEOUT * 1000) ;
			}
			catch(InterruptedException e) {
				System.out.println("main thread interrupted") ;
			}
		}

		// wait for the threads to terminate
		try {
			for (int i = 0; i < NUM_PEERS; i++) {
				threads[i].join() ;
			}
		}
		catch(InterruptedException e) {
		}


		int receiver = ((ReceiverPeer)simulators[0].getReceiver()).getNumberOfReceivedMessages() ;
		int sender1 = ((SenderPeer)simulators[1].getReceiver()).getNumberOfReceivedMessages() ;
		int sender2 = ((SenderPeer)simulators[2].getReceiver()).getNumberOfReceivedMessages() ;

		Assert.assertFalse(allMsgsReceived, "receiver received all messages from both peers") ;  
		Assert.assertTrue(receiver == NUM_MSGS, "receiver did not receive all messages from single peer") ;  
		Assert.assertTrue(sender1 == NUM_MSGS, "sender1 did not receive messages only from itself") ;  
		Assert.assertTrue(sender2 == NUM_MSGS, "sender2 did not receive messages only from itself") ;  
	}


	/**
	 * This is called by the Simulator when a message comes back up the stack.
	 * Used by message senders to simply display messages received from other peers.
	 */
	class SenderPeer implements Simulator.Receiver {
		Simulator simulator = null ;
		int num_mgs_received=0;

		SenderPeer(Simulator s) {
			this.simulator = s ;
		}

		// keep track of how many messages were received by this peer
		public void receive(Event evt) {
			if(evt.getType() == Event.MSG) {
				num_mgs_received++;
				if(num_mgs_received % MSGS_PER_STATUS_LINE == 0)
					System.out.println("<" + simulator.getLocalAddress() + ">:" + "<== " + num_mgs_received);
			}
		}

		public int getNumberOfReceivedMessages() {
			return num_mgs_received;
		}
	}

	/**
	 * This is called by the Simulator when a message comes back up the stack.
	 * This method should do the following:
	 * - receive messages from senders 
	 * - terminate when correct number of messages have been received
	 */
	class ReceiverPeer implements Simulator.Receiver {
		Simulator simulator = null ;
		int num_mgs_received=0;
		Message msg ;
		Address sender ;

		ReceiverPeer(Simulator s) {
			this.simulator = s ;
		}

		public synchronized void receive(Event evt) {

			if (evt.getType() == Event.MSG) {

				// keep track of seqno ordering of messages received
				msg=(Message)evt.getArg();
				sender=msg.getSrc();

				try {
					num_mgs_received++ ;

					Address address = simulator.getLocalAddress() ;

					if(num_mgs_received % MSGS_PER_STATUS_LINE == 0)
						System.out.println("<" + address + ">:" + "PASS: received msg #" + num_mgs_received + " from " + sender);


					// condition to terminate the test - all messages received (whether in 
					// correct order or not)
					if(num_mgs_received >= SimulatorTest.NUM_MSGS * (NUM_PEERS-1)) {

						// indicate that we have received the required number of messages
						// to differentiate between timeout and notifyAll cases on monitor
						allMsgsReceived = true ;

						// signal that all messages have been received - this will allow the receiver
						// thread to terminate normally
						synchronized(all_msgs_recd) {
							all_msgs_recd.notifyAll() ;
						}
					}   
				}
				catch(Exception ex) {
					System.out.println("SimulatorTest.receive()" + ex.toString());
				}
			}	
		}

		public int getNumberOfReceivedMessages() {
			return num_mgs_received;
		}
	}


	static class MyPeer extends Thread {

		Simulator s = null ;
		boolean sender = false ;

		public MyPeer(Simulator s, boolean sender) {
			this.s = s ;
			this.sender = sender ;
		}

		public void run() {

			// senders send NUM_MSGS messages to all peers, beginning with seqno 1
			if (sender) {

				Address address = s.getLocalAddress() ;

				// send a collection of dummy messages by mcast to the stack under test
				for(int i=1; i <= NUM_MSGS; i++) {

					Message msg=new Message(null, address, new Long(i));
					Event evt=new Event(Event.MSG, msg);

					// call Simulator.send() to introduce the event into the stack under test
					s.send(evt);

					// status indicator
					if(i % MSGS_PER_STATUS_LINE == 0)
						System.out.println("<" + address + ">:" + " ==> " + i);
				}	   
			}

			if (!sender) {
				// wait for the receiver callback to signal that it has received messages, or timeout
				// this just causes this thread to block until its receiver has finished 
				synchronized(all_msgs_recd) {
					try {
						all_msgs_recd.wait(WAIT_TIMEOUT * 1000) ;
					}
					catch(InterruptedException e) {
						System.out.println("thread interrupted") ;
					}
				}
			}
		}
	}

}



