FELIX-6250 JUnit Test for HealthCheckMonitor, upgrade to Mockito 3.3.3
diff --git a/healthcheck/api/src/main/java/org/apache/felix/hc/api/execution/HealthCheckSelector.java b/healthcheck/api/src/main/java/org/apache/felix/hc/api/execution/HealthCheckSelector.java
index 150ed66..0e0fdca 100644
--- a/healthcheck/api/src/main/java/org/apache/felix/hc/api/execution/HealthCheckSelector.java
+++ b/healthcheck/api/src/main/java/org/apache/felix/hc/api/execution/HealthCheckSelector.java
@@ -95,4 +95,30 @@
                 ", names=" + (names == null ? "*" : Arrays.toString(names)) +
                 '}';
     }
+
+    @Override
+    public int hashCode() {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result + Arrays.hashCode(names);
+        result = prime * result + Arrays.hashCode(tags);
+        return result;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj)
+            return true;
+        if (obj == null)
+            return false;
+        if (getClass() != obj.getClass())
+            return false;
+        HealthCheckSelector other = (HealthCheckSelector) obj;
+        if (!Arrays.equals(names, other.names))
+            return false;
+        if (!Arrays.equals(tags, other.tags))
+            return false;
+        return true;
+    }
+
 }
diff --git a/healthcheck/core/pom.xml b/healthcheck/core/pom.xml
index 620d11f..6363f32 100644
--- a/healthcheck/core/pom.xml
+++ b/healthcheck/core/pom.xml
@@ -186,19 +186,18 @@
             <version>1.11.0</version>
             <scope>provided</scope>
         </dependency>
-        
 
         <!-- START test scope dependencies -->
         <dependency>
             <groupId>junit</groupId>
             <artifactId>junit</artifactId>
-            <version>4.12</version>
+            <version>4.13</version>
             <scope>test</scope>
         </dependency>
         <dependency>
             <groupId>org.mockito</groupId>
             <artifactId>mockito-core</artifactId>
-            <version>1.9.5</version>
+            <version>3.3.3</version>
             <scope>test</scope>
         </dependency>
         <dependency>
diff --git a/healthcheck/core/src/test/java/org/apache/felix/hc/core/impl/CompositeHealthCheckTest.java b/healthcheck/core/src/test/java/org/apache/felix/hc/core/impl/CompositeHealthCheckTest.java
index 347c67d..eab7470 100644
--- a/healthcheck/core/src/test/java/org/apache/felix/hc/core/impl/CompositeHealthCheckTest.java
+++ b/healthcheck/core/src/test/java/org/apache/felix/hc/core/impl/CompositeHealthCheckTest.java
@@ -18,8 +18,8 @@
 package org.apache.felix.hc.core.impl;
 
 import static org.junit.Assert.assertEquals;
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.argThat;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.argThat;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.times;
@@ -45,6 +45,7 @@
 import org.hamcrest.TypeSafeMatcher;
 import org.junit.Before;
 import org.junit.Test;
+import org.mockito.ArgumentMatcher;
 import org.mockito.Matchers;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
@@ -97,16 +98,16 @@
 
     }
 
-    private Matcher<HealthCheckSelector> selectorWithTags(final String[] tags) {
-        return new TypeSafeMatcher<HealthCheckSelector>() {
+    private ArgumentMatcher<HealthCheckSelector> selectorWithTags(final String[] tags) {
+        return new ArgumentMatcher<HealthCheckSelector>() {
             @Override
-            protected boolean matchesSafely(HealthCheckSelector healthCheckSelector) {
+            public boolean matches(HealthCheckSelector healthCheckSelector) {
                 return Arrays.equals(healthCheckSelector.tags(), tags) && healthCheckSelector.names() == null;
             }
 
             @Override
-            public void describeTo(Description description) {
-                description.appendText("a select with tags (" + Arrays.toString(tags) + ") and no names.");
+            public String toString() {
+                return "a select with tags (" + Arrays.toString(tags) + ") and no names.";
             }
         };
     }
@@ -205,27 +206,27 @@
         verify(healthCheckExecutor, times(1)).execute(argThat(selectorWithTags(filterTags)), argThat(orOptions));
     }
 
-    private Matcher<HealthCheckExecutionOptions> orOptions = new TypeSafeMatcher<HealthCheckExecutionOptions>() {
+    private ArgumentMatcher<HealthCheckExecutionOptions> orOptions = new ArgumentMatcher<HealthCheckExecutionOptions>() {
         @Override
-        protected boolean matchesSafely(HealthCheckExecutionOptions options) {
+        public boolean matches(HealthCheckExecutionOptions options) {
             return options.isCombineTagsWithOr();
         }
 
         @Override
-        public void describeTo(Description description) {
-            description.appendText("options combining tags with or.");
+        public String toString() {
+            return "options combining tags with or.";
         }
     };
 
-    private Matcher<HealthCheckExecutionOptions> andOptions = new TypeSafeMatcher<HealthCheckExecutionOptions>() {
+    private ArgumentMatcher<HealthCheckExecutionOptions> andOptions = new ArgumentMatcher<HealthCheckExecutionOptions>() {
         @Override
-        protected boolean matchesSafely(HealthCheckExecutionOptions options) {
+        public boolean matches(HealthCheckExecutionOptions options) {
             return !options.isCombineTagsWithOr();
         }
 
         @Override
-        public void describeTo(Description description) {
-            description.appendText("options combining tags with and.");
+        public String toString() {
+            return "options combining tags with and.";
         }
     };
 
diff --git a/healthcheck/core/src/test/java/org/apache/felix/hc/core/impl/monitor/HealthCheckMonitorTest.java b/healthcheck/core/src/test/java/org/apache/felix/hc/core/impl/monitor/HealthCheckMonitorTest.java
new file mode 100644
index 0000000..70c3cd4
--- /dev/null
+++ b/healthcheck/core/src/test/java/org/apache/felix/hc/core/impl/monitor/HealthCheckMonitorTest.java
@@ -0,0 +1,238 @@
+/*
+ * 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 SF 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.felix.hc.core.impl.monitor;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoInteractions;
+import static org.mockito.Mockito.when;
+
+import java.lang.reflect.Method;
+import java.util.Arrays;
+import java.util.List;
+
+import org.apache.felix.hc.api.HealthCheck;
+import org.apache.felix.hc.api.Result;
+import org.apache.felix.hc.api.condition.Healthy;
+import org.apache.felix.hc.api.condition.Unhealthy;
+import org.apache.felix.hc.api.execution.HealthCheckExecutor;
+import org.apache.felix.hc.api.execution.HealthCheckMetadata;
+import org.apache.felix.hc.api.execution.HealthCheckSelector;
+import org.apache.felix.hc.core.impl.executor.ExecutionResult;
+import org.apache.felix.hc.core.impl.executor.HealthCheckExecutorThreadPool;
+import org.apache.felix.hc.core.impl.scheduling.AsyncIntervalJob;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.InvalidSyntaxException;
+import org.osgi.framework.ServiceReference;
+import org.osgi.framework.ServiceRegistration;
+import org.osgi.service.component.ComponentConstants;
+import org.osgi.service.component.ComponentContext;
+import org.osgi.service.event.Event;
+import org.osgi.service.event.EventAdmin;
+
+public class HealthCheckMonitorTest {
+
+    private static final String TEST_TAG = "test-tag";
+
+    @InjectMocks
+    private HealthCheckMonitor healthCheckMonitor = new HealthCheckMonitor();
+
+    @Mock
+    private BundleContext bundleContext;
+
+    @Mock
+    private ComponentContext componentContext;
+    
+    @Mock
+    private EventAdmin eventAdmin;
+
+    @Mock
+    private HealthCheckMonitor.Config config;
+
+    @Mock
+    private HealthCheckExecutorThreadPool healthCheckExecutorThreadPool;
+
+    @Mock
+    private HealthCheckExecutor healthCheckExecutor;
+    
+    @Mock
+    private HealthCheckMetadata healthCheckMetadata;
+
+    @Mock
+    private ServiceReference<HealthCheck> healthCheckServiceRef;
+    
+    @Captor
+    private ArgumentCaptor<Event> postedEventsCaptor1;
+
+    @Captor
+    private ArgumentCaptor<Event> postedEventsCaptor2;
+    
+    @Mock
+    private ServiceRegistration<? extends Healthy> healthyRegistration;
+    
+    @Mock
+    private ServiceRegistration<Unhealthy> unhealthyRegistration;
+    
+    @Before
+    public void before() throws ReflectiveOperationException {
+        MockitoAnnotations.initMocks(this);
+
+        for (Method m : HealthCheckMonitor.Config.class.getDeclaredMethods()) {
+            when(m.invoke(config)).thenReturn(m.getDefaultValue());
+        }
+        
+        when(config.intervalInSec()).thenReturn(1000L);
+        when(config.tags()).thenReturn(new String[] { TEST_TAG });
+        
+        when(healthCheckMetadata.getServiceReference()).thenReturn(healthCheckServiceRef);
+    }
+
+    @Test
+    public void testActivate() throws InvalidSyntaxException {
+
+
+        healthCheckMonitor.activate(bundleContext, config, componentContext);
+
+        assertTrue(healthCheckMonitor.monitorJob instanceof AsyncIntervalJob);
+
+        verify(healthCheckExecutorThreadPool).scheduleAtFixedRate(any(Runnable.class), eq(1000L));
+
+        assertEquals("[HealthCheckMonitor tags=[test-tag]/names=[], intervalInSec=1000/cron=]", healthCheckMonitor.toString());
+
+        assertEquals(1, healthCheckMonitor.healthStates.size());
+        assertEquals("[HealthState tagOrName=test-tag, isTag=true, status=null, isHealthy=false, statusChanged=false]", healthCheckMonitor.healthStates.get(0).toString());
+        
+        healthCheckMonitor.deactivate();
+        assertEquals(0, healthCheckMonitor.healthStates.size());
+        
+    }
+
+    @Test
+    public void testRunRegisterMarkerServices() throws InvalidSyntaxException {
+
+        when(config.registerHealthyMarkerService()).thenReturn(true);
+        when(config.registerUnhealthyMarkerService()).thenReturn(true);
+        healthCheckMonitor.activate(bundleContext, config, componentContext);
+
+        resetMarkerServicesContext();
+
+        setHcResult(Result.Status.OK);
+
+        healthCheckMonitor.run();
+        
+        verify(healthCheckExecutor).execute(HealthCheckSelector.tags(TEST_TAG));
+        
+        verify(bundleContext).registerService(eq(Healthy.class), eq(HealthCheckMonitor.MARKER_SERVICE_HEALTHY), any());
+        verify(bundleContext, never()).registerService(eq(Unhealthy.class), eq(HealthCheckMonitor.MARKER_SERVICE_UNHEALTHY), any());
+        verifyNoInteractions(healthyRegistration, unhealthyRegistration);
+
+        resetMarkerServicesContext();
+        healthCheckMonitor.run();
+        // no status change, no interaction
+        verifyNoInteractions(bundleContext, healthyRegistration, unhealthyRegistration);
+        
+        // change, unhealthy should be registered
+        resetMarkerServicesContext();
+        setHcResult(Result.Status.TEMPORARILY_UNAVAILABLE);
+        healthCheckMonitor.run();
+        
+        verify(bundleContext, never()).registerService(eq(Healthy.class), eq(HealthCheckMonitor.MARKER_SERVICE_HEALTHY), any());
+        verify(bundleContext).registerService(eq(Unhealthy.class), eq(HealthCheckMonitor.MARKER_SERVICE_UNHEALTHY), any());
+        verify(healthyRegistration).unregister();
+        verifyNoInteractions(unhealthyRegistration);
+        
+        // change, health should be registered
+        resetMarkerServicesContext();
+        setHcResult(Result.Status.WARN); // WARN is healthy by default config
+        healthCheckMonitor.run();
+        verify(bundleContext).registerService(eq(Healthy.class), eq(HealthCheckMonitor.MARKER_SERVICE_HEALTHY), any());
+        verify(bundleContext, never()).registerService(eq(Unhealthy.class), eq(HealthCheckMonitor.MARKER_SERVICE_UNHEALTHY), any());
+        verify(unhealthyRegistration).unregister();
+        verifyNoInteractions(healthyRegistration);
+    }
+
+    private void resetMarkerServicesContext() {
+        reset(bundleContext, healthyRegistration, unhealthyRegistration);
+        when(bundleContext.registerService(eq(Healthy.class), eq(HealthCheckMonitor.MARKER_SERVICE_HEALTHY), any())).thenReturn((ServiceRegistration<Healthy>) healthyRegistration);
+        when(bundleContext.registerService(eq(Unhealthy.class), eq(HealthCheckMonitor.MARKER_SERVICE_UNHEALTHY), any())).thenReturn(unhealthyRegistration);
+    }
+    
+    @Test
+    public void testRunSendEvents() throws InvalidSyntaxException {
+
+        when(config.sendEvents()).thenReturn(true);
+        when(healthCheckServiceRef.getProperty(ComponentConstants.COMPONENT_NAME)).thenReturn("org.apache.felix.TestHealthCheck");
+
+        healthCheckMonitor.activate(bundleContext, config, componentContext);
+
+        setHcResult(Result.Status.OK);
+
+        healthCheckMonitor.run();
+        
+        verify(healthCheckExecutor).execute(HealthCheckSelector.tags(TEST_TAG));
+        
+        verify(eventAdmin, times(2)).postEvent(postedEventsCaptor1.capture());
+        List<Event> postedEvents = postedEventsCaptor1.getAllValues();
+        assertEquals(2, postedEvents.size());
+        assertEquals("org/apache/felix/healthchange/tag/test-tag", postedEvents.get(0).getTopic());
+        assertEquals(Result.Status.OK, postedEvents.get(0).getProperty(HealthCheckMonitor.EVENT_PROP_STATUS));
+        assertEquals("org/apache/felix/healthchange/class/org/apache/felix/TestHealthCheck", postedEvents.get(1).getTopic());
+
+        reset(eventAdmin);
+        // without status change
+        healthCheckMonitor.run();
+        // no event
+        verifyNoInteractions(eventAdmin);
+        
+        setHcResult(Result.Status.CRITICAL);
+        reset(eventAdmin);
+        // with status change
+        healthCheckMonitor.run();
+        verify(eventAdmin, times(2)).postEvent(postedEventsCaptor2.capture());
+        postedEvents = postedEventsCaptor2.getAllValues();
+        assertEquals(2, postedEvents.size());
+        assertEquals("org/apache/felix/healthchange/tag/test-tag", postedEvents.get(0).getTopic());
+        assertEquals(Result.Status.CRITICAL, postedEvents.get(0).getProperty(HealthCheckMonitor.EVENT_PROP_STATUS));
+        assertEquals(Result.Status.OK, postedEvents.get(0).getProperty(HealthCheckMonitor.EVENT_PROP_PREVIOUS_STATUS));
+        assertEquals("org/apache/felix/healthchange/class/org/apache/felix/TestHealthCheck", postedEvents.get(1).getTopic());
+        
+        reset(eventAdmin);
+        // without status change
+        healthCheckMonitor.run();
+        // no event
+        verifyNoInteractions(eventAdmin);
+    }
+
+    private void setHcResult(Result.Status status) {
+        when(healthCheckExecutor.execute(HealthCheckSelector.tags(TEST_TAG)))
+            .thenReturn(Arrays.asList(new ExecutionResult(healthCheckMetadata, new Result(status, status.toString()), 1)));
+    }
+}
diff --git a/healthcheck/core/src/test/java/org/apache/felix/hc/core/impl/servlet/HealthCheckExecutorServletTest.java b/healthcheck/core/src/test/java/org/apache/felix/hc/core/impl/servlet/HealthCheckExecutorServletTest.java
index 5e5aa16..979dae9 100644
--- a/healthcheck/core/src/test/java/org/apache/felix/hc/core/impl/servlet/HealthCheckExecutorServletTest.java
+++ b/healthcheck/core/src/test/java/org/apache/felix/hc/core/impl/servlet/HealthCheckExecutorServletTest.java
@@ -18,15 +18,15 @@
 package org.apache.felix.hc.core.impl.servlet;
 
 import static org.junit.Assert.assertEquals;
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.anyString;
-import static org.mockito.Matchers.argThat;
-import static org.mockito.Matchers.contains;
-import static org.mockito.Matchers.eq;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.argThat;
+import static org.mockito.ArgumentMatchers.contains;
+import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.verifyNoInteractions;
 import static org.mockito.MockitoAnnotations.initMocks;
 
 import java.io.IOException;
@@ -48,7 +48,6 @@
 import org.apache.felix.hc.api.execution.HealthCheckMetadata;
 import org.apache.felix.hc.api.execution.HealthCheckSelector;
 import org.apache.felix.hc.core.impl.executor.ExecutionResult;
-import org.hamcrest.Description;
 import org.junit.Before;
 import org.junit.Test;
 import org.mockito.ArgumentMatcher;
@@ -118,9 +117,9 @@
 
         healthCheckExecutorServlet.doGet(request, response);
 
-        verifyZeroInteractions(jsonSerializer);
-        verifyZeroInteractions(txtSerializer);
-        verifyZeroInteractions(verboseTxtSerializer);
+        verifyNoInteractions(jsonSerializer);
+        verifyNoInteractions(txtSerializer);
+        verifyNoInteractions(verboseTxtSerializer);
         verify(htmlSerializer)
                 .serialize(resultEquals(new Result(Result.Status.CRITICAL, "Overall Status CRITICAL")), eq(executionResults),
                         contains("Supported URL parameters"), eq(false));
@@ -142,9 +141,9 @@
 
         verify(request, never()).getParameter(HealthCheckExecutorServlet.PARAM_TAGS.name);
         verify(request, never()).getParameter(HealthCheckExecutorServlet.PARAM_NAMES.name);
-        verifyZeroInteractions(jsonSerializer);
-        verifyZeroInteractions(txtSerializer);
-        verifyZeroInteractions(verboseTxtSerializer);
+        verifyNoInteractions(jsonSerializer);
+        verifyNoInteractions(txtSerializer);
+        verifyNoInteractions(verboseTxtSerializer);
         verify(htmlSerializer)
                 .serialize(resultEquals(new Result(Result.Status.CRITICAL, "Overall Status CRITICAL")), eq(executionResults),
                         contains("Supported URL parameters"), eq(false));
@@ -166,11 +165,11 @@
 
         healthCheckExecutorServlet.doGet(request, response);
 
-        verifyZeroInteractions(htmlSerializer);
-        verifyZeroInteractions(txtSerializer);
-        verifyZeroInteractions(verboseTxtSerializer);
+        verifyNoInteractions(htmlSerializer);
+        verifyNoInteractions(txtSerializer);
+        verifyNoInteractions(verboseTxtSerializer);
         verify(jsonSerializer).serialize(resultEquals(new Result(Result.Status.WARN, "Overall Status WARN")), eq(executionResults),
-                anyString(),
+                any(),
                 eq(false));
 
     }
@@ -193,9 +192,9 @@
 
         healthCheckExecutorServlet.doGet(request, response);
 
-        verifyZeroInteractions(htmlSerializer);
-        verifyZeroInteractions(jsonSerializer);
-        verifyZeroInteractions(verboseTxtSerializer);
+        verifyNoInteractions(htmlSerializer);
+        verifyNoInteractions(jsonSerializer);
+        verifyNoInteractions(verboseTxtSerializer);
         verify(txtSerializer).serialize(resultEquals(new Result(Result.Status.WARN, "Overall Status WARN")));
 
     }
@@ -213,9 +212,9 @@
 
         healthCheckExecutorServlet.doGet(request, response);
 
-        verifyZeroInteractions(htmlSerializer);
-        verifyZeroInteractions(jsonSerializer);
-        verifyZeroInteractions(txtSerializer);
+        verifyNoInteractions(htmlSerializer);
+        verifyNoInteractions(jsonSerializer);
+        verifyNoInteractions(txtSerializer);
         verify(verboseTxtSerializer).serialize(resultEquals(new Result(Result.Status.WARN, "Overall Status WARN")), eq(executionResults),
                 eq(false));
 
@@ -280,7 +279,7 @@
         return argThat(new ResultMatcher(expected));
     }
 
-    static class ResultMatcher extends ArgumentMatcher<Result> {
+    static class ResultMatcher implements ArgumentMatcher<Result> {
 
         private final Result expectedResult;
 
@@ -288,22 +287,20 @@
             this.expectedResult = expected;
         }
 
-        @Override
-        public boolean matches(Object actual) {
-            Result actualResult = (Result) actual;
+        public boolean matches(Result actualResult) {
             return actualResult.getStatus().equals(expectedResult.getStatus()); // simple status matching only sufficient for this test case
         }
 
         @Override
-        public void describeTo(Description description) {
-            description.appendText(expectedResult == null ? null : expectedResult.toString());
+        public String toString() {
+            return expectedResult == null ? null : expectedResult.toString();
         }
     }
 
     HealthCheckSelector selector(final String[] tags, final String[] names) {
         return argThat(new ArgumentMatcher<HealthCheckSelector>() {
             @Override
-            public boolean matches(Object actual) {
+            public boolean matches(HealthCheckSelector actual) {
                 if (actual instanceof HealthCheckSelector) {
                     HealthCheckSelector actualSelector = (HealthCheckSelector) actual;
                     return Arrays.equals(actualSelector.tags(), tags.length == 0 ? new String[] { "" } : tags) &&