From 2959d179a5993c2b6921d6013c1855e3482ec6e1 Mon Sep 17 00:00:00 2001 From: Kirk Lund Date: Wed, 6 Feb 2019 09:33:43 -0800 Subject: [PATCH] GEODE-6033: Add VMEventListener for DUnit Rules (#3161) A DUnit Rule (or even a test) can now register a VMEventListener: VM.getVMEventListenerRegistry().addVMEventListener(vmEventListener); VMEventListeners will receive the following notifications: void afterCreateVM(VM vm); void beforeBounceVM(VM vm); void afterBounceVM(VM vm); DUnit Rules can use these callbacks in order to support dynamic creation or bouncing of VMs. Add junitparams dependency to geode-dunit. --- geode-dunit/build.gradle | 1 + .../tests/VmEventListenerDistributedTest.java | 138 +++++++++++++++ .../org/apache/geode/test/dunit/Host.java | 12 +- .../java/org/apache/geode/test/dunit/VM.java | 26 ++- .../geode/test/dunit/VMEventListener.java | 44 +++++ .../test/dunit/VMEventListenerRegistry.java | 31 ++++ .../geode/test/dunit/internal/DUnitHost.java | 10 +- .../test/dunit/internal/DUnitLauncher.java | 6 +- .../test/dunit/internal/VMEventNotifier.java | 75 ++++++++ .../dunit/rules/AbstractDistributedRule.java | 75 +++++--- .../dunit/internal/VMEventNotifierTest.java | 164 ++++++++++++++++++ .../src/test/resources/expected-pom.xml | 5 + 12 files changed, 554 insertions(+), 33 deletions(-) create mode 100644 geode-dunit/src/distributedTest/java/org/apache/geode/test/dunit/rules/tests/VmEventListenerDistributedTest.java create mode 100644 geode-dunit/src/main/java/org/apache/geode/test/dunit/VMEventListener.java create mode 100644 geode-dunit/src/main/java/org/apache/geode/test/dunit/VMEventListenerRegistry.java create mode 100644 geode-dunit/src/main/java/org/apache/geode/test/dunit/internal/VMEventNotifier.java create mode 100644 geode-dunit/src/test/java/org/apache/geode/test/dunit/internal/VMEventNotifierTest.java diff --git a/geode-dunit/build.gradle b/geode-dunit/build.gradle index 7d29fecbd44a..269fb806ff19 100755 --- a/geode-dunit/build.gradle +++ b/geode-dunit/build.gradle @@ -50,6 +50,7 @@ dependencies { compile('org.assertj:assertj-core') compile('org.mockito:mockito-core') compile('org.awaitility:awaitility') + compile('pl.pragmatists:JUnitParams') compile('junit:junit') { exclude module: 'hamcrest-core' diff --git a/geode-dunit/src/distributedTest/java/org/apache/geode/test/dunit/rules/tests/VmEventListenerDistributedTest.java b/geode-dunit/src/distributedTest/java/org/apache/geode/test/dunit/rules/tests/VmEventListenerDistributedTest.java new file mode 100644 index 000000000000..2eb8e7f18a1c --- /dev/null +++ b/geode-dunit/src/distributedTest/java/org/apache/geode/test/dunit/rules/tests/VmEventListenerDistributedTest.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.tests; + +import static org.apache.geode.test.dunit.VM.getVM; +import static org.apache.geode.test.dunit.VM.getVMCount; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.inOrder; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.InOrder; + +import org.apache.geode.test.dunit.VM; +import org.apache.geode.test.dunit.VMEventListener; +import org.apache.geode.test.dunit.rules.DistributedRule; + +/** + * Distributed tests for {@link VMEventListener} callbacks. + */ +public class VmEventListenerDistributedTest { + + @Rule + public AccessibleDistributedRule distributedRule = spy(new AccessibleDistributedRule()); + + private ArgumentCaptor vmCaptor; + private int beforeVmCount; + private VM vm; + + @Before + public void setUp() { + vmCaptor = ArgumentCaptor.forClass(VM.class); + beforeVmCount = getVMCount(); + vm = getVM(0); + } + + @Test + public void afterCreateVmIsInvokedForNewlyCreatedVm() { + getVM(beforeVmCount); + + verify(distributedRule).afterCreateVM(eq(getVM(beforeVmCount))); + } + + @Test + public void afterCreateVmIsInvokedForMultipleNewlyCreatedVms() { + // getVM implicitly creates intervening VMs between beforeVmCount and beforeVmCount+2 + getVM(beforeVmCount + 2); + + verify(distributedRule, times(3)).afterCreateVM(vmCaptor.capture()); + assertThat(vmCaptor.getAllValues()).containsExactly(getVM(beforeVmCount), + getVM(beforeVmCount + 1), getVM(beforeVmCount + 2)); + } + + @Test + public void beforeBounceVmIsInvokedWhenInvokingBounce() { + vm.bounce(); + + verify(distributedRule).beforeBounceVM(eq(vm)); + } + + @Test + public void afterBounceVmIsInvokedWhenInvokingBounce() { + vm.bounce(); + + verify(distributedRule).afterBounceVM(eq(vm)); + } + + @Test + public void beforeAndAfterBounceVmAreInvokedInOrderWhenInvokingBounce() { + vm.bounce(); + + InOrder inOrder = inOrder(distributedRule); + inOrder.verify(distributedRule).beforeBounceVM(eq(vm)); + inOrder.verify(distributedRule).afterBounceVM(eq(vm)); + } + + @Test + public void beforeBounceVmIsInvokedWhenInvokingBounceForcibly() { + vm.bounceForcibly(); + + verify(distributedRule).beforeBounceVM(eq(vm)); + } + + @Test + public void afterBounceVmIsInvokedWhenInvokingBounceForcibly() { + vm.bounceForcibly(); + + verify(distributedRule).afterBounceVM(eq(vm)); + } + + @Test + public void beforeAndAfterBounceVmAreInvokedInOrderWhenInvokingBounceForcibly() { + vm.bounceForcibly(); + + InOrder inOrder = inOrder(distributedRule); + inOrder.verify(distributedRule).beforeBounceVM(eq(vm)); + inOrder.verify(distributedRule).afterBounceVM(eq(vm)); + } + + /** + * Increase visibility of {@link VMEventListener} callbacks in {@link DistributedRule}. + */ + private static class AccessibleDistributedRule extends DistributedRule { + + @Override + public void afterCreateVM(VM vm) { + // exposed for spy + } + + @Override + public void beforeBounceVM(VM vm) { + // exposed for spy + } + + @Override + public void afterBounceVM(VM vm) { + // exposed for spy + } + } +} diff --git a/geode-dunit/src/main/java/org/apache/geode/test/dunit/Host.java b/geode-dunit/src/main/java/org/apache/geode/test/dunit/Host.java index 9821b19ca869..685d940a66f6 100755 --- a/geode-dunit/src/main/java/org/apache/geode/test/dunit/Host.java +++ b/geode-dunit/src/main/java/org/apache/geode/test/dunit/Host.java @@ -21,6 +21,7 @@ import org.apache.geode.test.dunit.internal.ChildVMLauncher; import org.apache.geode.test.dunit.internal.ProcessHolder; import org.apache.geode.test.dunit.internal.RemoteDUnitVMIF; +import org.apache.geode.test.dunit.internal.VMEventNotifier; import org.apache.geode.test.version.VersionManager; /** @@ -31,7 +32,6 @@ * Additionally, it provides access to the Java RMI registry that runs on the host. By default, an * RMI registry is only started on the host on which Hydra's Master VM runs. RMI registries may be * started on other hosts via additional Hydra configuration. - * */ @SuppressWarnings("serial") public abstract class Host implements Serializable { @@ -47,6 +47,8 @@ public abstract class Host implements Serializable { /** The VMs that run on this host */ private final List vms; + private final transient VMEventNotifier vmEventNotifier; + /** * Returns the number of known hosts */ @@ -101,7 +103,7 @@ public static void setAllVMsToCurrentVersion() { /** * Creates a new {@code Host} with the given name */ - protected Host(String hostName) { + protected Host(String hostName, VMEventNotifier vmEventNotifier) { if (hostName == null) { String message = "Cannot create a Host with a null name"; throw new NullPointerException(message); @@ -109,6 +111,7 @@ protected Host(String hostName) { this.hostName = hostName; vms = new ArrayList<>(); + this.vmEventNotifier = vmEventNotifier; } /** @@ -168,6 +171,7 @@ protected void addVM(int vmid, RemoteDUnitVMIF client, ProcessHolder processHold ChildVMLauncher childVMLauncher) { VM vm = new VM(this, vmid, client, processHolder, childVMLauncher); vms.add(vm); + vmEventNotifier.notifyAfterCreateVM(vm); } public static VM getLocator() { @@ -207,4 +211,8 @@ public boolean equals(Object o) { public int hashCode() { return getHostName().hashCode(); } + + VMEventNotifier getVMEventNotifier() { + return vmEventNotifier; + } } diff --git a/geode-dunit/src/main/java/org/apache/geode/test/dunit/VM.java b/geode-dunit/src/main/java/org/apache/geode/test/dunit/VM.java index ad670d7042e0..6ef2724dd54c 100644 --- a/geode-dunit/src/main/java/org/apache/geode/test/dunit/VM.java +++ b/geode-dunit/src/main/java/org/apache/geode/test/dunit/VM.java @@ -33,6 +33,7 @@ import org.apache.geode.test.dunit.internal.ProcessHolder; import org.apache.geode.test.dunit.internal.RemoteDUnitVMIF; import org.apache.geode.test.dunit.internal.StandAloneDUnitEnv; +import org.apache.geode.test.dunit.internal.VMEventNotifier; import org.apache.geode.test.version.VersionManager; /** @@ -64,7 +65,7 @@ public class VM implements Serializable { private transient volatile ProcessHolder processHolder; - private transient ChildVMLauncher childVMLauncher; + private final transient ChildVMLauncher childVMLauncher; /** * Returns the {@code VM} identity. For {@link StandAloneDUnitEnv} the number returned is a @@ -153,6 +154,24 @@ public static VM[] toArray(VM... vms) { return vms; } + /** + * Registers a {@link VMEventListener}. + */ + public static void addVMEventListener(final VMEventListener listener) { + getVMEventNotifier().addVMEventListener(listener); + } + + /** + * Deregisters a {@link VMEventListener}. + */ + public static void removeVMEventListener(final VMEventListener listener) { + getVMEventNotifier().removeVMEventListener(listener); + } + + private static VMEventNotifier getVMEventNotifier() { + return Host.getHost(0).getVMEventNotifier(); + } + /** * Creates a new {@code VM} that runs on a given host with a given process id. */ @@ -480,6 +499,8 @@ private synchronized void bounce(final String targetVersion, boolean force) { checkAvailability(getClass().getName(), "bounceVM"); logger.info("Bouncing {} old pid is {}", id, getPid()); + getVMEventNotifier().notifyBeforeBounceVM(this); + available = false; try { if (force) { @@ -501,7 +522,10 @@ private synchronized void bounce(final String targetVersion, boolean force) { version = targetVersion; client = childVMLauncher.getStub(id); available = true; + logger.info("Bounced {} new pid is {}", id, getPid()); + getVMEventNotifier().notifyAfterBounceVM(this); + } catch (InterruptedException | IOException | NotBoundException e) { throw new Error("Unable to restart VM " + id, e); } diff --git a/geode-dunit/src/main/java/org/apache/geode/test/dunit/VMEventListener.java b/geode-dunit/src/main/java/org/apache/geode/test/dunit/VMEventListener.java new file mode 100644 index 000000000000..f2f639b9dce2 --- /dev/null +++ b/geode-dunit/src/main/java/org/apache/geode/test/dunit/VMEventListener.java @@ -0,0 +1,44 @@ +/* + * 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; + +/** + * Provides callback notifications for creation of and bouncing of dunit VMs. + */ +public interface VMEventListener { + + /** + * Invoked after creating a new dunit VM. + * + * @see VM#getVM(int) + */ + void afterCreateVM(VM vm); + + /** + * Invoked before bouncing a dunit VM. + * + * @see VM#bounce() + * @see VM#bounceForcibly() + */ + void beforeBounceVM(VM vm); + + /** + * Invoked after bouncing a dunit VM. + * + * @see VM#bounce() + * @see VM#bounceForcibly() + */ + void afterBounceVM(VM vm); +} diff --git a/geode-dunit/src/main/java/org/apache/geode/test/dunit/VMEventListenerRegistry.java b/geode-dunit/src/main/java/org/apache/geode/test/dunit/VMEventListenerRegistry.java new file mode 100644 index 000000000000..87b2d6e8c49e --- /dev/null +++ b/geode-dunit/src/main/java/org/apache/geode/test/dunit/VMEventListenerRegistry.java @@ -0,0 +1,31 @@ +/* + * 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; + +/** + * Manages registration of {@link VMEventListener}s. + */ +public interface VMEventListenerRegistry { + + /** + * Registers a {@code VMEventListener}. + */ + void addVMEventListener(VMEventListener listener); + + /** + * Deregisters a {@code VMEventListener}. + */ + void removeVMEventListener(VMEventListener listener); +} diff --git a/geode-dunit/src/main/java/org/apache/geode/test/dunit/internal/DUnitHost.java b/geode-dunit/src/main/java/org/apache/geode/test/dunit/internal/DUnitHost.java index 651377ef67b5..11e060644e29 100644 --- a/geode-dunit/src/main/java/org/apache/geode/test/dunit/internal/DUnitHost.java +++ b/geode-dunit/src/main/java/org/apache/geode/test/dunit/internal/DUnitHost.java @@ -28,13 +28,15 @@ class DUnitHost extends Host { private static final long serialVersionUID = -8034165624503666383L; private final transient VM debuggingVM; + private final transient ProcessManager processManager; + private final transient VMEventNotifier vmEventNotifier; - private transient ProcessManager processManager; - - public DUnitHost(String hostName, ProcessManager processManager) throws RemoteException { - super(hostName); + DUnitHost(String hostName, ProcessManager processManager, VMEventNotifier vmEventNotifier) + throws RemoteException { + super(hostName, vmEventNotifier); this.debuggingVM = new VM(this, -1, new RemoteDUnitVM(), null, null); this.processManager = processManager; + this.vmEventNotifier = vmEventNotifier; } public void init(Registry registry, int numVMs) diff --git a/geode-dunit/src/main/java/org/apache/geode/test/dunit/internal/DUnitLauncher.java b/geode-dunit/src/main/java/org/apache/geode/test/dunit/internal/DUnitLauncher.java index 3dea30423017..f80431e53804 100644 --- a/geode-dunit/src/main/java/org/apache/geode/test/dunit/internal/DUnitLauncher.java +++ b/geode-dunit/src/main/java/org/apache/geode/test/dunit/internal/DUnitLauncher.java @@ -123,6 +123,8 @@ public class DUnitLauncher { private static final String LAUNCHED_PROPERTY = GEMFIRE_PREFIX + "DUnitLauncher.LAUNCHED"; + private static final VMEventNotifier vmEventNotifier = new VMEventNotifier(); + private static Master master; private DUnitLauncher() {} @@ -228,9 +230,9 @@ private static void launch() throws AlreadyBoundException, IOException, // populate the Host class with our stubs. The tests use this host class DUnitHost host = - new DUnitHost(InetAddress.getLocalHost().getCanonicalHostName(), processManager); + new DUnitHost(InetAddress.getLocalHost().getCanonicalHostName(), processManager, + vmEventNotifier); host.init(registry, NUM_VMS); - } public static Properties getDistributedSystemProperties() { diff --git a/geode-dunit/src/main/java/org/apache/geode/test/dunit/internal/VMEventNotifier.java b/geode-dunit/src/main/java/org/apache/geode/test/dunit/internal/VMEventNotifier.java new file mode 100644 index 000000000000..426b5745eaab --- /dev/null +++ b/geode-dunit/src/main/java/org/apache/geode/test/dunit/internal/VMEventNotifier.java @@ -0,0 +1,75 @@ +/* + * 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.internal; + +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; + +import org.apache.geode.test.dunit.VM; +import org.apache.geode.test.dunit.VMEventListener; +import org.apache.geode.test.dunit.VMEventListenerRegistry; + +/** + * Implements {@code VMEventListenerRegistry} and provides thread-safe notifications to registered + * listeners. + */ +public class VMEventNotifier implements VMEventListenerRegistry { + + private final List listeners; + + VMEventNotifier() { + listeners = new CopyOnWriteArrayList<>(); + } + + @Override + public void addVMEventListener(VMEventListener listener) { + listeners.add(listener); + } + + @Override + public void removeVMEventListener(VMEventListener listener) { + listeners.remove(listener); + } + + /** + * Notifies currently registered listeners of {@link VMEventListener#afterCreateVM(VM)}. + * Concurrent changes to listener registration are ignored while notifying. + */ + public void notifyAfterCreateVM(VM vm) { + for (VMEventListener listener : listeners) { + listener.afterCreateVM(vm); + } + } + + /** + * Notifies currently registered listeners of {@link VMEventListener#beforeBounceVM(VM)}. + * Concurrent changes to listener registration are ignored while notifying. + */ + public void notifyBeforeBounceVM(VM vm) { + for (VMEventListener listener : listeners) { + listener.beforeBounceVM(vm); + } + } + + /** + * Notifies currently registered listeners of {@link VMEventListener#afterBounceVM(VM)}. + * Concurrent changes to listener registration are ignored while notifying. + */ + public void notifyAfterBounceVM(VM vm) { + for (VMEventListener listener : listeners) { + listener.afterBounceVM(vm); + } + } +} diff --git a/geode-dunit/src/main/java/org/apache/geode/test/dunit/rules/AbstractDistributedRule.java b/geode-dunit/src/main/java/org/apache/geode/test/dunit/rules/AbstractDistributedRule.java index 44cb498785e8..dea30dd5d72a 100644 --- a/geode-dunit/src/main/java/org/apache/geode/test/dunit/rules/AbstractDistributedRule.java +++ b/geode-dunit/src/main/java/org/apache/geode/test/dunit/rules/AbstractDistributedRule.java @@ -15,12 +15,12 @@ package org.apache.geode.test.dunit.rules; import static org.apache.geode.test.dunit.VM.DEFAULT_VM_COUNT; -import static org.apache.geode.test.dunit.VM.getVMCount; -import static org.assertj.core.api.Assertions.assertThat; import org.junit.runner.Description; import org.junit.runners.model.Statement; +import org.apache.geode.test.dunit.VM; +import org.apache.geode.test.dunit.VMEventListener; import org.apache.geode.test.dunit.internal.DUnitLauncher; import org.apache.geode.test.dunit.internal.TestHistoryLogger; import org.apache.geode.test.junit.rules.serializable.SerializableStatement; @@ -31,8 +31,6 @@ class AbstractDistributedRule implements SerializableTestRule { private final int vmCount; private final RemoteInvoker invoker; - private volatile int beforeVmCount; - protected AbstractDistributedRule() { this(DEFAULT_VM_COUNT); } @@ -46,50 +44,49 @@ protected AbstractDistributedRule(final int vmCount, final RemoteInvoker invoker this.invoker = invoker; } - @Override - public Statement apply(final Statement base, final Description description) { - return statement(base, description); + public Statement apply(final Statement statement, final Description description) { + return statement(statement, description); } - private Statement statement(final Statement base, Description testDescription) { + private Statement statement(final Statement baseStatement, final Description description) { return new SerializableStatement() { @Override public void evaluate() throws Throwable { - beforeDistributedTest(testDescription); + VMEventListener vmEventListener = new InternalVMEventListener(); + beforeDistributedTest(description); + VM.addVMEventListener(vmEventListener); before(); try { - base.evaluate(); + baseStatement.evaluate(); } finally { after(); - afterDistributedTest(testDescription); + VM.removeVMEventListener(vmEventListener); + afterDistributedTest(description); } } }; } - private void beforeDistributedTest(Description testDescription) throws Throwable { - TestHistoryLogger.logTestHistory(testDescription.getTestClass().getSimpleName(), - testDescription.getMethodName()); + private void beforeDistributedTest(final Description description) throws Throwable { + TestHistoryLogger.logTestHistory(description.getTestClass().getSimpleName(), + description.getMethodName()); DUnitLauncher.launchIfNeeded(vmCount); - beforeVmCount = getVMCount(); - System.out.println("\n\n[setup] START TEST " + testDescription.getClassName() + "." - + testDescription.getMethodName()); + System.out.println("\n\n[setup] START TEST " + description.getClassName() + "." + + description.getMethodName()); } - private void afterDistributedTest(Description testDescription) throws Throwable { - System.out.println("\n\n[setup] END TEST " + testDescription.getTestClass().getSimpleName() - + "." + testDescription.getMethodName()); - int afterVmCount = getVMCount(); - assertThat(afterVmCount).isEqualTo(beforeVmCount); + private void afterDistributedTest(final Description description) throws Throwable { + System.out.println("\n\n[setup] END TEST " + description.getTestClass().getSimpleName() + + "." + description.getMethodName()); } protected void before() throws Throwable { - // override + // override if needed } protected void after() throws Throwable { - // override + // override if needed } protected RemoteInvoker invoker() { @@ -99,4 +96,34 @@ protected RemoteInvoker invoker() { protected int vmCount() { return vmCount; } + + protected void afterCreateVM(VM vm) { + // override if needed + } + + protected void beforeBounceVM(VM vm) { + // override if needed + } + + protected void afterBounceVM(VM vm) { + // override if needed + } + + private class InternalVMEventListener implements VMEventListener { + + @Override + public void afterCreateVM(VM vm) { + AbstractDistributedRule.this.afterCreateVM(vm); + } + + @Override + public void beforeBounceVM(VM vm) { + AbstractDistributedRule.this.beforeBounceVM(vm); + } + + @Override + public void afterBounceVM(VM vm) { + AbstractDistributedRule.this.afterBounceVM(vm); + } + } } diff --git a/geode-dunit/src/test/java/org/apache/geode/test/dunit/internal/VMEventNotifierTest.java b/geode-dunit/src/test/java/org/apache/geode/test/dunit/internal/VMEventNotifierTest.java new file mode 100644 index 000000000000..039b471a89f0 --- /dev/null +++ b/geode-dunit/src/test/java/org/apache/geode/test/dunit/internal/VMEventNotifierTest.java @@ -0,0 +1,164 @@ +/* + * 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.internal; + +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyZeroInteractions; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.function.Consumer; + +import junitparams.JUnitParamsRunner; +import junitparams.Parameters; +import junitparams.naming.TestCaseName; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.apache.geode.test.awaitility.GeodeAwaitility; +import org.apache.geode.test.dunit.VM; +import org.apache.geode.test.dunit.VMEventListener; +import org.apache.geode.test.junit.rules.ExecutorServiceRule; + +/** + * Unit tests for {@link VMEventNotifier}; + */ +@RunWith(JUnitParamsRunner.class) +public class VMEventNotifierTest { + + private static final long TIMEOUT_MILLIS = GeodeAwaitility.getTimeout().getValueInMS(); + + @Rule + public ExecutorServiceRule executorServiceRule = new ExecutorServiceRule(); + + private final CountDownLatch startLatch = new CountDownLatch(1); + private final CountDownLatch stopLatch = new CountDownLatch(1); + + private VMEventListener vmEventListener1; + private VMEventListener vmEventListener2; + private VM vm; + + private VMEventNotifier vmEventNotifier; + + @Before + public void setUp() { + vmEventListener1 = mock(VMEventListener.class); + vmEventListener2 = mock(VMEventListener.class); + vm = mock(VM.class); + + vmEventNotifier = new VMEventNotifier(); + } + + @Test + @Parameters({"AFTER_CREATE_VM", "BEFORE_BOUNCE_VM", "AFTER_BOUNCE_VM"}) + @TestCaseName("{method}({params})") + public void addsListenerConcurrentlyWithNotification(Notification notification) throws Exception { + doAnswer(invocation -> { + startLatch.countDown(); + stopLatch.await(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS); + return null; + }).when(vmEventListener1).afterCreateVM(vm); + + vmEventNotifier.addVMEventListener(vmEventListener1); + + Future notifiedFuture = executorServiceRule.submit(() -> { + notification.notify(vmEventNotifier, vm); + return null; + }); + + startLatch.await(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS); + + vmEventNotifier.addVMEventListener(vmEventListener2); + + stopLatch.countDown(); + + notifiedFuture.get(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS); + + verify(vmEventListener1).afterCreateVM(eq(vm)); + verifyZeroInteractions(vmEventListener2); + } + + @Test + @Parameters({"AFTER_CREATE_VM", "BEFORE_BOUNCE_VM", "AFTER_BOUNCE_VM"}) + @TestCaseName("{method}({params})") + public void removesListenerConcurrentlyWithNotification(Notification notification) + throws Exception { + doAnswer(invocation -> { + startLatch.countDown(); + stopLatch.await(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS); + return null; + }).when(vmEventListener1).afterCreateVM(vm); + + vmEventNotifier.addVMEventListener(vmEventListener1); + vmEventNotifier.addVMEventListener(vmEventListener2); + + Future notifiedFuture = executorServiceRule.submit(() -> { + notification.notify(vmEventNotifier, vm); + return null; + }); + + startLatch.await(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS); + + vmEventNotifier.removeVMEventListener(vmEventListener2); + + stopLatch.countDown(); + + notifiedFuture.get(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS); + + verify(vmEventListener1).afterCreateVM(eq(vm)); + verify(vmEventListener2).afterCreateVM(eq(vm)); + } + + @SuppressWarnings("unused") + private enum Notification { + AFTER_CREATE_VM(params -> params.vmEventNotifier().notifyAfterCreateVM(params.vm())), + BEFORE_BOUNCE_VM(params -> params.vmEventNotifier().notifyAfterCreateVM(params.vm())), + AFTER_BOUNCE_VM(params -> params.vmEventNotifier().notifyAfterCreateVM(params.vm())); + + private final Consumer strategy; + + Notification(Consumer strategy) { + this.strategy = strategy; + } + + void notify(VMEventNotifier vmEventNotifier, VM vm) { + strategy.accept(new NotificationParams(vmEventNotifier, vm)); + } + } + + private static class NotificationParams { + private final VMEventNotifier vmEventNotifier; + private final VM vm; + + NotificationParams(VMEventNotifier vmEventNotifier, VM vm) { + this.vmEventNotifier = vmEventNotifier; + this.vm = vm; + } + + VMEventNotifier vmEventNotifier() { + return vmEventNotifier; + } + + VM vm() { + return vm; + } + } +} diff --git a/geode-dunit/src/test/resources/expected-pom.xml b/geode-dunit/src/test/resources/expected-pom.xml index e0c77c6e6f63..41d54224975b 100644 --- a/geode-dunit/src/test/resources/expected-pom.xml +++ b/geode-dunit/src/test/resources/expected-pom.xml @@ -148,6 +148,11 @@ awaitility compile + + pl.pragmatists + JUnitParams + compile + junit junit