aboutsummaryrefslogtreecommitdiff
path: root/headless
diff options
context:
space:
mode:
authorGravatar Benjamin Jones <bjones@galois.com>2012-11-12 18:07:51 -0800
committerGravatar Benjamin Jones <bjones@galois.com>2012-11-12 18:07:51 -0800
commitb2233f4d08e922416c1c10080744e6dd5513be9b (patch)
treef4afb9cd6c42101676ab355c3b979dc5f8fed986 /headless
parent5bb6964861acba3439a1430e999f7d014a796591 (diff)
initial commit of headless rule set tester
Diffstat (limited to 'headless')
-rw-r--r--headless/.classpath26
-rw-r--r--headless/.settings/org.eclipse.core.resources.prefs4
-rw-r--r--headless/.settings/org.eclipse.jdt.core.prefs5
-rw-r--r--headless/.settings/org.eclipse.m2e.core.prefs4
-rw-r--r--headless/pom.xml88
-rw-r--r--headless/src/main/java/com/galois/fiveui/BatchRunner.java195
-rw-r--r--headless/src/main/java/com/galois/fiveui/HeadlessAtom.java130
-rw-r--r--headless/src/main/java/com/galois/fiveui/HeadlessRunDescription.java171
-rw-r--r--headless/src/main/java/com/galois/fiveui/HeadlessRunner.java99
-rw-r--r--headless/src/test/java/com/galois/fiveui/BatchExecutorTest.java109
-rw-r--r--headless/src/test/java/com/galois/fiveui/HeadlessTest.java104
-rw-r--r--headless/src/test/resources/ruleSets/emptyRuleSet.json4
-rw-r--r--headless/src/test/resources/ruleSets/headingGuidelines.json37
-rw-r--r--headless/src/test/resources/runDescriptions/headlessRunTest0.json4
-rw-r--r--headless/src/test/resources/runDescriptions/headlessRunTestCNN.json4
-rw-r--r--headless/src/test/resources/runDescriptions/headlessRunTestGalois.json11
-rw-r--r--headless/src/test/resources/runDescriptions/headlessSample0.json4
-rw-r--r--headless/src/test/resources/runDescriptions/headlessSample1.json4
-rw-r--r--headless/src/test/resources/runDescriptions/headlessSample2.json9
-rw-r--r--headless/target/classes/com/galois/fiveui/BatchRunner.classbin0 -> 6241 bytes
-rw-r--r--headless/target/classes/com/galois/fiveui/HeadlessAtom.classbin0 -> 2626 bytes
-rw-r--r--headless/target/classes/com/galois/fiveui/HeadlessRunDescription$Deserializer.classbin0 -> 4417 bytes
-rw-r--r--headless/target/classes/com/galois/fiveui/HeadlessRunDescription.classbin0 -> 2775 bytes
-rw-r--r--headless/target/classes/com/galois/fiveui/HeadlessRunner.classbin0 -> 3059 bytes
-rw-r--r--headless/target/test-classes/com/galois/fiveui/HeadlessTest.classbin0 -> 3500 bytes
-rw-r--r--headless/target/test-classes/ruleSets/emptyRuleSet.json4
-rw-r--r--headless/target/test-classes/ruleSets/headingGuidelines.json37
-rw-r--r--headless/target/test-classes/runDescriptions/headlessRunTest0.json4
-rw-r--r--headless/target/test-classes/runDescriptions/headlessRunTestBBC.json4
-rw-r--r--headless/target/test-classes/runDescriptions/headlessRunTestCNN.json4
-rw-r--r--headless/target/test-classes/runDescriptions/headlessRunTestGalois.json11
-rw-r--r--headless/target/test-classes/runDescriptions/headlessSample0.json4
-rw-r--r--headless/target/test-classes/runDescriptions/headlessSample1.json4
-rw-r--r--headless/target/test-classes/runDescriptions/headlessSample2.json9
34 files changed, 1093 insertions, 0 deletions
diff --git a/headless/.classpath b/headless/.classpath
new file mode 100644
index 0000000..0a1dadd
--- /dev/null
+++ b/headless/.classpath
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+ <classpathentry kind="src" output="target/classes" path="src/main/java">
+ <attributes>
+ <attribute name="optional" value="true"/>
+ <attribute name="maven.pomderived" value="true"/>
+ </attributes>
+ </classpathentry>
+ <classpathentry kind="src" output="target/test-classes" path="src/test/java">
+ <attributes>
+ <attribute name="optional" value="true"/>
+ <attribute name="maven.pomderived" value="true"/>
+ </attributes>
+ </classpathentry>
+ <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/J2SE-1.5">
+ <attributes>
+ <attribute name="maven.pomderived" value="true"/>
+ </attributes>
+ </classpathentry>
+ <classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER">
+ <attributes>
+ <attribute name="maven.pomderived" value="true"/>
+ </attributes>
+ </classpathentry>
+ <classpathentry kind="output" path="target/classes"/>
+</classpath>
diff --git a/headless/.settings/org.eclipse.core.resources.prefs b/headless/.settings/org.eclipse.core.resources.prefs
new file mode 100644
index 0000000..f9fe345
--- /dev/null
+++ b/headless/.settings/org.eclipse.core.resources.prefs
@@ -0,0 +1,4 @@
+eclipse.preferences.version=1
+encoding//src/main/java=UTF-8
+encoding//src/test/java=UTF-8
+encoding/<project>=UTF-8
diff --git a/headless/.settings/org.eclipse.jdt.core.prefs b/headless/.settings/org.eclipse.jdt.core.prefs
new file mode 100644
index 0000000..abec6ca
--- /dev/null
+++ b/headless/.settings/org.eclipse.jdt.core.prefs
@@ -0,0 +1,5 @@
+eclipse.preferences.version=1
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.5
+org.eclipse.jdt.core.compiler.compliance=1.5
+org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning
+org.eclipse.jdt.core.compiler.source=1.5
diff --git a/headless/.settings/org.eclipse.m2e.core.prefs b/headless/.settings/org.eclipse.m2e.core.prefs
new file mode 100644
index 0000000..f897a7f
--- /dev/null
+++ b/headless/.settings/org.eclipse.m2e.core.prefs
@@ -0,0 +1,4 @@
+activeProfiles=
+eclipse.preferences.version=1
+resolveWorkspaceProjects=true
+version=1
diff --git a/headless/pom.xml b/headless/pom.xml
new file mode 100644
index 0000000..402c5f2
--- /dev/null
+++ b/headless/pom.xml
@@ -0,0 +1,88 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <groupId>com.galois.fiveui</groupId>
+ <artifactId>HeadlessRunner</artifactId>
+ <version>0.0.1-SNAPSHOT</version>
+ <packaging>jar</packaging>
+
+
+ <properties>
+ <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+ </properties>
+
+ <pluginRepositories>
+ <pluginRepository>
+ <id>onejar-maven-plugin.googlecode.com</id>
+ <url>http://onejar-maven-plugin.googlecode.com/svn/mavenrepo</url>
+ </pluginRepository>
+ </pluginRepositories>
+
+ <build>
+ <plugins>
+ <plugin>
+ <!-- package with 'mvn assembly:single' -->
+ <artifactId>maven-assembly-plugin</artifactId>
+ <configuration>
+ <archive>
+ <manifest>
+ <mainClass>com.galois.fiveui.HeadlessRunner</mainClass>
+ </manifest>
+ </archive>
+ <descriptorRefs>
+ <descriptorRef>jar-with-dependencies</descriptorRef>
+ </descriptorRefs>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.dstovall</groupId>
+ <artifactId>onejar-maven-plugin</artifactId>
+ <version>1.3.0</version>
+ <executions>
+ <execution>
+ <configuration>
+ <mainClass>com.galois.fiveui.HeadlessRunner</mainClass>
+ </configuration>
+ <goals>
+ <goal>one-jar</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.seleniumhq.selenium</groupId>
+ <artifactId>selenium-java</artifactId>
+ <version>2.26.0</version>
+ </dependency>
+ <dependency>
+ <groupId>com.google.guava</groupId>
+ <artifactId>guava</artifactId>
+ <version>13.0.1</version>
+ </dependency>
+ <dependency>
+ <groupId>com.google.code.gson</groupId>
+ <artifactId>gson</artifactId>
+ <version>2.2.2</version>
+ </dependency>
+ <dependency>
+ <groupId>com.galois.fiveui</groupId>
+ <artifactId>webdrivers</artifactId>
+ <version>0.0.1-SNAPSHOT</version>
+ </dependency>
+ <dependency>
+ <groupId>com.galois.fiveui</groupId>
+ <artifactId>RuleSetTester</artifactId>
+ <version>1.0</version>
+ </dependency>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <version>4.9</version>
+ </dependency>
+ </dependencies>
+</project>
diff --git a/headless/src/main/java/com/galois/fiveui/BatchRunner.java b/headless/src/main/java/com/galois/fiveui/BatchRunner.java
new file mode 100644
index 0000000..8c7338c
--- /dev/null
+++ b/headless/src/main/java/com/galois/fiveui/BatchRunner.java
@@ -0,0 +1,195 @@
+/**
+ * Module : BatchRunner.java Copyright : (c) 2011-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.IOException;
+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.galois.fiveui.Result;
+import com.galois.fiveui.RuleSet;
+import com.galois.fiveui.Utils;
+
+/**
+ * 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 final WebDriver _driver;
+ private final JavascriptExecutor _exe;
+
+
+ /**
+ * BatchRunner constructor, stores the given WebDriver.
+ *
+ * @param driver the WebDriver object to run tests with
+ */
+ public BatchRunner(WebDriver driver) {
+ _driver = driver;
+ _exe = (JavascriptExecutor) _driver;
+ }
+
+
+
+ /**
+ * 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(final HeadlessRunDescription run) {
+
+ Builder<Result> builder = ImmutableList.builder();
+ ImmutableList<Result> rawResults;
+ for (HeadlessAtom a: run.getAtoms()) {
+ RuleSet rs = a.getRuleSet();
+ try {
+ loadURL(a.getURL()); // set state of the WebDriver
+ 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(Result.exception(_driver, "Could not run rule: "+errStr));
+ }
+ }
+
+ return builder.build();
+ }
+
+ /**
+ * Run a rule set on a 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();
+
+ _exe.executeScript(contentScript);
+
+ try {
+ Thread.sleep(1000);
+ } catch (InterruptedException e1) {
+ e1.printStackTrace();
+ }
+
+ Object res = _exe.executeScript("return fiveui.selPort.query(type='ReportProblem')");
+
+ if (res.getClass() == String.class) {
+ // we received an error via the expected mechanisms:
+ System.err.println("Exception running rule: " + res);
+ builder.add(Result.exception(_driver, (String) res));
+ return builder.build();
+ } else {
+
+ try {
+ @SuppressWarnings({ "unchecked", "rawtypes" })
+ List<Map<String, Map<String, String>>> results = (List) res;
+
+ if (0 == results.size()) {
+ builder.add(Result.pass(_driver, "passed"));
+ }
+
+ for (Map<String, Map<String, String>> r : results) {
+ Map<String, String> problem = r.get("payload");
+
+ builder.add(Result.error(_driver, problem.get("descr")));
+ }
+
+ } catch (ClassCastException e) {
+ // An unexpected error happened:
+ builder.add(Result.exception(_driver, "Unexpected object returned: "
+ + res));
+ e.printStackTrace();
+ }
+ }
+ return builder.build();
+ }
+
+ // Relative to the batch directory.
+ private static final String DATA_DIR = "../contexts/data/";
+ private static final String J_QUERY_JS = DATA_DIR
+ + "lib/jquery/jquery-1.7.1.min.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";
+
+ /**
+ * 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.
+ *
+ * @param ruleSet a RuleSet object
+ * @throws IOException
+ */
+ private String wrapRule(RuleSet ruleSet) throws IOException {
+ String injected = "";
+ injected += Utils.readFile(SEL_INJECTED_COMPUTE_JS);
+ injected += Utils.readFile(J_QUERY_JS);
+ injected += Utils.readFile(PRELUDE_JS);
+ injected += Utils.readFile(MD5_JS);
+ injected += Utils.readFile(JQUERY_PLUGIN_JS);
+ injected += Utils.readFile(INJECTED_COMPUTE_JS);
+
+ injected += "return fiveui.selPort.send('SetRules', " + ruleSet + ");";
+
+ return injected;
+ }
+
+ /**
+ * Sets the state of the WebDriver by loading a given URL.
+ *
+ * @param url URL to load
+ */
+ private void loadURL(String url) {
+ _driver.get(url);
+ }
+
+}
diff --git a/headless/src/main/java/com/galois/fiveui/HeadlessAtom.java b/headless/src/main/java/com/galois/fiveui/HeadlessAtom.java
new file mode 100644
index 0000000..2d40156
--- /dev/null
+++ b/headless/src/main/java/com/galois/fiveui/HeadlessAtom.java
@@ -0,0 +1,130 @@
+/**
+ *
+ */
+package com.galois.fiveui;
+
+import java.io.File;
+import java.io.IOException;
+import com.google.gson.Gson;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParseException;
+
+import com.galois.fiveui.RuleSet;
+import com.galois.fiveui.Utils;
+
+/**
+ * @author bjones
+ *
+ */
+public class HeadlessAtom {
+
+ private String _url;
+ private RuleSet _ruleSet;
+
+ public HeadlessAtom(String url, RuleSet ruleSet) {
+ _url = url;
+ _ruleSet = ruleSet;
+ }
+
+ public String getURL() {
+ return _url;
+ }
+
+ public RuleSet getRuleSet() {
+ return _ruleSet;
+ }
+
+ public String toString() {
+ Gson gson = new Gson();
+ return gson.toJson(this);
+ }
+
+ /**
+ * Parse a JSON file into a HeadlessAtom
+ *
+ * @param str The string to parse
+ * @return A populated HeadlessAtom object.
+ */
+// public static HeadlessAtom parse(String str)
+// throws FileNotFoundException {
+// GsonBuilder gsonBuilder = new GsonBuilder();
+// gsonBuilder.registerTypeAdapter(HeadlessAtom.class,
+// new HeadlessAtom.Deserializer());
+// Gson gson = gsonBuilder.create();
+// return gson.fromJson(str, HeadlessAtom.class);
+// }
+
+ /**
+ * Construct a HeadlessAtom from a JsonObject
+ *
+ * @param obj JsonObject to convert from
+ * @param dir parent directory of the filenames referenced in the ruleSet field
+ * @return a HeadlessAtom POJO
+ * @throws IOException
+ * @throws JsonParseException
+ */
+ public static HeadlessAtom fromJsonObject(JsonObject obj, String dir) throws IOException {
+ String url = obj.get("url").getAsString();
+ String ruleSet = obj.get("ruleSet").getAsString();
+
+ if (url == null || ruleSet == null) {
+ throw new JsonParseException("could get either 'url' or 'ruleSet' properties");
+ }
+
+ String rsPath = dir + File.separator + ruleSet;
+ String ruleSetStr = Utils.readFile(rsPath);
+
+ RuleSet parsed = RuleSet.parse(ruleSetStr);
+
+ return new HeadlessAtom(url, parsed);
+ }
+
+// public static class Deserializer implements JsonDeserializer<HeadlessAtom> {
+//
+// public Deserializer() {}
+//
+// public HeadlessAtom deserialize(JsonElement json, Type typeOfT,
+// JsonDeserializationContext context) throws JsonParseException {
+// JsonObject obj = json.getAsJsonObject();
+// try {
+// return HeadlessAtom.fromJsonObject(obj);
+// } catch (IOException e) {
+// e.printStackTrace();
+// // TODO bad stop gap
+// return new HeadlessAtom(null, null);
+// }
+// }
+// }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + ((_url == null) ? 0 : _url.hashCode());
+ result = prime * result + ((_ruleSet == null) ? 0 : _ruleSet.hashCode());
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ HeadlessAtom other = (HeadlessAtom) obj;
+ if (_url == null) {
+ if (other._url != null)
+ return false;
+ } else if (!_url.equals(other._url))
+ return false;
+ if (_ruleSet == null) {
+ if (other._ruleSet != null)
+ return false;
+ } else if (!_ruleSet.equals(other._ruleSet))
+ return false;
+ return true;
+ }
+
+}
diff --git a/headless/src/main/java/com/galois/fiveui/HeadlessRunDescription.java b/headless/src/main/java/com/galois/fiveui/HeadlessRunDescription.java
new file mode 100644
index 0000000..49131ad
--- /dev/null
+++ b/headless/src/main/java/com/galois/fiveui/HeadlessRunDescription.java
@@ -0,0 +1,171 @@
+/**
+ *
+ */
+package com.galois.fiveui;
+
+
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.lang.reflect.Type;
+import java.util.List;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableList.Builder;
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.gson.JsonArray;
+import com.google.gson.JsonDeserializationContext;
+import com.google.gson.JsonDeserializer;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParseException;
+
+/**
+ * @author bjones
+ *
+ */
+public class HeadlessRunDescription {
+
+ private List<HeadlessAtom> _atoms;
+
+ public HeadlessRunDescription (List<HeadlessAtom> atoms) {
+ _atoms = atoms;
+ }
+
+ public List<HeadlessAtom> getAtoms() {
+ return _atoms;
+ }
+
+ public int size() {
+ return _atoms.size();
+ }
+
+ /**
+ * Parse a JSON file into a HeadlessRunDescription
+ *
+ * @param runDescFileName The file to load.
+ * @return A populated HeadlessRunDescription object.
+ * @throws FileNotFoundException if runDescFile can't be found.
+ */
+ public static HeadlessRunDescription parse(String runDescFileName)
+ throws FileNotFoundException {
+
+ GsonBuilder gsonBuilder = new GsonBuilder();
+ gsonBuilder.registerTypeAdapter(HeadlessRunDescription.class,
+ new HeadlessRunDescription.Deserializer(runDescFileName));
+ Gson gson = gsonBuilder.create();
+
+ Reader in = new InputStreamReader(new FileInputStream(runDescFileName));
+
+ return gson.fromJson(in, HeadlessRunDescription.class);
+ }
+
+ public static class Deserializer implements JsonDeserializer<HeadlessRunDescription> {
+
+ private String _fn; // stores the filename on disk being parsed
+ private String _ctxDir; // stores the parent dir of _fn
+
+ public Deserializer(String fn) {
+ _fn = fn;
+ _ctxDir = new File(_fn).getParent();
+ if (null == _ctxDir) {
+ _ctxDir = ".";
+ }
+ }
+
+ public static void printErr(JsonElement json) {
+ System.err.println("HeadlessRunDescription.parse: ran into unexpected jsonElement type");
+ }
+
+ /**
+ * Gracefully lookup property 'prop' in JsonObject 'obj'.
+ *
+ * @param obj a JSON object
+ * @param prop a key string to lookup in the JSON object
+ * @return string property or null if the prop doesn't resolve
+ */
+ public static String objGetString(JsonObject obj, String prop) {
+ try {
+ return obj.get(prop).getAsString();
+ } catch (NullPointerException e) {
+ System.err.println("HeadlessRunDescription.parse: failed to lookup property: " + prop);
+ e.printStackTrace();
+ return null;
+ }
+ }
+
+ public HeadlessRunDescription deserialize(JsonElement json, Type typeOfT,
+ JsonDeserializationContext context) throws JsonParseException {
+
+ @SuppressWarnings("unused")
+ String ruleSetDir, crawlType;
+ JsonArray arr;
+
+ if (json.isJsonObject()) { // check if the description is an extended one
+ JsonObject obj = json.getAsJsonObject();
+ ruleSetDir = objGetString(obj, "rulePath");
+ crawlType = objGetString(obj, "crawlType"); // TODO not implemented
+ arr = obj.get("runs").getAsJsonArray();
+ } else if (json.isJsonArray()) {
+ ruleSetDir = _ctxDir;
+ crawlType = "none";
+ arr = json.getAsJsonArray();
+ } else {
+ printErr(json);
+ return new HeadlessRunDescription(null);
+ }
+
+ Builder<HeadlessAtom> atoms = ImmutableList.builder();
+ for (JsonElement jsonElement : arr) {
+ try {
+ JsonObject obj = jsonElement.getAsJsonObject();
+ atoms.add(HeadlessAtom.fromJsonObject(obj, ruleSetDir));
+ } catch (IOException e) {
+ System.err.println("HeadlessAtom.parse: error parsing ruleSet file: " + e.getMessage());
+ } catch (IllegalStateException e) {
+ printErr(jsonElement);
+ }
+ }
+
+ return new HeadlessRunDescription(atoms.build());
+ }
+ }
+
+ public String toString() {
+ Gson gson = new Gson();
+ return gson.toJson(this);
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ for (HeadlessAtom a: _atoms) {
+ result = prime * result + a.hashCode();
+ }
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ HeadlessRunDescription other = (HeadlessRunDescription) obj;
+ if (_atoms == null) {
+ if (other._atoms != null)
+ return false;
+ } else if (!_atoms.equals(other._atoms))
+ return false;
+
+ return true;
+ }
+}
diff --git a/headless/src/main/java/com/galois/fiveui/HeadlessRunner.java b/headless/src/main/java/com/galois/fiveui/HeadlessRunner.java
new file mode 100644
index 0000000..e743064
--- /dev/null
+++ b/headless/src/main/java/com/galois/fiveui/HeadlessRunner.java
@@ -0,0 +1,99 @@
+/**
+ * Module : HeadlessRunner.java Copyright : (c) 2011-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.IOException;
+import java.net.URISyntaxException;
+
+import org.openqa.selenium.WebDriver;
+
+import com.galois.fiveui.Result;
+import com.galois.fiveui.drivers.Drivers;
+import com.google.common.collect.ImmutableList;
+
+/**
+ * The main entry point for running headless rule set runs.
+ * <p>
+ * The {@link #main(String[])} method of this class sets up a WebDriver, loads
+ * a headless run description from disk, and executes the run which includes
+ * loading URL's and running rule sets on their content.
+ *
+ * @author bjones
+ *
+ */
+public class HeadlessRunner {
+
+ /**
+ * @param args
+ * @throws IOException
+ * @throws URISyntaxException
+ */
+ public static void main(final String[] args) throws IOException,
+ URISyntaxException {
+
+ if (0 == args.length) {
+ printHelp();
+ System.exit(1);
+ }
+
+ for (int i = 0; i < args.length; i++) {
+ String runDescFileName = args[i];
+ HeadlessRunDescription descr = HeadlessRunDescription.parse(runDescFileName);
+
+ for (WebDriver driver : getDrivers()) {
+ try {
+ ImmutableList<Result> results = invokeHeadlessRun(driver, descr);
+
+ for (Result result : results) {
+ System.out.println(result);
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ } finally {
+ driver.quit();
+ }
+ }
+ }
+ }
+
+ /**
+ * Helper method for executing headless runs. This method runs a single
+ * HeadlessRunDescription and returns the list of results. If the file cannot
+ * be read, an empty list is returned.
+ *
+ * @param driver webdrive to use for the run
+ * @param descr a headless run description object
+ */
+ private static ImmutableList<Result> invokeHeadlessRun(WebDriver driver, HeadlessRunDescription descr) {
+ BatchRunner runner = new BatchRunner(driver); // setup the batch runner
+ return runner.runHeadless(descr); // execute the run
+ }
+
+ private static ImmutableList<WebDriver> getDrivers() {
+ return ImmutableList.<WebDriver>of(
+ Drivers.buildFFDriver()
+ // , Drivers.buildChromeDriver()
+ );
+ }
+
+ private static void printHelp() {
+ System.out.println(
+ "Usage: HeadlessRunner [<runDescirption.json>]");
+ }
+
+}
diff --git a/headless/src/test/java/com/galois/fiveui/BatchExecutorTest.java b/headless/src/test/java/com/galois/fiveui/BatchExecutorTest.java
new file mode 100644
index 0000000..f72c5a1
--- /dev/null
+++ b/headless/src/test/java/com/galois/fiveui/BatchExecutorTest.java
@@ -0,0 +1,109 @@
+/**
+ * Module : BatchExecutorTest.java
+ * Copyright : (c) 2011-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.IOException;
+
+import org.junit.Test;
+import org.openqa.selenium.WebDriver;
+
+import com.galois.fiveui.BatchRunner;
+import com.galois.fiveui.HeadlessRunDescription;
+import com.galois.fiveui.Result;
+import com.galois.fiveui.drivers.Drivers;
+import com.google.common.collect.ImmutableList;
+
+import junit.framework.Assert;
+
+
+/**
+ * @author bjones
+ *
+ */
+public class BatchExecutorTest {
+
+ private static final String RUN_DESCRIPTION_DIR = "src/test/resources/runDescriptions/";
+
+ @Test
+ public void simpleTest() {
+ Assert.assertEquals("Booleans are not equal.", true, true);
+ }
+
+ /**
+ * This unit test requires that a webserver be running locally on port 8000,
+ * what it serves does not matter.
+ *
+ * @throws IOException
+ * @throws FileNotFoundException
+ */
+ @Test
+ public void headlessRunTest0() throws IOException {
+ String jsonFileName = RUN_DESCRIPTION_DIR + "headlessRunTest0.json";
+ testHeadlessRun(jsonFileName);
+ }
+
+ /**
+ * This unit test requires internet access to http://www.cnn.com
+ *
+ * @throws IOException
+ * @throws FileNotFoundException
+ */
+ @Test
+ public void headlessRunTestCNN() throws IOException {
+ String jsonFileName = RUN_DESCRIPTION_DIR + "headlessRunTestCNN.json";
+ testHeadlessRun(jsonFileName);
+ }
+
+ /**
+ * This unit test requires internet access to http://www.cnn.com
+ *
+ * @throws IOException
+ * @throws FileNotFoundException
+ */
+ @Test
+ public void headlessRunTestMil() throws IOException {
+ String jsonFileName = RUN_DESCRIPTION_DIR + "headlessRunTestMil.json";
+ testHeadlessRun(jsonFileName);
+ }
+
+ /**
+ * Helper method for headless run unit tests.
+ *
+ * @param fn filename of a .json file containing a headless run description
+ */
+ private static void testHeadlessRun(String fn) {
+ WebDriver driver = Drivers.buildFFDriver(); // initialize the webdriver
+ boolean flag = true;
+ try {
+ HeadlessRunDescription descr = HeadlessRunDescription.parse(fn);
+ BatchRunner runner = new BatchRunner(driver); // setup the batch runner
+ ImmutableList<Result> results = runner.runHeadless(descr); // excecute the run
+ System.out.println(results.toString()); // print out results
+ } catch (Exception e) {
+ System.err.println("testHeadlessRun: exception caught while running a headless run description");
+ flag = false;
+ } finally {
+ driver.quit();
+ }
+ assert(flag);
+ }
+
+}
diff --git a/headless/src/test/java/com/galois/fiveui/HeadlessTest.java b/headless/src/test/java/com/galois/fiveui/HeadlessTest.java
new file mode 100644
index 0000000..e878661
--- /dev/null
+++ b/headless/src/test/java/com/galois/fiveui/HeadlessTest.java
@@ -0,0 +1,104 @@
+/**
+ *
+ */
+package com.galois.fiveui;
+
+import java.io.IOException;
+
+import junit.framework.Assert;
+
+import org.junit.Test;
+
+import com.galois.fiveui.HeadlessAtom;
+import com.galois.fiveui.HeadlessRunDescription;
+import com.galois.fiveui.RuleSet;
+import com.galois.fiveui.Utils;
+import com.google.common.collect.ImmutableList;
+
+/**
+ * @author bjones
+ *
+ */
+public class HeadlessTest {
+ private static final String RUN_DESCRIPTION_DIR = "src/test/resources/runDescriptions/";
+
+ /**
+ * Test method for {@link com.galois.com.galois.fiveui.HeadlessRunDescription}, parses
+ * 'src/test/resources/runDescriptions/headlessSample0.json'.
+ *
+ * @throws IOException
+ */
+ @Test
+ public final void testDeserialize_headlessSample0() throws IOException {
+
+ String jsonFileName = RUN_DESCRIPTION_DIR + "headlessSample0.json";
+ String ruleSetLoc =
+ RUN_DESCRIPTION_DIR + "../ruleSets/emptyRuleSet.json";
+ RuleSet ruleSetOracle = RuleSet.parse(Utils.readFile(ruleSetLoc));
+ HeadlessAtom headlessAtomOracle =
+ new HeadlessAtom("http://testhost", ruleSetOracle);
+ HeadlessRunDescription oracle =
+ new HeadlessRunDescription(ImmutableList.of(headlessAtomOracle));
+
+ HeadlessRunDescription actual = HeadlessRunDescription.parse(jsonFileName);
+ assertObjEqual("Object deserialized incorrectly.", oracle, actual);
+ }
+
+ /**
+ * Test method for {@link com.galois.com.galois.fiveui.HeadlessRunDescription}, parses
+ * 'src/test/resources/runDescriptions/headlessSample1.json'.
+ *
+ * @throws IOException
+ */
+ @Test
+ public final void testDeserialize_headlessSample1() throws IOException {
+
+ String jsonFileName = RUN_DESCRIPTION_DIR + "headlessSample1.json";
+ String ruleSetLoc =
+ RUN_DESCRIPTION_DIR + "../ruleSets/headingGuidelines.json";
+ RuleSet ruleSetOracle = RuleSet.parse(Utils.readFile(ruleSetLoc));
+ HeadlessAtom headlessAtomOracle =
+ new HeadlessAtom("http://testhost", ruleSetOracle);
+ HeadlessRunDescription oracle =
+ new HeadlessRunDescription(ImmutableList.of(headlessAtomOracle));
+
+ HeadlessRunDescription actual = HeadlessRunDescription.parse(jsonFileName);
+ assertObjEqual("Object deserialized incorrectly.", oracle, actual);
+ }
+
+ /**
+ * Test method for {@link com.galois.com.galois.fiveui.HeadlessRunDescription}, parses
+ * 'src/test/resources/runDescriptions/headlessSample2.json'.
+ *
+ * @throws IOException
+ */
+ @Test
+ public final void testDeserialize_headlessSample2() throws IOException {
+
+ String jsonFileName = RUN_DESCRIPTION_DIR + "headlessSample2.json";
+ // manually build first HeadlessAtom
+ String ruleSetLoc1 =
+ RUN_DESCRIPTION_DIR + "../ruleSets/emptyRuleSet.json";
+ RuleSet ruleSetOracle1 = RuleSet.parse(Utils.readFile(ruleSetLoc1));
+ HeadlessAtom headlessAtomOracle1 =
+ new HeadlessAtom("http://testhost1", ruleSetOracle1);
+ // manually build second HeadlessAtom
+ String ruleSetLoc2 =
+ RUN_DESCRIPTION_DIR + "../ruleSets/headingGuidelines.json";
+ RuleSet ruleSetOracle2 = RuleSet.parse(Utils.readFile(ruleSetLoc2));
+ HeadlessAtom headlessAtomOracle2 =
+ new HeadlessAtom("http://testhost2", ruleSetOracle2);
+
+ HeadlessRunDescription oracle =
+ new HeadlessRunDescription(ImmutableList.of(headlessAtomOracle1,
+ headlessAtomOracle2));
+
+ HeadlessRunDescription actual = HeadlessRunDescription.parse(jsonFileName);
+ assertObjEqual("Object deserialized incorrectly.", oracle, actual);
+ }
+
+ private void assertObjEqual(String msg, Object oracle, Object actual) {
+ Assert.assertTrue(msg + ";\n expected: "+oracle+"\n actual: "+actual,
+ oracle.equals(actual));
+ }
+}
diff --git a/headless/src/test/resources/ruleSets/emptyRuleSet.json b/headless/src/test/resources/ruleSets/emptyRuleSet.json
new file mode 100644
index 0000000..a01bc68
--- /dev/null
+++ b/headless/src/test/resources/ruleSets/emptyRuleSet.json
@@ -0,0 +1,4 @@
+{ "name": "emptyRuleSet"
+, "description": ""
+, "rules": []
+}
diff --git a/headless/src/test/resources/ruleSets/headingGuidelines.json b/headless/src/test/resources/ruleSets/headingGuidelines.json
new file mode 100644
index 0000000..be69fac
--- /dev/null
+++ b/headless/src/test/resources/ruleSets/headingGuidelines.json
@@ -0,0 +1,37 @@
+{ "name": "Heading Guidelines"
+, "description": "Guidelines pertaining to the formatting and content of headings."
+, "rules": [ { "id": 1
+ , "name": "Headings are capitalized"
+ , "description": "Check to see if all headings use leading capital letters."
+ , "rule":
+ function() {
+ var badHeadings =
+ fiveui.query(':header').filter(
+ function(idx) {
+ var ch = $(this).text()[0];
+ if (ch) {
+ return (ch == ch.toLowerCase() );
+ } else {
+ return false;
+ }
+ });
+ $(badHeadings).map(function(idx, elt){
+ report('Heading does not start with a capitol letter.', elt);
+ });
+
+ }
+ },
+ { "id": 2
+ , "name": "Disallow Empty Headers"
+ , "description": "Heading elements should contain text."
+ , "rule": function() {
+ fiveui.query(':header').each(
+ function(ix, elt) {
+ if($(elt).text() == '') {
+ report('Heading does not contain text', elt);
+ }
+ });
+ }
+ }
+ ]
+} \ No newline at end of file
diff --git a/headless/src/test/resources/runDescriptions/headlessRunTest0.json b/headless/src/test/resources/runDescriptions/headlessRunTest0.json
new file mode 100644
index 0000000..e6c4c3b
--- /dev/null
+++ b/headless/src/test/resources/runDescriptions/headlessRunTest0.json
@@ -0,0 +1,4 @@
+[{
+ 'url': 'http://localhost:8000',
+ 'ruleSet': '../ruleSets/emptyRuleSet.json'
+}]
diff --git a/headless/src/test/resources/runDescriptions/headlessRunTestCNN.json b/headless/src/test/resources/runDescriptions/headlessRunTestCNN.json
new file mode 100644
index 0000000..07aaab8
--- /dev/null
+++ b/headless/src/test/resources/runDescriptions/headlessRunTestCNN.json
@@ -0,0 +1,4 @@
+[{
+ 'url': 'http://www.cnn.com',
+ 'ruleSet': '../ruleSets/headingGuidelines.json'
+}]
diff --git a/headless/src/test/resources/runDescriptions/headlessRunTestGalois.json b/headless/src/test/resources/runDescriptions/headlessRunTestGalois.json
new file mode 100644
index 0000000..d12f9b0
--- /dev/null
+++ b/headless/src/test/resources/runDescriptions/headlessRunTestGalois.json
@@ -0,0 +1,11 @@
+{
+ 'rulePath' : '/Users/bjones/galois/FiveUI/exampleData/ruleSets',
+ 'crawlType' : 'none',
+ 'runs': [
+ { 'url': 'http://corp.galois.com', 'ruleSet': 'colorRulesRF.json' },
+ { 'url': 'http://corp.galois.com', 'ruleSet': 'emptyHeadings.json' },
+ { 'url': 'http://corp.galois.com', 'ruleSet': 'fontRules.json' },
+ { 'url': 'http://corp.galois.com', 'ruleSet': 'headingGuidelines.json' },
+ { 'url': 'http://corp.galois.com', 'ruleSet': 'imageRules.json' }
+ ]
+}
diff --git a/headless/src/test/resources/runDescriptions/headlessSample0.json b/headless/src/test/resources/runDescriptions/headlessSample0.json
new file mode 100644
index 0000000..2b3a51a
--- /dev/null
+++ b/headless/src/test/resources/runDescriptions/headlessSample0.json
@@ -0,0 +1,4 @@
+[{
+ 'url': 'http://testhost',
+ 'ruleSet': '../ruleSets/emptyRuleSet.json'
+}]
diff --git a/headless/src/test/resources/runDescriptions/headlessSample1.json b/headless/src/test/resources/runDescriptions/headlessSample1.json
new file mode 100644
index 0000000..a3ee34d
--- /dev/null
+++ b/headless/src/test/resources/runDescriptions/headlessSample1.json
@@ -0,0 +1,4 @@
+[{
+ 'url': 'http://testhost',
+ 'ruleSet': '../ruleSets/headingGuidelines.json'
+}]
diff --git a/headless/src/test/resources/runDescriptions/headlessSample2.json b/headless/src/test/resources/runDescriptions/headlessSample2.json
new file mode 100644
index 0000000..277bd4d
--- /dev/null
+++ b/headless/src/test/resources/runDescriptions/headlessSample2.json
@@ -0,0 +1,9 @@
+[
+{
+ 'url': 'http://testhost1',
+ 'ruleSet': '../ruleSets/emptyRuleSet.json'
+},
+{
+ 'url': 'http://testhost2',
+ 'ruleSet': '../ruleSets/headingGuidelines.json'
+}]
diff --git a/headless/target/classes/com/galois/fiveui/BatchRunner.class b/headless/target/classes/com/galois/fiveui/BatchRunner.class
new file mode 100644
index 0000000..9e0b316
--- /dev/null
+++ b/headless/target/classes/com/galois/fiveui/BatchRunner.class
Binary files differ
diff --git a/headless/target/classes/com/galois/fiveui/HeadlessAtom.class b/headless/target/classes/com/galois/fiveui/HeadlessAtom.class
new file mode 100644
index 0000000..f6532f9
--- /dev/null
+++ b/headless/target/classes/com/galois/fiveui/HeadlessAtom.class
Binary files differ
diff --git a/headless/target/classes/com/galois/fiveui/HeadlessRunDescription$Deserializer.class b/headless/target/classes/com/galois/fiveui/HeadlessRunDescription$Deserializer.class
new file mode 100644
index 0000000..aba4d2a
--- /dev/null
+++ b/headless/target/classes/com/galois/fiveui/HeadlessRunDescription$Deserializer.class
Binary files differ
diff --git a/headless/target/classes/com/galois/fiveui/HeadlessRunDescription.class b/headless/target/classes/com/galois/fiveui/HeadlessRunDescription.class
new file mode 100644
index 0000000..bc0b912
--- /dev/null
+++ b/headless/target/classes/com/galois/fiveui/HeadlessRunDescription.class
Binary files differ
diff --git a/headless/target/classes/com/galois/fiveui/HeadlessRunner.class b/headless/target/classes/com/galois/fiveui/HeadlessRunner.class
new file mode 100644
index 0000000..6d13821
--- /dev/null
+++ b/headless/target/classes/com/galois/fiveui/HeadlessRunner.class
Binary files differ
diff --git a/headless/target/test-classes/com/galois/fiveui/HeadlessTest.class b/headless/target/test-classes/com/galois/fiveui/HeadlessTest.class
new file mode 100644
index 0000000..c901198
--- /dev/null
+++ b/headless/target/test-classes/com/galois/fiveui/HeadlessTest.class
Binary files differ
diff --git a/headless/target/test-classes/ruleSets/emptyRuleSet.json b/headless/target/test-classes/ruleSets/emptyRuleSet.json
new file mode 100644
index 0000000..a01bc68
--- /dev/null
+++ b/headless/target/test-classes/ruleSets/emptyRuleSet.json
@@ -0,0 +1,4 @@
+{ "name": "emptyRuleSet"
+, "description": ""
+, "rules": []
+}
diff --git a/headless/target/test-classes/ruleSets/headingGuidelines.json b/headless/target/test-classes/ruleSets/headingGuidelines.json
new file mode 100644
index 0000000..be69fac
--- /dev/null
+++ b/headless/target/test-classes/ruleSets/headingGuidelines.json
@@ -0,0 +1,37 @@
+{ "name": "Heading Guidelines"
+, "description": "Guidelines pertaining to the formatting and content of headings."
+, "rules": [ { "id": 1
+ , "name": "Headings are capitalized"
+ , "description": "Check to see if all headings use leading capital letters."
+ , "rule":
+ function() {
+ var badHeadings =
+ fiveui.query(':header').filter(
+ function(idx) {
+ var ch = $(this).text()[0];
+ if (ch) {
+ return (ch == ch.toLowerCase() );
+ } else {
+ return false;
+ }
+ });
+ $(badHeadings).map(function(idx, elt){
+ report('Heading does not start with a capitol letter.', elt);
+ });
+
+ }
+ },
+ { "id": 2
+ , "name": "Disallow Empty Headers"
+ , "description": "Heading elements should contain text."
+ , "rule": function() {
+ fiveui.query(':header').each(
+ function(ix, elt) {
+ if($(elt).text() == '') {
+ report('Heading does not contain text', elt);
+ }
+ });
+ }
+ }
+ ]
+} \ No newline at end of file
diff --git a/headless/target/test-classes/runDescriptions/headlessRunTest0.json b/headless/target/test-classes/runDescriptions/headlessRunTest0.json
new file mode 100644
index 0000000..e6c4c3b
--- /dev/null
+++ b/headless/target/test-classes/runDescriptions/headlessRunTest0.json
@@ -0,0 +1,4 @@
+[{
+ 'url': 'http://localhost:8000',
+ 'ruleSet': '../ruleSets/emptyRuleSet.json'
+}]
diff --git a/headless/target/test-classes/runDescriptions/headlessRunTestBBC.json b/headless/target/test-classes/runDescriptions/headlessRunTestBBC.json
new file mode 100644
index 0000000..f91daf2
--- /dev/null
+++ b/headless/target/test-classes/runDescriptions/headlessRunTestBBC.json
@@ -0,0 +1,4 @@
+[{
+ 'url': 'http://www.bbc.co.uk',
+ 'ruleSet': '../ruleSets/headingGuidelines.json'
+}]
diff --git a/headless/target/test-classes/runDescriptions/headlessRunTestCNN.json b/headless/target/test-classes/runDescriptions/headlessRunTestCNN.json
new file mode 100644
index 0000000..07aaab8
--- /dev/null
+++ b/headless/target/test-classes/runDescriptions/headlessRunTestCNN.json
@@ -0,0 +1,4 @@
+[{
+ 'url': 'http://www.cnn.com',
+ 'ruleSet': '../ruleSets/headingGuidelines.json'
+}]
diff --git a/headless/target/test-classes/runDescriptions/headlessRunTestGalois.json b/headless/target/test-classes/runDescriptions/headlessRunTestGalois.json
new file mode 100644
index 0000000..d12f9b0
--- /dev/null
+++ b/headless/target/test-classes/runDescriptions/headlessRunTestGalois.json
@@ -0,0 +1,11 @@
+{
+ 'rulePath' : '/Users/bjones/galois/FiveUI/exampleData/ruleSets',
+ 'crawlType' : 'none',
+ 'runs': [
+ { 'url': 'http://corp.galois.com', 'ruleSet': 'colorRulesRF.json' },
+ { 'url': 'http://corp.galois.com', 'ruleSet': 'emptyHeadings.json' },
+ { 'url': 'http://corp.galois.com', 'ruleSet': 'fontRules.json' },
+ { 'url': 'http://corp.galois.com', 'ruleSet': 'headingGuidelines.json' },
+ { 'url': 'http://corp.galois.com', 'ruleSet': 'imageRules.json' }
+ ]
+}
diff --git a/headless/target/test-classes/runDescriptions/headlessSample0.json b/headless/target/test-classes/runDescriptions/headlessSample0.json
new file mode 100644
index 0000000..2b3a51a
--- /dev/null
+++ b/headless/target/test-classes/runDescriptions/headlessSample0.json
@@ -0,0 +1,4 @@
+[{
+ 'url': 'http://testhost',
+ 'ruleSet': '../ruleSets/emptyRuleSet.json'
+}]
diff --git a/headless/target/test-classes/runDescriptions/headlessSample1.json b/headless/target/test-classes/runDescriptions/headlessSample1.json
new file mode 100644
index 0000000..a3ee34d
--- /dev/null
+++ b/headless/target/test-classes/runDescriptions/headlessSample1.json
@@ -0,0 +1,4 @@
+[{
+ 'url': 'http://testhost',
+ 'ruleSet': '../ruleSets/headingGuidelines.json'
+}]
diff --git a/headless/target/test-classes/runDescriptions/headlessSample2.json b/headless/target/test-classes/runDescriptions/headlessSample2.json
new file mode 100644
index 0000000..277bd4d
--- /dev/null
+++ b/headless/target/test-classes/runDescriptions/headlessSample2.json
@@ -0,0 +1,9 @@
+[
+{
+ 'url': 'http://testhost1',
+ 'ruleSet': '../ruleSets/emptyRuleSet.json'
+},
+{
+ 'url': 'http://testhost2',
+ 'ruleSet': '../ruleSets/headingGuidelines.json'
+}]