// This file is part of OpenTSDB.
// Copyright (C) 2015 The OpenTSDB Authors.
//
// This program is free software: you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 2.1 of the License, or (at your
// option) any later version. This program is distributed in the hope that it
// will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
// General Public License for more details. You should have received a copy
// of the GNU Lesser General Public License along with this program. If not,
// see <http://www.gnu.org/licenses/>.
package net.opentsdb.query.expression;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import java.util.ArrayList;
import java.util.List;
import net.opentsdb.core.DataPoints;
import net.opentsdb.core.IllegalDataException;
import net.opentsdb.core.SeekableView;
import net.opentsdb.core.SeekableViewsForTest;
import net.opentsdb.core.TSQuery;
import net.opentsdb.query.expression.ExpressionTree.Parameter;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PowerMockIgnore;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
@RunWith(PowerMockRunner.class)
@PowerMockIgnore({"javax.management.*", "javax.xml.*",
"ch.qos.*", "org.slf4j.*",
"com.sum.*", "org.xml.*"})
@PrepareForTest({ TSQuery.class })
public class TestExpressionTree {
private final static String EXPR_NAME = "treeTestExpr";
private static long START_TIME = 1356998400000L;
private static int INTERVAL = 60000;
private static int NUM_POINTS = 5;
private static String METRIC = "sys.cpu";
private TSQuery data_query;
private SeekableView view;
private DataPoints dps;
private DataPoints[] group_bys;
private List<DataPoints[]> query_results;
private TreeTestExpr test_expression;
@Before
public void before() throws Exception {
view = SeekableViewsForTest.generator(START_TIME, INTERVAL,
NUM_POINTS, true, 1, 1);
data_query = mock(TSQuery.class);
when(data_query.startTime()).thenReturn(START_TIME);
when(data_query.endTime()).thenReturn(START_TIME + (INTERVAL * NUM_POINTS));
dps = PowerMockito.mock(DataPoints.class);
when(dps.iterator()).thenReturn(view);
when(dps.metricName()).thenReturn(METRIC);
group_bys = new DataPoints[] { dps };
query_results = new ArrayList<DataPoints[]>(1);
query_results.add(group_bys);
test_expression = new TreeTestExpr();
ExpressionFactory.addFunction(EXPR_NAME, test_expression);
}
@Test
public void ctorString() throws Exception {
final ExpressionTree tree = new ExpressionTree(EXPR_NAME, data_query);
assertEquals(EXPR_NAME + "()", tree.toString());
assertNull(tree.subExpressions());
assertNull(tree.funcParams());
assertNull(tree.subMetricQueries());
assertTrue(tree.parameterIndex().isEmpty());
}
@Test (expected = UnsupportedOperationException.class)
public void ctorStringNull() throws Exception {
new ExpressionTree((String)null, data_query);
}
@Test (expected = UnsupportedOperationException.class)
public void ctorStringEmpty() throws Exception {
new ExpressionTree("", data_query);
}
@Test (expected = UnsupportedOperationException.class)
public void ctorStringUnknown() throws Exception {
new ExpressionTree("No such method", data_query);
}
@Test
public void ctorExpression() throws Exception {
final ExpressionTree tree = new ExpressionTree(test_expression, data_query);
assertEquals(EXPR_NAME + "()", tree.toString());
assertNull(tree.subExpressions());
assertNull(tree.funcParams());
assertNull(tree.subMetricQueries());
assertTrue(tree.parameterIndex().isEmpty());
}
@Test
public void addSubExpression() throws Exception {
final ExpressionTree tree = new ExpressionTree(EXPR_NAME, data_query);
final ExpressionTree child = new ExpressionTree("scale", data_query);
tree.addSubExpression(child, 1);
assertEquals(1, tree.subExpressions().size());
assertSame(child, tree.subExpressions().get(0));
assertNull(tree.funcParams());
assertNull(tree.subMetricQueries());
assertEquals(1, tree.parameterIndex().size());
assertEquals(Parameter.SUB_EXPRESSION, tree.parameterIndex().get(1));
}
@Test (expected = IllegalArgumentException.class)
public void addSubExpressionNullTree() throws Exception {
final ExpressionTree tree = new ExpressionTree(EXPR_NAME, data_query);
tree.addSubExpression(null, 1);
}
@Test (expected = IllegalArgumentException.class)
public void addSubExpressionNegativeIndex() throws Exception {
final ExpressionTree tree = new ExpressionTree(EXPR_NAME, data_query);
tree.addSubExpression(null, 1);
}
@Test (expected = IllegalDataException.class)
public void addSubExpressionRecursion() throws Exception {
final ExpressionTree tree = new ExpressionTree(EXPR_NAME, data_query);
tree.addSubExpression(tree, 1);
}
@Test
public void addSubMetricQuery() throws Exception {
final ExpressionTree tree = new ExpressionTree(EXPR_NAME, data_query);
tree.addSubMetricQuery(METRIC, 1, 1);
assertEquals(EXPR_NAME + "(" + METRIC + ")", tree.toString());
assertNull(tree.subExpressions());
assertNull(tree.funcParams());
assertEquals(1, tree.subMetricQueries().size());
assertEquals(METRIC, tree.subMetricQueries().get(1));
assertEquals(1, tree.parameterIndex().size());
assertEquals(Parameter.METRIC_QUERY, tree.parameterIndex().get(1));
}
@Test (expected = IllegalArgumentException.class)
public void addSubMetricQueryNullMetric() throws Exception {
final ExpressionTree tree = new ExpressionTree(EXPR_NAME, data_query);
tree.addSubMetricQuery(null, 1, 1);
}
@Test (expected = IllegalArgumentException.class)
public void addSubMetricQueryEmptyMetric() throws Exception {
final ExpressionTree tree = new ExpressionTree(EXPR_NAME, data_query);
tree.addSubMetricQuery("", 1, 1);
}
@Test (expected = IllegalArgumentException.class)
public void addSubMetricQueryNegativeQueryIndex() throws Exception {
final ExpressionTree tree = new ExpressionTree(EXPR_NAME, data_query);
tree.addSubMetricQuery(METRIC, -1, 1);
}
@Test (expected = IllegalArgumentException.class)
public void addSubMetricQueryNegativeParamIndex() throws Exception {
final ExpressionTree tree = new ExpressionTree(EXPR_NAME, data_query);
tree.addSubMetricQuery(METRIC, 1, -1);
}
@Test
public void addFunctionParameter() throws Exception {
final ExpressionTree tree = new ExpressionTree(EXPR_NAME, data_query);
tree.addFunctionParameter("vimes");
assertEquals(EXPR_NAME + "()", tree.toString());
assertNull(tree.subExpressions());
assertEquals(1, tree.funcParams().size());
assertEquals("vimes", tree.funcParams().get(0));
assertNull(tree.subMetricQueries());
assertTrue(tree.parameterIndex().isEmpty());
}
@Test (expected = IllegalArgumentException.class)
public void addFunctionParameterNull() throws Exception {
final ExpressionTree tree = new ExpressionTree(EXPR_NAME, data_query);
tree.addFunctionParameter(null);
}
@Test (expected = IllegalArgumentException.class)
public void addFunctionParameterEmpty() throws Exception {
final ExpressionTree tree = new ExpressionTree(EXPR_NAME, data_query);
tree.addFunctionParameter("");
}
@Test
public void evaluateNothingSet() throws Exception {
final ExpressionTree tree = new ExpressionTree(EXPR_NAME, data_query);
assertEquals(EXPR_NAME + "()", tree.toString());
final DataPoints[] response = tree.evaluate(query_results);
assertEquals(1, response.length);
assertSame(data_query, test_expression.data_query);
assertEquals(0, test_expression.results.size());
assertNull(test_expression.params);
}
@Test
public void evaluateSubMetricQuerySet() throws Exception {
final ExpressionTree tree = new ExpressionTree(EXPR_NAME, data_query);
tree.addSubMetricQuery(METRIC, 0, 0);
assertEquals(EXPR_NAME + "(" + METRIC + ")", tree.toString());
final DataPoints[] response = tree.evaluate(query_results);
assertEquals(1, response.length);
assertSame(data_query, test_expression.data_query);
assertEquals(1, test_expression.results.size());
assertNull(test_expression.params);
}
@Test
public void evaluateSubMetricQuerySetWithParam() throws Exception {
final ExpressionTree tree = new ExpressionTree(EXPR_NAME, data_query);
tree.addSubMetricQuery(METRIC, 0, 0);
tree.addFunctionParameter("foo");
assertEquals(EXPR_NAME + "(" + METRIC + ")", tree.toString());
final DataPoints[] response = tree.evaluate(query_results);
assertEquals(1, response.length);
assertSame(data_query, test_expression.data_query);
assertEquals(1, test_expression.results.size());
assertEquals(1, test_expression.params.size());
assertEquals("foo", test_expression.params.get(0));
}
@Test
public void evaluateSubExpressionSet() throws Exception {
final ExpressionTree tree = new ExpressionTree(EXPR_NAME, data_query);
final ExpressionTree child = spy(new ExpressionTree("scale", data_query));
child.addSubMetricQuery(METRIC, 0, 0);
child.addFunctionParameter("1");
tree.addSubExpression(child, 0);
assertEquals(EXPR_NAME + "(scale(" + METRIC + "))", tree.toString());
final DataPoints[] response = tree.evaluate(query_results);
assertEquals(1, response.length);
assertSame(data_query, test_expression.data_query);
assertEquals(1, test_expression.results.size());
assertNull(test_expression.params);
verify(child, times(1)).evaluate(query_results);
}
// TODO - fix this up
// @Test
// public void evaluateSubExpressionAndSubMetricSet() throws Exception {
// final ExpressionTree tree = new ExpressionTree(EXPR_NAME, data_query);
// final ExpressionTree child = spy(new ExpressionTree("scale", data_query));
// child.addSubMetricQuery(METRIC, 0, 0);
// child.addFunctionParameter("1");
// tree.addSubExpression(child, 0);
//
// tree.addSubMetricQuery(METRIC, 1, 0);
// tree.addFunctionParameter("foo");
//
// assertEquals(EXPR_NAME + "(scale(" + METRIC + ")," + METRIC + ")",
// tree.toString());
//
// final DataPoints[] response = tree.evaluate(query_results);
// assertEquals(1, response.length);
// assertSame(data_query, test_expression.data_query);
// assertEquals(1, test_expression.results.size());
// assertEquals(1, test_expression.params.size());
// assertEquals("foo", test_expression.params.get(0));
// verify(child, times(1)).evaluate(query_results);
// }
// TODO - more tests around indexes, etc unless we cleanup the class
private class TreeTestExpr implements Expression {
TSQuery data_query;
List<DataPoints[]> results;
List<String> params;
@Override
public DataPoints[] evaluate(TSQuery data_query,
List<DataPoints[]> results, List<String> params) {
this.data_query = data_query;
this.results = results;
this.params = params;
// Returns an array the size of the total incoming results array
int num_results = 0;
for (DataPoints[] r: query_results) {
num_results += r.length;
}
final DataPoints[] response = new DataPoints[num_results];
return response;
}
@Override
public String writeStringField(List<String> params,
String inner_expression) {
return EXPR_NAME + "(" + inner_expression + ")";
}
}
}