Building with Maven

I’ve decided to use Apache Maven for building the code for my new project. So far I have had a love-hate relationship with Maven. If you don’t know what maven is, the folks over at Apache say Maven is …

“… a software project management and comprehension tool. Based on the concept of a project object model (POM), Maven can manage a project’s build, reporting and documentation from a central piece of information.”

Unfortunately that definition is about as vague as you can get so I will explain what Maven gives me and my project. This also happens to be the list of what I love about Maven:

  • Maven enforces a standardized project structure to all modules in a project.
  • Maven handles dependencies between libraries.
  • Maven builds all of my code and creates a distribution package.

Essentially by using Maven, I can get all of these things without having to write and maintain ant scripts to do most of the work for me. Maven is not without its problems though.

  • Often times the number of lines of XML you need to write ends up to be just as complicated and long as an ant script to do the same thing.
  • I’ve found a lot of bugs particularly with the assembly module. It seems to include dependencies of modules that are set with the “compile” or “test” scope only.
  • The Groovy building plugin for maven does not yet work for Groovy 1.5.x.

I still think using maven was a good choice for my project. It may have some qwerks but I think that I am further ahead than if I had to build everything from scratch.

Using Maven to Generate a Build Number

Earlier I metioned that I use the subversion revision number as a build number. This is actually quite easy to do from Maven. The following plugin descriptor provides you access to the subversion revision number using the ${scm.revision} property.


  <build>
    <plugins>
      <plugin>
        <artifactId>maven-scm-plugin</artifactId>
        <executions>
          <execution>
            <id>getting-scm.revision</id>
            <phase>validate</phase>
            <goals>
              <goal>update</goal>
            </goals>
          </execution>
        </executions>
      </plugin>
    </plugins>
  </build>

Once you have access to the revision number, you can construct a build number using the product version and the revision number. I have added this build number to the Implementation-Version property of the jar manifest. The following plugin definition does this for me:


      <plugin>
        <artifactId>maven-jar-plugin</artifactId>
        <version>2.1</version>
        <configuration>
          <archive>
            <manifestEntries>
              <Implementation-Version>${this.version}.${scm.revision}</Implementation-Version>
            </manifestEntries>
          </archive>
        </configuration>
      </plugin>

The resulting manifest in the jar looks something like this:


Manifest-Version: 1.0
Archiver-Version: Plexus Archiver
Created-By: Apache Maven
Built-By: cdail
Build-Jdk: 1.6.0_03
Implementation-Version: 1.0-SNAPSHOT.93

From Java code, this build number can be easily retrieved. The following code retrieves the Implementation-Version from the manifest:


Package p = getClass().getPackage();
String version = p.getImplementationVersion();

The result is quite elegant but it takes a bit of work to get things into place. Like with many other things to do with Maven, it takes a while to figure out how to do what you want and you need to write a bunch of XML. Once that was done, everything fell into place. It is hard to say at this point if this is better or worse than the chunk of ant script I used to use to perform the same function.

Commercial Software and Open Source Licensing

There are many different types of open source software licenses. These range from the Apache License which is very commercial friendly to the GPL License which is not. In a nutshell, the reason the GPL is not friendly towards commercial software is because of the requirement that anything using the GPL code must in tern by provided under the GPL license and also open source. It is understandable that a company creating commercial software would not want their source code to be open.

In many cases the commercial software just wants to use an existing open source library within the product. For this case, the Lesser GPL License (LGPL) exists. This is a lesser form of the GPL that is supposedly commercial friendly. It does not require the source code of the commercial application to be opened nor does it restrict the licensing.

Even though the LGPL was intended for this type of use, many companies are required to stay away from any LGPL libraries. This is because of the following clause in the LGPL:

You may convey a Combined Work under terms of your choice that, taken together, effectively do not restrict modification of the portions of the Library contained in the Combined Work and reverse engineering for debugging such modifications, if you also do each of the following…

http://www.gnu.org/licenses/lgpl.html

The license clearly indicates that reverse engineering is only permitted for debugging the LGPL licensed library. The problem is really what is the definition of “reverse engineering for debugging”. Many companies do not want to put themselves in a situation where a customer could reverse engineer their code for “debugging” and also manage to get any trade secrets from that process. That type of a legal battle would be difficult and costly to fight. If someone maliciously reverse engineers your code and you bring legal action against them, you do not want to give them any resources they could use as a defense. It is far simpler to avoid the LGPL license all together then you never have to worry about this reverse engineering clause.

That clause was added to protect the integrity of the LGPL library but it has the effect of turning many commercial companyies off the LGPL totally.

Apache CXF with HTTP Basic Authentication

Apache CXF is a great library for providing web services from Java. Their integration with the spring framework is the best of any web services library I have seen. But when it comes to documentation, they are a bit lacking in a few areas. Some tasks that you would think would be part of the framework are simply not provided.

There are two approaches to providing security for Web Services. You can secure the protocol itself (HTTP based security) or you can secure the web services call itself (the actual soap message). The new web services standard around security have opted to take the second approach. Web services and SOAP are designed to be separate from the protocol. The WS-Security specifications deal with securing the SOAP packet itself rather than relying on security provided by the underlying protocol (in many cases HTTP).

Apache CXF provides a WS-Security implementation for handling security and authentication for web services. They also provide a mechanism for setting up HTTPs to provide a secured HTTP connection. The one thing they do not provide is a mechanism to authenticate over HTTP. If you have the luxury to use WS-Security then that is not a problem. For a project I was working on, I needed to provide a web service older clients could access that may not know about WS-Security. The solution was to use HTTP Basic Authentication.

Apache CXF provides access to the AuthorizationPolicy from any message. This gives access to the authentication information used for HTTP. The approach I took was to create a CXF interceptor to intercept the message and check this information for HTTP Basic Authentication parameters. If an error occurs then a SOAP fault is thrown.

The problem with this approach was that a fault is returned to the client as an HTTP 500 response code. What we really want here is to use the 401/403 mechanism as a true web server would for HTTP based authentation. I found the following article on the CXF mailing lists that explained how to set some of that up.

http://mail-archives.apache.org/mod_mbox/incubator-cxf-dev/200702.mbox/
%3CFA1787F64A095C4090E76EBAF8B183E071FC0A@owa-emea.iona.com%3E

I modified that code provide an CXF interceptor that provides HTTP Basic Authentication. When a message is received the HTTP headers are checked. If no user/password is provided, a 401 is returned. This indicates to the client the HTTP authentication is required. If the user/password is invalid, a 403 is returned indicating that this username/password is forbidden to access the service.

Here is an excerpt from the code. I took out some of the implementation details and left the basic “meat” of how to do this from CXF.


public class BasicAuthAuthorizationInterceptor extends 
        SoapHeaderInterceptor {
    private Map<String,String users;
    protected Logger log = Logger.getLogger(getClass());
    @Override public void handleMessage(Message message) 
            throws Fault {
        // This is set by CXF
        AuthorizationPolicy policy = message.get(
            AuthorizationPolicy.class);
        // If the policy is not set, the user did not specify
        // credentials. A 401 is sent to the client to indicate
        // that authentication is required
        if (policy == null) {
            sendErrorResponse(message, 
                HttpURLConnection.HTTP_UNAUTHORIZED);
            return;
        }
        // Verify the password
        String realPassword = getAcualPassword(
            policy.getUserName());
        if (realPassword == null || 
                !realPassword.equals(policy.getPassword())) {
            log.warn("Invalid username or password for user: " 
                + policy.getUserName());
            sendErrorResponse(message, 
                HttpURLConnection.HTTP_FORBIDDEN);
        }
    }
    private void sendErrorResponse(Message message, 
            int responseCode) {
        Message outMessage = getOutMessage(message);
        outMessage.put(Message.RESPONSE_CODE, 
            responseCode);
        // Set the response headers
        Map responseHeaders = message.get(
            Message.PROTOCOL_HEADERS);
        if (responseHeaders != null) {
            responseHeaders.put("WWW-Authenticate", 
                Arrays.asList(new String[]{"Basic realm=realm"}));
            responseHeaders.put("Content-Length", 
                Arrays.asList(new String[]{"0"}));
        }
        message.getInterceptorChain().abort();
        try {
            getConduit(message).prepare(outMessage);
            close(outMessage);
        } catch (IOException e) {
            log.warn(e.getMessage(), e);
        }
    }
    private Message getOutMessage(Message inMessage) {
        Exchange exchange = inMessage.getExchange();
        Message outMessage = exchange.getOutMessage();
        if (outMessage == null) {
            Endpoint endpoint = exchange.get(Endpoint.class);
            outMessage = endpoint.getBinding().createMessage();
            exchange.setOutMessage(outMessage);
        }
        outMessage.putAll(inMessage);
        return outMessage;
    }
    private Conduit getConduit(Message inMessage) 
            throws IOException {
        Exchange exchange = inMessage.getExchange();
        EndpointReferenceType target = exchange.get(
            EndpointReferenceType.class);
        Conduit conduit =
            exchange.getDestination().getBackChannel(
            inMessage, null, target);
        exchange.setConduit(conduit);
        return conduit;
    }
    private void close(Message outMessage) 
            throws IOException {
        OutputStream os = outMessage.getContent(
            OutputStream.class);
        os.flush();
        os.close();
    }
}

New Job, New Company, Same Desk

The last few weeks have been quite busy for me. I’ve taken a new job with IWave Software, LLC. IWave is a subsidiary of Skywire Software who I previously worked for. So basically I work for a different company in a completely different field but I still have the same desk.

I was not really looking to move to a new job but this position had some definite benefits. I have a bit more job security than moving to a brand new company because I know the state of the company as a whole. Also, I still maintain all of my benefits and do not have a probation period. HR and IT are shared between IWave and Skywire so it makes the transition very easy.

IWave is a small company that has a real startup feel to it. They have been around for quite a while but are just starting a new series of products. If you are interested in learning more about IWave, you can check out the latest press release that explains some of the recent company changes.

My job position is a bit different. I have moved from a Team Leadership role to a Senior Software Engineer. It is really more of a lateral move with no more or less responsibility.

IWave is based out of the office in Frisco, Texas. I have already had a chance to get down there and see that office and meet the other people I work with. Since I am working remotely, it also gives me a bit more freedom to work from home as needed. This will help a lot when our first baby comes in the next few months.

With all of the business I have not had much of a chance to do any blogging. I’m hoping to pick it up though when things start to settle down a little bit.

One More Level of Abstraction – Commons Logging

The typical way to solve most problems in languages such as Java is to create one more level of abstraction. One example of this is Apache Commons Logging. There are many different logging libraries used in Java. Log4j is the big one that has been around for many years. Developers had been looking for a logging library to be part of the Java platform for years and this finally happened in Java 1.4 with the java.util.logging package. At this time, log4j was already the defacto standard for logging on the Java platform. The java.util.logging package lacked in features and integration with existing logging infrastructure like log4j. To this day, not many people are using the built in logging capabilities because things like log4j are just so much better.

Apache Commons Logging enters the scene to provide a level of abstraction around different logging packages. It provides a thin layer around many different logging implementations like Log4j, java.util.logging, LogKit and others. This is a great solution if you are creating an embedded application where the logging needs to be done through some other application’s facility.

A lot of projects started using Commons Logging but encountered a lot of problems using it. This is specifically problematic in application servers when dealing with complex classloader hierarchies. The Think again before adopting the commons-logging API article explains some of these issues in more detail. There is even an apology by the author of Commons Logging.

“The purpose of Commons Logging is not to isolate your code from changes in the underlying logging framework. (That’s certainly easy enough to do on your own, and not really worth doing in the first place given the ease of switching from one logging framework to another.) The purpose of Commons Logging is not to somehow be more useful than actual logging frameworks by being more general. The purpose of Commons Logging is not to somehow take the logging world by storm. In fact, there are very limited circumstances in which Commons Logging is useful. If you’re building a stand-alone application, don’t use commons-logging. If you’re building an application server, don’t use commons-logging. If you’re building a moderately large framework, don’t use commons-logging. If however, like the Jakarta Commons project, you’re building a tiny little component that you intend for other developers to embed in their applications and frameworks, and you believe that logging information might be useful to those clients, and you can’t be sure what logging framework they’re going to want to use, then commons-logging might be useful to you.” – http://radio.weblogs.com/0122027/2003/08/15.html

Tomcat uses commons logging. This can create all sorts of strange classloading problems when you deploy a web application that also uses commons logging. There are different versions of commons logging to contend with and difficulties in commons logging to find the actual logging implementation because of the parent tomcat classloader.

It is clear that one more level of abstraction is not always the key to solving all of your problems. In this case, Commons Logging was created for one specific problem and that is for small embedded components. This abstraction may make sense for things like an web application server. When it comes right down to it though, if you are writing a web application server, it is up to you to dictate how logging is to be done for your application. Most Commons Logging implementations in software will end up using Log4j anyway. So if you control the application environment, just use Log4j.

Loading Java Applications

Executing an application in Java is hard. When you work with a language that compiles to native code, the artifact created from your build process is a native executable. If you are using an interpreted language or a language that compiles to bytecode, a runtime environment is required to run the program.

Invoking the Java runtime is simple but for a typical enterprise application, many parameters are required to be passed to the runtime. These parameters consist of arguments to the virtual machine (like memory options), program parameters and classpath parameters.

For a large application, there are often many .jar files that need to be included on the classpath. The software author can create a shell script or small native executable that calls Java with all of the appropriate jar files.

A good alternative to having to specify all of the .jar files in the script is to use a simple boot loader jar. The following two classes make a very simple boot loader that can be used to start a much larger application.


public class ClassLoaderFactory {
    public static ClassLoader createClassLoader(File[] files) {
        List<URL> urlList = new ArrayList<URL>();

        for (File file: files) {
            addURLs(file, urlList);
        }
        URL[] urls = urlList.toArray(new URL[0]);
        return new URLClassLoader(urls, 
            ClassLoaderFactory.class.getClassLoader());
    }

    private static void addURLs(File file, List<URL> list) {
        if (file.isDirectory()) {
            File[] files = file.listFiles();
            for (int i = 0; i < files.length; i++) {
                addURLs(files[i], list);
            }
        }
        else if (file.isFile() && file.getName().endsWith(".jar")) {
            try {
                list.add(file.toURI().toURL());
            }
            catch (MalformedURLException e) {
                // This should not happen. Ignore this file.
            }
        }
    }
}

public class Launcher {
    public static void main(String[] args) {
        String mainClass = null;
        List<File> libraryList = new ArrayList<File>();
        List<String> actualArgsList = new ArrayList<String>();

        for (String arg: args) {
            if (arg.startsWith("-l")) {
                String libraryPath = arg.substring(2);
                libraryList.add(new File(libraryPath));
            }
            else if (mainClass == null) {
                mainClass = arg;
            }
            else {
                actualArgsList.add(arg);
            }
        }

        if (mainClass == null) {
            System.err.println("No main class specified");
            System.exit(-1);
        }

        // Load classloader based on the library path
        ClassLoader classLoader = 
            ClassLoaderFactory.createClassLoader(
            libraryList.toArray(new File[0]));
        Thread.currentThread().setContextClassLoader(classLoader);

        // Execute the main class
        Class bootClass;
        try {
            bootClass = classLoader.loadClass(mainClass);
            Method main = bootClass.getMethod("main", 
                new Class[] {String[].class});
            main.invoke(null, new Object[] {
                actualArgsList.toArray(new String[0])});
        }
        catch (Exception e) {
            e.printStackTrace();
        }
    }
}

You will also need to create a META-INF/MANIFEST.MF file along with these classes. This should specify which is the main class to be run. The contents should be as follows:

Manifest-Version: 1.0
Implementation-Title: XML Transport Bootloader
Main-Class: com.whitehill.bootstrap.Launcher

Those classes and manifest should be packaged into a single jar file called boot.jar. Note that these should be the only classes in boot.jar and it should not include any classes for your running application. If not, you may end up with some classloading issues.

Now to run your application, create the following directory structure

/ – Root directory where the application is run from. You can place a batch file or .exe to start the software here.
/lib – Directory to contain all the .jar files for the application. You can break it up into subdirectories for organizational purposes.

Now to run the application, you can simply run the following:

	java -jar boot.jar -llib com.softwareco.MainClass

This command will start the Java Virtual Machine and run the boot.jar application. This application parses the arguments “-llib com.softwareco.MainClass” and adds all .jar files in the lib directory to the classpath and runs the com.softwareco.MainClass of your application. This command can be placed into a script to make it easier for the user and include any JVM options.

Software Licensing (Part 2)

In Software Licensing (Part 1), I wrote about the issue concerning PC game piracy. This is not only an issue for the gaming market, but also for commercial software. Consider, Microsoft Windows. They took a lot of flack over their Windows Genuine Advantage system when they first rolled it out. The system took a “guilty until proven innocent” approach where it suspected all users of being pirates until they “validated” that their copy of windows was genuine. Microsoft took this system one step further with Windows Vista. After your Windows CD Key has been used twice (once to install the first time and once to allow for a reinstall), the key is locked and will not allow further validation against Windows Genuine Advantage. If you are in the habit of regularly reinstalling your operating system, you still can do this but it requires an extra step to call Microsoft and request that they reactivate your key.

At first, this validation mechanism seems similar to what Valve Software has done with Steam (discussed in part 1). The key difference here is that the software validation only works one way. Microsoft can verify that each CD Key is used once and only once for authorizing a copy of Windows Vista. What it does not do is authentication. The user has no way of proving to the Microsoft Genuine Advantage Servers that they own the CD key entered. Instead, the first person to happen to come along with that key is taken in as the rightful owner with no questions asked. If for some reason, you need to reinstall Windows Vista (more than once), Microsoft has to allow your CD Key to be used on additional computers. Since this is a common task for many users, this type of procedure is a common request of Microsoft Technical Support.

This type of request has become so common that you can even get a CD key reset without even giving your name. A friend of mine was telling me that it is possible to get Windows Vista Ultimate that is Windows Genuine Advantage validated without even owning a copy of the software.

  1. Download Windows Vista. This should be pretty easy to come by. I do not think Microsoft cares too much if you pirate the CD because you cannot use it without your copy being validated by Windows Genuine Advantage.
  2. Download a CD Key generator for Vista OR borrow a CD Key from a friend
  3. Call Microsoft support and indicate that you need to reinstall your computer but your CD key did not work. Microsoft will unlock this key to allow it to be used on more computers.

For all of the work Microsoft put into their new Anti-piracy system, a pirate can now easily get a “genuine” copy of their product simply by calling their tech support. I doubt this was what Microsoft had in mind when they implemented this new security scheme.

Licensing in Software Development

Make licensing easy. Do not treat your customers like criminals. Instead make the licensing process simple. For a long time PC games have required the physical CD to be in the drive in order to play the game even if the entire game contents are on the hard drive. This sort of a thing is more of an annoyance to paying customers rather than a deterrent against piracy. A pirate will crack the software so that no CD is required. Requiring the CD restricts the user more and actually makes the pirated way better. I knew a friend who usually purchased games legally and used cracks to allow him to play the games without the CD in the drive.

Long product keys are not the most friendly form of licensing. I work with business software and prefer using license files as opposed to a simple key. I also believe that these license files should be in plain text with a hash signature. The benefit to this is that you can store lots of information about the customer inside the file. The hash protects the file from tampering and the file can be read by a user.

For example, consider the following license file format:

<?xml version="1.0" encoding="UTF-8"?>
<license>
   <product>Product XYZ</product>
   <version>1.0</version>
   <customer-name>Jane Smith</customer-name>
   <key>647608973E40E3D2A31A886DC1AE3092</key>
</license>

A simple utility can be created to create this license XML file and generate the “key”. The key can be simply the content of the license file with a little salt thrown in. The salt can be secret predetermined random string that is added to the content before hashing. Unless the secret value of the salt is known, the hash can not be recreated with new values for the content. This protects the license from tampering.

To use this license file, the key can be checked to verify that the license has not been tampered with. After that the XML can be read with a standard XML parsing library to extract the license data. The software can store whatever information required here with no restrictions on length or type of content.

Piracy Protection

Licenses do not guarantee that the software will not be pirated. They provide a deterrent so that it is not as easy to pirate the software. So what should a software developer do to protect your software from piracy?

The simplest and probably best solution is to provide a service that accompanies your software. In part 1, Valve Software only allows users access to their online multi-player if they have an authorized account. If you are in a situation where you can provide services along with your software, it may provide an incentive for an otherwise pirate to purchase your software.

When it comes right down to it, if your users want to pirate your software, they will find a way. You can take whatever measures you want to make that harder for them, but they will inevitably find a way around them. Look at things like DVD encryption. Broken. HD-DVD and Blue-ray were said to be impossible to break within the lifespan of the media. Also broken. Providing security mechanisms is a good deterrent to casual pirates but even the best security can be eventually countered. The key is to not make the security too strict that it creates a hassle for paying customers.

Software Licensing (Part 1)

I read an interesting article today at gamepro about PC game piracy. The article quoted from Call of Duty 4’s game blog:

“On another PC related note, we pulled some disturbing numbers this past week about the amount of PC players currently playing multi-player (which was fantastic). What wasn’t fantastic was the percentage of those numbers who were playing on stolen copies of the game on stolen / cracked CD keys of pirated copies (and that was only people playing online).” – Robert Bowling

It was not the fact that PC game piracy was rampant that surprised me but the fact that the pirates with stolen copies of the game are permitted to play online. To me, this seems utterly ridiculous. To understand why this is ridiculous, it is necessary to understand how online gaming works. A typical multi-player PC game allows users to do two basic things, host games for other people to join and join a game hosted by someone else. This service is sort of like a directory listing for games that are being played. Since the listing is controlled by the software publisher’s service and not the game itself, it can validate users and only allow users who purchased the game to use the service.

I believe that no matter how difficult we make the software protection process, pirates will always find a way to break the simple protections put in place over software. When you ship a piece of software, it contains the complete working product. Even if we had 1024 bit encryption keys with 256 character long CD keys for users to enter, when it comes right down to things, the software product knows how to validate that key. All a hacker needs to do is figure out how the program does that validation.

The only way to protect this is by having the key validation done by something other than the software product given to the user. This way they could not modify the product to get around the problem. Games like the Orange Box from Valve Software do this very well. When you purchase the game (either physically or electronically), the game is linked to an account on the Steam service that Valve provides. This service authenticates users and only allows them to play the game if logged into the service. Essentially it provides both the authentication of the user and the game listing services for users.

When you move the validation to a third party system, a pirate would be required to simulate the entire third party system. If a user had a pirated copy of Orange box and a service that “emulated” the steam service, they would still not have access to the game listings that valve provides through the legitimate steam service.

Essentially what this gives is an incentive to users to purchase the game instead of pirating it. Pirating games is simply too easy today. A 12 year old kid can more easily download a cracked copy of a game than to go through the process of acquiring 60$ from their parents and a round trip ticket to the mall. Now, if suddenly you told that 12 year old kid that they will not be able to play the game online with the pirated version, it may provide them with an incentive to go with the more difficult process.

In part 2, I look into the piracy issue with Microsoft Windows.

Moving from Blogger to WordPress

I have wanted to get into blogging for a long time but I was not sure if I would actually stick with it if I did. I started this blog as a bit of an experiment to see if I could get into regularly writing. I think it has been successful and I plan to continue in the future.

A friend of mine at Perfect Hosting offered to help me with the hosting. This would require me to move from blogger to another blogging system. I chose WordPress for this because it seemed to be the easiest to use with the majority of the features I required. It also has a good plugin system. I liked the idea of having a site that I could control a bit more than blogger but I did not want to lose my posts and comments when I made the switch.

After I got WordPress set up, I was able to migrate all of my old content to WordPress. I was very surprised how easy this was to do. To do this, you click on “Manage->Import” in the WordPress admin site. From there you click on “Blogger”. This will take you to a google page to authorize access to your blogger account. After you do this, you select the blogger blog that you want to import. This imports all of the posts and comments from the blogger blog.

After this was complete, I had to update a few things manually. All of the “labels” from blogger were imported as categories in WordPress. Really, I think the labels are more of tags on a post or at least that is how I used them. So I updated all of the posts, changing the categories to tags and adding just 4 categories. I also had to update some of the formatting of code in the posts as it did not translate well to the new system.

It took only about an hour to get the new WordPress system configured how I wanted and all of my data migrated over from my blogger account.

Developer Testing

Testing is a hard problem because there is no way to guarantee that a certain product or piece of code is 100% bug free. Many organizations have testing or “quality assurance” departments who are responsible for doing the majority of product testing before software goes to the customer. Even with a dedicated testing department, developers still have a role to play in testing their code. This article describes the developer testing philosophies used on the project I work on. The project is a server application that mainly manipulates and creates documents.

After the developer testing is complete, the build should be in a good state to enter testing by the quality assurance group. The beauty of this is that it all can happen automatically overnight.

Nightly Builds
A full build of the project is automatically run each night after the developers go home. This is not a testing method by itself but it provides a process for further testing. Developers know that they cannot check in code into version control that will not build that evening. After a new feature has been committed, all members of the team can have access to this the next morning and ensure it works properly. If required, this build can be given to others outside the development team. This is a fully working version of the product that may even go to the customer.

A report of the build is emailed to the team indicating if the build passed or failed. Each build has a unique build number that is the same as the Subversion revision number. This way the build number is unique and the developers can get that exact build code from the source code repository if needed.

Nightly Unit Testing
After the nightly build is done all of the unit tests are run for the whole project. We try to restrict the unit tests to test true “units”. That is to say, test a single class or a very small number of classes. All of the unit tests are either Java JUnit tests or Groovy unit tests. A report of the unit tests is generated using the junitreport ant task. This indicates which tests passed and failed with information on any errors.

Nightly Integration Testing
After the unit tests are complete a set of integration tests are run. These integration tests test the entire product. The integration test suite installs the product, runs the product and then performs a series of tests to ensure that the basic end-to-end functionality of the product works. On this product a bunch of test input files are processed through this running server. The outputs of these are validated to ensure everything works properly. A lightweight test harness was created in Java and Groovy to do run the integration tests and perform this validation. This framework was created from scratch rather than basing it on JUnit as these tests are specific to the application domain. A report for these tests is generated.

Performance Testing
Performance is an important part of this application. The same test harness that runs the integration tests can be used to run the performance tests. Instead of validating if the software works, the speed in which the software performs its tasks is measured. This can be compared to previous versions of the software. This is not run with every nightly build because it takes more than 24 hours to run. It is instead run on occasion over the weekend.