[tor-commits] [arm/master] Alternate function for parsing arguments

atagar at torproject.org atagar at torproject.org
Sun Sep 15 22:29:20 UTC 2013


commit 435de196896e5b170777edfc1b2331e733997172
Author: Damian Johnson <atagar at torproject.org>
Date:   Sun Sep 8 14:06:50 2013 -0700

    Alternate function for parsing arguments
    
    Adding a _get_args() function for parsing commandline arguments and providing
    back a named tuple. This is similar to what I'm doing for stem's run_tests.py
    argument parsing, and allows for much cleaner code than the present config
    mess.
    
    This also includes unit tests for the new function. My plan is to add tests as
    I incrementally rewrite arm, but being a curses app I'm not sure if we'll be
    able to achieve too much coverage. Still, any bit helps.
    
    The _get_args() function is not yet used. It's gonna take a bit of work to
    untangle the present config starter parameters.
---
 arm/starter.py  |   82 +++++++++++++++++++++++++++++++++++++++++++++++++++----
 run_tests.py    |   14 ++++++++++
 test/starter.py |   66 ++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 157 insertions(+), 5 deletions(-)

diff --git a/arm/starter.py b/arm/starter.py
index 0131e97..7fe8d92 100644
--- a/arm/starter.py
+++ b/arm/starter.py
@@ -6,10 +6,14 @@ information. This is the starter for the application, handling and validating
 command line parameters.
 """
 
+import collections
+import getopt
+
+import stem.util.connection
+
 import os
 import sys
 import time
-import getopt
 import getpass
 import locale
 import logging
@@ -46,13 +50,9 @@ CONFIG = stem.util.conf.config_dict("arm", {
   "features.config.descriptions.persist": True,
 })
 
-OPT = "gi:s:c:dbe:vh"
-OPT_EXPANDED = ["interface=", "socket=", "config=", "debug", "blind", "event=", "version", "help"]
-
 HELP_MSG = """Usage arm [OPTION]
 Terminal status monitor for Tor relays.
 
-  -p, --prompt                    only start the control interpretor
   -i, --interface [ADDRESS:]PORT  change control interface from %s:%i
   -s, --socket SOCKET_PATH        attach using unix domain socket if present,
                                     SOCKET_PATH defaults to: %s
@@ -99,6 +99,78 @@ ARM_ROOT_NOTICE = "Arm is currently running with root permissions. This is not a
 
 os.putenv("LANG", "C")
 
+# Our default arguments. The _get_args() function provides a named tuple of
+# this merged with our argv.
+
+ARGS = {
+  'control_address': '127.0.0.1',
+  'control_port': 9051,
+  'control_socket': '/var/run/tor/control',
+  'config': None,
+  'debug': False,
+  'blind': False,
+  'logged_events': 'N3',
+  'print_version': False,
+  'print_help': False,
+}
+
+OPT = "gi:s:c:dbe:vh"
+OPT_EXPANDED = ["interface=", "socket=", "config=", "debug", "blind", "event=", "version", "help"]
+
+
+def _get_args(argv):
+  """
+  Parses our arguments, providing a named tuple with their values.
+
+  :param list argv: input arguments to be parsed
+
+  :returns: a **named tuple** with our parsed arguments
+
+  :raises: **ValueError** if we got an invalid argument
+  :raises: **getopt.GetoptError** if the arguments don't conform with what we
+    accept
+  """
+
+  args = dict(ARGS)
+
+  for opt, arg in getopt.getopt(argv, OPT, OPT_EXPANDED)[0]:
+    if opt in ("-i", "--interface"):
+      if ':' in arg:
+        address, port = arg.split(':', 1)
+      else:
+        address, port = None, arg
+
+      if address is not None:
+        if not stem.util.connection.is_valid_ipv4_address(address):
+          raise ValueError("'%s' isn't a valid IPv4 address" % address)
+
+        args['control_address'] = address
+
+      if not stem.util.connection.is_valid_port(port):
+        raise ValueError("'%s' isn't a valid port number" % port)
+
+      args['control_port'] = int(port)
+    elif opt in ("-s", "--socket"):
+      args['control_socket'] = arg
+    elif opt in ("-c", "--config"):
+      args['config'] = arg
+    elif opt in ("-d", "--debug"):
+      args['debug'] = True
+    elif opt in ("-b", "--blind"):
+      args['blind'] = True
+    elif opt in ("-e", "--event"):
+      args['logged_events'] = arg
+    elif opt in ("-v", "--version"):
+      args['print_version'] = True
+    elif opt in ("-h", "--help"):
+      args['print_help'] = True
+
+  # translates our args dict into a named tuple
+
+  Args = collections.namedtuple('Args', args.keys())
+  return Args(**args)
+
+
 def allowConnectionTypes():
   """
   This provides a tuple with booleans indicating if we should or shouldn't
diff --git a/run_tests.py b/run_tests.py
new file mode 100755
index 0000000..2fb88de
--- /dev/null
+++ b/run_tests.py
@@ -0,0 +1,14 @@
+#!/usr/bin/env python
+# Copyright 2013, Damian Johnson and The Tor Project
+# See LICENSE for licensing information
+
+"""
+Runs arm's unit tests. This is a curses application so we're pretty limited on
+the test coverage we can achieve, but exercising what we can.
+"""
+
+import unittest
+
+tests = unittest.defaultTestLoader.discover('test', pattern='*.py')
+test_runner = unittest.TextTestRunner()
+test_runner.run(tests)
diff --git a/test/starter.py b/test/starter.py
new file mode 100644
index 0000000..d4720b1
--- /dev/null
+++ b/test/starter.py
@@ -0,0 +1,66 @@
+"""
+Unit tests for arm's initialization module.
+"""
+
+import unittest
+
+from arm.starter import _get_args, ARGS
+
+class TestArgumentParsing(unittest.TestCase):
+  def test_that_we_get_default_values(self):
+    args = _get_args([])
+
+    for attr in ARGS:
+      self.assertEqual(ARGS[attr], getattr(args, attr))
+
+  def test_that_we_load_arguments(self):
+    args = _get_args(['--interface', '10.0.0.25:80'])
+    self.assertEqual('10.0.0.25', args.control_address)
+    self.assertEqual(80, args.control_port)
+
+    args = _get_args(['--interface', '80'])
+    self.assertEqual(ARGS['control_address'], args.control_address)
+    self.assertEqual(80, args.control_port)
+
+    args = _get_args(['--socket', '/tmp/my_socket', '--config', '/tmp/my_config'])
+    self.assertEqual('/tmp/my_socket', args.control_socket)
+    self.assertEqual('/tmp/my_config', args.config)
+
+    args = _get_args(['--debug', '--blind'])
+    self.assertEqual(True, args.debug)
+    self.assertEqual(True, args.blind)
+
+    args = _get_args(['--event', 'D1'])
+    self.assertEqual('D1', args.logged_events)
+
+    args = _get_args(['--version'])
+    self.assertEqual(True, args.print_version)
+
+    args = _get_args(['--help'])
+    self.assertEqual(True, args.print_help)
+
+  def test_examples(self):
+    args = _get_args(['-b', '-i', '1643'])
+    self.assertEqual(True, args.blind)
+    self.assertEqual(1643, args.control_port)
+
+    args = _get_args(['-e', 'we', '-c', '/tmp/cfg'])
+    self.assertEqual('we', args.logged_events)
+    self.assertEqual('/tmp/cfg', args.config)
+
+  def test_that_we_reject_invalid_interfaces(self):
+    invalid_inputs = (
+      '',
+      '    ',
+      'blarg',
+      '127.0.0.1',
+      '127.0.0.1:',
+      ':80',
+      '400.0.0.1:80',
+      '127.0.0.1:-5',
+      '127.0.0.1:500000',
+    )
+
+    for invalid_input in invalid_inputs:
+      self.assertRaises(ValueError, _get_args, ['--interface', invalid_input])
+





More information about the tor-commits mailing list