Repository: authorjapps/zerocode
Pull request #80 (2018-05-27 11:01:40)

ISS-73 # Load - Junit Load Runner


Patch:
diff --git a/src/main/java/org/jsmart/zerocode/core/domain/LoadWith.java b/src/main/java/org/jsmart/zerocode/core/domain/LoadWith.java
new file mode 100644
index 00000000..f41b542e
--- /dev/null
+++ b/src/main/java/org/jsmart/zerocode/core/domain/LoadWith.java
@@ -0,0 +1,12 @@
+package org.jsmart.zerocode.core.domain;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.TYPE)
+public @interface LoadWith {
+    String value();
+}
\ No newline at end of file
diff --git a/src/main/java/org/jsmart/zerocode/core/runner/StepNotificationHandler.java b/src/main/java/org/jsmart/zerocode/core/runner/StepNotificationHandler.java
index bb4d6f64..12b10430 100644
--- a/src/main/java/org/jsmart/zerocode/core/runner/StepNotificationHandler.java
+++ b/src/main/java/org/jsmart/zerocode/core/runner/StepNotificationHandler.java
@@ -15,7 +15,7 @@ import org.slf4j.LoggerFactory;
 import static java.util.Optional.ofNullable;
 
 public class StepNotificationHandler {
-    private static final Logger logger = LoggerFactory.getLogger(StepNotificationHandler.class);
+    private static final Logger LOGGER = LoggerFactory.getLogger(StepNotificationHandler.class);
     private final int MAX_LINE_LENGTH = 130;
     
     Boolean handleAssertionFailed(RunNotifier notifier,
@@ -26,14 +26,15 @@ public class StepNotificationHandler {
         /**
          * Generate error report and display clearly which expectation(s) did not match
          */
-        logger.info(String.format("Failed assertion during Scenario:%s, --> Step:%s, Details: %s\n",
+        LOGGER.error(String.format("Failed assertion during Scenario:%s, --> Step:%s, Details: %s\n",
                         scenarioName, stepName, StringUtils.join(failureReportList, "\n")));
-        notifier.fireTestFailure(new Failure(description, new RuntimeException(
-                        String.format("Assertion failed for :- \n\n[%s] \n\t|\n\t|\n\t+---Step --> [%s] \n\nFailures:\n--------- %n%s%n",
-                                        scenarioName,
-                                        stepName,
-                                        StringUtils.join(failureReportList, "\n" + deckedUpLine(maxEntryLengthOf(failureReportList)) + "\n"))
-        )));
+        String prettyFailureMessage =
+                String.format("Assertion failed for :- \n\n[%s] \n\t|\n\t|\n\t+---Step --> [%s] \n\nFailures:\n--------- %n%s%n",
+                scenarioName,
+                stepName,
+                StringUtils.join(failureReportList, "\n" + deckedUpLine(maxEntryLengthOf(failureReportList)) + "\n"));
+        LOGGER.error(prettyFailureMessage + "(See below 'Actual Vs Expected' to learn why this step failed) \n");
+        notifier.fireTestFailure(new Failure(description, new RuntimeException( prettyFailureMessage)));
         
         return false;
     }
@@ -43,7 +44,7 @@ public class StepNotificationHandler {
                     String scenarioName,
                     String stepName,
                     Exception stepException) {
-        logger.info(String.format("Exception occurred while executing Scenario:[%s], --> Step:[%s], Details: %s",
+        LOGGER.info(String.format("Exception occurred while executing Scenario:[%s], --> Step:[%s], Details: %s",
                         scenarioName, stepName, stepException));
         notifier.fireTestFailure(new Failure(description, stepException));
         
@@ -55,7 +56,7 @@ public class StepNotificationHandler {
                     String scenarioName,
                     String stepName,
                     List<AssertionReport> failureReportList) {
-        logger.info(String.format("\n***Step PASSED:%s->%s", scenarioName, stepName));
+        LOGGER.info(String.format("\n***Step PASSED:%s->%s", scenarioName, stepName));
         
         return true;
     }
diff --git a/src/main/java/org/jsmart/zerocode/core/runner/ZeroCodePackageRunner.java b/src/main/java/org/jsmart/zerocode/core/runner/ZeroCodePackageRunner.java
index 62fe7f25..3501e828 100644
--- a/src/main/java/org/jsmart/zerocode/core/runner/ZeroCodePackageRunner.java
+++ b/src/main/java/org/jsmart/zerocode/core/runner/ZeroCodePackageRunner.java
@@ -35,7 +35,7 @@ public class ZeroCodePackageRunner extends ParentRunner<ScenarioSpec> {
     Injector injector;
     SmartUtils smartUtils;
 
-    protected Description flowDescription;
+    protected Description scenarioDescription;
     protected boolean isRunSuccess;
     protected boolean passed;
     protected boolean testRunCompleted;
@@ -90,8 +90,8 @@ public class ZeroCodePackageRunner extends ParentRunner<ScenarioSpec> {
     @Override
     protected Description describeChild(ScenarioSpec child) {
 
-        this.flowDescription = Description.createTestDescription(testClass, child.getScenarioName());
-        return flowDescription;
+        this.scenarioDescription = Description.createTestDescription(testClass, child.getScenarioName());
+        return scenarioDescription;
 
         /*
          * Commented for right click, fix and enable, Try with IntelliJ or Eclipse, see if it works
@@ -129,8 +129,10 @@ public class ZeroCodePackageRunner extends ParentRunner<ScenarioSpec> {
 
         final Description description = Description.createTestDescription(testClass, child.getScenarioName());
 
+        // ----------------------------------------------
         // Notify that this single test has been started.
         // Supply the scenario/journey name
+        // ----------------------------------------------
         notifier.fireTestStarted(description);
 
         passed = getInjectedMultiStepsRunner().runScenario(child, notifier, description);
diff --git a/src/main/java/org/jsmart/zerocode/core/runner/parallel/LoadProcessor.java b/src/main/java/org/jsmart/zerocode/core/runner/parallel/LoadProcessor.java
new file mode 100644
index 00000000..3f063fbc
--- /dev/null
+++ b/src/main/java/org/jsmart/zerocode/core/runner/parallel/LoadProcessor.java
@@ -0,0 +1,70 @@
+package org.jsmart.zerocode.core.runner.parallel;
+
+import org.jsmart.zerocode.parallel.ExecutorServiceRunner;
+import org.junit.runner.JUnitCore;
+import org.junit.runner.Request;
+import org.junit.runner.Result;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.concurrent.atomic.AtomicInteger;
+
+import static java.time.LocalDateTime.now;
+
+public class LoadProcessor {
+    private static final Logger LOGGER = LoggerFactory.getLogger(LoadProcessor.class);
+
+    private final String loadPropertiesFile;
+    private final AtomicInteger passedCounter = new AtomicInteger();
+    private final AtomicInteger failedCounter = new AtomicInteger();
+    private ExecutorServiceRunner executorServiceRunner;
+    private boolean failed = true;
+    private boolean passed = !failed;
+
+    public LoadProcessor(String loadPropertiesFile) {
+        this.loadPropertiesFile = loadPropertiesFile;
+        executorServiceRunner = new ExecutorServiceRunner(loadPropertiesFile);
+    }
+
+    public LoadProcessor addTest(Class<?> testClass, String testMethod) {
+
+        Runnable zeroCodeJunitTest = createRunnable(testClass, testMethod);
+
+        executorServiceRunner.addRunnable(zeroCodeJunitTest);
+
+        return this;
+    }
+
+    public boolean process() {
+        executorServiceRunner.runRunnables();
+
+        LOGGER.info(
+                  "\n------------------------------------"
+                + "\n   >> Total load test count:" + (failedCounter.get() + passedCounter.get())
+                + "\n   >> Passed count:" + passedCounter.get()
+                + "\n   >> Failed count:" + failedCounter.get()
+                + "\n------------------------------------");
+
+        if(failedCounter.get() > 0){
+            return failed;
+        }
+
+        return passed;
+    }
+
+    private Runnable createRunnable(Class<?> testClass, String testMathod) {
+        return () -> {
+            LOGGER.info(Thread.currentThread().getName() + " Parallel Junit test- *Start. Time = " + now());
+
+            Result result = (new JUnitCore()).run(Request.method(testClass, testMathod));
+
+            LOGGER.info(Thread.currentThread().getName() + " Parallel Junit test- *  End. Time = " + now());
+
+            if (result.wasSuccessful()) {
+                passedCounter.incrementAndGet();
+            } else {
+                failedCounter.incrementAndGet();
+            }
+        };
+    }
+}
diff --git a/src/main/java/org/jsmart/zerocode/core/runner/parallel/ZeroCodeLoadRunner.java b/src/main/java/org/jsmart/zerocode/core/runner/parallel/ZeroCodeLoadRunner.java
new file mode 100644
index 00000000..65869e87
--- /dev/null
+++ b/src/main/java/org/jsmart/zerocode/core/runner/parallel/ZeroCodeLoadRunner.java
@@ -0,0 +1,93 @@
+package org.jsmart.zerocode.core.runner.parallel;
+
+import org.jsmart.zerocode.core.domain.LoadWith;
+import org.jsmart.zerocode.core.domain.TestMapping;
+import org.junit.runner.Description;
+import org.junit.runner.notification.Failure;
+import org.junit.runner.notification.RunNotifier;
+import org.junit.runners.ParentRunner;
+import org.junit.runners.model.InitializationError;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Arrays;
+import java.util.List;
+
+import static org.junit.runner.Description.createTestDescription;
+
+public class ZeroCodeLoadRunner extends ParentRunner<TestMapping> {
+    private static final Logger LOGGER = LoggerFactory.getLogger(ZeroCodeLoadRunner.class);
+    public static final String LOAD_LABEL = "<<Load>>";
+
+    private final Class<?> testClass;
+    private LoadProcessor loadProcessor;
+    private String loadPropertiesFile;
+    private Description testDescription;
+
+    public ZeroCodeLoadRunner(Class<?> testClass) throws InitializationError {
+        super(testClass);
+        this.testClass = testClass;
+        this.loadPropertiesFile = validateAndGetLoadPropertiesFile();
+        loadProcessor = new LoadProcessor(loadPropertiesFile);
+    }
+
+    @Override
+    protected List<TestMapping> getChildren() {
+        validateAnnotationPresence();
+        return Arrays.asList(testClass.getAnnotationsByType(TestMapping.class));
+    }
+
+    @Override
+    protected Description describeChild(TestMapping child) {
+        this.testDescription = createTestDescription(testClass, LOAD_LABEL + child.testMethod());
+        return testDescription;
+    }
+
+    @Override
+    protected void runChild(TestMapping child, RunNotifier notifier) {
+        notifier.fireTestStarted(testDescription);
+
+        boolean hasFailed = loadProcessor
+                .addTest(child.testClass(), child.testMethod())
+                .process();
+
+        if(hasFailed){
+            String failureMessage = testClass.getName() + "." + child.testMethod() + " Failed";
+            LOGGER.error(failureMessage + ". See target/logs -or- junit granular failure report(csv) -or- fuzzy search and filter report(html) for details");
+            notifier.fireTestFailure(new Failure(testDescription, new RuntimeException(failureMessage)));
+        }
+        notifier.fireTestFinished(testDescription);
+    }
+
+    @Override
+    public void run(RunNotifier notifier) {
+        super.run(notifier);
+    }
+
+    private String validateAndGetLoadPropertiesFile() {
+        LoadWith loadWithAnno = testClass.getAnnotation(LoadWith.class);
+        if(loadWithAnno == null){
+            throw new RuntimeException("Ah! You missed to put the @LoadWith(...) on the load-generating test class >> "
+                    + testClass.getName());
+        }
+
+        return loadWithAnno.value();
+    }
+
+    private void validateAnnotationPresence() {
+        TestMapping methodMapping = testClass.getAnnotation(TestMapping.class);
+        TestMapping[] testMappings = testClass.getAnnotationsByType(TestMapping.class);
+
+        if (testMappings.length > 1){
+            throw new RuntimeException("Oops! Needs single @TestMapping, but found multiple of it on the load-generating test class >>"
+                    + testClass.getName() + ". \n For running multiple tests as load use @RunWith(ZeroCodeMultiLoadRunner.class)");
+
+        } else if (methodMapping == null) {
+            throw new RuntimeException("Ah! You missed to put the @TestMapping on the load-generating test class >> "
+                    + testClass.getName());
+
+        }
+
+    }
+
+}
diff --git a/src/main/java/org/jsmart/zerocode/core/runner/parallel/ZeroCodeMultiLoadRunner.java b/src/main/java/org/jsmart/zerocode/core/runner/parallel/ZeroCodeMultiLoadRunner.java
new file mode 100644
index 00000000..51a8b6e5
--- /dev/null
+++ b/src/main/java/org/jsmart/zerocode/core/runner/parallel/ZeroCodeMultiLoadRunner.java
@@ -0,0 +1,69 @@
+package org.jsmart.zerocode.core.runner.parallel;
+
+import org.jsmart.zerocode.core.domain.TestMapping;
+import org.jsmart.zerocode.core.domain.TestMappings;
+import org.junit.runner.Description;
+import org.junit.runner.JUnitCore;
+import org.junit.runner.Request;
+import org.junit.runner.Result;
+import org.junit.runner.notification.Failure;
+import org.junit.runner.notification.RunNotifier;
+import org.junit.runners.ParentRunner;
+import org.junit.runners.model.InitializationError;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Arrays;
+import java.util.List;
+
+// TODO-- wip...
+public class ZeroCodeMultiLoadRunner extends ParentRunner<TestMapping> {
+    private static final Logger LOGGER = LoggerFactory.getLogger(ZeroCodeMultiLoadRunner.class);
+
+    private final Class<?> testClass;
+
+    public ZeroCodeMultiLoadRunner(Class<?> testClass) throws InitializationError {
+        super(testClass);
+        this.testClass = testClass;
+    }
+
+    @Override
+    protected List<TestMapping> getChildren() {
+        validateAnnotationPresence();
+        return Arrays.asList(testClass.getAnnotationsByType(TestMapping.class));
+    }
+
+    @Override
+    protected Description describeChild(TestMapping child) {
+        return Description.createTestDescription(testClass, child.testMethod());
+    }
+
+    @Override
+    protected void runChild(TestMapping child, RunNotifier notifier) {
+        final Description description = Description.createTestDescription(testClass, child.testMethod());
+        notifier.fireTestStarted(description);
+
+        Result result = (new JUnitCore()).run(Request.method(child.testClass(), child.testMethod()));
+
+        if(!result.wasSuccessful()){
+            String failureMessage = testClass.getName() + "." + child.testMethod() + " Failed";
+            LOGGER.error(failureMessage + ". See target/logs -or- junit granular failure report(csv) -or- fuzzy search and filter report(html) for details");
+            notifier.fireTestFailure(new Failure(description, new RuntimeException(failureMessage)));
+        }
+        notifier.fireTestFinished(description);
+    }
+
+    @Override
+    public void run(RunNotifier notifier) {
+        super.run(notifier);
+    }
+
+    private void validateAnnotationPresence() {
+        TestMappings testMappings = testClass.getAnnotation(TestMappings.class);
+        TestMapping[] methodMappings = testClass.getAnnotationsByType(TestMapping.class);
+        if (testMappings == null || methodMappings == null) {
+            throw new RuntimeException("Ah! You missed to put the @TestMappings or @TestMapping");
+        }
+    }
+
+}
diff --git a/src/main/java/org/jsmart/zerocode/parallel/ExecutorServiceRunner.java b/src/main/java/org/jsmart/zerocode/parallel/ExecutorServiceRunner.java
index 76de78af..81f5dc08 100644
--- a/src/main/java/org/jsmart/zerocode/parallel/ExecutorServiceRunner.java
+++ b/src/main/java/org/jsmart/zerocode/parallel/ExecutorServiceRunner.java
@@ -2,12 +2,10 @@ package org.jsmart.zerocode.parallel;
 
 
 import org.jboss.resteasy.spi.InternalServerErrorException;
-import org.jsmart.zerocode.core.runner.ZeroCodeUnitRunner;
 import org.jsmart.zerocode.core.utils.PropertiesProviderUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import java.time.LocalDateTime;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Properties;
@@ -16,14 +14,17 @@ import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Future;
 import java.util.function.Consumer;
 
+import static java.lang.Double.valueOf;
 import static java.lang.Integer.parseInt;
+import static java.lang.Thread.sleep;
+import static java.time.LocalDateTime.now;
 import static java.util.concurrent.Executors.newFixedThreadPool;
 
 public class ExecutorServiceRunner {
-    private static final Logger LOGGER = LoggerFactory.getLogger(ZeroCodeUnitRunner.class);
+    private static final Logger LOGGER = LoggerFactory.getLogger(ExecutorServiceRunner.class);
 
-    private List<Runnable> runnables = new ArrayList<>();
-    private List<Callable<Object>> callables = new ArrayList<>();
+    private final List<Runnable> runnables = new ArrayList<>();
+    private final List<Callable<Object>> callables = new ArrayList<>();
 
     private int numberOfThreads;
     private int rampUpPeriod;
@@ -31,10 +32,6 @@ public class ExecutorServiceRunner {
 
     private Double delayBetweenTwoThreadsInMilliSecs;
 
-//    public ExecutorServiceRunner() {
-//        delayBetweenTwoThreadsInMilliSecs = (Double.valueOf(rampUpPeriod) / Double.valueOf(numberOfThreads)) * 1000L;
-//    }
-
     public ExecutorServiceRunner(String loadPropertiesFile) {
         Properties properties = PropertiesProviderUtils.getProperties(loadPropertiesFile);
         numberOfThreads = parseInt(properties.getProperty("number.of.threads"));
@@ -78,17 +75,19 @@ public class ExecutorServiceRunner {
             for (int i = 0; i < loopCount; i++) {
                 runnables.stream().forEach(thisFunction -> {
                     for (int j = 0; j < numberOfThreads; j++) {
-                        LOGGER.info(Thread.currentThread().getName() + " JUnit test- Start. Time = " + LocalDateTime.now());
                         try {
-                            LOGGER.info("Waiting in the transit for next test flight to adjust overall ramp up time, wait time now = " + delayBetweenTwoThreadsInMilliSecs);
-                            Thread.sleep(delayBetweenTwoThreadsInMilliSecs.longValue());
+                            LOGGER.info("Waiting for the next test flight to adjust the overall ramp up time, " +
+                                    "waiting time in the transit now = " + delayBetweenTwoThreadsInMilliSecs);
+                            sleep(delayBetweenTwoThreadsInMilliSecs.longValue());
                         } catch (InterruptedException e) {
                             throw new RuntimeException(e);
                         }
 
+                        LOGGER.info(Thread.currentThread().getName() + " Executor - *Start... Time = " + now());
+
                         executorService.execute(thisFunction);
 
-                        LOGGER.info(Thread.currentThread().getName() + " JUnit test- *Finished Time = " + LocalDateTime.now());
+                        LOGGER.info(Thread.currentThread().getName() + " Executor - *Finished Time = " + now());
                     }
                 });
             }
@@ -97,7 +96,9 @@ public class ExecutorServiceRunner {
         } finally {
             executorService.shutdown();
             while (!executorService.isTerminated()) {
-                //wait for all tasks to finish execution
+                // --------------------------------------
+                // wait for all tasks to finish execution
+                // --------------------------------------
                 //LOGGER.info("Still waiting for all threads to complete execution...");
             }
             LOGGER.info("**Finished executing all threads**");
@@ -119,17 +120,18 @@ public class ExecutorServiceRunner {
         try {
             executorService.invokeAll(callables).stream().forEach(future -> {
                 for (int j = 0; j < numberOfThreads; j++) {
-                    LOGGER.info(Thread.currentThread().getName() + " Future execution- Start. Time = " + LocalDateTime.now());
                     try {
                         LOGGER.info("Waiting in the transit for next test flight to adjust overall ramp up time, wait time now = " + delayBetweenTwoThreadsInMilliSecs);
-                        Thread.sleep(delayBetweenTwoThreadsInMilliSecs.longValue());
+                        sleep(delayBetweenTwoThreadsInMilliSecs.longValue());
                     } catch (InterruptedException e) {
                         throw new RuntimeException(e);
                     }
 
+                    LOGGER.info(Thread.currentThread().getName() + " Future execution- Start.... Time = " + now());
+
                     execute(future);
 
-                    LOGGER.info(Thread.currentThread().getName() + " Future execution- *Finished Time = " + LocalDateTime.now());
+                    LOGGER.info(Thread.currentThread().getName() + " Future execution- *Finished Time = " + now());
                 }
             });
         } catch (InterruptedException interruptEx) {
@@ -169,10 +171,8 @@ public class ExecutorServiceRunner {
     private void calculateAndSetDelayBetweenTwoThreadsInSecs(int rampUpPeriod) {
         if (rampUpPeriod == 0) {
             delayBetweenTwoThreadsInMilliSecs = 0D;
-
         } else {
-
-            delayBetweenTwoThreadsInMilliSecs = (Double.valueOf(rampUpPeriod) / Double.valueOf(numberOfThreads)) * 1000L;
+            delayBetweenTwoThreadsInMilliSecs = (valueOf(rampUpPeriod) / valueOf(numberOfThreads)) * 1000L;
         }
     }
 
@@ -193,8 +193,13 @@ public class ExecutorServiceRunner {
     }
 
     private void logLoadingProperties() {
-        LOGGER.info("### numberOfThreads : " + numberOfThreads);
-        LOGGER.info("### rampUpPeriodInSeconds : " + rampUpPeriod);
-        LOGGER.info("### loopCount : " + loopCount);
+        LOGGER.info(
+                "\nLOAD:" +
+                "\n-----------------------------------" +
+                "\n   ### numberOfThreads : " + numberOfThreads +
+                "\n   ### rampUpPeriodInSeconds : " + rampUpPeriod +
+                "\n   ### loopCount : " + loopCount +
+                "\n-----------------------------------\n");
+
     }
 }


Test patch:
diff --git a/src/main/java/org/jsmart/zerocode/core/domain/TestMapping.java b/src/main/java/org/jsmart/zerocode/core/domain/TestMapping.java
new file mode 100644
index 00000000..c279ed7b
--- /dev/null
+++ b/src/main/java/org/jsmart/zerocode/core/domain/TestMapping.java
@@ -0,0 +1,12 @@
+package org.jsmart.zerocode.core.domain;
+
+
+import java.lang.annotation.*;
+
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.TYPE)
+@Repeatable( value = TestMappings.class )
+public @interface TestMapping {
+    Class<?> testClass();
+    String testMethod();
+}
\ No newline at end of file
diff --git a/src/main/java/org/jsmart/zerocode/core/domain/TestMappings.java b/src/main/java/org/jsmart/zerocode/core/domain/TestMappings.java
new file mode 100644
index 00000000..ee1280fe
--- /dev/null
+++ b/src/main/java/org/jsmart/zerocode/core/domain/TestMappings.java
@@ -0,0 +1,13 @@
+package org.jsmart.zerocode.core.domain;
+
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Target(ElementType.TYPE)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface TestMappings {
+    public TestMapping[] value() default {};
+}
\ No newline at end of file
diff --git a/src/test/java/org/jsmart/zerocode/core/runner/parallel/ZeroCodeLoadRunnerTest.java b/src/test/java/org/jsmart/zerocode/core/runner/parallel/ZeroCodeLoadRunnerTest.java
new file mode 100644
index 00000000..7901d84d
--- /dev/null
+++ b/src/test/java/org/jsmart/zerocode/core/runner/parallel/ZeroCodeLoadRunnerTest.java
@@ -0,0 +1,91 @@
+package org.jsmart.zerocode.core.runner.parallel;
+
+import org.jsmart.zerocode.core.domain.LoadWith;
+import org.jsmart.zerocode.core.domain.TestMapping;
+import org.jsmart.zerocode.parallel.restful.JunitRestTestSample;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.runner.RunWith;
+
+import java.util.List;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.core.Is.is;
+
+public class ZeroCodeLoadRunnerTest {
+
+    @Rule
+    public ExpectedException expectedException = ExpectedException.none();
+
+    ZeroCodeLoadRunner zeroCodePackageRunner;
+    ZeroCodeLoadRunner runnerNoTestMapping;
+    ZeroCodeLoadRunner runnerMultiTestMapping;
+
+    @LoadWith("load_config_test.properties")
+    @TestMapping(testClass = JunitRestTestSample.class, testMethod = "testGetCallToHome_pass")
+    @RunWith(ZeroCodeLoadRunner.class)
+    public class ExampleRunnerTest { //note- "public" is necessary
+
+    }
+
+    // @TestMapping not present for unit test
+    @LoadWith("load_config_test.properties")
+    @RunWith(ZeroCodeLoadRunner.class)
+    public class ExampleRunnerWithoutMappingTest {
+    }
+
+    @LoadWith("load_config_test.properties")
+    @TestMapping(testClass = JunitRestTestSample.class, testMethod = "testGetCallToHome_pass")
+    @TestMapping(testClass = JunitRestTestSample.class, testMethod = "testGetCallToHome_fail")
+    @RunWith(ZeroCodeLoadRunner.class)
+    public class ExampleRunnerMultiMappingTest {
+    }
+
+    @Before
+    public void initializeRunner() throws Exception {
+        zeroCodePackageRunner = new ZeroCodeLoadRunner(ExampleRunnerTest.class);
+        runnerNoTestMapping = new ZeroCodeLoadRunner(ExampleRunnerWithoutMappingTest.class);
+        runnerMultiTestMapping = new ZeroCodeLoadRunner(ExampleRunnerMultiMappingTest.class);
+    }
+
+    @Test
+    public void testPair_annotaionSingle() {
+        TestMapping mapping = ExampleRunnerTest.class.getAnnotation(TestMapping.class);
+        assertThat(mapping.testMethod(), is("testGetCallToHome_pass"));
+        assertThat(mapping.testClass().getName(), is((JunitRestTestSample.class.getName())));
+    }
+
+    @Test
+    public void test_noMapping() {
+        expectedException.expectMessage("Ah! You missed to put the @TestMapping on the load-generating test class");
+        List<TestMapping> children = runnerNoTestMapping.getChildren();
+    }
+
+    @Test
+    public void testPair_multiMappingException() {
+        expectedException.expectMessage("Oops! Needs single @TestMapping, but found multiple of it on the load-generating test class");
+        List<TestMapping> children = runnerMultiTestMapping.getChildren();
+    }
+
+    @Test
+    public void testPair_size() {
+        List<TestMapping> children = zeroCodePackageRunner.getChildren();
+        assertThat(children.size(), is(1));
+    }
+
+    @Test
+    public void testPair_singleClassVsMethodMap() {
+        List<TestMapping> children = zeroCodePackageRunner.getChildren();
+        assertThat(children.get(0).testMethod(), is("testGetCallToHome_pass"));
+        assertThat(children.get(0).testClass().getName(), is((JunitRestTestSample.class.getName())));
+    }
+
+    @Test
+    public void testLoad_properties() {
+        LoadWith loadWithFile = ExampleRunnerTest.class.getAnnotation(LoadWith.class);
+        assertThat(loadWithFile.value(), is("load_config_test.properties"));
+    }
+
+}
\ No newline at end of file
diff --git a/src/test/java/org/jsmart/zerocode/core/runner/parallel/ZeroCodeMultiLoadRunnerTest.java b/src/test/java/org/jsmart/zerocode/core/runner/parallel/ZeroCodeMultiLoadRunnerTest.java
new file mode 100644
index 00000000..bbf810ee
--- /dev/null
+++ b/src/test/java/org/jsmart/zerocode/core/runner/parallel/ZeroCodeMultiLoadRunnerTest.java
@@ -0,0 +1,64 @@
+package org.jsmart.zerocode.core.runner.parallel;
+
+import org.jsmart.zerocode.core.domain.LoadWith;
+import org.jsmart.zerocode.core.domain.TestMapping;
+import org.jsmart.zerocode.core.domain.TestMappings;
+import org.jsmart.zerocode.parallel.restful.JunitRestTestSample;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.List;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.core.Is.is;
+
+// TODO- wip
+public class ZeroCodeMultiLoadRunnerTest {
+
+
+    @LoadWith("load_config_test.properties")
+    @TestMappings({
+            @TestMapping(testClass = JunitRestTestSample.class, testMethod = "testGetCallToHome_pass"),
+            @TestMapping(testClass = JunitRestTestSample.class, testMethod = "testGetCallToHome_fail"),
+    })
+    @RunWith(ZeroCodeMultiLoadRunner.class)
+    public class LoadRestEndPointRunnerTest { //note "public" is necessary
+
+    }
+
+    ZeroCodeMultiLoadRunner zeroCodePackageRunner;
+
+    @Before
+    public void initializeRunner() throws Exception {
+        zeroCodePackageRunner = new ZeroCodeMultiLoadRunner(LoadRestEndPointRunnerTest.class);
+    }
+
+    @Test
+    public void testPair_size() {
+        List<TestMapping> children = zeroCodePackageRunner.getChildren();
+        assertThat(children.size(), is(2));
+    }
+
+    @Test
+    public void testPair_annotaionMulti() {
+        TestMapping[] mappings = LoadRestEndPointRunnerTest.class.getAnnotationsByType(TestMapping.class);
+        assertThat(mappings[0].testMethod(), is("testGetCallToHome_pass"));
+        assertThat(mappings[0].testClass().getName(), is((JunitRestTestSample.class.getName())));
+    }
+
+    @Test
+    public void testPair_classVsMethodMap() {
+        List<TestMapping> children = zeroCodePackageRunner.getChildren();
+        TestMapping[] testMappings = LoadRestEndPointRunnerTest.class.getAnnotationsByType(TestMapping.class);
+        assertThat(children.get(0).testMethod(), is("testGetCallToHome_pass"));
+        assertThat(children.get(0).testClass().getName(), is((JunitRestTestSample.class.getName())));
+    }
+
+    @Test
+    public void testLoad_properties() {
+        LoadWith loadWithFile = LoadRestEndPointRunnerTest.class.getAnnotation(LoadWith.class);
+        assertThat(loadWithFile.value(), is("load_config_test.properties"));
+    }
+
+}
\ No newline at end of file
diff --git a/src/test/java/org/jsmart/zerocode/parallel/restful/JunitRestTestSample.java b/src/test/java/org/jsmart/zerocode/parallel/restful/JunitRestTestSample.java
index 13fec26e..be5c1d11 100644
--- a/src/test/java/org/jsmart/zerocode/parallel/restful/JunitRestTestSample.java
+++ b/src/test/java/org/jsmart/zerocode/parallel/restful/JunitRestTestSample.java
@@ -23,6 +23,12 @@ public class JunitRestTestSample {
 
     }
 
+    @Test
+    @JsonTestCase("load/simple_load_at_localhost_fail.json")
+    public void testGetCallToHome_fail() throws Exception {
+
+    }
+
 
 }
 
diff --git a/src/test/java/org/jsmart/zerocode/parallel/restful/LoadRestEndPointMultiRunnerTest.java b/src/test/java/org/jsmart/zerocode/parallel/restful/LoadRestEndPointMultiRunnerTest.java
new file mode 100644
index 00000000..b605153b
--- /dev/null
+++ b/src/test/java/org/jsmart/zerocode/parallel/restful/LoadRestEndPointMultiRunnerTest.java
@@ -0,0 +1,22 @@
+package org.jsmart.zerocode.parallel.restful;
+
+import org.jsmart.zerocode.core.domain.LoadWith;
+import org.jsmart.zerocode.core.domain.TestMapping;
+import org.jsmart.zerocode.core.runner.parallel.ZeroCodeLoadRunner;
+import org.jsmart.zerocode.core.runner.parallel.ZeroCodeMultiLoadRunner;
+import org.junit.runner.RunWith;
+
+
+//@TestMappings({
+//        @TestMapping(testClass = JunitRestTestSample.class, testMethod = "testGetCallToHome_pass"),
+////        @TestMapping(testClass = JunitRestTestSample.class, testMethod = "testGetCallToHome_fail"),
+//})
+//@RunWith(ZeroCodeMultiLoadRunner.class)
+
+//@LoadWith("load_config_test.properties")
+//@TestMapping(testClass = JunitRestTestSample.class, testMethod = "testGetCallToHome_pass")
+//@TestMapping(testClass = JunitRestTestSample.class, testMethod = "testGetCallToHome_fail")
+//@RunWith(ZeroCodeMultiLoadRunner.class)
+//public class LoadRestEndPointMultiRunnerTest {
+//
+//}
\ No newline at end of file
diff --git a/src/test/java/org/jsmart/zerocode/parallel/restful/LoadRestEndPointSingleRunnerTest.java b/src/test/java/org/jsmart/zerocode/parallel/restful/LoadRestEndPointSingleRunnerTest.java
new file mode 100644
index 00000000..0e633ccf
--- /dev/null
+++ b/src/test/java/org/jsmart/zerocode/parallel/restful/LoadRestEndPointSingleRunnerTest.java
@@ -0,0 +1,13 @@
+package org.jsmart.zerocode.parallel.restful;
+
+import org.jsmart.zerocode.core.domain.LoadWith;
+import org.jsmart.zerocode.core.domain.TestMapping;
+import org.jsmart.zerocode.core.runner.parallel.ZeroCodeLoadRunner;
+import org.junit.runner.RunWith;
+
+@LoadWith("load_config_test.properties")
+@TestMapping(testClass = JunitRestTestSample.class, testMethod = "testGetCallToHome_pass")
+@RunWith(ZeroCodeLoadRunner.class)
+public class LoadRestEndPointSingleRunnerTest {
+
+}
\ No newline at end of file
diff --git a/src/test/resources/load/simple_load_at_localhost_fail.json b/src/test/resources/load/simple_load_at_localhost_fail.json
new file mode 100644
index 00000000..5c567127
--- /dev/null
+++ b/src/test/resources/load/simple_load_at_localhost_fail.json
@@ -0,0 +1,18 @@
+{
+    "scenarioName": "Load- Get call to home will fetch details -Fail",
+    "steps": [
+        {
+            "name": "fetch_home_details",
+            "url": "/home/bathroom/1",
+            "operation": "GET",
+            "request": {
+            },
+            "assertions": {
+                "status": 400,
+                "body": {
+                    "id": 1
+                }
+            }
+        }
+    ]
+}
diff --git a/src/test/resources/load_config_test.properties b/src/test/resources/load_config_test.properties
index f144c402..8a0132d3 100755
--- a/src/test/resources/load_config_test.properties
+++ b/src/test/resources/load_config_test.properties
@@ -1,23 +1,25 @@
-# You can enter as many threads to stimulate a load test. A single user is represented by each Thread.
-# So if you wish to simulate a load test with 5 concurrent users then you need to enter 5 as the value
-# for this property.
-number.of.threads=3
+# You can enter as many threads to stimulate a load test. A single user is represented by each Thread. So if you wish
+# to simulate a load test with 5 concurrent users then you need to enter 5 as the value for this property. A high end
+# machine will be able to spawn more number of threads. To keep the consistent(or nearly consistent) gap between the
+# threads, adjust this number with 'ramp.up.period.in.seconds' and the actual response time of the API end point.
+number.of.threads=2
 
-# It indicates the time taken to create all of the threads needed to fore the requests.
-# If you set 10 seconds as the ramp-up period for 5 threads then the framework will take 10 seconds to create those 5 threads,
-# i.e. each thread will be in use appx 2 secs gap between the requests.
-# Also by setting its value to 0 all the threads can be created at once at the same time.
-# Note- If you want to ramp up and fire more threads/user-requests in less time e.g. 5 threads in 2secs(or 1 sec),
-# then, use '@UseHttpClient(SslTrustHttpClient.class)' as this closes the connection before making next connection.
-ramp.up.period.in.seconds=3
+# It indicates the time taken to create all of the threads needed to fork the requests. If you set 10 seconds as the
+# ramp-up period for 5 threads then the framework will take  10 seconds to create those 5 threads, i.e. each thread
+# will be at work appx 2 secs gap between the requests. Also by setting its value to 0 all the threads can be created
+# at once at the same time. Note- If you want to fire more threads/user-requests in less ramp up time e.g. 5 threads
+# in 2secs(or 5 threads in 1 sec), then, use '@UseHttpClient(SslTrustHttpClient.class)' as this closes the connection
+# before making the next connection.
+ramp.up.period.in.seconds=2
 
-# Keep it equal or lesser than "number.of.threads" size
-#During the ramp up period, say 10 sec, how many requests you want to fire.
-#e.g. in 10 secs, I want to fire 5 requests, that means, if you loop 6 times ie loop.count=6
-#you fire 5*(60/10)=30 per min i.e. 30*60 = 1800 per hour i.e. 1800*8=14400 per 8hrs
-requests.during.ramp.up.period=5
-
-# By specifying its value framework gets to know that how many times the test(s) will be repeated
-# e.g.
-loop.count=2
+# By specifying its value framework gets to know that how many times the test(s), i.e. the number of requests will be
+# repeated per every 'ramp.up.period.in.seconds'.
+# Supposing number.of.threads = x, ramp.up.period.in.seconds = y, loop.count = i
+# then (x * i) = number of requests will be fired over (y * i) seconds. If x=5, i=3, y=20, then 15 requests will be
+# fired in 60 seconds which means- every request in 4 seconds gap. 60/15 or 20/5 = 4seconds.
+loop.count=1
 
+# If you have set the loop count to a higher digit which e.g. should take 3hrs(3*60*60=10800sec),
+# but due to load or network delay it could take more time(you are speculating) e.g. 4hrs, then you can
+# set this abort value to 3hrs i.e. 3*60*60=10800sec.
+abort.after.time.lapsed.in.seconds=600
\ No newline at end of file