stats_event.h/c tests

Add unit tests for the native API to log atoms to statsd.

Test: bit libstatssocket_test:*
Bug: 145231901
Change-Id: If427c17319787200260cbe3b71075ca556c9a82b
diff --git a/libstats/socket/Android.bp b/libstats/socket/Android.bp
index 3b6efbb..9fd9fbc 100644
--- a/libstats/socket/Android.bp
+++ b/libstats/socket/Android.bp
@@ -75,3 +75,22 @@
         "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 0000000..cf0592c
--- /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 <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);
+}