Thursday, January 08, 2015

Do you use mvn on a bash prompt?

Here is my bash version of the DOS script I wrote about in Do you use mvn on a DOS prompt?.

Do you use mvn on a bash prompt? Sick of having to scroll through a tiny bash prompt looking for an error amongst thousands of lines of output? Sick of not being able to search through the log? Want to save logs for future reference?

Well, look no further! I have written raven.sh, a wrapper around mvn that does one thing: saves the output to a file (which still gets displayed on the console) and opens the file in your favourite text editor.

The script itself includes instructions for setting up dependencies (not much set up) and how to use the script.

You can also see this script in GitHub: https://github.com/robertmarkbram/MavenUtilities/blob/master/raven.sh which is where I shall keep it updated. Pastebin too.

#!/bin/bash

# ------------------------------------------------------------------------------
# -- What is Raven?
# A Bash wrapper around mvn to save output to file and open it in an editor
# By Robert Mark Bram
# https://github.com/robertmarkbram/MavenUtilities
# http://robertmarkbramprogrammer.blogspot.com.au/2015/01/do-you-use-mvn-on-bash-prompt.html
# ------------------------------------------------------------------------------
# Set-up Dependencies first.
# - This script
#    - Save this script as raven.sh in some folder you like to use for utilities etc.
#    - Add the path (folder/directory name) where this script lives to your PATH (*see note 1*).
#    - Otherwise you will have to use the absolute path to this script: e.g. "/C/myApps/Batch/raven" instead of just "raven".
# - Maven
#    - Download it from: http://maven.apache.org/
#    - Set M2_HOME variable (*see note 1*) or LOCAL_M2_HOME in this script.
# - Java
#    - Download and install it from: http://www.oracle.com/technetwork/java/javase/downloads/index.html
#    - Set JAVA_HOME variable (*see note 1*) or LOCAL_JAVA_HOME in this script.
# - tee (to send output to file and console)
#    - Should already be part of Cygwin/nix install.
# - Your favourite text editor.
#    - This script defaults to notepad. If you like it, do nothing.
#    - If you want to use something else, set EDITOR in this script
#    - Because we are *nix, you can set it to vim or less as well.
# - Temp dir
#    - This script defaults to ${TEMP}/maven.
#    - If you want to use something else, set TMPDIR in this script
#
# How to use this script.
# - Instead of using "mvn", just use "raven", e.g.
#       raven clean deploy
#       /C/myApps/Batch/raven clean deploy
#
# Note 1 - changing PATH or other environment variables on Win7 and above: two options.
# - OPTION 1: Start button > Search for "Environment Variables for your account" > modify PATH (or other variable) in top section, "user variables for USERNAME"
#    - No re-boot required, just restart the DOS prompt.
#    - PATH is set only for your user. Other logged users will not see it.
# - OPTION 2: Start button > Search for "Edit the System Environment Variables" > Environment Variables > modify PATH (or other variable) in bottom section, "System Variables"
#    - Re-boot required.
#    - PATH is set for all logged in users.


# ------------------------------------------------------------------------------
# -- Journal of Changes
# ------------------------------------------------------------------------------
# Thursday 08 January 2015, 04:57:36 PM
# - Adapted so that shell version works the same way as batch version.
# - Modified the way this script looks for maven and java such that it always uses local versions first.


# ------------------------------------------------------------------------------
# -- Variables for this script.
#     Edit these variables - dependencies of this script.
# ------------------------------------------------------------------------------
# Could be vim or less too.
# EDITOR=/C/Program\ Files\ \(x86\)/IDM\ Computer\ Solutions/UltraEdit/Uedit32.exe
EDITOR=notepad
LOCAL_JAVA_HOME=/C/Program\ Files/Java/jdk1.7.0_67
LOCAL_M2_HOME=/C/apps/apache-maven-3.2.3
TMPDIR=${TEMP}/maven
MAVEN_OPTS="-Xms512m -Xmx1024m -XX:MaxPermSize=256m"
# ############################################################################
# DO NOT EDIT BELOW HERE
# ############################################################################

# ------------------------------------------------------------------------------
# -- Common functions for this script.
# ------------------------------------------------------------------------------

# ===  FUNCTION  ===============================================================
#   DESCRIPTION:  Output message if verbose is on
#    PARAMETERS:  1 - message to be printed
#                 2 - options
#                     - "off" - don't use -e in echo
#       RETURNS:  -
# ==============================================================================
function message() {
   if [ "$#" -eq 2 -a "$2" == "off" ] ; then
      echo "$1" 2>&1 | tee -a "${outputFile}"
   else
      echo -e "$1" 2>&1 | tee -a "${outputFile}"
   fi
}

# ===  FUNCTION  ===============================================================
#   DESCRIPTION:  Check that all dependencies exist.
#    PARAMETERS:  -
#       RETURNS:  -
# ==============================================================================
function checkDependencies() {
   # Temp dir for logs.
   if [ ! -e "$TMPDIR" ] ; then
      mkdir "$TMPDIR"
   fi

   # Check for tee.
   type tee >/dev/null 2>&1 || {
      echo -e "\n**************"
      echo Please update your *nix to install the \"tee\" command.
      echo -e "**************\n"
   }

   # Check editor.
   if [ "${EDITOR}" != "notepad" -a "${EDITOR}" != "notepad.exe" -a "${EDITOR}" != "less" -a "${EDITOR}" != "vim" ] ; then
      if [ ! -e "${EDITOR}" ] ; then
         message "\n**************"
         message "Please update EDITOR [${EDITOR}]"
         message "variable to point to an editor you wish to use. Using notepad."
         message "**************\n"
         set EDITOR=notepad
      fi
   fi

   # Check Maven.
   # First check if local version is defined and exists.
   # Allows user to specify a different maven than what exists in M2_HOME or PATH.
   if [ -e "${LOCAL_M2_HOME}/bin/mvn" ] ; then
      # All good, use local.
      M2_HOME="${LOCAL_M2_HOME}"
      PATH="${M2_HOME}/bin;${PATH}"

   # Next check if M2_HOME set.
   elif [ -e "${M2_HOME}/bin/mvn" ] ; then
      PATH="${M2_HOME}/bin;${PATH}"

   # LOCAL_M2_HOME and M2_HOME don't work.
   # OK, no mvn then.
   else
      # OK, no mvn then.
      message "\n**************"
      message "-- Please download Maven from http://maven.apache.org/ and make"
      message "--   the mvn command available via one of the following methods"
      message "--   (this script detects maven in this order):"
      message "-- 1. Set LOCAL_M2_HOME in this script."
      message "-- 2. Set M2_HOME environment variable (system or user level)."
      message "-- --"
      message "-- We must be able to set M2_HOME from one of these."
      message "**************\n"
      reportResults
      exit 3
   fi

   # Check Java.
   # First check if local version is defined and exists.
   # Allows user to specify a different maven than what exists in JAVA_HOME or PATH.
   if [ -e "${LOCAL_JAVA_HOME}/bin/java" ] ; then
      # All good, use local.
      JAVA_HOME="${LOCAL_JAVA_HOME}"
      PATH="${JAVA_HOME}/bin;${PATH}"

   # Next check if JAVA_HOME set.
   elif [ -e "${JAVA_HOME}/bin/java" ] ; then
      PATH="${JAVA_HOME}/bin;${PATH}"

   # LOCAL_JAVA_HOME and JAVA_HOME don't work.
   # One of these MUST be set because maven requires JAVA_HOME to be set.
   else
      message "\n**************"
      message "-- Please download and install Java from"
      message "--   http://www.oracle.com/technetwork/java/javase/downloads/index.html"
      message "--   and make the java command available via one of the following methods"
      message "--   (this script detects java in this order):"
      message "-- 1. Set LOCAL_JAVA_HOME in this script."
      message "-- 2. Set JAVA_HOME environment variable (system or user level)."
      message "-- --"
      message "-- We must be able to set JAVA_HOME from one of these or maven will fail."
      message "**************\n"
      reportResults
      exit 3
   fi

}


# ===  FUNCTION  ===============================================================
#   DESCRIPTION:  Open log in editor.
#    PARAMETERS:  -
#       RETURNS:  -
# ==============================================================================
function reportResults() {
   case "${EDITOR}" in
      less )
         message "Output sent to $outputFile"
         less -I "${outputFile}" ;;
      vim )
         message "\nOutput sent to $outputFile"
         vim "${outputFile}" ;;
      * )
         # Some windows app.
         outputFileWin=`cygpath -w -a "${outputFile}"`
         message "\n"
         message "Output sent to ${outputFileWin}" off
         unix2dos "${outputFile}"
         "${EDITOR}" "${outputFileWin}" &;;
   esac

}

# ===  FUNCTION  ===============================================================
#   DESCRIPTION:  Output environment details to aid debugging.
#    PARAMETERS:  -
#       RETURNS:  -
# ==============================================================================
function showEnvironmentDetails() {
   message "\n-----------"
   message "Current Directory [`pwd`]"
   message "This script [`pwd -P`/$0]"
   message "M2_HOME [$M2_HOME]"
   message "JAVA_HOME [$JAVA_HOME]"
   message "EDITOR [$EDITOR]"
   message "MTEE [`type tee`]"
   message "TMPDIR [$TMPDIR]"
   message "PATH [$PATH]"
   message "-----------\n"
}






# ------------------------------------------------------------------------------
# -- Script Logic
# ------------------------------------------------------------------------------
# No args means just open default notes file.


# Create timestamp.
timestamp=$(date +"%Y%m%d_%H%M%S")
outputFile=$TMPDIR/maven_$timestamp.txt


checkDependencies

showEnvironmentDetails

message "Caw caw said the Raven!\n"


message "==========================================================================="
message "Command:"
message "mvn $@"
message "===========================================================================\n"

mvn "$@"  2>&1 | tee -a "${outputFile}"


reportResults

Wednesday, January 07, 2015

Do you use mvn on a DOS prompt?

Update Thursday 08 January 2015, 05:17:54 PM - Modified the way this script looks for maven and java such that it always uses local versions first.

Do you use mvn on a DOS prompt? Sick of having to scroll through a tiny DOS prompt looking for an error amongst thousands of lines of output? Sick of not being able to search through the log? Want to save logs for future reference?

Well, look no further! I have written raven.bat, a wrapper around mvn that does one thing: saves the output to a file (which still gets displayed on the console) and opens the file in your favourite text editor.

The script itself includes instructions for setting up dependencies (not much set up) and how to use the script.

You can also see this script in GitHub: https://github.com/robertmarkbram/MavenUtilities/blob/master/raven.bat which is where I shall keep it updated. Pastebin too.

@echo off
SETLOCAL

:: ------------------------------------------------------------------------------
:: -- What is Raven?
:: A DOS Batch wrapper around mvn to save output to file and open it in an editor
:: By Robert Mark Bram
:: https://github.com/robertmarkbram/MavenUtilities
:: http://robertmarkbramprogrammer.blogspot.com.au/2015/01/do-you-use-mvn-on-dos-prompt.html
:: ------------------------------------------------------------------------------
:: Set-up Dependencies first.
:: - This script
::    - Save this script as raven.bat in some folder you like to use for utilities etc.
::    - Add the path (folder/directory name) where this script lives to your PATH (*see note 1*).
::    - Otherwise you will have to use the absolute path to this script: e.g. "C:\myApps\Batch\raven" instead of just "raven".
:: - Maven
::    - Download it from: http://maven.apache.org/
::    - Set M2_HOME variable (*see note 1*) or LOCAL_M2_HOME in this script.
:: - Java
::    - Download and install it from: http://www.oracle.com/technetwork/java/javase/downloads/index.html
::    - Set JAVA_HOME variable (*see note 1*) or LOCAL_JAVA_HOME in this script.
:: - mtee.exe (to send output to file and console)
::    - Download mtee.exe from http://www.commandline.co.uk/mtee/
::    - Add the path (folder/directory name) where mtee.exe lives to your PATH (*see note 1*) or set MTEE variable in this script.
:: - Your favourite text editor.
::    - This script defaults to notepad. If you like it, do nothing.
::    - If you want to use something else, set EDITOR in this script
:: - Temp dir
::    - This script defaults to %TEMP%\maven.
::    - If you want to use something else, set TMPDIR in this script

:: How to use this script.
:: - Instead of using "mvn", just use "raven", e.g.
::       raven clean deploy
::       C:\myApps\Batch\raven clean deploy

:: Note 1 - changing PATH or other environment variables on Win7 and above: two options.
:: - OPTION 1: Start button > Search for "Environment Variables for your account" > modify PATH (or other variable) in top section, "user variables for USERNAME"
::    - No re-boot required, just restart the DOS prompt.
::    - PATH is set only for your user. Other logged users will not see it.
:: - OPTION 2: Start button > Search for "Edit the System Environment Variables" > Environment Variables > modify PATH (or other variable) in bottom section, "System Variables"
::    - Re-boot required.
::    - PATH is set for all logged in users.

:: History
:: Wednesday 07 January 2015, 07:07:33 PM
:: - Updated to check java, maven, mtee temp dir and editor variables.
:: Thursday 08 January 2015, 04:57:36 PM
:: - Modified the way this script looks for maven and java such that it always uses local versions first.


:: ############################################################################
:: Edit these variables - dependencies of this script.
:: ############################################################################
:: set EDITOR=C:\Program Files (x86)\IDM Computer Solutions\UltraEdit\Uedit32.exe
set EDITOR=notepad
set LOCAL_JAVA_HOME=C:\Program Files\Java\jdk1.7.0_67
set LOCAL_M2_HOME=C:\apps\apache-maven-3.2.3
set MTEE=C:\apps\mtee.exe
set TMPDIR=%TEMP%\maven
set MAVEN_OPTS=-Xms512m -Xmx1024m -XX:MaxPermSize=256m
:: ############################################################################
:: DO NOT EDIT BELOW HERE
:: ############################################################################

:: Create timestamp.
SET hh=%time:~0,2%
if "%time:~0,1%"==" " SET hh=0%hh:~1,1%
SET YYYYMMDD_HHMMSS=%date:~10,4%%date:~7,2%%date:~4,2%_%hh%%time:~3,2%%time:~6,2%
set LOG_FILE=%TMPDIR%\maven_%YYYYMMDD_HHMMSS%.txt

:: Check temp dir.
if not exist %TMPDIR% mkdir %TMPDIR%

:: Check mtee.exe
:: Check in path first.
set MTEE_FOUND=
for %%e in (%PATHEXT%) do (
  for %%X in (mtee%%e) do (
    if not defined MTEE_FOUND (
      set MTEE_FOUND=%%~$PATH:X
    )
  )
)
if "%MTEE_FOUND%" == "" (
   :: Not in path. Check script variable.
   if not exist %MTEE% (
      echo.
      echo **************
      echo Please download mtee.exe from http://www.commandline.co.uk/mtee/
      echo and update MTEE variable in this script.
      echo **************
      echo.
      goto :exit
   )
) else (
   set MTEE=%MTEE_FOUND%
)

:: Check editor.
if "%EDITOR%" == "notepad.exe" goto :editor_check_end
if "%EDITOR%" == "notepad" goto :editor_check_end
if not exist "%EDITOR%" (
   echo. 2<&1 | "%MTEE%" /+ %LOG_FILE%
   echo ************** 2<&1 | "%MTEE%" /+ %LOG_FILE%
   echo Please update EDITOR ["%EDITOR%"] 2<&1 | "%MTEE%" /+ %LOG_FILE%
   echo variable to point to an editor you wish to use. Using notepad. 2<&1 | "%MTEE%" /+ %LOG_FILE%
   echo ************** 2<&1 | "%MTEE%" /+ %LOG_FILE%
   set EDITOR=notepad
)
:editor_check_end

:: Check Maven.
:: First check if local version is defined and exists.
:: Allows user to specify a different maven than what exists in M2_HOME or PATH.
if exist "%LOCAL_M2_HOME%\bin\mvn.bat" (
   set M2_HOME=%LOCAL_M2_HOME%
   set "PATH=%M2_HOME%\bin;%PATH%"

) else if exist "%M2_HOME%\bin\mvn" (
   :: Next check if M2_HOME set. It is.
   set "PATH=%M2_HOME%\bin;%PATH%"

) else (
   :: LOCAL_M2_HOME and M2_HOME don't work.
   :: OK, no mvn then.
   echo. 2<&1 | "%MTEE%" /+ %LOG_FILE%
   echo ************** 2<&1 | "%MTEE%" /+ %LOG_FILE%
   echo -- Please download Maven from http://maven.apache.org/ and make 2<&1 | "%MTEE%" /+ %LOG_FILE%
   echo --   the mvn command available via one of the following methods 2<&1 | "%MTEE%" /+ %LOG_FILE%
   echo --   ^(this script detects maven in this order^): 2<&1 | "%MTEE%" /+ %LOG_FILE%
   echo -- 1. Set LOCAL_M2_HOME in this script. 2<&1 | "%MTEE%" /+ %LOG_FILE%
   echo -- 2. Set M2_HOME environment variable ^(system or user level^). 2<&1 | "%MTEE%" /+ %LOG_FILE%
   echo -- -- 2<&1 | "%MTEE%" /+ %LOG_FILE%
   echo -- We must be able to set M2_HOME from one of these. 2<&1 | "%MTEE%" /+ %LOG_FILE%
   echo ************** 2<&1 | "%MTEE%" /+ %LOG_FILE%
   echo. 2<&1 | "%MTEE%" /+ %LOG_FILE%
   goto :report_results
)

:: Check Java.
:: First check if local version is defined and exists.
:: Allows user to specify a different java than what exists in JAVA_HOME.
if exist "%LOCAL_JAVA_HOME%\bin\java.exe" (
   set JAVA_HOME=%LOCAL_JAVA_HOME%
   set "PATH=%JAVA_HOME%\bin;%PATH%"
) else if exist "%JAVA_HOME%\bin\java.exe" (
   :: Next check if JAVA_HOME set. It is.
   set "PATH=%JAVA_HOME%\bin;%PATH%"
) else (
   :: LOCAL_JAVA_HOME and JAVA_HOME don't work.
   :: One of these MUST be set because maven requires JAVA_HOME to be set.
   :: OK, no java then.
   echo. 2<&1 | "%MTEE%" /+ %LOG_FILE%
   echo ************** 2<&1 | "%MTEE%" /+ %LOG_FILE%
   echo -- Please download and install Java from 2<&1 | "%MTEE%" /+ %LOG_FILE%
   echo --   http://www.oracle.com/technetwork/java/javase/downloads/index.html 2<&1 | "%MTEE%" /+ %LOG_FILE%
   echo --   and make the java command available via one of the following methods 2<&1 | "%MTEE%" /+ %LOG_FILE%
   echo --   ^(this script detects java in this order^): 2<&1 | "%MTEE%" /+ %LOG_FILE%
   echo -- 1. Set LOCAL_JAVA_HOME in this script. 2<&1 | "%MTEE%" /+ %LOG_FILE%
   echo -- 2. Set JAVA_HOME environment variable ^(system or user level^). 2<&1 | "%MTEE%" /+ %LOG_FILE%
   echo -- -- 2<&1 | "%MTEE%" /+ %LOG_FILE%
   echo -- We must be able to set JAVA_HOME from one of these or maven will fail. 2<&1 | "%MTEE%" /+ %LOG_FILE%
   echo ************** 2<&1 | "%MTEE%" /+ %LOG_FILE%
   echo. 2<&1 | "%MTEE%" /+ %LOG_FILE%
   goto :report_results
)

echo. 2<&1 | "%MTEE%" /+ %LOG_FILE%
echo ----------- 2<&1 | "%MTEE%" /+ %LOG_FILE%
echo Current Directory [%cd%] 2<&1 | "%MTEE%" /+ %LOG_FILE%
echo This script [%0] 2<&1 | "%MTEE%" /+ %LOG_FILE%
echo M2_HOME [%M2_HOME%] 2<&1 | "%MTEE%" /+ %LOG_FILE%
echo JAVA_HOME [%JAVA_HOME%] 2<&1 | "%MTEE%" /+ %LOG_FILE%
echo EDITOR [%EDITOR%] 2<&1 | "%MTEE%" /+ %LOG_FILE%
echo MTEE [%MTEE%] 2<&1 | "%MTEE%" /+ %LOG_FILE%
echo TMPDIR [%TMPDIR%] 2<&1 | "%MTEE%" /+ %LOG_FILE%
echo PATH [%PATH%] 2<&1 | "%MTEE%" /+ %LOG_FILE%
echo ----------- 2<&1 | "%MTEE%" /+ %LOG_FILE%
echo. 2<&1 | "%MTEE%" /+ %LOG_FILE%
echo Caw caw said the Raven! 2<&1 | "%MTEE%" /+ %LOG_FILE%
echo. 2<&1 | "%MTEE%" /+ %LOG_FILE%


echo =========================================================================== 2<&1 | "%MTEE%" /+ %LOG_FILE%
echo Command: 2<&1 | "%MTEE%" /+ %LOG_FILE%
echo mvn %* 2<&1 | "%MTEE%" /+ %LOG_FILE%
echo =========================================================================== 2<&1 | "%MTEE%" /+ %LOG_FILE%
echo. 2<&1 | "%MTEE%" /+ %LOG_FILE%

mvn %* 2<&1 | "%MTEE%" /+ %LOG_FILE%

:report_results
echo. 2<&1 | "%MTEE%" /+ %LOG_FILE%
echo Output sent to %LOG_FILE% 2<&1 | "%MTEE%" /+ %LOG_FILE%
start "" "%EDITOR%" %LOG_FILE%

:exit
echo.
echo Done.

Monday, November 17, 2014

Autohotkey script to generate a BIRT report from Eclipse Report Design perspective

An Autohotkey script to generate a BIRT report from Eclipse when using the Report Design perspective (Run → View Report → In Web Viewer/As Doc/As PDF etc). I use this because, frustratingly, there are no key bindings to generate a report and no accelerator keys under the View Report menu in Eclipse since Luna Release (4.4.0).

; In Eclipse, run BIRT report
#IfWinActive, Report Design - .*rptdesign - Eclipse
   +F8::
      SendInput, {ALT}rr{down}{down}{down}{down}{down}{down}{down}{enter}
      Sleep, 800
      clickOkToGenerateBirtReport()
   Return
#IfWinActive

; Eclipse Only - but a torn out window panel of it.
#IfWinActive, ahk_class #32770
   ; View BIRT report
   +F8::
      SendInput, {F12}
      Sleep, 100
      SendInput, {ALT}rr{down}{down}{down}{down}{down}{down}{down}{enter}
      Sleep, 1000
      clickOkToGenerateBirtReport()
   Return
#IfWinActive

clickOkToGenerateBirtReport() {
   ; If we can see the Parameter Selection Page, click OK.
   ; Look for it ten times, sleeping in between.
   Loop, 100
   {
      if WinActive("PARAMETER SELECTION PAGE") {
         WinGet, IEControlList, ControlList, ahk_class SWT_Window0
         Loop, Parse,IEControlList, `n
         {
            if (A_LoopField = "Internet Explorer_Server1") {
               MouseClick, left,  630,  580
               return true
            }
         }
      }
      Sleep, 200
   }
   MsgBox Sorry, couldn't find the button to click on.
   return false
}

Note that there are two conditions under which the trigger will execute. The first one is when I am in the main window of Eclipse: (IfWinActive, Report Design - .*rptdesign - Eclipse). The second is for when I am using Eclipse spread across two monitors and have torn off panels to sit on the other monitor and have run the command: IfWinActive, ahk_class #32770.

Tuesday, October 21, 2014

JUnit Parameterized and named tests

A relatively simple, working and complete example of JUnit Parameterized named tests.

Advantages of Parameterized in JUnit testing:

  • Re-use the same test methods over and over, just changing the parameters.
  • The method annotated by @Parameters could return a collection of data from a spreadsheet, database or hard-coded variable.
  • New naming mechanism lets you add a sensible label to each test to make it easy to identify the one that failed.
import static org.junit.Assert.assertEquals;

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 final class MultiplicationParameterizedTest {

   private final int expectedResult;
   private final int firstNumber;
   private final int secondNumber;

   public MultiplicationParameterizedTest(final int theExpectedResult,
         final int theFirstNumber, final int theSecondNumber) {
      expectedResult = theExpectedResult;
      firstNumber = theFirstNumber;
      secondNumber = theSecondNumber;
   }

   @Parameters(name = "Multiplication test {index}: {0}={1}x{2}")
   public static Collection<Integer[]> numbersToBeMultiplied() {
      return Arrays.asList(new Integer[][] {
            { 10, 5, 2 },
            { 48, 6, 8 },
            { 1452, 33, 44 },
            { 1044, 87, 12 },
            { 135, 3, 45 },
            });
   }

   @Test
   public void sum() {
      final int actualResult = multiplyNumbers(firstNumber, secondNumber);
      assertEquals("Expected [" + firstNumber + " * " + secondNumber + " = "
            + expectedResult + "], not [" + actualResult + "]", expectedResult,
            actualResult);
   }

   public int multiplyNumbers(int a, int b) {
      int product = a * b;
      return product;
   }

}

Here is what the tests look like when they pass in Eclipse. It shows you useful the naming mechanism is.

Now I change the last test case just to show what a failure looks like.

@Parameters(name = "Multiplication test {index}: {0}={1}x{2}")
public static Collection numbersToBeMultiplied() {
   return Arrays.asList(new Integer[][] {
         { 10, 5, 2 },
         { 48, 6, 8 },
         { 1452, 33, 44 },
         { 1044, 87, 12 },
         { 13, 3, 45 },
         });
}

And in Eclipse, that failure looks like this:

You can still use setup and tear down methods:

@Before
public void setup() {
   System.out.println("Set up [" + firstNumber + " * " + secondNumber
         + " = " + expectedResult + "].");
}

@After
public void tearDown() {
   System.out.println("Tear down [" + firstNumber + " * " + secondNumber
         + " = " + expectedResult + "].");
}

Note that names feature was introduced in JUnit 4.11, which is the latest stable build at the time of writing (Tuesday 21 October 2014).

What about data from a spreadsheet with lots of columns?

I am thinking of using this for tests that will have a lot of data that I want to write in a spreadsheet of ten columns or more. One problem I have with the above example is that I have a constructor accepting one parameter for each column of data. I need to encapsulate that in a class. This is because I am thinking of using this to drive Selenium tests, where I need lots of data to enter into every field in the UI. The example below shows part of how I will handle this.

import static org.junit.Assert.assertEquals;

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 final class MultiplicationParameterizedTestWithObject {

   private final DataUnderTest data;

   public MultiplicationParameterizedTestWithObject(final DataUnderTest theData) {
      data = theData;
   }

   @Parameters(name = "Multiplication test {index}: {0}")
   public static Collection<DataUnderTest[]> numbersToBeMultiplied() {
      return Arrays.asList(new DataUnderTest[][] {
            { new DataUnderTest("10 = 5 * 2", 10, 5, 2) },
            { new DataUnderTest("48 = 6 * 8", 48, 6, 8) },
            { new DataUnderTest("1,452 = 33 * 44", 1452, 33, 44) },
            { new DataUnderTest("1,044 = 87 * 12", 1044, 87, 12) },
            { new DataUnderTest("135 = 3 * 45", 135, 3, 45) },
            });
   }

   @Test
   public void sum() {
      final int firstNumber = data.getFirstNumber();
      final int secondNumber = data.getSecondNumber();
      final int actualResult = multiplyNumbers(firstNumber, secondNumber);
      final int expectedResult = data.getExpectedResult();
      assertEquals("Expected [" + firstNumber + " * " + secondNumber + " = "
            + expectedResult + "], not [" + actualResult + "]", expectedResult,
            actualResult);
   }

   public int multiplyNumbers(int a, int b) {
      int product = a * b;
      return product;
   }

   private static class DataUnderTest {
      private final String label;
      private final int expectedResult;
      private final int firstNumber;
      private final int secondNumber;

      public DataUnderTest(final String theLabel, final int theExpectedResult,
            final int theFirstNumber, final int theSecondNumber) {
         label = theLabel;
         expectedResult = theExpectedResult;
         firstNumber = theFirstNumber;
         secondNumber = theSecondNumber;
      }

      @Override
      public String toString() {
         return label;
      }

      public String getLabel() {
         return label;
      }

      public int getExpectedResult() {
         return expectedResult;
      }

      public int getFirstNumber() {
         return firstNumber;
      }

      public int getSecondNumber() {
         return secondNumber;
      }

   }
}

What I will need to do differently from above in my real test cases:

  • DataUnderTest will be much bigger - big enough that it will need to be in a separate java file.
  • The method annotated with @Parameters will read data from an Excel spreadsheet, perhaps using Java Excel API or The Apache POI Project.

What I like about this approach:

  • JUnit Parameterized seems to scale nicely because the method annotated with @Parameters can get data from anywhere.
  • By using DataUnderTest.toString(), I can still make good use of the same naming mechanism: @Parameters(name = "Multiplication test {index}: {0}"). By using this name declaration, the Eclipse results will look exactly the same as the first example because the {0} will be replaced by DataUnderTest.toString(). In my actual test, I plan to use the first column of data as the label.

What I don't like about this approach:

  • The fact that the method annotated with @Parameters has to return an Iterable of arrays, for example Collection<DataUnderTest[]>. It forces me to bind each element of the array to a constructor parameter, which is at least well defined, but doesn't seem so flexible, e.g. I cannot create an array that includes String and int without making it an Object array, in which case I would lose type information and would be forced to cast or somehow convert the values back to the types I want.

Thursday, October 16, 2014

Script to pump output into a text file and open it in an editor

I use this little script a lot when I am running some command in bash (on Cygwin in Windows) and I want to capture the output into a text file for display into my favourite editor (like UltreEdit).

For example, the tree command (or a recursive ls) will have a lot of output if you run it on a directory with a few children and a few levels of nested folders. Because the output is usually too large to view nicely in a single screen of text on a console, I will want to view it in a text editor where I can manipulate it: search through it, cut/copy/paste contents and generally modify it.

Before I wrote the script this post is about, I would do this in the following way:

tree > /tmp/temp.txt; u /tmp/temp.txt

Easy enough, but tiresome to type over and over.

In the above snippet, the u command refers to one of my most used custom scripts to open a text file in my favourite editor (with a few bells and whistles). It could be replaced with the following in this case:

tree > /tmp/temp.txt; Uedit32.exe `cygpath -w -a "/tmp/temp.txt"` &

Or more simply, a *nix tool like vim or less (though less won't let you edit it).

tree > /tmp/temp.txt; vim /tmp/temp.txt
tree > /tmp/temp.txt; less /tmp/temp.txt

Now, I will do this:

tree | intoTempFile

The intoTempFile script will write the output into a timestamped temp file (C:\cygwin\tmp\temp_20141016_220342.txt) and open it in UltraEdit for me. Here is the script.

#!/bin/bash

# ------------------------------------------------------------------------------
# -- Into Temp File.
# ------------------------------------------------------------------------------
# Redirect piped input into a temp file and open it in UltraEdit.
#     Usage: someCommand | intoTempFile.sh [altFilename.txt] [-t]

# ------------------------------------------------------------------------------
# -- Variables for this script.
# ------------------------------------------------------------------------------
# Shortcut for the name of this file - for docs.
commandName=`echo $0 | sed 's|.*/||'`

# Output file.
tempFile=/tmp/temp_$(date +"%Y%m%d_%H%M%S").txt

# To tee or not to tee?
shouldUseTee=no

# ------------------------------------------------------------------------------
# -- Common functions for this script.
# ------------------------------------------------------------------------------

# ===  FUNCTION  ===============================================================
#   DESCRIPTION:  Usage message.
#    PARAMETERS:  -
#       RETURNS:  -
# ==============================================================================
function usage() {
   echo "Usage: someCommand | $commandName [altFilename.txt] [-t]"
   echo "-t means use tee. Without it, nothing is output to console."
}

# ===  FUNCTION  ===============================================================
#   DESCRIPTION:  Process all arguments to script. How to handle getopts args
#                 and operands at the same time:
#                    http://stackoverflow.com/a/21169366/257233
#    PARAMETERS:  -
#       RETURNS:  -
# ==============================================================================
function processArguments() {
   # Arg checks.. No more than two args
   if [ $# -gt 2 ] ; then
      echo "** Incorrect number of args specified **"
      usage
      exit 22
   fi

   # Loop to handle all parameters
   non_option_parameters=()
   while true; do
      # Process single letter args.
      while getopts "$OPTIONS" option; do
         if ! processSingleLetterArguments "$option"; then exit 9; fi
      done
      if ((OPTIND > $#)); then break; fi
      non_option_parameters+=(${!OPTIND})
      # Handle operand arg - file name
      if [ -z "$searchTerm" ] ; then
         tempFile="${!OPTIND}"
      fi
     ((OPTIND++))
   done

   # ---------------------------------------------------------------------------
   # Post argument processing - logic that must be applied once we know all args.
   # ---------------------------------------------------------------------------

}

# ===  FUNCTION  ===============================================================
#   DESCRIPTION:  Process single letter args via getopts
#    PARAMETERS:  -
#       RETURNS:  -
# ==============================================================================
OPTIONS=":t"
function processSingleLetterArguments() {
   case "$1" in
      t ) shouldUseTee=yes;;
      \?)   usage "*** Invalid option to $commandName: -$OPTARG ***"; exit 10;;
      :)    usage "*** $commandName - option -$OPTARG requires an argument. ***"; exit 11;;
   esac
}

# ===  FUNCTION  ===============================================================
#   DESCRIPTION:  Output a single line
#                 Will query tee variable.
#    PARAMETERS:  1 - line to output
#       RETURNS:  -
# ==============================================================================
function outputLine() {
   if [ "$shouldUseTee" == "yes" ]  ; then
      echo "$1" 2>&1 | tee -a "${tempFile}"
   else
      echo "$1" >> "${tempFile}"
   fi
}


# ------------------------------------------------------------------------------
# -- Script logic.
# ------------------------------------------------------------------------------

# Process command line arguments.
processArguments "$@"

# Preserve indenting of source.
IFS='
'

touch  "${tempFile}"

outputLine "Starting output."
outputLine "----"
outputLine " "

while read -r x ; do
   outputLine "$x"
done

u.sh $tempFile

By default this will not show the output to the console. I can change that with the -t option as below - which will use tee to duplicate the output to console as well as a file. I don't usually use this though because it makes the script a lot slower.

tree | intoTempFile -t
Also, if I want to control the path to the text file being used, I can specify it like so.
tree | intoTempFile  /tmp/treeList.txt

Saturday, October 04, 2014

Add Eclipse Project to Local and Remote Git Repository

This is a brief tutorial on using Eclipse to check in projects to local and remote Git repositories.

  1. Install and configure EGit within Eclipse, if not already done.
  2. Create a project in Eclipse.
  3. Add project to local git repository.
  4. Create a project in GitHub (with the same name as our local project).
  5. Check in our project to the remote git repository.
  6. The two phases of commitment.
  7. Helpful Resources.

Prerequisites are as below.

  • You have an account already on GitHub. Free accounts can be used to create only public repositories i.e. everybody will be able to see your code. So if you need a private Git repository on GitHub, you have to pay.

To top of post.

Install and Configure EGit

Here you will install the EGit plugin within Eclipse. This gives Eclipse the ability to interact with Git repositories. If you have Eclipse Kepler (4.3), Eclipse Luna (4.4) or later, then this easily installed through the Eclipse Marketplace.

  1. Open Eclipse, go HelpEclipse Marketplace.
  2. In the search field, type EGit and press ENTER.
  3. The first result will be EGit - Git Team Provider 3.5.0 (latest version as of this writing).
  4. Click the Install button.
  5. Select Eclipse Git Team provider component.
  6. Select Next, accept license agreements, let it install, let Eclipse restart.

Before you can use EGit, you need to configure it.

Set up your identity. In Eclipse, go WindowPreferencesTeamGitConfiguration. Enter your name and email - Git will use this to identify who is making commits.

Set up location for local repositories. In Eclipse, go WindowPreferencesTeamGit and set a location for Default Repository Folder. I have used D:\Documents\Work\Git.

It is a good idea to create your Git repositories in a different directory than your Eclipse Workspace. The Git repository folders will hold lots of files related to Git setup, not just your project files. If you create your Git repository within your Eclipse Workspace, some operations in Eclipse would suffer a performance hit because they will have to scan all of these other files, not just project files.

You will need to create this directory first and it should be empty.

To top of post.

Create a project

In Eclipse, select FileNewProject → select whatever project type you want (I am using a Java Project). Give it a name and press Finish. The contents of the project aren't important - it is just a few bare files to show the next part of the process - adding it to a local repository and then a remote one.

Note that your project has been created in your Eclipse workspace. In my case, this was D:\Documents\Work\JavaTests\TestProject.

To top of post.

Add project to local git repository

In Package Explorer view, right click on the project you just created and select TeamShare Project. In the Share Project dialog, select Git and then click Next.

The next screen is Configure Git Repository.

I am not selecting Use or create repository in parent folder of project for reasons stated above - the repository folder will contain a lot of Git-specific files apart from my project files and I do not want Eclipse having to scan these folders.

The general idea is that you have one project per repository, so we make one repo for this project. Click Create.

Give the new repository the same name as your project and click Finish.

I am now back on the Configure Git Repository dialog.

I can see the following:

  • Repository: D:\Documents\Work\Git\TestProject\.git - all the files related to Git go here.
  • Working Directory: D:\Documents\Work\Git\TestProject - the parent folder for the Git directory and the (new, target) project directory.
  • Current Location: D:\Documents\Work\JavaTests\TestProject - where the project directory current is, but it will be moved to Target.
  • Target Location: D:\Documents\Work\Git\TestProject\TestProject - new location for the project files.

Note that the Working directory field (read only) gives the repository location (D:\Documents\Work\Git in my case). Also note that the Current Location and Target Location are different. Current Location shows where my files are right now - in my Eclipse workspace. Target Location shows where my files will be moved to - a folder underneath the Git repository Working directory.

This means that when you press Finish, Eclipse will move your files out of the Eclipse workspace and into the Git repository folder. Don't be surprised - this is normal. It takes some getting used to if you haven't used Git before. In the Git repository, you will also notice a new folder - .git, which contains all the files Git uses to manage the repository.

Click Finish.

Now take some time to notice what has changed. The icons in Package Explorer are different.

Note the duplication in names - I have a Git repository called TestProject containing a project called TestProject. The icons reflect Git state. See EGit/User Guide/State which has this image showing at a glance the different states.

Also note that your files have indeed been moved out of your Eclipse workspace and into your Git repository.

Open up the Git Staging view: WindowShow ViewOther → select GitGit Staging.

This view makes it very easy to commit changes.

Click and drag the files down to the Staged Changes box. Type a commit message in the Commit Message text area.

Click Commit.

You can also do this with the commit action. Control+shift+3 is the default keyboard shortcut, or right click on the project and select Teamcommit. However, I find the Git Staging view much easier to use.

To top of post.

Create a project in GitHub (with the same name as our local project)

Go to GitHub and log in.

On the actions menu, select New repository.

Give Repository name the same value as your Eclipse project's name, and whatever Description is appropriate. Note that my repository is Public, so that anyone will be able to see what I check into it. Click Create Repository.

This screen gives us very important information that we will use in the next step - the HTTP URL for interacting with this repository from EGit in Eclipse: https://github.com/robertmarkbram/TestProject.git.

To top of post.

Check in our project to the remote git repository

Back in Eclipse, right click on your project in Package Explorer and select TeamRemotePush...

Most importantly, in the URI field, enter the URL we got from GitHub earlier. The Host and Repository path fields will auto-fill correctly. In the Authentication section, enter your GitHub username and password. I let Eclipse store these details by selecting Store in Secure Store. If this is the first time I have done it, Eclipse will ask me to set up a Master Password.

Click Next and you will see the Push to: ... dialog.

Click Add All Branches Spec. Under Source ref select master [branch]. Under Destination ref, select refs/heads/master.

Click Next and see the Push Confirmation dialog. Click Finish.

The project files will then be pushed to the GitHub remote repository. When it is complete, you will see a results dialog.

If you refresh your GitHub repository, you should now see the files committed remotely.

And underneath the main project folder are my files.

To top of post.

The two phases of commitment

We know have a system of two phase commits, so to speak. Work locally and commit changes to the local repository so you have a history and backups as needed.

When you commit, you have a choice of using Commit or Commit and Push. Just choosing Commit will commit your changes to the local repository. Selecting Commit and Push will commit them to your local repository and then push them to the remote repository - e.g. GitHub. The first time you use Commit and Push, it will ask you to set up the remote repository.

Press next and you will see the Push Branch master dialog wherein you tell Git what to do when pulling changes from the upstream repository i.e. bring changes from GitHub back into your local.

Hit Next and the rest of this is like any other commit, but you will have sent your changes to two repositories - local and GitHub.

To top of post.

Helpful Resources

To top of post.

Tuesday, September 23, 2014

The installer is unable to instantiate the file KEY_XE.reg

I got this error when installing Oracle Database Express Edition 11g 32 bit on my Windows 7 64-bit machine.

---------------------------
Error
---------------------------
The installer is unable to instantiate the file C:\Users\<your user name>\AppData\Local\Temp\{60712028-B7B0-4EC3-9C28-663111EC954A}\KEY_XE.reg.  The file does not appear to exist.
---------------------------
OK
---------------------------

I was using Oracle Database Express Edition 11g Release 2 for Windows x32 because at the time there was no 64 bit version for Windows 7.

The answer I found was in a response to an Oracle forum post: XE11: KEY_XE.reg cannot be loaded on WIN7 prof 64b and I have integrated that process into the instructions below.

  1. Unzip the downloaded installer OracleXE112_Win32.zip somewhere.
    unzip /C/Users/<your user name>/Downloads/OracleXE112_Win32.zip -d /C/Users/<your user name>/Temp/OracleXe
    chmod -R 777 /C/Users/<your user name>/Temp/OracleXe
    
  2. Run the exe: C:\Users\<your user name>\Temp\OracleXe\DISK1\setup.exe.
  3. Click NEXT > click I accept.. > STOP!
  4. If you continued, you would see the error this post is about. Did you ignore these instructions and continue anyway? That's OK.
    1. Accept the error (click OK).
    2. Let the install finish.
    3. Un-install Oracle Express.
    4. Delete C:\oraclexe
    5. Start again.
  5. You should now be on the screen: Choose Destination location. DO NOT PRESS NEXT YET.
    1. Open Windows Explorer to C:\Users\<your user name>\AppData\Local\Temp and look for a folder that the install just created. It will have a name like this: {60712028-B7B0-4EC3-9C28-663111EC954A} and will be the same as what was reported in the error dialog.
    2. Inside that folder, find the file OracleMTSRecoveryService.reg and make a copy of it. Rename the copy to KEY_XE.reg.
    3. Now go back to the installer and press NEXT.
  6. Specify Database Passowords: someSecurePassword.
  7. Verify that the Current Installation settings are OK:
       Destination Folder: C:\oraclexe\
       Oracle Home: C:\oraclexe\app\oracle\product\11.2.0\server\
       Oracle Base:C:\oraclexe\
       Port for 'Oracle Database Listener': 1521
       Port for 'Oracle Services for Microsoft Transaction Server': 2030
       Port for 'Oracle HTTP Listener': 8080
       
  8. Install finished.

This worked for me on my previous install on a Windows 7 box.

This week I had to set myself up on a new Windows 7 64-bit install. I went through the steps I outlined above and the install seemed to work but I encountered another problem: Connected to an idle instance; trouble creating DB. The fix for this issue was to un-install the 32-bit Oracle Express and install Oracle Database Express Edition 11g Release 2 for Windows x64 - the 64-bit installer. I did not get the same error (The installer is unable to instantiate the file) when installing the 64-bit version.

Plugin execution not covered by lifecycle configuration

Eclipse is showing this error

Plugin execution not covered by lifecycle configuration: org.apache.maven.plugins:maven-jar-plugin:2.4:jar (execution: make-jar, phase: compile)

It shows the error against the <execution> line in one of our POMs, an extract of which is below.

<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <build>
  ...
    <plugins>
    ...
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-jar-plugin</artifactId>
        <version>2.4</version>
        <executions>
          <!-- Create shared ViewController JAR file which is used by MYPROJECT-API-V1 -->
          <execution>
            <id>make-jar</id>
            <phase>compile</phase>
            <goals>
              <goal>jar</goal>
            </goals>
          </execution>
    ...

I only notice this error in Ecliipse Luna - it was not showing in Eclipse Kepler. Further, I can still build OK from the command line and even Eclipse still builds the project.

The fix was surprisingly easy. I select the quick fix: Discover new m2e connectors. Nothing came up, but when I clicked on FINISH, I found that Eclipse was indeed updating the m2e Connectors. I let it happen, re-started Eclipse, and the error disappeared.