Thursday, March 3, 2016

AngularJS 1.5 And PhantomJS Compatibility Issue

Last week, I've committed the relevant changes1 to issue #5086802 but Codeship3 reported a broken build. I swear I ran the UI test suite before committing and Grunt said everything was good! I decided to take some time to investigate this issue tonight to get to the very bottom of this. 



Broken builds keep me awake at night.



It appears that I've been building from the recent release of AngularJS 1.5 (previously 1.4) and there seems to be some incompatibility with PhantomJS 1.x. 

Interestingly, Codeship reported:
Error: [$injector:modulerr] Failed to instantiate module ng due to: TypeError: '' is not an object (evaluating 'Function.prototype.bind.apply')

After some lucky searching, this link came up:
https://github.com/angular/angular.js/issues/13794

The suggested fix was to move up to PhantomJS2 and update the karma-phantomjs-launcher module.

As to why the test suite worked on my machine but not Codeship's is a big mystery to me...


Note: Last October, as a true believer of CI, I've got Codeship to automatically build Elwood every time I push my changes to BitBucket. Codeship has been doing a great job for the past couple of months happily doing the builds in the cloud. (Hoping CD to come soon but everything must come in baby steps :)) It's an irony that I run a local copy of Elwood and manually trigger the build on Elwood sources. Does this sound like eating my own dog food?



Friday, February 19, 2016

AngularJS Custom Directive: Display Elwood Build Result Stats

I've written my first successful custom AngularJS directive to display build result stats (see issue #508680).

The directive initially displays the build status indicating success, failure or in-progress. Adjacent to this is a clickable icon1 that goes back to the server and retrieve the build result statistics and displays them on the screen.

Display elwood build result stats

The journey to get here is challenging  (especially for someone who works at the back office) but very fulfilling except for one last thing - unit testing.


Below is the directive code:

angular.module('elwoodUiApp')
  .constant('BuildResultStatsUrl', 'http://localhost:8080/buildResultStats/:key/:count')
  .factory('BuildResultStatsResource', function($resource, BuildResultStatsUrl) {
    return $resource(BuildResultStatsUrl, {}, {
      'get': {method: 'GET'}
    });
  })
  .directive('elwBuildResultStats', function() {
    var controller = function($scope, BuildResultStatsResource, ToKeyCount) {
      $scope.toggle = false;
      $scope.getBuildResultStats = function(keyCount) {
        BuildResultStatsResource.get({'key': keyCount.key, 'count': keyCount.count},
          function (successResult) {
            console.log(successResult);
            if (!$scope.buildResultStats) {
              $scope.buildResultStats = [];
              $scope.buildResultStats[ToKeyCount(keyCount)] = {
                'successCount': successResult.successCount,
                'failedCount': successResult.failedCount,
                'ignoredCount': successResult.ignoredCount
              };
            }
            $scope.toggle = !$scope.toggle;
          }, function (errorResult) {
            console.log(errorResult);
          }
        );
      };

      $scope.isShowBuildResultStats = function(keyCount) {
        return $scope.toggle
          && ($scope.buildResultStats
            && ($scope.status == 'SUCCEEDED' || $scope.status === 'FAILED'));
      }
    };

    return {
      restrict: 'A',
      templateUrl: 'views/buildresultstats.html',
      controller: controller,
      scope: {
        status: '@',
        keyCount: '=',
      }
    };
  });


And below is the test code:

describe('Controller: BuildResultStatsCtrl', function () {

  // load the controller's module
  beforeEach(module('elwoodUiApp'));

  var
    scope,
    httpBackend;

  describe('BuildResultStatsCtrl', function() {
    var BuildResultStatsCtrl,
      buildResultStatsResource,
      toKeyCount;

    // Initialize the controller and a mock scope
    beforeEach(inject(function ($controller, $rootScope, $compile, $httpBackend, BuildResultStatsResource, ToKeyCount) {
      scope = $rootScope.$new();
      httpBackend = $httpBackend;
      buildResultStatsResource = BuildResultStatsResource;
      toKeyCount = ToKeyCount;

      var elem = '<div elw-build-result-stats="" key-count="keyCountTuple" status="{{buildStatus}}"></div>';
      scope.status = 'SUCCEEDED';
      scope.keyCount = {
        key: 'PRJ',
        count: '10'
      };

      httpBackend.whenGET('views/buildresultstats.html').respond(200, '');
      var template = $compile(elem)(scope, buildResultStatsResource, toKeyCount);
      console.log(template);

      scope.$digest();
    }));

    it('should display when status is SUCCEEDED', function() {
      var url = 'http://localhost:8080/buildResultStats/PRJ/10';

      expect(scope).toBeDefined();
      expect(scope.toggle).toBeFalsy();
      expect(scope.isShowBuildResultStats).toBeFalsy();
      expect(scope.buildResultStats).toBeUndefined();

      var mockData = {
        successCount: 10,
        failedCount: 3,
        ignoredCount: 2
      };

      httpBackend.expectGET(url).respond(mockData);

      expect(buildResultStatsResource).toBeDefined();
      scope.getBuildResultStats(scope.keyCount);
      httpBackend.flush();

      expect(scope.toggle).toBeTruthy();
    });
  });
});


After compiling the element at line number 30, I'm expecting the scope variables and functions will be fully defined and by the time I invoke "scope.getBuildResultStats(scope.keyCount)" the mocked GET should return the mock data.

Well, it looks like it didn't materialise for some unknown reason.


It's almost quarter past 2 in the morning.

Changes aren't committed yet.

I feel very tired but I think I'm almost there...

Update: (2016-02-23 12:10 AM) Extracting the directive's function into a full blown controller and referring the controller with a controller name might be an option. This allows me to test the controller as an standalone component outside of this directive.

.controller('BuildResultStatsCtrl', function($scope, BuildResultStatsResource, ToKeyCount) {
  // ...
}

return {
  restrict: 'A',
  templateUrl: 'views/buildresultstats.html',
  controller: 'BuildResultStatsCtrl',
  scope: {
    status: '@',
    keyCount: '=',
  }
};


1Generated from http://www.amp-what.com and I find this website very cool.

Saturday, February 6, 2016

Java 7 and Multi-Catch Exception

Java 7 has been out for a few years and I admit this is one of the features I seem to neglect. Perhaps, I've been living under a rock, yeah?

I've got JDK 8 installed and because of my sheer laziness, I'll stick to this version of the compiler to analyse how multi-catch exception is implemented.

She saw first some bracelets, then a pearl necklace, then a Venetian gold cross set with precious stones, of admirable workmanship. She tried on the ornaments before the mirror, hesitated and could not make up her mind to part with them, to give them back.

She kept asking: "Haven't you any more?"

Let's define a few exceptions before dipping a toe in the water.

PearlNecklaceException.java
package org.lyeung.thenecklace;

public class PearlNecklaceException extends RuntimeException {
    // do-nothing
}

VenetianCrossException.java
package org.lyeung.thenecklace;

public class VenetianCrossException extends RuntimeException {
    // do-nothing
}

DiamondNecklaceException.java
package org.lyeung.thenecklace;

public class DiamondNecklaceException extends RuntimeException {
    // do-nothing
}

Followed by the main class we'll look at:
Mathilde.java
package org.lyeung.thenecklace;

public class Mathilde {

    private void retrieveNecklace(int value) throws PearlNecklaceException,
            VenetianCrossException, DiamondNecklaceException {
        if (value == 0) {
            throw new PearlNecklaceException();
        } else if (value == 1) {
            throw new VenetianCrossException();
        } else {
            throw new DiamondNecklaceException();
        }
    }

    public void findNecklace1(int value) {
        try {
            retrieveNecklace(value);
        } catch (PearlNecklaceException e) {
            e.printStackTrace();
        } catch (VenetianCrossException e) {
            e.printStackTrace();
        } catch (DiamondNecklaceException e) {
            e.printStackTrace();
        }
    }

    public void findNecklace2(int value) {
        try {
            retrieveNecklace(value);
        } catch (PearlNecklaceException e) {
            handleException(e);
        } catch (VenetianCrossException e) {
            handleException(e);
        } catch (DiamondNecklaceException e) {
            handleException(e);
        }
    }

    private void handleException(Exception e) {
        e.printStackTrace();
    }

    public void findNecklace3(int value) {
        try {
            retrieveNecklace(value);
        } catch (VenetianCrossException
                | PearlNecklaceException
                | DiamondNecklaceException  e) {
            e.printStackTrace();
        }
    }
}


All three variants of find necklaces call retrieveNecklace which throws different exceptions base on input value.

The first variant of find necklace is pretty old school. We declare different catch blocks for each exception thrown.

The second variant of find necklace, similar to the first variant, but minimises code duplication by moving the print stack trace into a common method.

The third variant uses multi-catch exception that results to a more readable and more compact code.

Under the hood, these variants produce different bytecodes.


The disassembled code for the first variant:
public void findNecklace1(int);
  Code:
     0: aload_0
     1: iload_1
     2: invokespecial #8                  // Method retrieveNecklace:(I)V
     5: goto          29
     8: astore_2
     9: aload_2
    10: invokevirtual #9                  // Method org/lyeung/thenecklace/PearlNecklaceException.printStackTrace:()V
    13: goto          29
    16: astore_2
    17: aload_2
    18: invokevirtual #10                 // Method org/lyeung/thenecklace/VenetianCrossException.printStackTrace:()V
    21: goto          29
    24: astore_2
    25: aload_2
    26: invokevirtual #11                 // Method org/lyeung/thenecklace/DiamondNecklaceException.printStackTrace:()V
    29: return
  Exception table:
     from    to  target type
         0     5     8   Class org/lyeung/thenecklace/PearlNecklaceException
         0     5    16   Class org/lyeung/thenecklace/VenetianCrossException
         0     5    24   Class org/lyeung/thenecklace/DiamondNecklaceException

The exception table says,  "For program counter offset 0 to 5, inclusive and exclusive respectively, the program counter should jump to target offset 8 if PearlNecklaceException is thrown, 16 if VenetianCrossException is thrown and 24 if DiamondNecklaceException is thrown."

All offsets 8, 16 and 24 invoke the caught exception's printStackTrace() method.

The disassembled code for the second variant:
public void findNecklace2(int);
  Code:
     0: aload_0
     1: iload_1
     2: invokespecial #8                  // Method retrieveNecklace:(I)V
     5: goto          32
     8: astore_2
     9: aload_0
    10: aload_2
    11: invokespecial #12                 // Method handleException:(Ljava/lang/Exception;)V
    14: goto          32
    17: astore_2
    18: aload_0
    19: aload_2
    20: invokespecial #12                 // Method handleException:(Ljava/lang/Exception;)V
    23: goto          32
    26: astore_2
    27: aload_0
    28: aload_2
    29: invokespecial #12                 // Method handleException:(Ljava/lang/Exception;)V
    32: return
  Exception table:
     from    to  target type
         0     5     8   Class org/lyeung/thenecklace/PearlNecklaceException
         0     5    17   Class org/lyeung/thenecklace/VenetianCrossException
         0     5    26   Class org/lyeung/thenecklace/DiamondNecklaceException

The exception table is not much different from the first variant.

The disassembled code for the last variant:
public void findNecklace3(int);
    Code:
       0: aload_0
       1: iload_1
       2: invokespecial #8                  // Method retrieveNecklace:(I)V
       5: goto          13
       8: astore_2
       9: aload_2
      10: invokevirtual #14                 // Method java/lang/RuntimeException.printStackTrace:()V
      13: return
    Exception table:
       from    to  target type
           0     5     8   Class org/lyeung/thenecklace/VenetianCrossException
           0     5     8   Class org/lyeung/thenecklace/PearlNecklaceException
           0     5     8   Class org/lyeung/thenecklace/DiamondNecklaceException
}

Now, the exception table looks different. As you can see, the target values all point at 8, where offset 8 prints the stack trace!

The generated bytecodes are definitely smaller and more compact than the previous variants.


Below is the disassembled code for entire Mathilde class:
Compiled from "Mathilde.java"
public class org.lyeung.thenecklace.Mathilde {
  public org.lyeung.thenecklace.Mathilde();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."":()V
       4: return

  private void retrieveNecklace(int) throws org.lyeung.thenecklace.PearlNecklaceException, org.lyeung.thenecklace.VenetianCrossException, org.lyeung.thenecklace.DiamondNecklaceException;
    Code:
       0: iload_1
       1: ifne          12
       4: new           #2                  // class org/lyeung/thenecklace/PearlNecklaceException
       7: dup
       8: invokespecial #3                  // Method org/lyeung/thenecklace/PearlNecklaceException."":()V
      11: athrow
      12: iload_1
      13: iconst_1
      14: if_icmpne     25
      17: new           #4                  // class org/lyeung/thenecklace/VenetianCrossException
      20: dup
      21: invokespecial #5                  // Method org/lyeung/thenecklace/VenetianCrossException."":()V
      24: athrow
      25: new           #6                  // class org/lyeung/thenecklace/DiamondNecklaceException
      28: dup
      29: invokespecial #7                  // Method org/lyeung/thenecklace/DiamondNecklaceException."":()V
      32: athrow

  public void findNecklace1(int);
    Code:
       0: aload_0
       1: iload_1
       2: invokespecial #8                  // Method retrieveNecklace:(I)V
       5: goto          29
       8: astore_2
       9: aload_2
      10: invokevirtual #9                  // Method org/lyeung/thenecklace/PearlNecklaceException.printStackTrace:()V
      13: goto          29
      16: astore_2
      17: aload_2
      18: invokevirtual #10                 // Method org/lyeung/thenecklace/VenetianCrossException.printStackTrace:()V
      21: goto          29
      24: astore_2
      25: aload_2
      26: invokevirtual #11                 // Method org/lyeung/thenecklace/DiamondNecklaceException.printStackTrace:()V
      29: return
    Exception table:
       from    to  target type
           0     5     8   Class org/lyeung/thenecklace/PearlNecklaceException
           0     5    16   Class org/lyeung/thenecklace/VenetianCrossException
           0     5    24   Class org/lyeung/thenecklace/DiamondNecklaceException

  public void findNecklace2(int);
    Code:
       0: aload_0
       1: iload_1
       2: invokespecial #8                  // Method retrieveNecklace:(I)V
       5: goto          32
       8: astore_2
       9: aload_0
      10: aload_2
      11: invokespecial #12                 // Method handleException:(Ljava/lang/Exception;)V
      14: goto          32
      17: astore_2
      18: aload_0
      19: aload_2
      20: invokespecial #12                 // Method handleException:(Ljava/lang/Exception;)V
      23: goto          32
      26: astore_2
      27: aload_0
      28: aload_2
      29: invokespecial #12                 // Method handleException:(Ljava/lang/Exception;)V
      32: return
    Exception table:
       from    to  target type
           0     5     8   Class org/lyeung/thenecklace/PearlNecklaceException
           0     5    17   Class org/lyeung/thenecklace/VenetianCrossException
           0     5    26   Class org/lyeung/thenecklace/DiamondNecklaceException

  private void handleException(java.lang.Exception);
    Code:
       0: aload_1
       1: invokevirtual #13                 // Method java/lang/Exception.printStackTrace:()V
       4: return

  public void findNecklace3(int);
    Code:
       0: aload_0
       1: iload_1
       2: invokespecial #8                  // Method retrieveNecklace:(I)V
       5: goto          13
       8: astore_2
       9: aload_2
      10: invokevirtual #14                 // Method java/lang/RuntimeException.printStackTrace:()V
      13: return
    Exception table:
       from    to  target type
           0     5     8   Class org/lyeung/thenecklace/VenetianCrossException
           0     5     8   Class org/lyeung/thenecklace/PearlNecklaceException
           0     5     8   Class org/lyeung/thenecklace/DiamondNecklaceException
}


Friday, January 22, 2016

JUnit Test Categorisation With Maven

To group by test types, we annotate our test codes with JUnit's org.junit.experimental.categories.Category and customise Maven's test plugin to allow certain groups to run before others.

But just before we start, we need to provide group names to our test codes. As an example, we'll model these group types as Felicite's early and later life.

package org.lyeung.simpleheart;
public interface EarlyLife {}
package org.lyeung.simpleheart;
public interface LaterLife {}
Below is a sample test class:
public class TestFelicity {
    @Category(EarlyLife.class)
    @Test    public void isKnowsTheodore() {
        System.out.println("isKnowsTheodore");
        assertTrue(true);
    }

    @Category(EarlyLife.class)
    @Test    public void isKnowsPaul() {
        System.out.println("isKnowsPaul");
        assertTrue(true);
    }

    @Category(EarlyLife.class)
    @Test    public void isKnowsVirginie() {
        System.out.println("isKnowsVirginie");
        assertTrue(true);
    }

    @Category(LaterLife.class)
    @Test    public void isKnowsFatherColmiche() {
        System.out.println("isKnowsFatherColmiche");
        assertTrue(true);
    }

    @Category(LaterLife.class)
    @Test    public void isParrotLouLou() {
        System.out.println("isParrotLouLou");
        assertTrue(true);
    }
}

As you can see from above, we've categorised our test methods by annotating @Category with the desired group types.

To enable early life tests when Maven runs package goal, we need to update the pom file to customise Surefire plugin:

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-surefire-plugin</artifactId>
    <version>2.18.1</version>
    <dependencies>
        <dependency>
            <groupId>org.apache.maven.surefire</groupId>
            <artifactId>surefire-junit47</artifactId>
            <version>2.18.1</version>
        </dependency>
    </dependencies>
    <configuration>
        <groups>org.lyeung.simpleheart.EarlyLife</groups>
    </configuration>
</plugin>

To enable later life tests when Maven runs integration test or after, we need another update to Failsafe plugin:

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-failsafe-plugin</artifactId>
    <version>2.18.1</version>
    <dependencies>
        <dependency>
            <groupId>org.apache.maven.surefire</groupId>
            <artifactId>surefire-junit47</artifactId>
            <version>2.18.1</version>
        </dependency>
    </dependencies>
    <configuration>
        <groups>org.lyeung.simpleheart.LaterLife</groups>
    </configuration>
    <executions>
        <execution>
            <id>failsafe-integration-tests</id>
            <phase>integration-test</phase>
            <goals>
                <goal>integration-test</goal>
            </goals>
            <configuration>
                <includes>
                    <include>**/*.class</include>
                </includes>
            </configuration>
        </execution>
    </executions>
</plugin>

What this means is that when Maven runs package goal, only tests with early life are invoked. When Maven runs integration test goal or after, both early life and later life tests are invoked1.

"From the threshold of the room she saw Virginia, stretched on her back, her hands joined, her mouth open, and her head thrown back under a black cross bending towards her, between motionless curtains, less white than her face."

Below is a snippet log for package goal:
mvn clean package
-------------------------------------------------------
T E S T S
-------------------------------------------------------
Running org.lyeung.simpleheart.TestFelicity
isKnowsVirginie
isKnowsPaul
isKnowsTheodore
Tests run: 3, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.001 sec - in org.lyeung.simpleheart.TestFelicity

Results :

Tests run: 3, Failures: 0, Errors: 0, Skipped: 0

Below is a snippet log for package verify goal:
mvn clean verify
-------------------------------------------------------
T E S T S
-------------------------------------------------------
Running org.lyeung.simpleheart.TestFelicity
isKnowsVirginie
isKnowsPaul
isKnowsTheodore
Tests run: 3, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.002 sec - in org.lyeung.simpleheart.TestFelicity

Results :

Tests run: 3, Failures: 0, Errors: 0, Skipped: 0

-------------------------------------------------------
T E S T S
-------------------------------------------------------
Running org.lyeung.simpleheart.TestFelicity
isKnowsFatherColmiche
isParrotLouLou
Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.002 sec - in org.lyeung.simpleheart.TestFelicity

Results :

Tests run: 2, Failures: 0, Errors: 0, Skipped: 0

1http://maven.apache.org/guides/introduction/introduction-to-the-lifecycle.html

Friday, January 15, 2016

Why Test Groups Matter

I'm having poach eggs today.

Upon checking the fridge, to my horror, the egg trays are empty!

Just as I'm about to rush out to the nearest supermarket, I just remembered my stovetop broke down last night.

My instinct kicks in. Before I run towards the door, I quickly check the stovetop.

If it decides to take a holiday today, I'll surely head back to my bedroom and get more sleep.

Well, this is just an analogy.




Test tiers are usually made up of unit tests, integration tests and end-to-end tests (E2E).



And if we are to visualise the Agile Test Pyramid...

Unit test, as the name implies, tests for logical and boundary correctness. Most of these tests are in-memory, therefore, they run trail-blazing fast.

Integration test is an aggregation of unit tests that requires external resources. These resources could be transaction boundaries, filesystem or network calls. Resources are usually expensive to setup and perhaps torn down and they need to be made available to the integration test beforehand.

E2E tests for the whole workflow and may touch multiple integration points. It is usually more complex than an integration test. An example of E2E test is a test on checkout workflow. The test must log into the system, select a list of products, put them into the shopping cart then make a checkout.

As you can see, the test cases become more complex as we move up the tiers.






Many years ago, I said to my colleague that he needs to run the whole test suite before committing. He replied back saying, "But it takes too long to run the whole suite."

This made me realise that only a handful of people are willing to sit and wait while they twiddle their thumbs as they stare emptily at the screen. As for myself, I make a quick exit to the lift to get another cup of flat white.





From my experience, if test codes are grouped and ordered to run from bottom to the top of the pyramid, the development cycle will find and fix defects quicker and minimise waste.

You wouldn't run to the closest supermarket without ensuring your stovetop works, would you?

Below is an inverted test pyramid in running time.


Thursday, January 14, 2016

Elwood Build Process High Level Design

Below is a diagram of how the build process works at a conceptual level.


Saturday, January 9, 2016

Elwood High Level Design


"A picture is worth a thousand words..."



Elwood HLD