changeset 181:e37e710e45df

added support for compatibility tests and some other improvements (generating thermostat config files from templates, posiblity to show @Before methods logs in report, ...) Changes: Makefile: - added targets new for compatibility testing (and report generation) src/org/thermostat/qa2/framework/utils/ThermostatUtilities.java: - all thermostat and storage configuration code moved from TestRunner to ThermostatUtilities class - webapp deployment (tomcat) is moved from makefile to testsuite (ThermostatUtilities class) - storage configuration files are now generated from templates, which are placed in templates directory - added helper methods, which make using thermostat shell simpler src/org/thermostat/qa2/framework/utils/ThermostatUtilities.java: - code needed for replacing patterns moved from reporters Generator class to CommonUtilities class (because it is now also used to generate config files from templates) src/org/thermostat/qa2/reporter/Generator.java: src/org/thermostat/qa2/reporter/LogParser.java: src/org/thermostat/qa2/reporter/result/TestClassResult.java: src/org/thermostat/qa2/framework/TestRunner.java - added possibility to show logs of methods annotaded with @Before annotation, when some of tests in that their test class failes src/org/thermostat/qa2/tests/DBCommandsHeapDumpTest.java: - code updated to use new methods from ThermostatUtilities class src/org/thermostat/qa2/framework/annotations/SetupStorage.java - it is now possible set SAVE_ON_EXIT property in agent.properties using this annotation src/org/thermostat/qa2/framework/services/ThermostatStorage.java - framework is now not waiting for timeout, when message about thermostat setup is displayed src/org/thermostat/qa2/framework/ThermostatQAConfig.java - added/updated to contain framework new configuration data, removed code not needed anymore templates/storage-config/* - added thermostat config files templates
author Zdenek Zambersky <zzambers@redhat.com>
date Fri, 15 May 2015 18:51:56 +0200
parents aee16d21b7ad
children 98c90d0f337d
files Makefile src/org/thermostat/qa2/framework/TestRunner.java src/org/thermostat/qa2/framework/ThermostatQAConfig.java src/org/thermostat/qa2/framework/annotations/SetupStorage.java src/org/thermostat/qa2/framework/services/ThermostatFullStorage.java src/org/thermostat/qa2/framework/services/ThermostatStorage.java src/org/thermostat/qa2/framework/utils/CommonUtilities.java src/org/thermostat/qa2/framework/utils/ProcessUtilities.java src/org/thermostat/qa2/framework/utils/ThermostatUtilities.java src/org/thermostat/qa2/reporter/Generator.java src/org/thermostat/qa2/reporter/LogParser.java src/org/thermostat/qa2/reporter/result/TestClassResult.java src/org/thermostat/qa2/tests/DBCommandsHeapDumpTest.java templates/storage-config/agent.auth templates/storage-config/agent.properties templates/storage-config/client.properties templates/storage-config/thermostat-roles.properties templates/storage-config/thermostat-users.properties
diffstat 18 files changed, 733 insertions(+), 250 deletions(-) [+]
line wrap: on
line diff
--- a/Makefile	Wed May 06 09:50:46 2015 +0200
+++ b/Makefile	Fri May 15 18:51:56 2015 +0200
@@ -46,9 +46,9 @@
 THERMOSTAT_WEB_DEPLOY_DIR=$(TOMCAT_DIR)/webapps/thermostat
 
 # refers to thermostat against which compatibility is tested
-THERMOSTAT_OTHER_VERSION=1.0
+THERMOSTAT_OTHER_VERSION=1.2
 THERMOSTAT_OTHER_DIR=$(BUILD_DIR)/thermostat-other
-THERMOSTAT_OTHER_REPO_URL=http://icedtea.classpath.org/hg/release/thermostat-1.0/
+THERMOSTAT_OTHER_REPO_URL=http://icedtea.classpath.org/hg/release/thermostat-1.2/
 
 JAPI_COMPILANCE_CHECKER_VERSION=1.3.7
 JAPI_COMPILANCE_CHECKER_NAME=japi-compliance-checker-$(JAPI_COMPILANCE_CHECKER_VERSION)
@@ -70,7 +70,7 @@
 
 # all testsuites ( in $(SOURCE_DIR)/$(TEST_PACKAGE) package )
 TESTS = $(shell ls -1 $(SOURCE_DIR)/$(TEST_PACKAGE) | grep -v package-info.java | sed -n -e 's/\([A-Za-z0-9]*\)\.java/\1/p')
-
+COMPATIBILITY_TESTS = $(shell ls -1 $(SOURCE_DIR)/$(TEST_PACKAGE)/compatibility | grep -v package-info.java | sed -n -e 's/\([A-Za-z0-9]*\)\.java/\1/p')
 
 .PHONY: all
 all: build runtests
@@ -115,7 +115,7 @@
 .PHONY: runtests
 runtests:	$(TESTS)
 
-$(TESTS): build tomcat thermostat-build testwarning checkdeps $(THERMOSTAT_WEB_DEPLOY_DIR) | $(GNOME_KEYRING_USER_DATA_DIR)
+$(TESTS): build tomcat thermostat-build testwarning checkdeps | $(GNOME_KEYRING_USER_DATA_DIR)
 	killall mongod &> /dev/null || true
 	killall gnome-keyring-daemon &> /dev/null || true
 	mkdir -p $(BUILD_DIR)/backup
@@ -129,6 +129,7 @@
 	-Dthermostat.user.home=$(THERMOSTAT_USER_DIR) \
 	-Dthermostat.executable.name=thermostat \
 	-Dthermostat.executable.path=$(THERMOSTAT_DIR)/distribution/target/image/bin \
+	-Dthermostat.webapp.path=$(THERMOSTAT_DIR)/web/war/target \
 	-Dthermostat.version=$(THERMOSTAT_VERSION) \
 	-Dscreenshot.source=$(SCREENSHOT_SOURCE) \
 	-Dclean.after.gui.test=true	\
@@ -136,6 +137,31 @@
 	-Dbackup.path=$(BUILD_DIR)/backup \
 	org.thermostat.qa2.framework.TestRunner org.thermostat.qa2.tests.$@ 2>&1 | tee $(LOGS_DIR)/$(DATE)/$@.log
 
+.PHONY: run-compatibility-tests
+run-compatibility-tests:	$(COMPATIBILITY_TESTS)	
+
+$(COMPATIBILITY_TESTS): build tomcat thermostat-build thermostat-other-build testwarning checkdeps | $(GNOME_KEYRING_USER_DATA_DIR)
+	killall mongod &> /dev/null || true
+	killall gnome-keyring-daemon &> /dev/null || true
+	mkdir -p $(BUILD_DIR)/backup
+	mkdir -p $(SCREENSHOTS_DIR)
+	mkdir -p $(LOGS_DIR)
+	mkdir -p $(LOGS_DIR)/compatibility/$(THERMOSTAT_OTHER_VERSION)/$(DATE)
+	USER_THERMOSTAT_HOME=$(THERMOSTAT_USER_DIR) \
+	$(JAVA) -cp $(CLASSES_DIR) \
+	-Dtomcat.home=$(TOMCAT_DIR) \
+	-Dthermostat.home=$(THERMOSTAT_DIR)/distribution/target/image \
+	-Dthermostat.user.home=$(THERMOSTAT_USER_DIR) \
+	-Dthermostat.executable.path=$(THERMOSTAT_DIR)/distribution/target/image/bin \
+	-Dthermostat.version=$(THERMOSTAT_VERSION) \
+	-Dthermostat.webapp.path=$(THERMOSTAT_DIR)/web/war/target \
+	-Dthermostat.other.home=$(THERMOSTAT_OTHER_DIR)/distribution/target/image \
+	-Dthermostat.other.version=$(THERMOSTAT_OTHER_VERSION) \
+	-Dthermostat.other.webapp.path=$(THERMOSTAT_OTHER_DIR)/web/war/target \
+	-Dgnome-keyring.config.path=$(GNOME_KEYRING_USER_DATA_DIR) \
+	-Dbackup.path=$(BUILD_DIR)/backup \
+	org.thermostat.qa2.framework.TestRunner org.thermostat.qa2.tests.compatibility.$@ 2>&1 | tee $(LOGS_DIR)/compatibility/$(THERMOSTAT_OTHER_VERSION)/$(DATE)/$@.log
+
 .PHONY: testwarning
 testwarning:
 	@# if KILL_DAEMONS != 1 and mongo or gnome keyring is running
@@ -197,7 +223,17 @@
 	-Dlogs.path=$(LOGS_DIR) \
 	-Dreports.path=$(REPORT_DIR) \
 	org.thermostat.qa2.reporter.Reporter
-	
+
+report-compatibility-tests:	$(CLASSES_DIR) flotr
+	mkdir -p $(REPORT_DIR)/compatibility/$(THERMOSTAT_OTHER_VERSION)
+	cp -u $(TEMPLATE_DIR)/style.css $(REPORT_DIR)/compatibility/$(THERMOSTAT_OTHER_VERSION)/style.css
+	cp -r $(FLOTR_DIR) $(REPORT_DIR)/compatibility/$(THERMOSTAT_OTHER_VERSION)
+	$(JAVA) -cp $(CLASSES_DIR) \
+	-Dthermostat.version=$(THERMOSTAT_VERSION) \
+	-Dlogs.path=$(LOGS_DIR)/compatibility/$(THERMOSTAT_OTHER_VERSION)/ \
+	-Dreports.path=$(REPORT_DIR)/compatibility/$(THERMOSTAT_OTHER_VERSION) \
+	org.thermostat.qa2.reporter.Reporter
+
 javadoc:
 	mkdir -p $(JAVADOC_DIR)
 	javadoc -d $(JAVADOC_DIR) -classpath src org.thermostat.qa.testsuites
--- a/src/org/thermostat/qa2/framework/TestRunner.java	Wed May 06 09:50:46 2015 +0200
+++ b/src/org/thermostat/qa2/framework/TestRunner.java	Fri May 15 18:51:56 2015 +0200
@@ -30,7 +30,6 @@
  */
 package org.thermostat.qa2.framework;
 
-import java.io.File;
 import java.lang.reflect.Method;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -41,7 +40,6 @@
 import org.thermostat.qa2.framework.services.*;
 import org.thermostat.qa2.framework.utils.AnnotationUtilities;
 import org.thermostat.qa2.framework.utils.CommonUtilities;
-import org.thermostat.qa2.framework.utils.ProcessUtilities;
 import org.thermostat.qa2.framework.utils.ThermostatUtilities;
 
 /**
@@ -92,17 +90,26 @@
 
             List<Method> beforeMethods = AnnotationUtilities.getAnnotatedObjects(methods, Before.class);
             boolean beforeMethodFailed = false;
-            try {
-                for (Method m : beforeMethods) {
+            for (Method m : beforeMethods) {
+                Throwable t = null;
+                try {
                     runThermostatSetup(m);
                     CommonUtilities.printHeading("Running \"before\" method: " + m.getName() + " ...");
                     m.invoke(o);
+                } catch (Throwable e) {
+                    t = e;
+                    beforeMethodFailed = true;
+                } finally {
+                    runServiceCleanups();
                 }
-            } catch (Throwable e) {
-                e.printStackTrace();
-                beforeMethodFailed = true;
-            } finally {
-                runServiceCleanups();
+                String logNameString = "@Before: " + className + "." + m.getName();
+                if (t != null) {
+                    System.out.println("ERROR: " + logNameString);
+                    t.printStackTrace(System.out);
+                    break;
+                } else {
+                    System.out.println("PASSED: " + logNameString);
+                }
             }
             for (Method m : testMethods) {
                 TestResult result;
@@ -141,12 +148,15 @@
                     case FAILED:
                         System.out.println("FAILED: " + testName + ": FAILED null");
                         if (t != null) {
-                            t.printStackTrace();
+                            t.printStackTrace(System.out);
                         }
                         ++failedCount;
                         break;
                     case ERROR:
                         System.out.println("ERROR: " + testName);
+                        if (t != null) {
+                            t.printStackTrace(System.out);
+                        }
                         ++errorCount;
                         break;
                     case IGNORED:
@@ -183,17 +193,10 @@
         boolean startGui = startGuiA != null;
 
         String setupTarget = setupTargetA.value();
-        String thermostatHome = ThermostatQAConfig.getThermostatHome(setupTarget);
-        String thermostatUserHome = ThermostatQAConfig.getThermostatUserHome(setupTarget);
 
         // setup thermostat
         if (setupThermostat) {
-            if (new File(thermostatUserHome).exists()) {
-                BackupService backupService = new BackupService(thermostatUserHome);
-                backupService.start();
-                ProcessUtilities.run("rm", "-rf", "--", thermostatUserHome);
-            }
-            ThermostatUtilities.runThermostatSetup(thermostatHome, thermostatUserHome);
+            ThermostatUtilities.setupThermostat(setupTarget, true);
         }
         // setup storage
         if (setupStorage) {
@@ -201,20 +204,8 @@
             boolean web = ThermostatQAConfig.getStorageTypeAsBoolean(storageType);
             boolean badAgentLogin = setupStorageA.badAgentLogin();
             boolean badClientLogin = setupStorageA.badClientLogin();
-            if (!web) {
-                ThermostatUtilities.setupDefaultMongoUsers(setupTarget);
-            }
-            boolean needBackup = ThermostatQAConfig.getThermostatConfigFiles(setupTarget, web).length > 0;
-            if (needBackup) {
-                BackupService backupService = new BackupService(thermostatHome + File.separator + "etc");
-                backupService.start();
-            }
-            ThermostatUtilities.copyThermostatConfigFiles(setupTarget, web, badAgentLogin, badClientLogin);
-            String tomcatHome = ThermostatQAConfig.getTomcatHome();
-            //BackupService backupService = new BackupService(tomcatHome + File.separator + "logs");
-            //backupService.start();
-            CommonUtilities.printHeading("Cleaning up tomcat logs");
-            ProcessUtilities.shellRun("rm -rf -- \"" + tomcatHome + File.separator + "logs" + File.separator + "\"" + "*");
+            boolean agentSaveOnExit = setupStorageA.agentSaveOnExit();
+            ThermostatUtilities.setupStorage(setupTarget, web, badAgentLogin, badClientLogin, agentSaveOnExit, true, false);
         }
         // start storage
         if (startStorage) {
--- a/src/org/thermostat/qa2/framework/ThermostatQAConfig.java	Wed May 06 09:50:46 2015 +0200
+++ b/src/org/thermostat/qa2/framework/ThermostatQAConfig.java	Fri May 15 18:51:56 2015 +0200
@@ -32,7 +32,6 @@
 
 import java.io.File;
 import java.io.PrintStream;
-import java.util.ArrayList;
 
 /**
  *
@@ -53,7 +52,9 @@
     public static Login mongoLogin = new Login("mongodevuser", "mongodevpassword");
     public static Login agentLogin = new Login("agent-tester", "heslo1");
     public static Login clientLogin = new Login("client-tester", "heslo2");
-    public static Login commandChannelLogin = new Login("cmdc-tester", "heslo3");
+
+    public static Login agentBadLogin = new Login("doctor-evil", "mini-me");
+    public static Login clientBadLogin = new Login("time-for-tea", "aaargh");
 
     ///////////////////////
     // tested thermostat //
@@ -61,6 +62,7 @@
     public static String thermostatHome;
     public static String thermostatUserHome;
     public static String thermostatVersion;
+    public static String thermostatWebAppDir;
 
     //////////////////////
     // other thermostat //
@@ -68,6 +70,7 @@
     public static String thermostatOtherHome;
     //static String ThermostatOtherUserHome;
     public static String thermostatOtherVersion;
+    public static String thermostatOtherWebAppDir;
 
     static {
         tomcatHome = System.getProperty("tomcat.home");
@@ -75,13 +78,15 @@
         thermostatHome = System.getProperty("thermostat.home");
         thermostatUserHome = System.getProperty("thermostat.user.home");
         thermostatVersion = System.getProperty("thermostat.version");
+        thermostatWebAppDir = System.getProperty("thermostat.webapp.path");
 
         thermostatOtherHome = System.getProperty("thermostat.other.home");
         thermostatOtherVersion = System.getProperty("thermostat.other.version");
+        thermostatOtherWebAppDir = System.getProperty("thermostat.other.webapp.path");
 
         gnomeKeyringConfigDir = System.getProperty("gnome-keyring.config.path");
         backupDir = System.getProperty("backup.path");
-        
+
         logsDir = System.getProperty("logs.path");
         reportDir = System.getProperty("reports.path");
     }
@@ -116,6 +121,19 @@
         return tomcatHome;
     }
 
+    public static String getWebAppPath(String target) {
+        String dir = getTargetAsBoolean(target) ? thermostatWebAppDir : thermostatOtherWebAppDir;
+        File dirFile = new File(dir);
+        File[] files = dirFile.listFiles();
+        for (File f : files) {
+            String name = f.getName();
+            if (name.toLowerCase().endsWith(".war")) {
+                return dir + File.separator + name.substring(0, name.length() - 4);
+            }
+        }
+        throw new IllegalArgumentException("web app not found");
+    }
+
     public static String getThermostatHome(String target) {
         return getTargetAsBoolean(target) ? thermostatHome : thermostatOtherHome;
     }
@@ -123,7 +141,7 @@
     public static String getThermostatUserHome() {
         return getThermostatUserHome("tested");
     }
-    
+
     public static String getThermostatUserHome(String target) {
         return thermostatUserHome;
     }
@@ -140,47 +158,6 @@
         return getThermostatHome(target) + File.separator + "bin" + File.separator + "thermostat";
     }
 
-    public static String[] getThermostatUserConfigFiles(String target, boolean web, boolean agentBadlogin, boolean clientBadLogin) {
-        return getThermostatUserConfigFilesForVersion(getThermostatVersion(target), web, agentBadlogin, clientBadLogin);
-    }
-
-    public static String[] getThermostatUserConfigFilesNames() {
-        return new String[]{"agent.auth", "agent.properties", "client.properties"};
-    }
-
-    public static String[] getThermostatConfigFiles(String target, boolean web) {
-        return getThermostatConfigFilesForVersion(getThermostatVersion(target), web);
-    }
-
-    static String[] getThermostatUserConfigFilesForVersion(String thermostatVersion, boolean web, boolean agentBadlogin, boolean clientBadLogin) {
-        String prefix = "storageconfig" + File.separator + thermostatVersion;
-        if (web) {
-            prefix += File.separator + "web-tomcat";
-        } else {
-            prefix += File.separator + "db-mongodb";
-        }
-        ArrayList<String> configFiles = new ArrayList();
-        configFiles.add(prefix + File.separator + (agentBadlogin ? "agent.badauth" : "agent.auth"));
-        configFiles.add(prefix + File.separator + "agent.properties");
-        configFiles.add(prefix + File.separator + (clientBadLogin ? "client.badauth" : "client.properties"));
-        return configFiles.toArray(new String[configFiles.size()]);
-    }
-
-    static String[] getThermostatConfigFilesForVersion(String thermostatVersion, boolean web) {
-        String prefix = "storageconfig" + File.separator + thermostatVersion;
-        if (web) {
-            prefix += File.separator + "web-tomcat";
-        } else {
-            prefix += File.separator + "db-mongodb";
-        }
-        ArrayList<String> configFiles = new ArrayList();
-        if (web) {
-            configFiles.add(prefix + File.separator + "thermostat-users.properties");
-            configFiles.add(prefix + File.separator + "thermostat-roles.properties");
-        }
-        return configFiles.toArray(new String[configFiles.size()]);
-    }
-
     public static String getThermostatOutputTextsDir() {
         return "." + File.separator + "outputtexts" + File.separator + thermostatVersion;
     }
@@ -201,6 +178,18 @@
         return clientLogin;
     }
 
+    public static Login getAgentBadLogin() {
+        return agentBadLogin;
+    }
+
+    public static Login getClientBadLogin() {
+        return clientBadLogin;
+    }
+
+    public static String getStroageUrl(boolean web) {
+        return web ? "http://127.0.0.1:" + webStoragePort + "/thermostat/storage" : "mongodb://127.0.0.1:" + mongoPort;
+    }
+
     public static class Login {
 
         String username;
--- a/src/org/thermostat/qa2/framework/annotations/SetupStorage.java	Wed May 06 09:50:46 2015 +0200
+++ b/src/org/thermostat/qa2/framework/annotations/SetupStorage.java	Fri May 15 18:51:56 2015 +0200
@@ -47,4 +47,6 @@
     boolean badAgentLogin() default false;
 
     boolean badClientLogin() default false;
+    
+    boolean agentSaveOnExit() default false;
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/org/thermostat/qa2/framework/services/ThermostatFullStorage.java	Fri May 15 18:51:56 2015 +0200
@@ -0,0 +1,80 @@
+/*
+ ThermostatQA - test framework for Thermostat Monitoring Tool
+
+ Copyright 2015 Red Hat, Inc.
+
+ This file is part of ThermostatQA
+
+ ThermostatQA is distributed under the GNU General Public License,
+ version 2 or any later version (with a special exception described
+ below, commonly known as the "Classpath Exception").
+
+ A copy of GNU General Public License (GPL) is included in this
+ distribution, in the file COPYING.
+
+ Linking ThermostatQA code with other modules is making a combined work
+ based on ThermostatQA.  Thus, the terms and conditions of the GPL
+ cover the whole combination.
+
+ As a special exception, the copyright holders of ThermostatQA 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 ThermostatQA code.  If you modify ThermostatQA, you may
+ extend this exception to your version of the software, but you are
+ not obligated to do so.  If you do not wish to do so, delete this
+ exception statement from your version.
+ */
+package org.thermostat.qa2.framework.services;
+
+import org.thermostat.qa2.framework.utils.ThermostatUtilities;
+
+/**
+ *
+ * @author Zdeněk Žamberský
+ */
+public class ThermostatFullStorage extends AbstractService {
+
+    ThermostatStorage storage;
+    Tomcat tomcat;
+    boolean web;
+
+    public ThermostatFullStorage() {
+        this("tested");
+    }
+
+    public ThermostatFullStorage(String target) {
+        storage = new ThermostatStorage(target);
+        tomcat = new Tomcat(target);
+    }
+
+    @Override
+    public String getServiceName() throws Exception {
+        return "Thermostat full storage";
+    }
+
+    @Override
+    public void startServiceImpl() throws Exception {
+        setRunning(true);
+        storage.start();
+        web = ThermostatUtilities.isWebStorageConfigured();
+        if (web) {
+            tomcat.start();
+        }
+    }
+
+    @Override
+    public void stopServiceImpl() throws Exception {
+        if (storage.isRunning()) {
+            storage.stop();
+        }
+        if (web && tomcat.isRunning()) {
+            tomcat.stop();
+        }
+        setRunning(false);
+    }
+
+}
--- a/src/org/thermostat/qa2/framework/services/ThermostatStorage.java	Wed May 06 09:50:46 2015 +0200
+++ b/src/org/thermostat/qa2/framework/services/ThermostatStorage.java	Fri May 15 18:51:56 2015 +0200
@@ -35,6 +35,7 @@
 import java.util.List;
 import org.thermostat.qa2.framework.NativeProcess;
 import org.thermostat.qa2.framework.ThermostatQAConfig;
+import org.thermostat.qa2.framework.utils.CommonUtilities;
 
 /**
  *
@@ -72,11 +73,16 @@
         startProcess = new NativeProcess(thermostatHome + File.separator + "bin" + File.separator + "thermostat", "storage", "--start");
         startProcess.setLabel(getServiceName());
         startProcess.setEnvironmentVariable("USER_THERMOSTAT_HOME", thermostatUserHome);
-        if (startStdoutBuffering) {
-            startProcess.setStdOutMode(NativeProcess.MODE_LOG_AND_BUFFER);
-        }
+        //if (startStdoutBuffering) {
+        startProcess.setStdOutMode(NativeProcess.MODE_LOG_AND_BUFFER);
+        //}
         startProcess.start();
         startProcess.waitFor();
+
+        List<String> lines = startProcess.getStdoutLines();
+        if (CommonUtilities.findInLineList(lines, "Please run 'thermostat-setup'.", false)) {
+            throw new Exception("Storage requires thermostat-setup to be run.");
+        }
         waitForListeningPort(mongoPort, timeout);
     }
 
--- a/src/org/thermostat/qa2/framework/utils/CommonUtilities.java	Wed May 06 09:50:46 2015 +0200
+++ b/src/org/thermostat/qa2/framework/utils/CommonUtilities.java	Fri May 15 18:51:56 2015 +0200
@@ -34,6 +34,7 @@
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Map;
 
 /**
  *
@@ -82,4 +83,49 @@
         return CommonUtilities.addLinesToList(list, br);
     }
 
+    public static void replacePatterns(List<String> template, List<String> output, Map<String, List<String>> patterns) {
+        int patternsCount = patterns.size();
+        Map.Entry<String, List<String>>[] entries = new Map.Entry[patternsCount];
+        patterns.entrySet().toArray(entries);
+        for (String line : template) {
+            String restOfLine = line;
+            String toPrint = null;
+            Map.Entry<String, List<String>> entryFound;
+            do {
+                entryFound = null;
+                int index = -1;
+                for (int i = 0; i < patternsCount; ++i) {
+                    Map.Entry<String, List<String>> entry = entries[i];
+                    String pattern = entry.getKey();
+                    int foundIndex = restOfLine.indexOf(pattern);
+                    if (foundIndex >= 0 && (entryFound == null || foundIndex < index)) {
+                        index = foundIndex;
+                        entryFound = entry;
+                    }
+                }
+                if (entryFound != null) {
+                    String pattern = entryFound.getKey();
+                    List<String> replacement = entryFound.getValue();
+                    int replacementSize = replacement.size();
+                    toPrint = line.substring(0, index);
+                    for (int j = 0; j < replacementSize - 1; ++j) {
+                        String replacementLine = replacement.get(j);
+                        output.add(toPrint == null ? replacementLine : toPrint + replacementLine);
+                        toPrint = null;
+                    }
+                    String lastReplacementLine = replacementSize > 0 ? replacement.get(replacementSize - 1) : "";
+                    toPrint = toPrint == null ? lastReplacementLine : toPrint + lastReplacementLine;
+                    restOfLine = line.substring(index + pattern.length());
+                }
+            } while (entryFound != null);
+            output.add(toPrint == null ? restOfLine : toPrint + restOfLine);
+        }
+    }
+
+    public static void addReplacementToMap(Map<String, List<String>> map, String pattern, String replacement) {
+        List<String> replacementText = new ArrayList();
+        replacementText.add(replacement);
+        map.put(pattern, replacementText);
+    }
+
 }
--- a/src/org/thermostat/qa2/framework/utils/ProcessUtilities.java	Wed May 06 09:50:46 2015 +0200
+++ b/src/org/thermostat/qa2/framework/utils/ProcessUtilities.java	Fri May 15 18:51:56 2015 +0200
@@ -106,6 +106,7 @@
     public static Shell createThermostatShell(String target) {
         String[] thermostatCmds = {ThermostatQAConfig.getThermostatExecutablePath(target), "shell"};
         Shell thermostat = new Shell(thermostatCmds);
+        thermostat.setEnvironmentVariable("USER_THERMOSTAT_HOME", ThermostatQAConfig.getThermostatUserHome());
         return thermostat;
     }
 
--- a/src/org/thermostat/qa2/framework/utils/ThermostatUtilities.java	Wed May 06 09:50:46 2015 +0200
+++ b/src/org/thermostat/qa2/framework/utils/ThermostatUtilities.java	Fri May 15 18:51:56 2015 +0200
@@ -31,12 +31,16 @@
 package org.thermostat.qa2.framework.utils;
 
 import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
-import java.util.Properties;
 import org.thermostat.qa2.framework.NativeProcess;
 import org.thermostat.qa2.framework.Shell;
 import org.thermostat.qa2.framework.ThermostatQAConfig;
+import org.thermostat.qa2.framework.ThermostatQAConfig.Login;
+import org.thermostat.qa2.framework.services.BackupService;
 import org.thermostat.qa2.framework.services.ThermostatStorage;
 
 /**
@@ -45,34 +49,19 @@
  */
 public class ThermostatUtilities {
 
-    public static void setupDefaultMongoUsers(String target) throws Exception {
-        ThermostatStorage storage = new ThermostatStorage(target);
-        storage.start();
-        Map map = new Properties();
-        ThermostatQAConfig.Login agentLogin = ThermostatQAConfig.getAgentLogin();
-        ThermostatQAConfig.Login clientLogin = ThermostatQAConfig.getClientLogin();
-        map.put(agentLogin.getUsername(), agentLogin.getPassword());
-        map.put(clientLogin.getUsername(), clientLogin.getPassword());
-        setupMongoUsers("127.0.0.1:" + ThermostatQAConfig.mongoPort, map);
-        storage.stop();
-    }
-
-    public static void setupMongoUsers(String address, Map<String, String> users) throws Exception {
-        CommonUtilities.printHeading("Setting up mongo users ...");
-        Shell mongo = new Shell("mongo", address);
-        mongo.setLabel("Mongo users setup");
-        mongo.start();
-        mongo.writeln("use thermostat");
-        mongo.writeln("db.auth(\"mongodevuser\", \"mongodevpassword\")");
-        for (Map.Entry<String, String> entry : users.entrySet()) {
-            mongo.writeln("db.addUser(\"" + entry.getKey() + "\", \"" + entry.getValue() + "\")");
+    /* runs thermostat setup (creates thermostat user home dir (database)),
+     backup service (on thermostat user home) is started  before if requested */
+    public static void setupThermostat(String setupTarget, boolean backupUserHome) throws Exception {
+        String thermostatHome = ThermostatQAConfig.getThermostatHome(setupTarget);
+        String thermostatUserHome = ThermostatQAConfig.getThermostatUserHome(setupTarget);
+        if (new File(thermostatUserHome).exists()) {
+            if (backupUserHome) {
+                BackupService backupService = new BackupService(thermostatUserHome);
+                backupService.start();
+            }
+            ProcessUtilities.run("rm", "-rf", "--", thermostatUserHome);
         }
-        mongo.writeln("quit()");
-        mongo.waitFor();
-    }
-
-    public static void runThermostatSetup(String name) throws Exception {
-        runThermostatSetup(ThermostatQAConfig.getThermostatHome(name), ThermostatQAConfig.getThermostatUserHome(name));
+        runThermostatSetup(thermostatHome, thermostatUserHome);
     }
 
     public static void runThermostatSetup(String thermostatHome, String thermostatUserHome) throws Exception {
@@ -93,35 +82,187 @@
         process.waitFor();
     }
 
-    public static void copyThermostatConfigFiles(String setupTarget, boolean web, boolean badAgentLogin, boolean badClientLogin) throws Exception {
+    /* does all required tasks to setup storage as requested,
+     it does not modify existing storage (database),
+     it can also start backup services if reqested */
+    public static void setupStorage(String setupTarget, boolean web, boolean badAgentLogin, boolean badClientLogin, boolean agentSaveOnExit, boolean backupThermostatConfig, boolean backupUserHome) throws Exception {
+        if (!web) {
+            setupDefaultMongoUsers(setupTarget);
+        } else {
+            deployThermostatWebApp(setupTarget);
+
+            String tomcatHome = ThermostatQAConfig.getTomcatHome();
+            CommonUtilities.printHeading("Cleaning up tomcat logs");
+            ProcessUtilities.shellRun("rm -rf -- \"" + tomcatHome + File.separator + "logs" + File.separator + "\"" + "*");
+        }
+        if (backupThermostatConfig) {
+            BackupService backupService = new BackupService(ThermostatQAConfig.getThermostatHome(setupTarget) + File.separator + "etc");
+            backupService.start();
+        }
+        if (backupUserHome) {
+            String thermostatUserHome = ThermostatQAConfig.getThermostatUserHome(setupTarget);
+            if (new File(thermostatUserHome).exists()) {
+                BackupService backupService = new BackupService(thermostatUserHome);
+                backupService.start();
+            }
+        }
+
+        copyThermostatConfigFiles(setupTarget, web, badAgentLogin, badClientLogin, agentSaveOnExit);
+    }
+
+    public static void setupDefaultMongoUsers(String target) throws Exception {
+        ThermostatStorage storage = new ThermostatStorage(target);
+        storage.start();
+        List<Login> users = new ArrayList();
+        users.add(ThermostatQAConfig.getAgentLogin());
+        users.add(ThermostatQAConfig.getClientLogin());
+        setupMongoUsers("127.0.0.1:" + ThermostatQAConfig.mongoPort, users);
+        storage.stop();
+    }
+
+    public static void setupMongoUsers(String address, List<Login> users) throws Exception {
+        CommonUtilities.printHeading("Setting up mongo users ...");
+        Shell mongo = new Shell("mongo", address);
+        mongo.setLabel("Mongo users setup");
+        mongo.start();
+        mongo.writeln("use thermostat");
+        Login login = ThermostatQAConfig.getMongoLogin();
+        mongo.writeln("db.auth(\"" + login.getUsername() + "\", \"" + login.getPassword() + "\")");
+        for (Login user : users) {
+            mongo.writeln("db.addUser(\"" + user.getUsername() + "\", \"" + user.getPassword() + "\")");
+        }
+        mongo.writeln("quit()");
+        mongo.waitFor();
+    }
+
+    private static boolean webStorage = false;
+
+    public static void copyThermostatConfigFiles(String setupTarget, boolean web, boolean badAgentLogin, boolean badClientLogin, boolean saveOnExit) throws Exception {
         CommonUtilities.printHeading("Copying thermostat config files ...");
-        String thermostatHome = ThermostatQAConfig.getThermostatHome(setupTarget);
-        String thermostatUserHome = ThermostatQAConfig.getThermostatUserHome(setupTarget);
-        String[] confFiles = ThermostatQAConfig.getThermostatUserConfigFiles(setupTarget, web, badAgentLogin, badClientLogin);
-        String[] confFilesNames = ThermostatQAConfig.getThermostatUserConfigFilesNames();
-        for (int i = 0; i < confFiles.length; ++i) {
-            String file = confFiles[i];
-            String name = confFilesNames[i];
-            FileUtilities.copyFile(file, thermostatUserHome + File.separator + "etc" + File.separator + name);
-        }
-        confFiles = ThermostatQAConfig.getThermostatConfigFiles(setupTarget, web);
-        for (String file : confFiles) {
-            FileUtilities.copyFile(file, thermostatHome + File.separator + "etc" + File.separator + file.substring(file.lastIndexOf(File.separator) + 1));
-        }
+        webStorage = web;
+        String thermostatUserConfigDir = ThermostatQAConfig.getThermostatUserHome(setupTarget) + File.separator + "etc";
+        String thermostatConfigDir = ThermostatQAConfig.getThermostatHome(setupTarget) + File.separator + "etc";
+        FileUtilities.printLineListToFile(thermostatUserConfigDir + File.separator + "agent.auth", getAgentAuth(badAgentLogin));
+        FileUtilities.printLineListToFile(thermostatUserConfigDir + File.separator + "agent.properties", getAgentProperties(web, saveOnExit));
+        FileUtilities.printLineListToFile(thermostatUserConfigDir + File.separator + "client.properties", getClientProperties(web, badClientLogin));
+
+        FileUtilities.printLineListToFile(thermostatConfigDir + File.separator + "thermostat-users.properties", getThermostatUsers());
+        FileUtilities.printLineListToFile(thermostatConfigDir + File.separator + "thermostat-roles.properties", getThermostatRoles());
+    }
+
+    public static final String storageTemplatesDir = "templates" + File.separator + "storage-config";
+    public static final String usernamePattern = "${USERNAME}";
+    public static final String passwordPattern = "${PASSWORD}";
+
+    public static List<String> getAgentAuth(boolean badLogin) throws IOException {
+        List<String> lines = FileUtilities.getLineListFromFile(storageTemplatesDir + File.separator + "agent.auth");
+        Map<String, List<String>> patterns = new HashMap();
+
+        ThermostatQAConfig.Login login = badLogin ? ThermostatQAConfig.getAgentBadLogin() : ThermostatQAConfig.getAgentLogin();
+        CommonUtilities.addReplacementToMap(patterns, usernamePattern, login.getUsername());
+        CommonUtilities.addReplacementToMap(patterns, passwordPattern, login.getPassword());
+
+        List<String> output = new ArrayList();
+        CommonUtilities.replacePatterns(lines, output, patterns);
+        return output;
     }
 
-    public static List<String> getListVmsOutput() throws Exception {
-        ThermostatQAConfig.Login login = ThermostatQAConfig.getClientLogin();
-        return getListVmsOutput("tested", login.getUsername(), login.getPassword());
+    public static final String saveOnExitPattern = "${SAVE_ON_EXIT}";
+    public static final String dbUrlPattern = "${DB_URL}";
+
+    public static List<String> getAgentProperties(boolean web, boolean saveOnExit) throws IOException {
+        List<String> lines = FileUtilities.getLineListFromFile(storageTemplatesDir + File.separator + "agent.properties");
+        Map<String, List<String>> patterns = new HashMap();
+
+        CommonUtilities.addReplacementToMap(patterns, saveOnExitPattern, java.lang.Boolean.toString(saveOnExit));
+        CommonUtilities.addReplacementToMap(patterns, dbUrlPattern, ThermostatQAConfig.getStroageUrl(web));
+
+        List<String> output = new ArrayList();
+        CommonUtilities.replacePatterns(lines, output, patterns);
+        return output;
+    }
+
+    public static final String connectionUrlPattern = "${CONNECTION_URL}";
+
+    public static List<String> getClientProperties(boolean web, boolean badLogin) throws IOException {
+        List<String> lines = FileUtilities.getLineListFromFile(storageTemplatesDir + File.separator + "client.properties");
+        Map<String, List<String>> patterns = new HashMap();
+
+        ThermostatQAConfig.Login login = badLogin ? ThermostatQAConfig.getClientBadLogin() : ThermostatQAConfig.getClientLogin();
+        CommonUtilities.addReplacementToMap(patterns, usernamePattern, login.getUsername());
+        CommonUtilities.addReplacementToMap(patterns, connectionUrlPattern, ThermostatQAConfig.getStroageUrl(web).replace(":", "\\:"));
+
+        List<String> output = new ArrayList();
+        CommonUtilities.replacePatterns(lines, output, patterns);
+        return output;
     }
 
-    public static List<String> getListVmsOutput(String target, String username, String password) throws Exception {
+    public static final String usersPattern = "${USERS}";
+
+    public static List<String> getThermostatUsers() throws IOException {
+        List<String> lines = FileUtilities.getLineListFromFile(storageTemplatesDir + File.separator + "thermostat-users.properties");
+        Map<String, List<String>> patterns = new HashMap();
+
+        List<String> userLines = new ArrayList();
+        Login agentLogin = ThermostatQAConfig.getAgentLogin();
+        Login clientLogin = ThermostatQAConfig.getClientLogin();
+        userLines.add(agentLogin.getUsername() + "=" + agentLogin.getPassword());
+        userLines.add(clientLogin.getUsername() + "=" + clientLogin.getPassword());
+        patterns.put(usersPattern, userLines);
+
+        List<String> output = new ArrayList();
+        CommonUtilities.replacePatterns(lines, output, patterns);
+        return output;
+    }
+
+    public static final String agentUsernamePattern = "${AGENT_USERNAME}";
+    public static final String clientUsernamePattern = "${CLIENT_USERNAME}";
+
+    public static List<String> getThermostatRoles() throws IOException {
+        List<String> lines = FileUtilities.getLineListFromFile(storageTemplatesDir + File.separator + "thermostat-roles.properties");
+        Map<String, List<String>> patterns = new HashMap();
+
+        Login agentLogin = ThermostatQAConfig.getAgentLogin();
+        Login clientLogin = ThermostatQAConfig.getClientLogin();
+        CommonUtilities.addReplacementToMap(patterns, agentUsernamePattern, agentLogin.getUsername());
+        CommonUtilities.addReplacementToMap(patterns, clientUsernamePattern, clientLogin.getUsername());
+
+        List<String> output = new ArrayList();
+        CommonUtilities.replacePatterns(lines, output, patterns);
+        return output;
+    }
+
+    public static boolean isWebStorageConfigured() {
+        return webStorage;
+    }
+
+    public static void deployThermostatWebApp(String setupTarget) throws Exception {
+        CommonUtilities.printHeading("Deploying thermostat web app ...");
+        String deployDir = ThermostatQAConfig.getTomcatHome() + File.separator + "webapps";
+        String deployedAppPath = deployDir + File.separator + "thermostat";
+        if (new File(deployedAppPath).exists()) {
+            ProcessUtilities.shellRun("rm -rf " + deployedAppPath);
+        }
+        String path = ThermostatQAConfig.getWebAppPath(setupTarget);
+        String name = new File(path).getName();
+
+        ProcessUtilities.shellRun("cp -r " + path + " " + deployDir);
+        ProcessUtilities.shellRun("mv " + deployDir + File.separator + new File(path).getName() + " " + deployedAppPath);
+    }
+
+    /* Starts thermostat shell executes requested command in it,
+     writes username/password, exits shell and returns output as line list */
+    public static List<String> getShellCommandOutput(String target, String command) throws Exception {
+        ThermostatQAConfig.Login login = ThermostatQAConfig.getClientLogin();
+        String username = login.getUsername();
+        String password = login.getPassword();
+
         String[] thermostatCmds = {ThermostatQAConfig.getThermostatExecutablePath(target), "shell"};
         Shell thermostat = new Shell(thermostatCmds);
         thermostat.setEnvironmentVariable("USER_THERMOSTAT_HOME", ThermostatQAConfig.getThermostatUserHome(target));
         thermostat.setStdOutMode(NativeProcess.MODE_LOG_AND_BUFFER);
         thermostat.start();
-        thermostat.writeln("list-vms");
+        thermostat.writeln(command);
         thermostat.writeln(username);
         thermostat.writeln(password);
         thermostat.writeln("exit");
@@ -129,4 +270,62 @@
         return thermostat.getStdoutLines();
     }
 
+    public static List<String> getListVmsOutput() throws Exception {
+        return getListVmsOutput("tested");
+    }
+
+    public static List<String> getListVmsOutput(String target) throws Exception {
+        return getShellCommandOutput(target, "list-vms");
+    }
+
+    public static String getRunningVmLine() throws Exception {
+        return ThermostatUtilities.getRunningVmLine("tested");
+    }
+
+    public static String getRunningVmLine(String target) throws Exception {
+        List<String> vmsLines = ThermostatUtilities.getListVmsOutput(target);
+        String vmLine = null;
+
+        for (String line : vmsLines) {
+            if (line.contains("RUNNING") && !line.contains("com.redhat.thermostat.main.Thermostat")) {
+                vmLine = line;
+                break;
+            }
+        }
+        return vmLine;
+    }
+
+    public static List<String> getListHeapDumpsOutput(String target) throws Exception {
+        return getShellCommandOutput(target, "list-heap-dumps");
+    }
+
+    public static String getHeapDumpLine(String hostId, String vmId) throws Exception {
+        return getHeapDumpLine("tested", hostId, vmId);
+    }
+
+    public static String getHeapDumpLine(String target, String hostId, String vmId) throws Exception {
+        List<String> heapLines = ThermostatUtilities.getListHeapDumpsOutput(target);
+        String heapLine = null;
+
+        for (String line : heapLines) {
+            if (line.contains(hostId) && line.contains(vmId)) {
+                heapLine = line;
+                break;
+            }
+        }
+        return heapLine;
+    }
+
+    public static void dumpHeap(String hostId, String vmId) throws Exception {
+        dumpHeap("tested", hostId, vmId);
+    }
+
+    public static void dumpHeap(String target, String hostId, String vmId) throws Exception {
+        getShellCommandOutput(target, "dump-heap --hostId " + hostId + " --vmId " + vmId);
+    }
+
+    public static List<String> profileVm(String target, String hostId, String vmId, String action) throws Exception {
+        return getShellCommandOutput(target, "profile-vm --hostId " + hostId + " --vmId " + vmId + " " + action);
+    }
+
 }
--- a/src/org/thermostat/qa2/reporter/Generator.java	Wed May 06 09:50:46 2015 +0200
+++ b/src/org/thermostat/qa2/reporter/Generator.java	Fri May 15 18:51:56 2015 +0200
@@ -38,8 +38,8 @@
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
-import org.thermostat.qa2.framework.TestRunner;
 import org.thermostat.qa2.framework.TestResult;
+import org.thermostat.qa2.framework.utils.CommonUtilities;
 import org.thermostat.qa2.framework.utils.FileUtilities;
 
 /**
@@ -85,20 +85,20 @@
     ////////////////////
     public static void generateIndexPage(List<String> output, TestRunResult run) throws IOException {
         Map<String, List<String>> replacements = new HashMap();
-        addReplacementToMap(replacements, resultsUrlPattern, "log_" + run.date + ".xhtml");
-        addReplacementToMap(replacements, datePattern, run.date);
-        addReplacementToMap(replacements, passedPattern, Integer.toString(run.passedCount));
-        addReplacementToMap(replacements, failedPattern, Integer.toString(run.failedCount));
-        addReplacementToMap(replacements, errorPattern, Integer.toString(run.errorCount));
-        addReplacementToMap(replacements, ignoredPattern, Integer.toString(run.ignoredCount));
-        addReplacementToMap(replacements, osNamePattern, System.getProperty("os.name"));
-        addReplacementToMap(replacements, osVersionPattern, System.getProperty("os.version"));
-        addReplacementToMap(replacements, osArchPattern, System.getProperty("os.arch"));
-        addReplacementToMap(replacements, javaVersionPattern, System.getProperty("java.version"));
-        addReplacementToMap(replacements, vmNamePattern, System.getProperty("java.vm.name"));
-        addReplacementToMap(replacements, vmVersionPattern, System.getProperty("java.vm.version"));
+        CommonUtilities.addReplacementToMap(replacements, Generator.resultsUrlPattern, "log_" + run.date + ".xhtml");
+        CommonUtilities.addReplacementToMap(replacements, Generator.datePattern, run.date);
+        CommonUtilities.addReplacementToMap(replacements, Generator.passedPattern, Integer.toString(run.passedCount));
+        CommonUtilities.addReplacementToMap(replacements, Generator.failedPattern, Integer.toString(run.failedCount));
+        CommonUtilities.addReplacementToMap(replacements, Generator.errorPattern, Integer.toString(run.errorCount));
+        CommonUtilities.addReplacementToMap(replacements, Generator.ignoredPattern, Integer.toString(run.ignoredCount));
+        CommonUtilities.addReplacementToMap(replacements, Generator.osNamePattern, System.getProperty("os.name"));
+        CommonUtilities.addReplacementToMap(replacements, Generator.osVersionPattern, System.getProperty("os.version"));
+        CommonUtilities.addReplacementToMap(replacements, Generator.osArchPattern, System.getProperty("os.arch"));
+        CommonUtilities.addReplacementToMap(replacements, Generator.javaVersionPattern, System.getProperty("java.version"));
+        CommonUtilities.addReplacementToMap(replacements, Generator.vmNamePattern, System.getProperty("java.vm.name"));
+        CommonUtilities.addReplacementToMap(replacements, Generator.vmVersionPattern, System.getProperty("java.vm.version"));
         List<String> template = FileUtilities.getLineListFromFile(indexPageTemplate);
-        replacePatterns(template, output, replacements);
+        CommonUtilities.replacePatterns(template, output, replacements);
     }
 
     //////////////////
@@ -116,7 +116,7 @@
         replacements.put(sumaryPattern, summaryText);
         replacements.put(resultsPattern, resultsText);
         List<String> template = FileUtilities.getLineListFromFile(logPageTemplate);
-        replacePatterns(template, output, replacements);
+        CommonUtilities.replacePatterns(template, output, replacements);
     }
 
     public static void generateLogSummary(List<String> output, TestRunResult run) {
@@ -129,45 +129,62 @@
         for (TestClassResult test : run.results) {
             String testName = test.name;
             output.add("<tr id='" + testName + "' ><td class='table-header' colspan='2'>" + testName + "</td></tr>");
-            for (TestMethodResult method : test.methods) {
-                TestResult result = method.result;
-                String resultString = result.toString();
-                String resultLine = "<tr id='" + testName + "." + method.name + "' >";
-                resultLine += result == TestResult.PASSED ? "<td>" : "<td class='" + resultString.toLowerCase() + "-header'" + ">";
-                resultLine += "<span class='test-name-prefix'>" + testName + ".</span><span class='test-name-postfix'>" + method.name + "</span>";
-                if (method.reason != null) {
-                    resultLine += "<span class='test-name-postfix'>:&nbsp;&nbsp;</span><small class='reason'><![CDATA[" + method.reason + "]]></small>";
+            if (test.beforeMethods.size() > 0 && (test.failedCount + test.errorCount > 0)) {
+                for (TestMethodResult method : test.beforeMethods) {
+                    generateLogTableRow(output, test, method, true);
                 }
-                resultLine += "</td><td class='" + resultString.toLowerCase() + "-text'>" + resultString + "</td></tr>";
-                output.add(resultLine);
-                List<String> stackTrace = method.stackTrace;
-                if (stackTrace != null) {
-                    output.add("<tr><td>");
-                    output.add("<input id=\"log-toggle-" + testName + "-" + method.name + "\" type=\"checkbox\" class=\"log-toggle\" />");
-                    output.add("<label for=\"log-toggle-" + testName + "-" + method.name + "\"></label><br />");
-                    output.add("<div>");
-                    output.add("<h5>stack trace:</h5>");
-                    output.add("<pre class='stack-trace'><![CDATA[" + (stackTrace.size() > 0 ? stackTrace.get(0) : ""));
-                    for (int i = 1; i < stackTrace.size(); ++i) {
-                        output.add(stackTrace.get(i));
-                    }
-                    output.add("]]></pre>");
-                    output.add("<h5>log:</h5>");
-                    List<String> log = method.getLog();
-                    output.add("<pre class='stack-trace'><![CDATA[" + (log.size() > 0 ? log.get(0) : ""));
-                    for (int i = 1; i < log.size(); ++i) {
-                        output.add(log.get(i));
-                    }
-                    output.add("]]></pre>");
-                    output.add("</div>");
-                    output.add("</td></tr>");
-                }
+                output.add("<tr><td colspan='2'>&nbsp;</td></tr>");
+            }
+            for (TestMethodResult method : test.methods) {
+                generateLogTableRow(output, test, method, false);
             }
             output.add("<tr><td class='error-text' colspan='2'>SUMMARY: " + testName + "&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;total: " + test.methods.size() + "&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;passed: " + test.passedCount + "&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;failed: " + test.failedCount + "&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;error: " + test.errorCount + "&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;not applicable: " + test.ignoredCount + "&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;duration: " + test.duration + "</td></tr>");
             output.add("<tr><td colspan='2'>&nbsp;</td></tr>");
         }
     }
 
+    public static void generateLogTableRow(List<String> output, TestClassResult klass, TestMethodResult method, boolean beforeMethod) {
+        String testName = klass.name;
+        TestResult result = method.result;
+        String resultString = result.toString();
+        String resultLine = "<tr id='" + testName + "." + method.name + "' >";
+        resultLine += result == TestResult.PASSED ? "<td>" : "<td class='" + resultString.toLowerCase() + "-header'" + ">";
+        if (beforeMethod) {
+            resultLine += "<span class='test-name-postfix'>@Before </span>";
+        }
+        resultLine += "<span class='test-name-prefix'>" + testName + ".</span><span class='test-name-postfix'>" + method.name + "</span>";
+        if (method.reason != null) {
+            resultLine += "<span class='test-name-postfix'>:&nbsp;&nbsp;</span><small class='reason'><![CDATA[" + method.reason + "]]></small>";
+        }
+        String shownResultString = beforeMethod ? result == TestResult.PASSED ? "NO ERROR" : "ERROR" : resultString;
+        resultLine += "</td><td class='" + resultString.toLowerCase() + "-text'>" + shownResultString + "</td></tr>";
+        output.add(resultLine);
+        List<String> stackTrace = method.stackTrace;
+        if (result != TestResult.PASSED || beforeMethod) {
+            output.add("<tr><td>");
+            output.add("<input id=\"log-toggle-" + testName + "-" + method.name + "\" type=\"checkbox\" class=\"log-toggle\" />");
+            output.add("<label for=\"log-toggle-" + testName + "-" + method.name + "\"></label><br />");
+            output.add("<div>");
+            if (stackTrace != null && stackTrace.size() > 0) {
+                output.add("<h5>stack trace:</h5>");
+                output.add("<pre class='stack-trace'><![CDATA[" + (stackTrace.size() > 0 ? stackTrace.get(0) : ""));
+                for (int i = 1; i < stackTrace.size(); ++i) {
+                    output.add(stackTrace.get(i));
+                }
+                output.add("]]></pre>");
+            }
+            output.add("<h5>log:</h5>");
+            List<String> log = method.getLog();
+            output.add("<pre class='stack-trace'><![CDATA[" + ((log != null && log.size() > 0) ? log.get(0) : ""));
+            for (int i = 1; i < log.size(); ++i) {
+                output.add(log.get(i));
+            }
+            output.add("]]></pre>");
+            output.add("</div>");
+            output.add("</td></tr>");
+        }
+    }
+
     //////////////////////
     //// HISTORY PAGE ////
     //////////////////////
@@ -180,7 +197,7 @@
         replacements.put(resultsPattern, resultTypeText);
         replacements.put(tableDataPattern, historyTableText);
         List<String> template = FileUtilities.getLineListFromFile(historyPageTemplate);
-        replacePatterns(template, output, replacements);
+        CommonUtilities.replacePatterns(template, output, replacements);
     }
 
     public static void generateHistoryTable(List<String> output, List<TestRunResult> runs, int count, boolean onlyFail) {
@@ -277,7 +294,7 @@
         replacements.put(graphDataNonePattern, noneDataText);
         replacements.put(tableDataPattern, tableText);
         List<String> template = FileUtilities.getLineListFromFile(graphPageTemplate);
-        replacePatterns(template, output, replacements);
+        CommonUtilities.replacePatterns(template, output, replacements);
     }
 
     public static void generateGraphTable(List<String> output, List<TestRunResult> results, int count) {
@@ -304,52 +321,4 @@
         }
     }
 
-    ////////////////
-    //// COMMON ////
-    ////////////////
-    public static void replacePatterns(List<String> template, List<String> output, Map<String, List<String>> patterns) {
-        int patternsCount = patterns.size();
-        Map.Entry<String, List<String>>[] entries = new Map.Entry[patternsCount];
-        patterns.entrySet().toArray(entries);
-        for (String line : template) {
-            String restOfLine = line;
-            String toPrint = null;
-            Map.Entry<String, List<String>> entryFound;
-            do {
-                entryFound = null;
-                int index = -1;
-                for (int i = 0; i < patternsCount; ++i) {
-                    Map.Entry<String, List<String>> entry = entries[i];
-                    String pattern = entry.getKey();
-                    int foundIndex = restOfLine.indexOf(pattern);
-                    if (foundIndex >= 0 && (entryFound == null || foundIndex < index)) {
-                        index = foundIndex;
-                        entryFound = entry;
-                    }
-                }
-                if (entryFound != null) {
-                    String pattern = entryFound.getKey();
-                    List<String> replacement = entryFound.getValue();
-                    int replacementSize = replacement.size();
-                    toPrint = line.substring(0, index);
-                    for (int j = 0; j < replacementSize - 1; ++j) {
-                        String replacementLine = replacement.get(j);
-                        output.add(toPrint == null ? replacementLine : toPrint + replacementLine);
-                        toPrint = null;
-                    }
-                    String lastReplacementLine = replacementSize > 0 ? replacement.get(replacementSize - 1) : "";
-                    toPrint = toPrint == null ? lastReplacementLine : toPrint + lastReplacementLine;
-                    restOfLine = line.substring(index + pattern.length());
-                }
-            } while (entryFound != null);
-            output.add(toPrint == null ? restOfLine : toPrint + restOfLine);
-        }
-    }
-
-    public static void addReplacementToMap(Map<String, List<String>> map, String pattern, String replacement) {
-        List<String> replacementText = new ArrayList();
-        replacementText.add(replacement);
-        map.put(pattern, replacementText);
-    }
-
 }
--- a/src/org/thermostat/qa2/reporter/LogParser.java	Wed May 06 09:50:46 2015 +0200
+++ b/src/org/thermostat/qa2/reporter/LogParser.java	Fri May 15 18:51:56 2015 +0200
@@ -57,7 +57,7 @@
         }
         Collections.sort(testClasses, new Comparator<TestClassResult>() {
             Collator collator = Collator.getInstance();
-            
+
             @Override
             public int compare(TestClassResult o1, TestClassResult o2) {
                 return collator.compare(o1.name, o2.name);
@@ -70,6 +70,7 @@
     public static TestClassResult parseLog(File file) throws IOException {
         List<String> lineList = FileUtilities.getLineListFromFile(file.getAbsolutePath());
         List<TestMethodResult> methodList = new ArrayList();
+        List<TestMethodResult> beforeMethodList = new ArrayList();
         long duration = 0;
         List<String> log = new ArrayList();
         for (int i = 0; i < lineList.size(); ++i) {
@@ -78,8 +79,14 @@
             if (result != null) {
                 String methodLine = line.substring(result.toString().length() + 1);
                 String methodName = getMethodName(methodLine);
+                boolean beforeMethod = methodName.equals("@Before");
+                if (beforeMethod) {
+                    methodLine = methodLine.substring(methodLine.indexOf(methodName) + methodName.length() + 1);
+                    methodName = getMethodName(methodLine);
+                }
+
                 TestMethodResult testMethod = new TestMethodResult(methodName, result);
-                if (result == FAILED) {
+                if (result == FAILED || beforeMethod) {
                     String reason = getReason(methodLine);
                     List<String> stackTrace = new ArrayList();
                     for (;;) {
@@ -102,7 +109,11 @@
                 } else {
                     log.clear();
                 }
-                methodList.add(testMethod);
+                if (beforeMethod) {
+                    beforeMethodList.add(testMethod);
+                } else {
+                    methodList.add(testMethod);
+                }
             } else if (line.startsWith("INFO:")) {
                 log.add(line);
             } else if (line.startsWith("SUMMARY")) {
@@ -115,7 +126,7 @@
         }
         String testName = file.getName();
         testName = testName.substring(0, testName.length() - 4);
-        TestClassResult test = new TestClassResult(testName, methodList, duration);
+        TestClassResult test = new TestClassResult(testName, methodList, beforeMethodList, duration);
         return test;
     }
 
--- a/src/org/thermostat/qa2/reporter/result/TestClassResult.java	Wed May 06 09:50:46 2015 +0200
+++ b/src/org/thermostat/qa2/reporter/result/TestClassResult.java	Fri May 15 18:51:56 2015 +0200
@@ -44,6 +44,7 @@
 
     public String name;
     public List<TestMethodResult> methods;
+    public List<TestMethodResult> beforeMethods;
     public Map<String, TestMethodResult> methodsMap = new HashMap();
 
     public int methodCount;
@@ -54,9 +55,10 @@
 
     public long duration;
 
-    public TestClassResult(String name, List<TestMethodResult> methods, long duration) {
+    public TestClassResult(String name, List<TestMethodResult> methods, List<TestMethodResult> beforeMethods, long duration) {
         this.name = name;
         this.methods = methods;
+        this.beforeMethods = beforeMethods;
         this.duration = duration;
         for (TestMethodResult method : methods) {
             methodsMap.put(method.name, method);
--- a/src/org/thermostat/qa2/tests/DBCommandsHeapDumpTest.java	Wed May 06 09:50:46 2015 +0200
+++ b/src/org/thermostat/qa2/tests/DBCommandsHeapDumpTest.java	Fri May 15 18:51:56 2015 +0200
@@ -54,27 +54,14 @@
     @RunGnomeKeyring
     public void setUp() throws Exception {
         List<String> vmsLines = ThermostatUtilities.getListVmsOutput();
-        String vmLine = null;
-
-        for (String line : vmsLines) {
-            if (line.contains("RUNNING")) {
-                vmLine = line;
-                break;
-            }
-        }
+        String vmLine = ThermostatUtilities.getRunningVmLine();
+        
         assertTrue(vmLine != null, "Problem finding a running vm!");
         String[] lineParts = vmLine.split(" ");
         String hostId = lineParts[0];
         String vmId = lineParts[2];
 
-        String[] thermostatCmds = {ThermostatQAConfig.getThermostatExecutablePath(), "dump-heap", "-a", hostId, "-v", vmId};
-        Shell thermostat = new Shell(thermostatCmds);
-        thermostat.setEnvironmentVariable("USER_THERMOSTAT_HOME", ThermostatQAConfig.getThermostatUserHome());
-        thermostat.start();
-        ThermostatQAConfig.Login login = ThermostatQAConfig.getClientLogin();
-        thermostat.writeln(login.getUsername());
-        thermostat.writeln(login.getPassword());
-        thermostat.waitFor();
+        ThermostatUtilities.dumpHeap(hostId, vmId);
 
         String script = "." + File.separator + "scripts" + File.separator + "check-collections-heap-dump.js";
         scriptOutput = ProcessUtilities.runGetOutput("mongo", "127.0.0.1:" + ThermostatQAConfig.mongoPort, script);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/templates/storage-config/agent.auth	Fri May 15 18:51:56 2015 +0200
@@ -0,0 +1,8 @@
+# This file is intended to be read by a hand-rolled reader/parser, to avoid
+# passwords needing to be represented as String objects at runtime.  It must
+# be saved with Unix line end characters, and encoded as ascii.
+# Uncomment the following lines and replace with your storage authentication
+# parameters as needed.
+#
+username=${USERNAME}
+password=${PASSWORD}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/templates/storage-config/agent.properties	Fri May 15 18:51:56 2015 +0200
@@ -0,0 +1,16 @@
+# Indicates if this agent will save its data to the database on exit
+# or rather will purge the db
+SAVE_ON_EXIT=${SAVE_ON_EXIT}
+
+# A netty-based side channel for accepting configuration/tuning
+# requests from the client will listen for connections on the address
+# configured here.
+# If this is removed or commented out, the default port is 127.0.0.1:12000
+CONFIG_LISTEN_ADDRESS=127.0.0.1:12000
+
+# Connection URL to storage. This can be overridden with the -d option
+# on the command line. In order to use web storage instead, use something
+# similar to the following line:
+#DB_URL=https://storage-server.example.com:8443/thermostat/storage
+#DB_URL=mongodb://127.0.0.1:27518
+DB_URL=${DB_URL}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/templates/storage-config/client.properties	Fri May 15 18:51:56 2015 +0200
@@ -0,0 +1,6 @@
+#
+#Thu Aug 22 16:10:25 CEST 2013
+#password=heslo2
+connection-url=${CONNECTION_URL}
+save-entitlements=true
+username=${USERNAME}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/templates/storage-config/thermostat-roles.properties	Fri May 15 18:51:56 2015 +0200
@@ -0,0 +1,114 @@
+# This file is used if the PropertiesUsernameRolesLoginModule is used
+# as a delegate in the JAAS configuration *and* the 'roles.properties' option
+# has not been specified for the login module.
+#
+# If that is the case, this file does two things:
+#    1.  It maps user names to roles.
+#    2.  Defines an optional recursive set of roles. This is useful in order to
+#        define role sets. Users can then be members of such defined role sets.
+#        Note that every line which does not have a user name (as defined in the
+#        corresponding users.properties file) on the left hand side of the
+#        equals sign ('='), represents a role.
+#
+# A user is assigned multiple roles by separating them by a comma ','. Every
+# entity in this file which isn't a user name, will be implicitly defined as a
+# role.
+#
+# Format is as follows:
+#
+# user1 = my-role, my-role2
+# user2 = new-role, role1
+# role1 = other-role
+#
+# Considering users 'user1' and 'user2' are defined in users.properties, the
+# above would assign 'user1' the roles 'my-role' and 'my-role2'. 'user2' would
+# be a member of 'new-role', 'role1' and 'other-role' (transitively via role1)
+#
+#
+#
+# Example recursive role definition allowed-to-do-everything agent-users. You
+# can uncomment the following lines and assign your agent users this
+# "thermostat-agent" role.
+#thermostat-agent = thermostat-cmdc-verify, \
+#                   thermostat-login, \
+#                   thermostat-prepare-statement, \
+#                   thermostat-purge, \
+#                   thermostat-register-category, \
+#                   thermostat-realm, \
+#                   thermostat-save-file, \
+#                   thermostat-write, \
+#                   thermostat-files-grant-write-filename-ALL
+#
+# Example recursive role definition for allowed-to-see-everything client-users.
+# You may uncomment the following lines and assign your client users this
+# "thermostat-client" role. "thermostat-write" is needed by the notes plugin.
+# It's okay to not grant this permissions, the notes functionality will not
+# work.
+#thermostat-client = thermostat-agents-grant-read-agentId-ALL, \
+#                    thermostat-cmdc-generate, \
+#                    thermostat-hosts-grant-read-hostname-ALL, \
+#                    thermostat-load-file, \
+#                    thermostat-login, \
+#                    thermostat-prepare-statement, \
+#                    thermostat-query, \
+#                    thermostat-realm, \
+#                    thermostat-register-category, \
+#                    thermostat-vms-grant-read-username-ALL, \
+#                    thermostat-vms-grant-read-vmId-ALL, \
+#                    thermostat-files-grant-read-filename-ALL, \
+#                    thermostat-write
+#
+# Example recursive role definition which allows thermostat users to
+# use the clean-data command, which may perform global delete operations.
+# Consider assigning this role to client users if they need to use the
+# clean-data command. Note that other roles for thermostat client users
+# grant read-only access - at various levels - only.
+#thermostat-cmd-clean-data = thermostat-purge
+#
+# Example recursive role definition that grants all command channel privileges.
+# You may uncomment the following lines and assign your client users this
+# "thermostat-cmdc" role.
+#thermostat-cmdc = thermostat-cmdc-grant-garbage-collect, \
+#                  thermostat-cmdc-grant-dump-heap, \
+#                  thermostat-cmdc-grant-thread-harvester, \
+#                  thermostat-cmdc-grant-killvm, \
+#                  thermostat-cmdc-grant-ping, \
+#                  thermostat-cmdc-grant-jmx-toggle-notifications
+# Roles for our two dev users
+${AGENT_USERNAME}=thermostat-agent, thermostat-grant-write-files-all-agent
+# client-tester needs thermostat-purge role for clean-data command to work
+${CLIENT_USERNAME}=thermostat-grant-read-all-client, thermostat-cmdc, thermostat-purge
+# Agent recursive role
+thermostat-agent = thermostat-cmdc-verify, \
+                   thermostat-login, \
+                   thermostat-prepare-statement, \
+                   thermostat-purge, \
+                   thermostat-register-category, \
+                   thermostat-realm, \
+                   thermostat-save-file, \
+                   thermostat-write
+# Grant agent to write any file
+thermostat-grant-write-files-all-agent = thermostat-files-grant-write-filename-ALL
+                   
+# Client recursive role (granting a client to read all data + all files)
+thermostat-grant-read-all-client = thermostat-agents-grant-read-agentId-ALL, \
+                    thermostat-cmdc-generate, \
+                    thermostat-hosts-grant-read-hostname-ALL, \
+                    thermostat-load-file, \
+                    thermostat-login, \
+                    thermostat-prepare-statement, \
+                    thermostat-query, \
+                    thermostat-realm, \
+                    thermostat-register-category, \
+                    thermostat-vms-grant-read-username-ALL, \
+                    thermostat-vms-grant-read-vmId-ALL, \
+                    thermostat-files-grant-read-filename-ALL, \
+                    thermostat-write
+# Grants all command channel operations
+thermostat-cmdc = thermostat-cmdc-grant-garbage-collect, \
+                  thermostat-cmdc-grant-dump-heap, \
+                  thermostat-cmdc-grant-thread-harvester, \
+                  thermostat-cmdc-grant-killvm, \
+                  thermostat-cmdc-grant-profile-vm, \
+                  thermostat-cmdc-grant-ping, \
+                  thermostat-cmdc-grant-jmx-toggle-notifications
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/templates/storage-config/thermostat-users.properties	Fri May 15 18:51:56 2015 +0200
@@ -0,0 +1,20 @@
+# This file is used if the PropertiesUsernameRolesLoginModule is used
+# as a delegate in the JAAS configuration *and* the 'users.properties' option
+# has not been specified for the login module.
+#
+# If that is the case, this defines the database of users with corresponding
+# passwords, the thermostat web storage servlet knows about.
+# 
+# WARNING: Passwords of users are in plain text. This needs to be considered
+#          when using this module in production. The main goal of this login
+#          module is to provide a simple way to define thermostat users and
+#          their corresponding passwords.
+# 
+# The format of this file is as follows (whitespace in usernames/passwords are
+# not recommended):
+#
+# user1=password1
+# user2=password2
+# ...
+#
+${USERS}