diff options
Diffstat (limited to 'src/batchtools/headless/src/main/java/com/galois/fiveui/BatchRunner.java')
-rw-r--r-- | src/batchtools/headless/src/main/java/com/galois/fiveui/BatchRunner.java | 312 |
1 files changed, 312 insertions, 0 deletions
diff --git a/src/batchtools/headless/src/main/java/com/galois/fiveui/BatchRunner.java b/src/batchtools/headless/src/main/java/com/galois/fiveui/BatchRunner.java new file mode 100644 index 0000000..9f3f301 --- /dev/null +++ b/src/batchtools/headless/src/main/java/com/galois/fiveui/BatchRunner.java @@ -0,0 +1,312 @@ +/** + * Module : BatchRunner.java Copyright : (c) 2012, Galois, Inc. + * + * Maintainer : Stability : Provisional Portability: Portable + * + * 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 com.galois.fiveui; + +import java.io.File; +import java.io.IOException; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.openqa.selenium.JavascriptExecutor; +import org.openqa.selenium.WebDriver; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableList.Builder; +import com.google.common.io.Files; +import com.galois.fiveui.Result; +import com.galois.fiveui.RuleSet; +import com.galois.fiveui.Utils; +import com.galois.fiveui.drivers.Drivers; + +import edu.uci.ics.crawler4j.util.IO; + +import org.apache.log4j.Logger; // System.out.* is old fashioned + + +/** + * BatchRunner is initialized with a WebDriver object. It provides an interface + * for running {@code RuleSet}s and {@code RuleTest}s with the WebDriver. + * + * @author bjones + */ +public class BatchRunner { + + private WebDriver _driver; + private JavascriptExecutor _exe; + private String _root; // FiveUI root directory + + // Hard coded JS files, relative to the FiveUI root directory. + private static final String DATA_DIR = "contexts/data/"; + private static final String J_QUERY_JS = DATA_DIR + + "lib/jquery/jquery.js"; + private static final String PRELUDE_JS = DATA_DIR + + "fiveui/injected/prelude.js"; + private static final String MD5_JS = DATA_DIR + + "lib/jshash/md5.js"; + private static final String JQUERY_PLUGIN_JS = DATA_DIR + + "fiveui/injected/jquery-plugins.js"; + private static final String SEL_INJECTED_COMPUTE_JS = DATA_DIR + + "/fiveui/selenium/selenium-injected-compute.js"; + private static final String INJECTED_COMPUTE_JS = DATA_DIR + + "/fiveui/injected/fiveui-injected-compute.js"; + + private static Logger logger = Logger.getLogger("com.galois.fiveui.BatchRunner"); + + private void registerDriver(WebDriver driver) { + logger.debug("registering new webdriver..."); + this._driver = driver; + this._exe = (JavascriptExecutor) driver; + this._root = Drivers.getRootPath(); + logger.debug("root path for webdriver is " + _root); + } + + public BatchRunner() { + logger.debug("initializing BatchRunner ..."); + } + + /** + * Run a headless run description, returning the raw results: 'PASS' if + * no inconsistencies were found, 'ERROR' for each inconsistency found, + * 'EXCEPTION' for each uncaught exception. + * <p> + * The run.getURL() is loaded using the WebDriver and the rule set returned + * by {@code run.getRule()} is run. + * + * @param run a headless run description object + */ + public ImmutableList<Result> runHeadless(HeadlessRunDescription run) { + String seedUrl; + Builder<Result> builder = ImmutableList.builder(); // for results + ImmutableList<Result> rawResults; + CrawlParameters params = new CrawlParameters(run.getCrawlType()); + int politeness = params.toString().equals("none") ? 1000 : params.politeness; + List<String> urls; + Map<String, Map<String, List<String>>> urlCache; + //- URL, params, urls + urlCache = new HashMap<String, Map<String, List<String>>>(); + + for (HeadlessAtom a: run.getAtoms()) { + RuleSet rs = a.getRuleSet(); + seedUrl = a.getURL(); + logger.debug("setting seed URL for crawl: " + seedUrl); + urls = null; + + /************** + * Gather URLs + **************/ + + if (params.isNone()) { + urls = ImmutableList.of(seedUrl); + logger.debug("skipping webcrawl"); + } else if (urlCache.containsKey(seedUrl) && + urlCache.get(seedUrl).containsKey(params.toString())) { + logger.debug("retreiving urls list from cache"); + urls = urlCache.get(seedUrl).get(params.toString()); + } else { + File tmpPath = Files.createTempDir(); + logger.debug("tmp directory for crawl data: " + tmpPath.toString()); + logger.debug("starting webcrawl controller ..."); + BasicCrawlerController con = + new BasicCrawlerController(seedUrl, + params.matchFcn, + params.depth, params.maxFetch, + params.politeness, + 1, // TODO only one thread is currently supported + tmpPath.getAbsolutePath()); + try { + urls = con.go(); + logger.debug("adding urls list to cache"); + logger.debug("URLs: " + urls.toString()); + Map<String, List<String>> entry = (Map<String, List<String>>) new HashMap<String, List<String>>(); + entry.put(params.toString(), urls); + urlCache.put(seedUrl, entry); + } catch (Exception e) { + String errStr = "failed to complete webcrawl of" + seedUrl + "\n"; + errStr += e.toString(); + builder.add(new Result(ResType.Exception, _driver, errStr, seedUrl, + rs.getName(), rs.getDescription(), "")); + logger.error(errStr); + continue; + } finally { + IO.deleteFolder(tmpPath); // does its own logging + } + } + + /*********************** + * Drive the browser(s) + ***********************/ + + for (WebDriver driver: getDrivers()) { + registerDriver(driver); + for (String url: urls) { + logger.info("loading " + url + " for ruleset run ..."); + loadURL(url); // set state of the WebDriver (blocking) + try { + logger.info("running ruleset \"" + rs.getName() + "\""); + rawResults = runRule(rs); // run the ruleset, collect results + builder.addAll(rawResults); + } catch (Exception e) { + String errStr = "exception during runRule: " + rs.getName() + "\n"; + errStr += e.toString(); + builder.add(new Result(ResType.Exception, _driver, errStr, url, + rs.getName(), rs.getDescription(), "")); + logger.error(errStr); + } + try { + logger.debug("being polite for " + politeness + " millis..."); + Thread.sleep(politeness); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + driver.quit(); + } + } + return builder.build(); + } + + /** + * Run a rule set on the currently loaded page. + * <p> + * This method uses the web driver instance to run a rule set on the currently + * loaded page. The webdriver injects javascript that + * includes all the dependencies (JQuery, etc..) as well as the function which + * executes the rule check. The method sleeps the thread for 1 second and + * queries the results, which are then parsed and returned as a list of + * Result objects. + * + * @param ruleSet the rule set to be run + * @return results of running the rule set + * @throws IOException + */ + private ImmutableList<Result> runRule(final RuleSet ruleSet) throws IOException { + String contentScript = wrapRule(ruleSet); + Builder<Result> builder = ImmutableList.builder(); + String state = "url=" + _driver.getCurrentUrl() + + ", ruleSet=\"" + ruleSet.getName() + "\""; + logger.debug("runRule: " + state); + + _exe.executeScript(contentScript); + + try { + Thread.sleep(1000); + } catch (InterruptedException e1) { + logger.error(e1.toString()); + } + + Object res = _exe.executeScript("return fiveui.selPort.query(type='ReportProblem')"); + + if (res.getClass() == String.class) { + // we received an error via the expected mechanisms: + logger.error("exception running rule: " + res); + builder.add(new Result(ResType.Exception, _driver, "", _driver.getCurrentUrl(), + ruleSet.getName(), ruleSet.getDescription(), "")); + return builder.build(); + } else { + try { + @SuppressWarnings({ "unchecked", "rawtypes" }) + List<Map<String, Map<String, String>>> results = (List) res; + + if (0 == results.size()) { + builder.add(new Result(ResType.Pass, _driver, + "passed " + ruleSet.getRules().size() + " tests", + _driver.getCurrentUrl(), ruleSet.getName(), ruleSet.getDescription(), "")); + } + + for (Map<String, Map<String, String>> r : results) { + Map<String, String> problem = r.get("payload"); + // TODO decide what to extract from problem object and what + // to do with it. + // + // Probably we should just pass along the Map<String, String> + // and let the reporter deal with it. + String problemAsHTML = "Rule Name: " + problem.get("name") + " / " + + "Rule Desc: " + problem.get("descr") + " / " + + "XPath: " + problem.get("xpath"); + builder.add(new Result(ResType.Error, _driver, "", + _driver.getCurrentUrl(), + ruleSet.getName(), + ruleSet.getDescription(), + problemAsHTML)); + } + + } catch (ClassCastException e) { + // An unexpected error happened: + logger.error("unexpected object returned: " + e.toString()); + builder.add(new Result(ResType.Exception, _driver, + "Unexpected object returned: " + res + ", state: " + state, + _driver.getCurrentUrl(), + ruleSet.getName(), + ruleSet.getDescription(), + "")); + } + } + return builder.build(); + } + + /** + * Build up the complete content script needed to run the rule. + * <p> + * The string returned contains all the javascript dependencies required + * to run a rule set and the function that is injected into the page which + * executes the rule set. + * + * TODO DRY + * + * @param ruleSet a RuleSet object + * @throws IOException + */ + private String wrapRule(RuleSet ruleSet) throws IOException { + String injected = ""; + injected += Utils.readFile(_root + SEL_INJECTED_COMPUTE_JS); + injected += Utils.readFile(_root + J_QUERY_JS); + injected += Utils.readFile(_root + PRELUDE_JS); + injected += Utils.readFile(_root + MD5_JS); + injected += Utils.readFile(_root + JQUERY_PLUGIN_JS); + injected += Utils.readFile(_root + INJECTED_COMPUTE_JS); + + injected += "return fiveui.selPort.send('SetRules', " + ruleSet + ");"; + + return injected; + } + + /** + * Build a list of webdrivers with which to run each ruleset. + * + * @return list of initialized WebDriver objects + */ + private static ImmutableList<WebDriver> getDrivers() { + logger.debug("building webdrivers ..."); + ImmutableList<WebDriver> r = ImmutableList.<WebDriver>of( + Drivers.buildFFDriver() + // , Drivers.buildChromeDriver() + ); + logger.debug("built: " + r.toString()); + return r; + } + + /** + * Sets the state of the WebDriver by loading a given URL. + * + * @param url URL to load + */ + private void loadURL(String url) { + _driver.get(url); + } +} |