From 5d4dc02634457724f2e507d14b72d95ef2adf902 Mon Sep 17 00:00:00 2001
From: Dick Fickling <dick@fickling.us>
Date: Mon, 21 May 2012 10:10:56 -0400
Subject: [PATCH] Initial test script commit.

---
 .gitignore                                    |   4 +
 .../hashdatabase/HashDbMgmtAction.java        |   2 +-
 .../autopsy/hashdatabase/HashDbMgmtPanel.java |  22 +-
 Testing/build.xml                             |  71 +++++
 Testing/manifest.mf                           |   5 +
 Testing/nbproject/build-impl.xml              |  45 ++++
 Testing/nbproject/genfiles.properties         |   8 +
 Testing/nbproject/project.properties          |   2 +
 Testing/nbproject/project.xml                 |  56 ++++
 Testing/nbproject/suite.properties            |   1 +
 Testing/script/getcounts.py                   |  38 +++
 Testing/script/regression.py                  | 247 ++++++++++++++++++
 .../autopsy/testing/Bundle.properties         |   1 +
 .../autopsy/testing/RegressionTest.java       | 215 +++++++++++++++
 nbproject/platform.properties                 |   1 +
 nbproject/project.properties                  |   6 +-
 16 files changed, 718 insertions(+), 6 deletions(-)
 create mode 100644 Testing/build.xml
 create mode 100644 Testing/manifest.mf
 create mode 100644 Testing/nbproject/build-impl.xml
 create mode 100644 Testing/nbproject/genfiles.properties
 create mode 100644 Testing/nbproject/project.properties
 create mode 100644 Testing/nbproject/project.xml
 create mode 100644 Testing/nbproject/suite.properties
 create mode 100644 Testing/script/getcounts.py
 create mode 100644 Testing/script/regression.py
 create mode 100644 Testing/src/org/sleuthkit/autopsy/testing/Bundle.properties
 create mode 100644 Testing/test/qa-functional/src/org/sleuthkit/autopsy/testing/RegressionTest.java

diff --git a/.gitignore b/.gitignore
index a9b2026f25..8a6216b27d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -22,3 +22,7 @@ Bundle_*.properties
 /branding/core/core.jar/org/netbeans/core/startup/Bundle.properties
 /branding/modules/org-netbeans-core-windows.jar/org/netbeans/core/windows/view/ui/Bundle.properties
 /CoreUtils/src/org/sleuthkit/autopsy/coreutils/Bundle.properties
+/Testing/script/input/
+/Testing/script/output/
+/Testing/script/gold/
+*~
diff --git a/HashDatabase/src/org/sleuthkit/autopsy/hashdatabase/HashDbMgmtAction.java b/HashDatabase/src/org/sleuthkit/autopsy/hashdatabase/HashDbMgmtAction.java
index cd134795e3..b0d8b3c7bc 100644
--- a/HashDatabase/src/org/sleuthkit/autopsy/hashdatabase/HashDbMgmtAction.java
+++ b/HashDatabase/src/org/sleuthkit/autopsy/hashdatabase/HashDbMgmtAction.java
@@ -32,7 +32,7 @@
  */
 class HashDbMgmtAction extends CallableSystemAction {
 
-    private static final String ACTION_NAME = "Hash Database Configuration";
+    static final String ACTION_NAME = "Hash Database Configuration";
 
     @Override
     public void performAction() {
diff --git a/HashDatabase/src/org/sleuthkit/autopsy/hashdatabase/HashDbMgmtPanel.java b/HashDatabase/src/org/sleuthkit/autopsy/hashdatabase/HashDbMgmtPanel.java
index 216aa8dcf8..b3bf5254ec 100644
--- a/HashDatabase/src/org/sleuthkit/autopsy/hashdatabase/HashDbMgmtPanel.java
+++ b/HashDatabase/src/org/sleuthkit/autopsy/hashdatabase/HashDbMgmtPanel.java
@@ -63,6 +63,7 @@ public class HashDbMgmtPanel extends javax.swing.JPanel {
 
     /** Creates new form HashDbMgmtPanel */
     private HashDbMgmtPanel() {
+        setName(HashDbMgmtAction.ACTION_NAME);
         notableTableModel = new HashSetTableModel();
         initComponents();
         customizeComponents();
@@ -293,9 +294,24 @@ private void addNotableButtonActionPerformed(java.awt.event.ActionEvent evt) {//
                         JOptionPane.PLAIN_MESSAGE, null, null, derivedName);
                 
                 if(setName != null && !setName.equals("")) {
-                    HashDb newDb = new HashDb(setName, Arrays.asList(new String[] {filePath}), false);
-                    if(IndexStatus.isIngestible(newDb.status()))
-                            newDb.setUseForIngest(true);
+                    HashDb newDb = new HashDb(setName, Arrays.asList(new String[]{filePath}), false);
+                    int toIndex = JOptionPane.NO_OPTION;
+                    if (IndexStatus.isIngestible(newDb.status())) {
+                        newDb.setUseForIngest(true);
+                    } else {
+                        toIndex = JOptionPane.showConfirmDialog(this,
+                                "The database you added has no index.\n"
+                                + "It will not be used for ingest until you create one.\n"
+                                + "Would you like to do so now?", "No Index Exists", JOptionPane.YES_NO_OPTION);
+                    }
+
+                    if (toIndex == JOptionPane.YES_OPTION) {
+                        try {
+                            newDb.createIndex();
+                        } catch (TskException ex) {
+                            logger.log(Level.WARNING, "Error creating index", ex);
+                        }
+                    }
                     notableTableModel.newSet(newDb); // TODO: support multiple file paths
                 }
 
diff --git a/Testing/build.xml b/Testing/build.xml
new file mode 100644
index 0000000000..da9b6c050e
--- /dev/null
+++ b/Testing/build.xml
@@ -0,0 +1,71 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- You may freely edit this file. See harness/README in the NetBeans platform -->
+<!-- for some information on what you could do (e.g. targets to override). -->
+<!-- If you delete this file and reopen the project it will be recreated. -->
+<project name="org.sleuthkit.autopsy.testing" default="netbeans" basedir=".">
+    <description>Builds, tests, and runs the project org.sleuthkit.autopsy.testing.</description>
+    <import file="nbproject/build-impl.xml"/>
+    
+    <target name="set-args">
+        <property name="img_path" value="C:\Users\dfickling\Desktop\test-data\64mb2.img"/>
+        <property name="known_bad_path" value="C:\Users\dfickling\Desktop\test-data\notable_files.txt"/>
+        <property name="nsrl_path" value="C:\Users\dfickling\Desktop\NSRLComplete.txt-md5.idx"/>
+        <property name="keyword_path" value="C:\Users\dfickling\Desktop\test-data\notable_words.xml"/>
+        <property name="gold_path" value="C:\Users\dfickling\Desktop\test-data\win7-ren.txt"/>
+        <property name="out_path" value="C:\Users\dfickling\Desktop\test-data"/>
+    </target>
+    
+    <target name="check-args"> <!-- remove dependency on set-args to get from script -->
+        <fail message="Missing required argument: img_path" unless="img_path"/>
+        <fail message="Missing required argument: gold_path" unless="gold_path"/>
+        <fail message="Missing required argument: out_path" unless="out_path"/>
+        <fail message="Missing required argument: known_bad_path" unless="known_bad_path"/>
+        <fail message="Missing required argument: nsrl_path" unless="nsrl_path"/>
+        <fail message="Missing required argument: keyword_path" unless="keyword_path"/>
+    </target>
+    
+    <target name="regression-test" depends="check-args,init,test-init,test-build" if="exists.test.qa-functional.src.dir">
+        <test test.type="qa-functional"/>
+    </target>
+    
+    <macrodef name="test">
+        <attribute name="test.type"/>
+        <attribute name="disable.apple.ui" default="false"/>
+        <sequential>
+            <property name="test.config" value="default"/>
+            <property name="test.config.default.includes" value="**/*Test.class"/>
+            <property name="test.config.${test.config}.includes" value="NOTHING"/>
+            <metaproperty name="test.includes" value="test.config.${test.config}.includes"/>
+            <property name="test.config.${test.config}.excludes" value=""/>
+            <metaproperty name="test.excludes" value="test.config.${test.config}.excludes"/>
+            <mkdir dir="${build.test.@{test.type}.results.dir}"/>
+            <junit fork="true" failureproperty="tests.failed" errorproperty="tests.failed" filtertrace="${test.filter.trace}" tempdir="${build.test.@{test.type}.results.dir}">
+                <batchtest todir="${build.test.@{test.type}.results.dir}">
+                    <fileset dir="${build.test.@{test.type}.classes.dir}" includes="${test.includes}" excludes="${test.excludes}"/>
+                </batchtest>
+                <classpath refid="test.@{test.type}.run.cp"/>
+                <syspropertyset refid="test.@{test.type}.properties"/>
+                <jvmarg line="${test.bootclasspath.prepend.args}"/>
+                <jvmarg line="${test.run.args}"/>
+                <sysproperty key="img_path" value="${img_path}"/>
+                <sysproperty key="gold_path" value="${gold_path}"/>
+                <sysproperty key="out_path" value="${out_path}"/>
+                <sysproperty key="known_bad_path" value="${known_bad_path}"/>
+                <sysproperty key="nsrl_path" value="${nsrl_path}"/>
+                <sysproperty key="keyword_path" value="${keyword_path}"/>
+                <!--needed to have tests NOT to steal focus when running, works in latest apple jdk update only.-->
+                <sysproperty key="apple.awt.UIElement" value="@{disable.apple.ui}"/>
+                <formatter type="brief" usefile="false"/>
+                <formatter type="xml"/>
+            </junit>
+            <fail message="Some tests failed; see details above.">
+                <condition>
+                    <and>
+                        <isset property="tests.failed"/>
+                        <isfalse value="${continue.after.failing.tests}"/>
+                    </and>
+                </condition>
+            </fail>
+        </sequential>
+    </macrodef>
+</project>
diff --git a/Testing/manifest.mf b/Testing/manifest.mf
new file mode 100644
index 0000000000..a4acafe8e5
--- /dev/null
+++ b/Testing/manifest.mf
@@ -0,0 +1,5 @@
+Manifest-Version: 1.0
+OpenIDE-Module: org.sleuthkit.autopsy.testing
+OpenIDE-Module-Localizing-Bundle: org/sleuthkit/autopsy/testing/Bundle.properties
+OpenIDE-Module-Specification-Version: 1.0
+
diff --git a/Testing/nbproject/build-impl.xml b/Testing/nbproject/build-impl.xml
new file mode 100644
index 0000000000..50047b6352
--- /dev/null
+++ b/Testing/nbproject/build-impl.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+*** GENERATED FROM project.xml - DO NOT EDIT  ***
+***         EDIT ../build.xml INSTEAD         ***
+-->
+<project name="org.sleuthkit.autopsy.testing-impl" basedir="..">
+    <fail message="Please build using Ant 1.7.1 or higher.">
+        <condition>
+            <not>
+                <antversion atleast="1.7.1"/>
+            </not>
+        </condition>
+    </fail>
+    <property file="nbproject/private/suite-private.properties"/>
+    <property file="nbproject/suite.properties"/>
+    <fail unless="suite.dir">You must set 'suite.dir' to point to your containing module suite</fail>
+    <property file="${suite.dir}/nbproject/private/platform-private.properties"/>
+    <property file="${suite.dir}/nbproject/platform.properties"/>
+    <macrodef name="property" uri="http://www.netbeans.org/ns/nb-module-project/2">
+        <attribute name="name"/>
+        <attribute name="value"/>
+        <sequential>
+            <property name="@{name}" value="${@{value}}"/>
+        </sequential>
+    </macrodef>
+    <macrodef name="evalprops" uri="http://www.netbeans.org/ns/nb-module-project/2">
+        <attribute name="property"/>
+        <attribute name="value"/>
+        <sequential>
+            <property name="@{property}" value="@{value}"/>
+        </sequential>
+    </macrodef>
+    <property file="${user.properties.file}"/>
+    <nbmproject2:property name="harness.dir" value="nbplatform.${nbplatform.active}.harness.dir" xmlns:nbmproject2="http://www.netbeans.org/ns/nb-module-project/2"/>
+    <nbmproject2:property name="nbplatform.active.dir" value="nbplatform.${nbplatform.active}.netbeans.dest.dir" xmlns:nbmproject2="http://www.netbeans.org/ns/nb-module-project/2"/>
+    <nbmproject2:evalprops property="cluster.path.evaluated" value="${cluster.path}" xmlns:nbmproject2="http://www.netbeans.org/ns/nb-module-project/2"/>
+    <fail message="Path to 'platform' cluster missing in $${cluster.path} property or using corrupt Netbeans Platform (missing harness).">
+        <condition>
+            <not>
+                <contains string="${cluster.path.evaluated}" substring="platform"/>
+            </not>
+        </condition>
+    </fail>
+    <import file="${harness.dir}/build.xml"/>
+</project>
diff --git a/Testing/nbproject/genfiles.properties b/Testing/nbproject/genfiles.properties
new file mode 100644
index 0000000000..b4f5cac4ea
--- /dev/null
+++ b/Testing/nbproject/genfiles.properties
@@ -0,0 +1,8 @@
+build.xml.data.CRC32=7c2c586b
+build.xml.script.CRC32=323ed73c
+build.xml.stylesheet.CRC32=a56c6a5b@1.46.2
+# This file is used by a NetBeans-based IDE to track changes in generated files such as build-impl.xml.
+# Do not edit this file. You may delete it but then the IDE will never regenerate such files for you.
+nbproject/build-impl.xml.data.CRC32=64fc7023
+nbproject/build-impl.xml.script.CRC32=d8a1ea41
+nbproject/build-impl.xml.stylesheet.CRC32=238281d1@1.46.2
diff --git a/Testing/nbproject/project.properties b/Testing/nbproject/project.properties
new file mode 100644
index 0000000000..17255bac6b
--- /dev/null
+++ b/Testing/nbproject/project.properties
@@ -0,0 +1,2 @@
+javac.source=1.6
+javac.compilerargs=-Xlint -Xlint:-serial
diff --git a/Testing/nbproject/project.xml b/Testing/nbproject/project.xml
new file mode 100644
index 0000000000..8511bfbed8
--- /dev/null
+++ b/Testing/nbproject/project.xml
@@ -0,0 +1,56 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://www.netbeans.org/ns/project/1">
+    <type>org.netbeans.modules.apisupport.project</type>
+    <configuration>
+        <data xmlns="http://www.netbeans.org/ns/nb-module-project/3">
+            <code-name-base>org.sleuthkit.autopsy.testing</code-name-base>
+            <suite-component/>
+            <module-dependencies>
+                <dependency>
+                    <code-name-base>org.sleuthkit.autopsy.ingest</code-name-base>
+                    <build-prerequisite/>
+                    <compile-dependency/>
+                    <run-dependency>
+                        <release-version>0-1</release-version>
+                        <specification-version>1.0</specification-version>
+                    </run-dependency>
+                </dependency>
+            </module-dependencies>
+            <test-dependencies>
+                <test-type>
+                    <name>qa-functional</name>
+                    <test-dependency>
+                        <code-name-base>org.netbeans.libs.junit4</code-name-base>
+                        <compile-dependency/>
+                    </test-dependency>
+                    <test-dependency>
+                        <code-name-base>org.netbeans.modules.jellytools.platform</code-name-base>
+                        <compile-dependency/>
+                    </test-dependency>
+                    <test-dependency>
+                        <code-name-base>org.netbeans.modules.jemmy</code-name-base>
+                        <compile-dependency/>
+                    </test-dependency>
+                    <test-dependency>
+                        <code-name-base>org.netbeans.modules.nbjunit</code-name-base>
+                        <recursive/>
+                        <compile-dependency/>
+                    </test-dependency>
+                </test-type>
+                <test-type>
+                    <name>unit</name>
+                    <test-dependency>
+                        <code-name-base>org.netbeans.libs.junit4</code-name-base>
+                        <compile-dependency/>
+                    </test-dependency>
+                    <test-dependency>
+                        <code-name-base>org.netbeans.modules.nbjunit</code-name-base>
+                        <recursive/>
+                        <compile-dependency/>
+                    </test-dependency>
+                </test-type>
+            </test-dependencies>
+            <public-packages/>
+        </data>
+    </configuration>
+</project>
diff --git a/Testing/nbproject/suite.properties b/Testing/nbproject/suite.properties
new file mode 100644
index 0000000000..29d7cc9bd6
--- /dev/null
+++ b/Testing/nbproject/suite.properties
@@ -0,0 +1 @@
+suite.dir=${basedir}/..
diff --git a/Testing/script/getcounts.py b/Testing/script/getcounts.py
new file mode 100644
index 0000000000..8327923aca
--- /dev/null
+++ b/Testing/script/getcounts.py
@@ -0,0 +1,38 @@
+#!/usr/bin/python
+import os
+import sys
+import sqlite3
+
+def getNumbers(inFile):
+
+  if not inFile.endswith(".db") or not os.path.exists(inFile):
+    print("Not a database file: " + inFile)
+    return
+  # For now, comparing size of blackboard_artifacts,
+  #                            blackboard_attributes,
+  #                        and tsk_objects.
+  inFileConn = sqlite3.connect(inFile)
+  inFileC = inFileConn.cursor()
+  print(inFile)
+  inFileC.execute("select count(*) from tsk_objects")
+  inFileObjects = inFileC.fetchone()[0]
+  print("Objects: %d" % inFileObjects)
+  inFileC.execute("select count(*) from blackboard_artifacts")
+  inFileArtifacts = inFileC.fetchone()[0]
+  print("Artifacts: %d" % inFileArtifacts)
+  inFileC.execute("select count(*) from blackboard_attributes")
+  inFileAttributes = inFileC.fetchone()[0]
+  print("Attributes: %d" % inFileAttributes)
+
+def usage():
+    print("This script queries the databases given as arguments for \n\
+    TSK Object, Blackboard Artifact, and Blackboard Attribute counts.")
+
+if __name__ == "__main__":
+    if len(sys.argv) == 1:
+        usage()
+    argi = 1
+    while argi < len(sys.argv):
+        getNumbers(sys.argv[argi])
+        argi+=1
+
diff --git a/Testing/script/regression.py b/Testing/script/regression.py
new file mode 100644
index 0000000000..5277b44ac9
--- /dev/null
+++ b/Testing/script/regression.py
@@ -0,0 +1,247 @@
+#!/usr/bin/python 
+import sys
+import sqlite3
+import re
+import subprocess
+import os.path
+import shutil
+import time
+
+#  Usage: ./regression.py [-i FILE] [OPTIONS]
+#  Run the RegressionTest.java file, and compare the result with a gold standard
+#  When the -i flag is set, this script only tests the image given by FILE.
+#  By default, it tests every image in ./input/
+#  An indexed NSRL database is expected at ./input/nsrl.txt-md5.idx,
+#  and an indexed notable hash database at ./input/notablehashes.txt-md5.idx
+#  In addition, any keywords to search for must be in ./input/notablekeywords.xml
+#    Options:
+#    -r, --rebuild      Rebuild the gold standards from the test results for each image
+
+hadErrors = False # If any of the tests failed
+results = {}      # Dictionary in which to store map ({imgname}->errors)
+goldDir = "gold"  # Directory for gold standards (files should be ./gold/{imgname}/standard.db)
+inDir = "input"   # Image files, hash dbs, and keywords.
+# Results will be in ./output/{datetime}/{imgname}/
+outDir = os.path.join("output",time.strftime("%Y.%m.%d-%H.%M")) 
+
+
+# Run ingest on all the images in 'input', using notablekeywords.xml and notablehashes.txt-md5.idx
+def testAddImageIngest(inFile):
+  print "================================================"
+  print "Ingesting Image: " + inFile
+
+  # Set up case directory path
+  testCaseName = imageName(inFile)
+  if os.path.exists(os.path.join(outDir,testCaseName)):
+    shutil.rmtree(os.path.join(outDir,testCaseName))
+  os.makedirs(os.path.join(outDir,testCaseName))
+
+  cwd = wgetcwd()
+  testInFile = wabspath(inFile)
+  knownBadPath = os.path.join(cwd,inDir,"notablehashes.txt-md5.idx")
+  keywordPath = os.path.join(cwd,inDir,"notablekeywords.xml")
+  nsrlPath = os.path.join(cwd,inDir,"nsrl.txt-md5.idx")
+
+  # set up ant target
+  args = ["ant"]
+  args.append("-q")
+  args.append("-f")
+  args.append(os.path.join("..","build.xml"))
+  args.append("regression-test")
+  args.append("-l")
+  args.append(os.path.join(cwd,outDir,testCaseName,"antlog.txt"))
+  args.append("-Dimg_path=" + testInFile)
+  args.append("-Dknown_bad_path=" + knownBadPath)
+  args.append("-Dkeyword_path=" + keywordPath)
+  args.append("-Dnsrl_path=" + nsrlPath)
+  args.append("-Dgold_path=" + os.path.join(cwd,goldDir))
+  args.append("-Dout_path=" + os.path.join(cwd,outDir,testCaseName))
+
+  # print the ant testing command
+  print "CMD: " + " ".join(args)
+
+  print "Starting test..."
+  #fnull = open(os.devnull, 'w')
+  #subprocess.call(args, stderr=subprocess.STDOUT, stdout=fnull)
+  #fnull.close();
+  subprocess.call(args)
+
+def testCompareToGold(inFile):
+  print "-----------------------------------------------"
+  print "Comparing results for " + inFile + " with gold."
+
+  name = imageName(inFile)
+  cwd = wgetcwd()
+  goldFile = os.path.join(cwd,goldDir,name,"standard.db")
+  testFile = os.path.join(cwd,outDir,name,"AutopsyTestCase","autopsy.db")
+  if os.path.isfile(goldFile) == False:
+    markError("No gold standard exists", inFile)
+    return
+  if os.path.isfile(testFile) == False:
+    markError("No database exists", inFile)
+    return
+
+  # For now, comparing size of blackboard_artifacts,
+  #                            blackboard_attributes,
+  #                        and tsk_objects.
+  goldConn = sqlite3.connect(goldFile)
+  goldC = goldConn.cursor()
+  testConn = sqlite3.connect(testFile)
+  testC = testConn.cursor()
+
+  print("Comparing Artifacts: ")
+  goldC.execute("select count(*) from blackboard_artifacts")
+  goldArtifacts = goldC.fetchone()[0]
+  testC.execute("select count(*) from blackboard_artifacts")
+  testArtifacts = testC.fetchone()[0]
+  if(goldArtifacts != testArtifacts):
+      errString = "Artifact counts do not match!: "
+      errString += str("Gold: %d, Test: %d" % (goldArtifacts, testArtifacts))
+      markError(errString, inFile)
+  else:
+      print("Artifact counts match!")
+  print("Comparing Attributes: ")
+  goldC.execute("select count(*) from blackboard_attributes")
+  goldAttributes = goldC.fetchone()[0]
+  testC.execute("select count(*) from blackboard_attributes")
+  testAttributes = testC.fetchone()[0]
+  if(goldAttributes != testAttributes):
+      errString = "Attribute counts do not match!: "
+      errString += str("Gold: %d, Test: %d" % (goldAttributes, testAttributes))
+      markError(errString, inFile)
+  else:
+      print("Attribute counts match!")
+  print("Comparing TSK Objects: ")
+  goldC.execute("select count(*) from tsk_objects")
+  goldObjects = goldC.fetchone()[0]
+  testC.execute("select count(*) from tsk_objects")
+  testObjects = testC.fetchone()[0]
+  if(goldObjects != testObjects):
+      errString = "TSK Object counts do not match!: "
+      errString += str("Gold: %d, Test: %d" % (goldObjects, testObjects))
+      markError(errString, inFile)
+  else:
+      print("Object counts match!")
+
+def copyTestToGold(inFile): 
+  print "------------------------------------------------"
+  print "Recreating gold standard from results."
+  inFile = imageName(inFile)
+  cwd = wgetcwd()
+  goldFile = os.path.join(cwd,goldDir,inFile,"standard.db")
+  testFile = os.path.join(cwd,outDir,inFile,"AutopsyTestCase","autopsy.db")
+  if os.path.exists(os.path.join(cwd,goldDir,inFile)):
+      shutil.rmtree(os.path.join(cwd,goldDir,inFile))
+  os.makedirs(os.path.join(cwd,goldDir,inFile))
+  shutil.copy(testFile, goldFile)
+
+class ImgType:
+  RAW, ENCASE, SPLIT, UNKNOWN = range(4)
+
+def imageType(inFile):
+  extStart = inFile.rfind(".")
+  if (extStart == -1):
+    return ImgType.UNKNOWN
+  ext = inFile[extStart:].lower()
+  if (ext == ".img" or ext == ".dd"):
+    return ImgType.RAW
+  elif (ext == ".e01"):
+    return ImgType.ENCASE
+  elif (ext == ".aa" or ext == ".001"):
+    return ImgType.SPLIT
+  else:
+    return ImgType.UNKNOWN
+
+def imageName(inFile):
+    pathEnd = inFile.rfind("/")
+    extStart = inFile.rfind(".")
+    if(extStart == -1 and extStart == -1):
+        return inFile
+    elif(extStart == -1):
+        return inFile[pathEnd+1:]
+    elif(pathEnd == -1):
+        return inFile[:extStart]
+    else:
+        return inFile[pathEnd+1:extStart]
+
+def markError(errString, inFile):
+    global hadErrors
+    hadErrors = True
+    errors = results.get(inFile, [])
+    errors.append(errString)
+    results[inFile] = errors
+    print errString
+
+def wgetcwd():
+    proc = subprocess.Popen(("cygpath", "-m", os.getcwd()), stdout=subprocess.PIPE)
+    out,err = proc.communicate()
+    return out.rstrip()
+
+def wabspath(inFile):
+    proc = subprocess.Popen(("cygpath", "-m", os.path.abspath(inFile)), stdout=subprocess.PIPE)
+    out,err = proc.communicate()
+    return out.rstrip()
+
+def copyLogs(inFile):
+  logDir = os.path.join("..","build","test","qa-functional","work","userdir0","var","log")
+  shutil.copytree(logDir,os.path.join(outDir,imageName(inFile),"logs"))
+
+def testFile(image, rebuild):
+  if imageType(image) != ImgType.UNKNOWN:
+    testAddImageIngest(image)
+    #print imageName(image)
+    copyLogs(image)
+    if rebuild:
+      copyTestToGold(image)
+    else:
+      testCompareToGold(image)
+
+def usage() :
+  usage = "\
+  Usage: ./regression.py [-i FILE] [OPTIONS] \n\n\
+  Run the RegressionTest.java file, and compare the result with a gold standard \n\n\
+  When the -i flag is set, this script only tests the image given by FILE.\n\
+  By default, it tests every image in ./input/\n\n\
+  An indexed NSRL database is expected at ./input/nsrl.txt-md5.idx,\n\
+  and an indexed notable hash database at ./input/notablehashes.txt-md5.idx\n\
+  In addition, any keywords to search for must be in ./input/notablekeywords.xml\n\n\
+    Options:\n\n\
+    -r, --rebuild\t\tRebuild the gold standards from the test results for each image"
+  return usage
+
+def main():
+  rebuild = False
+  single = False
+  test = True
+  argi = 1
+  while argi < len(sys.argv):
+      arg = sys.argv[argi]
+      if arg == "-i" and argi+1 < len(sys.argv):
+          single = True
+          argi+=1
+          image = sys.argv[argi]
+          print "Running on single image: " + image
+      elif (arg  == "--rebuild") or (arg == "-r"):
+          rebuild = True
+          print "Running in REBUILD mode"
+      else:
+          test = False
+          print usage()
+      argi+=1
+  if single:
+    testFile(image, rebuild)
+  elif test:
+    for inFile in os.listdir(inDir):
+      testFile(os.path.join(inDir,inFile), rebuild)
+
+  if hadErrors == True:
+    print "**********************************************"
+    print "Tests complete: There were errors"
+
+  for k,v in results.items():
+    print k
+    for errString in v:
+      print("\t%s" % errString)
+
+if __name__ == "__main__":
+  main()
diff --git a/Testing/src/org/sleuthkit/autopsy/testing/Bundle.properties b/Testing/src/org/sleuthkit/autopsy/testing/Bundle.properties
new file mode 100644
index 0000000000..125ec1c485
--- /dev/null
+++ b/Testing/src/org/sleuthkit/autopsy/testing/Bundle.properties
@@ -0,0 +1 @@
+OpenIDE-Module-Name=Testing
diff --git a/Testing/test/qa-functional/src/org/sleuthkit/autopsy/testing/RegressionTest.java b/Testing/test/qa-functional/src/org/sleuthkit/autopsy/testing/RegressionTest.java
new file mode 100644
index 0000000000..7e1708603b
--- /dev/null
+++ b/Testing/test/qa-functional/src/org/sleuthkit/autopsy/testing/RegressionTest.java
@@ -0,0 +1,215 @@
+/*
+ * Autopsy Forensic Browser
+ * 
+ * Copyright 2011 Basis Technology Corp.
+ * Contact: carrier <at> sleuthkit <dot> org
+ * 
+ * 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 org.sleuthkit.autopsy.testing;
+
+import java.util.logging.Logger;
+import javax.swing.JDialog;
+import javax.swing.JTextField;
+import junit.framework.Test;
+import org.netbeans.jellytools.JellyTestCase;
+import org.netbeans.jellytools.NbDialogOperator;
+import org.netbeans.jellytools.WizardOperator;
+import org.netbeans.jemmy.Timeout;
+import org.netbeans.jemmy.operators.JButtonOperator;
+import org.netbeans.jemmy.operators.JCheckBoxOperator;
+import org.netbeans.jemmy.operators.JDialogOperator;
+import org.netbeans.jemmy.operators.JFileChooserOperator;
+import org.netbeans.jemmy.operators.JTableOperator;
+import org.netbeans.jemmy.operators.JTextFieldOperator;
+import org.netbeans.junit.NbModuleSuite;
+import org.sleuthkit.autopsy.ingest.IngestManager;
+import org.sleuthkit.autopsy.ingest.IngestServiceAbstract;
+/**
+ * This test expects the following system properties to be set:
+ * img_path: The fully qualified path to the image file (if split, the first file)
+ * out_path: The location where the case will be stored
+ * nsrl_path: Path to the nsrl database
+ * known_bad_path: Path to a database of known bad hashes
+ * keyword_path: Path to a keyword list xml file
+ * 
+ * Without these properties set, the test will fail to run correctly.
+ * To run this test correctly, you should use the script 'regression.py'
+ * located in the 'script' directory of the Testing module.
+ */
+public class RegressionTest extends JellyTestCase{
+    
+    private static final Logger logger = Logger.getLogger(RegressionTest.class.getName());
+    
+    /** Constructor required by JUnit */
+    public RegressionTest(String name) {
+        super(name);
+    }
+    
+    
+    /** Creates suite from particular test cases. */
+    public static Test suite() {
+
+        // run tests with specific configuration
+        NbModuleSuite.Configuration conf = NbModuleSuite.createConfiguration(RegressionTest.class).
+                clusters(".*").
+                enableModules(".*");
+        conf = conf.addTest("testNewCaseWizardOpen",
+                "testNewCaseWizard",
+                "testAddImageWizard1",
+                "testConfigureIngest1",
+                "testConfigureHash",
+                "testConfigureIngest2",
+                "testConfigureSearch",
+                "testIngest");
+        return NbModuleSuite.create(conf);
+    }
+
+    /** Method called before each test case. */
+    @Override
+    public void setUp() {
+        logger.info("########  " + System.getProperty("img_path") + "  #######");
+    }
+
+    /** Method called after each test case. */
+    @Override
+    public void tearDown() {
+    }
+    
+    public void testNewCaseWizardOpen() {
+        logger.info("New Case");
+        NbDialogOperator nbdo = new NbDialogOperator("Welcome");
+        JButtonOperator jbo = new JButtonOperator(nbdo, 0); // the "New Case" button
+        jbo.clickMouse();
+    }
+    
+    public void testNewCaseWizard() {
+        logger.info("New Case Wizard");
+        WizardOperator wo = new WizardOperator("New Case Information");
+        JTextFieldOperator jtfo1 = new JTextFieldOperator(wo, 1);
+        jtfo1.typeText("AutopsyTestCase"); // Name the case "AutopsyTestCase"
+        JTextFieldOperator jtfo0 = new JTextFieldOperator(wo, 0);
+        jtfo0.typeText(System.getProperty("out_path"));
+        wo.btNext().clickMouse();
+        JTextFieldOperator jtfo2 = new JTextFieldOperator(wo, 0);
+        jtfo2.typeText("000"); // Set the case number
+        JTextFieldOperator jtfo3 = new JTextFieldOperator(wo, 1);
+        jtfo3.typeText("Examiner 1"); // Set the case examiner
+        wo.btFinish().clickMouse();
+    }
+    
+    public void testAddImageWizard1() {
+        logger.info("AddImageWizard 1");
+        WizardOperator wo = new WizardOperator("Add Image");
+        JTextFieldOperator jtfo0 = new JTextFieldOperator(wo, 0);
+        String imageDir = System.getProperty("img_path");
+        ((JTextField)jtfo0.getSource()).setText(imageDir);
+        wo.btNext().clickMouse();
+        long start = System.currentTimeMillis();
+        while(!wo.btNext().isEnabled()) {
+            new Timeout("pausing", 1000).sleep(); // give it a second (or five) to process
+        }
+        logger.info("Add image took " + (System.currentTimeMillis()-start) + "ms");
+        wo.btNext().clickMouse();
+    }
+    
+    public void testConfigureIngest1() {
+        logger.info("Ingest 1");
+        WizardOperator wo = new WizardOperator("Add Image");
+        JTableOperator jto = new JTableOperator(wo, 0);
+        int row = jto.findCellRow("Hash Lookup", 1, 0);
+        jto.clickOnCell(row, 1);
+        JButtonOperator jbo1 = new JButtonOperator(wo, "Advanced");
+        jbo1.pushNoBlock();
+    }
+    
+    public void testConfigureHash() {
+        logger.info("Hash Configure");
+        JDialog jd = JDialogOperator.waitJDialog("Hash Database Configuration", false, false);
+        JDialogOperator jdo = new JDialogOperator(jd);
+        String databaseDir = System.getProperty("nsrl_path");
+        String badDir = System.getProperty("known_bad_path");
+        JButtonOperator jbo0 = new JButtonOperator(jdo, "Change");
+        jbo0.pushNoBlock();
+        JFileChooserOperator jfco0 = new JFileChooserOperator();
+        jfco0.chooseFile(databaseDir);
+        JButtonOperator jbo1 = new JButtonOperator(jdo, "Add Notable Database");
+        jbo1.pushNoBlock();
+        JFileChooserOperator jfco1 = new JFileChooserOperator();
+        jfco1.chooseFile(badDir);
+        JDialog jd2 = JDialogOperator.waitJDialog("New Hash Set", false, false);
+        JDialogOperator jdo2 = new JDialogOperator(jd2);
+        JButtonOperator jbo2 = new JButtonOperator(jdo2, "OK", 0);
+        jbo2.pushNoBlock();
+        // Used if the database has no index
+        //JDialog jd3 = JDialogOperator.waitJDialog("No Index Exists", false, false);
+        //JDialogOperator jdo3 = new JDialogOperator(jd3);
+        //JButtonOperator jbo3 = new JButtonOperator(jdo3, "Yes", 0);
+        //new Timeout("pausing", 1000).sleep(); // give it a second (or five) to process
+        //jbo3.pushNoBlock();
+        JButtonOperator jbo4 = new JButtonOperator(jdo, "OK", 0);
+        jbo4.pushNoBlock();
+    }
+    
+    public void testConfigureIngest2() {
+        logger.info("Ingest 2");
+        WizardOperator wo = new WizardOperator("Add Image");
+        JTableOperator jto = new JTableOperator(wo, 0);
+        int row = jto.findCellRow("Keyword Search", 1, 0);
+        jto.clickOnCell(row, 1);
+        JButtonOperator jbo1 = new JButtonOperator(wo, "Advanced");
+        jbo1.pushNoBlock();
+    }
+    
+    public void testConfigureSearch() {
+        logger.info("Search Configure");
+        JDialog jd = JDialogOperator.waitJDialog("Keyword List Configuration", false, false);
+        JDialogOperator jdo = new JDialogOperator(jd);
+        String words = System.getProperty("keyword_path");
+        JButtonOperator jbo0 = new JButtonOperator(jdo, "Import List", 0);
+        jbo0.pushNoBlock();
+        JFileChooserOperator jfco0 = new JFileChooserOperator();
+        jfco0.chooseFile(words);
+        JCheckBoxOperator jcbo = new JCheckBoxOperator(jdo, "Use during triage / ingest", 0);
+        jcbo.doClick();
+        JButtonOperator jbo2 = new JButtonOperator(jdo, "OK", 0);
+        jbo2.pushNoBlock();
+        WizardOperator wo = new WizardOperator("Add Image");
+        wo.btNext().clickMouse();
+        wo.btFinish().clickMouse();
+    }
+    
+    public void testIngest() {
+        logger.info("Ingest 3");
+        long start = System.currentTimeMillis();
+        IngestManager man = IngestManager.getDefault();
+        while(man.isEnqueueRunning()) {
+            new Timeout("pausing", 5000).sleep(); // give it a second (or five) to process
+        }
+        logger.info("Enqueue took " + (System.currentTimeMillis()-start) + "ms");
+        while(man.isIngestRunning()) {
+            new Timeout("pausing", 1000).sleep(); // give it a second (or five) to process
+        }
+        new Timeout("pausing", 15000).sleep(); // give it a second (or fifteen) to process
+        boolean sleep = true;
+        while (sleep) {
+            new Timeout("pausing", 5000).sleep(); // give it a second (or five) to process
+            sleep = false;
+            for (IngestServiceAbstract serv : IngestManager.enumerateFsContentServices()) {
+                sleep = sleep || serv.hasBackgroundJobsRunning();
+            }
+        }
+        logger.info("Ingest (including enqueue) took " + (System.currentTimeMillis()-start) + "ms");
+        new Timeout("pausing", 5000).sleep(); // allow keyword search to finish saving artifacts, just in case
+    }
+}
\ No newline at end of file
diff --git a/nbproject/platform.properties b/nbproject/platform.properties
index 38ecd5a92e..95295edd72 100644
--- a/nbproject/platform.properties
+++ b/nbproject/platform.properties
@@ -1,4 +1,5 @@
 cluster.path=\
+    ${nbplatform.active.dir}/harness:\
     ${nbplatform.active.dir}/java:\
     ${nbplatform.active.dir}/platform
 disabled.modules=\
diff --git a/nbproject/project.properties b/nbproject/project.properties
index 103f5f1935..e499096b46 100644
--- a/nbproject/project.properties
+++ b/nbproject/project.properties
@@ -29,7 +29,8 @@ modules=\
     ${project.org.sleuthkit.autopsy.hashdatabase}:\
     ${project.org.gnu.trove}:\
     ${project.org.sleuthkit.autopsy.recentactivity}:\
-    ${project.org.sleuthkit.autopsy.report}
+    ${project.org.sleuthkit.autopsy.report}:\
+    ${project.org.sleuthkit.autopsy.testing}
 project.org.gnu.trove=trove
 project.org.sleuthkit.autopsy.casemodule=Case
 project.org.sleuthkit.autopsy.corecomponentinterfaces=CoreComponentInterfaces
@@ -43,4 +44,5 @@ project.org.sleuthkit.autopsy.keywordsearch=KeywordSearch
 project.org.sleuthkit.autopsy.menuactions=MenuActions
 project.org.sleuthkit.autopsy.datamodel=DataModel
 project.org.sleuthkit.autopsy.recentactivity=RecentActivity
-project.org.sleuthkit.autopsy.report=Report
\ No newline at end of file
+project.org.sleuthkit.autopsy.report=Report
+project.org.sleuthkit.autopsy.testing=Testing
-- 
GitLab