From dde48fd554f156f0e6fa24c60470c9b6fc0d4259 Mon Sep 17 00:00:00 2001 From: Micha Reiser Date: Thu, 8 Apr 2021 09:25:28 -0700 Subject: [PATCH] Use mock clock Summary: Change the `TransactionTelemetryTest` to use a mock clock or change the tests to only test if at least x amount of time has passed. Using a mock clock is the only way to make the test deterministic and be able to assert on the sub-results of the captured phases. Changelog: [Internal] Change to the `TelemetryTest`. Neither changes the runtime behavior nor the API. Reviewed By: sammy-SC Differential Revision: D27618448 fbshipit-source-id: 0cbf51b050aabb75341112ea4a43bea0115082f9 --- .../tests/TransactionTelemetryTest.cpp | 112 ++++++++++++++---- 1 file changed, 86 insertions(+), 26 deletions(-) diff --git a/ReactCommon/react/renderer/telemetry/tests/TransactionTelemetryTest.cpp b/ReactCommon/react/renderer/telemetry/tests/TransactionTelemetryTest.cpp index cacb2af4abc..f5e2ef488e1 100644 --- a/ReactCommon/react/renderer/telemetry/tests/TransactionTelemetryTest.cpp +++ b/ReactCommon/react/renderer/telemetry/tests/TransactionTelemetryTest.cpp @@ -15,61 +15,87 @@ using namespace facebook::react; -template -void sleep(double durationInSeconds) { - auto timepoint = ClockT::now() + - std::chrono::milliseconds((long long)(durationInSeconds * 1000)); - while (ClockT::now() < timepoint) { +class MockClock { + public: + typedef std::chrono:: + time_point + time_point; + + static time_point now() noexcept { + return time_; } + + template + static void advance_by(const TDuration duration) { + time_ += duration; + } + + private: + static time_point time_; +}; + +MockClock::time_point MockClock::time_ = {}; + +/** + * Ensures that the at least the specified time passes on a real clock. + * Why at least? Because operating systems provide no guarantee that our thread + * gets processing time after the specified time. What about using a busywait? + * Busywait are also affected by the non-deterministic OS process scheduling. + * The OS might decide right before the specified time elapsed to schedule + * another thread/process, with the result that more time passes in reality than + * the caller intended. Prefer the `MockClock` and only use this function to + * verify that at least the specifid time has passed but wihtout making exact + * verifications. + */ +static void sleepAtLeast(double durationInSeconds) { + std::this_thread::sleep_for( + std::chrono::milliseconds((long long)(durationInSeconds * 1000))); } TEST(TransactionTelemetryTest, timepoints) { - auto threshold = int64_t{70}; - auto timepointA = telemetryTimePointNow(); - sleep(0.1); + sleepAtLeast(0.1); auto timepointB = telemetryTimePointNow(); auto duration = telemetryDurationToMilliseconds(timepointB - timepointA); - EXPECT_NEAR(duration, 100, threshold); + EXPECT_GE(duration, 100); } TEST(TransactionTelemetryTest, normalUseCase) { - auto threshold = int64_t{70}; - auto telemetry = TransactionTelemetry{}; + auto telemetry = TransactionTelemetry{[]() { return MockClock::now(); }}; telemetry.setAsThreadLocal(); telemetry.willCommit(); - sleep(0.1); + MockClock::advance_by(std::chrono::milliseconds(100)); telemetry.willLayout(); - sleep(0.2); + MockClock::advance_by(std::chrono::milliseconds(200)); TransactionTelemetry::threadLocalTelemetry()->willMeasureText(); - sleep(0.1); + MockClock::advance_by(std::chrono::milliseconds(100)); TransactionTelemetry::threadLocalTelemetry()->didMeasureText(); TransactionTelemetry::threadLocalTelemetry()->willMeasureText(); - sleep(0.2); + MockClock::advance_by(std::chrono::milliseconds(200)); TransactionTelemetry::threadLocalTelemetry()->didMeasureText(); TransactionTelemetry::threadLocalTelemetry()->willMeasureText(); - sleep(0.3); + MockClock::advance_by(std::chrono::milliseconds(300)); TransactionTelemetry::threadLocalTelemetry()->didMeasureText(); telemetry.didLayout(); - sleep(0.1); + MockClock::advance_by(std::chrono::milliseconds(100)); telemetry.didCommit(); telemetry.setRevisionNumber(42); telemetry.unsetAsThreadLocal(); - sleep(0.3); + MockClock::advance_by(std::chrono::milliseconds(300)); telemetry.willMount(); - sleep(0.1); + MockClock::advance_by(std::chrono::milliseconds(100)); telemetry.didMount(); auto commitDuration = telemetryDurationToMilliseconds( @@ -79,18 +105,52 @@ TEST(TransactionTelemetryTest, normalUseCase) { auto mountDuration = telemetryDurationToMilliseconds( telemetry.getMountEndTime() - telemetry.getMountStartTime()); - EXPECT_NEAR(commitDuration, 1000, threshold); - EXPECT_NEAR(layoutDuration, 800, threshold); - EXPECT_NEAR(mountDuration, 100, threshold); + EXPECT_EQ(commitDuration, 1000); + EXPECT_EQ(layoutDuration, 800); + EXPECT_EQ(mountDuration, 100); EXPECT_EQ(telemetry.getNumberOfTextMeasurements(), 3); - EXPECT_NEAR( - telemetryDurationToMilliseconds(telemetry.getTextMeasureTime()), - 600, - threshold); + EXPECT_EQ( + telemetryDurationToMilliseconds(telemetry.getTextMeasureTime()), 600); EXPECT_EQ(telemetry.getRevisionNumber(), 42); } +TEST(TransactionTelemetryTest, defaultImplementation) { + auto telemetry = TransactionTelemetry{}; + + telemetry.setAsThreadLocal(); + + telemetry.willCommit(); + sleepAtLeast(0.1); + telemetry.willLayout(); + sleepAtLeast(0.2); + + TransactionTelemetry::threadLocalTelemetry()->willMeasureText(); + sleepAtLeast(0.1); + TransactionTelemetry::threadLocalTelemetry()->didMeasureText(); + + telemetry.didLayout(); + sleepAtLeast(0.1); + telemetry.didCommit(); + + telemetry.unsetAsThreadLocal(); + + telemetry.willMount(); + sleepAtLeast(0.1); + telemetry.didMount(); + + auto commitDuration = telemetryDurationToMilliseconds( + telemetry.getCommitEndTime() - telemetry.getCommitStartTime()); + auto layoutDuration = telemetryDurationToMilliseconds( + telemetry.getLayoutEndTime() - telemetry.getLayoutStartTime()); + auto mountDuration = telemetryDurationToMilliseconds( + telemetry.getMountEndTime() - telemetry.getMountStartTime()); + + EXPECT_GE(commitDuration, 500); + EXPECT_GE(layoutDuration, 300); + EXPECT_GE(mountDuration, 100); +} + TEST(TransactionTelemetryTest, abnormalUseCases) { // Calling `did` before `will` should crash. EXPECT_DEATH_IF_SUPPORTED(