From 357aca42afe56dad70569d82782d6902df5656eb Mon Sep 17 00:00:00 2001 From: Cameron Dale Date: Tue, 15 Jan 2008 16:09:59 -0800 Subject: [PATCH] Added a test apparatus similar to DebTorrent's. --- test.py | 825 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 825 insertions(+) create mode 100755 test.py diff --git a/test.py b/test.py new file mode 100755 index 0000000..f66c7fb --- /dev/null +++ b/test.py @@ -0,0 +1,825 @@ +#!/usr/bin/env python + +"""Automated tests of the apt-dht functionality. + +This script runs several automatic tests of some of the functionality in +the apt-dht program. + +@type tests: C{dictionary} +@var tests: all of the tests that can be run. + The keys are the test names (strings) which are used on the command-line + to identify the tests (can not be 'all' or 'help'). The values are tuples + with four elements: a description of the test (C{string}), the bootstrap + nodes to start (C{dictionary}), the downloaders to start (C{dictionary}, + and the apt-get commands to run (C{list}). + + The bootstrap nodes keys are integers, which must be in the range 1-9. + The values are the dictionary of string formatting values for creating + the apt-dht configuration file (see L{apt_dht_conf_template} below). + + The downloaders keys are also integers in the range 1-99. The values are + the dictionary of string formatting values for creating the apt-dht + configuration file (see L{apt_dht_conf_template} below). + + The apt-get commands' list elements are tuples with 2 elements: the + downloader to run the command on, and the list of command-line + arguments to specify to the apt-get program. + +@type CWD: C{string} +@var CWD: the working directory the script was run from +@type apt_conf_template: C{string} +@var apt_conf_template: the template to use for the apt.conf file + +""" + +from time import sleep, time +import sys, os, signal +from traceback import print_exc +from os.path import exists + +tests = {'1': ('Start a single bootstrap and downloader, test updating and downloading ' + + 'using HTTP only.', + {1: {}}, + {1: {}}, + [(1, ['update']), + (1, ['install', 'aboot-base']), + (1, ['install', 'aap-doc']), + (1, ['install', 'ada-reference-manual']), + (1, ['install', 'aspectj-doc']), + (1, ['install', 'fop-doc']), + (1, ['install', 'jswat-doc']), + (1, ['install', 'asis-doc']), + (1, ['install', 'bison-doc']), + (1, ['install', 'crash-whitepaper']), + (1, ['install', 'doc-iana']), + ]), + + '2': ('Start a single bootstrap and 2 downloaders to test downloading from a peer.', + {1: {}}, + {1: {}, + 2: {}}, + [(1, ['update']), + (1, ['install', 'aboot-base']), + (1, ['install', 'aap-doc']), + (1, ['install', 'ada-reference-manual']), + (1, ['install', 'fop-doc']), + (1, ['install', 'jswat-doc']), + (1, ['install', 'bison-doc']), + (1, ['install', 'crash-whitepaper']), + (2, ['update']), + (2, ['install', 'aboot-base']), + (2, ['install', 'aap-doc']), + (2, ['install', 'ada-reference-manual']), + (2, ['install', 'fop-doc']), + (2, ['install', 'jswat-doc']), + (2, ['install', 'bison-doc']), + (2, ['install', 'crash-whitepaper']), + ]), + + '3': ('Start a single bootstrap and 6 downloaders, to test downloading' + + ' speeds from each other.', + {1: {}}, + {1: {}, + 2: {}, + 3: {}, + 4: {}, + 5: {}, + 6: {}}, + [(1, ['update']), + (1, ['install', 'aboot-base']), + (1, ['install', 'ada-reference-manual']), + (1, ['install', 'fop-doc']), + (1, ['install', 'doc-iana']), + (2, ['update']), + (2, ['install', 'aboot-base']), + (2, ['install', 'ada-reference-manual']), + (2, ['install', 'fop-doc']), + (2, ['install', 'doc-iana']), + (3, ['update']), + (3, ['install', 'aboot-base']), + (3, ['install', 'ada-reference-manual']), + (3, ['install', 'fop-doc']), + (3, ['install', 'doc-iana']), + (4, ['update']), + (4, ['install', 'aboot-base']), + (4, ['install', 'ada-reference-manual']), + (4, ['install', 'fop-doc']), + (4, ['install', 'doc-iana']), + (5, ['update']), + (5, ['install', 'aboot-base']), + (5, ['install', 'ada-reference-manual']), + (5, ['install', 'fop-doc']), + (5, ['install', 'doc-iana']), + (6, ['update']), + (6, ['install', 'aboot-base']), + (6, ['install', 'ada-reference-manual']), + (6, ['install', 'fop-doc']), + (6, ['install', 'doc-iana']), + ]), + + '4': ('Start a single bootstrap and 1 downloader, requesting the same' + + ' packages multiple times to test caching.', + {1: {}}, + {1: {}}, + [(1, ['update']), + (1, ['install', 'aboot-base']), + (1, ['install', 'ada-reference-manual']), + (1, ['install', 'fop-doc']), + (1, ['install', 'doc-iana']), + (1, ['update']), + (1, ['install', 'aboot-base']), + (1, ['install', 'ada-reference-manual']), + (1, ['install', 'fop-doc']), + (1, ['install', 'doc-iana']), + (1, ['update']), + (1, ['install', 'aboot-base']), + (1, ['install', 'ada-reference-manual']), + (1, ['install', 'fop-doc']), + (1, ['install', 'doc-iana']), + ]), + + '5': ('Start a single bootstrap and 6 downloaders, update all to test' + + ' that they can all see each other.', + {1: {}}, + {1: ([], {'suites': 'contrib non-free'}), + 2: ([], {'suites': 'contrib non-free'}), + 3: ([], {'suites': 'contrib non-free'}), + 4: ([], {'suites': 'contrib non-free'}), + 5: ([], {'suites': 'contrib non-free'}), + 6: ([], {'suites': 'contrib non-free'})}, + [(1, ['update']), + (2, ['update']), + (3, ['update']), + (4, ['update']), + (5, ['update']), + (6, ['update']), + ]), + + '6': ('Test caching with multiple apt-get updates.', + {1: {}}, + {1: {}}, + [(1, ['update']), + (1, ['update']), + (1, ['update']), + (1, ['update']), + ]), + + '7': ('Test pipelining of multiple simultaneous downloads.', + {1: {}}, + {1: {}}, + [(1, ['update']), + (1, ['install', 'aboot-base', 'aap-doc', 'ada-reference-manual', + 'aspectj-doc', 'fop-doc', 'jswat-doc', 'asis-doc', + 'bison-doc', 'crash-whitepaper', 'doc-iana', + 'bash-doc', 'apt-howto-common', 'autotools-dev', + 'aptitude-doc-en', 'armagetron-common', 'asr-manpages', + 'atomix-data', 'alcovebook-sgml-doc', 'alamin-doc', + 'aegis-doc', 'afbackup-common', 'airstrike-common', + ]), + ]), + + '8': ('Test pipelining of multiple simultaneous downloads with many peers.', + {1: {}}, + {1: {}, + 2: {}, + 3: {}, + 4: {}, + 5: {}, + 6: {}}, + [(1, ['update']), + (1, ['install', 'aboot-base', 'aap-doc', 'ada-reference-manual', + 'aspectj-doc', 'fop-doc', 'jswat-doc', 'asis-doc', + 'bison-doc', 'crash-whitepaper', 'doc-iana', + 'bash-doc', 'apt-howto-common', 'autotools-dev', + 'aptitude-doc-en', 'armagetron-common', 'asr-manpages', + 'atomix-data', 'alcovebook-sgml-doc', 'alamin-doc', + 'aegis-doc', 'afbackup-common', 'airstrike-common', + ]), + (2, ['update']), + (2, ['install', 'aboot-base', 'aap-doc', 'ada-reference-manual', + 'aspectj-doc', 'fop-doc', 'jswat-doc', 'asis-doc', + 'bison-doc', 'crash-whitepaper', 'doc-iana', + 'bash-doc', 'apt-howto-common', 'autotools-dev', + 'aptitude-doc-en', 'armagetron-common', 'asr-manpages', + 'atomix-data', 'alcovebook-sgml-doc', 'alamin-doc', + 'aegis-doc', 'afbackup-common', 'airstrike-common', + ]), + (3, ['update']), + (3, ['install', 'aboot-base', 'aap-doc', 'ada-reference-manual', + 'aspectj-doc', 'fop-doc', 'jswat-doc', 'asis-doc', + 'bison-doc', 'crash-whitepaper', 'doc-iana', + 'bash-doc', 'apt-howto-common', 'autotools-dev', + 'aptitude-doc-en', 'armagetron-common', 'asr-manpages', + 'atomix-data', 'alcovebook-sgml-doc', 'alamin-doc', + 'aegis-doc', 'afbackup-common', 'airstrike-common', + ]), + (4, ['update']), + (4, ['install', 'aboot-base', 'aap-doc', 'ada-reference-manual', + 'aspectj-doc', 'fop-doc', 'jswat-doc', 'asis-doc', + 'bison-doc', 'crash-whitepaper', 'doc-iana', + 'bash-doc', 'apt-howto-common', 'autotools-dev', + 'aptitude-doc-en', 'armagetron-common', 'asr-manpages', + 'atomix-data', 'alcovebook-sgml-doc', 'alamin-doc', + 'aegis-doc', 'afbackup-common', 'airstrike-common', + ]), + (5, ['update']), + (5, ['install', 'aboot-base', 'aap-doc', 'ada-reference-manual', + 'aspectj-doc', 'fop-doc', 'jswat-doc', 'asis-doc', + 'bison-doc', 'crash-whitepaper', 'doc-iana', + 'bash-doc', 'apt-howto-common', 'autotools-dev', + 'aptitude-doc-en', 'armagetron-common', 'asr-manpages', + 'atomix-data', 'alcovebook-sgml-doc', 'alamin-doc', + 'aegis-doc', 'afbackup-common', 'airstrike-common', + ]), + (6, ['update']), + (6, ['install', 'aboot-base', 'aap-doc', 'ada-reference-manual', + 'aspectj-doc', 'fop-doc', 'jswat-doc', 'asis-doc', + 'bison-doc', 'crash-whitepaper', 'doc-iana', + 'bash-doc', 'apt-howto-common', 'autotools-dev', + 'aptitude-doc-en', 'armagetron-common', 'asr-manpages', + 'atomix-data', 'alcovebook-sgml-doc', 'alamin-doc', + 'aegis-doc', 'afbackup-common', 'airstrike-common', + ]), + ]), + + } + +assert 'all' not in tests +assert 'help' not in tests + +CWD = os.getcwd() +apt_conf_template = """ +{ + // Location of the state dir + State "var/lib/apt/" + { + Lists "lists/"; + xstatus "xstatus"; + userstatus "status.user"; + cdroms "cdroms.list"; + }; + + // Location of the cache dir + Cache "var/cache/apt/" { + Archives "archives/"; + srcpkgcache "srcpkgcache.bin"; + pkgcache "pkgcache.bin"; + }; + + // Config files + Etc "etc/apt/" { + SourceList "sources.list"; + Main "apt.conf"; + Preferences "preferences"; + Parts "apt.conf.d/"; + }; + + // Locations of binaries + Bin { + methods "/usr/lib/apt/methods/"; + gzip "/bin/gzip"; + gpg "/usr/bin/gpgv"; + dpkg "/usr/bin/dpkg --simulate"; + dpkg-source "/usr/bin/dpkg-source"; + dpkg-buildpackage "/usr/bin/dpkg-buildpackage"; + apt-get "/usr/bin/apt-get"; + apt-cache "/usr/bin/apt-cache"; + }; +}; + +/* Options you can set to see some debugging text They correspond to names + of classes in the source code */ +Debug +{ + pkgProblemResolver "false"; + pkgDepCache::AutoInstall "false"; // what packages apt install to satify dependencies + pkgAcquire "false"; + pkgAcquire::Worker "false"; + pkgAcquire::Auth "false"; + pkgDPkgPM "false"; + pkgDPkgProgressReporting "false"; + pkgOrderList "false"; + BuildDeps "false"; + + pkgInitialize "false"; // This one will dump the configuration space + NoLocking "false"; + Acquire::Ftp "false"; // Show ftp command traffic + Acquire::Http "false"; // Show http command traffic + Acquire::Debtorrent "false"; // Show http command traffic + Acquire::gpgv "false"; // Show the gpgv traffic + aptcdrom "false"; // Show found package files + IdentCdrom "false"; + +} +""" +apt_dht_conf_template = """ +[DEFAULT] + +# Port to listen on for all requests (TCP and UDP) +PORT = %(PORT)s + +# Directory to store the downloaded files in +CACHE_DIR = %(CACHE_DIR)s + +# Other directories containing packages to share with others +# WARNING: all files in these directories will be hashed and available +# for everybody to download +# OTHER_DIRS = + +# User name to try and run as +# USERNAME = + +# Whether it's OK to use an IP addres from a known local/private range +LOCAL_OK = yes + +# Which DHT implementation to use. +# It must be possile to do "from .DHT import DHT" to get a class that +# implements the IDHT interface. +DHT = apt_dht_Khashmir + +# Whether to only run the DHT (for providing only a bootstrap node) +DHT-ONLY = %(DHT-ONLY)s + +[apt_dht_Khashmir] +# bootstrap nodes to contact to join the DHT +BOOTSTRAP = %(BOOTSTRAP)s + +# whether this node is a bootstrap node +BOOTSTRAP_NODE = %(BOOTSTRAP_NODE)s + +# Kademlia "K" constant, this should be an even number +K = 8 + +# SHA1 is 160 bits long +HASH_LENGTH = 160 + +# checkpoint every this many seconds +CHECKPOINT_INTERVAL = 15m + +# concurrent xmlrpc calls per find node/value request! +CONCURRENT_REQS = 4 + +# how many hosts to post to +STORE_REDUNDANCY = 3 + +# how many times in a row a node can fail to respond before it's booted from the routing table +MAX_FAILURES = 3 + +# never ping a node more often than this +MIN_PING_INTERVAL = 15m + +# refresh buckets that haven't been touched in this long +BUCKET_STALENESS = 1h + +# time before expirer starts running +KEINITIAL_DELAY = 15s + +# time between expirer runs +KE_DELAY = 20m + +# expire entries older than this +KE_AGE = 1h + +# whether to spew info about the requests/responses in the protocol +SPEW = yes +""" + +def rmrf(top): + """Remove all the files and directories below a top-level one. + + @type top: C{string} + @param top: the top-level directory to start at + + """ + + for root, dirs, files in os.walk(top, topdown=False): + for name in files: + os.remove(os.path.join(root, name)) + for name in dirs: + os.rmdir(os.path.join(root, name)) + +def join(dir): + """Join together a list of directories into a path string. + + @type dir: C{list} of C{string} + @param dir: the path to join together + @rtype: C{string} + @return: the joined together path + + """ + + joined = '' + for i in dir: + joined = os.path.join(joined, i) + return joined + +def makedirs(dir): + """Create all the directories to make a path. + + @type dir: C{list} of C{string} + @param dir: the path to create + + """ + if not os.path.exists(join(dir)): + os.makedirs(join(dir)) + +def touch(path): + """Create an empty file. + + @type path: C{list} of C{string} + @param path: the path to create + + """ + + f = open(join(path), 'w') + f.close() + +def start(cmd, args, work_dir = None): + """Fork and start a background process running. + + @type cmd: C{string} + @param cmd: the name of the command to run + @type args: C{list} of C{string} + @param args: the argument to pass to the command + @type work_dir: C{string} + @param work_dir: the directory to change to to execute the child process in + (optional, defaults to the current directory) + @rtype: C{int} + @return: the PID of the forked process + + """ + + new_cmd = [cmd] + args + pid = os.spawnvp(os.P_NOWAIT, new_cmd[0], new_cmd) + return pid + +def stop(pid): + """Stop a forked background process that is running. + + @type pid: C{int} + @param pid: the PID of the process to stop + @rtype: C{int} + @return: the return status code from the child + + """ + + # First try a keyboard interrupt + os.kill(pid, signal.SIGINT) + for i in xrange(5): + sleep(1) + (r_pid, r_value) = os.waitpid(pid, os.WNOHANG) + if r_pid: + return r_value + + # Try a keyboard interrupt again, just in case + os.kill(pid, signal.SIGINT) + for i in xrange(5): + sleep(1) + (r_pid, r_value) = os.waitpid(pid, os.WNOHANG) + if r_pid: + return r_value + + # Try a terminate + os.kill(pid, signal.SIGTERM) + for i in xrange(5): + sleep(1) + (r_pid, r_value) = os.waitpid(pid, os.WNOHANG) + if r_pid: + return r_value + + # Finally a kill, don't return until killed + os.kill(pid, signal.SIGKILL) + while not r_pid: + sleep(1) + (r_pid, r_value) = os.waitpid(pid, os.WNOHANG) + + return r_value + +def apt_get(num_down, cmd): + """Start an apt-get process in the background. + + The default argument specified to the apt-get invocation are + 'apt-get -d -q -c '. Any additional arguments (including + the apt-get action to use) should be specified. + + @type num_down: C{int} + @param num_down: the number of the downloader to use + @type cmd: C{list} of C{string} + @param cmd: the arguments to pass to the apt-get process + @rtype: C{int} + @return: the PID of the background process + + """ + + print '************************** apt-get (' + str(num_down) + ') ' + ' '.join(cmd) + ' **************************' + apt_conf = join([down_dir(num_down), 'etc', 'apt', 'apt.conf']) + dpkg_status = join([down_dir(num_down), 'var', 'lib', 'dpkg', 'status']) + args = ['-d', '-c', apt_conf, '-o', 'Dir::state::status='+dpkg_status] + cmd + pid = start('apt-get', args) + return pid + +def bootstrap_address(num_boot): + """Determine the bootstrap address to use for a node. + + @type num_boot: C{int} + @param num_boot: the number of the bootstrap node + @rtype: C{string} + @return: the bootstrap address to use + + """ + + return 'localhost:1' + str(num_boot) + '969' + +def down_dir(num_down): + """Determine the working directory to use for a downloader. + + @type num_down: C{int} + @param num_down: the number of the downloader + @rtype: C{string} + @return: the downloader's directory + + """ + + return os.path.join(CWD,'downloader' + str(num_down)) + +def boot_dir(num_boot): + """Determine the working directory to use for a bootstrap node. + + @type num_boot: C{int} + @param num_boot: the number of the bootstrap node + @rtype: C{string} + @return: the bootstrap node's directory + + """ + + return os.path.join(CWD,'bootstrap' + str(num_boot)) + +def start_downloader(bootstrap_addresses, num_down, options = {}, + mirror = 'ftp.us.debian.org/debian', + suites = 'main contrib non-free', clean = True): + """Initialize a new downloader process. + + The default arguments specified to the downloader invocation are + the configuration directory, apt port, minport, maxport and the + maximum upload rate. + Any additional arguments needed should be specified by L{options}. + + @type num_down: C{int} + @param num_down: the number of the downloader to use + @type options: C{dictionary} + @param options: the dictionary of string formatting values for creating + the apt-dht configuration file (see L{apt_dht_conf_template} above). + (optional, defaults to only using the default arguments) + @type mirror: C{string} + @param mirror: the Debian mirror to use + (optional, defaults to 'ftp.us.debian.org/debian') + @type suites: C{string} + @param suites: space separated list of suites to download + (optional, defaults to 'main contrib non-free') + @type clean: C{boolean} + @param clean: whether to remove any previous downloader files + (optional, defaults to removing them) + @rtype: C{int} + @return: the PID of the downloader process + + """ + + assert num_down < 100 + + print '************************** Starting Downloader ' + str(num_down) + ' **************************' + + downloader_dir = down_dir(num_down) + + if clean: + try: + rmrf(downloader_dir) + except: + pass + + # Create the directory structure needed by apt + makedirs([downloader_dir, 'etc', 'apt', 'apt.conf.d']) + makedirs([downloader_dir, 'var', 'lib', 'apt', 'lists', 'partial']) + makedirs([downloader_dir, 'var', 'lib', 'dpkg']) + makedirs([downloader_dir, 'var', 'cache', 'apt', 'archives', 'partial']) + touch([downloader_dir, 'var', 'lib', 'apt', 'lists', 'lock']) + touch([downloader_dir, 'var', 'lib', 'dpkg', 'lock']) + touch([downloader_dir, 'var', 'lib', 'dpkg', 'status']) + touch([downloader_dir, 'var', 'cache', 'apt', 'archives', 'lock']) + + # Create apt's config files + f = open(join([downloader_dir, 'etc', 'apt', 'sources.list']), 'w') + f.write('deb http://localhost:1%02d77/%s/ stable %s\n' % (num_down, mirror, suites)) + f.close() + + f = open(join([downloader_dir, 'etc', 'apt', 'apt.conf']), 'w') + f.write('Dir "' + downloader_dir + '"') + f.write(apt_conf_template) + f.close() + + defaults = {'PORT': '1%02d77' % num_down, + 'CACHE_DIR': downloader_dir, + 'DHT-ONLY': 'no', + 'BOOTSTRAP': bootstrap_addresses, + 'BOOTSTRAP_NODE': 'no'} + + for k in options: + defaults[k] = options[k] + f = open(join([downloader_dir, 'apt-dht.conf']), 'w') + f.write(apt_dht_conf_template % defaults) + f.close() + + pid = start('python', [join([sys.path[0], 'apt-dht.py']), + '--config-file=' + join([downloader_dir, 'apt-dht.conf']), + '--log-file=' + join([downloader_dir, 'apt-dht.log']),], + downloader_dir) + return pid + +def start_bootstrap(bootstrap_addresses, num_boot, options = [], clean = True): + """Initialize a new bootstrap node process. + + The default arguments specified to the apt-dht invocation are + the state file and port to use. Any additional arguments needed + should be specified by L{options}. + + @type num_boot: C{int} + @param num_boot: the number of the bootstrap node to use + @type options: C{list} of C{string} + @param options: the arguments to pass to the bootstrap node + (optional, defaults to only using the default arguments) + @type clean: C{boolean} + @param clean: whether to remove any previous bootstrap node files + (optional, defaults to removing them) + @rtype: C{int} + @return: the PID of the downloader process + + """ + + assert num_boot < 10 + + print '************************** Starting Bootstrap ' + str(num_boot) + ' **************************' + + bootstrap_dir = boot_dir(num_boot) + + if clean: + try: + rmrf(bootstrap_dir) + except: + pass + + makedirs([bootstrap_dir]) + + defaults = {'PORT': '1%d969' % num_boot, + 'CACHE_DIR': bootstrap_dir, + 'DHT-ONLY': 'yes', + 'BOOTSTRAP': bootstrap_addresses, + 'BOOTSTRAP_NODE': 'yes'} + + for k in options: + defaults[k] = options[k] + f = open(join([bootstrap_dir, 'apt-dht.conf']), 'w') + f.write(apt_dht_conf_template % defaults) + f.close() + + pid = start('python', [join([sys.path[0], 'apt-dht.py']), + '--config-file=' + join([bootstrap_dir, 'apt-dht.conf']), + '--log-file=' + join([bootstrap_dir, 'apt-dht.log']),], + bootstrap_dir) + + return pid + +def run_test(bootstraps, downloaders, apt_get_queue): + """Run a single test. + + @type bootstraps: C{dictionary} of {C{int}: C{list} of C{string}} + @param bootstraps: the bootstrap nodes to start, keys are the bootstrap numbers and + values are the list of options to invoke the bootstrap node with + @type downloaders: C{dictionary} of {C{int}: (C{int}, C{list} of C{string})} + @param downloaders: the downloaders to start, keys are the downloader numbers and + values are the list of options to invoke the downloader with + @type apt_get_queue: C{list} of (C{int}, C{list} of C{string}) + @param apt_get_queue: the apt-get downloader to use and commands to execute + @rtype: C{list} of (C{float}, C{int}) + @return: the execution time and returned status code for each element of apt_get_queue + + """ + + running_bootstraps = {} + running_downloaders = {} + running_apt_get = {} + apt_get_results = [] + + try: + boot_keys = bootstraps.keys() + boot_keys.sort() + bootstrap_addresses = bootstrap_address(boot_keys[0]) + for i in xrange(1, len(boot_keys)): + bootstrap_addresses += '\n ' + bootstrap_address(boot_keys[i]) + + for k, v in bootstraps.items(): + running_bootstraps[k] = start_bootstrap(bootstrap_addresses, k, v) + + sleep(5) + + for k, v in downloaders.items(): + running_downloaders[k] = start_downloader(bootstrap_addresses, k, v) + + sleep(5) + + for (num_down, cmd) in apt_get_queue: + running_apt_get[num_down] = apt_get(num_down, cmd) + start_time = time() + (pid, r_value) = os.waitpid(running_apt_get[num_down], 0) + elapsed = time() - start_time + del running_apt_get[num_down] + r_value = r_value / 256 + apt_get_results.append((elapsed, r_value)) + + if r_value == 0: + print '********************** apt-get completed successfully in ' + str(elapsed) + ' sec. **************************' + else: + print '********************** apt-get finished with status ' + str(r_value) + ' in ' + str(elapsed) + ' sec. **************************' + + sleep(5) + + except: + print '************************** Exception occurred **************************' + print_exc() + print '************************** will attempt to shut down *******************' + + print '*********************** shutting down the apt-gets *******************' + for k, v in running_apt_get.items(): + try: + print 'apt-get', k, stop(v) + except: + print '************************** Exception occurred **************************' + print_exc() + + sleep(5) + + print '*********************** shutting down the downloaders *******************' + for k, v in running_downloaders.items(): + try: + print 'downloader', k, stop(v) + except: + print '************************** Exception occurred **************************' + print_exc() + + sleep(5) + + print '************************** shutting down the bootstraps *******************' + for k, v in running_bootstraps.items(): + try: + print 'bootstrap', k, stop(v) + except: + print '************************** Exception occurred **************************' + print_exc() + + print '************************** Test Results *******************' + i = -1 + for (num_down, cmd) in apt_get_queue: + i += 1 + s = str(num_down) + ': "apt-get ' + ' '.join(cmd) + '" ' + if len(apt_get_results) > i: + (elapsed, r_value) = apt_get_results[i] + s += 'took ' + str(elapsed) + ' secs (' + str(r_value) + ')' + else: + s += 'did not complete' + print s + + return apt_get_results + +def get_usage(): + """Get the usage information to display to the user. + + @rtype: C{string} + @return: the usage information to display + + """ + + s = 'Usage: ' + sys.argv[0] + ' (all||help)\n\n' + s += ' all - run all the tests\n' + s += ' help - display this usage information\n' + s += ' - run the test (see list below for valid tests)\n\n' + + t = tests.items() + t.sort() + for k, v in t: + s += 'test "' + str(k) + '" - ' + v[0] + '\n' + + return s + +if __name__ == '__main__': + if len(sys.argv) != 2: + print get_usage() + elif sys.argv[1] == 'all': + for k, v in tests.items(): + run_test(v[1], v[2], v[3]) + elif sys.argv[1] in tests: + v = tests[sys.argv[1]] + run_test(v[1], v[2], v[3]) + elif sys.argv[1] == 'help': + print get_usage() + else: + print 'Unknown test to run:', sys.argv[1], '\n' + print get_usage() + \ No newline at end of file -- 2.39.2