diff --git a/config/dpkg/changelog b/config/dpkg/changelog index c2957b2b09..fa0cd05cf0 100644 --- a/config/dpkg/changelog +++ b/config/dpkg/changelog @@ -2,4 +2,4 @@ python-plaso (1.2.1-1) unstable; urgency=low * Auto-generated - -- Log2Timeline Mon, 27 Apr 2015 06:27:27 +0200 + -- Log2Timeline Sat, 02 May 2015 10:45:35 +0200 diff --git a/plaso/__init__.py b/plaso/__init__.py index f13398d2f0..4a9f948c8f 100644 --- a/plaso/__init__.py +++ b/plaso/__init__.py @@ -3,7 +3,7 @@ __version__ = '1.2.1' VERSION_DEV = True -VERSION_DATE = '20150427' +VERSION_DATE = '20150502' def GetVersion(): diff --git a/plaso/cli/analysis_tool.py b/plaso/cli/analysis_tool.py index 6d863fecd7..34432bbeee 100644 --- a/plaso/cli/analysis_tool.py +++ b/plaso/cli/analysis_tool.py @@ -25,6 +25,23 @@ def __init__(self, input_reader=None, output_writer=None): input_reader=input_reader, output_writer=output_writer) self._storage_file_path = None + def _ParseStorageFileOptions(self, options): + """Parses the storage file options. + + Args: + options: the command line arguments (instance of argparse.Namespace). + + Raises: + BadConfigOption: if the options are invalid. + """ + self._storage_file_path = getattr(options, u'storage_file', None) + if not self._storage_file_path: + raise errors.BadConfigOption(u'Missing storage file option.') + + if not os.path.isfile(self._storage_file_path): + raise errors.BadConfigOption( + u'No such storage file: {0:s}.'.format(self._storage_file_path)) + def AddStorageFileOptions(self, argument_group): """Adds the storage file options to the argument group. @@ -47,11 +64,4 @@ def ParseOptions(self, options): BadConfigOption: if the options are invalid. """ super(AnalysisTool, self).ParseOptions(options) - - self._storage_file_path = getattr(options, u'storage_file', None) - if not self._storage_file_path: - raise errors.BadConfigOption(u'Missing storage file option.') - - if not os.path.isfile(self._storage_file_path): - raise errors.BadConfigOption( - u'No such storage file {0:s}.'.format(self._storage_file_path)) + self._ParseStorageFileOptions(options) diff --git a/plaso/cli/tools.py b/plaso/cli/tools.py index bc2802f1f8..b2e819060c 100644 --- a/plaso/cli/tools.py +++ b/plaso/cli/tools.py @@ -62,7 +62,7 @@ def AddBasicOptions(self, argument_group): u'Show this help message and exit.')) argument_group.add_argument( - u'-v', u'--version', dest=u'version', action=u'version', + u'-V', u'--version', dest=u'version', action=u'version', version=version_string, help=u'Show the current version.') def ParseOptions(self, unused_options): diff --git a/plaso/lib/storage.py b/plaso/lib/storage.py index c8780f861f..c03a8e788a 100644 --- a/plaso/lib/storage.py +++ b/plaso/lib/storage.py @@ -1328,6 +1328,9 @@ def StoreReport(self, analysis_report): def GetReports(self): """Read in all stored analysis reports from storage and yield them. + Yields: + Analysis reports (instances of AnalysisReport). + Raises: IOError: if the stream cannot be opened. """ diff --git a/test_data/pinfo_test.out b/test_data/pinfo_test.out new file mode 100644 index 0000000000..2fb7d08ab1 Binary files /dev/null and b/test_data/pinfo_test.out differ diff --git a/tools/pinfo.py b/tools/pinfo.py index 31ce20d1c0..c52412eb9f 100755 --- a/tools/pinfo.py +++ b/tools/pinfo.py @@ -7,6 +7,7 @@ import argparse import logging +import os import pprint import sys @@ -39,15 +40,203 @@ def __init__(self, input_reader=None, output_writer=None): """ super(PinfoTool, self).__init__( input_reader=input_reader, output_writer=output_writer) + self._compare_storage_file_path = None + + # TODO: clean up arguments after front-end refactor. + self._front_end = analysis_frontend.AnalysisFrontend(None, None) + self._verbose = False + self.compare_storage_information = False - def _AddCollectionInformation(self, lines_of_text, collection_information): - """Adds the lines of text that make up the collection information. + def _CompareInformationDict( + self, identifier, storage_information, compare_storage_information, + ignore_values=None): + """Compares the information dictionaries. + + Args: + identifier: The identifier of the dictionary to compare. + storage_information: The storage information object (instance of + PreprocessObject). + compare_storage_information: The storage information object (instance of + PreprocessObject) to compare against. + ignore_values: optional list of value indentifier to ignore. The default + is None. + + Returns: + A boolean value indicating if the information dictionaries are identical + or not. + """ + information = getattr(storage_information, identifier, None) + compare_information = getattr(compare_storage_information, identifier, None) + + if not information and not compare_information: + return True + + # Determine the union of the keys. + if not information: + keys = set(compare_information.keys()) + elif not compare_information: + keys = set(information.keys()) + else: + keys = set(information.keys()) | set(compare_information.keys()) + + result = True + for key in keys: + if ignore_values and key in ignore_values: + continue + + description = u'{0:s}.{1:s}'.format(identifier, key) + if not self._CompareInformationValue( + key, description, information, compare_information): + result = False + + return result + + def _CompareInformationValue( + self, identifier, description, information, compare_information): + """Compares the information values. + + Args: + identifier: The identifier of the value to compare. + description: Human readable description of the value. + information: The information dictionary. + compare_information: The information dictionary to compare against. + + Returns: + A boolean value indicating if the information values are identical or not. + """ + has_value = information.has_key(identifier) + compare_has_value = compare_information.has_key(identifier) + + if not has_value and not compare_has_value: + return True + + if not has_value: + self._output_writer.Write(u'{0:s} value not present in {1:s}.\n'.format( + description, self._storage_file_path)) + return False + + if not compare_has_value: + self._output_writer.Write(u'{0:s} value not present in {1:s}.\n'.format( + description, self._compare_storage_file_path)) + return False + + value = information.get(identifier, None) + compare_value = compare_information.get(identifier, None) + if value != compare_value: + self._output_writer.Write( + u'{0:s} value mismatch {1!s} != {2!s}.\n'.format( + description, value, compare_value)) + return False + + return True + + def _CompareStorageInformationObjects( + self, storage_information, compare_storage_information): + """Compares the storage information objects. + + Args: + storage_information: The storage information object (instance of + PreprocessObject). + compare_storage_information: The storage information object (instance of + PreprocessObject) to compare against. + + Returns: + A boolean value indicating if the storage information objects are + identical or not. + """ + result = True + if not self._CompareInformationDict( + u'collection_information', storage_information, + compare_storage_information, ignore_values=[ + u'cmd_line', u'output_file', u'time_of_run']): + result = False + + if not self._CompareInformationDict( + u'counter', storage_information, compare_storage_information): + result = False + + if not self._CompareInformationDict( + u'plugin_counter', storage_information, compare_storage_information): + result = False + + # TODO: compare stores. + # TODO: compare remaining preprocess information. + # TODO: compare reports. + + return result + + def _CompareStorageInformation(self, storage_file, compare_storage_file): + """Compares the storage information. + + Args: + storage_file: The storage file (instance of StorageFile). + compare_storage_file: The storage file (instance of StorageFile) to + compare against. + + Returns: + A boolean value indicating if the storage information objects are + identical or not. + """ + storage_information_list = storage_file.GetStorageInformation() + compare_storage_information_list = ( + compare_storage_file.GetStorageInformation()) + + if not storage_information_list and not compare_storage_information_list: + self._output_writer.Write(u'No storage information found.\n') + return True + + storage_information_list_length = len(storage_information_list) + compare_storage_information_list_length = len( + compare_storage_information_list) + number_of_list_entries = min( + storage_information_list_length, + compare_storage_information_list_length) + + result = True + for list_entry in range(0, number_of_list_entries): + if not self._CompareStorageInformationObjects( + storage_information_list[list_entry], + compare_storage_information_list[list_entry]): + result = False + + if number_of_list_entries < storage_information_list_length: + self._output_writer.Write(( + u'Storage file: {0:s} contains: {1:d} addititional storage ' + u'information entries.\n').format( + self._storage_file_path, + storage_information_list_length - number_of_list_entries)) + result = False + + if number_of_list_entries < compare_storage_information_list_length: + self._output_writer.Write(( + u'Storage file: {0:s} contains: {1:d} addititional storage ' + u'information entries.\n').format( + self._compare_storage_file_path, + compare_storage_information_list_length - number_of_list_entries)) + result = False + + if result: + self._output_writer.Write(u'Storage files are identical.\n') + + return result + + def _FormatCollectionInformation(self, lines_of_text, storage_information): + """Formats the collection information. Args: lines_of_text: A list containing the lines of text. - collection_information: The collection information dict. + storage_information: The storage information object (instance of + PreprocessObject). """ + collection_information = getattr( + storage_information, u'collection_information', None) + if not collection_information: + lines_of_text.append(u'Missing collection information.') + return + + self._FormatHeader(lines_of_text) + filename = collection_information.get(u'file_processed', u'N/A') time_of_run = collection_information.get(u'time_of_run', 0) time_of_run = timelib.Timestamp.CopyToIsoFormat(time_of_run) @@ -60,27 +249,35 @@ def _AddCollectionInformation(self, lines_of_text, collection_information): lines_of_text.append(u'') lines_of_text.append(u'Collection information:') - for key, value in collection_information.items(): - if key not in [u'file_processed', u'time_of_run']: - lines_of_text.append(u'\t{0:s} = {1!s}'.format(key, value)) + for key, value in collection_information.iteritems(): + if key in [u'file_processed', u'time_of_run']: + continue + if key == u'parsers': + value = u', '.join(sorted(value)) + lines_of_text.append(u'\t{0:s} = {1!s}'.format(key, value)) - def _AddCounterInformation( + def _FormatCounterInformation( self, lines_of_text, description, counter_information): - """Adds the lines of text that make up the counter information. + """Formats the counter information. Args: lines_of_text: A list containing the lines of text. description: The counter information description. counter_information: The counter information dict. """ - lines_of_text.append(u'') + if not counter_information: + return + + if lines_of_text: + lines_of_text.append(u'') + lines_of_text.append(u'{0:s}:'.format(description)) for key, value in counter_information.most_common(): lines_of_text.append(u'\tCounter: {0:s} = {1:d}'.format(key, value)) - def _AddHeader(self, lines_of_text): - """Adds the lines of text that make up the header. + def _FormatHeader(self, lines_of_text): + """Formats a header. Args: lines_of_text: A list containing the lines of text. @@ -89,34 +286,61 @@ def _AddHeader(self, lines_of_text): lines_of_text.append(u'\t\tPlaso Storage Information') lines_of_text.append(u'-' * self._LINE_LENGTH) - def _AddStoreInformation( - self, printer_object, lines_of_text, store_information): - """Adds the lines of text that make up the store information. + def _FormatPreprocessingInformation( + self, printer_object, lines_of_text, storage_information): + """Formats the processing information. Args: printer_object: A pretty printer object (instance of PrettyPrinter). lines_of_text: A list containing the lines of text. - store_information: The store information dict. + storage_information: The storage information object (instance of + PreprocessObject). """ - lines_of_text.append(u'') - lines_of_text.append(u'Store information:') - lines_of_text.append(u'\tNumber of available stores: {0:d}'.format( - store_information[u'Number'])) + if lines_of_text: + lines_of_text.append(u'') if not self._verbose: lines_of_text.append( - u'\tStore information details omitted (to see use: --verbose)') - else: - for key, value in store_information.iteritems(): - if key not in ['Number']: - lines_of_text.append( - u'\t{0:s} =\n{1!s}'.format(key, printer_object.pformat(value))) + u'Preprocessing information omitted (to see use: --verbose).') + return + + lines_of_text.append(u'Preprocessing information:') + for key, value in storage_information.__dict__.iteritems(): + if key in [u'collection_information', u'counter', u'stores']: + continue + + if isinstance(value, list): + value = printer_object.pformat(value) + lines_of_text.append(u'\t{0:s} ='.format(key)) + lines_of_text.append(u'{0!s}'.format(value)) + else: + lines_of_text.append(u'\t{0:s} = {1!s}'.format(key, value)) + + def _FormatReports(self, storage_file): + """Formats the reports. + + Args: + storage_file: The storage file (instance of StorageFile). + + Returns: + A string containing the formatted reports. + """ + if not storage_file.HasReports(): + return u'No reports stored.' - def _FormatStorageInformation(self, info, storage_file, last_entry=False): + if not self._verbose: + return u'Reporting information omitted (to see use: --verbose).' + + return u'\n'.join([ + report.GetString() for report in storage_file.GetReports()]) + + def _FormatStorageInformation( + self, storage_information, storage_file, last_entry=False): """Formats the storage information. Args: - info: The storage information object (instance of PreprocessObject). + storage_information: The storage information object (instance of + PreprocessObject). storage_file: The storage file (instance of StorageFile). last_entry: Optional boolean value to indicate this is the last information entry. The default is False. @@ -127,61 +351,113 @@ def _FormatStorageInformation(self, info, storage_file, last_entry=False): printer_object = pprint.PrettyPrinter(indent=self._INDENTATION_LEVEL) lines_of_text = [] - collection_information = getattr(info, u'collection_information', None) - if collection_information: - self._AddHeader(lines_of_text) - self._AddCollectionInformation(lines_of_text, collection_information) - else: - lines_of_text.append(u'Missing collection information.') + self._FormatCollectionInformation(lines_of_text, storage_information) - counter_information = getattr(info, u'counter', None) - if counter_information: - self._AddCounterInformation( - lines_of_text, u'Parser counter information', counter_information) + counter_information = getattr(storage_information, u'counter', None) + self._FormatCounterInformation( + lines_of_text, u'Parser counter information', counter_information) - counter_information = getattr(info, u'plugin_counter', None) - if counter_information: - self._AddCounterInformation( - lines_of_text, u'Plugin counter information', counter_information) + counter_information = getattr(storage_information, u'plugin_counter', None) + self._FormatCounterInformation( + lines_of_text, u'Plugin counter information', counter_information) - store_information = getattr(info, u'stores', None) - if store_information: - self._AddStoreInformation( - printer_object, lines_of_text, store_information) + self._FormatStoreInformation( + printer_object, lines_of_text, storage_information) - information = u'\n'.join(lines_of_text) + self._FormatPreprocessingInformation( + printer_object, lines_of_text, storage_information) - if not self._verbose: - preprocessing = ( - u'Preprocessing information omitted (to see use: --verbose).') + if last_entry: + lines_of_text.append(u'') + reports = self._FormatReports(storage_file) else: - preprocessing = u'Preprocessing information:\n' - for key, value in info.__dict__.items(): - if key == u'collection_information': - continue - elif key in [u'counter', u'stores']: - continue - if isinstance(value, list): - preprocessing += u'\t{0:s} =\n{1!s}\n'.format( - key, printer_object.pformat(value)) - else: - preprocessing += u'\t{0:s} = {1!s}\n'.format(key, value) - - if not last_entry: reports = u'' - elif storage_file.HasReports(): - reports = u'Reporting information omitted (to see use: --verbose).' - else: - reports = u'No reports stored.' - if self._verbose and last_entry and storage_file.HasReports(): - report_list = [] - for report in storage_file.GetReports(): - report_list.append(report.GetString()) - reports = u'\n'.join(report_list) + information = u'\n'.join(lines_of_text) - return u'\n'.join([ - information, u'', preprocessing, u'', reports, u'-+' * 40]) + return u'\n'.join([information, reports, u'-+' * 40, u'']) + + def _FormatStoreInformation( + self, printer_object, lines_of_text, storage_information): + """Formats the store information. + + Args: + printer_object: A pretty printer object (instance of PrettyPrinter). + lines_of_text: A list containing the lines of text. + storage_information: The storage information object (instance of + PreprocessObject). + """ + store_information = getattr(storage_information, u'stores', None) + if not store_information: + return + + if lines_of_text: + lines_of_text.append(u'') + + lines_of_text.append(u'Store information:') + lines_of_text.append(u'\tNumber of available stores: {0:d}'.format( + store_information[u'Number'])) + + if not self._verbose: + lines_of_text.append( + u'\tStore information details omitted (to see use: --verbose)') + return + + for key, value in store_information.iteritems(): + if key not in [u'Number']: + lines_of_text.append( + u'\t{0:s} =\n{1!s}'.format(key, printer_object.pformat(value))) + + def _PrintStorageInformation(self, storage_file): + """Prints the storage information. + + Args: + storage_file: The storage file (instance of StorageFile). + """ + storage_information_list = storage_file.GetStorageInformation() + + if not storage_information_list: + self._output_writer.Write(u'No storage information found.\n') + return + + for index, storage_information in enumerate(storage_information_list): + last_entry = index + 1 == len(storage_information_list) + storage_information = self._FormatStorageInformation( + storage_information, storage_file, last_entry=last_entry) + + self._output_writer.Write(storage_information) + + def CompareStorageInformation(self): + """Compares the storage information. + + Returns: + A boolean value indicating if the storage information objects are + identical or not. + """ + try: + storage_file = self._front_end.OpenStorage(self._storage_file_path) + except IOError as exception: + logging.error( + u'Unable to open storage file: {0:s} with error: {1:s}'.format( + self._storage_file_path, exception)) + return + + try: + compare_storage_file = self._front_end.OpenStorage( + self._compare_storage_file_path) + except IOError as exception: + logging.error( + u'Unable to open storage file: {0:s} with error: {1:s}'.format( + self._compare_storage_file_path, exception)) + storage_file.Close() + return + + result = self._CompareStorageInformation(storage_file, compare_storage_file) + + storage_file.Close() + compare_storage_file.Close() + + return result def ParseArguments(self): """Parses the command line arguments. @@ -192,7 +468,8 @@ def ParseArguments(self): logging.basicConfig( level=logging.INFO, format=u'[%(levelname)s] %(message)s') - argument_parser = argparse.ArgumentParser(description=self.DESCRIPTION) + argument_parser = argparse.ArgumentParser( + description=self.DESCRIPTION, add_help=False) self.AddBasicOptions(argument_parser) self.AddStorageFileOptions(argument_parser) @@ -201,12 +478,17 @@ def ParseArguments(self): u'-v', u'--verbose', dest=u'verbose', action=u'store_true', default=False, help=u'Print verbose output.') + argument_parser.add_argument( + u'--compare', dest=u'compare_storage_file', type=unicode, + action=u'store', default=u'', metavar=u'STORAGE_FILE', help=( + u'The path of the storage file to compare against.')) + try: options = argument_parser.parse_args() except UnicodeEncodeError: # If we get here we are attempting to print help in a non-Unicode # terminal. - self._output_writer.Write(u'') + self._output_writer.Write(u'\n') self._output_writer.Write(argument_parser.format_help()) return False @@ -215,7 +497,7 @@ def ParseArguments(self): except errors.BadConfigOption as exception: logging.error(u'{0:s}'.format(exception)) - self._output_writer.Write(u'') + self._output_writer.Write(u'\n') self._output_writer.Write(argument_parser.format_help()) return False @@ -235,33 +517,28 @@ def ParseOptions(self, options): self._verbose = getattr(options, u'verbose', False) + compare_storage_file_path = getattr(options, u'compare_storage_file', None) + if compare_storage_file_path: + if not os.path.isfile(compare_storage_file_path): + raise errors.BadConfigOption( + u'No such storage file: {0:s}.'.format(compare_storage_file_path)) + + self._compare_storage_file_path = compare_storage_file_path + self.compare_storage_information = True + def PrintStorageInformation(self): """Prints the storage information.""" - # TODO: clean up arguments after front-end refactor. - front_end = analysis_frontend.AnalysisFrontend(None, None) - try: - storage = front_end.OpenStorage(self._storage_file_path) + storage_file = self._front_end.OpenStorage(self._storage_file_path) except IOError as exception: logging.error( u'Unable to open storage file: {0:s} with error: {1:s}'.format( self._storage_file_path, exception)) return - list_of_storage_information = storage.GetStorageInformation() - if not list_of_storage_information: - self._output_writer.Write(u'No storage information found.') - return + self._PrintStorageInformation(storage_file) - last_entry = False - - for index, info in enumerate(list_of_storage_information): - if index + 1 == len(list_of_storage_information): - last_entry = True - storage_information = self._FormatStorageInformation( - info, storage, last_entry=last_entry) - - self._output_writer.Write(storage_information) + storage_file.Close() def Main(): @@ -271,10 +548,12 @@ def Main(): if not tool.ParseArguments(): return False - # TODO: left over comment: To make YAML loading work. - - tool.PrintStorageInformation() - return True + result = True + if tool.compare_storage_information: + result = tool.CompareStorageInformation() + else: + tool.PrintStorageInformation() + return result if __name__ == '__main__': diff --git a/tools/pinfo_test.py b/tools/pinfo_test.py index 16fd3120a7..44360a5670 100644 --- a/tools/pinfo_test.py +++ b/tools/pinfo_test.py @@ -12,20 +12,85 @@ class PinfoToolTest(cli_test_lib.CLIToolTestCase): """Tests for the pinfo CLI tool.""" + def setUp(self): + """Sets up the needed objects used throughout the test.""" + self._output_writer = cli_test_lib.TestOutputWriter(encoding=u'utf-8') + self._test_tool = pinfo.PinfoTool(output_writer=self._output_writer) + + def testCompareStorageInformation(self): + """Tests the CompareStorageInformation function.""" + test_file1 = self._GetTestFilePath([u'psort_test.out']) + test_file2 = self._GetTestFilePath([u'pinfo_test.out']) + + options = frontend.Options() + options.compare_storage_file = test_file1 + options.storage_file = test_file1 + + self._test_tool.ParseOptions(options) + + self.assertTrue(self._test_tool.CompareStorageInformation()) + + output = self._output_writer.ReadOutput() + self.assertEqual(output, b'Storage files are identical.\n') + + options = frontend.Options() + options.compare_storage_file = test_file1 + options.storage_file = test_file2 + + self._test_tool.ParseOptions(options) + + self.assertFalse(self._test_tool.CompareStorageInformation()) + def testPrintStorageInformation(self): """Tests the PrintStorageInformation function.""" - # Make sure the test outputs UTF-8. - output_writer = cli_test_lib.TestOutputWriter(encoding=u'utf-8') - test_tool = pinfo.PinfoTool(output_writer=output_writer) + test_file = self._GetTestFilePath([u'psort_test.out']) options = frontend.Options() - options.storage_file = self._GetTestFilePath([u'psort_test.out']) + options.storage_file = test_file + + self._test_tool.ParseOptions(options) - test_tool.ParseOptions(options) + self._test_tool.PrintStorageInformation() - test_tool.PrintStorageInformation() + expected_parsers = u', '.join(sorted([ + u'asl_log', + u'bencode', + u'bsm_log', + u'filestat', + u'hachoir', + u'java_idx', + u'lnk', + u'mac_appfirewall_log', + u'mac_keychain', + u'mac_securityd', + u'mactime', + u'macwifi', + u'mcafee_protection', + u'msiecf', + u'olecf', + u'openxml', + u'opera_global', + u'opera_typed_history', + u'plist', + u'prefetch', + u'recycle_bin', + u'recycle_bin_info2', + u'selinux', + u'skydrive_log', + u'skydrive_log_error', + u'sqlite', + u'symantec_scanlog', + u'syslog', + u'utmp', + u'utmpx', + u'winevt', + u'winevtx', + u'winfirewall', + u'winjob', + u'winreg', + u'xchatlog', + u'xchatscrollback'])) - # TODO: clean up output so that u'...' is not generated. expected_output = ( b'---------------------------------------------------------------------' b'-----------\n' @@ -41,16 +106,7 @@ def testPrintStorageInformation(self): b'\tos_detected = N/A\n' b'\tconfigured_zone = UTC\n' b'\tdebug = False\n' - b'\tparsers = [u\'sqlite\', u\'winfirewall\', u\'selinux\', ' - b'u\'recycle_bin\', u\'filestat\', u\'syslog\', u\'lnk\', ' - b'u\'xchatscrollback\', u\'symantec_scanlog\', u\'recycle_bin_info2\', ' - b'u\'winevtx\', u\'plist\', u\'bsm_log\', u\'mac_keychain\', ' - b'u\'mac_securityd\', u\'utmp\', u\'asl_log\', u\'opera_global\', ' - b'u\'winjob\', u\'prefetch\', u\'winreg\', u\'msiecf\', u\'bencode\', ' - b'u\'skydrive_log\', u\'openxml\', u\'utmpx\', u\'winevt\', ' - b'u\'hachoir\', u\'opera_typed_history\', u\'mac_appfirewall_log\', ' - b'u\'olecf\', u\'xchatlog\', u\'macwifi\', u\'mactime\', ' - b'u\'java_idx\', u\'mcafee_protection\', u\'skydrive_log_error\']\n' + b'\tparsers = {1:s}\n' b'\tprotobuf_size = 300\n' b'\tvss parsing = False\n' b'\trecursive = False\n' @@ -77,11 +133,15 @@ def testPrintStorageInformation(self): b'\n' b'No reports stored.\n' b'-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+' - b'-+-+-+-+-+-+').format(options.storage_file.encode(u'utf-8')) + b'-+-+-+-+-+-+\n').format( + options.storage_file.encode(u'utf-8'), + expected_parsers.encode(u'utf-8')) - output = output_writer.ReadOutput() + output = self._output_writer.ReadOutput() - self.assertEqual(output, expected_output) + # Compare the output as list of lines which makes it easier to spot + # differences. + self.assertEqual(output.split(b'\n'), expected_output.split(b'\n')) if __name__ == '__main__':