Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

193 the phased group of a non interruptive phased test is the event #210

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -636,9 +636,10 @@ For now, we have not come around to deciding how retry should work in the case o

## Release Notes

### 8.11.2
### 8.11.3 in Progress
* **(new feature)** [#178 Allowing the injection in any step of a scenario](https://github.com/adobe/phased-testing/issues/178). We can now inject an event into a step in an arbitrary phased test. This is done by setting the syetm property PHASED.EVENTS.TARGET. This way you can inject the event into that step.
* **(new feature)** [#198 Adding Post Step Event actions](https://github.com/adobe/phased-testing/issues/198). We allow you to define a 'tearDownEvent' tool to allow you to put the system back to a normal state after the event has finished. Please refer to the chapter [Performing Event Cleanup Actions](#performing-event-cleanup-actions).
* [#202 Error Handling during Event Execution](https://github.com/adobe/phased-testing/issues/202). Exceptions in events were either not communicated or they caused unexpected behavior. We now return the exceptions thrown by the event.

### 8.11.1
* Renaming ConfigValueHandler to ConfigValueHandlerPhased
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1,906 changes: 1,905 additions & 1 deletion diagrams/PhasedDiagrams.drawio

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,12 @@

import com.adobe.campaign.tests.integro.phased.exceptions.PhasedTestingEventException;

import java.util.concurrent.Future;

public abstract class NonInterruptiveEvent implements Runnable {

Future<?> threadFuture = null;

/**
* Starts the non-interruptive event
*/
Expand All @@ -39,19 +43,14 @@ public enum states {DEFINED , STARTED, FAILURE, FINISHED};

@Override
public void run() {
state = startEvent() ? states.STARTED : states.FAILURE;

if (state.equals(states.FAILURE)) {
throw new PhasedTestingEventException("There was a problem starting this event.");
try {
startEvent();
state = states.STARTED;
} catch (Exception e) {
state = states.FAILURE;
throw new PhasedTestingEventException("There was a problem starting this event.", e);
}

waitTillFinished();

if (!isFinished()) {
throw new PhasedTestingEventException("This event did not finish as expected.");
}
state=states.FINISHED;
Thread.currentThread().interrupt();
return;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,20 +13,23 @@

import com.adobe.campaign.tests.integro.phased.exceptions.PhasedTestConfigurationException;
import com.adobe.campaign.tests.integro.phased.exceptions.PhasedTestException;
import com.adobe.campaign.tests.integro.phased.exceptions.PhasedTestingEventException;
import com.adobe.campaign.tests.integro.phased.utils.ClassPathParser;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.testng.ITestResult;

import java.lang.reflect.Method;
import java.util.*;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class PhasedEventManager {
private static final Logger log = LogManager.getLogger();

static Map<String, NonInterruptiveEvent> events = new HashMap<>();
private static ExecutorService eventExecutor = null;
private static List<PhasedEventLogEntry> eventLogs = new ArrayList();

/**
* Returns the declared event. if declared on the method. The declarations have the following precedence:
Expand All @@ -35,11 +38,12 @@ public class PhasedEventManager {
* <li>Declaration in @PhasedTest</li>
* <li>When the property PHASED.EVENTS.NONINTERRUPTIVE is set</li>
* </ol>Null is returned if no such declaration is present.
*
* @param in_method The method we are examining
* @return The event that is declared on the method. Null if there is no event declared for the method
*/
public static String fetchApplicableEvent(Method in_method) {
if (!PhasedTestManager.isPhasedTest(in_method) ) {
if (!PhasedTestManager.isPhasedTest(in_method)) {
return null;
}
/*
Expand Down Expand Up @@ -71,23 +75,53 @@ public static String fetchApplicableEvent(Method in_method) {
ConfigValueHandlerPhased.EVENTS_NONINTERRUPTIVE.fetchValue();

*/
}
else if (in_method.getDeclaringClass().getDeclaredAnnotation(PhasedTest.class).eventClasses().length > 0) {
} else if (in_method.getDeclaringClass().getDeclaredAnnotation(PhasedTest.class).eventClasses().length > 0) {
return in_method.getDeclaringClass().getDeclaredAnnotation(PhasedTest.class).eventClasses()[0];
} else if (ConfigValueHandlerPhased.EVENT_TARGET.isSet()) {
return PhasedTestManager.isPhasedTestTargetOfEvent(in_method) ? ConfigValueHandlerPhased.EVENTS_NONINTERRUPTIVE.fetchValue() : null;
return PhasedTestManager.isPhasedTestTargetOfEvent(
in_method) ? ConfigValueHandlerPhased.EVENTS_NONINTERRUPTIVE.fetchValue() : null;
} else if (ConfigValueHandlerPhased.EVENTS_NONINTERRUPTIVE.isSet()) {
return ConfigValueHandlerPhased.EVENTS_NONINTERRUPTIVE.fetchValue();
}
return null;
}

protected static enum EventMode {START, END};
;

static Map<String, NonInterruptiveEvent> events = new HashMap<>();
/**
* Returns the declared event. if declared on the method. The declarations have the following precedence:
* <ol>
* <li>Declaration in @PhaseEvent</li>
* <li>Declaration in @PhasedTest</li>
* <li>When the property PHASED.EVENTS.NONINTERRUPTIVE is set</li>
* </ol>Null is returned if no such declaration is present.
*
* @param in_scenario The class/scenario we are examining
* @return The event that is declared on the method. Null if there is no event declared for the method
*/
public static Class fetchApplicableEvent(Class in_scenario) {
Method l_foundMethod = Arrays.stream(in_scenario.getMethods()).filter(m -> fetchApplicableEvent(m) != null)
.findFirst().orElse(null);

if (l_foundMethod == null) {
return null;
}

try {
Class<?> lr_eventClass = Class.forName(fetchApplicableEvent(l_foundMethod));

private static List<PhasedEventLogEntry> eventLogs = new ArrayList();
if (!NonInterruptiveEvent.class.isAssignableFrom(lr_eventClass)) {
throw new PhasedTestConfigurationException(
"The given event " + lr_eventClass.getTypeName() + " should extend the abstract class "
+ NonInterruptiveEvent.class.getTypeName() + ".");
}

return lr_eventClass;
} catch (ClassNotFoundException e) {
throw new PhasedTestConfigurationException(
"The given event " + fetchApplicableEvent(l_foundMethod) + " could not be found.", e);
}
}

/**
* Used for logging events
Expand All @@ -112,19 +146,39 @@ protected static NonInterruptiveEvent startEvent(String in_event, String in_onAc
if (eventExecutor == null) {
eventExecutor = Executors.newSingleThreadExecutor();
}
log.info("Starting event {} for step {}.",in_event,in_onAccountOfStep);
log.info("Starting event {} for step {}.", in_event, in_onAccountOfStep);
NonInterruptiveEvent nie = instantiateClassFromString(in_event);
logEvent(EventMode.START, in_event, in_onAccountOfStep);
events.put(in_onAccountOfStep, nie);
eventExecutor.submit(nie);
nie.threadFuture = eventExecutor.submit(nie);

//Check that the vent really starts
while (nie.getState().equals(NonInterruptiveEvent.states.DEFINED)) {
try {
//log.debug("Waiting for event to start");
Thread.sleep(1);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new RuntimeException(e);
throw new PhasedTestingEventException("Un expected exception at startup",e);
}
}

//Check if the event had no issues
if (nie.getState().equals(NonInterruptiveEvent.states.FAILURE)) {
log.error("Event Exception : The event {} for step {} caused an exception during start.", in_event, in_onAccountOfStep);
try {
nie.threadFuture.get();
} catch (InterruptedException | ExecutionException ex) {
ex.getCause().printStackTrace();
nie.threadFuture.cancel(true);
}
}

//NON_INTERRUPTIVE 23
if (Phases.NON_INTERRUPTIVE.fetchType().startsWith("2")) {
log.info("Forcing Event End {} BEFORE step {} has started.", in_event, in_onAccountOfStep);
performWaitTilFinish(in_event, in_onAccountOfStep, nie);
}
return nie;
}

Expand All @@ -135,14 +189,17 @@ private static NonInterruptiveEvent instantiateClassFromString(String in_event)
Class<?> eventClass = Class.forName(in_event);

if (!NonInterruptiveEvent.class.isAssignableFrom(eventClass)) {
throw new PhasedTestConfigurationException("The given event "+in_event+ " should be a sub-class of the abstract class "+NonInterruptiveEvent.class.getTypeName()+".");
throw new PhasedTestConfigurationException(
"The given event " + in_event + " should be a sub-class of the abstract class "
+ NonInterruptiveEvent.class.getTypeName() + ".");
}
nie = (NonInterruptiveEvent) eventClass.newInstance();
} catch (IllegalAccessException | InstantiationException e) {

throw new PhasedTestConfigurationException("We have had a problem instantiating the event "+in_event+".", e);
throw new PhasedTestConfigurationException(
"We have had a problem instantiating the event " + in_event + ".", e);
} catch (ClassNotFoundException e) {
throw new PhasedTestConfigurationException("The given event class "+in_event+" could not be found.", e);
throw new PhasedTestConfigurationException("The given event class " + in_event + " could not be found.", e);
}
return nie;
}
Expand All @@ -158,24 +215,62 @@ protected static NonInterruptiveEvent finishEvent(String in_event, String in_onA
log.info("Finishing event {} for step {}.", in_event, in_onAccountOfStep);
NonInterruptiveEvent l_activeEvent = events.get(in_onAccountOfStep);
if (l_activeEvent == null) {
throw new PhasedTestException("No event of the type "+in_event+" was stored for the test step "+in_onAccountOfStep);
throw new PhasedTestException(
"No event of the type " + in_event + " was stored for the test step " + in_onAccountOfStep);
}

try {
if (Class.forName(in_event) != l_activeEvent.getClass()) {
throw new PhasedTestException("The given class "+in_event+" does not exist.");
throw new PhasedTestException("The given class " + in_event + " does not exist.");
}
} catch (ClassNotFoundException e) {
throw new PhasedTestConfigurationException("Class "+in_event+" not found.",e);
throw new PhasedTestConfigurationException("Class " + in_event + " not found.", e);
}

//if (Phases.NON_INTERRUPTIVE.fetchType().startsWith("3")) {
// log.info("Forcing Event End {} AFTER step {} has finished.", in_event, in_onAccountOfStep);
performWaitTilFinish(in_event, in_onAccountOfStep, l_activeEvent);
//}

if (!l_activeEvent.isFinished()) {
throw new PhasedTestingEventException("This event did not finish as expected.");
}

l_activeEvent.waitTillFinished();
l_activeEvent.state = NonInterruptiveEvent.states.FINISHED;
log.info("Event {} for step {} has finished.", in_event, in_onAccountOfStep);

if (!l_activeEvent.threadFuture.isDone()) {
log.error("The event {} for step {} did not finish as expected. Cancelling the event.", in_event, in_onAccountOfStep);
l_activeEvent.threadFuture.cancel(true);
}

logEvent(EventMode.END, in_event, in_onAccountOfStep);
l_activeEvent.tearDownEvent();
performTearDown(in_event, in_onAccountOfStep, l_activeEvent);
return l_activeEvent;
}

private static void performWaitTilFinish(String in_event, String in_onAccountOfStep, NonInterruptiveEvent nie) {
try {
nie.waitTillFinished();
} catch (Exception e) {
log.error("The waitTillFinished method for event {} caused an exception in the context of step {}.",
in_event, in_onAccountOfStep);
e.printStackTrace();
nie.threadFuture.cancel(true);
}
}

private static void performTearDown(String in_event, String in_onAccountOfStep, NonInterruptiveEvent l_activeEvent) {
try {
l_activeEvent.tearDownEvent();
} catch (Exception e) {
log.error("The tearDownEvent method for event {} caused an exception of type {} in the context of step {}.",
in_event, e.getCause(), in_onAccountOfStep);
e.printStackTrace();
l_activeEvent.threadFuture.cancel(true);
}
}

public static List<PhasedEventLogEntry> getEventLogs() {
return eventLogs;
}
Expand All @@ -194,6 +289,7 @@ public static Map<String, NonInterruptiveEvent> getEvents() {

/**
* Extracts the event for a given method. The choice of the event is based on where the event is declared.
*
* @param in_testResult A result object for a test containing the annotation {@link PhaseEvent}
* @return An event that can be executed with this method. Null if no event is applicable
*/
Expand All @@ -218,9 +314,10 @@ public static String fetchEvent(ITestResult in_testResult) {

int l_currentShuffleGroupNr = PhasedTestManager.asynchronousExtractIndex(in_testResult);

int l_currentStep = PhasedTestManager.getMethodMap().get(ClassPathParser.fetchFullName(l_currentMethod)).methodOrderInExecution;
int l_currentStep = PhasedTestManager.getMethodMap()
.get(ClassPathParser.fetchFullName(l_currentMethod)).methodOrderInExecution;

if (l_currentStep == l_currentShuffleGroupNr) {
if (l_currentStep == l_currentShuffleGroupNr) {
return fetchApplicableEvent(l_currentMethod);
}
return null;
Expand All @@ -232,9 +329,11 @@ public static ExecutorService getEventExecutor() {
}

public static void stopEventExecutor() {
if (eventExecutor != null)
if (eventExecutor != null) {
eventExecutor.shutdown();
}
}

protected static enum EventMode {START, END}

}
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ private PhasedTestManager() {
static final String STD_PHASED_GROUP_PREFIX = "phased-shuffledGroup_";
static final String STD_PHASED_GROUP_SINGLE = "phased-singleRun";

static final String STD_PHASED_GROUP_NIE_PREFIX = "phased-shuffledGroupNIE_";
static final String STD_PHASED_GROUP_NIE_PREFIX = "ni_event_";

public static final String STD_MERGE_STEP_ERROR_PREFIX = "Phased Error: Failure in step ";

Expand Down Expand Up @@ -553,7 +553,10 @@ public static Object[][] fetchProvidersShuffled(Method in_method, Phases in_phas
+ "_"
+ lt_nrAfterPhase;
} else {
l_objectArrayPhased[rows][0] = STD_PHASED_GROUP_NIE_PREFIX + (rows+1);
Class<NonInterruptiveEvent> l_event = PhasedEventManager.fetchApplicableEvent(
in_method.getDeclaringClass());

l_objectArrayPhased[rows][0] = STD_PHASED_GROUP_NIE_PREFIX + (l_event != null ? l_event.getSimpleName()+"_" : "" ) + (rows+1);
}
}

Expand Down Expand Up @@ -593,7 +596,11 @@ public static Object[] fetchProvidersSingle(Method in_method) {
}

if (Phases.ASYNCHRONOUS.isSelected()) {
return new Object[] { STD_PHASED_GROUP_SINGLE };
Class<NonInterruptiveEvent> l_event = PhasedEventManager.fetchApplicableEvent(
in_method.getDeclaringClass());

return new Object[] { l_event == null ? STD_PHASED_GROUP_SINGLE :
PhasedTestManager.STD_PHASED_GROUP_NIE_PREFIX + l_event.getSimpleName() };
}

return new Object[] {};
Expand Down
Loading
Loading