// ================================================================================================= // Copyright 2011 Twitter, Inc. // ------------------------------------------------------------------------------------------------- // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this work except in compliance with the License. // You may obtain a copy of the License in the LICENSE file, or 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.twitter.common.net.http.handlers; import com.google.common.base.Function; import com.google.common.base.Joiner; import com.google.common.base.Predicate; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import com.google.common.collect.Sets; import com.google.visualization.datasource.base.DataSourceException; import com.google.visualization.datasource.base.InvalidQueryException; import com.google.visualization.datasource.datatable.ColumnDescription; import com.google.visualization.datasource.datatable.DataTable; import com.google.visualization.datasource.datatable.TableCell; import com.google.visualization.datasource.datatable.TableRow; import com.google.visualization.datasource.datatable.value.NumberValue; import com.google.visualization.datasource.query.Query; import com.google.visualization.datasource.query.parser.ParseException; import com.google.visualization.datasource.query.parser.QueryParser; import com.twitter.common.collections.Iterables2; import com.twitter.common.stats.TimeSeries; import com.twitter.common.stats.TimeSeriesRepository; import com.twitter.common.testing.EasyMockTest; import org.junit.Before; import org.junit.Test; import java.util.Arrays; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import static org.easymock.EasyMock.expect; import static org.hamcrest.CoreMatchers.is; import static org.junit.Assert.assertThat; /** * @author William Farner */ public class TimeSeriesDataSourceTest extends EasyMockTest { private static final String TIME_COLUMN = TimeSeriesDataSource.TIME_COLUMN; private static final String TIME_SERIES_1 = "time_series_1"; private static final String TIME_SERIES_2 = "time_series_2"; private static final List<Number> TIMESTAMPS = Arrays.<Number>asList(1d, 2d, 3d, 4d); private static final Map<String, TimeSeries> TS_DATA = ImmutableMap.of( TIME_SERIES_1, makeTimeSeries(TIME_SERIES_1, 1, 2, 3, 4), TIME_SERIES_2, makeTimeSeries(TIME_SERIES_2, 0, 0, 0, 0) ); private TimeSeriesDataSource dataSource; private TimeSeriesRepository timeSeriesRepo; @Before public void setUp() { timeSeriesRepo = createMock(TimeSeriesRepository.class); dataSource = new TimeSeriesDataSource(timeSeriesRepo); } @Test public void testGetColumns() throws Exception { expect(timeSeriesRepo.getAvailableSeries()).andReturn(TS_DATA.keySet()); control.replay(); DataTable data = fetch("SELECT * LIMIT 0"); assertThat(data.getNumberOfRows(), is(0)); assertThat(data.getNumberOfColumns(), is(TS_DATA.keySet().size())); Set<String> colNames = Sets.newHashSet(Iterables.transform(data.getColumnDescriptions(), new Function<ColumnDescription, String>() { @Override public String apply(ColumnDescription col) { return col.getId(); } })); assertThat(colNames, is(TS_DATA.keySet())); } @Test @SuppressWarnings("unchecked") // Needed because type information lost in vargs. public void testGetAllData() throws Exception { expect(timeSeriesRepo.getTimestamps()).andReturn(TIMESTAMPS); expect(timeSeriesRepo.get(TIME_SERIES_1)).andReturn(TS_DATA.get(TIME_SERIES_1)); expect(timeSeriesRepo.get(TIME_SERIES_2)).andReturn(TS_DATA.get(TIME_SERIES_2)); control.replay(); String colString = Joiner.on(',').join( Arrays.asList(TIME_SERIES_1, TIME_SERIES_2, TIME_COLUMN)); DataTable data = fetch("SELECT " + colString); assertThat(data.getNumberOfColumns(), is(TS_DATA.keySet().size() + 1)); assertThat(data.getNumberOfRows(), is(TIMESTAMPS.size())); Iterable<List<Number>> expectedData = Iterables2.zip(0, getSamples(TIME_SERIES_1), getSamples(TIME_SERIES_2), TIMESTAMPS); checkRows(data.getRows(), expectedData); } @Test @SuppressWarnings("unchecked") // Needed because type information lost in vargs. public void testFilterByTime() throws Exception { expect(timeSeriesRepo.getTimestamps()).andReturn(TIMESTAMPS); expect(timeSeriesRepo.get(TIME_SERIES_1)).andReturn(TS_DATA.get(TIME_SERIES_1)); expect(timeSeriesRepo.get(TIME_SERIES_2)).andReturn(TS_DATA.get(TIME_SERIES_2)); control.replay(); String colString = Joiner.on(',').join( Arrays.asList(TIME_SERIES_1, TIME_SERIES_2, TIME_COLUMN)); DataTable data = fetch("SELECT " + colString + " WHERE time >= 3"); assertThat(data.getNumberOfColumns(), is(TS_DATA.keySet().size() + 1)); Iterable<List<Number>> expectedData = Iterables2.zip(0, getSamples(TIME_SERIES_1), getSamples(TIME_SERIES_2), TIMESTAMPS); expectedData = Iterables.filter(expectedData, new Predicate<List<Number>>() { @Override public boolean apply(List<Number> row) { return row.get(2).intValue() >= 3; } }); assertThat(data.getNumberOfRows(), is(Iterables.size(expectedData))); checkRows(data.getRows(), expectedData); } private static void checkRows(List<TableRow> rows, Iterable<List<Number>> expectedValues) { Iterator<List<Number>> expectedValueIterator = expectedValues.iterator(); for (TableRow row : rows) { List<Number> rowValues = Lists.transform(row.getCells(), new Function<TableCell, Number>() { @Override public Number apply(TableCell cell) { assertThat(cell.getValue() instanceof NumberValue, is(true)); return ((NumberValue) cell.getValue()).getValue(); } }); assertThat(rowValues, is(expectedValueIterator.next())); } } private DataTable fetch(String query) throws DataSourceException, ParseException { return dataSource.generateDataTable(getQuery(query), null); } private static Query getQuery(String query) throws InvalidQueryException, ParseException { return QueryParser.parseString(query); } private static Iterable<Number> getSamples(String tsName) { return TS_DATA.get(tsName).getSamples(); } private static TimeSeries makeTimeSeries(final String name, final Number... values) { final List<Number> samples = Lists.newArrayList(); for (Number value : values) samples.add(value.doubleValue()); return new TimeSeries() { @Override public String getName() { return name; } @Override public Iterable<Number> getSamples() { return samples; } }; } }