| /* | |
| * 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. | |
| */ | |
| #include "stats_event.h" | |
| #include <gtest/gtest.h> | |
| #include <utils/SystemClock.h> | |
| // Keep in sync with stats_event.c. Consider moving to separate header file to avoid duplication. | |
| /* ERRORS */ | |
| #define ERROR_NO_TIMESTAMP 0x1 | |
| #define ERROR_NO_ATOM_ID 0x2 | |
| #define ERROR_OVERFLOW 0x4 | |
| #define ERROR_ATTRIBUTION_CHAIN_TOO_LONG 0x8 | |
| #define ERROR_TOO_MANY_KEY_VALUE_PAIRS 0x10 | |
| #define ERROR_ANNOTATION_DOES_NOT_FOLLOW_FIELD 0x20 | |
| #define ERROR_INVALID_ANNOTATION_ID 0x40 | |
| #define ERROR_ANNOTATION_ID_TOO_LARGE 0x80 | |
| #define ERROR_TOO_MANY_ANNOTATIONS 0x100 | |
| #define ERROR_TOO_MANY_FIELDS 0x200 | |
| #define ERROR_INVALID_VALUE_TYPE 0x400 | |
| #define ERROR_STRING_NOT_NULL_TERMINATED 0x800 | |
| #define ERROR_ATOM_ID_INVALID_POSITION 0x2000 | |
| #define ERROR_LIST_TOO_LONG 0x4000 | |
| /* TYPE IDS */ | |
| #define INT32_TYPE 0x00 | |
| #define INT64_TYPE 0x01 | |
| #define STRING_TYPE 0x02 | |
| #define LIST_TYPE 0x03 | |
| #define FLOAT_TYPE 0x04 | |
| #define BOOL_TYPE 0x05 | |
| #define BYTE_ARRAY_TYPE 0x06 | |
| #define OBJECT_TYPE 0x07 | |
| #define KEY_VALUE_PAIRS_TYPE 0x08 | |
| #define ATTRIBUTION_CHAIN_TYPE 0x09 | |
| #define ERROR_TYPE 0x0F | |
| using std::string; | |
| using std::vector; | |
| // Side-effect: this function moves the start of the buffer past the read value | |
| template <class T> | |
| T readNext(uint8_t** buffer) { | |
| T value; | |
| if ((reinterpret_cast<uintptr_t>(*buffer) % alignof(T)) == 0) { | |
| value = *(T*)(*buffer); | |
| } else { | |
| memcpy(&value, *buffer, sizeof(T)); | |
| } | |
| *buffer += sizeof(T); | |
| return value; | |
| } | |
| void checkTypeHeader(uint8_t** buffer, uint8_t typeId, uint8_t numAnnotations = 0) { | |
| uint8_t typeHeader = (numAnnotations << 4) | typeId; | |
| EXPECT_EQ(readNext<uint8_t>(buffer), typeHeader); | |
| } | |
| template <class T> | |
| void checkScalar(uint8_t** buffer, T expectedValue) { | |
| EXPECT_EQ(readNext<T>(buffer), expectedValue); | |
| } | |
| void checkString(uint8_t** buffer, const string& expectedString) { | |
| uint32_t size = readNext<uint32_t>(buffer); | |
| string parsedString((char*)(*buffer), size); | |
| EXPECT_EQ(parsedString, expectedString); | |
| *buffer += size; // move buffer past string we just read | |
| } | |
| void checkByteArray(uint8_t** buffer, const vector<uint8_t>& expectedByteArray) { | |
| uint32_t size = readNext<uint32_t>(buffer); | |
| vector<uint8_t> parsedByteArray(*buffer, *buffer + size); | |
| EXPECT_EQ(parsedByteArray, expectedByteArray); | |
| *buffer += size; // move buffer past byte array we just read | |
| } | |
| void checkArrayMetadata(uint8_t** buffer, uint8_t numElements, uint8_t elementTypeId, | |
| uint8_t numAnnotations = 0) { | |
| checkTypeHeader(buffer, LIST_TYPE, numAnnotations); | |
| EXPECT_EQ(readNext<uint8_t>(buffer), numElements); | |
| checkTypeHeader(buffer, elementTypeId); | |
| } | |
| template <class T> | |
| void checkScalarArray(uint8_t** buffer, uint8_t numElements, uint8_t elementTypeId, | |
| const T* expectedArrayValues, uint8_t numAnnotations = 0) { | |
| checkArrayMetadata(buffer, numElements, elementTypeId, numAnnotations); | |
| for (int i = 0; i < numElements; i++) { | |
| checkScalar(buffer, expectedArrayValues[i]); | |
| } | |
| } | |
| template <class T> | |
| void checkAnnotation(uint8_t** buffer, uint8_t annotationId, uint8_t typeId, T annotationValue) { | |
| EXPECT_EQ(readNext<uint8_t>(buffer), annotationId); | |
| EXPECT_EQ(readNext<uint8_t>(buffer), typeId); | |
| checkScalar<T>(buffer, annotationValue); | |
| } | |
| void checkMetadata(uint8_t** buffer, uint8_t numElements, int64_t startTime, int64_t endTime, | |
| uint32_t atomId, uint8_t numAtomLevelAnnotations = 0) { | |
| // All events start with OBJECT_TYPE id. | |
| checkTypeHeader(buffer, OBJECT_TYPE); | |
| // We increment by 2 because the number of elements listed in the | |
| // serialization accounts for the timestamp and atom id as well. | |
| checkScalar(buffer, static_cast<uint8_t>(numElements + 2)); | |
| // Check timestamp | |
| checkTypeHeader(buffer, INT64_TYPE); | |
| int64_t timestamp = readNext<int64_t>(buffer); | |
| EXPECT_GE(timestamp, startTime); | |
| EXPECT_LE(timestamp, endTime); | |
| // Check atom id | |
| checkTypeHeader(buffer, INT32_TYPE, numAtomLevelAnnotations); | |
| checkScalar(buffer, atomId); | |
| } | |
| TEST(StatsEventTest, TestScalars) { | |
| uint32_t atomId = 100; | |
| int32_t int32Value = -5; | |
| int64_t int64Value = -2 * android::elapsedRealtimeNano(); | |
| float floatValue = 2.0; | |
| bool boolValue = false; | |
| int64_t startTime = android::elapsedRealtimeNano(); | |
| AStatsEvent* event = AStatsEvent_obtain(); | |
| AStatsEvent_setAtomId(event, atomId); | |
| AStatsEvent_writeInt32(event, int32Value); | |
| AStatsEvent_writeInt64(event, int64Value); | |
| AStatsEvent_writeFloat(event, floatValue); | |
| AStatsEvent_writeBool(event, boolValue); | |
| AStatsEvent_build(event); | |
| int64_t endTime = android::elapsedRealtimeNano(); | |
| size_t bufferSize; | |
| uint8_t* buffer = AStatsEvent_getBuffer(event, &bufferSize); | |
| uint8_t* bufferEnd = buffer + bufferSize; | |
| checkMetadata(&buffer, /*numElements=*/4, startTime, endTime, atomId); | |
| // check int32 element | |
| checkTypeHeader(&buffer, INT32_TYPE); | |
| checkScalar(&buffer, int32Value); | |
| // check int64 element | |
| checkTypeHeader(&buffer, INT64_TYPE); | |
| checkScalar(&buffer, int64Value); | |
| // check float element | |
| checkTypeHeader(&buffer, FLOAT_TYPE); | |
| checkScalar(&buffer, floatValue); | |
| // check bool element | |
| checkTypeHeader(&buffer, BOOL_TYPE); | |
| checkScalar(&buffer, boolValue); | |
| EXPECT_EQ(buffer, bufferEnd); // ensure that we have read the entire buffer | |
| EXPECT_EQ(AStatsEvent_getErrors(event), 0); | |
| AStatsEvent_release(event); | |
| } | |
| TEST(StatsEventTest, TestStrings) { | |
| uint32_t atomId = 100; | |
| string str = "test_string"; | |
| int64_t startTime = android::elapsedRealtimeNano(); | |
| AStatsEvent* event = AStatsEvent_obtain(); | |
| AStatsEvent_setAtomId(event, atomId); | |
| AStatsEvent_writeString(event, str.c_str()); | |
| AStatsEvent_build(event); | |
| int64_t endTime = android::elapsedRealtimeNano(); | |
| size_t bufferSize; | |
| uint8_t* buffer = AStatsEvent_getBuffer(event, &bufferSize); | |
| uint8_t* bufferEnd = buffer + bufferSize; | |
| checkMetadata(&buffer, /*numElements=*/1, startTime, endTime, atomId); | |
| checkTypeHeader(&buffer, STRING_TYPE); | |
| checkString(&buffer, str); | |
| EXPECT_EQ(buffer, bufferEnd); // ensure that we have read the entire buffer | |
| EXPECT_EQ(AStatsEvent_getErrors(event), 0); | |
| AStatsEvent_release(event); | |
| } | |
| TEST(StatsEventTest, TestNullString) { | |
| uint32_t atomId = 100; | |
| char* str = nullptr; | |
| int64_t startTime = android::elapsedRealtimeNano(); | |
| AStatsEvent* event = AStatsEvent_obtain(); | |
| AStatsEvent_setAtomId(event, atomId); | |
| AStatsEvent_writeString(event, str); | |
| AStatsEvent_build(event); | |
| int64_t endTime = android::elapsedRealtimeNano(); | |
| size_t bufferSize; | |
| uint8_t* buffer = AStatsEvent_getBuffer(event, &bufferSize); | |
| uint8_t* bufferEnd = buffer + bufferSize; | |
| checkMetadata(&buffer, /*numElements=*/1, startTime, endTime, atomId); | |
| checkTypeHeader(&buffer, STRING_TYPE); | |
| checkString(&buffer, ""); | |
| EXPECT_EQ(buffer, bufferEnd); // ensure that we have read the entire buffer | |
| EXPECT_EQ(AStatsEvent_getErrors(event), 0); | |
| AStatsEvent_release(event); | |
| } | |
| TEST(StatsEventTest, TestByteArrays) { | |
| uint32_t atomId = 100; | |
| vector<uint8_t> message = {'b', 'y', 't', '\0', 'e', 's'}; | |
| int64_t startTime = android::elapsedRealtimeNano(); | |
| AStatsEvent* event = AStatsEvent_obtain(); | |
| AStatsEvent_setAtomId(event, atomId); | |
| AStatsEvent_writeByteArray(event, message.data(), message.size()); | |
| AStatsEvent_build(event); | |
| int64_t endTime = android::elapsedRealtimeNano(); | |
| size_t bufferSize; | |
| uint8_t* buffer = AStatsEvent_getBuffer(event, &bufferSize); | |
| uint8_t* bufferEnd = buffer + bufferSize; | |
| checkMetadata(&buffer, /*numElements=*/1, startTime, endTime, atomId); | |
| checkTypeHeader(&buffer, BYTE_ARRAY_TYPE); | |
| checkByteArray(&buffer, message); | |
| EXPECT_EQ(buffer, bufferEnd); // ensure that we have read the entire buffer | |
| EXPECT_EQ(AStatsEvent_getErrors(event), 0); | |
| AStatsEvent_release(event); | |
| } | |
| TEST(StatsEventTest, TestNullByteArrays) { | |
| uint32_t atomId = 100; | |
| uint8_t* buf = nullptr; | |
| vector<uint8_t> message; | |
| int64_t startTime = android::elapsedRealtimeNano(); | |
| AStatsEvent* event = AStatsEvent_obtain(); | |
| AStatsEvent_setAtomId(event, atomId); | |
| AStatsEvent_writeByteArray(event, buf, 2); | |
| AStatsEvent_build(event); | |
| int64_t endTime = android::elapsedRealtimeNano(); | |
| size_t bufferSize; | |
| uint8_t* buffer = AStatsEvent_getBuffer(event, &bufferSize); | |
| uint8_t* bufferEnd = buffer + bufferSize; | |
| checkMetadata(&buffer, /*numElements=*/1, startTime, endTime, atomId); | |
| checkTypeHeader(&buffer, BYTE_ARRAY_TYPE); | |
| checkByteArray(&buffer, message); | |
| EXPECT_EQ(buffer, bufferEnd); // ensure that we have read the entire buffer | |
| EXPECT_EQ(AStatsEvent_getErrors(event), 0); | |
| AStatsEvent_release(event); | |
| } | |
| TEST(StatsEventTest, TestAllArrays) { | |
| uint32_t atomId = 100; | |
| uint8_t numElements = 3; | |
| int32_t int32Array[3] = {3, 6, 9}; | |
| int64_t int64Array[3] = {1000L, 1001L, 1002L}; | |
| float floatArray[3] = {0.1f, 0.3f, 0.09f}; | |
| bool boolArray[3] = {0, 1, 1}; | |
| vector<string> stringArray = {"str1", "str2", "str3"}; | |
| const char* cStringArray[3]; | |
| for (int i = 0; i < numElements; i++) { | |
| cStringArray[i] = stringArray[i].c_str(); | |
| } | |
| int64_t startTime = android::elapsedRealtimeNano(); | |
| AStatsEvent* event = AStatsEvent_obtain(); | |
| AStatsEvent_setAtomId(event, atomId); | |
| AStatsEvent_writeInt32Array(event, int32Array, numElements); | |
| AStatsEvent_writeInt64Array(event, int64Array, numElements); | |
| AStatsEvent_writeFloatArray(event, floatArray, numElements); | |
| AStatsEvent_writeBoolArray(event, boolArray, numElements); | |
| AStatsEvent_writeStringArray(event, cStringArray, numElements); | |
| AStatsEvent_build(event); | |
| int64_t endTime = android::elapsedRealtimeNano(); | |
| size_t bufferSize; | |
| uint8_t* buffer = AStatsEvent_getBuffer(event, &bufferSize); | |
| uint8_t* bufferEnd = buffer + bufferSize; | |
| checkMetadata(&buffer, /*numTopLevelElements=*/5, startTime, endTime, atomId); | |
| // check int32Array element | |
| checkScalarArray(&buffer, numElements, INT32_TYPE, int32Array); | |
| // check int64Array element | |
| checkScalarArray(&buffer, numElements, INT64_TYPE, int64Array); | |
| // check floatArray element | |
| checkScalarArray(&buffer, numElements, FLOAT_TYPE, floatArray); | |
| // check boolArray element | |
| checkScalarArray(&buffer, numElements, BOOL_TYPE, boolArray); | |
| // check stringArray element | |
| checkArrayMetadata(&buffer, numElements, STRING_TYPE); | |
| for (int i = 0; i < numElements; i++) { | |
| checkString(&buffer, stringArray[i]); | |
| } | |
| EXPECT_EQ(buffer, bufferEnd); // ensure that we have read the entire buffer | |
| EXPECT_EQ(AStatsEvent_getErrors(event), 0); | |
| AStatsEvent_release(event); | |
| } | |
| TEST(StatsEventTest, TestAttributionChains) { | |
| uint32_t atomId = 100; | |
| uint8_t numNodes = 50; | |
| uint32_t uids[numNodes]; | |
| vector<string> tags(numNodes); // storage that cTag elements point to | |
| const char* cTags[numNodes]; | |
| for (int i = 0; i < (int)numNodes; i++) { | |
| uids[i] = i; | |
| if (0 == i) { | |
| tags.push_back(""); | |
| cTags[i] = nullptr; | |
| } else { | |
| tags.push_back("test" + std::to_string(i)); | |
| cTags[i] = tags[i].c_str(); | |
| } | |
| } | |
| int64_t startTime = android::elapsedRealtimeNano(); | |
| AStatsEvent* event = AStatsEvent_obtain(); | |
| AStatsEvent_setAtomId(event, atomId); | |
| AStatsEvent_writeAttributionChain(event, uids, cTags, numNodes); | |
| AStatsEvent_build(event); | |
| int64_t endTime = android::elapsedRealtimeNano(); | |
| size_t bufferSize; | |
| uint8_t* buffer = AStatsEvent_getBuffer(event, &bufferSize); | |
| uint8_t* bufferEnd = buffer + bufferSize; | |
| checkMetadata(&buffer, /*numElements=*/1, startTime, endTime, atomId); | |
| checkTypeHeader(&buffer, ATTRIBUTION_CHAIN_TYPE); | |
| checkScalar(&buffer, numNodes); | |
| for (int i = 0; i < numNodes; i++) { | |
| checkScalar(&buffer, uids[i]); | |
| checkString(&buffer, tags[i]); | |
| } | |
| EXPECT_EQ(buffer, bufferEnd); // ensure that we have read the entire buffer | |
| EXPECT_EQ(AStatsEvent_getErrors(event), 0); | |
| AStatsEvent_release(event); | |
| } | |
| TEST(StatsEventTest, TestFieldAnnotations) { | |
| uint32_t atomId = 100; | |
| // first element information | |
| bool boolValue = false; | |
| uint8_t boolAnnotation1Id = 1; | |
| uint8_t boolAnnotation2Id = 2; | |
| bool boolAnnotation1Value = true; | |
| int32_t boolAnnotation2Value = 3; | |
| // second element information | |
| float floatValue = -5.0; | |
| uint8_t floatAnnotation1Id = 3; | |
| uint8_t floatAnnotation2Id = 4; | |
| int32_t floatAnnotation1Value = 8; | |
| bool floatAnnotation2Value = false; | |
| int64_t startTime = android::elapsedRealtimeNano(); | |
| AStatsEvent* event = AStatsEvent_obtain(); | |
| AStatsEvent_setAtomId(event, atomId); | |
| AStatsEvent_writeBool(event, boolValue); | |
| AStatsEvent_addBoolAnnotation(event, boolAnnotation1Id, boolAnnotation1Value); | |
| AStatsEvent_addInt32Annotation(event, boolAnnotation2Id, boolAnnotation2Value); | |
| AStatsEvent_writeFloat(event, floatValue); | |
| AStatsEvent_addInt32Annotation(event, floatAnnotation1Id, floatAnnotation1Value); | |
| AStatsEvent_addBoolAnnotation(event, floatAnnotation2Id, floatAnnotation2Value); | |
| AStatsEvent_build(event); | |
| int64_t endTime = android::elapsedRealtimeNano(); | |
| size_t bufferSize; | |
| uint8_t* buffer = AStatsEvent_getBuffer(event, &bufferSize); | |
| uint8_t* bufferEnd = buffer + bufferSize; | |
| checkMetadata(&buffer, /*numElements=*/2, startTime, endTime, atomId); | |
| // check first element | |
| checkTypeHeader(&buffer, BOOL_TYPE, /*numAnnotations=*/2); | |
| checkScalar(&buffer, boolValue); | |
| checkAnnotation(&buffer, boolAnnotation1Id, BOOL_TYPE, boolAnnotation1Value); | |
| checkAnnotation(&buffer, boolAnnotation2Id, INT32_TYPE, boolAnnotation2Value); | |
| // check second element | |
| checkTypeHeader(&buffer, FLOAT_TYPE, /*numAnnotations=*/2); | |
| checkScalar(&buffer, floatValue); | |
| checkAnnotation(&buffer, floatAnnotation1Id, INT32_TYPE, floatAnnotation1Value); | |
| checkAnnotation(&buffer, floatAnnotation2Id, BOOL_TYPE, floatAnnotation2Value); | |
| EXPECT_EQ(buffer, bufferEnd); // ensure that we have read the entire buffer | |
| EXPECT_EQ(AStatsEvent_getErrors(event), 0); | |
| AStatsEvent_release(event); | |
| } | |
| TEST(StatsEventTest, TestArrayFieldAnnotations) { | |
| uint32_t atomId = 100; | |
| // array annotation info | |
| uint8_t boolAnnotationId = 1; | |
| uint8_t int32AnnotationId = 2; | |
| bool boolAnnotationValue = true; | |
| int32_t int32AnnotationValue = 4; | |
| uint8_t numElements = 3; | |
| int32_t int32Array[3] = {3, 6, 9}; | |
| int64_t startTime = android::elapsedRealtimeNano(); | |
| AStatsEvent* event = AStatsEvent_obtain(); | |
| AStatsEvent_setAtomId(event, atomId); | |
| AStatsEvent_writeInt32Array(event, int32Array, numElements); | |
| AStatsEvent_addBoolAnnotation(event, boolAnnotationId, boolAnnotationValue); | |
| AStatsEvent_addInt32Annotation(event, int32AnnotationId, int32AnnotationValue); | |
| AStatsEvent_build(event); | |
| int64_t endTime = android::elapsedRealtimeNano(); | |
| size_t bufferSize; | |
| uint8_t* buffer = AStatsEvent_getBuffer(event, &bufferSize); | |
| uint8_t* bufferEnd = buffer + bufferSize; | |
| checkMetadata(&buffer, /*numElements=*/1, startTime, endTime, atomId); | |
| // check first element | |
| checkScalarArray(&buffer, numElements, INT32_TYPE, int32Array, /*numAnnotations=*/2); | |
| checkAnnotation(&buffer, boolAnnotationId, BOOL_TYPE, boolAnnotationValue); | |
| checkAnnotation(&buffer, int32AnnotationId, INT32_TYPE, int32AnnotationValue); | |
| EXPECT_EQ(buffer, bufferEnd); // ensure that we have read the entire buffer | |
| EXPECT_EQ(AStatsEvent_getErrors(event), 0); | |
| AStatsEvent_release(event); | |
| } | |
| TEST(StatsEventTest, TestAtomLevelAnnotations) { | |
| uint32_t atomId = 100; | |
| // atom-level annotation information | |
| uint8_t boolAnnotationId = 1; | |
| uint8_t int32AnnotationId = 2; | |
| bool boolAnnotationValue = false; | |
| int32_t int32AnnotationValue = 5; | |
| float fieldValue = -3.5; | |
| int64_t startTime = android::elapsedRealtimeNano(); | |
| AStatsEvent* event = AStatsEvent_obtain(); | |
| AStatsEvent_setAtomId(event, atomId); | |
| AStatsEvent_addBoolAnnotation(event, boolAnnotationId, boolAnnotationValue); | |
| AStatsEvent_addInt32Annotation(event, int32AnnotationId, int32AnnotationValue); | |
| AStatsEvent_writeFloat(event, fieldValue); | |
| AStatsEvent_build(event); | |
| int64_t endTime = android::elapsedRealtimeNano(); | |
| size_t bufferSize; | |
| uint8_t* buffer = AStatsEvent_getBuffer(event, &bufferSize); | |
| uint8_t* bufferEnd = buffer + bufferSize; | |
| checkMetadata(&buffer, /*numElements=*/1, startTime, endTime, atomId, | |
| /*numAtomLevelAnnotations=*/2); | |
| // check atom-level annotations | |
| checkAnnotation(&buffer, boolAnnotationId, BOOL_TYPE, boolAnnotationValue); | |
| checkAnnotation(&buffer, int32AnnotationId, INT32_TYPE, int32AnnotationValue); | |
| // check first element | |
| checkTypeHeader(&buffer, FLOAT_TYPE); | |
| checkScalar(&buffer, fieldValue); | |
| EXPECT_EQ(buffer, bufferEnd); // ensure that we have read the entire buffer | |
| EXPECT_EQ(AStatsEvent_getErrors(event), 0); | |
| AStatsEvent_release(event); | |
| } | |
| TEST(StatsEventTest, TestNoAtomIdError) { | |
| AStatsEvent* event = AStatsEvent_obtain(); | |
| // Don't set the atom id in order to trigger the error. | |
| AStatsEvent_build(event); | |
| uint32_t errors = AStatsEvent_getErrors(event); | |
| EXPECT_EQ(errors & ERROR_NO_ATOM_ID, ERROR_NO_ATOM_ID); | |
| AStatsEvent_release(event); | |
| } | |
| TEST(StatsEventTest, TestPushOverflowError) { | |
| const char* str = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; | |
| const int writeCount = 120; // Number of times to write str in the event. | |
| AStatsEvent* event = AStatsEvent_obtain(); | |
| AStatsEvent_setAtomId(event, 100); | |
| // Add str to the event 120 times. Each str takes >35 bytes so this will | |
| // overflow the 4068 byte buffer. | |
| // We want to keep writeCount less than 127 to avoid hitting | |
| // ERROR_TOO_MANY_FIELDS. | |
| for (int i = 0; i < writeCount; i++) { | |
| AStatsEvent_writeString(event, str); | |
| } | |
| AStatsEvent_write(event); | |
| uint32_t errors = AStatsEvent_getErrors(event); | |
| EXPECT_EQ(errors & ERROR_OVERFLOW, ERROR_OVERFLOW); | |
| AStatsEvent_release(event); | |
| } | |
| TEST(StatsEventTest, TestPullOverflowError) { | |
| const uint32_t atomId = 10100; | |
| const vector<uint8_t> bytes(430 /* number of elements */, 1 /* value of each element */); | |
| const int writeCount = 120; // Number of times to write bytes in the event. | |
| AStatsEvent* event = AStatsEvent_obtain(); | |
| AStatsEvent_setAtomId(event, atomId); | |
| // Add bytes to the event 120 times. Size of bytes is 430 so this will | |
| // overflow the 50 KB pulled event buffer. | |
| // We want to keep writeCount less than 127 to avoid hitting | |
| // ERROR_TOO_MANY_FIELDS. | |
| for (int i = 0; i < writeCount; i++) { | |
| AStatsEvent_writeByteArray(event, bytes.data(), bytes.size()); | |
| } | |
| AStatsEvent_build(event); | |
| uint32_t errors = AStatsEvent_getErrors(event); | |
| EXPECT_EQ(errors & ERROR_OVERFLOW, ERROR_OVERFLOW); | |
| AStatsEvent_release(event); | |
| } | |
| TEST(StatsEventTest, TestLargePull) { | |
| const uint32_t atomId = 100; | |
| const string str = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; | |
| const int writeCount = 120; // Number of times to write str in the event. | |
| const int64_t startTime = android::elapsedRealtimeNano(); | |
| AStatsEvent* event = AStatsEvent_obtain(); | |
| AStatsEvent_setAtomId(event, atomId); | |
| // Add str to the event 120 times. | |
| // We want to keep writeCount less than 127 to avoid hitting | |
| // ERROR_TOO_MANY_FIELDS. | |
| for (int i = 0; i < writeCount; i++) { | |
| AStatsEvent_writeString(event, str.c_str()); | |
| } | |
| AStatsEvent_build(event); | |
| int64_t endTime = android::elapsedRealtimeNano(); | |
| size_t bufferSize; | |
| uint8_t* buffer = AStatsEvent_getBuffer(event, &bufferSize); | |
| uint8_t* bufferEnd = buffer + bufferSize; | |
| checkMetadata(&buffer, writeCount, startTime, endTime, atomId); | |
| // Check all instances of str have been written. | |
| for (int i = 0; i < writeCount; i++) { | |
| checkTypeHeader(&buffer, STRING_TYPE); | |
| checkString(&buffer, str); | |
| } | |
| EXPECT_EQ(buffer, bufferEnd); // Ensure that we have read the entire buffer. | |
| EXPECT_EQ(AStatsEvent_getErrors(event), 0); | |
| AStatsEvent_release(event); | |
| } | |
| TEST(StatsEventTest, TestAtomIdInvalidPositionError) { | |
| AStatsEvent* event = AStatsEvent_obtain(); | |
| AStatsEvent_writeInt32(event, 0); | |
| AStatsEvent_setAtomId(event, 100); | |
| AStatsEvent_writeBool(event, true); | |
| AStatsEvent_build(event); | |
| uint32_t errors = AStatsEvent_getErrors(event); | |
| EXPECT_EQ(errors & ERROR_ATOM_ID_INVALID_POSITION, ERROR_ATOM_ID_INVALID_POSITION); | |
| AStatsEvent_release(event); | |
| } | |
| TEST(StatsEventTest, TestOverwriteTimestamp) { | |
| uint32_t atomId = 100; | |
| int64_t expectedTimestamp = 0x123456789; | |
| AStatsEvent* event = AStatsEvent_obtain(); | |
| AStatsEvent_setAtomId(event, atomId); | |
| AStatsEvent_overwriteTimestamp(event, expectedTimestamp); | |
| AStatsEvent_build(event); | |
| uint8_t* buffer = AStatsEvent_getBuffer(event, NULL); | |
| // Make sure that the timestamp is being overwritten. | |
| checkMetadata(&buffer, /*numElements=*/0, /*startTime=*/expectedTimestamp, | |
| /*endTime=*/expectedTimestamp, atomId); | |
| EXPECT_EQ(AStatsEvent_getErrors(event), 0); | |
| AStatsEvent_release(event); | |
| } | |
| TEST(StatsEventTest, TestAttributionChainTooLongError) { | |
| uint32_t atomId = 100; | |
| uint8_t numNodes = 128; | |
| uint32_t uids[numNodes]; | |
| vector<string> tags(numNodes); // storage that cTag elements point to | |
| const char* cTags[numNodes]; | |
| for (int i = 0; i < (int)numNodes; i++) { | |
| uids[i] = i; | |
| if (0 == i) { | |
| tags.push_back(""); | |
| cTags[i] = nullptr; | |
| } else { | |
| tags.push_back("test" + std::to_string(i)); | |
| cTags[i] = tags[i].c_str(); | |
| } | |
| } | |
| AStatsEvent* event = AStatsEvent_obtain(); | |
| AStatsEvent_setAtomId(event, atomId); | |
| AStatsEvent_writeAttributionChain(event, uids, cTags, numNodes); | |
| AStatsEvent_build(event); | |
| uint32_t errors = AStatsEvent_getErrors(event); | |
| EXPECT_EQ(errors & ERROR_ATTRIBUTION_CHAIN_TOO_LONG, ERROR_ATTRIBUTION_CHAIN_TOO_LONG); | |
| } | |
| TEST(StatsEventTest, TestListTooLongError) { | |
| uint32_t atomId = 100; | |
| uint8_t numElements = 128; | |
| int32_t int32Array[128] = {1}; | |
| AStatsEvent* event = AStatsEvent_obtain(); | |
| AStatsEvent_setAtomId(event, atomId); | |
| AStatsEvent_writeInt32Array(event, int32Array, numElements); | |
| AStatsEvent_build(event); | |
| uint32_t errors = AStatsEvent_getErrors(event); | |
| EXPECT_EQ(errors & ERROR_LIST_TOO_LONG, ERROR_LIST_TOO_LONG); | |
| } |