package org.jgroups.tests;

import org.jgroups.*;
import org.jgroups.conf.ClassConfigurator;
import org.jgroups.protocols.*;
import org.jgroups.protocols.pbcast.GMS;
import org.jgroups.protocols.pbcast.NAKACK2;
import org.jgroups.protocols.pbcast.STABLE;
import org.jgroups.stack.ProtocolStack;
import org.jgroups.util.MutableDigest;
import org.jgroups.util.Util;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;

import java.lang.reflect.Method;
import java.util.*;


/**
 * Tests a merge between asymmetric partitions where an isolated coord is still seen by others.
 * Example : {A,B,C,D} and {A}.  
 * 
 */
@Test(groups=Global.FUNCTIONAL,sequential=true)
public class MergeTest4 {
    protected JChannel a,b,c,d,e,f,g,h,i,j,s,t,u,v;

    protected static final Method stopInfoSender, startInfoSender;
    protected static final short  merge_id=ClassConfigurator.getProtocolId(MERGE3.class);

    static {
        try {
            stopInfoSender=MERGE3.class.getDeclaredMethod("stopInfoSender");
            startInfoSender=MERGE3.class.getDeclaredMethod("startInfoSender");
            stopInfoSender.setAccessible(true);
            startInfoSender.setAccessible(true);
        }
        catch(NoSuchMethodException e) {
            throw new RuntimeException(e);
        }
    }

    @BeforeMethod
    void setUp() throws Exception {
        a=createChannel("A", true);
        b=createChannel("B", true);
        c=createChannel("C", true);
        d=createChannel("D", true);
        Util.waitUntilAllChannelsHaveSameView(10000, 500, a, b, c, d);
        enableInfoSender(false, a, b, c, d); // stops INFO sending in MERGE3
    }

    @AfterMethod void tearDown() throws Exception {Util.close(v,u,t,s,j,i,h,g,f,e,d,c,b,a);}

    /**
     * Tests a simple split: {A,B} and {C,D} need to merge back into one subgroup. Checks how many MergeViews are installed
     */
    public void testSplitInTheMiddle() throws Exception {
        View v1=View.create(a.getAddress(), 10, a.getAddress(), b.getAddress());
        View v2=View.create(c.getAddress(), 10, c.getAddress(), d.getAddress());

        injectView(v1, a,b);
        injectView(v2, c,d);
        Util.waitUntilAllChannelsHaveSameView(10000, 500, a, b);
        Util.waitUntilAllChannelsHaveSameView(10000, 500, c, d);
        for(JChannel ch: Arrays.asList(a,b,c,d))
            System.out.println(ch.getName() + ": " + ch.getView());
        Address merge_leader_one=new Membership().add(a.getAddress(), b.getAddress()).sort().elementAt(0);
        Address merge_leader_two=new Membership().add(c.getAddress(), d.getAddress()).sort().elementAt(0);

        for(int x=0; x < 20; x++) {
            if(a.getView().size() == 4 && b.getView().size() == 4 && c.getView().size() == 4 && d.getView().size() == 4)
                break;

            for(JChannel ch: Arrays.asList(a,b,c,d)) {
                MERGE3 merge=ch.getProtocolStack().findProtocol(MERGE3.class);
                merge.sendInfo(); // multicasts an INFO msg to everybody else
            }

            for(JChannel ch: Arrays.asList(findChannel(merge_leader_one), findChannel(merge_leader_two))) {
                MERGE3 merge=ch.getProtocolStack().findProtocol(MERGE3.class);
                merge.checkInconsistencies();
            }
            Util.sleep(1000);
        }

        for(JChannel ch: Arrays.asList(a,b,c,d))
            System.out.println(ch.getName() + ": " + ch.getView());
        for(JChannel ch: Arrays.asList(a,b,c,d))
            assert ch.getView().size() == 4 : "view of " + ch.getName() + ": " + ch.getView();
    }


    /**
     * Tests a simple split: {A,B} and {C,D} need to merge back into one subgroup. Checks how many MergeViews are installed
     */
    public void testSplitInTheMiddle2() throws Exception {
        View v1=View.create(a.getAddress(), 10, a.getAddress(), b.getAddress());
        View v2=View.create(c.getAddress(), 10, c.getAddress(), d.getAddress());
        injectView(v1, a,b);
        injectView(v2, c,d);
        enableInfoSender(false, a,b,c,d);
        Util.waitUntilAllChannelsHaveSameView(10000, 500, a, b);
        Util.waitUntilAllChannelsHaveSameView(10000, 500, c, d);
        enableInfoSender(false, a,b,c,d);
        for(JChannel ch: Arrays.asList(a,b,c,d))
            System.out.println(ch.getName() + ": " + ch.getView());
        System.out.println("\nEnabling INFO sending in merge protocols to merge subclusters");
        enableInfoSender(true, a, b, c, d);

        Util.waitUntilAllChannelsHaveSameView(30000, 1000, a, b, c, d);
        System.out.println("\nResulting views:");
        for(JChannel ch: Arrays.asList(a,b,c,d)) {
            GMS gms=ch.getProtocolStack().findProtocol(GMS.class);
            View mv=gms.view();
            System.out.println(mv);
        }
        for(JChannel ch: Arrays.asList(a,b,c,d)) {
            GMS gms=ch.getProtocolStack().findProtocol(GMS.class);
            View mv=gms.view();
            assert mv instanceof MergeView;
            assert mv.size() == 4;
            assert ((MergeView)mv).getSubgroups().size() == 2;
        }
        for(JChannel ch: Arrays.asList(a,b,c,d)) {
            View view=ch.getView();
            assert view.size() == 4 : "view should have 4 members: " + view;
        }
    }


    public void testMergeWithAsymetricViewsCoordIsolated() throws Exception {
        // Isolate the coord
        Address coord = a.getView().getCreator();
        System.out.println("Isolating coord: " + coord);
        List<Address> members = new ArrayList<>();
        members.add(coord);
        View coord_view=new View(coord, 4, members);
        System.out.println("coord_view: " + coord_view);
        JChannel coord_channel = findChannel(coord);
        System.out.println("coord_channel: " + coord_channel.getAddress());
        
        MutableDigest digest=new MutableDigest(coord_view.getMembersRaw());
        NAKACK2 nakack=coord_channel.getProtocolStack().findProtocol(NAKACK2.class);
        digest.merge(nakack.getDigest(coord));
        
        GMS gms=coord_channel.getProtocolStack().findProtocol(GMS.class);
        gms.installView(coord_view, digest);
        System.out.println("gms.getView() " + gms.getView());
        
        System.out.println("Views are:");
        for(JChannel ch: Arrays.asList(a,b,c,d))
            System.out.println(ch.getAddress() + ": " + ch.getView());
        
        JChannel merge_leader=findChannel(coord);
        MyReceiver receiver=new MyReceiver();
        merge_leader.setReceiver(receiver);

        System.out.println("merge_leader: " + merge_leader.getAddressAsString());
        
        System.out.println("Injecting MERGE event into merge leader " + merge_leader.getAddress());
        Map<Address,View> merge_views=new HashMap<>(4);
        merge_views.put(a.getAddress(), a.getView());
        merge_views.put(b.getAddress(), b.getView());
        merge_views.put(c.getAddress(), c.getView());
        merge_views.put(d.getAddress(), d.getView());
        
        gms=merge_leader.getProtocolStack().findProtocol(GMS.class);
        gms.up(new Event(Event.MERGE, merge_views));
        
        Util.waitUntilAllChannelsHaveSameView(10000, 1000, a, b, c, d);
        
        System.out.println("Views are:");
        for(JChannel ch: Arrays.asList(a,b,c,d)) {
            View view=ch.getView();
            System.out.println(ch.getAddress() + ": " + view);
            assert view.size() == 4;
        }
        MergeView merge_view=receiver.getView();
        System.out.println("merge_view = " + merge_view);
        assert merge_view.size() == 4;
        assert merge_view.getSubgroups().size() == 2;

        for(View view: merge_view.getSubgroups())
            assert contains(view, a.getAddress()) || contains(view, b.getAddress(), c.getAddress(), d.getAddress());
    }

    /**
     * Tests a merge between ViewIds of the same coord, e.g. A|6, A|7, A|8, A|9
     */
    public void testViewsBySameCoord() throws Exception {
        View v1=View.create(a.getAddress(), 6, a.getAddress(),b.getAddress(),c.getAddress(),d.getAddress()); // {A,B,C,D}
        View v2=View.create(a.getAddress(), 7, a.getAddress(),b.getAddress(),c.getAddress());                // {A,B,C}
        View v3=View.create(a.getAddress(), 8, a.getAddress(),b.getAddress());                               // {A,B}
        View v4=View.create(a.getAddress(), 9, a.getAddress());                                              // {A}

        Util.close(b,c,d); // not interested in those...

        MERGE3 merge=a.getProtocolStack().findProtocol(MERGE3.class);
        for(View view: Arrays.asList(v1,v2,v4,v3)) {
            MERGE3.MergeHeader hdr=MERGE3.MergeHeader.createInfo(view.getViewId(), null, null);
            Message msg=new Message(null).src(a.getAddress()).putHeader(merge.getId(), hdr);
            merge.up(msg);
        }

        merge.checkInconsistencies(); // no merge will happen

        Util.waitUntilAllChannelsHaveSameView(10000, 500, a);
        System.out.println("A's view: " + a.getView());
        assert a.getView().size() == 1;
        assert a.getView().containsMember(a.getAddress());
    }


    /**
     * Tests A|6, A|7, A|8 and B|7, B|8, B|9 -> we should have a subviews in MergeView consisting of
     * only 2 elements: A|5 and B|5
     */
    public void testMultipleViewsBySameMembers() throws Exception {
        View a1=View.create(a.getAddress(), 6, a.getAddress(),b.getAddress(),c.getAddress(),d.getAddress()); // {A,B,C,D}
        View a2=View.create(a.getAddress(), 7, a.getAddress(),b.getAddress(),c.getAddress());                // {A,B,C}
        View a3=View.create(a.getAddress(), 8, a.getAddress(),b.getAddress());                               // {A,B}
        View a4=View.create(a.getAddress(), 9, a.getAddress());                                              // {A}

        View b1=View.create(b.getAddress(), 7, b.getAddress(), c.getAddress(), d.getAddress());
        View b2=View.create(b.getAddress(), 8, b.getAddress(), c.getAddress());
        View b3=View.create(b.getAddress(), 9, b.getAddress());

        Util.close(c,d); // not interested in those...

        // A and B cannot communicate:
        discard(true, a,b);

        // inject view A|6={A} into A and B|5={B} into B
        injectView(a4, a);
        injectView(b3, b);

        assert a.getView().equals(a4);
        assert b.getView().equals(b3);

        List<Message> merge_msgs=new ArrayList<>();
        for(View view: Arrays.asList(a3,a4,a2,a1)) {
            MERGE3.MergeHeader hdr=MERGE3.MergeHeader.createInfo(view.getViewId(), null, null);
            Message msg=new Message(null).src(a.getAddress()).putHeader(merge_id, hdr);
            merge_msgs.add(msg);
        }
        for(View view: Arrays.asList(b2,b3,b1)) {
            MERGE3.MergeHeader hdr=MERGE3.MergeHeader.createInfo(view.getViewId(), null, null);
            Message msg=new Message(null).src(b.getAddress()).putHeader(merge_id, hdr);
            merge_msgs.add(msg);
        }

        // A and B can communicate again
        discard(false, a,b);

        injectMergeEvents(merge_msgs, a,b);
        checkInconsistencies(a,b); // merge will happen between A and B
        Util.waitUntilAllChannelsHaveSameView(10000, 500, a, b);
        System.out.println("A's view: " + a.getView() + "\nB's view: " + b.getView());
        assert a.getView().size() == 2;
        assert a.getView().containsMember(a.getAddress());
        assert a.getView().containsMember(b.getAddress());
        assert a.getView().equals(b.getView());
        for(View merge_view: Arrays.asList(getViewFromGMS(a), getViewFromGMS(b))) {
            System.out.println(merge_view);
            assert merge_view instanceof MergeView;
            List<View> subviews=((MergeView)merge_view).getSubgroups();
            assert subviews.size() == 2;
        }
    }

    /**
     * Takes a membership of 10, e.g. {A,B,C,D,E,F,G,H,I,J}, creates multiple partitions (e.g. {A,B,C}, {D}, {E,F,G,H}, {I,J}),
     * and merges them back into one cluster. Asserts that there's only 1 MergeView
     */
    public void testMultipleSplits() throws Exception {
        e=createChannel("E", true);
        f=createChannel("F", true);
        g=createChannel("G", true);
        h=createChannel("H", true);
        i=createChannel("I", true);
        j=createChannel("J", true);
        Util.waitUntilAllChannelsHaveSameView(10000, 500, a, b, c, d, e, f, g, h, i, j);
        enableInfoSender(false, a,b,c,d,e,f,g,h,i,j); // stops INFO sending in MERGE3
        for(JChannel ch: Arrays.asList(a,b,c,d,e,f,g,h,i,j))
            System.out.println(ch.getName() + ": " + ch.getView());
        List<View> partitions=createPartitions(4, a,b,c,d,e,f,g,h,i,j);

        // Install views
        for(View partition: partitions) {
            for(Address mbr: partition.getMembersRaw()) {
                JChannel ch=findChannel(mbr);
                injectView(partition, ch);
            }
        }

        System.out.printf("\n%d partitions:\n", partitions.size());
        for(JChannel ch: Arrays.asList(a,b,c,d,e,f,g,h,i,j))
            System.out.println(ch.getName() + ": " + ch.getView());

        System.out.println("\nEnabling INFO sending in merge protocols to merge subclusters");
        enableInfoSender(true, a,b,c,d,e,f,g,h,i,j);

        Util.waitUntilAllChannelsHaveSameView(30000, 1000, a, b, c, d, e, f, g, h, i, j);
        System.out.println("\nResulting views:");
        for(JChannel ch: Arrays.asList(a,b,c,d,e,f,g,h,i,j)) {
            GMS gms=ch.getProtocolStack().findProtocol(GMS.class);
            View mv=gms.view();
            System.out.println(mv);
        }
        for(JChannel ch: Arrays.asList(a,b,c,d,e,f,g,h,i,j)) {
            GMS gms=ch.getProtocolStack().findProtocol(GMS.class);
            View mv=gms.view();
            assert mv instanceof MergeView;
            assert mv.size() == 10;
            assert ((MergeView)mv).getSubgroups().size() == partitions.size();
        }
        for(JChannel ch: Arrays.asList(a,b,c,d,e,f,g,h,i,j)) {
            View view=ch.getView();
            assert view.size() == 10 : "view should have 10 members: " + view;
        }
    }


    /**
     * A: {A,B}
     * B: {A,B}
     * C: {C}
     * C receives INFO from B only
     */
    public void testMergeWithIncompleteInfos() throws Exception {
        Util.close(d);
        enableInfoSender(false, a,b,c);
        View one=View.create(a.getAddress(), 10, a.getAddress(),b.getAddress());
        View two=View.create(c.getAddress(), 10, c.getAddress());
        injectView(one, a,b);
        injectView(two, c);
        enableInfoSender(false, a,b,c);

        Util.waitUntilAllChannelsHaveSameView(10000, 500, a, b);
        Util.waitUntilAllChannelsHaveSameView(10000, 500, c);
        System.out.printf("\nPartitions:\n");
        for(JChannel ch: Arrays.asList(a,b,c))
            System.out.println(ch.getName() + ": " + ch.getView());

        MERGE3.MergeHeader hdr=MERGE3.MergeHeader.createInfo(one.getViewId(), null, null);
        Message msg=new Message(null).src(b.getAddress()).putHeader(merge_id, hdr); // B sends the INFO message to C
        MERGE3 merge=c.getProtocolStack().findProtocol(MERGE3.class);
        merge.up(msg);
        enableInfoSender(true,a,b,c);
        System.out.println("\nEnabling INFO sending in merge protocols to merge subclusters");

        Util.waitUntilAllChannelsHaveSameView(30000, 1000, a, b, c);
        System.out.println("\nResulting views:");
        for(JChannel ch: Arrays.asList(a,b,c)) {
            GMS gms=ch.getProtocolStack().findProtocol(GMS.class);
            View mv=gms.view();
            System.out.println(mv);
        }
        for(JChannel ch: Arrays.asList(a,b,c)) {
            GMS gms=ch.getProtocolStack().findProtocol(GMS.class);
            View mv=gms.view();
            assert mv instanceof MergeView;
            assert mv.size() == 3;
            assert ((MergeView)mv).getSubgroups().size() == 2;
        }
        for(JChannel ch: Arrays.asList(a,b,c)) {
            View view=ch.getView();
            assert view.size() == 3 : "view should have 3 members: " + view;
        }
    }

    /** Tests the scenario described by Karim in https://issues.jboss.org/browse/JGRP-1876 */
    public void testJGRP_1876() throws Exception {
        e=createChannel("E", true);
        f=createChannel("F", true);
        g=createChannel("G", true);
        h=createChannel("H", true);
        Util.waitUntilAllChannelsHaveSameView(10000, 500, a, b, c, d, e, f, g, h);
        enableInfoSender(false, a,b,c,d,e,f,g,h); // stops INFO sending in MERGE3
        for(JChannel ch: Arrays.asList(a,b,c,d,e,f,g,h))
            System.out.println(ch.getName() + ": " + ch.getView());
        View v1=View.create(a.getAddress(), 10, a.getAddress(), b.getAddress(), c.getAddress(), d.getAddress());
        View v2=View.create(a.getAddress(), 9, a.getAddress(), b.getAddress(), c.getAddress(), d.getAddress(), e.getAddress(), f.getAddress());
        View v3=View.create(g.getAddress(), 8, g.getAddress(), h.getAddress());

        injectView(v1, a,b,c,d);
        injectView(v2, e,f);
        injectView(v3, g,h);
        enableInfoSender(false, a,b,c,d,e,f,g,h); // stops INFO sending in MERGE3
        Util.waitUntilAllChannelsHaveSameView(10000, 500, a, b, c, d);
        for(int x=0; x < 10; x++) { // can't use waitUntilAllChannelsHaveSameSize() as the channel list's length != view !
            if(e.getView().size() == v2.size() && f.getView().size() == v2.size())
                break;
            Util.sleep(500);
        }
        assert e.getView().size() == v2.size() : "E's view: " + e.getView();
        assert f.getView().size() == v2.size() : "F's view: " + f.getView();
        Util.waitUntilAllChannelsHaveSameView(10000, 500, g, h);

        System.out.printf("\nPartitions:\n");
        for(JChannel ch: Arrays.asList(a,b,c,d,e,f,g,h))
            System.out.println(ch.getName() + ": " + ch.getView());

        enableInfoSender(true,a,b,c,d,e,f,g,h);
        System.out.println("\nEnabling INFO sending in merge protocols to merge subclusters");

        Util.waitUntilAllChannelsHaveSameView(30000, 1000, a, b, c, d, e, f, g, h);
        System.out.println("\nResulting views:");
        for(JChannel ch: Arrays.asList(a,b,c,d,e,f,g,h)) {
            GMS gms=ch.getProtocolStack().findProtocol(GMS.class);
            View mv=gms.view();
            System.out.println(mv);
        }
        for(JChannel ch: Arrays.asList(a,b,c,d,e,f,g,h)) {
            GMS gms=ch.getProtocolStack().findProtocol(GMS.class);
            View mv=gms.view();
            assert mv instanceof MergeView;
            assert mv.size() == 8;
            assert ((MergeView)mv).getSubgroups().size() == 3;
        }
        for(JChannel ch: Arrays.asList(a,b,c,d,e,f,g,h)) {
            View view=ch.getView();
            assert view.size() == 8 : "view should have 8 members: " + view;
        }
    }


    /** Tests the scenario described by Dan in https://issues.jboss.org/browse/JGRP-1876 */
    public void testJGRP_1876_Dan() throws Exception {
        Util.close(d,c,b,a);
        s=createChannel("S", true);
        t=createChannel("T", true);
        u=createChannel("U", true);
        v=createChannel("V", true);
        Util.waitUntilAllChannelsHaveSameView(10000, 500, s, t, u, v);
        enableInfoSender(false, s,t,u,v); // stops INFO sending in MERGE3
        for(JChannel ch: Arrays.asList(s,t,u,v))
            System.out.println(ch.getName() + ": " + ch.getView());
        View v1=View.create(s.getAddress(), 10, s.getAddress());
        View v2=View.create(t.getAddress(), 10, t.getAddress());
        View v3=View.create(u.getAddress(), 11, u.getAddress(), v.getAddress());

        injectView(v1, s);
        injectView(v2, t);
        injectView(v3, u,v);
        enableInfoSender(false, s,t,u,v); // stops INFO sending in MERGE3
        Util.waitUntilAllChannelsHaveSameView(10000, 500, s);
        Util.waitUntilAllChannelsHaveSameView(10000, 500, t);
        Util.waitUntilAllChannelsHaveSameView(10000, 500, u, v);

        System.out.printf("\nPartitions:\n");
        for(JChannel ch: Arrays.asList(s,t,u,v))
            System.out.println(ch.getName() + ": " + ch.getView());

        enableInfoSender(true,s,t,u,v);
        System.out.println("\nEnabling INFO sending in merge protocols to merge subclusters");

        Util.waitUntilAllChannelsHaveSameView(30000, 1000, s, t, u, v);
        System.out.println("\nResulting views:");
        for(JChannel ch: Arrays.asList(s,t,u,v)) {
            GMS gms=ch.getProtocolStack().findProtocol(GMS.class);
            View mv=gms.view();
            System.out.println(mv);
        }
        for(JChannel ch: Arrays.asList(s,t,u,v)) {
            GMS gms=ch.getProtocolStack().findProtocol(GMS.class);
            View mv=gms.view();
            assert mv instanceof MergeView;
            assert mv.size() == 4;
            assert ((MergeView)mv).getSubgroups().size() == 3;
        }
        for(JChannel ch: Arrays.asList(s,t,u,v)) {
            View view=ch.getView();
            assert view.size() == 4 : "view should have 4 members: " + view;
        }
    }


    /** Tests the scenario described by Dan in https://issues.jboss.org/browse/JGRP-1876; INFO messages are injected */
    public void testJGRP_1876_Dan2() throws Exception {
        Util.close(d,c,b,a);
        s=createChannel("S", false);
        t=createChannel("T", false);
        u=createChannel("U", false);
        v=createChannel("V", false);
        for(JChannel ch: Arrays.asList(s,t,u,v))
            ch.getProtocolStack().removeProtocol(MERGE3.class);
        s.connect("MergeTest4");
        t.connect("MergeTest4");
        u.connect("MergeTest4");
        v.connect("MergeTest4");
        Util.waitUntilAllChannelsHaveSameView(10000, 500, s, t, u, v);

        for(JChannel ch: Arrays.asList(s,t,u,v))
            System.out.println(ch.getName() + ": " + ch.getView());
        View v1=View.create(s.getAddress(), 10, s.getAddress());
        View v2=View.create(t.getAddress(), 10, t.getAddress());
        View v3=View.create(u.getAddress(), 11, u.getAddress(), v.getAddress());
        injectView(v1, s);
        injectView(v2, t);
        injectView(v3, u, v);
        Util.waitUntilAllChannelsHaveSameView(10000, 500, s);
        Util.waitUntilAllChannelsHaveSameView(10000, 500, t);
        Util.waitUntilAllChannelsHaveSameView(10000, 500, u, v);

        System.out.printf("\nPartitions:\n");
        for(JChannel ch: Arrays.asList(s,t,u,v))
            System.out.println(ch.getName() + ": " + ch.getView());

        System.out.println("\nInjecting MERGE events into GMS to merge subclusters");
        Map<Address,View> different_views=new HashMap<>();
        different_views.put(s.getAddress(), v1);
        different_views.put(t.getAddress(), v2);
        different_views.put(v.getAddress(), v3);

        Collection<Address> coords=Util.determineActualMergeCoords(different_views);
        Address merge_leader=new Membership().add(coords).sort().elementAt(0);
        System.out.printf("--> coords=%s, merge_leader=%s\n", coords, merge_leader);
        GMS gms=findChannel(merge_leader).getProtocolStack().findProtocol(GMS.class);
        gms.up(new Event(Event.MERGE, different_views));

        Util.waitUntilAllChannelsHaveSameView(30000, 1000, s, t, u, v);
        System.out.println("\nResulting views:");
        for(JChannel ch: Arrays.asList(s,t,u,v)) {
            gms=ch.getProtocolStack().findProtocol(GMS.class);
            View mv=gms.view();
            System.out.println(mv);
        }
        for(JChannel ch: Arrays.asList(s,t,u,v)) {
            gms=ch.getProtocolStack().findProtocol(GMS.class);
            View mv=gms.view();
            assert mv instanceof MergeView;
            assert mv.size() == 4;
            assert ((MergeView)mv).getSubgroups().size() == 3;
        }
        for(JChannel ch: Arrays.asList(s,t,u,v)) {
            View view=ch.getView();
            assert view.size() == 4 : "view should have 4 members: " + view;
        }
    }



    /** Creates a list of randomly generated partitions, each having a max size of max_partition_size */
    protected List<View> createPartitions(int max_partition_size, JChannel ... channels) {
        long view_id=1;
        for(JChannel ch: channels)
            view_id=Math.max(view_id, ch.getView().getViewId().getId());
        List<View> partitions=new ArrayList<>();
        List<Address> tmp=new ArrayList<>();
        for(JChannel ch: channels)
            tmp.add(ch.getAddress());

        while(!tmp.isEmpty()) {
            int num_to_remove=(int)Util.random(max_partition_size);
            List<Address> part=new ArrayList<>(max_partition_size);
            for(int x=0; x < num_to_remove && !tmp.isEmpty(); x++)
                part.add(tmp.remove(0));
            partitions.add(new View(part.get(0), view_id+1, part));
        }
        return partitions;
    }


    protected static boolean contains(View view, Address ... members) {
        List<Address> mbrs=view.getMembers();
        for(Address member: members)
            if(!mbrs.contains(member))
                return false;
        return true;
    }

    protected static class MyReceiver extends ReceiverAdapter {
        protected MergeView view;

        public MergeView getView() {return view;}

        @Override public void viewAccepted(View view) {
            if(view instanceof MergeView)
                this.view=(MergeView)view;
        }
    }
    
    protected JChannel createChannel(String name, boolean connect) throws Exception {
        JChannel retval=new JChannel(new SHARED_LOOPBACK(),
                                     new SHARED_LOOPBACK_PING(),
                                     new MERGE3().setValue("min_interval", 3000).setValue("max_interval", 4000).setValue("check_interval", 7000),
                                     new NAKACK2().setValue("use_mcast_xmit",false)
                                       .setValue("log_discard_msgs",false).setValue("log_not_found_msgs",false),
                                     new UNICAST3(),
                                     new STABLE().setValue("max_bytes",50000),
                                     new GMS().setValue("print_local_addr",false)
                                       .setValue("join_timeout", 100)
                                       .setValue("leave_timeout", 100)
                                       .setValue("merge_timeout",5000)
                                       .setValue("log_view_warnings",false)
                                       .setValue("view_ack_collection_timeout",50)
                                       .setValue("log_collect_msgs",false))
          .name(name);
        if(connect)
            retval.connect("MergeTest4");
        return retval;
    }

    protected JChannel findChannel(Address mbr) {
        for(JChannel ch: Arrays.asList(a,b,c,d,e,f,g,h,i,j,s,t,u,v)) {
            if(ch != null && ch.getAddress() != null && ch.getAddress().equals(mbr))
                return ch;
        }
        return null;
    }


    protected void injectMergeEvents(List<Message> msgs, JChannel ... channels) {
        for(JChannel ch: channels) {
            MERGE3 merge=ch.getProtocolStack().findProtocol(MERGE3.class);
            msgs.forEach(merge::up);
        }
    }

    protected void discard(boolean flag, JChannel ... channels) throws Exception {
        for(JChannel ch: channels) {
            ProtocolStack stack=ch.getProtocolStack();
            DISCARD discard=stack.findProtocol(DISCARD.class);
            if(discard == null)
                stack.insertProtocol(discard=new DISCARD(), ProtocolStack.Position.ABOVE, stack.getTransport().getClass());
            discard.setDiscardAll(flag);
        }
    }

    protected void injectView(View view, JChannel ... channels) {
        for(JChannel ch: channels) {
            GMS gms=ch.getProtocolStack().findProtocol(GMS.class);
            gms.installView(view);
        }
    }

    protected void checkInconsistencies(JChannel ... channels) {
        for(JChannel ch: channels) {
            MERGE3 merge=ch.getProtocolStack().findProtocol(MERGE3.class);
            merge.checkInconsistencies();
        }
    }

    protected View getViewFromGMS(JChannel ch) {
        GMS gms=ch.getProtocolStack().findProtocol(GMS.class);
        return gms.view();
    }

    protected List<Address> getMembers(JChannel ... channels) {
        List<Address> members=new ArrayList<>(channels.length);
        for(JChannel ch: channels)
            members.add(ch.getAddress());
        return members;
    }

    protected void enableInfoSender(boolean enable, JChannel... channels) throws Exception {
        for(JChannel ch: channels) {
            MERGE3 merge=ch.getProtocolStack().findProtocol(MERGE3.class);
            Method meth=enable? startInfoSender : stopInfoSender;
            meth.invoke(merge);
        }
    }


    @Test(enabled=false)
    public static void main(String[] args) throws Exception {
        MergeTest4 test=new MergeTest4();
        test.setUp();
        test.testMergeWithAsymetricViewsCoordIsolated();
        test.tearDown();
    }
}
