spaceblog

a sudoku solver in Erlang

Matt will be pleased to hear that despite my claims that I’d have a productive weekend doing important things for LCA 2007 organisation, I spent the weekend programming.

Some bastard gave a great talk on Erlang at SLUG last Friday, and I really wanted to cut my teeth on it. So, hungover, I spent most of Saturday and a little bit of Sunday morning playing with the getting started guide and writing a Sudoku solver.

If you’re curious, you can pull it down from my bzr repository:

http://repo.spacepants.org/sudoku/sudoku.dev

mock LDAP server object

Today’s big achievement was an LDAP mock object, similar to the SMTP mock object found in paste.fixture. I was refactoring the sign in and sign out code of an in-house application that uses LDAP as an authentication store, and I needed to test that the logic of the controllers was correct. So, referring back to Paste’s lovely fixture module, I came up with the following:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import ldap

class Dummy_ldap(object):

    def __init__(self, server):
        print "dummy ldap init"
        self.server = server

    def install(cls):
        ldap.initialize = cls

    install = classmethod(install)

    def simple_bind_s(self, dn, passwd):
        return True

    def search(self, base, scope, search_filter):
        self.base = base
        self.filter = search_filter
        self.results = [(ldap.RES_SEARCH_ENTRY, [('dn', "cn=test,%s" % self.base),
                                                 ('mail', ['test'])]),
                        (ldap.RES_SEARCH_RESULT, []),
                        ]
        self.counter = 0
        return 1

    def result(self, rid, number):
        r = self.results[self.counter]
        self.counter += 1
        return r

A bit hackish, yes, but I’m not trying to reimplement LDAP here, I just want to trap the calls I use. Obviously a little bit of work is needed to, say, disallow the bind, or throw an exception, but these are trivial extensions.

Just as in Dummy_smtplib, you call the classmethod install to set it up (i.e. monkeypatch ldap.initialize) and you get to trap how it behaves.

1
2
3
4
5
6
class TestAccountController(ControllerTest):

    def test_signin_signout(self):
        Dummy_ldap.install()

        # ... do stuff

Simple!

paste.fixture's Dummy_smtplib

I’m working on two webapps that need to send email to users, and in both, the user is expected to click on a URL to confirm their registration. A common enough idiom for website accounts.

As a disciple of the cult of test-driven development, I want to be able to make a test that generates that email, inspects the contents for the URL, visits that generated URL, and then checks that the registration is completed.

Michael K, who previously suggested other abuses of Python’s dynamic nature, reminded me a few months ago that you could monkeypatch an imported library with your own, and it’d be preserved throughout the “address space” of the running Python program.

I’d not really played with it, and had been putting off writing a test for email because I didn’t really understand what I wanted. The other night at DebSIG, I asked (complained?) again about it, and he said “Hey, paste.fixture already does it!” I’d said I knew, but it had no documentation, and no-one on the paste users list had responded to my request for examples. He gave me a curt “Read the fucking source” response (in a much nicer way of course) and I thought, he’s right! Back in the day I used to read library source code in order to work out poorly documented APIs, why now do I rely on clear documentation so much? I should just dig in and write some example code to test it out, and JFDI.

Enough of the backstory.

Here’s a quick guide to setting up an email sender test, using paste.fixture around your Pylons application.

Firstly, in your controller, you have something that sends email, like so:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
import smtplib

from app.lib.base import BaseController, m

class FooController(BaseController):
    def index(self):
        """Do something and send email while you're at it"""
        s = smtplib.SMTP("localhost")
        
        s.sendmail("from@example.org",
                   "to@example.com",
                   """
Hey, this is a message

http://localhost/foo/activate/37
"""
        s.quit()
        
        m.subexec("foo/index.myt")

    def activate(self, id):
        m.write("awesome, activating %s" % id)

Pretty basic, if we visit /foo/ then we send an email, and then if we visit /foo/activate/N we inform the visitor of the activation.

The test is pretty simple too:

1
2
3
4
5
6
7
import re
import unittest

from paste.fixture import Dummy_smtplib

def TestFooController(unittest.TestCase):
    def test_foo_activation(self):

You just call the classmethod install on Dummy_smtplib to set it up, which does some magic behind the scenes (really it just replaces smtplib.SMTP with itself)

8
        Dummy_smtplib.install()

then run through the process you want to test

10
11
        # get the start page
        res = self.app.get('/foo')

and now we check that the message was sent, and its contents

13
14
15
16
17
18
19
        self.failIfEqual(None, Dummy_smtplib.existing, "no message sent")
        
        match = re.match(r'^.*/foo/activate/([^ ]+)', Dummy_smtplib.existing.message)
        self.failIfEqual(None, match)
        
        # visit the URL
        res = self.app.get('/foo/activate/%s' % match.group(1))

and finally, test the result of the activation.

20
21
        res.mustcontain('awesome')
        res.mustcontain(match.group(1))

You’ve also got to clean up, reset the dummy SMTP library for next time (you’ll get an exception thrown if you don’t, to remind you).

20
        Dummy_smtplib.existing.reset()

If you do any database stuff, then the times to check the status of the data model are just before the actuvation URL is visited, and again afterwards. I keep the model empty for each test, so I can pull out all the records and make sure that there’s only one afterwards, and that it has the right attributes before and after.

Pretty easy stuff.

lca2007 CFP open!

lca2007 CFP open! icon

I was on a jet from Austin to London when it happened, but busy the hours beforehand in a flat in Austin nursing a hangover putting the final touches on the website. So, though I missed the chance to announce at the time, I’ll take this opportunity to blog about it now!

The LCA 2007 CFP is open, so submit your proposal for a talk, miniconf, etc now! (Or in reality, start writing your proposal now, so that you can submit it before the CFP closes ;-)

We’re looking forward to your submissions!