SLING-9200 fixing implementation and adding junit tests
diff --git a/pom.xml b/pom.xml
index 0f4da65..6705016 100644
--- a/pom.xml
+++ b/pom.xml
@@ -28,7 +28,7 @@
     </parent>
 
     <artifactId>org.apache.sling.junit.core</artifactId>
-    <version>1.0.27-SNAPSHOT</version>
+    <version>1.0.27_B001-SNAPSHOT</version>
     <packaging>bundle</packaging>
 
     <name>Apache Sling JUnit Core</name>
@@ -160,7 +160,7 @@
         <dependency>
             <groupId>org.slf4j</groupId>
             <artifactId>slf4j-api</artifactId>
-            <version>1.5.11</version>
+            <version>1.7.30</version>
             <scope>provided</scope>
         </dependency>
         <dependency>
@@ -173,7 +173,7 @@
         <dependency>
             <groupId>ch.qos.logback</groupId>
             <artifactId>logback-classic</artifactId>
-            <version>1.0.13</version>
+            <version>1.2.3</version>
             <scope>provided</scope>
             <optional>true</optional>
         </dependency>
@@ -203,5 +203,17 @@
             <version>${hamcrest.version}</version>
             <scope>compile</scope>
         </dependency>
+        <dependency>
+            <groupId>org.powermock</groupId>
+            <artifactId>powermock-module-junit4</artifactId>
+            <version>2.0.5</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.powermock</groupId>
+            <artifactId>powermock-api-mockito2</artifactId>
+            <version>2.0.5</version>
+            <scope>test</scope>
+        </dependency>
     </dependencies>
 </project>
diff --git a/src/main/java/org/apache/sling/junit/impl/TestsManagerImpl.java b/src/main/java/org/apache/sling/junit/impl/TestsManagerImpl.java
index eeda491..b5a2c42 100644
--- a/src/main/java/org/apache/sling/junit/impl/TestsManagerImpl.java
+++ b/src/main/java/org/apache/sling/junit/impl/TestsManagerImpl.java
@@ -55,13 +55,19 @@
 
     // the inactivity timeout is the maximum time after the last bundle became active
     // before waiting for more bundles to become active should be aborted
-    private static final int DEFAULT_SYSTEM_STARTUP_INACTIVITY_TIMEOUT_SECONDS = 10;
+    public static final String PROPERTY_SYSTEM_STARTUP_INACTIVITY_TIMEOUT_SECONDS = "sling.junit.core.system_startup_inactive_timeout";
 
     // Global Timeout up to which it stop waiting for bundles to be all active.
-    private static final int DEFAULT_SYSTEM_STARTUP_GLOBAL_TIMEOUT_SECONDS = 60;
+    public static final String PROPERTY_SYSTEM_STARTUP_GLOBAL_TIMEOUT_SECONDS = "sling.junit.core.system_startup_global_timeout";
+
+    private static volatile int inactivityTimeoutSeconds = Integer.parseInt(System.getProperty(PROPERTY_SYSTEM_STARTUP_INACTIVITY_TIMEOUT_SECONDS, "10"));
+
+    private static volatile int globalTimeoutSeconds = Integer.parseInt(System.getProperty(PROPERTY_SYSTEM_STARTUP_GLOBAL_TIMEOUT_SECONDS, "40"));
 
     private static volatile boolean waitForSystemStartup = true;
 
+    private static volatile long timeWaitForSystemStartup = 0;
+
     private ServiceTracker tracker;
 
     private int lastTrackingCount = -1;
@@ -69,7 +75,7 @@
     private BundleContext bundleContext;
     
     // List of providers
-    private List<TestsProvider> providers = new ArrayList<TestsProvider>();
+    private final List<TestsProvider> providers = new ArrayList<TestsProvider>();
     
     // Map of test names to their provider's PID
     private Map<String, String> tests = new ConcurrentHashMap<String, String>();
@@ -129,7 +135,7 @@
         boolean reload = false;
         for(TestsProvider p : providers) {
             final Long lastMod = lastModified.get(p.getServicePid());
-            if(lastMod == null || lastMod.longValue() != p.lastModified()) {
+            if(lastMod == null || lastMod != p.lastModified()) {
                 reload = true;
                 log.debug("{} updated, will reload test names from all providers", p);
                 break;
@@ -144,7 +150,7 @@
                     log.warn("{} has null PID, ignored", p);
                     continue;
                 }
-                lastModified.put(pid, new Long(p.lastModified()));
+                lastModified.put(pid, p.lastModified());
                 final List<String> names = p.getTestNames(); 
                 for(String name : names) {
                     tests.put(name, pid);
@@ -165,7 +171,7 @@
                     result.add(test);
                 }
             }
-            log.debug("{} selected {} tests out of {}", new Object[] { selector, result.size(), allTests.size() });
+            log.debug("{} selected {} tests out of {}", selector, result.size(), allTests.size());
             return result;
         }
     }
@@ -225,7 +231,7 @@
         }
     }
 
-    public void listTests(Collection<String> testNames, Renderer renderer) throws Exception {
+    public void listTests(Collection<String> testNames, Renderer renderer) {
         renderer.title(2, "Test classes");
         final String note = "The test set can be restricted using partial test names"
                 + " as a suffix to this URL"
@@ -248,7 +254,10 @@
 
             // wait max inactivityTimeout after the last bundle became active before giving up
             long lastChange = System.currentTimeMillis();
-            long globalTimeout = lastChange + TimeUnit.SECONDS.toMillis(DEFAULT_SYSTEM_STARTUP_GLOBAL_TIMEOUT_SECONDS);
+            long globalTimeout = lastChange + TimeUnit.SECONDS.toMillis(globalTimeoutSeconds);
+
+            long startTime = System.currentTimeMillis();
+            
             while (isWaitNeeded(globalTimeout, lastChange, bundlesToWaitFor)) {
                 log.info("Waiting for the following bundles to start: {}", bundlesToWaitFor);
                 try {
@@ -267,9 +276,11 @@
                 }
             }
 
+            timeWaitForSystemStartup = System.currentTimeMillis() - startTime;
+
             if (!bundlesToWaitFor.isEmpty()) {
-                log.warn("Waited {} seconds but the following bundles are not yet started: {}",
-                        DEFAULT_SYSTEM_STARTUP_INACTIVITY_TIMEOUT_SECONDS, bundlesToWaitFor);
+                log.warn("Waited {} milliseconds but the following bundles are not yet started: {}",
+                    timeWaitForSystemStartup, bundlesToWaitFor);
             } else {
                 log.info("All bundles are active, starting to run tests.");
             }
@@ -277,9 +288,11 @@
     }
 
     private static boolean isWaitNeeded(final long globalTimeout, final long lastChange, final Set<Bundle> bundlesToWaitFor) {
-        long inactivityTimeout = TimeUnit.SECONDS.toMillis(DEFAULT_SYSTEM_STARTUP_INACTIVITY_TIMEOUT_SECONDS);
-        boolean canLoop = !bundlesToWaitFor.isEmpty() || (lastChange + inactivityTimeout < System.currentTimeMillis());
-        boolean notGloballyTimeout = globalTimeout < System.currentTimeMillis();
+        long inactivityTimeout = TimeUnit.SECONDS.toMillis(inactivityTimeoutSeconds);
+        long currentTime = System.currentTimeMillis();
+        boolean isInactive = lastChange + inactivityTimeout < currentTime;
+        boolean canLoop = !bundlesToWaitFor.isEmpty() || !isInactive;
+        boolean notGloballyTimeout = globalTimeout > currentTime;
         return notGloballyTimeout && canLoop;
     }
 
diff --git a/src/test/java/org/apache/sling/junit/impl/TestsManagerImplTest.java b/src/test/java/org/apache/sling/junit/impl/TestsManagerImplTest.java
new file mode 100644
index 0000000..7ab69ce
--- /dev/null
+++ b/src/test/java/org/apache/sling/junit/impl/TestsManagerImplTest.java
@@ -0,0 +1,150 @@
+/*
+ * 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.sling.junit.impl;
+
+import static junit.framework.TestCase.assertFalse;
+import static junit.framework.TestCase.assertTrue;
+import static org.powermock.api.mockito.PowerMockito.mock;
+import static org.powermock.api.mockito.PowerMockito.when;
+
+import java.util.Dictionary;
+import java.util.HashSet;
+import java.util.Hashtable;
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
+import org.apache.sling.junit.Activator;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mockito;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.powermock.api.mockito.PowerMockito;
+import org.powermock.core.classloader.annotations.PrepareForTest;
+import org.powermock.modules.junit4.PowerMockRunner;
+import org.powermock.reflect.Whitebox;
+
+/**
+ * Validate waitForSystemStartup method, along with private some implementations.
+ */
+@RunWith(PowerMockRunner.class)
+@PrepareForTest({ Activator.class, TestsManagerImpl.class })
+public class TestsManagerImplTest {
+
+  static {
+    // Set the system properties for this test as the default would wait longer.
+    System.setProperty(TestsManagerImpl.PROPERTY_SYSTEM_STARTUP_INACTIVITY_TIMEOUT_SECONDS, "1");
+    System.setProperty(TestsManagerImpl.PROPERTY_SYSTEM_STARTUP_GLOBAL_TIMEOUT_SECONDS, "2");
+  }
+
+  /**
+   * case if isWaitNeeded should return true, mainly it still have some bundles in the list to wait, and global timeout didn't pass.
+   */
+  @Test
+  public void isWaitNeededPositiveForNotInactiveNotEmptyList() throws Exception {
+    final TestsManagerImpl testsManager = new TestsManagerImpl();
+    long lastChange = System.currentTimeMillis();
+    long globalTimeout = lastChange + TimeUnit.SECONDS.toMillis(10);
+    final Set<Bundle> bundlesToWaitFor = new HashSet<Bundle>();
+    bundlesToWaitFor.add(Mockito.mock(Bundle.class));
+    boolean isWaitNeeded = Whitebox.invokeMethod(TestsManagerImpl.class, "isWaitNeeded", globalTimeout, lastChange, bundlesToWaitFor);
+    assertTrue(isWaitNeeded);
+  }
+
+  /**
+   * case if isWaitNeeded should return false, when for example there is no more bundles in the list.
+   */
+  @Test
+  public void isWaitNeededPositiveForEmptyListOnly() throws Exception {
+    final TestsManagerImpl testsManager = new TestsManagerImpl();
+    long lastChange = System.currentTimeMillis();
+    long globalTimeout = lastChange + TimeUnit.SECONDS.toMillis(10);
+    final Set<Bundle> bundlesToWaitFor = new HashSet<Bundle>();
+    boolean isWaitNeeded = Whitebox.invokeMethod(TestsManagerImpl.class, "isWaitNeeded", globalTimeout, lastChange, bundlesToWaitFor);
+    assertTrue(isWaitNeeded);
+  }
+
+  /**
+   * case if isWaitNeeded should return false, when for example there is no more bundles in the list.
+   */
+  @Test
+  public void isWaitNeededPositiveForInactivityTimeoutOnly() throws Exception {
+    final TestsManagerImpl testsManager = new TestsManagerImpl();
+    long lastChange = System.currentTimeMillis() - TimeUnit.SECONDS.toMillis(3);
+    long globalTimeout = lastChange + TimeUnit.SECONDS.toMillis(10);
+    final Set<Bundle> bundlesToWaitFor = new HashSet<Bundle>();
+    bundlesToWaitFor.add(Mockito.mock(Bundle.class));
+    boolean isWaitNeeded = Whitebox.invokeMethod(TestsManagerImpl.class, "isWaitNeeded", globalTimeout, lastChange, bundlesToWaitFor);
+    assertTrue(isWaitNeeded);
+  }
+
+  /**
+   * case if isWaitNeeded should return false, when for example it reached the global timeout limit.
+   */
+  @Test
+  public void isWaitNeededNegativeForGlobalTimeout() throws Exception {
+    final TestsManagerImpl testsManager = new TestsManagerImpl();
+    long lastChange = System.currentTimeMillis() - TimeUnit.SECONDS.toMillis(1);
+    long globalTimeout = lastChange - TimeUnit.SECONDS.toMillis(1);
+    final Set<Bundle> bundlesToWaitFor = new HashSet<Bundle>();
+    boolean isWaitNeeded = Whitebox.invokeMethod(TestsManagerImpl.class, "isWaitNeeded", globalTimeout, lastChange, bundlesToWaitFor);
+    assertFalse(isWaitNeeded);
+  }
+
+  /**
+   * case if isWaitNeeded should return false, when for example it reached the global timeout limit.
+   */
+  @Test
+  public void isWaitNeededNegativeForEmptyListInactiveTimeout() throws Exception {
+    final TestsManagerImpl testsManager = new TestsManagerImpl();
+    long lastChange = System.currentTimeMillis() - TimeUnit.SECONDS.toMillis(1);
+    long globalTimeout = lastChange + TimeUnit.SECONDS.toMillis(10);
+    final Set<Bundle> bundlesToWaitFor = new HashSet<Bundle>();
+    boolean isWaitNeeded = Whitebox.invokeMethod(TestsManagerImpl.class, "isWaitNeeded", globalTimeout, lastChange, bundlesToWaitFor);
+    assertTrue(isWaitNeeded);
+  }
+
+  @Test
+  public void waitForSystemStartupTimeout() {
+    setupBundleContextMock(Bundle.INSTALLED);
+    TestsManagerImpl.waitForSystemStartup();
+    long timeWaitForSystemStartup = Whitebox.getInternalState(TestsManagerImpl.class, "timeWaitForSystemStartup");
+    assertTrue(timeWaitForSystemStartup > TimeUnit.SECONDS.toMillis(2));
+    assertTrue(timeWaitForSystemStartup < TimeUnit.SECONDS.toMillis(3));
+    assertFalse((Boolean) Whitebox.getInternalState(TestsManagerImpl.class, "waitForSystemStartup"));
+  }
+
+  @Test
+  public void waitForSystemStartupAllActiveBundles() {
+    setupBundleContextMock(Bundle.ACTIVE);
+    TestsManagerImpl.waitForSystemStartup();
+    long timeWaitForSystemStartup = Whitebox.getInternalState(TestsManagerImpl.class, "timeWaitForSystemStartup");
+    assertTrue(timeWaitForSystemStartup < TimeUnit.SECONDS.toMillis(2));
+    assertFalse((Boolean) Whitebox.getInternalState(TestsManagerImpl.class, "waitForSystemStartup"));
+  }
+
+  private void setupBundleContextMock(final int bundleState) {
+    PowerMockito.mockStatic(Activator.class);
+    BundleContext mockedBundleContext = mock(BundleContext.class);
+    Bundle mockedBundle = mock(Bundle.class);
+    Hashtable<String, String> bundleHeaders = new Hashtable<String, String>();
+    when(mockedBundle.getState()).thenReturn(bundleState);
+    when(mockedBundle.getHeaders()).thenReturn(bundleHeaders);
+    when(mockedBundleContext.getBundles()).thenReturn(new Bundle[] { mockedBundle });
+    when(Activator.getBundleContext()).thenReturn(mockedBundleContext);
+    Whitebox.setInternalState(TestsManagerImpl.class, "waitForSystemStartup", true);
+  }
+}
\ No newline at end of file