Chromium Code Reviews| Index: components/data_usage/android/traffic_stats_amortizer_unittest.cc |
| diff --git a/components/data_usage/android/traffic_stats_amortizer_unittest.cc b/components/data_usage/android/traffic_stats_amortizer_unittest.cc |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..1fc43c0b5c546395b68c493471ffe9a9449e97e4 |
| --- /dev/null |
| +++ b/components/data_usage/android/traffic_stats_amortizer_unittest.cc |
| @@ -0,0 +1,348 @@ |
| +// Copyright 2015 The Chromium Authors. All rights reserved. |
| +// Use of this source code is governed by a BSD-style license that can be |
| +// found in the LICENSE file. |
| + |
| +#include "components/data_usage/android/traffic_stats_amortizer.h" |
| + |
| +#include <stdint.h> |
| + |
| +#include <string> |
| +#include <vector> |
| + |
| +#include "base/bind.h" |
| +#include "base/location.h" |
| +#include "base/macros.h" |
| +#include "base/memory/scoped_ptr.h" |
| +#include "base/message_loop/message_loop.h" |
| +#include "base/run_loop.h" |
| +#include "base/test/simple_test_tick_clock.h" |
| +#include "base/time/tick_clock.h" |
| +#include "base/time/time.h" |
| +#include "base/timer/mock_timer.h" |
| +#include "base/timer/timer.h" |
| +#include "components/data_usage/core/data_use.h" |
| +#include "components/data_usage/core/data_use_amortizer.h" |
| +#include "net/base/network_change_notifier.h" |
| +#include "testing/gtest/include/gtest/gtest.h" |
| +#include "url/gurl.h" |
| + |
| +namespace data_usage { |
| +namespace android { |
| + |
| +namespace { |
| + |
| +// The delay between receiving DataUse and querying TrafficStats byte counts for |
| +// amortization. |
| +const base::TimeDelta kTrafficStatsQueryDelay = |
| + base::TimeDelta::FromMilliseconds(50); |
| + |
| +// The longest amount of time that an amortization run can be delayed for. |
| +const base::TimeDelta kMaxAmortizationDelay = |
| + base::TimeDelta::FromMilliseconds(200); |
| + |
| +// The maximum allowed size of the DataUse buffer. |
| +const size_t kMaxDataUseBufferSize = 8; |
| + |
| +// Synthesizes a fake scoped_ptr<DataUse> with the given |tx_bytes| and |
| +// |rx_bytes|, using arbitrary values for all other fields. |
| +scoped_ptr<DataUse> CreateDataUse(int64_t tx_bytes, int64_t rx_bytes) { |
| + return scoped_ptr<DataUse>(new DataUse( |
| + GURL("http://example.com"), base::TimeTicks() /* request_start */, |
| + GURL("http://examplefirstparty.com"), 10 /* tab_id */, |
| + net::NetworkChangeNotifier::CONNECTION_2G, "example_mcc_mnc", tx_bytes, |
| + rx_bytes)); |
| +} |
| + |
| +class MockTimerWithTickClock : public base::MockTimer { |
| + public: |
| + MockTimerWithTickClock(bool retain_user_task, |
| + bool is_repeating, |
| + base::TickClock* tick_clock) |
| + : base::MockTimer(retain_user_task, is_repeating), |
| + tick_clock_(tick_clock) {} |
| + |
| + ~MockTimerWithTickClock() override {} |
| + |
| + void Reset() override { |
| + start_time_ = tick_clock_->NowTicks(); |
| + base::MockTimer::Reset(); |
| + } |
| + |
| + const base::TimeTicks& start_time() const { return start_time_; } |
| + |
| + private: |
| + base::TickClock* tick_clock_; |
| + base::TimeTicks start_time_; |
| + |
| + DISALLOW_COPY_AND_ASSIGN(MockTimerWithTickClock); |
| +}; |
| + |
| +class TestTrafficStatsAmortizer : public TrafficStatsAmortizer { |
| + public: |
| + TestTrafficStatsAmortizer(scoped_ptr<base::TickClock> tick_clock, |
| + scoped_ptr<base::Timer> traffic_stats_query_timer) |
| + : TrafficStatsAmortizer(tick_clock.Pass(), |
| + traffic_stats_query_timer.Pass(), |
| + kTrafficStatsQueryDelay, |
| + kMaxAmortizationDelay, |
| + kMaxDataUseBufferSize), |
| + next_traffic_stats_available_(false), |
| + next_traffic_stats_tx_bytes_(-1), |
| + next_traffic_stats_rx_bytes_(-1) {} |
| + |
| + ~TestTrafficStatsAmortizer() override {} |
| + |
| + void SetNextTrafficStats(bool available, int64_t tx_bytes, int64_t rx_bytes) { |
| + next_traffic_stats_available_ = available; |
| + next_traffic_stats_tx_bytes_ = tx_bytes; |
| + next_traffic_stats_rx_bytes_ = rx_bytes; |
| + } |
| + |
| + void AddTrafficStats(int64_t tx_bytes, int64_t rx_bytes) { |
| + next_traffic_stats_tx_bytes_ += tx_bytes; |
| + next_traffic_stats_rx_bytes_ += rx_bytes; |
| + } |
| + |
| + protected: |
| + bool QueryTrafficStats(int64_t* tx_bytes, int64_t* rx_bytes) const override { |
| + *tx_bytes = next_traffic_stats_tx_bytes_; |
| + *rx_bytes = next_traffic_stats_rx_bytes_; |
| + return next_traffic_stats_available_; |
| + } |
| + |
| + private: |
| + bool next_traffic_stats_available_; |
| + int64_t next_traffic_stats_tx_bytes_; |
| + int64_t next_traffic_stats_rx_bytes_; |
| + |
| + DISALLOW_COPY_AND_ASSIGN(TestTrafficStatsAmortizer); |
| +}; |
| + |
| +class TrafficStatsAmortizerTest : public testing::Test { |
| + public: |
| + TrafficStatsAmortizerTest() |
| + : test_tick_clock_(new base::SimpleTestTickClock()), |
| + mock_timer_(new MockTimerWithTickClock(false, false, test_tick_clock_)), |
| + amortizer_(scoped_ptr<base::TickClock>(test_tick_clock_), |
| + scoped_ptr<base::Timer>(mock_timer_)), |
| + data_use_callback_call_count_(0) {} |
| + |
| + ~TrafficStatsAmortizerTest() override { |
| + EXPECT_FALSE(mock_timer_->IsRunning()); |
| + } |
| + |
| + // Simulate the passage of time, firing timers if appropriate. |
| + void AdvanceTime(const base::TimeDelta& delta) { |
| + run_loop_.RunUntilIdle(); |
| + test_tick_clock_->Advance(delta); |
| + |
| + // Fire the |mock_timer_| if necessary. |
| + if (mock_timer_->IsRunning() && |
| + mock_timer_->start_time() + mock_timer_->GetCurrentDelay() >= |
| + test_tick_clock_->NowTicks()) { |
| + mock_timer_->Fire(); |
| + } |
| + run_loop_.RunUntilIdle(); |
| + } |
| + |
| + // Expects that |expected| and |actual| are equivalent. |
| + void ExpectDataUse(scoped_ptr<DataUse> expected, scoped_ptr<DataUse> actual) { |
| + ++data_use_callback_call_count_; |
| + |
| + // Have separate checks for the |tx_bytes| and |rx_bytes| for better error |
| + // messages. |
| + EXPECT_EQ(expected->tx_bytes, actual->tx_bytes); |
| + EXPECT_EQ(expected->rx_bytes, actual->rx_bytes); |
| + EXPECT_EQ(*expected, *actual); |
| + } |
| + |
| + // Convenience function for creating an ExpectDataUse callback. |
|
bengr
2015/11/10 18:12:50
// Creates an ExpectDataUse callback, as a conveni
sclittle
2015/11/11 02:10:08
Done.
|
| + DataUseAmortizer::DataUseConsumerCallback ExpectDataUseCallback( |
| + scoped_ptr<DataUse> expected) { |
| + return base::Bind(&TrafficStatsAmortizerTest::ExpectDataUse, |
| + base::Unretained(this), base::Passed(&expected)); |
| + } |
| + |
| + base::TimeTicks NowTicks() const { return test_tick_clock_->NowTicks(); } |
| + |
| + TestTrafficStatsAmortizer* amortizer() { return &amortizer_; } |
| + |
| + int data_use_callback_call_count() const { |
| + return data_use_callback_call_count_; |
| + } |
| + |
| + private: |
| + base::MessageLoop message_loop_; |
| + base::RunLoop run_loop_; |
| + // Weak, owned by |amortizer_|. |
|
bengr
2015/11/10 18:12:50
Add blank line above.
sclittle
2015/11/11 02:10:08
Done.
|
| + base::SimpleTestTickClock* test_tick_clock_; |
| + // Weak, owned by |amortizer_|. |
|
bengr
2015/11/10 18:12:50
Add blank line above.
sclittle
2015/11/11 02:10:08
Done.
|
| + MockTimerWithTickClock* mock_timer_; |
|
bengr
2015/11/10 18:12:50
Add blank line below.
sclittle
2015/11/11 02:10:08
Done.
|
| + TestTrafficStatsAmortizer amortizer_; |
| + |
| + // The number of times ExpectDataUse has been called. |
| + int data_use_callback_call_count_; |
| + |
| + DISALLOW_COPY_AND_ASSIGN(TrafficStatsAmortizerTest); |
| +}; |
| + |
| +TEST_F(TrafficStatsAmortizerTest, AmortizeWithTrafficStatsAlwaysUnavailable) { |
| + amortizer()->SetNextTrafficStats(false, -1, -1); |
| + // Do it three times for good measure. |
| + for (int i = 0; i < 3; ++i) { |
| + // Extra bytes should be ignored since TrafficStats are unavailable. |
| + amortizer()->OnExtraBytes(1337, 9001); |
| + // The original DataUse should be unchanged. |
| + amortizer()->AmortizeDataUse( |
| + CreateDataUse(100, 1000), |
| + ExpectDataUseCallback(CreateDataUse(100, 1000))); |
| + |
| + AdvanceTime(kTrafficStatsQueryDelay); |
| + EXPECT_EQ(i + 1, data_use_callback_call_count()); |
| + } |
| +} |
| + |
| +TEST_F(TrafficStatsAmortizerTest, AmortizeDataUse) { |
| + // The initial values of TrafficStats shouldn't matter. |
| + amortizer()->SetNextTrafficStats(true, 1337, 9001); |
| + |
| + // The first amortization run should not change any byte counts because |
| + // there's no TrafficStats delta to work with. |
| + amortizer()->AmortizeDataUse(CreateDataUse(50, 500), |
| + ExpectDataUseCallback(CreateDataUse(50, 500))); |
| + amortizer()->AmortizeDataUse(CreateDataUse(100, 1000), |
| + ExpectDataUseCallback(CreateDataUse(100, 1000))); |
| + AdvanceTime(kTrafficStatsQueryDelay); |
| + EXPECT_EQ(2, data_use_callback_call_count()); |
| + |
| + // This amortization run, tx_bytes and rx_bytes should be doubled. |
| + amortizer()->AmortizeDataUse(CreateDataUse(50, 500), |
| + ExpectDataUseCallback(CreateDataUse(100, 1000))); |
| + AdvanceTime(kTrafficStatsQueryDelay / 2); |
| + |
| + // Another DataUse is reported before the amortizer queries TrafficStats. |
| + amortizer()->AmortizeDataUse(CreateDataUse(100, 1000), |
| + ExpectDataUseCallback(CreateDataUse(200, 2000))); |
| + AdvanceTime(kTrafficStatsQueryDelay / 2); |
| + |
| + // Then, the TrafficStats values update with the new bytes. The second run |
| + // callbacks should not have been called yet. |
| + amortizer()->AddTrafficStats(300, 3000); |
| + EXPECT_EQ(2, data_use_callback_call_count()); |
| + |
| + // The callbacks should fire once kTrafficStatsQueryDelay has passed since the |
| + // DataUse was passed to the amortizer. |
| + AdvanceTime(kTrafficStatsQueryDelay / 2); |
| + EXPECT_EQ(4, data_use_callback_call_count()); |
| +} |
| + |
| +TEST_F(TrafficStatsAmortizerTest, AmortizeWithExtraBytes) { |
| + // The initial values of TrafficStats shouldn't matter. |
| + amortizer()->SetNextTrafficStats(true, 1337, 9001); |
| + |
| + // Do the first amortization run with TrafficStats unavailable. |
| + amortizer()->OnExtraBytes(100, 1000); |
| + AdvanceTime(kTrafficStatsQueryDelay); |
| + EXPECT_EQ(0, data_use_callback_call_count()); |
| + |
| + // On the second amortization run, byte counts should double. |
| + amortizer()->AmortizeDataUse(CreateDataUse(50, 500), |
| + ExpectDataUseCallback(CreateDataUse(100, 1000))); |
| + amortizer()->OnExtraBytes(500, 5000); |
| + amortizer()->AddTrafficStats(1100, 11000); |
| + AdvanceTime(kTrafficStatsQueryDelay); |
| + EXPECT_EQ(1, data_use_callback_call_count()); |
| +} |
| + |
| +TEST_F(TrafficStatsAmortizerTest, AmortizeWithNegativeOverhead) { |
| + // The initial values of TrafficStats shouldn't matter. |
| + amortizer()->SetNextTrafficStats(true, 1337, 9001); |
| + |
| + // Do the first amortization run with TrafficStats unavailable. |
| + amortizer()->OnExtraBytes(100, 1000); |
| + AdvanceTime(kTrafficStatsQueryDelay); |
| + EXPECT_EQ(0, data_use_callback_call_count()); |
| + |
| + // On the second amortization run, byte counts should halve. |
| + amortizer()->AmortizeDataUse(CreateDataUse(50, 500), |
|
bengr
2015/11/10 18:12:50
Also add cases where sent or received bytes is at
sclittle
2015/11/11 02:10:08
Done. Note that these are weird since floating poi
|
| + ExpectDataUseCallback(CreateDataUse(25, 250))); |
| + amortizer()->AddTrafficStats(25, 250); |
| + AdvanceTime(kTrafficStatsQueryDelay); |
| + EXPECT_EQ(1, data_use_callback_call_count()); |
| +} |
| + |
| +TEST_F(TrafficStatsAmortizerTest, AmortizeWithZeroPreAmortizationBytes) { |
| + // The initial values of TrafficStats shouldn't matter. |
| + amortizer()->SetNextTrafficStats(true, 1337, 9001); |
| + |
| + // Do the first amortization run with TrafficStats unavailable. |
| + amortizer()->OnExtraBytes(100, 1000); |
| + AdvanceTime(kTrafficStatsQueryDelay); |
| + EXPECT_EQ(0, data_use_callback_call_count()); |
| + |
| + // On the second amortization run, byte counts should stay 0. |
| + amortizer()->AmortizeDataUse(CreateDataUse(0, 0), |
|
bengr
2015/11/10 18:12:50
Add cases where sent or received bytes is 0.
sclittle
2015/11/11 02:10:08
Done.
|
| + ExpectDataUseCallback(CreateDataUse(0, 0))); |
| + amortizer()->AddTrafficStats(100, 1000); |
| + AdvanceTime(kTrafficStatsQueryDelay); |
| + EXPECT_EQ(1, data_use_callback_call_count()); |
| +} |
| + |
| +TEST_F(TrafficStatsAmortizerTest, AmortizeAtMaxDelay) { |
| + // The initial values of TrafficStats shouldn't matter. |
| + amortizer()->SetNextTrafficStats(true, 1337, 9001); |
| + |
| + // Do the first amortization run with TrafficStats unavailable. |
| + amortizer()->OnExtraBytes(100, 1000); |
| + AdvanceTime(kTrafficStatsQueryDelay); |
| + EXPECT_EQ(0, data_use_callback_call_count()); |
| + |
| + // On the second amortization run, byte counts should double. |
| + amortizer()->AddTrafficStats(1000, 10000); |
| + amortizer()->AmortizeDataUse(CreateDataUse(50, 500), |
| + ExpectDataUseCallback(CreateDataUse(100, 1000))); |
| + |
| + // Simulate 9 cases of extra bytes being reported, each before TrafficStats |
|
bengr
2015/11/10 18:12:50
Why 9? Do you mean kMaxBufferSize + 1?
sclittle
2015/11/11 02:10:08
Reworded comment. I meant that we'll keep reportin
|
| + // would be queried. |
| + const base::TimeDelta kSmallDelay = kMaxAmortizationDelay / 10; |
|
bengr
2015/11/10 18:12:50
Why / 10?
sclittle
2015/11/11 02:10:08
Added comments.
|
| + EXPECT_LT(kSmallDelay, kMaxAmortizationDelay); |
| + |
| + AdvanceTime(kSmallDelay); |
| + for (int i = 0; i < 9; ++i) { |
| + EXPECT_EQ(0, data_use_callback_call_count()); |
| + amortizer()->OnExtraBytes(50, 500); |
| + AdvanceTime(kSmallDelay); |
| + } |
| + |
| + // The final time, the amortizer should have given up on waiting to query |
| + // TrafficStats and just have amortized as soon as it hit the deadline of |
| + // 200ms. |
| + EXPECT_EQ(1, data_use_callback_call_count()); |
| +} |
| + |
| +TEST_F(TrafficStatsAmortizerTest, AmortizeAtMaxBufferSize) { |
| + // The initial values of TrafficStats shouldn't matter. |
| + amortizer()->SetNextTrafficStats(true, 1337, 9001); |
| + |
| + // Do the first amortization run with TrafficStats unavailable. |
| + amortizer()->OnExtraBytes(100, 1000); |
| + AdvanceTime(kTrafficStatsQueryDelay); |
| + EXPECT_EQ(0, data_use_callback_call_count()); |
| + |
| + // The second run will fill up with 9 consecutive DataUse objects, which will |
| + // be amortized immediately once the buffer exceeds maximum size. |
| + amortizer()->AddTrafficStats(900, 9000); |
| + for (int i = 0; i < 9; ++i) { |
| + EXPECT_EQ(0, data_use_callback_call_count()); |
| + amortizer()->AmortizeDataUse( |
| + CreateDataUse(50, 500), |
| + ExpectDataUseCallback(CreateDataUse(100, 1000))); |
| + } |
| + |
| + EXPECT_EQ(9, data_use_callback_call_count()); |
| +} |
| + |
| +} // namespace |
| + |
| +} // namespace android |
| +} // namespace data_usage |