changeset 938:ea9ff682404a thermostat-0.5-branch

Make actual use of Command.isAvailableInShell() and Command.isAvailableOutsideShell() Reviewed-by: omajid, sgehwolf Review-thread: http://icedtea.classpath.org/pipermail/thermostat/2013-January/005346.html Fixes PR1172 PR973
author Jon VanAlten <jon.vanalten@redhat.com>
date Tue, 29 Jan 2013 00:28:44 -0500
parents 37c9527f664f
children ad31199e5a56
files agent/cli/src/main/java/com/redhat/thermostat/agent/cli/impl/AgentApplication.java agent/cli/src/main/java/com/redhat/thermostat/agent/cli/impl/ServiceCommand.java client/cli/src/main/java/com/redhat/thermostat/client/cli/internal/ShellCommand.java client/cli/src/test/java/com/redhat/thermostat/client/cli/internal/ShellCommandTest.java client/swing/src/main/java/com/redhat/thermostat/client/swing/internal/GUIClientCommand.java common/core/src/main/java/com/redhat/thermostat/common/Launcher.java launcher/src/main/java/com/redhat/thermostat/launcher/internal/LauncherImpl.java launcher/src/main/java/com/redhat/thermostat/launcher/internal/LocaleResources.java launcher/src/main/resources/com/redhat/thermostat/launcher/internal/strings.properties launcher/src/test/java/com/redhat/thermostat/launcher/TestCommand.java launcher/src/test/java/com/redhat/thermostat/launcher/internal/LauncherImplTest.java launcher/src/test/java/com/redhat/thermostat/launcher/internal/LauncherTest.java main/src/main/java/com/redhat/thermostat/main/Thermostat.java
diffstat 13 files changed, 606 insertions(+), 532 deletions(-) [+]
line wrap: on
line diff
--- a/agent/cli/src/main/java/com/redhat/thermostat/agent/cli/impl/AgentApplication.java	Tue Jan 29 00:28:43 2013 -0500
+++ b/agent/cli/src/main/java/com/redhat/thermostat/agent/cli/impl/AgentApplication.java	Tue Jan 29 00:28:44 2013 -0500
@@ -246,5 +246,9 @@
         }
     }
 
+    @Override
+    public boolean isAvailableInShell() {
+    	return false;
+    }
 }
 
--- a/agent/cli/src/main/java/com/redhat/thermostat/agent/cli/impl/ServiceCommand.java	Tue Jan 29 00:28:43 2013 -0500
+++ b/agent/cli/src/main/java/com/redhat/thermostat/agent/cli/impl/ServiceCommand.java	Tue Jan 29 00:28:44 2013 -0500
@@ -77,11 +77,11 @@
         Launcher launcher = getLauncher();
         String[] storageStartArgs = new String[] { "storage", "--start" };
         launcher.setArgs(storageStartArgs);
-        launcher.run(listeners);
+        launcher.run(listeners, false);
         agentBarrier.acquireUninterruptibly();
         String[] storageStopArgs = new String[] { "storage", "--stop" };
         launcher.setArgs(storageStopArgs);
-        launcher.run();
+        launcher.run(false);
     }
 
     @Override
@@ -99,7 +99,7 @@
                 String[] agentArgs =  new String[] {"agent", "-d", dbUrl};
                 System.err.println(translator.localize(LocaleResources.STARTING_AGENT));
                 launcher.setArgs(agentArgs);
-                launcher.run();
+                launcher.run(false);
                 agentBarrier.release();
                 break;
             case FAIL:
--- a/client/cli/src/main/java/com/redhat/thermostat/client/cli/internal/ShellCommand.java	Tue Jan 29 00:28:43 2013 -0500
+++ b/client/cli/src/main/java/com/redhat/thermostat/client/cli/internal/ShellCommand.java	Tue Jan 29 00:28:44 2013 -0500
@@ -167,7 +167,7 @@
         if (launcherRef != null) {
             Launcher launcher = (Launcher) bundleContext.getService(launcherRef);
             launcher.setArgs(parsed);
-            launcher.run();
+            launcher.run(true);
         } else {
             throw new CommandException("Severe: Could not locate launcher");
         }
--- a/client/cli/src/test/java/com/redhat/thermostat/client/cli/internal/ShellCommandTest.java	Tue Jan 29 00:28:43 2013 -0500
+++ b/client/cli/src/test/java/com/redhat/thermostat/client/cli/internal/ShellCommandTest.java	Tue Jan 29 00:28:44 2013 -0500
@@ -98,7 +98,7 @@
         CommandContext ctx = ctxFactory.createContext(args);
         cmd.run(ctx);
         verify(launcher).setArgs(new String[]{"help"});
-        verify(launcher).run();
+        verify(launcher).run(true);
     }
 
     @Test
@@ -172,7 +172,7 @@
         assertEquals("", ctxFactory.getError());
 
         verify(launcher).setArgs(new String[] {"old-history-value"});
-        verify(launcher).run();
+        verify(launcher).run(true);
     }
 
     @Test
@@ -195,7 +195,7 @@
         cmd.run(ctx);
 
         verify(launcher).setArgs(new String[] {"add-to-history"});
-        verify(launcher).run();
+        verify(launcher).run(true);
         verify(mockHistory).add("add-to-history");
         verify(mockHistory).flush();
 
--- a/client/swing/src/main/java/com/redhat/thermostat/client/swing/internal/GUIClientCommand.java	Tue Jan 29 00:28:43 2013 -0500
+++ b/client/swing/src/main/java/com/redhat/thermostat/client/swing/internal/GUIClientCommand.java	Tue Jan 29 00:28:44 2013 -0500
@@ -64,5 +64,10 @@
         return false;
     }
 
+    @Override
+    public boolean isAvailableInShell() {
+    	return false;
+    }
+
 }
 
--- a/common/core/src/main/java/com/redhat/thermostat/common/Launcher.java	Tue Jan 29 00:28:43 2013 -0500
+++ b/common/core/src/main/java/com/redhat/thermostat/common/Launcher.java	Tue Jan 29 00:28:44 2013 -0500
@@ -50,17 +50,20 @@
 
     /**
      * Invoked in order to start a command, either when Thermostat starts, or within
-     * the thermostat shell.  Equivalent to calling run(null).
+     * the thermostat shell.  Equivalent to calling run(null, inShell).
+     * @param inShell whether invoked from within the thermostat shell
      */
-    void run();
+    void run(boolean inShell);
 
     /**
      * Invoked in order to start a command, either when Thermostat starts, or within
      * the thermostat shell.  If the command being run happens to be a AbstractStateNotifyingCommand,
      * and the argument is non-null, the listeners will be added to the command for
      * life cycle notifications.  Otherwise, the argument is ignored.
+     * @param listeners the collection of listeners to be added to the command
+     * @param inShell whether invoked from within the thermostat shell
      */
-    void run(Collection<ActionListener<ApplicationState>> listeners);
+    void run(Collection<ActionListener<ApplicationState>> listeners, boolean inShell);
 
     /**
      * Should be set before calling run()
--- a/launcher/src/main/java/com/redhat/thermostat/launcher/internal/LauncherImpl.java	Tue Jan 29 00:28:43 2013 -0500
+++ b/launcher/src/main/java/com/redhat/thermostat/launcher/internal/LauncherImpl.java	Tue Jan 29 00:28:44 2013 -0500
@@ -65,6 +65,7 @@
 import com.redhat.thermostat.common.cli.CommandRegistry;
 import com.redhat.thermostat.common.config.ClientPreferences;
 import com.redhat.thermostat.common.config.InvalidConfigurationException;
+import com.redhat.thermostat.common.locale.Translate;
 import com.redhat.thermostat.common.tools.ApplicationState;
 import com.redhat.thermostat.common.utils.LoggingUtils;
 import com.redhat.thermostat.common.utils.OSGIUtils;
@@ -90,6 +91,8 @@
     private BundleContext context;
     private BundleManager registry;
     private final DbServiceFactory dbServiceFactory;
+
+    private Translate<LocaleResources> t = LocaleResources.createLocalizer();
     
     public LauncherImpl(BundleContext context, CommandContextFactory cmdCtxFactory, BundleManager registry) {
         this(context, cmdCtxFactory, registry, new LoggingInitializer(), new DbServiceFactory());
@@ -106,12 +109,12 @@
     }
 
     @Override
-    public synchronized void run() {
-        run(null);
+    public synchronized void run(boolean inShell) {
+        run(null, inShell);
     }
 
     @Override
-    public synchronized void run(Collection<ActionListener<ApplicationState>> listeners) {
+    public synchronized void run(Collection<ActionListener<ApplicationState>> listeners, boolean inShell) {
 
         usageCount++;
         waitForArgs();
@@ -125,7 +128,7 @@
                 Version coreVersion = new Version();
                 cmdCtxFactory.getConsole().getOutput().println(coreVersion.getVersionInfo());
             } else {
-                runCommandFromArguments(listeners);
+                runCommandFromArguments(listeners, inShell);
             }
         } finally {
             args = null;
@@ -189,26 +192,27 @@
     }
 
     private void runHelpCommand() {
-        runCommand("help", new String[0], null);
+        runCommand("help", new String[0], null, false);
     }
 
     private void runHelpCommandFor(String cmdName) {
-        runCommand("help", new String[] { "--", cmdName }, null);
+        runCommand("help", new String[] { "--", cmdName }, null, false);
     }
 
-    private void runCommandFromArguments(Collection<ActionListener<ApplicationState>> listeners) {
-        runCommand(args[0], Arrays.copyOfRange(args, 1, args.length), listeners);
+    private void runCommandFromArguments(Collection<ActionListener<ApplicationState>> listeners, boolean inShell) {
+        runCommand(args[0], Arrays.copyOfRange(args, 1, args.length), listeners, inShell);
     }
 
-    private void runCommand(String cmdName, String[] cmdArgs, Collection<ActionListener<ApplicationState>> listeners) {
+    private void runCommand(String cmdName, String[] cmdArgs, Collection<ActionListener<ApplicationState>> listeners, boolean inShell) {
         try {
-            parseArgsAndRunCommand(cmdName, cmdArgs, listeners);
+            parseArgsAndRunCommand(cmdName, cmdArgs, listeners, inShell);
         } catch (CommandException e) {
             cmdCtxFactory.getConsole().getError().println(e.getMessage());
         }
     }
 
-    private void parseArgsAndRunCommand(String cmdName, String[] cmdArgs, Collection<ActionListener<ApplicationState>> listeners) throws CommandException {
+    private void parseArgsAndRunCommand(String cmdName, String[] cmdArgs,
+    		Collection<ActionListener<ApplicationState>> listeners, boolean inShell) throws CommandException {
 
         PrintStream out = cmdCtxFactory.getConsole().getOutput();
         try {
@@ -229,6 +233,10 @@
             runHelpCommandFor(cmdName);
             return;
         }
+        if ((inShell && !cmd.isAvailableInShell()) || (!inShell && !cmd.isAvailableOutsideShell())) {
+        	outputBadShellContext(inShell, out, cmdName);
+        	return;
+        }
         if (listeners != null && cmd instanceof AbstractStateNotifyingCommand) {
             AbstractStateNotifyingCommand basicCmd = (AbstractStateNotifyingCommand) cmd;
             ActionNotifier<ApplicationState> notifier = basicCmd.getNotifier();
@@ -243,6 +251,16 @@
         cmd.run(ctx);
     }
 
+    private void outputBadShellContext(boolean inShell, PrintStream out, String cmd) {
+    	String inOrOut = null;
+    	if (inShell) {
+    		inOrOut = t.localize(LocaleResources.WITHIN);
+    	} else {
+    		inOrOut = t.localize(LocaleResources.OUTSIDE);
+    	}
+    	out.println(t.localize(LocaleResources.COMMAND_USED_IN_WRONG_CONTEXT, cmd, inOrOut));
+    }
+
     private void setupLogLevel(Arguments args) {
         if (args.hasArgument(CommonOptions.LOG_LEVEL_ARG)) {
             String levelOption = args.getArgument(CommonOptions.LOG_LEVEL_ARG);
--- a/launcher/src/main/java/com/redhat/thermostat/launcher/internal/LocaleResources.java	Tue Jan 29 00:28:43 2013 -0500
+++ b/launcher/src/main/java/com/redhat/thermostat/launcher/internal/LocaleResources.java	Tue Jan 29 00:28:44 2013 -0500
@@ -46,6 +46,9 @@
     OPTION_USERNAME_DESC,
     OPTION_PASSWORD_DESC,
     OPTION_LOG_LEVEL_DESC,
+    WITHIN,
+    OUTSIDE,
+    COMMAND_USED_IN_WRONG_CONTEXT,
     ;
 
     static final String RESOURCE_BUNDLE = "com.redhat.thermostat.launcher.internal.strings";
--- a/launcher/src/main/resources/com/redhat/thermostat/launcher/internal/strings.properties	Tue Jan 29 00:28:43 2013 -0500
+++ b/launcher/src/main/resources/com/redhat/thermostat/launcher/internal/strings.properties	Tue Jan 29 00:28:44 2013 -0500
@@ -7,4 +7,8 @@
 OPTION_PASSWORD_DESC = the password to use for authentication
 OPTION_LOG_LEVEL_DESC = sets the log level for this invocation. Possible values\
  for <level> in decreasing severity are: SEVERE, WARNING, INFO, CONFIG, FINE,\
- FINER, FINEST and OFF
\ No newline at end of file
+ FINER, FINEST and OFF
+
+WITHIN = within
+OUTSIDE = outside
+COMMAND_USED_IN_WRONG_CONTEXT = The {0} command is not supported from {1} the thermostat shell.
\ No newline at end of file
--- a/launcher/src/test/java/com/redhat/thermostat/launcher/TestCommand.java	Tue Jan 29 00:28:43 2013 -0500
+++ b/launcher/src/test/java/com/redhat/thermostat/launcher/TestCommand.java	Tue Jan 29 00:28:44 2013 -0500
@@ -126,8 +126,8 @@
         return availableInShell;
     }
 
-    void setAvailableInShell(boolean avaiable) {
-        this.availableInShell = avaiable;
+    void setAvailableInShell(boolean available) {
+        this.availableInShell = available;
     }
 
     @Override
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/launcher/src/test/java/com/redhat/thermostat/launcher/internal/LauncherImplTest.java	Tue Jan 29 00:28:44 2013 -0500
@@ -0,0 +1,544 @@
+/*
+ * 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 static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Matchers.isA;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import java.io.IOException;
+import java.text.MessageFormat;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.concurrent.ExecutorService;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import org.apache.commons.cli.Option;
+import org.apache.commons.cli.Options;
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.BundleException;
+import org.osgi.framework.FrameworkUtil;
+import org.osgi.framework.ServiceReference;
+import org.powermock.api.mockito.PowerMockito;
+import org.powermock.core.classloader.annotations.PrepareForTest;
+import org.powermock.modules.junit4.PowerMockRunner;
+
+import com.redhat.thermostat.common.ActionListener;
+import com.redhat.thermostat.common.ActionNotifier;
+import com.redhat.thermostat.common.ApplicationInfo;
+import com.redhat.thermostat.common.ApplicationService;
+import com.redhat.thermostat.common.Version;
+import com.redhat.thermostat.common.cli.AbstractStateNotifyingCommand;
+import com.redhat.thermostat.common.cli.Arguments;
+import com.redhat.thermostat.common.cli.Command;
+import com.redhat.thermostat.common.cli.CommandContext;
+import com.redhat.thermostat.common.cli.CommandException;
+import com.redhat.thermostat.common.cli.CommandInfo;
+import com.redhat.thermostat.common.cli.CommandInfoNotFoundException;
+import com.redhat.thermostat.common.cli.CommandInfoSource;
+import com.redhat.thermostat.common.config.ClientPreferences;
+import com.redhat.thermostat.common.locale.LocaleResources;
+import com.redhat.thermostat.common.locale.Translate;
+import com.redhat.thermostat.common.tools.ApplicationState;
+import com.redhat.thermostat.common.utils.OSGIUtils;
+import com.redhat.thermostat.launcher.BundleManager;
+import com.redhat.thermostat.launcher.TestCommand;
+import com.redhat.thermostat.launcher.internal.HelpCommand;
+import com.redhat.thermostat.launcher.internal.LauncherImpl;
+import com.redhat.thermostat.launcher.internal.LauncherImpl.LoggingInitializer;
+import com.redhat.thermostat.storage.core.DbService;
+import com.redhat.thermostat.storage.core.DbServiceFactory;
+import com.redhat.thermostat.storage.core.Storage;
+import com.redhat.thermostat.test.TestCommandContextFactory;
+import com.redhat.thermostat.test.TestTimerFactory;
+import com.redhat.thermostat.testutils.StubBundleContext;
+import com.redhat.thermostat.utils.keyring.Keyring;
+import com.redhat.thermostat.utils.keyring.KeyringProvider;
+
+@RunWith(PowerMockRunner.class)
+@PrepareForTest({FrameworkUtil.class, HelpCommand.class, OSGIUtils.class})
+public class LauncherImplTest {
+    
+    private static String defaultKeyringProvider;
+    private static final String name1 = "test1";
+    private static final String name2 = "test2";
+    private static final String name3 = "test3";
+      
+    @BeforeClass
+    public static void beforeClassSetUp() {
+        defaultKeyringProvider = System.getProperty(KeyringProvider.KEYRING_FACTORY_PROPERTY);
+    }
+    
+    @AfterClass
+    public static void afterClassTearDown() {
+        if (defaultKeyringProvider != null) {
+            System.setProperty(KeyringProvider.KEYRING_FACTORY_PROPERTY, defaultKeyringProvider);
+        }
+    }
+    
+    private static class TestCmd1 implements TestCommand.Handle {
+
+        @Override
+        public void run(CommandContext ctx) {
+            Arguments args = ctx.getArguments();
+            ctx.getConsole().getOutput().print(args.getArgument("arg1") + ", " + args.getArgument("arg2"));
+        }
+    }
+
+    private static class TestCmd2 implements TestCommand.Handle {
+        @Override
+        public void run(CommandContext ctx) {
+            Arguments args = ctx.getArguments();
+            ctx.getConsole().getOutput().print(args.getArgument("arg4") + ": " + args.getArgument("arg3"));
+        }
+    }
+
+    private TestCommandContextFactory  ctxFactory;
+    private StubBundleContext bundleContext;
+    private Bundle sysBundle;
+    private TestTimerFactory timerFactory;
+    private BundleManager registry;
+    private LoggingInitializer loggingInitializer;
+    private DbServiceFactory dbServiceFactory;
+    private CommandInfoSource infos;
+    private ActionNotifier<ApplicationState> notifier;
+
+    private LauncherImpl launcher;
+    private Storage storage;
+
+    @Before
+    public void setUp() {
+        setupCommandContextFactory();
+
+        TestCommand cmd1 = new TestCommand(name1, new TestCmd1());
+        CommandInfo info1 = mock(CommandInfo.class);
+        when(info1.getName()).thenReturn(name1);
+        Options options1 = new Options();
+        Option opt1 = new Option(null, "arg1", true, null);
+        options1.addOption(opt1);
+        Option opt2 = new Option(null, "arg2", true, null);
+        options1.addOption(opt2);
+        // cmd1 needs logLevel option since it is used in tests if logLevel
+        // option is properly set up
+        Option logLevel = new Option("l", "logLevel", true, null);
+        options1.addOption(logLevel);
+        cmd1.addOptions(opt1, opt2, logLevel);
+        cmd1.setDescription("description 1");
+        when(info1.getDescription()).thenReturn("description 1");
+        when(info1.getOptions()).thenReturn(options1);
+        TestCommand cmd2 = new TestCommand("test2", new TestCmd2());
+        CommandInfo info2 = mock(CommandInfo.class);
+        when(info2.getName()).thenReturn(name2);
+        Options options2 = new Options();
+        Option opt3 = new Option(null, "arg3", true, null);
+        options2.addOption(opt3);
+        Option opt4 = new Option(null, "arg4", true, null);
+        options2.addOption(opt4);
+        cmd2.addOptions(opt3, opt4);
+        cmd2.setDescription("description 2");
+        when(info2.getDescription()).thenReturn("description 2");
+        when(info2.getOptions()).thenReturn(options2);
+
+        TestCommand cmd3 = new TestCommand(name3);
+        CommandInfo info3 = mock(CommandInfo.class);
+        when(info3.getName()).thenReturn(name3);
+        cmd3.setStorageRequired(true);
+        cmd3.setDescription("description 3");
+        when(info3.getDescription()).thenReturn("description 3");
+        when(info3.getOptions()).thenReturn(new Options());
+
+        AbstractStateNotifyingCommand basicCmd = mock(AbstractStateNotifyingCommand.class);
+        CommandInfo basicInfo = mock(CommandInfo.class);
+        when(basicCmd.getName()).thenReturn("basic");
+        when(basicInfo.getName()).thenReturn("basic");
+        when(basicCmd.getDescription()).thenReturn("nothing that means anything");
+        when(basicInfo.getDescription()).thenReturn("nothing that means anything");
+        when(basicCmd.isStorageRequired()).thenReturn(false);
+        when(basicCmd.isAvailableInShell()).thenReturn(true);
+        when(basicCmd.isAvailableOutsideShell()).thenReturn(true);
+        Options options = new Options();
+        when(basicCmd.getOptions()).thenReturn(options);
+        when(basicInfo.getOptions()).thenReturn(options);
+        notifier = mock(ActionNotifier.class);
+        when(basicCmd.getNotifier()).thenReturn(notifier);
+        CommandInfo helpCommandInfo = mock(CommandInfo.class);
+        when(helpCommandInfo.getName()).thenReturn("help");
+        when(helpCommandInfo.getDescription()).thenReturn("print help information");
+        when(helpCommandInfo.getDependencyResourceNames()).thenReturn(new ArrayList<String>());
+        when(helpCommandInfo.getOptions()).thenReturn(new Options());
+        when(helpCommandInfo.getUsage()).thenReturn("thermostat help");
+
+        ctxFactory.getCommandRegistry().registerCommands(Arrays.asList(new HelpCommand(), cmd1, cmd2, cmd3, basicCmd));
+
+        registry = mock(BundleManager.class);
+
+        infos = mock(CommandInfoSource.class);
+        when(infos.getCommandInfo(name1)).thenReturn(info1);
+        when(infos.getCommandInfo(name2)).thenReturn(info2);
+        when(infos.getCommandInfo(name3)).thenReturn(info3);
+        when(infos.getCommandInfo("basic")).thenReturn(basicInfo);
+        when(infos.getCommandInfo("help")).thenReturn(helpCommandInfo);
+
+        Collection<CommandInfo> infoList = new ArrayList<CommandInfo>();
+        infoList.add(helpCommandInfo);
+        infoList.add(basicInfo);
+        infoList.add(info1);
+        infoList.add(info2);
+        infoList.add(info3);
+        when(infos.getCommandInfos()).thenReturn(infoList);
+
+        PowerMockito.mockStatic(FrameworkUtil.class);
+        Bundle bundle = mock(Bundle.class);
+        BundleContext bCtx = mock(BundleContext.class);
+        when(bundle.getBundleContext()).thenReturn(bCtx);
+        ServiceReference infosRef = mock(ServiceReference.class);
+        when(bCtx.getServiceReference(CommandInfoSource.class)).thenReturn(infosRef);
+        when(bCtx.getService(infosRef)).thenReturn(infos);
+        when(FrameworkUtil.getBundle(isA(HelpCommand.class.getClass()))).thenReturn(bundle);
+
+        storage = mock(Storage.class);
+        ServiceReference storageRef = mock(ServiceReference.class);
+        Bundle launcherBundle = mock(Bundle.class);
+        BundleContext launcherBundleCtx = mock(BundleContext.class);
+        when(launcherBundleCtx.getServiceReference(Storage.class)).thenReturn(storageRef);
+        when(launcherBundleCtx.getService(storageRef)).thenReturn(storage);
+        when(launcherBundle.getBundleContext()).thenReturn(launcherBundleCtx);
+        when(FrameworkUtil.getBundle(LauncherImpl.class)).thenReturn(launcherBundle);
+
+        timerFactory = new TestTimerFactory();
+        ExecutorService exec = mock(ExecutorService.class);
+        ApplicationService appSvc = mock(ApplicationService.class);
+        when(appSvc.getTimerFactory()).thenReturn(timerFactory);
+        when(appSvc.getApplicationExecutor()).thenReturn(exec);
+        OSGIUtils osgi = mock(OSGIUtils.class);
+        when(osgi.getService(ApplicationService.class)).thenReturn(appSvc);
+        PowerMockito.mockStatic(OSGIUtils.class);
+        when(OSGIUtils.getInstance()).thenReturn(osgi);
+
+        loggingInitializer = mock(LoggingInitializer.class);
+        dbServiceFactory = mock(DbServiceFactory.class);
+
+        launcher = new LauncherImpl(bundleContext, ctxFactory, registry, loggingInitializer, dbServiceFactory);
+
+        Keyring keyring = mock(Keyring.class);
+        launcher.setPreferences(new ClientPreferences(keyring));
+    }
+
+    private void setupCommandContextFactory() {
+        sysBundle = mock(Bundle.class);
+        bundleContext = new StubBundleContext();
+        bundleContext.setBundle(0, sysBundle);
+        ctxFactory = new TestCommandContextFactory(bundleContext);
+    }
+
+
+    @After
+    public void tearDown() {
+        ctxFactory = null;
+        bundleContext = null;
+    }
+
+    @Test
+    public void testMain() {
+        runAndVerifyCommand(new String[] {name1, "--arg1", "Hello", "--arg2", "World"}, "Hello, World", false);
+
+        ctxFactory.reset();
+
+        runAndVerifyCommand(new String[] {"test2", "--arg3", "Hello", "--arg4", "World"}, "World: Hello", false);
+    }
+
+    @Test
+    public void testMainNoArgs() {
+        String expected = "list of commands:\n\n"
+                        + " help          print help information\n"
+                        + " basic         nothing that means anything\n"
+                        + " test1         description 1\n"
+                        + " test2         description 2\n"
+                        + " test3         description 3\n";
+        runAndVerifyCommand(new String[0], expected, false);
+    }
+
+    @Test
+    public void verifySetLogLevel() {
+        runAndVerifyCommand(new String[] {name1, "--logLevel", "WARNING", "--arg1", "Hello", "--arg2", "World"}, "Hello, World", false);
+        Logger globalLogger = Logger.getLogger("com.redhat.thermostat");
+        assertEquals(Level.WARNING, globalLogger.getLevel());
+    }
+
+    @Test
+    public void testMainBadCommand1() {
+        when(infos.getCommandInfo("--help")).thenThrow(new CommandInfoNotFoundException("--help"));
+
+        String expected = "unknown command '--help'\n"
+            + "list of commands:\n\n"
+            + " help          print help information\n"
+            + " basic         nothing that means anything\n"
+            + " test1         description 1\n"
+            + " test2         description 2\n"
+            + " test3         description 3\n";
+        runAndVerifyCommand(new String[] {"--help"}, expected, false);
+    }
+
+    @Test
+    public void testMainBadCommand2() {
+        when(infos.getCommandInfo("-help")).thenThrow(new CommandInfoNotFoundException("-help"));
+
+        String expected = "unknown command '-help'\n"
+            + "list of commands:\n\n"
+            + " help          print help information\n"
+            + " basic         nothing that means anything\n"
+            + " test1         description 1\n"
+            + " test2         description 2\n"
+            + " test3         description 3\n";
+        runAndVerifyCommand(new String[] {"-help"}, expected, false);
+    }
+
+    @Test
+    public void testMainBadCommand3() {
+        when(infos.getCommandInfo("foobarbaz")).thenThrow(new CommandInfoNotFoundException("foobarbaz"));
+
+        String expected = "unknown command 'foobarbaz'\n"
+            + "list of commands:\n\n"
+            + " help          print help information\n"
+            + " basic         nothing that means anything\n"
+            + " test1         description 1\n"
+            + " test2         description 2\n"
+            + " test3         description 3\n";
+        runAndVerifyCommand(new String[] {"foobarbaz"}, expected, false);
+    }
+
+    @Test
+    public void testMainBadCommand4() {
+        when(infos.getCommandInfo("foo")).thenThrow(new CommandInfoNotFoundException("foo"));
+
+        String expected = "unknown command 'foo'\n"
+            + "list of commands:\n\n"
+            + " help          print help information\n"
+            + " basic         nothing that means anything\n"
+            + " test1         description 1\n"
+            + " test2         description 2\n"
+            + " test3         description 3\n";
+        runAndVerifyCommand(new String[] {"foo",  "--bar", "baz"}, expected, false);
+    }
+
+    @Test
+    public void testCommandInfoNotFound() throws CommandInfoNotFoundException, BundleException, IOException {
+        when(infos.getCommandInfo("foo")).thenThrow(new CommandInfoNotFoundException("foo"));
+        doThrow(new CommandInfoNotFoundException("foo")).when(registry).addBundlesFor("foo");
+
+        String expected = "unknown command 'foo'\n"
+                + "list of commands:\n\n"
+                + " help          print help information\n"
+                + " basic         nothing that means anything\n"
+                + " test1         description 1\n"
+                + " test2         description 2\n"
+                + " test3         description 3\n";
+            runAndVerifyCommand(new String[] {"foo"}, expected, false);
+    }
+
+    @Test
+    public void testMainExceptionInCommand() {
+        TestCommand errorCmd = new TestCommand("error", new TestCommand.Handle() {
+
+            @Override
+            public void run(CommandContext ctx) throws CommandException {
+                throw new CommandException("test error");
+            }
+
+        });
+        ctxFactory.getCommandRegistry().registerCommands(Arrays.asList(errorCmd));
+
+        launcher.setArgs(new String[] { "error" });
+        launcher.run(false);
+        assertEquals("test error\n", ctxFactory.getError());
+
+    }
+
+    private void runAndVerifyCommand(String[] args, String expected, boolean inShell) {
+        launcher.setArgs(args);
+        launcher.run(inShell);
+        assertEquals(expected, ctxFactory.getOutput());
+        assertTrue(timerFactory.isShutdown());
+    }
+
+    @Test
+    public void verifyPrefsAreUsed() {
+        ClientPreferences prefs = mock(ClientPreferences.class);
+        String dbUrl = "mongo://fluff:12345";
+        when(prefs.getConnectionUrl()).thenReturn(dbUrl);
+
+        DbService dbService = mock(DbService.class);
+        ArgumentCaptor<String> dbUrlCaptor = ArgumentCaptor.forClass(String.class);
+        when(dbServiceFactory.createDbService(anyString(), anyString(), dbUrlCaptor.capture())).thenReturn(dbService);
+        launcher.setPreferences(prefs);
+        launcher.setArgs(new String[] { "test3" });
+        launcher.run(false);
+        verify(dbService).connect();
+        verify(prefs).getConnectionUrl();
+        assertEquals(dbUrl, dbUrlCaptor.getValue());
+    }
+
+    @Test
+    public void verifyDbServiceConnectIsCalledForStorageCommand() throws Exception {
+        Command mockCmd = mock(Command.class);
+        when(mockCmd.getName()).thenReturn("dummy");
+        when(mockCmd.isStorageRequired()).thenReturn(true);
+        Options options = mock(Options.class);
+        when(mockCmd.getOptions()).thenReturn(options);
+        when(mockCmd.isAvailableInShell()).thenReturn(true);
+        when(mockCmd.isAvailableOutsideShell()).thenReturn(true);
+        
+        ctxFactory.getCommandRegistry().registerCommand(mockCmd);
+        
+        DbService dbService = mock(DbService.class);
+        when(dbServiceFactory.createDbService(anyString(), anyString(), anyString())).thenReturn(dbService);
+
+        launcher.setArgs(new String[] { "dummy" });
+        launcher.run(false);
+        verify(dbService).connect();
+    }
+
+    @Test
+    public void verifyVersionInfoQuery() {
+        int major = 0;
+        int minor = 3;
+        int micro = 0;
+        
+        ApplicationInfo appInfo = new ApplicationInfo();
+        Translate<LocaleResources> t = LocaleResources.createLocalizer();
+        String format = MessageFormat.format(
+                t.localize(LocaleResources.APPLICATION_VERSION_INFO),
+                appInfo.getName())
+                + " " + Version.VERSION_NUMBER_FORMAT;
+        
+        String expectedVersionInfo = String.format(format,
+                major, minor, micro) + "\n";
+        
+        String qualifier = "201207241700";
+
+        org.osgi.framework.Version ver = org.osgi.framework.Version
+                .parseVersion(String.format(Version.VERSION_NUMBER_FORMAT,
+                        major, minor, micro) + "." + qualifier);
+        when(sysBundle.getVersion()).thenReturn(ver);
+        
+        PowerMockito.mockStatic(FrameworkUtil.class);
+        when(FrameworkUtil.getBundle(Version.class)).thenReturn(sysBundle);
+        launcher.setArgs(new String[] {Version.VERSION_OPTION});
+        launcher.run(false);
+
+        assertEquals(expectedVersionInfo, ctxFactory.getOutput());
+        assertTrue(timerFactory.isShutdown());
+    }
+
+    @Test
+    public void verifyListenersAdded() {
+        ActionListener<ApplicationState> listener = mock(ActionListener.class);
+        Collection<ActionListener<ApplicationState>> listeners = new ArrayList<>();
+        listeners.add(listener);
+        String[] args = new String[] {"basic"};
+
+        launcher.setArgs(args);
+        launcher.run(listeners, false);
+        verify(notifier).addActionListener(listener);
+    }
+
+    @Test
+    public void verifyLoggingIsInitialized() {
+        launcher.setArgs(new String[] { "test1" });
+        launcher.run(false);
+
+        verify(loggingInitializer).initialize();
+    }
+
+    @Test
+    public void verifyShutdown() throws BundleException {
+        launcher.setArgs(new String[] { "test1" });
+        launcher.run(false);
+
+        verify(sysBundle).stop();
+    }
+
+    @Test
+    public void verifyCommandSupportedInShellBehavesNormally() {
+    	runWithShellStatus(true, "fluff", true, true, "");
+    }
+
+    @Test
+    public void verifyCommandSupportedOutsideShellBehavesNormally() {
+    	runWithShellStatus(false, "fluff", true, true, "");
+    }
+
+    @Test
+    public void verifyCommandNotSupportedInShellDisplaysMessage() {
+    	runWithShellStatus(true, "fluff", false, true, "The fluff command is not supported from within the thermostat shell.\n");
+    }
+
+    @Test
+    public void verifyCommandNotSupportedOutsideShellDisplaysMessage() {
+    	runWithShellStatus(false, "fluff", true, false, "The fluff command is not supported from outside the thermostat shell.\n");
+    }
+
+    private void runWithShellStatus(boolean isInShell, String cmdName, boolean isAvailableInShell,
+    		boolean isAvailableOutsideShell, String expected) {
+    	Command mockCmd = mock(Command.class);
+        when(mockCmd.getName()).thenReturn(cmdName);
+        when(mockCmd.isStorageRequired()).thenReturn(false);
+        Options options = mock(Options.class);
+        when(mockCmd.getOptions()).thenReturn(options);
+        when(mockCmd.isAvailableInShell()).thenReturn(isAvailableInShell);
+        when(mockCmd.isAvailableOutsideShell()).thenReturn(isAvailableOutsideShell);
+
+        ctxFactory.getCommandRegistry().registerCommand(mockCmd);
+        runAndVerifyCommand(new String[] { cmdName }, expected, isInShell);
+    }
+}
+
--- a/launcher/src/test/java/com/redhat/thermostat/launcher/internal/LauncherTest.java	Tue Jan 29 00:28:43 2013 -0500
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,507 +0,0 @@
-/*
- * 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 static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.Matchers.anyString;
-import static org.mockito.Matchers.isA;
-import static org.mockito.Mockito.doThrow;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import java.io.IOException;
-import java.text.MessageFormat;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.concurrent.ExecutorService;
-import java.util.logging.Level;
-import java.util.logging.Logger;
-
-import org.apache.commons.cli.Option;
-import org.apache.commons.cli.Options;
-import org.junit.After;
-import org.junit.AfterClass;
-import org.junit.Before;
-import org.junit.BeforeClass;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
-import org.osgi.framework.Bundle;
-import org.osgi.framework.BundleContext;
-import org.osgi.framework.BundleException;
-import org.osgi.framework.FrameworkUtil;
-import org.osgi.framework.ServiceReference;
-import org.powermock.api.mockito.PowerMockito;
-import org.powermock.core.classloader.annotations.PrepareForTest;
-import org.powermock.modules.junit4.PowerMockRunner;
-
-import com.redhat.thermostat.common.ActionListener;
-import com.redhat.thermostat.common.ActionNotifier;
-import com.redhat.thermostat.common.ApplicationInfo;
-import com.redhat.thermostat.common.ApplicationService;
-import com.redhat.thermostat.common.Version;
-import com.redhat.thermostat.common.cli.AbstractStateNotifyingCommand;
-import com.redhat.thermostat.common.cli.Arguments;
-import com.redhat.thermostat.common.cli.Command;
-import com.redhat.thermostat.common.cli.CommandContext;
-import com.redhat.thermostat.common.cli.CommandException;
-import com.redhat.thermostat.common.cli.CommandInfo;
-import com.redhat.thermostat.common.cli.CommandInfoNotFoundException;
-import com.redhat.thermostat.common.cli.CommandInfoSource;
-import com.redhat.thermostat.common.config.ClientPreferences;
-import com.redhat.thermostat.common.locale.LocaleResources;
-import com.redhat.thermostat.common.locale.Translate;
-import com.redhat.thermostat.common.tools.ApplicationState;
-import com.redhat.thermostat.common.utils.OSGIUtils;
-import com.redhat.thermostat.launcher.BundleManager;
-import com.redhat.thermostat.launcher.TestCommand;
-import com.redhat.thermostat.launcher.internal.HelpCommand;
-import com.redhat.thermostat.launcher.internal.LauncherImpl;
-import com.redhat.thermostat.launcher.internal.LauncherImpl.LoggingInitializer;
-import com.redhat.thermostat.storage.core.DbService;
-import com.redhat.thermostat.storage.core.DbServiceFactory;
-import com.redhat.thermostat.storage.core.Storage;
-import com.redhat.thermostat.test.TestCommandContextFactory;
-import com.redhat.thermostat.test.TestTimerFactory;
-import com.redhat.thermostat.testutils.StubBundleContext;
-import com.redhat.thermostat.utils.keyring.Keyring;
-import com.redhat.thermostat.utils.keyring.KeyringProvider;
-
-@RunWith(PowerMockRunner.class)
-@PrepareForTest({FrameworkUtil.class, HelpCommand.class, OSGIUtils.class})
-public class LauncherTest {
-    
-    private static String defaultKeyringProvider;
-    private static final String name1 = "test1";
-    private static final String name2 = "test2";
-    private static final String name3 = "test3";
-      
-    @BeforeClass
-    public static void beforeClassSetUp() {
-        defaultKeyringProvider = System.getProperty(KeyringProvider.KEYRING_FACTORY_PROPERTY);
-    }
-    
-    @AfterClass
-    public static void afterClassTearDown() {
-        if (defaultKeyringProvider != null) {
-            System.setProperty(KeyringProvider.KEYRING_FACTORY_PROPERTY, defaultKeyringProvider);
-        }
-    }
-    
-    private static class TestCmd1 implements TestCommand.Handle {
-
-        @Override
-        public void run(CommandContext ctx) {
-            Arguments args = ctx.getArguments();
-            ctx.getConsole().getOutput().print(args.getArgument("arg1") + ", " + args.getArgument("arg2"));
-        }
-    }
-
-    private static class TestCmd2 implements TestCommand.Handle {
-        @Override
-        public void run(CommandContext ctx) {
-            Arguments args = ctx.getArguments();
-            ctx.getConsole().getOutput().print(args.getArgument("arg4") + ": " + args.getArgument("arg3"));
-        }
-    }
-
-    private TestCommandContextFactory  ctxFactory;
-    private StubBundleContext bundleContext;
-    private Bundle sysBundle;
-    private TestTimerFactory timerFactory;
-    private BundleManager registry;
-    private LoggingInitializer loggingInitializer;
-    private DbServiceFactory dbServiceFactory;
-    private CommandInfoSource infos;
-    private ActionNotifier<ApplicationState> notifier;
-
-    private LauncherImpl launcher;
-    private Storage storage;
-
-    @Before
-    public void setUp() {
-        setupCommandContextFactory();
-
-        TestCommand cmd1 = new TestCommand(name1, new TestCmd1());
-        CommandInfo info1 = mock(CommandInfo.class);
-        when(info1.getName()).thenReturn(name1);
-        Options options1 = new Options();
-        Option opt1 = new Option(null, "arg1", true, null);
-        options1.addOption(opt1);
-        Option opt2 = new Option(null, "arg2", true, null);
-        options1.addOption(opt2);
-        // cmd1 needs logLevel option since it is used in tests if logLevel
-        // option is properly set up
-        Option logLevel = new Option("l", "logLevel", true, null);
-        options1.addOption(logLevel);
-        cmd1.addOptions(opt1, opt2, logLevel);
-        cmd1.setDescription("description 1");
-        when(info1.getDescription()).thenReturn("description 1");
-        when(info1.getOptions()).thenReturn(options1);
-        TestCommand cmd2 = new TestCommand("test2", new TestCmd2());
-        CommandInfo info2 = mock(CommandInfo.class);
-        when(info2.getName()).thenReturn(name2);
-        Options options2 = new Options();
-        Option opt3 = new Option(null, "arg3", true, null);
-        options2.addOption(opt3);
-        Option opt4 = new Option(null, "arg4", true, null);
-        options2.addOption(opt4);
-        cmd2.addOptions(opt3, opt4);
-        cmd2.setDescription("description 2");
-        when(info2.getDescription()).thenReturn("description 2");
-        when(info2.getOptions()).thenReturn(options2);
-
-        TestCommand cmd3 = new TestCommand(name3);
-        CommandInfo info3 = mock(CommandInfo.class);
-        when(info3.getName()).thenReturn(name3);
-        cmd3.setStorageRequired(true);
-        cmd3.setDescription("description 3");
-        when(info3.getDescription()).thenReturn("description 3");
-        when(info3.getOptions()).thenReturn(new Options());
-
-        AbstractStateNotifyingCommand basicCmd = mock(AbstractStateNotifyingCommand.class);
-        CommandInfo basicInfo = mock(CommandInfo.class);
-        when(basicCmd.getName()).thenReturn("basic");
-        when(basicInfo.getName()).thenReturn("basic");
-        when(basicCmd.getDescription()).thenReturn("nothing that means anything");
-        when(basicInfo.getDescription()).thenReturn("nothing that means anything");
-        when(basicCmd.isStorageRequired()).thenReturn(false);
-        Options options = new Options();
-        when(basicCmd.getOptions()).thenReturn(options);
-        when(basicInfo.getOptions()).thenReturn(options);
-        notifier = mock(ActionNotifier.class);
-        when(basicCmd.getNotifier()).thenReturn(notifier);
-        CommandInfo helpCommandInfo = mock(CommandInfo.class);
-        when(helpCommandInfo.getName()).thenReturn("help");
-        when(helpCommandInfo.getDescription()).thenReturn("print help information");
-        when(helpCommandInfo.getDependencyResourceNames()).thenReturn(new ArrayList<String>());
-        when(helpCommandInfo.getOptions()).thenReturn(new Options());
-        when(helpCommandInfo.getUsage()).thenReturn("thermostat help");
-
-        ctxFactory.getCommandRegistry().registerCommands(Arrays.asList(new HelpCommand(), cmd1, cmd2, cmd3, basicCmd));
-
-        registry = mock(BundleManager.class);
-
-        infos = mock(CommandInfoSource.class);
-        when(infos.getCommandInfo(name1)).thenReturn(info1);
-        when(infos.getCommandInfo(name2)).thenReturn(info2);
-        when(infos.getCommandInfo(name3)).thenReturn(info3);
-        when(infos.getCommandInfo("basic")).thenReturn(basicInfo);
-        when(infos.getCommandInfo("help")).thenReturn(helpCommandInfo);
-
-        Collection<CommandInfo> infoList = new ArrayList<CommandInfo>();
-        infoList.add(helpCommandInfo);
-        infoList.add(basicInfo);
-        infoList.add(info1);
-        infoList.add(info2);
-        infoList.add(info3);
-        when(infos.getCommandInfos()).thenReturn(infoList);
-
-        PowerMockito.mockStatic(FrameworkUtil.class);
-        Bundle bundle = mock(Bundle.class);
-        BundleContext bCtx = mock(BundleContext.class);
-        when(bundle.getBundleContext()).thenReturn(bCtx);
-        ServiceReference infosRef = mock(ServiceReference.class);
-        when(bCtx.getServiceReference(CommandInfoSource.class)).thenReturn(infosRef);
-        when(bCtx.getService(infosRef)).thenReturn(infos);
-        when(FrameworkUtil.getBundle(isA(HelpCommand.class.getClass()))).thenReturn(bundle);
-
-        storage = mock(Storage.class);
-        ServiceReference storageRef = mock(ServiceReference.class);
-        Bundle launcherBundle = mock(Bundle.class);
-        BundleContext launcherBundleCtx = mock(BundleContext.class);
-        when(launcherBundleCtx.getServiceReference(Storage.class)).thenReturn(storageRef);
-        when(launcherBundleCtx.getService(storageRef)).thenReturn(storage);
-        when(launcherBundle.getBundleContext()).thenReturn(launcherBundleCtx);
-        when(FrameworkUtil.getBundle(LauncherImpl.class)).thenReturn(launcherBundle);
-
-        timerFactory = new TestTimerFactory();
-        ExecutorService exec = mock(ExecutorService.class);
-        ApplicationService appSvc = mock(ApplicationService.class);
-        when(appSvc.getTimerFactory()).thenReturn(timerFactory);
-        when(appSvc.getApplicationExecutor()).thenReturn(exec);
-        OSGIUtils osgi = mock(OSGIUtils.class);
-        when(osgi.getService(ApplicationService.class)).thenReturn(appSvc);
-        PowerMockito.mockStatic(OSGIUtils.class);
-        when(OSGIUtils.getInstance()).thenReturn(osgi);
-
-        loggingInitializer = mock(LoggingInitializer.class);
-        dbServiceFactory = mock(DbServiceFactory.class);
-
-        launcher = new LauncherImpl(bundleContext, ctxFactory, registry, loggingInitializer, dbServiceFactory);
-
-        Keyring keyring = mock(Keyring.class);
-        launcher.setPreferences(new ClientPreferences(keyring));
-    }
-
-    private void setupCommandContextFactory() {
-        sysBundle = mock(Bundle.class);
-        bundleContext = new StubBundleContext();
-        bundleContext.setBundle(0, sysBundle);
-        ctxFactory = new TestCommandContextFactory(bundleContext);
-    }
-
-
-    @After
-    public void tearDown() {
-        ctxFactory = null;
-        bundleContext = null;
-    }
-
-    @Test
-    public void testMain() {
-        runAndVerifyCommand(new String[] {name1, "--arg1", "Hello", "--arg2", "World"}, "Hello, World");
-
-        ctxFactory.reset();
-
-        runAndVerifyCommand(new String[] {"test2", "--arg3", "Hello", "--arg4", "World"}, "World: Hello");
-    }
-
-    @Test
-    public void testMainNoArgs() {
-        String expected = "list of commands:\n\n"
-                        + " help          print help information\n"
-                        + " basic         nothing that means anything\n"
-                        + " test1         description 1\n"
-                        + " test2         description 2\n"
-                        + " test3         description 3\n";
-        runAndVerifyCommand(new String[0], expected);
-    }
-
-    @Test
-    public void verifySetLogLevel() {
-        runAndVerifyCommand(new String[] {name1, "--logLevel", "WARNING", "--arg1", "Hello", "--arg2", "World"}, "Hello, World");
-        Logger globalLogger = Logger.getLogger("com.redhat.thermostat");
-        assertEquals(Level.WARNING, globalLogger.getLevel());
-    }
-
-    @Test
-    public void testMainBadCommand1() {
-        when(infos.getCommandInfo("--help")).thenThrow(new CommandInfoNotFoundException("--help"));
-
-        String expected = "unknown command '--help'\n"
-            + "list of commands:\n\n"
-            + " help          print help information\n"
-            + " basic         nothing that means anything\n"
-            + " test1         description 1\n"
-            + " test2         description 2\n"
-            + " test3         description 3\n";
-        runAndVerifyCommand(new String[] {"--help"}, expected);
-    }
-
-    @Test
-    public void testMainBadCommand2() {
-        when(infos.getCommandInfo("-help")).thenThrow(new CommandInfoNotFoundException("-help"));
-
-        String expected = "unknown command '-help'\n"
-            + "list of commands:\n\n"
-            + " help          print help information\n"
-            + " basic         nothing that means anything\n"
-            + " test1         description 1\n"
-            + " test2         description 2\n"
-            + " test3         description 3\n";
-        runAndVerifyCommand(new String[] {"-help"}, expected);
-    }
-
-    @Test
-    public void testMainBadCommand3() {
-        when(infos.getCommandInfo("foobarbaz")).thenThrow(new CommandInfoNotFoundException("foobarbaz"));
-
-        String expected = "unknown command 'foobarbaz'\n"
-            + "list of commands:\n\n"
-            + " help          print help information\n"
-            + " basic         nothing that means anything\n"
-            + " test1         description 1\n"
-            + " test2         description 2\n"
-            + " test3         description 3\n";
-        runAndVerifyCommand(new String[] {"foobarbaz"}, expected);
-    }
-
-    @Test
-    public void testMainBadCommand4() {
-        when(infos.getCommandInfo("foo")).thenThrow(new CommandInfoNotFoundException("foo"));
-
-        String expected = "unknown command 'foo'\n"
-            + "list of commands:\n\n"
-            + " help          print help information\n"
-            + " basic         nothing that means anything\n"
-            + " test1         description 1\n"
-            + " test2         description 2\n"
-            + " test3         description 3\n";
-        runAndVerifyCommand(new String[] {"foo",  "--bar", "baz"}, expected);
-    }
-
-    @Test
-    public void testCommandInfoNotFound() throws CommandInfoNotFoundException, BundleException, IOException {
-        when(infos.getCommandInfo("foo")).thenThrow(new CommandInfoNotFoundException("foo"));
-        doThrow(new CommandInfoNotFoundException("foo")).when(registry).addBundlesFor("foo");
-
-        String expected = "unknown command 'foo'\n"
-                + "list of commands:\n\n"
-                + " help          print help information\n"
-                + " basic         nothing that means anything\n"
-                + " test1         description 1\n"
-                + " test2         description 2\n"
-                + " test3         description 3\n";
-            runAndVerifyCommand(new String[] {"foo"}, expected);
-    }
-
-    @Test
-    public void testMainExceptionInCommand() {
-        TestCommand errorCmd = new TestCommand("error", new TestCommand.Handle() {
-
-            @Override
-            public void run(CommandContext ctx) throws CommandException {
-                throw new CommandException("test error");
-            }
-
-        });
-        ctxFactory.getCommandRegistry().registerCommands(Arrays.asList(errorCmd));
-
-        launcher.setArgs(new String[] { "error" });
-        launcher.run();
-        assertEquals("test error\n", ctxFactory.getError());
-
-    }
-
-    private void runAndVerifyCommand(String[] args, String expected) {
-        launcher.setArgs(args);
-        launcher.run();
-        assertEquals(expected, ctxFactory.getOutput());
-        assertTrue(timerFactory.isShutdown());
-    }
-
-    @Test
-    public void verifyPrefsAreUsed() {
-        ClientPreferences prefs = mock(ClientPreferences.class);
-        String dbUrl = "mongo://fluff:12345";
-        when(prefs.getConnectionUrl()).thenReturn(dbUrl);
-
-        DbService dbService = mock(DbService.class);
-        ArgumentCaptor<String> dbUrlCaptor = ArgumentCaptor.forClass(String.class);
-        when(dbServiceFactory.createDbService(anyString(), anyString(), dbUrlCaptor.capture())).thenReturn(dbService);
-        launcher.setPreferences(prefs);
-        launcher.setArgs(new String[] { "test3" });
-        launcher.run();
-        verify(dbService).connect();
-        verify(prefs).getConnectionUrl();
-        assertEquals(dbUrl, dbUrlCaptor.getValue());
-    }
-
-    @Test
-    public void verifyDbServiceConnectIsCalledForStorageCommand() throws Exception {
-        Command mockCmd = mock(Command.class);
-        when(mockCmd.getName()).thenReturn("dummy");
-        when(mockCmd.isStorageRequired()).thenReturn(true);
-        Options options = mock(Options.class);
-        when(mockCmd.getOptions()).thenReturn(options);
-        
-        ctxFactory.getCommandRegistry().registerCommand(mockCmd);
-        
-        DbService dbService = mock(DbService.class);
-        when(dbServiceFactory.createDbService(anyString(), anyString(), anyString())).thenReturn(dbService);
-
-        launcher.setArgs(new String[] { "dummy" });
-        launcher.run();
-        verify(dbService).connect();
-    }
-
-    @Test
-    public void verifyVersionInfoQuery() {
-        int major = 0;
-        int minor = 3;
-        int micro = 0;
-        
-        ApplicationInfo appInfo = new ApplicationInfo();
-        Translate<LocaleResources> t = LocaleResources.createLocalizer();
-        String format = MessageFormat.format(
-                t.localize(LocaleResources.APPLICATION_VERSION_INFO),
-                appInfo.getName())
-                + " " + Version.VERSION_NUMBER_FORMAT;
-        
-        String expectedVersionInfo = String.format(format,
-                major, minor, micro) + "\n";
-        
-        String qualifier = "201207241700";
-
-        org.osgi.framework.Version ver = org.osgi.framework.Version
-                .parseVersion(String.format(Version.VERSION_NUMBER_FORMAT,
-                        major, minor, micro) + "." + qualifier);
-        when(sysBundle.getVersion()).thenReturn(ver);
-        
-        PowerMockito.mockStatic(FrameworkUtil.class);
-        when(FrameworkUtil.getBundle(Version.class)).thenReturn(sysBundle);
-        launcher.setArgs(new String[] {Version.VERSION_OPTION});
-        launcher.run();
-
-        assertEquals(expectedVersionInfo, ctxFactory.getOutput());
-        assertTrue(timerFactory.isShutdown());
-    }
-
-    @Test
-    public void verifyListenersAdded() {
-        ActionListener<ApplicationState> listener = mock(ActionListener.class);
-        Collection<ActionListener<ApplicationState>> listeners = new ArrayList<>();
-        listeners.add(listener);
-        String[] args = new String[] {"basic"};
-
-        launcher.setArgs(args);
-        launcher.run(listeners);
-        verify(notifier).addActionListener(listener);
-    }
-
-    @Test
-    public void verifyLoggingIsInitialized() {
-        launcher.setArgs(new String[] { "test1" });
-        launcher.run();
-
-        verify(loggingInitializer).initialize();
-    }
-
-    @Test
-    public void verifyShutdown() throws BundleException {
-        launcher.setArgs(new String[] { "test1" });
-        launcher.run();
-
-        verify(sysBundle).stop();
-    }
-
-}
-
--- a/main/src/main/java/com/redhat/thermostat/main/Thermostat.java	Tue Jan 29 00:28:43 2013 -0500
+++ b/main/src/main/java/com/redhat/thermostat/main/Thermostat.java	Tue Jan 29 00:28:44 2013 -0500
@@ -70,7 +70,7 @@
         ServiceTracker tracker = new ServiceTracker(context, Launcher.class.getName(), null);
         tracker.open();
         launcher = (Launcher) tracker.waitForService(0);
-        launcher.run();
+        launcher.run(false);
         tracker.close();
     }