From c6e6c44e5096ee08a85e90beb107b3195461d34d Mon Sep 17 00:00:00 2001 From: Ruchir Rastogi Date: Tue, 7 Jan 2020 09:36:04 -0800 Subject: [PATCH] stats_event.h/c tests Add unit tests for the native API to log atoms to statsd. (This CL is being cherrypicked into AOSP in order to prevent automerger issues with the rename from libstatssocket to libstatspush.) Test: bit libstatssocket_test:* Bug: 145231901 Change-Id: If427c17319787200260cbe3b71075ca556c9a82b Merged-In: If427c17319787200260cbe3b71075ca556c9a82b --- libstats/socket/Android.bp | 19 ++ libstats/socket/tests/stats_event_test.cpp | 344 +++++++++++++++++++++ 2 files changed, 363 insertions(+) create mode 100644 libstats/socket/tests/stats_event_test.cpp diff --git a/libstats/socket/Android.bp b/libstats/socket/Android.bp index 3b6efbb55..9fd9fbc20 100644 --- a/libstats/socket/Android.bp +++ b/libstats/socket/Android.bp @@ -75,3 +75,22 @@ cc_benchmark { "libgtest_prod", ], } + +cc_test { + name: "libstatssocket_test", + srcs: ["tests/stats_event_test.cpp"], + cflags: [ + "-Wall", + "-Werror", + ], + static_libs: [ + "libgmock", + "libstatssocket", + ], + shared_libs: [ + "libcutils", + "liblog", + "libutils", + ], + test_suites: ["device_tests"], +} diff --git a/libstats/socket/tests/stats_event_test.cpp b/libstats/socket/tests/stats_event_test.cpp new file mode 100644 index 000000000..cf0592c3a --- /dev/null +++ b/libstats/socket/tests/stats_event_test.cpp @@ -0,0 +1,344 @@ +/* + * 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 +#include + +using std::string; +using std::vector; + +// Side-effect: this function moves the start of the buffer past the read value +template +T readNext(uint8_t** buffer) { + T value = *(T*)(*buffer); + *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(buffer), typeHeader); +} + +template +void checkScalar(uint8_t** buffer, T expectedValue) { + EXPECT_EQ(readNext(buffer), expectedValue); +} + +void checkString(uint8_t** buffer, const string& expectedString) { + uint32_t size = readNext(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& expectedByteArray) { + uint32_t size = readNext(buffer); + vector parsedByteArray(*buffer, *buffer + size); + EXPECT_EQ(parsedByteArray, expectedByteArray); + *buffer += size; // move buffer past byte array we just read +} + +template +void checkAnnotation(uint8_t** buffer, uint8_t annotationId, uint8_t typeId, T annotationValue) { + EXPECT_EQ(readNext(buffer), annotationId); + EXPECT_EQ(readNext(buffer), typeId); + checkScalar(buffer, annotationValue); +} + +void checkMetadata(uint8_t** buffer, uint8_t numElements, int64_t startTime, int64_t endTime, + uint32_t atomId) { + // 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(numElements + 2)); + + // Check timestamp + checkTypeHeader(buffer, INT64_TYPE); + int64_t timestamp = readNext(buffer); + EXPECT_GE(timestamp, startTime); + EXPECT_LE(timestamp, endTime); + + // Check atom id + checkTypeHeader(buffer, INT32_TYPE); + 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(); + struct stats_event* event = stats_event_obtain(); + stats_event_set_atom_id(event, atomId); + stats_event_write_int32(event, int32Value); + stats_event_write_int64(event, int64Value); + stats_event_write_float(event, floatValue); + stats_event_write_bool(event, boolValue); + stats_event_build(event); + int64_t endTime = android::elapsedRealtimeNano(); + + size_t bufferSize; + uint8_t* buffer = stats_event_get_buffer(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(stats_event_get_errors(event), 0); + stats_event_release(event); +} + +TEST(StatsEventTest, TestStrings) { + uint32_t atomId = 100; + string str = "test_string"; + + int64_t startTime = android::elapsedRealtimeNano(); + struct stats_event* event = stats_event_obtain(); + stats_event_set_atom_id(event, atomId); + stats_event_write_string8(event, str.c_str()); + stats_event_build(event); + int64_t endTime = android::elapsedRealtimeNano(); + + size_t bufferSize; + uint8_t* buffer = stats_event_get_buffer(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(stats_event_get_errors(event), 0); + stats_event_release(event); +} + +TEST(StatsEventTest, TestByteArrays) { + uint32_t atomId = 100; + vector message = {'b', 'y', 't', '\0', 'e', 's'}; + + int64_t startTime = android::elapsedRealtimeNano(); + struct stats_event* event = stats_event_obtain(); + stats_event_set_atom_id(event, atomId); + stats_event_write_byte_array(event, message.data(), message.size()); + stats_event_build(event); + int64_t endTime = android::elapsedRealtimeNano(); + + size_t bufferSize; + uint8_t* buffer = stats_event_get_buffer(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(stats_event_get_errors(event), 0); + stats_event_release(event); +} + +TEST(StatsEventTest, TestAttributionChains) { + uint32_t atomId = 100; + + uint8_t numNodes = 50; + uint32_t uids[numNodes]; + vector tags(numNodes); // storage that cTag elements point to + const char* cTags[numNodes]; + for (int i = 0; i < (int)numNodes; i++) { + uids[i] = i; + tags.push_back("test" + std::to_string(i)); + cTags[i] = tags[i].c_str(); + } + + int64_t startTime = android::elapsedRealtimeNano(); + struct stats_event* event = stats_event_obtain(); + stats_event_set_atom_id(event, atomId); + stats_event_write_attribution_chain(event, uids, cTags, numNodes); + stats_event_build(event); + int64_t endTime = android::elapsedRealtimeNano(); + + size_t bufferSize; + uint8_t* buffer = stats_event_get_buffer(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(stats_event_get_errors(event), 0); + stats_event_release(event); +} + +TEST(StatsEventTest, TestKeyValuePairs) { + uint32_t atomId = 100; + + uint8_t numPairs = 4; + struct key_value_pair pairs[numPairs]; + pairs[0] = {.key = 0, .valueType = INT32_TYPE, .int32Value = -1}; + pairs[1] = {.key = 1, .valueType = INT64_TYPE, .int64Value = 0x123456789}; + pairs[2] = {.key = 2, .valueType = FLOAT_TYPE, .floatValue = 5.5}; + string str = "test_key_value_pair_string"; + pairs[3] = {.key = 3, .valueType = STRING_TYPE, .stringValue = str.c_str()}; + + int64_t startTime = android::elapsedRealtimeNano(); + struct stats_event* event = stats_event_obtain(); + stats_event_set_atom_id(event, atomId); + stats_event_write_key_value_pairs(event, pairs, numPairs); + stats_event_build(event); + int64_t endTime = android::elapsedRealtimeNano(); + + size_t bufferSize; + uint8_t* buffer = stats_event_get_buffer(event, &bufferSize); + uint8_t* bufferEnd = buffer + bufferSize; + + checkMetadata(&buffer, /*numElements=*/1, startTime, endTime, atomId); + + checkTypeHeader(&buffer, KEY_VALUE_PAIRS_TYPE); + checkScalar(&buffer, numPairs); + + // first pair + checkScalar(&buffer, pairs[0].key); + checkTypeHeader(&buffer, pairs[0].valueType); + checkScalar(&buffer, pairs[0].int32Value); + + // second pair + checkScalar(&buffer, pairs[1].key); + checkTypeHeader(&buffer, pairs[1].valueType); + checkScalar(&buffer, pairs[1].int64Value); + + // third pair + checkScalar(&buffer, pairs[2].key); + checkTypeHeader(&buffer, pairs[2].valueType); + checkScalar(&buffer, pairs[2].floatValue); + + // fourth pair + checkScalar(&buffer, pairs[3].key); + checkTypeHeader(&buffer, pairs[3].valueType); + checkString(&buffer, str); + + EXPECT_EQ(buffer, bufferEnd); // ensure that we have read the entire buffer + EXPECT_EQ(stats_event_get_errors(event), 0); + stats_event_release(event); +} + +TEST(StatsEventTest, TestAnnotations) { + 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(); + struct stats_event* event = stats_event_obtain(); + stats_event_set_atom_id(event, 100); + stats_event_write_bool(event, boolValue); + stats_event_add_bool_annotation(event, boolAnnotation1Id, boolAnnotation1Value); + stats_event_add_int32_annotation(event, boolAnnotation2Id, boolAnnotation2Value); + stats_event_write_float(event, floatValue); + stats_event_add_int32_annotation(event, floatAnnotation1Id, floatAnnotation1Value); + stats_event_add_bool_annotation(event, floatAnnotation2Id, floatAnnotation2Value); + stats_event_build(event); + int64_t endTime = android::elapsedRealtimeNano(); + + size_t bufferSize; + uint8_t* buffer = stats_event_get_buffer(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(stats_event_get_errors(event), 0); + stats_event_release(event); +} + +TEST(StatsEventTest, TestNoAtomIdError) { + struct stats_event* event = stats_event_obtain(); + // Don't set the atom id in order to trigger the error. + stats_event_build(event); + + uint32_t errors = stats_event_get_errors(event); + EXPECT_NE(errors | ERROR_NO_ATOM_ID, 0); + + stats_event_release(event); +} + +TEST(StatsEventTest, TestOverflowError) { + struct stats_event* event = stats_event_obtain(); + stats_event_set_atom_id(event, 100); + // Add 1000 int32s to the event. Each int32 takes 5 bytes so this will + // overflow the 4068 byte buffer. + for (int i = 0; i < 1000; i++) { + stats_event_write_int32(event, 0); + } + stats_event_build(event); + + uint32_t errors = stats_event_get_errors(event); + EXPECT_NE(errors | ERROR_OVERFLOW, 0); + + stats_event_release(event); +}