diff options
Diffstat (limited to 'impl_core/src/test')
48 files changed, 8740 insertions, 0 deletions
diff --git a/impl_core/src/test/java/io/opencensus/implcore/internal/CurrentStateTest.java b/impl_core/src/test/java/io/opencensus/implcore/internal/CurrentStateTest.java new file mode 100644 index 00000000..b7e6a93a --- /dev/null +++ b/impl_core/src/test/java/io/opencensus/implcore/internal/CurrentStateTest.java @@ -0,0 +1,57 @@ +/* + * Copyright 2018, OpenCensus Authors + * + * 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 io.opencensus.implcore.internal; + +import static com.google.common.truth.Truth.assertThat; + +import io.opencensus.implcore.internal.CurrentState.State; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Tests for {@link CurrentState}. */ +@RunWith(JUnit4.class) +public final class CurrentStateTest { + + @Rule public final ExpectedException thrown = ExpectedException.none(); + + @Test + public void defaultState() { + assertThat(new CurrentState(State.ENABLED).get()).isEqualTo(State.ENABLED); + } + + @Test + public void setState() { + CurrentState currentState = new CurrentState(State.ENABLED); + assertThat(currentState.set(State.DISABLED)).isTrue(); + assertThat(currentState.getInternal()).isEqualTo(State.DISABLED); + assertThat(currentState.set(State.ENABLED)).isTrue(); + assertThat(currentState.getInternal()).isEqualTo(State.ENABLED); + assertThat(currentState.set(State.ENABLED)).isFalse(); + } + + @Test + public void preventSettingStateAfterReadingState() { + CurrentState currentState = new CurrentState(State.ENABLED); + currentState.get(); + thrown.expect(IllegalStateException.class); + thrown.expectMessage("State was already read, cannot set state."); + currentState.set(State.DISABLED); + } +} diff --git a/impl_core/src/test/java/io/opencensus/implcore/internal/TimestampConverterTest.java b/impl_core/src/test/java/io/opencensus/implcore/internal/TimestampConverterTest.java new file mode 100644 index 00000000..32a3e687 --- /dev/null +++ b/impl_core/src/test/java/io/opencensus/implcore/internal/TimestampConverterTest.java @@ -0,0 +1,51 @@ +/* + * Copyright 2017, OpenCensus Authors + * + * 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 io.opencensus.implcore.internal; + +import static com.google.common.truth.Truth.assertThat; +import static org.mockito.Mockito.when; + +import io.opencensus.common.Clock; +import io.opencensus.common.Timestamp; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +/** Unit tests for {@link TimestampConverter}. */ +@RunWith(JUnit4.class) +public class TimestampConverterTest { + private final Timestamp timestamp = Timestamp.create(1234, 5678); + @Mock private Clock mockClock; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + } + + @Test + public void convertNanoTime() { + when(mockClock.now()).thenReturn(timestamp); + when(mockClock.nowNanos()).thenReturn(1234L); + TimestampConverter timeConverter = TimestampConverter.now(mockClock); + assertThat(timeConverter.convertNanoTime(6234)).isEqualTo(Timestamp.create(1234, 10678)); + assertThat(timeConverter.convertNanoTime(1000)).isEqualTo(Timestamp.create(1234, 5444)); + assertThat(timeConverter.convertNanoTime(999995556)).isEqualTo(Timestamp.create(1235, 0)); + } +} diff --git a/impl_core/src/test/java/io/opencensus/implcore/internal/UtilsTest.java b/impl_core/src/test/java/io/opencensus/implcore/internal/UtilsTest.java new file mode 100644 index 00000000..2e0bde21 --- /dev/null +++ b/impl_core/src/test/java/io/opencensus/implcore/internal/UtilsTest.java @@ -0,0 +1,47 @@ +/* + * Copyright 2018, OpenCensus Authors + * + * 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 io.opencensus.implcore.internal; + +import java.util.Arrays; +import java.util.List; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Unit tests for {@link Utils}. */ +@RunWith(JUnit4.class) +public class UtilsTest { + @Rule public ExpectedException thrown = ExpectedException.none(); + + @Test + public void checkListElementNull() { + List<Double> list = Arrays.asList(0.0, 1.0, 2.0, null); + thrown.expect(NullPointerException.class); + thrown.expectMessage("null"); + Utils.checkListElementNotNull(list, null); + } + + @Test + public void checkListElementNull_WithMessage() { + List<Double> list = Arrays.asList(0.0, 1.0, 2.0, null); + thrown.expect(NullPointerException.class); + thrown.expectMessage("list should not be null."); + Utils.checkListElementNotNull(list, "list should not be null."); + } +} diff --git a/impl_core/src/test/java/io/opencensus/implcore/metrics/DerivedDoubleGaugeImplTest.java b/impl_core/src/test/java/io/opencensus/implcore/metrics/DerivedDoubleGaugeImplTest.java new file mode 100644 index 00000000..e69a284f --- /dev/null +++ b/impl_core/src/test/java/io/opencensus/implcore/metrics/DerivedDoubleGaugeImplTest.java @@ -0,0 +1,221 @@ +/* + * Copyright 2018, OpenCensus Authors + * + * 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 io.opencensus.implcore.metrics; + +import static com.google.common.truth.Truth.assertThat; + +import io.opencensus.common.Timestamp; +import io.opencensus.common.ToDoubleFunction; +import io.opencensus.metrics.LabelKey; +import io.opencensus.metrics.LabelValue; +import io.opencensus.metrics.export.Metric; +import io.opencensus.metrics.export.MetricDescriptor; +import io.opencensus.metrics.export.MetricDescriptor.Type; +import io.opencensus.metrics.export.Point; +import io.opencensus.metrics.export.TimeSeries; +import io.opencensus.metrics.export.Value; +import io.opencensus.testing.common.TestClock; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Unit tests for {@link DerivedDoubleGaugeImpl}. */ +@RunWith(JUnit4.class) +public class DerivedDoubleGaugeImplTest { + @Rule public ExpectedException thrown = ExpectedException.none(); + + private static final String METRIC_NAME = "name"; + private static final String METRIC_DESCRIPTION = "description"; + private static final String METRIC_UNIT = "1"; + private static final List<LabelKey> LABEL_KEY = + Collections.singletonList(LabelKey.create("key", "key description")); + private static final List<LabelValue> LABEL_VALUES = + Collections.singletonList(LabelValue.create("value")); + private static final List<LabelValue> LABEL_VALUES_1 = + Collections.singletonList(LabelValue.create("value1")); + private static final Timestamp TEST_TIME = Timestamp.create(1234, 123); + private final TestClock testClock = TestClock.create(TEST_TIME); + private static final MetricDescriptor METRIC_DESCRIPTOR = + MetricDescriptor.create( + METRIC_NAME, METRIC_DESCRIPTION, METRIC_UNIT, Type.GAUGE_DOUBLE, LABEL_KEY); + + private final DerivedDoubleGaugeImpl derivedDoubleGauge = + new DerivedDoubleGaugeImpl(METRIC_NAME, METRIC_DESCRIPTION, METRIC_UNIT, LABEL_KEY); + + // helper class + public static class QueueManager { + public double size() { + return 2.5; + } + } + + private static final ToDoubleFunction<Object> doubleFunction = + new ToDoubleFunction<Object>() { + @Override + public double applyAsDouble(Object value) { + return 5.5; + } + }; + private static final ToDoubleFunction<Object> negativeDoubleFunction = + new ToDoubleFunction<Object>() { + @Override + public double applyAsDouble(Object value) { + return -200.5; + } + }; + private static final ToDoubleFunction<QueueManager> queueManagerFunction = + new ToDoubleFunction<QueueManager>() { + @Override + public double applyAsDouble(QueueManager queue) { + return queue.size(); + } + }; + + @Test + public void createTimeSeries_WithNullLabelValues() { + thrown.expect(NullPointerException.class); + thrown.expectMessage("labelValues"); + derivedDoubleGauge.createTimeSeries(null, null, doubleFunction); + } + + @Test + public void createTimeSeries_WithNullElement() { + List<LabelKey> labelKeys = + Arrays.asList(LabelKey.create("key1", "desc"), LabelKey.create("key2", "desc")); + List<LabelValue> labelValues = Arrays.asList(LabelValue.create("value1"), null); + DerivedDoubleGaugeImpl derivedDoubleGauge = + new DerivedDoubleGaugeImpl(METRIC_NAME, METRIC_DESCRIPTION, METRIC_UNIT, labelKeys); + thrown.expect(NullPointerException.class); + thrown.expectMessage("labelValue element should not be null."); + derivedDoubleGauge.createTimeSeries(labelValues, null, doubleFunction); + } + + @Test + public void createTimeSeries_WithInvalidLabelSize() { + List<LabelValue> labelValues = + Arrays.asList(LabelValue.create("value1"), LabelValue.create("value2")); + thrown.expect(IllegalArgumentException.class); + thrown.expectMessage("Incorrect number of labels."); + derivedDoubleGauge.createTimeSeries(labelValues, null, doubleFunction); + } + + @Test + public void createTimeSeries_WithNullFunction() { + thrown.expect(NullPointerException.class); + thrown.expectMessage("function"); + derivedDoubleGauge.createTimeSeries(LABEL_VALUES, null, null); + } + + @Test + public void createTimeSeries_WithObjFunction() { + derivedDoubleGauge.createTimeSeries(LABEL_VALUES, new QueueManager(), queueManagerFunction); + Metric metric = derivedDoubleGauge.getMetric(testClock); + assertThat(metric).isNotNull(); + assertThat(metric) + .isEqualTo( + Metric.createWithOneTimeSeries( + METRIC_DESCRIPTOR, + TimeSeries.createWithOnePoint( + LABEL_VALUES, Point.create(Value.doubleValue(2.5), TEST_TIME), null))); + } + + @Test + public void createTimeSeries_WithSameLabel() { + derivedDoubleGauge.createTimeSeries(LABEL_VALUES, new QueueManager(), queueManagerFunction); + thrown.expect(IllegalArgumentException.class); + thrown.expectMessage("A different time series with the same labels already exists."); + derivedDoubleGauge.createTimeSeries(LABEL_VALUES, null, queueManagerFunction); + } + + @Test + public void addTimeSeries_WithNullObj() { + derivedDoubleGauge.createTimeSeries(LABEL_VALUES, null, negativeDoubleFunction); + Metric metric = derivedDoubleGauge.getMetric(testClock); + assertThat(metric).isNotNull(); + assertThat(metric) + .isEqualTo( + Metric.createWithOneTimeSeries( + METRIC_DESCRIPTOR, + TimeSeries.createWithOnePoint( + LABEL_VALUES, Point.create(Value.doubleValue(-200.5), TEST_TIME), null))); + } + + @Test + public void removeTimeSeries() { + derivedDoubleGauge.createTimeSeries(LABEL_VALUES, null, doubleFunction); + Metric metric = derivedDoubleGauge.getMetric(testClock); + assertThat(metric).isNotNull(); + assertThat(metric.getMetricDescriptor()).isEqualTo(METRIC_DESCRIPTOR); + assertThat(metric.getTimeSeriesList().size()).isEqualTo(1); + derivedDoubleGauge.removeTimeSeries(LABEL_VALUES); + assertThat(derivedDoubleGauge.getMetric(testClock)).isNull(); + } + + @Test + public void removeTimeSeries_WithNullLabelValues() { + thrown.expect(NullPointerException.class); + thrown.expectMessage("labelValues"); + derivedDoubleGauge.removeTimeSeries(null); + } + + @Test + public void multipleMetrics_GetMetric() { + derivedDoubleGauge.createTimeSeries(LABEL_VALUES, null, doubleFunction); + derivedDoubleGauge.createTimeSeries(LABEL_VALUES_1, new QueueManager(), queueManagerFunction); + List<TimeSeries> expectedTimeSeriesList = new ArrayList<TimeSeries>(); + expectedTimeSeriesList.add( + TimeSeries.createWithOnePoint( + LABEL_VALUES, Point.create(Value.doubleValue(5.5), TEST_TIME), null)); + expectedTimeSeriesList.add( + TimeSeries.createWithOnePoint( + LABEL_VALUES_1, Point.create(Value.doubleValue(2.5), TEST_TIME), null)); + Metric metric = derivedDoubleGauge.getMetric(testClock); + assertThat(metric).isNotNull(); + assertThat(metric.getMetricDescriptor()).isEqualTo(METRIC_DESCRIPTOR); + assertThat(metric.getTimeSeriesList().size()).isEqualTo(2); + assertThat(metric.getTimeSeriesList()).containsExactlyElementsIn(expectedTimeSeriesList); + assertThat(metric.getTimeSeriesList().get(0).getLabelValues().size()).isEqualTo(1); + assertThat(metric.getTimeSeriesList().get(0).getLabelValues().get(0)) + .isEqualTo(LabelValue.create("value")); + assertThat(metric.getTimeSeriesList().get(1).getLabelValues().size()).isEqualTo(1); + assertThat(metric.getTimeSeriesList().get(1).getLabelValues().get(0)) + .isEqualTo(LabelValue.create("value1")); + } + + @Test + public void clear() { + derivedDoubleGauge.createTimeSeries(LABEL_VALUES, null, doubleFunction); + derivedDoubleGauge.createTimeSeries(LABEL_VALUES_1, new QueueManager(), queueManagerFunction); + Metric metric = derivedDoubleGauge.getMetric(testClock); + assertThat(metric).isNotNull(); + assertThat(metric.getMetricDescriptor()).isEqualTo(METRIC_DESCRIPTOR); + assertThat(metric.getTimeSeriesList().size()).isEqualTo(2); + derivedDoubleGauge.clear(); + assertThat(derivedDoubleGauge.getMetric(testClock)).isNull(); + } + + @Test + public void empty_GetMetrics() { + assertThat(derivedDoubleGauge.getMetric(testClock)).isNull(); + } +} diff --git a/impl_core/src/test/java/io/opencensus/implcore/metrics/DerivedLongGaugeImplTest.java b/impl_core/src/test/java/io/opencensus/implcore/metrics/DerivedLongGaugeImplTest.java new file mode 100644 index 00000000..ec9cad6c --- /dev/null +++ b/impl_core/src/test/java/io/opencensus/implcore/metrics/DerivedLongGaugeImplTest.java @@ -0,0 +1,224 @@ +/* + * Copyright 2018, OpenCensus Authors + * + * 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 io.opencensus.implcore.metrics; + +import static com.google.common.truth.Truth.assertThat; + +import io.opencensus.common.Timestamp; +import io.opencensus.common.ToLongFunction; +import io.opencensus.metrics.LabelKey; +import io.opencensus.metrics.LabelValue; +import io.opencensus.metrics.export.Metric; +import io.opencensus.metrics.export.MetricDescriptor; +import io.opencensus.metrics.export.MetricDescriptor.Type; +import io.opencensus.metrics.export.Point; +import io.opencensus.metrics.export.TimeSeries; +import io.opencensus.metrics.export.Value; +import io.opencensus.testing.common.TestClock; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Unit tests for {@link DerivedLongGaugeImpl}. */ +@RunWith(JUnit4.class) +public class DerivedLongGaugeImplTest { + @Rule public ExpectedException thrown = ExpectedException.none(); + + private static final String METRIC_NAME = "name"; + private static final String METRIC_DESCRIPTION = "description"; + private static final String METRIC_UNIT = "1"; + private static final List<LabelKey> LABEL_KEY = + Collections.singletonList(LabelKey.create("key", "key description")); + private static final List<LabelValue> LABEL_VALUES = + Collections.singletonList(LabelValue.create("value")); + private static final List<LabelValue> LABEL_VALUES_1 = + Collections.singletonList(LabelValue.create("value1")); + + private static final Timestamp TEST_TIME = Timestamp.create(1234, 123); + private final TestClock testClock = TestClock.create(TEST_TIME); + + private static final MetricDescriptor METRIC_DESCRIPTOR = + MetricDescriptor.create( + METRIC_NAME, METRIC_DESCRIPTION, METRIC_UNIT, Type.GAUGE_INT64, LABEL_KEY); + + private final DerivedLongGaugeImpl derivedLongGauge = + new DerivedLongGaugeImpl(METRIC_NAME, METRIC_DESCRIPTION, METRIC_UNIT, LABEL_KEY); + + // helper class + public static class QueueManager { + public long size() { + return 2; + } + } + + private static final ToLongFunction<Object> longFunction = + new ToLongFunction<Object>() { + @Override + public long applyAsLong(Object value) { + return 5; + } + }; + private static final ToLongFunction<Object> negativeLongFunction = + new ToLongFunction<Object>() { + @Override + public long applyAsLong(Object value) { + return -200; + } + }; + private static final ToLongFunction<QueueManager> queueManagerFunction = + new ToLongFunction<QueueManager>() { + @Override + public long applyAsLong(QueueManager queue) { + return queue.size(); + } + }; + + @Test + public void createTimeSeries_WithNullLabelValues() { + thrown.expect(NullPointerException.class); + thrown.expectMessage("labelValues"); + derivedLongGauge.createTimeSeries(null, null, longFunction); + } + + @Test + public void createTimeSeries_WithNullElement() { + List<LabelKey> labelKeys = + Arrays.asList(LabelKey.create("key1", "desc"), LabelKey.create("key2", "desc")); + List<LabelValue> labelValues = Arrays.asList(LabelValue.create("value1"), null); + + DerivedLongGaugeImpl derivedLongGauge = + new DerivedLongGaugeImpl(METRIC_NAME, METRIC_DESCRIPTION, METRIC_UNIT, labelKeys); + thrown.expect(NullPointerException.class); + thrown.expectMessage("labelValue element should not be null."); + derivedLongGauge.createTimeSeries(labelValues, null, longFunction); + } + + @Test + public void createTimeSeries_WithInvalidLabelSize() { + List<LabelValue> labelValues = + Arrays.asList(LabelValue.create("value1"), LabelValue.create("value2")); + + thrown.expect(IllegalArgumentException.class); + thrown.expectMessage("Incorrect number of labels."); + derivedLongGauge.createTimeSeries(labelValues, null, longFunction); + } + + @Test + public void createTimeSeries_WithNullFunction() { + thrown.expect(NullPointerException.class); + thrown.expectMessage("function"); + derivedLongGauge.createTimeSeries(LABEL_VALUES, null, null); + } + + @Test + public void createTimeSeries_WithObjFunction() { + derivedLongGauge.createTimeSeries(LABEL_VALUES, new QueueManager(), queueManagerFunction); + + Metric metric = derivedLongGauge.getMetric(testClock); + assertThat(metric).isNotNull(); + assertThat(metric) + .isEqualTo( + Metric.createWithOneTimeSeries( + METRIC_DESCRIPTOR, + TimeSeries.createWithOnePoint( + LABEL_VALUES, Point.create(Value.longValue(2), TEST_TIME), null))); + } + + @Test + public void addTimeSeries_WithNullObj() { + derivedLongGauge.createTimeSeries(LABEL_VALUES, null, negativeLongFunction); + + Metric metric = derivedLongGauge.getMetric(testClock); + assertThat(metric).isNotNull(); + assertThat(metric) + .isEqualTo( + Metric.createWithOneTimeSeries( + METRIC_DESCRIPTOR, + TimeSeries.createWithOnePoint( + LABEL_VALUES, Point.create(Value.longValue(-200), TEST_TIME), null))); + } + + @Test + public void removeTimeSeries() { + derivedLongGauge.createTimeSeries(LABEL_VALUES, null, longFunction); + Metric metric = derivedLongGauge.getMetric(testClock); + assertThat(metric).isNotNull(); + assertThat(metric.getMetricDescriptor()).isEqualTo(METRIC_DESCRIPTOR); + assertThat(metric.getTimeSeriesList().size()).isEqualTo(1); + + derivedLongGauge.removeTimeSeries(LABEL_VALUES); + assertThat(derivedLongGauge.getMetric(testClock)).isNull(); + } + + @Test + public void removeTimeSeries_WithNullLabelValues() { + thrown.expect(NullPointerException.class); + thrown.expectMessage("labelValues"); + derivedLongGauge.removeTimeSeries(null); + } + + @Test + public void multipleMetrics_GetMetric() { + derivedLongGauge.createTimeSeries(LABEL_VALUES, null, longFunction); + derivedLongGauge.createTimeSeries(LABEL_VALUES_1, new QueueManager(), queueManagerFunction); + + List<TimeSeries> expectedTimeSeriesList = new ArrayList<TimeSeries>(); + expectedTimeSeriesList.add( + TimeSeries.createWithOnePoint( + LABEL_VALUES, Point.create(Value.longValue(5), TEST_TIME), null)); + expectedTimeSeriesList.add( + TimeSeries.createWithOnePoint( + LABEL_VALUES_1, Point.create(Value.longValue(2), TEST_TIME), null)); + + Metric metric = derivedLongGauge.getMetric(testClock); + assertThat(metric).isNotNull(); + assertThat(metric.getMetricDescriptor()).isEqualTo(METRIC_DESCRIPTOR); + assertThat(metric.getTimeSeriesList().size()).isEqualTo(2); + assertThat(metric.getTimeSeriesList()).containsExactlyElementsIn(expectedTimeSeriesList); + assertThat(metric.getTimeSeriesList().get(0).getLabelValues().size()).isEqualTo(1); + assertThat(metric.getTimeSeriesList().get(0).getLabelValues().get(0)) + .isEqualTo(LabelValue.create("value")); + assertThat(metric.getTimeSeriesList().get(1).getLabelValues().size()).isEqualTo(1); + assertThat(metric.getTimeSeriesList().get(1).getLabelValues().get(0)) + .isEqualTo(LabelValue.create("value1")); + } + + @Test + public void clear() { + derivedLongGauge.createTimeSeries(LABEL_VALUES, null, longFunction); + derivedLongGauge.createTimeSeries(LABEL_VALUES_1, new QueueManager(), queueManagerFunction); + + Metric metric = derivedLongGauge.getMetric(testClock); + assertThat(metric).isNotNull(); + assertThat(metric.getMetricDescriptor()).isEqualTo(METRIC_DESCRIPTOR); + assertThat(metric.getTimeSeriesList().size()).isEqualTo(2); + + derivedLongGauge.clear(); + assertThat(derivedLongGauge.getMetric(testClock)).isNull(); + } + + @Test + public void empty_GetMetrics() { + assertThat(derivedLongGauge.getMetric(testClock)).isNull(); + } +} diff --git a/impl_core/src/test/java/io/opencensus/implcore/metrics/DoubleGaugeImplTest.java b/impl_core/src/test/java/io/opencensus/implcore/metrics/DoubleGaugeImplTest.java new file mode 100644 index 00000000..b0899084 --- /dev/null +++ b/impl_core/src/test/java/io/opencensus/implcore/metrics/DoubleGaugeImplTest.java @@ -0,0 +1,292 @@ +/* + * Copyright 2018, OpenCensus Authors + * + * 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 io.opencensus.implcore.metrics; + +import static com.google.common.truth.Truth.assertThat; +import static io.opencensus.implcore.metrics.DoubleGaugeImpl.UNSET_VALUE; + +import com.google.common.testing.EqualsTester; +import io.opencensus.common.Timestamp; +import io.opencensus.metrics.DoubleGauge.DoublePoint; +import io.opencensus.metrics.LabelKey; +import io.opencensus.metrics.LabelValue; +import io.opencensus.metrics.export.Metric; +import io.opencensus.metrics.export.MetricDescriptor; +import io.opencensus.metrics.export.MetricDescriptor.Type; +import io.opencensus.metrics.export.Point; +import io.opencensus.metrics.export.TimeSeries; +import io.opencensus.metrics.export.Value; +import io.opencensus.testing.common.TestClock; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Unit tests for {@link DoubleGaugeImpl}. */ +@RunWith(JUnit4.class) +public class DoubleGaugeImplTest { + @Rule public ExpectedException thrown = ExpectedException.none(); + + private static final String METRIC_NAME = "name"; + private static final String METRIC_DESCRIPTION = "description"; + private static final String METRIC_UNIT = "1"; + private static final List<LabelKey> LABEL_KEY = + Collections.singletonList(LabelKey.create("key", "key description")); + private static final List<LabelValue> LABEL_VALUES = + Collections.singletonList(LabelValue.create("value")); + private static final List<LabelValue> LABEL_VALUES1 = + Collections.singletonList(LabelValue.create("value1")); + private static final List<LabelValue> DEFAULT_LABEL_VALUES = + Collections.singletonList(UNSET_VALUE); + + private static final Timestamp TEST_TIME = Timestamp.create(1234, 123); + private final TestClock testClock = TestClock.create(TEST_TIME); + private static final MetricDescriptor METRIC_DESCRIPTOR = + MetricDescriptor.create( + METRIC_NAME, METRIC_DESCRIPTION, METRIC_UNIT, Type.GAUGE_DOUBLE, LABEL_KEY); + private final DoubleGaugeImpl doubleGauge = + new DoubleGaugeImpl(METRIC_NAME, METRIC_DESCRIPTION, METRIC_UNIT, LABEL_KEY); + + @Test + public void getOrCreateTimeSeries_WithNullLabelValues() { + thrown.expect(NullPointerException.class); + thrown.expectMessage("labelValues"); + doubleGauge.getOrCreateTimeSeries(null); + } + + @Test + public void getOrCreateTimeSeries_WithNullElement() { + List<LabelKey> labelKeys = + Arrays.asList(LabelKey.create("key1", "desc"), LabelKey.create("key2", "desc")); + List<LabelValue> labelValues = Arrays.asList(LabelValue.create("value1"), null); + + DoubleGaugeImpl doubleGauge = + new DoubleGaugeImpl(METRIC_NAME, METRIC_DESCRIPTION, METRIC_UNIT, labelKeys); + thrown.expect(NullPointerException.class); + thrown.expectMessage("labelValue element should not be null."); + doubleGauge.getOrCreateTimeSeries(labelValues); + } + + @Test + public void getOrCreateTimeSeries_WithInvalidLabelSize() { + List<LabelValue> labelValues = + Arrays.asList(LabelValue.create("value1"), LabelValue.create("value2")); + + thrown.expect(IllegalArgumentException.class); + thrown.expectMessage("Incorrect number of labels."); + doubleGauge.getOrCreateTimeSeries(labelValues); + } + + @Test + public void getOrCreateTimeSeries() { + DoublePoint point = doubleGauge.getOrCreateTimeSeries(LABEL_VALUES); + point.add(100); + DoublePoint point1 = doubleGauge.getOrCreateTimeSeries(LABEL_VALUES); + point1.set(500); + + Metric metric = doubleGauge.getMetric(testClock); + assertThat(metric).isNotNull(); + assertThat(metric) + .isEqualTo( + Metric.create( + METRIC_DESCRIPTOR, + Collections.singletonList( + TimeSeries.createWithOnePoint( + LABEL_VALUES, Point.create(Value.doubleValue(500), TEST_TIME), null)))); + assertThat(point).isSameAs(point1); + } + + @Test + public void getOrCreateTimeSeries_WithNegativePointValues() { + DoublePoint point = doubleGauge.getOrCreateTimeSeries(LABEL_VALUES); + point.add(-100); + point.add(-33); + + Metric metric = doubleGauge.getMetric(testClock); + assertThat(metric).isNotNull(); + assertThat(metric.getMetricDescriptor()).isEqualTo(METRIC_DESCRIPTOR); + assertThat(metric.getTimeSeriesList().size()).isEqualTo(1); + assertThat(metric.getTimeSeriesList().get(0).getPoints().size()).isEqualTo(1); + assertThat(metric.getTimeSeriesList().get(0).getPoints().get(0).getValue()) + .isEqualTo(Value.doubleValue(-133)); + assertThat(metric.getTimeSeriesList().get(0).getPoints().get(0).getTimestamp()) + .isEqualTo(TEST_TIME); + assertThat(metric.getTimeSeriesList().get(0).getStartTimestamp()).isNull(); + } + + @Test + public void getDefaultTimeSeries() { + DoublePoint point = doubleGauge.getDefaultTimeSeries(); + point.add(100); + point.set(500); + + DoublePoint point1 = doubleGauge.getDefaultTimeSeries(); + point1.add(-100); + + Metric metric = doubleGauge.getMetric(testClock); + assertThat(metric).isNotNull(); + assertThat(metric) + .isEqualTo( + Metric.create( + METRIC_DESCRIPTOR, + Collections.singletonList( + TimeSeries.createWithOnePoint( + DEFAULT_LABEL_VALUES, + Point.create(Value.doubleValue(400), TEST_TIME), + null)))); + assertThat(point).isSameAs(point1); + } + + @Test + public void removeTimeSeries() { + doubleGauge.getOrCreateTimeSeries(LABEL_VALUES); + assertThat(doubleGauge.getMetric(testClock)) + .isEqualTo( + Metric.create( + METRIC_DESCRIPTOR, + Collections.singletonList( + TimeSeries.createWithOnePoint( + LABEL_VALUES, Point.create(Value.doubleValue(0), TEST_TIME), null)))); + + doubleGauge.removeTimeSeries(LABEL_VALUES); + assertThat(doubleGauge.getMetric(testClock)).isNull(); + } + + @Test + public void removeTimeSeries_WithNullLabelValues() { + thrown.expect(NullPointerException.class); + thrown.expectMessage("labelValues"); + doubleGauge.removeTimeSeries(null); + } + + @Test + public void clear() { + DoublePoint doublePoint = doubleGauge.getOrCreateTimeSeries(LABEL_VALUES); + doublePoint.add(-11); + DoublePoint defaultPoint = doubleGauge.getDefaultTimeSeries(); + defaultPoint.set(100); + + Metric metric = doubleGauge.getMetric(testClock); + assertThat(metric).isNotNull(); + assertThat(metric.getMetricDescriptor()).isEqualTo(METRIC_DESCRIPTOR); + assertThat(metric.getTimeSeriesList().size()).isEqualTo(2); + + doubleGauge.clear(); + assertThat(doubleGauge.getMetric(testClock)).isNull(); + } + + @Test + public void setDefaultLabelValues() { + List<LabelKey> labelKeys = + Arrays.asList(LabelKey.create("key1", "desc"), LabelKey.create("key2", "desc")); + DoubleGaugeImpl doubleGauge = + new DoubleGaugeImpl(METRIC_NAME, METRIC_DESCRIPTION, METRIC_UNIT, labelKeys); + DoublePoint defaultPoint = doubleGauge.getDefaultTimeSeries(); + defaultPoint.set(-230); + + Metric metric = doubleGauge.getMetric(testClock); + assertThat(metric).isNotNull(); + assertThat(metric.getTimeSeriesList().size()).isEqualTo(1); + assertThat(metric.getTimeSeriesList().get(0).getLabelValues().size()).isEqualTo(2); + assertThat(metric.getTimeSeriesList().get(0).getLabelValues().get(0)).isEqualTo(UNSET_VALUE); + assertThat(metric.getTimeSeriesList().get(0).getLabelValues().get(1)).isEqualTo(UNSET_VALUE); + } + + @Test + public void pointImpl_InstanceOf() { + DoublePoint doublePoint = doubleGauge.getOrCreateTimeSeries(LABEL_VALUES); + assertThat(doublePoint).isInstanceOf(DoubleGaugeImpl.PointImpl.class); + } + + @Test + public void multipleMetrics_GetMetric() { + DoublePoint doublePoint = doubleGauge.getOrCreateTimeSeries(LABEL_VALUES); + doublePoint.add(1); + doublePoint.add(2); + + DoublePoint defaultPoint = doubleGauge.getDefaultTimeSeries(); + defaultPoint.set(100); + + DoublePoint doublePoint1 = doubleGauge.getOrCreateTimeSeries(LABEL_VALUES1); + doublePoint1.add(-100); + doublePoint1.add(-20); + + List<TimeSeries> expectedTimeSeriesList = new ArrayList<TimeSeries>(); + expectedTimeSeriesList.add( + TimeSeries.createWithOnePoint( + LABEL_VALUES, Point.create(Value.doubleValue(3), TEST_TIME), null)); + expectedTimeSeriesList.add( + TimeSeries.createWithOnePoint( + DEFAULT_LABEL_VALUES, Point.create(Value.doubleValue(100), TEST_TIME), null)); + expectedTimeSeriesList.add( + TimeSeries.createWithOnePoint( + LABEL_VALUES1, Point.create(Value.doubleValue(-120), TEST_TIME), null)); + + Metric metric = doubleGauge.getMetric(testClock); + assertThat(metric).isNotNull(); + assertThat(metric.getMetricDescriptor()).isEqualTo(METRIC_DESCRIPTOR); + assertThat(metric.getTimeSeriesList().size()).isEqualTo(3); + assertThat(metric.getTimeSeriesList()).containsExactlyElementsIn(expectedTimeSeriesList); + } + + @Test + public void empty_GetMetrics() { + assertThat(doubleGauge.getMetric(testClock)).isNull(); + } + + @Test + public void testEquals() { + List<LabelKey> labelKeys = + Arrays.asList(LabelKey.create("key1", "desc"), LabelKey.create("key2", "desc")); + List<LabelValue> labelValues = + Arrays.asList(LabelValue.create("value1"), LabelValue.create("value2")); + + DoubleGaugeImpl doubleGauge = + new DoubleGaugeImpl(METRIC_NAME, METRIC_DESCRIPTION, METRIC_UNIT, labelKeys); + + DoublePoint defaultPoint1 = doubleGauge.getDefaultTimeSeries(); + DoublePoint defaultPoint2 = doubleGauge.getDefaultTimeSeries(); + DoublePoint doublePoint1 = doubleGauge.getOrCreateTimeSeries(labelValues); + DoublePoint doublePoint2 = doubleGauge.getOrCreateTimeSeries(labelValues); + + new EqualsTester() + .addEqualityGroup(defaultPoint1, defaultPoint2) + .addEqualityGroup(doublePoint1, doublePoint2) + .testEquals(); + + doubleGauge.clear(); + + DoublePoint newDefaultPointAfterClear = doubleGauge.getDefaultTimeSeries(); + DoublePoint newDoublePointAfterClear = doubleGauge.getOrCreateTimeSeries(labelValues); + + doubleGauge.removeTimeSeries(labelValues); + DoublePoint newDoublePointAfterRemove = doubleGauge.getOrCreateTimeSeries(labelValues); + + new EqualsTester() + .addEqualityGroup(defaultPoint1, defaultPoint2) + .addEqualityGroup(doublePoint1, doublePoint2) + .addEqualityGroup(newDefaultPointAfterClear) + .addEqualityGroup(newDoublePointAfterClear) + .addEqualityGroup(newDoublePointAfterRemove) + .testEquals(); + } +} diff --git a/impl_core/src/test/java/io/opencensus/implcore/metrics/LongGaugeImplTest.java b/impl_core/src/test/java/io/opencensus/implcore/metrics/LongGaugeImplTest.java new file mode 100644 index 00000000..e83bb642 --- /dev/null +++ b/impl_core/src/test/java/io/opencensus/implcore/metrics/LongGaugeImplTest.java @@ -0,0 +1,287 @@ +/* + * Copyright 2018, OpenCensus Authors + * + * 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 io.opencensus.implcore.metrics; + +import static com.google.common.truth.Truth.assertThat; +import static io.opencensus.implcore.metrics.LongGaugeImpl.UNSET_VALUE; + +import com.google.common.testing.EqualsTester; +import io.opencensus.common.Timestamp; +import io.opencensus.metrics.LabelKey; +import io.opencensus.metrics.LabelValue; +import io.opencensus.metrics.LongGauge.LongPoint; +import io.opencensus.metrics.export.Metric; +import io.opencensus.metrics.export.MetricDescriptor; +import io.opencensus.metrics.export.MetricDescriptor.Type; +import io.opencensus.metrics.export.Point; +import io.opencensus.metrics.export.TimeSeries; +import io.opencensus.metrics.export.Value; +import io.opencensus.testing.common.TestClock; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Unit tests for {@link LongGaugeImpl}. */ +@RunWith(JUnit4.class) +public class LongGaugeImplTest { + @Rule public ExpectedException thrown = ExpectedException.none(); + + private static final String METRIC_NAME = "name"; + private static final String METRIC_DESCRIPTION = "description"; + private static final String METRIC_UNIT = "1"; + private static final List<LabelKey> LABEL_KEY = + Collections.singletonList(LabelKey.create("key", "key description")); + private static final List<LabelValue> LABEL_VALUES = + Collections.singletonList(LabelValue.create("value")); + private static final List<LabelValue> LABEL_VALUES1 = + Collections.singletonList(LabelValue.create("value1")); + private static final List<LabelValue> DEFAULT_LABEL_VALUES = + Collections.singletonList(UNSET_VALUE); + + private static final Timestamp TEST_TIME = Timestamp.create(1234, 123); + private final TestClock testClock = TestClock.create(TEST_TIME); + private static final MetricDescriptor METRIC_DESCRIPTOR = + MetricDescriptor.create( + METRIC_NAME, METRIC_DESCRIPTION, METRIC_UNIT, Type.GAUGE_INT64, LABEL_KEY); + private final LongGaugeImpl longGaugeMetric = + new LongGaugeImpl(METRIC_NAME, METRIC_DESCRIPTION, METRIC_UNIT, LABEL_KEY); + + @Test + public void getOrCreateTimeSeries_WithNullLabelValues() { + thrown.expect(NullPointerException.class); + thrown.expectMessage("labelValues"); + longGaugeMetric.getOrCreateTimeSeries(null); + } + + @Test + public void getOrCreateTimeSeries_WithNullElement() { + List<LabelKey> labelKeys = + Arrays.asList(LabelKey.create("key1", "desc"), LabelKey.create("key2", "desc")); + List<LabelValue> labelValues = Arrays.asList(LabelValue.create("value1"), null); + + LongGaugeImpl longGauge = + new LongGaugeImpl(METRIC_NAME, METRIC_DESCRIPTION, METRIC_UNIT, labelKeys); + thrown.expect(NullPointerException.class); + thrown.expectMessage("labelValue element should not be null."); + longGauge.getOrCreateTimeSeries(labelValues); + } + + @Test + public void getOrCreateTimeSeries_WithInvalidLabelSize() { + List<LabelValue> labelValues = + Arrays.asList(LabelValue.create("value1"), LabelValue.create("value2")); + + thrown.expect(IllegalArgumentException.class); + thrown.expectMessage("Incorrect number of labels."); + longGaugeMetric.getOrCreateTimeSeries(labelValues); + } + + @Test + public void getOrCreateTimeSeries() { + LongPoint point = longGaugeMetric.getOrCreateTimeSeries(LABEL_VALUES); + point.add(100); + LongPoint point1 = longGaugeMetric.getOrCreateTimeSeries(LABEL_VALUES); + point1.set(500); + + Metric metric = longGaugeMetric.getMetric(testClock); + assertThat(metric).isNotNull(); + assertThat(metric) + .isEqualTo( + Metric.createWithOneTimeSeries( + METRIC_DESCRIPTOR, + TimeSeries.createWithOnePoint( + LABEL_VALUES, Point.create(Value.longValue(500), TEST_TIME), null))); + assertThat(point).isSameAs(point1); + } + + @Test + public void getOrCreateTimeSeries_WithNegativePointValues() { + LongPoint point = longGaugeMetric.getOrCreateTimeSeries(LABEL_VALUES); + point.add(-100); + point.add(-33); + + Metric metric = longGaugeMetric.getMetric(testClock); + assertThat(metric).isNotNull(); + assertThat(metric.getMetricDescriptor()).isEqualTo(METRIC_DESCRIPTOR); + assertThat(metric.getTimeSeriesList().size()).isEqualTo(1); + assertThat(metric.getTimeSeriesList().get(0).getPoints().size()).isEqualTo(1); + assertThat(metric.getTimeSeriesList().get(0).getPoints().get(0).getValue()) + .isEqualTo(Value.longValue(-133)); + assertThat(metric.getTimeSeriesList().get(0).getPoints().get(0).getTimestamp()) + .isEqualTo(TEST_TIME); + assertThat(metric.getTimeSeriesList().get(0).getStartTimestamp()).isNull(); + } + + @Test + public void getDefaultTimeSeries() { + LongPoint point = longGaugeMetric.getDefaultTimeSeries(); + point.add(100); + point.set(500); + + LongPoint point1 = longGaugeMetric.getDefaultTimeSeries(); + point1.add(-100); + + Metric metric = longGaugeMetric.getMetric(testClock); + assertThat(metric).isNotNull(); + assertThat(metric) + .isEqualTo( + Metric.createWithOneTimeSeries( + METRIC_DESCRIPTOR, + TimeSeries.createWithOnePoint( + DEFAULT_LABEL_VALUES, Point.create(Value.longValue(400), TEST_TIME), null))); + assertThat(point).isSameAs(point1); + } + + @Test + public void removeTimeSeries() { + longGaugeMetric.getOrCreateTimeSeries(LABEL_VALUES); + assertThat(longGaugeMetric.getMetric(testClock)) + .isEqualTo( + Metric.createWithOneTimeSeries( + METRIC_DESCRIPTOR, + TimeSeries.createWithOnePoint( + LABEL_VALUES, Point.create(Value.longValue(0), TEST_TIME), null))); + + longGaugeMetric.removeTimeSeries(LABEL_VALUES); + assertThat(longGaugeMetric.getMetric(testClock)).isNull(); + } + + @Test + public void removeTimeSeries_WithNullLabelValues() { + thrown.expect(NullPointerException.class); + thrown.expectMessage("labelValues"); + longGaugeMetric.removeTimeSeries(null); + } + + @Test + public void clear() { + LongPoint longPoint = longGaugeMetric.getOrCreateTimeSeries(LABEL_VALUES); + longPoint.add(-11); + LongPoint defaultPoint = longGaugeMetric.getDefaultTimeSeries(); + defaultPoint.set(100); + + Metric metric = longGaugeMetric.getMetric(testClock); + assertThat(metric).isNotNull(); + assertThat(metric.getMetricDescriptor()).isEqualTo(METRIC_DESCRIPTOR); + assertThat(metric.getTimeSeriesList().size()).isEqualTo(2); + + longGaugeMetric.clear(); + assertThat(longGaugeMetric.getMetric(testClock)).isNull(); + } + + @Test + public void setDefaultLabelValues() { + List<LabelKey> labelKeys = + Arrays.asList(LabelKey.create("key1", "desc"), LabelKey.create("key2", "desc")); + LongGaugeImpl longGauge = + new LongGaugeImpl(METRIC_NAME, METRIC_DESCRIPTION, METRIC_UNIT, labelKeys); + LongPoint defaultPoint = longGauge.getDefaultTimeSeries(); + defaultPoint.set(-230); + + Metric metric = longGauge.getMetric(testClock); + assertThat(metric).isNotNull(); + assertThat(metric.getTimeSeriesList().size()).isEqualTo(1); + assertThat(metric.getTimeSeriesList().get(0).getLabelValues().size()).isEqualTo(2); + assertThat(metric.getTimeSeriesList().get(0).getLabelValues().get(0)).isEqualTo(UNSET_VALUE); + assertThat(metric.getTimeSeriesList().get(0).getLabelValues().get(1)).isEqualTo(UNSET_VALUE); + } + + @Test + public void pointImpl_InstanceOf() { + LongPoint longPoint = longGaugeMetric.getOrCreateTimeSeries(LABEL_VALUES); + assertThat(longPoint).isInstanceOf(LongGaugeImpl.PointImpl.class); + } + + @Test + public void multipleMetrics_GetMetric() { + LongPoint longPoint = longGaugeMetric.getOrCreateTimeSeries(LABEL_VALUES); + longPoint.add(1); + longPoint.add(2); + + LongPoint defaultPoint = longGaugeMetric.getDefaultTimeSeries(); + defaultPoint.set(100); + + LongPoint longPoint1 = longGaugeMetric.getOrCreateTimeSeries(LABEL_VALUES1); + longPoint1.add(-100); + longPoint1.add(-20); + + List<TimeSeries> expectedTimeSeriesList = new ArrayList<TimeSeries>(); + expectedTimeSeriesList.add( + TimeSeries.createWithOnePoint( + LABEL_VALUES, Point.create(Value.longValue(3), TEST_TIME), null)); + expectedTimeSeriesList.add( + TimeSeries.createWithOnePoint( + DEFAULT_LABEL_VALUES, Point.create(Value.longValue(100), TEST_TIME), null)); + expectedTimeSeriesList.add( + TimeSeries.createWithOnePoint( + LABEL_VALUES1, Point.create(Value.longValue(-120), TEST_TIME), null)); + + Metric metric = longGaugeMetric.getMetric(testClock); + assertThat(metric).isNotNull(); + assertThat(metric.getMetricDescriptor()).isEqualTo(METRIC_DESCRIPTOR); + assertThat(metric.getTimeSeriesList().size()).isEqualTo(3); + assertThat(metric.getTimeSeriesList()).containsExactlyElementsIn(expectedTimeSeriesList); + } + + @Test + public void empty_GetMetrics() { + assertThat(longGaugeMetric.getMetric(testClock)).isNull(); + } + + @Test + public void testEquals() { + List<LabelKey> labelKeys = + Arrays.asList(LabelKey.create("key1", "desc"), LabelKey.create("key2", "desc")); + List<LabelValue> labelValues = + Arrays.asList(LabelValue.create("value1"), LabelValue.create("value2")); + + LongGaugeImpl longGauge = + new LongGaugeImpl(METRIC_NAME, METRIC_DESCRIPTION, METRIC_UNIT, labelKeys); + + LongPoint defaultPoint1 = longGauge.getDefaultTimeSeries(); + LongPoint defaultPoint2 = longGauge.getDefaultTimeSeries(); + LongPoint longPoint1 = longGauge.getOrCreateTimeSeries(labelValues); + LongPoint longPoint2 = longGauge.getOrCreateTimeSeries(labelValues); + + new EqualsTester() + .addEqualityGroup(defaultPoint1, defaultPoint2) + .addEqualityGroup(longPoint1, longPoint2) + .testEquals(); + + longGauge.clear(); + + LongPoint newDefaultPointAfterClear = longGauge.getDefaultTimeSeries(); + LongPoint newLongPointAfterClear = longGauge.getOrCreateTimeSeries(labelValues); + + longGauge.removeTimeSeries(labelValues); + LongPoint newLongPointAfterRemove = longGauge.getOrCreateTimeSeries(labelValues); + + new EqualsTester() + .addEqualityGroup(defaultPoint1, defaultPoint2) + .addEqualityGroup(longPoint1, longPoint2) + .addEqualityGroup(newDefaultPointAfterClear) + .addEqualityGroup(newLongPointAfterClear) + .addEqualityGroup(newLongPointAfterRemove) + .testEquals(); + } +} diff --git a/impl_core/src/test/java/io/opencensus/implcore/metrics/MetricRegistryImplTest.java b/impl_core/src/test/java/io/opencensus/implcore/metrics/MetricRegistryImplTest.java new file mode 100644 index 00000000..68bfda31 --- /dev/null +++ b/impl_core/src/test/java/io/opencensus/implcore/metrics/MetricRegistryImplTest.java @@ -0,0 +1,356 @@ +/* + * Copyright 2018, OpenCensus Authors + * + * 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 io.opencensus.implcore.metrics; + +import static com.google.common.truth.Truth.assertThat; + +import io.opencensus.common.Timestamp; +import io.opencensus.common.ToDoubleFunction; +import io.opencensus.common.ToLongFunction; +import io.opencensus.metrics.DerivedDoubleGauge; +import io.opencensus.metrics.DerivedLongGauge; +import io.opencensus.metrics.DoubleGauge; +import io.opencensus.metrics.DoubleGauge.DoublePoint; +import io.opencensus.metrics.LabelKey; +import io.opencensus.metrics.LabelValue; +import io.opencensus.metrics.LongGauge; +import io.opencensus.metrics.LongGauge.LongPoint; +import io.opencensus.metrics.export.Metric; +import io.opencensus.metrics.export.MetricDescriptor; +import io.opencensus.metrics.export.MetricDescriptor.Type; +import io.opencensus.metrics.export.Point; +import io.opencensus.metrics.export.TimeSeries; +import io.opencensus.metrics.export.Value; +import io.opencensus.testing.common.TestClock; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Unit tests for {@link MetricRegistryImpl}. */ +@RunWith(JUnit4.class) +public class MetricRegistryImplTest { + @Rule public ExpectedException thrown = ExpectedException.none(); + + private static final String NAME = "name"; + private static final String NAME_2 = "name2"; + private static final String NAME_3 = "name3"; + private static final String NAME_4 = "name4"; + private static final String DESCRIPTION = "description"; + private static final String UNIT = "1"; + private static final List<LabelKey> LABEL_KEY = + Collections.singletonList(LabelKey.create("key", "key description")); + private static final List<LabelValue> LABEL_VALUES = + Collections.singletonList(LabelValue.create("value")); + + private static final Timestamp TEST_TIME = Timestamp.create(1234, 123); + private final TestClock testClock = TestClock.create(TEST_TIME); + private final MetricRegistryImpl metricRegistry = new MetricRegistryImpl(testClock); + + private static final MetricDescriptor LONG_METRIC_DESCRIPTOR = + MetricDescriptor.create(NAME, DESCRIPTION, UNIT, Type.GAUGE_INT64, LABEL_KEY); + private static final MetricDescriptor DOUBLE_METRIC_DESCRIPTOR = + MetricDescriptor.create(NAME_2, DESCRIPTION, UNIT, Type.GAUGE_DOUBLE, LABEL_KEY); + private static final MetricDescriptor DERIVED_LONG_METRIC_DESCRIPTOR = + MetricDescriptor.create(NAME_3, DESCRIPTION, UNIT, Type.GAUGE_INT64, LABEL_KEY); + private static final MetricDescriptor DERIVED_DOUBLE_METRIC_DESCRIPTOR = + MetricDescriptor.create(NAME_4, DESCRIPTION, UNIT, Type.GAUGE_DOUBLE, LABEL_KEY); + + private static final ToLongFunction<Object> longFunction = + new ToLongFunction<Object>() { + @Override + public long applyAsLong(Object value) { + return 5; + } + }; + private static final ToDoubleFunction<Object> doubleFunction = + new ToDoubleFunction<Object>() { + @Override + public double applyAsDouble(Object value) { + return 5.0; + } + }; + + @Test + public void addLongGauge_NullName() { + thrown.expect(NullPointerException.class); + thrown.expectMessage("name"); + metricRegistry.addLongGauge(null, DESCRIPTION, UNIT, LABEL_KEY); + } + + @Test + public void addLongGauge_NullDescription() { + thrown.expect(NullPointerException.class); + thrown.expectMessage("description"); + metricRegistry.addLongGauge(NAME, null, UNIT, LABEL_KEY); + } + + @Test + public void addLongGauge_NullUnit() { + thrown.expect(NullPointerException.class); + thrown.expectMessage("unit"); + metricRegistry.addLongGauge(NAME, DESCRIPTION, null, LABEL_KEY); + } + + @Test + public void addLongGauge_NullLabels() { + thrown.expect(NullPointerException.class); + thrown.expectMessage("labelKeys"); + metricRegistry.addLongGauge(NAME, DESCRIPTION, UNIT, null); + } + + @Test + public void addLongGauge_WithNullElement() { + List<LabelKey> labelKeys = Collections.singletonList(null); + thrown.expect(NullPointerException.class); + thrown.expectMessage("labelKey element should not be null."); + metricRegistry.addLongGauge(NAME, DESCRIPTION, UNIT, labelKeys); + } + + @Test + public void addDoubleGauge_NullName() { + thrown.expect(NullPointerException.class); + thrown.expectMessage("name"); + metricRegistry.addDoubleGauge(null, DESCRIPTION, UNIT, LABEL_KEY); + } + + @Test + public void addDoubleGauge_NullDescription() { + thrown.expect(NullPointerException.class); + thrown.expectMessage("description"); + metricRegistry.addDoubleGauge(NAME_2, null, UNIT, LABEL_KEY); + } + + @Test + public void addDoubleGauge_NullUnit() { + thrown.expect(NullPointerException.class); + thrown.expectMessage("unit"); + metricRegistry.addDoubleGauge(NAME_2, DESCRIPTION, null, LABEL_KEY); + } + + @Test + public void addDoubleGauge_NullLabels() { + thrown.expect(NullPointerException.class); + thrown.expectMessage("labelKeys"); + metricRegistry.addDoubleGauge(NAME_2, DESCRIPTION, UNIT, null); + } + + @Test + public void addDoubleGauge_WithNullElement() { + List<LabelKey> labelKeys = Collections.singletonList(null); + thrown.expect(NullPointerException.class); + thrown.expectMessage("labelKey element should not be null."); + metricRegistry.addDoubleGauge(NAME_2, DESCRIPTION, UNIT, labelKeys); + } + + @Test + public void addDerivedLongGauge_NullName() { + thrown.expect(NullPointerException.class); + thrown.expectMessage("name"); + metricRegistry.addDerivedLongGauge(null, DESCRIPTION, UNIT, LABEL_KEY); + } + + @Test + public void addDerivedLongGauge_NullDescription() { + thrown.expect(NullPointerException.class); + thrown.expectMessage("description"); + metricRegistry.addDerivedLongGauge(NAME_3, null, UNIT, LABEL_KEY); + } + + @Test + public void addDerivedLongGauge_NullUnit() { + thrown.expect(NullPointerException.class); + thrown.expectMessage("unit"); + metricRegistry.addDerivedLongGauge(NAME_3, DESCRIPTION, null, LABEL_KEY); + } + + @Test + public void addDerivedLongGauge_NullLabels() { + thrown.expect(NullPointerException.class); + thrown.expectMessage("labelKeys"); + metricRegistry.addDerivedLongGauge(NAME_3, DESCRIPTION, UNIT, null); + } + + @Test + public void addDerivedLongGauge_WithNullElement() { + List<LabelKey> labelKeys = Collections.singletonList(null); + thrown.expect(NullPointerException.class); + thrown.expectMessage("labelKey element should not be null."); + metricRegistry.addDerivedLongGauge(NAME_3, DESCRIPTION, UNIT, labelKeys); + } + + @Test + public void addDerivedDoubleGauge_NullName() { + thrown.expect(NullPointerException.class); + thrown.expectMessage("name"); + metricRegistry.addDerivedDoubleGauge(null, DESCRIPTION, UNIT, LABEL_KEY); + } + + @Test + public void addDerivedDoubleGauge_NullDescription() { + thrown.expect(NullPointerException.class); + thrown.expectMessage("description"); + metricRegistry.addDerivedDoubleGauge(NAME_4, null, UNIT, LABEL_KEY); + } + + @Test + public void addDerivedDoubleGauge_NullUnit() { + thrown.expect(NullPointerException.class); + thrown.expectMessage("unit"); + metricRegistry.addDerivedDoubleGauge(NAME_4, DESCRIPTION, null, LABEL_KEY); + } + + @Test + public void addDerivedDoubleGauge_NullLabels() { + thrown.expect(NullPointerException.class); + thrown.expectMessage("labelKeys"); + metricRegistry.addDerivedDoubleGauge(NAME_4, DESCRIPTION, UNIT, null); + } + + @Test + public void addDerivedDoubleGauge_WithNullElement() { + List<LabelKey> labelKeys = Collections.singletonList(null); + thrown.expect(NullPointerException.class); + thrown.expectMessage("labelKey element should not be null."); + metricRegistry.addDerivedDoubleGauge(NAME_4, DESCRIPTION, UNIT, labelKeys); + } + + @Test + public void addLongGauge_GetMetrics() { + LongGauge longGauge = metricRegistry.addLongGauge(NAME, DESCRIPTION, UNIT, LABEL_KEY); + longGauge.getOrCreateTimeSeries(LABEL_VALUES); + + Collection<Metric> metricCollections = metricRegistry.getMetricProducer().getMetrics(); + assertThat(metricCollections.size()).isEqualTo(1); + assertThat(metricCollections) + .containsExactly( + Metric.createWithOneTimeSeries( + LONG_METRIC_DESCRIPTOR, + TimeSeries.createWithOnePoint( + LABEL_VALUES, Point.create(Value.longValue(0), TEST_TIME), null))); + } + + @Test + public void addDoubleGauge_GetMetrics() { + DoubleGauge doubleGauge = metricRegistry.addDoubleGauge(NAME_2, DESCRIPTION, UNIT, LABEL_KEY); + doubleGauge.getOrCreateTimeSeries(LABEL_VALUES); + Collection<Metric> metricCollections = metricRegistry.getMetricProducer().getMetrics(); + assertThat(metricCollections.size()).isEqualTo(1); + assertThat(metricCollections) + .containsExactly( + Metric.createWithOneTimeSeries( + DOUBLE_METRIC_DESCRIPTOR, + TimeSeries.createWithOnePoint( + LABEL_VALUES, Point.create(Value.doubleValue(0.0), TEST_TIME), null))); + } + + @Test + public void addDerivedLongGauge_GetMetrics() { + DerivedLongGauge derivedLongGauge = + metricRegistry.addDerivedLongGauge(NAME_3, DESCRIPTION, UNIT, LABEL_KEY); + derivedLongGauge.createTimeSeries(LABEL_VALUES, null, longFunction); + Collection<Metric> metricCollections = metricRegistry.getMetricProducer().getMetrics(); + assertThat(metricCollections.size()).isEqualTo(1); + assertThat(metricCollections) + .containsExactly( + Metric.createWithOneTimeSeries( + DERIVED_LONG_METRIC_DESCRIPTOR, + TimeSeries.createWithOnePoint( + LABEL_VALUES, Point.create(Value.longValue(5), TEST_TIME), null))); + } + + @Test + public void addDerivedDoubleGauge_GetMetrics() { + DerivedDoubleGauge derivedDoubleGauge = + metricRegistry.addDerivedDoubleGauge(NAME_4, DESCRIPTION, UNIT, LABEL_KEY); + derivedDoubleGauge.createTimeSeries(LABEL_VALUES, null, doubleFunction); + Collection<Metric> metricCollections = metricRegistry.getMetricProducer().getMetrics(); + assertThat(metricCollections.size()).isEqualTo(1); + assertThat(metricCollections) + .containsExactly( + Metric.createWithOneTimeSeries( + DERIVED_DOUBLE_METRIC_DESCRIPTOR, + TimeSeries.createWithOnePoint( + LABEL_VALUES, Point.create(Value.doubleValue(5.0), TEST_TIME), null))); + } + + @Test + public void empty_GetMetrics() { + assertThat(metricRegistry.getMetricProducer().getMetrics()).isEmpty(); + } + + @Test + public void checkInstanceOf() { + assertThat(metricRegistry.addLongGauge(NAME, DESCRIPTION, UNIT, LABEL_KEY)) + .isInstanceOf(LongGaugeImpl.class); + assertThat(metricRegistry.addDoubleGauge(NAME_2, DESCRIPTION, UNIT, LABEL_KEY)) + .isInstanceOf(DoubleGaugeImpl.class); + assertThat(metricRegistry.addDerivedLongGauge(NAME_3, DESCRIPTION, UNIT, LABEL_KEY)) + .isInstanceOf(DerivedLongGaugeImpl.class); + assertThat(metricRegistry.addDerivedDoubleGauge(NAME_4, DESCRIPTION, UNIT, LABEL_KEY)) + .isInstanceOf(DerivedDoubleGaugeImpl.class); + } + + @Test + public void getMetrics() { + LongGauge longGauge = metricRegistry.addLongGauge(NAME, DESCRIPTION, UNIT, LABEL_KEY); + LongPoint longPoint = longGauge.getOrCreateTimeSeries(LABEL_VALUES); + longPoint.set(200); + DoubleGauge doubleGauge = metricRegistry.addDoubleGauge(NAME_2, DESCRIPTION, UNIT, LABEL_KEY); + DoublePoint doublePoint = doubleGauge.getOrCreateTimeSeries(LABEL_VALUES); + doublePoint.set(-300.13); + DerivedLongGauge derivedLongGauge = + metricRegistry.addDerivedLongGauge(NAME_3, DESCRIPTION, UNIT, LABEL_KEY); + derivedLongGauge.createTimeSeries(LABEL_VALUES, null, longFunction); + DerivedDoubleGauge derivedDoubleGauge = + metricRegistry.addDerivedDoubleGauge(NAME_4, DESCRIPTION, UNIT, LABEL_KEY); + derivedDoubleGauge.createTimeSeries(LABEL_VALUES, null, doubleFunction); + + Collection<Metric> metricCollections = metricRegistry.getMetricProducer().getMetrics(); + assertThat(metricCollections.size()).isEqualTo(4); + assertThat(metricCollections) + .containsExactly( + Metric.createWithOneTimeSeries( + LONG_METRIC_DESCRIPTOR, + TimeSeries.createWithOnePoint( + LABEL_VALUES, Point.create(Value.longValue(200), TEST_TIME), null)), + Metric.createWithOneTimeSeries( + DOUBLE_METRIC_DESCRIPTOR, + TimeSeries.createWithOnePoint( + LABEL_VALUES, Point.create(Value.doubleValue(-300.13), TEST_TIME), null)), + Metric.createWithOneTimeSeries( + DERIVED_LONG_METRIC_DESCRIPTOR, + TimeSeries.createWithOnePoint( + LABEL_VALUES, Point.create(Value.longValue(5), TEST_TIME), null)), + Metric.createWithOneTimeSeries( + DERIVED_DOUBLE_METRIC_DESCRIPTOR, + TimeSeries.createWithOnePoint( + LABEL_VALUES, Point.create(Value.doubleValue(5.0), TEST_TIME), null))); + } + + @Test + public void registerDifferentMetricSameName() { + metricRegistry.addLongGauge(NAME, DESCRIPTION, UNIT, LABEL_KEY); + thrown.expect(IllegalArgumentException.class); + thrown.expectMessage("A different metric with the same name already registered."); + metricRegistry.addDoubleGauge(NAME, DESCRIPTION, UNIT, LABEL_KEY); + } +} diff --git a/impl_core/src/test/java/io/opencensus/implcore/metrics/MetricsComponentImplBaseTest.java b/impl_core/src/test/java/io/opencensus/implcore/metrics/MetricsComponentImplBaseTest.java new file mode 100644 index 00000000..7f8515d3 --- /dev/null +++ b/impl_core/src/test/java/io/opencensus/implcore/metrics/MetricsComponentImplBaseTest.java @@ -0,0 +1,53 @@ +/* + * Copyright 2018, OpenCensus Authors + * + * 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 io.opencensus.implcore.metrics; + +import static com.google.common.truth.Truth.assertThat; + +import io.opencensus.implcore.metrics.export.ExportComponentImpl; +import io.opencensus.testing.common.TestClock; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Unit tests for {@link MetricsComponentImplBase}. */ +@RunWith(JUnit4.class) +public class MetricsComponentImplBaseTest { + private final MetricsComponentImplBase metricsComponentImplBase = + new MetricsComponentImplBase(TestClock.create()); + + @Test + public void getExportComponent() { + assertThat(metricsComponentImplBase.getExportComponent()) + .isInstanceOf(ExportComponentImpl.class); + } + + @Test + public void getMetricRegistry() { + assertThat(metricsComponentImplBase.getMetricRegistry()).isInstanceOf(MetricRegistryImpl.class); + } + + @Test + public void metricRegistry_InstalledToMetricProducerManger() { + assertThat( + metricsComponentImplBase + .getExportComponent() + .getMetricProducerManager() + .getAllMetricProducer()) + .containsExactly(metricsComponentImplBase.getMetricRegistry().getMetricProducer()); + } +} diff --git a/impl_core/src/test/java/io/opencensus/implcore/metrics/export/ExportComponentImplTest.java b/impl_core/src/test/java/io/opencensus/implcore/metrics/export/ExportComponentImplTest.java new file mode 100644 index 00000000..fb91641c --- /dev/null +++ b/impl_core/src/test/java/io/opencensus/implcore/metrics/export/ExportComponentImplTest.java @@ -0,0 +1,35 @@ +/* + * Copyright 2018, OpenCensus Authors + * + * 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 io.opencensus.implcore.metrics.export; + +import static com.google.common.truth.Truth.assertThat; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Unit tests for {@link ExportComponentImpl}. */ +@RunWith(JUnit4.class) +public class ExportComponentImplTest { + + @Test + public void getMetricProducerManager() { + ExportComponentImpl exportComponent = new ExportComponentImpl(); + assertThat(exportComponent.getMetricProducerManager()) + .isInstanceOf(MetricProducerManagerImpl.class); + } +} diff --git a/impl_core/src/test/java/io/opencensus/implcore/metrics/export/MetricProducerManagerImplTest.java b/impl_core/src/test/java/io/opencensus/implcore/metrics/export/MetricProducerManagerImplTest.java new file mode 100644 index 00000000..e549dadb --- /dev/null +++ b/impl_core/src/test/java/io/opencensus/implcore/metrics/export/MetricProducerManagerImplTest.java @@ -0,0 +1,115 @@ +/* + * Copyright 2018, OpenCensus Authors + * + * 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 io.opencensus.implcore.metrics.export; + +import static com.google.common.truth.Truth.assertThat; + +import io.opencensus.metrics.export.MetricProducer; +import io.opencensus.metrics.export.MetricProducerManager; +import java.util.Set; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +/** Unit tests for {@link MetricProducerManagerImpl}. */ +@RunWith(JUnit4.class) +public class MetricProducerManagerImplTest { + + private final MetricProducerManager metricProducerManager = new MetricProducerManagerImpl(); + @Mock private MetricProducer metricProducer; + @Mock private MetricProducer metricProducerOther; + + @Rule public final ExpectedException thrown = ExpectedException.none(); + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + } + + @Test + public void add_DisallowsNull() { + thrown.expect(NullPointerException.class); + metricProducerManager.add(null); + } + + @Test + public void add() { + metricProducerManager.add(metricProducer); + assertThat(metricProducerManager.getAllMetricProducer()).containsExactly(metricProducer); + } + + @Test + public void add_DuplicateElement() { + metricProducerManager.add(metricProducer); + Set<MetricProducer> metricProducerSet = metricProducerManager.getAllMetricProducer(); + assertThat(metricProducerSet).containsExactly(metricProducer); + metricProducerManager.add(metricProducer); + // Returns the same object. + assertThat(metricProducerManager.getAllMetricProducer()).isSameAs(metricProducerSet); + } + + @Test + public void add_MultipleElements() { + metricProducerManager.add(metricProducer); + Set<MetricProducer> metricProducerSet = metricProducerManager.getAllMetricProducer(); + assertThat(metricProducerSet).containsExactly(metricProducer); + metricProducerManager.add(metricProducerOther); + // Returns the same object. + assertThat(metricProducerManager.getAllMetricProducer()) + .containsExactly(metricProducer, metricProducerOther); + } + + @Test + public void addAndRemove() { + metricProducerManager.add(metricProducer); + assertThat(metricProducerManager.getAllMetricProducer()).containsExactly(metricProducer); + metricProducerManager.remove(metricProducer); + assertThat(metricProducerManager.getAllMetricProducer()).isEmpty(); + } + + @Test + public void remove_DisallowsNull() { + thrown.expect(NullPointerException.class); + metricProducerManager.remove(null); + } + + @Test + public void remove_FromEmpty() { + metricProducerManager.remove(metricProducer); + assertThat(metricProducerManager.getAllMetricProducer()).isEmpty(); + } + + @Test + public void remove_NotPresent() { + metricProducerManager.add(metricProducer); + Set<MetricProducer> metricProducerSet = metricProducerManager.getAllMetricProducer(); + assertThat(metricProducerSet).containsExactly(metricProducer); + metricProducerManager.remove(metricProducerOther); + // Returns the same object. + assertThat(metricProducerManager.getAllMetricProducer()).isSameAs(metricProducerSet); + } + + @Test + public void getAllMetricProducer_empty() { + assertThat(metricProducerManager.getAllMetricProducer()).isEmpty(); + } +} diff --git a/impl_core/src/test/java/io/opencensus/implcore/stats/IntervalBucketTest.java b/impl_core/src/test/java/io/opencensus/implcore/stats/IntervalBucketTest.java new file mode 100644 index 00000000..39a53e1a --- /dev/null +++ b/impl_core/src/test/java/io/opencensus/implcore/stats/IntervalBucketTest.java @@ -0,0 +1,133 @@ +/* + * Copyright 2017, OpenCensus Authors + * + * 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 io.opencensus.implcore.stats; + +import static com.google.common.truth.Truth.assertThat; + +import io.opencensus.common.Duration; +import io.opencensus.common.Timestamp; +import io.opencensus.implcore.stats.MutableAggregation.MutableMean; +import io.opencensus.stats.Aggregation.Mean; +import io.opencensus.stats.Measure.MeasureDouble; +import io.opencensus.tags.TagValue; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Tests for {@link IntervalBucket}. */ +@RunWith(JUnit4.class) +public class IntervalBucketTest { + + @Rule public final ExpectedException thrown = ExpectedException.none(); + + private static final double TOLERANCE = 1e-6; + private static final MeasureDouble MEASURE_DOUBLE = + MeasureDouble.create("measure1", "description", "1"); + private static final Duration MINUTE = Duration.create(60, 0); + private static final Duration NEGATIVE_TEN_SEC = Duration.create(-10, 0); + private static final Timestamp START = Timestamp.create(60, 0); + private static final Mean MEAN = Mean.create(); + + @Test + public void preventNullStartTime() { + thrown.expect(NullPointerException.class); + new IntervalBucket(null, MINUTE, MEAN, MEASURE_DOUBLE); + } + + @Test + public void preventNullDuration() { + thrown.expect(NullPointerException.class); + new IntervalBucket(START, null, MEAN, MEASURE_DOUBLE); + } + + @Test + public void preventNegativeDuration() { + thrown.expect(IllegalArgumentException.class); + new IntervalBucket(START, NEGATIVE_TEN_SEC, MEAN, MEASURE_DOUBLE); + } + + @Test + public void preventNullAggregation() { + thrown.expect(NullPointerException.class); + new IntervalBucket(START, MINUTE, null, MEASURE_DOUBLE); + } + + @Test + public void preventNullMeasure() { + thrown.expect(NullPointerException.class); + new IntervalBucket(START, MINUTE, MEAN, null); + } + + @Test + public void testGetTagValueAggregationMap_empty() { + assertThat(new IntervalBucket(START, MINUTE, MEAN, MEASURE_DOUBLE).getTagValueAggregationMap()) + .isEmpty(); + } + + @Test + public void testGetStart() { + assertThat(new IntervalBucket(START, MINUTE, MEAN, MEASURE_DOUBLE).getStart()).isEqualTo(START); + } + + @Test + public void testRecord() { + IntervalBucket bucket = new IntervalBucket(START, MINUTE, MEAN, MEASURE_DOUBLE); + List<TagValue> tagValues1 = Arrays.<TagValue>asList(TagValue.create("VALUE1")); + List<TagValue> tagValues2 = Arrays.<TagValue>asList(TagValue.create("VALUE2")); + bucket.record(tagValues1, 5.0, Collections.<String, String>emptyMap(), START); + bucket.record(tagValues1, 15.0, Collections.<String, String>emptyMap(), START); + bucket.record(tagValues2, 10.0, Collections.<String, String>emptyMap(), START); + assertThat(bucket.getTagValueAggregationMap().keySet()).containsExactly(tagValues1, tagValues2); + MutableMean mutableMean1 = (MutableMean) bucket.getTagValueAggregationMap().get(tagValues1); + MutableMean mutableMean2 = (MutableMean) bucket.getTagValueAggregationMap().get(tagValues2); + assertThat(mutableMean1.getSum()).isWithin(TOLERANCE).of(20); + assertThat(mutableMean2.getSum()).isWithin(TOLERANCE).of(10); + assertThat(mutableMean1.getCount()).isEqualTo(2); + assertThat(mutableMean2.getCount()).isEqualTo(1); + } + + @Test + public void testGetFraction() { + Timestamp thirtySecondsAfterStart = Timestamp.create(90, 0); + assertThat( + new IntervalBucket(START, MINUTE, MEAN, MEASURE_DOUBLE) + .getFraction(thirtySecondsAfterStart)) + .isWithin(TOLERANCE) + .of(0.5); + } + + @Test + public void preventCallingGetFractionOnPastBuckets() { + IntervalBucket bucket = new IntervalBucket(START, MINUTE, MEAN, MEASURE_DOUBLE); + Timestamp twoMinutesAfterStart = Timestamp.create(180, 0); + thrown.expect(IllegalArgumentException.class); + bucket.getFraction(twoMinutesAfterStart); + } + + @Test + public void preventCallingGetFractionOnFutureBuckets() { + IntervalBucket bucket = new IntervalBucket(START, MINUTE, MEAN, MEASURE_DOUBLE); + Timestamp thirtySecondsBeforeStart = Timestamp.create(30, 0); + thrown.expect(IllegalArgumentException.class); + bucket.getFraction(thirtySecondsBeforeStart); + } +} diff --git a/impl_core/src/test/java/io/opencensus/implcore/stats/MeasureMapInternalTest.java b/impl_core/src/test/java/io/opencensus/implcore/stats/MeasureMapInternalTest.java new file mode 100644 index 00000000..19e8a6c5 --- /dev/null +++ b/impl_core/src/test/java/io/opencensus/implcore/stats/MeasureMapInternalTest.java @@ -0,0 +1,159 @@ +/* + * Copyright 2016-17, OpenCensus Authors + * + * 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 io.opencensus.implcore.stats; + +import static com.google.common.truth.Truth.assertThat; + +import com.google.common.collect.Lists; +import io.opencensus.stats.Measure; +import io.opencensus.stats.Measure.MeasureDouble; +import io.opencensus.stats.Measure.MeasureLong; +import io.opencensus.stats.Measurement; +import io.opencensus.stats.Measurement.MeasurementDouble; +import io.opencensus.stats.Measurement.MeasurementLong; +import java.util.ArrayList; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Tests for {@link MeasureMapInternal}. */ +@RunWith(JUnit4.class) +public class MeasureMapInternalTest { + + @Test + public void testPutDouble() { + MeasureMapInternal metrics = MeasureMapInternal.builder().put(M1, 44.4).build(); + assertContains(metrics, MeasurementDouble.create(M1, 44.4)); + } + + @Test + public void testPutLong() { + MeasureMapInternal metrics = MeasureMapInternal.builder().put(M3, 9999L).put(M4, 8888L).build(); + assertContains(metrics, MeasurementLong.create(M3, 9999L), MeasurementLong.create(M4, 8888L)); + } + + @Test + public void testPutAttachment() { + MeasureMapInternal metrics = + MeasureMapInternal.builder() + .putAttachment("k1", "v1") + .putAttachment("k2", "v2") + .putAttachment("k1", "v3") + .build(); + assertThat(metrics.getAttachments()).containsExactly("k1", "v3", "k2", "v2"); + assertContains(metrics); + } + + @Test + public void testCombination() { + MeasureMapInternal metrics = + MeasureMapInternal.builder() + .put(M1, 44.4) + .put(M2, 66.6) + .put(M3, 9999L) + .put(M4, 8888L) + .build(); + assertContains( + metrics, + MeasurementDouble.create(M1, 44.4), + MeasurementDouble.create(M2, 66.6), + MeasurementLong.create(M3, 9999L), + MeasurementLong.create(M4, 8888L)); + } + + @Test + public void testBuilderEmpty() { + MeasureMapInternal metrics = MeasureMapInternal.builder().build(); + assertContains(metrics); + } + + @Test + public void testBuilder() { + ArrayList<Measurement> expected = new ArrayList<Measurement>(10); + MeasureMapInternal.Builder builder = MeasureMapInternal.builder(); + for (int i = 1; i <= 10; i++) { + expected.add(MeasurementDouble.create(makeSimpleMeasureDouble("m" + i), i * 11.1)); + builder.put(makeSimpleMeasureDouble("m" + i), i * 11.1); + assertContains(builder.build(), expected.toArray(new Measurement[i])); + } + } + + @Test + public void testDuplicateMeasureDoubles() { + assertContains( + MeasureMapInternal.builder().put(M1, 1.0).put(M1, 2.0).build(), + MeasurementDouble.create(M1, 2.0)); + assertContains( + MeasureMapInternal.builder().put(M1, 1.0).put(M1, 2.0).put(M1, 3.0).build(), + MeasurementDouble.create(M1, 3.0)); + assertContains( + MeasureMapInternal.builder().put(M1, 1.0).put(M2, 2.0).put(M1, 3.0).build(), + MeasurementDouble.create(M1, 3.0), + MeasurementDouble.create(M2, 2.0)); + assertContains( + MeasureMapInternal.builder().put(M1, 1.0).put(M1, 2.0).put(M2, 2.0).build(), + MeasurementDouble.create(M1, 2.0), + MeasurementDouble.create(M2, 2.0)); + } + + @Test + public void testDuplicateMeasureLongs() { + assertContains( + MeasureMapInternal.builder().put(M3, 100L).put(M3, 100L).build(), + MeasurementLong.create(M3, 100L)); + assertContains( + MeasureMapInternal.builder().put(M3, 100L).put(M3, 200L).put(M3, 300L).build(), + MeasurementLong.create(M3, 300L)); + assertContains( + MeasureMapInternal.builder().put(M3, 100L).put(M4, 200L).put(M3, 300L).build(), + MeasurementLong.create(M3, 300L), + MeasurementLong.create(M4, 200L)); + assertContains( + MeasureMapInternal.builder().put(M3, 100L).put(M3, 200L).put(M4, 200L).build(), + MeasurementLong.create(M3, 200L), + MeasurementLong.create(M4, 200L)); + } + + @Test + public void testDuplicateMeasures() { + assertContains( + MeasureMapInternal.builder().put(M3, 100L).put(M1, 1.0).put(M3, 300L).build(), + MeasurementLong.create(M3, 300L), + MeasurementDouble.create(M1, 1.0)); + assertContains( + MeasureMapInternal.builder().put(M2, 2.0).put(M3, 100L).put(M2, 3.0).build(), + MeasurementDouble.create(M2, 3.0), + MeasurementLong.create(M3, 100L)); + } + + private static final MeasureDouble M1 = makeSimpleMeasureDouble("m1"); + private static final MeasureDouble M2 = makeSimpleMeasureDouble("m2"); + private static final MeasureLong M3 = makeSimpleMeasureLong("m3"); + private static final MeasureLong M4 = makeSimpleMeasureLong("m4"); + + private static MeasureDouble makeSimpleMeasureDouble(String measure) { + return Measure.MeasureDouble.create(measure, measure + " description", "1"); + } + + private static MeasureLong makeSimpleMeasureLong(String measure) { + return Measure.MeasureLong.create(measure, measure + " description", "1"); + } + + private static void assertContains(MeasureMapInternal metrics, Measurement... measurements) { + assertThat(Lists.newArrayList(metrics.iterator())).containsExactly((Object[]) measurements); + } +} diff --git a/impl_core/src/test/java/io/opencensus/implcore/stats/MeasureToViewMapTest.java b/impl_core/src/test/java/io/opencensus/implcore/stats/MeasureToViewMapTest.java new file mode 100644 index 00000000..25f33a94 --- /dev/null +++ b/impl_core/src/test/java/io/opencensus/implcore/stats/MeasureToViewMapTest.java @@ -0,0 +1,69 @@ +/* + * Copyright 2017, OpenCensus Authors + * + * 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 io.opencensus.implcore.stats; + +import static com.google.common.truth.Truth.assertThat; + +import io.opencensus.common.Timestamp; +import io.opencensus.implcore.internal.CurrentState.State; +import io.opencensus.stats.Aggregation.Mean; +import io.opencensus.stats.Measure; +import io.opencensus.stats.View; +import io.opencensus.stats.View.AggregationWindow.Cumulative; +import io.opencensus.stats.View.Name; +import io.opencensus.stats.ViewData; +import io.opencensus.stats.ViewData.AggregationWindowData.CumulativeData; +import io.opencensus.tags.TagKey; +import io.opencensus.testing.common.TestClock; +import java.util.Arrays; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Tests for {@link MeasureToViewMap}. */ +@RunWith(JUnit4.class) +public class MeasureToViewMapTest { + + private static final Measure MEASURE = + Measure.MeasureDouble.create("my measurement", "measurement description", "By"); + + private static final Name VIEW_NAME = View.Name.create("my view"); + + private static final Cumulative CUMULATIVE = Cumulative.create(); + + private static final View VIEW = + View.create( + VIEW_NAME, + "view description", + MEASURE, + Mean.create(), + Arrays.asList(TagKey.create("my key")), + CUMULATIVE); + + @Test + public void testRegisterAndGetView() { + MeasureToViewMap measureToViewMap = new MeasureToViewMap(); + TestClock clock = TestClock.create(Timestamp.create(10, 20)); + measureToViewMap.registerView(VIEW, clock); + clock.setTime(Timestamp.create(30, 40)); + ViewData viewData = measureToViewMap.getView(VIEW_NAME, clock, State.ENABLED); + assertThat(viewData.getView()).isEqualTo(VIEW); + assertThat(viewData.getWindowData()) + .isEqualTo(CumulativeData.create(Timestamp.create(10, 20), Timestamp.create(30, 40))); + assertThat(viewData.getAggregationMap()).isEmpty(); + } +} diff --git a/impl_core/src/test/java/io/opencensus/implcore/stats/MetricUtilsTest.java b/impl_core/src/test/java/io/opencensus/implcore/stats/MetricUtilsTest.java new file mode 100644 index 00000000..66e971f6 --- /dev/null +++ b/impl_core/src/test/java/io/opencensus/implcore/stats/MetricUtilsTest.java @@ -0,0 +1,129 @@ +/* + * Copyright 2018, OpenCensus Authors + * + * 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 io.opencensus.implcore.stats; + +import static com.google.common.truth.Truth.assertThat; + +import io.opencensus.common.Duration; +import io.opencensus.common.Timestamp; +import io.opencensus.metrics.LabelKey; +import io.opencensus.metrics.LabelValue; +import io.opencensus.metrics.export.MetricDescriptor; +import io.opencensus.metrics.export.MetricDescriptor.Type; +import io.opencensus.stats.Aggregation.Count; +import io.opencensus.stats.Aggregation.Distribution; +import io.opencensus.stats.Aggregation.LastValue; +import io.opencensus.stats.Aggregation.Mean; +import io.opencensus.stats.Aggregation.Sum; +import io.opencensus.stats.BucketBoundaries; +import io.opencensus.stats.Measure.MeasureDouble; +import io.opencensus.stats.Measure.MeasureLong; +import io.opencensus.stats.View; +import io.opencensus.stats.View.AggregationWindow.Interval; +import io.opencensus.stats.View.Name; +import io.opencensus.tags.TagKey; +import io.opencensus.tags.TagValue; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Tests for {@link MetricUtils}. */ +@RunWith(JUnit4.class) +public class MetricUtilsTest { + + private static final TagKey KEY = TagKey.create("KEY"); + private static final TagValue VALUE = TagValue.create("VALUE"); + private static final TagValue VALUE_2 = TagValue.create("VALUE_2"); + private static final String MEASURE_NAME = "my measurement"; + private static final String MEASURE_NAME_2 = "my measurement 2"; + private static final String MEASURE_UNIT = "us"; + private static final String MEASURE_DESCRIPTION = "measure description"; + private static final MeasureDouble MEASURE_DOUBLE = + MeasureDouble.create(MEASURE_NAME, MEASURE_DESCRIPTION, MEASURE_UNIT); + private static final MeasureLong MEASURE_LONG = + MeasureLong.create(MEASURE_NAME_2, MEASURE_DESCRIPTION, MEASURE_UNIT); + private static final Name VIEW_NAME = Name.create("my view"); + private static final Name VIEW_NAME_2 = Name.create("my view 2"); + private static final String VIEW_DESCRIPTION = "view description"; + private static final Duration TEN_SECONDS = Duration.create(10, 0); + private static final Interval INTERVAL = Interval.create(TEN_SECONDS); + private static final BucketBoundaries BUCKET_BOUNDARIES = + BucketBoundaries.create(Arrays.asList(-10.0, 0.0, 10.0)); + private static final Sum SUM = Sum.create(); + private static final Count COUNT = Count.create(); + private static final Mean MEAN = Mean.create(); + private static final Distribution DISTRIBUTION = Distribution.create(BUCKET_BOUNDARIES); + private static final LastValue LAST_VALUE = LastValue.create(); + private static final View VIEW_1 = + View.create( + VIEW_NAME, VIEW_DESCRIPTION, MEASURE_DOUBLE, LAST_VALUE, Collections.singletonList(KEY)); + private static final View VIEW_2 = + View.create( + VIEW_NAME_2, + VIEW_DESCRIPTION, + MEASURE_DOUBLE, + MEAN, + Collections.singletonList(KEY), + INTERVAL); + private static final Timestamp TIMESTAMP = Timestamp.fromMillis(1000); + + @Test + public void viewToMetricDescriptor() { + MetricDescriptor metricDescriptor = MetricUtils.viewToMetricDescriptor(VIEW_1); + assertThat(metricDescriptor).isNotNull(); + assertThat(metricDescriptor.getName()).isEqualTo(VIEW_NAME.asString()); + assertThat(metricDescriptor.getUnit()).isEqualTo(MEASURE_UNIT); + assertThat(metricDescriptor.getType()).isEqualTo(Type.GAUGE_DOUBLE); + assertThat(metricDescriptor.getDescription()).isEqualTo(VIEW_DESCRIPTION); + assertThat(metricDescriptor.getLabelKeys()).containsExactly(LabelKey.create(KEY.getName(), "")); + } + + @Test + public void viewToMetricDescriptor_NoIntervalViews() { + MetricDescriptor metricDescriptor = MetricUtils.viewToMetricDescriptor(VIEW_2); + assertThat(metricDescriptor).isNull(); + } + + @Test + public void getType() { + assertThat(MetricUtils.getType(MEASURE_DOUBLE, LAST_VALUE)).isEqualTo(Type.GAUGE_DOUBLE); + assertThat(MetricUtils.getType(MEASURE_LONG, LAST_VALUE)).isEqualTo(Type.GAUGE_INT64); + assertThat(MetricUtils.getType(MEASURE_DOUBLE, SUM)).isEqualTo(Type.CUMULATIVE_DOUBLE); + assertThat(MetricUtils.getType(MEASURE_LONG, SUM)).isEqualTo(Type.CUMULATIVE_INT64); + assertThat(MetricUtils.getType(MEASURE_DOUBLE, MEAN)).isEqualTo(Type.CUMULATIVE_DOUBLE); + assertThat(MetricUtils.getType(MEASURE_LONG, MEAN)).isEqualTo(Type.CUMULATIVE_DOUBLE); + assertThat(MetricUtils.getType(MEASURE_DOUBLE, COUNT)).isEqualTo(Type.CUMULATIVE_INT64); + assertThat(MetricUtils.getType(MEASURE_LONG, COUNT)).isEqualTo(Type.CUMULATIVE_INT64); + assertThat(MetricUtils.getType(MEASURE_DOUBLE, DISTRIBUTION)) + .isEqualTo(Type.CUMULATIVE_DISTRIBUTION); + assertThat(MetricUtils.getType(MEASURE_LONG, DISTRIBUTION)) + .isEqualTo(Type.CUMULATIVE_DISTRIBUTION); + } + + @Test + public void tagValuesToLabelValues() { + List<TagValue> tagValues = Arrays.asList(VALUE, VALUE_2, null); + assertThat(MetricUtils.tagValuesToLabelValues(tagValues)) + .containsExactly( + LabelValue.create(VALUE.asString()), + LabelValue.create(VALUE_2.asString()), + LabelValue.create(null)); + } +} diff --git a/impl_core/src/test/java/io/opencensus/implcore/stats/MutableAggregationTest.java b/impl_core/src/test/java/io/opencensus/implcore/stats/MutableAggregationTest.java new file mode 100644 index 00000000..a6139e53 --- /dev/null +++ b/impl_core/src/test/java/io/opencensus/implcore/stats/MutableAggregationTest.java @@ -0,0 +1,339 @@ +/* + * Copyright 2017, OpenCensus Authors + * + * 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 io.opencensus.implcore.stats; + +import static com.google.common.truth.Truth.assertThat; +import static io.opencensus.implcore.stats.StatsTestUtil.assertAggregationDataEquals; + +import com.google.common.collect.ImmutableList; +import io.opencensus.common.Timestamp; +import io.opencensus.implcore.stats.MutableAggregation.MutableCount; +import io.opencensus.implcore.stats.MutableAggregation.MutableDistribution; +import io.opencensus.implcore.stats.MutableAggregation.MutableLastValueDouble; +import io.opencensus.implcore.stats.MutableAggregation.MutableLastValueLong; +import io.opencensus.implcore.stats.MutableAggregation.MutableMean; +import io.opencensus.implcore.stats.MutableAggregation.MutableSumDouble; +import io.opencensus.implcore.stats.MutableAggregation.MutableSumLong; +import io.opencensus.metrics.export.Distribution; +import io.opencensus.metrics.export.Distribution.Bucket; +import io.opencensus.metrics.export.Distribution.BucketOptions; +import io.opencensus.metrics.export.Point; +import io.opencensus.metrics.export.Value; +import io.opencensus.stats.AggregationData; +import io.opencensus.stats.AggregationData.CountData; +import io.opencensus.stats.AggregationData.DistributionData; +import io.opencensus.stats.AggregationData.DistributionData.Exemplar; +import io.opencensus.stats.AggregationData.LastValueDataDouble; +import io.opencensus.stats.AggregationData.LastValueDataLong; +import io.opencensus.stats.AggregationData.MeanData; +import io.opencensus.stats.AggregationData.SumDataDouble; +import io.opencensus.stats.AggregationData.SumDataLong; +import io.opencensus.stats.BucketBoundaries; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Unit tests for {@link io.opencensus.implcore.stats.MutableAggregation}. */ +@RunWith(JUnit4.class) +public class MutableAggregationTest { + + @Rule public ExpectedException thrown = ExpectedException.none(); + + private static final double TOLERANCE = 1e-6; + private static final BucketBoundaries BUCKET_BOUNDARIES = + BucketBoundaries.create(Arrays.asList(-10.0, 0.0, 10.0)); + private static final BucketBoundaries BUCKET_BOUNDARIES_EMPTY = + BucketBoundaries.create(Collections.<Double>emptyList()); + private static final Timestamp TIMESTAMP = Timestamp.create(60, 0); + + @Test + public void testCreateEmpty() { + assertThat(MutableSumDouble.create().getSum()).isWithin(TOLERANCE).of(0); + assertThat(MutableSumLong.create().getSum()).isWithin(TOLERANCE).of(0); + assertThat(MutableCount.create().getCount()).isEqualTo(0); + assertThat(MutableMean.create().getMean()).isWithin(TOLERANCE).of(0); + assertThat(MutableLastValueDouble.create().getLastValue()).isNaN(); + assertThat(MutableLastValueLong.create().getLastValue()).isNaN(); + + BucketBoundaries bucketBoundaries = BucketBoundaries.create(Arrays.asList(0.1, 2.2, 33.3)); + MutableDistribution mutableDistribution = MutableDistribution.create(bucketBoundaries); + assertThat(mutableDistribution.getMean()).isWithin(TOLERANCE).of(0); + assertThat(mutableDistribution.getCount()).isEqualTo(0); + assertThat(mutableDistribution.getMin()).isPositiveInfinity(); + assertThat(mutableDistribution.getMax()).isNegativeInfinity(); + assertThat(mutableDistribution.getSumOfSquaredDeviations()).isWithin(TOLERANCE).of(0); + assertThat(mutableDistribution.getBucketCounts()).isEqualTo(new long[4]); + assertThat(mutableDistribution.getExemplars()).isEqualTo(new Exemplar[4]); + + MutableDistribution mutableDistributionNoHistogram = + MutableDistribution.create(BUCKET_BOUNDARIES_EMPTY); + assertThat(mutableDistributionNoHistogram.getExemplars()).isNull(); + } + + @Test + public void testNullBucketBoundaries() { + thrown.expect(NullPointerException.class); + thrown.expectMessage("bucketBoundaries should not be null."); + MutableDistribution.create(null); + } + + @Test + public void testNoBoundaries() { + List<Double> buckets = Arrays.asList(); + MutableDistribution noBoundaries = MutableDistribution.create(BucketBoundaries.create(buckets)); + assertThat(noBoundaries.getBucketCounts().length).isEqualTo(1); + assertThat(noBoundaries.getBucketCounts()[0]).isEqualTo(0); + } + + @Test + public void testAdd() { + List<MutableAggregation> aggregations = + Arrays.asList( + MutableSumDouble.create(), + MutableSumLong.create(), + MutableCount.create(), + MutableMean.create(), + MutableDistribution.create(BUCKET_BOUNDARIES), + MutableLastValueDouble.create(), + MutableLastValueLong.create()); + + List<Double> values = Arrays.asList(-1.0, 1.0, -5.0, 20.0, 5.0); + + for (double value : values) { + for (MutableAggregation aggregation : aggregations) { + aggregation.add(value, Collections.<String, String>emptyMap(), TIMESTAMP); + } + } + + assertAggregationDataEquals( + aggregations.get(0).toAggregationData(), + AggregationData.SumDataDouble.create(20.0), + TOLERANCE); + assertAggregationDataEquals( + aggregations.get(1).toAggregationData(), AggregationData.SumDataLong.create(20), TOLERANCE); + assertAggregationDataEquals( + aggregations.get(2).toAggregationData(), AggregationData.CountData.create(5), TOLERANCE); + assertAggregationDataEquals( + aggregations.get(3).toAggregationData(), + AggregationData.MeanData.create(4.0, 5), + TOLERANCE); + assertAggregationDataEquals( + aggregations.get(4).toAggregationData(), + AggregationData.DistributionData.create( + 4.0, 5, -5.0, 20.0, 372, Arrays.asList(0L, 2L, 2L, 1L)), + TOLERANCE); + assertAggregationDataEquals( + aggregations.get(5).toAggregationData(), + AggregationData.LastValueDataDouble.create(5.0), + TOLERANCE); + assertAggregationDataEquals( + aggregations.get(6).toAggregationData(), + AggregationData.LastValueDataLong.create(5), + TOLERANCE); + } + + @Test + public void testAdd_DistributionWithExemplarAttachments() { + MutableDistribution mutableDistribution = MutableDistribution.create(BUCKET_BOUNDARIES); + MutableDistribution mutableDistributionNoHistogram = + MutableDistribution.create(BUCKET_BOUNDARIES_EMPTY); + List<Double> values = Arrays.asList(-1.0, 1.0, -5.0, 20.0, 5.0); + List<Map<String, String>> attachmentsList = + ImmutableList.<Map<String, String>>of( + Collections.<String, String>singletonMap("k1", "v1"), + Collections.<String, String>singletonMap("k2", "v2"), + Collections.<String, String>singletonMap("k3", "v3"), + Collections.<String, String>singletonMap("k4", "v4"), + Collections.<String, String>singletonMap("k5", "v5")); + List<Timestamp> timestamps = + Arrays.asList( + Timestamp.fromMillis(500), + Timestamp.fromMillis(1000), + Timestamp.fromMillis(2000), + Timestamp.fromMillis(3000), + Timestamp.fromMillis(4000)); + for (int i = 0; i < values.size(); i++) { + mutableDistribution.add(values.get(i), attachmentsList.get(i), timestamps.get(i)); + mutableDistributionNoHistogram.add(values.get(i), attachmentsList.get(i), timestamps.get(i)); + } + + // Each bucket can only have up to one exemplar. If there are more than one exemplars in a + // bucket, only the last one will be kept. + List<Exemplar> expected = + Arrays.<Exemplar>asList( + null, + Exemplar.create(values.get(2), timestamps.get(2), attachmentsList.get(2)), + Exemplar.create(values.get(4), timestamps.get(4), attachmentsList.get(4)), + Exemplar.create(values.get(3), timestamps.get(3), attachmentsList.get(3))); + assertThat(mutableDistribution.getExemplars()) + .asList() + .containsExactlyElementsIn(expected) + .inOrder(); + assertThat(mutableDistributionNoHistogram.getExemplars()).isNull(); + } + + @Test + public void testCombine_SumCountMean() { + // combine() for Mutable Sum, Count and Mean will pick up fractional stats + List<MutableAggregation> aggregations1 = + Arrays.asList( + MutableSumDouble.create(), + MutableSumLong.create(), + MutableCount.create(), + MutableMean.create()); + List<MutableAggregation> aggregations2 = + Arrays.asList( + MutableSumDouble.create(), + MutableSumLong.create(), + MutableCount.create(), + MutableMean.create()); + + for (double val : Arrays.asList(-1.0, -5.0)) { + for (MutableAggregation aggregation : aggregations1) { + aggregation.add(val, Collections.<String, String>emptyMap(), TIMESTAMP); + } + } + for (double val : Arrays.asList(10.0, 50.0)) { + for (MutableAggregation aggregation : aggregations2) { + aggregation.add(val, Collections.<String, String>emptyMap(), TIMESTAMP); + } + } + + List<MutableAggregation> combined = + Arrays.asList( + MutableSumDouble.create(), + MutableSumLong.create(), + MutableCount.create(), + MutableMean.create()); + double fraction1 = 1.0; + double fraction2 = 0.6; + for (int i = 0; i < combined.size(); i++) { + combined.get(i).combine(aggregations1.get(i), fraction1); + combined.get(i).combine(aggregations2.get(i), fraction2); + } + + assertThat(((MutableSumDouble) combined.get(0)).getSum()).isWithin(TOLERANCE).of(30); + assertThat(((MutableSumLong) combined.get(1)).getSum()).isWithin(TOLERANCE).of(30); + assertThat(((MutableCount) combined.get(2)).getCount()).isEqualTo(3); + assertThat(((MutableMean) combined.get(3)).getMean()).isWithin(TOLERANCE).of(10); + } + + @Test + public void testCombine_Distribution() { + // combine() for Mutable Distribution will ignore fractional stats + MutableDistribution distribution1 = MutableDistribution.create(BUCKET_BOUNDARIES); + MutableDistribution distribution2 = MutableDistribution.create(BUCKET_BOUNDARIES); + MutableDistribution distribution3 = MutableDistribution.create(BUCKET_BOUNDARIES); + + for (double val : Arrays.asList(5.0, -5.0)) { + distribution1.add(val, Collections.<String, String>emptyMap(), TIMESTAMP); + } + for (double val : Arrays.asList(10.0, 20.0)) { + distribution2.add(val, Collections.<String, String>emptyMap(), TIMESTAMP); + } + for (double val : Arrays.asList(-10.0, 15.0, -15.0, -20.0)) { + distribution3.add(val, Collections.<String, String>emptyMap(), TIMESTAMP); + } + + MutableDistribution combined = MutableDistribution.create(BUCKET_BOUNDARIES); + combined.combine(distribution1, 1.0); // distribution1 will be combined + combined.combine(distribution2, 0.6); // distribution2 will be ignored + verifyMutableDistribution(combined, 0, 2, -5, 5, 50.0, new long[] {0, 1, 1, 0}, TOLERANCE); + + combined.combine(distribution2, 1.0); // distribution2 will be combined + verifyMutableDistribution(combined, 7.5, 4, -5, 20, 325.0, new long[] {0, 1, 1, 2}, TOLERANCE); + + combined.combine(distribution3, 1.0); // distribution3 will be combined + verifyMutableDistribution(combined, 0, 8, -20, 20, 1500.0, new long[] {2, 2, 1, 3}, TOLERANCE); + } + + @Test + public void mutableAggregation_ToAggregationData() { + assertThat(MutableSumDouble.create().toAggregationData()).isEqualTo(SumDataDouble.create(0)); + assertThat(MutableSumLong.create().toAggregationData()).isEqualTo(SumDataLong.create(0)); + assertThat(MutableCount.create().toAggregationData()).isEqualTo(CountData.create(0)); + assertThat(MutableMean.create().toAggregationData()).isEqualTo(MeanData.create(0, 0)); + assertThat(MutableDistribution.create(BUCKET_BOUNDARIES).toAggregationData()) + .isEqualTo( + DistributionData.create( + 0, + 0, + Double.POSITIVE_INFINITY, + Double.NEGATIVE_INFINITY, + 0, + Arrays.asList(0L, 0L, 0L, 0L))); + assertThat(MutableLastValueDouble.create().toAggregationData()) + .isEqualTo(LastValueDataDouble.create(Double.NaN)); + assertThat(MutableLastValueLong.create().toAggregationData()) + .isEqualTo(LastValueDataLong.create(0)); + } + + @Test + public void mutableAggregation_ToPoint() { + assertThat(MutableSumDouble.create().toPoint(TIMESTAMP)) + .isEqualTo(Point.create(Value.doubleValue(0), TIMESTAMP)); + assertThat(MutableSumLong.create().toPoint(TIMESTAMP)) + .isEqualTo(Point.create(Value.longValue(0), TIMESTAMP)); + assertThat(MutableCount.create().toPoint(TIMESTAMP)) + .isEqualTo(Point.create(Value.longValue(0), TIMESTAMP)); + assertThat(MutableMean.create().toPoint(TIMESTAMP)) + .isEqualTo(Point.create(Value.doubleValue(0), TIMESTAMP)); + + thrown.expect(IllegalArgumentException.class); + thrown.expectMessage("bucket boundary should be > 0"); + assertThat(MutableDistribution.create(BUCKET_BOUNDARIES).toPoint(TIMESTAMP)) + .isEqualTo( + Point.create( + Value.distributionValue( + Distribution.create( + 0, + 0, + 0, + BucketOptions.explicitOptions(BUCKET_BOUNDARIES.getBoundaries()), + Arrays.asList( + Bucket.create(0), + Bucket.create(0), + Bucket.create(0), + Bucket.create(0)))), + TIMESTAMP)); + } + + private static void verifyMutableDistribution( + MutableDistribution mutableDistribution, + double mean, + long count, + double min, + double max, + double sumOfSquaredDeviations, + long[] bucketCounts, + double tolerance) { + assertThat(mutableDistribution.getMean()).isWithin(tolerance).of(mean); + assertThat(mutableDistribution.getCount()).isEqualTo(count); + assertThat(mutableDistribution.getMin()).isWithin(tolerance).of(min); + assertThat(mutableDistribution.getMax()).isWithin(tolerance).of(max); + assertThat(mutableDistribution.getSumOfSquaredDeviations()) + .isWithin(tolerance) + .of(sumOfSquaredDeviations); + assertThat(mutableDistribution.getBucketCounts()).isEqualTo(bucketCounts); + } +} diff --git a/impl_core/src/test/java/io/opencensus/implcore/stats/MutableViewDataTest.java b/impl_core/src/test/java/io/opencensus/implcore/stats/MutableViewDataTest.java new file mode 100644 index 00000000..06f50fed --- /dev/null +++ b/impl_core/src/test/java/io/opencensus/implcore/stats/MutableViewDataTest.java @@ -0,0 +1,34 @@ +/* + * Copyright 2017, OpenCensus Authors + * + * 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 io.opencensus.implcore.stats; + +import static com.google.common.truth.Truth.assertThat; + +import io.opencensus.common.Timestamp; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Tests for {@link MutableViewData}. */ +@RunWith(JUnit4.class) +public class MutableViewDataTest { + + @Test + public void testConstants() { + assertThat(MutableViewData.ZERO_TIMESTAMP).isEqualTo(Timestamp.create(0, 0)); + } +} diff --git a/impl_core/src/test/java/io/opencensus/implcore/stats/RecordUtilsTest.java b/impl_core/src/test/java/io/opencensus/implcore/stats/RecordUtilsTest.java new file mode 100644 index 00000000..1e22a7a1 --- /dev/null +++ b/impl_core/src/test/java/io/opencensus/implcore/stats/RecordUtilsTest.java @@ -0,0 +1,116 @@ +/* + * Copyright 2018, OpenCensus Authors + * + * 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 io.opencensus.implcore.stats; + +import static com.google.common.truth.Truth.assertThat; + +import com.google.common.collect.ImmutableMap; +import io.opencensus.implcore.stats.MutableAggregation.MutableDistribution; +import io.opencensus.stats.Aggregation.Count; +import io.opencensus.stats.Aggregation.Distribution; +import io.opencensus.stats.Aggregation.LastValue; +import io.opencensus.stats.Aggregation.Mean; +import io.opencensus.stats.Aggregation.Sum; +import io.opencensus.stats.AggregationData.CountData; +import io.opencensus.stats.AggregationData.LastValueDataDouble; +import io.opencensus.stats.AggregationData.LastValueDataLong; +import io.opencensus.stats.AggregationData.MeanData; +import io.opencensus.stats.AggregationData.SumDataDouble; +import io.opencensus.stats.AggregationData.SumDataLong; +import io.opencensus.stats.BucketBoundaries; +import io.opencensus.stats.Measure.MeasureDouble; +import io.opencensus.stats.Measure.MeasureLong; +import io.opencensus.tags.TagKey; +import io.opencensus.tags.TagValue; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Tests for {@link RecordUtils}. */ +@RunWith(JUnit4.class) +public class RecordUtilsTest { + + private static final double EPSILON = 1e-7; + private static final MeasureDouble MEASURE_DOUBLE = + MeasureDouble.create("measure1", "description", "1"); + private static final MeasureLong MEASURE_LONG = + MeasureLong.create("measure2", "description", "1"); + private static final TagKey ORIGINATOR = TagKey.create("originator"); + private static final TagKey CALLER = TagKey.create("caller"); + private static final TagKey METHOD = TagKey.create("method"); + private static final TagValue CALLER_V = TagValue.create("some caller"); + private static final TagValue METHOD_V = TagValue.create("some method"); + + @Test + public void testConstants() { + assertThat(RecordUtils.UNKNOWN_TAG_VALUE).isNull(); + } + + @Test + public void testGetTagValues() { + List<TagKey> columns = Arrays.asList(CALLER, METHOD, ORIGINATOR); + Map<TagKey, TagValue> tags = ImmutableMap.of(CALLER, CALLER_V, METHOD, METHOD_V); + + assertThat(RecordUtils.getTagValues(tags, columns)) + .containsExactly(CALLER_V, METHOD_V, RecordUtils.UNKNOWN_TAG_VALUE) + .inOrder(); + } + + @Test + public void createMutableAggregation() { + BucketBoundaries bucketBoundaries = BucketBoundaries.create(Arrays.asList(-1.0, 0.0, 1.0)); + + assertThat( + RecordUtils.createMutableAggregation(Sum.create(), MEASURE_DOUBLE).toAggregationData()) + .isEqualTo(SumDataDouble.create(0)); + assertThat(RecordUtils.createMutableAggregation(Sum.create(), MEASURE_LONG).toAggregationData()) + .isEqualTo(SumDataLong.create(0)); + assertThat( + RecordUtils.createMutableAggregation(Count.create(), MEASURE_DOUBLE) + .toAggregationData()) + .isEqualTo(CountData.create(0)); + assertThat( + RecordUtils.createMutableAggregation(Count.create(), MEASURE_LONG).toAggregationData()) + .isEqualTo(CountData.create(0)); + assertThat( + RecordUtils.createMutableAggregation(Mean.create(), MEASURE_DOUBLE).toAggregationData()) + .isEqualTo(MeanData.create(0, 0)); + assertThat( + RecordUtils.createMutableAggregation(Mean.create(), MEASURE_LONG).toAggregationData()) + .isEqualTo(MeanData.create(0, 0)); + assertThat( + RecordUtils.createMutableAggregation(LastValue.create(), MEASURE_DOUBLE) + .toAggregationData()) + .isEqualTo(LastValueDataDouble.create(Double.NaN)); + assertThat( + RecordUtils.createMutableAggregation(LastValue.create(), MEASURE_LONG) + .toAggregationData()) + .isEqualTo(LastValueDataLong.create(0)); + + MutableDistribution mutableDistribution = + (MutableDistribution) + RecordUtils.createMutableAggregation( + Distribution.create(bucketBoundaries), MEASURE_DOUBLE); + assertThat(mutableDistribution.getMin()).isPositiveInfinity(); + assertThat(mutableDistribution.getMax()).isNegativeInfinity(); + assertThat(mutableDistribution.getSumOfSquaredDeviations()).isWithin(EPSILON).of(0); + assertThat(mutableDistribution.getBucketCounts()).isEqualTo(new long[4]); + } +} diff --git a/impl_core/src/test/java/io/opencensus/implcore/stats/StatsComponentImplBaseTest.java b/impl_core/src/test/java/io/opencensus/implcore/stats/StatsComponentImplBaseTest.java new file mode 100644 index 00000000..04861df9 --- /dev/null +++ b/impl_core/src/test/java/io/opencensus/implcore/stats/StatsComponentImplBaseTest.java @@ -0,0 +1,77 @@ +/* + * Copyright 2017, OpenCensus Authors + * + * 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 io.opencensus.implcore.stats; + +import static com.google.common.truth.Truth.assertThat; + +import io.opencensus.implcore.internal.SimpleEventQueue; +import io.opencensus.stats.StatsCollectionState; +import io.opencensus.stats.StatsComponent; +import io.opencensus.testing.common.TestClock; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Tests for {@link StatsComponentImplBase}. */ +@RunWith(JUnit4.class) +public final class StatsComponentImplBaseTest { + + @Rule public final ExpectedException thrown = ExpectedException.none(); + + private final StatsComponent statsComponent = + new StatsComponentImplBase(new SimpleEventQueue(), TestClock.create()); + + @Test + public void defaultState() { + assertThat(statsComponent.getState()).isEqualTo(StatsCollectionState.ENABLED); + } + + @Test + @SuppressWarnings("deprecation") + public void setState_Disabled() { + statsComponent.setState(StatsCollectionState.DISABLED); + assertThat(statsComponent.getState()).isEqualTo(StatsCollectionState.DISABLED); + } + + @Test + @SuppressWarnings("deprecation") + public void setState_Enabled() { + statsComponent.setState(StatsCollectionState.DISABLED); + statsComponent.setState(StatsCollectionState.ENABLED); + assertThat(statsComponent.getState()).isEqualTo(StatsCollectionState.ENABLED); + } + + @Test + @SuppressWarnings("deprecation") + public void setState_DisallowsNull() { + thrown.expect(NullPointerException.class); + thrown.expectMessage("newState"); + statsComponent.setState(null); + } + + @Test + @SuppressWarnings("deprecation") + public void preventSettingStateAfterGettingState() { + statsComponent.setState(StatsCollectionState.DISABLED); + statsComponent.getState(); + thrown.expect(IllegalStateException.class); + thrown.expectMessage("State was already read, cannot set state."); + statsComponent.setState(StatsCollectionState.ENABLED); + } +} diff --git a/impl_core/src/test/java/io/opencensus/implcore/stats/StatsRecorderImplTest.java b/impl_core/src/test/java/io/opencensus/implcore/stats/StatsRecorderImplTest.java new file mode 100644 index 00000000..bd8b5b88 --- /dev/null +++ b/impl_core/src/test/java/io/opencensus/implcore/stats/StatsRecorderImplTest.java @@ -0,0 +1,349 @@ +/* + * Copyright 2017, OpenCensus Authors + * + * 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 io.opencensus.implcore.stats; + +import static com.google.common.truth.Truth.assertThat; +import static io.opencensus.implcore.stats.MutableViewData.ZERO_TIMESTAMP; +import static io.opencensus.implcore.stats.StatsTestUtil.createEmptyViewData; + +import com.google.common.collect.ImmutableMap; +import io.grpc.Context; +import io.opencensus.common.Duration; +import io.opencensus.common.Timestamp; +import io.opencensus.implcore.internal.SimpleEventQueue; +import io.opencensus.implcore.stats.StatsTestUtil.SimpleTagContext; +import io.opencensus.stats.Aggregation.Count; +import io.opencensus.stats.Aggregation.Distribution; +import io.opencensus.stats.Aggregation.Sum; +import io.opencensus.stats.AggregationData.CountData; +import io.opencensus.stats.AggregationData.DistributionData; +import io.opencensus.stats.AggregationData.DistributionData.Exemplar; +import io.opencensus.stats.BucketBoundaries; +import io.opencensus.stats.Measure.MeasureDouble; +import io.opencensus.stats.MeasureMap; +import io.opencensus.stats.StatsCollectionState; +import io.opencensus.stats.StatsComponent; +import io.opencensus.stats.StatsRecorder; +import io.opencensus.stats.View; +import io.opencensus.stats.View.AggregationWindow.Cumulative; +import io.opencensus.stats.ViewData; +import io.opencensus.stats.ViewData.AggregationWindowData.CumulativeData; +import io.opencensus.stats.ViewManager; +import io.opencensus.tags.Tag; +import io.opencensus.tags.TagContext; +import io.opencensus.tags.TagKey; +import io.opencensus.tags.TagValue; +import io.opencensus.tags.unsafe.ContextUtils; +import io.opencensus.testing.common.TestClock; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Tests for {@link StatsRecorderImpl}. */ +@RunWith(JUnit4.class) +public final class StatsRecorderImplTest { + private static final TagKey KEY = TagKey.create("KEY"); + private static final TagValue VALUE = TagValue.create("VALUE"); + private static final TagValue VALUE_2 = TagValue.create("VALUE_2"); + private static final MeasureDouble MEASURE_DOUBLE = + MeasureDouble.create("my measurement", "description", "us"); + private static final MeasureDouble MEASURE_DOUBLE_NO_VIEW_1 = + MeasureDouble.create("my measurement no view 1", "description", "us"); + private static final MeasureDouble MEASURE_DOUBLE_NO_VIEW_2 = + MeasureDouble.create("my measurement no view 2", "description", "us"); + private static final View.Name VIEW_NAME = View.Name.create("my view"); + private static final BucketBoundaries BUCKET_BOUNDARIES = + BucketBoundaries.create(Arrays.asList(-10.0, 0.0, 10.0)); + private static final Distribution DISTRIBUTION = Distribution.create(BUCKET_BOUNDARIES); + private static final Distribution DISTRIBUTION_NO_HISTOGRAM = + Distribution.create(BucketBoundaries.create(Collections.<Double>emptyList())); + private static final Timestamp START_TIME = Timestamp.fromMillis(0); + private static final Duration ONE_SECOND = Duration.fromMillis(1000); + + private final TestClock testClock = TestClock.create(); + private final StatsComponent statsComponent = + new StatsComponentImplBase(new SimpleEventQueue(), testClock); + + private final ViewManager viewManager = statsComponent.getViewManager(); + private final StatsRecorder statsRecorder = statsComponent.getStatsRecorder(); + + @Test + public void record_CurrentContextNotSet() { + View view = + View.create( + VIEW_NAME, + "description", + MEASURE_DOUBLE, + Sum.create(), + Arrays.asList(KEY), + Cumulative.create()); + viewManager.registerView(view); + statsRecorder.newMeasureMap().put(MEASURE_DOUBLE, 1.0).record(); + ViewData viewData = viewManager.getView(VIEW_NAME); + + // record() should have used the default TagContext, so the tag value should be null. + assertThat(viewData.getAggregationMap().keySet()) + .containsExactly(Arrays.asList((TagValue) null)); + } + + @Test + public void record_CurrentContextSet() { + View view = + View.create( + VIEW_NAME, + "description", + MEASURE_DOUBLE, + Sum.create(), + Arrays.asList(KEY), + Cumulative.create()); + viewManager.registerView(view); + Context orig = + Context.current() + .withValue(ContextUtils.TAG_CONTEXT_KEY, new SimpleTagContext(Tag.create(KEY, VALUE))) + .attach(); + try { + statsRecorder.newMeasureMap().put(MEASURE_DOUBLE, 1.0).record(); + } finally { + Context.current().detach(orig); + } + ViewData viewData = viewManager.getView(VIEW_NAME); + + // record() should have used the given TagContext. + assertThat(viewData.getAggregationMap().keySet()).containsExactly(Arrays.asList(VALUE)); + } + + @Test + public void record_UnregisteredMeasure() { + View view = + View.create( + VIEW_NAME, + "description", + MEASURE_DOUBLE, + Sum.create(), + Arrays.asList(KEY), + Cumulative.create()); + viewManager.registerView(view); + statsRecorder + .newMeasureMap() + .put(MEASURE_DOUBLE_NO_VIEW_1, 1.0) + .put(MEASURE_DOUBLE, 2.0) + .put(MEASURE_DOUBLE_NO_VIEW_2, 3.0) + .record(new SimpleTagContext(Tag.create(KEY, VALUE))); + ViewData viewData = viewManager.getView(VIEW_NAME); + + // There should be one entry. + StatsTestUtil.assertAggregationMapEquals( + viewData.getAggregationMap(), + ImmutableMap.of( + Arrays.asList(VALUE), + StatsTestUtil.createAggregationData(Sum.create(), MEASURE_DOUBLE, 2.0)), + 1e-6); + } + + @Test + public void record_WithAttachments_Distribution() { + testClock.setTime(START_TIME); + View view = + View.create(VIEW_NAME, "description", MEASURE_DOUBLE, DISTRIBUTION, Arrays.asList(KEY)); + viewManager.registerView(view); + recordWithAttachments(); + ViewData viewData = viewManager.getView(VIEW_NAME); + assertThat(viewData).isNotNull(); + DistributionData distributionData = + (DistributionData) viewData.getAggregationMap().get(Collections.singletonList(VALUE)); + List<Exemplar> expected = + Arrays.asList( + Exemplar.create(-20.0, Timestamp.create(4, 0), Collections.singletonMap("k3", "v1")), + Exemplar.create(-5.0, Timestamp.create(5, 0), Collections.singletonMap("k3", "v3")), + Exemplar.create(1.0, Timestamp.create(2, 0), Collections.singletonMap("k2", "v2")), + Exemplar.create(12.0, Timestamp.create(3, 0), Collections.singletonMap("k1", "v3"))); + assertThat(distributionData.getExemplars()).containsExactlyElementsIn(expected).inOrder(); + } + + @Test + public void record_WithAttachments_DistributionNoHistogram() { + testClock.setTime(START_TIME); + View view = + View.create( + VIEW_NAME, + "description", + MEASURE_DOUBLE, + DISTRIBUTION_NO_HISTOGRAM, + Arrays.asList(KEY)); + viewManager.registerView(view); + recordWithAttachments(); + ViewData viewData = viewManager.getView(VIEW_NAME); + assertThat(viewData).isNotNull(); + DistributionData distributionData = + (DistributionData) viewData.getAggregationMap().get(Collections.singletonList(VALUE)); + // Recording exemplar has no effect if there's no histogram. + assertThat(distributionData.getExemplars()).isEmpty(); + } + + @Test + public void record_WithAttachments_Count() { + testClock.setTime(START_TIME); + View view = + View.create(VIEW_NAME, "description", MEASURE_DOUBLE, Count.create(), Arrays.asList(KEY)); + viewManager.registerView(view); + recordWithAttachments(); + ViewData viewData = viewManager.getView(VIEW_NAME); + assertThat(viewData).isNotNull(); + CountData countData = + (CountData) viewData.getAggregationMap().get(Collections.singletonList(VALUE)); + // Recording exemplar does not affect views with an aggregation other than distribution. + assertThat(countData.getCount()).isEqualTo(6L); + } + + private void recordWithAttachments() { + TagContext context = new SimpleTagContext(Tag.create(KEY, VALUE)); + + // The test Distribution has bucket boundaries [-10.0, 0.0, 10.0]. + + testClock.advanceTime(ONE_SECOND); // 1st second. + // -1.0 is in the 2nd bucket [-10.0, 0.0). + statsRecorder + .newMeasureMap() + .put(MEASURE_DOUBLE, -1.0) + .putAttachment("k1", "v1") + .record(context); + + testClock.advanceTime(ONE_SECOND); // 2nd second. + // 1.0 is in the 3rd bucket [0.0, 10.0). + statsRecorder + .newMeasureMap() + .put(MEASURE_DOUBLE, 1.0) + .putAttachment("k2", "v2") + .record(context); + + testClock.advanceTime(ONE_SECOND); // 3rd second. + // 12.0 is in the 4th bucket [10.0, +Inf). + statsRecorder + .newMeasureMap() + .put(MEASURE_DOUBLE, 12.0) + .putAttachment("k1", "v3") + .record(context); + + testClock.advanceTime(ONE_SECOND); // 4th second. + // -20.0 is in the 1st bucket [-Inf, -10.0). + statsRecorder + .newMeasureMap() + .put(MEASURE_DOUBLE, -20.0) + .putAttachment("k3", "v1") + .record(context); + + testClock.advanceTime(ONE_SECOND); // 5th second. + // -5.0 is in the 2nd bucket [-10.0, 0), should overwrite the previous exemplar -1.0. + statsRecorder + .newMeasureMap() + .put(MEASURE_DOUBLE, -5.0) + .putAttachment("k3", "v3") + .record(context); + + testClock.advanceTime(ONE_SECOND); // 6th second. + // -3.0 is in the 2nd bucket [-10.0, 0), but this value doesn't come with attachments, so it + // shouldn't overwrite the previous exemplar (-5.0). + statsRecorder.newMeasureMap().put(MEASURE_DOUBLE, -3.0).record(context); + } + + @Test + public void recordTwice() { + View view = + View.create( + VIEW_NAME, + "description", + MEASURE_DOUBLE, + Sum.create(), + Arrays.asList(KEY), + Cumulative.create()); + viewManager.registerView(view); + MeasureMap statsRecord = statsRecorder.newMeasureMap().put(MEASURE_DOUBLE, 1.0); + statsRecord.record(new SimpleTagContext(Tag.create(KEY, VALUE))); + statsRecord.record(new SimpleTagContext(Tag.create(KEY, VALUE_2))); + ViewData viewData = viewManager.getView(VIEW_NAME); + + // There should be two entries. + StatsTestUtil.assertAggregationMapEquals( + viewData.getAggregationMap(), + ImmutableMap.of( + Arrays.asList(VALUE), + StatsTestUtil.createAggregationData(Sum.create(), MEASURE_DOUBLE, 1.0), + Arrays.asList(VALUE_2), + StatsTestUtil.createAggregationData(Sum.create(), MEASURE_DOUBLE, 1.0)), + 1e-6); + } + + @Test + @SuppressWarnings("deprecation") + public void record_StatsDisabled() { + View view = + View.create( + VIEW_NAME, + "description", + MEASURE_DOUBLE, + Sum.create(), + Arrays.asList(KEY), + Cumulative.create()); + + viewManager.registerView(view); + statsComponent.setState(StatsCollectionState.DISABLED); + statsRecorder + .newMeasureMap() + .put(MEASURE_DOUBLE, 1.0) + .record(new SimpleTagContext(Tag.create(KEY, VALUE))); + assertThat(viewManager.getView(VIEW_NAME)).isEqualTo(createEmptyViewData(view)); + } + + @Test + @SuppressWarnings("deprecation") + public void record_StatsReenabled() { + View view = + View.create( + VIEW_NAME, + "description", + MEASURE_DOUBLE, + Sum.create(), + Arrays.asList(KEY), + Cumulative.create()); + viewManager.registerView(view); + + statsComponent.setState(StatsCollectionState.DISABLED); + statsRecorder + .newMeasureMap() + .put(MEASURE_DOUBLE, 1.0) + .record(new SimpleTagContext(Tag.create(KEY, VALUE))); + assertThat(viewManager.getView(VIEW_NAME)).isEqualTo(createEmptyViewData(view)); + + statsComponent.setState(StatsCollectionState.ENABLED); + assertThat(viewManager.getView(VIEW_NAME).getAggregationMap()).isEmpty(); + assertThat(viewManager.getView(VIEW_NAME).getWindowData()) + .isNotEqualTo(CumulativeData.create(ZERO_TIMESTAMP, ZERO_TIMESTAMP)); + statsRecorder + .newMeasureMap() + .put(MEASURE_DOUBLE, 4.0) + .record(new SimpleTagContext(Tag.create(KEY, VALUE))); + StatsTestUtil.assertAggregationMapEquals( + viewManager.getView(VIEW_NAME).getAggregationMap(), + ImmutableMap.of( + Arrays.asList(VALUE), + StatsTestUtil.createAggregationData(Sum.create(), MEASURE_DOUBLE, 4.0)), + 1e-6); + } +} diff --git a/impl_core/src/test/java/io/opencensus/implcore/stats/StatsTestUtil.java b/impl_core/src/test/java/io/opencensus/implcore/stats/StatsTestUtil.java new file mode 100644 index 00000000..ea1bf346 --- /dev/null +++ b/impl_core/src/test/java/io/opencensus/implcore/stats/StatsTestUtil.java @@ -0,0 +1,232 @@ +/* + * Copyright 2017, OpenCensus Authors + * + * 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 io.opencensus.implcore.stats; + +import static com.google.common.truth.Truth.assertThat; +import static io.opencensus.implcore.stats.MutableViewData.ZERO_TIMESTAMP; + +import com.google.common.collect.Iterables; +import com.google.common.collect.Lists; +import io.opencensus.common.Function; +import io.opencensus.common.Functions; +import io.opencensus.common.Timestamp; +import io.opencensus.stats.Aggregation; +import io.opencensus.stats.AggregationData; +import io.opencensus.stats.AggregationData.CountData; +import io.opencensus.stats.AggregationData.DistributionData; +import io.opencensus.stats.AggregationData.LastValueDataDouble; +import io.opencensus.stats.AggregationData.LastValueDataLong; +import io.opencensus.stats.AggregationData.MeanData; +import io.opencensus.stats.AggregationData.SumDataDouble; +import io.opencensus.stats.AggregationData.SumDataLong; +import io.opencensus.stats.Measure; +import io.opencensus.stats.View; +import io.opencensus.stats.ViewData; +import io.opencensus.stats.ViewData.AggregationWindowData; +import io.opencensus.stats.ViewData.AggregationWindowData.CumulativeData; +import io.opencensus.stats.ViewData.AggregationWindowData.IntervalData; +import io.opencensus.tags.Tag; +import io.opencensus.tags.TagContext; +import io.opencensus.tags.TagValue; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import javax.annotation.Nullable; + +/** Stats test utilities. */ +final class StatsTestUtil { + + private static final Timestamp EMPTY = Timestamp.create(0, 0); + + private StatsTestUtil() {} + + /** + * Creates an {@link AggregationData} by adding the given sequence of values, based on the + * definition of the given {@link Aggregation}. + * + * @param aggregation the {@code Aggregation} to apply the values to. + * @param values the values to add to the {@code MutableAggregation}s. + * @return an {@code AggregationData}. + */ + static AggregationData createAggregationData( + Aggregation aggregation, Measure measure, double... values) { + MutableAggregation mutableAggregation = + RecordUtils.createMutableAggregation(aggregation, measure); + for (double value : values) { + mutableAggregation.add(value, Collections.<String, String>emptyMap(), EMPTY); + } + return mutableAggregation.toAggregationData(); + } + + /** + * Compare the actual and expected AggregationMap within the given tolerance. + * + * @param expected the expected map. + * @param actual the actual mapping from {@code List<TagValue>} to {@code AggregationData}. + * @param tolerance the tolerance used for {@code double} comparison. + */ + static void assertAggregationMapEquals( + Map<? extends List<? extends TagValue>, ? extends AggregationData> actual, + Map<? extends List<? extends TagValue>, ? extends AggregationData> expected, + double tolerance) { + assertThat(actual.keySet()).containsExactlyElementsIn(expected.keySet()); + for (Entry<? extends List<? extends TagValue>, ? extends AggregationData> entry : + actual.entrySet()) { + assertAggregationDataEquals(expected.get(entry.getKey()), entry.getValue(), tolerance); + } + } + + /** + * Compare the expected and actual {@code AggregationData} within the given tolerance. + * + * @param expected the expected {@code AggregationData}. + * @param actual the actual {@code AggregationData}. + * @param tolerance the tolerance used for {@code double} comparison. + */ + static void assertAggregationDataEquals( + AggregationData expected, final AggregationData actual, final double tolerance) { + expected.match( + new Function<SumDataDouble, Void>() { + @Override + public Void apply(SumDataDouble arg) { + assertThat(actual).isInstanceOf(SumDataDouble.class); + assertThat(((SumDataDouble) actual).getSum()).isWithin(tolerance).of(arg.getSum()); + return null; + } + }, + new Function<SumDataLong, Void>() { + @Override + public Void apply(SumDataLong arg) { + assertThat(actual).isInstanceOf(SumDataLong.class); + assertThat(((SumDataLong) actual).getSum()).isEqualTo(arg.getSum()); + return null; + } + }, + new Function<CountData, Void>() { + @Override + public Void apply(CountData arg) { + assertThat(actual).isInstanceOf(CountData.class); + assertThat(((CountData) actual).getCount()).isEqualTo(arg.getCount()); + return null; + } + }, + new Function<DistributionData, Void>() { + @Override + public Void apply(DistributionData arg) { + assertThat(actual).isInstanceOf(DistributionData.class); + assertDistributionDataEquals(arg, (DistributionData) actual, tolerance); + return null; + } + }, + new Function<LastValueDataDouble, Void>() { + @Override + public Void apply(LastValueDataDouble arg) { + assertThat(actual).isInstanceOf(LastValueDataDouble.class); + assertThat(((LastValueDataDouble) actual).getLastValue()) + .isWithin(tolerance) + .of(arg.getLastValue()); + return null; + } + }, + new Function<LastValueDataLong, Void>() { + @Override + public Void apply(LastValueDataLong arg) { + assertThat(actual).isInstanceOf(LastValueDataLong.class); + assertThat(((LastValueDataLong) actual).getLastValue()).isEqualTo(arg.getLastValue()); + return null; + } + }, + new Function<AggregationData, Void>() { + @Override + public Void apply(AggregationData arg) { + if (arg instanceof MeanData) { + assertThat(actual).isInstanceOf(MeanData.class); + assertThat(((MeanData) actual).getMean()) + .isWithin(tolerance) + .of(((MeanData) arg).getMean()); + return null; + } + throw new IllegalArgumentException("Unknown Aggregation."); + } + }); + } + + // Create an empty ViewData with the given View. + static ViewData createEmptyViewData(View view) { + return ViewData.create( + view, + Collections.<List<TagValue>, AggregationData>emptyMap(), + view.getWindow() + .match( + Functions.<AggregationWindowData>returnConstant( + CumulativeData.create(ZERO_TIMESTAMP, ZERO_TIMESTAMP)), + Functions.<AggregationWindowData>returnConstant( + IntervalData.create(ZERO_TIMESTAMP)), + Functions.<AggregationWindowData>throwAssertionError())); + } + + // Compare the expected and actual DistributionData within the given tolerance. + private static void assertDistributionDataEquals( + DistributionData expected, DistributionData actual, double tolerance) { + assertThat(actual.getMean()).isWithin(tolerance).of(expected.getMean()); + assertThat(actual.getCount()).isEqualTo(expected.getCount()); + assertThat(actual.getMean()).isWithin(tolerance).of(expected.getMean()); + assertThat(actual.getSumOfSquaredDeviations()) + .isWithin(tolerance) + .of(expected.getSumOfSquaredDeviations()); + + if (expected.getMax() == Double.NEGATIVE_INFINITY + && expected.getMin() == Double.POSITIVE_INFINITY) { + assertThat(actual.getMax()).isNegativeInfinity(); + assertThat(actual.getMin()).isPositiveInfinity(); + } else { + assertThat(actual.getMax()).isWithin(tolerance).of(expected.getMax()); + assertThat(actual.getMin()).isWithin(tolerance).of(expected.getMin()); + } + + assertThat(removeTrailingZeros((actual).getBucketCounts())) + .isEqualTo(removeTrailingZeros(expected.getBucketCounts())); + } + + @Nullable + private static List<Long> removeTrailingZeros(List<Long> longs) { + if (longs == null) { + return null; + } + List<Long> truncated = new ArrayList<Long>(longs); + while (!truncated.isEmpty() && Iterables.getLast(truncated) == 0) { + truncated.remove(truncated.size() - 1); + } + return truncated; + } + + static final class SimpleTagContext extends TagContext { + private final List<Tag> tags; + + SimpleTagContext(Tag... tags) { + this.tags = Collections.unmodifiableList(Lists.newArrayList(tags)); + } + + @Override + protected Iterator<Tag> getIterator() { + return tags.iterator(); + } + } +} diff --git a/impl_core/src/test/java/io/opencensus/implcore/stats/ViewManagerImplTest.java b/impl_core/src/test/java/io/opencensus/implcore/stats/ViewManagerImplTest.java new file mode 100644 index 00000000..a4018b79 --- /dev/null +++ b/impl_core/src/test/java/io/opencensus/implcore/stats/ViewManagerImplTest.java @@ -0,0 +1,1021 @@ +/* + * Copyright 2017, OpenCensus Authors + * + * 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 io.opencensus.implcore.stats; + +import static com.google.common.truth.Truth.assertThat; +import static io.opencensus.implcore.stats.StatsTestUtil.assertAggregationMapEquals; +import static io.opencensus.implcore.stats.StatsTestUtil.createAggregationData; +import static io.opencensus.implcore.stats.StatsTestUtil.createEmptyViewData; + +import com.google.common.collect.ImmutableMap; +import io.opencensus.common.Duration; +import io.opencensus.common.Timestamp; +import io.opencensus.implcore.internal.SimpleEventQueue; +import io.opencensus.implcore.tags.TagsComponentImplBase; +import io.opencensus.stats.Aggregation; +import io.opencensus.stats.Aggregation.Distribution; +import io.opencensus.stats.Aggregation.LastValue; +import io.opencensus.stats.Aggregation.Mean; +import io.opencensus.stats.Aggregation.Sum; +import io.opencensus.stats.AggregationData; +import io.opencensus.stats.AggregationData.LastValueDataDouble; +import io.opencensus.stats.AggregationData.LastValueDataLong; +import io.opencensus.stats.AggregationData.MeanData; +import io.opencensus.stats.AggregationData.SumDataDouble; +import io.opencensus.stats.AggregationData.SumDataLong; +import io.opencensus.stats.BucketBoundaries; +import io.opencensus.stats.Measure; +import io.opencensus.stats.Measure.MeasureDouble; +import io.opencensus.stats.Measure.MeasureLong; +import io.opencensus.stats.MeasureMap; +import io.opencensus.stats.StatsCollectionState; +import io.opencensus.stats.View; +import io.opencensus.stats.View.AggregationWindow.Cumulative; +import io.opencensus.stats.View.AggregationWindow.Interval; +import io.opencensus.stats.View.Name; +import io.opencensus.stats.ViewData; +import io.opencensus.stats.ViewData.AggregationWindowData; +import io.opencensus.stats.ViewData.AggregationWindowData.CumulativeData; +import io.opencensus.stats.ViewData.AggregationWindowData.IntervalData; +import io.opencensus.tags.TagContext; +import io.opencensus.tags.TagKey; +import io.opencensus.tags.TagValue; +import io.opencensus.tags.Tagger; +import io.opencensus.tags.TagsComponent; +import io.opencensus.testing.common.TestClock; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Set; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Tests for {@link ViewManagerImpl}. */ +@RunWith(JUnit4.class) +public class ViewManagerImplTest { + + @Rule public final ExpectedException thrown = ExpectedException.none(); + + private static final TagKey KEY = TagKey.create("KEY"); + + private static final TagValue VALUE = TagValue.create("VALUE"); + private static final TagValue VALUE_2 = TagValue.create("VALUE_2"); + + private static final String MEASURE_NAME = "my measurement"; + + private static final String MEASURE_NAME_2 = "my measurement 2"; + + private static final String MEASURE_UNIT = "us"; + + private static final String MEASURE_DESCRIPTION = "measure description"; + + private static final MeasureDouble MEASURE_DOUBLE = + MeasureDouble.create(MEASURE_NAME, MEASURE_DESCRIPTION, MEASURE_UNIT); + + private static final MeasureLong MEASURE_LONG = + MeasureLong.create(MEASURE_NAME_2, MEASURE_DESCRIPTION, MEASURE_UNIT); + + private static final Name VIEW_NAME = Name.create("my view"); + private static final Name VIEW_NAME_2 = Name.create("my view 2"); + + private static final String VIEW_DESCRIPTION = "view description"; + + private static final Cumulative CUMULATIVE = Cumulative.create(); + + private static final double EPSILON = 1e-7; + private static final long MILLIS_PER_SECOND = 1000; + private static final Duration TEN_SECONDS = Duration.create(10, 0); + private static final Interval INTERVAL = Interval.create(TEN_SECONDS); + + private static final BucketBoundaries BUCKET_BOUNDARIES = + BucketBoundaries.create( + Arrays.asList( + 0.0, 0.2, 0.5, 1.0, 2.0, 3.0, 4.0, 5.0, 7.0, 10.0, 15.0, 20.0, 30.0, 40.0, 50.0)); + + private static final Sum SUM = Sum.create(); + private static final Mean MEAN = Mean.create(); + private static final Distribution DISTRIBUTION = Distribution.create(BUCKET_BOUNDARIES); + private static final LastValue LAST_VALUE = LastValue.create(); + + private final TestClock clock = TestClock.create(); + + private final StatsComponentImplBase statsComponent = + new StatsComponentImplBase(new SimpleEventQueue(), clock); + private final TagsComponent tagsComponent = new TagsComponentImplBase(); + + private final Tagger tagger = tagsComponent.getTagger(); + private final ViewManagerImpl viewManager = statsComponent.getViewManager(); + private final StatsRecorderImpl statsRecorder = statsComponent.getStatsRecorder(); + + private static View createCumulativeView() { + return createCumulativeView(VIEW_NAME, MEASURE_DOUBLE, DISTRIBUTION, Arrays.asList(KEY)); + } + + private static View createCumulativeView( + View.Name name, Measure measure, Aggregation aggregation, List<TagKey> keys) { + return View.create(name, VIEW_DESCRIPTION, measure, aggregation, keys, CUMULATIVE); + } + + @Test + public void testRegisterAndGetCumulativeView() { + View view = createCumulativeView(); + viewManager.registerView(view); + assertThat(viewManager.getView(VIEW_NAME).getView()).isEqualTo(view); + assertThat(viewManager.getView(VIEW_NAME).getAggregationMap()).isEmpty(); + assertThat(viewManager.getView(VIEW_NAME).getWindowData()).isInstanceOf(CumulativeData.class); + } + + @Test + public void testGetAllExportedViews() { + assertThat(viewManager.getAllExportedViews()).isEmpty(); + View cumulativeView1 = + createCumulativeView( + View.Name.create("View 1"), MEASURE_DOUBLE, DISTRIBUTION, Arrays.asList(KEY)); + View cumulativeView2 = + createCumulativeView( + View.Name.create("View 2"), MEASURE_DOUBLE, DISTRIBUTION, Arrays.asList(KEY)); + View intervalView = + View.create( + View.Name.create("View 3"), + VIEW_DESCRIPTION, + MEASURE_DOUBLE, + DISTRIBUTION, + Arrays.asList(KEY), + INTERVAL); + viewManager.registerView(cumulativeView1); + viewManager.registerView(cumulativeView2); + viewManager.registerView(intervalView); + + // Only cumulative views should be exported. + assertThat(viewManager.getAllExportedViews()).containsExactly(cumulativeView1, cumulativeView2); + } + + @Test + public void getAllExportedViewsResultIsUnmodifiable() { + View view1 = + View.create( + View.Name.create("View 1"), + VIEW_DESCRIPTION, + MEASURE_DOUBLE, + DISTRIBUTION, + Arrays.asList(KEY), + CUMULATIVE); + viewManager.registerView(view1); + Set<View> exported = viewManager.getAllExportedViews(); + + View view2 = + View.create( + View.Name.create("View 2"), + VIEW_DESCRIPTION, + MEASURE_DOUBLE, + DISTRIBUTION, + Arrays.asList(KEY), + CUMULATIVE); + thrown.expect(UnsupportedOperationException.class); + exported.add(view2); + } + + @Test + public void testRegisterAndGetIntervalView() { + View intervalView = + View.create( + VIEW_NAME, + VIEW_DESCRIPTION, + MEASURE_DOUBLE, + DISTRIBUTION, + Arrays.asList(KEY), + INTERVAL); + viewManager.registerView(intervalView); + assertThat(viewManager.getView(VIEW_NAME).getView()).isEqualTo(intervalView); + assertThat(viewManager.getView(VIEW_NAME).getAggregationMap()).isEmpty(); + assertThat(viewManager.getView(VIEW_NAME).getWindowData()).isInstanceOf(IntervalData.class); + } + + @Test + public void allowRegisteringSameViewTwice() { + View view = createCumulativeView(); + viewManager.registerView(view); + viewManager.registerView(view); + assertThat(viewManager.getView(VIEW_NAME).getView()).isEqualTo(view); + } + + @Test + public void preventRegisteringDifferentViewWithSameName() { + View view1 = + View.create( + VIEW_NAME, + "View description.", + MEASURE_DOUBLE, + DISTRIBUTION, + Arrays.asList(KEY), + CUMULATIVE); + View view2 = + View.create( + VIEW_NAME, + "This is a different description.", + MEASURE_DOUBLE, + DISTRIBUTION, + Arrays.asList(KEY), + CUMULATIVE); + testFailedToRegisterView( + view1, view2, "A different view with the same name is already registered"); + } + + @Test + public void preventRegisteringDifferentMeasureWithSameName() { + MeasureDouble measure1 = MeasureDouble.create("measure", "description", "1"); + MeasureLong measure2 = MeasureLong.create("measure", "description", "1"); + View view1 = + View.create( + VIEW_NAME, VIEW_DESCRIPTION, measure1, DISTRIBUTION, Arrays.asList(KEY), CUMULATIVE); + View view2 = + View.create( + VIEW_NAME_2, VIEW_DESCRIPTION, measure2, DISTRIBUTION, Arrays.asList(KEY), CUMULATIVE); + testFailedToRegisterView( + view1, view2, "A different measure with the same name is already registered"); + } + + private void testFailedToRegisterView(View view1, View view2, String message) { + viewManager.registerView(view1); + try { + thrown.expect(IllegalArgumentException.class); + thrown.expectMessage(message); + viewManager.registerView(view2); + } finally { + assertThat(viewManager.getView(VIEW_NAME).getView()).isEqualTo(view1); + } + } + + @Test + public void returnNullWhenGettingNonexistentViewData() { + assertThat(viewManager.getView(VIEW_NAME)).isNull(); + } + + @Test + public void testRecordDouble_distribution_cumulative() { + testRecordCumulative(MEASURE_DOUBLE, DISTRIBUTION, 10.0, 20.0, 30.0, 40.0); + } + + @Test + public void testRecordLong_distribution_cumulative() { + testRecordCumulative(MEASURE_LONG, DISTRIBUTION, 1000, 2000, 3000, 4000); + } + + @Test + public void testRecordDouble_sum_cumulative() { + testRecordCumulative(MEASURE_DOUBLE, SUM, 11.1, 22.2, 33.3, 44.4); + } + + @Test + public void testRecordLong_sum_cumulative() { + testRecordCumulative(MEASURE_LONG, SUM, 1000, 2000, 3000, 4000); + } + + @Test + public void testRecordDouble_lastvalue_cumulative() { + testRecordCumulative(MEASURE_DOUBLE, LAST_VALUE, 11.1, 22.2, 33.3, 44.4); + } + + @Test + public void testRecordLong_lastvalue_cumulative() { + testRecordCumulative(MEASURE_LONG, LAST_VALUE, 1000, 2000, 3000, 4000); + } + + private void testRecordCumulative(Measure measure, Aggregation aggregation, double... values) { + View view = createCumulativeView(VIEW_NAME, measure, aggregation, Arrays.asList(KEY)); + clock.setTime(Timestamp.create(1, 2)); + viewManager.registerView(view); + TagContext tags = tagger.emptyBuilder().put(KEY, VALUE).build(); + for (double val : values) { + putToMeasureMap(statsRecorder.newMeasureMap(), measure, val).record(tags); + } + clock.setTime(Timestamp.create(3, 4)); + ViewData viewData = viewManager.getView(VIEW_NAME); + assertThat(viewData.getView()).isEqualTo(view); + assertThat(viewData.getWindowData()) + .isEqualTo(CumulativeData.create(Timestamp.create(1, 2), Timestamp.create(3, 4))); + StatsTestUtil.assertAggregationMapEquals( + viewData.getAggregationMap(), + ImmutableMap.of( + Arrays.asList(VALUE), + StatsTestUtil.createAggregationData(aggregation, measure, values)), + EPSILON); + } + + @Test + public void testRecordDouble_mean_interval() { + testRecordInterval( + MEASURE_DOUBLE, + MEAN, + new double[] {20.0, -1.0, 1.0, -5.0, 5.0}, + 9.0, + 30.0, + MeanData.create((19 * 0.6 + 1) / 4, 4), + MeanData.create(0.2 * 5 + 9, 1), + MeanData.create(30.0, 1)); + } + + @Test + public void testRecordLong_mean_interval() { + testRecordInterval( + MEASURE_LONG, + MEAN, + new double[] {1000, 2000, 3000, 4000, 5000}, + -5000, + 30, + MeanData.create((3000 * 0.6 + 12000) / 4, 4), + MeanData.create(-4000, 1), + MeanData.create(30, 1)); + } + + @Test + public void testRecordDouble_sum_interval() { + testRecordInterval( + MEASURE_DOUBLE, + SUM, + new double[] {20.0, -1.0, 1.0, -5.0, 5.0}, + 9.0, + 30.0, + SumDataDouble.create(19 * 0.6 + 1), + SumDataDouble.create(0.2 * 5 + 9), + SumDataDouble.create(30.0)); + } + + @Test + public void testRecordLong_sum_interval() { + testRecordInterval( + MEASURE_LONG, + SUM, + new double[] {10, 24, 30, 40, 50}, + -50, + 30, + SumDataLong.create(Math.round(34 * 0.6 + 120)), + SumDataLong.create(-40), + SumDataLong.create(30)); + } + + @Test + public void testRecordDouble_lastvalue_interval() { + testRecordInterval( + MEASURE_DOUBLE, + LAST_VALUE, + new double[] {20.0, -1.0, 1.0, -5.0, 5.0}, + 9.0, + 30.0, + LastValueDataDouble.create(5.0), + LastValueDataDouble.create(9.0), + LastValueDataDouble.create(30.0)); + } + + @Test + public void testRecordLong_lastvalue_interval() { + testRecordInterval( + MEASURE_LONG, + LAST_VALUE, + new double[] {1000, 2000, 3000, 4000, 5000}, + -5000, + 30, + LastValueDataLong.create(5000), + LastValueDataLong.create(-5000), + LastValueDataLong.create(30)); + } + + private final void testRecordInterval( + Measure measure, + Aggregation aggregation, + double[] initialValues, /* There are 5 initial values recorded before we call getView(). */ + double value6, + double value7, + AggregationData expectedValues1, + AggregationData expectedValues2, + AggregationData expectedValues3) { + // The interval is 10 seconds, i.e. values should expire after 10 seconds. + // Each bucket has a duration of 2.5 seconds. + View view = + View.create( + VIEW_NAME, + VIEW_DESCRIPTION, + measure, + aggregation, + Arrays.asList(KEY), + Interval.create(TEN_SECONDS)); + long startTimeMillis = 30 * MILLIS_PER_SECOND; // start at 30s + clock.setTime(Timestamp.fromMillis(startTimeMillis)); + viewManager.registerView(view); + + TagContext tags = tagger.emptyBuilder().put(KEY, VALUE).build(); + + for (int i = 1; i <= 5; i++) { + /* + * Add each value in sequence, at 31s, 32s, 33s, etc. + * 1st and 2nd values should fall into the first bucket [30.0, 32.5), + * 3rd and 4th values should fall into the second bucket [32.5, 35.0), + * 5th value should fall into the third bucket [35.0, 37.5). + */ + clock.setTime(Timestamp.fromMillis(startTimeMillis + i * MILLIS_PER_SECOND)); + putToMeasureMap(statsRecorder.newMeasureMap(), measure, initialValues[i - 1]).record(tags); + } + + clock.setTime(Timestamp.fromMillis(startTimeMillis + 8 * MILLIS_PER_SECOND)); + // 38s, no values should have expired + StatsTestUtil.assertAggregationMapEquals( + viewManager.getView(VIEW_NAME).getAggregationMap(), + ImmutableMap.of( + Arrays.asList(VALUE), + StatsTestUtil.createAggregationData(aggregation, measure, initialValues)), + EPSILON); + + clock.setTime(Timestamp.fromMillis(startTimeMillis + 11 * MILLIS_PER_SECOND)); + // 41s, 40% of the values in the first bucket should have expired (1 / 2.5 = 0.4). + StatsTestUtil.assertAggregationMapEquals( + viewManager.getView(VIEW_NAME).getAggregationMap(), + ImmutableMap.of(Arrays.asList(VALUE), expectedValues1), + EPSILON); + + clock.setTime(Timestamp.fromMillis(startTimeMillis + 12 * MILLIS_PER_SECOND)); + // 42s, add a new value value1, should fall into bucket [40.0, 42.5) + putToMeasureMap(statsRecorder.newMeasureMap(), measure, value6).record(tags); + + clock.setTime(Timestamp.fromMillis(startTimeMillis + 17 * MILLIS_PER_SECOND)); + // 47s, values in the first and second bucket should have expired, and 80% of values in the + // third bucket should have expired. The new value should persist. + StatsTestUtil.assertAggregationMapEquals( + viewManager.getView(VIEW_NAME).getAggregationMap(), + ImmutableMap.of(Arrays.asList(VALUE), expectedValues2), + EPSILON); + + clock.setTime(Timestamp.fromMillis(60 * MILLIS_PER_SECOND)); + // 60s, all previous values should have expired, add another value value2 + putToMeasureMap(statsRecorder.newMeasureMap(), measure, value7).record(tags); + StatsTestUtil.assertAggregationMapEquals( + viewManager.getView(VIEW_NAME).getAggregationMap(), + ImmutableMap.of(Arrays.asList(VALUE), expectedValues3), + EPSILON); + + clock.setTime(Timestamp.fromMillis(100 * MILLIS_PER_SECOND)); + // 100s, all values should have expired + assertThat(viewManager.getView(VIEW_NAME).getAggregationMap()).isEmpty(); + } + + @Test + public void getViewDoesNotClearStats() { + View view = createCumulativeView(VIEW_NAME, MEASURE_DOUBLE, DISTRIBUTION, Arrays.asList(KEY)); + clock.setTime(Timestamp.create(10, 0)); + viewManager.registerView(view); + TagContext tags = tagger.emptyBuilder().put(KEY, VALUE).build(); + statsRecorder.newMeasureMap().put(MEASURE_DOUBLE, 0.1).record(tags); + clock.setTime(Timestamp.create(11, 0)); + ViewData viewData1 = viewManager.getView(VIEW_NAME); + assertThat(viewData1.getWindowData()) + .isEqualTo(CumulativeData.create(Timestamp.create(10, 0), Timestamp.create(11, 0))); + StatsTestUtil.assertAggregationMapEquals( + viewData1.getAggregationMap(), + ImmutableMap.of( + Arrays.asList(VALUE), + StatsTestUtil.createAggregationData(DISTRIBUTION, MEASURE_DOUBLE, 0.1)), + EPSILON); + + statsRecorder.newMeasureMap().put(MEASURE_DOUBLE, 0.2).record(tags); + clock.setTime(Timestamp.create(12, 0)); + ViewData viewData2 = viewManager.getView(VIEW_NAME); + + // The second view should have the same start time as the first view, and it should include both + // recorded values: + assertThat(viewData2.getWindowData()) + .isEqualTo(CumulativeData.create(Timestamp.create(10, 0), Timestamp.create(12, 0))); + StatsTestUtil.assertAggregationMapEquals( + viewData2.getAggregationMap(), + ImmutableMap.of( + Arrays.asList(VALUE), + StatsTestUtil.createAggregationData(DISTRIBUTION, MEASURE_DOUBLE, 0.1, 0.2)), + EPSILON); + } + + @Test + public void testRecordCumulativeMultipleTagValues() { + viewManager.registerView( + createCumulativeView(VIEW_NAME, MEASURE_DOUBLE, DISTRIBUTION, Arrays.asList(KEY))); + statsRecorder + .newMeasureMap() + .put(MEASURE_DOUBLE, 10.0) + .record(tagger.emptyBuilder().put(KEY, VALUE).build()); + statsRecorder + .newMeasureMap() + .put(MEASURE_DOUBLE, 30.0) + .record(tagger.emptyBuilder().put(KEY, VALUE_2).build()); + statsRecorder + .newMeasureMap() + .put(MEASURE_DOUBLE, 50.0) + .record(tagger.emptyBuilder().put(KEY, VALUE_2).build()); + ViewData viewData = viewManager.getView(VIEW_NAME); + assertAggregationMapEquals( + viewData.getAggregationMap(), + ImmutableMap.of( + Arrays.asList(VALUE), + createAggregationData(DISTRIBUTION, MEASURE_DOUBLE, 10.0), + Arrays.asList(VALUE_2), + createAggregationData(DISTRIBUTION, MEASURE_DOUBLE, 30.0, 50.0)), + EPSILON); + } + + @Test + public void testRecordIntervalMultipleTagValues() { + // The interval is 10 seconds, i.e. values should expire after 10 seconds. + View view = + View.create( + VIEW_NAME, + VIEW_DESCRIPTION, + MEASURE_DOUBLE, + DISTRIBUTION, + Arrays.asList(KEY), + Interval.create(TEN_SECONDS)); + clock.setTime(Timestamp.create(10, 0)); // Start at 10s + viewManager.registerView(view); + + // record for TagValue1 at 11s + clock.setTime(Timestamp.fromMillis(11 * MILLIS_PER_SECOND)); + statsRecorder + .newMeasureMap() + .put(MEASURE_DOUBLE, 10.0) + .record(tagger.emptyBuilder().put(KEY, VALUE).build()); + + // record for TagValue2 at 15s + clock.setTime(Timestamp.fromMillis(15 * MILLIS_PER_SECOND)); + statsRecorder + .newMeasureMap() + .put(MEASURE_DOUBLE, 30.0) + .record(tagger.emptyBuilder().put(KEY, VALUE_2).build()); + statsRecorder + .newMeasureMap() + .put(MEASURE_DOUBLE, 50.0) + .record(tagger.emptyBuilder().put(KEY, VALUE_2).build()); + + // get ViewData at 19s, no stats should have expired. + clock.setTime(Timestamp.fromMillis(19 * MILLIS_PER_SECOND)); + ViewData viewData1 = viewManager.getView(VIEW_NAME); + StatsTestUtil.assertAggregationMapEquals( + viewData1.getAggregationMap(), + ImmutableMap.of( + Arrays.asList(VALUE), + StatsTestUtil.createAggregationData(DISTRIBUTION, MEASURE_DOUBLE, 10.0), + Arrays.asList(VALUE_2), + StatsTestUtil.createAggregationData(DISTRIBUTION, MEASURE_DOUBLE, 30.0, 50.0)), + EPSILON); + + // record for TagValue2 again at 20s + clock.setTime(Timestamp.fromMillis(20 * MILLIS_PER_SECOND)); + statsRecorder + .newMeasureMap() + .put(MEASURE_DOUBLE, 40.0) + .record(tagger.emptyBuilder().put(KEY, VALUE_2).build()); + + // get ViewData at 25s, stats for TagValue1 should have expired. + clock.setTime(Timestamp.fromMillis(25 * MILLIS_PER_SECOND)); + ViewData viewData2 = viewManager.getView(VIEW_NAME); + StatsTestUtil.assertAggregationMapEquals( + viewData2.getAggregationMap(), + ImmutableMap.of( + Arrays.asList(VALUE_2), + StatsTestUtil.createAggregationData(DISTRIBUTION, MEASURE_DOUBLE, 30.0, 50.0, 40.0)), + EPSILON); + + // get ViewData at 30s, the first two values for TagValue2 should have expired. + clock.setTime(Timestamp.fromMillis(30 * MILLIS_PER_SECOND)); + ViewData viewData3 = viewManager.getView(VIEW_NAME); + StatsTestUtil.assertAggregationMapEquals( + viewData3.getAggregationMap(), + ImmutableMap.of( + Arrays.asList(VALUE_2), + StatsTestUtil.createAggregationData(DISTRIBUTION, MEASURE_DOUBLE, 40.0)), + EPSILON); + + // get ViewData at 40s, all stats should have expired. + clock.setTime(Timestamp.fromMillis(40 * MILLIS_PER_SECOND)); + ViewData viewData4 = viewManager.getView(VIEW_NAME); + assertThat(viewData4.getAggregationMap()).isEmpty(); + } + + // This test checks that MeasureMaper.record(...) does not throw an exception when no views are + // registered. + @Test + public void allowRecordingWithoutRegisteringMatchingViewData() { + statsRecorder + .newMeasureMap() + .put(MEASURE_DOUBLE, 10) + .record(tagger.emptyBuilder().put(KEY, VALUE).build()); + } + + @Test + public void testRecordWithEmptyStatsContext() { + viewManager.registerView( + createCumulativeView(VIEW_NAME, MEASURE_DOUBLE, DISTRIBUTION, Arrays.asList(KEY))); + // DEFAULT doesn't have tags, but the view has tag key "KEY". + statsRecorder.newMeasureMap().put(MEASURE_DOUBLE, 10.0).record(tagger.empty()); + ViewData viewData = viewManager.getView(VIEW_NAME); + assertAggregationMapEquals( + viewData.getAggregationMap(), + ImmutableMap.of( + // Tag is missing for associated measureValues, should use default tag value + // "unknown/not set". + Arrays.asList(RecordUtils.UNKNOWN_TAG_VALUE), + // Should record stats with default tag value: "KEY" : "unknown/not set". + createAggregationData(DISTRIBUTION, MEASURE_DOUBLE, 10.0)), + EPSILON); + } + + @Test + public void testRecord_MeasureNameNotMatch() { + testRecord_MeasureNotMatch( + MeasureDouble.create(MEASURE_NAME, "measure", MEASURE_UNIT), + MeasureDouble.create(MEASURE_NAME_2, "measure", MEASURE_UNIT), + 10.0); + } + + @Test + public void testRecord_MeasureTypeNotMatch() { + testRecord_MeasureNotMatch( + MeasureLong.create(MEASURE_NAME, "measure", MEASURE_UNIT), + MeasureDouble.create(MEASURE_NAME, "measure", MEASURE_UNIT), + 10.0); + } + + private void testRecord_MeasureNotMatch(Measure measure1, Measure measure2, double value) { + viewManager.registerView(createCumulativeView(VIEW_NAME, measure1, MEAN, Arrays.asList(KEY))); + TagContext tags = tagger.emptyBuilder().put(KEY, VALUE).build(); + putToMeasureMap(statsRecorder.newMeasureMap(), measure2, value).record(tags); + ViewData view = viewManager.getView(VIEW_NAME); + assertThat(view.getAggregationMap()).isEmpty(); + } + + @Test + public void testRecordWithTagsThatDoNotMatchViewData() { + viewManager.registerView( + createCumulativeView(VIEW_NAME, MEASURE_DOUBLE, DISTRIBUTION, Arrays.asList(KEY))); + statsRecorder + .newMeasureMap() + .put(MEASURE_DOUBLE, 10.0) + .record(tagger.emptyBuilder().put(TagKey.create("wrong key"), VALUE).build()); + statsRecorder + .newMeasureMap() + .put(MEASURE_DOUBLE, 50.0) + .record(tagger.emptyBuilder().put(TagKey.create("another wrong key"), VALUE).build()); + ViewData viewData = viewManager.getView(VIEW_NAME); + assertAggregationMapEquals( + viewData.getAggregationMap(), + ImmutableMap.of( + // Won't record the unregistered tag key, for missing registered keys will use default + // tag value : "unknown/not set". + Arrays.asList(RecordUtils.UNKNOWN_TAG_VALUE), + // Should record stats with default tag value: "KEY" : "unknown/not set". + createAggregationData(DISTRIBUTION, MEASURE_DOUBLE, 10.0, 50.0)), + EPSILON); + } + + @Test + public void testViewDataWithMultipleTagKeys() { + TagKey key1 = TagKey.create("Key-1"); + TagKey key2 = TagKey.create("Key-2"); + viewManager.registerView( + createCumulativeView(VIEW_NAME, MEASURE_DOUBLE, DISTRIBUTION, Arrays.asList(key1, key2))); + statsRecorder + .newMeasureMap() + .put(MEASURE_DOUBLE, 1.1) + .record( + tagger + .emptyBuilder() + .put(key1, TagValue.create("v1")) + .put(key2, TagValue.create("v10")) + .build()); + statsRecorder + .newMeasureMap() + .put(MEASURE_DOUBLE, 2.2) + .record( + tagger + .emptyBuilder() + .put(key1, TagValue.create("v1")) + .put(key2, TagValue.create("v20")) + .build()); + statsRecorder + .newMeasureMap() + .put(MEASURE_DOUBLE, 3.3) + .record( + tagger + .emptyBuilder() + .put(key1, TagValue.create("v2")) + .put(key2, TagValue.create("v10")) + .build()); + statsRecorder + .newMeasureMap() + .put(MEASURE_DOUBLE, 4.4) + .record( + tagger + .emptyBuilder() + .put(key1, TagValue.create("v1")) + .put(key2, TagValue.create("v10")) + .build()); + ViewData viewData = viewManager.getView(VIEW_NAME); + assertAggregationMapEquals( + viewData.getAggregationMap(), + ImmutableMap.of( + Arrays.asList(TagValue.create("v1"), TagValue.create("v10")), + StatsTestUtil.createAggregationData(DISTRIBUTION, MEASURE_DOUBLE, 1.1, 4.4), + Arrays.asList(TagValue.create("v1"), TagValue.create("v20")), + StatsTestUtil.createAggregationData(DISTRIBUTION, MEASURE_DOUBLE, 2.2), + Arrays.asList(TagValue.create("v2"), TagValue.create("v10")), + StatsTestUtil.createAggregationData(DISTRIBUTION, MEASURE_DOUBLE, 3.3)), + EPSILON); + } + + @Test + public void testMultipleViewSameMeasure() { + final View view1 = + createCumulativeView(VIEW_NAME, MEASURE_DOUBLE, DISTRIBUTION, Arrays.asList(KEY)); + final View view2 = + createCumulativeView(VIEW_NAME_2, MEASURE_DOUBLE, DISTRIBUTION, Arrays.asList(KEY)); + clock.setTime(Timestamp.create(1, 1)); + viewManager.registerView(view1); + clock.setTime(Timestamp.create(2, 2)); + viewManager.registerView(view2); + statsRecorder + .newMeasureMap() + .put(MEASURE_DOUBLE, 5.0) + .record(tagger.emptyBuilder().put(KEY, VALUE).build()); + clock.setTime(Timestamp.create(3, 3)); + ViewData viewData1 = viewManager.getView(VIEW_NAME); + clock.setTime(Timestamp.create(4, 4)); + ViewData viewData2 = viewManager.getView(VIEW_NAME_2); + assertThat(viewData1.getWindowData()) + .isEqualTo(CumulativeData.create(Timestamp.create(1, 1), Timestamp.create(3, 3))); + StatsTestUtil.assertAggregationMapEquals( + viewData1.getAggregationMap(), + ImmutableMap.of( + Arrays.asList(VALUE), + StatsTestUtil.createAggregationData(DISTRIBUTION, MEASURE_DOUBLE, 5.0)), + EPSILON); + assertThat(viewData2.getWindowData()) + .isEqualTo(CumulativeData.create(Timestamp.create(2, 2), Timestamp.create(4, 4))); + StatsTestUtil.assertAggregationMapEquals( + viewData2.getAggregationMap(), + ImmutableMap.of( + Arrays.asList(VALUE), + StatsTestUtil.createAggregationData(DISTRIBUTION, MEASURE_DOUBLE, 5.0)), + EPSILON); + } + + @Test + public void testMultipleViews_DifferentMeasureNames() { + testMultipleViews_DifferentMeasures( + MeasureDouble.create(MEASURE_NAME, MEASURE_DESCRIPTION, MEASURE_UNIT), + MeasureDouble.create(MEASURE_NAME_2, MEASURE_DESCRIPTION, MEASURE_UNIT), + 1.1, + 2.2); + } + + @Test + public void testMultipleViews_DifferentMeasureTypes() { + testMultipleViews_DifferentMeasures( + MeasureDouble.create(MEASURE_NAME, MEASURE_DESCRIPTION, MEASURE_UNIT), + MeasureLong.create(MEASURE_NAME_2, MEASURE_DESCRIPTION, MEASURE_UNIT), + 1.1, + 5000); + } + + private void testMultipleViews_DifferentMeasures( + Measure measure1, Measure measure2, double value1, double value2) { + final View view1 = createCumulativeView(VIEW_NAME, measure1, DISTRIBUTION, Arrays.asList(KEY)); + final View view2 = + createCumulativeView(VIEW_NAME_2, measure2, DISTRIBUTION, Arrays.asList(KEY)); + clock.setTime(Timestamp.create(1, 0)); + viewManager.registerView(view1); + clock.setTime(Timestamp.create(2, 0)); + viewManager.registerView(view2); + TagContext tags = tagger.emptyBuilder().put(KEY, VALUE).build(); + MeasureMap measureMap = statsRecorder.newMeasureMap(); + putToMeasureMap(measureMap, measure1, value1); + putToMeasureMap(measureMap, measure2, value2); + measureMap.record(tags); + clock.setTime(Timestamp.create(3, 0)); + ViewData viewData1 = viewManager.getView(VIEW_NAME); + clock.setTime(Timestamp.create(4, 0)); + ViewData viewData2 = viewManager.getView(VIEW_NAME_2); + assertThat(viewData1.getWindowData()) + .isEqualTo(CumulativeData.create(Timestamp.create(1, 0), Timestamp.create(3, 0))); + StatsTestUtil.assertAggregationMapEquals( + viewData1.getAggregationMap(), + ImmutableMap.of( + Arrays.asList(VALUE), + StatsTestUtil.createAggregationData(DISTRIBUTION, measure1, value1)), + EPSILON); + assertThat(viewData2.getWindowData()) + .isEqualTo(CumulativeData.create(Timestamp.create(2, 0), Timestamp.create(4, 0))); + StatsTestUtil.assertAggregationMapEquals( + viewData2.getAggregationMap(), + ImmutableMap.of( + Arrays.asList(VALUE), + StatsTestUtil.createAggregationData(DISTRIBUTION, measure2, value2)), + EPSILON); + } + + @Test + public void testGetCumulativeViewDataWithEmptyBucketBoundaries() { + Aggregation noHistogram = + Distribution.create(BucketBoundaries.create(Collections.<Double>emptyList())); + View view = createCumulativeView(VIEW_NAME, MEASURE_DOUBLE, noHistogram, Arrays.asList(KEY)); + clock.setTime(Timestamp.create(1, 0)); + viewManager.registerView(view); + statsRecorder + .newMeasureMap() + .put(MEASURE_DOUBLE, 1.1) + .record(tagger.emptyBuilder().put(KEY, VALUE).build()); + clock.setTime(Timestamp.create(3, 0)); + ViewData viewData = viewManager.getView(VIEW_NAME); + assertThat(viewData.getWindowData()) + .isEqualTo(CumulativeData.create(Timestamp.create(1, 0), Timestamp.create(3, 0))); + StatsTestUtil.assertAggregationMapEquals( + viewData.getAggregationMap(), + ImmutableMap.of( + Arrays.asList(VALUE), + StatsTestUtil.createAggregationData(noHistogram, MEASURE_DOUBLE, 1.1)), + EPSILON); + } + + @Test + public void testGetCumulativeViewDataWithoutBucketBoundaries() { + View view = createCumulativeView(VIEW_NAME, MEASURE_DOUBLE, MEAN, Arrays.asList(KEY)); + clock.setTime(Timestamp.create(1, 0)); + viewManager.registerView(view); + statsRecorder + .newMeasureMap() + .put(MEASURE_DOUBLE, 1.1) + .record(tagger.emptyBuilder().put(KEY, VALUE).build()); + clock.setTime(Timestamp.create(3, 0)); + ViewData viewData = viewManager.getView(VIEW_NAME); + assertThat(viewData.getWindowData()) + .isEqualTo(CumulativeData.create(Timestamp.create(1, 0), Timestamp.create(3, 0))); + StatsTestUtil.assertAggregationMapEquals( + viewData.getAggregationMap(), + ImmutableMap.of( + Arrays.asList(VALUE), StatsTestUtil.createAggregationData(MEAN, MEASURE_DOUBLE, 1.1)), + EPSILON); + } + + @Test + @SuppressWarnings("deprecation") + public void registerRecordAndGetView_StatsDisabled() { + statsComponent.setState(StatsCollectionState.DISABLED); + View view = createCumulativeView(VIEW_NAME, MEASURE_DOUBLE, MEAN, Arrays.asList(KEY)); + viewManager.registerView(view); + statsRecorder + .newMeasureMap() + .put(MEASURE_DOUBLE, 1.1) + .record(tagger.emptyBuilder().put(KEY, VALUE).build()); + assertThat(viewManager.getView(VIEW_NAME)).isEqualTo(createEmptyViewData(view)); + } + + @Test + @SuppressWarnings("deprecation") + public void registerRecordAndGetView_StatsReenabled() { + statsComponent.setState(StatsCollectionState.DISABLED); + statsComponent.setState(StatsCollectionState.ENABLED); + View view = createCumulativeView(VIEW_NAME, MEASURE_DOUBLE, MEAN, Arrays.asList(KEY)); + viewManager.registerView(view); + statsRecorder + .newMeasureMap() + .put(MEASURE_DOUBLE, 1.1) + .record(tagger.emptyBuilder().put(KEY, VALUE).build()); + StatsTestUtil.assertAggregationMapEquals( + viewManager.getView(VIEW_NAME).getAggregationMap(), + ImmutableMap.of( + Arrays.asList(VALUE), StatsTestUtil.createAggregationData(MEAN, MEASURE_DOUBLE, 1.1)), + EPSILON); + } + + @Test + @SuppressWarnings("deprecation") + public void registerViewWithStatsDisabled_RecordAndGetViewWithStatsEnabled() { + statsComponent.setState(StatsCollectionState.DISABLED); + View view = createCumulativeView(VIEW_NAME, MEASURE_DOUBLE, MEAN, Arrays.asList(KEY)); + viewManager.registerView(view); // view will still be registered. + + statsComponent.setState(StatsCollectionState.ENABLED); + statsRecorder + .newMeasureMap() + .put(MEASURE_DOUBLE, 1.1) + .record(tagger.emptyBuilder().put(KEY, VALUE).build()); + StatsTestUtil.assertAggregationMapEquals( + viewManager.getView(VIEW_NAME).getAggregationMap(), + ImmutableMap.of( + Arrays.asList(VALUE), StatsTestUtil.createAggregationData(MEAN, MEASURE_DOUBLE, 1.1)), + EPSILON); + } + + @Test + @SuppressWarnings("deprecation") + public void registerDifferentViewWithSameNameWithStatsDisabled() { + statsComponent.setState(StatsCollectionState.DISABLED); + View view1 = + View.create( + VIEW_NAME, + "View description.", + MEASURE_DOUBLE, + DISTRIBUTION, + Arrays.asList(KEY), + CUMULATIVE); + View view2 = + View.create( + VIEW_NAME, + "This is a different description.", + MEASURE_DOUBLE, + DISTRIBUTION, + Arrays.asList(KEY), + CUMULATIVE); + testFailedToRegisterView( + view1, view2, "A different view with the same name is already registered"); + } + + @Test + public void settingStateToDisabledWillClearStats_Cumulative() { + View cumulativeView = createCumulativeView(VIEW_NAME, MEASURE_DOUBLE, MEAN, Arrays.asList(KEY)); + settingStateToDisabledWillClearStats(cumulativeView); + } + + @Test + public void settingStateToDisabledWillClearStats_Interval() { + View intervalView = + View.create( + VIEW_NAME_2, + VIEW_DESCRIPTION, + MEASURE_DOUBLE, + MEAN, + Arrays.asList(KEY), + Interval.create(Duration.create(60, 0))); + settingStateToDisabledWillClearStats(intervalView); + } + + @SuppressWarnings("deprecation") + private void settingStateToDisabledWillClearStats(View view) { + Timestamp timestamp1 = Timestamp.create(1, 0); + clock.setTime(timestamp1); + viewManager.registerView(view); + statsRecorder + .newMeasureMap() + .put(MEASURE_DOUBLE, 1.1) + .record(tagger.emptyBuilder().put(KEY, VALUE).build()); + StatsTestUtil.assertAggregationMapEquals( + viewManager.getView(view.getName()).getAggregationMap(), + ImmutableMap.of( + Arrays.asList(VALUE), + StatsTestUtil.createAggregationData(view.getAggregation(), view.getMeasure(), 1.1)), + EPSILON); + + Timestamp timestamp2 = Timestamp.create(2, 0); + clock.setTime(timestamp2); + statsComponent.setState(StatsCollectionState.DISABLED); // This will clear stats. + assertThat(viewManager.getView(view.getName())).isEqualTo(createEmptyViewData(view)); + + Timestamp timestamp3 = Timestamp.create(3, 0); + clock.setTime(timestamp3); + statsComponent.setState(StatsCollectionState.ENABLED); + + Timestamp timestamp4 = Timestamp.create(4, 0); + clock.setTime(timestamp4); + // This ViewData does not have any stats, but it should not be an empty ViewData, since it has + // non-zero TimeStamps. + ViewData viewData = viewManager.getView(view.getName()); + assertThat(viewData.getAggregationMap()).isEmpty(); + AggregationWindowData windowData = viewData.getWindowData(); + if (windowData instanceof CumulativeData) { + assertThat(windowData).isEqualTo(CumulativeData.create(timestamp3, timestamp4)); + } else { + assertThat(windowData).isEqualTo(IntervalData.create(timestamp4)); + } + } + + private static MeasureMap putToMeasureMap(MeasureMap measureMap, Measure measure, double value) { + if (measure instanceof MeasureDouble) { + return measureMap.put((MeasureDouble) measure, value); + } else if (measure instanceof MeasureLong) { + return measureMap.put((MeasureLong) measure, Math.round(value)); + } else { + // Future measures. + throw new AssertionError(); + } + } +} diff --git a/impl_core/src/test/java/io/opencensus/implcore/tags/CurrentTagContextUtilsTest.java b/impl_core/src/test/java/io/opencensus/implcore/tags/CurrentTagContextUtilsTest.java new file mode 100644 index 00000000..1a14ac6e --- /dev/null +++ b/impl_core/src/test/java/io/opencensus/implcore/tags/CurrentTagContextUtilsTest.java @@ -0,0 +1,103 @@ +/* + * Copyright 2017, OpenCensus Authors + * + * 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 io.opencensus.implcore.tags; + +import static com.google.common.truth.Truth.assertThat; +import static io.opencensus.implcore.tags.TagsTestUtil.tagContextToList; + +import com.google.common.collect.ImmutableSet; +import io.grpc.Context; +import io.opencensus.common.Scope; +import io.opencensus.tags.Tag; +import io.opencensus.tags.TagContext; +import io.opencensus.tags.TagKey; +import io.opencensus.tags.TagValue; +import io.opencensus.tags.unsafe.ContextUtils; +import java.util.Iterator; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Unit tests for {@link CurrentTagContextUtils}. */ +@RunWith(JUnit4.class) +public class CurrentTagContextUtilsTest { + private static final Tag TAG = Tag.create(TagKey.create("key"), TagValue.create("value")); + + private final TagContext tagContext = + new TagContext() { + + @Override + protected Iterator<Tag> getIterator() { + return ImmutableSet.<Tag>of(TAG).iterator(); + } + }; + + @Test + public void testGetCurrentTagContext_DefaultContext() { + TagContext tags = CurrentTagContextUtils.getCurrentTagContext(); + assertThat(tags).isNotNull(); + assertThat(tagContextToList(tags)).isEmpty(); + } + + @Test + public void testGetCurrentTagContext_ContextSetToNull() { + Context orig = Context.current().withValue(ContextUtils.TAG_CONTEXT_KEY, null).attach(); + try { + TagContext tags = CurrentTagContextUtils.getCurrentTagContext(); + assertThat(tags).isNotNull(); + assertThat(tagContextToList(tags)).isEmpty(); + } finally { + Context.current().detach(orig); + } + } + + @Test + public void testWithTagContext() { + assertThat(tagContextToList(CurrentTagContextUtils.getCurrentTagContext())).isEmpty(); + Scope scopedTags = CurrentTagContextUtils.withTagContext(tagContext); + try { + assertThat(CurrentTagContextUtils.getCurrentTagContext()).isSameAs(tagContext); + } finally { + scopedTags.close(); + } + assertThat(tagContextToList(CurrentTagContextUtils.getCurrentTagContext())).isEmpty(); + } + + @Test + public void testWithTagContextUsingWrap() { + Runnable runnable; + Scope scopedTags = CurrentTagContextUtils.withTagContext(tagContext); + try { + assertThat(CurrentTagContextUtils.getCurrentTagContext()).isSameAs(tagContext); + runnable = + Context.current() + .wrap( + new Runnable() { + @Override + public void run() { + assertThat(CurrentTagContextUtils.getCurrentTagContext()) + .isSameAs(tagContext); + } + }); + } finally { + scopedTags.close(); + } + assertThat(tagContextToList(CurrentTagContextUtils.getCurrentTagContext())).isEmpty(); + // When we run the runnable we will have the TagContext in the current Context. + runnable.run(); + } +} diff --git a/impl_core/src/test/java/io/opencensus/implcore/tags/ScopedTagContextsTest.java b/impl_core/src/test/java/io/opencensus/implcore/tags/ScopedTagContextsTest.java new file mode 100644 index 00000000..6a8fe4c7 --- /dev/null +++ b/impl_core/src/test/java/io/opencensus/implcore/tags/ScopedTagContextsTest.java @@ -0,0 +1,112 @@ +/* + * Copyright 2017, OpenCensus Authors + * + * 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 io.opencensus.implcore.tags; + +import static com.google.common.truth.Truth.assertThat; +import static io.opencensus.implcore.tags.TagsTestUtil.tagContextToList; + +import io.opencensus.common.Scope; +import io.opencensus.implcore.internal.CurrentState; +import io.opencensus.implcore.internal.CurrentState.State; +import io.opencensus.tags.Tag; +import io.opencensus.tags.TagContext; +import io.opencensus.tags.TagKey; +import io.opencensus.tags.TagValue; +import io.opencensus.tags.Tagger; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** + * Unit tests for the methods in {@link TaggerImpl} and {@link TagContextBuilderImpl} that interact + * with the current {@link TagContext}. + */ +@RunWith(JUnit4.class) +public class ScopedTagContextsTest { + private static final TagKey KEY_1 = TagKey.create("key 1"); + private static final TagKey KEY_2 = TagKey.create("key 2"); + + private static final TagValue VALUE_1 = TagValue.create("value 1"); + private static final TagValue VALUE_2 = TagValue.create("value 2"); + + private final Tagger tagger = new TaggerImpl(new CurrentState(State.ENABLED)); + + @Test + public void defaultTagContext() { + TagContext defaultTagContext = tagger.getCurrentTagContext(); + assertThat(tagContextToList(defaultTagContext)).isEmpty(); + assertThat(defaultTagContext).isInstanceOf(TagContextImpl.class); + } + + @Test + public void withTagContext() { + assertThat(tagContextToList(tagger.getCurrentTagContext())).isEmpty(); + TagContext scopedTags = tagger.emptyBuilder().put(KEY_1, VALUE_1).build(); + Scope scope = tagger.withTagContext(scopedTags); + try { + assertThat(tagger.getCurrentTagContext()).isSameAs(scopedTags); + } finally { + scope.close(); + } + assertThat(tagContextToList(tagger.getCurrentTagContext())).isEmpty(); + } + + @Test + public void createBuilderFromCurrentTags() { + TagContext scopedTags = tagger.emptyBuilder().put(KEY_1, VALUE_1).build(); + Scope scope = tagger.withTagContext(scopedTags); + try { + TagContext newTags = tagger.currentBuilder().put(KEY_2, VALUE_2).build(); + assertThat(tagContextToList(newTags)) + .containsExactly(Tag.create(KEY_1, VALUE_1), Tag.create(KEY_2, VALUE_2)); + assertThat(tagger.getCurrentTagContext()).isSameAs(scopedTags); + } finally { + scope.close(); + } + } + + @Test + public void setCurrentTagsWithBuilder() { + assertThat(tagContextToList(tagger.getCurrentTagContext())).isEmpty(); + Scope scope = tagger.emptyBuilder().put(KEY_1, VALUE_1).buildScoped(); + try { + assertThat(tagContextToList(tagger.getCurrentTagContext())) + .containsExactly(Tag.create(KEY_1, VALUE_1)); + } finally { + scope.close(); + } + assertThat(tagContextToList(tagger.getCurrentTagContext())).isEmpty(); + } + + @Test + public void addToCurrentTagsWithBuilder() { + TagContext scopedTags = tagger.emptyBuilder().put(KEY_1, VALUE_1).build(); + Scope scope1 = tagger.withTagContext(scopedTags); + try { + Scope scope2 = tagger.currentBuilder().put(KEY_2, VALUE_2).buildScoped(); + try { + assertThat(tagContextToList(tagger.getCurrentTagContext())) + .containsExactly(Tag.create(KEY_1, VALUE_1), Tag.create(KEY_2, VALUE_2)); + } finally { + scope2.close(); + } + assertThat(tagger.getCurrentTagContext()).isSameAs(scopedTags); + } finally { + scope1.close(); + } + } +} diff --git a/impl_core/src/test/java/io/opencensus/implcore/tags/TagContextImplTest.java b/impl_core/src/test/java/io/opencensus/implcore/tags/TagContextImplTest.java new file mode 100644 index 00000000..1859e081 --- /dev/null +++ b/impl_core/src/test/java/io/opencensus/implcore/tags/TagContextImplTest.java @@ -0,0 +1,167 @@ +/* + * Copyright 2017, OpenCensus Authors + * + * 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 io.opencensus.implcore.tags; + +import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Lists; +import com.google.common.testing.EqualsTester; +import io.opencensus.implcore.internal.CurrentState; +import io.opencensus.implcore.internal.CurrentState.State; +import io.opencensus.tags.Tag; +import io.opencensus.tags.TagContext; +import io.opencensus.tags.TagContextBuilder; +import io.opencensus.tags.TagKey; +import io.opencensus.tags.TagValue; +import io.opencensus.tags.Tagger; +import java.util.Arrays; +import java.util.Iterator; +import java.util.NoSuchElementException; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** + * Tests for {@link TagContextImpl} and {@link TagContextBuilderImpl}. + * + * <p>Tests for {@link TagContextBuilderImpl#buildScoped()} are in {@link ScopedTagContextsTest}. + */ +@RunWith(JUnit4.class) +public class TagContextImplTest { + private final Tagger tagger = new TaggerImpl(new CurrentState(State.ENABLED)); + + private static final TagKey K1 = TagKey.create("k1"); + private static final TagKey K2 = TagKey.create("k2"); + + private static final TagValue V1 = TagValue.create("v1"); + private static final TagValue V2 = TagValue.create("v2"); + + @Rule public final ExpectedException thrown = ExpectedException.none(); + + @Test + public void getTags_empty() { + TagContextImpl tags = new TagContextImpl(ImmutableMap.<TagKey, TagValue>of()); + assertThat(tags.getTags()).isEmpty(); + } + + @Test + public void getTags_nonEmpty() { + TagContextImpl tags = new TagContextImpl(ImmutableMap.of(K1, V1, K2, V2)); + assertThat(tags.getTags()).containsExactly(K1, V1, K2, V2); + } + + @Test + public void put_newKey() { + TagContext tags = new TagContextImpl(ImmutableMap.of(K1, V1)); + assertThat(((TagContextImpl) tagger.toBuilder(tags).put(K2, V2).build()).getTags()) + .containsExactly(K1, V1, K2, V2); + } + + @Test + public void put_existingKey() { + TagContext tags = new TagContextImpl(ImmutableMap.of(K1, V1)); + assertThat(((TagContextImpl) tagger.toBuilder(tags).put(K1, V2).build()).getTags()) + .containsExactly(K1, V2); + } + + @Test + public void put_nullKey() { + TagContext tags = new TagContextImpl(ImmutableMap.of(K1, V1)); + TagContextBuilder builder = tagger.toBuilder(tags); + thrown.expect(NullPointerException.class); + thrown.expectMessage("key"); + builder.put(null, V2); + } + + @Test + public void put_nullValue() { + TagContext tags = new TagContextImpl(ImmutableMap.of(K1, V1)); + TagContextBuilder builder = tagger.toBuilder(tags); + thrown.expect(NullPointerException.class); + thrown.expectMessage("value"); + builder.put(K2, null); + } + + @Test + public void remove_existingKey() { + TagContext tags = new TagContextImpl(ImmutableMap.of(K1, V1, K2, V2)); + assertThat(((TagContextImpl) tagger.toBuilder(tags).remove(K1).build()).getTags()) + .containsExactly(K2, V2); + } + + @Test + public void remove_differentKey() { + TagContext tags = new TagContextImpl(ImmutableMap.of(K1, V1)); + assertThat(((TagContextImpl) tagger.toBuilder(tags).remove(K2).build()).getTags()) + .containsExactly(K1, V1); + } + + @Test + public void remove_nullKey() { + TagContext tags = new TagContextImpl(ImmutableMap.of(K1, V1)); + TagContextBuilder builder = tagger.toBuilder(tags); + thrown.expect(NullPointerException.class); + thrown.expectMessage("key"); + builder.remove(null); + } + + @Test + public void testIterator() { + TagContextImpl tags = new TagContextImpl(ImmutableMap.of(K1, V1, K2, V2)); + Iterator<Tag> i = tags.getIterator(); + assertTrue(i.hasNext()); + Tag tag1 = i.next(); + assertTrue(i.hasNext()); + Tag tag2 = i.next(); + assertFalse(i.hasNext()); + assertThat(Arrays.asList(tag1, tag2)).containsExactly(Tag.create(K1, V1), Tag.create(K2, V2)); + thrown.expect(NoSuchElementException.class); + i.next(); + } + + @Test + public void disallowCallingRemoveOnIterator() { + TagContextImpl tags = new TagContextImpl(ImmutableMap.of(K1, V1, K2, V2)); + Iterator<Tag> i = tags.getIterator(); + i.next(); + thrown.expect(UnsupportedOperationException.class); + i.remove(); + } + + @Test + public void testEquals() { + new EqualsTester() + .addEqualityGroup( + tagger.emptyBuilder().put(K1, V1).put(K2, V2).build(), + tagger.emptyBuilder().put(K1, V1).put(K2, V2).build(), + tagger.emptyBuilder().put(K2, V2).put(K1, V1).build(), + new TagContext() { + @Override + protected Iterator<Tag> getIterator() { + return Lists.<Tag>newArrayList(Tag.create(K1, V1), Tag.create(K2, V2)).iterator(); + } + }) + .addEqualityGroup(tagger.emptyBuilder().put(K1, V1).put(K2, V1).build()) + .addEqualityGroup(tagger.emptyBuilder().put(K1, V2).put(K2, V1).build()) + .testEquals(); + } +} diff --git a/impl_core/src/test/java/io/opencensus/implcore/tags/TaggerImplTest.java b/impl_core/src/test/java/io/opencensus/implcore/tags/TaggerImplTest.java new file mode 100644 index 00000000..4ca2ae76 --- /dev/null +++ b/impl_core/src/test/java/io/opencensus/implcore/tags/TaggerImplTest.java @@ -0,0 +1,318 @@ +/* + * Copyright 2017, OpenCensus Authors + * + * 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 io.opencensus.implcore.tags; + +import static com.google.common.truth.Truth.assertThat; +import static io.opencensus.implcore.tags.TagsTestUtil.tagContextToList; + +import com.google.common.collect.Lists; +import io.grpc.Context; +import io.opencensus.common.Scope; +import io.opencensus.implcore.internal.NoopScope; +import io.opencensus.tags.Tag; +import io.opencensus.tags.TagContext; +import io.opencensus.tags.TagContextBuilder; +import io.opencensus.tags.TagKey; +import io.opencensus.tags.TagValue; +import io.opencensus.tags.Tagger; +import io.opencensus.tags.TaggingState; +import io.opencensus.tags.TagsComponent; +import io.opencensus.tags.unsafe.ContextUtils; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Tests for {@link TaggerImpl}. */ +@RunWith(JUnit4.class) +public class TaggerImplTest { + private final TagsComponent tagsComponent = new TagsComponentImplBase(); + private final Tagger tagger = tagsComponent.getTagger(); + + private static final TagKey K1 = TagKey.create("k1"); + private static final TagKey K2 = TagKey.create("k2"); + private static final TagKey K3 = TagKey.create("k3"); + + private static final TagValue V1 = TagValue.create("v1"); + private static final TagValue V2 = TagValue.create("v2"); + private static final TagValue V3 = TagValue.create("v3"); + + private static final Tag TAG1 = Tag.create(K1, V1); + private static final Tag TAG2 = Tag.create(K2, V2); + private static final Tag TAG3 = Tag.create(K3, V3); + + @Test + public void empty() { + assertThat(tagContextToList(tagger.empty())).isEmpty(); + assertThat(tagger.empty()).isInstanceOf(TagContextImpl.class); + } + + @Test + public void empty_TaggingDisabled() { + tagsComponent.setState(TaggingState.DISABLED); + assertThat(tagContextToList(tagger.empty())).isEmpty(); + assertThat(tagger.empty()).isInstanceOf(TagContextImpl.class); + } + + @Test + public void emptyBuilder() { + TagContextBuilder builder = tagger.emptyBuilder(); + assertThat(builder).isInstanceOf(TagContextBuilderImpl.class); + assertThat(tagContextToList(builder.build())).isEmpty(); + } + + @Test + public void emptyBuilder_TaggingDisabled() { + tagsComponent.setState(TaggingState.DISABLED); + assertThat(tagger.emptyBuilder()).isSameAs(NoopTagContextBuilder.INSTANCE); + } + + @Test + public void emptyBuilder_TaggingReenabled() { + tagsComponent.setState(TaggingState.DISABLED); + assertThat(tagger.emptyBuilder()).isSameAs(NoopTagContextBuilder.INSTANCE); + tagsComponent.setState(TaggingState.ENABLED); + TagContextBuilder builder = tagger.emptyBuilder(); + assertThat(builder).isInstanceOf(TagContextBuilderImpl.class); + assertThat(tagContextToList(builder.put(K1, V1).build())).containsExactly(Tag.create(K1, V1)); + } + + @Test + public void currentBuilder() { + TagContext tags = new SimpleTagContext(TAG1, TAG2, TAG3); + TagContextBuilder result = getResultOfCurrentBuilder(tags); + assertThat(result).isInstanceOf(TagContextBuilderImpl.class); + assertThat(tagContextToList(result.build())).containsExactly(TAG1, TAG2, TAG3); + } + + @Test + public void currentBuilder_DefaultIsEmpty() { + TagContextBuilder currentBuilder = tagger.currentBuilder(); + assertThat(currentBuilder).isInstanceOf(TagContextBuilderImpl.class); + assertThat(tagContextToList(currentBuilder.build())).isEmpty(); + } + + @Test + public void currentBuilder_RemoveDuplicateTags() { + Tag tag1 = Tag.create(K1, V1); + Tag tag2 = Tag.create(K1, V2); + TagContext tagContextWithDuplicateTags = new SimpleTagContext(tag1, tag2); + TagContextBuilder result = getResultOfCurrentBuilder(tagContextWithDuplicateTags); + assertThat(tagContextToList(result.build())).containsExactly(tag2); + } + + @Test + public void currentBuilder_SkipNullTag() { + TagContext tagContextWithNullTag = new SimpleTagContext(TAG1, null, TAG2); + TagContextBuilder result = getResultOfCurrentBuilder(tagContextWithNullTag); + assertThat(tagContextToList(result.build())).containsExactly(TAG1, TAG2); + } + + @Test + public void currentBuilder_TaggingDisabled() { + tagsComponent.setState(TaggingState.DISABLED); + assertThat(getResultOfCurrentBuilder(new SimpleTagContext(TAG1))) + .isSameAs(NoopTagContextBuilder.INSTANCE); + } + + @Test + public void currentBuilder_TaggingReenabled() { + TagContext tags = new SimpleTagContext(TAG1); + tagsComponent.setState(TaggingState.DISABLED); + assertThat(getResultOfCurrentBuilder(tags)).isSameAs(NoopTagContextBuilder.INSTANCE); + tagsComponent.setState(TaggingState.ENABLED); + TagContextBuilder builder = getResultOfCurrentBuilder(tags); + assertThat(builder).isInstanceOf(TagContextBuilderImpl.class); + assertThat(tagContextToList(builder.build())).containsExactly(TAG1); + } + + private TagContextBuilder getResultOfCurrentBuilder(TagContext tagsToSet) { + Context orig = Context.current().withValue(ContextUtils.TAG_CONTEXT_KEY, tagsToSet).attach(); + try { + return tagger.currentBuilder(); + } finally { + Context.current().detach(orig); + } + } + + @Test + public void toBuilder_ConvertUnknownTagContextToTagContextImpl() { + TagContext unknownTagContext = new SimpleTagContext(TAG1, TAG2, TAG3); + TagContext newTagContext = tagger.toBuilder(unknownTagContext).build(); + assertThat(tagContextToList(newTagContext)).containsExactly(TAG1, TAG2, TAG3); + assertThat(newTagContext).isInstanceOf(TagContextImpl.class); + } + + @Test + public void toBuilder_RemoveDuplicatesFromUnknownTagContext() { + Tag tag1 = Tag.create(K1, V1); + Tag tag2 = Tag.create(K1, V2); + TagContext tagContextWithDuplicateTags = new SimpleTagContext(tag1, tag2); + TagContext newTagContext = tagger.toBuilder(tagContextWithDuplicateTags).build(); + assertThat(tagContextToList(newTagContext)).containsExactly(tag2); + } + + @Test + public void toBuilder_SkipNullTag() { + TagContext tagContextWithNullTag = new SimpleTagContext(TAG1, null, TAG2); + TagContext newTagContext = tagger.toBuilder(tagContextWithNullTag).build(); + assertThat(tagContextToList(newTagContext)).containsExactly(TAG1, TAG2); + } + + @Test + public void toBuilder_TaggingDisabled() { + tagsComponent.setState(TaggingState.DISABLED); + assertThat(tagger.toBuilder(new SimpleTagContext(TAG1))) + .isSameAs(NoopTagContextBuilder.INSTANCE); + } + + @Test + public void toBuilder_TaggingReenabled() { + TagContext tags = new SimpleTagContext(TAG1); + tagsComponent.setState(TaggingState.DISABLED); + assertThat(tagger.toBuilder(tags)).isSameAs(NoopTagContextBuilder.INSTANCE); + tagsComponent.setState(TaggingState.ENABLED); + TagContextBuilder builder = tagger.toBuilder(tags); + assertThat(builder).isInstanceOf(TagContextBuilderImpl.class); + assertThat(tagContextToList(builder.build())).containsExactly(TAG1); + } + + @Test + public void getCurrentTagContext_DefaultIsEmptyTagContextImpl() { + TagContext currentTagContext = tagger.getCurrentTagContext(); + assertThat(tagContextToList(currentTagContext)).isEmpty(); + assertThat(currentTagContext).isInstanceOf(TagContextImpl.class); + } + + @Test + public void getCurrentTagContext_ConvertUnknownTagContextToTagContextImpl() { + TagContext unknownTagContext = new SimpleTagContext(TAG1, TAG2, TAG3); + TagContext result = getResultOfGetCurrentTagContext(unknownTagContext); + assertThat(result).isInstanceOf(TagContextImpl.class); + assertThat(tagContextToList(result)).containsExactly(TAG1, TAG2, TAG3); + } + + @Test + public void getCurrentTagContext_RemoveDuplicatesFromUnknownTagContext() { + Tag tag1 = Tag.create(K1, V1); + Tag tag2 = Tag.create(K1, V2); + TagContext tagContextWithDuplicateTags = new SimpleTagContext(tag1, tag2); + TagContext result = getResultOfGetCurrentTagContext(tagContextWithDuplicateTags); + assertThat(tagContextToList(result)).containsExactly(tag2); + } + + @Test + public void getCurrentTagContext_SkipNullTag() { + TagContext tagContextWithNullTag = new SimpleTagContext(TAG1, null, TAG2); + TagContext result = getResultOfGetCurrentTagContext(tagContextWithNullTag); + assertThat(tagContextToList(result)).containsExactly(TAG1, TAG2); + } + + @Test + public void getCurrentTagContext_TaggingDisabled() { + tagsComponent.setState(TaggingState.DISABLED); + assertThat(tagContextToList(getResultOfGetCurrentTagContext(new SimpleTagContext(TAG1)))) + .isEmpty(); + } + + @Test + public void getCurrentTagContext_TaggingReenabled() { + TagContext tags = new SimpleTagContext(TAG1); + tagsComponent.setState(TaggingState.DISABLED); + assertThat(tagContextToList(getResultOfGetCurrentTagContext(tags))).isEmpty(); + tagsComponent.setState(TaggingState.ENABLED); + assertThat(tagContextToList(getResultOfGetCurrentTagContext(tags))).containsExactly(TAG1); + } + + private TagContext getResultOfGetCurrentTagContext(TagContext tagsToSet) { + Context orig = Context.current().withValue(ContextUtils.TAG_CONTEXT_KEY, tagsToSet).attach(); + try { + return tagger.getCurrentTagContext(); + } finally { + Context.current().detach(orig); + } + } + + @Test + public void withTagContext_ConvertUnknownTagContextToTagContextImpl() { + TagContext unknownTagContext = new SimpleTagContext(TAG1, TAG2, TAG3); + TagContext result = getResultOfWithTagContext(unknownTagContext); + assertThat(result).isInstanceOf(TagContextImpl.class); + assertThat(tagContextToList(result)).containsExactly(TAG1, TAG2, TAG3); + } + + @Test + public void withTagContext_RemoveDuplicatesFromUnknownTagContext() { + Tag tag1 = Tag.create(K1, V1); + Tag tag2 = Tag.create(K1, V2); + TagContext tagContextWithDuplicateTags = new SimpleTagContext(tag1, tag2); + TagContext result = getResultOfWithTagContext(tagContextWithDuplicateTags); + assertThat(tagContextToList(result)).containsExactly(tag2); + } + + @Test + public void withTagContext_SkipNullTag() { + TagContext tagContextWithNullTag = new SimpleTagContext(TAG1, null, TAG2); + TagContext result = getResultOfWithTagContext(tagContextWithNullTag); + assertThat(tagContextToList(result)).containsExactly(TAG1, TAG2); + } + + @Test + public void withTagContext_ReturnsNoopScopeWhenTaggingIsDisabled() { + tagsComponent.setState(TaggingState.DISABLED); + assertThat(tagger.withTagContext(new SimpleTagContext(TAG1))).isSameAs(NoopScope.getInstance()); + } + + @Test + public void withTagContext_TaggingDisabled() { + tagsComponent.setState(TaggingState.DISABLED); + assertThat(tagContextToList(getResultOfWithTagContext(new SimpleTagContext(TAG1)))).isEmpty(); + } + + @Test + public void withTagContext_TaggingReenabled() { + TagContext tags = new SimpleTagContext(TAG1); + tagsComponent.setState(TaggingState.DISABLED); + assertThat(tagContextToList(getResultOfWithTagContext(tags))).isEmpty(); + tagsComponent.setState(TaggingState.ENABLED); + assertThat(tagContextToList(getResultOfWithTagContext(tags))).containsExactly(TAG1); + } + + private TagContext getResultOfWithTagContext(TagContext tagsToSet) { + Scope scope = tagger.withTagContext(tagsToSet); + try { + return ContextUtils.TAG_CONTEXT_KEY.get(); + } finally { + scope.close(); + } + } + + private static final class SimpleTagContext extends TagContext { + private final List<Tag> tags; + + SimpleTagContext(Tag... tags) { + this.tags = Collections.unmodifiableList(Lists.newArrayList(tags)); + } + + @Override + protected Iterator<Tag> getIterator() { + return tags.iterator(); + } + } +} diff --git a/impl_core/src/test/java/io/opencensus/implcore/tags/TagsComponentImplBaseTest.java b/impl_core/src/test/java/io/opencensus/implcore/tags/TagsComponentImplBaseTest.java new file mode 100644 index 00000000..1bc13c59 --- /dev/null +++ b/impl_core/src/test/java/io/opencensus/implcore/tags/TagsComponentImplBaseTest.java @@ -0,0 +1,84 @@ +/* + * Copyright 2017, OpenCensus Authors + * + * 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 io.opencensus.implcore.tags; + +import static com.google.common.truth.Truth.assertThat; + +import io.opencensus.tags.TaggingState; +import io.opencensus.tags.TagsComponent; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Tests for {@link TagsComponentImplBase}. */ +@RunWith(JUnit4.class) +public class TagsComponentImplBaseTest { + + @Rule public final ExpectedException thrown = ExpectedException.none(); + + private final TagsComponent tagsComponent = new TagsComponentImplBase(); + + @Test + public void defaultState() { + assertThat(tagsComponent.getState()).isEqualTo(TaggingState.ENABLED); + } + + @Test + @SuppressWarnings("deprecation") + public void setState_Disabled() { + tagsComponent.setState(TaggingState.DISABLED); + assertThat(tagsComponent.getState()).isEqualTo(TaggingState.DISABLED); + } + + @Test + @SuppressWarnings("deprecation") + public void setState_Enabled() { + tagsComponent.setState(TaggingState.DISABLED); + tagsComponent.setState(TaggingState.ENABLED); + assertThat(tagsComponent.getState()).isEqualTo(TaggingState.ENABLED); + } + + @Test + @SuppressWarnings("deprecation") + public void setState_DisallowsNull() { + thrown.expect(NullPointerException.class); + thrown.expectMessage("newState"); + tagsComponent.setState(null); + } + + @Test + @SuppressWarnings("deprecation") + public void preventSettingStateAfterGettingState_DifferentState() { + tagsComponent.setState(TaggingState.DISABLED); + tagsComponent.getState(); + thrown.expect(IllegalStateException.class); + thrown.expectMessage("State was already read, cannot set state."); + tagsComponent.setState(TaggingState.ENABLED); + } + + @Test + @SuppressWarnings("deprecation") + public void preventSettingStateAfterGettingState_SameState() { + tagsComponent.setState(TaggingState.DISABLED); + tagsComponent.getState(); + thrown.expect(IllegalStateException.class); + thrown.expectMessage("State was already read, cannot set state."); + tagsComponent.setState(TaggingState.DISABLED); + } +} diff --git a/impl_core/src/test/java/io/opencensus/implcore/tags/TagsTestUtil.java b/impl_core/src/test/java/io/opencensus/implcore/tags/TagsTestUtil.java new file mode 100644 index 00000000..dcfba508 --- /dev/null +++ b/impl_core/src/test/java/io/opencensus/implcore/tags/TagsTestUtil.java @@ -0,0 +1,33 @@ +/* + * Copyright 2017, OpenCensus Authors + * + * 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 io.opencensus.implcore.tags; + +import com.google.common.collect.Lists; +import io.opencensus.tags.InternalUtils; +import io.opencensus.tags.Tag; +import io.opencensus.tags.TagContext; +import java.util.Collection; + +/** Test utilities for tagging. */ +public class TagsTestUtil { + private TagsTestUtil() {} + + /** Returns a collection of all tags in a {@link TagContext}. */ + public static Collection<Tag> tagContextToList(TagContext tags) { + return Lists.newArrayList(InternalUtils.getTags(tags)); + } +} diff --git a/impl_core/src/test/java/io/opencensus/implcore/tags/propagation/TagContextBinarySerializerImplTest.java b/impl_core/src/test/java/io/opencensus/implcore/tags/propagation/TagContextBinarySerializerImplTest.java new file mode 100644 index 00000000..26a072f6 --- /dev/null +++ b/impl_core/src/test/java/io/opencensus/implcore/tags/propagation/TagContextBinarySerializerImplTest.java @@ -0,0 +1,94 @@ +/* + * Copyright 2017, OpenCensus Authors + * + * 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 io.opencensus.implcore.tags.propagation; + +import static com.google.common.truth.Truth.assertThat; + +import com.google.common.collect.ImmutableSet; +import io.opencensus.implcore.tags.TagsComponentImplBase; +import io.opencensus.implcore.tags.TagsTestUtil; +import io.opencensus.tags.Tag; +import io.opencensus.tags.TagContext; +import io.opencensus.tags.TagKey; +import io.opencensus.tags.TagValue; +import io.opencensus.tags.TaggingState; +import io.opencensus.tags.TagsComponent; +import io.opencensus.tags.propagation.TagContextBinarySerializer; +import io.opencensus.tags.propagation.TagContextDeserializationException; +import io.opencensus.tags.propagation.TagContextSerializationException; +import java.util.Iterator; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** + * Tests for {@link TagContextBinarySerializerImpl}. + * + * <p>Thorough serialization/deserialization tests are in {@link TagContextSerializationTest}, + * {@link TagContextDeserializationTest}, and {@link TagContextRoundtripTest}. + */ +@RunWith(JUnit4.class) +public final class TagContextBinarySerializerImplTest { + private final TagsComponent tagsComponent = new TagsComponentImplBase(); + private final TagContextBinarySerializer serializer = + tagsComponent.getTagPropagationComponent().getBinarySerializer(); + + private final TagContext tagContext = + new TagContext() { + @Override + public Iterator<Tag> getIterator() { + return ImmutableSet.<Tag>of(Tag.create(TagKey.create("key"), TagValue.create("value"))) + .iterator(); + } + }; + + @Test + @SuppressWarnings("deprecation") + public void toByteArray_TaggingDisabled() throws TagContextSerializationException { + tagsComponent.setState(TaggingState.DISABLED); + assertThat(serializer.toByteArray(tagContext)).isEmpty(); + } + + @Test + @SuppressWarnings("deprecation") + public void toByteArray_TaggingReenabled() throws TagContextSerializationException { + final byte[] serialized = serializer.toByteArray(tagContext); + tagsComponent.setState(TaggingState.DISABLED); + assertThat(serializer.toByteArray(tagContext)).isEmpty(); + tagsComponent.setState(TaggingState.ENABLED); + assertThat(serializer.toByteArray(tagContext)).isEqualTo(serialized); + } + + @Test + @SuppressWarnings("deprecation") + public void fromByteArray_TaggingDisabled() + throws TagContextDeserializationException, TagContextSerializationException { + byte[] serialized = serializer.toByteArray(tagContext); + tagsComponent.setState(TaggingState.DISABLED); + assertThat(TagsTestUtil.tagContextToList(serializer.fromByteArray(serialized))).isEmpty(); + } + + @Test + public void fromByteArray_TaggingReenabled() + throws TagContextDeserializationException, TagContextSerializationException { + byte[] serialized = serializer.toByteArray(tagContext); + tagsComponent.setState(TaggingState.DISABLED); + assertThat(TagsTestUtil.tagContextToList(serializer.fromByteArray(serialized))).isEmpty(); + tagsComponent.setState(TaggingState.ENABLED); + assertThat(serializer.fromByteArray(serialized)).isEqualTo(tagContext); + } +} diff --git a/impl_core/src/test/java/io/opencensus/implcore/tags/propagation/TagContextDeserializationTest.java b/impl_core/src/test/java/io/opencensus/implcore/tags/propagation/TagContextDeserializationTest.java new file mode 100644 index 00000000..8db0e389 --- /dev/null +++ b/impl_core/src/test/java/io/opencensus/implcore/tags/propagation/TagContextDeserializationTest.java @@ -0,0 +1,329 @@ +/* + * Copyright 2016-17, OpenCensus Authors + * + * 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 io.opencensus.implcore.tags.propagation; + +import static com.google.common.truth.Truth.assertThat; + +import com.google.common.base.Charsets; +import com.google.common.io.ByteArrayDataOutput; +import com.google.common.io.ByteStreams; +import io.opencensus.implcore.internal.VarInt; +import io.opencensus.implcore.tags.TagsComponentImplBase; +import io.opencensus.tags.TagContext; +import io.opencensus.tags.TagKey; +import io.opencensus.tags.TagValue; +import io.opencensus.tags.Tagger; +import io.opencensus.tags.TagsComponent; +import io.opencensus.tags.propagation.TagContextBinarySerializer; +import io.opencensus.tags.propagation.TagContextDeserializationException; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** + * Tests for deserializing tags with {@link SerializationUtils} and {@link + * TagContextBinarySerializerImpl}. + */ +@RunWith(JUnit4.class) +public class TagContextDeserializationTest { + + @Rule public final ExpectedException thrown = ExpectedException.none(); + + private final TagsComponent tagsComponent = new TagsComponentImplBase(); + private final TagContextBinarySerializer serializer = + tagsComponent.getTagPropagationComponent().getBinarySerializer(); + private final Tagger tagger = tagsComponent.getTagger(); + + @Test + public void testConstants() { + // Refer to the JavaDoc on SerializationUtils for the definitions on these constants. + assertThat(SerializationUtils.VERSION_ID).isEqualTo(0); + assertThat(SerializationUtils.TAG_FIELD_ID).isEqualTo(0); + assertThat(SerializationUtils.TAGCONTEXT_SERIALIZED_SIZE_LIMIT).isEqualTo(8192); + } + + @Test + public void testDeserializeNoTags() throws TagContextDeserializationException { + TagContext expected = tagger.empty(); + TagContext actual = + serializer.fromByteArray( + new byte[] {SerializationUtils.VERSION_ID}); // One byte that represents Version ID. + assertThat(actual).isEqualTo(expected); + } + + @Test + public void testDeserializeEmptyByteArrayThrowException() + throws TagContextDeserializationException { + thrown.expect(TagContextDeserializationException.class); + thrown.expectMessage("Input byte[] can not be empty."); + serializer.fromByteArray(new byte[0]); + } + + @Test + public void testDeserializeTooLargeByteArrayThrowException() + throws TagContextDeserializationException { + ByteArrayDataOutput output = ByteStreams.newDataOutput(); + output.write(SerializationUtils.VERSION_ID); + for (int i = 0; i < SerializationUtils.TAGCONTEXT_SERIALIZED_SIZE_LIMIT / 8 - 1; i++) { + // Each tag will be with format {key : "0123", value : "0123"}, so the length of it is 8. + String str; + if (i < 10) { + str = "000" + i; + } else if (i < 100) { + str = "00" + i; + } else if (i < 1000) { + str = "0" + i; + } else { + str = String.valueOf(i); + } + encodeTagToOutput(str, str, output); + } + // The last tag will be of size 9, so the total size of the TagContext (8193) will be one byte + // more than limit. + encodeTagToOutput("last", "last1", output); + + byte[] bytes = output.toByteArray(); + thrown.expect(TagContextDeserializationException.class); + thrown.expectMessage("Size of TagContext exceeds the maximum serialized size "); + serializer.fromByteArray(bytes); + } + + // Deserializing this input should cause an error, even though it represents a relatively small + // TagContext. + @Test + public void testDeserializeTooLargeByteArrayThrowException_WithDuplicateTagKeys() + throws TagContextDeserializationException { + ByteArrayDataOutput output = ByteStreams.newDataOutput(); + output.write(SerializationUtils.VERSION_ID); + for (int i = 0; i < SerializationUtils.TAGCONTEXT_SERIALIZED_SIZE_LIMIT / 8 - 1; i++) { + // Each tag will be with format {key : "key_", value : "0123"}, so the length of it is 8. + String str; + if (i < 10) { + str = "000" + i; + } else if (i < 100) { + str = "00" + i; + } else if (i < 1000) { + str = "0" + i; + } else { + str = String.valueOf(i); + } + encodeTagToOutput("key_", str, output); + } + // The last tag will be of size 9, so the total size of the TagContext (8193) will be one byte + // more than limit. + encodeTagToOutput("key_", "last1", output); + + byte[] bytes = output.toByteArray(); + thrown.expect(TagContextDeserializationException.class); + thrown.expectMessage("Size of TagContext exceeds the maximum serialized size "); + serializer.fromByteArray(bytes); + } + + @Test + public void testDeserializeInvalidTagKey() throws TagContextDeserializationException { + ByteArrayDataOutput output = ByteStreams.newDataOutput(); + output.write(SerializationUtils.VERSION_ID); + + // Encode an invalid tag key and a valid tag value: + encodeTagToOutput("\2key", "value", output); + final byte[] bytes = output.toByteArray(); + + thrown.expect(TagContextDeserializationException.class); + thrown.expectMessage("Invalid tag key: \2key"); + serializer.fromByteArray(bytes); + } + + @Test + public void testDeserializeInvalidTagValue() throws TagContextDeserializationException { + ByteArrayDataOutput output = ByteStreams.newDataOutput(); + output.write(SerializationUtils.VERSION_ID); + + // Encode a valid tag key and an invalid tag value: + encodeTagToOutput("my key", "val\3", output); + final byte[] bytes = output.toByteArray(); + + thrown.expect(TagContextDeserializationException.class); + thrown.expectMessage("Invalid tag value for key TagKey{name=my key}: val\3"); + serializer.fromByteArray(bytes); + } + + @Test + public void testDeserializeOneTag() throws TagContextDeserializationException { + ByteArrayDataOutput output = ByteStreams.newDataOutput(); + output.write(SerializationUtils.VERSION_ID); + encodeTagToOutput("Key", "Value", output); + TagContext expected = + tagger.emptyBuilder().put(TagKey.create("Key"), TagValue.create("Value")).build(); + assertThat(serializer.fromByteArray(output.toByteArray())).isEqualTo(expected); + } + + @Test + public void testDeserializeMultipleTags() throws TagContextDeserializationException { + ByteArrayDataOutput output = ByteStreams.newDataOutput(); + output.write(SerializationUtils.VERSION_ID); + encodeTagToOutput("Key1", "Value1", output); + encodeTagToOutput("Key2", "Value2", output); + TagContext expected = + tagger + .emptyBuilder() + .put(TagKey.create("Key1"), TagValue.create("Value1")) + .put(TagKey.create("Key2"), TagValue.create("Value2")) + .build(); + assertThat(serializer.fromByteArray(output.toByteArray())).isEqualTo(expected); + } + + @Test + public void testDeserializeDuplicateKeys() throws TagContextDeserializationException { + ByteArrayDataOutput output = ByteStreams.newDataOutput(); + output.write(SerializationUtils.VERSION_ID); + encodeTagToOutput("Key1", "Value1", output); + encodeTagToOutput("Key1", "Value2", output); + TagContext expected = + tagger.emptyBuilder().put(TagKey.create("Key1"), TagValue.create("Value2")).build(); + assertThat(serializer.fromByteArray(output.toByteArray())).isEqualTo(expected); + } + + @Test + public void testDeserializeNonConsecutiveDuplicateKeys() + throws TagContextDeserializationException { + ByteArrayDataOutput output = ByteStreams.newDataOutput(); + output.write(SerializationUtils.VERSION_ID); + encodeTagToOutput("Key1", "Value1", output); + encodeTagToOutput("Key2", "Value2", output); + encodeTagToOutput("Key3", "Value3", output); + encodeTagToOutput("Key1", "Value4", output); + encodeTagToOutput("Key2", "Value5", output); + TagContext expected = + tagger + .emptyBuilder() + .put(TagKey.create("Key1"), TagValue.create("Value4")) + .put(TagKey.create("Key2"), TagValue.create("Value5")) + .put(TagKey.create("Key3"), TagValue.create("Value3")) + .build(); + assertThat(serializer.fromByteArray(output.toByteArray())).isEqualTo(expected); + } + + @Test + public void testDeserializeDuplicateTags() throws TagContextDeserializationException { + ByteArrayDataOutput output = ByteStreams.newDataOutput(); + output.write(SerializationUtils.VERSION_ID); + encodeTagToOutput("Key1", "Value1", output); + encodeTagToOutput("Key1", "Value1", output); + TagContext expected = + tagger.emptyBuilder().put(TagKey.create("Key1"), TagValue.create("Value1")).build(); + assertThat(serializer.fromByteArray(output.toByteArray())).isEqualTo(expected); + } + + @Test + public void testDeserializeNonConsecutiveDuplicateTags() + throws TagContextDeserializationException { + ByteArrayDataOutput output = ByteStreams.newDataOutput(); + output.write(SerializationUtils.VERSION_ID); + encodeTagToOutput("Key1", "Value1", output); + encodeTagToOutput("Key2", "Value2", output); + encodeTagToOutput("Key3", "Value3", output); + encodeTagToOutput("Key1", "Value1", output); + encodeTagToOutput("Key2", "Value2", output); + TagContext expected = + tagger + .emptyBuilder() + .put(TagKey.create("Key1"), TagValue.create("Value1")) + .put(TagKey.create("Key2"), TagValue.create("Value2")) + .put(TagKey.create("Key3"), TagValue.create("Value3")) + .build(); + assertThat(serializer.fromByteArray(output.toByteArray())).isEqualTo(expected); + } + + @Test + public void stopParsingAtUnknownField() throws TagContextDeserializationException { + ByteArrayDataOutput output = ByteStreams.newDataOutput(); + output.write(SerializationUtils.VERSION_ID); + encodeTagToOutput("Key1", "Value1", output); + encodeTagToOutput("Key2", "Value2", output); + + // Write unknown field ID 1. + output.write(1); + output.write(new byte[] {1, 2, 3, 4}); + + encodeTagToOutput("Key3", "Value3", output); + + // key 3 should not be included + TagContext expected = + tagger + .emptyBuilder() + .put(TagKey.create("Key1"), TagValue.create("Value1")) + .put(TagKey.create("Key2"), TagValue.create("Value2")) + .build(); + assertThat(serializer.fromByteArray(output.toByteArray())).isEqualTo(expected); + } + + @Test + public void stopParsingAtUnknownTagAtStart() throws TagContextDeserializationException { + ByteArrayDataOutput output = ByteStreams.newDataOutput(); + output.write(SerializationUtils.VERSION_ID); + + // Write unknown field ID 1. + output.write(1); + output.write(new byte[] {1, 2, 3, 4}); + + encodeTagToOutput("Key", "Value", output); + assertThat(serializer.fromByteArray(output.toByteArray())).isEqualTo(tagger.empty()); + } + + @Test + public void testDeserializeWrongFormat() throws TagContextDeserializationException { + // encoded tags should follow the format <version_id>(<tag_field_id><tag_encoding>)* + thrown.expect(TagContextDeserializationException.class); + serializer.fromByteArray(new byte[3]); + } + + @Test + public void testDeserializeWrongVersionId() throws TagContextDeserializationException { + thrown.expect(TagContextDeserializationException.class); + thrown.expectMessage("Wrong Version ID: 1. Currently supports version up to: 0"); + serializer.fromByteArray(new byte[] {(byte) (SerializationUtils.VERSION_ID + 1)}); + } + + @Test + public void testDeserializeNegativeVersionId() throws TagContextDeserializationException { + thrown.expect(TagContextDeserializationException.class); + thrown.expectMessage("Wrong Version ID: -1. Currently supports version up to: 0"); + serializer.fromByteArray(new byte[] {(byte) -1}); + } + + // <tag_encoding> == + // <tag_key_len><tag_key><tag_val_len><tag_val> + // <tag_key_len> == varint encoded integer + // <tag_key> == tag_key_len bytes comprising tag key name + // <tag_val_len> == varint encoded integer + // <tag_val> == tag_val_len bytes comprising UTF-8 string + private static void encodeTagToOutput(String key, String value, ByteArrayDataOutput output) { + output.write(SerializationUtils.TAG_FIELD_ID); + encodeString(key, output); + encodeString(value, output); + } + + private static void encodeString(String input, ByteArrayDataOutput output) { + int length = input.length(); + byte[] bytes = new byte[VarInt.varIntSize(length)]; + VarInt.putVarInt(length, bytes, 0); + output.write(bytes); + output.write(input.getBytes(Charsets.UTF_8)); + } +} diff --git a/impl_core/src/test/java/io/opencensus/implcore/tags/propagation/TagContextRoundtripTest.java b/impl_core/src/test/java/io/opencensus/implcore/tags/propagation/TagContextRoundtripTest.java new file mode 100644 index 00000000..1b1aa042 --- /dev/null +++ b/impl_core/src/test/java/io/opencensus/implcore/tags/propagation/TagContextRoundtripTest.java @@ -0,0 +1,85 @@ +/* + * Copyright 2017, OpenCensus Authors + * + * 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 io.opencensus.implcore.tags.propagation; + +import static com.google.common.truth.Truth.assertThat; + +import io.opencensus.implcore.tags.TagsComponentImplBase; +import io.opencensus.tags.TagContext; +import io.opencensus.tags.TagContextBuilder; +import io.opencensus.tags.TagKey; +import io.opencensus.tags.TagValue; +import io.opencensus.tags.Tagger; +import io.opencensus.tags.TagsComponent; +import io.opencensus.tags.propagation.TagContextBinarySerializer; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Tests for roundtrip serialization with {@link TagContextBinarySerializerImpl}. */ +@RunWith(JUnit4.class) +public class TagContextRoundtripTest { + + private static final TagKey K1 = TagKey.create("k1"); + private static final TagKey K2 = TagKey.create("k2"); + private static final TagKey K3 = TagKey.create("k3"); + + private static final TagValue V_EMPTY = TagValue.create(""); + private static final TagValue V1 = TagValue.create("v1"); + private static final TagValue V2 = TagValue.create("v2"); + private static final TagValue V3 = TagValue.create("v3"); + + private final TagsComponent tagsComponent = new TagsComponentImplBase(); + private final TagContextBinarySerializer serializer = + tagsComponent.getTagPropagationComponent().getBinarySerializer(); + private final Tagger tagger = tagsComponent.getTagger(); + + @Test + public void testRoundtripSerialization_NormalTagContext() throws Exception { + testRoundtripSerialization(tagger.empty()); + testRoundtripSerialization(tagger.emptyBuilder().put(K1, V1).build()); + testRoundtripSerialization(tagger.emptyBuilder().put(K1, V1).put(K2, V2).put(K3, V3).build()); + testRoundtripSerialization(tagger.emptyBuilder().put(K1, V_EMPTY).build()); + } + + @Test + public void testRoundtrip_TagContextWithMaximumSize() throws Exception { + TagContextBuilder builder = tagger.emptyBuilder(); + for (int i = 0; i < SerializationUtils.TAGCONTEXT_SERIALIZED_SIZE_LIMIT / 8; i++) { + // Each tag will be with format {key : "0123", value : "0123"}, so the length of it is 8. + // Add 1024 tags, the total size should just be 8192. + String str; + if (i < 10) { + str = "000" + i; + } else if (i < 100) { + str = "00" + i; + } else if (i < 1000) { + str = "0" + i; + } else { + str = "" + i; + } + builder.put(TagKey.create(str), TagValue.create(str)); + } + testRoundtripSerialization(builder.build()); + } + + private void testRoundtripSerialization(TagContext expected) throws Exception { + byte[] bytes = serializer.toByteArray(expected); + TagContext actual = serializer.fromByteArray(bytes); + assertThat(actual).isEqualTo(expected); + } +} diff --git a/impl_core/src/test/java/io/opencensus/implcore/tags/propagation/TagContextSerializationTest.java b/impl_core/src/test/java/io/opencensus/implcore/tags/propagation/TagContextSerializationTest.java new file mode 100644 index 00000000..ed68fe3d --- /dev/null +++ b/impl_core/src/test/java/io/opencensus/implcore/tags/propagation/TagContextSerializationTest.java @@ -0,0 +1,147 @@ +/* + * Copyright 2016-17, OpenCensus Authors + * + * 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 io.opencensus.implcore.tags.propagation; + +import static com.google.common.truth.Truth.assertThat; + +import com.google.common.base.Charsets; +import com.google.common.collect.Collections2; +import io.opencensus.implcore.internal.VarInt; +import io.opencensus.implcore.tags.TagsComponentImplBase; +import io.opencensus.tags.Tag; +import io.opencensus.tags.TagContext; +import io.opencensus.tags.TagContextBuilder; +import io.opencensus.tags.TagKey; +import io.opencensus.tags.TagValue; +import io.opencensus.tags.Tagger; +import io.opencensus.tags.TagsComponent; +import io.opencensus.tags.propagation.TagContextBinarySerializer; +import io.opencensus.tags.propagation.TagContextSerializationException; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** + * Tests for serializing tags with {@link SerializationUtils} and {@link + * TagContextBinarySerializerImpl}. + */ +@RunWith(JUnit4.class) +public class TagContextSerializationTest { + + @Rule public final ExpectedException thrown = ExpectedException.none(); + + private static final TagKey K1 = TagKey.create("k1"); + private static final TagKey K2 = TagKey.create("k2"); + private static final TagKey K3 = TagKey.create("k3"); + private static final TagKey K4 = TagKey.create("k4"); + + private static final TagValue V1 = TagValue.create("v1"); + private static final TagValue V2 = TagValue.create("v2"); + private static final TagValue V3 = TagValue.create("v3"); + private static final TagValue V4 = TagValue.create("v4"); + + private static final Tag T1 = Tag.create(K1, V1); + private static final Tag T2 = Tag.create(K2, V2); + private static final Tag T3 = Tag.create(K3, V3); + private static final Tag T4 = Tag.create(K4, V4); + + private final TagsComponent tagsComponent = new TagsComponentImplBase(); + private final TagContextBinarySerializer serializer = + tagsComponent.getTagPropagationComponent().getBinarySerializer(); + private final Tagger tagger = tagsComponent.getTagger(); + + @Test + public void testSerializeDefault() throws Exception { + testSerialize(); + } + + @Test + public void testSerializeWithOneTag() throws Exception { + testSerialize(T1); + } + + @Test + public void testSerializeWithMultipleTags() throws Exception { + testSerialize(T1, T2, T3, T4); + } + + @Test + public void testSerializeTooLargeTagContext() throws TagContextSerializationException { + TagContextBuilder builder = tagger.emptyBuilder(); + for (int i = 0; i < SerializationUtils.TAGCONTEXT_SERIALIZED_SIZE_LIMIT / 8 - 1; i++) { + // Each tag will be with format {key : "0123", value : "0123"}, so the length of it is 8. + String str; + if (i < 10) { + str = "000" + i; + } else if (i < 100) { + str = "00" + i; + } else if (i < 1000) { + str = "0" + i; + } else { + str = String.valueOf(i); + } + builder.put(TagKey.create(str), TagValue.create(str)); + } + // The last tag will be of size 9, so the total size of the TagContext (8193) will be one byte + // more than limit. + builder.put(TagKey.create("last"), TagValue.create("last1")); + + TagContext tagContext = builder.build(); + thrown.expect(TagContextSerializationException.class); + thrown.expectMessage("Size of TagContext exceeds the maximum serialized size "); + serializer.toByteArray(tagContext); + } + + private void testSerialize(Tag... tags) throws IOException, TagContextSerializationException { + TagContextBuilder builder = tagger.emptyBuilder(); + for (Tag tag : tags) { + builder.put(tag.getKey(), tag.getValue()); + } + + byte[] actual = serializer.toByteArray(builder.build()); + + Collection<List<Tag>> tagPermutation = Collections2.permutations(Arrays.asList(tags)); + Set<String> possibleOutputs = new HashSet<String>(); + for (List<Tag> list : tagPermutation) { + ByteArrayOutputStream expected = new ByteArrayOutputStream(); + expected.write(SerializationUtils.VERSION_ID); + for (Tag tag : list) { + expected.write(SerializationUtils.TAG_FIELD_ID); + encodeString(tag.getKey().getName(), expected); + encodeString(tag.getValue().asString(), expected); + } + possibleOutputs.add(new String(expected.toByteArray(), Charsets.UTF_8)); + } + + assertThat(possibleOutputs).contains(new String(actual, Charsets.UTF_8)); + } + + private static void encodeString(String input, ByteArrayOutputStream byteArrayOutputStream) + throws IOException { + VarInt.putVarInt(input.length(), byteArrayOutputStream); + byteArrayOutputStream.write(input.getBytes(Charsets.UTF_8)); + } +} diff --git a/impl_core/src/test/java/io/opencensus/implcore/trace/NoRecordEventsSpanImplTest.java b/impl_core/src/test/java/io/opencensus/implcore/trace/NoRecordEventsSpanImplTest.java new file mode 100644 index 00000000..c576860d --- /dev/null +++ b/impl_core/src/test/java/io/opencensus/implcore/trace/NoRecordEventsSpanImplTest.java @@ -0,0 +1,91 @@ +/* + * Copyright 2018, OpenCensus Authors + * + * 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 io.opencensus.implcore.trace; + +import static com.google.common.truth.Truth.assertThat; + +import io.opencensus.trace.Annotation; +import io.opencensus.trace.AttributeValue; +import io.opencensus.trace.EndSpanOptions; +import io.opencensus.trace.Link; +import io.opencensus.trace.MessageEvent; +import io.opencensus.trace.NetworkEvent; +import io.opencensus.trace.Span.Options; +import io.opencensus.trace.SpanContext; +import io.opencensus.trace.SpanId; +import io.opencensus.trace.Status; +import io.opencensus.trace.TraceId; +import io.opencensus.trace.TraceOptions; +import io.opencensus.trace.Tracestate; +import java.util.HashMap; +import java.util.Map; +import java.util.Random; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Unit tests for {@link NoRecordEventsSpanImpl}. */ +@RunWith(JUnit4.class) +public class NoRecordEventsSpanImplTest { + private final Random random = new Random(1234); + private final SpanContext spanContext = + SpanContext.create( + TraceId.generateRandomId(random), + SpanId.generateRandomId(random), + TraceOptions.DEFAULT, + Tracestate.builder().build()); + private final NoRecordEventsSpanImpl noRecordEventsSpan = + NoRecordEventsSpanImpl.create(spanContext); + + @Test + public void propagatesSpanContext() { + assertThat(noRecordEventsSpan.getContext()).isEqualTo(spanContext); + } + + @Test + public void hasNoRecordEventsOption() { + assertThat(noRecordEventsSpan.getOptions()).doesNotContain(Options.RECORD_EVENTS); + } + + @Test + public void doNotCrash() { + Map<String, AttributeValue> attributes = new HashMap<String, AttributeValue>(); + attributes.put( + "MyStringAttributeKey", AttributeValue.stringAttributeValue("MyStringAttributeValue")); + Map<String, AttributeValue> multipleAttributes = new HashMap<String, AttributeValue>(); + multipleAttributes.put( + "MyStringAttributeKey", AttributeValue.stringAttributeValue("MyStringAttributeValue")); + multipleAttributes.put("MyBooleanAttributeKey", AttributeValue.booleanAttributeValue(true)); + multipleAttributes.put("MyLongAttributeKey", AttributeValue.longAttributeValue(123)); + // Tests only that all the methods are not crashing/throwing errors. + noRecordEventsSpan.putAttribute( + "MyStringAttributeKey2", AttributeValue.stringAttributeValue("MyStringAttributeValue2")); + noRecordEventsSpan.addAttributes(attributes); + noRecordEventsSpan.addAttributes(multipleAttributes); + noRecordEventsSpan.addAnnotation("MyAnnotation"); + noRecordEventsSpan.addAnnotation("MyAnnotation", attributes); + noRecordEventsSpan.addAnnotation("MyAnnotation", multipleAttributes); + noRecordEventsSpan.addAnnotation(Annotation.fromDescription("MyAnnotation")); + noRecordEventsSpan.addNetworkEvent(NetworkEvent.builder(NetworkEvent.Type.SENT, 1L).build()); + noRecordEventsSpan.addMessageEvent(MessageEvent.builder(MessageEvent.Type.SENT, 1L).build()); + noRecordEventsSpan.addLink( + Link.fromSpanContext(SpanContext.INVALID, Link.Type.CHILD_LINKED_SPAN)); + noRecordEventsSpan.setStatus(Status.OK); + noRecordEventsSpan.end(EndSpanOptions.DEFAULT); + noRecordEventsSpan.end(); + } +} diff --git a/impl_core/src/test/java/io/opencensus/implcore/trace/RecordEventsSpanImplTest.java b/impl_core/src/test/java/io/opencensus/implcore/trace/RecordEventsSpanImplTest.java new file mode 100644 index 00000000..b293a225 --- /dev/null +++ b/impl_core/src/test/java/io/opencensus/implcore/trace/RecordEventsSpanImplTest.java @@ -0,0 +1,594 @@ +/* + * Copyright 2017, OpenCensus Authors + * + * 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 io.opencensus.implcore.trace; + +import static com.google.common.truth.Truth.assertThat; + +import io.opencensus.common.Duration; +import io.opencensus.common.Timestamp; +import io.opencensus.implcore.internal.TimestampConverter; +import io.opencensus.implcore.trace.RecordEventsSpanImpl.StartEndHandler; +import io.opencensus.testing.common.TestClock; +import io.opencensus.trace.Annotation; +import io.opencensus.trace.AttributeValue; +import io.opencensus.trace.EndSpanOptions; +import io.opencensus.trace.Link; +import io.opencensus.trace.NetworkEvent; +import io.opencensus.trace.Span.Kind; +import io.opencensus.trace.SpanContext; +import io.opencensus.trace.SpanId; +import io.opencensus.trace.Status; +import io.opencensus.trace.TraceId; +import io.opencensus.trace.TraceOptions; +import io.opencensus.trace.config.TraceParams; +import io.opencensus.trace.export.SpanData; +import java.util.HashMap; +import java.util.Map; +import java.util.Random; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; + +/** Unit tests for {@link RecordEventsSpanImpl}. */ +@RunWith(JUnit4.class) +public class RecordEventsSpanImplTest { + private static final String SPAN_NAME = "MySpanName"; + private static final String ANNOTATION_DESCRIPTION = "MyAnnotation"; + private final Random random = new Random(1234); + private final SpanContext spanContext = + SpanContext.create( + TraceId.generateRandomId(random), SpanId.generateRandomId(random), TraceOptions.DEFAULT); + private final SpanId parentSpanId = SpanId.generateRandomId(random); + private final Timestamp timestamp = Timestamp.create(1234, 5678); + private final TestClock testClock = TestClock.create(timestamp); + private final TimestampConverter timestampConverter = TimestampConverter.now(testClock); + private final Map<String, AttributeValue> attributes = new HashMap<String, AttributeValue>(); + private final Map<String, AttributeValue> expectedAttributes = + new HashMap<String, AttributeValue>(); + @Mock private StartEndHandler startEndHandler; + @Rule public final ExpectedException exception = ExpectedException.none(); + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + attributes.put( + "MyStringAttributeKey", AttributeValue.stringAttributeValue("MyStringAttributeValue")); + attributes.put("MyLongAttributeKey", AttributeValue.longAttributeValue(123L)); + attributes.put("MyBooleanAttributeKey", AttributeValue.booleanAttributeValue(false)); + expectedAttributes.putAll(attributes); + expectedAttributes.put( + "MySingleStringAttributeKey", + AttributeValue.stringAttributeValue("MySingleStringAttributeValue")); + } + + @Test + public void noEventsRecordedAfterEnd() { + RecordEventsSpanImpl span = + RecordEventsSpanImpl.startSpan( + spanContext, + SPAN_NAME, + null, + parentSpanId, + false, + TraceParams.DEFAULT, + startEndHandler, + timestampConverter, + testClock); + span.end(); + // Check that adding trace events after Span#end() does not throw any exception and are not + // recorded. + span.putAttributes(attributes); + span.putAttribute( + "MySingleStringAttributeKey", + AttributeValue.stringAttributeValue("MySingleStringAttributeValue")); + span.addAnnotation(Annotation.fromDescription(ANNOTATION_DESCRIPTION)); + span.addAnnotation(ANNOTATION_DESCRIPTION, attributes); + span.addNetworkEvent( + NetworkEvent.builder(NetworkEvent.Type.RECV, 1).setUncompressedMessageSize(3).build()); + span.addLink(Link.fromSpanContext(spanContext, Link.Type.CHILD_LINKED_SPAN)); + SpanData spanData = span.toSpanData(); + assertThat(spanData.getStartTimestamp()).isEqualTo(timestamp); + assertThat(spanData.getAttributes().getAttributeMap()).isEmpty(); + assertThat(spanData.getAnnotations().getEvents()).isEmpty(); + assertThat(spanData.getNetworkEvents().getEvents()).isEmpty(); + assertThat(spanData.getLinks().getLinks()).isEmpty(); + assertThat(spanData.getStatus()).isEqualTo(Status.OK); + assertThat(spanData.getEndTimestamp()).isEqualTo(timestamp); + } + + @Test + public void deprecatedAddAttributesStillWorks() { + RecordEventsSpanImpl span = + RecordEventsSpanImpl.startSpan( + spanContext, + SPAN_NAME, + null, + parentSpanId, + false, + TraceParams.DEFAULT, + startEndHandler, + timestampConverter, + testClock); + span.addAttributes(attributes); + span.end(); + SpanData spanData = span.toSpanData(); + assertThat(spanData.getAttributes().getAttributeMap()).isEqualTo(attributes); + } + + @Test + public void toSpanData_ActiveSpan() { + RecordEventsSpanImpl span = + RecordEventsSpanImpl.startSpan( + spanContext, + SPAN_NAME, + null, + parentSpanId, + true, + TraceParams.DEFAULT, + startEndHandler, + timestampConverter, + testClock); + Mockito.verify(startEndHandler, Mockito.times(1)).onStart(span); + span.putAttribute( + "MySingleStringAttributeKey", + AttributeValue.stringAttributeValue("MySingleStringAttributeValue")); + span.putAttributes(attributes); + testClock.advanceTime(Duration.create(0, 100)); + span.addAnnotation(Annotation.fromDescription(ANNOTATION_DESCRIPTION)); + testClock.advanceTime(Duration.create(0, 100)); + span.addAnnotation(ANNOTATION_DESCRIPTION, attributes); + testClock.advanceTime(Duration.create(0, 100)); + NetworkEvent networkEvent = + NetworkEvent.builder(NetworkEvent.Type.RECV, 1).setUncompressedMessageSize(3).build(); + span.addNetworkEvent(networkEvent); + testClock.advanceTime(Duration.create(0, 100)); + Link link = Link.fromSpanContext(spanContext, Link.Type.CHILD_LINKED_SPAN); + span.addLink(link); + SpanData spanData = span.toSpanData(); + assertThat(spanData.getContext()).isEqualTo(spanContext); + assertThat(spanData.getName()).isEqualTo(SPAN_NAME); + assertThat(spanData.getParentSpanId()).isEqualTo(parentSpanId); + assertThat(spanData.getHasRemoteParent()).isTrue(); + assertThat(spanData.getAttributes().getDroppedAttributesCount()).isEqualTo(0); + assertThat(spanData.getAttributes().getAttributeMap()).isEqualTo(expectedAttributes); + assertThat(spanData.getAnnotations().getDroppedEventsCount()).isEqualTo(0); + assertThat(spanData.getAnnotations().getEvents().size()).isEqualTo(2); + assertThat(spanData.getAnnotations().getEvents().get(0).getTimestamp()) + .isEqualTo(timestamp.addNanos(100)); + assertThat(spanData.getAnnotations().getEvents().get(0).getEvent()) + .isEqualTo(Annotation.fromDescription(ANNOTATION_DESCRIPTION)); + assertThat(spanData.getAnnotations().getEvents().get(1).getTimestamp()) + .isEqualTo(timestamp.addNanos(200)); + assertThat(spanData.getAnnotations().getEvents().get(1).getEvent()) + .isEqualTo(Annotation.fromDescriptionAndAttributes(ANNOTATION_DESCRIPTION, attributes)); + assertThat(spanData.getNetworkEvents().getDroppedEventsCount()).isEqualTo(0); + assertThat(spanData.getNetworkEvents().getEvents().size()).isEqualTo(1); + assertThat(spanData.getNetworkEvents().getEvents().get(0).getTimestamp()) + .isEqualTo(timestamp.addNanos(300)); + assertThat(spanData.getNetworkEvents().getEvents().get(0).getEvent()).isEqualTo(networkEvent); + assertThat(spanData.getLinks().getDroppedLinksCount()).isEqualTo(0); + assertThat(spanData.getLinks().getLinks().size()).isEqualTo(1); + assertThat(spanData.getLinks().getLinks().get(0)).isEqualTo(link); + assertThat(spanData.getStartTimestamp()).isEqualTo(timestamp); + assertThat(spanData.getStatus()).isNull(); + assertThat(spanData.getEndTimestamp()).isNull(); + } + + @Test + public void toSpanData_EndedSpan() { + RecordEventsSpanImpl span = + RecordEventsSpanImpl.startSpan( + spanContext, + SPAN_NAME, + null, + parentSpanId, + false, + TraceParams.DEFAULT, + startEndHandler, + timestampConverter, + testClock); + Mockito.verify(startEndHandler, Mockito.times(1)).onStart(span); + span.putAttribute( + "MySingleStringAttributeKey", + AttributeValue.stringAttributeValue("MySingleStringAttributeValue")); + span.putAttributes(attributes); + testClock.advanceTime(Duration.create(0, 100)); + span.addAnnotation(Annotation.fromDescription(ANNOTATION_DESCRIPTION)); + testClock.advanceTime(Duration.create(0, 100)); + span.addAnnotation(ANNOTATION_DESCRIPTION, attributes); + testClock.advanceTime(Duration.create(0, 100)); + NetworkEvent networkEvent = + NetworkEvent.builder(NetworkEvent.Type.RECV, 1).setUncompressedMessageSize(3).build(); + span.addNetworkEvent(networkEvent); + Link link = Link.fromSpanContext(spanContext, Link.Type.CHILD_LINKED_SPAN); + span.addLink(link); + testClock.advanceTime(Duration.create(0, 100)); + span.end(EndSpanOptions.builder().setStatus(Status.CANCELLED).build()); + Mockito.verify(startEndHandler, Mockito.times(1)).onEnd(span); + SpanData spanData = span.toSpanData(); + assertThat(spanData.getContext()).isEqualTo(spanContext); + assertThat(spanData.getName()).isEqualTo(SPAN_NAME); + assertThat(spanData.getParentSpanId()).isEqualTo(parentSpanId); + assertThat(spanData.getHasRemoteParent()).isFalse(); + assertThat(spanData.getAttributes().getDroppedAttributesCount()).isEqualTo(0); + assertThat(spanData.getAttributes().getAttributeMap()).isEqualTo(expectedAttributes); + assertThat(spanData.getAnnotations().getDroppedEventsCount()).isEqualTo(0); + assertThat(spanData.getAnnotations().getEvents().size()).isEqualTo(2); + assertThat(spanData.getAnnotations().getEvents().get(0).getTimestamp()) + .isEqualTo(timestamp.addNanos(100)); + assertThat(spanData.getAnnotations().getEvents().get(0).getEvent()) + .isEqualTo(Annotation.fromDescription(ANNOTATION_DESCRIPTION)); + assertThat(spanData.getAnnotations().getEvents().get(1).getTimestamp()) + .isEqualTo(timestamp.addNanos(200)); + assertThat(spanData.getAnnotations().getEvents().get(1).getEvent()) + .isEqualTo(Annotation.fromDescriptionAndAttributes(ANNOTATION_DESCRIPTION, attributes)); + assertThat(spanData.getNetworkEvents().getDroppedEventsCount()).isEqualTo(0); + assertThat(spanData.getNetworkEvents().getEvents().size()).isEqualTo(1); + assertThat(spanData.getNetworkEvents().getEvents().get(0).getTimestamp()) + .isEqualTo(timestamp.addNanos(300)); + assertThat(spanData.getNetworkEvents().getEvents().get(0).getEvent()).isEqualTo(networkEvent); + assertThat(spanData.getLinks().getDroppedLinksCount()).isEqualTo(0); + assertThat(spanData.getLinks().getLinks().size()).isEqualTo(1); + assertThat(spanData.getLinks().getLinks().get(0)).isEqualTo(link); + assertThat(spanData.getStartTimestamp()).isEqualTo(timestamp); + assertThat(spanData.getStatus()).isEqualTo(Status.CANCELLED); + assertThat(spanData.getEndTimestamp()).isEqualTo(timestamp.addNanos(400)); + } + + @Test + public void status_ViaSetStatus() { + RecordEventsSpanImpl span = + RecordEventsSpanImpl.startSpan( + spanContext, + SPAN_NAME, + null, + parentSpanId, + false, + TraceParams.DEFAULT, + startEndHandler, + timestampConverter, + testClock); + Mockito.verify(startEndHandler, Mockito.times(1)).onStart(span); + testClock.advanceTime(Duration.create(0, 100)); + assertThat(span.getStatus()).isEqualTo(Status.OK); + span.setStatus(Status.CANCELLED); + assertThat(span.getStatus()).isEqualTo(Status.CANCELLED); + span.end(); + assertThat(span.getStatus()).isEqualTo(Status.CANCELLED); + } + + @Test + public void status_ViaEndSpanOptions() { + RecordEventsSpanImpl span = + RecordEventsSpanImpl.startSpan( + spanContext, + SPAN_NAME, + null, + parentSpanId, + false, + TraceParams.DEFAULT, + startEndHandler, + timestampConverter, + testClock); + Mockito.verify(startEndHandler, Mockito.times(1)).onStart(span); + testClock.advanceTime(Duration.create(0, 100)); + assertThat(span.getStatus()).isEqualTo(Status.OK); + span.setStatus(Status.CANCELLED); + assertThat(span.getStatus()).isEqualTo(Status.CANCELLED); + span.end(EndSpanOptions.builder().setStatus(Status.ABORTED).build()); + assertThat(span.getStatus()).isEqualTo(Status.ABORTED); + } + + @Test + public void droppingAttributes() { + final int maxNumberOfAttributes = 8; + TraceParams traceParams = + TraceParams.DEFAULT.toBuilder().setMaxNumberOfAttributes(maxNumberOfAttributes).build(); + RecordEventsSpanImpl span = + RecordEventsSpanImpl.startSpan( + spanContext, + SPAN_NAME, + null, + parentSpanId, + false, + traceParams, + startEndHandler, + timestampConverter, + testClock); + for (int i = 0; i < 2 * maxNumberOfAttributes; i++) { + Map<String, AttributeValue> attributes = new HashMap<String, AttributeValue>(); + attributes.put("MyStringAttributeKey" + i, AttributeValue.longAttributeValue(i)); + span.putAttributes(attributes); + } + SpanData spanData = span.toSpanData(); + assertThat(spanData.getAttributes().getDroppedAttributesCount()) + .isEqualTo(maxNumberOfAttributes); + assertThat(spanData.getAttributes().getAttributeMap().size()).isEqualTo(maxNumberOfAttributes); + for (int i = 0; i < maxNumberOfAttributes; i++) { + assertThat( + spanData + .getAttributes() + .getAttributeMap() + .get("MyStringAttributeKey" + (i + maxNumberOfAttributes))) + .isEqualTo(AttributeValue.longAttributeValue(i + maxNumberOfAttributes)); + } + span.end(); + spanData = span.toSpanData(); + assertThat(spanData.getAttributes().getDroppedAttributesCount()) + .isEqualTo(maxNumberOfAttributes); + assertThat(spanData.getAttributes().getAttributeMap().size()).isEqualTo(maxNumberOfAttributes); + for (int i = 0; i < maxNumberOfAttributes; i++) { + assertThat( + spanData + .getAttributes() + .getAttributeMap() + .get("MyStringAttributeKey" + (i + maxNumberOfAttributes))) + .isEqualTo(AttributeValue.longAttributeValue(i + maxNumberOfAttributes)); + } + } + + @Test + public void droppingAndAddingAttributes() { + final int maxNumberOfAttributes = 8; + TraceParams traceParams = + TraceParams.DEFAULT.toBuilder().setMaxNumberOfAttributes(maxNumberOfAttributes).build(); + RecordEventsSpanImpl span = + RecordEventsSpanImpl.startSpan( + spanContext, + SPAN_NAME, + null, + parentSpanId, + false, + traceParams, + startEndHandler, + timestampConverter, + testClock); + for (int i = 0; i < 2 * maxNumberOfAttributes; i++) { + Map<String, AttributeValue> attributes = new HashMap<String, AttributeValue>(); + attributes.put("MyStringAttributeKey" + i, AttributeValue.longAttributeValue(i)); + span.putAttributes(attributes); + } + SpanData spanData = span.toSpanData(); + assertThat(spanData.getAttributes().getDroppedAttributesCount()) + .isEqualTo(maxNumberOfAttributes); + assertThat(spanData.getAttributes().getAttributeMap().size()).isEqualTo(maxNumberOfAttributes); + for (int i = 0; i < maxNumberOfAttributes; i++) { + assertThat( + spanData + .getAttributes() + .getAttributeMap() + .get("MyStringAttributeKey" + (i + maxNumberOfAttributes))) + .isEqualTo(AttributeValue.longAttributeValue(i + maxNumberOfAttributes)); + } + for (int i = 0; i < maxNumberOfAttributes / 2; i++) { + Map<String, AttributeValue> attributes = new HashMap<String, AttributeValue>(); + attributes.put("MyStringAttributeKey" + i, AttributeValue.longAttributeValue(i)); + span.putAttributes(attributes); + } + spanData = span.toSpanData(); + assertThat(spanData.getAttributes().getDroppedAttributesCount()) + .isEqualTo(maxNumberOfAttributes * 3 / 2); + assertThat(spanData.getAttributes().getAttributeMap().size()).isEqualTo(maxNumberOfAttributes); + // Test that we still have in the attributes map the latest maxNumberOfAttributes / 2 entries. + for (int i = 0; i < maxNumberOfAttributes / 2; i++) { + assertThat( + spanData + .getAttributes() + .getAttributeMap() + .get("MyStringAttributeKey" + (i + maxNumberOfAttributes * 3 / 2))) + .isEqualTo(AttributeValue.longAttributeValue(i + maxNumberOfAttributes * 3 / 2)); + } + // Test that we have the newest re-added initial entries. + for (int i = 0; i < maxNumberOfAttributes / 2; i++) { + assertThat(spanData.getAttributes().getAttributeMap().get("MyStringAttributeKey" + i)) + .isEqualTo(AttributeValue.longAttributeValue(i)); + } + } + + @Test + public void droppingAnnotations() { + final int maxNumberOfAnnotations = 8; + TraceParams traceParams = + TraceParams.DEFAULT.toBuilder().setMaxNumberOfAnnotations(maxNumberOfAnnotations).build(); + RecordEventsSpanImpl span = + RecordEventsSpanImpl.startSpan( + spanContext, + SPAN_NAME, + null, + parentSpanId, + false, + traceParams, + startEndHandler, + timestampConverter, + testClock); + Annotation annotation = Annotation.fromDescription(ANNOTATION_DESCRIPTION); + for (int i = 0; i < 2 * maxNumberOfAnnotations; i++) { + span.addAnnotation(annotation); + testClock.advanceTime(Duration.create(0, 100)); + } + SpanData spanData = span.toSpanData(); + assertThat(spanData.getAnnotations().getDroppedEventsCount()).isEqualTo(maxNumberOfAnnotations); + assertThat(spanData.getAnnotations().getEvents().size()).isEqualTo(maxNumberOfAnnotations); + for (int i = 0; i < maxNumberOfAnnotations; i++) { + assertThat(spanData.getAnnotations().getEvents().get(i).getTimestamp()) + .isEqualTo(timestamp.addNanos(100L * (maxNumberOfAnnotations + i))); + assertThat(spanData.getAnnotations().getEvents().get(i).getEvent()).isEqualTo(annotation); + } + span.end(); + spanData = span.toSpanData(); + assertThat(spanData.getAnnotations().getDroppedEventsCount()).isEqualTo(maxNumberOfAnnotations); + assertThat(spanData.getAnnotations().getEvents().size()).isEqualTo(maxNumberOfAnnotations); + for (int i = 0; i < maxNumberOfAnnotations; i++) { + assertThat(spanData.getAnnotations().getEvents().get(i).getTimestamp()) + .isEqualTo(timestamp.addNanos(100L * (maxNumberOfAnnotations + i))); + assertThat(spanData.getAnnotations().getEvents().get(i).getEvent()).isEqualTo(annotation); + } + } + + @Test + public void droppingNetworkEvents() { + final int maxNumberOfNetworkEvents = 8; + TraceParams traceParams = + TraceParams.DEFAULT + .toBuilder() + .setMaxNumberOfNetworkEvents(maxNumberOfNetworkEvents) + .build(); + RecordEventsSpanImpl span = + RecordEventsSpanImpl.startSpan( + spanContext, + SPAN_NAME, + null, + parentSpanId, + false, + traceParams, + startEndHandler, + timestampConverter, + testClock); + NetworkEvent networkEvent = + NetworkEvent.builder(NetworkEvent.Type.RECV, 1).setUncompressedMessageSize(3).build(); + for (int i = 0; i < 2 * maxNumberOfNetworkEvents; i++) { + span.addNetworkEvent(networkEvent); + testClock.advanceTime(Duration.create(0, 100)); + } + SpanData spanData = span.toSpanData(); + assertThat(spanData.getNetworkEvents().getDroppedEventsCount()) + .isEqualTo(maxNumberOfNetworkEvents); + assertThat(spanData.getNetworkEvents().getEvents().size()).isEqualTo(maxNumberOfNetworkEvents); + for (int i = 0; i < maxNumberOfNetworkEvents; i++) { + assertThat(spanData.getNetworkEvents().getEvents().get(i).getTimestamp()) + .isEqualTo(timestamp.addNanos(100L * (maxNumberOfNetworkEvents + i))); + assertThat(spanData.getNetworkEvents().getEvents().get(i).getEvent()).isEqualTo(networkEvent); + } + span.end(); + spanData = span.toSpanData(); + assertThat(spanData.getNetworkEvents().getDroppedEventsCount()) + .isEqualTo(maxNumberOfNetworkEvents); + assertThat(spanData.getNetworkEvents().getEvents().size()).isEqualTo(maxNumberOfNetworkEvents); + for (int i = 0; i < maxNumberOfNetworkEvents; i++) { + assertThat(spanData.getNetworkEvents().getEvents().get(i).getTimestamp()) + .isEqualTo(timestamp.addNanos(100L * (maxNumberOfNetworkEvents + i))); + assertThat(spanData.getNetworkEvents().getEvents().get(i).getEvent()).isEqualTo(networkEvent); + } + } + + @Test + public void droppingLinks() { + final int maxNumberOfLinks = 8; + TraceParams traceParams = + TraceParams.DEFAULT.toBuilder().setMaxNumberOfLinks(maxNumberOfLinks).build(); + RecordEventsSpanImpl span = + RecordEventsSpanImpl.startSpan( + spanContext, + SPAN_NAME, + null, + parentSpanId, + false, + traceParams, + startEndHandler, + timestampConverter, + testClock); + Link link = Link.fromSpanContext(spanContext, Link.Type.CHILD_LINKED_SPAN); + for (int i = 0; i < 2 * maxNumberOfLinks; i++) { + span.addLink(link); + } + SpanData spanData = span.toSpanData(); + assertThat(spanData.getLinks().getDroppedLinksCount()).isEqualTo(maxNumberOfLinks); + assertThat(spanData.getLinks().getLinks().size()).isEqualTo(maxNumberOfLinks); + for (int i = 0; i < maxNumberOfLinks; i++) { + assertThat(spanData.getLinks().getLinks().get(i)).isEqualTo(link); + } + span.end(); + spanData = span.toSpanData(); + assertThat(spanData.getLinks().getDroppedLinksCount()).isEqualTo(maxNumberOfLinks); + assertThat(spanData.getLinks().getLinks().size()).isEqualTo(maxNumberOfLinks); + for (int i = 0; i < maxNumberOfLinks; i++) { + assertThat(spanData.getLinks().getLinks().get(i)).isEqualTo(link); + } + } + + @Test + public void sampleToLocalSpanStore() { + RecordEventsSpanImpl span = + RecordEventsSpanImpl.startSpan( + spanContext, + SPAN_NAME, + null, + parentSpanId, + false, + TraceParams.DEFAULT, + startEndHandler, + timestampConverter, + testClock); + span.end(EndSpanOptions.builder().setSampleToLocalSpanStore(true).build()); + Mockito.verify(startEndHandler, Mockito.times(1)).onEnd(span); + assertThat(span.getSampleToLocalSpanStore()).isTrue(); + span = + RecordEventsSpanImpl.startSpan( + spanContext, + SPAN_NAME, + null, + parentSpanId, + false, + TraceParams.DEFAULT, + startEndHandler, + timestampConverter, + testClock); + span.end(); + Mockito.verify(startEndHandler, Mockito.times(1)).onEnd(span); + assertThat(span.getSampleToLocalSpanStore()).isFalse(); + } + + @Test + public void sampleToLocalSpanStore_RunningSpan() { + RecordEventsSpanImpl span = + RecordEventsSpanImpl.startSpan( + spanContext, + SPAN_NAME, + null, + parentSpanId, + false, + TraceParams.DEFAULT, + startEndHandler, + timestampConverter, + testClock); + exception.expect(IllegalStateException.class); + exception.expectMessage("Running span does not have the SampleToLocalSpanStore set."); + span.getSampleToLocalSpanStore(); + } + + @Test + public void getSpanKind() { + RecordEventsSpanImpl span = + RecordEventsSpanImpl.startSpan( + spanContext, + SPAN_NAME, + Kind.SERVER, + parentSpanId, + false, + TraceParams.DEFAULT, + startEndHandler, + timestampConverter, + testClock); + assertThat(span.getKind()).isEqualTo(Kind.SERVER); + } +} diff --git a/impl_core/src/test/java/io/opencensus/implcore/trace/SpanBuilderImplTest.java b/impl_core/src/test/java/io/opencensus/implcore/trace/SpanBuilderImplTest.java new file mode 100644 index 00000000..3267eac5 --- /dev/null +++ b/impl_core/src/test/java/io/opencensus/implcore/trace/SpanBuilderImplTest.java @@ -0,0 +1,404 @@ +/* + * Copyright 2017, OpenCensus Authors + * + * 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 io.opencensus.implcore.trace; + +import static com.google.common.truth.Truth.assertThat; +import static org.mockito.Mockito.when; + +import io.opencensus.implcore.trace.RecordEventsSpanImpl.StartEndHandler; +import io.opencensus.implcore.trace.internal.RandomHandler; +import io.opencensus.testing.common.TestClock; +import io.opencensus.trace.Span; +import io.opencensus.trace.Span.Kind; +import io.opencensus.trace.Span.Options; +import io.opencensus.trace.SpanContext; +import io.opencensus.trace.SpanId; +import io.opencensus.trace.TraceId; +import io.opencensus.trace.TraceOptions; +import io.opencensus.trace.config.TraceConfig; +import io.opencensus.trace.config.TraceParams; +import io.opencensus.trace.export.SpanData; +import io.opencensus.trace.samplers.Samplers; +import java.util.Collections; +import java.util.Random; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +/** Unit tests for {@link SpanBuilderImpl}. */ +@RunWith(JUnit4.class) +public class SpanBuilderImplTest { + private static final String SPAN_NAME = "MySpanName"; + private SpanBuilderImpl.Options spanBuilderOptions; + private final TraceParams alwaysSampleTraceParams = + TraceParams.DEFAULT.toBuilder().setSampler(Samplers.alwaysSample()).build(); + private final TestClock testClock = TestClock.create(); + private final RandomHandler randomHandler = new FakeRandomHandler(); + @Mock private StartEndHandler startEndHandler; + @Mock private TraceConfig traceConfig; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + spanBuilderOptions = + new SpanBuilderImpl.Options(randomHandler, startEndHandler, testClock, traceConfig); + when(traceConfig.getActiveTraceParams()).thenReturn(alwaysSampleTraceParams); + } + + @Test + public void startSpan_CreatesTheCorrectSpanImplInstance() { + assertThat( + SpanBuilderImpl.createWithParent(SPAN_NAME, null, spanBuilderOptions) + .setSampler(Samplers.alwaysSample()) + .startSpan()) + .isInstanceOf(RecordEventsSpanImpl.class); + assertThat( + SpanBuilderImpl.createWithParent(SPAN_NAME, null, spanBuilderOptions) + .setRecordEvents(true) + .setSampler(Samplers.neverSample()) + .startSpan()) + .isInstanceOf(RecordEventsSpanImpl.class); + assertThat( + SpanBuilderImpl.createWithParent(SPAN_NAME, null, spanBuilderOptions) + .setSampler(Samplers.neverSample()) + .startSpan()) + .isInstanceOf(NoRecordEventsSpanImpl.class); + } + + @Test + public void setSpanKind_NotNull() { + RecordEventsSpanImpl span = + (RecordEventsSpanImpl) + SpanBuilderImpl.createWithParent(SPAN_NAME, null, spanBuilderOptions) + .setSpanKind(Kind.CLIENT) + .setRecordEvents(true) + .startSpan(); + assertThat(span.getKind()).isEqualTo(Kind.CLIENT); + assertThat(span.toSpanData().getKind()).isEqualTo(Kind.CLIENT); + } + + @Test + public void setSpanKind_DefaultNull() { + RecordEventsSpanImpl span = + (RecordEventsSpanImpl) + SpanBuilderImpl.createWithParent(SPAN_NAME, null, spanBuilderOptions) + .setRecordEvents(true) + .startSpan(); + assertThat(span.getKind()).isNull(); + assertThat(span.toSpanData().getKind()).isNull(); + } + + @Test + public void startSpanNullParent() { + RecordEventsSpanImpl span = + (RecordEventsSpanImpl) + SpanBuilderImpl.createWithParent(SPAN_NAME, null, spanBuilderOptions) + .setRecordEvents(true) + .startSpan(); + assertThat(span.getContext().isValid()).isTrue(); + assertThat(span.getOptions().contains(Options.RECORD_EVENTS)).isTrue(); + assertThat(span.getContext().getTraceOptions().isSampled()).isTrue(); + SpanData spanData = span.toSpanData(); + assertThat(spanData.getParentSpanId()).isNull(); + assertThat(spanData.getHasRemoteParent()).isNull(); + assertThat(spanData.getStartTimestamp()).isEqualTo(testClock.now()); + assertThat(spanData.getName()).isEqualTo(SPAN_NAME); + } + + @Test + public void startSpanNullParentWithRecordEvents() { + RecordEventsSpanImpl span = + (RecordEventsSpanImpl) + SpanBuilderImpl.createWithParent(SPAN_NAME, null, spanBuilderOptions) + .setSampler(Samplers.neverSample()) + .setRecordEvents(true) + .startSpan(); + assertThat(span.getContext().isValid()).isTrue(); + assertThat(span.getOptions().contains(Options.RECORD_EVENTS)).isTrue(); + assertThat(span.getContext().getTraceOptions().isSampled()).isFalse(); + SpanData spanData = span.toSpanData(); + assertThat(spanData.getParentSpanId()).isNull(); + assertThat(spanData.getHasRemoteParent()).isNull(); + } + + @Test + public void startSpanNullParentNoRecordOptions() { + Span span = + SpanBuilderImpl.createWithParent(SPAN_NAME, null, spanBuilderOptions) + .setSampler(Samplers.neverSample()) + .startSpan(); + assertThat(span.getContext().isValid()).isTrue(); + assertThat(span.getOptions().contains(Options.RECORD_EVENTS)).isFalse(); + assertThat(span.getContext().getTraceOptions().isSampled()).isFalse(); + } + + @Test + public void startChildSpan() { + Span rootSpan = + SpanBuilderImpl.createWithParent(SPAN_NAME, null, spanBuilderOptions).startSpan(); + assertThat(rootSpan.getContext().isValid()).isTrue(); + assertThat(rootSpan.getOptions().contains(Options.RECORD_EVENTS)).isTrue(); + assertThat(rootSpan.getContext().getTraceOptions().isSampled()).isTrue(); + assertThat(((RecordEventsSpanImpl) rootSpan).toSpanData().getHasRemoteParent()).isNull(); + Span childSpan = + SpanBuilderImpl.createWithParent(SPAN_NAME, rootSpan, spanBuilderOptions).startSpan(); + assertThat(childSpan.getContext().isValid()).isTrue(); + assertThat(childSpan.getContext().getTraceId()).isEqualTo(rootSpan.getContext().getTraceId()); + assertThat(((RecordEventsSpanImpl) childSpan).toSpanData().getParentSpanId()) + .isEqualTo(rootSpan.getContext().getSpanId()); + assertThat(((RecordEventsSpanImpl) childSpan).toSpanData().getHasRemoteParent()).isFalse(); + assertThat(((RecordEventsSpanImpl) childSpan).getTimestampConverter()) + .isEqualTo(((RecordEventsSpanImpl) rootSpan).getTimestampConverter()); + } + + @Test + public void startRemoteSpan_NullParent() { + RecordEventsSpanImpl span = + (RecordEventsSpanImpl) + SpanBuilderImpl.createWithRemoteParent(SPAN_NAME, null, spanBuilderOptions) + .setRecordEvents(true) + .startSpan(); + assertThat(span.getContext().isValid()).isTrue(); + assertThat(span.getOptions().contains(Options.RECORD_EVENTS)).isTrue(); + assertThat(span.getContext().getTraceOptions().isSampled()).isTrue(); + SpanData spanData = span.toSpanData(); + assertThat(spanData.getParentSpanId()).isNull(); + assertThat(spanData.getHasRemoteParent()).isNull(); + } + + @Test + public void startRemoteSpanInvalidParent() { + RecordEventsSpanImpl span = + (RecordEventsSpanImpl) + SpanBuilderImpl.createWithRemoteParent( + SPAN_NAME, SpanContext.INVALID, spanBuilderOptions) + .startSpan(); + assertThat(span.getContext().isValid()).isTrue(); + assertThat(span.getOptions().contains(Options.RECORD_EVENTS)).isTrue(); + assertThat(span.getContext().getTraceOptions().isSampled()).isTrue(); + SpanData spanData = span.toSpanData(); + assertThat(spanData.getParentSpanId()).isNull(); + assertThat(spanData.getHasRemoteParent()).isNull(); + } + + @Test + public void startRemoteSpan() { + SpanContext spanContext = + SpanContext.create( + TraceId.generateRandomId(randomHandler.current()), + SpanId.generateRandomId(randomHandler.current()), + TraceOptions.DEFAULT); + RecordEventsSpanImpl span = + (RecordEventsSpanImpl) + SpanBuilderImpl.createWithRemoteParent(SPAN_NAME, spanContext, spanBuilderOptions) + .setRecordEvents(true) + .startSpan(); + assertThat(span.getContext().isValid()).isTrue(); + assertThat(span.getContext().getTraceId()).isEqualTo(spanContext.getTraceId()); + assertThat(span.getContext().getTraceOptions().isSampled()).isTrue(); + SpanData spanData = span.toSpanData(); + assertThat(spanData.getParentSpanId()).isEqualTo(spanContext.getSpanId()); + assertThat(spanData.getHasRemoteParent()).isTrue(); + } + + @Test + public void startRootSpan_WithSpecifiedSampler() { + // Apply given sampler before default sampler for root spans. + Span rootSpan = + SpanBuilderImpl.createWithParent(SPAN_NAME, null, spanBuilderOptions) + .setSampler(Samplers.neverSample()) + .startSpan(); + assertThat(rootSpan.getContext().isValid()).isTrue(); + assertThat(rootSpan.getContext().getTraceOptions().isSampled()).isFalse(); + } + + @Test + public void startRootSpan_WithoutSpecifiedSampler() { + // Apply default sampler (always true in the tests) for root spans. + Span rootSpan = + SpanBuilderImpl.createWithParent(SPAN_NAME, null, spanBuilderOptions).startSpan(); + assertThat(rootSpan.getContext().isValid()).isTrue(); + assertThat(rootSpan.getContext().getTraceOptions().isSampled()).isTrue(); + } + + @Test + public void startRemoteChildSpan_WithSpecifiedSampler() { + Span rootSpan = + SpanBuilderImpl.createWithParent(SPAN_NAME, null, spanBuilderOptions) + .setSampler(Samplers.alwaysSample()) + .startSpan(); + assertThat(rootSpan.getContext().isValid()).isTrue(); + assertThat(rootSpan.getContext().getTraceOptions().isSampled()).isTrue(); + // Apply given sampler before default sampler for spans with remote parent. + Span childSpan = + SpanBuilderImpl.createWithRemoteParent(SPAN_NAME, rootSpan.getContext(), spanBuilderOptions) + .setSampler(Samplers.neverSample()) + .startSpan(); + assertThat(childSpan.getContext().isValid()).isTrue(); + assertThat(childSpan.getContext().getTraceId()).isEqualTo(rootSpan.getContext().getTraceId()); + assertThat(childSpan.getContext().getTraceOptions().isSampled()).isFalse(); + } + + @Test + public void startRemoteChildSpan_WithoutSpecifiedSampler() { + Span rootSpan = + SpanBuilderImpl.createWithParent(SPAN_NAME, null, spanBuilderOptions) + .setSampler(Samplers.neverSample()) + .startSpan(); + assertThat(rootSpan.getContext().isValid()).isTrue(); + assertThat(rootSpan.getContext().getTraceOptions().isSampled()).isFalse(); + // Apply default sampler (always true in the tests) for spans with remote parent. + Span childSpan = + SpanBuilderImpl.createWithRemoteParent(SPAN_NAME, rootSpan.getContext(), spanBuilderOptions) + .startSpan(); + assertThat(childSpan.getContext().isValid()).isTrue(); + assertThat(childSpan.getContext().getTraceId()).isEqualTo(rootSpan.getContext().getTraceId()); + assertThat(childSpan.getContext().getTraceOptions().isSampled()).isTrue(); + } + + @Test + public void startChildSpan_WithSpecifiedSampler() { + Span rootSpan = + SpanBuilderImpl.createWithParent(SPAN_NAME, null, spanBuilderOptions) + .setSampler(Samplers.alwaysSample()) + .startSpan(); + assertThat(rootSpan.getContext().isValid()).isTrue(); + assertThat(rootSpan.getContext().getTraceOptions().isSampled()).isTrue(); + // Apply the given sampler for child spans. + Span childSpan = + SpanBuilderImpl.createWithParent(SPAN_NAME, rootSpan, spanBuilderOptions) + .setSampler(Samplers.neverSample()) + .startSpan(); + assertThat(childSpan.getContext().isValid()).isTrue(); + assertThat(childSpan.getContext().getTraceId()).isEqualTo(rootSpan.getContext().getTraceId()); + assertThat(childSpan.getContext().getTraceOptions().isSampled()).isFalse(); + } + + @Test + public void startChildSpan_WithoutSpecifiedSampler() { + Span rootSpan = + SpanBuilderImpl.createWithParent(SPAN_NAME, null, spanBuilderOptions) + .setSampler(Samplers.neverSample()) + .startSpan(); + assertThat(rootSpan.getContext().isValid()).isTrue(); + assertThat(rootSpan.getContext().getTraceOptions().isSampled()).isFalse(); + // Don't apply the default sampler (always true) for child spans. + Span childSpan = + SpanBuilderImpl.createWithParent(SPAN_NAME, rootSpan, spanBuilderOptions).startSpan(); + assertThat(childSpan.getContext().isValid()).isTrue(); + assertThat(childSpan.getContext().getTraceId()).isEqualTo(rootSpan.getContext().getTraceId()); + assertThat(childSpan.getContext().getTraceOptions().isSampled()).isFalse(); + } + + @Test + public void startChildSpan_SampledLinkedParent() { + Span rootSpanUnsampled = + SpanBuilderImpl.createWithParent(SPAN_NAME, null, spanBuilderOptions) + .setSampler(Samplers.neverSample()) + .startSpan(); + assertThat(rootSpanUnsampled.getContext().getTraceOptions().isSampled()).isFalse(); + Span rootSpanSampled = + SpanBuilderImpl.createWithParent(SPAN_NAME, null, spanBuilderOptions) + .setSampler(Samplers.alwaysSample()) + .startSpan(); + assertThat(rootSpanSampled.getContext().getTraceOptions().isSampled()).isTrue(); + // Sampled because the linked parent is sampled. + Span childSpan = + SpanBuilderImpl.createWithParent(SPAN_NAME, rootSpanUnsampled, spanBuilderOptions) + .setParentLinks(Collections.singletonList(rootSpanSampled)) + .startSpan(); + assertThat(childSpan.getContext().isValid()).isTrue(); + assertThat(childSpan.getContext().getTraceId()) + .isEqualTo(rootSpanUnsampled.getContext().getTraceId()); + assertThat(childSpan.getContext().getTraceOptions().isSampled()).isTrue(); + } + + @Test + public void startRemoteChildSpan_WithProbabilitySamplerDefaultSampler() { + when(traceConfig.getActiveTraceParams()).thenReturn(TraceParams.DEFAULT); + // This traceId will not be sampled by the ProbabilitySampler because the first 8 bytes as long + // is not less than probability * Long.MAX_VALUE; + TraceId traceId = + TraceId.fromBytes( + new byte[] { + (byte) 0x8F, + (byte) 0xFF, + (byte) 0xFF, + (byte) 0xFF, + (byte) 0xFF, + (byte) 0xFF, + (byte) 0xFF, + (byte) 0xFF, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + }); + + // If parent is sampled then the remote child must be sampled. + Span childSpan = + SpanBuilderImpl.createWithRemoteParent( + SPAN_NAME, + SpanContext.create( + traceId, + SpanId.generateRandomId(randomHandler.current()), + TraceOptions.builder().setIsSampled(true).build()), + spanBuilderOptions) + .startSpan(); + assertThat(childSpan.getContext().isValid()).isTrue(); + assertThat(childSpan.getContext().getTraceId()).isEqualTo(traceId); + assertThat(childSpan.getContext().getTraceOptions().isSampled()).isTrue(); + childSpan.end(); + + assertThat(traceConfig.getActiveTraceParams()).isEqualTo(TraceParams.DEFAULT); + + // If parent is not sampled then the remote child must be not sampled. + childSpan = + SpanBuilderImpl.createWithRemoteParent( + SPAN_NAME, + SpanContext.create( + traceId, + SpanId.generateRandomId(randomHandler.current()), + TraceOptions.DEFAULT), + spanBuilderOptions) + .startSpan(); + assertThat(childSpan.getContext().isValid()).isTrue(); + assertThat(childSpan.getContext().getTraceId()).isEqualTo(traceId); + assertThat(childSpan.getContext().getTraceOptions().isSampled()).isFalse(); + childSpan.end(); + } + + private static final class FakeRandomHandler extends RandomHandler { + private final Random random; + + FakeRandomHandler() { + this.random = new Random(1234); + } + + @Override + public Random current() { + return random; + } + } +} diff --git a/impl_core/src/test/java/io/opencensus/implcore/trace/TraceComponentImplBaseTest.java b/impl_core/src/test/java/io/opencensus/implcore/trace/TraceComponentImplBaseTest.java new file mode 100644 index 00000000..9f468442 --- /dev/null +++ b/impl_core/src/test/java/io/opencensus/implcore/trace/TraceComponentImplBaseTest.java @@ -0,0 +1,57 @@ +/* + * Copyright 2017, OpenCensus Authors + * + * 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 io.opencensus.implcore.trace; + +import static com.google.common.truth.Truth.assertThat; + +import io.opencensus.implcore.common.MillisClock; +import io.opencensus.implcore.internal.SimpleEventQueue; +import io.opencensus.implcore.trace.export.ExportComponentImpl; +import io.opencensus.implcore.trace.internal.RandomHandler.SecureRandomHandler; +import io.opencensus.implcore.trace.propagation.PropagationComponentImpl; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Unit tests for {@link TraceComponentImplBase}. */ +@RunWith(JUnit4.class) +public class TraceComponentImplBaseTest { + private final TraceComponentImplBase traceComponentImplBase = + new TraceComponentImplBase( + MillisClock.getInstance(), new SecureRandomHandler(), new SimpleEventQueue()); + + @Test + public void implementationOfTracer() { + assertThat(traceComponentImplBase.getTracer()).isInstanceOf(TracerImpl.class); + } + + @Test + public void implementationOfBinaryPropagationHandler() { + assertThat(traceComponentImplBase.getPropagationComponent()) + .isInstanceOf(PropagationComponentImpl.class); + } + + @Test + public void implementationOfClock() { + assertThat(traceComponentImplBase.getClock()).isInstanceOf(MillisClock.class); + } + + @Test + public void implementationOfTraceExporter() { + assertThat(traceComponentImplBase.getExportComponent()).isInstanceOf(ExportComponentImpl.class); + } +} diff --git a/impl_core/src/test/java/io/opencensus/implcore/trace/TracerImplTest.java b/impl_core/src/test/java/io/opencensus/implcore/trace/TracerImplTest.java new file mode 100644 index 00000000..d10be6a2 --- /dev/null +++ b/impl_core/src/test/java/io/opencensus/implcore/trace/TracerImplTest.java @@ -0,0 +1,61 @@ +/* + * Copyright 2017, OpenCensus Authors + * + * 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 io.opencensus.implcore.trace; + +import static com.google.common.truth.Truth.assertThat; + +import io.opencensus.implcore.trace.RecordEventsSpanImpl.StartEndHandler; +import io.opencensus.implcore.trace.internal.RandomHandler.SecureRandomHandler; +import io.opencensus.testing.common.TestClock; +import io.opencensus.trace.BlankSpan; +import io.opencensus.trace.SpanBuilder; +import io.opencensus.trace.SpanContext; +import io.opencensus.trace.config.TraceConfig; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +/** Unit tests for {@link TracerImpl}. */ +@RunWith(JUnit4.class) +public class TracerImplTest { + private static final String SPAN_NAME = "MySpanName"; + @Mock private StartEndHandler startEndHandler; + @Mock private TraceConfig traceConfig; + private TracerImpl tracer; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + tracer = + new TracerImpl(new SecureRandomHandler(), startEndHandler, TestClock.create(), traceConfig); + } + + @Test + public void createSpanBuilder() { + SpanBuilder spanBuilder = tracer.spanBuilderWithExplicitParent(SPAN_NAME, BlankSpan.INSTANCE); + assertThat(spanBuilder).isInstanceOf(SpanBuilderImpl.class); + } + + @Test + public void createSpanBuilderWithRemoteParet() { + SpanBuilder spanBuilder = tracer.spanBuilderWithRemoteParent(SPAN_NAME, SpanContext.INVALID); + assertThat(spanBuilder).isInstanceOf(SpanBuilderImpl.class); + } +} diff --git a/impl_core/src/test/java/io/opencensus/implcore/trace/config/TraceConfigImplTest.java b/impl_core/src/test/java/io/opencensus/implcore/trace/config/TraceConfigImplTest.java new file mode 100644 index 00000000..ecaeda6d --- /dev/null +++ b/impl_core/src/test/java/io/opencensus/implcore/trace/config/TraceConfigImplTest.java @@ -0,0 +1,53 @@ +/* + * Copyright 2017, OpenCensus Authors + * + * 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 io.opencensus.implcore.trace.config; + +import static com.google.common.truth.Truth.assertThat; + +import io.opencensus.trace.config.TraceParams; +import io.opencensus.trace.samplers.Samplers; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Unit tests for {@link TraceConfigImpl}. */ +@RunWith(JUnit4.class) +public class TraceConfigImplTest { + private final TraceConfigImpl traceConfig = new TraceConfigImpl(); + + @Test + public void defaultActiveTraceParams() { + assertThat(traceConfig.getActiveTraceParams()).isEqualTo(TraceParams.DEFAULT); + } + + @Test + public void updateTraceParams() { + TraceParams traceParams = + TraceParams.DEFAULT + .toBuilder() + .setSampler(Samplers.alwaysSample()) + .setMaxNumberOfAttributes(8) + .setMaxNumberOfAnnotations(9) + .setMaxNumberOfNetworkEvents(10) + .setMaxNumberOfLinks(11) + .build(); + traceConfig.updateActiveTraceParams(traceParams); + assertThat(traceConfig.getActiveTraceParams()).isEqualTo(traceParams); + traceConfig.updateActiveTraceParams(TraceParams.DEFAULT); + assertThat(traceConfig.getActiveTraceParams()).isEqualTo(TraceParams.DEFAULT); + } +} diff --git a/impl_core/src/test/java/io/opencensus/implcore/trace/export/ExportComponentImplTest.java b/impl_core/src/test/java/io/opencensus/implcore/trace/export/ExportComponentImplTest.java new file mode 100644 index 00000000..4b8993ff --- /dev/null +++ b/impl_core/src/test/java/io/opencensus/implcore/trace/export/ExportComponentImplTest.java @@ -0,0 +1,55 @@ +/* + * Copyright 2017, OpenCensus Authors + * + * 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 io.opencensus.implcore.trace.export; + +import static com.google.common.truth.Truth.assertThat; + +import io.opencensus.implcore.internal.SimpleEventQueue; +import io.opencensus.trace.export.ExportComponent; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Unit tests for {@link ExportComponentImpl}. */ +@RunWith(JUnit4.class) +public class ExportComponentImplTest { + private final ExportComponent exportComponentWithInProcess = + ExportComponentImpl.createWithInProcessStores(new SimpleEventQueue()); + private final ExportComponent exportComponentWithoutInProcess = + ExportComponentImpl.createWithoutInProcessStores(new SimpleEventQueue()); + + @Test + public void implementationOfSpanExporter() { + assertThat(exportComponentWithInProcess.getSpanExporter()).isInstanceOf(SpanExporterImpl.class); + } + + @Test + public void implementationOfActiveSpans() { + assertThat(exportComponentWithInProcess.getRunningSpanStore()) + .isInstanceOf(InProcessRunningSpanStoreImpl.class); + assertThat(exportComponentWithoutInProcess.getRunningSpanStore()) + .isInstanceOf(RunningSpanStoreImpl.getNoopRunningSpanStoreImpl().getClass()); + } + + @Test + public void implementationOfSampledSpanStore() { + assertThat(exportComponentWithInProcess.getSampledSpanStore()) + .isInstanceOf(InProcessSampledSpanStoreImpl.class); + assertThat(exportComponentWithoutInProcess.getSampledSpanStore()) + .isInstanceOf(SampledSpanStoreImpl.getNoopSampledSpanStoreImpl().getClass()); + } +} diff --git a/impl_core/src/test/java/io/opencensus/implcore/trace/export/InProcessRunningSpanStoreImplTest.java b/impl_core/src/test/java/io/opencensus/implcore/trace/export/InProcessRunningSpanStoreImplTest.java new file mode 100644 index 00000000..68ce1c18 --- /dev/null +++ b/impl_core/src/test/java/io/opencensus/implcore/trace/export/InProcessRunningSpanStoreImplTest.java @@ -0,0 +1,168 @@ +/* + * Copyright 2017, OpenCensus Authors + * + * 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 io.opencensus.implcore.trace.export; + +import static com.google.common.truth.Truth.assertThat; + +import io.opencensus.common.Duration; +import io.opencensus.implcore.common.MillisClock; +import io.opencensus.implcore.internal.SimpleEventQueue; +import io.opencensus.implcore.trace.RecordEventsSpanImpl; +import io.opencensus.implcore.trace.RecordEventsSpanImpl.StartEndHandler; +import io.opencensus.implcore.trace.StartEndHandlerImpl; +import io.opencensus.trace.SpanContext; +import io.opencensus.trace.SpanId; +import io.opencensus.trace.TraceId; +import io.opencensus.trace.TraceOptions; +import io.opencensus.trace.config.TraceParams; +import io.opencensus.trace.export.RunningSpanStore.Filter; +import java.util.Random; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Unit tests for {@link InProcessRunningSpanStoreImpl}. */ +@RunWith(JUnit4.class) +public class InProcessRunningSpanStoreImplTest { + + private static final String SPAN_NAME_1 = "MySpanName/1"; + private static final String SPAN_NAME_2 = "MySpanName/2"; + private final Random random = new Random(1234); + private final SpanExporterImpl sampledSpansServiceExporter = + SpanExporterImpl.create(4, Duration.create(1, 0)); + private final InProcessRunningSpanStoreImpl activeSpansExporter = + new InProcessRunningSpanStoreImpl(); + private final StartEndHandler startEndHandler = + new StartEndHandlerImpl( + sampledSpansServiceExporter, activeSpansExporter, null, new SimpleEventQueue()); + + private RecordEventsSpanImpl createSpan(String spanName) { + final SpanContext spanContext = + SpanContext.create( + TraceId.generateRandomId(random), + SpanId.generateRandomId(random), + TraceOptions.DEFAULT); + return RecordEventsSpanImpl.startSpan( + spanContext, + spanName, + null, + SpanId.generateRandomId(random), + false, + TraceParams.DEFAULT, + startEndHandler, + null, + MillisClock.getInstance()); + } + + @Test + public void getSummary_SpansWithDifferentNames() { + final RecordEventsSpanImpl span1 = createSpan(SPAN_NAME_1); + final RecordEventsSpanImpl span2 = createSpan(SPAN_NAME_2); + assertThat(activeSpansExporter.getSummary().getPerSpanNameSummary().size()).isEqualTo(2); + assertThat( + activeSpansExporter + .getSummary() + .getPerSpanNameSummary() + .get(SPAN_NAME_1) + .getNumRunningSpans()) + .isEqualTo(1); + assertThat( + activeSpansExporter + .getSummary() + .getPerSpanNameSummary() + .get(SPAN_NAME_2) + .getNumRunningSpans()) + .isEqualTo(1); + span1.end(); + assertThat(activeSpansExporter.getSummary().getPerSpanNameSummary().size()).isEqualTo(1); + assertThat(activeSpansExporter.getSummary().getPerSpanNameSummary().get(SPAN_NAME_1)).isNull(); + assertThat( + activeSpansExporter + .getSummary() + .getPerSpanNameSummary() + .get(SPAN_NAME_2) + .getNumRunningSpans()) + .isEqualTo(1); + span2.end(); + assertThat(activeSpansExporter.getSummary().getPerSpanNameSummary().size()).isEqualTo(0); + } + + @Test + public void getSummary_SpansWithSameName() { + final RecordEventsSpanImpl span1 = createSpan(SPAN_NAME_1); + final RecordEventsSpanImpl span2 = createSpan(SPAN_NAME_1); + final RecordEventsSpanImpl span3 = createSpan(SPAN_NAME_1); + assertThat(activeSpansExporter.getSummary().getPerSpanNameSummary().size()).isEqualTo(1); + assertThat( + activeSpansExporter + .getSummary() + .getPerSpanNameSummary() + .get(SPAN_NAME_1) + .getNumRunningSpans()) + .isEqualTo(3); + span1.end(); + assertThat(activeSpansExporter.getSummary().getPerSpanNameSummary().size()).isEqualTo(1); + assertThat( + activeSpansExporter + .getSummary() + .getPerSpanNameSummary() + .get(SPAN_NAME_1) + .getNumRunningSpans()) + .isEqualTo(2); + span2.end(); + assertThat(activeSpansExporter.getSummary().getPerSpanNameSummary().size()).isEqualTo(1); + assertThat( + activeSpansExporter + .getSummary() + .getPerSpanNameSummary() + .get(SPAN_NAME_1) + .getNumRunningSpans()) + .isEqualTo(1); + span3.end(); + assertThat(activeSpansExporter.getSummary().getPerSpanNameSummary().size()).isEqualTo(0); + } + + @Test + public void getActiveSpans_SpansWithDifferentNames() { + RecordEventsSpanImpl span1 = createSpan(SPAN_NAME_1); + RecordEventsSpanImpl span2 = createSpan(SPAN_NAME_2); + assertThat(activeSpansExporter.getRunningSpans(Filter.create(SPAN_NAME_1, 0))) + .containsExactly(span1.toSpanData()); + assertThat(activeSpansExporter.getRunningSpans(Filter.create(SPAN_NAME_1, 2))) + .containsExactly(span1.toSpanData()); + assertThat(activeSpansExporter.getRunningSpans(Filter.create(SPAN_NAME_2, 0))) + .containsExactly(span2.toSpanData()); + span1.end(); + span2.end(); + } + + @Test + public void getActiveSpans_SpansWithSameName() { + RecordEventsSpanImpl span1 = createSpan(SPAN_NAME_1); + RecordEventsSpanImpl span2 = createSpan(SPAN_NAME_1); + RecordEventsSpanImpl span3 = createSpan(SPAN_NAME_1); + assertThat(activeSpansExporter.getRunningSpans(Filter.create(SPAN_NAME_1, 0))) + .containsExactly(span1.toSpanData(), span2.toSpanData(), span3.toSpanData()); + assertThat(activeSpansExporter.getRunningSpans(Filter.create(SPAN_NAME_1, 2)).size()) + .isEqualTo(2); + assertThat(activeSpansExporter.getRunningSpans(Filter.create(SPAN_NAME_1, 2))) + .containsAnyOf(span1.toSpanData(), span2.toSpanData(), span3.toSpanData()); + span1.end(); + span2.end(); + span3.end(); + } +} diff --git a/impl_core/src/test/java/io/opencensus/implcore/trace/export/InProcessSampledSpanStoreImplTest.java b/impl_core/src/test/java/io/opencensus/implcore/trace/export/InProcessSampledSpanStoreImplTest.java new file mode 100644 index 00000000..7d8b434e --- /dev/null +++ b/impl_core/src/test/java/io/opencensus/implcore/trace/export/InProcessSampledSpanStoreImplTest.java @@ -0,0 +1,368 @@ +/* + * Copyright 2017, OpenCensus Authors + * + * 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 io.opencensus.implcore.trace.export; + +import static com.google.common.truth.Truth.assertThat; + +import io.opencensus.common.Duration; +import io.opencensus.common.Timestamp; +import io.opencensus.implcore.internal.SimpleEventQueue; +import io.opencensus.implcore.trace.RecordEventsSpanImpl; +import io.opencensus.implcore.trace.RecordEventsSpanImpl.StartEndHandler; +import io.opencensus.testing.common.TestClock; +import io.opencensus.trace.EndSpanOptions; +import io.opencensus.trace.Span; +import io.opencensus.trace.SpanContext; +import io.opencensus.trace.SpanId; +import io.opencensus.trace.Status; +import io.opencensus.trace.Status.CanonicalCode; +import io.opencensus.trace.TraceId; +import io.opencensus.trace.TraceOptions; +import io.opencensus.trace.config.TraceParams; +import io.opencensus.trace.export.SampledSpanStore.ErrorFilter; +import io.opencensus.trace.export.SampledSpanStore.LatencyBucketBoundaries; +import io.opencensus.trace.export.SampledSpanStore.LatencyFilter; +import io.opencensus.trace.export.SampledSpanStore.PerSpanNameSummary; +import io.opencensus.trace.export.SpanData; +import java.util.Collection; +import java.util.Collections; +import java.util.Map; +import java.util.Random; +import java.util.concurrent.TimeUnit; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Unit tests for {@link InProcessSampledSpanStoreImpl}. */ +@RunWith(JUnit4.class) +public class InProcessSampledSpanStoreImplTest { + private static final String REGISTERED_SPAN_NAME = "MySpanName/1"; + private static final String NOT_REGISTERED_SPAN_NAME = "MySpanName/2"; + private static final long NUM_NANOS_PER_SECOND = TimeUnit.SECONDS.toNanos(1); + private final Random random = new Random(1234); + private final SpanContext sampledSpanContext = + SpanContext.create( + TraceId.generateRandomId(random), + SpanId.generateRandomId(random), + TraceOptions.builder().setIsSampled(true).build()); + private final SpanContext notSampledSpanContext = + SpanContext.create( + TraceId.generateRandomId(random), SpanId.generateRandomId(random), TraceOptions.DEFAULT); + private final SpanId parentSpanId = SpanId.generateRandomId(random); + private final TestClock testClock = TestClock.create(Timestamp.create(12345, 54321)); + private final InProcessSampledSpanStoreImpl sampleStore = + new InProcessSampledSpanStoreImpl(new SimpleEventQueue()); + private final StartEndHandler startEndHandler = + new StartEndHandler() { + @Override + public void onStart(RecordEventsSpanImpl span) { + // Do nothing. + } + + @Override + public void onEnd(RecordEventsSpanImpl span) { + sampleStore.considerForSampling(span); + } + }; + + @Before + public void setUp() { + sampleStore.registerSpanNamesForCollection(Collections.singletonList(REGISTERED_SPAN_NAME)); + } + + private RecordEventsSpanImpl createSampledSpan(String spanName) { + return RecordEventsSpanImpl.startSpan( + sampledSpanContext, + spanName, + null, + parentSpanId, + false, + TraceParams.DEFAULT, + startEndHandler, + null, + testClock); + } + + private RecordEventsSpanImpl createNotSampledSpan(String spanName) { + return RecordEventsSpanImpl.startSpan( + notSampledSpanContext, + spanName, + null, + parentSpanId, + false, + TraceParams.DEFAULT, + startEndHandler, + null, + testClock); + } + + private void addSpanNameToAllLatencyBuckets(String spanName) { + for (LatencyBucketBoundaries boundaries : LatencyBucketBoundaries.values()) { + Span sampledSpan = createSampledSpan(spanName); + Span notSampledSpan = createNotSampledSpan(spanName); + if (boundaries.getLatencyLowerNs() < NUM_NANOS_PER_SECOND) { + testClock.advanceTime(Duration.create(0, (int) boundaries.getLatencyLowerNs())); + } else { + testClock.advanceTime( + Duration.create( + boundaries.getLatencyLowerNs() / NUM_NANOS_PER_SECOND, + (int) (boundaries.getLatencyLowerNs() % NUM_NANOS_PER_SECOND))); + } + sampledSpan.end(); + notSampledSpan.end(); + } + } + + private void addSpanNameToAllErrorBuckets(String spanName) { + for (CanonicalCode code : CanonicalCode.values()) { + if (code != CanonicalCode.OK) { + Span sampledSpan = createSampledSpan(spanName); + Span notSampledSpan = createNotSampledSpan(spanName); + testClock.advanceTime(Duration.create(0, 1000)); + sampledSpan.end(EndSpanOptions.builder().setStatus(code.toStatus()).build()); + notSampledSpan.end(EndSpanOptions.builder().setStatus(code.toStatus()).build()); + } + } + } + + @Test + public void addSpansWithRegisteredNamesInAllLatencyBuckets() { + addSpanNameToAllLatencyBuckets(REGISTERED_SPAN_NAME); + Map<String, PerSpanNameSummary> perSpanNameSummary = + sampleStore.getSummary().getPerSpanNameSummary(); + assertThat(perSpanNameSummary.size()).isEqualTo(1); + Map<LatencyBucketBoundaries, Integer> latencyBucketsSummaries = + perSpanNameSummary.get(REGISTERED_SPAN_NAME).getNumbersOfLatencySampledSpans(); + assertThat(latencyBucketsSummaries.size()).isEqualTo(LatencyBucketBoundaries.values().length); + for (Map.Entry<LatencyBucketBoundaries, Integer> it : latencyBucketsSummaries.entrySet()) { + assertThat(it.getValue()).isEqualTo(2); + } + } + + @Test + public void addSpansWithoutRegisteredNamesInAllLatencyBuckets() { + addSpanNameToAllLatencyBuckets(NOT_REGISTERED_SPAN_NAME); + Map<String, PerSpanNameSummary> perSpanNameSummary = + sampleStore.getSummary().getPerSpanNameSummary(); + assertThat(perSpanNameSummary.size()).isEqualTo(1); + assertThat(perSpanNameSummary.containsKey(NOT_REGISTERED_SPAN_NAME)).isFalse(); + } + + @Test + public void registerUnregisterAndListSpanNames() { + assertThat(sampleStore.getRegisteredSpanNamesForCollection()) + .containsExactly(REGISTERED_SPAN_NAME); + sampleStore.registerSpanNamesForCollection(Collections.singletonList(NOT_REGISTERED_SPAN_NAME)); + assertThat(sampleStore.getRegisteredSpanNamesForCollection()) + .containsExactly(REGISTERED_SPAN_NAME, NOT_REGISTERED_SPAN_NAME); + sampleStore.unregisterSpanNamesForCollection( + Collections.singletonList(NOT_REGISTERED_SPAN_NAME)); + assertThat(sampleStore.getRegisteredSpanNamesForCollection()) + .containsExactly(REGISTERED_SPAN_NAME); + } + + @Test + public void registerSpanNamesViaSpanBuilderOption() { + assertThat(sampleStore.getRegisteredSpanNamesForCollection()) + .containsExactly(REGISTERED_SPAN_NAME); + createSampledSpan(NOT_REGISTERED_SPAN_NAME) + .end(EndSpanOptions.builder().setSampleToLocalSpanStore(true).build()); + assertThat(sampleStore.getRegisteredSpanNamesForCollection()) + .containsExactly(REGISTERED_SPAN_NAME, NOT_REGISTERED_SPAN_NAME); + } + + @Test + public void addSpansWithRegisteredNamesInAllErrorBuckets() { + addSpanNameToAllErrorBuckets(REGISTERED_SPAN_NAME); + Map<String, PerSpanNameSummary> perSpanNameSummary = + sampleStore.getSummary().getPerSpanNameSummary(); + assertThat(perSpanNameSummary.size()).isEqualTo(1); + Map<CanonicalCode, Integer> errorBucketsSummaries = + perSpanNameSummary.get(REGISTERED_SPAN_NAME).getNumbersOfErrorSampledSpans(); + assertThat(errorBucketsSummaries.size()).isEqualTo(CanonicalCode.values().length - 1); + for (Map.Entry<CanonicalCode, Integer> it : errorBucketsSummaries.entrySet()) { + assertThat(it.getValue()).isEqualTo(2); + } + } + + @Test + public void addSpansWithoutRegisteredNamesInAllErrorBuckets() { + addSpanNameToAllErrorBuckets(NOT_REGISTERED_SPAN_NAME); + Map<String, PerSpanNameSummary> perSpanNameSummary = + sampleStore.getSummary().getPerSpanNameSummary(); + assertThat(perSpanNameSummary.size()).isEqualTo(1); + assertThat(perSpanNameSummary.containsKey(NOT_REGISTERED_SPAN_NAME)).isFalse(); + } + + @Test + public void getErrorSampledSpans() { + RecordEventsSpanImpl span = createSampledSpan(REGISTERED_SPAN_NAME); + testClock.advanceTime(Duration.create(0, 1000)); + span.end(EndSpanOptions.builder().setStatus(Status.CANCELLED).build()); + Collection<SpanData> samples = + sampleStore.getErrorSampledSpans( + ErrorFilter.create(REGISTERED_SPAN_NAME, CanonicalCode.CANCELLED, 0)); + assertThat(samples.size()).isEqualTo(1); + assertThat(samples.contains(span.toSpanData())).isTrue(); + } + + @Test + public void getErrorSampledSpans_MaxSpansToReturn() { + RecordEventsSpanImpl span1 = createSampledSpan(REGISTERED_SPAN_NAME); + testClock.advanceTime(Duration.create(0, 1000)); + span1.end(EndSpanOptions.builder().setStatus(Status.CANCELLED).build()); + // Advance time to allow other spans to be sampled. + testClock.advanceTime(Duration.create(5, 0)); + RecordEventsSpanImpl span2 = createSampledSpan(REGISTERED_SPAN_NAME); + testClock.advanceTime(Duration.create(0, 1000)); + span2.end(EndSpanOptions.builder().setStatus(Status.CANCELLED).build()); + Collection<SpanData> samples = + sampleStore.getErrorSampledSpans( + ErrorFilter.create(REGISTERED_SPAN_NAME, CanonicalCode.CANCELLED, 1)); + assertThat(samples.size()).isEqualTo(1); + // No order guaranteed so one of the spans should be in the list. + assertThat(samples).containsAnyOf(span1.toSpanData(), span2.toSpanData()); + } + + @Test + public void getErrorSampledSpans_NullCode() { + RecordEventsSpanImpl span1 = createSampledSpan(REGISTERED_SPAN_NAME); + testClock.advanceTime(Duration.create(0, 1000)); + span1.end(EndSpanOptions.builder().setStatus(Status.CANCELLED).build()); + RecordEventsSpanImpl span2 = createSampledSpan(REGISTERED_SPAN_NAME); + testClock.advanceTime(Duration.create(0, 1000)); + span2.end(EndSpanOptions.builder().setStatus(Status.UNKNOWN).build()); + Collection<SpanData> samples = + sampleStore.getErrorSampledSpans(ErrorFilter.create(REGISTERED_SPAN_NAME, null, 0)); + assertThat(samples.size()).isEqualTo(2); + assertThat(samples).containsExactly(span1.toSpanData(), span2.toSpanData()); + } + + @Test + public void getErrorSampledSpans_NullCode_MaxSpansToReturn() { + RecordEventsSpanImpl span1 = createSampledSpan(REGISTERED_SPAN_NAME); + testClock.advanceTime(Duration.create(0, 1000)); + span1.end(EndSpanOptions.builder().setStatus(Status.CANCELLED).build()); + RecordEventsSpanImpl span2 = createSampledSpan(REGISTERED_SPAN_NAME); + testClock.advanceTime(Duration.create(0, 1000)); + span2.end(EndSpanOptions.builder().setStatus(Status.UNKNOWN).build()); + Collection<SpanData> samples = + sampleStore.getErrorSampledSpans(ErrorFilter.create(REGISTERED_SPAN_NAME, null, 1)); + assertThat(samples.size()).isEqualTo(1); + assertThat(samples).containsAnyOf(span1.toSpanData(), span2.toSpanData()); + } + + @Test + public void getLatencySampledSpans() { + RecordEventsSpanImpl span = createSampledSpan(REGISTERED_SPAN_NAME); + testClock.advanceTime(Duration.create(0, (int) TimeUnit.MICROSECONDS.toNanos(20))); + span.end(); + Collection<SpanData> samples = + sampleStore.getLatencySampledSpans( + LatencyFilter.create( + REGISTERED_SPAN_NAME, + TimeUnit.MICROSECONDS.toNanos(15), + TimeUnit.MICROSECONDS.toNanos(25), + 0)); + assertThat(samples.size()).isEqualTo(1); + assertThat(samples.contains(span.toSpanData())).isTrue(); + } + + @Test + public void getLatencySampledSpans_ExclusiveUpperBound() { + RecordEventsSpanImpl span = createSampledSpan(REGISTERED_SPAN_NAME); + testClock.advanceTime(Duration.create(0, (int) TimeUnit.MICROSECONDS.toNanos(20))); + span.end(); + Collection<SpanData> samples = + sampleStore.getLatencySampledSpans( + LatencyFilter.create( + REGISTERED_SPAN_NAME, + TimeUnit.MICROSECONDS.toNanos(15), + TimeUnit.MICROSECONDS.toNanos(20), + 0)); + assertThat(samples.size()).isEqualTo(0); + } + + @Test + public void getLatencySampledSpans_InclusiveLowerBound() { + RecordEventsSpanImpl span = createSampledSpan(REGISTERED_SPAN_NAME); + testClock.advanceTime(Duration.create(0, (int) TimeUnit.MICROSECONDS.toNanos(20))); + span.end(); + Collection<SpanData> samples = + sampleStore.getLatencySampledSpans( + LatencyFilter.create( + REGISTERED_SPAN_NAME, + TimeUnit.MICROSECONDS.toNanos(20), + TimeUnit.MICROSECONDS.toNanos(25), + 0)); + assertThat(samples.size()).isEqualTo(1); + assertThat(samples.contains(span.toSpanData())).isTrue(); + } + + @Test + public void getLatencySampledSpans_QueryBetweenMultipleBuckets() { + RecordEventsSpanImpl span1 = createSampledSpan(REGISTERED_SPAN_NAME); + testClock.advanceTime(Duration.create(0, (int) TimeUnit.MICROSECONDS.toNanos(20))); + span1.end(); + // Advance time to allow other spans to be sampled. + testClock.advanceTime(Duration.create(5, 0)); + RecordEventsSpanImpl span2 = createSampledSpan(REGISTERED_SPAN_NAME); + testClock.advanceTime(Duration.create(0, (int) TimeUnit.MICROSECONDS.toNanos(200))); + span2.end(); + Collection<SpanData> samples = + sampleStore.getLatencySampledSpans( + LatencyFilter.create( + REGISTERED_SPAN_NAME, + TimeUnit.MICROSECONDS.toNanos(15), + TimeUnit.MICROSECONDS.toNanos(250), + 0)); + assertThat(samples).containsExactly(span1.toSpanData(), span2.toSpanData()); + } + + @Test + public void getLatencySampledSpans_MaxSpansToReturn() { + RecordEventsSpanImpl span1 = createSampledSpan(REGISTERED_SPAN_NAME); + testClock.advanceTime(Duration.create(0, (int) TimeUnit.MICROSECONDS.toNanos(20))); + span1.end(); + // Advance time to allow other spans to be sampled. + testClock.advanceTime(Duration.create(5, 0)); + RecordEventsSpanImpl span2 = createSampledSpan(REGISTERED_SPAN_NAME); + testClock.advanceTime(Duration.create(0, (int) TimeUnit.MICROSECONDS.toNanos(200))); + span2.end(); + Collection<SpanData> samples = + sampleStore.getLatencySampledSpans( + LatencyFilter.create( + REGISTERED_SPAN_NAME, + TimeUnit.MICROSECONDS.toNanos(15), + TimeUnit.MICROSECONDS.toNanos(250), + 1)); + assertThat(samples.size()).isEqualTo(1); + assertThat(samples.contains(span1.toSpanData())).isTrue(); + } + + @Test + public void ignoreNegativeSpanLatency() { + RecordEventsSpanImpl span = createSampledSpan(REGISTERED_SPAN_NAME); + testClock.advanceTime(Duration.create(0, (int) TimeUnit.MICROSECONDS.toNanos(-20))); + span.end(); + Collection<SpanData> samples = + sampleStore.getLatencySampledSpans( + LatencyFilter.create(REGISTERED_SPAN_NAME, 0, Long.MAX_VALUE, 0)); + assertThat(samples.size()).isEqualTo(0); + } +} diff --git a/impl_core/src/test/java/io/opencensus/implcore/trace/export/NoopRunningSpanStoreImplTest.java b/impl_core/src/test/java/io/opencensus/implcore/trace/export/NoopRunningSpanStoreImplTest.java new file mode 100644 index 00000000..96669df7 --- /dev/null +++ b/impl_core/src/test/java/io/opencensus/implcore/trace/export/NoopRunningSpanStoreImplTest.java @@ -0,0 +1,95 @@ +/* + * Copyright 2018, OpenCensus Authors + * + * 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 io.opencensus.implcore.trace.export; + +import static com.google.common.truth.Truth.assertThat; + +import io.opencensus.common.Timestamp; +import io.opencensus.implcore.internal.EventQueue; +import io.opencensus.implcore.internal.SimpleEventQueue; +import io.opencensus.implcore.internal.TimestampConverter; +import io.opencensus.implcore.trace.RecordEventsSpanImpl; +import io.opencensus.implcore.trace.RecordEventsSpanImpl.StartEndHandler; +import io.opencensus.testing.common.TestClock; +import io.opencensus.trace.SpanContext; +import io.opencensus.trace.SpanId; +import io.opencensus.trace.TraceId; +import io.opencensus.trace.TraceOptions; +import io.opencensus.trace.config.TraceParams; +import io.opencensus.trace.export.RunningSpanStore.Filter; +import java.util.Random; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +/** Unit tests for {@link RunningSpanStoreImpl.NoopRunningSpanStoreImpl}. */ +@RunWith(JUnit4.class) +public class NoopRunningSpanStoreImplTest { + + private static final String SPAN_NAME = "MySpanName"; + + private final Timestamp timestamp = Timestamp.create(1234, 5678); + private final Random random = new Random(1234); + private final SpanContext spanContext = + SpanContext.create( + TraceId.generateRandomId(random), SpanId.generateRandomId(random), TraceOptions.DEFAULT); + private final TestClock testClock = TestClock.create(timestamp); + private final TimestampConverter timestampConverter = TimestampConverter.now(testClock); + @Mock private StartEndHandler startEndHandler; + private RecordEventsSpanImpl recordEventsSpanImpl; + // maxSpansToReturn=0 means all + private final Filter filter = Filter.create(SPAN_NAME, 0 /* maxSpansToReturn */); + private final EventQueue eventQueue = new SimpleEventQueue(); + private final RunningSpanStoreImpl runningSpanStoreImpl = + ExportComponentImpl.createWithoutInProcessStores(eventQueue).getRunningSpanStore(); + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + recordEventsSpanImpl = + RecordEventsSpanImpl.startSpan( + spanContext, + SPAN_NAME, + null, + null, + false, + TraceParams.DEFAULT, + startEndHandler, + timestampConverter, + testClock); + } + + private void getMethodsShouldReturnEmpty() { + // get methods should always return empty collections. + assertThat(runningSpanStoreImpl.getSummary().getPerSpanNameSummary()).isEmpty(); + assertThat(runningSpanStoreImpl.getRunningSpans(filter)).isEmpty(); + } + + @Test + public void noopImplementation() { + getMethodsShouldReturnEmpty(); + // onStart() does not affect the result. + runningSpanStoreImpl.onStart(recordEventsSpanImpl); + getMethodsShouldReturnEmpty(); + // onEnd() does not affect the result. + runningSpanStoreImpl.onEnd(recordEventsSpanImpl); + getMethodsShouldReturnEmpty(); + } +} diff --git a/impl_core/src/test/java/io/opencensus/implcore/trace/export/NoopSampledSpanStoreImplTest.java b/impl_core/src/test/java/io/opencensus/implcore/trace/export/NoopSampledSpanStoreImplTest.java new file mode 100644 index 00000000..b9fbd432 --- /dev/null +++ b/impl_core/src/test/java/io/opencensus/implcore/trace/export/NoopSampledSpanStoreImplTest.java @@ -0,0 +1,118 @@ +/* + * Copyright 2018, OpenCensus Authors + * + * 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 io.opencensus.implcore.trace.export; + +import static com.google.common.truth.Truth.assertThat; + +import io.opencensus.common.Timestamp; +import io.opencensus.implcore.internal.EventQueue; +import io.opencensus.implcore.internal.SimpleEventQueue; +import io.opencensus.implcore.internal.TimestampConverter; +import io.opencensus.implcore.trace.RecordEventsSpanImpl; +import io.opencensus.implcore.trace.RecordEventsSpanImpl.StartEndHandler; +import io.opencensus.testing.common.TestClock; +import io.opencensus.trace.SpanContext; +import io.opencensus.trace.SpanId; +import io.opencensus.trace.TraceId; +import io.opencensus.trace.TraceOptions; +import io.opencensus.trace.config.TraceParams; +import io.opencensus.trace.export.SampledSpanStore.ErrorFilter; +import io.opencensus.trace.export.SampledSpanStore.LatencyFilter; +import java.util.Collection; +import java.util.Collections; +import java.util.Random; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +/** Unit tests for {@link SampledSpanStoreImpl.NoopSampledSpanStoreImpl}. */ +@RunWith(JUnit4.class) +public final class NoopSampledSpanStoreImplTest { + + private static final String SPAN_NAME = "MySpanName"; + private static final Collection<String> NAMES_FOR_COLLECTION = + Collections.<String>singletonList(SPAN_NAME); + + private final Timestamp timestamp = Timestamp.create(1234, 5678); + private final Random random = new Random(1234); + private final SpanContext spanContext = + SpanContext.create( + TraceId.generateRandomId(random), SpanId.generateRandomId(random), TraceOptions.DEFAULT); + private final TestClock testClock = TestClock.create(timestamp); + private final TimestampConverter timestampConverter = TimestampConverter.now(testClock); + @Mock private StartEndHandler startEndHandler; + private RecordEventsSpanImpl recordEventsSpanImpl; + // maxSpansToReturn=0 means all + private final ErrorFilter errorFilter = + ErrorFilter.create(SPAN_NAME, null /* canonicalCode */, 0 /* maxSpansToReturn */); + private final LatencyFilter latencyFilter = + LatencyFilter.create( + SPAN_NAME, + 0 /* latencyLowerNs */, + Long.MAX_VALUE /* latencyUpperNs */, + 0 /* maxSpansToReturn */); + private final EventQueue eventQueue = new SimpleEventQueue(); + private final SampledSpanStoreImpl sampledSpanStoreImpl = + ExportComponentImpl.createWithoutInProcessStores(eventQueue).getSampledSpanStore(); + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + } + + private void getMethodsShouldReturnEmpty() { + // get methods always return empty collections. + assertThat(sampledSpanStoreImpl.getSummary().getPerSpanNameSummary()).isEmpty(); + assertThat(sampledSpanStoreImpl.getRegisteredSpanNamesForCollection()).isEmpty(); + assertThat(sampledSpanStoreImpl.getErrorSampledSpans(errorFilter)).isEmpty(); + assertThat(sampledSpanStoreImpl.getLatencySampledSpans(latencyFilter)).isEmpty(); + } + + @Test + public void noopImplementation() { + // None of the get methods should yield non-empty result. + getMethodsShouldReturnEmpty(); + + // registerSpanNamesForCollection() should do nothing and do not affect the result. + sampledSpanStoreImpl.registerSpanNamesForCollection(NAMES_FOR_COLLECTION); + getMethodsShouldReturnEmpty(); + + // considerForSampling() should do nothing and do not affect the result. + // It should be called after registerSpanNamesForCollection. + recordEventsSpanImpl = + RecordEventsSpanImpl.startSpan( + spanContext, + SPAN_NAME, + null, + null, + false, + TraceParams.DEFAULT, + startEndHandler, + timestampConverter, + testClock); + recordEventsSpanImpl.end(); + sampledSpanStoreImpl.considerForSampling(recordEventsSpanImpl); + getMethodsShouldReturnEmpty(); + + // unregisterSpanNamesForCollection() should do nothing and do not affect the result. + sampledSpanStoreImpl.unregisterSpanNamesForCollection(NAMES_FOR_COLLECTION); + getMethodsShouldReturnEmpty(); + } +} diff --git a/impl_core/src/test/java/io/opencensus/implcore/trace/export/SpanExporterImplTest.java b/impl_core/src/test/java/io/opencensus/implcore/trace/export/SpanExporterImplTest.java new file mode 100644 index 00000000..f8f1d917 --- /dev/null +++ b/impl_core/src/test/java/io/opencensus/implcore/trace/export/SpanExporterImplTest.java @@ -0,0 +1,233 @@ +/* + * Copyright 2017, OpenCensus Authors + * + * 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 io.opencensus.implcore.trace.export; + +import static com.google.common.truth.Truth.assertThat; +import static org.mockito.Matchers.anyListOf; +import static org.mockito.Mockito.doThrow; + +import io.opencensus.common.Duration; +import io.opencensus.implcore.common.MillisClock; +import io.opencensus.implcore.internal.SimpleEventQueue; +import io.opencensus.implcore.trace.RecordEventsSpanImpl; +import io.opencensus.implcore.trace.RecordEventsSpanImpl.StartEndHandler; +import io.opencensus.implcore.trace.StartEndHandlerImpl; +import io.opencensus.testing.export.TestHandler; +import io.opencensus.trace.SpanContext; +import io.opencensus.trace.SpanId; +import io.opencensus.trace.TraceId; +import io.opencensus.trace.TraceOptions; +import io.opencensus.trace.config.TraceParams; +import io.opencensus.trace.export.SpanData; +import io.opencensus.trace.export.SpanExporter.Handler; +import java.util.List; +import java.util.Random; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +/** Unit tests for {@link SpanExporterImpl}. */ +@RunWith(JUnit4.class) +public class SpanExporterImplTest { + private static final String SPAN_NAME_1 = "MySpanName/1"; + private static final String SPAN_NAME_2 = "MySpanName/2"; + private final Random random = new Random(1234); + private final SpanContext sampledSpanContext = + SpanContext.create( + TraceId.generateRandomId(random), + SpanId.generateRandomId(random), + TraceOptions.builder().setIsSampled(true).build()); + private final SpanContext notSampledSpanContext = + SpanContext.create( + TraceId.generateRandomId(random), SpanId.generateRandomId(random), TraceOptions.DEFAULT); + private final RunningSpanStoreImpl runningSpanStore = new InProcessRunningSpanStoreImpl(); + private final TestHandler serviceHandler = new TestHandler(); + @Mock private Handler mockServiceHandler; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + } + + private RecordEventsSpanImpl createSampledEndedSpan( + StartEndHandler startEndHandler, String spanName) { + RecordEventsSpanImpl span = + RecordEventsSpanImpl.startSpan( + sampledSpanContext, + spanName, + null, + null, + false, + TraceParams.DEFAULT, + startEndHandler, + null, + MillisClock.getInstance()); + span.end(); + return span; + } + + private RecordEventsSpanImpl createNotSampledEndedSpan( + StartEndHandler startEndHandler, String spanName) { + RecordEventsSpanImpl span = + RecordEventsSpanImpl.startSpan( + notSampledSpanContext, + spanName, + null, + null, + false, + TraceParams.DEFAULT, + startEndHandler, + null, + MillisClock.getInstance()); + span.end(); + return span; + } + + @Test + public void exportDifferentSampledSpans() { + SpanExporterImpl spanExporter = SpanExporterImpl.create(4, Duration.create(1, 0)); + StartEndHandler startEndHandler = + new StartEndHandlerImpl(spanExporter, runningSpanStore, null, new SimpleEventQueue()); + + spanExporter.registerHandler("test.service", serviceHandler); + + RecordEventsSpanImpl span1 = createSampledEndedSpan(startEndHandler, SPAN_NAME_1); + RecordEventsSpanImpl span2 = createSampledEndedSpan(startEndHandler, SPAN_NAME_2); + List<SpanData> exported = serviceHandler.waitForExport(2); + assertThat(exported).containsExactly(span1.toSpanData(), span2.toSpanData()); + } + + @Test + public void exportMoreSpansThanTheBufferSize() { + SpanExporterImpl spanExporter = SpanExporterImpl.create(4, Duration.create(1, 0)); + StartEndHandler startEndHandler = + new StartEndHandlerImpl(spanExporter, runningSpanStore, null, new SimpleEventQueue()); + + spanExporter.registerHandler("test.service", serviceHandler); + + RecordEventsSpanImpl span1 = createSampledEndedSpan(startEndHandler, SPAN_NAME_1); + RecordEventsSpanImpl span2 = createSampledEndedSpan(startEndHandler, SPAN_NAME_1); + RecordEventsSpanImpl span3 = createSampledEndedSpan(startEndHandler, SPAN_NAME_1); + RecordEventsSpanImpl span4 = createSampledEndedSpan(startEndHandler, SPAN_NAME_1); + RecordEventsSpanImpl span5 = createSampledEndedSpan(startEndHandler, SPAN_NAME_1); + RecordEventsSpanImpl span6 = createSampledEndedSpan(startEndHandler, SPAN_NAME_1); + List<SpanData> exported = serviceHandler.waitForExport(6); + assertThat(exported) + .containsExactly( + span1.toSpanData(), + span2.toSpanData(), + span3.toSpanData(), + span4.toSpanData(), + span5.toSpanData(), + span6.toSpanData()); + } + + @Test + public void interruptWorkerThreadStops() throws InterruptedException { + SpanExporterImpl spanExporter = SpanExporterImpl.create(4, Duration.create(1, 0)); + + spanExporter.registerHandler("test.service", serviceHandler); + + Thread serviceExporterThread = spanExporter.getServiceExporterThread(); + serviceExporterThread.interrupt(); + // Test that the worker thread will stop. + serviceExporterThread.join(); + } + + @Test + public void serviceHandlerThrowsException() { + doThrow(new IllegalArgumentException("No export for you.")) + .when(mockServiceHandler) + .export(anyListOf(SpanData.class)); + + SpanExporterImpl spanExporter = SpanExporterImpl.create(4, Duration.create(1, 0)); + StartEndHandler startEndHandler = + new StartEndHandlerImpl(spanExporter, runningSpanStore, null, new SimpleEventQueue()); + + spanExporter.registerHandler("test.service", serviceHandler); + + spanExporter.registerHandler("mock.service", mockServiceHandler); + RecordEventsSpanImpl span1 = createSampledEndedSpan(startEndHandler, SPAN_NAME_1); + List<SpanData> exported = serviceHandler.waitForExport(1); + assertThat(exported).containsExactly(span1.toSpanData()); + // Continue to export after the exception was received. + RecordEventsSpanImpl span2 = createSampledEndedSpan(startEndHandler, SPAN_NAME_1); + exported = serviceHandler.waitForExport(1); + assertThat(exported).containsExactly(span2.toSpanData()); + } + + @Test + public void exportSpansToMultipleServices() { + SpanExporterImpl spanExporter = SpanExporterImpl.create(4, Duration.create(1, 0)); + StartEndHandler startEndHandler = + new StartEndHandlerImpl(spanExporter, runningSpanStore, null, new SimpleEventQueue()); + + spanExporter.registerHandler("test.service", serviceHandler); + + TestHandler serviceHandler2 = new TestHandler(); + spanExporter.registerHandler("test.service2", serviceHandler2); + RecordEventsSpanImpl span1 = createSampledEndedSpan(startEndHandler, SPAN_NAME_1); + RecordEventsSpanImpl span2 = createSampledEndedSpan(startEndHandler, SPAN_NAME_2); + List<SpanData> exported1 = serviceHandler.waitForExport(2); + List<SpanData> exported2 = serviceHandler2.waitForExport(2); + assertThat(exported1).containsExactly(span1.toSpanData(), span2.toSpanData()); + assertThat(exported2).containsExactly(span1.toSpanData(), span2.toSpanData()); + } + + @Test + public void exportNotSampledSpans() { + SpanExporterImpl spanExporter = SpanExporterImpl.create(4, Duration.create(1, 0)); + StartEndHandler startEndHandler = + new StartEndHandlerImpl(spanExporter, runningSpanStore, null, new SimpleEventQueue()); + + spanExporter.registerHandler("test.service", serviceHandler); + + RecordEventsSpanImpl span1 = createNotSampledEndedSpan(startEndHandler, SPAN_NAME_1); + RecordEventsSpanImpl span2 = createSampledEndedSpan(startEndHandler, SPAN_NAME_2); + // Spans are recorded and exported in the same order as they are ended, we test that a non + // sampled span is not exported by creating and ending a sampled span after a non sampled span + // and checking that the first exported span is the sampled span (the non sampled did not get + // exported). + List<SpanData> exported = serviceHandler.waitForExport(1); + // Need to check this because otherwise the variable span1 is unused, other option is to not + // have a span1 variable. + assertThat(exported).doesNotContain(span1.toSpanData()); + assertThat(exported).containsExactly(span2.toSpanData()); + } + + @Test(timeout = 10000L) + public void exportNotSampledSpansFlushed() { + // Set the export delay to zero, for no timeout, in order to confirm the #flush() below works + SpanExporterImpl spanExporter = SpanExporterImpl.create(4, Duration.create(0, 0)); + StartEndHandler startEndHandler = + new StartEndHandlerImpl(spanExporter, runningSpanStore, null, new SimpleEventQueue()); + + spanExporter.registerHandler("test.service", serviceHandler); + + RecordEventsSpanImpl span2 = createSampledEndedSpan(startEndHandler, SPAN_NAME_2); + + // Force a flush, without this, the #waitForExport() call below would block indefinitely. + spanExporter.flush(); + + List<SpanData> exported = serviceHandler.waitForExport(1); + + assertThat(exported).containsExactly(span2.toSpanData()); + } +} diff --git a/impl_core/src/test/java/io/opencensus/implcore/trace/internal/ConcurrentIntrusiveListTest.java b/impl_core/src/test/java/io/opencensus/implcore/trace/internal/ConcurrentIntrusiveListTest.java new file mode 100644 index 00000000..d7ac2ae8 --- /dev/null +++ b/impl_core/src/test/java/io/opencensus/implcore/trace/internal/ConcurrentIntrusiveListTest.java @@ -0,0 +1,123 @@ +/* + * Copyright 2017, OpenCensus Authors + * + * 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 io.opencensus.implcore.trace.internal; + +import static com.google.common.truth.Truth.assertThat; + +import io.opencensus.implcore.trace.internal.ConcurrentIntrusiveList.Element; +import javax.annotation.Nullable; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Unit tests for {@link ConcurrentIntrusiveList}. */ +@RunWith(JUnit4.class) +public class ConcurrentIntrusiveListTest { + private final ConcurrentIntrusiveList<FakeElement> intrusiveList = + new ConcurrentIntrusiveList<FakeElement>(); + @Rule public final ExpectedException exception = ExpectedException.none(); + + @Test + public void emptyList() { + assertThat(intrusiveList.size()).isEqualTo(0); + assertThat(intrusiveList.getAll().isEmpty()).isTrue(); + } + + @Test + public void addRemoveAdd_SameElement() { + FakeElement element = new FakeElement(); + intrusiveList.addElement(element); + assertThat(intrusiveList.size()).isEqualTo(1); + intrusiveList.removeElement(element); + assertThat(intrusiveList.size()).isEqualTo(0); + intrusiveList.addElement(element); + assertThat(intrusiveList.size()).isEqualTo(1); + } + + @Test + public void addAndRemoveElements() { + FakeElement element1 = new FakeElement(); + FakeElement element2 = new FakeElement(); + FakeElement element3 = new FakeElement(); + intrusiveList.addElement(element1); + intrusiveList.addElement(element2); + intrusiveList.addElement(element3); + assertThat(intrusiveList.size()).isEqualTo(3); + assertThat(intrusiveList.getAll()).containsExactly(element3, element2, element1).inOrder(); + // Remove element from the middle of the list. + intrusiveList.removeElement(element2); + assertThat(intrusiveList.size()).isEqualTo(2); + assertThat(intrusiveList.getAll()).containsExactly(element3, element1).inOrder(); + // Remove element from the tail of the list. + intrusiveList.removeElement(element1); + assertThat(intrusiveList.size()).isEqualTo(1); + assertThat(intrusiveList.getAll().contains(element3)).isTrue(); + intrusiveList.addElement(element1); + assertThat(intrusiveList.size()).isEqualTo(2); + assertThat(intrusiveList.getAll()).containsExactly(element1, element3).inOrder(); + // Remove element from the head of the list when there are other elements after. + intrusiveList.removeElement(element1); + assertThat(intrusiveList.size()).isEqualTo(1); + assertThat(intrusiveList.getAll().contains(element3)).isTrue(); + // Remove element from the head of the list when no more other elements in the list. + intrusiveList.removeElement(element3); + assertThat(intrusiveList.size()).isEqualTo(0); + assertThat(intrusiveList.getAll().isEmpty()).isTrue(); + } + + @Test + public void addAlreadyAddedElement() { + FakeElement element = new FakeElement(); + intrusiveList.addElement(element); + exception.expect(IllegalArgumentException.class); + intrusiveList.addElement(element); + } + + @Test + public void removeNotAddedElement() { + FakeElement element = new FakeElement(); + exception.expect(IllegalArgumentException.class); + intrusiveList.removeElement(element); + } + + private static final class FakeElement implements Element<FakeElement> { + @Nullable private FakeElement next = null; + @Nullable private FakeElement prev = null; + + @Override + public FakeElement getNext() { + return next; + } + + @Override + public void setNext(FakeElement element) { + next = element; + } + + @Override + public FakeElement getPrev() { + return prev; + } + + @Override + public void setPrev(FakeElement element) { + prev = element; + } + } +} diff --git a/impl_core/src/test/java/io/opencensus/implcore/trace/propagation/B3FormatTest.java b/impl_core/src/test/java/io/opencensus/implcore/trace/propagation/B3FormatTest.java new file mode 100644 index 00000000..52e6bb3c --- /dev/null +++ b/impl_core/src/test/java/io/opencensus/implcore/trace/propagation/B3FormatTest.java @@ -0,0 +1,221 @@ +/* + * Copyright 2017, OpenCensus Authors + * + * 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 io.opencensus.implcore.trace.propagation; + +import static com.google.common.truth.Truth.assertThat; +import static io.opencensus.implcore.trace.propagation.B3Format.X_B3_FLAGS; +import static io.opencensus.implcore.trace.propagation.B3Format.X_B3_PARENT_SPAN_ID; +import static io.opencensus.implcore.trace.propagation.B3Format.X_B3_SAMPLED; +import static io.opencensus.implcore.trace.propagation.B3Format.X_B3_SPAN_ID; +import static io.opencensus.implcore.trace.propagation.B3Format.X_B3_TRACE_ID; + +import io.opencensus.trace.SpanContext; +import io.opencensus.trace.SpanId; +import io.opencensus.trace.TraceId; +import io.opencensus.trace.TraceOptions; +import io.opencensus.trace.propagation.SpanContextParseException; +import io.opencensus.trace.propagation.TextFormat.Getter; +import io.opencensus.trace.propagation.TextFormat.Setter; +import java.util.HashMap; +import java.util.Map; +import javax.annotation.Nullable; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Unit tests for {@link B3Format}. */ +@RunWith(JUnit4.class) +public class B3FormatTest { + private static final String TRACE_ID_BASE16 = "ff000000000000000000000000000041"; + private static final TraceId TRACE_ID = TraceId.fromLowerBase16(TRACE_ID_BASE16); + private static final String TRACE_ID_BASE16_EIGHT_BYTES = "0000000000000041"; + private static final TraceId TRACE_ID_EIGHT_BYTES = + TraceId.fromLowerBase16("0000000000000000" + TRACE_ID_BASE16_EIGHT_BYTES); + private static final String SPAN_ID_BASE16 = "ff00000000000041"; + private static final SpanId SPAN_ID = SpanId.fromLowerBase16(SPAN_ID_BASE16); + private static final byte TRACE_OPTIONS_BYTE = 1; + private static final TraceOptions TRACE_OPTIONS = TraceOptions.fromByte(TRACE_OPTIONS_BYTE); + private static final Setter<Map<String, String>> setter = + new Setter<Map<String, String>>() { + @Override + public void put(Map<String, String> carrier, String key, String value) { + carrier.put(key, value); + } + }; + private static final Getter<Map<String, String>> getter = + new Getter<Map<String, String>>() { + @Nullable + @Override + public String get(Map<String, String> carrier, String key) { + return carrier.get(key); + } + }; + private final B3Format b3Format = new B3Format(); + @Rule public ExpectedException thrown = ExpectedException.none(); + + @Test + public void serialize_SampledContext() { + Map<String, String> carrier = new HashMap<String, String>(); + b3Format.inject(SpanContext.create(TRACE_ID, SPAN_ID, TRACE_OPTIONS), carrier, setter); + assertThat(carrier) + .containsExactly( + X_B3_TRACE_ID, TRACE_ID_BASE16, X_B3_SPAN_ID, SPAN_ID_BASE16, X_B3_SAMPLED, "1"); + } + + @Test + public void serialize_NotSampledContext() { + Map<String, String> carrier = new HashMap<String, String>(); + b3Format.inject(SpanContext.create(TRACE_ID, SPAN_ID, TraceOptions.DEFAULT), carrier, setter); + assertThat(carrier) + .containsExactly(X_B3_TRACE_ID, TRACE_ID_BASE16, X_B3_SPAN_ID, SPAN_ID_BASE16); + } + + @Test + public void parseMissingSampledAndMissingFlag() throws SpanContextParseException { + Map<String, String> headersNotSampled = new HashMap<String, String>(); + headersNotSampled.put(X_B3_TRACE_ID, TRACE_ID_BASE16); + headersNotSampled.put(X_B3_SPAN_ID, SPAN_ID_BASE16); + SpanContext spanContext = SpanContext.create(TRACE_ID, SPAN_ID, TraceOptions.DEFAULT); + assertThat(b3Format.extract(headersNotSampled, getter)).isEqualTo(spanContext); + } + + @Test + public void parseSampled() throws SpanContextParseException { + Map<String, String> headersSampled = new HashMap<String, String>(); + headersSampled.put(X_B3_TRACE_ID, TRACE_ID_BASE16); + headersSampled.put(X_B3_SPAN_ID, SPAN_ID_BASE16); + headersSampled.put(X_B3_SAMPLED, "1"); + assertThat(b3Format.extract(headersSampled, getter)) + .isEqualTo(SpanContext.create(TRACE_ID, SPAN_ID, TRACE_OPTIONS)); + } + + @Test + public void parseZeroSampled() throws SpanContextParseException { + Map<String, String> headersNotSampled = new HashMap<String, String>(); + headersNotSampled.put(X_B3_TRACE_ID, TRACE_ID_BASE16); + headersNotSampled.put(X_B3_SPAN_ID, SPAN_ID_BASE16); + headersNotSampled.put(X_B3_SAMPLED, "0"); + assertThat(b3Format.extract(headersNotSampled, getter)) + .isEqualTo(SpanContext.create(TRACE_ID, SPAN_ID, TraceOptions.DEFAULT)); + } + + @Test + public void parseFlag() throws SpanContextParseException { + Map<String, String> headersFlagSampled = new HashMap<String, String>(); + headersFlagSampled.put(X_B3_TRACE_ID, TRACE_ID_BASE16); + headersFlagSampled.put(X_B3_SPAN_ID, SPAN_ID_BASE16); + headersFlagSampled.put(X_B3_FLAGS, "1"); + assertThat(b3Format.extract(headersFlagSampled, getter)) + .isEqualTo(SpanContext.create(TRACE_ID, SPAN_ID, TRACE_OPTIONS)); + } + + @Test + public void parseZeroFlag() throws SpanContextParseException { + Map<String, String> headersFlagNotSampled = new HashMap<String, String>(); + headersFlagNotSampled.put(X_B3_TRACE_ID, TRACE_ID_BASE16); + headersFlagNotSampled.put(X_B3_SPAN_ID, SPAN_ID_BASE16); + headersFlagNotSampled.put(X_B3_FLAGS, "0"); + assertThat(b3Format.extract(headersFlagNotSampled, getter)) + .isEqualTo(SpanContext.create(TRACE_ID, SPAN_ID, TraceOptions.DEFAULT)); + } + + @Test + public void parseEightBytesTraceId() throws SpanContextParseException { + Map<String, String> headersEightBytes = new HashMap<String, String>(); + headersEightBytes.put(X_B3_TRACE_ID, TRACE_ID_BASE16_EIGHT_BYTES); + headersEightBytes.put(X_B3_SPAN_ID, SPAN_ID_BASE16); + headersEightBytes.put(X_B3_SAMPLED, "1"); + assertThat(b3Format.extract(headersEightBytes, getter)) + .isEqualTo(SpanContext.create(TRACE_ID_EIGHT_BYTES, SPAN_ID, TRACE_OPTIONS)); + } + + @Test + public void parseEightBytesTraceId_NotSampledSpanContext() throws SpanContextParseException { + Map<String, String> headersEightBytes = new HashMap<String, String>(); + headersEightBytes.put(X_B3_TRACE_ID, TRACE_ID_BASE16_EIGHT_BYTES); + headersEightBytes.put(X_B3_SPAN_ID, SPAN_ID_BASE16); + assertThat(b3Format.extract(headersEightBytes, getter)) + .isEqualTo(SpanContext.create(TRACE_ID_EIGHT_BYTES, SPAN_ID, TraceOptions.DEFAULT)); + } + + @Test + public void parseInvalidTraceId() throws SpanContextParseException { + Map<String, String> invalidHeaders = new HashMap<String, String>(); + invalidHeaders.put(X_B3_TRACE_ID, "abcdefghijklmnop"); + invalidHeaders.put(X_B3_SPAN_ID, SPAN_ID_BASE16); + thrown.expect(SpanContextParseException.class); + thrown.expectMessage("Invalid input."); + b3Format.extract(invalidHeaders, getter); + } + + @Test + public void parseInvalidTraceId_Size() throws SpanContextParseException { + Map<String, String> invalidHeaders = new HashMap<String, String>(); + invalidHeaders.put(X_B3_TRACE_ID, "0123456789abcdef00"); + invalidHeaders.put(X_B3_SPAN_ID, SPAN_ID_BASE16); + thrown.expect(SpanContextParseException.class); + thrown.expectMessage("Invalid input."); + b3Format.extract(invalidHeaders, getter); + } + + @Test + public void parseMissingTraceId() throws SpanContextParseException { + Map<String, String> invalidHeaders = new HashMap<String, String>(); + invalidHeaders.put(X_B3_SPAN_ID, SPAN_ID_BASE16); + thrown.expect(SpanContextParseException.class); + thrown.expectMessage("Missing X_B3_TRACE_ID."); + b3Format.extract(invalidHeaders, getter); + } + + @Test + public void parseInvalidSpanId() throws SpanContextParseException { + Map<String, String> invalidHeaders = new HashMap<String, String>(); + invalidHeaders.put(X_B3_TRACE_ID, TRACE_ID_BASE16); + invalidHeaders.put(X_B3_SPAN_ID, "abcdefghijklmnop"); + thrown.expect(SpanContextParseException.class); + thrown.expectMessage("Invalid input."); + b3Format.extract(invalidHeaders, getter); + } + + @Test + public void parseInvalidSpanId_Size() throws SpanContextParseException { + Map<String, String> invalidHeaders = new HashMap<String, String>(); + invalidHeaders.put(X_B3_TRACE_ID, TRACE_ID_BASE16); + invalidHeaders.put(X_B3_SPAN_ID, "0123456789abcdef00"); + thrown.expect(SpanContextParseException.class); + thrown.expectMessage("Invalid input."); + b3Format.extract(invalidHeaders, getter); + } + + @Test + public void parseMissingSpanId() throws SpanContextParseException { + Map<String, String> invalidHeaders = new HashMap<String, String>(); + invalidHeaders.put(X_B3_TRACE_ID, TRACE_ID_BASE16); + thrown.expect(SpanContextParseException.class); + thrown.expectMessage("Missing X_B3_SPAN_ID."); + b3Format.extract(invalidHeaders, getter); + } + + @Test + public void fields_list() { + assertThat(b3Format.fields()) + .containsExactly( + X_B3_TRACE_ID, X_B3_SPAN_ID, X_B3_PARENT_SPAN_ID, X_B3_SAMPLED, X_B3_FLAGS); + } +} diff --git a/impl_core/src/test/java/io/opencensus/implcore/trace/propagation/BinaryFormatImplTest.java b/impl_core/src/test/java/io/opencensus/implcore/trace/propagation/BinaryFormatImplTest.java new file mode 100644 index 00000000..f43be479 --- /dev/null +++ b/impl_core/src/test/java/io/opencensus/implcore/trace/propagation/BinaryFormatImplTest.java @@ -0,0 +1,191 @@ +/* + * Copyright 2017, OpenCensus Authors + * + * 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 io.opencensus.implcore.trace.propagation; + +import static com.google.common.truth.Truth.assertThat; +import static com.google.common.truth.Truth.assertWithMessage; + +import io.opencensus.trace.SpanContext; +import io.opencensus.trace.SpanId; +import io.opencensus.trace.TraceId; +import io.opencensus.trace.TraceOptions; +import io.opencensus.trace.propagation.BinaryFormat; +import io.opencensus.trace.propagation.SpanContextParseException; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Unit tests for {@link BinaryFormatImpl}. */ +@RunWith(JUnit4.class) +public class BinaryFormatImplTest { + private static final byte[] TRACE_ID_BYTES = + new byte[] {64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79}; + private static final TraceId TRACE_ID = TraceId.fromBytes(TRACE_ID_BYTES); + private static final byte[] SPAN_ID_BYTES = new byte[] {97, 98, 99, 100, 101, 102, 103, 104}; + private static final SpanId SPAN_ID = SpanId.fromBytes(SPAN_ID_BYTES); + private static final byte TRACE_OPTIONS_BYTES = 1; + private static final TraceOptions TRACE_OPTIONS = TraceOptions.fromByte(TRACE_OPTIONS_BYTES); + private static final byte[] EXAMPLE_BYTES = + new byte[] { + 0, 0, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 1, 97, 98, 99, 100, + 101, 102, 103, 104, 2, 1 + }; + private static final SpanContext EXAMPLE_SPAN_CONTEXT = + SpanContext.create(TRACE_ID, SPAN_ID, TRACE_OPTIONS); + @Rule public ExpectedException expectedException = ExpectedException.none(); + private final BinaryFormat binaryFormat = new BinaryFormatImpl(); + + private void testSpanContextConversion(SpanContext spanContext) throws SpanContextParseException { + SpanContext propagatedBinarySpanContext = + binaryFormat.fromByteArray(binaryFormat.toByteArray(spanContext)); + + assertWithMessage("BinaryFormat propagated context is not equal with the initial context.") + .that(propagatedBinarySpanContext) + .isEqualTo(spanContext); + } + + @Test + public void propagate_SpanContextTracingEnabled() throws SpanContextParseException { + testSpanContextConversion( + SpanContext.create(TRACE_ID, SPAN_ID, TraceOptions.builder().setIsSampled(true).build())); + } + + @Test + public void propagate_SpanContextNoTracing() throws SpanContextParseException { + testSpanContextConversion(SpanContext.create(TRACE_ID, SPAN_ID, TraceOptions.DEFAULT)); + } + + @Test(expected = NullPointerException.class) + public void toBinaryValue_NullSpanContext() { + binaryFormat.toByteArray(null); + } + + @Test + public void toBinaryValue_InvalidSpanContext() { + assertThat(binaryFormat.toByteArray(SpanContext.INVALID)) + .isEqualTo( + new byte[] { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0 + }); + } + + @Test + public void fromBinaryValue_BinaryExampleValue() throws SpanContextParseException { + assertThat(binaryFormat.fromByteArray(EXAMPLE_BYTES)).isEqualTo(EXAMPLE_SPAN_CONTEXT); + } + + @Test(expected = NullPointerException.class) + public void fromBinaryValue_NullInput() throws SpanContextParseException { + binaryFormat.fromByteArray(null); + } + + @Test + public void fromBinaryValue_EmptyInput() throws SpanContextParseException { + expectedException.expect(SpanContextParseException.class); + expectedException.expectMessage("Unsupported version."); + binaryFormat.fromByteArray(new byte[0]); + } + + @Test + public void fromBinaryValue_UnsupportedVersionId() throws SpanContextParseException { + expectedException.expect(SpanContextParseException.class); + expectedException.expectMessage("Unsupported version."); + binaryFormat.fromByteArray( + new byte[] { + 66, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 97, 98, 99, 100, 101, + 102, 103, 104, 1 + }); + } + + @Test + public void fromBinaryValue_UnsupportedFieldIdFirst() throws SpanContextParseException { + expectedException.expect(SpanContextParseException.class); + expectedException.expectMessage( + "Invalid input: expected trace ID at offset " + BinaryFormatImpl.TRACE_ID_FIELD_ID_OFFSET); + binaryFormat.fromByteArray( + new byte[] { + 0, 4, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 1, 97, 98, 99, 100, + 101, 102, 103, 104, 2, 1 + }); + } + + @Test + public void fromBinaryValue_UnsupportedFieldIdSecond() throws SpanContextParseException { + expectedException.expect(SpanContextParseException.class); + expectedException.expectMessage( + "Invalid input: expected span ID at offset " + BinaryFormatImpl.SPAN_ID_FIELD_ID_OFFSET); + binaryFormat.fromByteArray( + new byte[] { + 0, 0, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 3, 97, 98, 99, 100, + 101, 102, 103, 104, 2, 1 + }); + } + + @Test + public void fromBinaryValue_UnsupportedFieldIdThird_skipped() throws SpanContextParseException { + assertThat( + binaryFormat + .fromByteArray( + new byte[] { + 0, 0, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 1, 97, + 98, 99, 100, 101, 102, 103, 104, 0, 1 + }) + .isValid()) + .isTrue(); + } + + @Test + public void fromBinaryValue_ShorterTraceId() throws SpanContextParseException { + expectedException.expect(SpanContextParseException.class); + expectedException.expectMessage("Invalid input: truncated"); + binaryFormat.fromByteArray( + new byte[] {0, 0, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76}); + } + + @Test + public void fromBinaryValue_ShorterSpanId() throws SpanContextParseException { + expectedException.expect(SpanContextParseException.class); + expectedException.expectMessage("Invalid input: truncated"); + binaryFormat.fromByteArray(new byte[] {0, 1, 97, 98, 99, 100, 101, 102, 103}); + } + + @Test + public void fromBinaryValue_ShorterTraceOptions() throws SpanContextParseException { + expectedException.expect(SpanContextParseException.class); + expectedException.expectMessage("Invalid input: truncated"); + binaryFormat.fromByteArray( + new byte[] { + 0, 0, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 1, 97, 98, 99, 100, + 101, 102, 103, 104, 2 + }); + } + + @Test + public void fromBinaryValue_MissingTraceOptionsOk() throws SpanContextParseException { + SpanContext extracted = + binaryFormat.fromByteArray( + new byte[] { + 0, 0, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 1, 97, 98, 99, + 100, 101, 102, 103, 104 + }); + + assertThat(extracted.isValid()).isTrue(); + assertThat(extracted.getTraceOptions()).isEqualTo(TraceOptions.DEFAULT); + } +} diff --git a/impl_core/src/test/java/io/opencensus/implcore/trace/propagation/PropagationComponentImplTest.java b/impl_core/src/test/java/io/opencensus/implcore/trace/propagation/PropagationComponentImplTest.java new file mode 100644 index 00000000..00ed90fe --- /dev/null +++ b/impl_core/src/test/java/io/opencensus/implcore/trace/propagation/PropagationComponentImplTest.java @@ -0,0 +1,40 @@ +/* + * Copyright 2017, OpenCensus Authors + * + * 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 io.opencensus.implcore.trace.propagation; + +import static com.google.common.truth.Truth.assertThat; + +import io.opencensus.trace.propagation.PropagationComponent; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Unit tests for {@link PropagationComponentImpl}. */ +@RunWith(JUnit4.class) +public class PropagationComponentImplTest { + private final PropagationComponent propagationComponent = new PropagationComponentImpl(); + + @Test + public void implementationOfBinary() { + assertThat(propagationComponent.getBinaryFormat()).isInstanceOf(BinaryFormatImpl.class); + } + + @Test + public void implementationOfB3Format() { + assertThat(propagationComponent.getB3Format()).isInstanceOf(B3Format.class); + } +} |