Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to set the exit status in a Groovy Script

We have a Groovy Script that exits with a status of 0 when everything worked and a non-0 status for different types of failure conditions. For example if the script took a user and an email address as arguments it would exit with a status of 1 for an invalid user, and a status of 2 for an invalid email address format. We use System.exit(statusCode) for this. This works fine, but makes the the script difficult to write test cases for.

In a test we create our GroovyShell, create our Binding and call shell.run(script,args). For tests that assert failure conditions, the System.exit() causes the JVM (and the test) to exit.

Are there alternatives to using System.exit() to exit a Groovy Script? I experimented with throwing uncaught exceptions, but that clutters the output and always makes the status code 1.

In our test cases I have also experimented with using System.metaClass.static.invokeMethod to change the behavior of System.exit() to not exit the JVM, but that seems like an ugly hack.

like image 655
Patrick Avatar asked Feb 16 '12 17:02

Patrick


People also ask

What does System exit 0); do?

Exiting with a code of zero means a normal exit: System. exit(0); We can pass any integer as an argument to the method.


2 Answers

imho System.metaClass.static.invokeMethod looks fine. It's test, and hacking is fine here.

Also you can create your own wrapper around it, like:

class ExitUtils {

    static boolean enabled = true

    static exit(int code) {
        if (!ExitUtils.enabled) {
            return  //TODO set some flag?
        }
        System.exit(code)
    }

}

and disable it for tests.

like image 183
Igor Artamonov Avatar answered Oct 23 '22 00:10

Igor Artamonov


Here is the technique we eventually used.

We can't just ignore a call to System.exit() since the script would continue to run. Instead we want to throw an exception with the desired status code. We throw a (custom) ProgramExitException when System.exit() is called in our tests

class ProgramExitException extends RuntimeException {

    int statusCode

    public ProgramExitException(int statusCode) {
        super("Exited with " + statusCode)
        this.statusCode = statusCode
    }
}

then we intercept System.exit() to throw this exception

/**
 * Make System.exit throw ProgramExitException to fake exiting the VM
 */
System.metaClass.static.invokeMethod = { String name, args ->
    if (name == 'exit')
        throw new ProgramExitException(args[0])
    def validMethod =  System.metaClass.getStaticMetaMethod(name, args)
    if (validMethod != null) {
        validMethod.invoke(delegate, args)
    }
    else {
        return  System.metaClass.invokeMissingMethod(delegate, name, args)
    }
}

and lastly we have GroovyShell catch any ProgramExitException and return the status code from the run method.

/**
 * Catch ProgramExitException exceptions to mimic exit status codes
 * without exiting the VM
 */
GroovyShell.metaClass.invokeMethod = { String name, args ->
    def validMethod = GroovyShell.metaClass.getMetaMethod(name, args)
    if (validMethod != null) {
        try {
            validMethod.invoke(delegate, args)
        } catch (ProgramExitException e) {
            return e.statusCode
        }
    }
    else {
        return GroovyShell.metaClass.invokeMissingMethod(delegate, name, args)
    }
 }

Our tests can stay looking simple, we don't need to change anything in the scripts and we get the behavior we expect from running on the command line.

assertEquals 'Unexpected status code', 0, shell.run(script,[arg1, arg2])
assertEquals 'Unexpected status code', 10, shell.run(script,[badarg1, badarg2])
like image 42
Patrick Avatar answered Oct 23 '22 01:10

Patrick