diff --git a/tests/test_argparse.py b/tests/test_argparse.py new file mode 100644 index 0000000..a93096d --- /dev/null +++ b/tests/test_argparse.py @@ -0,0 +1,390 @@ +from __future__ import print_function + +import argparse +import subprocess +import sys +import unittest +from unittest.mock import patch + +PYTHON = sys.executable + +MODULES = { + "mutate": "universalmutator.genmutants", + "analyze_mutants": "universalmutator.analyze", + "check_covered": "universalmutator.checkcov", + "find_missing": "universalmutator.findmissing", + "intersect_mutants": "universalmutator.intersect", + "prioritize_mutants":"universalmutator.prioritize", + "prune_mutants": "universalmutator.prune", + "show_mutants": "universalmutator.show", +} + +# UNIT TESTING ALL ARGS ADDED BY 3/28/2026 (any args added after that date have not been included) + + +def run_module(module, args): + return subprocess.run( + [PYTHON, "-m", module] + args, + capture_output=True, text=True + ) + + +def capture_parsed_args(module_main, argv): + + captured = {} + original_parse_args = argparse.ArgumentParser.parse_args + + def intercepting(self, args=None, namespace=None): + result = original_parse_args(self, args, namespace) + captured["parsed"] = result + raise SystemExit(0) # stop before any file I/O runs + + with patch.object(argparse.ArgumentParser, "parse_args", intercepting): + with patch("sys.argv", argv): + try: + module_main() + except (SystemExit, Exception): + pass + + return captured.get("parsed") + + +# 1. --help exits 0 for every command + +class TestHelpOutput(unittest.TestCase): + + def _check_help(self, module): + result = run_module(module, ["--help"]) + self.assertEqual(result.returncode, 0, + msg=f"{module} --help returned {result.returncode}\n{result.stderr}") + self.assertIn("usage", result.stdout.lower()) + + def test_mutate_help(self): self._check_help(MODULES["mutate"]) + def test_analyze_help(self): self._check_help(MODULES["analyze_mutants"]) + def test_check_covered_help(self): self._check_help(MODULES["check_covered"]) + def test_find_missing_help(self): self._check_help(MODULES["find_missing"]) + def test_intersect_mutants_help(self):self._check_help(MODULES["intersect_mutants"]) + def test_prioritize_mutants_help(self):self._check_help(MODULES["prioritize_mutants"]) + def test_prune_mutants_help(self): self._check_help(MODULES["prune_mutants"]) + def test_show_mutants_help(self): self._check_help(MODULES["show_mutants"]) + + +# 2. Missing required positional args goes to exit code 2 + +class TestMissingRequiredArgs(unittest.TestCase): + + def _assert_usage_error(self, module, args): + result = run_module(module, args) + self.assertEqual(result.returncode, 2, + msg=f"Expected exit 2 for {module} {args}\n{result.stderr}") + + def test_mutate_requires_source(self): + self._assert_usage_error(MODULES["mutate"], []) + + def test_analyze_requires_sourcefile(self): + self._assert_usage_error(MODULES["analyze_mutants"], []) + + def test_analyze_requires_testscript(self): + self._assert_usage_error(MODULES["analyze_mutants"], ["src.py"]) + + def test_check_covered_requires_sourcefile(self): + self._assert_usage_error(MODULES["check_covered"], []) + + def test_check_covered_requires_coverfile(self): + self._assert_usage_error(MODULES["check_covered"], ["src.py"]) + + def test_check_covered_requires_outfile(self): + self._assert_usage_error(MODULES["check_covered"], ["src.py", "cover.txt"]) + + def test_find_missing_requires_all_three(self): + self._assert_usage_error(MODULES["find_missing"], []) + self._assert_usage_error(MODULES["find_missing"], ["foo.py"]) + self._assert_usage_error(MODULES["find_missing"], ["foo.py", "dir1"]) + + def test_intersect_requires_all_three(self): + self._assert_usage_error(MODULES["intersect_mutants"], []) + self._assert_usage_error(MODULES["intersect_mutants"], ["a.txt"]) + self._assert_usage_error(MODULES["intersect_mutants"], ["a.txt", "b.txt"]) + + def test_prioritize_requires_infile_and_outfile(self): + self._assert_usage_error(MODULES["prioritize_mutants"], []) + self._assert_usage_error(MODULES["prioritize_mutants"], ["in.txt"]) + + def test_prune_requires_all_three(self): + self._assert_usage_error(MODULES["prune_mutants"], []) + self._assert_usage_error(MODULES["prune_mutants"], ["in.txt"]) + self._assert_usage_error(MODULES["prune_mutants"], ["in.txt", "out.txt"]) + + def test_show_requires_infile(self): + self._assert_usage_error(MODULES["show_mutants"], []) + + +# 3. Type validation (argparse rejects bad types before touching files) + +class TestTypeValidation(unittest.TestCase): + + def _assert_type_error(self, module, args): + result = run_module(module, args) + self.assertEqual(result.returncode, 2, + msg=f"Expected type error (exit 2) for {module} {args}\n{result.stderr}") + + def test_analyze_seed_must_be_int(self): + self._assert_type_error(MODULES["analyze_mutants"], + ["src.py", "pytest", "--seed", "notanint"]) + + def test_analyze_timeout_must_be_float(self): + self._assert_type_error(MODULES["analyze_mutants"], + ["src.py", "pytest", "--timeout", "notafloat"]) + + def test_analyze_num_mutants_must_be_int(self): + self._assert_type_error(MODULES["analyze_mutants"], + ["src.py", "pytest", "--numMutants", "notanint"]) + + def test_prioritize_cutoff_must_be_float(self): + self._assert_type_error(MODULES["prioritize_mutants"], + ["in.txt", "out.txt", "--cutoff", "notafloat"]) + + def test_prioritize_N_must_be_int(self): + self._assert_type_error(MODULES["prioritize_mutants"], + ["in.txt", "out.txt", "notanint"]) + + +# 4. Default values +class TestDefaults(unittest.TestCase): + + def test_mutate_defaults(self): + from universalmutator.genmutants import main + p = capture_parsed_args(main, ["mutate", "source.py"]) + self.assertIsNotNone(p) + self.assertEqual(p.source, "source.py") + self.assertIsNone(p.language_or_rules) + self.assertEqual(p.rules, []) + self.assertFalse(p.noCheck) + self.assertFalse(p.comby) + self.assertFalse(p.redundantOK) + self.assertFalse(p.showRules) + self.assertFalse(p.mutateInStrings) + self.assertFalse(p.mutateTestCode) + self.assertFalse(p.mutateBoth) + self.assertFalse(p.noFastCheck) + self.assertFalse(p.swap) + self.assertFalse(p.tstl) + self.assertFalse(p.fuzz) + self.assertFalse(p.printStat) + self.assertIsNone(p.cmd) + self.assertIsNone(p.lines) + self.assertEqual(p.mutantDir, ".") + self.assertIsNone(p.ignore) + self.assertIsNone(p.compile) + self.assertIsNone(p.only) + + def test_analyze_defaults(self): + from universalmutator.analyze import main + p = capture_parsed_args(main, ["analyze_mutants", "src.py", "pytest"]) + self.assertIsNotNone(p) + self.assertFalse(p.verbose) + self.assertFalse(p.show) + self.assertFalse(p.resume) + self.assertFalse(p.noShuffle) + self.assertIsNone(p.prefix) + self.assertIsNone(p.fromFile) + self.assertIsNone(p.seed) + self.assertEqual(p.timeout, 30) + self.assertEqual(p.numMutants, -1) + self.assertIsNone(p.compileCommand) + self.assertEqual(p.mutantDir, ".") + self.assertIsNone(p.ignorefile) + + def test_check_covered_defaults(self): + from universalmutator.checkcov import main + p = capture_parsed_args(main, ["check_covered", "src.py", "cover.txt", "out.txt"]) + self.assertIsNotNone(p) + self.assertEqual(p.mutantDir, ".") + self.assertFalse(p.tstl) + + def test_prioritize_defaults(self): + from universalmutator.prioritize import main + p = capture_parsed_args(main, ["prioritize_mutants", "in.txt", "out.txt"]) + self.assertIsNotNone(p) + self.assertFalse(p.verbose) + self.assertFalse(p.noSDPriority) + self.assertEqual(p.mutantDir, ".") + self.assertEqual(p.sourceDir, ".") + self.assertEqual(p.cutoff, 0.0) + self.assertIsNone(p.N) + + def test_show_defaults(self): + from universalmutator.show import main + p = capture_parsed_args(main, ["show_mutants", "in.txt"]) + self.assertIsNotNone(p) + self.assertFalse(p.concise) + self.assertEqual(p.mutantDir, ".") + self.assertEqual(p.sourceDir, ".") + + def test_prune_defaults(self): + from universalmutator.prune import main + p = capture_parsed_args(main, ["prune_mutants", "in.txt", "out.txt", "config.txt"]) + self.assertIsNotNone(p) + self.assertEqual(p.mutantDir, ".") + self.assertEqual(p.sourceDir, ".") + + +# 5. Flag and positional parsing +class TestFlagParsing(unittest.TestCase): + + def test_mutate_all_flags(self): + from universalmutator.genmutants import main + p = capture_parsed_args(main, [ + "mutate", "source.py", + "--noCheck", "--comby", "--redundantOK", "--showRules", + "--mutateInStrings", "--mutateTestCode", "--mutateBoth", + "--noFastCheck", "--swap", "--tstl", "--fuzz", "--printStat", + "--cmd", "compile MUTANT", + "--lines", "lines.txt", + "--mutantDir", "/tmp/mutants", + "--ignore", "ignore.txt", + "--compile", "compile.sol", + "--only", "custom.rules", + ]) + self.assertIsNotNone(p) + self.assertTrue(p.noCheck) + self.assertTrue(p.comby) + self.assertTrue(p.redundantOK) + self.assertTrue(p.showRules) + self.assertTrue(p.mutateInStrings) + self.assertTrue(p.mutateTestCode) + self.assertTrue(p.mutateBoth) + self.assertTrue(p.noFastCheck) + self.assertTrue(p.swap) + self.assertTrue(p.tstl) + self.assertTrue(p.fuzz) + self.assertTrue(p.printStat) + self.assertEqual(p.cmd, "compile MUTANT") + self.assertEqual(p.lines, "lines.txt") + self.assertEqual(p.mutantDir, "/tmp/mutants") + self.assertEqual(p.ignore, "ignore.txt") + self.assertEqual(p.compile, "compile.sol") + self.assertEqual(p.only, "custom.rules") + + def test_mutate_language_positional(self): + from universalmutator.genmutants import main + p = capture_parsed_args(main, ["mutate", "source.py", "python"]) + self.assertEqual(p.source, "source.py") + self.assertEqual(p.language_or_rules, "python") + self.assertEqual(p.rules, []) + + def test_mutate_extra_rules(self): + from universalmutator.genmutants import main + p = capture_parsed_args(main, ["mutate", "source.py", "python", "a.rules", "b.rules"]) + self.assertEqual(p.language_or_rules, "python") + self.assertEqual(p.rules, ["a.rules", "b.rules"]) + + def test_mutate_rules_file_as_language_or_rules(self): + from universalmutator.genmutants import main + p = capture_parsed_args(main, ["mutate", "source.py", "custom.rules"]) + self.assertEqual(p.language_or_rules, "custom.rules") + + def test_analyze_all_flags(self): + from universalmutator.analyze import main + p = capture_parsed_args(main, [ + "analyze_mutants", "src.py", "pytest", + "--verbose", "--show", "--resume", "--noShuffle", + "--prefix", "myprefix", + "--fromFile", "mutants.txt", + "--seed", "42", + "--timeout", "60.5", + "--numMutants", "100", + "--compileCommand", "make", + "--mutantDir", "./mutants", + ]) + self.assertIsNotNone(p) + self.assertTrue(p.verbose) + self.assertTrue(p.show) + self.assertTrue(p.resume) + self.assertTrue(p.noShuffle) + self.assertEqual(p.prefix, "myprefix") + self.assertEqual(p.fromFile, "mutants.txt") + self.assertEqual(p.seed, 42) + self.assertAlmostEqual(p.timeout, 60.5) + self.assertEqual(p.numMutants, 100) + self.assertEqual(p.compileCommand, "make") + self.assertEqual(p.mutantDir, "./mutants") + + def test_analyze_optional_ignorefile(self): + from universalmutator.analyze import main + p = capture_parsed_args(main, ["analyze_mutants", "src.py", "pytest", "ignore.txt"]) + self.assertEqual(p.ignorefile, "ignore.txt") + + def test_check_covered_tstl_flag(self): + from universalmutator.checkcov import main + p = capture_parsed_args(main, ["check_covered", "src.py", "cover.txt", "out.txt", "--tstl"]) + self.assertTrue(p.tstl) + + def test_check_covered_mutant_dir(self): + from universalmutator.checkcov import main + p = capture_parsed_args(main, ["check_covered", "src.py", "cover.txt", "out.txt", + "--mutantDir", "./mymutants"]) + self.assertEqual(p.mutantDir, "./mymutants") + + def test_prioritize_with_N(self): + from universalmutator.prioritize import main + p = capture_parsed_args(main, ["prioritize_mutants", "in.txt", "out.txt", "50"]) + self.assertEqual(p.N, 50) + + def test_prioritize_all_flags(self): + from universalmutator.prioritize import main + p = capture_parsed_args(main, [ + "prioritize_mutants", "in.txt", "out.txt", + "--verbose", "--noSDPriority", + "--mutantDir", "./m", + "--sourceDir", "./s", + "--cutoff", "0.5", + ]) + self.assertTrue(p.verbose) + self.assertTrue(p.noSDPriority) + self.assertEqual(p.mutantDir, "./m") + self.assertEqual(p.sourceDir, "./s") + self.assertAlmostEqual(p.cutoff, 0.5) + + def test_show_concise_flag(self): + from universalmutator.show import main + p = capture_parsed_args(main, ["show_mutants", "in.txt", "--concise"]) + self.assertTrue(p.concise) + + def test_show_dirs(self): + from universalmutator.show import main + p = capture_parsed_args(main, ["show_mutants", "in.txt", + "--mutantDir", "./m", "--sourceDir", "./s"]) + self.assertEqual(p.mutantDir, "./m") + self.assertEqual(p.sourceDir, "./s") + + def test_intersect_positionals(self): + from universalmutator.intersect import main + p = capture_parsed_args(main, ["intersect_mutants", "a.txt", "b.txt", "out.txt"]) + self.assertEqual(p.infile1, "a.txt") + self.assertEqual(p.infile2, "b.txt") + self.assertEqual(p.outfile, "out.txt") + + def test_find_missing_positionals(self): + from universalmutator.findmissing import main + p = capture_parsed_args(main, ["find_missing", "foo.py", "./dir1", "./dir2"]) + self.assertEqual(p.f, "foo.py") + self.assertEqual(p.d1, "./dir1") + self.assertEqual(p.d2, "./dir2") + + def test_prune_positionals_and_dirs(self): + from universalmutator.prune import main + p = capture_parsed_args(main, [ + "prune_mutants", "in.txt", "out.txt", "config.txt", + "--mutantDir", "./m", "--sourceDir", "./s", + ]) + self.assertEqual(p.infile, "in.txt") + self.assertEqual(p.outfile, "out.txt") + self.assertEqual(p.config, "config.txt") + self.assertEqual(p.mutantDir, "./m") + self.assertEqual(p.sourceDir, "./s") + + +# There are 45 tests in total, final output will be "OK" if they all pass +if __name__ == "__main__": + unittest.main() diff --git a/universalmutator.egg-info/PKG-INFO b/universalmutator.egg-info/PKG-INFO new file mode 100644 index 0000000..c907983 --- /dev/null +++ b/universalmutator.egg-info/PKG-INFO @@ -0,0 +1,152 @@ +Metadata-Version: 2.4 +Name: universalmutator +Version: 1.1.13 +Summary: Universal regexp-based mutation tool +Home-page: https://github.com/agroce/universalmutator +License: MIT +Keywords: testing mutation mutation-testing +Classifier: Intended Audience :: Developers +Classifier: Development Status :: 4 - Beta +Classifier: Programming Language :: Python :: 2 +Classifier: Programming Language :: Python :: 3 +Description-Content-Type: text/markdown +License-File: LICENSE +Requires-Dist: comby +Requires-Dist: python-levenshtein +Requires-Dist: tabulate +Dynamic: classifier +Dynamic: description +Dynamic: description-content-type +Dynamic: home-page +Dynamic: keywords +Dynamic: license +Dynamic: license-file +Dynamic: requires-dist +Dynamic: summary + +This is a tool based on source-based rewrite of code lines for mutation generation, including +multi-language rules aided by special rules for languages or even projects. Originally, the approach used only regular expressions, +treating code as text. However, there is also a mode that can use the [Comby](https://github.com/comby-tools/comby) tool +for more sophisticated mutation that produces fewer invalid mutants. Regular-expression based mutation works well, in our experience; +comby-aided mutation works even better. The key advantage of either approach is that the tool can probably mutate approximately *any* interesting source code you have, and language changes don't force +rewriting of the mutation tool. To use the comby mode, just make sure comby is installed and add `--comby` when you run `mutate`. + +More information on this project can be found in a [2024 FSE paper](https://agroce.github.io/fse24.pdf), and in the original [2018 ICSE Tool Paper](https://agroce.github.io/icse18t.pdf). + +A [guest blog post](https://blog.trailofbits.com/2019/01/23/fuzzing-an-api-with-deepstate-part-2/) for Trail of Bits shows how to use the universalmutator to help improve a C/C++ API fuzzing effort using [DeepState](https://github.com/trailofbits/deepstate) and libFuzzer. + +The universalmutator has support for extracting coverage information to guide mutation from the [TSTL](https://github.com/agroce/tstl) testing tool for Python. + +HOW TO USE IT +============= + +To use this, you should really just do: + +`pip install universalmutator` + +then + +`mutate --help` + +SIMPLE EXAMPLE USAGE +==================== + +`mutate foo.py` + +or + +`mutate foo.swift` + +should, if you have the appropriate compilers installed, generate a bunch of valid, non-trivially redundant, mutants. + + +A MORE COMPLEX EXAMPLE +====================== + +Sometimes the mutated code needs to be built with a more complicated command than a simple compiler call, and of course you want help discovering which mutants are killed and not killed. For example, to mutate and test mutants for the mandelbrot plotting example included in the PROGRAMMING RUST book (http://shop.oreilly.com/product/0636920040385.do), just do this: + + + git clone https://github.com/ProgrammingRust/mandelbrot + cd mandelbrot + cargo build + target/debug/mandelbrot origmandel.png 1000x750 -1.20,0.35 -1,0.20 + mkdir mutants + mutate src/main.rs --mutantDir mutants --noCheck + analyze_mutants src/main.rs "cargo clean; cargo build; rm mandel.png; target/debug/mandelbrot mandel.png 1000x750 -1.20,0.35 -1,0.20; diff mandel.png origmandel.png" --mutantDir mutants + +(It will go faster if you edit `main.rs` to lower the maximum number of threads used to something like 8, not 90.) At the moment, this won't use any Trivial Compiler Equivalence, but still kills about 60% of the 1000+ mutants. The killed mutant filenames will be in `killed.txt` and the non-killed ones in `not-killed.txt`. + +Working with something like maven is very similar, except you can probably edit the complicated build/clean stuff to just a 'mvn test' or similar. + +CURRENTLY SUPPORTED LANGUAGES +============================= + +The tool will likely mutate other things, if you tell it they are "c" or something, but there is auto-detection based on file ending and specific rule support for: + +``` +C +C++ +Java +JavaScript +Python +Swift +R +Rust +Go +Lisp +Fortran +Solidity +Vyper +Fe +``` + +(the last three are smart contract languages for the Ethereum blockchain). + +All but C, C++, JavaScript, and Go will try, by default, to compile the mutated +file and use TCE to detect redundancy. Of course, build dependencies +may frustrate this process, in which case --noCheck will turn off TCE +and just dump all the mutants in the directory, for pruning using a +real build process. In the long run, we plan to integrate with +standard build systems to avoid this problem, and with automated test +generation systems such as TSTL (https://github.com/agroce/tstl) for +Python or Echidna for Solidity +(https://github.com/trailofbits/echidna). Even now, however, with +`analyze_mutants` it is fairly easy to set up automatic evaluation of +your automated test generator. + +MUTATING SOLIDITY CODE +====================== + +The universalmutator has been most frequently applied to smart +contracts written in the Solidity language. It supports a few special +features that are particularly useful in this context. + +First, +Solidity libraries are often written with only `internal` functions +--- and the compiler will not emit code for such functions if you +compile a library by itself, resulting in no non-redundant mutants. +In order to handle this case, `mutate` can take a `--compile` option +that specifies another file (a contract using the library, or the +tests in question) that is used to check whether mutants are +redundant. + +Second, swapping adjacent lines of code is a seldom-used mutation +operator that is unusually attractive in a Solidity context because +swapping a state-changing operation and a requirement may reveal that +testing is incapable of detecting some +[re-entrancy](https://github.com/crytic/not-so-smart-contracts/tree/master/reentrancy) +vulnerabilities. The testing may notice the absence of the check, but +not a mis-ordering, and these mutants may reveal that. To add code +swaps to your mutations, just add `--swap` to the `mutate` call. Note +that swaps work in any language; they are just particularly appealing +for smart contracts. + +MORE INFORMATON +=============== + +For much more information, again see https://agroce.github.io/icse18t.pdf -- demo/tool paper at ICSE 18 and especially our full FSE 2024 paper -- https://agroce.github.io/fse24.pdf -- the latter discusses the latest version of the tool/approach, and includes a comparison with many other mutation testing tools. + +The aim of this project is partly to see how quickly mutation can be applied to new languages, partly how much the work of a tool can be +offloaded to the compiler / test analysis tools. + +Authors: Alex Groce, Josie Holmes, Darko Marinov, August Shi, Lingming Zhang, Kush Jain, Rijnard van Tonder, Sourav Deb diff --git a/universalmutator.egg-info/SOURCES.txt b/universalmutator.egg-info/SOURCES.txt new file mode 100644 index 0000000..208cd26 --- /dev/null +++ b/universalmutator.egg-info/SOURCES.txt @@ -0,0 +1,67 @@ +LICENSE +README.md +setup.py +tests/test_foo_example.py +universalmutator/__init__.py +universalmutator/analyze.py +universalmutator/c_handler.py +universalmutator/checkcov.py +universalmutator/cpp_handler.py +universalmutator/fe_handler.py +universalmutator/findmissing.py +universalmutator/fortran_handler.py +universalmutator/genmutants.py +universalmutator/go_handler.py +universalmutator/intersect.py +universalmutator/java_handler.py +universalmutator/javascript_handler.py +universalmutator/lisp_handler.py +universalmutator/mutator.py +universalmutator/prioritize.py +universalmutator/prune.py +universalmutator/python_handler.py +universalmutator/r_handler.py +universalmutator/rust_handler.py +universalmutator/show.py +universalmutator/solidity_handler.py +universalmutator/swift_handler.py +universalmutator/utils.py +universalmutator/vyper_handler.py +universalmutator.egg-info/PKG-INFO +universalmutator.egg-info/SOURCES.txt +universalmutator.egg-info/dependency_links.txt +universalmutator.egg-info/entry_points.txt +universalmutator.egg-info/requires.txt +universalmutator.egg-info/top_level.txt +universalmutator/comby/c.rules +universalmutator/comby/c_like.rules +universalmutator/comby/cpp.rules +universalmutator/comby/fortran.rules +universalmutator/comby/go.rules +universalmutator/comby/java.rules +universalmutator/comby/lisp.rules +universalmutator/comby/none.rules +universalmutator/comby/python.rules +universalmutator/comby/r.rules +universalmutator/comby/rust.rules +universalmutator/comby/solidity.rules +universalmutator/comby/swift.rules +universalmutator/comby/universal.rules +universalmutator/comby/vyper.rules +universalmutator/static/c.rules +universalmutator/static/c_like.rules +universalmutator/static/cpp.rules +universalmutator/static/fe.rules +universalmutator/static/fortran.rules +universalmutator/static/go.rules +universalmutator/static/java.rules +universalmutator/static/javascript.rules +universalmutator/static/lisp.rules +universalmutator/static/none.rules +universalmutator/static/python.rules +universalmutator/static/r.rules +universalmutator/static/rust.rules +universalmutator/static/solidity.rules +universalmutator/static/swift.rules +universalmutator/static/universal.rules +universalmutator/static/vyper.rules \ No newline at end of file diff --git a/universalmutator.egg-info/dependency_links.txt b/universalmutator.egg-info/dependency_links.txt new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/universalmutator.egg-info/dependency_links.txt @@ -0,0 +1 @@ + diff --git a/universalmutator.egg-info/entry_points.txt b/universalmutator.egg-info/entry_points.txt new file mode 100644 index 0000000..e75b6a1 --- /dev/null +++ b/universalmutator.egg-info/entry_points.txt @@ -0,0 +1,8 @@ +[console_scripts] +analyze_mutants = universalmutator.analyze:main +check_covered = universalmutator.checkcov:main +intersect_mutants = universalmutator.intersect:main +mutate = universalmutator.genmutants:main +prioritize_mutants = universalmutator.prioritize:main +prune_mutants = universalmutator.prune:main +show_mutants = universalmutator.show:main diff --git a/universalmutator.egg-info/requires.txt b/universalmutator.egg-info/requires.txt new file mode 100644 index 0000000..f68a6dc --- /dev/null +++ b/universalmutator.egg-info/requires.txt @@ -0,0 +1,3 @@ +comby +python-levenshtein +tabulate diff --git a/universalmutator.egg-info/top_level.txt b/universalmutator.egg-info/top_level.txt new file mode 100644 index 0000000..132847f --- /dev/null +++ b/universalmutator.egg-info/top_level.txt @@ -0,0 +1 @@ +universalmutator diff --git a/universalmutator/analyze.py b/universalmutator/analyze.py index d48450b..ded568c 100644 --- a/universalmutator/analyze.py +++ b/universalmutator/analyze.py @@ -1,5 +1,6 @@ from __future__ import print_function +import argparse import difflib import subprocess import sys @@ -15,134 +16,64 @@ def main(): isWindows = platform.system() - args = sys.argv - - if ("--help" in args) or (len(sys.argv) < 3): - if len(sys.argv) < 3: - print("ERROR: analyze_mutants requires at least two arguments\n") - print("USAGE: analyze_mutants [--mutantDir ] [--fromFile ]") - print(" is command to execute to run tests; non-zero return indicates mutant killed") - print(" --mutantDir: directory with all mutants; defaults to current directory") - print(" --fromFile: file containing list of mutants to process; others ignored") - print(" --timeout : change the timeout setting") - print(" --show: show mutants") - print(" --verbose: show mutants and output of analysis") - print(" --seed: random seed for shuffling of mutants") - print(" --noShuffle: do not randomize order of mutants") - print(" --resume: use existing killed.txt and notkilled.txt, resume mutation analysis") - print(" --prefix: add a prefix to killed.txt and notkilled.txt") - print(" --numMutants: run with specific number of mutants") - print(" --compileCommand: compile command to run in selecting mutants") - sys.exit(0) - - verbose = "--verbose" in sys.argv - if verbose: - args.remove("--verbose") - - showM = "--show" in sys.argv - if showM: - args.remove("--show") - - resume = "--resume" in sys.argv - if resume: - args.remove("--resume") - - noShuffle = "--noShuffle" in sys.argv - if noShuffle: - args.remove("--noShuffle") - - prefix = None - try: - prefixpos = args.index("--prefix") - except ValueError: - prefixpos = -1 - - if prefixpos != -1: - prefix = args[prefixpos + 1] - args.remove("--prefix") - args.remove(prefix) - - fromFile = None - try: - filepos = args.index("--fromFile") - except ValueError: - filepos = -1 - - if filepos != -1: - fromFile = args[filepos + 1] - args.remove("--fromFile") - args.remove(fromFile) - - seed = None - try: - seedpos = args.index("--seed") - except ValueError: - seedpos = -1 - if seedpos != -1: - seed = args[seedpos + 1] - args.remove("--seed") - args.remove(seed) - seed = int(seed) - - timeout = 30 - try: - topos = args.index("--timeout") - except ValueError: - topos = -1 - - if topos != -1: - timeout = args[topos + 1] - args.remove("--timeout") - args.remove(timeout) - timeout = float(timeout) - - numMutants = -1 - try: - nmpos = args.index("--numMutants") - except ValueError: - nmpos = -1 - - if nmpos != -1: - numMutants = args[nmpos + 1] - args.remove("--numMutants") - args.remove(numMutants) - numMutants = int(numMutants) - - compileCommand = None - try: - ccmdpos = args.index("--compileCommand") - except ValueError: - ccmdpos = -1 - - if ccmdpos != -1: - compileCommand = args[ccmdpos + 1] - args.remove("--compileCommand") - args.remove(compileCommand) + + parser = argparse.ArgumentParser(prog="analyze_mutants", + description="Analyze mutants by running a test command against each one.") + parser.add_argument("sourcefile", help="source file being mutated") + parser.add_argument("testscript", + help="command to execute to run tests; non-zero return indicates mutant killed") + parser.add_argument("ignorefile", nargs="?", default=None, + help="optional file whose first-column entries list mutants to skip") + parser.add_argument("--verbose", action="store_true", default=False, + help="show mutants and output of analysis") + parser.add_argument("--show", action="store_true", default=False, + help="show mutants") + parser.add_argument("--resume", action="store_true", default=False, + help="use existing killed.txt and notkilled.txt, resume mutation analysis") + parser.add_argument("--noShuffle", action="store_true", default=False, + help="do not randomize order of mutants") + parser.add_argument("--prefix", default=None, + help="add a prefix to killed.txt and notkilled.txt") + parser.add_argument("--fromFile", default=None, + help="file containing list of mutants to process; others ignored") + parser.add_argument("--seed", type=int, default=None, + help="random seed for shuffling of mutants") + parser.add_argument("--timeout", type=float, default=30, + help="change the timeout setting") + parser.add_argument("--numMutants", type=int, default=-1, + help="run with a specific number of mutants") + parser.add_argument("--compileCommand", default=None, + help="compile command to run in selecting mutants") + parser.add_argument("--mutantDir", default=".", + help="directory with all mutants; defaults to current directory") + parsed = parser.parse_args() + + verbose = parsed.verbose + showM = parsed.show + resume = parsed.resume + noShuffle = parsed.noShuffle + prefix = parsed.prefix + fromFile = parsed.fromFile + seed = parsed.seed + timeout = parsed.timeout + numMutants = parsed.numMutants + compileCommand = parsed.compileCommand onlyMutants = None if fromFile is not None: with open(fromFile, 'r') as file: onlyMutants = file.read().split() - mdir = "." - try: - mdirpos = args.index("--mutantDir") - except ValueError: - mdirpos = -1 - - if mdirpos != -1: - mdir = args[mdirpos + 1] - args.remove("--mutantDir") - args.remove(mdir) + mdir = parsed.mutantDir if mdir[-1] != "/": mdir += "/" - src = args[1] - tstCmd = [args[2]] + src = parsed.sourcefile + tstCmd = [parsed.testscript] ignore = [] - if len(args) > 3: - with open(sys.argv[3]) as file: + if parsed.ignorefile is not None: + with open(parsed.ignorefile) as file: for l in file: ignore.append(l.split()[0]) diff --git a/universalmutator/checkcov.py b/universalmutator/checkcov.py index adbf48e..37ec98d 100644 --- a/universalmutator/checkcov.py +++ b/universalmutator/checkcov.py @@ -1,39 +1,30 @@ from __future__ import print_function -import sys +import argparse import glob def main(): - args = sys.argv + parser = argparse.ArgumentParser(prog="check_covered", + description="Filter mutants to those on lines covered by a coverage file.") + parser.add_argument("sourcefile", help="source file being mutated") + parser.add_argument("coverfile", help="coverage file") + parser.add_argument("outfile", help="output file to write covered mutant names into") + parser.add_argument("--mutantDir", default=".", + help="directory with all mutants; defaults to current directory") + parser.add_argument("--tstl", action="store_true", default=False, + help="process coverfile that is output from TSTL internal report") + parsed = parser.parse_args() - if ("--help" in args) or (len(sys.argv) < 4): - if len(sys.argv) < 4: - print("ERROR: check_covered requires at least three arguments\n") - print("USAGE: check_covered [--tstl] [--mutantDir directory]") - print(" --mutantDir: directory to put generated mutants in; defaults to current directory") - print(" --tstl: process that is output from TSTL internal report") - sys.exit(0) - - mdir = "." - try: - mdirpos = args.index("--mutantDir") - except ValueError: - mdirpos = -1 - - if mdirpos != -1: - mdir = args[mdirpos + 1] - args.remove("--mutantDir") - args.remove(mdir) + mdir = parsed.mutantDir if mdir[-1] != "/": mdir += "/" - src = args[1] - coverFile = args[2] - outFile = args[3] - - tstl = "--tstl" in sys.argv + src = parsed.sourcefile + coverFile = parsed.coverfile + outFile = parsed.outfile + tstl = parsed.tstl srcBase = src.split("/")[-1] srcEnd = src.split(".")[-1] diff --git a/universalmutator/findmissing.py b/universalmutator/findmissing.py index 457b904..40c441c 100644 --- a/universalmutator/findmissing.py +++ b/universalmutator/findmissing.py @@ -1,15 +1,22 @@ from __future__ import print_function +import argparse import glob -import sys from universalmutator import utils def main(): - f = sys.argv[1] - d1 = sys.argv[2] - d2 = sys.argv[3] + parser = argparse.ArgumentParser(prog="find_missing", + description="Show mutants present in one directory but not another.") + parser.add_argument("f", help="base source filename (e.g. foo.py)") + parser.add_argument("d1", help="first mutant directory") + parser.add_argument("d2", help="second mutant directory") + parsed = parser.parse_args() + + f = parsed.f + d1 = parsed.d1 + d2 = parsed.d2 fsplit = f.split(".") pattern = "/" + fsplit[0] + ".mutant.*." + fsplit[-1] diff --git a/universalmutator/genmutants.py b/universalmutator/genmutants.py index e7352a0..0ae8834 100644 --- a/universalmutator/genmutants.py +++ b/universalmutator/genmutants.py @@ -1,9 +1,9 @@ from __future__ import print_function from tabulate import tabulate +import argparse import os import random -from re import T import sys import shutil import subprocess @@ -87,151 +87,69 @@ def main(): except BaseException: pass - args = sys.argv - - languages = {".c": "c", - ".h": "c", - ".cpp": "cpp", - ".c++": "cpp", - ".cc": "cpp", - ".hpp": "cpp", - ".py": "python", - ".java": "java", - ".js": "javascript", - ".ts": "javascript", - ".swift": "swift", - ".rs": "rust", - ".go": "go", - ".lisp": "lisp", - ".lsp": "lisp", - ".f": "fortran", - ".f90": "fortran", - ".for": "fortran", - ".R": "r", - ".sol": "solidity", - ".vy": "vyper", - ".fe": "fe"} - - print("*** UNIVERSALMUTATOR ***") - - if ("--help" in args) or (len(sys.argv) < 2): - if len(sys.argv) < 2: - print("ERROR: mutate requires at least one argument (a file to mutate)\n") - print("USAGE: mutate [] [ ...]", - "[--noCheck] [--cmd ] [--mutantDir ]", - "[--lines [--tstl]] [--mutateTestCode] [--mutateBoth]", - "[--ignore ] [--compile ] [--noFastCheck] [--swap]", - "[--redundantOK] [--showRules] [--only ]") - print() - print(" --noCheck: skips compilation/comparison and just generates mutant files") - print(" --cmd executes command string, replacing MUTANT with the mutant name, and uses return code") - print(" to determine mutant validity") - print(" --mutantDir: directory to put generated mutants in; defaults to current directory") - print(" --lines: only generate mutants for lines contained in ") - print(" --tstl: is TSTL output") - print(" --mutateInStrings: mutate inside strings (not just turn to empty string)") - print(" --mutateTestCode: mutate only test code") - print(" --mutateBoth: mutate both test and normal code") - print(" --ignore : ignore lines matching patterns in ") - print(" --compile : compile instead of source (solidity handler only)") - print(" --comby: use comby as the method of mutating code") - print(" --noFastCheck: do not use fast dead code/comment detection heuristic") - print(" --swap: also try adjacent-code swaps") - print(" --redundantOK: keep redundant mutants (for compiler output issues)") - print(" --showRules: show rule source used to generate each mutant") - print(" --only : only use rule file ") - print(" --printStat: print stats for the rules and generated mutants into files") - print() - print("Currently supported languages: ", ", ".join(list(set(languages.values())))) - print("If not supplying a command to compile/build, you should use --noCheck for C, C++,") - print("javascript, and other languages with only a default handler.") - sys.exit(0) - - noCheck = False - if "--noCheck" in args: - noCheck = True - args.remove("--noCheck") - - comby = False - if "--comby" in args: - comby = True - args.remove("--comby") - - redundantOK = False - if "--redundantOK" in args: - redundantOK = True - args.remove("--redundantOK") - - showRules = False - if "--showRules" in args: - showRules = True - args.remove("--showRules") - - mutateInStrings = False - if "--mutateInStrings" in args: - mutateInStrings = True - args.remove("--mutateInStrings") - - mutateTestCode = False - if "--mutateTestCode" in args: - mutateTestCode = True - args.remove("--mutateTestCode") - - mutateBoth = False - if "--mutateBoth" in args: - mutateBoth = True - args.remove("--mutateBoth") - - noFastCheck = False - if "--noFastCheck" in args: - noFastCheck = True - args.remove("--noFastCheck") - - doSwaps = False - if "--swap" in args: - doSwaps = True - args.remove("--swap") - - tstl = False - if "--tstl" in args: - tstl = True - args.remove("--tstl") - - fuzz = False - if "--fuzz" in args: - fuzz = True - args.remove("--fuzz") - - printStat = False - if "--printStat" in args: - printStat = True - args.remove("--printStat") - - cmd = None - try: - cmdpos = args.index("--cmd") - except ValueError: - cmdpos = -1 - - if cmdpos != -1: - cmd = args[cmdpos + 1] - args.remove("--cmd") - args.remove(cmd) - - sourceFile = args[1] + parser = argparse.ArgumentParser(prog="mutate", + description="Generate mutants from a source file.") + parser.add_argument("source", help="source file to mutate") + parser.add_argument("language_or_rules", nargs="?", default=None, + help="optional language name or first .rules file") + parser.add_argument("rules", nargs="*", default=[], + help="additional rule files") + parser.add_argument("--noCheck", action="store_true", default=False, + help="skip compilation/comparison and just generate mutant files") + parser.add_argument("--comby", action="store_true", default=False, + help="use comby as the method of mutating code") + parser.add_argument("--redundantOK", action="store_true", default=False, + help="keep redundant mutants") + parser.add_argument("--showRules", action="store_true", default=False, + help="show rule source used to generate each mutant") + parser.add_argument("--mutateInStrings", action="store_true", default=False, + help="mutate inside strings") + parser.add_argument("--mutateTestCode", action="store_true", default=False, + help="mutate only test code") + parser.add_argument("--mutateBoth", action="store_true", default=False, + help="mutate both test and normal code") + parser.add_argument("--noFastCheck", action="store_true", default=False, + help="do not use fast dead code/comment detection heuristic") + parser.add_argument("--swap", action="store_true", default=False, + help="also try adjacent-code swaps") + parser.add_argument("--tstl", action="store_true", default=False, + help="coverfile is TSTL output") + parser.add_argument("--fuzz", action="store_true", default=False, + help="fuzzing mode: pick one random mutant") + parser.add_argument("--printStat", action="store_true", default=False, + help="print stats for the rules and generated mutants into files") + parser.add_argument("--cmd", default=None, + help="command string; MUTANT replaced with the mutant name") + parser.add_argument("--lines", default=None, + help="only generate mutants for lines contained in this file") + parser.add_argument("--mutantDir", default=".", + help="directory to put generated mutants in; defaults to current directory") + parser.add_argument("--ignore", default=None, + help="ignore lines matching patterns in this file") + parser.add_argument("--compile", default=None, + help="compile this file instead of source (solidity handler only)") + parser.add_argument("--only", default=None, + help="only use this rule file") + parsed = parser.parse_args() + + noCheck = parsed.noCheck + comby = parsed.comby + redundantOK = parsed.redundantOK + showRules = parsed.showRules + mutateInStrings = parsed.mutateInStrings + mutateTestCode = parsed.mutateTestCode + mutateBoth = parsed.mutateBoth + noFastCheck = parsed.noFastCheck + doSwaps = parsed.swap + tstl = parsed.tstl + fuzz = parsed.fuzz + printStat = parsed.printStat + cmd = parsed.cmd + + sourceFile = parsed.source ending = "." + sourceFile.split(".")[-1] - lineFile = None - try: - linepos = args.index("--lines") - except ValueError: - linepos = -1 - - if linepos != -1: - lineFile = args[linepos + 1] - args.remove("--lines") - args.remove(lineFile) - + lineFile = parsed.lines if lineFile is not None: with open(lineFile) as file: if not tstl: @@ -247,40 +165,12 @@ def main(): for line in d: lines.append(int(line)) - mdir = "." - try: - mdirpos = args.index("--mutantDir") - except ValueError: - mdirpos = -1 - - if mdirpos != -1: - mdir = args[mdirpos + 1] - args.remove("--mutantDir") - args.remove(mdir) + mdir = parsed.mutantDir if mdir[-1] != "/": mdir += "/" - ignoreFile = None - try: - ignorepos = args.index("--ignore") - except ValueError: - ignorepos = -1 - - if ignorepos != -1: - ignoreFile = args[ignorepos + 1] - args.remove("--ignore") - args.remove(ignoreFile) - - compileFile = None - try: - compilepos = args.index("--compile") - except ValueError: - compilepos = -1 - - if compilepos != -1: - compileFile = args[compilepos + 1] - args.remove("--compile") - args.remove(compileFile) + ignoreFile = parsed.ignore + compileFile = parsed.compile ignorePatterns = [] if ignoreFile is not None: @@ -289,6 +179,31 @@ def main(): for l in ignoref: ignorePatterns.append(l[:-1]) + languages = {".c": "c", + ".h": "c", + ".cpp": "cpp", + ".c++": "cpp", + ".cc": "cpp", + ".hpp": "cpp", + ".py": "python", + ".java": "java", + ".js": "javascript", + ".ts": "javascript", + ".swift": "swift", + ".rs": "rust", + ".go": "go", + ".lisp": "lisp", + ".lsp": "lisp", + ".f": "fortran", + ".f90": "fortran", + ".for": "fortran", + ".R": "r", + ".sol": "solidity", + ".vy": "vyper", + ".fe": "fe"} + + print("*** UNIVERSALMUTATOR ***") + handlers = {"python": python_handler, "c": c_handler, "c++": cpp_handler, @@ -321,24 +236,22 @@ def main(): except BaseException: pass - sourceFile = args[1] base = (".".join((sourceFile.split(".")[:-1]))).split("/")[-1] - ending = "." + sourceFile.split(".")[-1] - if "--only" not in args: - if len(args) < 3: + if parsed.only is None: + if parsed.language_or_rules is None: try: language = languages[ending] except KeyError: language = "none" otherRules = [] else: - if ".rules" in args[2]: + if ".rules" in parsed.language_or_rules: language = languages[ending] - otherRules = args[2:] + otherRules = [parsed.language_or_rules] + parsed.rules else: - language = args[2] - otherRules = args[3:] + language = parsed.language_or_rules + otherRules = parsed.rules if language not in handlers: if language.lower() in handlers: @@ -361,10 +274,9 @@ def main(): fuzzRules = ["universal.rules", "c_like.rules", "python.rules", "vyper.rules", "solidity.rules"] rules = list(set(fuzzRules + rules)) else: - onlyPos = args.index("--only") - rules = [args[onlyPos + 1]] - if args[2] != "--only": - language = args[2] + rules = [parsed.only] + if parsed.language_or_rules is not None and ".rules" not in parsed.language_or_rules: + language = parsed.language_or_rules else: try: language = languages[ending] diff --git a/universalmutator/intersect.py b/universalmutator/intersect.py index e960dd2..4cb3210 100644 --- a/universalmutator/intersect.py +++ b/universalmutator/intersect.py @@ -1,20 +1,19 @@ from __future__ import print_function -import sys +import argparse def main(): - args = sys.argv + parser = argparse.ArgumentParser(prog="intersect_mutants", + description="Write the intersection of two mutant list files to an output file.") + parser.add_argument("infile1", help="first input mutant list file") + parser.add_argument("infile2", help="second input mutant list file") + parser.add_argument("outfile", help="output file for the intersection") + parsed = parser.parse_args() - if ("--help" in args) or (len(sys.argv) < 2): - if len(sys.argv) < 2: - print("ERROR: intersect_mutants requires at least three arguments\n") - print("USAGE: intersect_mutants ") - sys.exit(0) - - infile1 = sys.argv[1] - infile2 = sys.argv[2] - outfile = sys.argv[3] + infile1 = parsed.infile1 + infile2 = parsed.infile2 + outfile = parsed.outfile infile1_mutants = [] with open(infile1, 'r') as if1: diff --git a/universalmutator/prioritize.py b/universalmutator/prioritize.py index 003efa4..8deb873 100644 --- a/universalmutator/prioritize.py +++ b/universalmutator/prioritize.py @@ -1,82 +1,47 @@ from __future__ import print_function +import argparse import glob -import sys from universalmutator import utils def main(): - args = sys.argv - - if ("--help" in args) or (len(sys.argv) < 3): - if len(sys.argv) < 3: - print("ERROR: prioritize_mutants requires at least two arguments\n") - print("USAGE: prioritize_mutants [N] [--cutoff ]", end="") - print("[--mutantDir ] [--sourceDir ]") - print(" --verbose: produce verbose output") - print(" --noSDPriority: do not prioritize statement deletions over other mutants") - print(" --mutantDir: directory with all mutants; defaults to current directory") - print(" --sourceDir: directory of source files; defaults to current directory") - print(" --cutoff: if minimum distance is less than , stop") - sys.exit(0) - - infile = sys.argv[1] - outfile = sys.argv[2] - - infiles = glob.glob(infile) - - verbose = False - if "--verbose" in args: - args.remove("--verbose") - verbose = True - - noSDPriority = False - if "--noSDPriority" in args: - args.remove("--noSDPriority") - noSDPriority = True - - mdir = "." - try: - mdirpos = args.index("--mutantDir") - except ValueError: - mdirpos = -1 - - if mdirpos != -1: - mdir = args[mdirpos + 1] - args.remove("--mutantDir") - args.remove(mdir) + parser = argparse.ArgumentParser(prog="prioritize_mutants", + description="Prioritize a set of mutants using furthest-point-first ranking.") + parser.add_argument("infile", help="input file (glob pattern supported) listing mutants") + parser.add_argument("outfile", help="output file for prioritized mutant list") + parser.add_argument("N", nargs="?", type=int, default=None, + help="optional maximum number of mutants to select") + parser.add_argument("--verbose", action="store_true", default=False, + help="produce verbose output") + parser.add_argument("--noSDPriority", action="store_true", default=False, + help="do not prioritize statement deletions over other mutants") + parser.add_argument("--mutantDir", default=".", + help="directory with all mutants; defaults to current directory") + parser.add_argument("--sourceDir", default=".", + help="directory of source files; defaults to current directory") + parser.add_argument("--cutoff", type=float, default=0.0, + help="if minimum distance is less than this value, stop") + parsed = parser.parse_args() + + infile = parsed.infile + outfile = parsed.outfile + verbose = parsed.verbose + noSDPriority = parsed.noSDPriority + cutoff = parsed.cutoff + + mdir = parsed.mutantDir if mdir[-1] != "/": mdir += "/" - sdir = "." - try: - sdirpos = args.index("--sourceDir") - except ValueError: - sdirpos = -1 - - if sdirpos != -1: - sdir = args[sdirpos + 1] - args.remove("--sourceDir") - args.remove(sdir) + sdir = parsed.sourceDir if sdir[-1] != "/": sdir += "/" - cutoff = 0.0 - try: - cutoffpos = args.index("--cutoff") - except ValueError: - cutoffpos = -1 - - if cutoffpos != -1: - cutoff = args[cutoffpos + 1] - args.remove("--cutoff") - args.remove(cutoff) - cutoff = float(cutoff) - - N = -1 - if len(args) >= 4: - N = int(args[3]) + N = parsed.N if parsed.N is not None else -1 + + infiles = glob.glob(infile) mutants = [] for f in infiles: diff --git a/universalmutator/prune.py b/universalmutator/prune.py index 9a81009..8ee62eb 100644 --- a/universalmutator/prune.py +++ b/universalmutator/prune.py @@ -1,49 +1,32 @@ from __future__ import print_function +import argparse import re -import sys from universalmutator import utils def main(): - args = sys.argv - - if ("--help" in args) or (len(sys.argv) < 2): - if len(sys.argv) < 2: - print("ERROR: show_mutants requires at least one argument\n") - print("USAGE: prune_mutants [--mutantDir ] [--sourceDir ]") - print(" --mutantDir: directory with all mutants; defaults to current directory") - print(" --sourceDir: directory of source files; defaults to current directory") - sys.exit(0) - - infile = sys.argv[1] - outfile = sys.argv[2] - config = sys.argv[3] - - mdir = "." - try: - mdirpos = args.index("--mutantDir") - except ValueError: - mdirpos = -1 - - if mdirpos != -1: - mdir = args[mdirpos + 1] - args.remove("--mutantDir") - args.remove(mdir) + parser = argparse.ArgumentParser(prog="prune_mutants", + description="Prune a mutant list using a constraint configuration file.") + parser.add_argument("infile", help="input file listing mutants") + parser.add_argument("outfile", help="output file for the pruned mutant list") + parser.add_argument("config", help="pruning configuration file") + parser.add_argument("--mutantDir", default=".", + help="directory with all mutants; defaults to current directory") + parser.add_argument("--sourceDir", default=".", + help="directory of source files; defaults to current directory") + parsed = parser.parse_args() + + infile = parsed.infile + outfile = parsed.outfile + config = parsed.config + + mdir = parsed.mutantDir if mdir[-1] != "/": mdir += "/" - sdir = "." - try: - sdirpos = args.index("--sourceDir") - except ValueError: - sdirpos = -1 - - if sdirpos != -1: - sdir = args[sdirpos + 1] - args.remove("--sourceDir") - args.remove(sdir) + sdir = parsed.sourceDir if sdir[-1] != "/": sdir += "/" diff --git a/universalmutator/show.py b/universalmutator/show.py index b63bd68..b370202 100644 --- a/universalmutator/show.py +++ b/universalmutator/show.py @@ -1,51 +1,30 @@ from __future__ import print_function -import sys +import argparse from universalmutator import utils def main(): - args = sys.argv - - if ("--help" in args) or (len(sys.argv) < 2): - if len(sys.argv) < 2: - print("ERROR: show_mutants requires at least one argument\n") - print("USAGE: show_mutants [--mutantDir ] [--sourceDir ]") - print(" --mutantDir: directory with all mutants; defaults to current directory") - print(" --sourceDir: directory of source files; defaults to current directory") - print(" --concise: display in concise mutant format") - sys.exit(0) - - infile = sys.argv[1] - - concise = "--concise" in sys.argv - if concise: - args.remove("--concise") - - mdir = "." - try: - mdirpos = args.index("--mutantDir") - except ValueError: - mdirpos = -1 - - if mdirpos != -1: - mdir = args[mdirpos + 1] - args.remove("--mutantDir") - args.remove(mdir) + parser = argparse.ArgumentParser(prog="show_mutants", + description="Display mutants listed in an input file.") + parser.add_argument("infile", help="input file listing mutants") + parser.add_argument("--concise", action="store_true", default=False, + help="display in concise mutant format") + parser.add_argument("--mutantDir", default=".", + help="directory with all mutants; defaults to current directory") + parser.add_argument("--sourceDir", default=".", + help="directory of source files; defaults to current directory") + parsed = parser.parse_args() + + infile = parsed.infile + concise = parsed.concise + + mdir = parsed.mutantDir if mdir[-1] != "/": mdir += "/" - sdir = "." - try: - sdirpos = args.index("--sourceDir") - except ValueError: - sdirpos = -1 - - if sdirpos != -1: - sdir = args[sdirpos + 1] - args.remove("--sourceDir") - args.remove(sdir) + sdir = parsed.sourceDir if sdir[-1] != "/": sdir += "/"