/*
* SonarQube
* Copyright (C) 2009-2017 SonarSource SA
* mailto:info AT sonarsource DOT com
*
* This program 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.
*
* This program 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 java.io.IOException;
import org.apache.commons.io.FileUtils;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.DisableOnDebug;
import org.junit.rules.TemporaryFolder;
import org.junit.rules.TestRule;
import org.junit.rules.Timeout;
import org.sonar.process.Lifecycle.State;
import org.sonar.process.test.StandardProcess;
import java.io.File;
import java.util.Properties;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.Assert.fail;
import static org.mockito.Mockito.mock;
import static org.sonar.process.ProcessEntryPoint.PROPERTY_PROCESS_INDEX;
import static org.sonar.process.ProcessEntryPoint.PROPERTY_PROCESS_KEY;
import static org.sonar.process.ProcessEntryPoint.PROPERTY_SHARED_PATH;
import static org.sonar.process.ProcessEntryPoint.PROPERTY_TERMINATION_TIMEOUT;
public class ProcessEntryPointTest {
private SystemExit exit = mock(SystemExit.class);
@Rule
public TestRule safeguardTimeout = new DisableOnDebug(Timeout.seconds(60));
@Rule
public TemporaryFolder temp = new TemporaryFolder();
ProcessCommands commands = mock(ProcessCommands.class);
@Test
public void load_properties_from_file() throws Exception {
File propsFile = temp.newFile();
FileUtils.write(propsFile, "sonar.foo=bar\nprocess.key=web\nprocess.index=1\nprocess.sharedDir=" + temp.newFolder().getAbsolutePath().replaceAll("\\\\", "/"));
ProcessEntryPoint entryPoint = ProcessEntryPoint.createForArguments(new String[] {propsFile.getAbsolutePath()});
assertThat(entryPoint.getProps().value("sonar.foo")).isEqualTo("bar");
assertThat(entryPoint.getProps().value("process.key")).isEqualTo("web");
}
@Test
public void test_initial_state() throws Exception {
Props props = createProps();
ProcessEntryPoint entryPoint = new ProcessEntryPoint(props, exit, commands);
assertThat(entryPoint.getProps()).isSameAs(props);
assertThat(entryPoint.isStarted()).isFalse();
assertThat(entryPoint.getState()).isEqualTo(State.INIT);
}
@Test
public void fail_to_launch_multiple_times() throws IOException {
Props props = createProps();
ProcessEntryPoint entryPoint = new ProcessEntryPoint(props, exit, commands);
entryPoint.launch(new NoopProcess());
try {
entryPoint.launch(new NoopProcess());
fail();
} catch (IllegalStateException e) {
assertThat(e).hasMessage("Already started");
}
}
@Test
public void launch_then_request_graceful_stop() throws Exception {
Props props = createProps();
final ProcessEntryPoint entryPoint = new ProcessEntryPoint(props, exit, commands);
final StandardProcess process = new StandardProcess();
Thread runner = new Thread() {
@Override
public void run() {
// starts and waits until terminated
entryPoint.launch(process);
}
};
runner.start();
while (process.getState() != State.STARTED) {
Thread.sleep(10L);
}
// requests for graceful stop -> waits until down
// Should terminate before the timeout of 30s
entryPoint.stop();
assertThat(process.getState()).isEqualTo(State.STOPPED);
}
@Test
public void terminate_if_unexpected_shutdown() throws Exception {
Props props = createProps();
final ProcessEntryPoint entryPoint = new ProcessEntryPoint(props, exit, commands);
final StandardProcess process = new StandardProcess();
Thread runner = new Thread() {
@Override
public void run() {
// starts and waits until terminated
entryPoint.launch(process);
}
};
runner.start();
while (process.getState() != State.STARTED) {
Thread.sleep(10L);
}
// emulate signal to shutdown process
entryPoint.getShutdownHook().start();
// hack to prevent JUnit JVM to fail when executing the shutdown hook a second time
Runtime.getRuntime().removeShutdownHook(entryPoint.getShutdownHook());
while (process.getState() != State.STOPPED) {
Thread.sleep(10L);
}
// exit before test timeout, ok !
}
@Test
public void terminate_if_startup_error() throws IOException {
Props props = createProps();
final ProcessEntryPoint entryPoint = new ProcessEntryPoint(props, exit, commands);
final Monitored process = new StartupErrorProcess();
entryPoint.launch(process);
assertThat(entryPoint.getState()).isEqualTo(State.STOPPED);
}
private Props createProps() throws IOException {
Props props = new Props(new Properties());
props.set(PROPERTY_SHARED_PATH, temp.newFolder().getAbsolutePath());
props.set(PROPERTY_PROCESS_INDEX, "1");
props.set(PROPERTY_PROCESS_KEY, "test");
props.set(PROPERTY_TERMINATION_TIMEOUT, "30000");
return props;
}
private static class NoopProcess implements Monitored {
@Override
public void start() {
}
@Override
public Status getStatus() {
return Status.OPERATIONAL;
}
@Override
public void awaitStop() {
}
@Override
public void stop() {
}
}
private static class StartupErrorProcess implements Monitored {
@Override
public void start() {
throw new IllegalStateException("ERROR");
}
@Override
public Status getStatus() {
return Status.DOWN;
}
@Override
public void awaitStop() {
}
@Override
public void stop() {
}
}
}