summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlon Levy <alevy@redhat.com>2011-07-19 15:57:45 +0300
committerAlon Levy <alevy@redhat.com>2011-07-19 15:57:45 +0300
commit27734ecaddedd26d379009b671c6cb170c01c719 (patch)
treed9d34260726fb06ff2b343d1ac342fdda371eff4
parent3c3d548267f6e2d3f3990de570922cd52641ed1b (diff)
multiple
add spicedump hooks add SIGHUP handler (you can do killall -SIGHUP win7)
-rwxr-xr-xspice2348
1 files changed, 251 insertions, 97 deletions
diff --git a/spice2 b/spice2
index d76d6f3..a7a2d5a 100755
--- a/spice2
+++ b/spice2
@@ -11,6 +11,7 @@ import datetime
import re
import time
import shutil
+import itertools
try:
import guestfs
@@ -19,15 +20,33 @@ except:
print "missing guestfs or hivex"
guestfs = None
+def which(x):
+ for f in path.split(':'):
+ full_path = os.path.join(f, x)
+ if os.path.exists(full_path):
+ return full_path
+ return None
+
+################################################################################
+lddpath = which('ldd')
+spicedump_exe = which('spicedump.py')
+python_exe = which('python')
+#python_exe = which('pypy')
+
HOME = os.environ['HOME']
################################################################################
+# Edit <config_path> instead of this default section.
+config_path = os.path.join(HOME, '.spice_launcher')
+initial_defaults = """# vim: set filetype=python :
+import os
+HOME = os.environ['HOME']
qemu_exec_name = "qemu-system-x86_64"
images = {
'windevel': '/store/images/windbg_winxp.img',
'winxp': '/store/images/winxp_sp3_qxl_after_cirrus.img',
'win7': '/home/alon/images2/win7x86_qxl_tests.img',
- 'kindle': '/home/alon/images2/win7_kindle.img',
'win2008r2': '/media/PassportExt4/images/win2008r2_qxl.qcow2',
+ 'wlk': '/media/PassportExt4/images/win2008r2_wlk.qcow2',
'f14ccid': '/media/PassportExt4/images/F14_CCID.testing.qcow2',
'winxp.inst': '/store/images/winxp_installation_test.qcow2',
}
@@ -39,17 +58,29 @@ qemus = {'rhel6':'rhel6', 'upstream':'upstream'}
qemu_default = 'upstream'
defaults = dict(
+ winxp=dict(
+ qemu='upstream',
+ args='--port 9001 --clients 1 --tablet --guestdebug 1 --qxldebug 1 --windbg-client 9000'.split(),
+ ),
+ wlk=dict(
+ qemu='upstream',
+ args='--port 9002 --clients 1 --tablet'.split(),
+ ),
win7=dict(
qemu='upstream',
- args='--port 8888 --clients 1 --tablet --guestdebug 2 --qxldebug 1 --windbg-client 9000'.split()
+ args='--port 9003 --clients 1 --tablet --guestdebug 2 --qxldebug 1 --windbg-client 9000'.split()
),
windevel=dict(
qemu='rhel6',
- args='--port 6666 --clients 1 --windbg-server 9000'.split()
+ args='--port 9004 --clients 1 --windbg-server 9000'.split()
),
f14ccid=dict(
qemu='upstream',
- args='--port 9999 --clients 1 --tablet --guestdebug 4 --qxldebug 1 --smartcard'.split()
+ args='--port 9005 --clients 1 --tablet --guestdebug 4 --qxldebug 1 --smartcard'.split()
+ ),
+ win2008r2=dict(
+ qemu='upstream',
+ args='--port 9006 --clients 1 --tablet --revision 2 --'.split(),
),
)
@@ -57,6 +88,27 @@ spice_src_path = os.path.join(HOME, 'src/spice_upstream/spice')
qxl_win_root = os.path.join(HOME, 'shared_win/qxl/dist')
+# required utilities for automated windows tests
+res_change_exe = HOME+'/shared_win/spice_upstream/util/reschange/reschange.exe'
+suspend_exe = HOME+'/shared_win/spice_upstream/util/suspend/suspend.exe'
+"""
+
+if not os.path.exists(config_path):
+ with open(config_path, 'w+') as fd:
+ fd.write(initial_defaults)
+ print "created config file %s" % config_path
+with open(config_path, 'r') as fd:
+ exec(fd)
+
+for name, default_location in [
+ ('res_change_exe', 'res_change.exe'),
+ ('suspend_exe', ('suspend.exe')]:
+ if not os.path.exists(globals()[name]) and os.path.exists(default_location):
+ print "config file points to non existant %r (%r), changing to local copy" % (
+ name, globals()[name])
+ globals()[name] = default_location
+
+
################################################################################
win_tests = dict(
@@ -69,7 +121,16 @@ shutdown /p
""" % ("""
c:\suspend.exe
rem ping -n 2 localhost
-""" * 20), files=[HOME+'/shared_win/spice_upstream/util/suspend/suspend.exe']),
+""" * 20), files=[suspend_exe]),
+suspend2=dict(contents=r"""
+set RESCHANGE="c:\reschange.exe"
+%%RESCHANGE%% -attach -mon 1024,768,32,0,0 -mon 800,600,32,1024,0
+%s
+shutdown /p
+""" % (r"""
+c:\suspend.exe
+rem ping -n 2 localhost
+""" * 20), files=[res_change_exe, suspend_exe]),
resolution1=dict(contents=r"""
set RESCHANGE="c:\reschange.exe"
%s
@@ -80,7 +141,7 @@ shutdown /p
%RESCHANGE% -mon 1024,768,32,0,0
%RESCHANGE% -mon 768,1024,32,0,0
%RESCHANGE% -mon 1024,768,32,0,0
-""" * 60), files=[HOME+'/shared_win/spice_upstream/util/reschange/reschange.exe']),
+""" * 60), files=[res_change_exe]),
resolution2=dict(contents=r"""
set RESCHANGE="c:\reschange.exe"
%s
@@ -90,11 +151,45 @@ shutdown /p
%RESCHANGE% -attach -mon 1024,768,32,0,0
%RESCHANGE% -attach -mon 1024,768,32,0,0 -mon 800,600,32,1024,0
%RESCHANGE% -attach -mon 1024,768,32,0,0
-""" * 60), files=[HOME+'/shared_win/spice_upstream/util/reschange/reschange.exe']),
+""" * 60), files=[res_change_exe]),
)
################################################################################
+"""
+tests are run without any clients connected, right now they can only
+create / kill clients, later they should:
+ send events to the clients (mouse/keyboard)
+ parse all messages going to client and coming from client
+ parse all messages going from qemu to guest agent and from it
+ (presumably this can be done using a proxy and two chardevs)
+ send qmp messages to qemu (connect/disconnect stuff, do screenshots)
+
+Yes, this is all autotest stuff. maybe I should ask them to turn autotest into
+a library.
+"""
+def test_agent_connection_stress(server, args):
+ display = 2
+ run_xephyr(display=display)
+ auto_conf = itertools.cycle([False, True])
+ times = itertools.chain([120], itertools.repeat(10))
+ for i in xrange(100):
+ client = run_client(host=args.spice_host, port=args.spice_port,
+ oldclient=True, smartcard=False,
+ auto_conf=auto_conf.next(),
+ certs=[], dbdir=None,
+ display=display)
+ t = times.next()
+ print "sleeping %d" % t
+ time.sleep(t)
+ client.spice_kill()
+ client.wait()
+
+g = globals().keys()
+tests = [x[5:] for x in g if x.startswith('test_')]
+
+################################################################################
+
red = "\x1b[101m%s\x1b[0m"
path = os.environ['PATH']
@@ -106,13 +201,6 @@ def set_title(x):
def file_in_use(x):
return not os.system('lsof -t %s > /dev/null' % x)
-def which(x):
- for f in path.split(':'):
- full_path = os.path.join(f, x)
- if os.path.exists(full_path):
- return full_path
- return None
-
def oldness(f):
if not os.path.exists(f):
return 'file doesn\'t exist'
@@ -125,7 +213,8 @@ def oldness(f):
return '%d minutes' % int(d.seconds / 60)
return '%d seconds' % d.seconds
-lddpath = which('ldd')
+################################################################################
+
def get_lib(fname, rexp):
l = os.popen('%s "%s"' % (lddpath, fname)).readlines()
refs_raw=[x.split('=>') for x in l if '=>' in x]
@@ -190,23 +279,24 @@ def get_image(args):
print "image %s" % image_fullpath
return image_fullpath
+test_progs = {
+ 'streaming': 'test_display_streaming',
+ 'display': 'test_display_no_ssl',
+ 'playback': 'test_playback',
+ 'sockets': 'test_just_sockets_no_ssl',
+ 'replay': 'replay',
+}
def build_test_cmdline(args):
- test_progs = {
- 'streaming': 'test_display_streaming',
- 'display': 'test_display_no_ssl',
- 'playback': 'test_playback',
- 'sockets': 'test_just_sockets_no_ssl',
- 'replay': 'replay',
- }
- parts = (['/usr/bin/libtool', '--mode=execute', 'cgdb', '--args'] if args.cgdb else []) + [os.path.join(spice_src_path, 'server/tests', test_progs[args.test])]
- if args.test == 'replay' and args.replay_file:
+ parts = (['/usr/bin/libtool', '--mode=execute', 'cgdb', '--args'] if args.cgdb else []) + [os.path.join(spice_src_path, 'server/tests',
+ test_progs[args.test_prog])]
+ if args.test_prog == 'replay' and args.replay_file:
parts.append(args.replay_file)
# tests have this port hardcoded
args.port = 5912
return ' '.join(parts)
def build_cmdline(args):
- if args.test:
+ if args.test_prog:
return build_test_cmdline(args)
qemu = args.qemu
if qemu not in qemus:
@@ -263,7 +353,7 @@ def build_cmdline(args):
else:
print "nothing listening on %d, do you want to start something there? (p.s. maybe systemd service?)" % args.windbg_client
# smartcard
- if args.smartcard:
+ if args.smartcard != 'off':
ccid_debug = 1
passthru_debug = 1
cmdline.extend([' -chardev spicevmc,id=smartcard,debug=3,name=smartcard',
@@ -299,7 +389,7 @@ def build_cmdline(args):
if not os.path.exists(args.cdrom):
print "missing cdrom image %r" % args.cdrom
sys.exit(1)
- cmdline.append(' -cdrom "%s"' % args.cdrom)
+ cmdline.append(' -cdrom %s' % args.cdrom)
######
cmdline = ''.join(cmdline)
@@ -321,28 +411,42 @@ def build_cmdline(args):
cmdline = '/usr/bin/time --verbose %s' % cmdline
return cmdline
-def run_xephyr(display, size=(1024,768+40)):
+def run_xephyr(display, size=(1024,768+40), show_output=False):
+ # TODO -wait for it to start listening to socket
return start_process(('Xephyr -screen %dx%d :%d.0' %
(size[0], size[1], display)).split())
-def run_client(host, port, oldclient, smartcard, certs, dbdir, display=None):
+def rpdb2_break():
+ import rpbd2
+ rpdb2.rpdb2.start_embedded_debugger('a')
+
+def run_client(host, port, oldclient, smartcard, certs, dbdir, display=None,
+ auto_conf=False):
s_opts = ''
+ ac_opts = ''
old_display = None
if display is not None:
old_display = os.environ['DISPLAY']
os.environ['DISPLAY'] = ':%d.0' % display
- if smartcard and certs:
+ if smartcard == 'sw':
if oldclient:
s_opts = '--smartcard --smartcard-certs %s --smartcard-db %s' % (
','.join(certs), dbdir)
else:
s_opts = '--certificates=%s --certificate-db=%s' % (
','.join(certs), dbdir)
-
+ if auto_conf and oldclient:
+ ac_opts = '--full-screen=auto-conf'
+ if dbdir and not os.path.exists(dbdir):
+ print "error - dbdir supplied does not exist"
+ sys.exit(1)
p = start_process(
- args=('%(exe)s -h %(host)s -p %(port)s %(smartcard)s' % dict(
- host=host, port=port, exe=spicec_exe if oldclient else spicy_exe,
- smartcard=s_opts)).split(), kill=True)
+ args=('%(exe)s -h %(host)s -p %(port)s %(smartcard)s %(autoconf)s' %
+ dict(
+ host=host, port=port,
+ exe=spicec_exe if oldclient else spicy_exe,
+ autoconf=ac_opts,
+ smartcard=s_opts)).split(), kill=True)
if old_display:
os.environ['DISPLAY'] = old_display
return p
@@ -381,25 +485,30 @@ def parseargs():
# if provided with a name of the form image.qemu take that
# instead of --image <image> , --qemu <qemu>
# but still let --image and --qemu override it
- image, qemu = None, None
- name = sys.argv[0]
- parts = name.split('.')
+ image, qemu = None, qemu_default
+ name = os.path.basename(sys.argv[0])
default_args = []
- if len(parts) == 1:
- # this used to be a bunch of bash scripts until I found it prevented
- # from ever quoting a space
- image = os.path.basename(parts[0])
- if len(parts) == 2:
- image, qemu = parts
- image = os.path.basename(image)
- if image:
- if image in defaults:
- default_args = defaults[image]['args']
- if image not in images:
- print "missing image %r" % image
- sys.exit(1)
- image = images[image]
- parser = argparse.ArgumentParser(description='Process some integers.')
+ parts = []
+ if os.path.islink(sys.argv[0]) and name != os.path.basename(os.readlink(sys.argv[0])):
+ # you can symlink spice2 -> win7.upstream or win7, it will run the win7
+ # from the images dictionary
+ parts = name.split('.')
+ base_part = parts[0] if len(parts) >= 1 else None
+ if len(parts) == 1:
+ if base_part in defaults and 'qemu' in defaults[base_part]:
+ qemu = defaults[base_part]['qemu']
+ if len(parts) == 2:
+ image, qemu = parts
+ image = os.path.basename(image)
+ if base_part:
+ if base_part in defaults:
+ default_args = defaults[base_part]['args']
+ if base_part not in images:
+ print "missing key in images %r" % base_part
+ print images.keys()
+ sys.exit(1)
+ image = images[base_part]
+ parser = argparse.ArgumentParser(description='SPICE Launcher script, mark 2. Run spice, qemu, spice tests, automated tests on pre prepared images. Still missing: automated driver installation.')
parser.add_argument('--record-cmd', dest='record_cmd', help='record qxl command ring')
parser.add_argument('--copy-in', dest='copy_in', action='append')
parser.add_argument('--nobacking-file', default=True, action='store_false', dest='backing_file', help='use the image as a backing file for the actual image')
@@ -430,15 +539,14 @@ def parseargs():
parser.add_argument('--windbg-client', dest='windbg_client', type=int, default=None)
parser.add_argument('--memory', dest='memory', type=int, default=512)
parser.add_argument('--cpus', dest='cpus', type=int, default=2)
- parser.add_argument('--nosmartcard', dest='smartcard', action='store_false', default=False)
- parser.add_argument('--smartcard', dest='smartcard', action='store_true', default=False)
+ parser.add_argument('--smartcard', dest='smartcard', choices=['off','hw','sw'], default='off')
parser.add_argument('--smartcard-dbdir', dest='dbdir')
parser.add_argument('--smartcard-certs', dest='certs', action='append')
parser.add_argument('--debugbios', dest='debugbios', action='store_true', default=False)
parser.add_argument('--image', dest='image', default=image)
parser.add_argument('--cdrom', dest='cdrom', default=None)
parser.add_argument('--winqxl-cdrom', dest='winqxl_cdrom', default=False, action='store_true')
- parser.add_argument('--qemu', dest='qemu', default=None, choices=qemus.keys() + [None])
+ parser.add_argument('--qemu', dest='qemu', default=qemu, choices=qemus.keys() + [None])
parser.add_argument('--incoming', dest='incoming', type=int, default=None)
parser.add_argument('--title', dest='title', default=None)
parser.add_argument('--second', dest='second', default=False, action='store_true')
@@ -448,19 +556,25 @@ def parseargs():
parser.add_argument('--vncclients', dest='vnc_clients', type=int, default=1)
parser.add_argument('--cpu', dest='cpu', choices=['host'], default='host', help='qemu cpu flag')
parser.add_argument('--keep-temp-backing-file', dest='erase_temp_backing_file', default=True, action='store_false')
+ parser.add_argument('--spicedump', dest='spicedump', default=False, action='store_true')
+ parser.add_argument('--spicedump-filter', dest='spicedump_filter')
# convenient to run tests via the same framework, though this is not using
# any of of the qemu flags, just the client running parts.
- parser.add_argument('--test', dest='test', choices=['streaming', 'display', 'playback', 'replay'])
+ parser.add_argument('--test-prog', dest='test_prog',
+ choices=test_progs.keys())
+ parser.add_argument('--test-func', dest='test_func', choices=tests)
parser.add_argument('--replay-file', dest='replay_file')
args = parser.parse_args(default_args + sys.argv[1:])
- if len(parts) == 1 and not args.test and not args.image:
+ if len(parts) == 1 and not args.test_prog and not args.image:
if image not in defaults:
print "symlink %r not in %r" % (image, defaults.keys())
sys.exit(1)
if not args.qemu:
args.qemu = defaults[image]['qemu']
- if not args.qemu:
- args.qemu = qemu_default
+ elif not args.image:
+ print "image not provided. use --image"
+ parser.print_usage()
+ sys.exit(1)
return args
def set_runonce_registry(g, root, runonce_paths):
@@ -473,7 +587,7 @@ def set_runonce_registry(g, root, runonce_paths):
p = h.root()
for k in r'Microsoft\Windows\CurrentVersion\RunOnce'.split('\\'):
p = h.node_get_child(p, k)
- # 1 - hive_t_REG_SZ - unknown
+ # 1 - hive_t_REG_SZ - unknown
h.node_set_values(p, [{'key': 'Spice2RunOnce', 't': 1, 'value': runonce_path.encode('utf-16le')} for runonce_path in runonce_paths])
h.commit(None)
g.upload("/tmp/software", path)
@@ -551,14 +665,32 @@ def set_startup_from_contents(g, image, filename, contents):
g.write(dest, contents)
return g
+def update_certs(args):
+ if args.smartcard != 'sw' or args.certs != []:
+ return
+ if os.environ['SPICE_LAUNCHER_CERTS'] != '':
+ print "missing SPICE_LAUNCHER_CERTS"
+ sys.exit(1)
+ if os.environ['SPICE_LAUNCHER_DB']:
+ args.dbdir = os.environ['SPICE_LAUNCHER_DB']
+
################################################################################
processes = []
temp_files = []
-def start_process(args, kill=False, **kw):
+class MyProcess(subprocess.Popen):
+ def spice_kill(self):
+ processes.remove(self)
+ return self.kill()
+
+def start_process(args, kill=False, show_output=True, **kw):
global processes
- p = subprocess.Popen(args, **kw)
- p.spice_kill = kill
+ if show_output:
+ p = MyProcess(args, **kw)
+ else:
+ p = MyProcess(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE,
+ **kw)
+ p._spice_kill = kill
processes.append(p)
return p
@@ -579,20 +711,37 @@ def cleanup():
os.system('reset -I')
def if_processes_are_dead_then_exit(*args):
- if all((p.poll() is not None or p.spice_kill) for p in processes):
+ if all((p.poll() is not None or p._spice_kill) for p in processes):
cleanup()
sys.exit(0)
- #print '|'.join(('%d: %s, %s' % (p.pid, p.poll(), p.spice_kill)) for p in processes)
+ #print '|'.join(('%d: %s, %s' % (p.pid, p.poll(), p._spice_kill)) for p in processes)
def ignore_ctrlc():
signal.signal(signal.SIGINT, if_processes_are_dead_then_exit)
+def exit_on_hup():
+ def on_exit(*args):
+ cleanup()
+ sys.exit(1)
+ old = signal.signal(signal.SIGHUP, on_exit)
+ print "SIGHUP old:", old
+
################################################################################
clients = []
vnc_clients = []
+def default_get_display():
+ while True:
+ yield None
+def xephyr_get_display():
+ i = 2
+ while True:
+ yield i
+ i += 1
+
def main():
args = parseargs()
+ update_certs(args)
cmdline = build_cmdline(args)
atexit.register(cleanup)
if args.runonce:
@@ -623,42 +772,47 @@ def main():
if args.showcmdline:
return
ignore_ctrlc()
- test_process = start_process(args=cmdline.split())
- def default_get_display():
- while True:
- yield None
- def xephyr_get_display():
- i = 2
- while True:
- yield i
- i += 1
+ exit_on_hup()
+ test_process = start_process(args=cmdline.split(), show_output=True)
if args.xephyr:
display = xephyr_get_display()
else:
display = default_get_display()
- if args.clients > 0:
- print "starting clients"
- cur_display = display.next()
- if cur_display:
- run_xephyr(display=cur_display)
- wait_for_port(args.spice_port)
- for i in xrange(args.clients):
- client_process = run_client(
- host=args.spice_host, port=args.spice_port,
- oldclient=args.oldclient, smartcard=args.smartcard,
- certs=args.certs, dbdir=args.dbdir,
- display=cur_display)
- clients.append(client_process)
- print "waiting for test_process"
- if args.vnc_port and args.vnc_clients > 0:
- if not vncviewer:
- print "vncviewer not in path - not starting"
- else:
- print "starting vnc clients"
- wait_for_port(args.vnc_port)
- for i in xrange(args.vnc_clients):
- client_process = run_vnc_client(host=args.spice_host, port=args.vnc_port)
- vnc_clients.append(client_process)
+ if args.spicedump:
+ real_spice_port = args.spice_port
+ args.spice_port = proxy_port = real_spice_port + 1000
+ # run a spicedump proxy in the middle
+ start_process(args=('%s %s -p -l %s -r localhost:%s%s' %
+ (python_exe, spicedump_exe, proxy_port, real_spice_port,
+ ' -f %s' % args.spicedump_filter if args.spicedump_filter else ''
+ )).split())
+ if args.test_func:
+ test_func = globals()['test_'+args.test_func]
+ test_func(server=test_process, args=args)
+ else:
+ if args.clients > 0:
+ print "starting clients"
+ cur_display = display.next()
+ if cur_display:
+ run_xephyr(display=cur_display)
+ wait_for_port(args.spice_port)
+ for i in xrange(args.clients):
+ client_process = run_client(
+ host=args.spice_host, port=args.spice_port,
+ oldclient=args.oldclient, smartcard=args.smartcard,
+ certs=args.certs, dbdir=args.dbdir,
+ display=cur_display)
+ clients.append(client_process)
+ if args.vnc_port and args.vnc_clients > 0:
+ if not vncviewer:
+ print "vncviewer not in path - not starting"
+ else:
+ print "starting vnc clients"
+ wait_for_port(args.vnc_port)
+ for i in xrange(args.vnc_clients):
+ client_process = run_vnc_client(host=args.spice_host, port=args.vnc_port)
+ vnc_clients.append(client_process)
+ print "waiting for test_process"
test_process.wait()
if __name__ == '__main__':