2

Since Java's assert keyword is fundamentally broken on Android, I am about to implement an assertion class that can be configured to check assertions in release builds as well.

Now I can do something like:

MyAssertion.assert(a != 2)

which throws an AssertionException when the expression is false. But how can I get a String representation of the expression to pass to the error message?

Community
  • 1
  • 1
Simon Warta
  • 10,850
  • 5
  • 40
  • 78
  • 1
    `assertEquals` and `assertNotEquals` could be a solution for basic expressions – jhamon Sep 24 '15 at 08:26
  • Could you explain what is fundamentally broken or at least provide a link? – Axel Sep 24 '15 at 08:31
  • @Axel, sure. Added a source. – Simon Warta Sep 24 '15 at 08:34
  • Just an advice since you're starting something new - take a look at Guava's Preconditions. – Dmitry Zaytsev Sep 24 '15 at 08:39
  • Is there a reason you arent using jUnit to unit test your releases? Otherwise, asserts are generally just bad practice in production code. – Stephan Oct 10 '15 at 05:12
  • @Stephan I do some unittesting, especially low level stuff. This is not really practical for higher level or UI parts. *generally just bad practice in production code* I don't think that is true. Starting with Java having no unsigned integer types, there is massive use for runtime assertions. It is always better to catch a programming error sooner than later, i.e. before some null pointer throws or data is corrupted. Prominent software houses sell runtime assertions as a feature to their customers, see https://www.think-cell.com/en/career/jobs/development.shtml – Simon Warta Oct 10 '15 at 05:25

4 Answers4

5

The only way is to add a String parameter to your assert method:

MyAssertion.assert(a != 2, "a must not be equal to 2");

What you get as input for assert is either true or false so you can't build a representative String from that.

Otherwise, you could implement assert like this:

MyAssertion.assertNotEquals(a, 2);

When this fails, you know that it is because what you tested was equal to 2 and you can build an informative message (though you won't know what specifically was equal to 2).


If you want to somehow be able to construct a meaningful message from an assertion, the only way I see it possible is to construct an String expression, ask the JavaScript engine to evaluate it and build a message if the expression evaluates to false. Note that will degrade a lot performance as launching the JavaScript engine takes a lot of time. This could be solved with a mechanism of disabling assertions in production.

The following is an example of that. Note that I'm using the new Java 8 Nashorn JavaScript engine but this should work with the older Rhino.

Usage example:

int value = 3;
String str = "test";
Assertions.assertTrue("$1 == 3", value);
Assertions.assertTrue("$1 == 3 && $2 == 'test'", value, str);
Assertions.assertTrue("$1 == 4 && $2 == 'test'", value, str);

This will throw for the 3rd assertion:

An assertion has failed: 3 == 4 && 'test' == 'test'

The idea is that you can write any JavaScript-friendly expression that can be evaluated to a boolean. The placeholders $i will be replaced by what's given as a parameter to the method ($1 will be replaced by the first parameter, etc.).

This is the class. It could be improved (handle error conditions like not enough parameters, etc.) but this should be enough to get you started.

public final class Assertions {

    private static final ScriptEngine ENGINE = new ScriptEngineManager().getEngineByName("nashorn");

    private Assertions() { }

    public static void assertTrue(String expression, Object... values) {
        for (int i = 0; i < values.length; i++) {
            ENGINE.put("$" + (i+1), values[i]);
        }
        try {
            boolean pass = (Boolean) ENGINE.eval(expression);
            if (!pass) {
                for (int i = 0; i < values.length; i++) {
                    expression = expression.replace("$" + (i+1), stringRepresentation(values[i]));
                }
                throw new AssertionError("An assertion has failed: " + expression);
            }
        } catch (ScriptException e) {
            throw new InternalError(e);
        } finally {
            for (int i = 0; i < values.length; i++) {
                ENGINE.getBindings(ScriptContext.ENGINE_SCOPE).remove("$" + (i+1));
            }
        }
    }

    private static String stringRepresentation(Object o) {
        if (o instanceof String) {
            return "'" + o + "'";
        }
        return o.toString();
    }

}
Tunaki
  • 132,869
  • 46
  • 340
  • 423
  • Thanks for your answer. Both ways are compromises which I don't want to make: The first one requires redundant information in the source code. Whenever I change the equation I need to take care to adapt the text with it. Composing trivial text is also useless burning of developer time. The second one requires a different method for all kinds of equations which is not cool but acceptable. The bigger problem is readability: `a == 2` vs. `a != 2` or `a != null` is much easier to read than different assertion function names. Still, thank you. It might be the best I can archive in Java. – Simon Warta Oct 04 '15 at 19:23
  • @SimonWarta Every assertion utility (take Spring [Assert](http://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/util/Assert.html) class, Guava [Preconditions](https://github.com/google/guava/wiki/PreconditionsExplained), JUnit [Assert](http://junit.sourceforge.net/javadoc/org/junit/Assert.html)) are built this way because you can't make a meaningful String by giving `a == 2` as parameter of a function. What you get is `true` or `false` but we could be talking about everything and anything. – Tunaki Oct 04 '15 at 19:33
  • Isn't there any kind of expression object in java that has both an `.evaluate()` and a `.toString()` function? – Simon Warta Oct 04 '15 at 19:37
  • @SimonWarta I'm thinking this can be possible by using the JavaScript engine but you will performance will degrade considerably. I'm trying to put up an example. – Tunaki Oct 04 '15 at 19:46
  • I don't think that makes sense given performance concerns and different available data types. – Simon Warta Oct 04 '15 at 19:54
4

Annotation processing can do this. You'd create an annotation e.g. @InlineAssertExpressions. Then write a processor that parses your source file and creates a string representing the expression and adds it to the call to your assert method, which you could overload to take an optional String argument. This way is quite optimal performance-wise, since the inlining happens compile-time. Annotation processing is a bit overlooked, but I think this is a great use for it.

Kafkaesque
  • 1,233
  • 9
  • 13
  • This is the most promising approach yet. I'll try it out. I did not create my own annotations yet, but let's see. – Simon Warta Oct 10 '15 at 04:43
  • I have a working Annotation Processor now that is able to write new classes. But how to I modify the caller, such that I can use my custom code? – Simon Warta Oct 11 '15 at 10:18
  • You can user the Sun tools, the Eclipse JDT library, or your own simple (but probably error-prone) search-replace, to manipulate the actual java source file. The annotation processing in this case should be idempotent since you're editing source files. – Kafkaesque Oct 12 '15 at 07:54
  • My previous comment is incorrect, in that you (as I understand it) have to create new source files instead of manipulating existing ones. The Filer will not let your overwrite existing files. See http://docs.oracle.com/javase/7/docs/api/javax/annotation/processing/Filer.html for ways around this limitation. – Kafkaesque Oct 12 '15 at 08:03
  • Jap, subclassing does not really help I think because I need to change the caller (from `assert(x)` to `assert(x, "x")`) at any place where it is used. There is another approach for the issue called bytecode manipulation, which is really overkill but somehow necessary: http://brianattwell.com/using-bytecode-manipulation-to-further-kill-android-boilerplate/ – Simon Warta Oct 12 '15 at 08:17
  • You could still do it. You could subclass your original classes (make the original ones abstract so you can't use them by accident) and use the generated ones from the rest of your code. Using factories or DI like suggested on that javadoc page I linked to. There is also [this option](http://notatube.blogspot.nl/2010/11/project-lombok-trick-explained.html) – Kafkaesque Oct 12 '15 at 09:41
  • But how does subclassing my assertion class help? I cannot subclass all the callers in my entire project. – Simon Warta Oct 12 '15 at 09:45
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/92024/discussion-between-kafkaesque-and-simon-warta). – Kafkaesque Oct 12 '15 at 09:50
2

(1) Simple to use, but hard to implement. Java 8 required. You can use lambda expressions:

Assert.isTrue(() => a != 2)

On evaluation failure your implementation of Assert.isTrue method should repeat all steps as IDEs do - (a) discover bytecode of lambda class, (b) decompile e.g. with JAD, (c) discover sources if available

(2) Simple to use, simple to implement, but does not fully cover your requirements. You can use CodeStyle rules to check & force correct assertions usage. One regexp will check there is no single-argument assertions, the second (using regexp back refs) will check code and text description are similar.

(3) Simple to use, simple to implement, but relies on your build system. You can automatically check and fix source code during project build.

E.g. for Maven build system you can create your own plugin to check and fix assertions usage in sources on process-sources stage.

ursa
  • 4,404
  • 1
  • 24
  • 38
  • Interesting. But I do not get what is going on here. Where do I get that `Assert` class from? How do I get the implementation string of the lambda expression? Is this Java 8 only? – Simon Warta Oct 04 '15 at 21:03
  • I don't see how this answers the question as this does not allow the creation of a meaningful String representation of the assertion. @SimonWarta This is for Java 8, the lambda expression creates a [`Predicate`](https://docs.oracle.com/javase/8/docs/api/java/util/function/Predicate.html) object that you can test with [`Predicate.test`](https://docs.oracle.com/javase/8/docs/api/java/util/function/Predicate.html#test-T-) – Tunaki Oct 05 '15 at 08:17
  • To (1): Can I run JAD on the user's device when the assertion was triggered? Or can that only be a development solution? – Simon Warta Oct 07 '15 at 10:43
  • Never tried... but if you pack .java sources within .class files - then you can extract violation position from stack (StackTraceElement) and original source from corresponding .java file. – ursa Oct 08 '15 at 19:36
2

I think you cannot access the internal java expression, which was passed to method call.

You can, however, use some expression language libraries and do a custom expression handling for your assertion implementation.

Here is a list of some expression language libraries:

http://java-source.net/open-source/expression-languages

Hope that helps :)

Michal
  • 993
  • 6
  • 8
  • Any recommendations of which one to choose? It must be able to handle JVM stuff like `customObject.myfunction() != null` and `customObject.myMember != null`. – Simon Warta Oct 07 '15 at 10:45
  • I think OGNL is used quite widely (e.g. Thymeleaf templates) and it also supports `null` literal, boolean literals, some sort of lambda expressions and many more. You can check the language guide [here](https://commons.apache.org/proper/commons-ognl/language-guide.html). – Michal Oct 07 '15 at 11:37