/*
* *************************************************************************************
* Copyright (C) 2008 EsperTech, Inc. All rights reserved. *
* http://esper.codehaus.org *
* http://www.espertech.com *
* ---------------------------------------------------------------------------------- *
* The software in this package is published under the terms of the GPL license *
* a copy of which has been included with this distribution in the license.txt file. *
* *************************************************************************************
*/
package com.espertech.esper.regression.support;
import com.espertech.esper.client.*;
import com.espertech.esper.client.scopetest.SupportUpdateListener;
import com.espertech.esper.client.soda.EPStatementObjectModel;
import com.espertech.esper.client.time.CurrentTimeEvent;
import com.espertech.esper.client.time.TimerEvent;
import com.espertech.esper.event.EventBeanUtility;
import com.espertech.esper.support.bean.*;
import com.espertech.esper.support.client.SupportConfigFactory;
import junit.framework.Assert;
import junit.framework.TestCase;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.Map;
/**
* Test harness for testing expressions and comparing received MatchedEventMap instances against against expected results.
*/
public class PatternTestHarness implements SupportBeanConstants
{
private final EventCollection sendEventCollection;
private final CaseList caseList;
// Array of expressions and match listeners for listening to events for each test descriptor
private EPStatement expressions[];
private SupportUpdateListener listeners[];
public PatternTestHarness( EventCollection sendEventCollection,
CaseList caseList)
{
this.sendEventCollection = sendEventCollection;
this.caseList = caseList;
// Create a listener for each test descriptor
this.listeners = new SupportUpdateListener[caseList.getNumTests()];
for (int i = 0; i < listeners.length; i++)
{
listeners[i] = new SupportUpdateListener();
}
expressions = new EPStatement[listeners.length];
}
public void runTest() throws Exception
{
runTest(PatternTestStyle.USE_PATTERN_LANGUAGE);
runTest(PatternTestStyle.USE_EPL);
runTest(PatternTestStyle.COMPILE_TO_MODEL);
runTest(PatternTestStyle.COMPILE_TO_EPL);
}
private void runTest(PatternTestStyle testStyle) throws Exception
{
Configuration config = SupportConfigFactory.getConfiguration();
config.addEventType("A", SupportBean_A.class);
config.addEventType("B", SupportBean_B.class);
config.addEventType("C", SupportBean_C.class);
config.addEventType("D", SupportBean_D.class);
config.addEventType("E", SupportBean_E.class);
config.addEventType("F", SupportBean_F.class);
config.addEventType("G", SupportBean_G.class);
EPServiceProvider serviceProvider = EPServiceProviderManager.getDefaultProvider(config);
serviceProvider.initialize();
EPRuntime runtime = serviceProvider.getEPRuntime();
// Send the start time to the runtime
if (sendEventCollection.getTime(EventCollection.ON_START_EVENT_ID) != null)
{
TimerEvent startTime = new CurrentTimeEvent(sendEventCollection.getTime(EventCollection.ON_START_EVENT_ID));
runtime.sendEvent(startTime);
log.debug(".runTest Start time is " + startTime);
}
// Set up expression filters and match listeners
int index = 0;
for (EventExpressionCase descriptor : caseList.getResults())
{
String expressionText = descriptor.getExpressionText();
EPStatementObjectModel model = descriptor.getObjectModel();
EPStatement statement = null;
try
{
if (model != null)
{
statement = serviceProvider.getEPAdministrator().create(model, "name--" + expressionText);
}
else
{
if (testStyle == PatternTestStyle.USE_PATTERN_LANGUAGE)
{
statement = serviceProvider.getEPAdministrator().createPattern(expressionText, "name--" + expressionText);
}
else if (testStyle == PatternTestStyle.USE_EPL)
{
String text = "@Audit('pattern') @Audit('pattern-instances') select * from pattern [" + expressionText + "]";
statement = serviceProvider.getEPAdministrator().createEPL(text);
expressionText = text;
}
else if (testStyle == PatternTestStyle.COMPILE_TO_MODEL)
{
String text = "select * from pattern [" + expressionText + "]";
EPStatementObjectModel mymodel = serviceProvider.getEPAdministrator().compileEPL(text);
statement = serviceProvider.getEPAdministrator().create(mymodel);
expressionText = text;
}
else if (testStyle == PatternTestStyle.COMPILE_TO_EPL)
{
String text = "select * from pattern [" + expressionText + "]";
EPStatementObjectModel mymodel = serviceProvider.getEPAdministrator().compileEPL(text);
String reverse = mymodel.toEPL();
statement = serviceProvider.getEPAdministrator().createEPL(reverse);
expressionText = reverse;
}
else
{
throw new IllegalArgumentException("Unknown test style");
}
}
}
catch (Exception ex)
{
String text = expressionText;
if (model != null)
{
text = "Model: " + model.toEPL();
}
log.fatal(".runTest Failed to create statement for style " + testStyle + " pattern expression=" + text, ex);
TestCase.fail();
}
// We stop the statement again and start after the first listener was added.
// Thus we can handle patterns that fireStatementStopped on startup.
statement.stop();
expressions[index] = statement;
expressions[index].addListener(listeners[index]);
// Start the statement again: listeners now got called for on-start events such as for a "not"
statement.start();
index++;
}
// Some expressions may fireStatementStopped as soon as they are started, such as a "not b()" expression, for example.
// Check results for any such listeners/expressions.
// NOTE: For EPL statements we do not support calling listeners when a pattern that fires upon start.
// Reason is that this should not be a relevant functionality of a pattern, the start pattern
// event itself cannot carry any information and is thus ignore. Note subsequent events
// generated by the same pattern are fine.
int totalEventsReceived = 0;
if (testStyle != PatternTestStyle.USE_PATTERN_LANGUAGE)
{
clearListenerEvents();
totalEventsReceived += countExpectedEvents(EventCollection.ON_START_EVENT_ID);
}
else // Patterns do need to handle event publishing upon pattern expression start (patterns that turn true right away)
{
checkResults(testStyle, EventCollection.ON_START_EVENT_ID);
totalEventsReceived += countListenerEvents();
clearListenerEvents();
}
// Send actual test events
for (Map.Entry<String, Object> entry : sendEventCollection.entrySet())
{
String eventId = entry.getKey();
// Manipulate the time when this event was send
if (sendEventCollection.getTime(eventId) != null)
{
TimerEvent currentTimeEvent = new CurrentTimeEvent(sendEventCollection.getTime(eventId));
runtime.sendEvent(currentTimeEvent);
log.debug(".runTest Sending event " + entry.getKey()
+ " = " + entry.getValue() +
" timed " + currentTimeEvent);
}
// Send event itself
runtime.sendEvent(entry.getValue());
// Check expected results for this event
checkResults(testStyle, eventId);
// Count and clear the list of events that each listener has received
totalEventsReceived += countListenerEvents();
clearListenerEvents();
}
// Count number of expected matches
int totalExpected = 0;
for (EventExpressionCase descriptor : caseList.getResults())
{
for (LinkedList<EventDescriptor> events : descriptor.getExpectedResults().values())
{
totalExpected += events.size();
}
}
if (totalExpected != totalEventsReceived)
{
log.debug(".test Count expected does not match count received, expected=" + totalExpected +
" received=" + totalEventsReceived);
TestCase.assertTrue(false);
}
// Kill all expressions
for (EPStatement expression : expressions)
{
expression.removeAllListeners();
}
// Send test events again to also test that all were indeed killed
for (Map.Entry<String, Object> entry : sendEventCollection.entrySet())
{
runtime.sendEvent(entry.getValue());
}
// Make sure all listeners are still at zero
for (SupportUpdateListener listener : listeners)
{
if (listener.getNewDataList().size() > 0)
{
log.debug(".test A match was received after stopping all expressions");
TestCase.assertTrue(false);
}
}
}
private void checkResults(PatternTestStyle testStyle, String eventId)
{
// For each test descriptor, make sure the listener has received exactly the events expected
int index = 0;
log.debug(".checkResults Checking results for event " + eventId);
for (EventExpressionCase descriptor : caseList.getResults())
{
String expressionText = expressions[index].getText();
LinkedHashMap<String, LinkedList<EventDescriptor>> allExpectedResults = descriptor.getExpectedResults();
EventBean[] receivedResults = listeners[index].getLastNewData();
index++;
// If nothing at all was expected for this event, make sure nothing was received
if (!(allExpectedResults.containsKey(eventId)))
{
if ((receivedResults != null) && (receivedResults.length > 0))
{
log.debug(".checkResults Incorrect result for style " + testStyle + " expression : " + expressionText);
log.debug(".checkResults Expected no results for event " + eventId + ", but received " + receivedResults.length + " events");
log.debug(".checkResults Received, have " + receivedResults.length + " entries");
printList(receivedResults);
TestCase.assertFalse(true);
}
continue;
}
LinkedList<EventDescriptor> expectedResults = allExpectedResults.get(eventId);
// Compare the result lists, not caring about the order of the elements
try {
if (!(compareLists(receivedResults, expectedResults)))
{
log.debug(".checkResults Incorrect result for style " + testStyle + " expression : " + expressionText);
log.debug(".checkResults Expected size=" + expectedResults.size() + " received size=" + (receivedResults == null ? 0 : receivedResults.length));
log.debug(".checkResults Expected, have " + expectedResults.size() + " entries");
printList(expectedResults);
log.debug(".checkResults Received, have " + (receivedResults == null ? 0 : receivedResults.length) + " entries");
printList(receivedResults);
TestCase.assertFalse(true);
}
}
catch (Exception ex) {
ex.printStackTrace();
Assert.fail("For statement '" + expressionText + "' failed to assert: " + ex.getMessage());
}
}
}
private boolean compareLists(EventBean[] receivedResults,
LinkedList<EventDescriptor> expectedResults)
{
int receivedSize = (receivedResults == null) ? 0 : receivedResults.length;
if (expectedResults.size() != receivedSize)
{
return false;
}
// To make sure all received events have been expected
LinkedList<EventDescriptor> expectedResultsClone = new LinkedList<EventDescriptor>(expectedResults);
// Go through the list of expected results and remove from received result list if found
for (EventDescriptor desc : expectedResults)
{
EventDescriptor foundMatch = null;
for (EventBean received : receivedResults)
{
if (compareEvents(desc, received))
{
foundMatch = desc;
break;
}
}
// No match between expected and received
if (foundMatch == null)
{
return false;
}
expectedResultsClone.remove(foundMatch);
}
// Any left over received results also invalidate the test
if (expectedResultsClone.size() > 0)
{
return false;
}
return true;
}
private static boolean compareEvents(EventDescriptor eventDesc, EventBean eventBean)
{
for (Map.Entry<String, Object> entry : eventDesc.getEventProperties().entrySet())
{
Object left = eventBean.get(entry.getKey());
Object right = entry.getValue();
if (left != right)
{
return false;
}
}
return true;
}
/**
* Clear the event list of all listeners
*/
private void clearListenerEvents()
{
for (SupportUpdateListener listener : listeners)
{
listener.reset();
}
}
/**
* Clear the event list of all listeners
*/
private int countListenerEvents()
{
int count = 0;
for (SupportUpdateListener listener : listeners)
{
for (EventBean[] events : listener.getNewDataList())
{
count += events.length;
}
}
return count;
}
private void printList(LinkedList<EventDescriptor> events)
{
int index = 0;
for (EventDescriptor desc : events)
{
StringBuffer buffer = new StringBuffer();
int count = 0;
for (Map.Entry<String, Object> entry : desc.getEventProperties().entrySet())
{
buffer.append(" (" + (count++) + ") ");
buffer.append("tag=" + entry.getKey());
String id = findValue(entry.getValue());
buffer.append(" eventId=" + id);
}
log.debug(".printList (" + index + ") : " + buffer.toString());
index++;
}
}
private void printList(EventBean[] events)
{
if (events == null)
{
log.debug(".printList : null-value events array");
return;
}
log.debug(".printList : " + events.length + " elements...");
for (int i = 0; i < events.length; i++)
{
log.debug(" " + EventBeanUtility.printEvent(events[i]));
}
}
/**
* Find the value object in the map of object names and values
*/
private String findValue(Object value)
{
for (Map.Entry<String, Object> entry : sendEventCollection.entrySet())
{
if (value == entry.getValue())
{
return entry.getKey();
}
}
return null;
}
private int countExpectedEvents(String eventId)
{
int result = 0;
for (EventExpressionCase descriptor : caseList.getResults())
{
LinkedHashMap<String, LinkedList<EventDescriptor>> allExpectedResults = descriptor.getExpectedResults();
// If nothing at all was expected for this event, make sure nothing was received
if (allExpectedResults.containsKey(eventId))
{
result++;
}
}
return result;
}
private enum PatternTestStyle
{
USE_PATTERN_LANGUAGE,
USE_EPL,
COMPILE_TO_MODEL,
COMPILE_TO_EPL;
}
private static final Log log = LogFactory.getLog(PatternTestHarness.class);
}