/** * Copyright 2015 Nabarun Mondal * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package com.noga.njexl.testing; import com.noga.njexl.lang.Interpreter.AnonymousParam; import com.noga.njexl.lang.JexlException; import com.noga.njexl.lang.extension.TypeUtility; import java.util.EventObject; import java.util.HashSet; /** * A central assertion framework * Created by noga on 15/04/15. */ public final class TestAssert { public static final String ASSERT_VAR = "assert" ; public static final String ASSERT_NS = "assert" ; boolean error; /** * Does the assert has error? * @return true if it has, false if it does not */ public boolean hasError(){ return error; } /** * Clears the assert error */ public void clearError(){ error = false ; } /** * Types of assertion */ public enum AssertionType { /** * Test assertion : when true it passes, when false it fails */ TEST, /** * When true, the current test script will be aborted , false means no effect */ ABORT } /** * A standard class to axiomatize the assertions */ public static final class AssertionAssignment extends Throwable{ private AssertionAssignment(){ } @Override public String toString() { return "Assertion"; } } /** * A final field to ensure default assertion type is axiomatic */ public static final AssertionAssignment assertion = new AssertionAssignment(); /** * An Assertion Event object */ public static class AssertionEvent extends EventObject { /** * Type of assertion */ public final AssertionType type; /** * In case the caller passed any data */ public final Object[] data; /** * The final value of this assertion */ public final boolean value; /** * <pre> * What caused this assertion? * Either it is an expression evaluated, in which case it will be @{AssertionAssignment} * Else it is created by failure to evaluate block of code ( anonymous function ) * where the resultant error would be stored. * </pre> */ public final Throwable cause; /** * Creates an object * @param source source of the event * @param type what type the assertion is * @param value for test true passes, for abort false passes * @param cause cause of the assertion * @param data any data people wants to pass */ public AssertionEvent(Object source, AssertionType type, boolean value, Throwable cause, Object[] data) { super(source); this.type = type; this.cause = cause ; this.data = data ; this.value = value ; } @Override public String toString(){ boolean failed = ((TestAssert)getSource()).hasError(); String ret = String.format("%s %s => %s | caused by : %s", type, value, com.noga.njexl.lang.Main.strArr(data), cause ); if ( failed ){ return "!!!" + ret ; } return ret; } } /** * Anyone who wants to listen to assertions */ public interface AssertionEventListener{ void onAssertion(AssertionEvent assertionEvent); } /** * The event listeners */ public final HashSet<AssertionEventListener> eventListeners; public TestAssert(){ eventListeners = new HashSet<>(); } /** * Fires a test assertion - * @param v - Either a boolean, or an anonymous function or an object to be evaluated as boolean * true passes it, false fails it * @param args any args one may pass */ public void test(Object v, Object...args){ boolean value = false ; Throwable cause = assertion; if ( v instanceof Boolean ){ value = (boolean)v; } else if ( v instanceof AnonymousParam ){ AnonymousParam anon = (AnonymousParam)v; try { Object o = anon.execute(); value = TypeUtility.castBoolean(o,false); }catch (Throwable t){ cause = t.getCause(); if ( cause == null ){ cause = t; } } }else{ value = TypeUtility.castBoolean(v,false); } error = !value ; for ( AssertionEventListener listener : eventListeners ){ AssertionEvent event = new AssertionEvent(this, AssertionType.TEST, value, cause, args); try { listener.onAssertion(event); }catch (Throwable t){ System.err.printf("Error *test* asserting to listener : %s [%s]\n",listener,t); } } } /** * Fires a abort assertion - * @param v - Either a boolean, or an anonymous function or an object to be evaluated as boolean * true aborts current script , false is a no operation * @param args any args one may pass */ public void abort(Object v, Object...args) throws JexlException.Return { boolean value = true ; Throwable cause = assertion; if ( v instanceof Boolean ){ value = (boolean)v; } else if ( v instanceof AnonymousParam ){ AnonymousParam anon = (AnonymousParam)v; try { Object o = anon.execute(); value = TypeUtility.castBoolean(o,true); }catch (Throwable t){ cause = t.getCause(); if ( cause == null ){ cause = t; } } }else{ value = TypeUtility.castBoolean(v,true); } error = value ; for ( AssertionEventListener listener : eventListeners ){ AssertionEvent event = new AssertionEvent(this, AssertionType.ABORT, value,cause,args); try { listener.onAssertion(event); }catch (Throwable t){ System.err.printf("Error *abort* asserting to listener : %s [%s]\n",listener,t); } } if ( value ){ TypeUtility.bye(args); } } }