This is an automated email from the ASF dual-hosted git repository. burcham pushed a commit to branch support/1.13 in repository https://gitbox.apache.org/repos/asf/geode.git
commit cde469c6b6955a334e6bbf22accfc0735f0c70f4 Author: Kirk Lund <kl...@apache.org> AuthorDate: Wed Sep 30 09:39:30 2020 -0700 GEODE-8540: Create new DistributedBlackboard Rule (#5557) Package up DUnitBlackboard as a JUnit Rule named DistributedBlackboard. (cherry picked from commit 26cb822f2ee467545dd708ecc867cebbd2473c70) --- .../dunit/internal/DUnitBlackboardDUnitTest.java | 75 +++--- .../DistributedBlackboardDistributedTest.java | 297 +++++++++++++++++++++ .../InternalBlackboard.java => Blackboard.java} | 54 ++-- .../apache/geode/test/dunit/DUnitBlackboard.java | 55 ++-- .../test/dunit/internal/InternalBlackboard.java | 33 ++- .../dunit/internal/InternalBlackboardImpl.java | 59 ++-- .../test/dunit/rules/DistributedBlackboard.java | 138 ++++++++++ 7 files changed, 584 insertions(+), 127 deletions(-) diff --git a/geode-dunit/src/distributedTest/java/org/apache/geode/test/dunit/internal/DUnitBlackboardDUnitTest.java b/geode-dunit/src/distributedTest/java/org/apache/geode/test/dunit/internal/DUnitBlackboardDUnitTest.java index ae78247..5e151d7 100755 --- a/geode-dunit/src/distributedTest/java/org/apache/geode/test/dunit/internal/DUnitBlackboardDUnitTest.java +++ b/geode-dunit/src/distributedTest/java/org/apache/geode/test/dunit/internal/DUnitBlackboardDUnitTest.java @@ -14,83 +14,70 @@ */ package org.apache.geode.test.dunit.internal; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; +import static java.util.concurrent.TimeUnit.SECONDS; +import static org.apache.geode.test.dunit.VM.getVM; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.catchThrowable; -import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import org.junit.Test; -import org.apache.geode.test.dunit.Host; import org.apache.geode.test.dunit.VM; - +@SuppressWarnings("serial") public class DUnitBlackboardDUnitTest extends JUnit4DistributedTestCase { + @Test - public void canPassDataBetweenVMs() throws Exception { + public void canPassDataBetweenVMs() { final String MBOX = "myMailbox"; - VM vm0 = Host.getHost(0).getVM(0); - VM vm1 = Host.getHost(0).getVM(1); + VM vm0 = getVM(0); + VM vm1 = getVM(1); vm0.invoke("put data in mailbox", () -> getBlackboard().setMailbox(MBOX, "testing")); - String result = (String) vm1.invoke("get data from mailbox", () -> { - return getBlackboard().getMailbox(MBOX); - }); + String result = vm1.invoke("get data from mailbox", () -> getBlackboard().getMailbox(MBOX)); - assertEquals("testing", result); + assertThat(result).isEqualTo("testing"); } @Test - public void canSignalAnotherVM() throws Exception { + public void canSignalAnotherVM() { final String GATE = "myGate"; - VM vm0 = Host.getHost(0).getVM(0); - VM vm1 = Host.getHost(0).getVM(1); + VM vm0 = getVM(0); + VM vm1 = getVM(1); vm1.invoke("wait on gate not yet signalled", () -> { - assertFalse(getBlackboard().isGateSignaled(GATE)); - try { - getBlackboard().waitForGate(GATE, 1, TimeUnit.SECONDS); - } catch (TimeoutException e) { - // expected - return; - } catch (InterruptedException e) { - fail("unexpected interrupt"); - } - fail("unexpected success"); + assertThat(getBlackboard().isGateSignaled(GATE)).isFalse(); + + Throwable thrown = catchThrowable(() -> { + getBlackboard().waitForGate(GATE, 1, SECONDS); + }); + + assertThat(thrown).isInstanceOf(TimeoutException.class); }); vm0.invoke("signal gate", () -> getBlackboard().signalGate(GATE)); - vm1.invoke("wait on gate not yet signalled", () -> { - try { - getBlackboard().waitForGate(GATE, 1, TimeUnit.SECONDS); - } catch (TimeoutException e) { - fail("unexpected timeout"); - } catch (InterruptedException e) { - fail("unexpected interrupt"); - } - // success expected - }); + vm1.invoke("wait on gate not yet signalled", + () -> getBlackboard().waitForGate(GATE, 1, SECONDS)); } @Test - public void initBlackboardClearsEverything() throws Exception { + public void initBlackboardClearsEverything() { for (int i = 0; i < 100; i++) { getBlackboard().setMailbox("MBOX" + i, "value" + i); - assertEquals("value" + i, getBlackboard().getMailbox("MBOX" + i)); + assertThat((Object) getBlackboard().getMailbox("MBOX" + i)).isEqualTo("value" + i); + getBlackboard().signalGate("GATE" + i); - assertTrue(getBlackboard().isGateSignaled("GATE" + i)); + assertThat(getBlackboard().isGateSignaled("GATE" + i)).isTrue(); } - Host.getHost(0).getVM(1).invoke("clear blackboard", () -> getBlackboard().initBlackboard()); + + getVM(1).invoke("clear blackboard", () -> getBlackboard().initBlackboard()); for (int i = 0; i < 100; i++) { - assertNull(getBlackboard().getMailbox("MBOX" + i)); - assertFalse(getBlackboard().isGateSignaled("GATE" + i)); + assertThat((Object) getBlackboard().getMailbox("MBOX" + i)).isNull(); + assertThat(getBlackboard().isGateSignaled("GATE" + i)).isFalse(); } } } diff --git a/geode-dunit/src/distributedTest/java/org/apache/geode/test/dunit/rules/tests/DistributedBlackboardDistributedTest.java b/geode-dunit/src/distributedTest/java/org/apache/geode/test/dunit/rules/tests/DistributedBlackboardDistributedTest.java new file mode 100644 index 0000000..ea3ed2e --- /dev/null +++ b/geode-dunit/src/distributedTest/java/org/apache/geode/test/dunit/rules/tests/DistributedBlackboardDistributedTest.java @@ -0,0 +1,297 @@ +/* + * 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.geode.test.dunit.rules.tests; + +import static java.util.Arrays.asList; +import static java.util.concurrent.TimeUnit.SECONDS; +import static org.apache.geode.test.dunit.VM.getController; +import static org.apache.geode.test.dunit.VM.getVM; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.catchThrowable; + +import java.io.Serializable; +import java.util.concurrent.TimeoutException; + +import org.junit.Rule; +import org.junit.Test; + +import org.apache.geode.test.dunit.VM; +import org.apache.geode.test.dunit.rules.DistributedBlackboard; +import org.apache.geode.test.dunit.rules.DistributedRule; +import org.apache.geode.test.junit.rules.serializable.SerializableTestName; + +@SuppressWarnings({"serial", "CodeBlock2Expr"}) +public class DistributedBlackboardDistributedTest implements Serializable { + + @Rule + public DistributedRule distributedRule = new DistributedRule(); + @Rule + public DistributedBlackboard blackboard = new DistributedBlackboard(); + @Rule + public SerializableTestName testName = new SerializableTestName(); + + @Test + public void canPassDataBetweenVMs() { + VM vm0 = getVM(0); + VM vm1 = getVM(1); + + vm0.invoke("put data in mailbox", () -> blackboard.setMailbox(mailbox(), value())); + + String result = vm1.invoke("get data from mailbox", () -> blackboard.getMailbox(mailbox())); + + assertThat(result).isEqualTo(value()); + } + + @Test + public void canSignalAnotherVM() { + VM vm0 = getVM(0); + VM vm1 = getVM(1); + + vm1.invoke("wait on gate not yet signalled", () -> { + assertThat(blackboard.isGateSignaled(gate())).isFalse(); + + Throwable thrown = catchThrowable(() -> { + blackboard.waitForGate(gate(), 1, SECONDS); + }); + + assertThat(thrown).isInstanceOf(TimeoutException.class); + }); + + vm0.invoke("signal gate", () -> blackboard.signalGate(gate())); + + vm1.invoke("wait on gate not yet signalled", () -> blackboard.waitForGate(gate(), 1, SECONDS)); + } + + @Test + public void initBlackboardClearsEverything() { + for (int i = 0; i < 100; i++) { + blackboard.setMailbox(mailbox(i), value(i)); + assertThat((Object) blackboard.getMailbox(mailbox(i))).isEqualTo(value(i)); + + blackboard.signalGate(gate(i)); + assertThat(blackboard.isGateSignaled(gate(i))).isTrue(); + } + + getVM(1).invoke("clear blackboard", () -> blackboard.initBlackboard()); + + for (int i = 0; i < 100; i++) { + assertThat((Object) blackboard.getMailbox(mailbox(i))).isNull(); + assertThat(blackboard.isGateSignaled(gate(i))).isFalse(); + } + } + + @Test + public void getMailbox_returnsValueFromSameVM() { + getVM(0).invoke(() -> blackboard.setMailbox(mailbox(), value())); + + getVM(0).invoke(() -> { + assertThat((Object) blackboard.getMailbox(mailbox())).isEqualTo(value()); + }); + } + + @Test + public void getMailbox_returnsValueFromOtherVM() { + getVM(0).invoke(() -> blackboard.setMailbox(mailbox(), value())); + + getVM(1).invoke(() -> { + assertThat((Object) blackboard.getMailbox(mailbox())).isEqualTo(value()); + }); + } + + @Test + public void setMailbox_overwrites_valueFromSameVM() { + getVM(0).invoke(() -> blackboard.setMailbox(mailbox(), value(1))); + getVM(0).invoke(() -> blackboard.setMailbox(mailbox(), value(2))); + + getVM(0).invoke(() -> { + assertThat((Object) blackboard.getMailbox(mailbox())).isEqualTo(value(2)); + }); + } + + @Test + public void setMailbox_overwrites_valueFromOtherVM() { + getVM(0).invoke(() -> blackboard.setMailbox(mailbox(), value(1))); + getVM(1).invoke(() -> blackboard.setMailbox(mailbox(), value(2))); + + getVM(2).invoke(() -> { + assertThat((Object) blackboard.getMailbox(mailbox())).isEqualTo(value(2)); + }); + } + + @Test + public void getMailbox_returnsValueFromSameVM_afterBouncingVM() { + getVM(0).invoke(() -> blackboard.setMailbox(mailbox(), value())); + + getVM(0).bounceForcibly(); + + getVM(0).invoke(() -> { + assertThat((Object) blackboard.getMailbox(mailbox())).isEqualTo(value()); + }); + } + + @Test + public void getMailbox_returnsValueFromOtherVM_afterBouncingVM() { + getVM(0).invoke(() -> blackboard.setMailbox(mailbox(), value())); + + getVM(0).bounceForcibly(); + + getVM(1).invoke(() -> { + assertThat((Object) blackboard.getMailbox(mailbox())).isEqualTo(value()); + }); + } + + @Test + public void setMailbox_overwrites_valueFromSameVM_afterBouncingVM() { + getVM(0).invoke(() -> blackboard.setMailbox(mailbox(), value(1))); + getVM(0).invoke(() -> blackboard.setMailbox(mailbox(), value(2))); + + getVM(0).bounceForcibly(); + + getVM(0).invoke(() -> { + assertThat((Object) blackboard.getMailbox(mailbox(1))).isEqualTo(value(2)); + }); + } + + @Test + public void setMailbox_overwrites_valueFromOtherVM_afterBouncingFirstVM() { + getVM(0).invoke(() -> blackboard.setMailbox(mailbox(), value(1))); + getVM(1).invoke(() -> blackboard.setMailbox(mailbox(), value(2))); + + getVM(0).bounceForcibly(); + + getVM(2).invoke(() -> { + assertThat((Object) blackboard.getMailbox(mailbox())).isEqualTo(value(2)); + }); + } + + @Test + public void setMailbox_overwrites_valueFromOtherVM_afterBouncingSecondVM() { + getVM(0).invoke(() -> blackboard.setMailbox(mailbox(), value(1))); + getVM(1).invoke(() -> blackboard.setMailbox(mailbox(), value(2))); + + getVM(1).bounceForcibly(); + + getVM(2).invoke(() -> { + assertThat((Object) blackboard.getMailbox(mailbox())).isEqualTo(value(2)); + }); + } + + @Test + public void setMailbox_overwrites_valueFromOtherVM_afterBouncingBothVMs() { + getVM(0).invoke(() -> blackboard.setMailbox(mailbox(), value(1))); + getVM(1).invoke(() -> blackboard.setMailbox(mailbox(), value(2))); + + getVM(0).bounceForcibly(); + getVM(1).bounceForcibly(); + + getVM(2).invoke(() -> { + assertThat((Object) blackboard.getMailbox(mailbox())).isEqualTo(value(2)); + }); + } + + @Test + public void getMailbox_returnsValueFromSameVM_afterBouncingEveryVM() { + getVM(0).invoke(() -> blackboard.setMailbox(mailbox(), value())); + + getVM(0).bounceForcibly(); + getVM(1).bounceForcibly(); + getVM(2).bounceForcibly(); + getVM(3).bounceForcibly(); + + getVM(0).invoke(() -> { + assertThat((Object) blackboard.getMailbox(mailbox())).isEqualTo(value()); + }); + } + + @Test + public void getMailbox_returnsValueFromOtherVM_afterBouncingEveryVM() { + getVM(0).invoke(() -> blackboard.setMailbox(mailbox(), value())); + + getVM(0).bounceForcibly(); + getVM(1).bounceForcibly(); + getVM(2).bounceForcibly(); + getVM(3).bounceForcibly(); + + getVM(1).invoke(() -> { + assertThat((Object) blackboard.getMailbox(mailbox())).isEqualTo(value()); + }); + } + + @Test + public void getMailbox_returnsValueFromControllerVM_afterBouncingEveryVM() { + blackboard.setMailbox(mailbox(), value()); + + getVM(0).bounceForcibly(); + getVM(1).bounceForcibly(); + getVM(2).bounceForcibly(); + getVM(3).bounceForcibly(); + + getVM(3).invoke(() -> { + assertThat((Object) blackboard.getMailbox(mailbox())).isEqualTo(value()); + }); + } + + @Test + public void getMailbox_returnsValueInControllerVM_afterBouncingEveryVM() { + blackboard.setMailbox(mailbox(), value()); + + getVM(0).bounceForcibly(); + getVM(1).bounceForcibly(); + getVM(2).bounceForcibly(); + getVM(3).bounceForcibly(); + + assertThat((Object) blackboard.getMailbox(mailbox())).isEqualTo(value()); + } + + @Test + public void getMailbox_returnsValueInEveryVM() { + blackboard.setMailbox(mailbox(), value()); + + assertThat((Object) blackboard.getMailbox(mailbox())).isEqualTo(value()); + for (VM vm : asList(getController(), getVM(0), getVM(1), getVM(2), getVM(3))) { + vm.invoke(() -> { + assertThat((Object) blackboard.getMailbox(mailbox())).isEqualTo(value()); + }); + } + } + + private String mailbox() { + return value("mailbox", 1); + } + + private String value() { + return value("value", 1); + } + + private String gate() { + return value("gate", 1); + } + + private String mailbox(int count) { + return value("mailbox", count); + } + + private String value(int count) { + return value("value", count); + } + + private String gate(int count) { + return value("gate", count); + } + + private String value(String prefix, int count) { + return prefix + "-" + testName.getMethodName() + "-" + count; + } +} diff --git a/geode-dunit/src/main/java/org/apache/geode/test/dunit/internal/InternalBlackboard.java b/geode-dunit/src/main/java/org/apache/geode/test/dunit/Blackboard.java old mode 100755 new mode 100644 similarity index 51% copy from geode-dunit/src/main/java/org/apache/geode/test/dunit/internal/InternalBlackboard.java copy to geode-dunit/src/main/java/org/apache/geode/test/dunit/Blackboard.java index 24abf4f..2b15ebd --- a/geode-dunit/src/main/java/org/apache/geode/test/dunit/internal/InternalBlackboard.java +++ b/geode-dunit/src/main/java/org/apache/geode/test/dunit/Blackboard.java @@ -12,66 +12,68 @@ * or implied. See the License for the specific language governing permissions and limitations under * the License. */ -package org.apache.geode.test.dunit.internal; +package org.apache.geode.test.dunit; -import java.io.Serializable; -import java.rmi.Remote; -import java.rmi.RemoteException; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; +import org.apache.geode.test.awaitility.GeodeAwaitility; + /** - * DUnitBlackboard provides mailboxes and synchronization gateways for distributed unit tests. + * Blackboard provides mailboxes and synchronization gateways for distributed tests. + * * <p> * Tests may use the blackboard to pass objects and status between JVMs with mailboxes instead of * using static variables in classes. The caveat being that the objects will be serialized using * Java serialization. + * * <p> - * Gates may be used to synchronize operations between unit test JVMs. Combined with Awaitility - * these can be used to test for conditions being met, actions having happened, etc. + * Gates may be used to synchronize operations between distributed test JVMs. Combined with + * Awaitility these can be used to test for conditions being met, actions having happened, etc. + * * <p> * Look for references to the given methods in your IDE for examples. */ -public interface InternalBlackboard extends Remote, Serializable { +public interface Blackboard { + /** - * resets the blackboard + * Resets the blackboard. */ - void initBlackboard() throws RemoteException; + void initBlackboard(); /** - * signals a boolean gate + * Signals a boolean gate. */ - void signalGate(String gateName) throws RemoteException; + void signalGate(String gateName); /** - * wait for a gate to be signaled + * Waits at most {@link GeodeAwaitility#getTimeout()} for a gate to be signaled. */ - void waitForGate(String gateName, long timeout, TimeUnit units) - throws RemoteException, TimeoutException, InterruptedException; + void waitForGate(String gateName) throws TimeoutException, InterruptedException; /** - * clears a gate + * Waits at most the specified timeout for a gate to be signaled. */ - void clearGate(String gateName) throws RemoteException; + void waitForGate(String gateName, long timeout, TimeUnit units) + throws TimeoutException, InterruptedException; /** - * test to see if a gate has been signeled + * Clears a gate. */ - boolean isGateSignaled(String gateName) throws RemoteException; + void clearGate(String gateName); /** - * put an object into a mailbox slot. The object must be java-serializable + * Checks to see if a gate has been signaled. */ - void setMailbox(String boxName, Object value) throws RemoteException; + boolean isGateSignaled(String gateName); /** - * retrieve an object from a mailbox slot + * Puts an object into a mailbox slot. The object must be java-serializable. */ - <T> T getMailbox(String boxName) throws RemoteException; + <T> void setMailbox(String boxName, T value); /** - * ping the blackboard to make sure it's there + * Retrieves an object from a mailbox slot. */ - void ping() throws RemoteException; - + <T> T getMailbox(String boxName); } diff --git a/geode-dunit/src/main/java/org/apache/geode/test/dunit/DUnitBlackboard.java b/geode-dunit/src/main/java/org/apache/geode/test/dunit/DUnitBlackboard.java index d87b99d..1ff5a0a 100755 --- a/geode-dunit/src/main/java/org/apache/geode/test/dunit/DUnitBlackboard.java +++ b/geode-dunit/src/main/java/org/apache/geode/test/dunit/DUnitBlackboard.java @@ -14,6 +14,9 @@ */ package org.apache.geode.test.dunit; +import static java.util.concurrent.TimeUnit.MINUTES; +import static org.apache.geode.test.awaitility.GeodeAwaitility.getTimeout; + import java.rmi.RemoteException; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; @@ -22,7 +25,7 @@ import org.apache.geode.test.dunit.internal.InternalBlackboard; import org.apache.geode.test.dunit.internal.InternalBlackboardImpl; /** - * DUnitBlackboard provides mailboxes and synchronization gateways for distributed unit tests. + * DUnitBlackboard provides mailboxes and synchronization gateways for distributed tests. * * <p> * Tests may use the blackboard to pass objects and status between JVMs with mailboxes instead of @@ -36,17 +39,19 @@ import org.apache.geode.test.dunit.internal.InternalBlackboardImpl; * <p> * Look for references to the given methods in your IDE for examples. */ -public class DUnitBlackboard { +public class DUnitBlackboard implements Blackboard { - private InternalBlackboard blackboard; + private final InternalBlackboard blackboard; public DUnitBlackboard() { - blackboard = InternalBlackboardImpl.getInstance(); + this(InternalBlackboardImpl.getInstance()); + } + + public DUnitBlackboard(InternalBlackboard blackboard) { + this.blackboard = blackboard; } - /** - * resets the blackboard - */ + @Override public void initBlackboard() { try { blackboard.initBlackboard(); @@ -55,11 +60,8 @@ public class DUnitBlackboard { } } - /** - * signals a boolean gate - */ + @Override public void signalGate(String gateName) { - // System.out.println(Thread.currentThread().getName()+": signaling gate " + gateName); try { blackboard.signalGate(gateName); } catch (RemoteException e) { @@ -67,12 +69,15 @@ public class DUnitBlackboard { } } - /** - * wait for a gate to be signaled - */ + @Override + public void waitForGate(String gateName) + throws TimeoutException, InterruptedException { + waitForGate(gateName, getTimeout().toMinutes(), MINUTES); + } + + @Override public void waitForGate(String gateName, long timeout, TimeUnit units) throws TimeoutException, InterruptedException { - // System.out.println(Thread.currentThread().getName()+": waiting for gate " + gateName); try { blackboard.waitForGate(gateName, timeout, units); } catch (RemoteException e) { @@ -80,9 +85,7 @@ public class DUnitBlackboard { } } - /** - * clear a gate - */ + @Override public void clearGate(String gateName) { try { blackboard.clearGate(gateName); @@ -91,9 +94,7 @@ public class DUnitBlackboard { } } - /** - * test to see if a gate has been signeled - */ + @Override public boolean isGateSignaled(String gateName) { try { return blackboard.isGateSignaled(gateName); @@ -102,9 +103,7 @@ public class DUnitBlackboard { } } - /** - * put an object into a mailbox slot. The object must be java-serializable - */ + @Override public void setMailbox(String boxName, Object value) { try { blackboard.setMailbox(boxName, value); @@ -113,9 +112,7 @@ public class DUnitBlackboard { } } - /** - * retrieve an object from a mailbox slot - */ + @Override public <T> T getMailbox(String boxName) { try { return blackboard.getMailbox(boxName); @@ -123,4 +120,8 @@ public class DUnitBlackboard { throw new RuntimeException("remote call failed", e); } } + + public InternalBlackboard internal() { + return blackboard; + } } diff --git a/geode-dunit/src/main/java/org/apache/geode/test/dunit/internal/InternalBlackboard.java b/geode-dunit/src/main/java/org/apache/geode/test/dunit/internal/InternalBlackboard.java index 24abf4f..222a301 100755 --- a/geode-dunit/src/main/java/org/apache/geode/test/dunit/internal/InternalBlackboard.java +++ b/geode-dunit/src/main/java/org/apache/geode/test/dunit/internal/InternalBlackboard.java @@ -17,61 +17,70 @@ package org.apache.geode.test.dunit.internal; import java.io.Serializable; import java.rmi.Remote; import java.rmi.RemoteException; +import java.util.Map; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; /** - * DUnitBlackboard provides mailboxes and synchronization gateways for distributed unit tests. + * InternalBlackboard provides mailboxes and synchronization gateways for distributed tests. + * * <p> * Tests may use the blackboard to pass objects and status between JVMs with mailboxes instead of * using static variables in classes. The caveat being that the objects will be serialized using * Java serialization. + * * <p> * Gates may be used to synchronize operations between unit test JVMs. Combined with Awaitility * these can be used to test for conditions being met, actions having happened, etc. - * <p> - * Look for references to the given methods in your IDE for examples. */ public interface InternalBlackboard extends Remote, Serializable { + /** - * resets the blackboard + * Resets the blackboard. */ void initBlackboard() throws RemoteException; /** - * signals a boolean gate + * Signals a boolean gate. */ void signalGate(String gateName) throws RemoteException; /** - * wait for a gate to be signaled + * Waits for a gate to be signaled. */ void waitForGate(String gateName, long timeout, TimeUnit units) throws RemoteException, TimeoutException, InterruptedException; /** - * clears a gate + * Clears a gate. */ void clearGate(String gateName) throws RemoteException; /** - * test to see if a gate has been signeled + * Checks to see if a gate has been signaled. */ boolean isGateSignaled(String gateName) throws RemoteException; /** - * put an object into a mailbox slot. The object must be java-serializable + * Puts an object into a mailbox slot. The object must be java-serializable. */ - void setMailbox(String boxName, Object value) throws RemoteException; + <T> void setMailbox(String boxName, T value) throws RemoteException; /** - * retrieve an object from a mailbox slot + * Retrieves an object from a mailbox slot. */ <T> T getMailbox(String boxName) throws RemoteException; /** - * ping the blackboard to make sure it's there + * Pings the blackboard to make sure it's there. */ void ping() throws RemoteException; + Map<String, Boolean> gates() throws RemoteException; + + Map<String, Serializable> mailboxes() throws RemoteException; + + void putGates(Map<String, Boolean> gates) throws RemoteException; + + void putMailboxes(Map<String, Serializable> mailboxes) throws RemoteException; } diff --git a/geode-dunit/src/main/java/org/apache/geode/test/dunit/internal/InternalBlackboardImpl.java b/geode-dunit/src/main/java/org/apache/geode/test/dunit/internal/InternalBlackboardImpl.java index bbed22e..e24a5a0 100755 --- a/geode-dunit/src/main/java/org/apache/geode/test/dunit/internal/InternalBlackboardImpl.java +++ b/geode-dunit/src/main/java/org/apache/geode/test/dunit/internal/InternalBlackboardImpl.java @@ -14,6 +14,12 @@ */ package org.apache.geode.test.dunit.internal; +import static java.util.Collections.unmodifiableMap; +import static java.util.concurrent.TimeUnit.MILLISECONDS; +import static org.apache.geode.util.internal.UncheckedUtils.uncheckedCast; + +import java.io.Serializable; +import java.net.MalformedURLException; import java.rmi.AlreadyBoundException; import java.rmi.Naming; import java.rmi.NotBoundException; @@ -24,26 +30,24 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; - public class InternalBlackboardImpl extends UnicastRemoteObject implements InternalBlackboard { - public static InternalBlackboard blackboard; - private Map<String, Boolean> gates = new ConcurrentHashMap<>(); - - private Map<String, Object> mailboxes = new ConcurrentHashMap(); + private static InternalBlackboard blackboard; + private final Map<String, Boolean> gates = new ConcurrentHashMap<>(); + private final Map<String, Serializable> mailboxes = new ConcurrentHashMap<>(); /** * Zero-arg constructor for remote method invocations. */ public InternalBlackboardImpl() throws RemoteException { - super(); + // nothing } /** * Creates a singleton event listeners blackboard. */ - public static InternalBlackboard getInstance() { + public static synchronized InternalBlackboard getInstance() { if (blackboard == null) { try { initialize(); @@ -56,11 +60,12 @@ public class InternalBlackboardImpl extends UnicastRemoteObject implements Inter return blackboard; } - private static synchronized void initialize() throws Exception { + private static synchronized void initialize() + throws AlreadyBoundException, MalformedURLException, RemoteException { if (blackboard == null) { System.out.println( DUnitLauncher.RMI_PORT_PARAM + "=" + System.getProperty(DUnitLauncher.RMI_PORT_PARAM)); - int namingPort = Integer.getInteger(DUnitLauncher.RMI_PORT_PARAM).intValue(); + int namingPort = Integer.getInteger(DUnitLauncher.RMI_PORT_PARAM); String name = "//localhost:" + namingPort + "/" + "InternalBlackboard"; try { blackboard = (InternalBlackboard) Naming.lookup(name); @@ -74,8 +79,8 @@ public class InternalBlackboardImpl extends UnicastRemoteObject implements Inter @Override public void initBlackboard() throws RemoteException { - this.gates.clear(); - this.mailboxes.clear(); + gates.clear(); + mailboxes.clear(); } @Override @@ -90,8 +95,8 @@ public class InternalBlackboardImpl extends UnicastRemoteObject implements Inter @Override public void waitForGate(final String gateName, final long timeout, final TimeUnit units) - throws RemoteException, TimeoutException, InterruptedException { - long giveupTime = System.currentTimeMillis() + TimeUnit.MILLISECONDS.convert(timeout, units); + throws InterruptedException, RemoteException, TimeoutException { + long giveupTime = System.currentTimeMillis() + MILLISECONDS.convert(timeout, units); while (System.currentTimeMillis() < giveupTime) { Boolean gate = gates.get(gateName); if (gate != null && gate) { @@ -105,17 +110,17 @@ public class InternalBlackboardImpl extends UnicastRemoteObject implements Inter @Override public boolean isGateSignaled(final String gateName) { Boolean gate = gates.get(gateName); - return (gate != null && gate); + return gate != null && gate; } @Override - public void setMailbox(String boxName, Object value) { - mailboxes.put(boxName, value); + public <T> void setMailbox(String boxName, T value) { + mailboxes.put(boxName, (Serializable) value); } @Override - public Object getMailbox(String boxName) { - return mailboxes.get(boxName); + public <T> T getMailbox(String boxName) { + return uncheckedCast(mailboxes.get(boxName)); } @Override @@ -123,5 +128,23 @@ public class InternalBlackboardImpl extends UnicastRemoteObject implements Inter // no-op } + @Override + public Map<String, Boolean> gates() { + return unmodifiableMap(gates); + } + @Override + public Map<String, Serializable> mailboxes() { + return unmodifiableMap(mailboxes); + } + + @Override + public void putGates(Map<String, Boolean> gates) { + this.gates.putAll(gates); + } + + @Override + public void putMailboxes(Map<String, Serializable> mailboxes) { + this.mailboxes.putAll(mailboxes); + } } diff --git a/geode-dunit/src/main/java/org/apache/geode/test/dunit/rules/DistributedBlackboard.java b/geode-dunit/src/main/java/org/apache/geode/test/dunit/rules/DistributedBlackboard.java new file mode 100644 index 0000000..8161b6d --- /dev/null +++ b/geode-dunit/src/main/java/org/apache/geode/test/dunit/rules/DistributedBlackboard.java @@ -0,0 +1,138 @@ +/* + * 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.geode.test.dunit.rules; + +import java.io.Serializable; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicReference; + +import org.apache.geode.test.dunit.Blackboard; +import org.apache.geode.test.dunit.DUnitBlackboard; +import org.apache.geode.test.dunit.VM; +import org.apache.geode.test.dunit.internal.InternalBlackboard; +import org.apache.geode.test.dunit.internal.InternalBlackboardImpl; + +/** + * DistributedBlackboard provides mailboxes and synchronization gateways for distributed tests. + * + * <p> + * Tests may use the blackboard to pass objects and status between JVMs with mailboxes instead of + * using static variables in classes. The caveat being that the objects will be serialized using + * Java serialization. + * + * <p> + * Gates may be used to synchronize operations between distributed test JVMs. Combined with + * Awaitility these can be used to test for conditions being met, actions having happened, etc. + * + * <p> + * Look for references to the given methods in your IDE for examples. + */ +@SuppressWarnings({"serial", "unused"}) +public class DistributedBlackboard extends AbstractDistributedRule implements Blackboard { + + private static final AtomicReference<DUnitBlackboard> BLACKBOARD = new AtomicReference<>(); + private static final AtomicReference<InternalBlackboard> INTERNAL = new AtomicReference<>(); + + private final Map<Integer, Map<String, Boolean>> keepGates = new ConcurrentHashMap<>(); + private final Map<Integer, Map<String, Serializable>> keepMailboxes = new ConcurrentHashMap<>(); + + @Override + protected void before() { + invoker().invokeInEveryVMAndController(() -> invokeBefore()); + } + + @Override + protected void after() throws Throwable { + invoker().invokeInEveryVMAndController(() -> invokeAfter()); + } + + @Override + protected void afterCreateVM(VM vm) { + vm.invoke(() -> invokeBefore()); + } + + @Override + protected void beforeBounceVM(VM vm) { + keepGates.put(vm.getId(), vm.invoke(() -> INTERNAL.get().gates())); + keepMailboxes.put(vm.getId(), vm.invoke(() -> INTERNAL.get().mailboxes())); + } + + @Override + protected void afterBounceVM(VM vm) { + Map<String, Boolean> keepGatesForVM = keepGates.remove(vm.getId()); + Map<String, Serializable> keepMailboxesForVM = keepMailboxes.remove(vm.getId()); + + vm.invoke(() -> { + invokeBefore(); + INTERNAL.get().putGates(keepGatesForVM); + INTERNAL.get().putMailboxes(keepMailboxesForVM); + }); + } + + private void invokeBefore() { + InternalBlackboard internalBlackboard = InternalBlackboardImpl.getInstance(); + INTERNAL.set(internalBlackboard); + BLACKBOARD.set(new DUnitBlackboard(internalBlackboard)); + } + + private void invokeAfter() { + BLACKBOARD.set(null); + INTERNAL.set(null); + } + + @Override + public void initBlackboard() { + BLACKBOARD.get().initBlackboard(); + } + + @Override + public void signalGate(String gateName) { + BLACKBOARD.get().signalGate(gateName); + } + + @Override + public void waitForGate(String gateName) throws TimeoutException, InterruptedException { + BLACKBOARD.get().waitForGate(gateName); + } + + @Override + public void waitForGate(String gateName, long timeout, TimeUnit units) + throws TimeoutException, InterruptedException { + BLACKBOARD.get().waitForGate(gateName, timeout, units); + } + + @Override + public void clearGate(String gateName) { + BLACKBOARD.get().clearGate(gateName); + } + + @Override + public boolean isGateSignaled(String gateName) { + return BLACKBOARD.get().isGateSignaled(gateName); + } + + @Override + public <T> void setMailbox(String boxName, T value) { + BLACKBOARD.get().setMailbox(boxName, value); + } + + @Override + public <T> T getMailbox(String boxName) { + return BLACKBOARD.get().getMailbox(boxName); + } +}