/** * 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 * <p/> * http://www.apache.org/licenses/LICENSE-2.0 * <p/> * 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.api.junit; import com.noga.njexl.lang.*; import com.noga.njexl.lang.extension.TypeUtility; import com.noga.njexl.lang.internal.logging.Log; import com.noga.njexl.lang.internal.logging.LogFactory; import com.noga.njexl.testing.api.Annotations; import com.noga.njexl.testing.api.Annotations.MethodRunInformation; import com.noga.njexl.testing.api.CallContainer; import com.noga.njexl.testing.dataprovider.collection.XStreamIterator; import org.junit.*; import org.junit.runners.model.InitializationError; import org.junit.runners.model.TestClass; import org.junit.runners.parameterized.BlockJUnit4ClassRunnerWithParameters; import org.junit.runners.parameterized.TestWithParameters; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; /** * An Implementation of the JUnit Runner */ public class JApiRunner extends BlockJUnit4ClassRunnerWithParameters { /* Conversion of nano second to milli second */ public static final double NANO_TO_MILLIS = 1000000.0 ; public static class WorkerThread implements Runnable{ final CallContainer[] cc; final int delay; /** * Delays ( waits ) by sleeping * @param ms amount of millisecond */ public static void delay(int ms){ try{ Thread.sleep(ms); }catch (Exception e){ } } /** * Creates a worker thread * @param c an array of call container * @param d the delay time */ public WorkerThread(CallContainer[] c, int d ){ cc = c ; delay = d ; } @Override public void run() { for (int i = 0; i < cc.length ; i++) { delay(delay); cc[i].call(); } } } /** * This is the class that actually gets invoked by JUnit */ public static class ProxyTest { /** * The call container - name to access from nJexl script */ public static final String INPUT = "_cc_" ; /** * The globals - name to access from nJexl script */ public static final String GLOBALS = "_g_" ; Log logger = LogFactory.getLog( ProxyTest.class ); int testNumber ; XStreamIterator<CallContainer> iterator; CallContainer callContainer ; Annotations.NApiThread nApiThread ; /** * Executes the nJexl script from the file * @param file the script file location * @return true if script returns true, false if failure */ public boolean script(String file) { JexlContext context = Main.getContext(); JexlEngine engine = Main.getJexl(context); try { try{ Expression expression = engine.createExpression( callContainer.globals); Object g = expression.evaluate( context ); context.set(GLOBALS,g); }catch (Exception e){ logger.error( String.format("Error generating global : '%s' ", callContainer.globals), e); } Script script = engine.importScript(file); context.set(INPUT, callContainer ); Object o = script.execute(context); return TypeUtility.castBoolean(o,false); }catch (Throwable t){ logger.error( String.format("Error running script : [ %s ] ", file), t); } finally { context.clear(); System.gc(); //collect... } return false; } /** * Creates a proxy test * @param testNumber the unique order no of the test * @param iterator an iterator to get call container from * @param nApiThread the run thread information */ public ProxyTest(int testNumber, XStreamIterator<CallContainer> iterator, Annotations.NApiThread nApiThread){ this.nApiThread = nApiThread ; this.testNumber = testNumber ; this.iterator = iterator ; callContainer = this.iterator.get( this.testNumber ); } /** * Gets the performance check done * @param workers data gathered from these worker threads * @param percentile the percentile value to calculate * @param percentileValueLessThan the expected percentile benchmark value * @return true if performance benchmakring passes, false if fails */ public boolean doPerformanceCheck(WorkerThread[] workers, double percentile, double percentileValueLessThan ) { ArrayList<Double> results = new ArrayList<>(); boolean oneFailed = false ; for ( int i = 0 ; i < workers.length;i++ ){ for ( int j = 0 ; j < workers[i].cc.length ; j++ ){ double d = workers[i].cc[j].timing / NANO_TO_MILLIS ; String uId = workers[i].cc[j].uniqueId(j+1) ; if ( d > 0 ){ results.add(d); System.out.printf("%s -> %f \n", uId, d); } else{ oneFailed = true ; System.err.printf("**ERROR %s -> %s \n", uId , workers[i].cc[j].error); System.err.printf("**ON INPUT %s\n", Main.strArr(workers[i].cc[j].parameters)); } } } if ( results.isEmpty() ) { System.err.println("All Inputs failed, can not have any performance check!"); return false ; } if ( oneFailed ){ System.err.println("Some Input failed, still doing performance check!"); } Collections.sort( results ); int s = results.size() ; double m = results.get(s - 1); // the max int minSample = (int)Math.ceil(1.0/( 1.0 - percentile )); if ( s >= minSample ){ s = (int)Math.ceil(percentile * s); m = results.get(s); } results.clear(); System.out.printf("Expected percentile[%f] ( %f ) , Actual ( %f )\n", percentile, percentileValueLessThan, m ); //always use open set, not closed or semi closed return ( m < percentileValueLessThan ); } @Test public void callMethod() throws Exception{ if ( nApiThread.use() ){ // make use of multi threading int nT = nApiThread.numThreads() ; WorkerThread[] workers = new WorkerThread[nT]; ExecutorService executor = Executors.newFixedThreadPool(nT); int pt = nApiThread.pacingTime(); int sd = nApiThread.spawnTime() ; int nc = nApiThread.numCallPerThread() ; for (int i = 0; i < nT ; i++) { WorkerThread.delay( sd ); CallContainer[] containers = new CallContainer[nc]; if ( nApiThread.dcd() ){ for ( int j = 0 ; j < nc ; j++ ){ callContainer = iterator.next() ; containers[j] = CallContainer.clone(callContainer); } }else{ for ( int j = 0 ; j < nc ; j++ ){ containers[j] = CallContainer.clone(callContainer); } } workers[i] = new WorkerThread( containers , pt); //calling execute method of ExecutorService executor.execute(workers[i]); } executor.shutdown(); while (!executor.isTerminated()) { WorkerThread.delay(100); } // if it was performance ... Annotations.Performance performance = nApiThread.performance(); if ( performance.use() ){ Double lessThan = callContainer.percentile(); short intPercentile = performance.percentile() ; double percentile = 0.9 ; if ( intPercentile > 0 && intPercentile < 100 ){ percentile = intPercentile/100.0; } if ( lessThan == null ){ lessThan = performance.lessThan() ; } boolean passed = doPerformanceCheck( workers, percentile , lessThan ); if ( !passed ){ throw new Exception("Performance Test Failed!"); } } }else { callContainer.call(); } } @Before public void before()throws Exception{ if ( callContainer.pre.isEmpty() ) return; // no pre/post for Threaded/this is for performance testing only if ( nApiThread.use() ) return; callContainer.validationResult = false ; if ( callContainer.pre.endsWith(".jexl") ){ callContainer.validationResult = script(callContainer.pre); if ( !callContainer.validationResult ){ throw new Exception( "Error running input : " + callContainer.toString() ); } } } @After public void after()throws Exception{ if ( callContainer.post.isEmpty() ) return; // no pre/post for Threaded/this is for performance testing only if ( nApiThread.use() ) return; callContainer.validationResult = false ; if ( callContainer.post.endsWith(".jexl") ){ callContainer.validationResult = script(callContainer.post); if ( !callContainer.validationResult ){ throw new Exception( "Error running input : " + callContainer.toString() ); } } } } /** * One single static class field */ public static final Class proxy = ProxyTest.class ; /** * Creates a jUnit runner * @param testNumber unique test number * @param iterator iterator to get the data * @param mi method information to wrap to create tests * @return one runner * @throws Exception in case of failure to do so */ public static JApiRunner createRunner( int testNumber, XStreamIterator<CallContainer> iterator, MethodRunInformation mi) throws Exception{ TestClass testClass = new TestClass(proxy); String name = mi.method.getName() ; List<Object> parameters = new ArrayList<>(); parameters.add( testNumber ); parameters.add( iterator ); parameters.add( mi.nApiThread ); TestWithParameters test = new TestWithParameters( name, testClass, parameters); return new JApiRunner(test); } /** * Wraps a test * @param test a jUnit test with parameters * @throws InitializationError in case it fails to wrap */ public JApiRunner(TestWithParameters test) throws InitializationError { super(test); } }