Repository: incubator-ratis
Updated Branches:
  refs/heads/master d28b6493f -> 5c37675fa


http://git-wip-us.apache.org/repos/asf/incubator-ratis/blob/5c37675f/ratis-test/src/test/java/org/apache/ratis/statemachine/TestStateMachine.java
----------------------------------------------------------------------
diff --git 
a/ratis-test/src/test/java/org/apache/ratis/statemachine/TestStateMachine.java 
b/ratis-test/src/test/java/org/apache/ratis/statemachine/TestStateMachine.java
new file mode 100644
index 0000000..a4dc88a
--- /dev/null
+++ 
b/ratis-test/src/test/java/org/apache/ratis/statemachine/TestStateMachine.java
@@ -0,0 +1,201 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.ratis.statemachine;
+
+import org.apache.log4j.Level;
+import org.apache.ratis.BaseTest;
+import org.apache.ratis.MiniRaftCluster;
+import org.apache.ratis.RaftTestUtil;
+import org.apache.ratis.client.RaftClient;
+import org.apache.ratis.conf.RaftProperties;
+import org.apache.ratis.protocol.Message;
+import org.apache.ratis.protocol.RaftClientRequest;
+import org.apache.ratis.protocol.RaftGroup;
+import org.apache.ratis.protocol.RaftGroupId;
+import org.apache.ratis.protocol.RaftPeer;
+import org.apache.ratis.protocol.RaftPeerId;
+import org.apache.ratis.server.RaftServerConfigKeys;
+import org.apache.ratis.server.impl.RaftServerImpl;
+import org.apache.ratis.server.impl.RaftServerProxy;
+import org.apache.ratis.server.impl.RaftServerTestUtil;
+import org.apache.ratis.server.simulation.MiniRaftClusterWithSimulatedRpc;
+import org.apache.ratis.util.LogUtils;
+import org.junit.*;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.stream.Collectors;
+
+import static org.junit.Assert.*;
+
+/**
+ * Test StateMachine related functionality
+ */
+public class TestStateMachine extends BaseTest implements 
MiniRaftClusterWithSimulatedRpc.FactoryGet {
+  static {
+    LogUtils.setLogLevel(RaftServerImpl.LOG, Level.DEBUG);
+    LogUtils.setLogLevel(RaftClient.LOG, Level.DEBUG);
+  }
+
+  public static final int NUM_SERVERS = 3;
+
+  static class SMTransactionContext extends SimpleStateMachine4Testing {
+    public static SMTransactionContext get(RaftServerImpl s) {
+      return (SMTransactionContext)s.getStateMachine();
+    }
+
+    AtomicReference<Throwable> throwable = new AtomicReference<>(null);
+    AtomicLong transactions = new AtomicLong(0);
+    AtomicBoolean isLeader = new AtomicBoolean(false);
+    AtomicLong numApplied = new AtomicLong(0);
+    ConcurrentLinkedQueue<Long> applied = new ConcurrentLinkedQueue<>();
+
+    @Override
+    public TransactionContext startTransaction(RaftClientRequest request) {
+      // only leader will get this call
+      isLeader.set(true);
+      // send the next transaction id as the "context" from SM
+      return TransactionContext.newBuilder()
+          .setStateMachine(this)
+          .setClientRequest(request)
+          .setStateMachineContext(transactions.incrementAndGet())
+          .build();
+    }
+
+    @Override
+    public CompletableFuture<Message> applyTransaction(TransactionContext trx) 
{
+      try {
+        assertNotNull(trx.getLogEntry());
+        assertNotNull(trx.getStateMachineLogEntry());
+        Object context = trx.getStateMachineContext();
+        if (isLeader.get()) {
+          assertNotNull(trx.getClientRequest());
+          assertNotNull(context);
+          assertTrue(context instanceof Long);
+          Long val = (Long)context;
+          assertTrue(val <= transactions.get());
+          applied.add(val);
+        } else {
+          assertNull(trx.getClientRequest());
+          assertNull(context);
+        }
+        numApplied.incrementAndGet();
+      } catch (Throwable t) {
+        throwable.set(t);
+      }
+      return CompletableFuture.completedFuture(null);
+    }
+
+    void rethrowIfException() throws Throwable {
+      Throwable t = throwable.get();
+      if (t != null) {
+        throw t;
+      }
+    }
+  }
+
+  @Test
+  public void testTransactionContextIsPassedBack() throws Throwable {
+    runTestTransactionContextIsPassedBack(false);
+  }
+
+  @Test
+  public void testTransactionContextIsPassedBackUseMemory() throws Throwable {
+    runTestTransactionContextIsPassedBack(true);
+  }
+
+  void runTestTransactionContextIsPassedBack(boolean useMemory) throws 
Throwable {
+    final RaftProperties properties = new RaftProperties();
+    properties.setClass(MiniRaftCluster.STATEMACHINE_CLASS_KEY, 
SMTransactionContext.class, StateMachine.class);
+    RaftServerConfigKeys.Log.setUseMemory(properties, useMemory);
+
+    try(MiniRaftClusterWithSimulatedRpc cluster = 
getFactory().newCluster(NUM_SERVERS, properties)) {
+      cluster.start();
+      runTestTransactionContextIsPassedBack(cluster);
+    }
+  }
+
+  static void runTestTransactionContextIsPassedBack(MiniRaftCluster cluster) 
throws Throwable {
+    // tests that the TrxContext set by the StateMachine in Leader is passed 
back to the SM
+    int numTrx = 100;
+    final RaftTestUtil.SimpleMessage[] messages = 
RaftTestUtil.SimpleMessage.create(numTrx);
+    try(final RaftClient client = cluster.createClient()) {
+      for (RaftTestUtil.SimpleMessage message : messages) {
+        client.send(message);
+      }
+    }
+
+    // TODO: there eshould be a better way to ensure all data is replicated 
and applied
+    Thread.sleep(cluster.getMaxTimeout() + 100);
+
+    for (RaftServerImpl raftServer : cluster.iterateServerImpls()) {
+      final SMTransactionContext sm = SMTransactionContext.get(raftServer);
+      sm.rethrowIfException();
+      assertEquals(numTrx, sm.numApplied.get());
+    }
+
+    // check leader
+    RaftServerImpl raftServer = cluster.getLeader();
+    // assert every transaction has obtained context in leader
+    final SMTransactionContext sm = SMTransactionContext.get(raftServer);
+    List<Long> ll = sm.applied.stream().collect(Collectors.toList());
+    Collections.sort(ll);
+    assertEquals(ll.toString(), ll.size(), numTrx);
+    for (int i=0; i < numTrx; i++) {
+      assertEquals(ll.toString(), Long.valueOf(i+1), ll.get(i));
+    }
+  }
+
+  @Test
+  public void testStateMachineRegistry() throws Throwable {
+    final Map<RaftGroupId, StateMachine> registry = new ConcurrentHashMap<>();
+    registry.put(RaftGroupId.randomId(), new SimpleStateMachine4Testing());
+    registry.put(RaftGroupId.randomId(), new SMTransactionContext());
+
+    try(MiniRaftClusterWithSimulatedRpc cluster = newCluster(0)) {
+      cluster.setStateMachineRegistry(registry::get);
+
+      final RaftPeerId id = RaftPeerId.valueOf("s0");
+      cluster.putNewServer(id, null, true);
+      cluster.start();
+
+      for(RaftGroupId gid : registry.keySet()) {
+        final RaftGroup newGroup = RaftGroup.valueOf(gid, cluster.getPeers());
+        LOG.info("add new group: " + newGroup);
+        final RaftClient client = cluster.createClient(newGroup);
+        for(RaftPeer p : newGroup.getPeers()) {
+          client.groupAdd(newGroup, p.getId());
+        }
+      }
+
+      final RaftServerProxy proxy = cluster.getServer(id);
+      for(Map.Entry<RaftGroupId, StateMachine> e: registry.entrySet()) {
+        final RaftServerImpl impl = 
RaftServerTestUtil.getRaftServerImpl(proxy, e.getKey());
+        Assert.assertSame(e.getValue(), impl.getStateMachine());
+      }
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/incubator-ratis/blob/5c37675f/ratis-test/src/test/java/org/apache/ratis/util/TestLifeCycle.java
----------------------------------------------------------------------
diff --git a/ratis-test/src/test/java/org/apache/ratis/util/TestLifeCycle.java 
b/ratis-test/src/test/java/org/apache/ratis/util/TestLifeCycle.java
new file mode 100644
index 0000000..9782792
--- /dev/null
+++ b/ratis-test/src/test/java/org/apache/ratis/util/TestLifeCycle.java
@@ -0,0 +1,53 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.ratis.util;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import static org.apache.ratis.util.LifeCycle.State.*;
+
+import java.util.*;
+
+public class TestLifeCycle {
+  /**
+   * Test if the successor map and the predecessor map are consistent.
+   * {@link LifeCycle} uses predecessors to validate transitions
+   * while this test uses successors.
+   */
+  @Test(timeout = 1000)
+  public void testIsValid() throws Exception {
+    final Map<LifeCycle.State, List<LifeCycle.State>> successors
+        = new EnumMap<>(LifeCycle.State.class);
+    put(NEW,       successors, STARTING, CLOSED);
+    put(STARTING,  successors, NEW, RUNNING, CLOSING, EXCEPTION);
+    put(RUNNING,   successors, CLOSING, PAUSING, EXCEPTION);
+    put(PAUSING,   successors, PAUSED, CLOSING, EXCEPTION);
+    put(PAUSED,    successors, STARTING, CLOSING);
+    put(EXCEPTION, successors, CLOSING);
+    put(CLOSING ,  successors, CLOSED);
+    put(CLOSED,    successors);
+
+    final List<LifeCycle.State> states = 
Arrays.asList(LifeCycle.State.values());
+    states.stream().forEach(
+        from -> states.forEach(
+            to -> Assert.assertEquals(from + " -> " + to,
+                successors.get(from).contains(to),
+                isValid(from, to))));
+  }
+}

http://git-wip-us.apache.org/repos/asf/incubator-ratis/blob/5c37675f/ratis-test/src/test/java/org/apache/ratis/util/TestMinMax.java
----------------------------------------------------------------------
diff --git a/ratis-test/src/test/java/org/apache/ratis/util/TestMinMax.java 
b/ratis-test/src/test/java/org/apache/ratis/util/TestMinMax.java
new file mode 100644
index 0000000..8d315b7
--- /dev/null
+++ b/ratis-test/src/test/java/org/apache/ratis/util/TestMinMax.java
@@ -0,0 +1,57 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.ratis.util;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.OptionalLong;
+import java.util.concurrent.ThreadLocalRandom;
+import java.util.stream.LongStream;
+
+public class TestMinMax {
+  @Test(timeout = 1000)
+  public void testMinMax() {
+    runTestMinMax(LongStream.empty());
+    runTestMinMax(LongStream.iterate(0, n -> n).limit(10));
+    for(int count = 1; count < 10; count++) {
+      runTestMinMax(LongStream.iterate(1, n -> n + 1).limit(count));
+    }
+    for(int count = 1; count < 10; count++) {
+      runTestMinMax(LongStream.iterate(0, _dummy -> 
ThreadLocalRandom.current().nextLong()).limit(count));
+    }
+  }
+
+  static void runTestMinMax(LongStream stream) {
+    final List<Long> list = stream.collect(ArrayList::new, List::add, 
List::addAll);
+    final LongMinMax longMinMax = toLongStream(list).collect(LongMinMax::new, 
LongMinMax::accumulate, LongMinMax::combine);
+    if (longMinMax.isInitialized()) {
+      Assert.assertEquals(toLongStream(list).min().getAsLong(), 
longMinMax.getMin());
+      Assert.assertEquals(toLongStream(list).max().getAsLong(), 
longMinMax.getMax());
+    } else {
+      Assert.assertEquals(OptionalLong.empty(), toLongStream(list).min());
+      Assert.assertEquals(OptionalLong.empty(), toLongStream(list).max());
+    }
+  }
+
+  static LongStream toLongStream(List<Long> list) {
+    return list.stream().mapToLong(Long::longValue);
+  }
+}

http://git-wip-us.apache.org/repos/asf/incubator-ratis/blob/5c37675f/ratis-test/src/test/java/org/apache/ratis/util/TestTimeDuration.java
----------------------------------------------------------------------
diff --git 
a/ratis-test/src/test/java/org/apache/ratis/util/TestTimeDuration.java 
b/ratis-test/src/test/java/org/apache/ratis/util/TestTimeDuration.java
new file mode 100644
index 0000000..06d9301
--- /dev/null
+++ b/ratis-test/src/test/java/org/apache/ratis/util/TestTimeDuration.java
@@ -0,0 +1,84 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.ratis.util;
+
+import org.junit.Test;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
+
+import static org.apache.ratis.util.TimeDuration.Abbreviation;
+import static org.apache.ratis.util.TimeDuration.parse;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+public class TestTimeDuration {
+  @Test(timeout = 1000)
+  public void testTimeDuration() throws Exception {
+    Arrays.asList(TimeUnit.values())
+        .forEach(a -> assertNotNull(Abbreviation.valueOf(a.name())));
+    assertEquals(TimeUnit.values().length, Abbreviation.values().length);
+
+    final List<String> allSymbols = 
Arrays.asList(Abbreviation.values()).stream()
+        .map(Abbreviation::getSymbols)
+        .flatMap(List::stream)
+        .collect(Collectors.toList());
+    Arrays.asList(TimeUnit.values()).forEach(unit ->
+        allSymbols.stream()
+            .map(s -> "0" + s)
+            .forEach(s -> assertEquals(s, 0L, parse(s, unit))));
+
+    assertEquals(1L, parse("1000000 ns", TimeUnit.MILLISECONDS));
+    assertEquals(10L, parse("10000000 nanos", TimeUnit.MILLISECONDS));
+    assertEquals(100L, parse("100000000 nanosecond", TimeUnit.MILLISECONDS));
+    assertEquals(1000L, parse("1000000000 nanoseconds", 
TimeUnit.MILLISECONDS));
+
+    assertEquals(1L, parse("1000 us", TimeUnit.MILLISECONDS));
+    assertEquals(10L, parse("10000 μs", TimeUnit.MILLISECONDS));
+    assertEquals(100L, parse("100000 micros", TimeUnit.MILLISECONDS));
+    assertEquals(1000L, parse("1000000 microsecond", TimeUnit.MILLISECONDS));
+    assertEquals(10000L, parse("10000000 microseconds", 
TimeUnit.MILLISECONDS));
+
+    assertEquals(1L, parse("1 ms", TimeUnit.MILLISECONDS));
+    assertEquals(10L, parse("10 msec", TimeUnit.MILLISECONDS));
+    assertEquals(100L, parse("100 millis", TimeUnit.MILLISECONDS));
+    assertEquals(1000L, parse("1000 millisecond", TimeUnit.MILLISECONDS));
+    assertEquals(10000L, parse("10000 milliseconds", TimeUnit.MILLISECONDS));
+
+    assertEquals(1000L, parse("1 s", TimeUnit.MILLISECONDS));
+    assertEquals(10000L, parse("10 sec", TimeUnit.MILLISECONDS));
+    assertEquals(100000L, parse("100 second", TimeUnit.MILLISECONDS));
+    assertEquals(1000000L, parse("1000 seconds", TimeUnit.MILLISECONDS));
+
+    assertEquals(60, parse("1 m", TimeUnit.SECONDS));
+    assertEquals(600, parse("10 min", TimeUnit.SECONDS));
+    assertEquals(6000, parse("100 minutes", TimeUnit.SECONDS));
+    assertEquals(60000, parse("1000 minutes", TimeUnit.SECONDS));
+
+    assertEquals(60, parse("1 h", TimeUnit.MINUTES));
+    assertEquals(600, parse("10 hr", TimeUnit.MINUTES));
+    assertEquals(6000, parse("100 hour", TimeUnit.MINUTES));
+    assertEquals(60000, parse("1000 hours", TimeUnit.MINUTES));
+
+    assertEquals(24, parse("1 d", TimeUnit.HOURS));
+    assertEquals(240, parse("10 day", TimeUnit.HOURS));
+    assertEquals(2400, parse("100 days", TimeUnit.HOURS));
+  }
+}

http://git-wip-us.apache.org/repos/asf/incubator-ratis/blob/5c37675f/ratis-test/src/test/java/org/apache/ratis/util/TestTimeoutScheduler.java
----------------------------------------------------------------------
diff --git 
a/ratis-test/src/test/java/org/apache/ratis/util/TestTimeoutScheduler.java 
b/ratis-test/src/test/java/org/apache/ratis/util/TestTimeoutScheduler.java
new file mode 100644
index 0000000..6a63569
--- /dev/null
+++ b/ratis-test/src/test/java/org/apache/ratis/util/TestTimeoutScheduler.java
@@ -0,0 +1,210 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.ratis.util;
+
+import org.apache.log4j.Level;
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.function.Consumer;
+
+public class TestTimeoutScheduler {
+  {
+    LogUtils.setLogLevel(TimeoutScheduler.LOG, Level.ALL);
+  }
+
+  static class ErrorHandler implements Consumer<RuntimeException> {
+    private final AtomicBoolean hasError = new AtomicBoolean(false);
+
+    @Override
+    public void accept(RuntimeException e) {
+      hasError.set(true);
+      TimeoutScheduler.LOG.error("Failed", e);
+    }
+
+    void assertNoError() {
+      Assert.assertFalse(hasError.get());
+    }
+  }
+
+  @Test(timeout = 1000)
+  public void testSingleTask() throws Exception {
+    final TimeoutScheduler scheduler = TimeoutScheduler.newInstance(1);
+    final TimeDuration grace = TimeDuration.valueOf(100, 
TimeUnit.MILLISECONDS);
+    scheduler.setGracePeriod(grace);
+    Assert.assertFalse(scheduler.hasScheduler());
+
+    final ErrorHandler errorHandler = new ErrorHandler();
+
+    final AtomicBoolean fired = new AtomicBoolean(false);
+    scheduler.onTimeout(TimeDuration.valueOf(250, TimeUnit.MILLISECONDS), () 
-> {
+      Assert.assertFalse(fired.get());
+      fired.set(true);
+    }, errorHandler);
+    Assert.assertTrue(scheduler.hasScheduler());
+
+    Thread.sleep(100);
+    Assert.assertFalse(fired.get());
+    Assert.assertTrue(scheduler.hasScheduler());
+
+    Thread.sleep(100);
+    Assert.assertFalse(fired.get());
+    Assert.assertTrue(scheduler.hasScheduler());
+
+    Thread.sleep(100);
+    Assert.assertTrue(fired.get());
+    Assert.assertTrue(scheduler.hasScheduler());
+
+    Thread.sleep(100);
+    Assert.assertTrue(fired.get());
+    Assert.assertFalse(scheduler.hasScheduler());
+
+    errorHandler.assertNoError();
+  }
+
+  @Test(timeout = 1000)
+  public void testMultipleTasks() throws Exception {
+    final TimeoutScheduler scheduler = TimeoutScheduler.newInstance(1);
+    final TimeDuration grace = TimeDuration.valueOf(100, 
TimeUnit.MILLISECONDS);
+    scheduler.setGracePeriod(grace);
+    Assert.assertFalse(scheduler.hasScheduler());
+
+    final ErrorHandler errorHandler = new ErrorHandler();
+
+    final AtomicBoolean[] fired = new AtomicBoolean[3];
+    for(int i = 0; i < fired.length; i++) {
+      final AtomicBoolean f = fired[i] = new AtomicBoolean(false);
+      scheduler.onTimeout(TimeDuration.valueOf(100*i + 50, 
TimeUnit.MILLISECONDS), () -> {
+        Assert.assertFalse(f.get());
+        f.set(true);
+      }, errorHandler);
+      Assert.assertTrue(scheduler.hasScheduler());
+    }
+
+    Thread.sleep(100);
+    Assert.assertTrue(fired[0].get());
+    Assert.assertFalse(fired[1].get());
+    Assert.assertFalse(fired[2].get());
+    Assert.assertTrue(scheduler.hasScheduler());
+
+    Thread.sleep(100);
+    Assert.assertTrue(fired[0].get());
+    Assert.assertTrue(fired[1].get());
+    Assert.assertFalse(fired[2].get());
+    Assert.assertTrue(scheduler.hasScheduler());
+
+    Thread.sleep(100);
+    Assert.assertTrue(fired[0].get());
+    Assert.assertTrue(fired[1].get());
+    Assert.assertTrue(fired[2].get());
+    Assert.assertTrue(scheduler.hasScheduler());
+
+    Thread.sleep(100);
+    Assert.assertTrue(fired[0].get());
+    Assert.assertTrue(fired[1].get());
+    Assert.assertTrue(fired[2].get());
+    Assert.assertFalse(scheduler.hasScheduler());
+
+    errorHandler.assertNoError();
+  }
+
+  @Test(timeout = 1000)
+  public void testExtendingGracePeriod() throws Exception {
+    final TimeoutScheduler scheduler = TimeoutScheduler.newInstance(1);
+    final TimeDuration grace = TimeDuration.valueOf(100, 
TimeUnit.MILLISECONDS);
+    scheduler.setGracePeriod(grace);
+    Assert.assertFalse(scheduler.hasScheduler());
+
+    final ErrorHandler errorHandler = new ErrorHandler();
+
+    {
+      final AtomicBoolean fired = new AtomicBoolean(false);
+      scheduler.onTimeout(TimeDuration.valueOf(150, TimeUnit.MILLISECONDS), () 
-> {
+        Assert.assertFalse(fired.get());
+        fired.set(true);
+      }, errorHandler);
+      Assert.assertTrue(scheduler.hasScheduler());
+
+      Thread.sleep(100);
+      Assert.assertFalse(fired.get());
+      Assert.assertTrue(scheduler.hasScheduler());
+
+      Thread.sleep(100);
+      Assert.assertTrue(fired.get());
+      Assert.assertTrue(scheduler.hasScheduler());
+    }
+
+    {
+      // submit another task during grace period
+      final AtomicBoolean fired2 = new AtomicBoolean(false);
+      scheduler.onTimeout(TimeDuration.valueOf(150, TimeUnit.MILLISECONDS), () 
-> {
+        Assert.assertFalse(fired2.get());
+        fired2.set(true);
+      }, errorHandler);
+
+      Thread.sleep(100);
+      Assert.assertFalse(fired2.get());
+      Assert.assertTrue(scheduler.hasScheduler());
+
+      Thread.sleep(100);
+      Assert.assertTrue(fired2.get());
+      Assert.assertTrue(scheduler.hasScheduler());
+
+      Thread.sleep(100);
+      Assert.assertTrue(fired2.get());
+      Assert.assertFalse(scheduler.hasScheduler());
+    }
+
+    errorHandler.assertNoError();
+  }
+
+  @Test(timeout = 1000)
+  public void testRestartingScheduler() throws Exception {
+    final TimeoutScheduler scheduler = TimeoutScheduler.newInstance(1);
+    final TimeDuration grace = TimeDuration.valueOf(100, 
TimeUnit.MILLISECONDS);
+    scheduler.setGracePeriod(grace);
+    Assert.assertFalse(scheduler.hasScheduler());
+
+    final ErrorHandler errorHandler = new ErrorHandler();
+
+    for(int i = 0; i < 2; i++) {
+      final AtomicBoolean fired = new AtomicBoolean(false);
+      scheduler.onTimeout(TimeDuration.valueOf(150, TimeUnit.MILLISECONDS), () 
-> {
+        Assert.assertFalse(fired.get());
+        fired.set(true);
+      }, errorHandler);
+      Assert.assertTrue(scheduler.hasScheduler());
+
+      Thread.sleep(100);
+      Assert.assertFalse(fired.get());
+      Assert.assertTrue(scheduler.hasScheduler());
+
+      Thread.sleep(100);
+      Assert.assertTrue(fired.get());
+      Assert.assertTrue(scheduler.hasScheduler());
+
+      Thread.sleep(100);
+      Assert.assertTrue(fired.get());
+      Assert.assertFalse(scheduler.hasScheduler());
+    }
+
+    errorHandler.assertNoError();
+  }
+}

http://git-wip-us.apache.org/repos/asf/incubator-ratis/blob/5c37675f/ratis-test/src/test/java/org/apache/ratis/util/TestTraditionalBinaryPrefix.java
----------------------------------------------------------------------
diff --git 
a/ratis-test/src/test/java/org/apache/ratis/util/TestTraditionalBinaryPrefix.java
 
b/ratis-test/src/test/java/org/apache/ratis/util/TestTraditionalBinaryPrefix.java
new file mode 100644
index 0000000..26a62da
--- /dev/null
+++ 
b/ratis-test/src/test/java/org/apache/ratis/util/TestTraditionalBinaryPrefix.java
@@ -0,0 +1,145 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.ratis.util;
+
+import org.junit.Test;
+
+import static org.apache.ratis.util.TraditionalBinaryPrefix.long2String;
+import static org.apache.ratis.util.TraditionalBinaryPrefix.string2long;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+public class TestTraditionalBinaryPrefix {
+  @Test(timeout = 1000)
+  public void testTraditionalBinaryPrefix() throws Exception {
+    //test string2long(..)
+    String[] symbol = {"k", "m", "g", "t", "p", "e"};
+    long m = 1024;
+    for(String s : symbol) {
+      assertEquals(0, string2long(0 + s));
+      assertEquals(m, string2long(1 + s));
+      m *= 1024;
+    }
+
+    assertEquals(0L, string2long("0"));
+    assertEquals(1024L, string2long("1k"));
+    assertEquals(-1024L, string2long("-1k"));
+    assertEquals(1259520L, string2long("1230K"));
+    assertEquals(-1259520L, string2long("-1230K"));
+    assertEquals(104857600L, string2long("100m"));
+    assertEquals(-104857600L, string2long("-100M"));
+    assertEquals(956703965184L, string2long("891g"));
+    assertEquals(-956703965184L, string2long("-891G"));
+    assertEquals(501377302265856L, string2long("456t"));
+    assertEquals(-501377302265856L, string2long("-456T"));
+    assertEquals(11258999068426240L, string2long("10p"));
+    assertEquals(-11258999068426240L, string2long("-10P"));
+    assertEquals(1152921504606846976L, string2long("1e"));
+    assertEquals(-1152921504606846976L, string2long("-1E"));
+
+    String tooLargeNumStr = "10e";
+    try {
+      string2long(tooLargeNumStr);
+      fail("Test passed for a number " + tooLargeNumStr + " too large");
+    } catch (IllegalArgumentException e) {
+      assertEquals(tooLargeNumStr + " does not fit in a Long", e.getMessage());
+    }
+
+    String tooSmallNumStr = "-10e";
+    try {
+      string2long(tooSmallNumStr);
+      fail("Test passed for a number " + tooSmallNumStr + " too small");
+    } catch (IllegalArgumentException e) {
+      assertEquals(tooSmallNumStr + " does not fit in a Long", e.getMessage());
+    }
+
+    String invalidFormatNumStr = "10kb";
+    char invalidPrefix = 'b';
+    try {
+      string2long(invalidFormatNumStr);
+      fail("Test passed for a number " + invalidFormatNumStr
+          + " has invalid format");
+    } catch (IllegalArgumentException e) {
+      assertEquals("Invalid size prefix '" + invalidPrefix + "' in '"
+              + invalidFormatNumStr
+              + "'. Allowed prefixes are k, m, g, t, p, e (case insensitive)",
+          e.getMessage());
+    }
+
+    //test long2string(..)
+    assertEquals("0", long2String(0, null, 2));
+    for(int decimalPlace = 0; decimalPlace < 2; decimalPlace++) {
+      for(int n = 1; n < TraditionalBinaryPrefix.KILO.getValue(); n++) {
+        assertEquals(n + "", long2String(n, null, decimalPlace));
+        assertEquals(-n + "", long2String(-n, null, decimalPlace));
+      }
+      assertEquals("1 K", long2String(1L << 10, null, decimalPlace));
+      assertEquals("-1 K", long2String(-1L << 10, null, decimalPlace));
+    }
+
+    assertEquals("8.00 E", long2String(Long.MAX_VALUE, null, 2));
+    assertEquals("8.00 E", long2String(Long.MAX_VALUE - 1, null, 2));
+    assertEquals("-8 E", long2String(Long.MIN_VALUE, null, 2));
+    assertEquals("-8.00 E", long2String(Long.MIN_VALUE + 1, null, 2));
+
+    final String[] zeros = {" ", ".0 ", ".00 "};
+    for(int decimalPlace = 0; decimalPlace < zeros.length; decimalPlace++) {
+      final String trailingZeros = zeros[decimalPlace];
+
+      for(int e = 11; e < Long.SIZE - 1; e++) {
+        final TraditionalBinaryPrefix p
+            = TraditionalBinaryPrefix.values()[e/10 - 1];
+
+        { // n = 2^e
+          final long n = 1L << e;
+          final String expected = (n/p.getValue()) + " " + p.getSymbol();
+          assertEquals("n=" + n, expected, long2String(n, null, 2));
+        }
+
+        { // n = 2^e + 1
+          final long n = (1L << e) + 1;
+          final String expected = (n/p.getValue()) + trailingZeros + 
p.getSymbol();
+          assertEquals("n=" + n, expected, long2String(n, null, decimalPlace));
+        }
+
+        { // n = 2^e - 1
+          final long n = (1L << e) - 1;
+          final String expected = ((n+1)/p.getValue()) + trailingZeros + 
p.getSymbol();
+          assertEquals("n=" + n, expected, long2String(n, null, decimalPlace));
+        }
+      }
+    }
+
+    assertEquals("1.50 K", long2String(3L << 9, null, 2));
+    assertEquals("1.5 K", long2String(3L << 9, null, 1));
+    assertEquals("1.50 M", long2String(3L << 19, null, 2));
+    assertEquals("2 M", long2String(3L << 19, null, 0));
+    assertEquals("3 G", long2String(3L << 30, null, 2));
+
+    assertEquals("0 B", byteDescription(0));
+    assertEquals("-100 B", byteDescription(-100));
+    assertEquals("1 KB", byteDescription(1024));
+    assertEquals("1.50 KB", byteDescription(3L << 9));
+    assertEquals("1.50 MB", byteDescription(3L << 19));
+    assertEquals("3 GB", byteDescription(3L << 30));
+  }
+
+  private static String byteDescription(long len) {
+    return long2String(len, "B", 2);
+  }
+}

http://git-wip-us.apache.org/repos/asf/incubator-ratis/blob/5c37675f/ratis-test/src/test/resources/log4j.properties
----------------------------------------------------------------------
diff --git a/ratis-test/src/test/resources/log4j.properties 
b/ratis-test/src/test/resources/log4j.properties
new file mode 100644
index 0000000..ced0687
--- /dev/null
+++ b/ratis-test/src/test/resources/log4j.properties
@@ -0,0 +1,18 @@
+#   Licensed under the Apache License, Version 2.0 (the "License");
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+# log4j configuration used during build and unit tests
+
+log4j.rootLogger=info,stdout
+log4j.threshold=ALL
+log4j.appender.stdout=org.apache.log4j.ConsoleAppender
+log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
+log4j.appender.stdout.layout.ConversionPattern=%d{ISO8601} %-5p %c{2} 
(%F:%M(%L)) - %m%n

Reply via email to