From 49912d966d716b6b7a9e3ce18a69c3892be0b3d8 Mon Sep 17 00:00:00 2001 From: "U-BASIS\\jwallace" Date: Fri, 5 Jul 2013 12:29:57 -0400 Subject: [PATCH 01/43] Renamed TestAutopsy class to TestConfiguration. Moved Configuration logic from run_tests to TestConfiguration class --- test/script/regression.py | 3938 +++++++++++++++++++------------------ 1 file changed, 1985 insertions(+), 1953 deletions(-) diff --git a/test/script/regression.py b/test/script/regression.py index e2dbde4c26..1cd5d6c1da 100644 --- a/test/script/regression.py +++ b/test/script/regression.py @@ -1,1953 +1,1985 @@ -#!/usr/bin/python -# -*- coding: utf_8 -*- - - # Autopsy Forensic Browser - # - # Copyright 2013 Basis Technology Corp. - # - # 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. - - -import codecs -import datetime -import logging -import os -import re -import shutil -import socket -import sqlite3 -import subprocess -import sys -from sys import platform as _platform -import time -import traceback -import xml -from time import localtime, strftime -from xml.dom.minidom import parse, parseString -import smtplib -from email.mime.image import MIMEImage -from email.mime.multipart import MIMEMultipart -from email.mime.text import MIMEText -import re -import zipfile -import zlib -import Emailer -import srcupdater - -# -# Please read me... -# -# This is the regression testing Python script. -# It uses an ant command to run build.xml for RegressionTest.java -# -# The code is cleanly sectioned and commented. -# Please follow the current formatting. -# It is a long and potentially confusing script. -# -# Variable, function, and class names are written in Python conventions: -# this_is_a_variable this_is_a_function() ThisIsAClass -# -# All variables that are needed throughout the script have been initialized -# in a global class. -# - Command line arguments are in Args (named args) -# - Information pertaining to each test is in TestAutopsy (named test_case) -# - Queried information from the databases is in DatabaseDiff (named database) -# Feel free to add additional global classes or add to the existing ones, -# but do not overwrite any existing variables as they are used frequently. -# - -Day = 0 -#-------------------------------------------------------------# -# Parses argv and stores booleans to match command line input # -#-------------------------------------------------------------# -class Args: - def __init__(self): - self.single = False - self.single_file = "" - self.rebuild = False - self.list = False - self.config_file = "" - self.unallocated = False - self.ignore = False - self.keep = False - self.verbose = False - self.exception = False - self.exception_string = "" - self.fr = False - - def parse(self): - global nxtproc - nxtproc = [] - nxtproc.append("python3") - nxtproc.append(sys.argv.pop(0)) - while sys.argv: - arg = sys.argv.pop(0) - nxtproc.append(arg) - if(arg == "-f"): - #try: @@@ Commented out until a more specific except statement is added - arg = sys.argv.pop(0) - print("Running on a single file:") - print(Emailer.path_fix(arg) + "\n") - self.single = True - self.single_file = Emailer.path_fix(arg) - #except: - # print("Error: No single file given.\n") - # return False - elif(arg == "-r" or arg == "--rebuild"): - print("Running in rebuild mode.\n") - self.rebuild = True - elif(arg == "-l" or arg == "--list"): - try: - arg = sys.argv.pop(0) - nxtproc.append(arg) - print("Running from configuration file:") - print(arg + "\n") - self.list = True - self.config_file = arg - except: - print("Error: No configuration file given.\n") - return False - elif(arg == "-u" or arg == "--unallocated"): - print("Ignoring unallocated space.\n") - self.unallocated = True - elif(arg == "-k" or arg == "--keep"): - print("Keeping the Solr index.\n") - self.keep = True - elif(arg == "-v" or arg == "--verbose"): - print("Running in verbose mode:") - print("Printing all thrown exceptions.\n") - self.verbose = True - elif(arg == "-e" or arg == "--exception"): - try: - arg = sys.argv.pop(0) - nxtproc.append(arg) - print("Running in exception mode: ") - print("Printing all exceptions with the string '" + arg + "'\n") - self.exception = True - self.exception_string = arg - except: - print("Error: No exception string given.") - elif arg == "-h" or arg == "--help": - print(usage()) - return False - elif arg == "-fr" or arg == "--forcerun": - print("Not downloading new images") - self.fr = True - else: - print(usage()) - return False - # Return the args were sucessfully parsed - return True -#-----------------------------------------------------# -# Holds all global variables for each individual test # -#-----------------------------------------------------# -class TestAutopsy: - def __init__(self, args): - self.args = args - # Paths: - self.output_dir = "" - self.input_dir = Emailer.make_local_path("..","input") - self.gold = Emailer.make_path("..", "output", "gold") - self.img_gold = Emailer.make_path(self.gold, 'tmp') - self.gold_parse = "" - self.img_gold_parse = "" - self.common_log = "AutopsyErrors.txt" - self.test_db_file = "autopsy.db" - self.Img_Test_Folder = "AutopsyTestCase" - # Logs: - self.csv = "" - self.global_csv = "" - self.html_log = "" - # Ant info: - self.known_bad_path = "" - self.keyword_path = "" - self.nsrl_path = "" - self.build_path = "" - # test_case info - self.autopsy_version = "" - self.ingest_messages = 0 - self.indexed_files = 0 - self.indexed_chunks = 0 - # Infinite Testing info - timer = 0 - self.images = [] - # Set the timeout to something huge - # The entire tester should not timeout before this number in ms - # However it only seems to take about half this time - # And it's very buggy, so we're being careful - self.timeout = 24 * 60 * 60 * 1000 * 1000 - self.ant = [] - - def get_image_name(self, image_file): - path_end = image_file.rfind("/") - path_end2 = image_file.rfind("\\") - ext_start = image_file.rfind(".") - if(ext_start == -1): - name = image_file - if(path_end2 != -1): - name = image_file[path_end2+1:ext_start] - elif(ext_start == -1): - name = image_file[path_end+1:] - elif(path_end == -1): - name = image_file[:ext_start] - elif(path_end!=-1 and ext_start!=-1): - name = image_file[path_end+1:ext_start] - else: - name = image_file[path_end2+1:ext_start] - return name - - def ant_to_string(self): - string = "" - for arg in self.ant: - string += (arg + " ") - return string - - def reset(self): - # Error tracking - self.printerror = [] - self.printout = [] - - # Set the timeout to something huge - # The entire tester should not timeout before this number in ms - # However it only seems to take about half this time - # And it's very buggy, so we're being careful - self.timeout = 24 * 60 * 60 * 1000 * 1000 - self.ant = [] - -#---------------------------------------------------------# -# Contains methods to compare two databases and internally -# stores some of the results. # -#---------------------------------------------------------# -class DatabaseDiff: - def __init__(self, case): - self.gold_artifacts = [] - self.autopsy_artifacts = [] - self.gold_attributes = 0 - self.autopsy_attributes = 0 - self.gold_objects = 0 - self.autopsy_objects = 0 - self.artifact_comparison = [] - self.attribute_comparison = [] - self.test_data = case - - def clear(self): - self.gold_artifacts = [] - self.autopsy_artifacts = [] - self.gold_attributes = 0 - self.autopsy_attributes = 0 - self.gold_objects = 0 - self.autopsy_objects = 0 - self.artifact_comparison = [] - self.attribute_comparison = [] - - - - def get_artifacts_count(self): - total = 0 - for nums in self.autopsy_artifacts: - total += nums - return total - - def get_artifact_comparison(self): - if not self.artifact_comparison: - return "All counts matched" - else: - global failedbool - global errorem - failedbool = True - global imgfail - imgfail = True - return "; ".join(self.artifact_comparison) - - def get_attribute_comparison(self): - if not self.attribute_comparison: - return "All counts matched" - global failedbool - global errorem - failedbool = True - global imgfail - imgfail = True - list = [] - for error in self.attribute_comparison: - list.append(error) - return ";".join(list) - - def _count_output_artifacts(self): - if not self.autopsy_artifacts: - autopsy_db_file = Emailer.make_path(test_case.output_dir, self.test_data.image_name, - test_case.Img_Test_Folder, test_case.test_db_file) - autopsy_con = sqlite3.connect(autopsy_db_file) - autopsy_cur = autopsy_con.cursor() - autopsy_cur.execute("SELECT COUNT(*) FROM blackboard_artifact_types") - length = autopsy_cur.fetchone()[0] + 1 - for type_id in range(1, length): - autopsy_cur.execute("SELECT COUNT(*) FROM blackboard_artifacts WHERE artifact_type_id=%d" % type_id) - self.autopsy_artifacts.append(autopsy_cur.fetchone()[0]) - - def _count_output_attributes(self): - if self.autopsy_attributes == 0: - autopsy_db_file = Emailer.make_path(test_case.output_dir, self.test_data.image_name, - test_case.Img_Test_Folder, test_case.test_db_file) - autopsy_con = sqlite3.connect(autopsy_db_file) - autopsy_cur = autopsy_con.cursor() - autopsy_cur.execute("SELECT COUNT(*) FROM blackboard_attributes") - autopsy_attributes = autopsy_cur.fetchone()[0] - self.autopsy_attributes = autopsy_attributes - - # Counts number of objects and saves them into database. - # @@@ Does not need to connect again. Should be storing connection in DatabaseDiff - # See also for _generate_autopsy_attributes - def _count_output_objects(self): - if self.autopsy_objects == 0: - autopsy_db_file = Emailer.make_path(test_case.output_dir, self.test_data.image_name, - test_case.Img_Test_Folder, test_case.test_db_file) - autopsy_con = sqlite3.connect(autopsy_db_file) - autopsy_cur = autopsy_con.cursor() - autopsy_cur.execute("SELECT COUNT(*) FROM tsk_objects") - autopsy_objects = autopsy_cur.fetchone()[0] - self.autopsy_objects = autopsy_objects - - # @@@ see _generate_autopsy_objects comment about saving connections, etc. Or could have design where connection - # is passed in so that we do not need separate methods for gold and output. - def _count_gold_artifacts(self): - if not self.gold_artifacts: - gold_db_file = Emailer.make_path(test_case.img_gold, self.test_data.image_name, test_case.test_db_file) - if(not Emailer.file_exists(gold_db_file)): - gold_db_file = Emailer.make_path(test_case.img_gold_parse, self.test_data.image_name, test_case.test_db_file) - gold_con = sqlite3.connect(gold_db_file) - gold_cur = gold_con.cursor() - gold_cur.execute("SELECT COUNT(*) FROM blackboard_artifact_types") - length = gold_cur.fetchone()[0] + 1 - for type_id in range(1, length): - gold_cur.execute("SELECT COUNT(*) FROM blackboard_artifacts WHERE artifact_type_id=%d" % type_id) - self.gold_artifacts.append(gold_cur.fetchone()[0]) - gold_cur.execute("SELECT * FROM blackboard_artifacts") - self.gold_artifacts_list = [] - for row in gold_cur.fetchall(): - for item in row: - self.gold_artifacts_list.append(item) - - def _count_gold_attributes(self): - if self.gold_attributes == 0: - gold_db_file = Emailer.make_path(test_case.img_gold, self.test_data.image_name, test_case.test_db_file) - if(not Emailer.file_exists(gold_db_file)): - gold_db_file = Emailer.make_path(test_case.img_gold_parse, self.test_data.image_name, test_case.test_db_file) - gold_con = sqlite3.connect(gold_db_file) - gold_cur = gold_con.cursor() - gold_cur.execute("SELECT COUNT(*) FROM blackboard_attributes") - self.gold_attributes = gold_cur.fetchone()[0] - - def _count_gold_objects(self): - if self.gold_objects == 0: - gold_db_file = Emailer.make_path(test_case.img_gold, self.test_data.image_name, test_case.test_db_file) - if(not Emailer.file_exists(gold_db_file)): - gold_db_file = Emailer.make_path(test_case.img_gold_parse, self.test_data.image_name, test_case.test_db_file) - gold_con = sqlite3.connect(gold_db_file) - gold_cur = gold_con.cursor() - gold_cur.execute("SELECT COUNT(*) FROM tsk_objects") - self.gold_objects = gold_cur.fetchone()[0] - - # Compares the blackboard artifact counts of two databases - def _compare_bb_artifacts(self): - exceptions = [] - try: - global failedbool - global errorem - if self.gold_artifacts != self.autopsy_artifacts: - failedbool = True - global imgfail - imgfail = True - errorem += self.test_data.image + ":There was a difference in the number of artifacts.\n" - rner = len(self.gold_artifacts) - for type_id in range(1, rner): - if self.gold_artifacts[type_id] != self.autopsy_artifacts[type_id]: - error = str("Artifact counts do not match for type id %d. " % type_id) - error += str("Gold: %d, Test: %d" % - (self.gold_artifacts[type_id], - self.autopsy_artifacts[type_id])) - exceptions.append(error) - return exceptions - except Exception as e: - printerror(self.test_data, str(e)) - exceptions.append("Error: Unable to compare blackboard_artifacts.\n") - return exceptions - - # Compares the blackboard atribute counts of two databases - # given the two database cursors - def _compare_bb_attributes(self): - exceptions = [] - try: - if self.gold_attributes != self.autopsy_attributes: - error = "Attribute counts do not match. " - error += str("Gold: %d, Test: %d" % (self.gold_attributes, self.autopsy_attributes)) - exceptions.append(error) - global failedbool - global errorem - failedbool = True - global imgfail - imgfail = True - errorem += self.test_data.image + ":There was a difference in the number of attributes.\n" - return exceptions - except Exception as e: - exceptions.append("Error: Unable to compare blackboard_attributes.\n") - return exceptions - - # Compares the tsk object counts of two databases - # given the two database cursors - def _compare_tsk_objects(self): - exceptions = [] - try: - if self.gold_objects != self.autopsy_objects: - error = "TSK Object counts do not match. " - error += str("Gold: %d, Test: %d" % (self.gold_objects, self.autopsy_objects)) - exceptions.append(error) - global failedbool - global errorem - failedbool = True - global imgfail - imgfail = True - errorem += self.test_data.image + ":There was a difference between the tsk object counts.\n" - return exceptions - except Exception as e: - exceptions.append("Error: Unable to compare tsk_objects.\n") - return exceptions - - - # Basic test between output and gold databases. Compares only counts of objects and blackboard items - def compare_basic_counts(self): - # SQLITE needs unix style pathing - - # Get connection to output database from current run - autopsy_db_file = Emailer.make_path(test_case.output_dir, self.test_data.image_name, - test_case.Img_Test_Folder, test_case.test_db_file) - autopsy_con = sqlite3.connect(autopsy_db_file) - autopsy_cur = autopsy_con.cursor() - - # Get connection to gold DB and count artifacts, etc. - gold_db_file = Emailer.make_path(test_case.img_gold, self.test_data.image_name, test_case.test_db_file) - if(not Emailer.file_exists(gold_db_file)): - gold_db_file = Emailer.make_path(test_case.img_gold_parse, self.test_data.image_name, test_case.test_db_file) - try: - self._count_gold_objects() - self._count_gold_artifacts() - self._count_gold_attributes() - except Exception as e: - printerror(self.test_data, "Way out:" + str(e)) - - # This is where we return if a file doesn't exist, because we don't want to - # compare faulty databases, but we do however want to try to run all queries - # regardless of the other database - if not Emailer.file_exists(autopsy_db_file): - printerror(self.test_data, "Error: DatabaseDiff file does not exist at:") - printerror(self.test_data, autopsy_db_file + "\n") - return - if not Emailer.file_exists(gold_db_file): - printerror(self.test_data, "Error: Gold database file does not exist at:") - printerror(self.test_data, gold_db_file + "\n") - return - - # compare size of bb artifacts, attributes, and tsk objects - gold_con = sqlite3.connect(gold_db_file) - gold_cur = gold_con.cursor() - - exceptions = [] - - autopsy_db_file = Emailer.make_path(test_case.output_dir, self.test_data.image_name, - test_case.Img_Test_Folder, test_case.test_db_file) - # Connect again and count things - autopsy_con = sqlite3.connect(autopsy_db_file) - try: - self._count_output_objects() - self._count_output_artifacts() - self._count_output_attributes() - except Exception as e: - printerror(self.test_data, "Way out:" + str(e)) - - # Compare counts - exceptions.append(self._compare_tsk_objects()) - exceptions.append(self._compare_bb_artifacts()) - exceptions.append(self._compare_bb_attributes()) - - self.artifact_comparison = exceptions[1] - self.attribute_comparison = exceptions[2] - - okay = "All counts match." - print_report(self.test_data, exceptions[0], "COMPARE TSK OBJECTS", okay) - print_report(self.test_data, exceptions[1], "COMPARE ARTIFACTS", okay) - print_report(self.test_data, exceptions[2], "COMPARE ATTRIBUTES", okay) - - - - - - - - # smart method that deals with blackboard comparison to avoid issues with different IDs based on when artifacts were created. - # Dumps sorted text results to output location stored in test_data. - # autopsy_db_file: Output database file - def _dump_output_db_bb(autopsy_con, autopsy_db_file, test_data): - autopsy_cur2 = autopsy_con.cursor() - global errorem - global attachl - global failedbool - # Get the list of all artifacts - # @@@ Could add a SORT by parent_path in here since that is how we are going to later sort it. - autopsy_cur2.execute("SELECT tsk_files.parent_path, tsk_files.name, blackboard_artifact_types.display_name, blackboard_artifacts.artifact_id FROM blackboard_artifact_types INNER JOIN blackboard_artifacts ON blackboard_artifact_types.artifact_type_id = blackboard_artifacts.artifact_type_id INNER JOIN tsk_files ON tsk_files.obj_id = blackboard_artifacts.obj_id") - database_log = codecs.open(test_data.autopsy_data_file, "wb", "utf_8") - rw = autopsy_cur2.fetchone() - appnd = False - counter = 0 - # Cycle through artifacts - try: - while (rw != None): - # File Name and artifact type - if(rw[0] != None): - database_log.write(rw[0] + rw[1] + ' ') - else: - database_log.write(rw[1] + ' ') - - # Get attributes for this artifact - autopsy_cur1 = autopsy_con.cursor() - looptry = True - test_data.artifact_count += 1 - try: - key = "" - key = str(rw[3]) - key = key, - autopsy_cur1.execute("SELECT blackboard_attributes.source, blackboard_attribute_types.display_name, blackboard_attributes.value_type, blackboard_attributes.value_text, blackboard_attributes.value_int32, blackboard_attributes.value_int64, blackboard_attributes.value_double FROM blackboard_attributes INNER JOIN blackboard_attribute_types ON blackboard_attributes.attribute_type_id = blackboard_attribute_types.attribute_type_id WHERE artifact_id =? ORDER BY blackboard_attributes.source, blackboard_attribute_types.display_name, blackboard_attributes.value_type, blackboard_attributes.value_text, blackboard_attributes.value_int32, blackboard_attributes.value_int64, blackboard_attributes.value_double", key) - attributes = autopsy_cur1.fetchall() - except Exception as e: - printerror(test_data, str(e)) - printerror(test_data, str(rw[3])) - print(test_data.image_name) - errorem += test_data.image_name + ":Attributes in artifact id (in output DB)# " + str(rw[3]) + " encountered an error: " + str(e) +" .\n" - looptry = False - print(test_data.artifact_fail) - test_data.artifact_fail += 1 - print(test_data.artifact_fail) - database_log.write('Error Extracting Attributes'); - - # Print attributes - if(looptry == True): - src = attributes[0][0] - for attr in attributes: - val = 3 + attr[2] - numvals = 0 - for x in range(3, 6): - if(attr[x] != None): - numvals += 1 - if(numvals > 1): - errorem += test_data.image_name + ":There were too many values for attribute type: " + attr[1] + " for artifact with id #" + str(rw[3]) + ".\n" - printerror(test_data, "There were too many values for attribute type: " + attr[1] + " for artifact with id #" + str(rw[3]) + " for image " + test_data.image_name + ".") - failedbool = True - if(not appnd): - attachl.append(autopsy_db_file) - appnd = True - if(not attr[0] == src): - errorem += test_data.image_name + ":There were inconsistent sources for artifact with id #" + str(rw[3]) + ".\n" - printerror(test_data, "There were inconsistent sources for artifact with id #" + str(rw[3]) + " for image " + test_data.image_name + ".") - failedbool = True - if(not appnd): - attachl.append(autopsy_db_file) - appnd = True - try: - database_log.write('') - database_log.write(' \n') - rw = autopsy_cur2.fetchone() - - # Now sort the file - srtcmdlst = ["sort", test_data.autopsy_data_file, "-o", test_data.sorted_data_file] - subprocess.call(srtcmdlst) - print(test_data.artifact_fail) - if(test_data.artifact_fail > 0): - errorem += test_data.image_name + ":There were " + str(test_data.artifact_count) + " artifacts and " + str(test_data.artifact_fail) + " threw an exception while loading.\n" - except Exception as e: - printerror(test_data, 'outer exception: ' + str(e)) - - # Dumps a database (minus the artifact and attributes) to a text file. - def _dump_output_db_nonbb(test_data): - # Make a copy of the DB - autopsy_db_file = Emailer.make_path(test_case.output_dir, test_data.image_name, - test_case.Img_Test_Folder, test_case.test_db_file) - backup_db_file = Emailer.make_path(test_case.output_dir, test_data.image_name, - test_case.Img_Test_Folder, "autopsy_backup.db") - copy_file(autopsy_db_file,backup_db_file) - autopsy_con = sqlite3.connect(backup_db_file) - - # Delete the blackboard tables - autopsy_con.execute("DROP TABLE blackboard_artifacts") - autopsy_con.execute("DROP TABLE blackboard_attributes") - dump_file = Emailer.make_path(test_case.output_dir, test_data.image_name, test_data.image_name + "Dump.txt") - database_log = codecs.open(dump_file, "wb", "utf_8") - dump_list = autopsy_con.iterdump() - try: - for line in dump_list: - try: - database_log.write(line + "\n") - except Exception as e: - printerror(test_data, "dump_output_db_nonbb: Inner dump Exception:" + str(e)) - except Exception as e: - printerror(test_data, "dump_output_db_nonbb: Outer dump Exception:" + str(e)) - - - # Dumps the given database to text files for later comparison - def dump_output_db(test_data): - autopsy_db_file = Emailer.make_path(test_case.output_dir, test_data.image_name, - test_case.Img_Test_Folder, test_case.test_db_file) - autopsy_con = sqlite3.connect(autopsy_db_file) - autopsy_cur = autopsy_con.cursor() - # Try to query the databases. Ignore any exceptions, the function will - # return an error later on if these do fail - DatabaseDiff._dump_output_db_bb(autopsy_con,autopsy_db_file, test_data) - DatabaseDiff._dump_output_db_nonbb(test_data) - - - -#-------------------------------------------------# -# Functions relating to comparing outputs # -#-------------------------------------------------# -class TestDiffer: - - # Compares results for a single test. Autopsy has already been run. - # test_data: TestData object - # databaseDiff: DatabaseDiff object created based on test_data - def run_diff(test_data, databaseDiff): - try: - gold_path = test_case.gold - # Tmp location to extract ZIP file into - img_gold = Emailer.make_path(test_case.gold, "tmp", test_data.image_name) - - # Open gold archive file - img_archive = Emailer.make_path("..", "output", "gold", test_data.image_name+"-archive.zip") - if(not Emailer.file_exists(img_archive)): - img_archive = Emailer.make_path(test_case.gold_parse, test_data.image_name+"-archive.zip") - gold_path = test_case.gold_parse - img_gold = Emailer.make_path(gold_path, "tmp", test_data.image_name) - extrctr = zipfile.ZipFile(img_archive, 'r', compression=zipfile.ZIP_DEFLATED) - extrctr.extractall(gold_path) - extrctr.close - time.sleep(2) - - # Lists of tests to run - TestDiffer._compare_errors(test_data) - - # Compare database count to gold - databaseDiff.compare_basic_counts() - - # Compare smart blackboard results - TestDiffer._compare_text(test_data.sorted_data_file, "SortedData", test_data) - - # Compare the rest of the database (non-BB) - TestDiffer._compare_text(test_data.test_dbdump, "DBDump", test_data) - - # Compare html output - TestDiffer._compare_to_gold_html(test_data) - - # Clean up tmp folder - del_dir(img_gold) - - except Exception as e: - printerror(test_data, "Tests failed due to an error, try rebuilding or creating gold standards.\n") - printerror(test_data, str(e) + "\n") - print(traceback.format_exc()) - - - - # @@@ _compare_text could be made more generic with how it forms the paths (i.e. not add ".txt" in the method) and probably merged with - # compare_errors since they both do basic comparison of text files - - # Compares two text files - # output_file: output text file - # gold_file: gold text file - # test_data: Test being performed - def _compare_text(output_file, gold_file, test_data): - gold_dir = Emailer.make_path(test_case.img_gold, test_data.image_name, test_data.image_name + gold_file + ".txt") - if(not Emailer.file_exists(gold_dir)): - gold_dir = Emailer.make_path(test_case.img_gold_parse, test_data.image_name, test_data.image_name + gold_file + ".txt") - if(not Emailer.file_exists(output_file)): - return - srtd_data = codecs.open(output_file, "r", "utf_8") - gold_data = codecs.open(gold_dir, "r", "utf_8") - gold_dat = gold_data.read() - srtd_dat = srtd_data.read() - if (not(gold_dat == srtd_dat)): - diff_dir = Emailer.make_local_path(test_case.output_dir, test_data.image_name, test_data.image_name+gold_file+"-Diff.txt") - diff_file = codecs.open(diff_dir, "wb", "utf_8") - dffcmdlst = ["diff", test_data.sorted_data_file, gold_dir] - subprocess.call(dffcmdlst, stdout = diff_file) - global attachl - global errorem - global failedbool - attachl.append(diff_dir) - errorem += test_data.image_name + ":There was a difference in the database file " + gold_file + ".\n" - printerror(test_data, "There was a database difference for " + test_data.image_name + " versus " + gold_file + ".\n") - failedbool = True - global imgfail - imgfail = True - - # Compare merged error log files - def _compare_errors(test_data): - gold_dir = Emailer.make_path(test_case.img_gold, test_data.image_name, test_data.image_name + "SortedErrors.txt") - if(not Emailer.file_exists(gold_dir)): - gold_dir = Emailer.make_path(test_case.img_gold_parse, test_data.image_name, test_data.image_name + "SortedErrors.txt") - common_log = codecs.open(test_data.sorted_log, "r", "utf_8") - gold_log = codecs.open(gold_dir, "r", "utf_8") - gold_dat = gold_log.read() - common_dat = common_log.read() - patrn = re.compile("\d") - if (not((re.sub(patrn, 'd', gold_dat)) == (re.sub(patrn, 'd', common_dat)))): - diff_dir = Emailer.make_local_path(test_case.output_dir, test_data.image_name, test_data.image_name+"AutopsyErrors-Diff.txt") - diff_file = open(diff_dir, "w") - dffcmdlst = ["diff", test_data.sorted_log, gold_dir] - subprocess.call(dffcmdlst, stdout = diff_file) - global attachl - global errorem - global failedbool - attachl.append(test_data.sorted_log) - attachl.append(diff_dir) - errorem += test_data.image_name + ":There was a difference in the exceptions Log.\n" - printerror(test_data, "Exceptions didn't match.\n") - failedbool = True - global imgfail - imgfail = True - - # Compare the html report file made by - # the regression test against the gold standard html report - def _compare_to_gold_html(test_data): - gold_html_file = Emailer.make_path(test_case.img_gold, test_data.image_name, "Report", "index.html") - if(not Emailer.file_exists(gold_html_file)): - gold_html_file = Emailer.make_path(test_case.img_gold_parse, test_data.image_name, "Report", "index.html") - htmlfolder = "" - for fs in os.listdir(Emailer.make_path(test_case.output_dir, test_data.image_name, test_case.Img_Test_Folder, "Reports")): - if os.path.isdir(Emailer.make_path(test_case.output_dir, test_data.image_name, test_case.Img_Test_Folder, "Reports", fs)): - htmlfolder = fs - autopsy_html_path = Emailer.make_path(test_case.output_dir, test_data.image_name, test_case.Img_Test_Folder, "Reports", htmlfolder, "HTML Report") - - - try: - autopsy_html_file = get_file_in_dir(autopsy_html_path, "index.html") - if not Emailer.file_exists(gold_html_file): - printerror(test_data, "Error: No gold html report exists at:") - printerror(test_data, gold_html_file + "\n") - return - if not Emailer.file_exists(autopsy_html_file): - printerror(test_data, "Error: No test_case html report exists at:") - printerror(test_data, autopsy_html_file + "\n") - return - #Find all gold .html files belonging to this test_case - ListGoldHTML = [] - for fs in os.listdir(Emailer.make_path(test_case.output_dir, test_data.image_name, test_case.Img_Test_Folder, "Reports", htmlfolder)): - if(fs.endswith(".html")): - ListGoldHTML.append(Emailer.make_path(test_case.output_dir, test_data.image_name, test_case.Img_Test_Folder, "Reports", htmlfolder, fs)) - #Find all new .html files belonging to this test_case - ListNewHTML = [] - if(os.path.exists(Emailer.make_path(test_case.img_gold, test_data.image_name))): - for fs in os.listdir(Emailer.make_path(test_case.img_gold, test_data.image_name)): - if (fs.endswith(".html")): - ListNewHTML.append(Emailer.make_path(test_case.img_gold, test_data.image_name, fs)) - if(not test_case.img_gold_parse == "" or test_case.img_gold == test_case.img_gold_parse): - if(Emailer.file_exists(Emailer.make_path(test_case.img_gold_parse, test_data.image_name))): - for fs in os.listdir(Emailer.make_path(test_case.img_gold_parse,test_data.image_name)): - if (fs.endswith(".html")): - ListNewHTML.append(Emailer.make_path(test_case.img_gold_parse, test_data.image_name, fs)) - #ensure both reports have the same number of files and are in the same order - if(len(ListGoldHTML) != len(ListNewHTML)): - printerror(test_data, "The reports did not have the same number of files. One of the reports may have been corrupted") - else: - ListGoldHTML.sort() - ListNewHTML.sort() - - total = {"Gold": 0, "New": 0} - for x in range(0, len(ListGoldHTML)): - count = TestDiffer._compare_report_files(ListGoldHTML[x], ListNewHTML[x]) - total["Gold"]+=count[0] - total["New"]+=count[1] - okay = "The test report matches the gold report." - errors=["Gold report had " + str(total["Gold"]) +" errors", "New report had " + str(total["New"]) + " errors."] - print_report(test_data, errors, "REPORT COMPARISON", okay) - if total["Gold"] == total["New"]: - test_data.report_passed = True - else: - printerror(test_data, "The reports did not match each other.\n " + errors[0] +" and the " + errors[1]) - except FileNotFoundException as e: - e.print_error() - except DirNotFoundException as e: - e.print_error() - except Exception as e: - printerror(test_data, "Error: Unknown fatal error comparing reports.") - printerror(test_data, str(e) + "\n") - logging.critical(traceback.format_exc()) - - # Compares file a to file b and any differences are returned - # Only works with report html files, as it searches for the first
    - def _compare_report_files(a_path, b_path): - a_file = open(a_path) - b_file = open(b_path) - a = a_file.read() - b = b_file.read() - a = a[a.find("
      "):] - b = b[b.find("
        "):] - - a_list = TestDiffer._split(a, 50) - b_list = TestDiffer._split(b, 50) - if not len(a_list) == len(b_list): - ex = (len(a_list), len(b_list)) - return ex - else: - return (0, 0) - - # Split a string into an array of string of the given size - def _split(input, size): - return [input[start:start+size] for start in range(0, len(input), size)] - -class TestData: - def __init__(self): - self.image = "" - self.image_file = "" - self.image_name = "" - self.sorted_log = "" - self.warning_log = "" - self.autopsy_data_file = "" - self.sorted_data_file = "" - self.common_log_path = "" - self.antlog_dir = "" - self.test_dbdump = "" - self.total_test_time = "" - self.start_date = "" - self.end_date = "" - self.total_ingest_time = "" - self.artifact_count = 0 - self.artifact_fail = 0 - self.heap_space = "" - self.service_times = "" - self.report_passed = False - # Error tracking - self.printerror = [] - self.printout = [] - def reset(self): - self.image = "" - self.image_file = "" - self.image_name = "" - self.sorted_log = "" - self.warning_log = "" - self.autopsy_data_file = "" - self.sorted_data_file = "" - self.common_log_path = "" - self.antlog_dir = "" - self.test_dbdump = "" - self.total_test_time = "" - self.start_date = "" - self.end_date = "" - self.total_ingest_time = "" - self.artifact_count = 0 - self.artifact_fail = 0 - self.heap_space = "" - self.service_times = "" - # Error tracking - self.printerror = [] - self.printout = [] - -class Reports: - def generate_reports(csv_path, database, test_data): - Reports._generate_html(database, test_data) - if test_case.global_csv: - Reports._generate_csv(test_case.global_csv, database, test_data) - else: - Reports._generate_csv(csv_path, database, test_data) - - # Generates the HTML log file - def _generate_html(database, test_data): - # If the file doesn't exist yet, this is the first test_case to run for - # this test, so we need to make the start of the html log - global imgfail - if not Emailer.file_exists(test_case.html_log): - Reports.write_html_head() - try: - global html - html = open(test_case.html_log, "a") - # The image title - title = "

        " + test_data.image_name + " \ - tested on " + socket.gethostname() + "

        \ -

        \ - Errors and Warnings |\ - Information |\ - General Output |\ - Logs\ -

        " - # The script errors found - if imgfail: - ids = 'errors1' - else: - ids = 'errors' - errors = "
        \ -

        Errors and Warnings

        \ -
        " - # For each error we have logged in the test_case - for error in test_data.printerror: - # Replace < and > to avoid any html display errors - errors += "

        " + error.replace("<", "<").replace(">", ">") + "

        " - # If there is a \n, we probably want a
        in the html - if "\n" in error: - errors += "
        " - errors += "
        " - - # Links to the logs - logs = "
        \ -

        Logs

        \ -
        " - logs_path = Emailer.make_local_path(test_case.output_dir, test_data.image_name, "logs") - for file in os.listdir(logs_path): - logs += "

        " + file + "

        " - logs += "
        " - - # All the testing information - info = "
        \ -

        Information

        \ -
        \ - " - # The individual elements - info += "" - info += "" - info += "" - info += "" - info += "" - info += "" - info += "" - info += "" - info += "" - info += "" - info += "" - info += "" - info += "" - info += "" - info += "" - info += "" - info += "" - info += "" - info += "" - info += "" - info += "" - info += "" - info += "" - info += "" - info += "" - info += "" - info += "" - info += "" - info += "" - info += "" - info += "" - info += "" - info += "" - info += "" - info += "" - info += "" - info += "" - info += "" - info += "" - info += "" - info += "" - info += "" - info += "" - info += "" - info += "" - info += "" - info += "
        Image Path:" + test_data.image_file + "
        Image Name:" + test_data.image_name + "
        test_case Output Directory:" + test_case.output_dir + "
        Autopsy Version:" + test_case.autopsy_version + "
        Heap Space:" + test_data.heap_space + "
        Test Start Date:" + test_data.start_date + "
        Test End Date:" + test_data.end_date + "
        Total Test Time:" + test_data.total_test_time + "
        Total Ingest Time:" + test_data.total_ingest_time + "
        Exceptions Count:" + str(len(get_exceptions(test_data))) + "
        Autopsy OutOfMemoryExceptions:" + str(len(search_logs("OutOfMemoryException", test_data))) + "
        Autopsy OutOfMemoryErrors:" + str(len(search_logs("OutOfMemoryError", test_data))) + "
        Tika OutOfMemoryErrors/Exceptions:" + str(Reports._get_num_memory_errors("tika", test_data)) + "
        Solr OutOfMemoryErrors/Exceptions:" + str(Reports._get_num_memory_errors("solr", test_data)) + "
        TskCoreExceptions:" + str(len(search_log_set("autopsy", "TskCoreException", test_data))) + "
        TskDataExceptions:" + str(len(search_log_set("autopsy", "TskDataException", test_data))) + "
        Ingest Messages Count:" + str(test_case.ingest_messages) + "
        Indexed Files Count:" + str(test_case.indexed_files) + "
        Indexed File Chunks Count:" + str(test_case.indexed_chunks) + "
        Out Of Disk Space:\ -

        (will skew other test results)

        " + str(len(search_log_set("autopsy", "Stopping ingest due to low disk space on disk", test_data))) + "
        TSK Objects Count:" + str(database.autopsy_objects) + "
        Artifacts Count:" + str(database.get_artifacts_count()) + "
        Attributes Count:" + str(database.autopsy_attributes) + "
        \ -
        " - # For all the general print statements in the test_case - output = "
        \ -

        General Output

        \ -
        " - # For each printout in the test_case's list - for out in test_data.printout: - output += "

        " + out + "

        " - # If there was a \n it probably means we want a
        in the html - if "\n" in out: - output += "
        " - output += "
        " - - html.write(title) - html.write(errors) - html.write(info) - html.write(logs) - html.write(output) - html.close() - except Exception as e: - printerror(test_data, "Error: Unknown fatal error when creating HTML log at:") - printerror(test_data, test_case.html_log) - printerror(test_data, str(e) + "\n") - logging.critical(traceback.format_exc()) - - # Writed the top of the HTML log file - def write_html_head(): - print(test_case.html_log) - html = open(str(test_case.html_log), "a") - head = "\ - \ - AutopsyTesttest_case Output\ - \ - \ - " - html.write(head) - html.close() - - # Writed the bottom of the HTML log file - def write_html_foot(): - html = open(test_case.html_log, "a") - head = "" - html.write(head) - html.close() - - # Adds all the image names to the HTML log for easy access - def html_add_images(full_image_names): - # If the file doesn't exist yet, this is the first test_case to run for - # this test, so we need to make the start of the html log - if not Emailer.file_exists(test_case.html_log): - Reports.write_html_head() - html = open(test_case.html_log, "a") - links = [] - for full_name in full_image_names: - name = test_case.get_image_name(full_name) - links.append("" + name + "") - html.write("

        " + (" | ".join(links)) + "

        ") - - # Generate the CSV log file - def _generate_csv(csv_path, database, test_data): - try: - # If the CSV file hasn't already been generated, this is the - # first run, and we need to add the column names - if not Emailer.file_exists(csv_path): - Reports.csv_header(csv_path) - # Now add on the fields to a new row - csv = open(csv_path, "a") - - # Variables that need to be written - vars = [] - vars.append( test_data.image_file ) - vars.append( test_data.image_name ) - vars.append( test_case.output_dir ) - vars.append( socket.gethostname() ) - vars.append( test_case.autopsy_version ) - vars.append( test_data.heap_space ) - vars.append( test_data.start_date ) - vars.append( test_data.end_date ) - vars.append( test_data.total_test_time ) - vars.append( test_data.total_ingest_time ) - vars.append( test_data.service_times ) - vars.append( str(len(get_exceptions(test_data))) ) - vars.append( str(Reports._get_num_memory_errors("autopsy", test_data)) ) - vars.append( str(Reports._get_num_memory_errors("tika", test_data)) ) - vars.append( str(Reports._get_num_memory_errors("solr", test_data)) ) - vars.append( str(len(search_log_set("autopsy", "TskCoreException", test_data))) ) - vars.append( str(len(search_log_set("autopsy", "TskDataException", test_data))) ) - vars.append( str(test_case.ingest_messages) ) - vars.append( str(test_case.indexed_files) ) - vars.append( str(test_case.indexed_chunks) ) - vars.append( str(len(search_log_set("autopsy", "Stopping ingest due to low disk space on disk", test_data))) ) - vars.append( str(database.autopsy_objects) ) - vars.append( str(database.get_artifacts_count()) ) - vars.append( str(database.autopsy_attributes) ) - vars.append( Emailer.make_local_path("gold", test_data.image_name, test_case.test_db_file) ) - vars.append( database.get_artifact_comparison() ) - vars.append( database.get_attribute_comparison() ) - vars.append( Emailer.make_local_path("gold", test_data.image_name, "standard.html") ) - vars.append( str(test_data.report_passed) ) - vars.append( test_case.ant_to_string() ) - # Join it together with a ", " - output = "|".join(vars) - output += "\n" - # Write to the log! - csv.write(output) - csv.close() - except Exception as e: - printerror(test_data, "Error: Unknown fatal error when creating CSV file at:") - printerror(test_data, csv_path) - printerror(test_data, str(e) + "\n") - print(traceback.format_exc()) - logging.critical(traceback.format_exc()) - - # Generates the CSV header (column names) - def csv_header(csv_path): - csv = open(csv_path, "w") - titles = [] - titles.append("Image Path") - titles.append("Image Name") - titles.append("Output test_case Directory") - titles.append("Host Name") - titles.append("Autopsy Version") - titles.append("Heap Space Setting") - titles.append("Test Start Date") - titles.append("Test End Date") - titles.append("Total Test Time") - titles.append("Total Ingest Time") - titles.append("Service Times") - titles.append("Autopsy Exceptions") - titles.append("Autopsy OutOfMemoryErrors/Exceptions") - titles.append("Tika OutOfMemoryErrors/Exceptions") - titles.append("Solr OutOfMemoryErrors/Exceptions") - titles.append("TskCoreExceptions") - titles.append("TskDataExceptions") - titles.append("Ingest Messages Count") - titles.append("Indexed Files Count") - titles.append("Indexed File Chunks Count") - titles.append("Out Of Disk Space") - titles.append("Tsk Objects Count") - titles.append("Artifacts Count") - titles.append("Attributes Count") - titles.append("Gold Database Name") - titles.append("Artifacts Comparison") - titles.append("Attributes Comparison") - titles.append("Gold Report Name") - titles.append("Report Comparison") - titles.append("Ant Command Line") - output = "|".join(titles) - output += "\n" - csv.write(output) - csv.close() - - # Returns the number of OutOfMemoryErrors and OutOfMemoryExceptions - # for a certain type of log - def _get_num_memory_errors(type, test_data): - return (len(search_log_set(type, "OutOfMemoryError", test_data)) + - len(search_log_set(type, "OutOfMemoryException", test_data))) - -class Logs: - def generate_log_data(test_data): - Logs._generate_common_log(test_data) - try: - Logs._fill_test_case_data(test_data) - except Exception as e: - printerror(test_data, "Error: Unknown fatal error when filling test_case data.") - printerror(test_data, str(e) + "\n") - logging.critical(traceback.format_exc()) - # If running in verbose mode (-v) - if test_case.args.verbose: - errors = Logs._report_all_errors() - okay = "No warnings or errors in any log files." - print_report(test_data, errors, "VERBOSE", okay) - # Generate the "common log": a log of all exceptions and warnings - # from each log file generated by Autopsy - def _generate_common_log(test_data): - try: - logs_path = Emailer.make_local_path(test_case.output_dir, test_data.image_name, "logs") - common_log = codecs.open(test_case.common_log_path, "w", "utf_8") - warning_log = codecs.open(test_data.warning_log, "w", "utf_8") - common_log.write("--------------------------------------------------\n") - common_log.write(test_data.image_name + "\n") - common_log.write("--------------------------------------------------\n") - rep_path = Emailer.make_local_path(test_case.output_dir) - rep_path = rep_path.replace("\\\\", "\\") - for file in os.listdir(logs_path): - log = codecs.open(Emailer.make_path(logs_path, file), "r", "utf_8") - for line in log: - line = line.replace(rep_path, "test_data") - if line.startswith("Exception"): - common_log.write(file +": " + line) - elif line.startswith("Error"): - common_log.write(file +": " + line) - elif line.startswith("SEVERE"): - common_log.write(file +":" + line) - else: - warning_log.write(file +": " + line) - log.close() - common_log.write("\n") - common_log.close() - print(test_data.sorted_log) - srtcmdlst = ["sort", test_case.common_log_path, "-o", test_data.sorted_log] - subprocess.call(srtcmdlst) - except Exception as e: - printerror(test_data, "Error: Unable to generate the common log.") - printerror(test_data, str(e) + "\n") - printerror(test_data, traceback.format_exc()) - logging.critical(traceback.format_exc()) - - # Fill in the global test_case's variables that require the log files - def _fill_test_case_data(test_data): - try: - # Open autopsy.log.0 - log_path = Emailer.make_path(test_case.output_dir, test_data.image_name, "logs", "autopsy.log.0") - log = open(log_path) - - # Set the test_case starting time based off the first line of autopsy.log.0 - # *** If logging time format ever changes this will break *** - test_data.start_date = log.readline().split(" org.")[0] - - # Set the test_case ending time based off the "create" time (when the file was copied) - test_data.end_date = time.ctime(os.path.getmtime(log_path)) - except Exception as e: - printerror(test_data, "Error: Unable to open autopsy.log.0.") - printerror(test_data, str(e) + "\n") - logging.warning(traceback.format_exc()) - # Set the test_case total test time - # Start date must look like: "Jul 16, 2012 12:57:53 PM" - # End date must look like: "Mon Jul 16 13:02:42 2012" - # *** If logging time format ever changes this will break *** - start = datetime.datetime.strptime(test_data.start_date, "%b %d, %Y %I:%M:%S %p") - end = datetime.datetime.strptime(test_data.end_date, "%a %b %d %H:%M:%S %Y") - test_data.total_test_time = str(end - start) - - try: - # Set Autopsy version, heap space, ingest time, and service times - - version_line = search_logs("INFO: Application name: Autopsy, version:", test_data)[0] - test_case.autopsy_version = Emailer.get_word_at(version_line, 5).rstrip(",") - - test_data.heap_space = search_logs("Heap memory usage:", test_data)[0].rstrip().split(": ")[1] - - ingest_line = search_logs("Ingest (including enqueue)", test_data)[0] - test_data.total_ingest_time = Emailer.get_word_at(ingest_line, 6).rstrip() - - message_line = search_log_set("autopsy", "Ingest messages count:", test_data)[0] - test_case.ingest_messages = int(message_line.rstrip().split(": ")[2]) - - files_line = search_log_set("autopsy", "Indexed files count:", test_data)[0] - test_case.indexed_files = int(files_line.rstrip().split(": ")[2]) - - chunks_line = search_log_set("autopsy", "Indexed file chunks count:", test_data)[0] - test_case.indexed_chunks = int(chunks_line.rstrip().split(": ")[2]) - except Exception as e: - printerror(test_data, "Error: Unable to find the required information to fill test_case data.") - printerror(test_data, str(e) + "\n") - logging.critical(traceback.format_exc()) - print(traceback.format_exc()) - try: - service_lines = search_log("autopsy.log.0", "to process()", test_data) - service_list = [] - for line in service_lines: - words = line.split(" ") - # Kind of forcing our way into getting this data - # If this format changes, the tester will break - i = words.index("secs.") - times = words[i-4] + " " - times += words[i-3] + " " - times += words[i-2] + " " - times += words[i-1] + " " - times += words[i] - service_list.append(times) - test_data.service_times = "; ".join(service_list) - except Exception as e: - printerror(test_data, "Error: Unknown fatal error when finding service times.") - printerror(test_data, str(e) + "\n") - logging.critical(traceback.format_exc()) - - # Returns all the errors found in the common log in a list - def _report_all_errors(): - try: - return get_warnings() + get_exceptions() - except Exception as e: - printerror(test_data, "Error: Unknown fatal error when reporting all errors.") - printerror(test_data, str(e) + "\n") - logging.warning(traceback.format_exc()) - # Searches the common log for any instances of a specific string. - def search_common_log(string, test_data): - results = [] - log = codecs.open(test_case.common_log_path, "r", "utf_8") - for line in log: - if string in line: - results.append(line) - log.close() - return results - -# Returns the type of image file, based off extension -class IMGTYPE: - RAW, ENCASE, SPLIT, UNKNOWN = range(4) - -def image_type(image_file): - ext_start = image_file.rfind(".") - if (ext_start == -1): - return IMGTYPE.UNKNOWN - ext = image_file[ext_start:].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 - -# Search through all the known log files for a specific string. -# Returns a list of all lines with that string -def search_logs(string, test_data): - logs_path = Emailer.make_local_path(test_case.output_dir, test_data.image_name, "logs") - results = [] - for file in os.listdir(logs_path): - log = codecs.open(Emailer.make_path(logs_path, file), "r", "utf_8") - for line in log: - if string in line: - results.append(line) - log.close() - return results - -# Searches the given log for the given string -# Returns a list of all lines with that string -def search_log(log, string, test_data): - logs_path = Emailer.make_local_path(test_case.output_dir, test_data.image_name, "logs", log) - try: - results = [] - log = codecs.open(logs_path, "r", "utf_8") - for line in log: - if string in line: - results.append(line) - log.close() - if results: - return results - except: - raise FileNotFoundException(logs_path) - -# Search through all the the logs of the given type -# Types include autopsy, tika, and solr -def search_log_set(type, string, test_data): - logs_path = Emailer.make_local_path(test_case.output_dir, test_data.image_name, "logs") - results = [] - for file in os.listdir(logs_path): - if type in file: - log = codecs.open(Emailer.make_path(logs_path, file), "r", "utf_8") - for line in log: - if string in line: - results.append(line) - log.close() - return results - -# Print a report for the given errors with the report name as name -# and if no errors are found, print the okay message -def print_report(test_data, errors, name, okay): - if errors: - printerror(test_data, "--------< " + name + " >----------") - for error in errors: - printerror(test_data, str(error)) - printerror(test_data, "--------< / " + name + " >--------\n") - else: - printout(test_data, "-----------------------------------------------------------------") - printout(test_data, "< " + name + " - " + okay + " />") - printout(test_data, "-----------------------------------------------------------------\n") - -# Used instead of the print command when printing out an error -def printerror(test_data, string): - print(string) - test_data.printerror.append(string) - -# Used instead of the print command when printing out anything besides errors -def printout(test_data, string): - print(string) - test_data.printout.append(string) - -#----------------------------------# -# Helper functions # -#----------------------------------# -# Returns a list of all the exceptions listed in all the autopsy logs -def get_exceptions(test_data): - exceptions = [] - logs_path = Emailer.make_path(test_case.output_dir, test_data.image_name, "logs") - results = [] - for file in os.listdir(logs_path): - if "autopsy.log" in file: - log = codecs.open(Emailer.make_path(logs_path, file), "r", "utf_8") - ex = re.compile("\SException") - er = re.compile("\SError") - for line in log: - if ex.search(line) or er.search(line): - exceptions.append(line) - log.close() - return exceptions - -# Returns a list of all the warnings listed in the common log -def get_warnings(test_data): - warnings = [] - common_log = codecs.open(test_data.warning_log, "r", "utf_8") - for line in common_log: - if "warning" in line.lower(): - warnings.append(line) - common_log.close() - return warnings - -def copy_logs(test_data): - try: - log_dir = os.path.join("..", "..", "Testing","build","test","qa-functional","work","userdir0","var","log") - shutil.copytree(log_dir, Emailer.make_local_path(test_case.output_dir, test_data.image_name, "logs")) - except Exception as e: - printerror(test_data,"Error: Failed to copy the logs.") - printerror(test_data,str(e) + "\n") - logging.warning(traceback.format_exc()) -# Clears all the files from a directory and remakes it -def clear_dir(dir): - try: - if Emailer.dir_exists(dir): - shutil.rmtree(dir) - os.makedirs(dir) - return True; - except Exception as e: - printerror(test_data,"Error: Cannot clear the given directory:") - printerror(test_data,dir + "\n") - print(str(e)) - return False; - -def del_dir(dir): - try: - if Emailer.dir_exists(dir): - shutil.rmtree(dir) - return True; - except: - printerror(test_data,"Error: Cannot delete the given directory:") - printerror(test_data,dir + "\n") - return False; -#Copies a given file from "ffrom" to "to" -def copy_file(ffrom, to): - try : - shutil.copy(ffrom, to) - except Exception as e: - print(str(e)) - print(traceback.format_exc()) - -# Copies a directory file from "ffrom" to "to" -def copy_dir(ffrom, to): - try : - if not os.path.isdir(ffrom): - raise FileNotFoundException(ffrom) - shutil.copytree(ffrom, to) - except: - raise FileNotFoundException(to) -# Returns the first file in the given directory with the given extension -def get_file_in_dir(dir, ext): - try: - for file in os.listdir(dir): - if file.endswith(ext): - return Emailer.make_path(dir, file) - # If nothing has been found, raise an exception - raise FileNotFoundException(dir) - except: - raise DirNotFoundException(dir) - -def find_file_in_dir(dir, name, ext): - try: - for file in os.listdir(dir): - if file.startswith(name): - if file.endswith(ext): - return Emailer.make_path(dir, file) - raise FileNotFoundException(dir) - except: - raise DirNotFoundException(dir) - -def setDay(): - global Day - Day = int(strftime("%d", localtime())) - -def getLastDay(): - return Day - -def getDay(): - return int(strftime("%d", localtime())) - -def newDay(): - return getLastDay() != getDay() - -# Returns the args of the test script -def usage(): - return """ -Usage: ./regression.py [-f FILE] [OPTIONS] - - Run RegressionTest.java, and compare the result with a gold standard. - By default, the script tests every image in ../input - When the -f flag is set, this script only tests a single given image. - When the -l flag is set, the script looks for a configuration file, - which may outsource to a new input directory and to individual images. - - Expected files: - An NSRL database at: ../input/nsrl.txt-md5.idx - A notable hash database at: ../input/notablehashes.txt-md5.idx - A notable keyword file at: ../input/notablekeywords.xml - -Options: - -r Rebuild the gold standards for the image(s) tested. - -i Ignores the ../input directory and all files within it. - -u Tells Autopsy not to ingest unallocated space. - -k Keeps each image's Solr index instead of deleting it. - -v Verbose mode; prints all errors to the screen. - -e ex Prints out all errors containing ex. - -l cfg Runs from configuration file cfg. - -c Runs in a loop over the configuration file until canceled. Must be used in conjunction with -l - -fr Will not try download gold standard images - """ - -#------------------------------------------------------------# -# Exception classes to manage "acceptable" thrown exceptions # -# versus unexpected and fatal exceptions # -#------------------------------------------------------------# - -# If a file cannot be found by one of the helper functions -# they will throw a FileNotFoundException unless the purpose -# is to return False -class FileNotFoundException(Exception): - def __init__(self, file): - self.file = file - self.strerror = "FileNotFoundException: " + file - - def print_error(self): - printerror(test_data,"Error: File could not be found at:") - printerror(test_data,self.file + "\n") - def error(self): - error = "Error: File could not be found at:\n" + self.file + "\n" - return error - -# If a directory cannot be found by a helper function, -# it will throw this exception -class DirNotFoundException(Exception): - def __init__(self, dir): - self.dir = dir - self.strerror = "DirNotFoundException: " + dir - - def print_error(self): - printerror(test_data, "Error: Directory could not be found at:") - printerror(test_data, self.dir + "\n") - def error(self): - error = "Error: Directory could not be found at:\n" + self.dir + "\n" - return error - -############################# -# Main Testing Functions # -############################# -class Test_Runner: - - #Executes the tests, makes continuous testing easier - # Identifies the tests to run and runs the tests - def run_tests(): - global parsed - global errorem - global failedbool - global html - global attachl - - # Setup output folder and reporting infrastructure - if(not Emailer.dir_exists(Emailer.make_path("..", "output", "results"))): - os.makedirs(Emailer.make_path("..", "output", "results",)) - test_case.output_dir = Emailer.make_path("..", "output", "results", time.strftime("%Y.%m.%d-%H.%M.%S")) - os.makedirs(test_case.output_dir) - test_case.csv = Emailer.make_local_path(test_case.output_dir, "CSV.txt") - test_case.html_log = Emailer.make_path(test_case.output_dir, "AutopsyTestCase.html") - test_data = TestData() - log_name = test_case.output_dir + "\\regression.log" - logging.basicConfig(filename=log_name, level=logging.DEBUG) - - - #Identify tests to run and populate test_case with list - # If user wants to do a single file and a list (contradictory?) - if test_case.args.single and test_case.args.list: - printerror(test_data, "Error: Cannot run both from config file and on a single file.") - return - # If working from a configuration file - if test_case.args.list: - if not Emailer.file_exists(test_case.args.config_file): - printerror(test_data, "Error: Configuration file does not exist at:") - printerror(test_data, test_case.args.config_file) - return - Test_Runner._load_config_file(test_case.args.config_file,test_data) - # Else if working on a single file - elif test_case.args.single: - if not Emailer.file_exists(test_case.args.single_file): - printerror(test_data, "Error: Image file does not exist at:") - printerror(test_data, test_case.args.single_file) - return - test_case.images.append(test_case.args.single_file) - - # If user has not selected a single file, and does not want to ignore - # the input directory, continue on to parsing ../input - if (not test_case.args.single) and (not test_case.args.ignore) and (not test_case.args.list): - test_case.args.config_file = "config.xml" - if not Emailer.file_exists(test_case.args.config_file): - printerror(test_data, "Error: Configuration file does not exist at:") - printerror(test_data, test_case.args.config_file) - return - Test_Runner._load_config_file(test_case.args.config_file, test_data) - - # Cycle through images in test_case and run tests - logres =[] - for img in test_case.images: - if Emailer.file_exists(img): - # Set the test_case to work for this test - test_data.image_file = str(img) - # @@@ This 0 should be be refactored out, but it will require rebuilding and changing of outputs. - test_data.image_name = test_case.get_image_name(test_data.image_file) + "(0)" - test_data.autopsy_data_file = Emailer.make_path(test_case.output_dir, test_data.image_name, test_data.image_name + "Autopsy_data.txt") - test_data.sorted_data_file = Emailer.make_path(test_case.output_dir, test_data.image_name, "Sorted_Autopsy_data.txt") - test_data.warning_log = Emailer.make_local_path(test_case.output_dir, test_data.image_name, "AutopsyLogs.txt") - test_data.antlog_dir = Emailer.make_local_path(test_case.output_dir, test_data.image_name, "antlog.txt") - test_data.test_dbdump = Emailer.make_path(test_case.output_dir, test_data.image_name, - test_data.image_name + "Dump.txt") - test_data.image = test_case.get_image_name(test_data.image_file) - - logres.append(Test_Runner._run_test(test_data)) - else: - printerror(test_data, "Warning: Image file listed in configuration does not exist:") - printerror(value + "\n") - test_data.reset() - Reports.write_html_foot() - html.close() - if (len(logres)>0): - failedbool = True - imgfail = True - passFail = False - for lm in logres: - for ln in lm: - errorem += ln - html.close() - if failedbool: - passFail = False - errorem += "The test output didn't match the gold standard.\n" - errorem += "Autopsy test failed.\n" - attachl.insert(0, html.name) - else: - errorem += "Autopsy test passed.\n" - passFail = True - attachl = [] - - # @@@ This fails here if we didn't parse an XML file - try: - Emailer.send_email(parsed, errorem, attachl, passFail) - except NameError: - printerror(test_data, "Could not send e-mail because of no XML file --maybe"); - - - # Run autopsy for a single test to generate output file and do comparison - # test_data: TestData object populated with locations and such for test - def _run_test(test_data): - global parsed - global imgfail - global failedbool - imgfail = False - if image_type(test_data.image_file) == IMGTYPE.UNKNOWN: - printerror(test_data, "Error: Image type is unrecognized:") - printerror(test_data, test_data.image_file + "\n") - return - - if(test_case.args.list): - element = parsed.getElementsByTagName("build") - if(len(element)<=0): - toval = Emailer.make_path("..", "build.xml") - else: - element = element[0] - toval = element.getAttribute("value").encode().decode("utf_8") - if(toval==None): - toval = Emailer.make_path("..", "build.xml") - else: - toval = Emailer.make_path("..", "build.xml") - test_case.build_path = toval - test_case.known_bad_path = Emailer.make_path(test_case.input_dir, "notablehashes.txt-md5.idx") - test_case.keyword_path = Emailer.make_path(test_case.input_dir, "notablekeywords.xml") - test_case.nsrl_path = Emailer.make_path(test_case.input_dir, "nsrl.txt-md5.idx") - logging.debug("--------------------") - logging.debug(test_data.image_name) - logging.debug("--------------------") - Test_Runner._run_ant(test_data) - time.sleep(2) # Give everything a second to process - - - # Autopsy has finished running, we will now process the results - test_case.common_log_path = Emailer.make_local_path(test_case.output_dir, test_data.image_name, test_data.image_name+test_case.common_log) - - # Dump the database before we diff or use it for rebuild - DatabaseDiff.dump_output_db(test_data) - - # merges logs into a single log for later diff / rebuild - copy_logs(test_data) - test_data.sorted_log = Emailer.make_local_path(test_case.output_dir, test_data.image_name, test_data.image_name + "SortedErrors.txt") - Logs.generate_log_data(test_data) - - # Look for core exceptions - # @@@ Should be moved to TestDiffer, but it didn't know about logres -- need to look into that - logres = Logs.search_common_log("TskCoreException", test_data) - - # Cleanup SOLR: If NOT keeping Solr index (-k) - if not test_case.args.keep: - solr_index = Emailer.make_path(test_case.output_dir, test_data.image_name, test_case.Img_Test_Folder, "ModuleOutput", "KeywordSearch") - if clear_dir(solr_index): - print_report(test_data, [], "DELETE SOLR INDEX", "Solr index deleted.") - elif test_case.args.keep: - print_report(test_data, [], "KEEP SOLR INDEX", "Solr index has been kept.") - - # If running in exception mode, print exceptions to log - if test_case.args.exception: - exceptions = search_logs(test_case.args.exception_string, test_data) - okay = "No warnings or exceptions found containing text '" + test_case.args.exception_string + "'." - print_report(test_data, exceptions, "EXCEPTION", okay) - - # @@@ We only need to create this here so that it can be passed into the - # Report because it stores results. Results should be stored somewhere else - # and then this can get pushed into only the diffing code. - databaseDiff = DatabaseDiff(test_data) - - # Now either diff or rebuild - if not test_case.args.rebuild: - TestDiffer.run_diff(test_data, databaseDiff) - # If running in rebuild mode (-r) - else: - Test_Runner.rebuild(test_data) - - # @@@ COnsider if we want to do this for a rebuild. - # Make the CSV log and the html log viewer - Reports.generate_reports(test_case.csv, databaseDiff, test_data) - # Reset the test_case and return the tests sucessfully finished - clear_dir(Emailer.make_path(test_case.output_dir, test_data.image_name, test_case.Img_Test_Folder, "ModuleOutput", "keywordsearch")) - if(failedbool): - attachl.append(test_case.common_log_path) - test_case.reset() - return logres - - # Iterates through an XML configuration file to find all given elements. Populates global test_case object with tests to run - # config_file: Path to the config file - # test_data: TestData object (@@@ Only being passed in for print messages) - def _load_config_file(config_file, test_data): - try: - global parsed - global errorem - global attachl - count = 0 - parsed = parse(config_file) - logres = [] - test_case - counts = {} - if parsed.getElementsByTagName("indir"): - test_case.input_dir = parsed.getElementsByTagName("indir")[0].getAttribute("value").encode().decode("utf_8") - if parsed.getElementsByTagName("global_csv"): - test_case.global_csv = parsed.getElementsByTagName("global_csv")[0].getAttribute("value").encode().decode("utf_8") - test_case.global_csv = Emailer.make_local_path(test_case.global_csv) - if parsed.getElementsByTagName("golddir"): - test_case.gold_parse = parsed.getElementsByTagName("golddir")[0].getAttribute("value").encode().decode("utf_8") - test_case.img_gold_parse = Emailer.make_path(test_case.gold_parse, 'tmp') - else: - test_case.gold_parse = test_case.gold - test_case.img_gold_parse = Emailer.make_path(test_case.gold_parse, 'tmp') - - # Generate the top navbar of the HTML for easy access to all images - images = [] - for element in parsed.getElementsByTagName("image"): - value = element.getAttribute("value").encode().decode("utf_8") - print ("Image in Config File: " + value) - if Emailer.file_exists(value): - test_case.images.append(value) - else: - printout(test_data, "File: " + value + " doesn't exist") - image_count = len(images) - Reports.html_add_images(images) - - # Sanity check to see if there are obvious gold images that we are not testing - gold_count = 0 - for file in os.listdir(test_case.gold): - if not(file == 'tmp'): - gold_count+=1 - - if (image_count > gold_count): - print("******Alert: There are more input images than gold standards, some images will not be properly tested.\n") - elif (image_count < gold_count): - print("******Alert: There are more gold standards than input images, this will not check all gold Standards.\n") - - except Exception as e: - printerror(test_data, "Error: There was an error running with the configuration file.") - printerror(test_data, str(e) + "\n") - logging.critical(traceback.format_exc()) - print(traceback.format_exc()) - - - # Rebuilds the gold standards by copying the test-generated database - # and html report files into the gold directory. Autopsy has already been run - def rebuild(test_data): - # Errors to print - errors = [] - if(test_case.gold_parse == "" ): - test_case.gold_parse = test_case.gold - test_case.img_gold_parse = test_case.img_gold - # Delete the current gold standards - gold_dir = test_case.img_gold_parse - clear_dir(test_case.img_gold_parse) - tmpdir = Emailer.make_path(gold_dir, test_data.image_name) - dbinpth = Emailer.make_path(test_case.output_dir, test_data.image_name, test_case.Img_Test_Folder, test_case.test_db_file) - dboutpth = Emailer.make_path(tmpdir, test_case.test_db_file) - dataoutpth = Emailer.make_path(tmpdir, test_data.image_name + "SortedData.txt") - dbdumpinpth = test_data.test_dbdump - dbdumpoutpth = Emailer.make_path(tmpdir, test_data.image_name + "DBDump.txt") - if not os.path.exists(test_case.img_gold_parse): - os.makedirs(test_case.img_gold_parse) - if not os.path.exists(gold_dir): - os.makedirs(gold_dir) - if not os.path.exists(tmpdir): - os.makedirs(tmpdir) - try: - copy_file(dbinpth, dboutpth) - if Emailer.file_exists(test_data.sorted_data_file): - copy_file(test_data.sorted_data_file, dataoutpth) - copy_file(dbdumpinpth, dbdumpoutpth) - error_pth = Emailer.make_path(tmpdir, test_data.image_name+"SortedErrors.txt") - copy_file(test_data.sorted_log, error_pth) - except Exception as e: - printerror(test_data, str(e)) - print(str(e)) - print(traceback.format_exc()) - # Rebuild the HTML report - htmlfolder = "" - for fs in os.listdir(os.path.join(os.getcwd(),test_case.output_dir, test_data.image_name, test_case.Img_Test_Folder, "Reports")): - if os.path.isdir(os.path.join(os.getcwd(), test_case.output_dir, test_data.image_name, test_case.Img_Test_Folder, "Reports", fs)): - htmlfolder = fs - autopsy_html_path = Emailer.make_local_path(test_case.output_dir, test_data.image_name, test_case.Img_Test_Folder, "Reports", htmlfolder) - - html_path = Emailer.make_path(test_case.output_dir, test_data.image_name, - test_case.Img_Test_Folder, "Reports") - try: - if not os.path.exists(Emailer.make_path(tmpdir, htmlfolder)): - os.makedirs(Emailer.make_path(tmpdir, htmlfolder)) - for file in os.listdir(autopsy_html_path): - html_to = Emailer.make_path(tmpdir, file.replace("HTML Report", "Report")) - copy_dir(get_file_in_dir(autopsy_html_path, file), html_to) - except FileNotFoundException as e: - errors.append(e.error) - except Exception as e: - errors.append("Error: Unknown fatal error when rebuilding the gold html report.") - errors.append(str(e) + "\n") - print(traceback.format_exc()) - oldcwd = os.getcwd() - zpdir = gold_dir - os.chdir(zpdir) - os.chdir("..") - img_gold = "tmp" - img_archive = Emailer.make_path(test_data.image_name+"-archive.zip") - comprssr = zipfile.ZipFile(img_archive, 'w',compression=zipfile.ZIP_DEFLATED) - Test_Runner.zipdir(img_gold, comprssr) - comprssr.close() - os.chdir(oldcwd) - del_dir(test_case.img_gold_parse) - okay = "Sucessfully rebuilt all gold standards." - print_report(test_data, errors, "REBUILDING", okay) - - def zipdir(path, zip): - for root, dirs, files in os.walk(path): - for file in files: - zip.write(os.path.join(root, file)) - - # Tests Autopsy with RegressionTest.java by by running - # the build.xml file through ant - def _run_ant(test_data): - # Set up the directories - test_case_path = os.path.join(test_case.output_dir, test_data.image_name) - if Emailer.dir_exists(test_case_path): - shutil.rmtree(test_case_path) - os.makedirs(test_case_path) - test_case.ant = ["ant"] - test_case.ant.append("-v") - test_case.ant.append("-f") - # case.ant.append(case.build_path) - test_case.ant.append(os.path.join("..","..","Testing","build.xml")) - test_case.ant.append("regression-test") - test_case.ant.append("-l") - test_case.ant.append(test_data.antlog_dir) - test_case.ant.append("-Dimg_path=" + test_data.image_file) - test_case.ant.append("-Dknown_bad_path=" + test_case.known_bad_path) - test_case.ant.append("-Dkeyword_path=" + test_case.keyword_path) - test_case.ant.append("-Dnsrl_path=" + test_case.nsrl_path) - test_case.ant.append("-Dgold_path=" + Emailer.make_path(test_case.gold)) - test_case.ant.append("-Dout_path=" + Emailer.make_local_path(test_case.output_dir, test_data.image_name)) - test_case.ant.append("-Dignore_unalloc=" + "%s" % test_case.args.unallocated) - test_case.ant.append("-Dtest.timeout=" + str(test_case.timeout)) - - printout(test_data, "Ingesting Image:\n" + test_data.image_file + "\n") - printout(test_data, "CMD: " + " ".join(test_case.ant)) - printout(test_data, "Starting test...\n") - antoutpth = Emailer.make_local_path(test_case.output_dir, "antRunOutput.txt") - antout = open(antoutpth, "a") - if SYS is OS.CYGWIN: - subprocess.call(test_case.ant, stdout=subprocess.PIPE) - elif SYS is OS.WIN: - theproc = subprocess.Popen(test_case.ant, shell = True, stdout=subprocess.PIPE) - theproc.communicate() - antout.close() - -#----------------------# -# Main # -#----------------------# -def main(): - # Global variables - global failedbool - global inform - global fl - global test_case - global errorem - global attachl - global daycount - global redo - global passed - daycount = 0 - failedbool = False - redo = False - errorem = "" - args = Args() - test_case = TestAutopsy(args) - attachl = [] - passed = False - # The arguments were given wrong: - if not args.parse(): - case.reset() - return - if(not args.fr): - antin = ["ant"] - antin.append("-f") - antin.append(os.path.join("..","..","build.xml")) - antin.append("test-download-imgs") - if SYS is OS.CYGWIN: - subprocess.call(antin) - elif SYS is OS.WIN: - theproc = subprocess.Popen(antin, shell = True, stdout=subprocess.PIPE) - theproc.communicate() - # Otherwise test away! - Test_Runner.run_tests() - -class OS: - LINUX, MAC, WIN, CYGWIN = range(4) -if __name__ == "__main__": - global SYS - if _platform == "linux" or _platform == "linux2": - SYS = OS.LINUX - elif _platform == "darwin": - SYS = OS.MAC - elif _platform == "win32": - SYS = OS.WIN - elif _platform == "cygwin": - SYS = OS.CYGWIN - - if SYS is OS.WIN or SYS is OS.CYGWIN: - main() - else: - print("We only support Windows and Cygwin at this time.") \ No newline at end of file +#!/usr/bin/python +# -*- coding: utf_8 -*- + + # Autopsy Forensic Browser + # + # Copyright 2013 Basis Technology Corp. + # + # 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. + + +import codecs +import datetime +import logging +import os +import re +import shutil +import socket +import sqlite3 +import subprocess +import sys +from sys import platform as _platform +import time +import traceback +import xml +from time import localtime, strftime +from xml.dom.minidom import parse, parseString +import smtplib +from email.mime.image import MIMEImage +from email.mime.multipart import MIMEMultipart +from email.mime.text import MIMEText +import re +import zipfile +import zlib +import Emailer +import srcupdater + +# +# Please read me... +# +# This is the regression testing Python script. +# It uses an ant command to run build.xml for RegressionTest.java +# +# The code is cleanly sectioned and commented. +# Please follow the current formatting. +# It is a long and potentially confusing script. +# +# Variable, function, and class names are written in Python conventions: +# this_is_a_variable this_is_a_function() ThisIsAClass +# +# All variables that are needed throughout the script have been initialized +# in a global class. +# - Command line arguments are in Args (named args) +# - Global Test Configuration is in TestConfiguration(named test_case) +# - Queried information from the databases is in DatabaseDiff (named database) +# Feel free to add additional global classes or add to the existing ones, +# but do not overwrite any existing variables as they are used frequently. +# + + +# Data Definitions: +# +# Path: A path to a file or directory. +# ConfigFile: An XML file formatted according to the template in myconfig.xml +# +# + + +Day = 0 +#-------------------------------------------------------------# +# Parses argv and stores booleans to match command line input # +#-------------------------------------------------------------# +class Args: + def __init__(self): + self.single = False + self.single_file = "" + self.rebuild = False + self.list = False + self.config_file = "" + self.unallocated = False + self.ignore = False + self.keep = False + self.verbose = False + self.exception = False + self.exception_string = "" + self.fr = False + + def parse(self): + global nxtproc + nxtproc = [] + nxtproc.append("python3") + nxtproc.append(sys.argv.pop(0)) + while sys.argv: + arg = sys.argv.pop(0) + nxtproc.append(arg) + if(arg == "-f"): + #try: @@@ Commented out until a more specific except statement is added + arg = sys.argv.pop(0) + print("Running on a single file:") + print(Emailer.path_fix(arg) + "\n") + self.single = True + self.single_file = Emailer.path_fix(arg) + #except: + # print("Error: No single file given.\n") + # return False + elif(arg == "-r" or arg == "--rebuild"): + print("Running in rebuild mode.\n") + self.rebuild = True + elif(arg == "-l" or arg == "--list"): + try: + arg = sys.argv.pop(0) + nxtproc.append(arg) + print("Running from configuration file:") + print(arg + "\n") + self.list = True + self.config_file = arg + except: + print("Error: No configuration file given.\n") + return False + elif(arg == "-u" or arg == "--unallocated"): + print("Ignoring unallocated space.\n") + self.unallocated = True + elif(arg == "-k" or arg == "--keep"): + print("Keeping the Solr index.\n") + self.keep = True + elif(arg == "-v" or arg == "--verbose"): + print("Running in verbose mode:") + print("Printing all thrown exceptions.\n") + self.verbose = True + elif(arg == "-e" or arg == "--exception"): + try: + arg = sys.argv.pop(0) + nxtproc.append(arg) + print("Running in exception mode: ") + print("Printing all exceptions with the string '" + arg + "'\n") + self.exception = True + self.exception_string = arg + except: + print("Error: No exception string given.") + elif arg == "-h" or arg == "--help": + print(usage()) + return False + elif arg == "-fr" or arg == "--forcerun": + print("Not downloading new images") + self.fr = True + else: + print(usage()) + return False + # Return the args were sucessfully parsed + return True + + +class TestConfiguration: + """ + The Master Test Configuration. Encapsulates consolidated high level input from + config XML file and command-line arguments. + """ + def __init__(self, args): + self.args = args # Args : the command line arguments + # Paths: + self.output_dir = "" # Path : the path to the output directory + self.input_dir = Emailer.make_local_path("..","input") # Path : the Path to the input directory + self.gold = Emailer.make_path("..", "output", "gold") # Path : the Path to the gold directory + self.img_gold = Emailer.make_path(self.gold, 'tmp') + self.gold_parse = "" + self.img_gold_parse = "" + self.common_log = "AutopsyErrors.txt" + self.test_db_file = "autopsy.db" + self.Img_Test_Folder = "AutopsyTestCase" + # Logs: + self.csv = "" + self.global_csv = "" + self.html_log = "" + # Ant info: + self.known_bad_path = "" + self.keyword_path = "" + self.nsrl_path = "" + self.build_path = "" + # test_case info + self.autopsy_version = "" + self.ingest_messages = 0 + self.indexed_files = 0 + self.indexed_chunks = 0 + # Infinite Testing info + timer = 0 + self.images = [] + # Set the timeout to something huge + # The entire tester should not timeout before this number in ms + # However it only seems to take about half this time + # And it's very buggy, so we're being careful + self.timeout = 24 * 60 * 60 * 1000 * 1000 + self.ant = [] + + # Temporary fix until error printing can be independent of TestData: + self.test_data = TestData() + + # Initialize Attributes + self._init_logs() + self._init_imgs() + + def get_image_name(self, image_file): + path_end = image_file.rfind("/") + path_end2 = image_file.rfind("\\") + ext_start = image_file.rfind(".") + if(ext_start == -1): + name = image_file + if(path_end2 != -1): + name = image_file[path_end2+1:ext_start] + elif(ext_start == -1): + name = image_file[path_end+1:] + elif(path_end == -1): + name = image_file[:ext_start] + elif(path_end!=-1 and ext_start!=-1): + name = image_file[path_end+1:ext_start] + else: + name = image_file[path_end2+1:ext_start] + return name + + def ant_to_string(self): + string = "" + for arg in self.ant: + string += (arg + " ") + return string + + def reset(self): + # Error tracking + self.printerror = [] + self.printout = [] + + # Set the timeout to something huge + # The entire tester should not timeout before this number in ms + # However it only seems to take about half this time + # And it's very buggy, so we're being careful + self.timeout = 24 * 60 * 60 * 1000 * 1000 + self.ant = [] + + def _init_imgs(self): + """ + Initialize the list of images to run test on. + """ + #Identify tests to run and populate test_case with list + # If user wants to do a single file and a list (contradictory?) + if self.args.single and self.args.list: + printerror(self.test_data, "Error: Cannot run both from config file and on a single file.") + return + # If working from a configuration file + if self.args.list: + if not Emailer.file_exists(self.args.config_file): + printerror(self.test_data, "Error: Configuration file does not exist at:") + printerror(self.test_data, self.args.config_file) + return + self._load_config_file(self.args.config_file) + # Else if working on a single file + elif self.args.single: + if not Emailer.file_exists(self.args.single_file): + printerror(self.test_data, "Error: Image file does not exist at:") + printerror(self.test_data, self.args.single_file) + return + test_case.images.append(self.args.single_file) + + # If user has not selected a single file, and does not want to ignore + # the input directory, continue on to parsing ../input + if (not self.args.single) and (not self.args.ignore) and (not self.args.list): + self.args.config_file = "config.xml" + if not Emailer.file_exists(self.args.config_file): + printerror(self.test_data, "Error: Configuration file does not exist at:") + printerror(self.test_data, self.args.config_file) + return + self._load_config_file(self.args.config_file) + + def _init_logs(self): + """ + Setup output folder, logs, and reporting infrastructure + """ + if(not Emailer.dir_exists(Emailer.make_path("..", "output", "results"))): + os.makedirs(Emailer.make_path("..", "output", "results",)) + self.output_dir = Emailer.make_path("..", "output", "results", time.strftime("%Y.%m.%d-%H.%M.%S")) + os.makedirs(self.output_dir) + self.csv = Emailer.make_local_path(self.output_dir, "CSV.txt") + self.html_log = Emailer.make_path(self.output_dir, "AutopsyTestCase.html") + log_name = self.output_dir + "\\regression.log" + logging.basicConfig(filename=log_name, level=logging.DEBUG) + + + # _load_config_file: ConfigFile + def _load_config_file(self, config_file): + """ + Initializes this TestConfiguration by iterating through the XML config file + command-line argument. Populates self.images and optional email configuration + """ + try: + global parsed + global errorem + global attachl + count = 0 + parsed = parse(config_file) + logres = [] + counts = {} + if parsed.getElementsByTagName("indir"): + self.input_dir = parsed.getElementsByTagName("indir")[0].getAttribute("value").encode().decode("utf_8") + if parsed.getElementsByTagName("global_csv"): + self.global_csv = parsed.getElementsByTagName("global_csv")[0].getAttribute("value").encode().decode("utf_8") + self.global_csv = Emailer.make_local_path(self.global_csv) + if parsed.getElementsByTagName("golddir"): + self.gold_parse = parsed.getElementsByTagName("golddir")[0].getAttribute("value").encode().decode("utf_8") + self.img_gold_parse = Emailer.make_path(self.gold_parse, 'tmp') + else: + self.gold_parse = self.gold + self.img_gold_parse = Emailer.make_path(self.gold_parse, 'tmp') + + # Generate the top navbar of the HTML for easy access to all images + images = [] + for element in parsed.getElementsByTagName("image"): + value = element.getAttribute("value").encode().decode("utf_8") + print ("Image in Config File: " + value) + if Emailer.file_exists(value): + self.images.append(value) + else: + printout(self.test_data, "File: " + value + " doesn't exist") + image_count = len(images) + + # Sanity check to see if there are obvious gold images that we are not testing + gold_count = 0 + for file in os.listdir(self.gold): + if not(file == 'tmp'): + gold_count+=1 + + if (image_count > gold_count): + print("******Alert: There are more input images than gold standards, some images will not be properly tested.\n") + elif (image_count < gold_count): + print("******Alert: There are more gold standards than input images, this will not check all gold Standards.\n") + + except Exception as e: + printerror(self.test_data, "Error: There was an error running with the configuration file.") + printerror(self.test_data, str(e) + "\n") + logging.critical(traceback.format_exc()) + print(traceback.format_exc()) + + +#---------------------------------------------------------# +# Contains methods to compare two databases and internally +# stores some of the results. # +#---------------------------------------------------------# +class DatabaseDiff: + def __init__(self, case): + self.gold_artifacts = [] + self.autopsy_artifacts = [] + self.gold_attributes = 0 + self.autopsy_attributes = 0 + self.gold_objects = 0 + self.autopsy_objects = 0 + self.artifact_comparison = [] + self.attribute_comparison = [] + self.test_data = case + + def clear(self): + self.gold_artifacts = [] + self.autopsy_artifacts = [] + self.gold_attributes = 0 + self.autopsy_attributes = 0 + self.gold_objects = 0 + self.autopsy_objects = 0 + self.artifact_comparison = [] + self.attribute_comparison = [] + + + + def get_artifacts_count(self): + total = 0 + for nums in self.autopsy_artifacts: + total += nums + return total + + def get_artifact_comparison(self): + if not self.artifact_comparison: + return "All counts matched" + else: + global failedbool + global errorem + failedbool = True + global imgfail + imgfail = True + return "; ".join(self.artifact_comparison) + + def get_attribute_comparison(self): + if not self.attribute_comparison: + return "All counts matched" + global failedbool + global errorem + failedbool = True + global imgfail + imgfail = True + list = [] + for error in self.attribute_comparison: + list.append(error) + return ";".join(list) + + def _count_output_artifacts(self): + if not self.autopsy_artifacts: + autopsy_db_file = Emailer.make_path(test_case.output_dir, self.test_data.image_name, + test_case.Img_Test_Folder, test_case.test_db_file) + autopsy_con = sqlite3.connect(autopsy_db_file) + autopsy_cur = autopsy_con.cursor() + autopsy_cur.execute("SELECT COUNT(*) FROM blackboard_artifact_types") + length = autopsy_cur.fetchone()[0] + 1 + for type_id in range(1, length): + autopsy_cur.execute("SELECT COUNT(*) FROM blackboard_artifacts WHERE artifact_type_id=%d" % type_id) + self.autopsy_artifacts.append(autopsy_cur.fetchone()[0]) + + def _count_output_attributes(self): + if self.autopsy_attributes == 0: + autopsy_db_file = Emailer.make_path(test_case.output_dir, self.test_data.image_name, + test_case.Img_Test_Folder, test_case.test_db_file) + autopsy_con = sqlite3.connect(autopsy_db_file) + autopsy_cur = autopsy_con.cursor() + autopsy_cur.execute("SELECT COUNT(*) FROM blackboard_attributes") + autopsy_attributes = autopsy_cur.fetchone()[0] + self.autopsy_attributes = autopsy_attributes + + # Counts number of objects and saves them into database. + # @@@ Does not need to connect again. Should be storing connection in DatabaseDiff + # See also for _generate_autopsy_attributes + def _count_output_objects(self): + if self.autopsy_objects == 0: + autopsy_db_file = Emailer.make_path(test_case.output_dir, self.test_data.image_name, + test_case.Img_Test_Folder, test_case.test_db_file) + autopsy_con = sqlite3.connect(autopsy_db_file) + autopsy_cur = autopsy_con.cursor() + autopsy_cur.execute("SELECT COUNT(*) FROM tsk_objects") + autopsy_objects = autopsy_cur.fetchone()[0] + self.autopsy_objects = autopsy_objects + + # @@@ see _generate_autopsy_objects comment about saving connections, etc. Or could have design where connection + # is passed in so that we do not need separate methods for gold and output. + def _count_gold_artifacts(self): + if not self.gold_artifacts: + gold_db_file = Emailer.make_path(test_case.img_gold, self.test_data.image_name, test_case.test_db_file) + if(not Emailer.file_exists(gold_db_file)): + gold_db_file = Emailer.make_path(test_case.img_gold_parse, self.test_data.image_name, test_case.test_db_file) + gold_con = sqlite3.connect(gold_db_file) + gold_cur = gold_con.cursor() + gold_cur.execute("SELECT COUNT(*) FROM blackboard_artifact_types") + length = gold_cur.fetchone()[0] + 1 + for type_id in range(1, length): + gold_cur.execute("SELECT COUNT(*) FROM blackboard_artifacts WHERE artifact_type_id=%d" % type_id) + self.gold_artifacts.append(gold_cur.fetchone()[0]) + gold_cur.execute("SELECT * FROM blackboard_artifacts") + self.gold_artifacts_list = [] + for row in gold_cur.fetchall(): + for item in row: + self.gold_artifacts_list.append(item) + + def _count_gold_attributes(self): + if self.gold_attributes == 0: + gold_db_file = Emailer.make_path(test_case.img_gold, self.test_data.image_name, test_case.test_db_file) + if(not Emailer.file_exists(gold_db_file)): + gold_db_file = Emailer.make_path(test_case.img_gold_parse, self.test_data.image_name, test_case.test_db_file) + gold_con = sqlite3.connect(gold_db_file) + gold_cur = gold_con.cursor() + gold_cur.execute("SELECT COUNT(*) FROM blackboard_attributes") + self.gold_attributes = gold_cur.fetchone()[0] + + def _count_gold_objects(self): + if self.gold_objects == 0: + gold_db_file = Emailer.make_path(test_case.img_gold, self.test_data.image_name, test_case.test_db_file) + if(not Emailer.file_exists(gold_db_file)): + gold_db_file = Emailer.make_path(test_case.img_gold_parse, self.test_data.image_name, test_case.test_db_file) + gold_con = sqlite3.connect(gold_db_file) + gold_cur = gold_con.cursor() + gold_cur.execute("SELECT COUNT(*) FROM tsk_objects") + self.gold_objects = gold_cur.fetchone()[0] + + # Compares the blackboard artifact counts of two databases + def _compare_bb_artifacts(self): + exceptions = [] + try: + global failedbool + global errorem + if self.gold_artifacts != self.autopsy_artifacts: + failedbool = True + global imgfail + imgfail = True + errorem += self.test_data.image + ":There was a difference in the number of artifacts.\n" + rner = len(self.gold_artifacts) + for type_id in range(1, rner): + if self.gold_artifacts[type_id] != self.autopsy_artifacts[type_id]: + error = str("Artifact counts do not match for type id %d. " % type_id) + error += str("Gold: %d, Test: %d" % + (self.gold_artifacts[type_id], + self.autopsy_artifacts[type_id])) + exceptions.append(error) + return exceptions + except Exception as e: + printerror(self.test_data, str(e)) + exceptions.append("Error: Unable to compare blackboard_artifacts.\n") + return exceptions + + # Compares the blackboard atribute counts of two databases + # given the two database cursors + def _compare_bb_attributes(self): + exceptions = [] + try: + if self.gold_attributes != self.autopsy_attributes: + error = "Attribute counts do not match. " + error += str("Gold: %d, Test: %d" % (self.gold_attributes, self.autopsy_attributes)) + exceptions.append(error) + global failedbool + global errorem + failedbool = True + global imgfail + imgfail = True + errorem += self.test_data.image + ":There was a difference in the number of attributes.\n" + return exceptions + except Exception as e: + exceptions.append("Error: Unable to compare blackboard_attributes.\n") + return exceptions + + # Compares the tsk object counts of two databases + # given the two database cursors + def _compare_tsk_objects(self): + exceptions = [] + try: + if self.gold_objects != self.autopsy_objects: + error = "TSK Object counts do not match. " + error += str("Gold: %d, Test: %d" % (self.gold_objects, self.autopsy_objects)) + exceptions.append(error) + global failedbool + global errorem + failedbool = True + global imgfail + imgfail = True + errorem += self.test_data.image + ":There was a difference between the tsk object counts.\n" + return exceptions + except Exception as e: + exceptions.append("Error: Unable to compare tsk_objects.\n") + return exceptions + + + # Basic test between output and gold databases. Compares only counts of objects and blackboard items + def compare_basic_counts(self): + # SQLITE needs unix style pathing + + # Get connection to output database from current run + autopsy_db_file = Emailer.make_path(test_case.output_dir, self.test_data.image_name, + test_case.Img_Test_Folder, test_case.test_db_file) + autopsy_con = sqlite3.connect(autopsy_db_file) + autopsy_cur = autopsy_con.cursor() + + # Get connection to gold DB and count artifacts, etc. + gold_db_file = Emailer.make_path(test_case.img_gold, self.test_data.image_name, test_case.test_db_file) + if(not Emailer.file_exists(gold_db_file)): + gold_db_file = Emailer.make_path(test_case.img_gold_parse, self.test_data.image_name, test_case.test_db_file) + try: + self._count_gold_objects() + self._count_gold_artifacts() + self._count_gold_attributes() + except Exception as e: + printerror(self.test_data, "Way out:" + str(e)) + + # This is where we return if a file doesn't exist, because we don't want to + # compare faulty databases, but we do however want to try to run all queries + # regardless of the other database + if not Emailer.file_exists(autopsy_db_file): + printerror(self.test_data, "Error: DatabaseDiff file does not exist at:") + printerror(self.test_data, autopsy_db_file + "\n") + return + if not Emailer.file_exists(gold_db_file): + printerror(self.test_data, "Error: Gold database file does not exist at:") + printerror(self.test_data, gold_db_file + "\n") + return + + # compare size of bb artifacts, attributes, and tsk objects + gold_con = sqlite3.connect(gold_db_file) + gold_cur = gold_con.cursor() + + exceptions = [] + + autopsy_db_file = Emailer.make_path(test_case.output_dir, self.test_data.image_name, + test_case.Img_Test_Folder, test_case.test_db_file) + # Connect again and count things + autopsy_con = sqlite3.connect(autopsy_db_file) + try: + self._count_output_objects() + self._count_output_artifacts() + self._count_output_attributes() + except Exception as e: + printerror(self.test_data, "Way out:" + str(e)) + + # Compare counts + exceptions.append(self._compare_tsk_objects()) + exceptions.append(self._compare_bb_artifacts()) + exceptions.append(self._compare_bb_attributes()) + + self.artifact_comparison = exceptions[1] + self.attribute_comparison = exceptions[2] + + okay = "All counts match." + print_report(self.test_data, exceptions[0], "COMPARE TSK OBJECTS", okay) + print_report(self.test_data, exceptions[1], "COMPARE ARTIFACTS", okay) + print_report(self.test_data, exceptions[2], "COMPARE ATTRIBUTES", okay) + + + + + + + + # smart method that deals with blackboard comparison to avoid issues with different IDs based on when artifacts were created. + # Dumps sorted text results to output location stored in test_data. + # autopsy_db_file: Output database file + def _dump_output_db_bb(autopsy_con, autopsy_db_file, test_data): + autopsy_cur2 = autopsy_con.cursor() + global errorem + global attachl + global failedbool + # Get the list of all artifacts + # @@@ Could add a SORT by parent_path in here since that is how we are going to later sort it. + autopsy_cur2.execute("SELECT tsk_files.parent_path, tsk_files.name, blackboard_artifact_types.display_name, blackboard_artifacts.artifact_id FROM blackboard_artifact_types INNER JOIN blackboard_artifacts ON blackboard_artifact_types.artifact_type_id = blackboard_artifacts.artifact_type_id INNER JOIN tsk_files ON tsk_files.obj_id = blackboard_artifacts.obj_id") + database_log = codecs.open(test_data.autopsy_data_file, "wb", "utf_8") + rw = autopsy_cur2.fetchone() + appnd = False + counter = 0 + # Cycle through artifacts + try: + while (rw != None): + # File Name and artifact type + if(rw[0] != None): + database_log.write(rw[0] + rw[1] + ' ') + else: + database_log.write(rw[1] + ' ') + + # Get attributes for this artifact + autopsy_cur1 = autopsy_con.cursor() + looptry = True + test_data.artifact_count += 1 + try: + key = "" + key = str(rw[3]) + key = key, + autopsy_cur1.execute("SELECT blackboard_attributes.source, blackboard_attribute_types.display_name, blackboard_attributes.value_type, blackboard_attributes.value_text, blackboard_attributes.value_int32, blackboard_attributes.value_int64, blackboard_attributes.value_double FROM blackboard_attributes INNER JOIN blackboard_attribute_types ON blackboard_attributes.attribute_type_id = blackboard_attribute_types.attribute_type_id WHERE artifact_id =? ORDER BY blackboard_attributes.source, blackboard_attribute_types.display_name, blackboard_attributes.value_type, blackboard_attributes.value_text, blackboard_attributes.value_int32, blackboard_attributes.value_int64, blackboard_attributes.value_double", key) + attributes = autopsy_cur1.fetchall() + except Exception as e: + printerror(test_data, str(e)) + printerror(test_data, str(rw[3])) + print(test_data.image_name) + errorem += test_data.image_name + ":Attributes in artifact id (in output DB)# " + str(rw[3]) + " encountered an error: " + str(e) +" .\n" + looptry = False + print(test_data.artifact_fail) + test_data.artifact_fail += 1 + print(test_data.artifact_fail) + database_log.write('Error Extracting Attributes'); + + # Print attributes + if(looptry == True): + src = attributes[0][0] + for attr in attributes: + val = 3 + attr[2] + numvals = 0 + for x in range(3, 6): + if(attr[x] != None): + numvals += 1 + if(numvals > 1): + errorem += test_data.image_name + ":There were too many values for attribute type: " + attr[1] + " for artifact with id #" + str(rw[3]) + ".\n" + printerror(test_data, "There were too many values for attribute type: " + attr[1] + " for artifact with id #" + str(rw[3]) + " for image " + test_data.image_name + ".") + failedbool = True + if(not appnd): + attachl.append(autopsy_db_file) + appnd = True + if(not attr[0] == src): + errorem += test_data.image_name + ":There were inconsistent sources for artifact with id #" + str(rw[3]) + ".\n" + printerror(test_data, "There were inconsistent sources for artifact with id #" + str(rw[3]) + " for image " + test_data.image_name + ".") + failedbool = True + if(not appnd): + attachl.append(autopsy_db_file) + appnd = True + try: + database_log.write('') + database_log.write(' \n') + rw = autopsy_cur2.fetchone() + + # Now sort the file + srtcmdlst = ["sort", test_data.autopsy_data_file, "-o", test_data.sorted_data_file] + subprocess.call(srtcmdlst) + print(test_data.artifact_fail) + if(test_data.artifact_fail > 0): + errorem += test_data.image_name + ":There were " + str(test_data.artifact_count) + " artifacts and " + str(test_data.artifact_fail) + " threw an exception while loading.\n" + except Exception as e: + printerror(test_data, 'outer exception: ' + str(e)) + + # Dumps a database (minus the artifact and attributes) to a text file. + def _dump_output_db_nonbb(test_data): + # Make a copy of the DB + autopsy_db_file = Emailer.make_path(test_case.output_dir, test_data.image_name, + test_case.Img_Test_Folder, test_case.test_db_file) + backup_db_file = Emailer.make_path(test_case.output_dir, test_data.image_name, + test_case.Img_Test_Folder, "autopsy_backup.db") + copy_file(autopsy_db_file,backup_db_file) + autopsy_con = sqlite3.connect(backup_db_file) + + # Delete the blackboard tables + autopsy_con.execute("DROP TABLE blackboard_artifacts") + autopsy_con.execute("DROP TABLE blackboard_attributes") + dump_file = Emailer.make_path(test_case.output_dir, test_data.image_name, test_data.image_name + "Dump.txt") + database_log = codecs.open(dump_file, "wb", "utf_8") + dump_list = autopsy_con.iterdump() + try: + for line in dump_list: + try: + database_log.write(line + "\n") + except Exception as e: + printerror(test_data, "dump_output_db_nonbb: Inner dump Exception:" + str(e)) + except Exception as e: + printerror(test_data, "dump_output_db_nonbb: Outer dump Exception:" + str(e)) + + + # Dumps the given database to text files for later comparison + def dump_output_db(test_data): + autopsy_db_file = Emailer.make_path(test_case.output_dir, test_data.image_name, + test_case.Img_Test_Folder, test_case.test_db_file) + autopsy_con = sqlite3.connect(autopsy_db_file) + autopsy_cur = autopsy_con.cursor() + # Try to query the databases. Ignore any exceptions, the function will + # return an error later on if these do fail + DatabaseDiff._dump_output_db_bb(autopsy_con,autopsy_db_file, test_data) + DatabaseDiff._dump_output_db_nonbb(test_data) + + + +#-------------------------------------------------# +# Functions relating to comparing outputs # +#-------------------------------------------------# +class TestDiffer: + + # Compares results for a single test. Autopsy has already been run. + # test_data: TestData object + # databaseDiff: DatabaseDiff object created based on test_data + def run_diff(test_data, databaseDiff): + try: + gold_path = test_case.gold + # Tmp location to extract ZIP file into + img_gold = Emailer.make_path(test_case.gold, "tmp", test_data.image_name) + + # Open gold archive file + img_archive = Emailer.make_path("..", "output", "gold", test_data.image_name+"-archive.zip") + if(not Emailer.file_exists(img_archive)): + img_archive = Emailer.make_path(test_case.gold_parse, test_data.image_name+"-archive.zip") + gold_path = test_case.gold_parse + img_gold = Emailer.make_path(gold_path, "tmp", test_data.image_name) + extrctr = zipfile.ZipFile(img_archive, 'r', compression=zipfile.ZIP_DEFLATED) + extrctr.extractall(gold_path) + extrctr.close + time.sleep(2) + + # Lists of tests to run + TestDiffer._compare_errors(test_data) + + # Compare database count to gold + databaseDiff.compare_basic_counts() + + # Compare smart blackboard results + TestDiffer._compare_text(test_data.sorted_data_file, "SortedData", test_data) + + # Compare the rest of the database (non-BB) + TestDiffer._compare_text(test_data.test_dbdump, "DBDump", test_data) + + # Compare html output + TestDiffer._compare_to_gold_html(test_data) + + # Clean up tmp folder + del_dir(img_gold) + + except Exception as e: + printerror(test_data, "Tests failed due to an error, try rebuilding or creating gold standards.\n") + printerror(test_data, str(e) + "\n") + print(traceback.format_exc()) + + + + # @@@ _compare_text could be made more generic with how it forms the paths (i.e. not add ".txt" in the method) and probably merged with + # compare_errors since they both do basic comparison of text files + + # Compares two text files + # output_file: output text file + # gold_file: gold text file + # test_data: Test being performed + def _compare_text(output_file, gold_file, test_data): + gold_dir = Emailer.make_path(test_case.img_gold, test_data.image_name, test_data.image_name + gold_file + ".txt") + if(not Emailer.file_exists(gold_dir)): + gold_dir = Emailer.make_path(test_case.img_gold_parse, test_data.image_name, test_data.image_name + gold_file + ".txt") + if(not Emailer.file_exists(output_file)): + return + srtd_data = codecs.open(output_file, "r", "utf_8") + gold_data = codecs.open(gold_dir, "r", "utf_8") + gold_dat = gold_data.read() + srtd_dat = srtd_data.read() + if (not(gold_dat == srtd_dat)): + diff_dir = Emailer.make_local_path(test_case.output_dir, test_data.image_name, test_data.image_name+gold_file+"-Diff.txt") + diff_file = codecs.open(diff_dir, "wb", "utf_8") + dffcmdlst = ["diff", test_data.sorted_data_file, gold_dir] + subprocess.call(dffcmdlst, stdout = diff_file) + global attachl + global errorem + global failedbool + attachl.append(diff_dir) + errorem += test_data.image_name + ":There was a difference in the database file " + gold_file + ".\n" + printerror(test_data, "There was a database difference for " + test_data.image_name + " versus " + gold_file + ".\n") + failedbool = True + global imgfail + imgfail = True + + # Compare merged error log files + def _compare_errors(test_data): + gold_dir = Emailer.make_path(test_case.img_gold, test_data.image_name, test_data.image_name + "SortedErrors.txt") + if(not Emailer.file_exists(gold_dir)): + gold_dir = Emailer.make_path(test_case.img_gold_parse, test_data.image_name, test_data.image_name + "SortedErrors.txt") + common_log = codecs.open(test_data.sorted_log, "r", "utf_8") + gold_log = codecs.open(gold_dir, "r", "utf_8") + gold_dat = gold_log.read() + common_dat = common_log.read() + patrn = re.compile("\d") + if (not((re.sub(patrn, 'd', gold_dat)) == (re.sub(patrn, 'd', common_dat)))): + diff_dir = Emailer.make_local_path(test_case.output_dir, test_data.image_name, test_data.image_name+"AutopsyErrors-Diff.txt") + diff_file = open(diff_dir, "w") + dffcmdlst = ["diff", test_data.sorted_log, gold_dir] + subprocess.call(dffcmdlst, stdout = diff_file) + global attachl + global errorem + global failedbool + attachl.append(test_data.sorted_log) + attachl.append(diff_dir) + errorem += test_data.image_name + ":There was a difference in the exceptions Log.\n" + printerror(test_data, "Exceptions didn't match.\n") + failedbool = True + global imgfail + imgfail = True + + # Compare the html report file made by + # the regression test against the gold standard html report + def _compare_to_gold_html(test_data): + gold_html_file = Emailer.make_path(test_case.img_gold, test_data.image_name, "Report", "index.html") + if(not Emailer.file_exists(gold_html_file)): + gold_html_file = Emailer.make_path(test_case.img_gold_parse, test_data.image_name, "Report", "index.html") + htmlfolder = "" + for fs in os.listdir(Emailer.make_path(test_case.output_dir, test_data.image_name, test_case.Img_Test_Folder, "Reports")): + if os.path.isdir(Emailer.make_path(test_case.output_dir, test_data.image_name, test_case.Img_Test_Folder, "Reports", fs)): + htmlfolder = fs + autopsy_html_path = Emailer.make_path(test_case.output_dir, test_data.image_name, test_case.Img_Test_Folder, "Reports", htmlfolder, "HTML Report") + + + try: + autopsy_html_file = get_file_in_dir(autopsy_html_path, "index.html") + if not Emailer.file_exists(gold_html_file): + printerror(test_data, "Error: No gold html report exists at:") + printerror(test_data, gold_html_file + "\n") + return + if not Emailer.file_exists(autopsy_html_file): + printerror(test_data, "Error: No test_case html report exists at:") + printerror(test_data, autopsy_html_file + "\n") + return + #Find all gold .html files belonging to this test_case + ListGoldHTML = [] + for fs in os.listdir(Emailer.make_path(test_case.output_dir, test_data.image_name, test_case.Img_Test_Folder, "Reports", htmlfolder)): + if(fs.endswith(".html")): + ListGoldHTML.append(Emailer.make_path(test_case.output_dir, test_data.image_name, test_case.Img_Test_Folder, "Reports", htmlfolder, fs)) + #Find all new .html files belonging to this test_case + ListNewHTML = [] + if(os.path.exists(Emailer.make_path(test_case.img_gold, test_data.image_name))): + for fs in os.listdir(Emailer.make_path(test_case.img_gold, test_data.image_name)): + if (fs.endswith(".html")): + ListNewHTML.append(Emailer.make_path(test_case.img_gold, test_data.image_name, fs)) + if(not test_case.img_gold_parse == "" or test_case.img_gold == test_case.img_gold_parse): + if(Emailer.file_exists(Emailer.make_path(test_case.img_gold_parse, test_data.image_name))): + for fs in os.listdir(Emailer.make_path(test_case.img_gold_parse,test_data.image_name)): + if (fs.endswith(".html")): + ListNewHTML.append(Emailer.make_path(test_case.img_gold_parse, test_data.image_name, fs)) + #ensure both reports have the same number of files and are in the same order + if(len(ListGoldHTML) != len(ListNewHTML)): + printerror(test_data, "The reports did not have the same number of files. One of the reports may have been corrupted") + else: + ListGoldHTML.sort() + ListNewHTML.sort() + + total = {"Gold": 0, "New": 0} + for x in range(0, len(ListGoldHTML)): + count = TestDiffer._compare_report_files(ListGoldHTML[x], ListNewHTML[x]) + total["Gold"]+=count[0] + total["New"]+=count[1] + okay = "The test report matches the gold report." + errors=["Gold report had " + str(total["Gold"]) +" errors", "New report had " + str(total["New"]) + " errors."] + print_report(test_data, errors, "REPORT COMPARISON", okay) + if total["Gold"] == total["New"]: + test_data.report_passed = True + else: + printerror(test_data, "The reports did not match each other.\n " + errors[0] +" and the " + errors[1]) + except FileNotFoundException as e: + e.print_error() + except DirNotFoundException as e: + e.print_error() + except Exception as e: + printerror(test_data, "Error: Unknown fatal error comparing reports.") + printerror(test_data, str(e) + "\n") + logging.critical(traceback.format_exc()) + + # Compares file a to file b and any differences are returned + # Only works with report html files, as it searches for the first
          + def _compare_report_files(a_path, b_path): + a_file = open(a_path) + b_file = open(b_path) + a = a_file.read() + b = b_file.read() + a = a[a.find("
            "):] + b = b[b.find("
              "):] + + a_list = TestDiffer._split(a, 50) + b_list = TestDiffer._split(b, 50) + if not len(a_list) == len(b_list): + ex = (len(a_list), len(b_list)) + return ex + else: + return (0, 0) + + # Split a string into an array of string of the given size + def _split(input, size): + return [input[start:start+size] for start in range(0, len(input), size)] + +class TestData: + def __init__(self): + self.image = "" + self.image_file = "" + self.image_name = "" + self.sorted_log = "" + self.warning_log = "" + self.autopsy_data_file = "" + self.sorted_data_file = "" + self.common_log_path = "" + self.antlog_dir = "" + self.test_dbdump = "" + self.total_test_time = "" + self.start_date = "" + self.end_date = "" + self.total_ingest_time = "" + self.artifact_count = 0 + self.artifact_fail = 0 + self.heap_space = "" + self.service_times = "" + self.report_passed = False + # Error tracking + self.printerror = [] + self.printout = [] + def reset(self): + self.image = "" + self.image_file = "" + self.image_name = "" + self.sorted_log = "" + self.warning_log = "" + self.autopsy_data_file = "" + self.sorted_data_file = "" + self.common_log_path = "" + self.antlog_dir = "" + self.test_dbdump = "" + self.total_test_time = "" + self.start_date = "" + self.end_date = "" + self.total_ingest_time = "" + self.artifact_count = 0 + self.artifact_fail = 0 + self.heap_space = "" + self.service_times = "" + # Error tracking + self.printerror = [] + self.printout = [] + +class Reports: + def generate_reports(csv_path, database, test_data): + Reports._generate_html(database, test_data) + if test_case.global_csv: + Reports._generate_csv(test_case.global_csv, database, test_data) + else: + Reports._generate_csv(csv_path, database, test_data) + + # Generates the HTML log file + def _generate_html(database, test_data): + # If the file doesn't exist yet, this is the first test_case to run for + # this test, so we need to make the start of the html log + global imgfail + if not Emailer.file_exists(test_case.html_log): + Reports.write_html_head() + try: + global html + html = open(test_case.html_log, "a") + # The image title + title = "

              " + test_data.image_name + " \ + tested on " + socket.gethostname() + "

              \ +

              \ + Errors and Warnings |\ + Information |\ + General Output |\ + Logs\ +

              " + # The script errors found + if imgfail: + ids = 'errors1' + else: + ids = 'errors' + errors = "
              \ +

              Errors and Warnings

              \ +
              " + # For each error we have logged in the test_case + for error in test_data.printerror: + # Replace < and > to avoid any html display errors + errors += "

              " + error.replace("<", "<").replace(">", ">") + "

              " + # If there is a \n, we probably want a
              in the html + if "\n" in error: + errors += "
              " + errors += "
              " + + # Links to the logs + logs = "
              \ +

              Logs

              \ +
              " + logs_path = Emailer.make_local_path(test_case.output_dir, test_data.image_name, "logs") + for file in os.listdir(logs_path): + logs += "

              " + file + "

              " + logs += "
              " + + # All the testing information + info = "
              \ +

              Information

              \ +
              \ + " + # The individual elements + info += "" + info += "" + info += "" + info += "" + info += "" + info += "" + info += "" + info += "" + info += "" + info += "" + info += "" + info += "" + info += "" + info += "" + info += "" + info += "" + info += "" + info += "" + info += "" + info += "" + info += "" + info += "" + info += "" + info += "" + info += "" + info += "" + info += "" + info += "" + info += "" + info += "" + info += "" + info += "" + info += "" + info += "" + info += "" + info += "" + info += "" + info += "" + info += "" + info += "" + info += "" + info += "" + info += "" + info += "" + info += "" + info += "" + info += "
              Image Path:" + test_data.image_file + "
              Image Name:" + test_data.image_name + "
              test_case Output Directory:" + test_case.output_dir + "
              Autopsy Version:" + test_case.autopsy_version + "
              Heap Space:" + test_data.heap_space + "
              Test Start Date:" + test_data.start_date + "
              Test End Date:" + test_data.end_date + "
              Total Test Time:" + test_data.total_test_time + "
              Total Ingest Time:" + test_data.total_ingest_time + "
              Exceptions Count:" + str(len(get_exceptions(test_data))) + "
              Autopsy OutOfMemoryExceptions:" + str(len(search_logs("OutOfMemoryException", test_data))) + "
              Autopsy OutOfMemoryErrors:" + str(len(search_logs("OutOfMemoryError", test_data))) + "
              Tika OutOfMemoryErrors/Exceptions:" + str(Reports._get_num_memory_errors("tika", test_data)) + "
              Solr OutOfMemoryErrors/Exceptions:" + str(Reports._get_num_memory_errors("solr", test_data)) + "
              TskCoreExceptions:" + str(len(search_log_set("autopsy", "TskCoreException", test_data))) + "
              TskDataExceptions:" + str(len(search_log_set("autopsy", "TskDataException", test_data))) + "
              Ingest Messages Count:" + str(test_case.ingest_messages) + "
              Indexed Files Count:" + str(test_case.indexed_files) + "
              Indexed File Chunks Count:" + str(test_case.indexed_chunks) + "
              Out Of Disk Space:\ +

              (will skew other test results)

              " + str(len(search_log_set("autopsy", "Stopping ingest due to low disk space on disk", test_data))) + "
              TSK Objects Count:" + str(database.autopsy_objects) + "
              Artifacts Count:" + str(database.get_artifacts_count()) + "
              Attributes Count:" + str(database.autopsy_attributes) + "
              \ +
              " + # For all the general print statements in the test_case + output = "
              \ +

              General Output

              \ +
              " + # For each printout in the test_case's list + for out in test_data.printout: + output += "

              " + out + "

              " + # If there was a \n it probably means we want a
              in the html + if "\n" in out: + output += "
              " + output += "
              " + + html.write(title) + html.write(errors) + html.write(info) + html.write(logs) + html.write(output) + html.close() + except Exception as e: + printerror(test_data, "Error: Unknown fatal error when creating HTML log at:") + printerror(test_data, test_case.html_log) + printerror(test_data, str(e) + "\n") + logging.critical(traceback.format_exc()) + + # Writed the top of the HTML log file + def write_html_head(): + print(test_case.html_log) + html = open(str(test_case.html_log), "a") + head = "\ + \ + AutopsyTesttest_case Output\ + \ + \ + " + html.write(head) + html.close() + + # Writed the bottom of the HTML log file + def write_html_foot(): + html = open(test_case.html_log, "a") + head = "" + html.write(head) + html.close() + + # Adds all the image names to the HTML log for easy access + def html_add_images(full_image_names): + # If the file doesn't exist yet, this is the first test_case to run for + # this test, so we need to make the start of the html log + if not Emailer.file_exists(test_case.html_log): + Reports.write_html_head() + html = open(test_case.html_log, "a") + links = [] + for full_name in full_image_names: + name = test_case.get_image_name(full_name) + links.append("" + name + "") + html.write("

              " + (" | ".join(links)) + "

              ") + + # Generate the CSV log file + def _generate_csv(csv_path, database, test_data): + try: + # If the CSV file hasn't already been generated, this is the + # first run, and we need to add the column names + if not Emailer.file_exists(csv_path): + Reports.csv_header(csv_path) + # Now add on the fields to a new row + csv = open(csv_path, "a") + + # Variables that need to be written + vars = [] + vars.append( test_data.image_file ) + vars.append( test_data.image_name ) + vars.append( test_case.output_dir ) + vars.append( socket.gethostname() ) + vars.append( test_case.autopsy_version ) + vars.append( test_data.heap_space ) + vars.append( test_data.start_date ) + vars.append( test_data.end_date ) + vars.append( test_data.total_test_time ) + vars.append( test_data.total_ingest_time ) + vars.append( test_data.service_times ) + vars.append( str(len(get_exceptions(test_data))) ) + vars.append( str(Reports._get_num_memory_errors("autopsy", test_data)) ) + vars.append( str(Reports._get_num_memory_errors("tika", test_data)) ) + vars.append( str(Reports._get_num_memory_errors("solr", test_data)) ) + vars.append( str(len(search_log_set("autopsy", "TskCoreException", test_data))) ) + vars.append( str(len(search_log_set("autopsy", "TskDataException", test_data))) ) + vars.append( str(test_case.ingest_messages) ) + vars.append( str(test_case.indexed_files) ) + vars.append( str(test_case.indexed_chunks) ) + vars.append( str(len(search_log_set("autopsy", "Stopping ingest due to low disk space on disk", test_data))) ) + vars.append( str(database.autopsy_objects) ) + vars.append( str(database.get_artifacts_count()) ) + vars.append( str(database.autopsy_attributes) ) + vars.append( Emailer.make_local_path("gold", test_data.image_name, test_case.test_db_file) ) + vars.append( database.get_artifact_comparison() ) + vars.append( database.get_attribute_comparison() ) + vars.append( Emailer.make_local_path("gold", test_data.image_name, "standard.html") ) + vars.append( str(test_data.report_passed) ) + vars.append( test_case.ant_to_string() ) + # Join it together with a ", " + output = "|".join(vars) + output += "\n" + # Write to the log! + csv.write(output) + csv.close() + except Exception as e: + printerror(test_data, "Error: Unknown fatal error when creating CSV file at:") + printerror(test_data, csv_path) + printerror(test_data, str(e) + "\n") + print(traceback.format_exc()) + logging.critical(traceback.format_exc()) + + # Generates the CSV header (column names) + def csv_header(csv_path): + csv = open(csv_path, "w") + titles = [] + titles.append("Image Path") + titles.append("Image Name") + titles.append("Output test_case Directory") + titles.append("Host Name") + titles.append("Autopsy Version") + titles.append("Heap Space Setting") + titles.append("Test Start Date") + titles.append("Test End Date") + titles.append("Total Test Time") + titles.append("Total Ingest Time") + titles.append("Service Times") + titles.append("Autopsy Exceptions") + titles.append("Autopsy OutOfMemoryErrors/Exceptions") + titles.append("Tika OutOfMemoryErrors/Exceptions") + titles.append("Solr OutOfMemoryErrors/Exceptions") + titles.append("TskCoreExceptions") + titles.append("TskDataExceptions") + titles.append("Ingest Messages Count") + titles.append("Indexed Files Count") + titles.append("Indexed File Chunks Count") + titles.append("Out Of Disk Space") + titles.append("Tsk Objects Count") + titles.append("Artifacts Count") + titles.append("Attributes Count") + titles.append("Gold Database Name") + titles.append("Artifacts Comparison") + titles.append("Attributes Comparison") + titles.append("Gold Report Name") + titles.append("Report Comparison") + titles.append("Ant Command Line") + output = "|".join(titles) + output += "\n" + csv.write(output) + csv.close() + + # Returns the number of OutOfMemoryErrors and OutOfMemoryExceptions + # for a certain type of log + def _get_num_memory_errors(type, test_data): + return (len(search_log_set(type, "OutOfMemoryError", test_data)) + + len(search_log_set(type, "OutOfMemoryException", test_data))) + +class Logs: + def generate_log_data(test_data): + Logs._generate_common_log(test_data) + try: + Logs._fill_test_case_data(test_data) + except Exception as e: + printerror(test_data, "Error: Unknown fatal error when filling test_case data.") + printerror(test_data, str(e) + "\n") + logging.critical(traceback.format_exc()) + # If running in verbose mode (-v) + if test_case.args.verbose: + errors = Logs._report_all_errors() + okay = "No warnings or errors in any log files." + print_report(test_data, errors, "VERBOSE", okay) + # Generate the "common log": a log of all exceptions and warnings + # from each log file generated by Autopsy + def _generate_common_log(test_data): + try: + logs_path = Emailer.make_local_path(test_case.output_dir, test_data.image_name, "logs") + common_log = codecs.open(test_case.common_log_path, "w", "utf_8") + warning_log = codecs.open(test_data.warning_log, "w", "utf_8") + common_log.write("--------------------------------------------------\n") + common_log.write(test_data.image_name + "\n") + common_log.write("--------------------------------------------------\n") + rep_path = Emailer.make_local_path(test_case.output_dir) + rep_path = rep_path.replace("\\\\", "\\") + for file in os.listdir(logs_path): + log = codecs.open(Emailer.make_path(logs_path, file), "r", "utf_8") + for line in log: + line = line.replace(rep_path, "test_data") + if line.startswith("Exception"): + common_log.write(file +": " + line) + elif line.startswith("Error"): + common_log.write(file +": " + line) + elif line.startswith("SEVERE"): + common_log.write(file +":" + line) + else: + warning_log.write(file +": " + line) + log.close() + common_log.write("\n") + common_log.close() + print(test_data.sorted_log) + srtcmdlst = ["sort", test_case.common_log_path, "-o", test_data.sorted_log] + subprocess.call(srtcmdlst) + except Exception as e: + printerror(test_data, "Error: Unable to generate the common log.") + printerror(test_data, str(e) + "\n") + printerror(test_data, traceback.format_exc()) + logging.critical(traceback.format_exc()) + + # Fill in the global test_case's variables that require the log files + def _fill_test_case_data(test_data): + try: + # Open autopsy.log.0 + log_path = Emailer.make_path(test_case.output_dir, test_data.image_name, "logs", "autopsy.log.0") + log = open(log_path) + + # Set the test_case starting time based off the first line of autopsy.log.0 + # *** If logging time format ever changes this will break *** + test_data.start_date = log.readline().split(" org.")[0] + + # Set the test_case ending time based off the "create" time (when the file was copied) + test_data.end_date = time.ctime(os.path.getmtime(log_path)) + except Exception as e: + printerror(test_data, "Error: Unable to open autopsy.log.0.") + printerror(test_data, str(e) + "\n") + logging.warning(traceback.format_exc()) + # Set the test_case total test time + # Start date must look like: "Jul 16, 2012 12:57:53 PM" + # End date must look like: "Mon Jul 16 13:02:42 2012" + # *** If logging time format ever changes this will break *** + start = datetime.datetime.strptime(test_data.start_date, "%b %d, %Y %I:%M:%S %p") + end = datetime.datetime.strptime(test_data.end_date, "%a %b %d %H:%M:%S %Y") + test_data.total_test_time = str(end - start) + + try: + # Set Autopsy version, heap space, ingest time, and service times + + version_line = search_logs("INFO: Application name: Autopsy, version:", test_data)[0] + test_case.autopsy_version = Emailer.get_word_at(version_line, 5).rstrip(",") + + test_data.heap_space = search_logs("Heap memory usage:", test_data)[0].rstrip().split(": ")[1] + + ingest_line = search_logs("Ingest (including enqueue)", test_data)[0] + test_data.total_ingest_time = Emailer.get_word_at(ingest_line, 6).rstrip() + + message_line = search_log_set("autopsy", "Ingest messages count:", test_data)[0] + test_case.ingest_messages = int(message_line.rstrip().split(": ")[2]) + + files_line = search_log_set("autopsy", "Indexed files count:", test_data)[0] + test_case.indexed_files = int(files_line.rstrip().split(": ")[2]) + + chunks_line = search_log_set("autopsy", "Indexed file chunks count:", test_data)[0] + test_case.indexed_chunks = int(chunks_line.rstrip().split(": ")[2]) + except Exception as e: + printerror(test_data, "Error: Unable to find the required information to fill test_case data.") + printerror(test_data, str(e) + "\n") + logging.critical(traceback.format_exc()) + print(traceback.format_exc()) + try: + service_lines = search_log("autopsy.log.0", "to process()", test_data) + service_list = [] + for line in service_lines: + words = line.split(" ") + # Kind of forcing our way into getting this data + # If this format changes, the tester will break + i = words.index("secs.") + times = words[i-4] + " " + times += words[i-3] + " " + times += words[i-2] + " " + times += words[i-1] + " " + times += words[i] + service_list.append(times) + test_data.service_times = "; ".join(service_list) + except Exception as e: + printerror(test_data, "Error: Unknown fatal error when finding service times.") + printerror(test_data, str(e) + "\n") + logging.critical(traceback.format_exc()) + + # Returns all the errors found in the common log in a list + def _report_all_errors(): + try: + return get_warnings() + get_exceptions() + except Exception as e: + printerror(test_data, "Error: Unknown fatal error when reporting all errors.") + printerror(test_data, str(e) + "\n") + logging.warning(traceback.format_exc()) + # Searches the common log for any instances of a specific string. + def search_common_log(string, test_data): + results = [] + log = codecs.open(test_case.common_log_path, "r", "utf_8") + for line in log: + if string in line: + results.append(line) + log.close() + return results + +# Returns the type of image file, based off extension +class IMGTYPE: + RAW, ENCASE, SPLIT, UNKNOWN = range(4) + +def image_type(image_file): + ext_start = image_file.rfind(".") + if (ext_start == -1): + return IMGTYPE.UNKNOWN + ext = image_file[ext_start:].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 + +# Search through all the known log files for a specific string. +# Returns a list of all lines with that string +def search_logs(string, test_data): + logs_path = Emailer.make_local_path(test_case.output_dir, test_data.image_name, "logs") + results = [] + for file in os.listdir(logs_path): + log = codecs.open(Emailer.make_path(logs_path, file), "r", "utf_8") + for line in log: + if string in line: + results.append(line) + log.close() + return results + +# Searches the given log for the given string +# Returns a list of all lines with that string +def search_log(log, string, test_data): + logs_path = Emailer.make_local_path(test_case.output_dir, test_data.image_name, "logs", log) + try: + results = [] + log = codecs.open(logs_path, "r", "utf_8") + for line in log: + if string in line: + results.append(line) + log.close() + if results: + return results + except: + raise FileNotFoundException(logs_path) + +# Search through all the the logs of the given type +# Types include autopsy, tika, and solr +def search_log_set(type, string, test_data): + logs_path = Emailer.make_local_path(test_case.output_dir, test_data.image_name, "logs") + results = [] + for file in os.listdir(logs_path): + if type in file: + log = codecs.open(Emailer.make_path(logs_path, file), "r", "utf_8") + for line in log: + if string in line: + results.append(line) + log.close() + return results + +# Print a report for the given errors with the report name as name +# and if no errors are found, print the okay message +def print_report(test_data, errors, name, okay): + if errors: + printerror(test_data, "--------< " + name + " >----------") + for error in errors: + printerror(test_data, str(error)) + printerror(test_data, "--------< / " + name + " >--------\n") + else: + printout(test_data, "-----------------------------------------------------------------") + printout(test_data, "< " + name + " - " + okay + " />") + printout(test_data, "-----------------------------------------------------------------\n") + +# Used instead of the print command when printing out an error +def printerror(test_data, string): + print(string) + test_data.printerror.append(string) + +# Used instead of the print command when printing out anything besides errors +def printout(test_data, string): + print(string) + test_data.printout.append(string) + +#----------------------------------# +# Helper functions # +#----------------------------------# +# Returns a list of all the exceptions listed in all the autopsy logs +def get_exceptions(test_data): + exceptions = [] + logs_path = Emailer.make_path(test_case.output_dir, test_data.image_name, "logs") + results = [] + for file in os.listdir(logs_path): + if "autopsy.log" in file: + log = codecs.open(Emailer.make_path(logs_path, file), "r", "utf_8") + ex = re.compile("\SException") + er = re.compile("\SError") + for line in log: + if ex.search(line) or er.search(line): + exceptions.append(line) + log.close() + return exceptions + +# Returns a list of all the warnings listed in the common log +def get_warnings(test_data): + warnings = [] + common_log = codecs.open(test_data.warning_log, "r", "utf_8") + for line in common_log: + if "warning" in line.lower(): + warnings.append(line) + common_log.close() + return warnings + +def copy_logs(test_data): + try: + log_dir = os.path.join("..", "..", "Testing","build","test","qa-functional","work","userdir0","var","log") + shutil.copytree(log_dir, Emailer.make_local_path(test_case.output_dir, test_data.image_name, "logs")) + except Exception as e: + printerror(test_data,"Error: Failed to copy the logs.") + printerror(test_data,str(e) + "\n") + logging.warning(traceback.format_exc()) +# Clears all the files from a directory and remakes it +def clear_dir(dir): + try: + if Emailer.dir_exists(dir): + shutil.rmtree(dir) + os.makedirs(dir) + return True; + except Exception as e: + printerror(test_data,"Error: Cannot clear the given directory:") + printerror(test_data,dir + "\n") + print(str(e)) + return False; + +def del_dir(dir): + try: + if Emailer.dir_exists(dir): + shutil.rmtree(dir) + return True; + except: + printerror(test_data,"Error: Cannot delete the given directory:") + printerror(test_data,dir + "\n") + return False; +#Copies a given file from "ffrom" to "to" +def copy_file(ffrom, to): + try : + shutil.copy(ffrom, to) + except Exception as e: + print(str(e)) + print(traceback.format_exc()) + +# Copies a directory file from "ffrom" to "to" +def copy_dir(ffrom, to): + try : + if not os.path.isdir(ffrom): + raise FileNotFoundException(ffrom) + shutil.copytree(ffrom, to) + except: + raise FileNotFoundException(to) +# Returns the first file in the given directory with the given extension +def get_file_in_dir(dir, ext): + try: + for file in os.listdir(dir): + if file.endswith(ext): + return Emailer.make_path(dir, file) + # If nothing has been found, raise an exception + raise FileNotFoundException(dir) + except: + raise DirNotFoundException(dir) + +def find_file_in_dir(dir, name, ext): + try: + for file in os.listdir(dir): + if file.startswith(name): + if file.endswith(ext): + return Emailer.make_path(dir, file) + raise FileNotFoundException(dir) + except: + raise DirNotFoundException(dir) + +def setDay(): + global Day + Day = int(strftime("%d", localtime())) + +def getLastDay(): + return Day + +def getDay(): + return int(strftime("%d", localtime())) + +def newDay(): + return getLastDay() != getDay() + +# Returns the args of the test script +def usage(): + return """ +Usage: ./regression.py [-f FILE] [OPTIONS] + + Run RegressionTest.java, and compare the result with a gold standard. + By default, the script tests every image in ../input + When the -f flag is set, this script only tests a single given image. + When the -l flag is set, the script looks for a configuration file, + which may outsource to a new input directory and to individual images. + + Expected files: + An NSRL database at: ../input/nsrl.txt-md5.idx + A notable hash database at: ../input/notablehashes.txt-md5.idx + A notable keyword file at: ../input/notablekeywords.xml + +Options: + -r Rebuild the gold standards for the image(s) tested. + -i Ignores the ../input directory and all files within it. + -u Tells Autopsy not to ingest unallocated space. + -k Keeps each image's Solr index instead of deleting it. + -v Verbose mode; prints all errors to the screen. + -e ex Prints out all errors containing ex. + -l cfg Runs from configuration file cfg. + -c Runs in a loop over the configuration file until canceled. Must be used in conjunction with -l + -fr Will not try download gold standard images + """ + +#------------------------------------------------------------# +# Exception classes to manage "acceptable" thrown exceptions # +# versus unexpected and fatal exceptions # +#------------------------------------------------------------# + +# If a file cannot be found by one of the helper functions +# they will throw a FileNotFoundException unless the purpose +# is to return False +class FileNotFoundException(Exception): + def __init__(self, file): + self.file = file + self.strerror = "FileNotFoundException: " + file + + def print_error(self): + printerror(test_data,"Error: File could not be found at:") + printerror(test_data,self.file + "\n") + def error(self): + error = "Error: File could not be found at:\n" + self.file + "\n" + return error + +# If a directory cannot be found by a helper function, +# it will throw this exception +class DirNotFoundException(Exception): + def __init__(self, dir): + self.dir = dir + self.strerror = "DirNotFoundException: " + dir + + def print_error(self): + printerror(test_data, "Error: Directory could not be found at:") + printerror(test_data, self.dir + "\n") + def error(self): + error = "Error: Directory could not be found at:\n" + self.dir + "\n" + return error + +############################# +# Main Testing Functions # +############################# +class Test_Runner: + + #Executes the tests, makes continuous testing easier + # Identifies the tests to run and runs the tests + def run_tests(): + global parsed + global errorem + global failedbool + global html + global attachl + + # TODO: Get test-data out of TestConfiguration. This is a temporary fix needed + # because printing error messages requires an instance of TestData + test_data = test_case.test_data + + Reports.html_add_images(test_case.images) + # Cycle through images in test_case and run tests + logres =[] + for img in test_case.images: + if Emailer.file_exists(img): + # Set the test_case to work for this test + test_data.image_file = str(img) + # @@@ This 0 should be be refactored out, but it will require rebuilding and changing of outputs. + test_data.image_name = test_case.get_image_name(test_data.image_file) + "(0)" + test_data.autopsy_data_file = Emailer.make_path(test_case.output_dir, test_data.image_name, test_data.image_name + "Autopsy_data.txt") + test_data.sorted_data_file = Emailer.make_path(test_case.output_dir, test_data.image_name, "Sorted_Autopsy_data.txt") + test_data.warning_log = Emailer.make_local_path(test_case.output_dir, test_data.image_name, "AutopsyLogs.txt") + test_data.antlog_dir = Emailer.make_local_path(test_case.output_dir, test_data.image_name, "antlog.txt") + test_data.test_dbdump = Emailer.make_path(test_case.output_dir, test_data.image_name, + test_data.image_name + "Dump.txt") + test_data.image = test_case.get_image_name(test_data.image_file) + + logres.append(Test_Runner._run_test(test_data)) + else: + printerror(test_data, "Warning: Image file listed in configuration does not exist:") + printerror(value + "\n") + test_data.reset() + Reports.write_html_foot() + html.close() + if (len(logres)>0): + failedbool = True + imgfail = True + passFail = False + for lm in logres: + for ln in lm: + errorem += ln + html.close() + if failedbool: + passFail = False + errorem += "The test output didn't match the gold standard.\n" + errorem += "Autopsy test failed.\n" + attachl.insert(0, html.name) + else: + errorem += "Autopsy test passed.\n" + passFail = True + attachl = [] + + # @@@ This fails here if we didn't parse an XML file + try: + Emailer.send_email(parsed, errorem, attachl, passFail) + except NameError: + printerror(test_data, "Could not send e-mail because of no XML file --maybe"); + + + # Run autopsy for a single test to generate output file and do comparison + # test_data: TestData object populated with locations and such for test + def _run_test(test_data): + global parsed + global imgfail + global failedbool + imgfail = False + if image_type(test_data.image_file) == IMGTYPE.UNKNOWN: + printerror(test_data, "Error: Image type is unrecognized:") + printerror(test_data, test_data.image_file + "\n") + return + + if(test_case.args.list): + element = parsed.getElementsByTagName("build") + if(len(element)<=0): + toval = Emailer.make_path("..", "build.xml") + else: + element = element[0] + toval = element.getAttribute("value").encode().decode("utf_8") + if(toval==None): + toval = Emailer.make_path("..", "build.xml") + else: + toval = Emailer.make_path("..", "build.xml") + test_case.build_path = toval + test_case.known_bad_path = Emailer.make_path(test_case.input_dir, "notablehashes.txt-md5.idx") + test_case.keyword_path = Emailer.make_path(test_case.input_dir, "notablekeywords.xml") + test_case.nsrl_path = Emailer.make_path(test_case.input_dir, "nsrl.txt-md5.idx") + logging.debug("--------------------") + logging.debug(test_data.image_name) + logging.debug("--------------------") + Test_Runner._run_ant(test_data) + time.sleep(2) # Give everything a second to process + + + # Autopsy has finished running, we will now process the results + test_case.common_log_path = Emailer.make_local_path(test_case.output_dir, test_data.image_name, test_data.image_name+test_case.common_log) + + # Dump the database before we diff or use it for rebuild + DatabaseDiff.dump_output_db(test_data) + + # merges logs into a single log for later diff / rebuild + copy_logs(test_data) + test_data.sorted_log = Emailer.make_local_path(test_case.output_dir, test_data.image_name, test_data.image_name + "SortedErrors.txt") + Logs.generate_log_data(test_data) + + # Look for core exceptions + # @@@ Should be moved to TestDiffer, but it didn't know about logres -- need to look into that + logres = Logs.search_common_log("TskCoreException", test_data) + + # Cleanup SOLR: If NOT keeping Solr index (-k) + if not test_case.args.keep: + solr_index = Emailer.make_path(test_case.output_dir, test_data.image_name, test_case.Img_Test_Folder, "ModuleOutput", "KeywordSearch") + if clear_dir(solr_index): + print_report(test_data, [], "DELETE SOLR INDEX", "Solr index deleted.") + elif test_case.args.keep: + print_report(test_data, [], "KEEP SOLR INDEX", "Solr index has been kept.") + + # If running in exception mode, print exceptions to log + if test_case.args.exception: + exceptions = search_logs(test_case.args.exception_string, test_data) + okay = "No warnings or exceptions found containing text '" + test_case.args.exception_string + "'." + print_report(test_data, exceptions, "EXCEPTION", okay) + + # @@@ We only need to create this here so that it can be passed into the + # Report because it stores results. Results should be stored somewhere else + # and then this can get pushed into only the diffing code. + databaseDiff = DatabaseDiff(test_data) + + # Now either diff or rebuild + if not test_case.args.rebuild: + TestDiffer.run_diff(test_data, databaseDiff) + # If running in rebuild mode (-r) + else: + Test_Runner.rebuild(test_data) + + # @@@ COnsider if we want to do this for a rebuild. + # Make the CSV log and the html log viewer + Reports.generate_reports(test_case.csv, databaseDiff, test_data) + # Reset the test_case and return the tests sucessfully finished + clear_dir(Emailer.make_path(test_case.output_dir, test_data.image_name, test_case.Img_Test_Folder, "ModuleOutput", "keywordsearch")) + if(failedbool): + attachl.append(test_case.common_log_path) + test_case.reset() + return logres + + + + # Rebuilds the gold standards by copying the test-generated database + # and html report files into the gold directory. Autopsy has already been run + def rebuild(test_data): + # Errors to print + errors = [] + if(test_case.gold_parse == "" ): + test_case.gold_parse = test_case.gold + test_case.img_gold_parse = test_case.img_gold + # Delete the current gold standards + gold_dir = test_case.img_gold_parse + clear_dir(test_case.img_gold_parse) + tmpdir = Emailer.make_path(gold_dir, test_data.image_name) + dbinpth = Emailer.make_path(test_case.output_dir, test_data.image_name, test_case.Img_Test_Folder, test_case.test_db_file) + dboutpth = Emailer.make_path(tmpdir, test_case.test_db_file) + dataoutpth = Emailer.make_path(tmpdir, test_data.image_name + "SortedData.txt") + dbdumpinpth = test_data.test_dbdump + dbdumpoutpth = Emailer.make_path(tmpdir, test_data.image_name + "DBDump.txt") + if not os.path.exists(test_case.img_gold_parse): + os.makedirs(test_case.img_gold_parse) + if not os.path.exists(gold_dir): + os.makedirs(gold_dir) + if not os.path.exists(tmpdir): + os.makedirs(tmpdir) + try: + copy_file(dbinpth, dboutpth) + if Emailer.file_exists(test_data.sorted_data_file): + copy_file(test_data.sorted_data_file, dataoutpth) + copy_file(dbdumpinpth, dbdumpoutpth) + error_pth = Emailer.make_path(tmpdir, test_data.image_name+"SortedErrors.txt") + copy_file(test_data.sorted_log, error_pth) + except Exception as e: + printerror(test_data, str(e)) + print(str(e)) + print(traceback.format_exc()) + # Rebuild the HTML report + htmlfolder = "" + for fs in os.listdir(os.path.join(os.getcwd(),test_case.output_dir, test_data.image_name, test_case.Img_Test_Folder, "Reports")): + if os.path.isdir(os.path.join(os.getcwd(), test_case.output_dir, test_data.image_name, test_case.Img_Test_Folder, "Reports", fs)): + htmlfolder = fs + autopsy_html_path = Emailer.make_local_path(test_case.output_dir, test_data.image_name, test_case.Img_Test_Folder, "Reports", htmlfolder) + + html_path = Emailer.make_path(test_case.output_dir, test_data.image_name, + test_case.Img_Test_Folder, "Reports") + try: + if not os.path.exists(Emailer.make_path(tmpdir, htmlfolder)): + os.makedirs(Emailer.make_path(tmpdir, htmlfolder)) + for file in os.listdir(autopsy_html_path): + html_to = Emailer.make_path(tmpdir, file.replace("HTML Report", "Report")) + copy_dir(get_file_in_dir(autopsy_html_path, file), html_to) + except FileNotFoundException as e: + errors.append(e.error) + except Exception as e: + errors.append("Error: Unknown fatal error when rebuilding the gold html report.") + errors.append(str(e) + "\n") + print(traceback.format_exc()) + oldcwd = os.getcwd() + zpdir = gold_dir + os.chdir(zpdir) + os.chdir("..") + img_gold = "tmp" + img_archive = Emailer.make_path(test_data.image_name+"-archive.zip") + comprssr = zipfile.ZipFile(img_archive, 'w',compression=zipfile.ZIP_DEFLATED) + Test_Runner.zipdir(img_gold, comprssr) + comprssr.close() + os.chdir(oldcwd) + del_dir(test_case.img_gold_parse) + okay = "Sucessfully rebuilt all gold standards." + print_report(test_data, errors, "REBUILDING", okay) + + def zipdir(path, zip): + for root, dirs, files in os.walk(path): + for file in files: + zip.write(os.path.join(root, file)) + + # Tests Autopsy with RegressionTest.java by by running + # the build.xml file through ant + def _run_ant(test_data): + # Set up the directories + test_case_path = os.path.join(test_case.output_dir, test_data.image_name) + if Emailer.dir_exists(test_case_path): + shutil.rmtree(test_case_path) + os.makedirs(test_case_path) + test_case.ant = ["ant"] + test_case.ant.append("-v") + test_case.ant.append("-f") + # case.ant.append(case.build_path) + test_case.ant.append(os.path.join("..","..","Testing","build.xml")) + test_case.ant.append("regression-test") + test_case.ant.append("-l") + test_case.ant.append(test_data.antlog_dir) + test_case.ant.append("-Dimg_path=" + test_data.image_file) + test_case.ant.append("-Dknown_bad_path=" + test_case.known_bad_path) + test_case.ant.append("-Dkeyword_path=" + test_case.keyword_path) + test_case.ant.append("-Dnsrl_path=" + test_case.nsrl_path) + test_case.ant.append("-Dgold_path=" + Emailer.make_path(test_case.gold)) + test_case.ant.append("-Dout_path=" + Emailer.make_local_path(test_case.output_dir, test_data.image_name)) + test_case.ant.append("-Dignore_unalloc=" + "%s" % test_case.args.unallocated) + test_case.ant.append("-Dtest.timeout=" + str(test_case.timeout)) + + printout(test_data, "Ingesting Image:\n" + test_data.image_file + "\n") + printout(test_data, "CMD: " + " ".join(test_case.ant)) + printout(test_data, "Starting test...\n") + antoutpth = Emailer.make_local_path(test_case.output_dir, "antRunOutput.txt") + antout = open(antoutpth, "a") + if SYS is OS.CYGWIN: + subprocess.call(test_case.ant, stdout=subprocess.PIPE) + elif SYS is OS.WIN: + theproc = subprocess.Popen(test_case.ant, shell = True, stdout=subprocess.PIPE) + theproc.communicate() + antout.close() + +#----------------------# +# Main # +#----------------------# +def main(): + # Global variables + global failedbool + global inform + global fl + global test_case + global errorem + global attachl + global daycount + global redo + global passed + daycount = 0 + failedbool = False + redo = False + errorem = "" + args = Args() + test_case = TestConfiguration(args) + attachl = [] + passed = False + # The arguments were given wrong: + if not args.parse(): + case.reset() + return + if(not args.fr): + antin = ["ant"] + antin.append("-f") + antin.append(os.path.join("..","..","build.xml")) + antin.append("test-download-imgs") + if SYS is OS.CYGWIN: + subprocess.call(antin) + elif SYS is OS.WIN: + theproc = subprocess.Popen(antin, shell = True, stdout=subprocess.PIPE) + theproc.communicate() + # Otherwise test away! + Test_Runner.run_tests() + +class OS: + LINUX, MAC, WIN, CYGWIN = range(4) +if __name__ == "__main__": + global SYS + if _platform == "linux" or _platform == "linux2": + SYS = OS.LINUX + elif _platform == "darwin": + SYS = OS.MAC + elif _platform == "win32": + SYS = OS.WIN + elif _platform == "cygwin": + SYS = OS.CYGWIN + + if SYS is OS.WIN or SYS is OS.CYGWIN: + main() + else: + print("We only support Windows and Cygwin at this time.") From 28bec75e38c9441ca3cb779787688c44ceab739e Mon Sep 17 00:00:00 2001 From: "U-BASIS\\jwallace" Date: Fri, 5 Jul 2013 12:32:40 -0400 Subject: [PATCH 02/43] Added information on Config Files to testing README --- test/README.txt | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/test/README.txt b/test/README.txt index 658a906181..d0064b4f95 100644 --- a/test/README.txt +++ b/test/README.txt @@ -1,8 +1,13 @@ -This folder contains the data and scripts required to run regression tests -for Autopsy. There is a 'Testing' folder in the root directory that contains -the Java code that drives Autopsy to perform the tests. - -To run these tests: -- You will need python3. We run this from within Cygwin. -- Download the input images by typing 'ant test-download-imgs' in the root Autopsy folder. This will place images in 'test/input'. -- Run 'regression.py' from inside of the 'test/scripts' folder. +This folder contains the data and scripts required to run regression tests +for Autopsy. There is a 'Testing' folder in the root directory that contains +the Java code that drives Autopsy to perform the tests. + +To run these tests: +- You will need python3. We run this from within Cygwin. +- Download the input images by typing 'ant test-download-imgs' in the root Autopsy folder. + This will place images in 'test/input'. +- Run 'python3 regression.py' from inside of the 'test/scripts' folder. +- Alternatively, run 'python3 regression.py -l [CONFIGFILE] to run the tests on a specified + list of images using a configuration file. See config.xml in the 'test/scripts' folder to + see configuration file formatting. +- Run 'python3 regression.py -h' to see other options. From d929a6793bb43794e5e19d7f3ef6c23b11b4ceae Mon Sep 17 00:00:00 2001 From: adam-m Date: Sat, 6 Jul 2013 11:36:31 -0400 Subject: [PATCH 03/43] adding local files: handle case when listFiles() returns null --- .../casemodule/services/FileManager.java | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/services/FileManager.java b/Core/src/org/sleuthkit/autopsy/casemodule/services/FileManager.java index a48c52168d..7caba45e39 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/services/FileManager.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/services/FileManager.java @@ -197,7 +197,8 @@ public class FileManager implements Closeable { } /** - * Interface for receiving notifications on folders being added via a callback + * Interface for receiving notifications on folders being added via a + * callback */ public interface FileAddProgressUpdater { @@ -304,12 +305,18 @@ public class FileManager implements Closeable { if (isDir) { //create virtual folder final VirtualDirectory childVd = tskCase.addVirtualDirectory(parentVd.getId(), childLocalFile.getName()); - if (childVd != null && addProgressUpdater != null ) { + if (childVd != null && addProgressUpdater != null) { addProgressUpdater.fileAdded(childVd); } //add children recursively - for (java.io.File childChild : childLocalFile.listFiles()) { - addLocalDirectoryRecInt(childVd, childChild, addProgressUpdater); + final java.io.File[] childrenFiles = childLocalFile.listFiles(); + if (childrenFiles != null) { + for (java.io.File childChild : childrenFiles) { + addLocalDirectoryRecInt(childVd, childChild, addProgressUpdater); + } + } else { + //add leaf file, base case + this.addLocalFileSingle(childLocalFile.getAbsolutePath(), parentVd); } } else { //add leaf file, base case @@ -428,7 +435,7 @@ public class FileManager implements Closeable { * closed * */ - private synchronized LocalFile addLocalFileSingle(String localAbsPath, AbstractFile parentFile ) throws TskCoreException { + private synchronized LocalFile addLocalFileSingle(String localAbsPath, AbstractFile parentFile) throws TskCoreException { if (tskCase == null) { throw new TskCoreException("Attempted to use FileManager after it was closed."); From 71d35554a14ec063f93175d16f865e31737dd4b1 Mon Sep 17 00:00:00 2001 From: Jeff Wallace Date: Tue, 9 Jul 2013 10:14:07 -0400 Subject: [PATCH 04/43] Fixed regression test script so the test configuration has access to the parsed command line arguments --- test/script/regression.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/test/script/regression.py b/test/script/regression.py index 1cd5d6c1da..e43bb12294 100644 --- a/test/script/regression.py +++ b/test/script/regression.py @@ -1946,12 +1946,13 @@ def main(): redo = False errorem = "" args = Args() + parse_result = args.parse() test_case = TestConfiguration(args) attachl = [] passed = False # The arguments were given wrong: - if not args.parse(): - case.reset() + if not parse_result: + test_case.reset() return if(not args.fr): antin = ["ant"] From 345cb2582f92629b7985056fa232836ebf8cb053 Mon Sep 17 00:00:00 2001 From: Jeff Wallace Date: Tue, 9 Jul 2013 11:22:05 -0400 Subject: [PATCH 05/43] TestConfiguration no longer relys on TestData to print errors --- test/script/regression.py | 45 +++++++++++++++++++++------------------ 1 file changed, 24 insertions(+), 21 deletions(-) diff --git a/test/script/regression.py b/test/script/regression.py index e43bb12294..6cc2665c39 100644 --- a/test/script/regression.py +++ b/test/script/regression.py @@ -200,9 +200,6 @@ class TestConfiguration: self.timeout = 24 * 60 * 60 * 1000 * 1000 self.ant = [] - # Temporary fix until error printing can be independent of TestData: - self.test_data = TestData() - # Initialize Attributes self._init_logs() self._init_imgs() @@ -232,10 +229,6 @@ class TestConfiguration: return string def reset(self): - # Error tracking - self.printerror = [] - self.printout = [] - # Set the timeout to something huge # The entire tester should not timeout before this number in ms # However it only seems to take about half this time @@ -250,20 +243,21 @@ class TestConfiguration: #Identify tests to run and populate test_case with list # If user wants to do a single file and a list (contradictory?) if self.args.single and self.args.list: - printerror(self.test_data, "Error: Cannot run both from config file and on a single file.") + msg = "Cannot run both from config file and on a single file." + self._print_error(msg) return # If working from a configuration file if self.args.list: if not Emailer.file_exists(self.args.config_file): - printerror(self.test_data, "Error: Configuration file does not exist at:") - printerror(self.test_data, self.args.config_file) + msg = "Configuration file does not exist at:" + self.args.config_file + self._print_error(msg) return self._load_config_file(self.args.config_file) # Else if working on a single file elif self.args.single: if not Emailer.file_exists(self.args.single_file): - printerror(self.test_data, "Error: Image file does not exist at:") - printerror(self.test_data, self.args.single_file) + msg = "Image file does not exist at: " + self.args.single_file + self._print_error(msg) return test_case.images.append(self.args.single_file) @@ -272,8 +266,8 @@ class TestConfiguration: if (not self.args.single) and (not self.args.ignore) and (not self.args.list): self.args.config_file = "config.xml" if not Emailer.file_exists(self.args.config_file): - printerror(self.test_data, "Error: Configuration file does not exist at:") - printerror(self.test_data, self.args.config_file) + msg = "Configuration file does not exist at: " + self.args.config_file + self._print_error(msg) return self._load_config_file(self.args.config_file) @@ -291,7 +285,7 @@ class TestConfiguration: logging.basicConfig(filename=log_name, level=logging.DEBUG) - # _load_config_file: ConfigFile + # ConfigFile -> void def _load_config_file(self, config_file): """ Initializes this TestConfiguration by iterating through the XML config file @@ -325,7 +319,8 @@ class TestConfiguration: if Emailer.file_exists(value): self.images.append(value) else: - printout(self.test_data, "File: " + value + " doesn't exist") + msg = "File: " + value + " doesn't exist" + self._print_error(msg) image_count = len(images) # Sanity check to see if there are obvious gold images that we are not testing @@ -340,11 +335,21 @@ class TestConfiguration: print("******Alert: There are more gold standards than input images, this will not check all gold Standards.\n") except Exception as e: - printerror(self.test_data, "Error: There was an error running with the configuration file.") - printerror(self.test_data, str(e) + "\n") + msg = "There was an error running with the configuration file.\n" + msg += "\t" + str(e) + self._print_error(msg) logging.critical(traceback.format_exc()) print(traceback.format_exc()) + # String -> void + def _print_error(self, msg): + """ + Append the given error message to the global error message and print the message to the screen. + """ + global errorem + error_msg = "Configuration: " + msg + print(error_msg) + errorem += error_msg + "\n" #---------------------------------------------------------# # Contains methods to compare two databases and internally @@ -1676,9 +1681,7 @@ class Test_Runner: global html global attachl - # TODO: Get test-data out of TestConfiguration. This is a temporary fix needed - # because printing error messages requires an instance of TestData - test_data = test_case.test_data + test_data = TestData() Reports.html_add_images(test_case.images) # Cycle through images in test_case and run tests From 92712af0ec315af3e5c3754964d569e5e7bdab0b Mon Sep 17 00:00:00 2001 From: Jeff Wallace Date: Tue, 9 Jul 2013 11:49:31 -0400 Subject: [PATCH 06/43] Created separate method to generate a list of TestData to be used by run_tests --- test/script/regression.py | 399 +++++++++++++++++++------------------- 1 file changed, 202 insertions(+), 197 deletions(-) diff --git a/test/script/regression.py b/test/script/regression.py index 6cc2665c39..638b0eee2c 100644 --- a/test/script/regression.py +++ b/test/script/regression.py @@ -60,7 +60,7 @@ import srcupdater # All variables that are needed throughout the script have been initialized # in a global class. # - Command line arguments are in Args (named args) -# - Global Test Configuration is in TestConfiguration(named test_case) +# - Global Test Configuration is in TestConfiguration(named test_config) # - Queried information from the databases is in DatabaseDiff (named database) # Feel free to add additional global classes or add to the existing ones, # but do not overwrite any existing variables as they are used frequently. @@ -185,7 +185,7 @@ class TestConfiguration: self.keyword_path = "" self.nsrl_path = "" self.build_path = "" - # test_case info + # test_config info self.autopsy_version = "" self.ingest_messages = 0 self.indexed_files = 0 @@ -240,7 +240,7 @@ class TestConfiguration: """ Initialize the list of images to run test on. """ - #Identify tests to run and populate test_case with list + #Identify tests to run and populate test_config with list # If user wants to do a single file and a list (contradictory?) if self.args.single and self.args.list: msg = "Cannot run both from config file and on a single file." @@ -259,7 +259,7 @@ class TestConfiguration: msg = "Image file does not exist at: " + self.args.single_file self._print_error(msg) return - test_case.images.append(self.args.single_file) + test_config.images.append(self.args.single_file) # If user has not selected a single file, and does not want to ignore # the input directory, continue on to parsing ../input @@ -411,8 +411,8 @@ class DatabaseDiff: def _count_output_artifacts(self): if not self.autopsy_artifacts: - autopsy_db_file = Emailer.make_path(test_case.output_dir, self.test_data.image_name, - test_case.Img_Test_Folder, test_case.test_db_file) + autopsy_db_file = Emailer.make_path(test_config.output_dir, self.test_data.image_name, + test_config.Img_Test_Folder, test_case.test_db_file) autopsy_con = sqlite3.connect(autopsy_db_file) autopsy_cur = autopsy_con.cursor() autopsy_cur.execute("SELECT COUNT(*) FROM blackboard_artifact_types") @@ -423,8 +423,8 @@ class DatabaseDiff: def _count_output_attributes(self): if self.autopsy_attributes == 0: - autopsy_db_file = Emailer.make_path(test_case.output_dir, self.test_data.image_name, - test_case.Img_Test_Folder, test_case.test_db_file) + autopsy_db_file = Emailer.make_path(test_config.output_dir, self.test_data.image_name, + test_config.Img_Test_Folder, test_case.test_db_file) autopsy_con = sqlite3.connect(autopsy_db_file) autopsy_cur = autopsy_con.cursor() autopsy_cur.execute("SELECT COUNT(*) FROM blackboard_attributes") @@ -436,8 +436,8 @@ class DatabaseDiff: # See also for _generate_autopsy_attributes def _count_output_objects(self): if self.autopsy_objects == 0: - autopsy_db_file = Emailer.make_path(test_case.output_dir, self.test_data.image_name, - test_case.Img_Test_Folder, test_case.test_db_file) + autopsy_db_file = Emailer.make_path(test_config.output_dir, self.test_data.image_name, + test_config.Img_Test_Folder, test_case.test_db_file) autopsy_con = sqlite3.connect(autopsy_db_file) autopsy_cur = autopsy_con.cursor() autopsy_cur.execute("SELECT COUNT(*) FROM tsk_objects") @@ -448,9 +448,9 @@ class DatabaseDiff: # is passed in so that we do not need separate methods for gold and output. def _count_gold_artifacts(self): if not self.gold_artifacts: - gold_db_file = Emailer.make_path(test_case.img_gold, self.test_data.image_name, test_case.test_db_file) + gold_db_file = Emailer.make_path(test_config.img_gold, self.test_data.image_name, test_case.test_db_file) if(not Emailer.file_exists(gold_db_file)): - gold_db_file = Emailer.make_path(test_case.img_gold_parse, self.test_data.image_name, test_case.test_db_file) + gold_db_file = Emailer.make_path(test_config.img_gold_parse, self.test_data.image_name, test_case.test_db_file) gold_con = sqlite3.connect(gold_db_file) gold_cur = gold_con.cursor() gold_cur.execute("SELECT COUNT(*) FROM blackboard_artifact_types") @@ -466,9 +466,9 @@ class DatabaseDiff: def _count_gold_attributes(self): if self.gold_attributes == 0: - gold_db_file = Emailer.make_path(test_case.img_gold, self.test_data.image_name, test_case.test_db_file) + gold_db_file = Emailer.make_path(test_config.img_gold, self.test_data.image_name, test_case.test_db_file) if(not Emailer.file_exists(gold_db_file)): - gold_db_file = Emailer.make_path(test_case.img_gold_parse, self.test_data.image_name, test_case.test_db_file) + gold_db_file = Emailer.make_path(test_config.img_gold_parse, self.test_data.image_name, test_case.test_db_file) gold_con = sqlite3.connect(gold_db_file) gold_cur = gold_con.cursor() gold_cur.execute("SELECT COUNT(*) FROM blackboard_attributes") @@ -476,9 +476,9 @@ class DatabaseDiff: def _count_gold_objects(self): if self.gold_objects == 0: - gold_db_file = Emailer.make_path(test_case.img_gold, self.test_data.image_name, test_case.test_db_file) + gold_db_file = Emailer.make_path(test_config.img_gold, self.test_data.image_name, test_case.test_db_file) if(not Emailer.file_exists(gold_db_file)): - gold_db_file = Emailer.make_path(test_case.img_gold_parse, self.test_data.image_name, test_case.test_db_file) + gold_db_file = Emailer.make_path(test_config.img_gold_parse, self.test_data.image_name, test_case.test_db_file) gold_con = sqlite3.connect(gold_db_file) gold_cur = gold_con.cursor() gold_cur.execute("SELECT COUNT(*) FROM tsk_objects") @@ -555,15 +555,15 @@ class DatabaseDiff: # SQLITE needs unix style pathing # Get connection to output database from current run - autopsy_db_file = Emailer.make_path(test_case.output_dir, self.test_data.image_name, - test_case.Img_Test_Folder, test_case.test_db_file) + autopsy_db_file = Emailer.make_path(test_config.output_dir, self.test_data.image_name, + test_config.Img_Test_Folder, test_case.test_db_file) autopsy_con = sqlite3.connect(autopsy_db_file) autopsy_cur = autopsy_con.cursor() # Get connection to gold DB and count artifacts, etc. - gold_db_file = Emailer.make_path(test_case.img_gold, self.test_data.image_name, test_case.test_db_file) + gold_db_file = Emailer.make_path(test_config.img_gold, self.test_data.image_name, test_case.test_db_file) if(not Emailer.file_exists(gold_db_file)): - gold_db_file = Emailer.make_path(test_case.img_gold_parse, self.test_data.image_name, test_case.test_db_file) + gold_db_file = Emailer.make_path(test_config.img_gold_parse, self.test_data.image_name, test_case.test_db_file) try: self._count_gold_objects() self._count_gold_artifacts() @@ -589,8 +589,8 @@ class DatabaseDiff: exceptions = [] - autopsy_db_file = Emailer.make_path(test_case.output_dir, self.test_data.image_name, - test_case.Img_Test_Folder, test_case.test_db_file) + autopsy_db_file = Emailer.make_path(test_config.output_dir, self.test_data.image_name, + test_config.Img_Test_Folder, test_case.test_db_file) # Connect again and count things autopsy_con = sqlite3.connect(autopsy_db_file) try: @@ -716,17 +716,17 @@ class DatabaseDiff: # Dumps a database (minus the artifact and attributes) to a text file. def _dump_output_db_nonbb(test_data): # Make a copy of the DB - autopsy_db_file = Emailer.make_path(test_case.output_dir, test_data.image_name, - test_case.Img_Test_Folder, test_case.test_db_file) - backup_db_file = Emailer.make_path(test_case.output_dir, test_data.image_name, - test_case.Img_Test_Folder, "autopsy_backup.db") + autopsy_db_file = Emailer.make_path(test_config.output_dir, test_data.image_name, + test_config.Img_Test_Folder, test_case.test_db_file) + backup_db_file = Emailer.make_path(test_config.output_dir, test_data.image_name, + test_config.Img_Test_Folder, "autopsy_backup.db") copy_file(autopsy_db_file,backup_db_file) autopsy_con = sqlite3.connect(backup_db_file) # Delete the blackboard tables autopsy_con.execute("DROP TABLE blackboard_artifacts") autopsy_con.execute("DROP TABLE blackboard_attributes") - dump_file = Emailer.make_path(test_case.output_dir, test_data.image_name, test_data.image_name + "Dump.txt") + dump_file = Emailer.make_path(test_config.output_dir, test_data.image_name, test_data.image_name + "Dump.txt") database_log = codecs.open(dump_file, "wb", "utf_8") dump_list = autopsy_con.iterdump() try: @@ -741,8 +741,8 @@ class DatabaseDiff: # Dumps the given database to text files for later comparison def dump_output_db(test_data): - autopsy_db_file = Emailer.make_path(test_case.output_dir, test_data.image_name, - test_case.Img_Test_Folder, test_case.test_db_file) + autopsy_db_file = Emailer.make_path(test_config.output_dir, test_data.image_name, + test_config.Img_Test_Folder, test_case.test_db_file) autopsy_con = sqlite3.connect(autopsy_db_file) autopsy_cur = autopsy_con.cursor() # Try to query the databases. Ignore any exceptions, the function will @@ -762,15 +762,15 @@ class TestDiffer: # databaseDiff: DatabaseDiff object created based on test_data def run_diff(test_data, databaseDiff): try: - gold_path = test_case.gold + gold_path = test_config.gold # Tmp location to extract ZIP file into - img_gold = Emailer.make_path(test_case.gold, "tmp", test_data.image_name) + img_gold = Emailer.make_path(test_config.gold, "tmp", test_data.image_name) # Open gold archive file img_archive = Emailer.make_path("..", "output", "gold", test_data.image_name+"-archive.zip") if(not Emailer.file_exists(img_archive)): - img_archive = Emailer.make_path(test_case.gold_parse, test_data.image_name+"-archive.zip") - gold_path = test_case.gold_parse + img_archive = Emailer.make_path(test_config.gold_parse, test_data.image_name+"-archive.zip") + gold_path = test_config.gold_parse img_gold = Emailer.make_path(gold_path, "tmp", test_data.image_name) extrctr = zipfile.ZipFile(img_archive, 'r', compression=zipfile.ZIP_DEFLATED) extrctr.extractall(gold_path) @@ -810,9 +810,9 @@ class TestDiffer: # gold_file: gold text file # test_data: Test being performed def _compare_text(output_file, gold_file, test_data): - gold_dir = Emailer.make_path(test_case.img_gold, test_data.image_name, test_data.image_name + gold_file + ".txt") + gold_dir = Emailer.make_path(test_config.img_gold, test_data.image_name, test_data.image_name + gold_file + ".txt") if(not Emailer.file_exists(gold_dir)): - gold_dir = Emailer.make_path(test_case.img_gold_parse, test_data.image_name, test_data.image_name + gold_file + ".txt") + gold_dir = Emailer.make_path(test_config.img_gold_parse, test_data.image_name, test_data.image_name + gold_file + ".txt") if(not Emailer.file_exists(output_file)): return srtd_data = codecs.open(output_file, "r", "utf_8") @@ -820,7 +820,7 @@ class TestDiffer: gold_dat = gold_data.read() srtd_dat = srtd_data.read() if (not(gold_dat == srtd_dat)): - diff_dir = Emailer.make_local_path(test_case.output_dir, test_data.image_name, test_data.image_name+gold_file+"-Diff.txt") + diff_dir = Emailer.make_local_path(test_config.output_dir, test_data.image_name, test_data.image_name+gold_file+"-Diff.txt") diff_file = codecs.open(diff_dir, "wb", "utf_8") dffcmdlst = ["diff", test_data.sorted_data_file, gold_dir] subprocess.call(dffcmdlst, stdout = diff_file) @@ -836,16 +836,16 @@ class TestDiffer: # Compare merged error log files def _compare_errors(test_data): - gold_dir = Emailer.make_path(test_case.img_gold, test_data.image_name, test_data.image_name + "SortedErrors.txt") + gold_dir = Emailer.make_path(test_config.img_gold, test_data.image_name, test_data.image_name + "SortedErrors.txt") if(not Emailer.file_exists(gold_dir)): - gold_dir = Emailer.make_path(test_case.img_gold_parse, test_data.image_name, test_data.image_name + "SortedErrors.txt") + gold_dir = Emailer.make_path(test_config.img_gold_parse, test_data.image_name, test_data.image_name + "SortedErrors.txt") common_log = codecs.open(test_data.sorted_log, "r", "utf_8") gold_log = codecs.open(gold_dir, "r", "utf_8") gold_dat = gold_log.read() common_dat = common_log.read() patrn = re.compile("\d") if (not((re.sub(patrn, 'd', gold_dat)) == (re.sub(patrn, 'd', common_dat)))): - diff_dir = Emailer.make_local_path(test_case.output_dir, test_data.image_name, test_data.image_name+"AutopsyErrors-Diff.txt") + diff_dir = Emailer.make_local_path(test_config.output_dir, test_data.image_name, test_data.image_name+"AutopsyErrors-Diff.txt") diff_file = open(diff_dir, "w") dffcmdlst = ["diff", test_data.sorted_log, gold_dir] subprocess.call(dffcmdlst, stdout = diff_file) @@ -863,14 +863,14 @@ class TestDiffer: # Compare the html report file made by # the regression test against the gold standard html report def _compare_to_gold_html(test_data): - gold_html_file = Emailer.make_path(test_case.img_gold, test_data.image_name, "Report", "index.html") + gold_html_file = Emailer.make_path(test_config.img_gold, test_data.image_name, "Report", "index.html") if(not Emailer.file_exists(gold_html_file)): - gold_html_file = Emailer.make_path(test_case.img_gold_parse, test_data.image_name, "Report", "index.html") + gold_html_file = Emailer.make_path(test_config.img_gold_parse, test_data.image_name, "Report", "index.html") htmlfolder = "" - for fs in os.listdir(Emailer.make_path(test_case.output_dir, test_data.image_name, test_case.Img_Test_Folder, "Reports")): - if os.path.isdir(Emailer.make_path(test_case.output_dir, test_data.image_name, test_case.Img_Test_Folder, "Reports", fs)): + for fs in os.listdir(Emailer.make_path(test_config.output_dir, test_data.image_name, test_case.Img_Test_Folder, "Reports")): + if os.path.isdir(Emailer.make_path(test_config.output_dir, test_data.image_name, test_case.Img_Test_Folder, "Reports", fs)): htmlfolder = fs - autopsy_html_path = Emailer.make_path(test_case.output_dir, test_data.image_name, test_case.Img_Test_Folder, "Reports", htmlfolder, "HTML Report") + autopsy_html_path = Emailer.make_path(test_config.output_dir, test_data.image_name, test_case.Img_Test_Folder, "Reports", htmlfolder, "HTML Report") try: @@ -880,25 +880,25 @@ class TestDiffer: printerror(test_data, gold_html_file + "\n") return if not Emailer.file_exists(autopsy_html_file): - printerror(test_data, "Error: No test_case html report exists at:") + printerror(test_data, "Error: No test_config html report exists at:") printerror(test_data, autopsy_html_file + "\n") return - #Find all gold .html files belonging to this test_case + #Find all gold .html files belonging to this test_config ListGoldHTML = [] - for fs in os.listdir(Emailer.make_path(test_case.output_dir, test_data.image_name, test_case.Img_Test_Folder, "Reports", htmlfolder)): + for fs in os.listdir(Emailer.make_path(test_config.output_dir, test_data.image_name, test_case.Img_Test_Folder, "Reports", htmlfolder)): if(fs.endswith(".html")): - ListGoldHTML.append(Emailer.make_path(test_case.output_dir, test_data.image_name, test_case.Img_Test_Folder, "Reports", htmlfolder, fs)) - #Find all new .html files belonging to this test_case + ListGoldHTML.append(Emailer.make_path(test_config.output_dir, test_data.image_name, test_case.Img_Test_Folder, "Reports", htmlfolder, fs)) + #Find all new .html files belonging to this test_config ListNewHTML = [] - if(os.path.exists(Emailer.make_path(test_case.img_gold, test_data.image_name))): - for fs in os.listdir(Emailer.make_path(test_case.img_gold, test_data.image_name)): + if(os.path.exists(Emailer.make_path(test_config.img_gold, test_data.image_name))): + for fs in os.listdir(Emailer.make_path(test_config.img_gold, test_data.image_name)): if (fs.endswith(".html")): - ListNewHTML.append(Emailer.make_path(test_case.img_gold, test_data.image_name, fs)) - if(not test_case.img_gold_parse == "" or test_case.img_gold == test_case.img_gold_parse): - if(Emailer.file_exists(Emailer.make_path(test_case.img_gold_parse, test_data.image_name))): - for fs in os.listdir(Emailer.make_path(test_case.img_gold_parse,test_data.image_name)): + ListNewHTML.append(Emailer.make_path(test_config.img_gold, test_data.image_name, fs)) + if(not test_config.img_gold_parse == "" or test_case.img_gold == test_case.img_gold_parse): + if(Emailer.file_exists(Emailer.make_path(test_config.img_gold_parse, test_data.image_name))): + for fs in os.listdir(Emailer.make_path(test_config.img_gold_parse,test_data.image_name)): if (fs.endswith(".html")): - ListNewHTML.append(Emailer.make_path(test_case.img_gold_parse, test_data.image_name, fs)) + ListNewHTML.append(Emailer.make_path(test_config.img_gold_parse, test_data.image_name, fs)) #ensure both reports have the same number of files and are in the same order if(len(ListGoldHTML) != len(ListNewHTML)): printerror(test_data, "The reports did not have the same number of files. One of the reports may have been corrupted") @@ -999,21 +999,21 @@ class TestData: class Reports: def generate_reports(csv_path, database, test_data): Reports._generate_html(database, test_data) - if test_case.global_csv: - Reports._generate_csv(test_case.global_csv, database, test_data) + if test_config.global_csv: + Reports._generate_csv(test_config.global_csv, database, test_data) else: Reports._generate_csv(csv_path, database, test_data) # Generates the HTML log file def _generate_html(database, test_data): - # If the file doesn't exist yet, this is the first test_case to run for + # If the file doesn't exist yet, this is the first test_config to run for # this test, so we need to make the start of the html log global imgfail - if not Emailer.file_exists(test_case.html_log): + if not Emailer.file_exists(test_config.html_log): Reports.write_html_head() try: global html - html = open(test_case.html_log, "a") + html = open(test_config.html_log, "a") # The image title title = "

              " + test_data.image_name + " \ tested on " + socket.gethostname() + "

              \ @@ -1031,7 +1031,7 @@ class Reports: errors = "
              \

              Errors and Warnings

              \
              " - # For each error we have logged in the test_case + # For each error we have logged in the test_config for error in test_data.printerror: # Replace < and > to avoid any html display errors errors += "

              " + error.replace("<", "<").replace(">", ">") + "

              " @@ -1044,7 +1044,7 @@ class Reports: logs = "
              \

              Logs

              \
              " - logs_path = Emailer.make_local_path(test_case.output_dir, test_data.image_name, "logs") + logs_path = Emailer.make_local_path(test_config.output_dir, test_data.image_name, "logs") for file in os.listdir(logs_path): logs += "

              " + file + "

              " logs += "
              " @@ -1059,10 +1059,10 @@ class Reports: info += "" + test_data.image_file + "" info += "Image Name:" info += "" + test_data.image_name + "" - info += "test_case Output Directory:" - info += "" + test_case.output_dir + "" + info += "test_config Output Directory:" + info += "" + test_config.output_dir + "" info += "Autopsy Version:" - info += "" + test_case.autopsy_version + "" + info += "" + test_config.autopsy_version + "" info += "Heap Space:" info += "" + test_data.heap_space + "" info += "Test Start Date:" @@ -1088,11 +1088,11 @@ class Reports: info += "TskDataExceptions:" info += "" + str(len(search_log_set("autopsy", "TskDataException", test_data))) + "" info += "Ingest Messages Count:" - info += "" + str(test_case.ingest_messages) + "" + info += "" + str(test_config.ingest_messages) + "" info += "Indexed Files Count:" - info += "" + str(test_case.indexed_files) + "" + info += "" + str(test_config.indexed_files) + "" info += "Indexed File Chunks Count:" - info += "" + str(test_case.indexed_chunks) + "" + info += "" + str(test_config.indexed_chunks) + "" info += "Out Of Disk Space:\

              (will skew other test results)

              " info += "" + str(len(search_log_set("autopsy", "Stopping ingest due to low disk space on disk", test_data))) + "" @@ -1104,11 +1104,11 @@ class Reports: info += "" + str(database.autopsy_attributes) + "" info += "\
              " - # For all the general print statements in the test_case + # For all the general print statements in the test_config output = "
              \

              General Output

              \
              " - # For each printout in the test_case's list + # For each printout in the test_config's list for out in test_data.printout: output += "

              " + out + "

              " # If there was a \n it probably means we want a
              in the html @@ -1124,17 +1124,17 @@ class Reports: html.close() except Exception as e: printerror(test_data, "Error: Unknown fatal error when creating HTML log at:") - printerror(test_data, test_case.html_log) + printerror(test_data, test_config.html_log) printerror(test_data, str(e) + "\n") logging.critical(traceback.format_exc()) # Writed the top of the HTML log file def write_html_head(): - print(test_case.html_log) - html = open(str(test_case.html_log), "a") + print(test_config.html_log) + html = open(str(test_config.html_log), "a") head = "\ \ - AutopsyTesttest_case Output\ + AutopsyTesttest_config Output\ \ "); + buffer.append(""); buffer.append(""); + + // artifact name header buffer.append("

              "); buffer.append(wrapped.getDisplayName()); buffer.append("

              "); + + // start table for attributes buffer.append(""); buffer.append(""); buffer.append(""); + + // cycle through each attribute and display in a row in the table. for (BlackboardAttribute attr : wrapped.getAttributes()) { + + // name column buffer.append(""); + + // value column buffer.append(""); - buffer.append(""); - buffer.append(""); - buffer.append(""); + buffer.append(""); buffer.append(""); buffer.append("" info += "" info += "" - info += "" + info += "" info += "" - info += "" + info += "" info += "" info += "" info += "" @@ -1364,11 +1365,11 @@ class Reports(object): info += "" info += "" info += "" - info += "" + info += "" info += "" - info += "" + info += "" info += "" - info += "" + info += "" info += "" info += "" @@ -1400,14 +1401,18 @@ class Reports(object): html.close() except Exception as e: Errors.print_error("Error: Unknown fatal error when creating HTML log at:") - Errors.print_error(test_config.html_log) + Errors.print_error(html_log) Errors.print_error(str(e) + "\n") logging.critical(traceback.format_exc()) - def write_html_head(): - """Write the top of the HTML log file.""" - print(test_config.html_log) - html = open(str(test_config.html_log), "a") + def write_html_head(html_log): + """Write the top of the HTML log file. + + Args: + html_log: a pathto_File, the global HTML log + """ + print(html_log) + html = open(str(html_log), "a") head = "\ \ AutopsyTesttest_config Output\ @@ -1431,24 +1436,29 @@ class Reports(object): html.write(head) html.close() - def write_html_foot(): - """Write the bottom of the HTML log file.""" - html = open(test_config.html_log, "a") + def write_html_foot(html_log): + """Write the bottom of the HTML log file. + + Args: + html_log: a pathto_File, the global HTML log + """ + html = open(html_log, "a") head = "" html.write(head) html.close() - def html_add_images(full_image_names): + def html_add_images(html_log, full_image_names): """Add all the image names to the HTML log. Args: full_image_names: a listof_String, each representing an image name + html_log: a pathto_File, the global HTML log """ # If the file doesn't exist yet, this is the first test_config to run for # this test, so we need to make the start of the html log - if not file_exists(test_config.html_log): - Reports.write_html_head() - html = open(test_config.html_log, "a") + if not file_exists(html_log): + Reports.write_html_head(html_log) + html = open(html_log, "a") links = [] for full_name in full_image_names: name = get_image_name(full_name) @@ -1470,9 +1480,9 @@ class Reports(object): vars = [] vars.append( test_data.image_file ) vars.append( test_data.image_name ) - vars.append( test_config.output_dir ) + vars.append( test_data.main_config.output_dir ) vars.append( socket.gethostname() ) - vars.append( test_config.autopsy_version ) + vars.append( test_data.autopsy_version ) vars.append( test_data.heap_space ) vars.append( test_data.start_date ) vars.append( test_data.end_date ) @@ -1485,9 +1495,9 @@ class Reports(object): vars.append( str(Reports._get_num_memory_errors("solr", test_data)) ) vars.append( str(len(search_log_set("autopsy", "TskCoreException", test_data))) ) vars.append( str(len(search_log_set("autopsy", "TskDataException", test_data))) ) - vars.append( str(test_config.ingest_messages) ) - vars.append( str(test_config.indexed_files) ) - vars.append( str(test_config.indexed_chunks) ) + vars.append( str(test_data.ingest_messages) ) + vars.append( str(test_data.indexed_files) ) + vars.append( str(test_data.indexed_chunks) ) vars.append( str(len(search_log_set("autopsy", "Stopping ingest due to low disk space on disk", test_data))) ) vars.append( str(test_data.db_diff_results.output_objs) ) vars.append( str(test_data.db_diff_results.output_artifacts) ) @@ -1497,7 +1507,7 @@ class Reports(object): vars.append( test_data.db_diff_results.get_attribute_comparison() ) vars.append( make_local_path("gold", test_data.image_name, "standard.html") ) vars.append( str(test_data.html_report_passed) ) - vars.append( test_config.ant_to_string() ) + vars.append( test_data.ant_to_string() ) # Join it together with a ", " output = "|".join(vars) output += "\n" @@ -1564,13 +1574,13 @@ class Logs(object): def generate_log_data(test_data): Logs._generate_common_log(test_data) try: - Logs._fill_test_config_data(test_data) + Logs._fill_ingest_data(test_data) except Exception as e: Errors.print_error("Error: Unknown fatal error when filling test_config data.") Errors.print_error(str(e) + "\n") logging.critical(traceback.format_exc()) # If running in verbose mode (-v) - if test_config.args.verbose: + if test_data.main_config.args.verbose: errors = Logs._report_all_errors() okay = "No warnings or errors in any log files." print_report(errors, "VERBOSE", okay) @@ -1584,7 +1594,7 @@ class Logs(object): common_log.write("--------------------------------------------------\n") common_log.write(test_data.image_name + "\n") common_log.write("--------------------------------------------------\n") - rep_path = make_local_path(test_config.output_dir) + rep_path = make_local_path(test_data.main_config.output_dir) rep_path = rep_path.replace("\\\\", "\\") for file in os.listdir(logs_path): log = codecs.open(make_path(logs_path, file), "r", "utf_8") @@ -1610,7 +1620,7 @@ class Logs(object): Errors.print_error(traceback.format_exc()) logging.critical(traceback.format_exc()) - def _fill_test_config_data(test_data): + def _fill_ingest_data(test_data): """Fill the global test config's variables that require the log files.""" try: # Open autopsy.log.0 @@ -1639,7 +1649,7 @@ class Logs(object): # Set Autopsy version, heap space, ingest time, and service times version_line = search_logs("INFO: Application name: Autopsy, version:", test_data)[0] - test_config.autopsy_version = get_word_at(version_line, 5).rstrip(",") + test_data.autopsy_version = get_word_at(version_line, 5).rstrip(",") test_data.heap_space = search_logs("Heap memory usage:", test_data)[0].rstrip().split(": ")[1] @@ -1647,13 +1657,13 @@ class Logs(object): test_data.total_ingest_time = get_word_at(ingest_line, 6).rstrip() message_line = search_log_set("autopsy", "Ingest messages count:", test_data)[0] - test_config.ingest_messages = int(message_line.rstrip().split(": ")[2]) + test_data.ingest_messages = int(message_line.rstrip().split(": ")[2]) files_line = search_log_set("autopsy", "Indexed files count:", test_data)[0] - test_config.indexed_files = int(files_line.rstrip().split(": ")[2]) + test_data.indexed_files = int(files_line.rstrip().split(": ")[2]) chunks_line = search_log_set("autopsy", "Indexed file chunks count:", test_data)[0] - test_config.indexed_chunks = int(chunks_line.rstrip().split(": ")[2]) + test_data.indexed_chunks = int(chunks_line.rstrip().split(": ")[2]) except Exception as e: Errors.print_error("Error: Unable to find the required information to fill test_config data.") Errors.print_error(str(e) + "\n") From f180665333fae941dd7b5cbb1765d7f36537a6e2 Mon Sep 17 00:00:00 2001 From: Jeff Wallace Date: Tue, 16 Jul 2013 15:18:03 -0400 Subject: [PATCH 42/43] Updated documentation of functions and classes. --- test/script/regression.py | 124 ++++++++++++++++++++++++++------------ 1 file changed, 85 insertions(+), 39 deletions(-) diff --git a/test/script/regression.py b/test/script/regression.py index 417a207b00..93b68457f6 100644 --- a/test/script/regression.py +++ b/test/script/regression.py @@ -57,13 +57,6 @@ from regression_utils import * # Variable, function, and class names are written in Python conventions: # this_is_a_variable this_is_a_function() ThisIsAClass # -# All variables that are needed throughout the script have been initialized -# in a global class. -# - Command line arguments are in Args (named args) -# - Global Test Configuration is in TestConfiguration(named test_config) -# - Queried information from the databases is in TskDbDiff (named database) -# Feel free to add additional global classes or add to the existing ones, -# but do not overwrite any existing variables as they are used frequently. # @@ -100,7 +93,7 @@ Day = 0 # Main # #----------------------# def main(): - # Global variables + """Parse the command-line arguments, create the configuration, and run the tests.""" args = Args() parse_result = args.parse() test_config = TestConfiguration(args) @@ -122,6 +115,7 @@ def main(): class TestRunner(object): + """A collection of functions to run the regression tests.""" def run_tests(test_config): """Run the tests specified by the main TestConfiguration. @@ -676,7 +670,7 @@ class TestConfiguration(object): self.build_path = build_path def _init_imgs(self, parsed_config): - """Initialize the list of images to run test on.""" + """Initialize the list of images to run tests on.""" for element in parsed_config.getElementsByTagName("image"): value = element.getAttribute("value").encode().decode("utf_8") print ("Image in Config File: " + value) @@ -742,7 +736,8 @@ class TskDbDiff(object): """Constructor for TskDbDiff. Args: - test_data: TestData - the test data to compare + output_db_path: a pathto_File, the output database + gold_db_path: a pathto_File, the gold database """ self.gold_artifacts = [] self.autopsy_artifacts = [] @@ -881,9 +876,6 @@ class TskDbDiff(object): Args: autopsy_cur: SQLCursor - the cursor for the output database gold_cur: SQLCursor - the cursor for the gold database - - Returns: - """ try: # Objects @@ -1139,7 +1131,7 @@ class TestResultsDiffer(object): # Compare html output gold_report_path = test_data.get_html_report_path(DBType.GOLD) output_report_path = test_data.get_html_report_path(DBType.OUTPUT) - passed = TestResultsDiffer._html_report_diff(test_data, gold_report_path, + passed = TestResultsDiffer._html_report_diff(gold_report_path, output_report_path) test_data.html_report_passed = passed @@ -1151,10 +1143,6 @@ class TestResultsDiffer(object): Errors.print_error(str(e) + "\n") print(traceback.format_exc()) - - # TODO: _compare_text could be made more generic with how it forms the paths (i.e. not add ".txt" in the method) and probably merged with - # compare_errors since they both do basic comparison of text files - def _compare_text(output_file, gold_file, process=None): """Compare two text files. @@ -1188,12 +1176,10 @@ class TestResultsDiffer(object): else: return True - # TODO: get rid of test_data by changing the error reporting - def _html_report_diff(test_data, gold_report_path, output_report_path): + def _html_report_diff(gold_report_path, output_report_path): """Compare the output and gold html reports. Args: - test_data: the TestData of the test being performed. gold_report_path: a pathto_Dir, the gold HTML report directory output_report_path: a pathto_Dir, the output HTML report directory @@ -1571,7 +1557,13 @@ class Reports(object): len(search_log_set(type, "OutOfMemoryException", test_data))) class Logs(object): + def generate_log_data(test_data): + """Find and handle relevent data from the Autopsy logs. + + Args: + test_data: the TestData whose logs to examine + """ Logs._generate_common_log(test_data) try: Logs._fill_ingest_data(test_data) @@ -1584,9 +1576,14 @@ class Logs(object): errors = Logs._report_all_errors() okay = "No warnings or errors in any log files." print_report(errors, "VERBOSE", okay) - # Generate the "common log": a log of all exceptions and warnings - # from each log file generated by Autopsy + def _generate_common_log(test_data): + """Generate the common log, the log of all exceptions and warnings from + each log file generated by Autopsy. + + Args: + test_data: the TestData to generate a log for + """ try: logs_path = test_data.logs_dir common_log = codecs.open(test_data.common_log_path, "w", "utf_8") @@ -1621,23 +1618,26 @@ class Logs(object): logging.critical(traceback.format_exc()) def _fill_ingest_data(test_data): - """Fill the global test config's variables that require the log files.""" + """Fill the TestDatas variables that require the log files. + + Args: + test_data: the TestData to modify + """ try: # Open autopsy.log.0 log_path = make_path(test_data.logs_dir, "autopsy.log.0") log = open(log_path) - # Set the test_config starting time based off the first line of autopsy.log.0 + # Set the TestData start time based off the first line of autopsy.log.0 # *** If logging time format ever changes this will break *** test_data.start_date = log.readline().split(" org.")[0] - # Set the test_config ending time based off the "create" time (when the file was copied) + # Set the test_data ending time based off the "create" time (when the file was copied) test_data.end_date = time.ctime(os.path.getmtime(log_path)) except Exception as e: Errors.print_error("Error: Unable to open autopsy.log.0.") Errors.print_error(str(e) + "\n") logging.warning(traceback.format_exc()) - # Set the test_config total test time # Start date must look like: "Jul 16, 2012 12:57:53 PM" # End date must look like: "Mon Jul 16 13:02:42 2012" # *** If logging time format ever changes this will break *** @@ -1743,6 +1743,8 @@ def print_report(errors, name, okay): def get_exceptions(test_data): """Get a list of the exceptions in the autopsy logs. + Args: + test_data: the TestData to use to find the exceptions. Returns: a listof_String, the exceptions found in the logs. """ @@ -1763,6 +1765,9 @@ def get_exceptions(test_data): def get_warnings(test_data): """Get a list of the warnings listed in the common log. + Args: + test_data: the TestData to use to find the warnings + Returns: listof_String, the warnings found. """ @@ -1775,6 +1780,11 @@ def get_warnings(test_data): return warnings def copy_logs(test_data): + """Copy the Autopsy generated logs to output directory. + + Args: + test_data: the TestData whose logs will be copied + """ try: log_dir = os.path.join("..", "..", "Testing","build","test","qa-functional","work","userdir0","var","log") shutil.copytree(log_dir, test_data.logs_dir) @@ -1812,8 +1822,9 @@ class FileNotFoundException(Exception): self.strerror = "FileNotFoundException: " + file def print_error(self): - printerror(test_data,"Error: File could not be found at:") - printerror(test_data,self.file + "\n") + Errors.print_error("Error: File could not be found at:") + Errors.print_error(self.file + "\n") + def error(self): error = "Error: File could not be found at:\n" + self.file + "\n" return error @@ -1830,6 +1841,7 @@ class DirNotFoundException(Exception): def print_error(self): Errors.print_error("Error: Directory could not be found at:") Errors.print_error(self.dir + "\n") + def error(self): error = "Error: Directory could not be found at:\n" + self.dir + "\n" return error @@ -1981,7 +1993,7 @@ class Args(object): self.fr = False def parse(self): - global nxtproc + """Get the command line arguments and parse them.""" nxtproc = [] nxtproc.append("python3") nxtproc.append(sys.argv.pop(0)) @@ -1990,11 +2002,11 @@ class Args(object): nxtproc.append(arg) if(arg == "-f"): #try: @@@ Commented out until a more specific except statement is added - arg = sys.argv.pop(0) - print("Running on a single file:") - print(path_fix(arg) + "\n") - self.single = True - self.single_file = path_fix(arg) + arg = sys.argv.pop(0) + print("Running on a single file:") + print(path_fix(arg) + "\n") + self.single = True + self.single_file = path_fix(arg) #except: # print("Error: No single file given.\n") # return False @@ -2145,7 +2157,11 @@ def search_log_set(type, string, test_data): def clear_dir(dir): - """Clears all files from a directory and remakes it.""" + """Clears all files from a directory and remakes it. + + Args: + dir: a pathto_Dir, the directory to clear + """ try: if dir_exists(dir): shutil.rmtree(dir) @@ -2158,6 +2174,11 @@ def clear_dir(dir): return False; def del_dir(dir): + """Delete the given directory. + + Args: + dir: a pathto_Dir, the directory to delete + """ try: if dir_exists(dir): shutil.rmtree(dir) @@ -2168,7 +2189,12 @@ def del_dir(dir): return False; def copy_file(ffrom, to): - """Copies a given file from "ffrom" to "to".""" + """Copies a given file from "ffrom" to "to". + + Args: + ffrom: a pathto_File, the origin path + to: a pathto_File, the destination path + """ try : shutil.copy(ffrom, to) except Exception as e: @@ -2176,7 +2202,12 @@ def copy_file(ffrom, to): print(traceback.format_exc()) def copy_dir(ffrom, to): - """Copies a directory file from "ffrom" to "to".""" + """Copies a directory file from "ffrom" to "to". + + Args: + ffrom: a pathto_Dir, the origin path + to: a pathto_Dir, the destination path + """ try : if not os.path.isdir(ffrom): raise FileNotFoundException(ffrom) @@ -2185,7 +2216,15 @@ def copy_dir(ffrom, to): raise FileNotFoundException(to) def get_file_in_dir(dir, ext): - """Returns the first file in the given directory with the given extension.""" + """Returns the first file in the given directory with the given extension. + + Args: + dir: a pathto_Dir, the directory to search + ext: a String, the extension to search for + + Returns: + pathto_File, the file that was found + """ try: for file in os.listdir(dir): if file.endswith(ext): @@ -2196,6 +2235,13 @@ def get_file_in_dir(dir, ext): raise DirNotFoundException(dir) def find_file_in_dir(dir, name, ext): + """Find the file with the given name in the given directory. + + Args: + dir: a pathto_Dir, the directory to search + name: a String, the basename of the file to search for + ext: a String, the extension of the file to search for + """ try: for file in os.listdir(dir): if file.startswith(name): From dc7fcb3790aaef65e809cf0c01fdba8aad3bb773 Mon Sep 17 00:00:00 2001 From: Jeff Wallace Date: Wed, 17 Jul 2013 11:39:55 -0400 Subject: [PATCH 43/43] Changed except statements to be more specific. Changed places where files are opened to use the safer with statement. --- test/script/regression.py | 361 ++++++++++++++++---------------------- 1 file changed, 147 insertions(+), 214 deletions(-) diff --git a/test/script/regression.py b/test/script/regression.py index 93b68457f6..6cf2172c4a 100644 --- a/test/script/regression.py +++ b/test/script/regression.py @@ -188,8 +188,13 @@ class TestRunner(object): TestRunner._run_ant(test_data) time.sleep(2) # Give everything a second to process - # Dump the database before we diff or use it for rebuild - TskDbDiff.dump_output_db(test_data) + try: + # Dump the database before we diff or use it for rebuild + TskDbDiff.dump_output_db(test_data) + except sqlite3.OperationalError as e: + print("Ingest did not run properly.", + "Make sure no other instances of Autopsy are open and try again.") + sys.exit() # merges logs into a single log for later diff / rebuild copy_logs(test_data) @@ -281,14 +286,15 @@ class TestRunner(object): if not os.path.exists(tmpdir): os.makedirs(tmpdir) try: - copy_file(dbinpth, dboutpth) + shutil.copy(dbinpth, dboutpth) if file_exists(test_data.get_sorted_data_path(DBType.OUTPUT)): - copy_file(test_data.get_sorted_data_path(DBType.OUTPUT), dataoutpth) - copy_file(dbdumpinpth, dbdumpoutpth) + shutil.copy(test_data.get_sorted_data_path(DBType.OUTPUT), dataoutpth) + shutil.copy(dbdumpinpth, dbdumpoutpth) error_pth = make_path(tmpdir, test_data.image_name+"SortedErrors.txt") - copy_file(test_data.sorted_log, error_pth) - except Exception as e: + shutil.copy(test_data.sorted_log, error_pth) + except IOError as e: Errors.print_error(str(e)) + Errors.add_email_message("Not rebuilt properly") print(str(e)) print(traceback.format_exc()) # Rebuild the HTML report @@ -296,8 +302,8 @@ class TestRunner(object): gold_html_report_dir = make_path(tmpdir, "Report") try: - copy_dir(output_html_report_dir, gold_html_report_dir) - except FileNotFoundException as e: + shutil.copytree(output_html_report_dir, gold_html_report_dir) + except OSError as e: errors.append(e.error()) except Exception as e: errors.append("Error: Unknown fatal error when rebuilding the gold html report.") @@ -631,20 +637,20 @@ class TestConfiguration(object): logres = [] counts = {} if parsed_config.getElementsByTagName("indir"): - self.input_dir = parsed.getElementsByTagName("indir")[0].getAttribute("value").encode().decode("utf_8") + self.input_dir = parsed_config.getElementsByTagName("indir")[0].getAttribute("value").encode().decode("utf_8") if parsed_config.getElementsByTagName("global_csv"): - self.global_csv = parsed.getElementsByTagName("global_csv")[0].getAttribute("value").encode().decode("utf_8") + self.global_csv = parsed_config.getElementsByTagName("global_csv")[0].getAttribute("value").encode().decode("utf_8") self.global_csv = make_local_path(self.global_csv) if parsed_config.getElementsByTagName("golddir"): - self.gold = parsed.getElementsByTagName("golddir")[0].getAttribute("value").encode().decode("utf_8") + self.gold = parsed_config.getElementsByTagName("golddir")[0].getAttribute("value").encode().decode("utf_8") self.img_gold = make_path(self.gold, 'tmp') self._init_imgs(parsed_config) self._init_build_info(parsed_config) self._init_email_info(parsed_config) - except Exception as e: - msg = "There was an error running with the configuration file.\n" + except IOError as e: + msg = "There was an error loading the configuration file.\n" msg += "\t" + str(e) Errors.add_email_msg(msg) logging.critical(traceback.format_exc()) @@ -800,10 +806,12 @@ class TskDbDiff(object): """ exceptions = [] passed = True - try: - if self.gold_artifacts != self.autopsy_artifacts: - msg = "There was a difference in the number of artifacts.\n" - Errors.add_email_msg(msg) + if self.gold_artifacts != self.autopsy_artifacts: + msg = "There was a difference in the number of artifacts.\n" + exceptions.append(msg) + Errors.add_email_msg(msg) + passed = False + else: rner = len(self.gold_artifacts) for type_id in range(1, rner): if self.gold_artifacts[type_id] != self.autopsy_artifacts[type_id]: @@ -813,13 +821,8 @@ class TskDbDiff(object): self.autopsy_artifacts[type_id])) exceptions.append(error) passed = False - self.report_errors.append(exceptions) - return passed - except Exception as e: - Errors.print_error(str(e)) - exceptions.append("Error: Unable to compare blackboard_artifacts.\n") - self.report_errors.append(exceptions) - return False + self.report_errors.append(exceptions) + return passed def _compare_bb_attributes(self): """Compares the blackboard attribute counts of two databases. @@ -832,39 +835,29 @@ class TskDbDiff(object): """ exceptions = [] passed = True - try: - if self.gold_attributes != self.autopsy_attributes: - error = "Attribute counts do not match. " - error += str("Gold: %d, Test: %d" % (self.gold_attributes, self.autopsy_attributes)) - exceptions.append(error) - msg = "There was a difference in the number of attributes.\n" - Errors.add_email_msg(msg) - passed = False - self.report_errors.append(exceptions) - return passed - except Exception as e: - exceptions.append("Error: Unable to compare blackboard_attributes.\n") - self.report_errors.append(exceptions) - return False + if self.gold_attributes != self.autopsy_attributes: + error = "Attribute counts do not match. " + error += str("Gold: %d, Test: %d" % (self.gold_attributes, self.autopsy_attributes)) + exceptions.append(error) + msg = "There was a difference in the number of attributes.\n" + Errors.add_email_msg(msg) + passed = False + self.report_errors.append(exceptions) + return passed def _compare_tsk_objects(self): """Compares the TSK object counts of two databases.""" exceptions = [] passed = True - try: - if self.gold_objects != self.autopsy_objects: - error = "TSK Object counts do not match. " - error += str("Gold: %d, Test: %d" % (self.gold_objects, self.autopsy_objects)) - exceptions.append(error) - msg ="There was a difference between the tsk object counts.\n" - Errors.add_email_msg(msg) - passed = False - self.report_errors.append(exceptions) - return passed - except Exception as e: - exceptions.append("Error: Unable to compare tsk_objects.\n") - self.report_errors.append(exceptions) - return False + if self.gold_objects != self.autopsy_objects: + error = "TSK Object counts do not match. " + error += str("Gold: %d, Test: %d" % (self.gold_objects, self.autopsy_objects)) + exceptions.append(error) + msg ="There was a difference between the tsk object counts.\n" + Errors.add_email_msg(msg) + passed = False + self.report_errors.append(exceptions) + return passed def _get_basic_counts(self, autopsy_cur, gold_cur): """Count the items necessary to compare the databases. @@ -887,25 +880,19 @@ class TskDbDiff(object): # Attributes self.gold_attributes = self._count_attributes(gold_cur) self.autopsy_attributes = self._count_attributes(autopsy_cur) - except Exception as e: - Errors.print_error("Way out:" + str(e)) + except sqlite3.Error as e: + Errors.print_error("Error while querying the databases:" + str(e)) def run_diff(self): """Basic test between output and gold databases. Compares only counts of objects and blackboard items. Note: SQLITE needs unix style pathing - """ - # Check to make sure both db files exist - if not file_exists(self.autopsy_db_file): - Errors.print_error("Error: TskDbDiff file does not exist at:") - Errors.print_error(self.autopsy_db_file + "\n") - return - if not file_exists(self.gold_db_file): - Errors.print_error("Error: Gold database file does not exist at:") - Errors.print_error(self.gold_db_file + "\n") - return + Raises: + sqlite3.OperationalError, if either of the database files do not + exist + """ # Get connections and cursors to output / gold databases autopsy_con = sqlite3.connect(self.autopsy_db_file) autopsy_cur = autopsy_con.cursor() @@ -977,7 +964,7 @@ class TskDbDiff(object): key = key, autopsy_cur1.execute("SELECT blackboard_attributes.source, blackboard_attribute_types.display_name, blackboard_attributes.value_type, blackboard_attributes.value_text, blackboard_attributes.value_int32, blackboard_attributes.value_int64, blackboard_attributes.value_double FROM blackboard_attributes INNER JOIN blackboard_attribute_types ON blackboard_attributes.attribute_type_id = blackboard_attribute_types.attribute_type_id WHERE artifact_id =? ORDER BY blackboard_attributes.source, blackboard_attribute_types.display_name, blackboard_attributes.value_type, blackboard_attributes.value_text, blackboard_attributes.value_int32, blackboard_attributes.value_int64, blackboard_attributes.value_double", key) attributes = autopsy_cur1.fetchall() - except Exception as e: + except sqlite3.Error as e: Errors.print_error(str(e)) Errors.print_error(str(rw[3])) msg ="Attributes in artifact id (in output DB)# " + str(rw[3]) + " encountered an error: " + str(e) +" .\n" @@ -1018,12 +1005,10 @@ class TskDbDiff(object): inpval = str(inpval) patrn = re.compile("[\n\0\a\b\r\f\e]") inpval = re.sub(patrn, ' ', inpval) - try: - database_log.write(inpval) - except Exception as e: - Errors.print_error("Inner exception" + outp) - except Exception as e: - Errors.print_error(str(e)) + database_log.write(inpval) + except IOError as e: + Errors.print_error(str(e)) + database_log.write('" />') database_log.write(' \n') rw = autopsy_cur2.fetchone() @@ -1049,23 +1034,17 @@ class TskDbDiff(object): # Make a copy of the DB autopsy_db_file = test_data.get_db_path(DBType.OUTPUT) backup_db_file = test_data.get_db_path(DBType.BACKUP) - copy_file(autopsy_db_file, backup_db_file) + shutil.copy(autopsy_db_file, backup_db_file) autopsy_con = sqlite3.connect(backup_db_file) # Delete the blackboard tables autopsy_con.execute("DROP TABLE blackboard_artifacts") autopsy_con.execute("DROP TABLE blackboard_attributes") - dump_file = test_data.test_dbdump - database_log = codecs.open(dump_file, "wb", "utf_8") - dump_list = autopsy_con.iterdump() - try: - for line in dump_list: - try: - database_log.write(line + "\n") - except Exception as e: - Errors.print_error("dump_output_db_nonbb: Inner dump Exception:" + str(e)) - except Exception as e: - Errors.print_error("dump_output_db_nonbb: Outer dump Exception:" + str(e)) + + # Write to the database dump + with codecs.open(test_data.test_dbdump, "wb", "utf_8") as db_log: + for line in autopsy_con.iterdump(): + db_log.write('%s\n' %line) def dump_output_db(test_data): @@ -1099,7 +1078,6 @@ class TestResultsDiffer(object): databaseDiff: TskDbDiff object created based off test_data """ try: - # Diff the gold and output databases output_db_path = test_data.get_db_path(DBType.OUTPUT) gold_db_path = test_data.get_db_path(DBType.GOLD) @@ -1138,6 +1116,9 @@ class TestResultsDiffer(object): # Clean up tmp folder del_dir(test_data.gold_data_dir) + except sqlite3.OperationalError as e: + Errors.print_error("Tests failed while running the diff:\n") + Errors.print_error(str(e)) except Exception as e: Errors.print_error("Tests failed due to an error, try rebuilding or creating gold standards.\n") Errors.print_error(str(e) + "\n") @@ -1214,7 +1195,7 @@ class TestResultsDiffer(object): else: Errors.print_error("The reports did not match each other.\n " + errors[0] +" and the " + errors[1]) return False - except DirNotFoundException as e: + except OSError as e: e.print_error() return False except Exception as e: @@ -1275,8 +1256,7 @@ class Reports(object): html_log = test_data.main_config.html_log if not file_exists(html_log): Reports.write_html_head() - try: - html = open(html_log, "a") + with open(html_log, "a") as html: # The image title title = "

              " + test_data.image_name + " \ tested on " + socket.gethostname() + "

              \ @@ -1384,12 +1364,6 @@ class Reports(object): html.write(info) html.write(logs) html.write(output) - html.close() - except Exception as e: - Errors.print_error("Error: Unknown fatal error when creating HTML log at:") - Errors.print_error(html_log) - Errors.print_error(str(e) + "\n") - logging.critical(traceback.format_exc()) def write_html_head(html_log): """Write the top of the HTML log file. @@ -1397,30 +1371,28 @@ class Reports(object): Args: html_log: a pathto_File, the global HTML log """ - print(html_log) - html = open(str(html_log), "a") - head = "\ - \ - AutopsyTesttest_config Output\ - \ - \ - " - html.write(head) - html.close() + with open(str(html_log), "a") as html: + head = "\ + \ + AutopsyTesttest_config Output\ + \ + \ + " + html.write(head) def write_html_foot(html_log): """Write the bottom of the HTML log file. @@ -1428,10 +1400,9 @@ class Reports(object): Args: html_log: a pathto_File, the global HTML log """ - html = open(html_log, "a") - head = "" - html.write(head) - html.close() + with open(html_log, "a") as html: + head = "" + html.write(head) def html_add_images(html_log, full_image_names): """Add all the image names to the HTML log. @@ -1444,24 +1415,21 @@ class Reports(object): # this test, so we need to make the start of the html log if not file_exists(html_log): Reports.write_html_head(html_log) - html = open(html_log, "a") - links = [] - for full_name in full_image_names: - name = get_image_name(full_name) - links.append("" + name + "") - html.write("

              " + (" | ".join(links)) + "

              ") - html.close() + with open(html_log, "a") as html: + links = [] + for full_name in full_image_names: + name = get_image_name(full_name) + links.append("" + name + "") + html.write("

              " + (" | ".join(links)) + "

              ") def _generate_csv(csv_path, test_data): """Generate the CSV log file""" - try: - # If the CSV file hasn't already been generated, this is the - # first run, and we need to add the column names - if not file_exists(csv_path): - Reports.csv_header(csv_path) - # Now add on the fields to a new row - csv = open(csv_path, "a") - + # If the CSV file hasn't already been generated, this is the + # first run, and we need to add the column names + if not file_exists(csv_path): + Reports.csv_header(csv_path) + # Now add on the fields to a new row + with open(csv_path, "a") as csv: # Variables that need to be written vars = [] vars.append( test_data.image_file ) @@ -1499,52 +1467,44 @@ class Reports(object): output += "\n" # Write to the log! csv.write(output) - csv.close() - except Exception as e: - Errors.print_error("Error: Unknown fatal error when creating CSV file at:") - Errors.print_error(csv_path) - Errors.print_error(str(e) + "\n") - print(traceback.format_exc()) - logging.critical(traceback.format_exc()) def csv_header(csv_path): """Generate the CSV column names.""" - csv = open(csv_path, "w") - titles = [] - titles.append("Image Path") - titles.append("Image Name") - titles.append("Output test_config Directory") - titles.append("Host Name") - titles.append("Autopsy Version") - titles.append("Heap Space Setting") - titles.append("Test Start Date") - titles.append("Test End Date") - titles.append("Total Test Time") - titles.append("Total Ingest Time") - titles.append("Service Times") - titles.append("Autopsy Exceptions") - titles.append("Autopsy OutOfMemoryErrors/Exceptions") - titles.append("Tika OutOfMemoryErrors/Exceptions") - titles.append("Solr OutOfMemoryErrors/Exceptions") - titles.append("TskCoreExceptions") - titles.append("TskDataExceptions") - titles.append("Ingest Messages Count") - titles.append("Indexed Files Count") - titles.append("Indexed File Chunks Count") - titles.append("Out Of Disk Space") - titles.append("Tsk Objects Count") - titles.append("Artifacts Count") - titles.append("Attributes Count") - titles.append("Gold Database Name") - titles.append("Artifacts Comparison") - titles.append("Attributes Comparison") - titles.append("Gold Report Name") - titles.append("Report Comparison") - titles.append("Ant Command Line") - output = "|".join(titles) - output += "\n" - csv.write(output) - csv.close() + with open(csv_path, "w") as csv: + titles = [] + titles.append("Image Path") + titles.append("Image Name") + titles.append("Output test_config Directory") + titles.append("Host Name") + titles.append("Autopsy Version") + titles.append("Heap Space Setting") + titles.append("Test Start Date") + titles.append("Test End Date") + titles.append("Total Test Time") + titles.append("Total Ingest Time") + titles.append("Service Times") + titles.append("Autopsy Exceptions") + titles.append("Autopsy OutOfMemoryErrors/Exceptions") + titles.append("Tika OutOfMemoryErrors/Exceptions") + titles.append("Solr OutOfMemoryErrors/Exceptions") + titles.append("TskCoreExceptions") + titles.append("TskDataExceptions") + titles.append("Ingest Messages Count") + titles.append("Indexed Files Count") + titles.append("Indexed File Chunks Count") + titles.append("Out Of Disk Space") + titles.append("Tsk Objects Count") + titles.append("Artifacts Count") + titles.append("Attributes Count") + titles.append("Gold Database Name") + titles.append("Artifacts Comparison") + titles.append("Attributes Comparison") + titles.append("Gold Report Name") + titles.append("Report Comparison") + titles.append("Ant Command Line") + output = "|".join(titles) + output += "\n" + csv.write(output) def _get_num_memory_errors(type, test_data): """Get the number of OutOfMemory errors and Exceptions. @@ -1611,7 +1571,7 @@ class Logs(object): print(test_data.sorted_log) srtcmdlst = ["sort", test_data.common_log_path, "-o", test_data.sorted_log] subprocess.call(srtcmdlst) - except Exception as e: + except (OSError, IOError) as e: Errors.print_error("Error: Unable to generate the common log.") Errors.print_error(str(e) + "\n") Errors.print_error(traceback.format_exc()) @@ -1634,7 +1594,7 @@ class Logs(object): # Set the test_data ending time based off the "create" time (when the file was copied) test_data.end_date = time.ctime(os.path.getmtime(log_path)) - except Exception as e: + except IOError as e: Errors.print_error("Error: Unable to open autopsy.log.0.") Errors.print_error(str(e) + "\n") logging.warning(traceback.format_exc()) @@ -1664,7 +1624,7 @@ class Logs(object): chunks_line = search_log_set("autopsy", "Indexed file chunks count:", test_data)[0] test_data.indexed_chunks = int(chunks_line.rstrip().split(": ")[2]) - except Exception as e: + except (OSError, IOError) as e: Errors.print_error("Error: Unable to find the required information to fill test_config data.") Errors.print_error(str(e) + "\n") logging.critical(traceback.format_exc()) @@ -1684,7 +1644,7 @@ class Logs(object): times += words[i] service_list.append(times) test_data.service_times = "; ".join(service_list) - except Exception as e: + except (OSError, IOError) as e: Errors.print_error("Error: Unknown fatal error when finding service times.") Errors.print_error(str(e) + "\n") logging.critical(traceback.format_exc()) @@ -1697,7 +1657,7 @@ class Logs(object): """ try: return get_warnings() + get_exceptions() - except Exception as e: + except (OSError, IOError) as e: Errors.print_error("Error: Unknown fatal error when reporting all errors.") Errors.print_error(str(e) + "\n") logging.warning(traceback.format_exc()) @@ -1788,7 +1748,7 @@ def copy_logs(test_data): try: log_dir = os.path.join("..", "..", "Testing","build","test","qa-functional","work","userdir0","var","log") shutil.copytree(log_dir, test_data.logs_dir) - except Exception as e: + except OSError as e: printerror(test_data,"Error: Failed to copy the logs.") printerror(test_data,str(e) + "\n") logging.warning(traceback.format_exc()) @@ -2167,7 +2127,7 @@ def clear_dir(dir): shutil.rmtree(dir) os.makedirs(dir) return True; - except Exception as e: + except OSError as e: printerror(test_data,"Error: Cannot clear the given directory:") printerror(test_data,dir + "\n") print(str(e)) @@ -2188,33 +2148,6 @@ def del_dir(dir): printerror(test_data,dir + "\n") return False; -def copy_file(ffrom, to): - """Copies a given file from "ffrom" to "to". - - Args: - ffrom: a pathto_File, the origin path - to: a pathto_File, the destination path - """ - try : - shutil.copy(ffrom, to) - except Exception as e: - print(str(e)) - print(traceback.format_exc()) - -def copy_dir(ffrom, to): - """Copies a directory file from "ffrom" to "to". - - Args: - ffrom: a pathto_Dir, the origin path - to: a pathto_Dir, the destination path - """ - try : - if not os.path.isdir(ffrom): - raise FileNotFoundException(ffrom) - shutil.copytree(ffrom, to) - except: - raise FileNotFoundException(to) - def get_file_in_dir(dir, ext): """Returns the first file in the given directory with the given extension.
              "); buffer.append(attr.getAttributeTypeDisplayName()); buffer.append(""); if (attr.getAttributeTypeID() == ATTRIBUTE_TYPE.TSK_DATETIME.getTypeID() || attr.getAttributeTypeID() == ATTRIBUTE_TYPE.TSK_DATETIME_ACCESSED.getTypeID()) { @@ -82,7 +92,12 @@ public class ArtifactStringContent implements StringContent { } else { switch (attr.getValueType()) { case STRING: - buffer.append(attr.getValueString()); + String str = attr.getValueString(); + str = str.replaceAll(" ", " "); + str = str.replaceAll("<", "<"); + str = str.replaceAll(">", ">"); + str = str.replaceAll("(\r\n|\n)", "
              "); + buffer.append(str); break; case INTEGER: buffer.append(attr.getValueInt()); @@ -113,16 +128,11 @@ public class ArtifactStringContent implements StringContent { try { path = content.getUniquePath(); } catch (TskCoreException ex) { - logger.log(Level.SEVERE, "Except while calling Content.getUniquePath() on " + content); + logger.log(Level.SEVERE, "Exception while calling Content.getUniquePath() on {0} : {1}", new Object[]{content, ex.getLocalizedMessage()}); } //add file path - buffer.append("
              Source File"); - buffer.append(content.getName()); - buffer.append("
              Source File Path"); diff --git a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/ExtractRegistry.java b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/ExtractRegistry.java index 1800f68d96..5643a8e0c1 100644 --- a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/ExtractRegistry.java +++ b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/ExtractRegistry.java @@ -191,7 +191,7 @@ public class ExtractRegistry extends Extract { if (s == null) { break; } - sb.append(s); + sb.append(s).append("\n"); } catch (IOException ex) { java.util.logging.Logger.getLogger(ExtractRegistry.class.getName()).log(Level.SEVERE, null, ex); break; From 65471d726a757a72cdfb897b94ed234518d34310 Mon Sep 17 00:00:00 2001 From: Jeff Wallace Date: Tue, 16 Jul 2013 14:20:14 -0400 Subject: [PATCH 41/43] Removed global variable test_config, updated appropriate references --- test/script/regression.py | 186 ++++++++++++++++++++------------------ 1 file changed, 98 insertions(+), 88 deletions(-) diff --git a/test/script/regression.py b/test/script/regression.py index 4a9e86ac20..417a207b00 100644 --- a/test/script/regression.py +++ b/test/script/regression.py @@ -101,7 +101,6 @@ Day = 0 #----------------------# def main(): # Global variables - global test_config args = Args() parse_result = args.parse() test_config = TestConfiguration(args) @@ -119,22 +118,20 @@ def main(): theproc = subprocess.Popen(antin, shell = True, stdout=subprocess.PIPE) theproc.communicate() # Otherwise test away! - TestRunner.run_tests() + TestRunner.run_tests(test_config) class TestRunner(object): - def run_tests(): + def run_tests(test_config): """Run the tests specified by the main TestConfiguration. Executes the AutopsyIngest for each image and dispatches the results based on the mode (rebuild or testing) """ - global html - test_data_list = [ TestData(image, test_config) for image in test_config.images ] - Reports.html_add_images(test_config.images) + Reports.html_add_images(test_config.html_log, test_config.images) logres =[] for test_data in test_data_list: @@ -154,7 +151,7 @@ class TestRunner(object): test_data.printout = Errors.printout test_data.printerror = Errors.printerror - Reports.write_html_foot() + Reports.write_html_foot(test_config.html_log) # TODO: move this elsewhere if (len(logres)>0): for lm in logres: @@ -228,9 +225,7 @@ class TestRunner(object): test_data.errors_diff_passed and test_data.sorted_data_passed and test_data.db_dump_passed and test_data.db_diff_results.passed) - # @@@ COnsider if we want to do this for a rebuild. - # Make the CSV log and the html log viewer - Reports.generate_reports(test_config.csv, test_data) + Reports.generate_reports(test_data) if(not test_data.overall_passed): Errors.add_email_attachment(test_data.common_log_path) return logres @@ -252,7 +247,7 @@ class TestRunner(object): Args: test_data: the TestData """ - if not test_config.args.keep: + if not test_data.main_config.args.keep: if clear_dir(test_data.solr_index): print_report([], "DELETE SOLR INDEX", "Solr index deleted.") else: @@ -264,9 +259,10 @@ class TestRunner(object): Args: test_data: the TestData """ - if test_config.args.exception: - exceptions = search_logs(test_config.args.exception_string, test_data) - okay = "No warnings or exceptions found containing text '" + test_config.args.exception_string + "'." + if test_data.main_config.args.exception: + exceptions = search_logs(test_data.main_config.args.exception_string, test_data) + okay = ("No warnings or exceptions found containing text '" + + test_data.main_config.args.exception_string + "'.") print_report(exceptions, "EXCEPTION", okay) def rebuild(test_data): @@ -274,6 +270,7 @@ class TestRunner(object): Copies the test-generated database and html report files into the gold directory. """ + test_config = test_data.main_config # Errors to print errors = [] # Delete the current gold standards @@ -339,38 +336,38 @@ class TestRunner(object): Args: test_data: the TestData """ + test_config = test_data.main_config # Set up the directories - test_config_path = os.path.join(test_config.output_dir, test_data.image_name) - if dir_exists(test_config_path): - shutil.rmtree(test_config_path) - os.makedirs(test_config_path) - test_config.ant = ["ant"] - test_config.ant.append("-v") - test_config.ant.append("-f") + if dir_exists(test_data.output_path): + shutil.rmtree(test_data.output_path) + os.makedirs(test_data.output_path) + test_data.ant = ["ant"] + test_data.ant.append("-v") + test_data.ant.append("-f") # case.ant.append(case.build_path) - test_config.ant.append(os.path.join("..","..","Testing","build.xml")) - test_config.ant.append("regression-test") - test_config.ant.append("-l") - test_config.ant.append(test_data.antlog_dir) - test_config.ant.append("-Dimg_path=" + test_data.image_file) - test_config.ant.append("-Dknown_bad_path=" + test_config.known_bad_path) - test_config.ant.append("-Dkeyword_path=" + test_config.keyword_path) - test_config.ant.append("-Dnsrl_path=" + test_config.nsrl_path) - test_config.ant.append("-Dgold_path=" + test_config.gold) - test_config.ant.append("-Dout_path=" + + test_data.ant.append(os.path.join("..","..","Testing","build.xml")) + test_data.ant.append("regression-test") + test_data.ant.append("-l") + test_data.ant.append(test_data.antlog_dir) + test_data.ant.append("-Dimg_path=" + test_data.image_file) + test_data.ant.append("-Dknown_bad_path=" + test_config.known_bad_path) + test_data.ant.append("-Dkeyword_path=" + test_config.keyword_path) + test_data.ant.append("-Dnsrl_path=" + test_config.nsrl_path) + test_data.ant.append("-Dgold_path=" + test_config.gold) + test_data.ant.append("-Dout_path=" + make_local_path(test_data.output_path)) - test_config.ant.append("-Dignore_unalloc=" + "%s" % test_config.args.unallocated) - test_config.ant.append("-Dtest.timeout=" + str(test_config.timeout)) + test_data.ant.append("-Dignore_unalloc=" + "%s" % test_config.args.unallocated) + test_data.ant.append("-Dtest.timeout=" + str(test_config.timeout)) Errors.print_out("Ingesting Image:\n" + test_data.image_file + "\n") - Errors.print_out("CMD: " + " ".join(test_config.ant)) + Errors.print_out("CMD: " + " ".join(test_data.ant)) Errors.print_out("Starting test...\n") - antoutpth = make_local_path(test_config.output_dir, "antRunOutput.txt") + antoutpth = make_local_path(test_data.main_config.output_dir, "antRunOutput.txt") antout = open(antoutpth, "a") if SYS is OS.CYGWIN: - subprocess.call(test_config.ant, stdout=subprocess.PIPE) + subprocess.call(test_data.ant, stdout=subprocess.PIPE) elif SYS is OS.WIN: - theproc = subprocess.Popen(test_config.ant, shell = True, stdout=subprocess.PIPE) + theproc = subprocess.Popen(test_data.ant, shell = True, stdout=subprocess.PIPE) theproc.communicate() antout.close() @@ -383,6 +380,7 @@ class TestData(object): Attributes: main_config: the global TestConfiguration + ant: a listof_String, the ant command for this TestData image_file: a pathto_Image, the image for this TestData image: a String, the image file's name image_name: a String, the image file's name with a trailing (0) @@ -411,6 +409,10 @@ class TestData(object): artifact_fail: a Nat, the number of artifact failures heap_space: a String representation of TODO service_times: a String representation of TODO + autopsy_version: a String, the version of autopsy that was run + ingest_messages: a Nat, the number of ingest messages + indexed_files: a Nat, the number of files indexed during the ingest + indexed_chunks: a Nat, the number of chunks indexed during the ingest printerror: a listof_String, the error messages printed during this TestData's test printout: a listof_String, the messages pritned during this TestData's test """ @@ -424,6 +426,7 @@ class TestData(object): """ # Configuration Data self.main_config = main_config + self.ant = [] self.image_file = str(image) # TODO: This 0 should be be refactored out, but it will require rebuilding and changing of outputs. self.image = get_image_name(self.image_file) @@ -451,6 +454,7 @@ class TestData(object): self.sorted_data_passed = False self.db_dump_passed = False self.overall_passed = False + # Ingest info self.total_test_time = "" self.start_date = "" self.end_date = "" @@ -459,11 +463,20 @@ class TestData(object): self.artifact_fail = 0 self.heap_space = "" self.service_times = "" - + self.autopsy_version = "" + self.ingest_messages = 0 + self.indexed_files = 0 + self.indexed_chunks = 0 # Error tracking self.printerror = [] self.printout = [] + def ant_to_string(self): + string = "" + for arg in self.ant: + string += (arg + " ") + return string + def get_db_path(self, db_type): """Get the path to the database file that corresponds to the given DBType. @@ -586,11 +599,6 @@ class TestConfiguration(object): self.keyword_path = make_path(self.input_dir, "notablekeywords.xml") self.nsrl_path = make_path(self.input_dir, "nsrl.txt-md5.idx") self.build_path = make_path("..", "build.xml") - # test_config info - self.autopsy_version = "" - self.ingest_messages = 0 - self.indexed_files = 0 - self.indexed_chunks = 0 # Infinite Testing info timer = 0 self.images = [] @@ -604,7 +612,6 @@ class TestConfiguration(object): # However it only seems to take about half this time # And it's very buggy, so we're being careful self.timeout = 24 * 60 * 60 * 1000 * 1000 - self.ant = [] if not self.args.single: self._load_config_file(self.args.config_file) @@ -614,11 +621,6 @@ class TestConfiguration(object): #self._init_imgs() #self._init_build_info() - def ant_to_string(self): - string = "" - for arg in self.ant: - string += (arg + " ") - return string def _load_config_file(self, config_file): """Updates this TestConfiguration's attributes from the config file. @@ -1268,28 +1270,27 @@ class TestResultsDiffer(object): class Reports(object): - def generate_reports(csv_path, test_data): + def generate_reports(test_data): """Generate the reports for a single test Args: - csv_path: a pathto_File, the csv file test_data: the TestData """ Reports._generate_html(test_data) - if test_config.global_csv: - Reports._generate_csv(test_config.global_csv, test_data) + if test_data.main_config.global_csv: + Reports._generate_csv(test_data.main_config.global_csv, test_data) else: - Reports._generate_csv(csv_path, test_data) + Reports._generate_csv(test_data.main_config.csv, test_data) def _generate_html(test_data): """Generate the HTML log file.""" # If the file doesn't exist yet, this is the first test_config to run for # this test, so we need to make the start of the html log - if not file_exists(test_config.html_log): + html_log = test_data.main_config.html_log + if not file_exists(html_log): Reports.write_html_head() try: - global html - html = open(test_config.html_log, "a") + html = open(html_log, "a") # The image title title = "

              " + test_data.image_name + " \ tested on " + socket.gethostname() + "

              \ @@ -1336,9 +1337,9 @@ class Reports(object): info += "
              Image Name:" + test_data.image_name + "
              test_config Output Directory:" + test_config.output_dir + "
              " + test_data.main_config.output_dir + "
              Autopsy Version:" + test_config.autopsy_version + "
              " + test_data.autopsy_version + "
              Heap Space:" + test_data.heap_space + "
              Test Start Date:
              TskDataExceptions:" + str(len(search_log_set("autopsy", "TskDataException", test_data))) + "
              Ingest Messages Count:" + str(test_config.ingest_messages) + "
              " + str(test_data.ingest_messages) + "
              Indexed Files Count:" + str(test_config.indexed_files) + "
              " + str(test_data.indexed_files) + "
              Indexed File Chunks Count:" + str(test_config.indexed_chunks) + "
              " + str(test_data.indexed_chunks) + "
              Out Of Disk Space:\

              (will skew other test results)

              " + str(len(search_log_set("autopsy", "Stopping ingest due to low disk space on disk", test_data))) + "