Initial version of the heartbeat plugin
diff --git a/.buckconfig b/.buckconfig
new file mode 100644
index 0000000..48f1c0a
--- /dev/null
+++ b/.buckconfig
@@ -0,0 +1,15 @@
+[alias]
+  heartbeat = //:heartbeat
+  plugin = //:heartbeat
+  src = //:heartbeat-sources
+
+[java]
+  src_roots = java, resources
+
+[project]
+  ignore = .git, eclipse-out
+
+[cache]
+  mode = dir
+  dir = buck-out/cache
+
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..4c23cda
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,8 @@
+/.buckversion
+/.classpath
+/.project
+/.settings/
+/.watchmanconfig
+/buck-out/
+/bucklets
+/eclipse-out
diff --git a/BUCK b/BUCK
new file mode 100644
index 0000000..fa50925
--- /dev/null
+++ b/BUCK
@@ -0,0 +1,37 @@
+include_defs('//bucklets/gerrit_plugin.bucklet')
+include_defs('//bucklets/java_sources.bucklet')
+
+SOURCES = glob(['src/main/java/**/*.java'])
+RESOURCES = glob(['src/main/resources/**/*'])
+TEST_DEPS = GERRIT_PLUGIN_API + [
+  ':heartbeat__plugin',
+  '//lib:junit',
+  '//lib/easymock:easymock',
+]
+
+gerrit_plugin(
+  name = 'heartbeat',
+  srcs = SOURCES,
+  resources = RESOURCES,
+  manifest_entries = [
+    'Gerrit-PluginName: heartbeat',
+    'Gerrit-Module: com.ericsson.gerrit.plugins.heartbeat.HeartbeatModule',
+  ],
+)
+
+java_library(
+  name = 'classpath',
+  deps = TEST_DEPS,
+)
+
+java_test(
+  name = 'heartbeat_tests',
+  labels = ['heartbeat'],
+  srcs = glob(['src/test/java/**/*.java']),
+  deps = TEST_DEPS,
+)
+
+java_sources(
+  name = 'heartbeat-sources',
+  srcs = SOURCES + RESOURCES,
+)
diff --git a/lib/BUCK b/lib/BUCK
new file mode 100644
index 0000000..1d9dd26
--- /dev/null
+++ b/lib/BUCK
@@ -0,0 +1,17 @@
+include_defs('//bucklets/maven_jar.bucklet')
+
+maven_jar(
+  name = 'junit',
+  id = 'junit:junit:4.10',
+  sha1 = 'e4f1766ce7404a08f45d859fb9c226fc9e41a861',
+  license = 'DO_NOT_DISTRIBUTE',
+  deps = [':hamcrest-core'],
+)
+
+maven_jar(
+  name = 'hamcrest-core',
+  id = 'org.hamcrest:hamcrest-core:1.3',
+  sha1 = '42a25dc3219429f0e5d060061f71acb49bf010a0',
+  license = 'DO_NOT_DISTRIBUTE',
+  visibility = ['//lib:junit'],
+)
diff --git a/lib/easymock/BUCK b/lib/easymock/BUCK
new file mode 100644
index 0000000..2a774a9
--- /dev/null
+++ b/lib/easymock/BUCK
@@ -0,0 +1,29 @@
+include_defs('//bucklets/maven_jar.bucklet')
+
+maven_jar(
+  name = 'easymock',
+  id = 'org.easymock:easymock:3.2',
+  sha1 = '00c82f7fa3ef377d8954b1db25123944b5af2ba4',
+  license = 'DO_NOT_DISTRIBUTE',
+  deps = [
+    ':cglib-2_2',
+    ':objenesis',
+  ],
+)
+
+maven_jar(
+  name = 'cglib-2_2',
+  id = 'cglib:cglib-nodep:2.2.2',
+  sha1 = '00d456bb230c70c0b95c76fb28e429d42f275941',
+  license = 'DO_NOT_DISTRIBUTE',
+  attach_source = False,
+)
+
+maven_jar(
+  name = 'objenesis',
+  id = 'org.objenesis:objenesis:1.2',
+  sha1 = 'bfcb0539a071a4c5a30690388903ac48c0667f2a',
+  license = 'DO_NOT_DISTRIBUTE',
+  visibility = ['//lib/powermock:powermock-reflect'],
+  attach_source = False,
+)
diff --git a/lib/gerrit/BUCK b/lib/gerrit/BUCK
new file mode 100644
index 0000000..488801f
--- /dev/null
+++ b/lib/gerrit/BUCK
@@ -0,0 +1,14 @@
+include_defs('//bucklets/maven_jar.bucklet')
+
+VER = '2.12'
+REPO = MAVEN_CENTRAL
+
+maven_jar(
+  name = 'plugin-api',
+  id = 'com.google.gerrit:gerrit-plugin-api:' + VER,
+  sha1 ='8ce1f6e65078bbcf03a1758f96b3ebca19b7fe3c',
+  license = 'Apache2.0',
+  attach_source = False,
+  repository = REPO,
+)
+
diff --git a/src/main/java/com/ericsson/gerrit/plugins/heartbeat/HeartbeatConfig.java b/src/main/java/com/ericsson/gerrit/plugins/heartbeat/HeartbeatConfig.java
new file mode 100644
index 0000000..9fd659c
--- /dev/null
+++ b/src/main/java/com/ericsson/gerrit/plugins/heartbeat/HeartbeatConfig.java
@@ -0,0 +1,105 @@
+// Copyright (C) 2013 Ericsson
+//
+// 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.ericsson.gerrit.plugins.heartbeat;
+
+import java.io.File;
+import java.io.IOException;
+
+import org.eclipse.jgit.errors.ConfigInvalidException;
+import org.eclipse.jgit.storage.file.FileBasedConfig;
+import org.eclipse.jgit.util.FS;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.base.Strings;
+import com.google.gerrit.reviewdb.client.RefNames;
+import com.google.gerrit.server.config.AllProjectsNameProvider;
+import com.google.gerrit.server.config.SitePaths;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+
+/**
+ * Plugin-specific config file data loader and holder.
+ */
+@Singleton
+public class HeartbeatConfig {
+  private static final Logger logger = LoggerFactory.getLogger(HeartbeatConfig.class);
+
+  public static final int DEFAULT_DELAY = 15000;
+  public static final String DEFAULT_REF = RefNames.REFS_CONFIG;
+  public static final String DELAY_KEY = "delay";
+  public static final String FILENAME = "heartbeat.config";
+  public static final String HEARTBEAT_SECTION = "heartbeat";
+  public static final String PROJECT_KEY = "project";
+  public static final String REF_KEY = "ref";
+
+  private int delay = DEFAULT_DELAY;
+  private String project;
+  private String ref = DEFAULT_REF;
+
+  /**
+   * Constructor that loads the config from the plugin file.
+   *
+   * @param site the site path
+   * @param allProjects the provided name for All-Projects project
+   * @throws ConfigInvalidException If loaded config from plugin file is invalid
+   * @throws IOException If there is an issue with loading the config from plugin file
+   */
+  @Inject
+  public HeartbeatConfig(SitePaths site, AllProjectsNameProvider allProjects)
+      throws ConfigInvalidException, IOException {
+    project = allProjects.get().toString();
+    load(site.etc_dir.resolve(FILENAME).toFile());
+  }
+
+  private void load(File configPath) throws ConfigInvalidException, IOException {
+    FileBasedConfig cfg = new FileBasedConfig(configPath, FS.DETECTED);
+    if (!cfg.getFile().exists() || cfg.getFile().length() == 0) {
+      logger.debug("No " + cfg.getFile() + " or empty; using all default values");
+      return;
+    }
+
+    try {
+      cfg.load();
+    } catch (ConfigInvalidException e) {
+      throw new ConfigInvalidException(String.format(
+          "Config file %s is invalid: %s", cfg.getFile(), e.getMessage()), e);
+    } catch (IOException e) {
+      throw new IOException(String.format(
+          "Cannot read %s: %s", cfg.getFile(),  e.getMessage()), e);
+    }
+    delay = cfg.getInt(HEARTBEAT_SECTION, DELAY_KEY, DEFAULT_DELAY);
+    String configuredProject = cfg.getString(HEARTBEAT_SECTION, null, PROJECT_KEY);
+    if (!Strings.isNullOrEmpty(configuredProject)) {
+      project = configuredProject;
+    }
+    String configuredRef = cfg.getString(HEARTBEAT_SECTION, null, REF_KEY);
+    if (!Strings.isNullOrEmpty(configuredRef)) {
+      ref = configuredRef;
+    }
+  }
+
+  public int getDelay() {
+    return delay;
+  }
+
+  public String getProject() {
+    return project;
+  }
+
+  public String getRef() {
+    return ref;
+  }
+}
diff --git a/src/main/java/com/ericsson/gerrit/plugins/heartbeat/HeartbeatDaemon.java b/src/main/java/com/ericsson/gerrit/plugins/heartbeat/HeartbeatDaemon.java
new file mode 100644
index 0000000..7a66b0f
--- /dev/null
+++ b/src/main/java/com/ericsson/gerrit/plugins/heartbeat/HeartbeatDaemon.java
@@ -0,0 +1,77 @@
+// Copyright (C) 2013 Ericsson
+//
+// 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.ericsson.gerrit.plugins.heartbeat;
+
+import com.google.gerrit.common.EventDispatcher;
+import com.google.gerrit.extensions.events.LifecycleListener;
+import com.google.gerrit.reviewdb.client.Branch;
+import com.google.gerrit.reviewdb.client.Project;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Timer;
+import java.util.TimerTask;
+
+/**
+ * Timer-based daemon doing the actual heartbeat task.
+ */
+@Singleton
+public class HeartbeatDaemon implements LifecycleListener {
+
+  private static final Logger logger = LoggerFactory.getLogger(HeartbeatDaemon.class);
+  private static final String HEARTBEAT_THREAD_NAME = "StreamEventHeartbeat";
+  private final EventDispatcher dispatcher;
+  private final HeartbeatConfig config;
+  private final Timer timer;
+  private final Branch.NameKey branch;
+
+  /**
+   * Constructor that sets the heartbeat timer.
+   *
+   * @param dispatcher the event dispatcher
+   * @param config the plugin config
+   */
+  @Inject
+  public HeartbeatDaemon(EventDispatcher dispatcher, HeartbeatConfig config) {
+    this.dispatcher = dispatcher;
+    this.config = config;
+    branch = new Branch.NameKey(Project.NameKey.parse(config.getProject()),
+        config.getRef());
+    timer = new Timer(HEARTBEAT_THREAD_NAME, true);
+  }
+
+  @Override
+  public void start() {
+    timer.schedule(new HeartbeatTask(), 0, config.getDelay());
+    logger.info("Initialized to send heartbeat event every " + config.getDelay()
+        + " milliseconds");
+  }
+
+  @Override
+  public void stop() {
+    timer.cancel();
+    logger.info("Stopped sending heartbeat event");
+  }
+
+  private class HeartbeatTask extends TimerTask {
+    @Override
+    public void run() {
+      dispatcher.postEvent(branch, new HeartbeatEvent());
+    }
+  }
+}
diff --git a/src/main/java/com/ericsson/gerrit/plugins/heartbeat/HeartbeatEvent.java b/src/main/java/com/ericsson/gerrit/plugins/heartbeat/HeartbeatEvent.java
new file mode 100644
index 0000000..c834d76
--- /dev/null
+++ b/src/main/java/com/ericsson/gerrit/plugins/heartbeat/HeartbeatEvent.java
@@ -0,0 +1,35 @@
+// Copyright (C) 2013 Ericsson
+//
+// 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.ericsson.gerrit.plugins.heartbeat;
+
+import com.google.gerrit.server.events.Event;
+
+/**
+ * Simple event implementation for plugin purposes.
+ */
+public class HeartbeatEvent extends Event {
+
+  /**
+   * Constructor setting our custom event.
+   */
+  public HeartbeatEvent() {
+    super("heartbeat");
+  }
+
+  @Override
+  public String getType() {
+    return type;
+  }
+}
diff --git a/src/main/java/com/ericsson/gerrit/plugins/heartbeat/HeartbeatModule.java b/src/main/java/com/ericsson/gerrit/plugins/heartbeat/HeartbeatModule.java
new file mode 100644
index 0000000..09f02f4
--- /dev/null
+++ b/src/main/java/com/ericsson/gerrit/plugins/heartbeat/HeartbeatModule.java
@@ -0,0 +1,28 @@
+// Copyright (C) 2013 Ericsson
+//
+// 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.ericsson.gerrit.plugins.heartbeat;
+
+import com.google.gerrit.lifecycle.LifecycleModule;
+
+/**
+ * Listener bound to plugin's daemon implementation.
+ */
+public class HeartbeatModule extends LifecycleModule {
+
+  @Override
+  protected void configure() {
+    listener().to(HeartbeatDaemon.class);
+  }
+}
diff --git a/src/main/resources/Documentation/about.md b/src/main/resources/Documentation/about.md
new file mode 100644
index 0000000..901bad1
--- /dev/null
+++ b/src/main/resources/Documentation/about.md
@@ -0,0 +1,5 @@
+Plugin that sends heartbeat stream event.
+
+Stream events are associated with a project ref for visibility purposes so the heartbeat is no different. The defaut settings will send an event on the refs/meta/config ref of the All-Projects project every 15 seconds.
+
+To configure heartbeat, refer to [config](config.html) documentation.
diff --git a/src/main/resources/Documentation/build.md b/src/main/resources/Documentation/build.md
new file mode 100644
index 0000000..e013ce2
--- /dev/null
+++ b/src/main/resources/Documentation/build.md
@@ -0,0 +1,90 @@
+Build
+=====
+
+This plugin is built with Buck.
+
+Two build modes are supported: Standalone and in Gerrit tree. Standalone
+build mode is recommended, as this mode doesn't require local Gerrit
+tree to exist.
+
+Build standalone
+----------------
+
+Clone bucklets library:
+
+```
+  git clone https://gerrit.googlesource.com/bucklets
+
+```
+and link it to heartbeat directory:
+
+```
+  cd heartbeat && ln -s ../bucklets .
+```
+
+Add link to the .buckversion file:
+
+```
+  cd heartbeat && ln -s bucklets/buckversion .buckversion
+```
+
+Add link to the .watchmanconfig file:
+
+```
+  cd heartbeat && ln -s bucklets/watchmanconfig .watchmanconfig
+```
+
+To build the plugin, issue the following command:
+
+```
+  buck build plugin
+```
+
+The output is created in
+
+```
+  buck-out/gen/heartbeat.jar
+```
+
+This project can be imported into the Eclipse IDE:
+
+```
+  ./bucklets/tools/eclipse.py
+```
+
+To execute the tests run:
+
+```
+  buck test
+```
+
+Build in Gerrit tree
+--------------------
+
+Clone or link this plugin to the plugins directory of Gerrit's source
+tree, and issue the command:
+
+```
+  buck build plugins/heartbeat
+```
+
+The output is created in
+
+```
+  buck-out/gen/plugins/heartbeat/heartbeat.jar
+```
+
+This project can be imported into the Eclipse IDE:
+
+```
+  ./tools/eclipse/project.py
+```
+
+To execute the tests run:
+
+```
+  buck test --include heartbeat
+```
+
+How to build the Gerrit Plugin API is described in the [Gerrit
+documentation](../../../Documentation/dev-buck.html#_extension_and_plugin_api_jar_files).
diff --git a/src/main/resources/Documentation/config.md b/src/main/resources/Documentation/config.md
new file mode 100644
index 0000000..cf24277
--- /dev/null
+++ b/src/main/resources/Documentation/config.md
@@ -0,0 +1,14 @@
+Heartbeat Configuration
+=========================
+File `heartbeat.config`
+-------------------------
+
+The optional file `$site_path/etc/heartbeat.config` is a Git-style config file that controls the heartbeat settings for the heartbeat plugin.
+
+The file is composed of one `heartbeat` section with the following optional parameters.
+
+delay: time in milliseconds to send heartbeat (defaults to 15000)
+
+project: project on which to send event (default is All-Projets)
+
+ref: ref on which to send event (default is refs/meta/config)
diff --git a/src/test/java/com/ericsson/gerrit/plugins/heartbeat/HeartbeatConfigTest.java b/src/test/java/com/ericsson/gerrit/plugins/heartbeat/HeartbeatConfigTest.java
new file mode 100644
index 0000000..e23ec76
--- /dev/null
+++ b/src/test/java/com/ericsson/gerrit/plugins/heartbeat/HeartbeatConfigTest.java
@@ -0,0 +1,142 @@
+// Copyright (C) 2013 Ericsson
+//
+// 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.ericsson.gerrit.plugins.heartbeat;
+
+import static org.easymock.EasyMock.createMock;
+import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.replay;
+import static org.junit.Assert.assertEquals;
+
+import com.google.gerrit.server.config.AllProjectsName;
+import com.google.gerrit.server.config.AllProjectsNameProvider;
+import com.google.gerrit.server.config.SitePaths;
+import com.google.gwtorm.client.KeyUtil;
+import com.google.gwtorm.server.StandardKeyEncoder;
+
+import org.eclipse.jgit.errors.ConfigInvalidException;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.PrintWriter;
+
+public class HeartbeatConfigTest {
+
+  static {
+    KeyUtil.setEncoderImpl(new StandardKeyEncoder());
+  }
+
+  private SitePaths sitePaths;
+  private AllProjectsNameProvider allProjectsNameProviderMock;
+  private HeartbeatConfig heartbeatConfig;
+
+  @Before
+  public void setUp() throws Exception {
+    File tmpSiteFolder = File.createTempFile("gerrit-site", null);
+    tmpSiteFolder.delete();
+    tmpSiteFolder.mkdirs();
+    sitePaths = new SitePaths(tmpSiteFolder.toPath());
+    allProjectsNameProviderMock = createMock(AllProjectsNameProvider.class);
+    expect(allProjectsNameProviderMock.get()).andReturn(
+        new AllProjectsName("AllProjectName")).anyTimes();
+    replay(allProjectsNameProviderMock);
+  }
+
+  @After
+  public void tearDown() throws Exception {
+    if(sitePaths != null){
+      deleteFolder(sitePaths.site_path.toFile());
+    }
+  }
+
+  @Test
+  public void shouldBeDefaultValuesWhenConfigFileDoesNotExist()
+      throws ConfigInvalidException, IOException {
+    heartbeatConfig = new HeartbeatConfig(sitePaths, allProjectsNameProviderMock);
+    assertEquals(HeartbeatConfig.DEFAULT_DELAY, heartbeatConfig.getDelay());
+    assertEquals(allProjectsNameProviderMock.get().get(), heartbeatConfig.getProject());
+    assertEquals(HeartbeatConfig.DEFAULT_REF, heartbeatConfig.getRef());
+  }
+
+  @Test
+  public void shouldReturnDelayConfiguredInFile()
+      throws ConfigInvalidException, IOException {
+    try (PrintWriter writer = getWriterForConfig()) {
+      writer.println("[" + HeartbeatConfig.HEARTBEAT_SECTION + "]");
+      writer.println(HeartbeatConfig.DELAY_KEY + " = 1234");
+    }
+    heartbeatConfig = new HeartbeatConfig(sitePaths, allProjectsNameProviderMock);
+    assertEquals(1234, heartbeatConfig.getDelay());
+    assertEquals(allProjectsNameProviderMock.get().get(), heartbeatConfig.getProject());
+    assertEquals(HeartbeatConfig.DEFAULT_REF, heartbeatConfig.getRef());
+  }
+
+  @Test
+  public void shouldReturnProjectConfiguredInFile()
+      throws ConfigInvalidException, IOException {
+    try (PrintWriter writer = getWriterForConfig()) {
+      writer.println("[" + HeartbeatConfig.HEARTBEAT_SECTION + "]");
+      writer.println(HeartbeatConfig.PROJECT_KEY + " = someProject");
+    }
+    heartbeatConfig = new HeartbeatConfig(sitePaths, allProjectsNameProviderMock);
+    assertEquals(HeartbeatConfig.DEFAULT_DELAY, heartbeatConfig.getDelay());
+    assertEquals("someProject", heartbeatConfig.getProject());
+    assertEquals(HeartbeatConfig.DEFAULT_REF, heartbeatConfig.getRef());
+  }
+
+  @Test
+  public void shouldReturnRefConfiguredInFile()
+      throws ConfigInvalidException, IOException {
+    try (PrintWriter writer = getWriterForConfig()) {
+      writer.println("["+HeartbeatConfig.HEARTBEAT_SECTION+"]");
+      writer.println(HeartbeatConfig.REF_KEY +  "= someRef");
+    }
+    heartbeatConfig = new HeartbeatConfig(sitePaths, allProjectsNameProviderMock);
+    assertEquals(HeartbeatConfig.DEFAULT_DELAY, heartbeatConfig.getDelay());
+    assertEquals(allProjectsNameProviderMock.get().get(), heartbeatConfig.getProject());
+    assertEquals("someRef", heartbeatConfig.getRef());
+  }
+
+  @Test(expected=ConfigInvalidException.class)
+  public void shouldThrowExceptionWhenFileHasBadFormat()
+      throws ConfigInvalidException, IOException {
+    try (PrintWriter writer = getWriterForConfig()) {
+      writer.println("[" + HeartbeatConfig.HEARTBEAT_SECTION);
+    }
+    heartbeatConfig = new HeartbeatConfig(sitePaths, allProjectsNameProviderMock);
+  }
+
+  private PrintWriter getWriterForConfig() throws FileNotFoundException{
+    sitePaths.etc_dir.toFile().mkdirs();
+    return new PrintWriter(sitePaths.etc_dir.resolve(HeartbeatConfig.FILENAME).toFile());
+  }
+
+  private void deleteFolder(File folder) {
+    File[] files = folder.listFiles();
+    if(files!=null) { //some JVMs return null for empty dirs
+      for(File f: files) {
+        if(f.isDirectory()) {
+          deleteFolder(f);
+        } else {
+          f.delete();
+        }
+      }
+    }
+    folder.delete();
+  }
+}
diff --git a/src/test/java/com/ericsson/gerrit/plugins/heartbeat/HeartbeatDaemonTest.java b/src/test/java/com/ericsson/gerrit/plugins/heartbeat/HeartbeatDaemonTest.java
new file mode 100644
index 0000000..45a2bf8
--- /dev/null
+++ b/src/test/java/com/ericsson/gerrit/plugins/heartbeat/HeartbeatDaemonTest.java
@@ -0,0 +1,73 @@
+// Copyright (C) 2015 Ericsson
+//
+// 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.ericsson.gerrit.plugins.heartbeat;
+
+import static org.easymock.EasyMock.createMock;
+import static org.easymock.EasyMock.createNiceMock;
+import static org.easymock.EasyMock.eq;
+import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.expectLastCall;
+import static org.easymock.EasyMock.isA;
+import static org.easymock.EasyMock.replay;
+import static org.easymock.EasyMock.reset;
+import static org.easymock.EasyMock.verify;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import com.google.gerrit.common.EventDispatcher;
+import com.google.gerrit.reviewdb.client.Branch;
+import com.google.gerrit.reviewdb.client.Project;
+import com.google.gwtorm.client.KeyUtil;
+import com.google.gwtorm.server.StandardKeyEncoder;
+
+public class HeartbeatDaemonTest {
+
+  static {
+    KeyUtil.setEncoderImpl(new StandardKeyEncoder());
+  }
+
+  private EventDispatcher eventDispatcherMock;
+  private HeartbeatConfig heartbeatConfigMock;
+  private HeartbeatDaemon heartbeatDaemon;
+
+  @Before
+  public void setUp() throws Exception {
+    eventDispatcherMock = createNiceMock(EventDispatcher.class);
+    replay(eventDispatcherMock);
+    heartbeatConfigMock = createMock(HeartbeatConfig.class);
+    expect(heartbeatConfigMock.getDelay()).andReturn(1).anyTimes();
+    expect(heartbeatConfigMock.getProject()).andReturn("someProject").anyTimes();
+    expect(heartbeatConfigMock.getRef()).andReturn("someRef").anyTimes();
+    replay(heartbeatConfigMock);
+    heartbeatDaemon = new HeartbeatDaemon(eventDispatcherMock, heartbeatConfigMock);
+  }
+
+  @Test
+  public void thatDaemonSendsHeartbeatEvents() throws InterruptedException {
+    reset(eventDispatcherMock);
+    eventDispatcherMock.postEvent(
+        eq(new Branch.NameKey(Project.NameKey.parse(heartbeatConfigMock
+            .getProject()), heartbeatConfigMock.getRef())),
+        isA(HeartbeatEvent.class));
+    expectLastCall().atLeastOnce();
+    replay(eventDispatcherMock);
+
+    heartbeatDaemon.start();
+    Thread.sleep(500);
+    verify(eventDispatcherMock);
+    heartbeatDaemon.stop();
+  }
+}
diff --git a/src/test/java/com/ericsson/gerrit/plugins/heartbeat/HeartbeatEventTest.java b/src/test/java/com/ericsson/gerrit/plugins/heartbeat/HeartbeatEventTest.java
new file mode 100644
index 0000000..591bc7c
--- /dev/null
+++ b/src/test/java/com/ericsson/gerrit/plugins/heartbeat/HeartbeatEventTest.java
@@ -0,0 +1,27 @@
+// Copyright (C) 2013 Ericsson
+//
+// 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.ericsson.gerrit.plugins.heartbeat;
+
+import static org.junit.Assert.assertEquals;
+
+import org.junit.Test;
+
+public class HeartbeatEventTest {
+
+  @Test
+  public void shouldBeOfTypeHearbeatAsAnEvent() {
+    assertEquals("heartbeat", new HeartbeatEvent().getType());
+  }
+}