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
This commit is contained in:
Ruchir Rastogi 2020-01-07 09:36:04 -08:00
parent 929c9e8b40
commit c6e6c44e50
2 changed files with 363 additions and 0 deletions

View file

@ -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"],
}

View file

@ -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 <gtest/gtest.h>
#include <utils/SystemClock.h>
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 = *(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<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
}
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) {
// 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);
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<uint8_t> 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<string> 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);
}