/*
* Copyright 2012 Nodeable Inc
*
* 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.streamreduce.storm.bolts;
import backtype.storm.tuple.Tuple;
import backtype.storm.tuple.TupleImpl;
import backtype.storm.tuple.Values;
import com.mongodb.BasicDBList;
import com.mongodb.BasicDBObject;
import com.mongodb.util.JSON;
import com.streamreduce.Constants;
import com.streamreduce.analytics.MetricName;
import com.streamreduce.storm.JuggaloaderStreamState;
import com.streamreduce.util.JSONUtils;
import org.joda.time.DateTime;
import org.junit.Assert;
import org.junit.Test;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import static org.junit.Assert.assertNotNull;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
/**
* Tests the behavior of the JuggaloaderTimeBase is consistent with an external model.
*
* <p>Author: Nick Heudecker</p>
* <p>Created: 6/26/12 1:26 PM</p>
*/
public class JuggaloaderTimeBaseTest {
private static final double TOLERANCE = 0.0001;
private static DateTime dateTime = new DateTime();
@Test
public void testProcess_function0() throws Exception {
testProcess("function0");
}
@Test
public void testProcess_function1() throws Exception {
testProcess("function1");
}
@Test
public void testProcess_function2() throws Exception {
testProcess("function2");
}
@Test
public void testProcess_function3() throws Exception {
testProcess("function3");
}
@Test
public void testProcess_function4() throws Exception {
testProcess("function4");
}
@Test
public void testProcess_function5() throws Exception {
testProcess("function5");
}
@Test
public void testProcess_function6() throws Exception {
testProcess("function6");
}
@Test
public void testProcess_function7() throws Exception {
testProcess("function7");
}
/*
@Test
public void testProcess_function8() throws Exception {
testProcess("function8");
}
*/
private void testProcess(String testFunction) throws Exception {
BasicDBList testData = getTestData(testFunction);
Iterator<Object> iter = testData.iterator();
Map<String, JuggaloaderStreamState> states = new HashMap<>();
Values previousValues = null;
for (int i = 0; iter.hasNext(); i++) {
BasicDBObject dbObject = (BasicDBObject) iter.next();
dbObject.put("testFunction", testFunction);
Tuple tuple = createTuple(dbObject);
Values values = JuggaloaderTimeBase.process(tuple, states, Constants.PERIOD_MINUTE, null);
assertNotNull(values);
if (i > JuggaloaderTimeBase.W) {
if (dbObject.getString("isanomaly").equals("A") && !((Boolean)values.get(13))) {
fail(testFunction, true, dbObject, getStreamState(states), values, previousValues);
}
/* check for a false positive */
else if (!dbObject.getString("isanomaly").equals("A") && ((Boolean)values.get(13))) {
fail(testFunction, false, dbObject, getStreamState(states), values, previousValues);
}
else {
/* set anomalyReset on the state object to 0 to avoid the SNOOZE value. */
getStreamState(states).anomalyReset = 0;
previousValues = values;
}
}
/* test that difference between numerical values in input and calculated by JL are within tolerance */
/*
if (Math.abs(Double.parseDouble(dbObject.getString("y")) - (Float)values.get(4)) > TOLERANCE) {
Assert.fail("Tolerance check: " + dbObject + ", " + values);
}
if (Math.abs(Double.parseDouble(dbObject.getString("mean-d")) - (Float)values.get(8)) > TOLERANCE) {
Assert.fail("Tolerance check: " + dbObject + ", " + values);
}
if (Math.abs(Double.parseDouble(dbObject.getString("stddev-d")) - (Float)values.get(9)) > TOLERANCE) {
Assert.fail("Tolerance check: " + dbObject + ", " + values);
}
*/
}
}
private Tuple createTuple(BasicDBObject dbObject) {
Map<String,String> metricCriteria = new HashMap<>();
metricCriteria.put("one", "1");
metricCriteria.put("two", "2");
Tuple tuple = mock(TupleImpl.class);
when(tuple.getStringByField("metricAccount")).thenReturn("JuggaloaderTimeBaseTest-"+dbObject.getString("testFunction"));
when(tuple.getStringByField("metricName")).thenReturn(MetricName.TEST_STREAM.toString());
when(tuple.getStringByField("metricType")).thenReturn("ABSOLUTE");
when(tuple.getLongByField("metricTimestamp")).thenReturn(getMetricTimestamp());
when(tuple.getFloatByField("metricValue")).thenReturn(Float.parseFloat(dbObject.getString("y")));
when(tuple.getValueByField("metaData")).thenReturn(new HashMap<String,Object>());
when(tuple.getValueByField("metricCriteria")).thenReturn(metricCriteria);
return tuple;
}
private static long getMetricTimestamp() {
DateTime dt = dateTime.plusMinutes(1);
long ts = dt.getMillis();
dateTime = dt;
return ts;
}
private BasicDBList getTestData(String testFunction) throws Exception {
BasicDBList dbList = (BasicDBList) JSON.parse(
JSONUtils.readJSONFromClasspath(String.format("/com/streamreduce/storm/bolts/JuggaloaderTimeBaseTest-%s.json", testFunction))
);
assert dbList != null;
return dbList;
}
private JuggaloaderStreamState getStreamState(Map<String,JuggaloaderStreamState> states) {
for (Map.Entry<String,JuggaloaderStreamState> entry : states.entrySet()) {
return entry.getValue();
}
return null;
}
private void fail(String functionTest, boolean failedToDetect, BasicDBObject input, JuggaloaderStreamState state, Values values, Values previousValues) {
String label = failedToDetect ? "Juggaloader failed to detect an anomaly for a test row" : "Juggaloader falsely detected an anomaly for a test row";
String template = "%s - %s\n[Test Data] row: %s, y: %s, mean: %s, stddev-d: %s, isanomaly: %s\n" +
"[State Data] yAvgLast: %s, yStdDevLast: %s, tslast: %s\n" +
"[Values Data] y: %s, avgy: %s, stddev: %s, diff: %s, state.min: %s, state.max: %s, anomaly: %s\n"+
"[Previous Values Data] y: %s, avgy: %s, stddev: %s, diff: %s, state.min: %s, state.max: %s, anomaly: %s\n";
Assert.fail(String.format(template, label, functionTest,
input.getString("x"), input.getString("y"), input.getString("mean-d"), input.getString("stddev-d"), input.getString("isanomaly"),
state.yAvgLast, state.yStdDevLast, state.tslast,
values.get(4), values.get(8), values.get(9), values.get(10), values.get(11), values.get(12), values.get(13),
previousValues.get(4), previousValues.get(8), previousValues.get(9), previousValues.get(10), previousValues.get(11), previousValues.get(12), previousValues.get(13)
));
}
}