Saturday, June 25, 2011

JUnit Theories

A Theory is slightly different from a parameterised test. A theory defines a "universal truth" which should hold in possibly infinite numbers of potential scenarios (as long as assumptions hold). For example, the statement, "if two objects are equal, then they must have the same hashcode", is a theory.

To set up a Theory in JUnit 4.4, you need to:

  • Annotate the test class with @RunWith(Theories.class)
  • Create test data using the @DataPoints or @DataPoint annotation
  • Write a method (the "theory") annotated with @Theory and accepting arguments of your data points
Example:
The example below shows a test for the Theory that if two objects are equal, they must have the same hashcode. You can define your data points in different ways as I've demonstrated below. When you run the test, the method testHashcodeEquals will be called with all possible permutations of your test data e.g. (foo,foo), (foo,bar), (foo,baz), (foo,1), (foo,2), (bar, bar), (bar, foo) etc.
import static org.hamcrest.core.Is.is;
import static org.junit.Assert.assertThat;
import static org.junit.Assume.assumeThat;

import org.junit.experimental.theories.DataPoint;
import org.junit.experimental.theories.DataPoints;
import org.junit.experimental.theories.Theories;
import org.junit.experimental.theories.Theory;
import org.junit.runner.RunWith;

@RunWith(Theories.class)
public class HashcodeEqualsTheory {

  @DataPoints
  public static String[] data = new String[]{"foo", "bar"};

  @DataPoint
  public static String baz = "baz";

  @DataPoints
  public static Integer[] getData(){
      return new Integer[] {1, 2};
  }

  /**
   * If two objects are equal, they must have the same hashcode
   * @param x
   * @param y
   */
  @Theory
  public void testHashcodeEquals(Object x, Object y){
      assumeThat(x.equals(y), is(true));
      assertThat(x.hashCode(), is(y.hashCode()));
  }
}
Related Posts:
Parameterized Tests in JUnit

Parameterized Tests in JUnit

A parameterised test allows you to run a test against a varying set of data. If you find yourself calling the same test but with different inputs, over and over again, a parameterised test would help make your code cleaner. To create one in JUnit you need to:
  • Annotate the test class with @RunWith(Parameterized.class)
  • Create a static method annotated with @Parameters which returns a collection of arrays. Each array in the collection represents the test data and will be injected into the constructor of the test class
  • Make sure that the test class constructor matches the array items in the Parameters method and stores the test data
  • Write some test methods annotated with @Test
Example:
The example below shows a parameterised test which tests array sorting. The data method returns a collection of arrays. Each array contains an input array and an expected output (sorted) array. When you run the test, the class is instantiated with each of these arrays and then the testSort method is called.
import static org.junit.Assert.assertArrayEquals;

import java.util.Arrays;
import java.util.Collection;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;

@RunWith(Parameterized.class)
public class ArraySortTest {

  @Parameters
  public static Collection<Object[]> data() {
      return Arrays.asList(new Object[][] {
         { new int[] { 1, 3, 2 }, new int[] { 1, 2, 3 } },
         { new int[] { 3, 2, 1 }, new int[] { 1, 2, 3 } },
         { new int[] { 1, 2, 3 }, new int[] { 1, 2, 3 } },
         { new int[] { 1, 1, 1 }, new int[] { 1, 1, 1 } },
         { new int[] { 1 }, new int[] { 1 } },
         { new int[] {}, new int[] {} },
         });
  }

  private final int[] actual;
  private final int[] expected;

  public ArraySortTest(int[] actual, int[] expected){
      this.actual = actual;
      this.expected = expected;
  }

  @Test
  public void testSort(){
      Arrays.sort(actual);
      assertArrayEquals(expected, actual);
  }
}
Documentation
Javadocs: Class Parameterized

Saturday, June 18, 2011

Efficiently Navigating Directories on UNIX

I find myself, like most developers, spending a lot of time navigating directories. Flipping back and forth between logs and application directories with long names can be quite tedious. So, with the help of a few new functions, aliases and config tweaks I've made the navigation process easier and more efficient. You no longer need to remember long paths because you can jump straight to them using their names. You can also choose to bookmark your favourite directories. Here is my setup:

1. Go up to a specific directory
I have a function called upto which allows you to jump up to any directory, on the current path, just by name. This is very useful if you are deep in a directory. I also have autocompletion for this function, so that it shows me valid directory names and completes them for me.

#
# Go up to the specified directory
#
upto(){
  if [ -z $1 ]; then
      echo "Usage: upto [directory]"
      return 1
  fi
  local upto=$1
  cd "${PWD/\/$upto\/*//$upto}"
}

#
# Completion function for upto
#
_upto(){
  local cur=${COMP_WORDS[COMP_CWORD]}
  d=${PWD//\//\ }
  COMPREPLY=( $( compgen -W "$d" -- $cur ) )
}
complete -F _upto upto
Example:
[/www/public_html/animals/hippopotamus/habitat/swamps/images] $ upto h[TAB][TAB]
habitat       hippopotamus
[/www/public_html/animals/hippopotamus/habitat/swamps/images] $ upto hippopotamus
[/www/public_html/animals/hippopotamus] $
2. Go up a specific number of directories
If you know how many levels you want to go up, you can use the up function e.g. up 5 will move you up 5 directories.
#
# Go up a specified number of directories
#
up(){
  if [ -z $1 ]
  then
    cd ..
    return
  fi
  local levels=$1
  local result="."
  while [ $levels -gt 0 ]
  do
    result=$result/..
    ((levels--))
  done
  cd $result
}
3. Go down to a specific directory
Sometimes you want to change to a directory but can't remember the path, or the path name is too long to type. I have a function called jd which allows you to jump down to a directory any level below the current one. It uses Bash's globstar feature so make sure you have it enabled (using shopt -s globstar). (Warning: this may be slow on large directory structures because of the searching involved.)
#
# Jumps to a directory at any level below.
# using globstar
#
jd(){
  if [ -z $1 ]; then
      echo "Usage: jd [directory]";
      return 1
  else
      cd **/$1
  fi
}
Example:
[/www/public_html/animals/hippopotamus/habitat/swamps/images] $ upto hippopotamus
[/www/public_html/animals/hippopotamus] $ jd images
[/www/public_html/animals/hippopotamus/habitat/swamps/images] $
4. CDPATH
The CDPATH variable is a colon-separated list of directories in which the shell looks for destination directories specified by the cd command. Mine is shown below. No matter what directory I am currently in, I can quickly jump to a project in my dev directory with cd <project> because it is on my path.
export CDPATH=".::..:../..:~:~/dev/"
5. Shell Options
I have set the following useful shell options in my .bashrc. The autocd option allows you to change to a directory without using the cd command and cdspell automatically corrects typos in directory names.
shopt -s cdspell     # correct dir spelling errors on cd
shopt -s autocd      # if a command is a dir name, cd to it
shopt -s cdable_vars # if cd arg is not a dir, assume it is a var
6. Quick Aliases
alias ..='cd ..'
alias ...='cd ../..'
alias ....='cd ../../..'
alias .....='cd ../../../..'
alias ......='cd ../../../../..'
7. Keeping a history of visited directories
I came across a useful post on Linux Gazette: History of visited directories in BASH. It contains a script which maintains a history of directories you have visited and then allows you to switch to them easily using a reference number. The command cd -- shows you your history and cd -2 would take you to the second item in your history list. For example:
[/www/public_html/animals] $ cd --
 1  /tmp
 2  /www/public_html/animals/hippopotamus/habitat/swamps/images
 3  /www/public_html/animals/lion
[/www/public_html/animals] $ cd -2
[/www/public_html/animals/hippopotamus/habitat/swamps/images] $
8. Bookmarks
I spend a lot of time moving between different directories especially between logs and application directories. I have implemented a bookmarking feature which allows you to bookmark your favourite directories and then change to them easily.
  • bm: bookmark the current directory
  • bcd: change to the specified bookmark
  • brm: remove a bookmark
  • bcl: clear all bookmarks
  • bll: list all bookmarks
#-------------------------------
# Directory Bookmark Functions
#-------------------------------

#
# Add a bookmark, if it doesn't exist
#
bm(){
  local val=$(pwd)
  for i in ${bookmarks[@]}
  do
    if [ "$i" == "$val" ]
    then
       return 1
    fi
  done
  num=${#bookmarks[@]}
  bookmarks[$num]=$val
}

#
# Goto specified bookmark
# or previous one by default
#
bcd(){
  index=$1
  if [ -z $index ]
  then
     index=$((${#bookmarks[@]}-1))
  fi
  local val=${bookmarks[$index]}
  if [ -z $val ]
  then
     echo "No such bookmark. Type bll to list bookmarks."
     return 1
  else
     cd "$val"
  fi
}

#
# Remove a bookmark
#
brm(){
  if [ $# -lt 1 ]
  then
     echo "Usage: brm [bookmark-index]"
     return 1
  fi
  if [ -z ${bookmarks[$1]} ]
  then
     echo "No such bookmark"
     return 1
  fi
  bookmarks=(${bookmarks[@]:0:$1} ${bookmarks[@]:$(($1 + 1))})
}

#
# Remove all bookmarks
#
bcl(){
    bookmarks=()
}

#
# List all bookmarks
#
bll(){
  if [ ${#bookmarks[@]} -ne 0 ]
  then
     local i=0
     while [ $i -lt ${#bookmarks[@]} ]
     do
       echo $i: ${bookmarks[$i]}
       ((i++))
     done
  fi
  return 0
}

If you have any useful directory-related functions, share them in the comments below!

Related Posts:

Saturday, June 11, 2011

HTML5 - Adding Links to a Canvas

There is no easy way to create links on an HTML5 Canvas. It would be nice if there was, but you have to do it all manually. First draw the link text and then listen to mouse move and mouse click events. When the mouse moves over your link, you need to change the cursor to a "hand" and when the mouse clicks on your link you need to make the browser go to that page.

Here is a demo html page. Save the code to a file and open it in Firefox:

<html>
 <head>
  <script type="text/javascript">

var canvas = document.getElementById("myCanvas");
var ctx;
var linkText="http://www.google.com";
var linkX=5;
var linkY=15;
var linkHeight=10;
var linkWidth;
var inLink = false;

function draw(){
  canvas = document.getElementById("myCanvas");
  // check if supported
  if(canvas.getContext){

    ctx=canvas.getContext("2d");

    //clear canvas
    ctx.clearRect(0, 0, canvas.width, canvas.height);

    //draw the link
    ctx.font='10px sans-serif';
    ctx.fillStyle = "#0000ff";
    ctx.fillText(linkText,linkX,linkY);
    linkWidth=ctx.measureText(linkText).width;

    //add mouse listeners
    canvas.addEventListener("mousemove", on_mousemove, false);
    canvas.addEventListener("click", on_click, false);

  }
}

//check if the mouse is over the link and change cursor style
function on_mousemove (ev) {
  var x, y;

  // Get the mouse position relative to the canvas element.
  if (ev.layerX || ev.layerX) { //for firefox
    x = ev.layerX;
    y = ev.layerY;
  }
  x-=canvas.offsetLeft;
  y-=canvas.offsetTop;

  //is the mouse over the link?
  if(x>=linkX && x <= (linkX + linkWidth) &&
     y<=linkY && y>= (linkY-linkHeight)){
      document.body.style.cursor = "pointer";
      inLink=true;
  }
  else{
      document.body.style.cursor = "";
      inLink=false;
  }
}

//if the link has been clicked, go to link
function on_click(e) {
  if (inLink)  {
    window.location = linkText;
  }
}
  </script>
 </head>
 <body onload="draw()">
  <canvas id="myCanvas" width="150" height="200">Canvas not supported.</canvas>
 </body>
</html>
Related Posts:
HTML5 Canvas - Bouncing Balls

Saturday, June 04, 2011

Associative Arrays in Bash 4

Associative arrays allow you to store key-value pairs and retrieve values using their keys. You can think of them as maps (or hashes) in other programming languages. The following example illustrates how associative arrays can be used to map countries to their capital cities:
#!/bin/bash

#declare an associative array
declare -A capital

#there are different ways to populate the array:
capital=([UK]="London" [Japan]="Tokyo")

#or...
capital[Germany]="Berlin"
capital[China]="Beijing"

#you can even append using +=
capital+=([Belgium]="Brussels" [Egypt]="Cairo")

#print the number of entries
echo "Size: ${#capital[@]}"

#retrieve the capital of Germany
echo "Capital of Germany: ${capital[Germany]}"

#iterate over the keys and print all the entries
echo "Country -> Capital"
for country in "${!capital[@]}"
do
   echo "$country -> ${capital[$country]}"
done
Output:
Size: 6
Capital of Germany: Berlin
Country -> Capital
UK -> London
Germany -> Berlin
Belgium -> Brussels
China -> Beijing
Japan -> Tokyo
Egypt -> Cairo
Further reading:
Bash, version 4, Associative Arrays