This is an automated email from the ASF dual-hosted git repository.

klund pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/geode.git


The following commit(s) were added to refs/heads/develop by this push:
     new 2959d17  GEODE-6033: Add VMEventListener for DUnit Rules (#3161)
2959d17 is described below

commit 2959d179a5993c2b6921d6013c1855e3482ec6e1
Author: Kirk Lund <[email protected]>
AuthorDate: Wed Feb 6 09:33:43 2019 -0800

    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 +++++++++++++++++
 .../java/org/apache/geode/test/dunit/Host.java     |  12 +-
 .../main/java/org/apache/geode/test/dunit/VM.java  |  26 +++-
 .../apache/geode/test/dunit/VMEventListener.java   |  44 ++++++
 .../geode/test/dunit/VMEventListenerRegistry.java  |  31 ++++
 .../geode/test/dunit/internal/DUnitHost.java       |  10 +-
 .../geode/test/dunit/internal/DUnitLauncher.java   |   6 +-
 .../geode/test/dunit/internal/VMEventNotifier.java |  75 ++++++++++
 .../test/dunit/rules/AbstractDistributedRule.java  |  75 +++++++---
 .../test/dunit/internal/VMEventNotifierTest.java   | 164 +++++++++++++++++++++
 geode-dunit/src/test/resources/expected-pom.xml    |   5 +
 12 files changed, 554 insertions(+), 33 deletions(-)

diff --git a/geode-dunit/build.gradle b/geode-dunit/build.gradle
index 7d29fec..269fb80 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 0000000..2eb8e7f
--- /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<VM> 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 9821b19c..685d940 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 java.util.List;
 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 @@ import org.apache.geode.test.version.VersionManager;
  * 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<VM> vms;
 
+  private final transient VMEventNotifier vmEventNotifier;
+
   /**
    * Returns the number of known hosts
    */
@@ -101,7 +103,7 @@ public abstract class Host implements Serializable {
   /**
    * 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 @@ public abstract class Host implements Serializable {
 
     this.hostName = hostName;
     vms = new ArrayList<>();
+    this.vmEventNotifier = vmEventNotifier;
   }
 
   /**
@@ -168,6 +171,7 @@ public abstract class Host implements Serializable {
       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 abstract class Host implements Serializable {
   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 ad670d7..6ef2724 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.MethodInvokerResult;
 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
@@ -154,6 +155,24 @@ public class VM implements Serializable {
   }
 
   /**
+   * 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.
    */
   public VM(final Host host, final int id, final RemoteDUnitVMIF client,
@@ -480,6 +499,8 @@ public class VM implements Serializable {
     checkAvailability(getClass().getName(), "bounceVM");
 
     logger.info("Bouncing {} old pid is {}", id, getPid());
+    getVMEventNotifier().notifyBeforeBounceVM(this);
+
     available = false;
     try {
       if (force) {
@@ -501,7 +522,10 @@ public class VM implements Serializable {
       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 0000000..f2f639b
--- /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 0000000..87b2d6e
--- /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 651377e..11e0606 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 3dea304..f80431e 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 @@ public class DUnitLauncher {
 
     // 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 0000000..426b574
--- /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<VMEventListener> 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 44cb498..dea30dd 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 @@ class AbstractDistributedRule implements 
SerializableTestRule {
     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 @@ class AbstractDistributedRule implements 
SerializableTestRule {
   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 0000000..039b471
--- /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<Void> 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<Void> 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<NotificationParams> strategy;
+
+    Notification(Consumer<NotificationParams> 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 e0c77c6..41d5422 100644
--- a/geode-dunit/src/test/resources/expected-pom.xml
+++ b/geode-dunit/src/test/resources/expected-pom.xml
@@ -149,6 +149,11 @@
       <scope>compile</scope>
     </dependency>
     <dependency>
+      <groupId>pl.pragmatists</groupId>
+      <artifactId>JUnitParams</artifactId>
+      <scope>compile</scope>
+    </dependency>
+    <dependency>
       <groupId>junit</groupId>
       <artifactId>junit</artifactId>
       <scope>compile</scope>

Reply via email to