summaryrefslogtreecommitdiffstats
path: root/tools/axl
diff options
context:
space:
mode:
authorThe Android Open Source Project <initial-contribution@android.com>2009-03-03 19:29:09 -0800
committerThe Android Open Source Project <initial-contribution@android.com>2009-03-03 19:29:09 -0800
commit52d4c30ca52320ec92d1d1ddc8db3f07f69c4f98 (patch)
tree2eb01432fa3da41550dd622d29fb672509122b51 /tools/axl
parentd4aee0c0caa00aa02d4c50ed28151591ac0456b5 (diff)
downloadandroid_development-52d4c30ca52320ec92d1d1ddc8db3f07f69c4f98.tar.gz
android_development-52d4c30ca52320ec92d1d1ddc8db3f07f69c4f98.tar.bz2
android_development-52d4c30ca52320ec92d1d1ddc8db3f07f69c4f98.zip
auto import from //depot/cupcake/@135843
Diffstat (limited to 'tools/axl')
-rw-r--r--tools/axl/README.txt3
-rwxr-xr-xtools/axl/axl.py253
-rwxr-xr-xtools/axl/chewie.py184
-rwxr-xr-xtools/axl/chewperf.py87
-rw-r--r--tools/axl/log.py55
-rw-r--r--tools/axl/random.datbin0 -> 1480 bytes
-rw-r--r--tools/axl/singletonmixin.py220
-rw-r--r--tools/axl/stfu.jpgbin0 -> 943 bytes
-rwxr-xr-xtools/axl/udpEater.py45
-rwxr-xr-xtools/axl/udpServer.py29
10 files changed, 876 insertions, 0 deletions
diff --git a/tools/axl/README.txt b/tools/axl/README.txt
new file mode 100644
index 000000000..36f6811a6
--- /dev/null
+++ b/tools/axl/README.txt
@@ -0,0 +1,3 @@
+TCP and HTTP tests
+
+axl: hostile web server.
diff --git a/tools/axl/axl.py b/tools/axl/axl.py
new file mode 100755
index 000000000..f7b541083
--- /dev/null
+++ b/tools/axl/axl.py
@@ -0,0 +1,253 @@
+#!/usr/bin/env python
+
+#
+# Copyright 2007, The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+"""
+ axl.py: HTTP Client torture tester
+
+"""
+
+import sys, time
+
+from twisted.internet import protocol, reactor, defer
+from twisted.internet.protocol import ServerFactory, Protocol
+
+import singletonmixin, log
+
+class BaseProtocol(Protocol):
+ def __init__(self):
+ self.log = log.Log.getInstance()
+
+ def write(self, data):
+ self.log("BaseProtocol.write()", len(data), data)
+ return self.transport.write(data)
+
+ def dataReceived(self, data):
+ self.log("BaseProtocol.dataReceived()", len(data), data)
+
+ def connectionMade(self):
+ self.log("BaseProtocol.connectionMade()")
+ self.transport.setTcpNoDelay(1) # send immediately
+
+ def connectionLost(self, reason):
+ self.log("BaseProtocol.connectionLost():", reason)
+
+ def sendResponse(self, response):
+ self.write("HTTP/1.1 200 OK\r\n")
+ self.write("Content-Length: %d\r\n\r\n" % len(response))
+ if len(response) > 0:
+ self.write(response)
+
+
+# Tests
+# 8000: test driven by resource request
+
+class Drop(BaseProtocol):
+ """Drops connection immediately after connect"""
+ PORT = 8001
+ def connectionMade(self):
+ BaseProtocol.connectionMade(self)
+ self.transport.loseConnection()
+
+class ReadAndDrop(BaseProtocol):
+ """Read 1st line of request, then drop connection"""
+ PORT = 8002
+ def dataReceived(self, data):
+ BaseProtocol.dataReceived(self, data)
+ self.transport.loseConnection()
+
+class GarbageStatus(BaseProtocol):
+ """Send garbage statusline"""
+ PORT = 8003
+ def dataReceived(self, data):
+ BaseProtocol.dataReceived(self, data)
+ self.write("welcome to the jungle baby\r\n")
+
+class BadHeader(BaseProtocol):
+ """Drop connection after a header is half-sent"""
+ PORT = 8004
+ def dataReceived(self, data):
+ BaseProtocol.dataReceived(self, data)
+ self.write("HTTP/1.1 200 OK\r\n")
+ self.write("Cache-Contr")
+ time.sleep(1)
+ self.transport.loseConnection()
+
+class PauseHeader(BaseProtocol):
+ """Pause for a second in middle of a header"""
+ PORT = 8005
+ def dataReceived(self, data):
+ BaseProtocol.dataReceived(self, data)
+ self.write("HTTP/1.1 200 OK\r\n")
+ self.write("Cache-Contr")
+ time.sleep(1)
+ self.write("ol: private\r\n\r\nwe've got fun and games")
+ time.sleep(1)
+ self.transport.loseConnection()
+
+class Redirect(BaseProtocol):
+ PORT = 8006
+ def dataReceived(self, data):
+ BaseProtocol.dataReceived(self, data)
+ self.write("HTTP/1.1 302 Moved Temporarily\r\n")
+ self.write("Content-Length: 0\r\n")
+ self.write("Location: http://shopping.yahoo.com/p:Canon PowerShot SD630 Digital Camera:1993588104;_ylc=X3oDMTFhZXNmcjFjBF9TAzI3MTYxNDkEc2VjA2ZwLXB1bHNlBHNsawNyc3NfcHVsc2U0LmluYw--\r\n\r\n")
+ self.transport.loseConnection()
+
+class DataDrop(BaseProtocol):
+ """Drop connection in body"""
+ PORT = 8007
+ def dataReceived(self, data):
+ if data.find("favico") >= 0:
+ self.write("HTTP/1.1 404 Not Found\r\n\r\n")
+ self.transport.loseConnection()
+ return
+
+ BaseProtocol.dataReceived(self, data)
+ self.write("HTTP/1.1 200 OK\r\n")
+# self.write("Content-Length: 100\r\n\r\n")
+ self.write("\r\n")
+# self.write("Data cuts off < 100 here!")
+# time.sleep(4)
+ self.transport.loseConnection()
+
+class DropOnce(BaseProtocol):
+ """Drop every other connection"""
+ PORT = 8008
+ COUNT = 0
+ def dataReceived(self, data):
+ BaseProtocol.dataReceived(self, data)
+ self.write("HTTP/1.1 200 OK\r\n")
+ self.write("Content-Length: 5\r\n\r\n")
+
+ if (not(DropOnce.COUNT & 1)):
+ self.write("HE")
+ else:
+ self.write("HELLO")
+ self.transport.loseConnection()
+
+ DropOnce.COUNT += 1
+
+class NoCR(BaseProtocol):
+ """Send headers without carriage returns"""
+ PORT = 8009
+ def dataReceived(self, data):
+ BaseProtocol.dataReceived(self, data)
+ self.write("HTTP/1.1 200 OK\n")
+ self.write("Content-Length: 5\n\n")
+
+ self.write("HELLO")
+ self.transport.loseConnection()
+
+class PipeDrop(BaseProtocol):
+ PORT = 8010
+ COUNT = 0
+ def dataReceived(self, data):
+ BaseProtocol.dataReceived(self, data)
+ if not PipeDrop.COUNT % 3:
+ self.write("HTTP/1.1 200 OK\n")
+ self.write("Content-Length: 943\n\n")
+
+ self.write(open("./stfu.jpg").read())
+ PipeDrop.COUNT += 1
+
+ else:
+ self.transport.loseConnection()
+ PipeDrop.COUNT += 1
+
+class RedirectLoop(BaseProtocol):
+ """Redirect back to same resource"""
+ PORT = 8011
+ def dataReceived(self, data):
+ BaseProtocol.dataReceived(self, data)
+ self.write("HTTP/1.1 302 Moved Temporarily\r\n")
+ self.write("Content-Length: 0\r\n")
+ self.write("Location: http://localhost:8011/\r\n")
+ self.write("\r\n")
+ self.transport.loseConnection()
+
+class ReadAll(BaseProtocol):
+ """Read entire request"""
+ PORT = 8012
+
+ def connectionMade(self):
+ self.count = 0
+
+ def dataReceived(self, data):
+ BaseProtocol.dataReceived(self, data)
+ self.count += len(data)
+ if self.count == 190890:
+ self.transport.loseConnection()
+
+class Timeout(BaseProtocol):
+ """Timout sending body"""
+ PORT = 8013
+
+ def connectionMade(self):
+ self.count = 0
+
+ def dataReceived(self, data):
+ BaseProtocol.dataReceived(self, data)
+ if self.count == 0: self.write("HTTP/1.1 200 OK\r\n\r\n")
+ self.count += 1
+
+class SlowResponse(BaseProtocol):
+ """Ensure client does not time out on slow writes"""
+ PORT = 8014
+
+ def connectionMade(self):
+ self.count = 0
+
+ def dataReceived(self, data):
+ BaseProtocol.dataReceived(self, data)
+ if self.count == 0: self.write("HTTP/1.1 200 OK\r\n\r\n")
+ self.sendPack(0)
+
+ def sendPack(self, count):
+ if count > 10:
+ self.transport.loseConnection()
+
+ self.write("all work and no play makes jack a dull boy %s\n" % count)
+ d = defer.Deferred()
+ d.addCallback(self.sendPack)
+ reactor.callLater(15, d.callback, count + 1)
+
+
+# HTTP/1.1 200 OK
+# Cache-Control: private
+# Content-Type: text/html
+# Set-Cookie: PREF=ID=10644de62c423aa5:TM=1155044293:LM=1155044293:S=0lHtymefQRs2j7nD; expires=Sun, 17-Jan-2038 19:14:07 GMT; path=/; domain=.google.com
+# Server: GWS/2.1
+# Transfer-Encoding: chunked
+# Date: Tue, 08 Aug 2006 13:38:13 GMT
+
+def main():
+ # Initialize log
+ log.Log.getInstance(sys.stdout)
+
+ for protocol in Drop, ReadAndDrop, GarbageStatus, BadHeader, PauseHeader, \
+ Redirect, DataDrop, DropOnce, NoCR, PipeDrop, RedirectLoop, ReadAll, \
+ Timeout, SlowResponse:
+ factory = ServerFactory()
+ factory.protocol = protocol
+ reactor.listenTCP(protocol.PORT, factory)
+
+
+ reactor.run()
+
+if __name__ == '__main__':
+ main()
diff --git a/tools/axl/chewie.py b/tools/axl/chewie.py
new file mode 100755
index 000000000..7ed2ba6f3
--- /dev/null
+++ b/tools/axl/chewie.py
@@ -0,0 +1,184 @@
+#!/usr/bin/env python
+
+##
+## chewie.py
+## chews browser http log. draws graph of connections
+## Be sure there is only one pageload in the log.
+##
+## you'll want to
+## sudo apt-get install python-matplotlib
+## before running this
+##
+
+import sys, pylab
+
+# can't just use a dict, because there can be dups
+class Queue:
+ def __init__(self):
+ self.queue = []
+
+ def add(self, url, time):
+ self.queue.append([url, time])
+
+ def get(self, url):
+ for x in range(len(self.queue)):
+ rec = self.queue[x]
+ if rec[0] == url:
+ del self.queue[x]
+ return rec[1]
+
+## pull out request lag -- queue to start to done
+def lag():
+
+ font = {'color': '#909090', 'fontsize': 6}
+ extractMe = {
+ 'RequestQueue.queueRequest': "Q",
+ 'Connection.openHttpConnection()': "O",
+ 'Request.sendRequest()': "S",
+ 'Request.requestSent()': "T",
+ 'processRequests()': 'R',
+ 'Request.readResponse():': "D", # done
+ 'clearPipe()': 'U', # unqueue
+ 'Request.readResponse()': 'B', # read data block
+ 'Request.readResponseStatus():': 'HR', # read http response line
+ 'hdr': 'H', # http header
+ }
+ keys = extractMe.keys()
+
+ f = open(sys.argv[1], "r")
+
+ t0 = None
+
+ # thread, queued, opened, send, sent, reading, read, uri, server, y
+ # 0 1 2 3 4 5 6 7 8 9
+ vals = []
+
+ queued = Queue()
+ opened = {"http0": None,
+ "http1": None,
+ "http2": None,
+ "http3": None,
+ "http4": None,
+ "http5": None}
+ active = {"http0": [],
+ "http1": [],
+ "http2": [],
+ "http3": [],
+ "http4": [],
+ "http5": []}
+ connectionCount = 0
+ byteCount = 0
+ killed = [[], []]
+
+ while (True):
+ line = f.readline()
+ if len(line) == 0: break
+
+ splitup = line.split()
+
+ # http only
+ if splitup[0] != "V/http": continue
+
+ x = splitup[3:]
+
+ # filter to named lines
+ if x[2] not in keys: continue
+ x[2] = extractMe[x[2]]
+
+ # normalize time
+ if t0 == None: t0 = int(x[0])
+ x[0] = int(x[0]) - t0
+
+ thread, action = x[1], x[2]
+ if action == "Q":
+ time, url = x[0], x[3]
+ queued.add(url, time)
+ elif action == "O":
+ # save opened time and server for this thread, so we can stuff it in l8r
+ time, thread, host = x[0], x[1], x[4]
+ opened[thread] = [time, host, connectionCount]
+ connectionCount += 1
+ elif action == "S":
+ time, thread, url = x[0], x[1], x[3]
+ opentime, host, connection = opened[thread]
+ qtime = queued.get(url)
+ record = [thread, qtime, opentime, time, None, None, None, url, host, connection]
+ active[thread].append(record)
+ elif action == "T":
+ time, thread = x[0], x[1]
+ record = active[thread][-1]
+ record[4] = time
+ elif action == "R":
+ print x
+ if x[3] in ["sleep", "no", "wait"]: continue
+ time, thread, = x[0], x[1]
+ record = active[thread][0]
+ record[5] = time
+ elif action == 'U':
+ thread = x[1]
+ record = active[thread][0]
+ killed[0].append(record[9])
+ killed[1].append(x[0])
+ queued.add(record[7], record[1])
+ del active[thread][0]
+ elif action == "D":
+ time, thread = x[0], x[1]
+ record = active[thread][0]
+ record[6] = time
+ vals.append(record)
+ del active[thread][0]
+ print record
+ # print record[3] / 1000, record[6] / 1000, record[7]
+ elif action == "B":
+ byteCount += int(x[3])
+ elif action == "HR":
+ byteCount += int(x[2])
+
+ f.close()
+
+ rng = range(connectionCount)
+
+ opened = []
+ drawn = [False for x in rng]
+ for val in vals:
+ y= val[9]
+ if not drawn[y]:
+ drawn[y] = True
+ opened.append(val[2])
+ pylab.text(0, y - 0.25, "%s %s %s" % (val[9], val[0][4], val[8]), font)
+
+ # define limits
+ # pylab.plot([vals[-1][6]], rng)
+
+ print opened, rng
+ pylab.plot(opened, rng, 'ro')
+ pylab.plot(killed[1], killed[0], 'rx')
+
+ for val in vals:
+ thread, queued, opened, send, sent, reading, read, uri, server, y = val
+ # send arrow
+ arrow = pylab.Arrow(send, y, sent - send, 0)
+ arrow.set_facecolor("g")
+ ax = pylab.gca()
+ ax.add_patch(arrow)
+ # read arrow
+ arrow = pylab.Arrow(reading, y, read - reading, 0)
+ arrow.set_facecolor("r")
+ ax = pylab.gca()
+ ax.add_patch(arrow)
+
+ caption = \
+ "\nrequests: %s\n" % len(vals) + \
+ "byteCount: %s\n" % byteCount + \
+ "data rate: %s\n" % (1000 * byteCount / vals[-1][6])+ \
+ "connections: %s\n" % connectionCount
+
+ pylab.figtext(0.82, 0.30, caption, bbox=dict(facecolor='lightgrey', alpha=0.5))
+
+ # print lines, [[x, x] for x in range(len(vals))]
+ # pylab.plot(lines, [[x, x] for x in range(len(vals))], 'r-')
+
+ pylab.grid()
+ pylab.show()
+
+if __name__ == '__main__': lag()
diff --git a/tools/axl/chewperf.py b/tools/axl/chewperf.py
new file mode 100755
index 000000000..582bdb5ac
--- /dev/null
+++ b/tools/axl/chewperf.py
@@ -0,0 +1,87 @@
+#!/usr/bin/env python
+
+"""
+ chewperf.py: Chew an http perf log
+ bucketize
+
+"""
+
+import sys, time
+
+def resets():
+ f = open(sys.argv[1]).read()
+ rawLines = f.split('\n')
+
+ times = []
+ for x in range(len(rawLines)):
+ line = rawLines[x].split()
+ try:
+ if line[-1] == "SIGNAL_STRENGTH":
+ ts = int(rawLines[x - 1].split()[-1])
+ times.append(ts)
+ except:
+ pass
+
+ return times
+
+def augment():
+ f = open(sys.argv[1]).read()
+ rawLines = f.split('\r\n')
+
+ out = []
+ t0 = None
+ last = 0
+ for line in rawLines:
+ if "Pulled" in line:
+ chewed = [int(line.split()[5]), int(line.split()[7])]
+ if not t0: t0 = chewed[1]
+ tm = chewed[1] - t0
+ out.append("%s %d" % (line, (tm - last)))
+ last = tm
+ else:
+ out.append(line)
+ print "\n".join(out)
+
+def chew():
+ f = open(sys.argv[1]).read()
+ rawLines = f.split('\n')
+ lines = [x for x in rawLines if "Pulled" in x]
+
+ sidx = lines[0].split().index("Pulled")
+ print "sidx", sidx
+ chewed = [[int(x.split()[sidx + 2]), int(x.split()[sidx + 4])] for x in lines]
+
+ t0 = chewed[0][1]
+ tLast = chewed[-1][1]
+ chewed = [[x[1] - t0, x[0]] for x in chewed]
+
+ totalTime = tLast - t0
+ bytes = sum(x[1] for x in chewed)
+ print "total time", totalTime, "bytes", bytes, "rate", bytes * 1000 / totalTime
+
+ buckets = {}
+ for x in chewed:
+ bucket = x[0] / 1000
+ bytes = x[1]
+ if bucket in buckets:
+ buckets[bucket] += bytes
+ else:
+ buckets[bucket] = bytes
+
+ top = max(buckets.keys())
+ for x in range(top):
+ if x not in buckets.keys():
+ buckets[x] = 0
+
+ # smooth
+ window = [0 for x in range(5)]
+
+ for x in range(len(buckets.items())):
+ window[x % len(window)] = buckets.items()[x][1]
+ print "%s\t%s" % (buckets.items()[x][0], sum(window) / len(window))
+
+def main():
+ chew()
+
+if __name__ == '__main__':
+ main()
diff --git a/tools/axl/log.py b/tools/axl/log.py
new file mode 100644
index 000000000..7543270b1
--- /dev/null
+++ b/tools/axl/log.py
@@ -0,0 +1,55 @@
+#!/usr/bin/env python
+
+#
+# Copyright 2007, The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+import time, sys
+import singletonmixin
+
+class Log(singletonmixin.Singleton):
+
+ def __init__(self, file):
+ """_file: filename or open file"""
+
+ if type(file) is str:
+ self._file = open(file, "a")
+ else:
+ self._file = file
+
+ def _getTime(self):
+ tm = time.time()
+ return "%s:%.2d" % (time.strftime('%m/%d/%Y %H:%M:%S',
+ time.localtime(tm)),
+ int((tm - int(tm)) * 100))
+
+ def _log(self, *logstrs):
+ timeStr = self._getTime()
+ for ln in " ".join(map(str, logstrs)).split("\n"):
+ self._file.write("%s %s\n" % (timeStr, ln))
+ self._file.flush()
+
+ def debug(self, *logstrs):
+ self._log("D", *logstrs)
+ def info(self, *logstrs):
+ self._log("I", *logstrs)
+ def warn(self, *logstrs):
+ self._log("W", *logstrs)
+ def error(self, *logstrs):
+ self._log("E", *logstrs)
+
+ # default to info
+ log = info
+ __call__ = log
diff --git a/tools/axl/random.dat b/tools/axl/random.dat
new file mode 100644
index 000000000..48c5d48bf
--- /dev/null
+++ b/tools/axl/random.dat
Binary files differ
diff --git a/tools/axl/singletonmixin.py b/tools/axl/singletonmixin.py
new file mode 100644
index 000000000..7935d6d7c
--- /dev/null
+++ b/tools/axl/singletonmixin.py
@@ -0,0 +1,220 @@
+"""
+A Python Singleton mixin class that makes use of some of the ideas
+found at http://c2.com/cgi/wiki?PythonSingleton. Just inherit
+from it and you have a singleton. No code is required in
+subclasses to create singleton behavior -- inheritance from
+Singleton is all that is needed.
+
+Assume S is a class that inherits from Singleton. Useful behaviors
+are:
+
+1) Getting the singleton:
+
+ S.getInstance()
+
+returns the instance of S. If none exists, it is created.
+
+2) The usual idiom to construct an instance by calling the class, i.e.
+
+ S()
+
+is disabled for the sake of clarity. If it were allowed, a programmer
+who didn't happen notice the inheritance from Singleton might think he
+was creating a new instance. So it is felt that it is better to
+make that clearer by requiring the call of a class method that is defined in
+Singleton. An attempt to instantiate via S() will restult in an SingletonException
+being raised.
+
+3) If S.__init__(.) requires parameters, include them in the
+first call to S.getInstance(.). If subsequent calls have parameters,
+a SingletonException is raised.
+
+4) As an implementation detail, classes that inherit
+from Singleton may not have their own __new__
+methods. To make sure this requirement is followed,
+an exception is raised if a Singleton subclass includ
+es __new__. This happens at subclass instantiation
+time (by means of the MetaSingleton metaclass.
+
+By Gary Robinson, grobinson@transpose.com. No rights reserved --
+placed in the public domain -- which is only reasonable considering
+how much it owes to other people's version which are in the
+public domain. The idea of using a metaclass came from
+a comment on Gary's blog (see
+http://www.garyrobinson.net/2004/03/python_singleto.html#comments).
+Not guaranteed to be fit for any particular purpose.
+"""
+
+class SingletonException(Exception):
+ pass
+
+class MetaSingleton(type):
+ def __new__(metaclass, strName, tupBases, dict):
+ if '__new__' in dict:
+ raise SingletonException, 'Can not override __new__ in a Singleton'
+ return super(MetaSingleton,metaclass).__new__(metaclass, strName, tupBases, dict)
+
+ def __call__(cls, *lstArgs, **dictArgs):
+ raise SingletonException, 'Singletons may only be instantiated through getInstance()'
+
+class Singleton(object):
+ __metaclass__ = MetaSingleton
+
+ def getInstance(cls, *lstArgs):
+ """
+ Call this to instantiate an instance or retrieve the existing instance.
+ If the singleton requires args to be instantiated, include them the first
+ time you call getInstance.
+ """
+ if cls._isInstantiated():
+ if len(lstArgs) != 0:
+ raise SingletonException, 'If no supplied args, singleton must already be instantiated, or __init__ must require no args'
+ else:
+ if len(lstArgs) != cls._getConstructionArgCountNotCountingSelf():
+ raise SingletonException, 'If the singleton requires __init__ args, supply them on first instantiation'
+ instance = cls.__new__(cls)
+ instance.__init__(*lstArgs)
+ cls.cInstance = instance
+ return cls.cInstance
+ getInstance = classmethod(getInstance)
+
+ def _isInstantiated(cls):
+ return hasattr(cls, 'cInstance')
+ _isInstantiated = classmethod(_isInstantiated)
+
+ def _getConstructionArgCountNotCountingSelf(cls):
+ return cls.__init__.im_func.func_code.co_argcount - 1
+ _getConstructionArgCountNotCountingSelf = classmethod(_getConstructionArgCountNotCountingSelf)
+
+ def _forgetClassInstanceReferenceForTesting(cls):
+ """
+ This is designed for convenience in testing -- sometimes you
+ want to get rid of a singleton during test code to see what
+ happens when you call getInstance() under a new situation.
+
+ To really delete the object, all external references to it
+ also need to be deleted.
+ """
+ try:
+ delattr(cls,'cInstance')
+ except AttributeError:
+ # run up the chain of base classes until we find the one that has the instance
+ # and then delete it there
+ for baseClass in cls.__bases__:
+ if issubclass(baseClass, Singleton):
+ baseClass._forgetClassInstanceReferenceForTesting()
+ _forgetClassInstanceReferenceForTesting = classmethod(_forgetClassInstanceReferenceForTesting)
+
+
+if __name__ == '__main__':
+ import unittest
+
+ class PublicInterfaceTest(unittest.TestCase):
+ def testReturnsSameObject(self):
+ """
+ Demonstrates normal use -- just call getInstance and it returns a singleton instance
+ """
+
+ class A(Singleton):
+ def __init__(self):
+ super(A, self).__init__()
+
+ a1 = A.getInstance()
+ a2 = A.getInstance()
+ self.assertEquals(id(a1), id(a2))
+
+ def testInstantiateWithMultiArgConstructor(self):
+ """
+ If the singleton needs args to construct, include them in the first
+ call to get instances.
+ """
+
+ class B(Singleton):
+
+ def __init__(self, arg1, arg2):
+ super(B, self).__init__()
+ self.arg1 = arg1
+ self.arg2 = arg2
+
+ b1 = B.getInstance('arg1 value', 'arg2 value')
+ b2 = B.getInstance()
+ self.assertEquals(b1.arg1, 'arg1 value')
+ self.assertEquals(b1.arg2, 'arg2 value')
+ self.assertEquals(id(b1), id(b2))
+
+
+ def testTryToInstantiateWithoutNeededArgs(self):
+
+ class B(Singleton):
+
+ def __init__(self, arg1, arg2):
+ super(B, self).__init__()
+ self.arg1 = arg1
+ self.arg2 = arg2
+
+ self.assertRaises(SingletonException, B.getInstance)
+
+ def testTryToInstantiateWithoutGetInstance(self):
+ """
+ Demonstrates that singletons can ONLY be instantiated through
+ getInstance, as long as they call Singleton.__init__ during construction.
+
+ If this check is not required, you don't need to call Singleton.__init__().
+ """
+
+ class A(Singleton):
+ def __init__(self):
+ super(A, self).__init__()
+
+ self.assertRaises(SingletonException, A)
+
+ def testDontAllowNew(self):
+
+ def instantiatedAnIllegalClass():
+ class A(Singleton):
+ def __init__(self):
+ super(A, self).__init__()
+
+ def __new__(metaclass, strName, tupBases, dict):
+ return super(MetaSingleton,metaclass).__new__(metaclass, strName, tupBases, dict)
+
+ self.assertRaises(SingletonException, instantiatedAnIllegalClass)
+
+
+ def testDontAllowArgsAfterConstruction(self):
+ class B(Singleton):
+
+ def __init__(self, arg1, arg2):
+ super(B, self).__init__()
+ self.arg1 = arg1
+ self.arg2 = arg2
+
+ b1 = B.getInstance('arg1 value', 'arg2 value')
+ self.assertRaises(SingletonException, B, 'arg1 value', 'arg2 value')
+
+ def test_forgetClassInstanceReferenceForTesting(self):
+ class A(Singleton):
+ def __init__(self):
+ super(A, self).__init__()
+ class B(A):
+ def __init__(self):
+ super(B, self).__init__()
+
+ # check that changing the class after forgetting the instance produces
+ # an instance of the new class
+ a = A.getInstance()
+ assert a.__class__.__name__ == 'A'
+ A._forgetClassInstanceReferenceForTesting()
+ b = B.getInstance()
+ assert b.__class__.__name__ == 'B'
+
+ # check that invoking the 'forget' on a subclass still deletes the instance
+ B._forgetClassInstanceReferenceForTesting()
+ a = A.getInstance()
+ B._forgetClassInstanceReferenceForTesting()
+ b = B.getInstance()
+ assert b.__class__.__name__ == 'B'
+
+ unittest.main()
+
+
diff --git a/tools/axl/stfu.jpg b/tools/axl/stfu.jpg
new file mode 100644
index 000000000..2cf38a54d
--- /dev/null
+++ b/tools/axl/stfu.jpg
Binary files differ
diff --git a/tools/axl/udpEater.py b/tools/axl/udpEater.py
new file mode 100755
index 000000000..1440aadc4
--- /dev/null
+++ b/tools/axl/udpEater.py
@@ -0,0 +1,45 @@
+#!/usr/bin/env python
+
+#
+# Copyright 2007, The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+"""
+ udpEater.py: receives UDP traffic
+
+"""
+
+import time, socket, string
+
+def main():
+ port = 9001
+
+ svrsocket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
+ svrsocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+ svrsocket.bind(('', port))
+
+ hostname = socket.gethostname()
+ ip = socket.gethostbyname(hostname)
+ print 'Server is at IP adress: ', ip
+ print 'Listening for requests on port %s ...' % port
+
+ count = 0
+ while count < 400:
+ data, address = svrsocket.recvfrom(8192)
+ print 'Received packet', count, data[:34]
+ count += 1
+
+if __name__ == "__main__":
+ main()
diff --git a/tools/axl/udpServer.py b/tools/axl/udpServer.py
new file mode 100755
index 000000000..fc37ab85a
--- /dev/null
+++ b/tools/axl/udpServer.py
@@ -0,0 +1,29 @@
+# UDP server example
+import time, socket, string
+
+def main():
+
+ port = 9001
+ buf = open("random.dat").read()
+
+ svrsocket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
+ svrsocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+ svrsocket.bind(('', port))
+
+ # hostname = socket.gethostname()
+ hostname = "localhost"
+ ip = socket.gethostbyname(hostname)
+ print 'Server is at IP adress: ', ip
+ print 'Listening for requests on port %s ...' % port
+
+ data, address = svrsocket.recvfrom(8192)
+
+ count = 0
+ while count < 500:
+ print 'Sending packet', count, 'to', address[0]
+ svrsocket.sendto("%3.3s%s" % (count, buf), address)
+ time.sleep(0.08)
+ count += 1
+
+if __name__ == "__main__":
+ main()