From d2ed0f6729a9a728ec45344b73c70fb7ab46ab75 Mon Sep 17 00:00:00 2001 From: Muhammad Qureshi Date: Thu, 14 Nov 2019 17:41:02 -0800 Subject: [PATCH] StatsEvent unit tests Bug: 144605478 Test: bit FrameworksCoreTests:android.util.StatsEventTest Change-Id: Ied6a13bea3367488c7a6923830284b5e4625cbc5 Merged-In: Ied6a13bea3367488c7a6923830284b5e4625cbc5 --- .../src/android/util/StatsEventTest.java | 466 ++++++++++++++++++ 1 file changed, 466 insertions(+) create mode 100644 core/tests/coretests/src/android/util/StatsEventTest.java diff --git a/core/tests/coretests/src/android/util/StatsEventTest.java b/core/tests/coretests/src/android/util/StatsEventTest.java new file mode 100644 index 0000000000000..93f11dbccf649 --- /dev/null +++ b/core/tests/coretests/src/android/util/StatsEventTest.java @@ -0,0 +1,466 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * 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 android.util; + +import static com.google.common.truth.Truth.assertThat; +import static com.google.common.truth.Truth.assertWithMessage; + +import static java.nio.charset.StandardCharsets.UTF_8; + +import android.os.SystemClock; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import com.google.common.collect.Range; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +/** + * Internal tests for {@link StatsEvent}. + */ +@SmallTest +@RunWith(AndroidJUnit4.class) +public class StatsEventTest { + + @Test + public void testNoFields() { + final long minTimestamp = SystemClock.elapsedRealtimeNanos(); + final StatsEvent statsEvent = StatsEvent.newBuilder().build(); + final long maxTimestamp = SystemClock.elapsedRealtimeNanos(); + + final int expectedAtomId = 0; + assertThat(statsEvent.getAtomId()).isEqualTo(expectedAtomId); + + final ByteBuffer buffer = + ByteBuffer.wrap(statsEvent.getBytes()).order(ByteOrder.LITTLE_ENDIAN); + + assertWithMessage("Root element in buffer is not TYPE_ERRORS") + .that(buffer.get()).isEqualTo(StatsEvent.TYPE_ERRORS); + + assertWithMessage("Incorrect number of elements in root object") + .that(buffer.get()).isEqualTo(3); + + assertWithMessage("First element is not timestamp") + .that(buffer.get()).isEqualTo(StatsEvent.TYPE_LONG); + + assertWithMessage("Incorrect timestamp") + .that(buffer.getLong()).isIn(Range.closed(minTimestamp, maxTimestamp)); + + assertWithMessage("Second element is not atom id") + .that(buffer.get()).isEqualTo(StatsEvent.TYPE_INT); + + assertWithMessage("Incorrect atom id") + .that(buffer.getInt()).isEqualTo(expectedAtomId); + + final int errorMask = buffer.getInt(); + + assertWithMessage("ERROR_NO_ATOM_ID should be the only error in the error mask") + .that(errorMask).isEqualTo(StatsEvent.ERROR_NO_ATOM_ID); + + assertThat(statsEvent.getNumBytes()).isEqualTo(buffer.position()); + + statsEvent.release(); + } + + @Test + public void testIntBooleanIntInt() { + final int expectedAtomId = 109; + final int field1 = 1; + final boolean field2 = true; + final int field3 = 3; + final int field4 = 4; + + final long minTimestamp = SystemClock.elapsedRealtimeNanos(); + final StatsEvent statsEvent = StatsEvent.newBuilder() + .setAtomId(expectedAtomId) + .writeInt(field1) + .writeBoolean(field2) + .writeInt(field3) + .writeInt(field4) + .build(); + final long maxTimestamp = SystemClock.elapsedRealtimeNanos(); + + assertThat(statsEvent.getAtomId()).isEqualTo(expectedAtomId); + + final ByteBuffer buffer = + ByteBuffer.wrap(statsEvent.getBytes()).order(ByteOrder.LITTLE_ENDIAN); + + assertWithMessage("Root element in buffer is not TYPE_OBJECT") + .that(buffer.get()).isEqualTo(StatsEvent.TYPE_OBJECT); + + assertWithMessage("Incorrect number of elements in root object") + .that(buffer.get()).isEqualTo(6); + + assertWithMessage("First element is not timestamp") + .that(buffer.get()).isEqualTo(StatsEvent.TYPE_LONG); + + assertWithMessage("Incorrect timestamp") + .that(buffer.getLong()).isIn(Range.closed(minTimestamp, maxTimestamp)); + + assertWithMessage("Second element is not atom id") + .that(buffer.get()).isEqualTo(StatsEvent.TYPE_INT); + + assertWithMessage("Incorrect atom id") + .that(buffer.getInt()).isEqualTo(expectedAtomId); + + assertWithMessage("First field is not Int") + .that(buffer.get()).isEqualTo(StatsEvent.TYPE_INT); + + assertWithMessage("Incorrect field 1") + .that(buffer.getInt()).isEqualTo(field1); + + assertWithMessage("Second field is not Boolean") + .that(buffer.get()).isEqualTo(StatsEvent.TYPE_BOOLEAN); + + assertWithMessage("Incorrect field 2") + .that(buffer.get()).isEqualTo(1); + + assertWithMessage("Third field is not Int") + .that(buffer.get()).isEqualTo(StatsEvent.TYPE_INT); + + assertWithMessage("Incorrect field 3") + .that(buffer.getInt()).isEqualTo(field3); + + assertWithMessage("Fourth field is not Int") + .that(buffer.get()).isEqualTo(StatsEvent.TYPE_INT); + + assertWithMessage("Incorrect field 4") + .that(buffer.getInt()).isEqualTo(field4); + + assertThat(statsEvent.getNumBytes()).isEqualTo(buffer.position()); + + statsEvent.release(); + } + + @Test + public void testStringFloatByteArray() { + final int expectedAtomId = 109; + final String field1 = "Str 1"; + final float field2 = 9.334f; + final byte[] field3 = new byte[] { 56, 23, 89, -120 }; + + final long minTimestamp = SystemClock.elapsedRealtimeNanos(); + final StatsEvent statsEvent = StatsEvent.newBuilder() + .setAtomId(expectedAtomId) + .writeString(field1) + .writeFloat(field2) + .writeByteArray(field3) + .build(); + final long maxTimestamp = SystemClock.elapsedRealtimeNanos(); + + assertThat(statsEvent.getAtomId()).isEqualTo(expectedAtomId); + + final ByteBuffer buffer = + ByteBuffer.wrap(statsEvent.getBytes()).order(ByteOrder.LITTLE_ENDIAN); + + assertWithMessage("Root element in buffer is not TYPE_OBJECT") + .that(buffer.get()).isEqualTo(StatsEvent.TYPE_OBJECT); + + assertWithMessage("Incorrect number of elements in root object") + .that(buffer.get()).isEqualTo(5); + + assertWithMessage("First element is not timestamp") + .that(buffer.get()).isEqualTo(StatsEvent.TYPE_LONG); + + assertWithMessage("Incorrect timestamp") + .that(buffer.getLong()).isIn(Range.closed(minTimestamp, maxTimestamp)); + + assertWithMessage("Second element is not atom id") + .that(buffer.get()).isEqualTo(StatsEvent.TYPE_INT); + + assertWithMessage("Incorrect atom id") + .that(buffer.getInt()).isEqualTo(expectedAtomId); + + assertWithMessage("First field is not String") + .that(buffer.get()).isEqualTo(StatsEvent.TYPE_STRING); + + final String field1Actual = getStringFromByteBuffer(buffer); + assertWithMessage("Incorrect field 1") + .that(field1Actual).isEqualTo(field1); + + assertWithMessage("Second field is not Float") + .that(buffer.get()).isEqualTo(StatsEvent.TYPE_FLOAT); + + assertWithMessage("Incorrect field 2") + .that(buffer.getFloat()).isEqualTo(field2); + + assertWithMessage("Third field is not byte array") + .that(buffer.get()).isEqualTo(StatsEvent.TYPE_BYTE_ARRAY); + + final byte[] field3Actual = getByteArrayFromByteBuffer(buffer); + assertWithMessage("Incorrect field 3") + .that(field3Actual).isEqualTo(field3); + + assertThat(statsEvent.getNumBytes()).isEqualTo(buffer.position()); + + statsEvent.release(); + } + + @Test + public void testAttributionChainLong() { + final int expectedAtomId = 109; + final int[] uids = new int[] { 1, 2, 3, 4, 5 }; + final String[] tags = new String[] { "1", "2", "3", "4", "5" }; + final long field2 = -230909823L; + + final long minTimestamp = SystemClock.elapsedRealtimeNanos(); + final StatsEvent statsEvent = StatsEvent.newBuilder() + .setAtomId(expectedAtomId) + .writeAttributionChain(uids, tags) + .writeLong(field2) + .build(); + final long maxTimestamp = SystemClock.elapsedRealtimeNanos(); + + assertThat(statsEvent.getAtomId()).isEqualTo(expectedAtomId); + + final ByteBuffer buffer = + ByteBuffer.wrap(statsEvent.getBytes()).order(ByteOrder.LITTLE_ENDIAN); + + assertWithMessage("Root element in buffer is not TYPE_OBJECT") + .that(buffer.get()).isEqualTo(StatsEvent.TYPE_OBJECT); + + assertWithMessage("Incorrect number of elements in root object") + .that(buffer.get()).isEqualTo(4); + + assertWithMessage("First element is not timestamp") + .that(buffer.get()).isEqualTo(StatsEvent.TYPE_LONG); + + assertWithMessage("Incorrect timestamp") + .that(buffer.getLong()).isIn(Range.closed(minTimestamp, maxTimestamp)); + + assertWithMessage("Second element is not atom id") + .that(buffer.get()).isEqualTo(StatsEvent.TYPE_INT); + + assertWithMessage("Incorrect atom id") + .that(buffer.getInt()).isEqualTo(expectedAtomId); + + assertWithMessage("First field is not Attribution Chain") + .that(buffer.get()).isEqualTo(StatsEvent.TYPE_ATTRIBUTION_CHAIN); + + assertWithMessage("Incorrect number of attribution nodes") + .that(buffer.get()).isEqualTo((byte) uids.length); + + for (int i = 0; i < tags.length; i++) { + assertWithMessage("Incorrect uid in Attribution Chain") + .that(buffer.getInt()).isEqualTo(uids[i]); + + final String tag = getStringFromByteBuffer(buffer); + assertWithMessage("Incorrect tag in Attribution Chain") + .that(tag).isEqualTo(tags[i]); + } + + assertWithMessage("Second field is not Long") + .that(buffer.get()).isEqualTo(StatsEvent.TYPE_LONG); + + assertWithMessage("Incorrect field 2") + .that(buffer.getLong()).isEqualTo(field2); + + assertThat(statsEvent.getNumBytes()).isEqualTo(buffer.position()); + + statsEvent.release(); + } + + @Test + public void testKeyValuePairs() { + final int expectedAtomId = 109; + final SparseIntArray intMap = new SparseIntArray(); + final SparseLongArray longMap = new SparseLongArray(); + final SparseArray stringMap = new SparseArray<>(); + final SparseArray floatMap = new SparseArray<>(); + intMap.put(1, -1); + intMap.put(2, -2); + stringMap.put(3, "abc"); + stringMap.put(4, "2h"); + floatMap.put(9, -234.344f); + + final long minTimestamp = SystemClock.elapsedRealtimeNanos(); + final StatsEvent statsEvent = StatsEvent.newBuilder() + .setAtomId(expectedAtomId) + .writeKeyValuePairs(intMap, longMap, stringMap, floatMap) + .build(); + final long maxTimestamp = SystemClock.elapsedRealtimeNanos(); + + assertThat(statsEvent.getAtomId()).isEqualTo(expectedAtomId); + + final ByteBuffer buffer = + ByteBuffer.wrap(statsEvent.getBytes()).order(ByteOrder.LITTLE_ENDIAN); + + assertWithMessage("Root element in buffer is not TYPE_OBJECT") + .that(buffer.get()).isEqualTo(StatsEvent.TYPE_OBJECT); + + assertWithMessage("Incorrect number of elements in root object") + .that(buffer.get()).isEqualTo(3); + + assertWithMessage("First element is not timestamp") + .that(buffer.get()).isEqualTo(StatsEvent.TYPE_LONG); + + assertWithMessage("Incorrect timestamp") + .that(buffer.getLong()).isIn(Range.closed(minTimestamp, maxTimestamp)); + + assertWithMessage("Second element is not atom id") + .that(buffer.get()).isEqualTo(StatsEvent.TYPE_INT); + + assertWithMessage("Incorrect atom id") + .that(buffer.getInt()).isEqualTo(expectedAtomId); + + assertWithMessage("First field is not KeyValuePairs") + .that(buffer.get()).isEqualTo(StatsEvent.TYPE_KEY_VALUE_PAIRS); + + assertWithMessage("Incorrect number of key value pairs") + .that(buffer.get()).isEqualTo( + (byte) (intMap.size() + longMap.size() + stringMap.size() + + floatMap.size())); + + for (int i = 0; i < intMap.size(); i++) { + assertWithMessage("Incorrect key in intMap") + .that(buffer.getInt()).isEqualTo(intMap.keyAt(i)); + assertWithMessage("The type id of the value should be TYPE_INT in intMap") + .that(buffer.get()).isEqualTo(StatsEvent.TYPE_INT); + assertWithMessage("Incorrect value in intMap") + .that(buffer.getInt()).isEqualTo(intMap.valueAt(i)); + } + + for (int i = 0; i < longMap.size(); i++) { + assertWithMessage("Incorrect key in longMap") + .that(buffer.getInt()).isEqualTo(longMap.keyAt(i)); + assertWithMessage("The type id of the value should be TYPE_LONG in longMap") + .that(buffer.get()).isEqualTo(StatsEvent.TYPE_LONG); + assertWithMessage("Incorrect value in longMap") + .that(buffer.getLong()).isEqualTo(longMap.valueAt(i)); + } + + for (int i = 0; i < stringMap.size(); i++) { + assertWithMessage("Incorrect key in stringMap") + .that(buffer.getInt()).isEqualTo(stringMap.keyAt(i)); + assertWithMessage("The type id of the value should be TYPE_STRING in stringMap") + .that(buffer.get()).isEqualTo(StatsEvent.TYPE_STRING); + final String value = getStringFromByteBuffer(buffer); + assertWithMessage("Incorrect value in stringMap") + .that(value).isEqualTo(stringMap.valueAt(i)); + } + + for (int i = 0; i < floatMap.size(); i++) { + assertWithMessage("Incorrect key in floatMap") + .that(buffer.getInt()).isEqualTo(floatMap.keyAt(i)); + assertWithMessage("The type id of the value should be TYPE_FLOAT in floatMap") + .that(buffer.get()).isEqualTo(StatsEvent.TYPE_FLOAT); + assertWithMessage("Incorrect value in floatMap") + .that(buffer.getFloat()).isEqualTo(floatMap.valueAt(i)); + } + + assertThat(statsEvent.getNumBytes()).isEqualTo(buffer.position()); + + statsEvent.release(); + } + + @Test + public void testSingleAnnotations() { + final int expectedAtomId = 109; + final int field1 = 1; + final byte field1AnnotationId = 45; + final boolean field1AnnotationValue = false; + final boolean field2 = true; + final byte field2AnnotationId = 1; + final int field2AnnotationValue = 23; + + final long minTimestamp = SystemClock.elapsedRealtimeNanos(); + final StatsEvent statsEvent = StatsEvent.newBuilder() + .setAtomId(expectedAtomId) + .writeInt(field1) + .addBooleanAnnotation(field1AnnotationId, field1AnnotationValue) + .writeBoolean(field2) + .addIntAnnotation(field2AnnotationId, field2AnnotationValue) + .build(); + final long maxTimestamp = SystemClock.elapsedRealtimeNanos(); + + assertThat(statsEvent.getAtomId()).isEqualTo(expectedAtomId); + + final ByteBuffer buffer = + ByteBuffer.wrap(statsEvent.getBytes()).order(ByteOrder.LITTLE_ENDIAN); + + assertWithMessage("Root element in buffer is not TYPE_OBJECT") + .that(buffer.get()).isEqualTo(StatsEvent.TYPE_OBJECT); + + assertWithMessage("Incorrect number of elements in root object") + .that(buffer.get()).isEqualTo(4); + + assertWithMessage("First element is not timestamp") + .that(buffer.get()).isEqualTo(StatsEvent.TYPE_LONG); + + assertWithMessage("Incorrect timestamp") + .that(buffer.getLong()).isIn(Range.closed(minTimestamp, maxTimestamp)); + + assertWithMessage("Second element is not atom id") + .that(buffer.get()).isEqualTo(StatsEvent.TYPE_INT); + + assertWithMessage("Incorrect atom id") + .that(buffer.getInt()).isEqualTo(expectedAtomId); + + final byte field1Header = buffer.get(); + final int field1AnnotationValueCount = field1Header >> 4; + final byte field1Type = (byte) (field1Header & 0x0F); + assertWithMessage("First field is not Int") + .that(field1Type).isEqualTo(StatsEvent.TYPE_INT); + assertWithMessage("First field annotation count is wrong") + .that(field1AnnotationValueCount).isEqualTo(1); + assertWithMessage("Incorrect field 1") + .that(buffer.getInt()).isEqualTo(field1); + assertWithMessage("First field's annotation id is wrong") + .that(buffer.get()).isEqualTo(field1AnnotationId); + assertWithMessage("First field's annotation type is wrong") + .that(buffer.get()).isEqualTo(StatsEvent.TYPE_BOOLEAN); + assertWithMessage("First field's annotation value is wrong") + .that(buffer.get()).isEqualTo(field1AnnotationValue ? 1 : 0); + + final byte field2Header = buffer.get(); + final int field2AnnotationValueCount = field2Header >> 4; + final byte field2Type = (byte) (field2Header & 0x0F); + assertWithMessage("Second field is not boolean") + .that(field2Type).isEqualTo(StatsEvent.TYPE_BOOLEAN); + assertWithMessage("Second field annotation count is wrong") + .that(field2AnnotationValueCount).isEqualTo(1); + assertWithMessage("Incorrect field 2") + .that(buffer.get()).isEqualTo(field2 ? 1 : 0); + assertWithMessage("Second field's annotation id is wrong") + .that(buffer.get()).isEqualTo(field2AnnotationId); + assertWithMessage("Second field's annotation type is wrong") + .that(buffer.get()).isEqualTo(StatsEvent.TYPE_INT); + assertWithMessage("Second field's annotation value is wrong") + .that(buffer.getInt()).isEqualTo(field2AnnotationValue); + + assertThat(statsEvent.getNumBytes()).isEqualTo(buffer.position()); + + statsEvent.release(); + } + + private static byte[] getByteArrayFromByteBuffer(final ByteBuffer buffer) { + final int numBytes = buffer.getInt(); + byte[] bytes = new byte[numBytes]; + buffer.get(bytes); + return bytes; + } + + private static String getStringFromByteBuffer(final ByteBuffer buffer) { + final byte[] bytes = getByteArrayFromByteBuffer(buffer); + return new String(bytes, UTF_8); + } +}