Mercurial > hg > release > thermostat-0.7
changeset 1021:8ea56b57334c
Make thermostat capable to return exit codes.
Reviewed-by: vanaltj, omajid
Review-thread: http://icedtea.classpath.org/pipermail/thermostat/2013-February/005932.html
line wrap: on
line diff
--- a/agent/cli/pom.xml Fri Mar 01 14:05:40 2013 +0100 +++ b/agent/cli/pom.xml Thu Feb 21 16:00:50 2013 +0100 @@ -62,6 +62,16 @@ <scope>test</scope> </dependency> <dependency> + <groupId>org.powermock</groupId> + <artifactId>powermock-api-mockito</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.powermock</groupId> + <artifactId>powermock-module-junit4</artifactId> + <scope>test</scope> + </dependency> + <dependency> <groupId>com.redhat.thermostat</groupId> <artifactId>thermostat-agent-core</artifactId> <version>${project.version}</version>
--- a/agent/cli/src/main/java/com/redhat/thermostat/agent/cli/impl/AgentApplication.java Fri Mar 01 14:05:40 2013 +0100 +++ b/agent/cli/src/main/java/com/redhat/thermostat/agent/cli/impl/AgentApplication.java Thu Feb 21 16:00:50 2013 +0100 @@ -245,7 +245,7 @@ } catch (Exception e) { logger.log(Level.SEVERE, "Could not get BackendRegistry instance.", e); - System.exit(Constants.EXIT_BACKEND_LOAD_ERROR); + System.exit(Constants.EXIT_ERROR); } final Agent agent = new Agent(backendRegistry, configuration, storage, agentInfoDAO, backendInfoDAO); @@ -259,7 +259,7 @@ logger.log(Level.SEVERE, "Agent could not start, probably because a configured backend could not be activated.", le); - System.exit(Constants.EXIT_BACKEND_START_ERROR); + System.exit(Constants.EXIT_ERROR); } logger.fine("Agent started.");
--- a/agent/cli/src/main/java/com/redhat/thermostat/agent/cli/impl/db/StorageCommand.java Fri Mar 01 14:05:40 2013 +0100 +++ b/agent/cli/src/main/java/com/redhat/thermostat/agent/cli/impl/db/StorageCommand.java Thu Feb 21 16:00:50 2013 +0100 @@ -39,6 +39,12 @@ import java.io.File; import java.io.IOException; +import org.osgi.framework.BundleContext; +import org.osgi.framework.FrameworkUtil; +import org.osgi.framework.ServiceReference; + +import com.redhat.thermostat.common.Constants; +import com.redhat.thermostat.common.ExitStatus; import com.redhat.thermostat.common.cli.AbstractStateNotifyingCommand; import com.redhat.thermostat.common.cli.Arguments; import com.redhat.thermostat.common.cli.CommandContext; @@ -54,9 +60,19 @@ private DBStartupConfiguration configuration; private DBOptionParser parser; + private BundleContext context; private MongoProcessRunner runner; + public StorageCommand() { + this(FrameworkUtil.getBundle(StorageCommand.class).getBundleContext()); + } + + // For testing + StorageCommand(BundleContext context) { + this.context = context; + } + private void parseArguments(Arguments args) throws InvalidConfigurationException { Configuration thermostatConfiguration = new Configuration(); @@ -113,14 +129,29 @@ } private void startService() throws IOException, InterruptedException, InvalidConfigurationException, ApplicationException { - runner.startService(); + try { + runner.startService(); + } catch (ApplicationException | InvalidConfigurationException | IOException e) { + // something went wrong set status appropriately. This makes sure + // that the JVM exits with this status. + setExitStatus(Constants.EXIT_ERROR); + // rethrow + throw e; + } getNotifier().fireAction(ApplicationState.START); } private void stopService() throws IOException, InterruptedException, InvalidConfigurationException, ApplicationException { - check(); - runner.stopService(); + try { + check(); + runner.stopService(); + } catch (ApplicationException | InvalidConfigurationException | InterruptedException | IOException e) { + // something went wrong set status appropriately. This makes sure + // that the JVM exits with this status. + setExitStatus(Constants.EXIT_ERROR); + throw e; + } getNotifier().fireAction(ApplicationState.STOP); } @@ -136,6 +167,14 @@ throw new InvalidConfigurationException("database directories do not exist..."); } } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + private void setExitStatus(final int newStatus) { + ServiceReference exitStatusRef = context.getServiceReference(ExitStatus.class); + ExitStatus status = (ExitStatus) context.getService(exitStatusRef); + status.setExitStatus(newStatus); + context.ungetService(exitStatusRef); + } public DBStartupConfiguration getConfiguration() { return configuration;
--- a/agent/cli/src/test/java/com/redhat/thermostat/agent/cli/impl/ActivatorTest.java Fri Mar 01 14:05:40 2013 +0100 +++ b/agent/cli/src/test/java/com/redhat/thermostat/agent/cli/impl/ActivatorTest.java Thu Feb 21 16:00:50 2013 +0100 @@ -38,18 +38,32 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.when; +import static org.mockito.Mockito.mock; import org.junit.Test; +import org.junit.runner.RunWith; +import org.osgi.framework.Bundle; +import org.osgi.framework.FrameworkUtil; +import org.powermock.api.mockito.PowerMockito; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.junit4.PowerMockRunner; import com.redhat.thermostat.agent.cli.impl.db.StorageCommand; import com.redhat.thermostat.common.cli.Command; import com.redhat.thermostat.testutils.StubBundleContext; +@RunWith(PowerMockRunner.class) +@PrepareForTest({FrameworkUtil.class}) public class ActivatorTest { @Test public void verifyActivatorRegistersCommands() throws Exception { + PowerMockito.mockStatic(FrameworkUtil.class); + Bundle mockBundle = mock(Bundle.class); + when(FrameworkUtil.getBundle(StorageCommand.class)).thenReturn(mockBundle); + StubBundleContext bundleContext = new StubBundleContext(); Activator activator = new Activator();
--- a/agent/cli/src/test/java/com/redhat/thermostat/agent/cli/impl/db/StorageCommandTest.java Fri Mar 01 14:05:40 2013 +0100 +++ b/agent/cli/src/test/java/com/redhat/thermostat/agent/cli/impl/db/StorageCommandTest.java Thu Feb 21 16:00:50 2013 +0100 @@ -37,7 +37,6 @@ package com.redhat.thermostat.agent.cli.impl.db; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.doThrow; @@ -47,29 +46,29 @@ import java.io.File; import java.io.FileOutputStream; import java.io.IOException; -import java.util.Collection; import java.util.Properties; import java.util.Random; import java.util.concurrent.CountDownLatch; import junit.framework.Assert; -import org.apache.commons.cli.Option; -import org.apache.commons.cli.OptionGroup; -import org.apache.commons.cli.Options; +import org.junit.After; import org.junit.Before; -import org.junit.Ignore; import org.junit.Test; +import org.osgi.framework.BundleContext; +import org.osgi.framework.ServiceReference; -import com.redhat.thermostat.agent.cli.impl.db.MongoProcessRunner; import com.redhat.thermostat.common.ActionEvent; import com.redhat.thermostat.common.ActionListener; +import com.redhat.thermostat.common.Constants; +import com.redhat.thermostat.common.ExitStatus; import com.redhat.thermostat.common.cli.CommandContext; import com.redhat.thermostat.common.cli.CommandException; import com.redhat.thermostat.common.cli.SimpleArguments; import com.redhat.thermostat.common.config.InvalidConfigurationException; import com.redhat.thermostat.common.tools.ApplicationException; import com.redhat.thermostat.common.tools.ApplicationState; +import com.redhat.thermostat.testutils.StubBundleContext; public class StorageCommandTest { @@ -78,9 +77,11 @@ private static final String DB = "storage/db"; private String tmpDir; + private BundleContext context; @Before public void setup() { + context = mock(BundleContext.class); // need to create a dummy config file for the test try { Random random = new Random(); @@ -110,6 +111,11 @@ } } + @After + public void tearDown() { + context = null; + } + @Test public void testConfig() throws CommandException { SimpleArguments args = new SimpleArguments(); @@ -119,7 +125,7 @@ CommandContext ctx = mock(CommandContext.class); when(ctx.getArguments()).thenReturn(args); - StorageCommand service = new StorageCommand() { + StorageCommand service = new StorageCommand(context) { @Override MongoProcessRunner createRunner() { throw new AssertionError("dry run should never create an actual runner"); @@ -146,7 +152,7 @@ // TODO: stop not tested yet, but be sure it's not called from the code doThrow(new ApplicationException("mock exception")).when(runner).stopService(); - StorageCommand service = new StorageCommand() { + StorageCommand service = new StorageCommand(context) { @Override MongoProcessRunner createRunner() { return runner; @@ -224,6 +230,104 @@ Assert.assertTrue(result[0]); } + + @Test + public void exceptionSetsExitStatusOnFailure() throws Exception { + context = new StubBundleContext(); + ExitStatus exitStatusImpl = new ExitStatus() { + + private int exitStatus = -1; + + @Override + public void setExitStatus(int newExitStatus) { + exitStatus = newExitStatus; + } + + @Override + public int getExitStatus() { + return exitStatus; + } + }; + context.registerService(ExitStatus.class, exitStatusImpl, null); + assertEquals(-1, getExitStatusService(context).getExitStatus()); + StorageCommand command = prepareService(false); + final CountDownLatch latch = new CountDownLatch(1); + final boolean[] result = new boolean[1]; + command.getNotifier().addActionListener(new ActionListener<ApplicationState>() { + @SuppressWarnings("incomplete-switch") + @Override + public void actionPerformed(ActionEvent<ApplicationState> actionEvent) { + switch (actionEvent.getActionId()) { + case FAIL: + result[0] = true; + break; + + case SUCCESS: + result[0] = false; + break; + } + latch.countDown(); + } + }); + command.run(prepareContext()); + latch.await(); + // should have failed + assertTrue(result[0]); + assertEquals(Constants.EXIT_ERROR, getExitStatusService(context).getExitStatus()); + } + + @Test + public void exitStatusRemainsUntouchedOnSuccess() throws Exception { + context = new StubBundleContext(); + ExitStatus exitStatusImpl = new ExitStatus() { + + private int exitStatus = -1; + + @Override + public void setExitStatus(int newExitStatus) { + exitStatus = newExitStatus; + } + + @Override + public int getExitStatus() { + return exitStatus; + } + }; + context.registerService(ExitStatus.class, exitStatusImpl, null); + assertEquals(-1, getExitStatusService(context).getExitStatus()); + StorageCommand command = prepareService(true); + final CountDownLatch latch = new CountDownLatch(1); + final boolean[] result = new boolean[1]; + command.getNotifier().addActionListener(new ActionListener<ApplicationState>() { + @SuppressWarnings("incomplete-switch") + @Override + public void actionPerformed(ActionEvent<ApplicationState> actionEvent) { + switch (actionEvent.getActionId()) { + case FAIL: + result[0] = false; + break; + + case SUCCESS: + result[0] = true; + break; + } + latch.countDown(); + } + }); + command.run(prepareContext()); + latch.await(); + // should have worked + assertTrue(result[0]); + // this impl of ExitStatus has a default value of -1 + assertEquals(-1, getExitStatusService(context).getExitStatus()); + } + + @SuppressWarnings({ "rawtypes", "unchecked" }) + private ExitStatus getExitStatusService(BundleContext context) { + ServiceReference ref = context.getServiceReference(ExitStatus.class); + ExitStatus exitStatus = (ExitStatus)context.getService(ref); + return exitStatus; + } private CommandContext prepareContext() { SimpleArguments args = new SimpleArguments(); @@ -236,59 +340,16 @@ @Test public void testName() { - StorageCommand dbService = new StorageCommand(); + StorageCommand dbService = new StorageCommand(context); String name = dbService.getName(); assertEquals("storage", name); } @Test public void testDescAndUsage() { - StorageCommand dbService = new StorageCommand(); + StorageCommand dbService = new StorageCommand(context); assertNotNull(dbService.getDescription()); assertNotNull(dbService.getUsage()); } - - @Ignore - @Test - public void testOptions() { - StorageCommand dbService = new StorageCommand(); - Options options = dbService.getOptions(); - assertNotNull(options); - assertEquals(4, options.getOptions().size()); - - assertTrue(options.hasOption("dryRun")); - Option dry = options.getOption("dryRun"); - assertEquals("d", dry.getOpt()); - assertEquals("run the service in dry run mode", dry.getDescription()); - assertFalse(dry.isRequired()); - assertFalse(dry.hasArg()); - - assertTrue(options.hasOption("start")); - Option start = options.getOption("start"); - assertEquals("start the database", start.getDescription()); - assertFalse(start.isRequired()); - assertFalse(start.hasArg()); - - assertTrue(options.hasOption("stop")); - Option stop = options.getOption("stop"); - assertEquals("stop the database", stop.getDescription()); - assertFalse(stop.isRequired()); - assertFalse(stop.hasArg()); - - assertTrue(options.hasOption("quiet")); - Option quiet = options.getOption("quiet"); - assertEquals("q", quiet.getOpt()); - assertEquals("don't produce any output", quiet.getDescription()); - assertFalse(quiet.isRequired()); - assertFalse(quiet.hasArg()); - - OptionGroup startStop = options.getOptionGroup(start); - assertTrue(startStop.isRequired()); - @SuppressWarnings("unchecked") - Collection<Option> groupOpts = startStop.getOptions(); - assertEquals(2, groupOpts.size()); - assertTrue(groupOpts.contains(start)); - assertTrue(groupOpts.contains(stop)); - } }
--- a/common/core/src/main/java/com/redhat/thermostat/common/Constants.java Fri Mar 01 14:05:40 2013 +0100 +++ b/common/core/src/main/java/com/redhat/thermostat/common/Constants.java Thu Feb 21 16:00:50 2013 +0100 @@ -42,12 +42,8 @@ */ public class Constants { - public static final int EXIT_UNKNOWN_ERROR = 1; - public static final int EXIT_UNABLE_TO_CONNECT_TO_DATABASE = 2; - public static final int EXIT_UNABLE_TO_READ_PROPERTIES = 3; - public static final int EXIT_CONFIGURATION_ERROR = 4; - public static final int EXIT_BACKEND_LOAD_ERROR = 5; - public static final int EXIT_BACKEND_START_ERROR = 6; + public static final int EXIT_ERROR = 1; + public static final int EXIT_SUCCESS = 0; public static final String GENERIC_SERVICE_CLASSNAME = "GenericClassName";
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/common/core/src/main/java/com/redhat/thermostat/common/ExitStatus.java Thu Feb 21 16:00:50 2013 +0100 @@ -0,0 +1,27 @@ +package com.redhat.thermostat.common; + +import com.redhat.thermostat.annotations.Service; + +/** + * Service which can be used for certain commands to set the exit status on + * JVM termination. If the status is set multiple times, the last thread which + * calls {@link #setExitStatus(int)} wins. + */ +@Service +public interface ExitStatus { + + /** + * Sets the exit status, which will be used as status when the + * JVM terminates. + * + * @param newExitStatus + */ + void setExitStatus(int newExitStatus); + + /** + * Get the currently set exit status. + * + * @return The exit status which should be used on JVM shutdown. + */ + int getExitStatus(); +}
--- a/launcher/src/main/java/com/redhat/thermostat/launcher/internal/Activator.java Fri Mar 01 14:05:40 2013 +0100 +++ b/launcher/src/main/java/com/redhat/thermostat/launcher/internal/Activator.java Thu Feb 21 16:00:50 2013 +0100 @@ -45,6 +45,8 @@ import org.osgi.util.tracker.ServiceTracker; import org.osgi.util.tracker.ServiceTrackerCustomizer; +import com.redhat.thermostat.common.Constants; +import com.redhat.thermostat.common.ExitStatus; import com.redhat.thermostat.common.Launcher; import com.redhat.thermostat.common.cli.CommandContextFactory; import com.redhat.thermostat.common.cli.CommandInfoSource; @@ -62,6 +64,7 @@ private ServiceRegistration launcherReg; private ServiceRegistration bundleManReg; private ServiceRegistration cmdInfoReg; + private ServiceRegistration exitStatusReg; private BundleContext context; private BundleManager bundleService; @@ -91,6 +94,8 @@ new CommandContextFactory(context), bundleService); launcherReg = context.registerService(Launcher.class.getName(), launcher, null); bundleManReg = context.registerService(BundleManager.class, bundleService, null); + ExitStatus exitStatus = new ExitStatusImpl(Constants.EXIT_SUCCESS); + exitStatusReg = context.registerService(ExitStatus.class, exitStatus, null); return keyring; } @@ -105,6 +110,7 @@ launcherReg.unregister(); bundleManReg.unregister(); cmdInfoReg.unregister(); + exitStatusReg.unregister(); } }
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/launcher/src/main/java/com/redhat/thermostat/launcher/internal/ExitStatusImpl.java Thu Feb 21 16:00:50 2013 +0100 @@ -0,0 +1,24 @@ +package com.redhat.thermostat.launcher.internal; + +import com.redhat.thermostat.common.ExitStatus; + +/** + * @see {@link ExitStatus} + * + */ +public class ExitStatusImpl implements ExitStatus { + + private int exitStatus; + + ExitStatusImpl(int initialExitStatus) { + this.exitStatus = initialExitStatus; + } + + public synchronized void setExitStatus(int newExitStatus) { + this.exitStatus = newExitStatus; + } + + public synchronized int getExitStatus() { + return this.exitStatus; + } +}
--- a/launcher/src/main/java/com/redhat/thermostat/launcher/internal/LauncherImpl.java Fri Mar 01 14:05:40 2013 +0100 +++ b/launcher/src/main/java/com/redhat/thermostat/launcher/internal/LauncherImpl.java Thu Feb 21 16:00:50 2013 +0100 @@ -51,6 +51,8 @@ import com.redhat.thermostat.common.ActionListener; import com.redhat.thermostat.common.ActionNotifier; import com.redhat.thermostat.common.ApplicationService; +import com.redhat.thermostat.common.Constants; +import com.redhat.thermostat.common.ExitStatus; import com.redhat.thermostat.common.Launcher; import com.redhat.thermostat.common.Version; import com.redhat.thermostat.common.cli.AbstractStateNotifyingCommand; @@ -154,6 +156,7 @@ return usageCount == 0; } + @SuppressWarnings({ "unchecked", "rawtypes" }) private void shutdown() throws InternalError { try { ServiceReference appServiceRef = context.getServiceReference(ApplicationService.class); @@ -165,6 +168,8 @@ context.ungetService(appServiceRef); } + // default to success for exit status + int exitStatus = Constants.EXIT_SUCCESS; if (context != null) { ServiceReference storageRef = context.getServiceReference(Storage.class); if (storageRef != null) { @@ -173,9 +178,14 @@ storage.shutdown(); } } + ServiceReference exitStatusRef = context.getServiceReference(ExitStatus.class); + if (exitStatusRef != null) { + ExitStatus exitStatusService = (ExitStatus) context.getService(exitStatusRef); + exitStatus = exitStatusService.getExitStatus(); + } } - context.getBundle(0).stop(); + System.exit(exitStatus); } catch (BundleException e) { throw (InternalError) new InternalError().initCause(e); }
--- a/launcher/src/test/java/com/redhat/thermostat/launcher/internal/ActivatorTest.java Fri Mar 01 14:05:40 2013 +0100 +++ b/launcher/src/test/java/com/redhat/thermostat/launcher/internal/ActivatorTest.java Thu Feb 21 16:00:50 2013 +0100 @@ -74,6 +74,7 @@ import org.powermock.core.classloader.annotations.PrepareForTest; import org.powermock.modules.junit4.PowerMockRunner; +import com.redhat.thermostat.common.ExitStatus; import com.redhat.thermostat.common.Launcher; import com.redhat.thermostat.common.MultipleServiceTracker; import com.redhat.thermostat.common.MultipleServiceTracker.Action; @@ -199,6 +200,7 @@ assertTrue(context.isServiceRegistered(CommandInfoSource.class.getName(), mock(CompoundCommandInfoSource.class).getClass())); assertTrue(context.isServiceRegistered(BundleManager.class.getName(), BundleManagerImpl.class)); assertTrue(context.isServiceRegistered(Launcher.class.getName(), LauncherImpl.class)); + assertTrue(context.isServiceRegistered(ExitStatus.class.getName(), ExitStatusImpl.class)); customizer.removedService(null, null);
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/launcher/src/test/java/com/redhat/thermostat/launcher/internal/DisallowSystemExitSecurityManager.java Thu Feb 21 16:00:50 2013 +0100 @@ -0,0 +1,85 @@ +/* + * Copyright 2012, 2013 Red Hat, Inc. + * + * This file is part of Thermostat. + * + * Thermostat is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published + * by the Free Software Foundation; either version 2, or (at your + * option) any later version. + * + * Thermostat 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 + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Thermostat; see the file COPYING. If not see + * <http://www.gnu.org/licenses/>. + * + * Linking this code with other modules is making a combined work + * based on this code. Thus, the terms and conditions of the GNU + * General Public License cover the whole combination. + * + * As a special exception, the copyright holders of this code give + * you permission to link this code with independent modules to + * produce an executable, regardless of the license terms of these + * independent modules, and to copy and distribute the resulting + * executable under terms of your choice, provided that you also + * meet, for each linked independent module, the terms and conditions + * of the license of that module. An independent module is a module + * which is not derived from or based on this code. If you modify + * this code, you may extend this exception to your version of the + * library, but you are not obligated to do so. If you do not wish + * to do so, delete this exception statement from your version. + */ + +package com.redhat.thermostat.launcher.internal; + +import java.security.Permission; + +/* + * This is used in order to work around the problem of System.exit() being + * called in LauncherImpl. + * + * This needs to be in its own file. Right now it's ownly used in + * LauncherImplTest. If you are getting StackOverflowErrors, make sure this + * SecurityManager impl isn't in the same class which is run with + * PowerMockRunner. + * + */ +public class DisallowSystemExitSecurityManager extends SecurityManager { + + @Override + public void checkPermission(Permission perm) { + // allow anything. + } + + @Override + public void checkPermission(Permission perm, Object context) { + // allow anything. + } + + @Override + public void checkExit(int status) { + throw new ExitException(status); + } + + @SuppressWarnings("serial") + public static class ExitException extends SecurityException { + private int exitStatus; + + public ExitException(int exitStatus) { + this.exitStatus = exitStatus; + } + + public int getExitStatus() { + return this.exitStatus; + } + + @Override + public String getMessage() { + return "System.exit(" + this.exitStatus + ")"; + } + } +}
--- a/launcher/src/test/java/com/redhat/thermostat/launcher/internal/LauncherImplTest.java Fri Mar 01 14:05:40 2013 +0100 +++ b/launcher/src/test/java/com/redhat/thermostat/launcher/internal/LauncherImplTest.java Thu Feb 21 16:00:50 2013 +0100 @@ -38,6 +38,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; import static org.mockito.Matchers.anyString; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doThrow; @@ -46,6 +47,7 @@ import static org.mockito.Mockito.when; import java.io.IOException; +import java.security.Permission; import java.text.MessageFormat; import java.util.ArrayList; import java.util.Arrays; @@ -78,6 +80,7 @@ import com.redhat.thermostat.common.ActionNotifier; import com.redhat.thermostat.common.ApplicationInfo; import com.redhat.thermostat.common.ApplicationService; +import com.redhat.thermostat.common.Constants; import com.redhat.thermostat.common.Version; import com.redhat.thermostat.common.cli.AbstractStateNotifyingCommand; import com.redhat.thermostat.common.cli.Arguments; @@ -93,6 +96,7 @@ import com.redhat.thermostat.common.tools.ApplicationState; import com.redhat.thermostat.launcher.BundleManager; import com.redhat.thermostat.launcher.TestCommand; +import com.redhat.thermostat.launcher.internal.DisallowSystemExitSecurityManager.ExitException; import com.redhat.thermostat.launcher.internal.HelpCommand; import com.redhat.thermostat.launcher.internal.LauncherImpl; import com.redhat.thermostat.launcher.internal.LauncherImpl.LoggingInitializer; @@ -113,10 +117,17 @@ private static final String name1 = "test1"; private static final String name2 = "test2"; private static final String name3 = "test3"; + private static SecurityManager secMan; @BeforeClass public static void beforeClassSetUp() { defaultKeyringProvider = System.getProperty(KeyringProvider.KEYRING_FACTORY_PROPERTY); + // Launcher calls System.exit(). This causes issues for unit testing. + // We work around this by installing a security manager which disallows + // System.exit() and throws an ExitException instead. This exception in + // turn is caught by the wrapped launcher call. + secMan = System.getSecurityManager(); + System.setSecurityManager(new DisallowSystemExitSecurityManager()); } @AfterClass @@ -124,6 +135,7 @@ if (defaultKeyringProvider != null) { System.setProperty(KeyringProvider.KEYRING_FACTORY_PROPERTY, defaultKeyringProvider); } + System.setSecurityManager(secMan); } private static class TestCmd1 implements TestCommand.Handle { @@ -440,17 +452,29 @@ ctxFactory.getCommandRegistry().registerCommands(Arrays.asList(errorCmd)); launcher.setArgs(new String[] { "error" }); - launcher.run(false); + wrappedRun(launcher, false); assertEquals("test error\n", ctxFactory.getError()); } private void runAndVerifyCommand(String[] args, String expected, boolean inShell) { launcher.setArgs(args); - launcher.run(inShell); + wrappedRun(launcher, inShell); assertEquals(expected, ctxFactory.getOutput()); assertTrue(timerFactory.isShutdown()); } + + private void wrappedRun(LauncherImpl launcher, boolean inShell) { + wrappedRun(launcher, inShell, null); + } + + private void wrappedRun(LauncherImpl launcher, boolean inShell, Collection<ActionListener<ApplicationState>> listeners) { + try { + launcher.run(listeners, inShell); + } catch (ExitException e) { + System.out.println(e.getMessage()); + } + } @Test public void verifyPrefsAreUsed() { @@ -463,7 +487,7 @@ when(dbServiceFactory.createDbService(anyString(), anyString(), dbUrlCaptor.capture())).thenReturn(dbService); launcher.setPreferences(prefs); launcher.setArgs(new String[] { "test3" }); - launcher.run(false); + wrappedRun(launcher, false); verify(dbService).connect(); verify(prefs).getConnectionUrl(); assertEquals(dbUrl, dbUrlCaptor.getValue()); @@ -485,7 +509,7 @@ when(dbServiceFactory.createDbService(anyString(), anyString(), anyString())).thenReturn(dbService); launcher.setArgs(new String[] { "dummy" }); - launcher.run(false); + wrappedRun(launcher, false); verify(dbService).connect(); } @@ -515,7 +539,7 @@ PowerMockito.mockStatic(FrameworkUtil.class); when(FrameworkUtil.getBundle(Version.class)).thenReturn(sysBundle); launcher.setArgs(new String[] {Version.VERSION_OPTION}); - launcher.run(false); + wrappedRun(launcher, false); assertEquals(expectedVersionInfo, ctxFactory.getOutput()); assertTrue(timerFactory.isShutdown()); @@ -529,14 +553,14 @@ String[] args = new String[] {"basic"}; launcher.setArgs(args); - launcher.run(listeners, false); + wrappedRun(launcher, false, listeners); verify(notifier).addActionListener(listener); } @Test public void verifyLoggingIsInitialized() { launcher.setArgs(new String[] { "test1" }); - launcher.run(false); + wrappedRun(launcher, false); verify(loggingInitializer).initialize(); } @@ -544,10 +568,23 @@ @Test public void verifyShutdown() throws BundleException { launcher.setArgs(new String[] { "test1" }); - launcher.run(false); + wrappedRun(launcher, false); verify(sysBundle).stop(); } + + @Test + public void verifySetExitStatus() { + launcher.setArgs(new String[] { "test1" }); + try { + launcher.run(false); + fail("Should have called System.exit()"); + } catch (ExitException e) { + // pass, by default launcher exits with an exit status + // of 0. + assertEquals(Constants.EXIT_SUCCESS, e.getExitStatus()); + } + } @Test public void verifyCommandSupportedInShellBehavesNormally() { @@ -581,6 +618,6 @@ ctxFactory.getCommandRegistry().registerCommand(mockCmd); runAndVerifyCommand(new String[] { cmdName }, expected, isInShell); - } + } }