diff --git a/.gitignore b/.gitignore index 53bb4e6..7ae0c03 100644 --- a/.gitignore +++ b/.gitignore @@ -6,5 +6,10 @@ __pycache__/ # Sphinx documentation docs/_build/ +# Build files +venv/ + # Temporary files _.* +/output/ +/tests/_scratch/ diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..919a13e --- /dev/null +++ b/Makefile @@ -0,0 +1,7 @@ +default: example + +example: + ./venv/bin/python3 run_example.py -d . + +test: + @./venv/bin/python3 -m unittest diff --git a/ddhf/ddhf/decorated_context.py b/ddhf/ddhf/decorated_context.py index 52104a3..901b7bf 100644 --- a/ddhf/ddhf/decorated_context.py +++ b/ddhf/ddhf/decorated_context.py @@ -110,13 +110,29 @@ def from_argv(self): "AUTOARCHAEOLOGIST_BITSTORE_CACHE": "ddhf_bitstore_cache", } -def main(job, html_subdir="tmp", **kwargs): +def parse_arguments(argv=None): + parser = argparse.ArgumentParser() + parser.add_argument('-o', '--out', default='/tmp/_autoarchaologist') + + args = parser.parse_args(args=argv) + if args.out == '.': + args.out = os.path.join(os.getcwd(), "_autoarchaologist") + return args + +def main(job, html_subdir, **kwargs): + args = parse_arguments() + kwargs["html_dir"] = args.out + ''' A standard main routine to reduce boiler-plate ''' for key in os.environ: i = OK_ENVS.get(key) if i: kwargs[i] = os.environ[key] + if 'html_dir' not in kwargs: + raise AttributeError("missing: html_dir") + + kwargs['html_dir'] = os.path.join(kwargs['html_dir'], html_subdir) kwargs.setdefault('download_links', True) kwargs.setdefault('download_limit', 1 << 20) diff --git a/output/.gitkeep b/output/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/run.py b/run.py new file mode 100644 index 0000000..63421ec --- /dev/null +++ b/run.py @@ -0,0 +1,48 @@ +import argparse +import os +import sys + +from autoarchaeologist import Excavation + +def parse_arguments(argv=None): + parser = argparse.ArgumentParser() + parser.add_argument("-d", "--dir", default="/tmp/_autoarchaologist") + parser.add_argument('filename') + + return parser.parse_args(args=argv) + +def process_arguments(args): + if args.dir == ".": + args.dir = os.path.join(os.getcwd(), "output", "_autoarchaologist") + if args.filename is not None: + args.filename = os.path.abspath(args.filename) + else: + raise ValueError() + + return args + +def perform_excavation(args, action_tuple): + match action_tuple: + case "excavator", AnExcavation: + assert issubclass(AnExcavation, Excavation) + ctx = AnExcavation(html_dir=args.dir) + case action, _: + raise NotImplementedError(f"action: {action}") + + ff = ctx.add_file_artifact(args.filename) + + ctx.start_examination() + + return ctx + +if __name__ == "__main__": + args = process_arguments(parse_arguments()) + + try: + os.mkdir(args.dir) + except FileExistsError: + pass + + ctx = perform_excavation(args, ("none", None)) + ctx.produce_html() + print("Now point your browser at", ctx.filename_for(ctx).link) diff --git a/run_example.py b/run_example.py index ad7c063..808842b 100644 --- a/run_example.py +++ b/run_example.py @@ -1,32 +1,32 @@ - import os +import sys +from types import SimpleNamespace -import autoarchaeologist - -from autoarchaeologist.generic.bigdigits import BigDigits +from run import parse_arguments, process_arguments, perform_excavation +from autoarchaeologist.base.excavation import Excavation +from autoarchaeologist.generic.bigtext import BigText from autoarchaeologist.generic.samesame import SameSame from autoarchaeologist.data_general.absbin import AbsBin from autoarchaeologist.data_general.papertapechecksum import DGC_PaperTapeCheckSum -if __name__ == "__main__": - - ctx = autoarchaeologist.Excavation() - - ctx.add_examiner(BigDigits) - ctx.add_examiner(AbsBin) - ctx.add_examiner(DGC_PaperTapeCheckSum) - ctx.add_examiner(SameSame) +class ExampleExcavation(Excavation): + def __init__(self, **kwargs): + super().__init__(**kwargs) - ff = ctx.add_file_artifact("examples/30001393.bin") + self.add_examiner(BigText) + self.add_examiner(AbsBin) + self.add_examiner(DGC_PaperTapeCheckSum) + self.add_examiner(SameSame) - ctx.start_examination() - try: - os.mkdir("/tmp/_autoarchaologist") - except FileExistsError: - pass +if __name__ == "__main__": + argv = sys.argv[1:] + # force the example as the filename + argv.append("examples/30001393.bin") + args = process_arguments(parse_arguments(argv=argv)) - ctx.produce_html(html_dir="/tmp/_autoarchaologist") + ctx = perform_excavation(args, ("excavator", ExampleExcavation)) + ctx.produce_html() print("Now point your browser at", ctx.filename_for(ctx).link) diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/test_run_example.py b/tests/test_run_example.py new file mode 100644 index 0000000..41ed95d --- /dev/null +++ b/tests/test_run_example.py @@ -0,0 +1,119 @@ +import importlib +import os +import shutil +import sys +from types import SimpleNamespace +import unittest + +TESTS_DIR = os.path.dirname(os.path.abspath(__file__)) +SCRATCH_DIR = os.path.join(TESTS_DIR, "_scratch") +ROOT_DIR = os.path.normpath(os.path.join(TESTS_DIR, "..")) + +sys.path.append(TESTS_DIR) + +from run import perform_excavation +from run_example import ExampleExcavation +from autoarchaeologist.base.artifact import ArtifactBase, ArtifactStream + + +def example_arguments(output_dir): + example_arguments = SimpleNamespace() + example_arguments.dir = output_dir + example_arguments.filename = "examples/30001393.bin" + return example_arguments + + +class Test_RunExampleBasicHtml(unittest.TestCase): + """ + Ensure run_example produces expected HTML files for the example input. + """ + + DIR_TREE = None + + @classmethod + def setUpClass(cls): + args = example_arguments(SCRATCH_DIR) + shutil.rmtree(args.dir, ignore_errors=True) + os.makedirs(args.dir, exist_ok=True) + ctx = perform_excavation(args, ("excavator", ExampleExcavation)) + ctx.produce_html() + cls.DIR_TREE = list(os.walk(args.dir)) + + def toplevel(self): + return self.__class__.DIR_TREE[0] + + def toplevel_dirnames(self): + _, dirs, __ = self.toplevel() + dirs.sort() + return dirs + + def toplevel_filenames(self): + _, __, filenames = self.toplevel() + return filenames + + def test_produces_top_level_index(self): + toplevel_filenames = self.toplevel_filenames() + self.assertTrue("index.html" in toplevel_filenames) + self.assertTrue("index.css" in toplevel_filenames) + + def test_produces_digest_directories(self): + toplevel_dirnames = self.toplevel_dirnames() + self.assertEqual(toplevel_dirnames, ['08', '79', 'fa']) + + +class Test_RunExampleBasicArtifacts(unittest.TestCase): + """ + Ensure run_example excavates the expected artifacts for the example input. + """ + + CTX = None + + @classmethod + def setUpClass(cls): + args = example_arguments(SCRATCH_DIR) + shutil.rmtree(args.dir, ignore_errors=True) + os.makedirs(args.dir, exist_ok=True) + ctx = perform_excavation(args, ("excavator", ExampleExcavation)) + cls.CTX = ctx + + def assertArtifactIsChild(self, artifact, parent): + assert issubclass(artifact.__class__, ArtifactBase) + self.assertEqual(list(artifact.parents), [parent]) + + def excavation(self): + return self.__class__.CTX + + def test_excavated_three_total_artifacts(self): + arfifact_hash_keys = list(self.excavation().hashes.keys()) + self.assertEqual(len(arfifact_hash_keys), 3) + + def test_excavated_one_top_level_artifact(self): + excavatoin_child_count = len(self.excavation().children) + self.assertEqual(excavatoin_child_count, 1) + + def test_produces_top_level_artifact(self): + excavation = self.excavation() + artifact = self.excavation().children[0] + self.assertIsInstance(artifact, ArtifactStream) + self.assertEqual(artifact.digest, '083a3d5e3098aec38ee5d9bc9f9880d3026e120ff8f058782d49ee3ccafd2a6c') + self.assertTrue(artifact.digest in excavation.hashes) + + def test_produces_top_level_artifact_whose_parent_is_excavation(self): + artifact = self.excavation().children[0] + self.assertArtifactIsChild(artifact, self.excavation()) + + def test_produces_two_children_of_the_top_level(self): + excavation = self.excavation() + artifact = excavation.children[0] + artifact_children = sorted(artifact.children, key=lambda a: a.digest) + self.assertEqual(len(artifact_children), 2) + self.assertTrue(artifact_children[0].digest in excavation.hashes) + self.assertTrue(artifact_children[0].digest.startswith('79')) + self.assertArtifactIsChild(artifact_children[0], artifact) + self.assertTrue(artifact_children[1].digest in excavation.hashes) + self.assertTrue(artifact_children[1].digest.startswith('fa')) + self.assertArtifactIsChild(artifact_children[1], artifact) + + +if __name__ == '__main__': + unittest.main()