Tuesday, July 12, 2016

♔ Don't Panic See You In GitHub

After a few months on Hosted Redmine/Bitbucket, I've decided to pull all Elwood developments/wiki/issue tracker into one spot - GitHub. There's nothing wrong with Hosted Redmine and Bitbucket, they both provided me absolutely great services at $0.

It all boils down to splitting projects into personal and public projects. You see, I own 2 Git accounts: Bitbucket and GitHub. My overall intention was to keep all things private in Bitbucket and the rest publicly in GitHub. Elwood was suppose to be just a thought, a result of N-number of days day dreaming about writing a personal CI server. I was expecting this thought to vaporise as days go on, hence a private repository for late night coding activity.

Happy GitHubbing everyone!

♕ Keep Calm We're Moving To Gitflow

I'm evaluating Elwood development with Gitflow. One thing I like about Gitflow is that it gives the ability to better track changes, releases and bug fixes. It also allows me to stay focus in writing one feature at a time. You see, there is an adrenaline rush in me that I sometimes find myself shoving unrelated changes to my timeline. I could commit one large snowball of unrelated codes that may stretch the completion time to 1.5 times longer if I'm lucky or 3 times longer if I'm not-so-lucky.

This definitely contradicts the spirit of agile with the emphasis of delivering in small increments. (Ahhh, agile that starts with a lower case... How pragmatic do I sound?)

I did ask myself if this could be too much of an overhead for a lone developer like me?

Well, time will tell...

Monday, June 20, 2016

How Do I Laugh At Thee? Let Me Count The Ways...

I hate to admit that I'm resource poor, in the sense that I can't afford to keep running my hardwares for 24x7. Neither can I afford to keep installing a new Linux image (JDK/Maven/Jenkins) every time I find a temporary shelter.

Currently, I have Jenkins/Linux running on an old machine. There were a few attempts to have it "VirtualBoxed" on my old laptop. However, since there is a sad fact about me and my laziness, all attempts ended in failure.

How do I laugh at thee? Let me count the ways
I laugh at thee from morning till noon till night
My soul rants which, when Jenkins s'out of sight
For constant installs take hours to days.

By nature, VirtualBox allows me to move the image between hardwares, but I need something much more than this. I need something that I could bring up on demand, something that I can move the data around, something that is lightweight - and Docker seems to fit the bill!

I was fortunate to have a hands-on experience with Docker at work, coincidentally, after playing around a bit with it in my spare time. At that time, I was looking for possible solutions for my quest on CI/CD. 

After spending roughly 2 months, I finally ended up getting Jenkins 1.61 and Redis images running on its own subnet with data directories mounted to host. I had to build my own Redis image as the official Docker image had a few issues when persisting to files. It could be something as easy as disabling persistence which I don't need, but nonetheless, time is ticking and I have to get back to Elwood development!

Below is a diagram of POC:


Dockerised CI tools



The proof of concept is very basic, something that's good enough to keep me going and something to iron out when I come back.

Hopefully, when the dust settles, I'll push these images into Docker Hub.

Repository: https://github.com/lyeung/lyeung-jenkins-ci-tools


1Why am I using Jenkins as oppose to using Elwood for my build? Elwood is light years behind Jenkins and any mature products out there.



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
}