config.py: add command line options for querying the config table.
For example:
$ ./config has_moca -kname,ether,ip6,cpu,is_tv
t0-2,42:42:42:42:42:12,fe80::4042:42ff:fe42:4212,BCM7425B0,1
t0-3,42:21:06:66:aa:03,fe80::4021:6ff:fe66:aa03,BCM7425B0,1
b2-1,00:1a:11:30:63:77,fe80::21a:11ff:fe30:6377,BCM7425B2,
t0-4,00:03:42:21:03:69,fe80::203:42ff:fe21:369,BCM7425B0,1
Change-Id: I54679b19ea9c4ac5aee72312e539d9e441b2811f
diff --git a/Makefile b/Makefile
index 02ee80b..e3d52a3 100644
--- a/Makefile
+++ b/Makefile
@@ -10,7 +10,7 @@
lint:
gpylint *.py configs/core.py
-test: install-wvtest
+test:
wvtest/wvtestrun $(MAKE) runtests
#TODO(apenwarr): use a smarter allocator.
@@ -18,22 +18,26 @@
# looping through them one by one. But then we end up running multiple
# tests on the same device at once, which would cause conflicts. We need
# a smarter allocator first.
-runtests:
+runtests: localtests install-wvtest
for d in $(addsuffix .run,$(wildcard [0-9]*.sh [0-9]*.py)); do \
$(MAKE) $$d; \
done
-install-wvtest: map.stamp
- tar -cf - *.py *.sh wvtest/ | \
+localtests:
+ ./wvtest/wvtest.py *_test.py configs/*_test.py
+
+install-wvtest: map.stamp *.py *.sh wvtest/*
+ @echo 'Testing "install-wvtest" in Makefile:'
+ tar -cf - $^ | \
$(PORTSH) $(PORT) \
'mkdir -p /tmp/tests && cd /tmp/tests && tar -xf -'
-%.sh.run: %.sh install-wvtest
+%.sh.run: %.sh
echo "Testing $<"
$(PORTSH) $(PORT) \
'cd /tmp/tests && sh ./$<' </dev/null
-%.py.run: %.py install-wvtest
+%.py.run: %.py
echo "Testing $<"
$(PORTSH) $(PORT) \
'cd /tmp/tests && python ./wvtest/wvtest.py $<' </dev/null
diff --git a/config b/config
new file mode 120000
index 0000000..f85c6b1
--- /dev/null
+++ b/config
@@ -0,0 +1 @@
+config.py
\ No newline at end of file
diff --git a/config.py b/config.py
old mode 100644
new mode 100755
index 701d05c..9f65977
--- a/config.py
+++ b/config.py
@@ -3,13 +3,67 @@
#
"""Test configurations. Includes a list of auto-probed bruno devices."""
+import re
+import sys
from configs import hosts
+from configs.core import Host
+from configs.core import QueryError
+from portsh import options
__author__ = 'Avery Pennarun (apenwarr@google.com)'
+optspec = """
+config [options...] <bool|^bool|key=value>...
+--
+random Choose random matches instead of the first ones
+max= Return no more than max= matches
+min= If less than min= matches, return an error code
+n,num= Return exactly num= matches
+v,verbose Display the entire config entry for each match, not just the name
+k,keys= If -v isn't given, show these comma/space-separated attrs [name]
+"""
+
+
def main():
- print hosts
+ o = options.Options(optspec)
+ opt, _, extra = o.parse(sys.argv[1:])
+ if opt.num:
+ if opt.min or opt.max:
+ o.fatal("don't specify --num with --min/--max")
+ opt.min = opt.max = opt.num
+
+ kwargs = dict(is_alive=True)
+ for kvs in extra:
+ kv = kvs.split('=', 1) + [None]
+ key = kv[0]
+ value = kv[1]
+ if not hasattr(Host, key):
+ o.fatal('no key named %r; valid keys: %s'
+ % (key, ', '.join(Host.__slots__)))
+ sys.exit(1)
+ if key.startswith('^'):
+ kwargs[key[1:]] = False
+ elif value is not None:
+ kwargs[key] = value
+ else:
+ kwargs[key] = True
+ try:
+ matches = hosts.Query(mincount=opt.min, maxcount=opt.max,
+ randomize=opt.random, **kwargs)
+ except QueryError, e:
+ o.fatal(str(e))
+ if opt.verbose:
+ for match in matches:
+ print match
+ else:
+ keys = re.split(r'[,\s]', opt.keys)
+ for key in keys:
+ if not hasattr(Host, key):
+ o.fatal('no key named %r; valid keys: %s'
+ % (key, ', '.join(Host.__slots__)))
+ for match in matches:
+ print ','.join(str(getattr(match, key) or '') for key in keys)
if __name__ == '__main__':
diff --git a/configs/core.py b/configs/core.py
index 251c99a..42e17a1 100644
--- a/configs/core.py
+++ b/configs/core.py
@@ -6,6 +6,13 @@
__author__ = 'Avery Pennarun (apenwarr@google.com)'
+import random
+
+
+class QueryError(Exception):
+ pass
+
+
class AttrSet(object):
"""A simple set of key-value pairs represented as a class with members."""
@@ -27,6 +34,8 @@
#gpylint: disable-msg=C6403
if ival == False: ival = None
if value == False: value = None
+ if value == True and ival:
+ return True # any nonempty value is true, so match it
if ival != value:
return False
return True
@@ -89,6 +98,35 @@
h.name = h.ether or h.ip or h.ip6
return h
+ def Query(self, mincount=None, maxcount=None, randomize=False, **kwargs):
+ """Query the hosts list according to the given parameters.
+
+ Args:
+ mincount: the minimum number of hosts to return.
+ maxcount: the maximum number of hosts to return.
+ randomize: return a random subset or the first maxcount matches.
+ **kwargs: key=value pairs to pass to hosts.FindAll().
+ Returns:
+ A list of matching Host objects.
+ Raises:
+ QueryError: if mincount > maxcount or other invalid options.
+ """
+ matches = self.FindAll(**kwargs)
+ if maxcount and maxcount < 0:
+ raise QueryError('max(%s) must be >= 0' % maxcount)
+ if maxcount and mincount > maxcount:
+ raise QueryError('min(%s) > max(%s)' % (mincount, maxcount))
+ if mincount > len(matches):
+ raise QueryError('not enough matches found (got=%d, wanted=%r)'
+ % (len(matches), mincount))
+ if randomize:
+ random.shuffle(matches)
+ else:
+ matches.sort(key=lambda i: (i.name, i.serialno, i.ether, i.ip6, i.ip))
+ if maxcount is not None and len(matches) > maxcount:
+ matches = matches[:maxcount]
+ return matches
+
def __str__(self):
return '\n'.join(str(i) for i in self)
diff --git a/configs/core_test.py b/configs/core_test.py
new file mode 100644
index 0000000..db97834
--- /dev/null
+++ b/configs/core_test.py
@@ -0,0 +1,33 @@
+#!/usr/bin/python
+# Copyright 2012 Google Inc. All Rights Reserved.
+#
+# Disable some checks that aren't important for tests:
+#gpylint: disable-msg=E0602,C6409,C6111,C0111
+
+__author__ = 'Avery Pennarun (apenwarr@google.com)'
+
+from wvtest import *
+import core
+
+
+@wvtest
+def testConfigCore():
+ hosts = core.Hosts()
+ WVPASSEQ(len(hosts), 0)
+ h = hosts.FindOrAdd(ether='11:22:33:44:55:66')
+ h.Set(name='testname', ip='1.2.3.4')
+ h.platform = 'bob'
+ WVPASSEQ(len(hosts), 1)
+ h2 = hosts.FindOrAdd(ip='1.2.3.4')
+ WVPASSEQ(len(hosts), 1)
+ WVPASSEQ(h2.name, 'testname')
+ WVPASSEQ(h2.ether, '11:22:33:44:55:66')
+ WVPASSEQ(h, h2)
+
+ # test Host.Query()
+ WVPASSEQ(hosts.Query(ip=True), [h])
+ WVPASSEQ(hosts.Query(ip=False), [])
+ WVPASSEQ(hosts.Query(ip='1.2.3.4'), [h])
+ WVPASSEQ(hosts.Query(ip='1.2.3.5'), [])
+ WVPASSEQ(hosts.Query(ip='1.2.3.4', maxcount=0), [])
+ WVEXCEPT(core.QueryError, hosts.Query, ip='1.2.3.5', mincount=1)
diff --git a/portsh/__init__.py b/portsh/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/portsh/__init__.py