@Test
    public void testSingleNodeWithLearner() throws Exception {
        final Endpoint addr = new Endpoint(TestUtils.getMyIp(), TestUtils.INIT_PORT);
        final PeerId peer = new PeerId(addr, 0);

        final Endpoint learnerAddr = new Endpoint(TestUtils.getMyIp(), TestUtils.INIT_PORT + 1);
        final PeerId learnerPeer = new PeerId(learnerAddr, 0);

        NodeManager.getInstance().addAddress(addr);
        NodeManager.getInstance().addAddress(learnerAddr);
        MockStateMachine learnerFsm = null;
        Node learner = null;
        RaftGroupService learnerServer = null;
        {
            // Start learner
            final NodeOptions nodeOptions = createNodeOptionsWithSharedTimer();
            learnerFsm = new MockStateMachine(learnerAddr);
            nodeOptions.setFsm(learnerFsm);
            nodeOptions.setLogUri(this.dataPath + File.separator + "log1");
            nodeOptions.setRaftMetaUri(this.dataPath + File.separator + "meta1");
            nodeOptions.setSnapshotUri(this.dataPath + File.separator + "snapshot1");
            nodeOptions.setInitialConf(new Configuration(Collections.singletonList(peer), Collections
                .singletonList(learnerPeer)));

            final RpcServer rpcServer = RaftRpcServerFactory.createRaftRpcServer(learnerAddr);
            learnerServer = new RaftGroupService("unittest", new PeerId(learnerAddr, 0), nodeOptions, rpcServer);
            learner = learnerServer.start();
        }

        {
            // Start leader
            final NodeOptions nodeOptions = createNodeOptionsWithSharedTimer();
            final MockStateMachine fsm = new MockStateMachine(addr);
            nodeOptions.setFsm(fsm);
            nodeOptions.setLogUri(this.dataPath + File.separator + "log");
            nodeOptions.setRaftMetaUri(this.dataPath + File.separator + "meta");
            nodeOptions.setSnapshotUri(this.dataPath + File.separator + "snapshot");
            nodeOptions.setInitialConf(new Configuration(Collections.singletonList(peer), Collections
                .singletonList(learnerPeer)));
            final Node node = new NodeImpl("unittest", peer);
            assertTrue(node.init(nodeOptions));

            assertEquals(1, node.listPeers().size());
            assertTrue(node.listPeers().contains(peer));
            while (!node.isLeader()) {
                ;
            }
            sendTestTaskAndWait(node);
            assertEquals(10, fsm.getLogs().size());
            int i = 0;
            for (final ByteBuffer data : fsm.getLogs()) {
                assertEquals("hello" + i++, new String(data.array()));
            }
            Thread.sleep(1000); //wait for entries to be replicated to learner.
            node.shutdown();
            node.join();
        }
        {
            // assert learner fsm
            assertEquals(10, learnerFsm.getLogs().size());
            int i = 0;
            for (final ByteBuffer data : learnerFsm.getLogs()) {
                assertEquals("hello" + i++, new String(data.array()));
            }
            learnerServer.shutdown();
            learnerServer.join();
        }
    }
Test rollback stateMachine with readIndex for issue 317:
https://github.com/sofastack/sofa-jraft/issues/317
jraft-extension/java-log-storage-impl/src/test/java/com/alipay/sofa/jraft/core/NodeTest.java