JUnit + System.exit(-1): Looking for alternatives

K

Koos Pol

Hi,
 
Background: I have an application which basically is (although very
complicated) a batch script. It will run unattend from cron. The nature of
intermittant problems is unimportant. The batch script fails or it leads to
a successful result. So it either works, or it doesn't. Due to the many
possible failure points I created a general ErrorHandler class which dumps
to log4j and then quits via System.exit(-1).
 
Now I find that my JUnit tests (3.8.2) no longer work. As already read on
many forums, JUnit doesn't take care much for System.exit(). That's a real
bummer because now I have to implement a mucho cluttering and complicated
try/catch mechanism throughout the whole application which I don't need nor
care for.
 
1. Does anyone know of a way to make JUnit not barf on the System.exit(-1)?
2. Is there a way to gracefully abort besides System.exit(), (which is far
from gracefully)?
 
Thanks for any help.

Koos
 
K

Koos Pol

1. Does anyone know of a way to make JUnit not barf on the
System.exit(-1)? 2. Is there a way to gracefully abort besides
System.exit(), (which is far from gracefully)?
[snip]

Instead of terminating with System.exit(whateverValue), why
not throw an unchecked exception? In normal use it will drift
all the way out to the JVM's last-ditch catcher and shut your
script down

Whoa, why didn't I think of that. Thanks a lot Eric. Clecver thinking!

Koos
 
K

Koos Pol

(Aside: Does anyone know of a system where -1 makes sense
as a failure code? "Makes sense" not as in "It works," but as
in "Propagates unchanged to the environment and has a well-
understood meaning when it gets there.")

Yes. In my scenario the failure code does exactly what I want it to do. It
aborts and looks for the emergency exit. The process has failed and there
is no need whatsoever to keep it well contained in some artificial
try/catch stack. During a failure a secondary system is triggered from
anyone of the many possible failure points. So the emergency exit is just
fine. And makes sense.

Koos
 
K

Koos Pol

I think you misunderstood his point, which was specific to the value -1. [snip]
So yes, we're sure -1 works for you. Not so sure it's the best choice of
value.

Ah. That makes sense. I'll consider changing that. Thanks for clarifying.

Koos
 
A

Andreas Leitgeb

Wayne said:
In addition, some systems reserve negative values to indicate
termination via a signal.

I haven't yet seen *such* a system, but I've seen a few, where
termination through a signal was indicated (through the default-
signal-handler (from libc?)) as an exit code of (signal number + 128)
e.g. +139 for SIGSEGV (11). Even if this gets interpreted as a
signed byte, it's definitely not the same as -SigNr.

If you really meant "minus the signal number", then please tell
me/us on what system that was.
 
L

Lasse Reichstein Nielsen

....
Due to the many
possible failure points I created a general ErrorHandler class which dumps
to log4j and then quits via System.exit(-1).

1. Does anyone know of a way to make JUnit not barf on the System.exit(-1)?

Try modifying the TestCase to run with a security manager that
prevents calling System.exit, then catch the SecurityException.

Something like (untested!):
----
public class SecurityTestCase extends TestCase {
private class ExitException extends SecurityException {
public final int exitCode;
public ExitException(int code) {
super("There is no escape!");
this.exitCode = code;
}
}
private class NoExitSecurityManager extends SecurityManager {
public checkExit(int status) {
super.checkExit(); // first call on preventing.
throw new ExitException(status);
}
};

public void setUp() throws Exception {
System.setSecurityManager(new NoExitSecurityManager());
}

public void tearDown() throws Exception {
System.setSecurityManager(null);
}

}
---
and then make a test like:

public void testExit() throws Exception {
try {
somethingThatExits();
fail("System.exit() expected");
} catch (ExitException e) {
assertEquals("Exit code", 42, e.exitCode);
}
}


Or perhaps you can create a policy file that revokes the permission
to exit from your own packages.
See http://java.sun.com/j2se/1.5.0/docs/guide/security/PolicyFiles.html

2. Is there a way to gracefully abort besides System.exit(), (which is far
from gracefully)?

As already said, a custom unchecked exception causes no syntactic
overhead and won't be caught (unless you catch RuntimeException or
Exception directly somewhere else, which you really, really shouldn't,
for pretty much that reason), and won't be lost unless you have
a finally-block that ends abruptly (which you shouldn't really do either).

Dying gracefully will also allow finally blocks to release acquired
resources where necessary.

/L
 
L

Lasse Reichstein Nielsen

Lasse Reichstein Nielsen said:
Try modifying the TestCase to run with a security manager that
prevents calling System.exit, then catch the SecurityException.

Something like (untested!):

And now that it has been tested, it turns out to fail in several ways.

This one seems to work, though:

---
public class NoExitTestCase extends TestCase {

protected static class ExitException extends SecurityException {
public final int status;
public ExitException(int status) {
super("There is no escape!");
this.status = status;
}
}

private static class NoExitSecurityManager extends SecurityManager {
@Override
public void checkPermission(Permission perm) {
// allow anything.
}
@Override
public void checkPermission(Permission perm, Object context) {
// allow anything.
}
@Override
public void checkExit(int status) {
super.checkExit(status);
throw new ExitException(status);
}
}

@Override
protected void setUp() throws Exception {
super.setUp();
System.setSecurityManager(new NoExitSecurityManager());
}

@Override
protected void tearDown() throws Exception {
System.setSecurityManager(null); // or save and restore original
super.tearDown();
}

public void testNoExit() throws Exception {
System.out.println("Printing works");
}

public void testExit() throws Exception {
try {
System.exit(42);
} catch (ExitException e) {
assertEquals("Exit status", 42, e.status);
}
}
}
 
K

Koos Pol

And now that it has been tested, it turns out to fail in several ways.
This one seems to work, though:

This whole SecurityManager thing is new to me. So I have to dig into that
before I get it. So, "sort of" thanks :)

Koos
 
A

Arne Vajhøj

Wayne said:
Koos said:
I think you misunderstood his point, which was specific to the value -1. [snip]
So yes, we're sure -1 works for you. Not so sure it's the best choice of
value.

Ah. That makes sense. I'll consider changing that. Thanks for clarifying.

I recently looked into this on Unix (SUS/POSIX) and Linux. Turns out
the exit status is an int (signed two's complement 4 byte) however only
the least significant byte is ever made available to the parent process
(the process that "wait"s for the exit status). In addition, some systems
reserve negative values to indicate termination via a signal.

Of course many systems and applications pay no attention to these
standards and conventions. But to be truly portable on all
systems you should only use exit status values in the range
0..127 inclusive. If you implement a signal handler (not in
Java though, AFAIK) you would return -1 times the signal number.
Also, '-1' a.k.a '255' is reserved in POSIX (it has a special
meaning with 'xargs').

Also the BSD standard values are in /usr/include sysexits.h:
#define EX_OK 0 /* successful termination */
#define EX_USAGE 64 /* command line usage error */
#define EX_DATAERR 65 /* data format error */
#define EX_NOINPUT 66 /* cannot open input */
#define EX_NOUSER 67 /* addressee unknown */
#define EX_NOHOST 68 /* host name unknown */
#define EX_UNAVAILABLE 69 /* service unavailable */
#define EX_SOFTWARE 70 /* internal software error */
#define EX_OSERR 71 /* system error (e.g., can't fork) */
#define EX_OSFILE 72 /* critical OS file missing */
#define EX_CANTCREAT 73 /* can't create (user) output file */
#define EX_IOERR 74 /* input/output error */
#define EX_TEMPFAIL 75 /* temp failure; user is invited to
retry */
#define EX_PROTOCOL 76 /* remote error in protocol */
#define EX_NOPERM 77 /* permission denied */
#define EX_CONFIG 78 /* configuration error */

I think you have a narrow understanding of truly portable.

Portable to *nix style (POSIX) OS's maybe.

On OpenVMS some values are:

#define SS$_ACCVIO 12
#define SS$_BADPARAM 20
#define SS$_EXQUOTA 28
#define SS$_NOPRIV 36
#define SS$_ABORT 44
#define SS$_BADATTRIB 52
#define SS$_BADESCAPE 60
#define SS$_BADIMGHDR 68
#define SS$_CHANINTLK 76
#define SS$_CTRLERR 84
#define SS$_DATACHECK 92
#define SS$_DEVFOREIGN 100
#define SS$_DEVMOUNT 108
#define SS$_DEVNOTMBX 116
#define SS$_DEVNOTMOUNT 124
#define SS$_DEVOFFLINE 132
....
#define RMS$_NORMAL 65537
....
#define RMS$_ACT 98906
#define RMS$_DEL 98914
#define RMS$_INCOMPSHR 98922
#define RMS$_DNR 98930
#define RMS$_EOF 98938
#define RMS$_FEX 98946
#define RMS$_FLK 98954
#define RMS$_FNF 98962
#define RMS$_PRV 98970
#define RMS$_REX 98978
#define RMS$_RLK 98986
#define RMS$_RNF 98994
#define RMS$_WLK 99002
#define RMS$_EXP 99010
#define RMS$_NMF 99018
#define RMS$_SUP 99026

In Windows the exist code only has a range 0-255. The values
are strictly application specific and typical just sequentially.

Arne
 
A

Arne Vajhøj

Wayne said:
I guess for true portability, for POSIX compliant systems anyway,
we should stick to EXIT_SUCCESS and EXIT_FAILURE and not
use any other values. Too bad, having some defined failure
modes seemed like a good idea to me.

Maybe I should go back to my old C habit of defining exit statuses
(stati?) as an enum. Then those can be mapped in an OS specific way.

That is the route a lot of software goes. Just good or bad. And
get the cause communicated some other way.

Arne
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,999
Messages
2,570,243
Members
46,838
Latest member
KandiceChi

Latest Threads

Top