/** * Sahi - Web Automation and Test Tool * * Copyright 2006 V Narayan Raman * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package net.sf.sahi.test; import net.sf.sahi.config.Configuration; import net.sf.sahi.issue.IssueCreator; import net.sf.sahi.issue.IssueReporter; import net.sf.sahi.report.Report; import net.sf.sahi.report.SahiReporter; import net.sf.sahi.nashorn.NashornScriptRunner; import net.sf.sahi.session.Session; import net.sf.sahi.session.Status; import net.sf.sahi.util.Utils; import org.apache.log4j.Logger; import java.util.*; import java.util.concurrent.Semaphore; public class SahiTestSuite { private final String suitePath; private final String base; private List<TestLauncher> tests = new ArrayList<TestLauncher>(); private Map<String, TestLauncher> testsMap = new HashMap<String, TestLauncher>(); private int currentTestIndex = 0; private final String sessionId; private final String browser; private String suiteName; private List<TestLauncher> finishedTests = new ArrayList<TestLauncher>(); private List<SahiReporter> listReporter = new ArrayList<SahiReporter>(); private IssueReporter issueReporter; private String browserOption; private int availableThreads = 0; private volatile boolean[] freeThreads; private boolean killed = false; private boolean isMultiThreaded; private String browserProcessName; private static HashMap<String, SahiTestSuite> suites = new HashMap<String, SahiTestSuite>(); private HashMap<TestLauncher, TestLauncher> completed = new HashMap<TestLauncher, TestLauncher>(); private Map<String, String> variables; private long lastGenerationTime = 0; private Semaphore lock = new Semaphore(1, true); private String extraInfo; private String initJS; private boolean useSystemProxy; private boolean isSingleSession; private String logFolderName; private String junitLogDir; private String htmlLogDir; private String tm6LogDir; private String singleSessionChildSessionId; private BrowserLauncher singleSessionBrowserLauncher; private static Logger logger = Logger.getLogger(SahiTestSuite.class); public SahiTestSuite(final String suitePath, final String base, final String browser, final String sessionId, final String browseroption, String browserProcessName, boolean isSingleSession) { this.suitePath = suitePath; this.base = base; this.browser = browser; this.sessionId = Utils.stripChildSessionId(sessionId); this.singleSessionChildSessionId = Utils.addChildSessionId(sessionId); this.browserOption = browseroption; this.browserProcessName = browserProcessName; this.isSingleSession = isSingleSession; this.variables = new HashMap<String, String>(); setSuiteName(); this.logFolderName = Utils.createLogFileName(suiteName); suites.put(this.sessionId, this); } public Map<String, String> getInfo() { Map<String, String> info = new HashMap<String, String>(); info.put("suitePath", suitePath); info.put("base", base); info.put("browser", browser); info.put("sessionId", sessionId); info.put("browserOption", browserOption); info.put("browserProcessName", browserProcessName); info.put("suiteName", suiteName); return info; } public String getInfoJSON() { Map<String, String> info = getInfo(); StringBuilder sb = new StringBuilder(); sb.append("{"); int count = 0; for (Iterator<String> iterator = info.keySet().iterator(); iterator.hasNext(); ) { if (count++ != 0) sb.append(","); String key = (String) iterator.next(); sb.append(key); sb.append(":"); sb.append("\"" + Utils.makeString((String) info.get(key)) + "\""); } sb.append("}"); return sb.toString(); } public void loadScripts() { this.tests = new SuiteLoader(suitePath, base).getListTest(); logger.debug("Suite = " + suitePath); logger.debug("Tests size = " + this.tests.size()); for (Iterator<TestLauncher> iterator = tests.iterator(); iterator.hasNext(); ) { TestLauncher launcher = (TestLauncher) iterator.next(); prepareTestLauncher(launcher); testsMap.put(launcher.getChildSessionId(), launcher); } } private void prepareTestLauncher(TestLauncher launcher) { launcher.setSessionId(sessionId, isSingleSession ? singleSessionChildSessionId : Utils.addChildSessionId(sessionId)); launcher.setBrowser(browser); launcher.setBrowserOption(browserOption); launcher.setBrowserProcessName(browserProcessName); launcher.setIsSingleSession(isSingleSession); } public List<SahiReporter> getListReporter() { return listReporter; } public static SahiTestSuite getSuite(final String sessionId) { return (SahiTestSuite) suites.get(Utils.stripChildSessionId(sessionId)); } private void executeTest(final int threadNo) throws Exception { TestLauncher test = (TestLauncher) tests.get(currentTestIndex); test.setThreadNo(threadNo, isMultiThreaded); Session session = Session.getInstance(test.getChildSessionId()); session.touch(); test.execute(session); currentTestIndex++; } public void notifyComplete(final TestLauncher launcher) { if (completed.containsKey(launcher)) return; try { Thread.sleep(Configuration.getTimeBetweenTestsInSuite()); } catch (InterruptedException e) { e.printStackTrace(); } notifyComplete2(launcher); try { generateSuiteReport(finishedTests, false); } catch (Exception e) { e.printStackTrace(); } } private void notifyComplete2(final TestLauncher launcher) { if (completed.containsKey(launcher)) return; launcher.kill(); synchronized (this) { try { completed.put(launcher, launcher); finishedTests.add(launcher); availableThreads++; freeThreads[launcher.getThreadNo()] = true; } finally { this.notify(); } } } private void setSuiteName() { this.suiteName = Utils.makePathOSIndependent(suitePath); int lastIndexOfSlash = suiteName.lastIndexOf("/"); if (lastIndexOfSlash != -1) { this.suiteName = suiteName.substring(lastIndexOfSlash + 1); } } public String getSuiteName() { return this.suiteName; } private void markSuiteStatus() { Status status = finishedTests.size() > 0 ? Status.SUCCESS : Status.FAILURE; Session session; for (Iterator<TestLauncher> iterator = tests.iterator(); iterator.hasNext(); ) { TestLauncher testLauncher = (TestLauncher) iterator.next(); NashornScriptRunner scriptRunner = testLauncher.getScriptRunner(); if (scriptRunner == null || scriptRunner.hasErrors()) { status = Status.FAILURE; break; } } session = Session.getInstance(this.sessionId); session.setStatus(status); } public void run() { Session session = Session.getInstance(this.sessionId); session.setStatus(Status.RUNNING); new Thread(new Culler(this)).start(); executeSuite(); } public void finishCallBack() { try { markSuiteStatus(); generateSuiteReport(tests, true); createIssues(); cullInactiveTests(); } finally { suites.remove(sessionId); } } void cullInactiveTests() { Iterator<String> keys = testsMap.keySet().iterator(); long now = System.currentTimeMillis(); long inactivityLimit = Configuration.getMaxInactiveTimeForScript(); while (keys.hasNext()) { String sessionId = (String) keys.next(); Session session = Session.getExistingInstance(sessionId); if (session == null) continue; long lastActiveTime = session.lastActiveTime(); Status status = session.getStatus(); if (status != Status.SUCCESS && status != Status.FAILURE && status != Status.INITIAL && now - lastActiveTime > inactivityLimit) { String message = "*** Forcefully terminating script. \nNo response from browser within expected time (" + inactivityLimit / 1000 + " seconds)."; logger.info(message); NashornScriptRunner scriptRunner = session.getScriptRunner(); scriptRunner.setStatus(Status.FAILURE); Report report = scriptRunner.getReport(); if (report != null) { report.addResult(message, "ERROR", "", ""); } } } } private void createIssues() { Session session = Session.getInstance(sessionId); if (Status.FAILURE.equals(session.getStatus()) && issueReporter != null) { issueReporter.reportIssue(tests); } } private synchronized void executeSuite() { try { launchBrowserForSingleSession(); } catch (Exception e1) { e1.printStackTrace(); abort(); return; } while (currentTestIndex < tests.size()) { if (killed) { return; } for (; availableThreads > 0 && currentTestIndex < tests.size(); availableThreads--) { int freeThreadNo = getFreeThreadNo(); if (freeThreadNo != -1) { freeThreads[freeThreadNo] = false; try { this.executeTest(freeThreadNo); } catch (Exception e) { abort(); return; } } } try { this.wait(600000); } catch (InterruptedException e) { e.printStackTrace(); } } killBrowserForSingleSession(false); } private void abort() { kill(); finishCallBack(); } public void launchBrowserForSingleSession() throws Exception { if (isSingleSession) { singleSessionBrowserLauncher = new BrowserLauncher(browser, browserProcessName, browserOption, useSystemProxy); singleSessionBrowserLauncher.openURL(singleSessionBrowserLauncher.getPlayerAutoURL(singleSessionChildSessionId, base, isSingleSession)); } } public Status executeTestForSingleSession(String testName, String startURL) throws Exception { TestLauncher testLauncher = new TestLauncher(testName, startURL); prepareTestLauncher(testLauncher); testLauncher.setThreadNo(0, isMultiThreaded); Session session = Session.getInstance(testLauncher.getChildSessionId()); session.touch(); tests.add(testLauncher); testLauncher.execute(session, false, true); return testLauncher.getStatus(); } public void killBrowserForSingleSession(boolean isSingleTest) { if (isSingleSession) { singleSessionBrowserLauncher.kill(); if (isSingleTest) finishCallBack(); } } private int getFreeThreadNo() { for (int i = 0; i < freeThreads.length; i++) { if (freeThreads[i]) { return i; } } return -1; } private void generateSuiteReport(List<TestLauncher> listOfTests, boolean force) { try { if (force) { lock.acquire(); } else { long now = System.currentTimeMillis(); if (now - lastGenerationTime < 5000) return; lastGenerationTime = now; if (!lock.tryAcquire()) return; } } catch (InterruptedException e) { e.printStackTrace(); } try { for (Iterator<SahiReporter> iterator = listReporter.iterator(); iterator.hasNext(); ) { SahiReporter reporter = (SahiReporter) iterator.next(); reporter.generateSuiteReport(listOfTests); } } catch (Exception e) { e.printStackTrace(); } lock.release(); } public void setAvailableThreads(final int availableThreads) { this.availableThreads = availableThreads; this.isMultiThreaded = availableThreads > 1; freeThreads = new boolean[availableThreads]; int len = freeThreads.length; for (int i = 0; i < len; i++) { freeThreads[i] = true; } } public void addReporter(final SahiReporter reporter) { reporter.setSuiteName(suiteName); listReporter.add(reporter); } public void addIssueCreator(final IssueCreator issueCreator) { if (issueReporter == null) { issueReporter = new IssueReporter(suiteName); } issueReporter.addIssueCreator(issueCreator); } synchronized boolean isRunning() { return finishedTests.size() < tests.size() && !killed; } public void kill() { logger.info("Shutting down ..."); killed = true; } public String getVariable(final String name) { logger.debug("get name="+name + " value="+(String) (variables.get(name))); return (String) (variables.get(name)); } public void removeVariables(final String pattern) { for (Iterator<String> iterator = variables.keySet().iterator(); iterator.hasNext(); ) { String s = (String) iterator.next(); if (s.matches(pattern)) { iterator.remove(); } } } public void setVariable(final String name, final String value) { logger.debug("set name="+name + " value=" + value); variables.put(name, value); } public void setExtraInfo(String extraInfo) { this.extraInfo = extraInfo; } public String getExtraInfo() { return this.extraInfo; } public void setInitJS(String initJS) { this.initJS = initJS; } public String getInitJS() { return initJS; } public void setUseSystemProxy(boolean useSystemProxy) { this.useSystemProxy = useSystemProxy; } public String getLogFolderName() { return logFolderName; } public void setJunitLogDir(String logDir) { this.junitLogDir = logDir; } public String getJunitLogDir() { return junitLogDir; } public void setHtmlLogDir(String logDir) { this.htmlLogDir = logDir; } public String getHtmlLogDir() { return htmlLogDir; } public void setTM6LogDir(String logDir) { this.tm6LogDir = logDir; } public String getTM6LogDir() { return tm6LogDir; } } class Culler implements Runnable { private SahiTestSuite suite; Culler(SahiTestSuite suite) { this.suite = suite; } public void run() { waitForSuiteCompletion(); } private void waitForSuiteCompletion() { while (suite.isRunning()) { suite.cullInactiveTests(); try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } } suite.finishCallBack(); } }