Update cache cleaner to HA-plugin version and add tests

The HA-plugin implemented tests for the cache cleaner. These tests
however relied on a refactored version of the cache cleaner that allowed
to use a mocked cleanup task.

To be able to make use of the tests and to be closer to the HA-plugin
version of the cache cleaner, this change implements the refactoring and
adds the tests.

Change-Id: I72f65210d34049ae72baa094fa24bbe62f7db0f6
diff --git a/BUILD b/BUILD
index bf3aa27..f00a805 100644
--- a/BUILD
+++ b/BUILD
@@ -24,7 +24,17 @@
     srcs = glob(["src/test/java/**/*.java"]),
     resources = glob(["src/test/resources/**/*"]),
     tags = ["websession-flatfile"],
-    deps = PLUGIN_DEPS + PLUGIN_TEST_DEPS + [
+    deps = [
+        ":websession-flatfile__plugin_test_deps",
+    ],
+)
+
+java_library(
+    name = "websession-flatfile__plugin_test_deps",
+    testonly = 1,
+    visibility = ["//visibility:public"],
+    exports = PLUGIN_DEPS + PLUGIN_TEST_DEPS + [
         ":websession-flatfile__plugin",
+        "@mockito//jar",
     ],
 )
diff --git a/WORKSPACE b/WORKSPACE
new file mode 100644
index 0000000..78ac255
--- /dev/null
+++ b/WORKSPACE
@@ -0,0 +1,5 @@
+workspace(name = "websession_flatfile")
+
+load("//:external_plugin_deps.bzl", "external_plugin_deps")
+
+external_plugin_deps()
diff --git a/external_plugin_deps.bzl b/external_plugin_deps.bzl
new file mode 100644
index 0000000..1d8851b
--- /dev/null
+++ b/external_plugin_deps.bzl
@@ -0,0 +1,33 @@
+load("//tools/bzl:maven_jar.bzl", "maven_jar")
+
+def external_plugin_deps():
+    maven_jar(
+        name = "mockito",
+        artifact = "org.mockito:mockito-core:2.27.0",
+        sha1 = "835fc3283b481f4758b8ef464cd560c649c08b00",
+        deps = [
+            "@byte-buddy//jar",
+            "@byte-buddy-agent//jar",
+            "@objenesis//jar",
+        ],
+    )
+
+    BYTE_BUDDY_VERSION = "1.9.10"
+
+    maven_jar(
+        name = "byte-buddy",
+        artifact = "net.bytebuddy:byte-buddy:" + BYTE_BUDDY_VERSION,
+        sha1 = "211a2b4d3df1eeef2a6cacf78d74a1f725e7a840",
+    )
+
+    maven_jar(
+        name = "byte-buddy-agent",
+        artifact = "net.bytebuddy:byte-buddy-agent:" + BYTE_BUDDY_VERSION,
+        sha1 = "9674aba5ee793e54b864952b001166848da0f26b",
+    )
+
+    maven_jar(
+        name = "objenesis",
+        artifact = "org.objenesis:objenesis:2.6",
+        sha1 = "639033469776fd37c08358c6b92a4761feb2af4b",
+    )
diff --git a/src/main/java/com/googlesource/gerrit/plugins/websession/flatfile/FlatFileWebSessionCacheCleaner.java b/src/main/java/com/googlesource/gerrit/plugins/websession/flatfile/FlatFileWebSessionCacheCleaner.java
index d5842a0..426870b 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/websession/flatfile/FlatFileWebSessionCacheCleaner.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/websession/flatfile/FlatFileWebSessionCacheCleaner.java
@@ -16,53 +16,67 @@
 
 import static java.util.concurrent.TimeUnit.MILLISECONDS;
 
+import com.google.gerrit.extensions.annotations.PluginName;
 import com.google.gerrit.extensions.events.LifecycleListener;
 import com.google.gerrit.server.git.WorkQueue;
 import com.google.inject.Inject;
+import com.google.inject.Provider;
+import com.google.inject.Singleton;
+import java.util.concurrent.ScheduledFuture;
 
-class FlatFileWebSessionCacheCleaner implements Runnable {
+@Singleton
+class FlatFileWebSessionCacheCleaner implements LifecycleListener {
+  private static final int INITIAL_DELAY_MS = 1000;
+  private final WorkQueue queue;
+  private final Provider<CleanupTask> cleanupTaskProvider;
+  private final long cleanupInterval;
+  private ScheduledFuture<?> scheduledCleanupTask;
 
-  static class CleanerLifecycle implements LifecycleListener {
-    private static final int INITIAL_DELAY_MS = 1000;
-    private final WorkQueue queue;
-    private final FlatFileWebSessionCacheCleaner cleaner;
-    private final long cleanupInterval;
+  static class CleanupTask implements Runnable {
+    private final FlatFileWebSessionCache flatFileWebSessionCache;
+    private final String pluginName;
 
     @Inject
-    CleanerLifecycle(
-        WorkQueue queue,
-        FlatFileWebSessionCacheCleaner cleaner,
-        @CleanupInterval long cleanupInterval) {
-      this.queue = queue;
-      this.cleaner = cleaner;
-      this.cleanupInterval = cleanupInterval;
+    CleanupTask(FlatFileWebSessionCache flatFileWebSessionCache, @PluginName String pluginName) {
+      this.flatFileWebSessionCache = flatFileWebSessionCache;
+      this.pluginName = pluginName;
     }
 
     @Override
-    public void start() {
-      queue
-          .getDefaultQueue()
-          .scheduleAtFixedRate(cleaner, INITIAL_DELAY_MS, cleanupInterval, MILLISECONDS);
+    public void run() {
+      flatFileWebSessionCache.cleanUp();
     }
 
     @Override
-    public void stop() {}
+    public String toString() {
+      return String.format("[%s] Clean up expired file based websessions", pluginName);
+    }
   }
 
-  private FlatFileWebSessionCache flatFileWebSessionCache;
-
   @Inject
-  FlatFileWebSessionCacheCleaner(FlatFileWebSessionCache flatFileWebSessionCache) {
-    this.flatFileWebSessionCache = flatFileWebSessionCache;
+  FlatFileWebSessionCacheCleaner(
+      WorkQueue queue,
+      Provider<CleanupTask> cleanupTaskProvider,
+      @CleanupInterval long cleanupInterval) {
+    this.queue = queue;
+    this.cleanupTaskProvider = cleanupTaskProvider;
+    this.cleanupInterval = cleanupInterval;
   }
 
   @Override
-  public void run() {
-    flatFileWebSessionCache.cleanUp();
+  public void start() {
+    scheduledCleanupTask =
+        queue
+            .getDefaultQueue()
+            .scheduleAtFixedRate(
+                cleanupTaskProvider.get(), INITIAL_DELAY_MS, cleanupInterval, MILLISECONDS);
   }
 
   @Override
-  public String toString() {
-    return "FlatFile WebSession Cleaner";
+  public void stop() {
+    if (scheduledCleanupTask != null) {
+      scheduledCleanupTask.cancel(true);
+      scheduledCleanupTask = null;
+    }
   }
 }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/websession/flatfile/Module.java b/src/main/java/com/googlesource/gerrit/plugins/websession/flatfile/Module.java
index c98d0e9..4bc80b7 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/websession/flatfile/Module.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/websession/flatfile/Module.java
@@ -24,7 +24,6 @@
 import com.google.gerrit.server.config.SitePaths;
 import com.google.inject.Provides;
 import com.google.inject.Singleton;
-import com.googlesource.gerrit.plugins.websession.flatfile.FlatFileWebSessionCacheCleaner.CleanerLifecycle;
 import java.nio.file.Path;
 import java.nio.file.Paths;
 
@@ -34,7 +33,7 @@
 
   @Override
   protected void configure() {
-    listener().to(CleanerLifecycle.class);
+    listener().to(FlatFileWebSessionCacheCleaner.class);
   }
 
   @Provides
diff --git a/src/main/resources/Documentation/build.md b/src/main/resources/Documentation/build.md
index f893189..7e87215 100644
--- a/src/main/resources/Documentation/build.md
+++ b/src/main/resources/Documentation/build.md
@@ -3,7 +3,15 @@
 
 This @PLUGIN@ plugin is built with Bazel.
 
-Clone (or link) this plugin to the `plugins` directory of Gerrit's source tree.
+Clone (or link) this plugin to the `plugins` directory of Gerrit's
+source tree. Put the external dependency Bazel build file into
+the Gerrit /plugins directory, replacing the existing empty one.
+
+```
+  cd gerrit/plugins
+  rm external_plugin_deps.bzl
+  ln -s @PLUGIN@/external_plugin_deps.bzl .
+```
 
 Then issue
 
diff --git a/src/test/java/com/googlesource/gerrit/plugins/websession/flatfile/FlatFileWebSessionCacheCleanerTest.java b/src/test/java/com/googlesource/gerrit/plugins/websession/flatfile/FlatFileWebSessionCacheCleanerTest.java
new file mode 100644
index 0000000..5761bcc
--- /dev/null
+++ b/src/test/java/com/googlesource/gerrit/plugins/websession/flatfile/FlatFileWebSessionCacheCleanerTest.java
@@ -0,0 +1,104 @@
+// Copyright (C) 2017 The Android Open Source Project
+//
+// Licensed 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 com.googlesource.gerrit.plugins.websession.flatfile;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.isA;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import com.google.gerrit.server.git.WorkQueue;
+import com.google.gerrit.server.git.WorkQueue.Executor;
+import com.google.inject.Provider;
+import com.googlesource.gerrit.plugins.websession.flatfile.FlatFileWebSessionCacheCleaner.CleanupTask;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+
+@RunWith(MockitoJUnitRunner.class)
+public class FlatFileWebSessionCacheCleanerTest {
+
+  private static long CLEANUP_INTERVAL = 5000;
+  private static String SOME_PLUGIN_NAME = "somePluginName";
+
+  @Mock private Executor executorMock;
+  @Mock private ScheduledFuture<?> scheduledFutureMock;
+  @Mock private WorkQueue workQueueMock;
+  @Mock private Provider<CleanupTask> cleanupTaskProviderMock;
+
+  private FlatFileWebSessionCacheCleaner cleaner;
+
+  @Before
+  public void setUp() {
+    when(cleanupTaskProviderMock.get()).thenReturn(new CleanupTask(null, null));
+    when(workQueueMock.getDefaultQueue()).thenReturn(executorMock);
+    doReturn(scheduledFutureMock)
+        .when(executorMock)
+        .scheduleAtFixedRate(isA(CleanupTask.class), anyLong(), anyLong(), isA(TimeUnit.class));
+    cleaner =
+        new FlatFileWebSessionCacheCleaner(
+            workQueueMock, cleanupTaskProviderMock, CLEANUP_INTERVAL);
+  }
+
+  @Test
+  public void testCleanupTaskRun() {
+    FlatFileWebSessionCache cacheMock = mock(FlatFileWebSessionCache.class);
+    CleanupTask task = new CleanupTask(cacheMock, null);
+    int numberOfRuns = 5;
+    for (int i = 0; i < numberOfRuns; i++) {
+      task.run();
+    }
+    verify(cacheMock, times(numberOfRuns)).cleanUp();
+  }
+
+  @Test
+  public void testCleanupTaskToString() {
+    CleanupTask task = new CleanupTask(null, SOME_PLUGIN_NAME);
+    assertThat(task.toString())
+        .isEqualTo(String.format("[%s] Clean up expired file based websessions", SOME_PLUGIN_NAME));
+  }
+
+  @Test
+  public void testCleanupTaskIsScheduledOnStart() {
+    cleaner.start();
+    verify(executorMock, times(1))
+        .scheduleAtFixedRate(
+            isA(CleanupTask.class), eq(1000l), eq(CLEANUP_INTERVAL), eq(TimeUnit.MILLISECONDS));
+  }
+
+  @Test
+  public void testCleanupTaskIsCancelledOnStop() {
+    cleaner.start();
+    cleaner.stop();
+    verify(scheduledFutureMock, times(1)).cancel(true);
+  }
+
+  @Test
+  public void testCleanupTaskIsCancelledOnlyOnce() {
+    cleaner.start();
+    cleaner.stop();
+    cleaner.stop();
+    verify(scheduledFutureMock, times(1)).cancel(true);
+  }
+}