// Copyright (C) 2021 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 "src/metrics/KllMetricProducer.h"

#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <math.h>
#include <stdio.h>

#include <vector>

#include "metrics_test_helper.h"
#include "src/FieldValue.h"
#include "src/matchers/SimpleAtomMatchingTracker.h"
#include "src/metrics/MetricProducer.h"
#include "src/stats_log_util.h"
#include "tests/statsd_test_util.h"

using namespace testing;
using android::sp;
using dist_proc::aggregation::KllQuantile;
using std::make_shared;
using std::optional;
using std::set;
using std::shared_ptr;
using std::unique_ptr;
using std::unordered_map;
using std::vector;

#ifdef __ANDROID__

namespace android {
namespace os {
namespace statsd {

namespace {

const ConfigKey kConfigKey(0, 12345);
const int atomId = 1;
const int64_t metricId = 123;
const uint64_t protoHash = 0x1234567890;
const int logEventMatcherIndex = 0;
const int64_t bucketStartTimeNs = 10000000000;
const int64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(ONE_MINUTE) * 1000000LL;
const int64_t bucket2StartTimeNs = bucketStartTimeNs + bucketSizeNs;
const int64_t bucket3StartTimeNs = bucketStartTimeNs + 2 * bucketSizeNs;
const int64_t bucket4StartTimeNs = bucketStartTimeNs + 3 * bucketSizeNs;
const int64_t bucket5StartTimeNs = bucketStartTimeNs + 4 * bucketSizeNs;
const int64_t bucket6StartTimeNs = bucketStartTimeNs + 5 * bucketSizeNs;

static void assertPastBucketsSingleKey(
        const std::unordered_map<MetricDimensionKey,
                                 std::vector<PastBucket<unique_ptr<KllQuantile>>>>& mPastBuckets,
        const std::initializer_list<int>& expectedKllCountsList,
        const std::initializer_list<int64_t>& expectedDurationNsList,
        const std::initializer_list<int64_t>& expectedStartTimeNsList,
        const std::initializer_list<int64_t>& expectedEndTimeNsList) {
    vector<int> expectedKllCounts(expectedKllCountsList);
    vector<int64_t> expectedDurationNs(expectedDurationNsList);
    vector<int64_t> expectedStartTimeNs(expectedStartTimeNsList);
    vector<int64_t> expectedEndTimeNs(expectedEndTimeNsList);

    ASSERT_EQ(expectedKllCounts.size(), expectedDurationNs.size());
    ASSERT_EQ(expectedKllCounts.size(), expectedStartTimeNs.size());
    ASSERT_EQ(expectedKllCounts.size(), expectedEndTimeNs.size());

    if (expectedKllCounts.size() == 0) {
        ASSERT_EQ(0, mPastBuckets.size());
        return;
    }

    ASSERT_EQ(1, mPastBuckets.size());
    const vector<PastBucket<unique_ptr<KllQuantile>>>& buckets = mPastBuckets.begin()->second;
    ASSERT_EQ(expectedKllCounts.size(), buckets.size());

    for (int i = 0; i < expectedKllCounts.size(); i++) {
        EXPECT_EQ(expectedKllCounts[i], buckets[i].aggregates[0]->num_values())
                << "Number of entries in KLL sketch differ at index " << i;
        EXPECT_EQ(expectedDurationNs[i], buckets[i].mConditionTrueNs)
                << "Condition duration value differ at index " << i;
        EXPECT_EQ(expectedStartTimeNs[i], buckets[i].mBucketStartNs)
                << "Start time differs at index " << i;
        EXPECT_EQ(expectedEndTimeNs[i], buckets[i].mBucketEndNs)
                << "End time differs at index " << i;
    }
}

StatsLogReport onDumpReport(sp<KllMetricProducer>& producer, int64_t dumpTimeNs,
                            bool includeCurrentBucket) {
    ProtoOutputStream output;
    set<int32_t> usedUids;
    producer->onDumpReport(dumpTimeNs, includeCurrentBucket, true /*erase data*/, FAST, nullptr,
                           usedUids, &output);
    return outputStreamToProto(&output);
}

}  // anonymous namespace

class KllMetricProducerTestHelper {
public:
    static sp<KllMetricProducer> createKllProducerNoConditions(const KllMetric& metric) {
        return createKllProducer(metric);
    }

    static sp<KllMetricProducer> createKllProducerWithCondition(
            const KllMetric& metric, const ConditionState& initialCondition) {
        return createKllProducer(metric, initialCondition);
    }

    static sp<KllMetricProducer> createKllProducer(
            const KllMetric& metric, optional<ConditionState> initialCondition = nullopt,
            vector<int32_t> slicedStateAtoms = {},
            unordered_map<int, unordered_map<int, int64_t>> stateGroupMap = {},
            const int64_t timeBaseNs = bucketStartTimeNs,
            const int64_t startTimeNs = bucketStartTimeNs) {
        sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
        const int64_t bucketSizeNs = MillisToNano(
                TimeUnitToBucketSizeInMillisGuardrailed(kConfigKey.GetUid(), metric.bucket()));
        const bool containsAnyPositionInDimensionsInWhat =
                HasPositionANY(metric.dimensions_in_what());
        const bool shouldUseNestedDimensions =
                ShouldUseNestedDimensions(metric.dimensions_in_what());

        vector<Matcher> fieldMatchers;
        translateFieldMatcher(metric.kll_field(), &fieldMatchers);

        const auto [dimensionSoftLimit, dimensionHardLimit] =
                StatsdStats::getAtomDimensionKeySizeLimits(
                        atomId, StatsdStats::kDimensionKeySizeHardLimitMin);

        int conditionIndex = initialCondition ? 0 : -1;
        vector<ConditionState> initialConditionCache;
        if (initialCondition) {
            initialConditionCache.push_back(initialCondition.value());
        }

        sp<MockConfigMetadataProvider> provider = makeMockConfigMetadataProvider(/*enabled=*/false);
        return new KllMetricProducer(
                kConfigKey, metric, protoHash, {/*pullAtomId=*/-1, /*pullerManager=*/nullptr},
                {timeBaseNs, startTimeNs, bucketSizeNs, metric.min_bucket_size_nanos(),
                 /*conditionCorrectionThresholdNs=*/nullopt, metric.split_bucket_for_app_upgrade()},
                {containsAnyPositionInDimensionsInWhat, shouldUseNestedDimensions,
                 logEventMatcherIndex,
                 /*eventMatcherWizard=*/nullptr, metric.dimensions_in_what(), fieldMatchers},
                {conditionIndex, metric.links(), initialConditionCache, wizard},
                {metric.state_link(), slicedStateAtoms, stateGroupMap},
                {/*eventActivationMap=*/{}, /*eventDeactivationMap=*/{}},
                {dimensionSoftLimit, dimensionHardLimit}, provider);
    }

    static KllMetric createMetric() {
        KllMetric metric;
        metric.set_id(metricId);
        metric.set_bucket(ONE_MINUTE);
        metric.mutable_kll_field()->set_field(atomId);
        metric.mutable_kll_field()->add_child()->set_field(2);
        metric.set_split_bucket_for_app_upgrade(true);
        return metric;
    }

    static KllMetric createMetricWithCondition() {
        KllMetric metric = KllMetricProducerTestHelper::createMetric();
        metric.set_condition(StringToId("SCREEN_ON"));
        return metric;
    }
};

// Setup for parameterized tests.
class KllMetricProducerTest_PartialBucket : public TestWithParam<BucketSplitEvent> {};

INSTANTIATE_TEST_SUITE_P(KllMetricProducerTest_PartialBucket, KllMetricProducerTest_PartialBucket,
                         testing::Values(APP_UPGRADE, BOOT_COMPLETE));

TEST_P(KllMetricProducerTest_PartialBucket, TestPushedEventsMultipleBuckets) {
    const KllMetric& metric = KllMetricProducerTestHelper::createMetric();
    sp<KllMetricProducer> kllProducer =
            KllMetricProducerTestHelper::createKllProducerNoConditions(metric);

    LogEvent event1(/*uid=*/0, /*pid=*/0);
    CreateRepeatedValueLogEvent(&event1, atomId, bucketStartTimeNs + 10, 10);
    kllProducer->onMatchedLogEvent(1 /*log matcher index*/, event1);
    ASSERT_EQ(1UL, kllProducer->mCurrentSlicedBucket.size());

    const int64_t partialBucketSplitTimeNs = bucketStartTimeNs + 150;
    switch (GetParam()) {
        case APP_UPGRADE:
            kllProducer->notifyAppUpgrade(partialBucketSplitTimeNs);
            break;
        case BOOT_COMPLETE:
            kllProducer->onStatsdInitCompleted(partialBucketSplitTimeNs);
            break;
    }
    TRACE_CALL(assertPastBucketsSingleKey, kllProducer->mPastBuckets, {1},
               {partialBucketSplitTimeNs - bucketStartTimeNs}, {bucketStartTimeNs},
               {partialBucketSplitTimeNs});
    EXPECT_EQ(partialBucketSplitTimeNs, kllProducer->mCurrentBucketStartTimeNs);
    EXPECT_EQ(0, kllProducer->getCurrentBucketNum());

    // Event arrives after the bucket split.
    LogEvent event2(/*uid=*/0, /*pid=*/0);
    CreateRepeatedValueLogEvent(&event2, atomId, bucketStartTimeNs + 59 * NS_PER_SEC, 20);
    kllProducer->onMatchedLogEvent(1 /*log matcher index*/, event2);

    TRACE_CALL(assertPastBucketsSingleKey, kllProducer->mPastBuckets, {1},
               {partialBucketSplitTimeNs - bucketStartTimeNs}, {bucketStartTimeNs},
               {partialBucketSplitTimeNs});
    EXPECT_EQ(partialBucketSplitTimeNs, kllProducer->mCurrentBucketStartTimeNs);
    EXPECT_EQ(0, kllProducer->getCurrentBucketNum());

    // Next value should create a new bucket.
    LogEvent event3(/*uid=*/0, /*pid=*/0);
    CreateRepeatedValueLogEvent(&event3, atomId, bucket2StartTimeNs + 5 * NS_PER_SEC, 10);
    kllProducer->onMatchedLogEvent(1 /*log matcher index*/, event3);
    TRACE_CALL(assertPastBucketsSingleKey, kllProducer->mPastBuckets, {1, 1},
               {partialBucketSplitTimeNs - bucketStartTimeNs,
                bucket2StartTimeNs - partialBucketSplitTimeNs},
               {bucketStartTimeNs, partialBucketSplitTimeNs},
               {partialBucketSplitTimeNs, bucket2StartTimeNs});
    EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, kllProducer->mCurrentBucketStartTimeNs);
    EXPECT_EQ(1, kllProducer->getCurrentBucketNum());
}

TEST(KllMetricProducerTest, TestPushedEventsWithoutCondition) {
    const KllMetric& metric = KllMetricProducerTestHelper::createMetric();
    sp<KllMetricProducer> kllProducer =
            KllMetricProducerTestHelper::createKllProducerNoConditions(metric);

    LogEvent event1(/*uid=*/0, /*pid=*/0);
    CreateRepeatedValueLogEvent(&event1, atomId, bucketStartTimeNs + 10, 10);

    LogEvent event2(/*uid=*/0, /*pid=*/0);
    CreateRepeatedValueLogEvent(&event2, atomId, bucketStartTimeNs + 20, 20);

    kllProducer->onMatchedLogEvent(1 /*log matcher index*/, event1);
    // has one slice
    ASSERT_EQ(1UL, kllProducer->mCurrentSlicedBucket.size());
    const KllMetricProducer::Interval& curInterval0 =
            kllProducer->mCurrentSlicedBucket.begin()->second.intervals[0];
    EXPECT_EQ(1, curInterval0.aggregate->num_values());
    EXPECT_GT(curInterval0.sampleSize, 0);

    kllProducer->onMatchedLogEvent(1 /*log matcher index*/, event2);

    // has one slice
    ASSERT_EQ(1UL, kllProducer->mCurrentSlicedBucket.size());
    EXPECT_EQ(2, curInterval0.aggregate->num_values());

    kllProducer->flushIfNeededLocked(bucket2StartTimeNs);
    TRACE_CALL(assertPastBucketsSingleKey, kllProducer->mPastBuckets, {2}, {bucketSizeNs},
               {bucketStartTimeNs}, {bucket2StartTimeNs});
}

TEST(KllMetricProducerTest, TestPushedEventsWithCondition) {
    const KllMetric& metric = KllMetricProducerTestHelper::createMetric();
    sp<KllMetricProducer> kllProducer = KllMetricProducerTestHelper::createKllProducerWithCondition(
            metric, ConditionState::kFalse);

    LogEvent event1(/*uid=*/0, /*pid=*/0);
    CreateRepeatedValueLogEvent(&event1, atomId, bucketStartTimeNs + 10, 10);
    kllProducer->onMatchedLogEvent(1 /*log matcher index*/, event1);
    // Has 0 slices.
    ASSERT_EQ(0UL, kllProducer->mCurrentSlicedBucket.size());

    kllProducer->onConditionChangedLocked(true, bucketStartTimeNs + 15);

    LogEvent event2(/*uid=*/0, /*pid=*/0);
    CreateRepeatedValueLogEvent(&event2, atomId, bucketStartTimeNs + 20, 20);
    kllProducer->onMatchedLogEvent(1 /*log matcher index*/, event2);

    // has one slice
    ASSERT_EQ(1UL, kllProducer->mCurrentSlicedBucket.size());
    const KllMetricProducer::Interval& curInterval0 =
            kllProducer->mCurrentSlicedBucket.begin()->second.intervals[0];
    EXPECT_EQ(1, curInterval0.aggregate->num_values());

    LogEvent event3(/*uid=*/0, /*pid=*/0);
    CreateRepeatedValueLogEvent(&event3, atomId, bucketStartTimeNs + 30, 30);
    kllProducer->onMatchedLogEvent(1 /*log matcher index*/, event3);

    // has one slice
    ASSERT_EQ(1UL, kllProducer->mCurrentSlicedBucket.size());
    EXPECT_EQ(2, curInterval0.aggregate->num_values());

    kllProducer->onConditionChangedLocked(false, bucketStartTimeNs + 35);

    LogEvent event4(/*uid=*/0, /*pid=*/0);
    CreateRepeatedValueLogEvent(&event4, atomId, bucketStartTimeNs + 40, 40);
    kllProducer->onMatchedLogEvent(1 /*log matcher index*/, event4);

    // has one slice
    ASSERT_EQ(1UL, kllProducer->mCurrentSlicedBucket.size());
    EXPECT_EQ(2, curInterval0.aggregate->num_values());

    kllProducer->flushIfNeededLocked(bucket2StartTimeNs);
    TRACE_CALL(assertPastBucketsSingleKey, kllProducer->mPastBuckets, {2}, {20},
               {bucketStartTimeNs}, {bucket2StartTimeNs});
}

/*
 * Test that CONDITION_UNKNOWN dump reason is logged due to an unknown condition
 * when a metric is initialized.
 */
TEST(KllMetricProducerTest_BucketDrop, TestInvalidBucketWhenConditionUnknown) {
    const KllMetric& metric = KllMetricProducerTestHelper::createMetricWithCondition();
    sp<KllMetricProducer> kllProducer = KllMetricProducerTestHelper::createKllProducerWithCondition(
            metric, ConditionState::kUnknown);

    // Condition change event.
    kllProducer->onConditionChanged(true, bucketStartTimeNs + 50);

    // Check dump report.
    int64_t dumpReportTimeNs = bucketStartTimeNs + 10000;
    StatsLogReport report =
            onDumpReport(kllProducer, dumpReportTimeNs, true /* include current bucket */);
    EXPECT_TRUE(report.has_kll_metrics());
    ASSERT_EQ(0, report.kll_metrics().data_size());
    ASSERT_EQ(1, report.kll_metrics().skipped_size());

    EXPECT_EQ(NanoToMillis(bucketStartTimeNs),
              report.kll_metrics().skipped(0).start_bucket_elapsed_millis());
    EXPECT_EQ(NanoToMillis(dumpReportTimeNs),
              report.kll_metrics().skipped(0).end_bucket_elapsed_millis());
    ASSERT_EQ(1, report.kll_metrics().skipped(0).drop_event_size());

    auto dropEvent = report.kll_metrics().skipped(0).drop_event(0);
    EXPECT_EQ(BucketDropReason::CONDITION_UNKNOWN, dropEvent.drop_reason());
    EXPECT_EQ(NanoToMillis(dumpReportTimeNs), dropEvent.drop_time_millis());
}

/*
 * Test that BUCKET_TOO_SMALL dump reason is logged when a flushed bucket size
 * is smaller than the "min_bucket_size_nanos" specified in the metric config.
 */
TEST(KllMetricProducerTest_BucketDrop, TestBucketDropWhenBucketTooSmall) {
    KllMetric metric = KllMetricProducerTestHelper::createMetric();
    metric.set_min_bucket_size_nanos(10 * NS_PER_SEC);  // 10 seconds

    sp<KllMetricProducer> kllProducer =
            KllMetricProducerTestHelper::createKllProducerNoConditions(metric);

    LogEvent event1(/*uid=*/0, /*pid=*/0);
    CreateRepeatedValueLogEvent(&event1, atomId, bucketStartTimeNs + 10, 10);
    kllProducer->onMatchedLogEvent(1 /*log matcher index*/, event1);

    // Check dump report.
    int64_t dumpReportTimeNs = bucketStartTimeNs + 9000000;
    StatsLogReport report =
            onDumpReport(kllProducer, dumpReportTimeNs, true /* include current bucket */);
    EXPECT_TRUE(report.has_kll_metrics());
    ASSERT_EQ(0, report.kll_metrics().data_size());
    ASSERT_EQ(1, report.kll_metrics().skipped_size());

    EXPECT_EQ(NanoToMillis(bucketStartTimeNs),
              report.kll_metrics().skipped(0).start_bucket_elapsed_millis());
    EXPECT_EQ(NanoToMillis(dumpReportTimeNs),
              report.kll_metrics().skipped(0).end_bucket_elapsed_millis());
    ASSERT_EQ(1, report.kll_metrics().skipped(0).drop_event_size());

    auto dropEvent = report.kll_metrics().skipped(0).drop_event(0);
    EXPECT_EQ(BucketDropReason::BUCKET_TOO_SMALL, dropEvent.drop_reason());
    EXPECT_EQ(NanoToMillis(dumpReportTimeNs), dropEvent.drop_time_millis());
}

/*
 * Test that NO_DATA dump reason is logged when a flushed bucket contains no data.
 */
TEST(KllMetricProducerTest_BucketDrop, TestBucketDropWhenDataUnavailable) {
    const KllMetric& metric = KllMetricProducerTestHelper::createMetricWithCondition();

    sp<KllMetricProducer> kllProducer = KllMetricProducerTestHelper::createKllProducerWithCondition(
            metric, ConditionState::kFalse);

    // Check dump report.
    int64_t dumpReportTimeNs = bucketStartTimeNs + 10000000000;  // 10 seconds
    StatsLogReport report =
            onDumpReport(kllProducer, dumpReportTimeNs, true /* include current bucket */);
    EXPECT_TRUE(report.has_kll_metrics());
    ASSERT_EQ(0, report.kll_metrics().data_size());
    ASSERT_EQ(1, report.kll_metrics().skipped_size());

    EXPECT_EQ(NanoToMillis(bucketStartTimeNs),
              report.kll_metrics().skipped(0).start_bucket_elapsed_millis());
    EXPECT_EQ(NanoToMillis(dumpReportTimeNs),
              report.kll_metrics().skipped(0).end_bucket_elapsed_millis());
    ASSERT_EQ(1, report.kll_metrics().skipped(0).drop_event_size());

    auto dropEvent = report.kll_metrics().skipped(0).drop_event(0);
    EXPECT_EQ(BucketDropReason::NO_DATA, dropEvent.drop_reason());
    EXPECT_EQ(NanoToMillis(dumpReportTimeNs), dropEvent.drop_time_millis());
}

/*
 * Test bucket splits when condition is unknown.
 */
TEST(KllMetricProducerTest, TestForcedBucketSplitWhenConditionUnknownSkipsBucket) {
    const KllMetric& metric = KllMetricProducerTestHelper::createMetricWithCondition();

    sp<KllMetricProducer> kllProducer = KllMetricProducerTestHelper::createKllProducerWithCondition(
            metric, ConditionState::kUnknown);

    // App update event.
    int64_t appUpdateTimeNs = bucketStartTimeNs + 1000;
    kllProducer->notifyAppUpgrade(appUpdateTimeNs);

    // Check dump report.
    int64_t dumpReportTimeNs = bucketStartTimeNs + 10000000000;  // 10 seconds
    StatsLogReport report =
            onDumpReport(kllProducer, dumpReportTimeNs, false /* include current bucket */);
    EXPECT_TRUE(report.has_kll_metrics());
    ASSERT_EQ(0, report.kll_metrics().data_size());
    ASSERT_EQ(1, report.kll_metrics().skipped_size());

    EXPECT_EQ(NanoToMillis(bucketStartTimeNs),
              report.kll_metrics().skipped(0).start_bucket_elapsed_millis());
    EXPECT_EQ(NanoToMillis(appUpdateTimeNs),
              report.kll_metrics().skipped(0).end_bucket_elapsed_millis());
    ASSERT_EQ(1, report.kll_metrics().skipped(0).drop_event_size());

    auto dropEvent = report.kll_metrics().skipped(0).drop_event(0);
    EXPECT_EQ(BucketDropReason::CONDITION_UNKNOWN, dropEvent.drop_reason());
    EXPECT_EQ(NanoToMillis(appUpdateTimeNs), dropEvent.drop_time_millis());
}

TEST(KllMetricProducerTest, TestByteSize) {
    const KllMetric& metric = KllMetricProducerTestHelper::createMetric();
    sp<KllMetricProducer> kllProducer =
            KllMetricProducerTestHelper::createKllProducerNoConditions(metric);

    LogEvent event1(/*uid=*/0, /*pid=*/0);
    CreateRepeatedValueLogEvent(&event1, atomId, bucketStartTimeNs + 10, 10);

    LogEvent event2(/*uid=*/0, /*pid=*/0);
    CreateRepeatedValueLogEvent(&event2, atomId, bucketStartTimeNs + 20, 20);

    kllProducer->onMatchedLogEvent(1 /*log matcher index*/, event1);
    kllProducer->onMatchedLogEvent(1 /*log matcher index*/, event2);
    kllProducer->flushIfNeededLocked(bucket2StartTimeNs);

    const size_t expectedSize = kllProducer->kBucketSize + 4 /* one int aggIndex entry */ +
                                16 /* two int64_t entries in KllQuantile object */;

    EXPECT_EQ(expectedSize, kllProducer->byteSize());
}

TEST(KllMetricProducerTest, TestCorruptedDataReason_WhatLoss) {
    const KllMetric& metric = KllMetricProducerTestHelper::createMetric();
    sp<KllMetricProducer> kllProducer =
            KllMetricProducerTestHelper::createKllProducerNoConditions(metric);

    kllProducer->onMatchedLogEventLost(atomId, DATA_CORRUPTED_SOCKET_LOSS,
                                       MetricProducer::LostAtomType::kWhat);
    {
        // Check dump report content.
        StatsLogReport report = onDumpReport(kllProducer, bucketStartTimeNs + 50,
                                             true /* include current bucket */);
        EXPECT_THAT(report.data_corrupted_reason(), ElementsAre(DATA_CORRUPTED_SOCKET_LOSS));
    }

    kllProducer->onMatchedLogEventLost(atomId, DATA_CORRUPTED_EVENT_QUEUE_OVERFLOW,
                                       MetricProducer::LostAtomType::kWhat);
    {
        // Check dump report content.
        StatsLogReport report = onDumpReport(kllProducer, bucketStartTimeNs + 150,
                                             true /* include current bucket */);
        EXPECT_THAT(report.data_corrupted_reason(),
                    ElementsAre(DATA_CORRUPTED_EVENT_QUEUE_OVERFLOW));
    }

    kllProducer->onMatchedLogEventLost(atomId, DATA_CORRUPTED_SOCKET_LOSS,
                                       MetricProducer::LostAtomType::kWhat);
    kllProducer->onMatchedLogEventLost(atomId, DATA_CORRUPTED_EVENT_QUEUE_OVERFLOW,
                                       MetricProducer::LostAtomType::kWhat);
    {
        // Check dump report content.
        StatsLogReport report = onDumpReport(kllProducer, bucketStartTimeNs + 250,
                                             true /* include current bucket */);
        EXPECT_THAT(report.data_corrupted_reason(),
                    ElementsAre(DATA_CORRUPTED_EVENT_QUEUE_OVERFLOW, DATA_CORRUPTED_SOCKET_LOSS));
    }
}

TEST(KllMetricProducerTest, TestCorruptedDataReason_ConditionLoss) {
    const int conditionId = 10;

    const KllMetric& metric = KllMetricProducerTestHelper::createMetricWithCondition();

    sp<KllMetricProducer> kllProducer = KllMetricProducerTestHelper::createKllProducerWithCondition(
            metric, ConditionState::kFalse);

    kllProducer->onMatchedLogEventLost(conditionId, DATA_CORRUPTED_SOCKET_LOSS,
                                       MetricProducer::LostAtomType::kCondition);
    {
        // Check dump report content.
        StatsLogReport report = onDumpReport(kllProducer, bucketStartTimeNs + 50,
                                             true /* include current bucket */);
        EXPECT_THAT(report.data_corrupted_reason(), ElementsAre(DATA_CORRUPTED_SOCKET_LOSS));
    }

    kllProducer->onMatchedLogEventLost(conditionId, DATA_CORRUPTED_EVENT_QUEUE_OVERFLOW,
                                       MetricProducer::LostAtomType::kCondition);
    {
        // Check dump report content.
        StatsLogReport report = onDumpReport(kllProducer, bucketStartTimeNs + 150,
                                             true /* include current bucket */);
        EXPECT_THAT(report.data_corrupted_reason(),
                    ElementsAre(DATA_CORRUPTED_EVENT_QUEUE_OVERFLOW, DATA_CORRUPTED_SOCKET_LOSS));
    }
}

TEST(KllMetricProducerTest, TestCorruptedDataReason_StateLoss) {
    const int stateAtomId = 10;

    const KllMetric& metric = KllMetricProducerTestHelper::createMetricWithCondition();

    sp<KllMetricProducer> kllProducer = KllMetricProducerTestHelper::createKllProducerWithCondition(
            metric, ConditionState::kFalse);

    kllProducer->onStateEventLost(stateAtomId, DATA_CORRUPTED_SOCKET_LOSS);
    {
        // Check dump report content.
        StatsLogReport report = onDumpReport(kllProducer, bucketStartTimeNs + 50,
                                             true /* include current bucket */);
        EXPECT_THAT(report.data_corrupted_reason(), ElementsAre(DATA_CORRUPTED_SOCKET_LOSS));
    }

    // validation that data corruption signal remains accurate after another dump
    {
        // Check dump report content.
        StatsLogReport report = onDumpReport(kllProducer, bucketStartTimeNs + 150,
                                             true /* include current bucket */);
        EXPECT_THAT(report.data_corrupted_reason(), ElementsAre(DATA_CORRUPTED_SOCKET_LOSS));
    }
}

}  // namespace statsd
}  // namespace os
}  // namespace android
#else
GTEST_LOG_(INFO) << "This test does nothing.\n";
#endif
