Showing posts with label testing. Show all posts
Showing posts with label testing. Show all posts

Sunday, February 7, 2010

When Standards Collide

I can't help but shake my head in disappointment every time I run across some specification that is blatantly in violation of the standards it is built on. When I encounter a specification published by an ostensibly-reputable professional organization that contradicts my understanding of the underlying protocols, at first I'm confused. Is my understanding faulty? My memory bad (hint: it is)? Is there a new RFC that updated the protocol when I wasn't looking?

As a recent example, I finally got my hands on the WRIX Interconnect 1.03 specification from the Wireless Broadband Alliance. In which, they specify that partner ISPs must support "passwords up to 253 characters which contain a mixture of alphabetic, numeric, and special characters"; this is written in the comments for the the User-Password attribute in a RADIUS Access-Request packet.

There are two problems with this text that I'm amazed industry professionals did not correct (assuming they noticed):

  1. RFC 2865 section 5.2 clearly states that the maximum length of the User-Password attribute is 130 octets, including the 2-byte attribute header. In other words, the encrypted password text cannot be longer than 128 octets. If this were the only issue, I'd be inclined to believe that the 128 octet limit has been relaxed in a later RFC.

  2. Which leads to the second issue: RFC 2865 says the *encrypted* password text cannot be longer than 128 octets. Section 5.2 also lays out the encryption algorithm, which is a 16-byte block cipher. Being a block cipher, the password plaintext is padded out to a multiple of 16 bytes before the encryption is applied. Which means that a 7 octet password will encrypt to 16 octets of encrypted text. The 253 character password specified by WRIX would encrypt to 256 octets of encrypted text. RADIUS allocates 8 bits per attribute to represent the length of that attribute in octets, including the attribute's 2-byte header. So, if you have 256 octets of encrypted text, the total length of the attribute would be 258 octets.



So, in order to comply with the WRIX Interconnect 1.03 specification, it would appear you are required to violate both RFC 2865 and physics (to get 258 values represented with just 8 bits).

What baffles me is that I would have expected the difficulty of implementing the specification as written would have become obvious in compatibility testing. Surely someone has written unit tests based on the spec to test their implementation. At least once. Right? Right?

Anyway, I imagine the intent of the specification's authors was only to violate RFC 2865 and leave the violation of physics to the hardware guys. It looks like they wanted partner ISPs to allow the longest password representable by the User-Password attribute, ignoring the 128-octet stated limit. Since the encryption algorithm always produces encrypted text that is a multiple of 16-bytes in length and the longest encrypted text that can be stored in the User-Password attribute is 255 - 2 = 253 octets, the question is what is the largest multiple of 16 less than or equal to 253?

240. 240 octets.

Any plaintext 241 octets long or longer will encrypt to 256 bytes of encrypted data, which is not representable by a RADIUS User-Password attribute. So, a more reasonable specification would require implementers to support "passwords up to 240 characters which contain a mixture of alphabetic, numeric, and special characters."

Maybe in revision 1.04.

Friday, October 12, 2007

My last BayPiggies

Well, last night was my last BayPiggies meeting for a few years. The speakers were Mikeal Rogers & Adam Christian from the Open Source Applications Foundation, presenting their new automated web U.I. testing framework, Windmill.

It was a pretty neat demo; if I actually did any serious web development, I would consider using it (or the preexisting Selenium tool) for testing. Being that it was a python user's group rather than a product show-and-tell, I would have liked more details about how Windmill worked, especially from the python side, but I am assured it is "really really really really cool". ;) I guess I'll have to find time to read the code myself.

Anyway, afterwards I got to hang out with my buddies JJ and Zach for a while and chat over a burger. I'm afraid mine was not particularly stimulating conversation, but I'm really looking forward to finding out what exactly it is JJ has been coding up over at MultiCosmic. He assures me it will go live in a few days, and that I'll have to wait to learn what it is when everyone else does. :) I just know it involves FaceBook.

I've only been going to BayPiggies for the past couple of months, but I'm going to miss the monthly chance to learn about some new tool or technology utilizing python. More importantly, I'm going to miss the opportunity to hang out and hear the state-of-web-development address from JJ (he admitted he loves Rails, by the way :) ). Hopefully, I'll still see Zach from time to time in Tokyo.

Speaking of which, I have yet to find a web page for a Python user's group in Tokyo; pyJug doesn't appear to actually hold casual group meetings just weekend retreats and workshop-style meetings. That sounds like fun, but not quite the casual get-together I've gotten used to with BAFUG and BayPiggies meetings.

Friday, June 15, 2007

Python's unittest module ain't that bad

Collin Winter was kind enough to speak to BayPiggies last night about his unittest module replacement, test_harness. The basic premise of the talk is that unittest does not support extensions very well, hence he wrote his own testing framework that did. The same argument is presented in Collin's blog posting titled "Python's unittest module sucks".

However, I have an issue with several of Collin's claimed deficiencies in unittest: they simply aren't there. For example, he claimed that extensions cannot be composed (i.e. multiple extensions cannot be applied to a single test case) easily. I raised the point in the meeting that python's decorators are trivially composable and the TODO annotation described in his blog is trivially implementable as a decorator, usable via unittest, nose, or just about any testing framework. In his presentation, he claimed TODO annotations required over a hundred lines of code across 5 classes to implement using unittest. This simply isn't true: I implemented it in 8 lines while he spoke; adding some polish it's up to 11 plus doc-string, but nowhere near 100 and there isn't a class in sight:

def TODO(func):
"""unittest test method decorator that ignores
exceptions raised by test

Used to annotate test methods for code that may
not be written yet. Ignores failures in the
annotated test method; fails if the text
unexpectedly succeeds.
"""
def wrapper(*args, **kw):
try:
func(*args, **kw)
succeeded = True
except:
succeeded = False
assert succeeded is False, \
"%s marked TODO but passed" % func.__name__
wrapper.__name__ = func.__name__
wrapper.__doc__ = func.__doc__
return wrapper

Collin demonstrated a platform-specific test annotation in his framework. He claimed this would require almost 200 lines of code to implement in unittest, but that too is an overstatement. I had it implemented before he could finish the slide:

def PlatformSpecific(platformList):
"""unittest test method decorator that only
runs test method if os.name is in the
given list of platforms
"""
def decorator(func):
import os
def wrapper(*args, **kw):
if os.name in platformList:
return func(*args, **kw)
wrapper.__name__ = func.__name__
wrapper.__doc__ = func.__doc__
return wrapper
return decorator

The point is that python decorators are a language feature that allow you to trivially wrap any callable with another callable; the latter of which can perform any pre- or post- processing or even avoid calling the decorated function at all. You get transparent composition for free:

class ExampleTestCase(unittest.TestCase):
@TODO
def testToDo(self):
MyModule.DoesNotExistYet('boo')

@PlatformSpecific(('mac', ))
def testMacOnly(self):
MyModule.SomeMacSpecificFunction()

@TODO
@PlatformSpecific(('nt', 'ce'))
def testComposition(self):
MyModule.PukePukePuke()

(If you aren't familar with decorators in python, IBM has a pretty thorough article on the subject)

For the record, I also implemented a proof-of-concept of Collin's reference counting example in a similarly-succinct decorator. In the example presented at BayPiggies, Collin ran the test case 5 times, checking a reference count after each run. I missed how he was getting references counts (len(gc.get_referrers(...)) maybe?) so you need to fill in how to get your object reference counts:

def CheckReferences(func):
def wrapper(*args, **kw):
refCounts = []
for i in range(5):
func(*args, **kw)
refCounts.append(XXXGetRefCount())
assert min(refCounts) != max(refCounts), \
"reference counts changed"
wrapper.__name__ = func.__name__
wrapper.__doc__ = func.__doc__
return wrapper

Adding the repetition count as a parameter would be trivial (see the PlatformSpecific decorator above for an example how). I hard-coded 5 repetitions since that is what Collin used in his presentation.

In all, it took me about 5 minutes to write all three decorators and test them (admittedly, I already had unittest test cases to try my decorators on). I didn't tinker a bit nor was there any poking or prodding of unittest; I didn't subclass anything. You can implement these extensions using standard python syntax and the standard pytingn unittest module. To claim otherwise is simply disingenuous.

Of course, Collin's testframework module uses decorators too, so Collin was clearly aware of their existence. Which prompted me to question Collin's claims of 100+ lines to implement these features using unittest when simple decorators are sufficient. His response was that his numbers were the number of lines of code that would be necessary to implement the TODO and platform-specific annotations using the unittest module without decorators. Which seemed inconsistent with his examples, involving decorators, of how easy it is to use these annotations with test_harness. I wanted to ask him about this contradiction face-to-face after the presentation, but unfortunately he had to catch the Google shuttle home immediately after his talk.

One point that Collin did repeatedly come back to was that logging extensions cannot be implemented using decorators. For example, you cannot have the unittest module log the test run results to a database by wrapping test methods in decorators. In theory you just need to implement your own TestRunner and TestResult subclasses and pass the TestRunner subclass to unittest.TestProgram(). However, if Sebastian Rittau's XML logger TestRunner class for unittest is any indication, changing loggers is non-trivial.

Collin said in his presentation, and I would have to agree, extending unittest logging is painful; composing multiple loggers is prohibitively painful. Of course, if more TestRunner implementations were included in the python standard library, half of this argument would be moot as there would be less need to extend. Right now, only a text logger TestRunner is included.

But to be honest, I don't expect most people really need to replace the logging mechanism (which may be why the standard library doesn't include more loggers). Marking tests as TODO or platform-specific or whatever is pretty universal; recording test run results to a database for analysis is probably far outside the realm of what most people (or even companies outside the Fortune 500 for that matter) need from their test framework. Which may be more of a comment on the sad state of testing than anything, but I digress. In any event, Collin's re-implementation adds value by facilitating logger composition, but to say that not facilitating logger composition makes unittest "suck" seems like a gross overstatement to me.

In all, I left BayPiggies last night having thought a lot more about unittest than I ever have before. And I can't help but think that, for the vast majority of us python hackers down in the trenchs, python's unittest ain't that bad.

Update 2007/06/15 10:05am:
I found the lines-of-code numbers quoted in Collin's presentation in his blog also. My memory was pretty close on the supposed ~100 lines to implement TODO annotations. But it looks like I may have confused his ~200 lines quote for implementing composition of TODO and reference counting with the supposed number of lines to implement platform-specific test annotations. To be clear, though, composition using decorators as I described above requires 0 core classes and 2 lines of code (see my testComposition example above).