From 63bc2df04c02b6eb489aaaa3e31c8f07ec40e57e Mon Sep 17 00:00:00 2001 From: Ariyan Eghbal Date: Sat, 18 Jun 2022 22:59:19 +0430 Subject: [PATCH] feature: unittests (#57) * feature: unittests * [fix+feature]: fix config file test and add codecoverage - `test_config_file` unittest fixed for missing keys in config file Now it tests against existing keys in config file and ignores the rest (the rest of keys are default and previously are tested in `test_defaults`) * [feature]: add unittest github action a github action file for running unittest on PR and pushes with action result badge in README.md * [fix]: fix test dir name * [fix]: fix GH action for requirements * [fix]: typo fix * update new arguments from main updates now supports port option --- .github/workflows/unittest.yml | 22 ++++++ .gitignore | 1 + README.md | 2 + requirements.txt | 2 +- test/__init__.py | 0 test/test_operationality.py | 121 +++++++++++++++++++++++++++++++++ tracevis.py | 8 +-- 7 files changed, 151 insertions(+), 5 deletions(-) create mode 100644 .github/workflows/unittest.yml create mode 100644 test/__init__.py create mode 100644 test/test_operationality.py diff --git a/.github/workflows/unittest.yml b/.github/workflows/unittest.yml new file mode 100644 index 0000000..720419e --- /dev/null +++ b/.github/workflows/unittest.yml @@ -0,0 +1,22 @@ +name: UnitTest + +on: + pull_request: + push: + +jobs: + unittest: + runs-on: ubuntu-latest + name: Run UnitTests + steps: + - uses: actions/checkout@v2 + - name: Set up Python + id: setup-python + uses: actions/setup-python@v2 + with: + python-version: 3 + - name: Install requirements + run: pip install -r requirements.txt + - name: Launch tests + run: python -m unittest discover test + diff --git a/.gitignore b/.gitignore index 0179ed4..19afeff 100755 --- a/.gitignore +++ b/.gitignore @@ -129,3 +129,4 @@ dmypy.json .pyre/ tracevis_data/ +.coverage diff --git a/README.md b/README.md index 043bc5b..18529f6 100755 --- a/README.md +++ b/README.md @@ -3,6 +3,8 @@ Traceroute with any packet. Visualize the routes. Discover Middleboxes and Firew [![CodeQL](https://github.com/wikicensorship/tracevis/actions/workflows/codeql-analysis.yml/badge.svg)](https://github.com/wikicensorship/tracevis/actions/workflows/codeql-analysis.yml) [![Dockerise](https://github.com/wikicensorship/tracevis/actions/workflows/docker.yml/badge.svg)](https://github.com/wikicensorship/tracevis/actions/workflows/docker.yml) +[![unittest](https://github.com/wikicensorship/tracevis/actions/workflows/unittest.yml/badge.svg)](https://github.com/wikicensorship/tracevis/actions/workflows/unittest.yml) + TraceVis is a research project whose main goal is to find middleboxes. Where a packet is tampered with or blocked. This tool also has other features such as downloading and visualizing traceroute data from RIPE Atlas probes. diff --git a/requirements.txt b/requirements.txt index b532f95..47bdcc6 100755 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,2 @@ scapy -pyvis \ No newline at end of file +pyvis diff --git a/test/__init__.py b/test/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test/test_operationality.py b/test/test_operationality.py new file mode 100644 index 0000000..37e2ee3 --- /dev/null +++ b/test/test_operationality.py @@ -0,0 +1,121 @@ +import unittest +import tracevis +import sys + + +class TestArguments(unittest.TestCase): + def test_help(self): + from io import StringIO + out,err = StringIO(), StringIO() + sys.stdout, sys.stderr = out, err + with self.assertRaises(SystemExit): + tracevis.get_args(['-h'], auto_exit=True) + self.assertIn(err.getvalue(), "usage:") + sys.stdout, sys.stderr = sys.__stdout__, sys.__stderr__ + + def test_no_args(self): + from io import StringIO + out,err = StringIO(), StringIO() + sys.stdout, sys.stderr = out, err + with self.assertRaises(SystemExit): + tracevis.get_args([], auto_exit=True) + self.assertIn(err.getvalue(), "usage:") + sys.stdout, sys.stderr = sys.__stdout__, sys.__stderr__ + + + def test_defaults(self): + from io import StringIO + out,err = StringIO(), StringIO() + sys.stdout, sys.stderr = out, err + args = tracevis.get_args([], auto_exit=False) + expected = {'config_file': None, 'name': None, 'ips': None, 'packet': False, 'packet_input_method': 'hex', + 'packet_data': None, 'dns': False, 'dnstcp': False, 'continue': False, 'maxttl': None, + 'timeout': None, 'repeat': None, 'ripe': None, 'ripemids': None, 'file': None, 'csv': False, + 'csvraw': False, 'attach': False, 'label': None, 'domain1': None, 'domain2': None, 'annot1': None, + 'annot2': None, 'rexmit': False, 'paris': False, 'options': 'new', 'iface': None, 'show_ifaces': False, 'port': None} + self.assertEqual(args, expected) + sys.stdout, sys.stderr = sys.__stdout__, sys.__stderr__ + + def test_config_file(self): + from io import StringIO + import os, json + md = self.maxDiff + self.maxDiff = None + out,err = StringIO(), StringIO() + sys.stdout, sys.stderr = out, err + samples_dir = 'samples/' + for file in os.listdir(samples_dir): + args = tracevis.get_args(['--config-file', os.path.join(samples_dir, file)], auto_exit=False) + with open(os.path.join(samples_dir, file), 'r') as f: + expected = json.load(f) + del args['config_file'] + for k,v in args.items(): + if k in expected: + self.assertEqual(v, expected[k]) + + sys.stdout, sys.stderr = sys.__stdout__, sys.__stderr__ + self.maxDiff = md + + def test_dns_mode(self): + from io import StringIO + out,err = StringIO(), StringIO() + sys.stdout, sys.stderr = out, err + args = tracevis.get_args(['--dns'], auto_exit=False) + expected = {'config_file': None, 'name': None, 'ips': None, 'packet': False, 'packet_input_method': None, + 'packet_data': None, 'dns': True, 'dnstcp': False, 'continue': False, 'maxttl': None, + 'timeout': None, 'repeat': None, 'ripe': None, 'ripemids': None, 'file': None, 'csv': False, + 'csvraw': False, 'attach': False, 'label': None, 'domain1': None, 'domain2': None, 'annot1': None, + 'annot2': None, 'rexmit': False, 'paris': False, 'options': 'new', 'iface': None, 'show_ifaces': False, 'port': None} + self.assertEqual(args, expected) + sys.stdout, sys.stderr = sys.__stdout__, sys.__stderr__ + + def test_packet_mode(self): + from io import StringIO + out,err = StringIO(), StringIO() + sys.stdout, sys.stderr = out, err + args = tracevis.get_args(['--packet'], auto_exit=False) + expected = {'config_file': None, 'name': None, 'ips': None, 'packet': True, 'packet_input_method': 'hex', + 'packet_data': None, 'dns': False, 'dnstcp': False, 'continue': False, 'maxttl': None, + 'timeout': None, 'repeat': None, 'ripe': None, 'ripemids': None, 'file': None, 'csv': False, + 'csvraw': False, 'attach': False, 'label': None, 'domain1': None, 'domain2': None, 'annot1': None, + 'annot2': None, 'rexmit': False, 'paris': False, 'options': 'new', 'iface': None, 'show_ifaces': False, 'port': None} + self.assertEqual(args, expected) + sys.stdout, sys.stderr = sys.__stdout__, sys.__stderr__ + + def test_packet_input_types(self): + from io import StringIO + out,err = StringIO(), StringIO() + sys.stdout, sys.stderr = out, err + args = tracevis.get_args(['--packet', '--packet-input-method', 'hex'], auto_exit=False) + expected = {'config_file': None, 'name': None, 'ips': None, 'packet': True, 'packet_input_method': 'hex', + 'packet_data': None, 'dns': False, 'dnstcp': False, 'continue': False, 'maxttl': None, + 'timeout': None, 'repeat': None, 'ripe': None, 'ripemids': None, 'file': None, 'csv': False, + 'csvraw': False, 'attach': False, 'label': None, 'domain1': None, 'domain2': None, 'annot1': None, + 'annot2': None, 'rexmit': False, 'paris': False, 'options': 'new', 'iface': None, 'show_ifaces': False, 'port': None} + self.assertEqual(args, expected) + + args = tracevis.get_args(['--packet', '--packet-input-method', 'json'], auto_exit=False) + expected = {'config_file': None, 'name': None, 'ips': None, 'packet': True, 'packet_input_method': 'json', + 'packet_data': None, 'dns': False, 'dnstcp': False, 'continue': False, 'maxttl': None, + 'timeout': None, 'repeat': None, 'ripe': None, 'ripemids': None, 'file': None, 'csv': False, + 'csvraw': False, 'attach': False, 'label': None, 'domain1': None, 'domain2': None, 'annot1': None, + 'annot2': None, 'rexmit': False, 'paris': False, 'options': 'new', 'iface': None, 'show_ifaces': False, 'port': None} + self.assertEqual(args, expected) + + args = tracevis.get_args(['--packet', '--packet-input-method', 'interactive'], auto_exit=False) + expected = {'config_file': None, 'name': None, 'ips': None, 'packet': True, 'packet_input_method': 'interactive', + 'packet_data': None, 'dns': False, 'dnstcp': False, 'continue': False, 'maxttl': None, + 'timeout': None, 'repeat': None, 'ripe': None, 'ripemids': None, 'file': None, 'csv': False, + 'csvraw': False, 'attach': False, 'label': None, 'domain1': None, 'domain2': None, 'annot1': None, + 'annot2': None, 'rexmit': False, 'paris': False, 'options': 'new', 'iface': None, 'show_ifaces': False, 'port': None} + self.assertEqual(args, expected) + + args = tracevis.get_args(['--packet', '--packet-input-method', 'json', '--packet-data', 'b64:e30='], auto_exit=False) + expected = {'config_file': None, 'name': None, 'ips': None, 'packet': True, 'packet_input_method': 'json', + 'packet_data': 'b64:e30=', 'dns': False, 'dnstcp': False, 'continue': False, 'maxttl': None, + 'timeout': None, 'repeat': None, 'ripe': None, 'ripemids': None, 'file': None, 'csv': False, + 'csvraw': False, 'attach': False, 'label': None, 'domain1': None, 'domain2': None, 'annot1': None, + 'annot2': None, 'rexmit': False, 'paris': False, 'options': 'new', 'iface': None, 'show_ifaces': False, 'port': None} + self.assertEqual(args, expected) + sys.stdout, sys.stderr = sys.__stdout__, sys.__stderr__ + diff --git a/tracevis.py b/tracevis.py index 9a7b102..5b38721 100755 --- a/tracevis.py +++ b/tracevis.py @@ -84,7 +84,7 @@ def process_input_args(args, parser): return args_dict -def get_args(): +def get_args(sys_args, auto_exit=True): parser = argparse.ArgumentParser( description='Traceroute with any packet. \ Visualize the routes. Discover Middleboxes and Firewalls', formatter_class=argparse.RawTextHelpFormatter) @@ -155,10 +155,10 @@ def get_args(): help="set the target network interface") parser.add_argument('--show-ifaces', action='store_true', help="show the network interfaces (conf.route)") - if len(sys.argv) == 1: + if len(sys_args) == 0 and auto_exit: parser.print_help() sys.exit(1) - args = parser.parse_args() + args = parser.parse_args(sys_args) args_dict = process_input_args(args, parser) return args_dict @@ -339,4 +339,4 @@ def main(args): if __name__ == "__main__": - main(get_args()) + main(get_args(sys.argv[1:]))