In this article we cover what we were trying to achieve, what we managed to do and some of the challenges along the way.
Enigmail is a plugin for the mail client Thunderbird. It allows you to read and write encrypted and signed emails from inside of Thunderbird; it’s currently the most popular way of using OpenPGP (the most popular standard for email encryption). The project is free software and was started in 2001. Since then it has grown and acquired a lot of functionality. Not only does Enigmail allow you to decrypt, encrypt, sign and verify emails - it also supports management of encryption keys from inside of Thunderbird.
Before talking about what we actually did, we should talk briefly about why the team chose to work on Enigmail. Our team was created to work specifically on issues related to online privacy, anonymity and security. Among all open source projects in that field, we felt that encrypted email is probably one of the most common forms of private communication currently in use. At the same time, many of the projects out there are suffering from working in an environment with very scarce resources while trying to support high risk people around the world.
Enigmail is an extremely important project, and one that has suffered from some technical debt. Keep in mind that the project has been around for almost 15 years and is supporting a large number of environments and weird idiosyncrasies in tools and operating systems. On top of that, the community supporting Enigmail is quite small and there are no full-time developers on the project.
These things together made the decision quite easy:
We decided that our contributions to Enigmail would be focused on making the project easier to work with so that more people could easily contribute, and to make it much easier to identify the causes for bugs.
In order to run code from an extension you have to compile it into a file format called XPI, and then load it inside Thunderbird. That makes the development cycle a bit longer than we were used to from other types of projects - we can't just make a one-line change and see the result in our tests in less than a second. That said, the compilation process for Enigmail is quite fast so we still got pretty rapid feedback.
With the purpose of making Enigmail easier to contribute to, our focus was mostly on things that won't be visible to end users. We did end up achieving quite a lot in the time working on this project.
In order to make it easy to contribute, the first step is to make it extremely easy to get the initial environment up and running so you can write code. There are not that many steps involved in doing it manually, but we are fans of automation, so we decided to make the process of getting an environment automated. We ended up doing this with both Vagrant and Docker - that means you can always have a repeatable environment to do development in. We ended up providing support for both to make it possible for people from different environments to get going quickly - Vagrant works better on OS X, while Docker gives a much nicer development experience on Linux. Assuming you have Vagrant or Docker installed, you only need one command in a checked out repository to have a running environment.
After we had figured out what needed to happen in order to provision new machines from scratch for the project, we decided that we wanted a way to automatically know when we broke the build. We achieved this by hooking Travis CI to our repository. With that in place, every time we push a new commit, all tests will be run against a new environment and we will be notified if something fails.
Most of the time, this worked really well - but there were also moments where the Travis environment was different enough that it caused weird issues for us. Probably the worst of those were based on strange MIME settings in the Travis environment. But overall, this provides for a very easy path to keep our code tested and running. It also means that if someone clones the repository they can get continuous integration setup for their repository very easily.
When we first started working on the project, there were almost no tests in the repository. Since we believe automated testing is a good way of keeping track of the quality of a project, we decided early on that adding both unit tests and larger functional tests should be a priority for us.
Sadly, testing is not so easy to do inside of Thunderbird, for the reasons mentioned above - but the developers of Enigmail had helpfully created a small test framework to make it possible (JSUnit). We ended up using this framework for building tests, and we currently have around 150 tests. There are still large parts of the system that are untested, but the situation is better now than it was - and our hope is that new contributors can start out by adding to the tests.
There is another tool called mozmill, that provides for a way to run UI-level tests. However, we didn’t have time to investigate this further.
Once we had some tests to make us feel comfortable, we started out doing refactoring. A large part of these refactorings were focused on breaking out functionality into smaller components that could be understood and worked with independently of each other. In the beginning, almost all Enigmail functionality could be found in only a few modules - EnigmailCommon, EnigmailCore, Enigmail, EnigmailFuncs, EnigmailKeyMgmt and EnigmailDecryptPermanently. Together these modules were responsible for almost 10,000 lines of code. A lot of the functionality in EnigmailCommon, EnigmailFuncs, Enigmail and EnigmailCore was also in the form of different kinds of utilities that didn't really have that much in common with each other.
After we finished our refactorings we are now in a state where there is a much larger number of independent modules - before our refactorings there were 16 main modules, and afterwards 55. We ended up breaking out functionality like encryption, decryption and hashing into different modules. Interacting with GPG and interacting with the GPG agent also got their own modules. We added modules for dealing with logging, external execution and error handling, and so on. EnigmailCommon is completely gone, while EnigmailCore, Enigmail and EnigmailFuncs are minimal. Our hope is that these changes will make it easier for contributors to get an overview of the project and start working in different areas of the code.
We also made smaller changes all over the code base, such as changing var-declarations into const-declarations where they made sense.
Our hope is that going forward, the Enigmail project can keep running static analysis tools to make sure the code stays as clean as possible. Following these kind of recommendations make it easier to avoid simple mistakes that cause tricky bugs.
You might ask whether we have any data on our test coverage. We tried figuring out how to get a solution for this working, but it seems that test coverage inside of Thunderbird is an unsolved problem. After we looked at the problem for a while we decided to move on and contribute in other ways instead - but we still would love to see this problem solved. We’re sure there are many Firefox and Thunderbird extensions out there for whom having a way of checking test coverage would be extremely valuable.
At the end of this stint, we feel we have achieved what we wanted to achieve. The code base feels like it's in better shape now - and hopefully that will translate into more contributions and an easier time building new features. I know I will continue hacking on Enigmail in my spare time.
Much thanks to Patrick Brunschwig and Nico Josuttis of the Enigmail team for their help and patience with us. Also thanks to Rosalie Tolentino who provided some very needed testing and documentation of our provisioning scripts.
The team from Thoughtworks is currently composed of Fan Jiang, Iván Pazmiño, Reinaldo de Souza and Ola Bini.
Disclaimer: The statements and opinions expressed in this article are those of the author(s) and do not necessarily reflect the positions of Thoughtworks.