autotest: IPv6 support

IPv4 requires a DHCP server. IPv4 addresses change more often than
IPv6 ones, which breaks long-running tests. IPv4 addresses are mangled
by NATs. etc.

Unit tests:
- Extend coverage with additional tests.
- Adjust exception types in some negative tests.
- "host:port" negative test removed since port detection is now
  stricter, considering digits only. Preserving the laxer detection
  would make the code a bit more complex for no real value.
- "host:22:33" is now mistaken for an IPv6 address.  Remove this
  negative test too since there isn't anymore a way to invalidate
  without adding / backporting from Python 3.3 some minimal IPv6
  address parser.

Manually tested these two types of input and they still fail; however
failures come later as "Could not resolve hostname" network errors,
beyond the reach of unit tests.

BUG=none
TEST=./utils/unittest_suite.py --debug -r server
TEST=\
  v4=10.1.2.3
  v6=fe80::9249:faff:fe00:7400%p4p1
  hname=yourdut.local
  mytest=hardware_DiskSize
  (set -ex;
   for a in $v4 ${v4}:22 $v6 [$v6] [$v6]:22 $hname ${hname}:22; do
       test_that "$a" $mytest
       stat /tmp/test_that_latest/results-1-"$mytest/$mytest/"
   done ); [ $? -eq 0 ] || echo FAILED
  # cannot trust autotest's "PASS", see http://crbug.com/555073
TEST=\
  # Disable rsync on DUT:
  mount -o remount,rw /
 ( cd /usr/local/bin/; mv rsync rsync.disabled )
  # On host:
  #  run again same test loop above (this time with "scp" instead of rsync)

Change-Id: Icb7d232a9f64d1277810758a5c6c16d5711a0163
Reviewed-on: https://chromium-review.googlesource.com/312719
Commit-Ready: Marc Herbert <[email protected]>
Tested-by: Michael W Mason <[email protected]>
Reviewed-by: Dan Shi <[email protected]>
Reviewed-by: Michael W Mason <[email protected]>
diff --git a/server/base_utils_unittest.py b/server/base_utils_unittest.py
index feb9ace..9b291c0 100755
--- a/server/base_utils_unittest.py
+++ b/server/base_utils_unittest.py
@@ -34,6 +34,19 @@
                     ('user@host',           ('host', 'user', '', 22)),
                     ('user:pass@host',      ('host', 'user', 'pass', 22)),
                     ('user:pass@host:1234', ('host', 'user', 'pass', 1234)),
+
+                    ('user:[email protected]',
+                     ('10.3.2.1', 'user', 'pass', 22)),
+                    ('user:[email protected]:1234',
+                     ('10.3.2.1', 'user', 'pass', 1234)),
+
+                    ('::1',                 ('::1', 'root', '', 22)),
+                    ('user:pass@abdc::ef',  ('abdc::ef', 'user', 'pass', 22)),
+                    ('abdc::ef:99',         ('abdc::ef:99', 'root', '', 22)),
+                    ('user:pass@[abdc::ef:99]',
+                     ('abdc::ef:99', 'user', 'pass', 22)),
+                    ('user:pass@[abdc::ef]:1234',
+                     ('abdc::ef', 'user', 'pass', 1234)),
                    )
         for machine, result in gooddata:
             self.assertEquals(utils.parse_machine(machine), result)
@@ -47,12 +60,12 @@
 
     def test_parse_machine_bad(self):
         '''test that bad data passed to parse_machine() will raise an exception'''
-        baddata = (('host:port', ValueError),   # pass a non-integer string for port
-                   ('host:22:33', ValueError),  # pass two ports
-                   (':22', ValueError),         # neglect to pass a hostname #1
-                   ('user@', ValueError),       # neglect to pass a hostname #2
-                   ('user@:22', ValueError),    # neglect to pass a hostname #3
+        baddata = ((':22', IndexError),         # neglect to pass a hostname #1
+                   ('user@', IndexError),       # neglect to pass a hostname #2
+                   ('user@:22', IndexError),    # neglect to pass a hostname #3
                    (':pass@host', ValueError),  # neglect to pass a username
+                   ('host:', ValueError),       # empty port after hostname
+                   ('[1::2]:', ValueError),     # empty port after IPv6
                   )
         for machine, exception in baddata:
             self.assertRaises(exception, utils.parse_machine, machine)