[DVQA] Change API to pause and resume all streams from a sender.
Also make it possible to pause an already paused stream by making it a no-op.
Change-Id: Id10f74a4c6464067ae63208162194f020c6470eb
Bug: b/271542055
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/298202
Commit-Queue: Jeremy Leconte <jleconte@google.com>
Reviewed-by: Artem Titov <titovartem@webrtc.org>
Cr-Commit-Position: refs/heads/main@{#39620}
diff --git a/api/test/video_quality_analyzer_interface.h b/api/test/video_quality_analyzer_interface.h
index abd0426..f20ae3e 100644
--- a/api/test/video_quality_analyzer_interface.h
+++ b/api/test/video_quality_analyzer_interface.h
@@ -150,16 +150,15 @@
// call.
virtual void UnregisterParticipantInCall(absl::string_view peer_name) {}
- // Informs analyzer that peer `peer_name` is expected to receive stream
- // `stream_label`.
- virtual void OnPeerStartedReceiveVideoStream(absl::string_view peer_name,
- absl::string_view stream_label) {
- }
- // Informs analyzer that peer `peer_name` shouldn't receive stream
- // `stream_label`.
- virtual void OnPeerStoppedReceiveVideoStream(absl::string_view peer_name,
- absl::string_view stream_label) {
- }
+ // Informs analyzer that peer `receiver_peer_name` shouldn't receive all
+ // streams from sender `sender_peer_name`.
+ virtual void OnPauseAllStreamsFrom(absl::string_view sender_peer_name,
+ absl::string_view receiver_peer_name) {}
+
+ // Informs analyzer that peer `receiver_peer_name` is expected to receive all
+ // streams from `sender_peer_name`.
+ virtual void OnResumeAllStreamsFrom(absl::string_view sender_peer_name,
+ absl::string_view receiver_peer_name) {}
// Tells analyzer that analysis complete and it should calculate final
// statistics.
diff --git a/test/pc/e2e/analyzer/video/default_video_quality_analyzer.cc b/test/pc/e2e/analyzer/video/default_video_quality_analyzer.cc
index 1b96423..ebd20b6 100644
--- a/test/pc/e2e/analyzer/video/default_video_quality_analyzer.cc
+++ b/test/pc/e2e/analyzer/video/default_video_quality_analyzer.cc
@@ -719,32 +719,36 @@
}
}
-void DefaultVideoQualityAnalyzer::OnPeerStartedReceiveVideoStream(
- absl::string_view peer_name,
- absl::string_view stream_label) {
+void DefaultVideoQualityAnalyzer::OnPauseAllStreamsFrom(
+ absl::string_view sender_peer_name,
+ absl::string_view receiver_peer_name) {
MutexLock lock(&mutex_);
- RTC_CHECK(peers_->HasName(peer_name));
- size_t peer_index = peers_->index(peer_name);
- RTC_CHECK(streams_.HasName(stream_label));
- size_t stream_index = streams_.index(stream_label);
+ RTC_CHECK(peers_->HasName(sender_peer_name));
+ size_t sender_peer_index = peers_->index(sender_peer_name);
+ RTC_CHECK(peers_->HasName(receiver_peer_name));
+ size_t receiver_peer_index = peers_->index(receiver_peer_name);
- auto it = stream_states_.find(stream_index);
- RTC_CHECK(it != stream_states_.end());
- it->second.GetPausableState(peer_index)->Resume();
+ for (auto& [unused, stream_state] : stream_states_) {
+ if (stream_state.sender() == sender_peer_index) {
+ stream_state.GetPausableState(receiver_peer_index)->Pause();
+ }
+ }
}
-void DefaultVideoQualityAnalyzer::OnPeerStoppedReceiveVideoStream(
- absl::string_view peer_name,
- absl::string_view stream_label) {
+void DefaultVideoQualityAnalyzer::OnResumeAllStreamsFrom(
+ absl::string_view sender_peer_name,
+ absl::string_view receiver_peer_name) {
MutexLock lock(&mutex_);
- RTC_CHECK(peers_->HasName(peer_name));
- size_t peer_index = peers_->index(peer_name);
- RTC_CHECK(streams_.HasName(stream_label));
- size_t stream_index = streams_.index(stream_label);
+ RTC_CHECK(peers_->HasName(sender_peer_name));
+ size_t sender_peer_index = peers_->index(sender_peer_name);
+ RTC_CHECK(peers_->HasName(receiver_peer_name));
+ size_t receiver_peer_index = peers_->index(receiver_peer_name);
- auto it = stream_states_.find(stream_index);
- RTC_CHECK(it != stream_states_.end());
- it->second.GetPausableState(peer_index)->Pause();
+ for (auto& [unused, stream_state] : stream_states_) {
+ if (stream_state.sender() == sender_peer_index) {
+ stream_state.GetPausableState(receiver_peer_index)->Resume();
+ }
+ }
}
void DefaultVideoQualityAnalyzer::Stop() {
diff --git a/test/pc/e2e/analyzer/video/default_video_quality_analyzer.h b/test/pc/e2e/analyzer/video/default_video_quality_analyzer.h
index 34b1fc8..1ea59bd 100644
--- a/test/pc/e2e/analyzer/video/default_video_quality_analyzer.h
+++ b/test/pc/e2e/analyzer/video/default_video_quality_analyzer.h
@@ -81,10 +81,10 @@
void RegisterParticipantInCall(absl::string_view peer_name) override;
void UnregisterParticipantInCall(absl::string_view peer_name) override;
- void OnPeerStartedReceiveVideoStream(absl::string_view peer_name,
- absl::string_view stream_label) override;
- void OnPeerStoppedReceiveVideoStream(absl::string_view peer_name,
- absl::string_view stream_label) override;
+ void OnPauseAllStreamsFrom(absl::string_view sender_peer_name,
+ absl::string_view receiver_peer_name) override;
+ void OnResumeAllStreamsFrom(absl::string_view sender_peer_name,
+ absl::string_view receiver_peer_name) override;
void Stop() override;
std::string GetStreamLabel(uint16_t frame_id) override;
diff --git a/test/pc/e2e/analyzer/video/default_video_quality_analyzer_test.cc b/test/pc/e2e/analyzer/video/default_video_quality_analyzer_test.cc
index 4222fcc..a66d1a9 100644
--- a/test/pc/e2e/analyzer/video/default_video_quality_analyzer_test.cc
+++ b/test/pc/e2e/analyzer/video/default_video_quality_analyzer_test.cc
@@ -144,21 +144,23 @@
void PassFramesThroughAnalyzerSenderOnly(
DefaultVideoQualityAnalyzer& analyzer,
absl::string_view sender,
- absl::string_view stream_label,
+ std::vector<absl::string_view> stream_labels,
std::vector<absl::string_view> receivers,
int frames_count,
test::FrameGeneratorInterface& frame_generator,
int interframe_delay_ms = 0,
TimeController* time_controller = nullptr) {
for (int i = 0; i < frames_count; ++i) {
- VideoFrame frame = NextFrame(&frame_generator, /*timestamp_us=*/1);
- uint16_t frame_id =
- analyzer.OnFrameCaptured(sender, std::string(stream_label), frame);
- frame.set_id(frame_id);
- analyzer.OnFramePreEncode(sender, frame);
- analyzer.OnFrameEncoded(sender, frame.id(), FakeEncode(frame),
- VideoQualityAnalyzerInterface::EncoderStats(),
- false);
+ for (absl::string_view stream_label : stream_labels) {
+ VideoFrame frame = NextFrame(&frame_generator, /*timestamp_us=*/1);
+ uint16_t frame_id =
+ analyzer.OnFrameCaptured(sender, std::string(stream_label), frame);
+ frame.set_id(frame_id);
+ analyzer.OnFramePreEncode(sender, frame);
+ analyzer.OnFrameEncoded(sender, frame.id(), FakeEncode(frame),
+ VideoQualityAnalyzerInterface::EncoderStats(),
+ false);
+ }
if (i < frames_count - 1 && interframe_delay_ms > 0) {
if (time_controller == nullptr) {
SleepMs(interframe_delay_ms);
@@ -171,28 +173,30 @@
void PassFramesThroughAnalyzer(DefaultVideoQualityAnalyzer& analyzer,
absl::string_view sender,
- absl::string_view stream_label,
+ std::vector<absl::string_view> stream_labels,
std::vector<absl::string_view> receivers,
int frames_count,
test::FrameGeneratorInterface& frame_generator,
int interframe_delay_ms = 0,
TimeController* time_controller = nullptr) {
for (int i = 0; i < frames_count; ++i) {
- VideoFrame frame = NextFrame(&frame_generator, /*timestamp_us=*/1);
- uint16_t frame_id =
- analyzer.OnFrameCaptured(sender, std::string(stream_label), frame);
- frame.set_id(frame_id);
- analyzer.OnFramePreEncode(sender, frame);
- analyzer.OnFrameEncoded(sender, frame.id(), FakeEncode(frame),
- VideoQualityAnalyzerInterface::EncoderStats(),
- false);
- for (absl::string_view receiver : receivers) {
- VideoFrame received_frame = DeepCopy(frame);
- analyzer.OnFramePreDecode(receiver, received_frame.id(),
- FakeEncode(received_frame));
- analyzer.OnFrameDecoded(receiver, received_frame,
- VideoQualityAnalyzerInterface::DecoderStats());
- analyzer.OnFrameRendered(receiver, received_frame);
+ for (absl::string_view stream_label : stream_labels) {
+ VideoFrame frame = NextFrame(&frame_generator, /*timestamp_us=*/1);
+ uint16_t frame_id =
+ analyzer.OnFrameCaptured(sender, std::string(stream_label), frame);
+ frame.set_id(frame_id);
+ analyzer.OnFramePreEncode(sender, frame);
+ analyzer.OnFrameEncoded(sender, frame.id(), FakeEncode(frame),
+ VideoQualityAnalyzerInterface::EncoderStats(),
+ false);
+ for (absl::string_view receiver : receivers) {
+ VideoFrame received_frame = DeepCopy(frame);
+ analyzer.OnFramePreDecode(receiver, received_frame.id(),
+ FakeEncode(received_frame));
+ analyzer.OnFrameDecoded(receiver, received_frame,
+ VideoQualityAnalyzerInterface::DecoderStats());
+ analyzer.OnFrameRendered(receiver, received_frame);
+ }
}
if (i < frames_count - 1 && interframe_delay_ms > 0) {
if (time_controller == nullptr) {
@@ -1612,10 +1616,10 @@
analyzer.Start("test_case", std::vector<std::string>{"alice", "bob"},
kAnalyzerMaxThreadsCount);
- PassFramesThroughAnalyzer(analyzer, "alice", "alice_video", {"bob"},
+ PassFramesThroughAnalyzer(analyzer, "alice", {"alice_video"}, {"bob"},
/*frames_count=*/1, *frame_generator);
analyzer.UnregisterParticipantInCall("bob");
- PassFramesThroughAnalyzer(analyzer, "alice", "alice_video", {},
+ PassFramesThroughAnalyzer(analyzer, "alice", {"alice_video"}, {},
/*frames_count=*/1, *frame_generator);
// Give analyzer some time to process frames on async thread. The computations
@@ -1788,12 +1792,12 @@
std::vector<std::string>{"alice", "bob", "charlie"},
kAnalyzerMaxThreadsCount);
- PassFramesThroughAnalyzer(analyzer, "alice", "alice_video",
+ PassFramesThroughAnalyzer(analyzer, "alice", {"alice_video"},
{"bob", "charlie"},
/*frames_count=*/2, *frame_generator);
analyzer.UnregisterParticipantInCall("bob");
analyzer.RegisterParticipantInCall("david");
- PassFramesThroughAnalyzer(analyzer, "alice", "alice_video",
+ PassFramesThroughAnalyzer(analyzer, "alice", {"alice_video"},
{"charlie", "david"},
/*frames_count=*/4, *frame_generator);
@@ -1850,14 +1854,14 @@
std::vector<std::string>{"alice", "bob", "charlie"},
kAnalyzerMaxThreadsCount);
- PassFramesThroughAnalyzer(analyzer, "alice", "alice_video",
+ PassFramesThroughAnalyzer(analyzer, "alice", {"alice_video"},
{"bob", "charlie"},
/*frames_count=*/2, *frame_generator);
analyzer.UnregisterParticipantInCall("bob");
- PassFramesThroughAnalyzer(analyzer, "alice", "alice_video", {"charlie"},
+ PassFramesThroughAnalyzer(analyzer, "alice", {"alice_video"}, {"charlie"},
/*frames_count=*/4, *frame_generator);
analyzer.RegisterParticipantInCall("bob");
- PassFramesThroughAnalyzer(analyzer, "alice", "alice_video",
+ PassFramesThroughAnalyzer(analyzer, "alice", {"alice_video"},
{"bob", "charlie"},
/*frames_count=*/6, *frame_generator);
@@ -1940,7 +1944,7 @@
analyzer.Start("test_case", std::vector<std::string>{"alice", "bob"},
kAnalyzerMaxThreadsCount);
- PassFramesThroughAnalyzer(analyzer, "alice", "alice_video", {"bob"},
+ PassFramesThroughAnalyzer(analyzer, "alice", {"alice_video"}, {"bob"},
/*frames_count=*/1, *frame_generator);
// Give analyzer some time to process frames on async thread. The computations
@@ -1972,7 +1976,7 @@
analyzer.Start("test_case", std::vector<std::string>{"alice", "bob"},
kAnalyzerMaxThreadsCount);
- PassFramesThroughAnalyzer(analyzer, "alice", "alice_video", {"bob"},
+ PassFramesThroughAnalyzer(analyzer, "alice", {"alice_video"}, {"bob"},
/*frames_count=*/1, *frame_generator);
// Give analyzer some time to process frames on async thread. The computations
@@ -2017,7 +2021,7 @@
VideoFrame received_to_be_dropped_frame = DeepCopy(to_be_dropped_frame);
analyzer.OnFramePreDecode("bob", received_to_be_dropped_frame.id(),
FakeEncode(received_to_be_dropped_frame));
- PassFramesThroughAnalyzer(analyzer, "alice", "alice_video", {"bob"},
+ PassFramesThroughAnalyzer(analyzer, "alice", {"alice_video"}, {"bob"},
/*frames_count=*/1, *frame_generator);
// Give analyzer some time to process frames on async thread. The computations
@@ -2046,7 +2050,7 @@
analyzer.Start("test_case", std::vector<std::string>{"alice", "bob"},
kAnalyzerMaxThreadsCount);
- PassFramesThroughAnalyzer(analyzer, "alice", "alice_video", {"bob"},
+ PassFramesThroughAnalyzer(analyzer, "alice", {"alice_video"}, {"bob"},
/*frames_count=*/5, *frame_generator,
/*interframe_delay_ms=*/50);
if (GetParam()) {
@@ -2102,25 +2106,25 @@
kAnalyzerMaxThreadsCount);
// Pass 20 frames as 20 fps.
- PassFramesThroughAnalyzer(analyzer, "alice", "alice_video",
+ PassFramesThroughAnalyzer(analyzer, "alice", {"alice_video"},
{"bob", "charlie"},
/*frames_count=*/20, *frame_generator,
/*interframe_delay_ms=*/50, time_controller());
AdvanceTime(TimeDelta::Millis(50));
// Mark stream paused for Bob, but not for Charlie.
- analyzer.OnPeerStoppedReceiveVideoStream("bob", "alice_video");
+ analyzer.OnPauseAllStreamsFrom("alice", "bob");
// Freeze for 1 second.
PassFramesThroughAnalyzerSenderOnly(
- analyzer, "alice", "alice_video", {"bob", "charlie"},
+ analyzer, "alice", {"alice_video"}, {"bob", "charlie"},
/*frames_count=*/20, *frame_generator,
/*interframe_delay_ms=*/50, time_controller());
AdvanceTime(TimeDelta::Millis(50));
// Unpause stream for Bob.
- analyzer.OnPeerStartedReceiveVideoStream("bob", "alice_video");
+ analyzer.OnResumeAllStreamsFrom("alice", "bob");
// Pass 20 frames as 20 fps.
- PassFramesThroughAnalyzer(analyzer, "alice", "alice_video",
+ PassFramesThroughAnalyzer(analyzer, "alice", {"alice_video"},
{"bob", "charlie"},
/*frames_count=*/20, *frame_generator,
/*interframe_delay_ms=*/50, time_controller());
@@ -2163,6 +2167,84 @@
}
TEST_F(DefaultVideoQualityAnalyzerSimulatedTimeTest,
+ PausedAndResumedTwoStreamsAreAccountedInStatsCorrectly) {
+ std::unique_ptr<test::FrameGeneratorInterface> frame_generator =
+ test::CreateSquareFrameGenerator(kFrameWidth, kFrameHeight,
+ /*type=*/absl::nullopt,
+ /*num_squares=*/absl::nullopt);
+
+ DefaultVideoQualityAnalyzer analyzer(
+ GetClock(), test::GetGlobalMetricsLogger(), AnalyzerOptionsForTest());
+ analyzer.Start("test_case", std::vector<std::string>{"alice", "bob"},
+ kAnalyzerMaxThreadsCount);
+
+ // Pass 20 frames as 20 fps on 2 streams.
+ PassFramesThroughAnalyzer(analyzer, "alice",
+ {"alice_video_1", "alice_video_2"}, {"bob"},
+ /*frames_count=*/20, *frame_generator,
+ /*interframe_delay_ms=*/50, time_controller());
+ AdvanceTime(TimeDelta::Millis(50));
+
+ // Mark streams paused.
+ analyzer.OnPauseAllStreamsFrom("alice", "bob");
+ // Freeze for 1 second.
+ PassFramesThroughAnalyzerSenderOnly(
+ analyzer, "alice", {"alice_video_1", "alice_video_2"}, {"bob"},
+ /*frames_count=*/20, *frame_generator,
+ /*interframe_delay_ms=*/50, time_controller());
+ AdvanceTime(TimeDelta::Millis(50));
+ // Unpause streams.
+ analyzer.OnResumeAllStreamsFrom("alice", "bob");
+
+ // Pass 20 frames as 20 fps on the 2 streams.
+ PassFramesThroughAnalyzer(analyzer, "alice",
+ {"alice_video_1", "alice_video_2"}, {"bob"},
+ /*frames_count=*/20, *frame_generator,
+ /*interframe_delay_ms=*/50, time_controller());
+
+ analyzer.Stop();
+
+ // Bob should have 20 fps without freeze on both streams.
+ std::map<StatsKey, StreamStats> streams_stats = analyzer.GetStats();
+ std::map<StatsKey, FrameCounters> frame_counters =
+ analyzer.GetPerStreamCounters();
+ StreamStats bob_stream_stats1 =
+ streams_stats.at(StatsKey("alice_video_1", "bob"));
+ FrameCounters bob_frame_counters1 =
+ frame_counters.at(StatsKey("alice_video_1", "bob"));
+ EXPECT_THAT(bob_frame_counters1.dropped, Eq(0));
+ EXPECT_THAT(bob_frame_counters1.rendered, Eq(40));
+ EXPECT_THAT(GetTimeSortedValues(bob_stream_stats1.freeze_time_ms),
+ ElementsAre(0.0));
+ // TODO(bugs.webrtc.org/14995): value should exclude pause
+ EXPECT_THAT(GetTimeSortedValues(bob_stream_stats1.time_between_freezes_ms),
+ ElementsAre(2950.0));
+ // TODO(bugs.webrtc.org/14995): Fix capture_frame_rate (has to be ~20.0)
+ ExpectRateIs(bob_stream_stats1.capture_frame_rate, 13.559322);
+ // TODO(bugs.webrtc.org/14995): Fix encode_frame_rate (has to be ~20.0)
+ ExpectRateIs(bob_stream_stats1.encode_frame_rate, 13.559322);
+ EXPECT_DOUBLE_EQ(bob_stream_stats1.harmonic_framerate_fps, 20.0);
+
+ // Bob should have 20 fps without freeze on both streams.
+ StreamStats bob_stream_stats_2 =
+ streams_stats.at(StatsKey("alice_video_2", "bob"));
+ FrameCounters bob_frame_counters_2 =
+ frame_counters.at(StatsKey("alice_video_2", "bob"));
+ EXPECT_THAT(bob_frame_counters_2.dropped, Eq(0));
+ EXPECT_THAT(bob_frame_counters_2.rendered, Eq(40));
+ EXPECT_THAT(GetTimeSortedValues(bob_stream_stats_2.freeze_time_ms),
+ ElementsAre(0.0));
+ // TODO(bugs.webrtc.org/14995): value should exclude pause
+ EXPECT_THAT(GetTimeSortedValues(bob_stream_stats_2.time_between_freezes_ms),
+ ElementsAre(2950.0));
+ // TODO(bugs.webrtc.org/14995): Fix capture_frame_rate (has to be ~20.0)
+ ExpectRateIs(bob_stream_stats_2.capture_frame_rate, 13.559322);
+ // TODO(bugs.webrtc.org/14995): Fix encode_frame_rate (has to be ~20.0)
+ ExpectRateIs(bob_stream_stats_2.encode_frame_rate, 13.559322);
+ EXPECT_DOUBLE_EQ(bob_stream_stats_2.harmonic_framerate_fps, 20.0);
+}
+
+TEST_F(DefaultVideoQualityAnalyzerSimulatedTimeTest,
PausedStreamIsAccountedInStatsCorrectly) {
std::unique_ptr<test::FrameGeneratorInterface> frame_generator =
test::CreateSquareFrameGenerator(kFrameWidth, kFrameHeight,
@@ -2178,23 +2260,23 @@
kAnalyzerMaxThreadsCount);
// Pass 20 frames as 20 fps.
- PassFramesThroughAnalyzer(analyzer, "alice", "alice_video",
+ PassFramesThroughAnalyzer(analyzer, "alice", {"alice_video"},
{"bob", "charlie"},
/*frames_count=*/20, *frame_generator,
/*interframe_delay_ms=*/50, time_controller());
AdvanceTime(TimeDelta::Millis(50));
// Mark stream paused for Bob, but not for Charlie.
- analyzer.OnPeerStoppedReceiveVideoStream("bob", "alice_video");
+ analyzer.OnPauseAllStreamsFrom("alice", "bob");
// Freeze for 1 second.
PassFramesThroughAnalyzerSenderOnly(
- analyzer, "alice", "alice_video", {"bob", "charlie"},
+ analyzer, "alice", {"alice_video"}, {"bob", "charlie"},
/*frames_count=*/20, *frame_generator,
/*interframe_delay_ms=*/50, time_controller());
AdvanceTime(TimeDelta::Millis(50));
// Pass 20 frames as 20 fps.
- PassFramesThroughAnalyzer(analyzer, "alice", "alice_video", {"charlie"},
+ PassFramesThroughAnalyzer(analyzer, "alice", {"alice_video"}, {"charlie"},
/*frames_count=*/20, *frame_generator,
/*interframe_delay_ms=*/50, time_controller());
diff --git a/test/pc/e2e/analyzer/video/dvqa/pausable_state.cc b/test/pc/e2e/analyzer/video/dvqa/pausable_state.cc
index f416e9b..ac191b8 100644
--- a/test/pc/e2e/analyzer/video/dvqa/pausable_state.cc
+++ b/test/pc/e2e/analyzer/video/dvqa/pausable_state.cc
@@ -19,13 +19,15 @@
namespace webrtc {
void PausableState::Pause() {
- RTC_CHECK(!IsPaused());
- events_.push_back(Event{.time = clock_->CurrentTime(), .is_paused = true});
+ if (!IsPaused()) {
+ events_.push_back(Event{.time = clock_->CurrentTime(), .is_paused = true});
+ }
}
void PausableState::Resume() {
- RTC_CHECK(IsPaused());
- events_.push_back(Event{.time = clock_->CurrentTime(), .is_paused = false});
+ if (IsPaused()) {
+ events_.push_back(Event{.time = clock_->CurrentTime(), .is_paused = false});
+ }
}
bool PausableState::IsPaused() const {
diff --git a/test/pc/e2e/analyzer/video/dvqa/pausable_state_test.cc b/test/pc/e2e/analyzer/video/dvqa/pausable_state_test.cc
index fe2b37b..ee48dde 100644
--- a/test/pc/e2e/analyzer/video/dvqa/pausable_state_test.cc
+++ b/test/pc/e2e/analyzer/video/dvqa/pausable_state_test.cc
@@ -59,6 +59,32 @@
EXPECT_FALSE(state.IsPaused());
}
+TEST_F(PausableStateTest, PauseAlreadyPausedIsNoOp) {
+ PausableState state(GetClock());
+
+ AdvanceTime(TimeDelta::Seconds(1));
+ Timestamp test_time = Now();
+ state.Pause();
+ AdvanceTime(TimeDelta::Seconds(1));
+ state.Pause();
+
+ EXPECT_EQ(state.GetLastEventTime(), test_time);
+}
+
+TEST_F(PausableStateTest, ResumeAlreadyResumedIsNoOp) {
+ PausableState state(GetClock());
+
+ AdvanceTime(TimeDelta::Seconds(1));
+ state.Pause();
+ AdvanceTime(TimeDelta::Seconds(1));
+ Timestamp test_time = Now();
+ state.Resume();
+ AdvanceTime(TimeDelta::Seconds(1));
+ state.Resume();
+
+ EXPECT_EQ(state.GetLastEventTime(), test_time);
+}
+
TEST_F(PausableStateTest, WasPausedAtFalseWhenMultiplePauseResumeAtSameTime) {
PausableState state(GetClock());