

package org.jgroups.tests;


import org.jgroups.*;
import org.jgroups.protocols.pbcast.GMS;
import org.jgroups.util.Util;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;

import java.util.LinkedList;
import java.util.List;


/**
 * Tests a SEQUENCER based stack: A, B and C. B starts multicasting messages with a monotonically increasing
 * number. Then A is crashed. C and B should receive *all* numbers *without* a gap.
 * @author Bela Ban
 */
@Test(groups=Global.STACK_INDEPENDENT,sequential=true)
public class SequencerFailoverTest {
    JChannel a, b, c; // A is the coordinator
    static final String GROUP="SequencerFailoverTest";
    static final int    NUM_MSGS=50;
    static final String props="sequencer.xml";


    @BeforeMethod
    public void setUp() throws Exception {
        a=new JChannel(props);
        a.setName("A");
        a.connect(GROUP);

        b=new JChannel(props);
        b.setName("B");
        b.connect(GROUP);

        c=new JChannel(props);
        c.setName("C");
        c.connect(GROUP);
    }

    @AfterMethod
    public void tearDown() throws Exception {
        Util.close(c, b, a);
    }

    @Test
    public void testBroadcastSequence() throws Exception {
        MyReceiver rb=new MyReceiver("B"), rc=new MyReceiver("C");
        b.setReceiver(rb); c.setReceiver(rc);

        View v2=b.getView(), v3=c.getView();
        System.out.println("ch2's view: " + v2 + "\nch3's view: " + v3);
        assert v2.equals(v3);

        new Thread() {
            public void run() {
                Util.sleep(3000);
                System.out.println("** killing A");
                try {
                    Util.shutdown(a);
                }
                catch(Exception e) {
                    System.err.println("failed shutting down channel " + a.getAddress() + ", exception=" + e);
                }
                System.out.println("** A killed");
                injectSuspectEvent(a.getAddress(), b, c);
                a=null;
            }
        }.start();

        for(int i=1; i <= NUM_MSGS; i++) {
            Util.sleep(300);
            b.send(new Message(null, null, new Integer(i)));
            System.out.print("-- messages sent: " + i + "/" + NUM_MSGS + "\r");
        }
        System.out.println("");
        v2=b.getView();
        v3=c.getView();
        System.out.println("B's view: " + v2 + "\nC's view: " + v3);
        assert v2.equals(v3);
        assert v2.size() == 2;
        int s2, s3;
        for(int i=15000; i > 0; i-=1000) {
            s2=rb.size(); s3=rc.size();
            if(s2 >= NUM_MSGS && s3 >= NUM_MSGS) {
                System.out.print("B: " + s2 + " msgs, C: " + s3 + " msgs\r");
                break;
            }
            Util.sleep(1000);
            System.out.print("sleeping for " + (i/1000) + " seconds (B: " + s2 + " msgs, C: " + s3 + " msgs)\r");
        }
        System.out.println("-- verifying messages on B and C");
        List<Integer> list_b=rb.getList(), list_c=rc.getList();
        System.out.println("B: " + list_b + "\nC: " + list_c);

        assert list_b.size() == list_c.size();
        System.out.println("OK: both B and C have the same number of messages (" + list_b.size() + ")");

        assert list_b.size() == NUM_MSGS && list_c.size() == NUM_MSGS;
        System.out.println("OK: both B and C have the expected number (" + NUM_MSGS + ") of messages");

        System.out.println("verifying B and C have the same order");
        for(int i=0; i < list_b.size(); i++) {
            Integer el_b=list_b.get(i), el_c=list_c.get(i);
            assert el_b.equals(el_c) : "element at index=" + i + " in B (" + el_b +
                    ") is different from element " + i + " in C (" + el_c + ")"; 
        }
        System.out.println("OK: B and C's message are in the same order");
    }




    /** Injects SUSPECT event(suspected_mbr) into channels */
    private static void injectSuspectEvent(Address suspected_mbr, JChannel ... channels) {
        Event evt=new Event(Event.SUSPECT, suspected_mbr);
        for(JChannel ch: channels) {
            GMS gms=(GMS)ch.getProtocolStack().findProtocol(GMS.class);
            if(gms != null)
                gms.up(evt);
        }
    }


    private static class MyReceiver extends ReceiverAdapter {
        private final List<Integer> list=new LinkedList<Integer>();
        private final String name;

        public MyReceiver(String name) {
            this.name=name;
        }

        public List<Integer> getList() {return list;}

        public int size() {return list.size();}

        public void receive(Message msg) {
            Integer val=(Integer)msg.getObject();
            synchronized(list) {
                list.add(val);
            }
        }

        void clear() {list.clear();}
    }


}
