From 528ef6f788bc06880b5adb7eb642ccb6865efb57 Mon Sep 17 00:00:00 2001 From: Stephane Gamard Date: Wed, 13 Aug 2014 18:53:18 +0200 Subject: [PATCH] Added test for MonitoredProcess Class --- .../org/sonar/process/MonitoredProcess.java | 37 ++++- .../java/org/sonar/process/DummyProcess.java | 84 +++++++++++ .../sonar/process/MonitoredProcessTest.java | 136 ++++++++++++++++++ 3 files changed, 252 insertions(+), 5 deletions(-) create mode 100644 server/process/sonar-process/src/test/java/org/sonar/process/DummyProcess.java create mode 100644 server/process/sonar-process/src/test/java/org/sonar/process/MonitoredProcessTest.java diff --git a/server/process/sonar-process/src/main/java/org/sonar/process/MonitoredProcess.java b/server/process/sonar-process/src/main/java/org/sonar/process/MonitoredProcess.java index b620bec8b88..49ed4618ed0 100644 --- a/server/process/sonar-process/src/main/java/org/sonar/process/MonitoredProcess.java +++ b/server/process/sonar-process/src/main/java/org/sonar/process/MonitoredProcess.java @@ -32,15 +32,19 @@ public abstract class MonitoredProcess implements ProcessMXBean { public static final String NAME_PROPERTY = "pName"; private static final long AUTOKILL_TIMEOUT_MS = 30000L; - private static final long AUTOKILL_CHECK_DELAY_MS = 5000L; + private static final long AUTOKILL_CHECK_DELAY_MS = 2000L; public static final String MISSING_NAME_ARGUMENT = "Missing Name argument"; private Long lastPing; private final String name; + private boolean terminated = false; + private long timeout = AUTOKILL_TIMEOUT_MS; + private long checkDelay = AUTOKILL_CHECK_DELAY_MS; + protected final Props props; private ScheduledFuture pingTask = null; - private ScheduledExecutorService monitor; + private ScheduledExecutorService monitor; private final boolean isMonitored; protected MonitoredProcess(Props props) throws Exception { @@ -61,6 +65,24 @@ public abstract class MonitoredProcess implements ProcessMXBean { ProcessUtils.addSelfShutdownHook(this); } + public MonitoredProcess setTimeout(long timeout) { + this.timeout = timeout; + return this; + } + + private long getTimeout() { + return timeout; + } + + public MonitoredProcess setCheckDelay(long checkDelay) { + this.checkDelay = checkDelay; + return this; + } + + private long getCheckDelay() { + return checkDelay; + } + public final void start() { if (monitor != null) { throw new IllegalStateException("Already started"); @@ -82,9 +104,9 @@ public abstract class MonitoredProcess implements ProcessMXBean { @Override public void run() { long time = System.currentTimeMillis(); - if (time - lastPing > AUTOKILL_TIMEOUT_MS) { + if (time - lastPing > getTimeout()) { LoggerFactory.getLogger(getClass()).info(String.format( - "Did not receive any ping during %d seconds. Shutting down.", AUTOKILL_TIMEOUT_MS / 1000)); + "Did not receive any ping during %d seconds. Shutting down.", getTimeout() / 1000)); if (isMonitored) { terminate(); } @@ -93,7 +115,7 @@ public abstract class MonitoredProcess implements ProcessMXBean { }; lastPing = System.currentTimeMillis(); monitor = Executors.newScheduledThreadPool(1); - pingTask = monitor.scheduleAtFixedRate(breakOnMissingPing, AUTOKILL_CHECK_DELAY_MS, AUTOKILL_CHECK_DELAY_MS, TimeUnit.MILLISECONDS); + pingTask = monitor.scheduleAtFixedRate(breakOnMissingPing, getCheckDelay(), getCheckDelay(), TimeUnit.MILLISECONDS); } @Override @@ -120,9 +142,14 @@ public abstract class MonitoredProcess implements ProcessMXBean { // do not propagate exception } logger.debug("Process[{}] terminated", name); + terminated = true; } } + public boolean isTerminated() { + return terminated && monitor == null; + } + @Override public final boolean isReady() { try { diff --git a/server/process/sonar-process/src/test/java/org/sonar/process/DummyProcess.java b/server/process/sonar-process/src/test/java/org/sonar/process/DummyProcess.java new file mode 100644 index 00000000000..15d3ec7af09 --- /dev/null +++ b/server/process/sonar-process/src/test/java/org/sonar/process/DummyProcess.java @@ -0,0 +1,84 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * SonarQube is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.process; + +import org.apache.commons.io.FileUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.File; +import java.util.Properties; + +public class DummyProcess extends MonitoredProcess { + + public static final String NAME = "DummyName"; + public static final String CHECKFILE_NAME = "check.tmp"; + + private static final Logger LOGGER = LoggerFactory.getLogger(DummyProcess.class); + + private boolean isReady = false; + private boolean isRunning = true; + private File checkFile; + + + protected DummyProcess(Props props, boolean monitored) throws Exception { + super(props, monitored); + } + + protected DummyProcess(Props props) throws Exception { + this(props, false); + } + + @Override + protected void doStart() { + isReady = true; + checkFile = new File(FileUtils.getTempDirectory(), CHECKFILE_NAME); + LOGGER.info("Starting Dummy OK Process"); + while (isRunning) { + try { + Thread.sleep(100); + } catch (InterruptedException e) { + isRunning = false; + } + } + } + + @Override + protected void doTerminate() { + LOGGER.info("Terminating Dummy OK Process"); + this.isRunning = false; + } + + @Override + protected boolean doIsReady() { + return isReady; + } + + public static void main(String[] args) throws Exception { + Props props = new Props(new Properties()); + props.set(MonitoredProcess.NAME_PROPERTY, DummyProcess.class.getSimpleName()); + new DummyProcess(props).start(); + System.exit(1); + } + + public File getCheckFile() { + return checkFile; + } +} diff --git a/server/process/sonar-process/src/test/java/org/sonar/process/MonitoredProcessTest.java b/server/process/sonar-process/src/test/java/org/sonar/process/MonitoredProcessTest.java new file mode 100644 index 00000000000..515d3c1ea3d --- /dev/null +++ b/server/process/sonar-process/src/test/java/org/sonar/process/MonitoredProcessTest.java @@ -0,0 +1,136 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * SonarQube is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.process; + +import org.junit.Test; + +import java.util.Properties; + +import static org.fest.assertions.Assertions.assertThat; +import static org.fest.assertions.Fail.fail; + +public class MonitoredProcessTest { + + @Test + public void fail_on_missing_name() throws Exception { + Properties properties = new Properties(); + + try { + new DummyProcess(new Props(properties), true); + fail(); + } catch (Exception e) { + assertThat(e.getMessage()).isEqualTo("Missing Name argument"); + } + + properties.setProperty(MonitoredProcess.NAME_PROPERTY, DummyProcess.NAME); + DummyProcess dummyProcess = new DummyProcess(new Props(properties), true); + assertThat(dummyProcess).isNotNull(); + } + + @Test(timeout = 3000L) + public void monitor_dies_when_no_pings() throws Exception { + Properties properties = new Properties(); + properties.setProperty(MonitoredProcess.NAME_PROPERTY, DummyProcess.NAME); + final DummyProcess dummyProcess = new DummyProcess(new Props(properties), true); + dummyProcess.setTimeout(1000L) + .setCheckDelay(500L); + Thread process = new Thread(new Runnable() { + @Override + public void run() { + dummyProcess.start(); + } + }); + assertThat(dummyProcess.isReady()).isFalse(); + assertThat(dummyProcess.isTerminated()).isFalse(); + process.start(); + Thread.sleep(100); + assertThat(dummyProcess.isReady()).isTrue(); + assertThat(dummyProcess.isTerminated()).isFalse(); + process.join(); + assertThat(dummyProcess.isReady()).isTrue(); + assertThat(dummyProcess.isTerminated()).isTrue(); + + assertThat(dummyProcess.getCheckFile()).isNotNull(); + assertThat(dummyProcess.getCheckFile().getName()).isEqualTo(DummyProcess.CHECKFILE_NAME); + } + + @Test(timeout = 3000L) + public void monitor_dies_after_stopping_to_ping() throws Exception { + Properties properties = new Properties(); + properties.setProperty(MonitoredProcess.NAME_PROPERTY, DummyProcess.NAME); + final DummyProcess dummyProcess = new DummyProcess(new Props(properties), true); + dummyProcess.setTimeout(1000L) + .setCheckDelay(500L); + Thread process = new Thread(new Runnable() { + @Override + public void run() { + dummyProcess.start(); + } + }); + assertThat(dummyProcess.isReady()).isFalse(); + assertThat(dummyProcess.isTerminated()).isFalse(); + process.start(); + Thread.sleep(100); + + int count = 0; + for (int i = 0; i < 3; i++) { + dummyProcess.ping(); + assertThat(dummyProcess.isReady()).isTrue(); + assertThat(dummyProcess.isTerminated()).isFalse(); + Thread.sleep(300); + count++; + } + assertThat(count).isEqualTo(3); + process.join(); + assertThat(dummyProcess.isReady()).isTrue(); + assertThat(dummyProcess.isTerminated()).isTrue(); + + assertThat(dummyProcess.getCheckFile()).isNotNull(); + assertThat(dummyProcess.getCheckFile().getName()).isEqualTo(DummyProcess.CHECKFILE_NAME); + } + + @Test(timeout = 3000L) + public void monitor_explicitely_shutdown() throws Exception { + Properties properties = new Properties(); + properties.setProperty(MonitoredProcess.NAME_PROPERTY, DummyProcess.NAME); + final DummyProcess dummyProcess = new DummyProcess(new Props(properties), true); + dummyProcess.setTimeout(Long.MAX_VALUE).setCheckDelay(500L); + Thread process = new Thread(new Runnable() { + @Override + public void run() { + dummyProcess.start(); + } + }); + assertThat(dummyProcess.isReady()).isFalse(); + assertThat(dummyProcess.isTerminated()).isFalse(); + process.start(); + Thread.sleep(100); + assertThat(dummyProcess.isReady()).isTrue(); + assertThat(dummyProcess.isTerminated()).isFalse(); + dummyProcess.terminate(); + Thread.sleep(100); + assertThat(dummyProcess.isReady()).isTrue(); + assertThat(dummyProcess.isTerminated()).isTrue(); + + assertThat(dummyProcess.getCheckFile()).isNotNull(); + assertThat(dummyProcess.getCheckFile().getName()).isEqualTo(DummyProcess.CHECKFILE_NAME); + } + +} \ No newline at end of file -- 2.39.5