From 1c76fd384cb9c7a41c183e6d64147265733a71b6 Mon Sep 17 00:00:00 2001 From: Jon Simantov Date: Mon, 12 Feb 2024 13:24:46 -0800 Subject: [PATCH] Switch to a more generic method to get a server timestamp for mobile tests. (#1528) * Add a method to get a server timestamp on mobile. * Change tests to use new server time method. * Add desktop source file missing from some tests. * Fix 64-bit integers on Windows. * Add offset to Windows time calculation. * Add missing source file for tvOS Auth tests. --- analytics/integration_test/CMakeLists.txt | 1 + app/integration_test/CMakeLists.txt | 1 + app_check/integration_test/CMakeLists.txt | 1 + auth/integration_test/CMakeLists.txt | 1 + .../project.pbxproj | 2 + database/integration_test/CMakeLists.txt | 1 + .../integration_test/src/integration_test.cc | 40 ++++-------- firestore/integration_test/CMakeLists.txt | 1 + .../integration_test_internal/CMakeLists.txt | 1 + functions/integration_test/CMakeLists.txt | 1 + installations/integration_test/CMakeLists.txt | 1 + remote_config/integration_test/CMakeLists.txt | 1 + storage/integration_test/CMakeLists.txt | 1 + .../integration_test/src/integration_test.cc | 28 +------- .../src/desktop/desktop_app_framework.cc | 6 +- .../src/firebase_test_framework.cc | 65 +++++++++++++++++++ .../src/firebase_test_framework.h | 5 ++ 17 files changed, 99 insertions(+), 58 deletions(-) diff --git a/analytics/integration_test/CMakeLists.txt b/analytics/integration_test/CMakeLists.txt index 956206969d..4824536107 100644 --- a/analytics/integration_test/CMakeLists.txt +++ b/analytics/integration_test/CMakeLists.txt @@ -189,6 +189,7 @@ else() # Platform abstraction layer for the desktop integration test. set(FIREBASE_APP_FRAMEWORK_DESKTOP_SRCS src/desktop/desktop_app_framework.cc + src/desktop/desktop_firebase_test_framework.cc ) set(integration_test_target_name "integration_test") diff --git a/app/integration_test/CMakeLists.txt b/app/integration_test/CMakeLists.txt index 5195f99feb..e0aaacdfb7 100644 --- a/app/integration_test/CMakeLists.txt +++ b/app/integration_test/CMakeLists.txt @@ -189,6 +189,7 @@ else() # Platform abstraction layer for the desktop integration test. set(FIREBASE_APP_FRAMEWORK_DESKTOP_SRCS src/desktop/desktop_app_framework.cc + src/desktop/desktop_firebase_test_framework.cc ) set(integration_test_target_name "integration_test") diff --git a/app_check/integration_test/CMakeLists.txt b/app_check/integration_test/CMakeLists.txt index 859b7cf9ce..6e0f3f2a15 100644 --- a/app_check/integration_test/CMakeLists.txt +++ b/app_check/integration_test/CMakeLists.txt @@ -189,6 +189,7 @@ else() # Platform abstraction layer for the desktop integration test. set(FIREBASE_APP_FRAMEWORK_DESKTOP_SRCS src/desktop/desktop_app_framework.cc + src/desktop/desktop_firebase_test_framework.cc ) set(integration_test_target_name "integration_test") diff --git a/auth/integration_test/CMakeLists.txt b/auth/integration_test/CMakeLists.txt index ab5be09e71..a3bfb8256a 100644 --- a/auth/integration_test/CMakeLists.txt +++ b/auth/integration_test/CMakeLists.txt @@ -189,6 +189,7 @@ else() # Platform abstraction layer for the desktop integration test. set(FIREBASE_APP_FRAMEWORK_DESKTOP_SRCS src/desktop/desktop_app_framework.cc + src/desktop/desktop_firebase_test_framework.cc ) set(integration_test_target_name "integration_test") diff --git a/auth/integration_test/integration_test.xcodeproj/project.pbxproj b/auth/integration_test/integration_test.xcodeproj/project.pbxproj index 9503d69287..bdbceb97fc 100644 --- a/auth/integration_test/integration_test.xcodeproj/project.pbxproj +++ b/auth/integration_test/integration_test.xcodeproj/project.pbxproj @@ -28,6 +28,7 @@ D62CCBC022F367140099BE9F /* gmock-all.cc in Sources */ = {isa = PBXBuildFile; fileRef = D62CCBBF22F367140099BE9F /* gmock-all.cc */; }; D66B16871CE46E8900E5638A /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = D66B16861CE46E8900E5638A /* LaunchScreen.storyboard */; }; D67D355822BABD2200292C1D /* gtest-all.cc in Sources */ = {isa = PBXBuildFile; fileRef = D67D355622BABD2100292C1D /* gtest-all.cc */; }; + D6A908EC2B786EDF00EF5160 /* ios_firebase_test_framework.mm in Sources */ = {isa = PBXBuildFile; fileRef = D6C179E822CB322900C2651A /* ios_firebase_test_framework.mm */; }; D6C179E922CB322900C2651A /* ios_app_framework.mm in Sources */ = {isa = PBXBuildFile; fileRef = D6C179E722CB322900C2651A /* ios_app_framework.mm */; }; D6C179EA22CB322900C2651A /* ios_firebase_test_framework.mm in Sources */ = {isa = PBXBuildFile; fileRef = D6C179E822CB322900C2651A /* ios_firebase_test_framework.mm */; }; D6C179EE22CB323300C2651A /* firebase_test_framework.cc in Sources */ = {isa = PBXBuildFile; fileRef = D6C179EC22CB323300C2651A /* firebase_test_framework.cc */; }; @@ -368,6 +369,7 @@ 9F3A09032669792100E1D69F /* app_framework.cc in Sources */, 9F3A09022669791E00E1D69F /* gtest-all.cc in Sources */, 9F3A09042669792400E1D69F /* firebase_test_framework.cc in Sources */, + D6A908EC2B786EDF00EF5160 /* ios_firebase_test_framework.mm in Sources */, 9F5DD0AC266A7AD30058C144 /* integration_test.cc in Sources */, 9F5DD0AB266A7ACE0058C144 /* ios_app_framework.mm in Sources */, ); diff --git a/database/integration_test/CMakeLists.txt b/database/integration_test/CMakeLists.txt index 4a8cc6f19a..1bab3ecd56 100644 --- a/database/integration_test/CMakeLists.txt +++ b/database/integration_test/CMakeLists.txt @@ -189,6 +189,7 @@ else() # Platform abstraction layer for the desktop integration test. set(FIREBASE_APP_FRAMEWORK_DESKTOP_SRCS src/desktop/desktop_app_framework.cc + src/desktop/desktop_firebase_test_framework.cc ) set(integration_test_target_name "integration_test") diff --git a/database/integration_test/src/integration_test.cc b/database/integration_test/src/integration_test.cc index 5c8d93a572..23e1497b78 100644 --- a/database/integration_test/src/integration_test.cc +++ b/database/integration_test/src/integration_test.cc @@ -147,8 +147,6 @@ class FirebaseDatabaseTest : public FirebaseTest { // Shut down Firebase Database. void TerminateDatabase(); - int64_t GetRemoteTimeInMilliseconds(); - firebase::database::DatabaseReference CreateWorkingPath( bool suppress_cleanup = false); @@ -368,30 +366,6 @@ firebase::database::DatabaseReference FirebaseDatabaseTest::CreateWorkingPath( return ref; } -int64_t FirebaseDatabaseTest::GetRemoteTimeInMilliseconds() { - firebase::database::DatabaseReference ref = - CreateWorkingPath().Child("timestamp"); - WaitForCompletionAnyResult( - ref.SetValue(firebase::database::ServerTimestamp()), - "GetRemoteTime_SetValue"); - firebase::Future future = ref.GetValue(); - WaitForCompletionAnyResult(future, "GetRemoteTime_GetValue"); - int64_t timestamp = 0; - if (future.error() == 0 && future.result() && - future.result()->value().is_int64()) { - timestamp = future.result()->value().int64_value(); - } - if (timestamp > 0) { - LogDebug("Got server timestamp: %lld", timestamp); - LogDebug(" Local timestamp: %lld", - app_framework::GetCurrentTimeInMicroseconds() / 1000L); - return timestamp; - } else { - LogWarning("Couldn't get remote timestamp, using local time"); - return app_framework::GetCurrentTimeInMicroseconds() / 1000L; - } -} - // Test cases below. TEST_F(FirebaseDatabaseTest, TestInitializeAndTerminate) { // Already tested via SetUp() and TearDown(). @@ -487,7 +461,11 @@ TEST_F(FirebaseDatabaseTest, TestSetAndGetSimpleValues) { WaitForCompletion(f7, "GetLongDouble"); // Get the current time to compare to the Timestamp. - int64_t current_time_milliseconds = GetRemoteTimeInMilliseconds(); + int64_t current_time_milliseconds = + GetCurrentTimeInSecondsSinceEpoch() * 1000LL; + LogDebug("Comparing current time %" PRId64 " with timestamp %" PRId64, + current_time_milliseconds, + f5.result()->value().AsInt64().int64_value()); EXPECT_EQ(f1.result()->value().AsString(), kSimpleString); EXPECT_EQ(f2.result()->value().AsInt64(), kSimpleInt); @@ -698,8 +676,12 @@ TEST_F(FirebaseDatabaseTest, TestUpdateChildren) { WaitForCompletion(update_future, "UpdateChildren"); read_future = ref.Child(test_name).GetValue(); WaitForCompletion(read_future, "GetValue 2"); - int64_t current_time_milliseconds = GetRemoteTimeInMilliseconds(); - + int64_t current_time_milliseconds = + GetCurrentTimeInSecondsSinceEpoch() * 1000L; + LogDebug( + "Comparing current time %" PRId64 " with timestamp %" PRId64, + current_time_milliseconds, + read_future.result()->value().map()["timestamp"].AsInt64().int64_value()); EXPECT_THAT( read_future.result()->value().map(), UnorderedElementsAre( diff --git a/firestore/integration_test/CMakeLists.txt b/firestore/integration_test/CMakeLists.txt index af2d1b8150..7dd3952a47 100644 --- a/firestore/integration_test/CMakeLists.txt +++ b/firestore/integration_test/CMakeLists.txt @@ -189,6 +189,7 @@ else() # Platform abstraction layer for the desktop integration test. set(FIREBASE_APP_FRAMEWORK_DESKTOP_SRCS src/desktop/desktop_app_framework.cc + src/desktop/desktop_firebase_test_framework.cc ) set(integration_test_target_name "integration_test") diff --git a/firestore/integration_test_internal/CMakeLists.txt b/firestore/integration_test_internal/CMakeLists.txt index 8e651e75cd..99ba6ccaa3 100644 --- a/firestore/integration_test_internal/CMakeLists.txt +++ b/firestore/integration_test_internal/CMakeLists.txt @@ -345,6 +345,7 @@ else() # Platform abstraction layer for the desktop integration test. set(FIREBASE_APP_FRAMEWORK_DESKTOP_SRCS src/desktop/desktop_app_framework.cc + src/desktop/desktop_firebase_test_framework.cc ) set(integration_test_target_name "integration_test") diff --git a/functions/integration_test/CMakeLists.txt b/functions/integration_test/CMakeLists.txt index dccb88cd50..b65f09a42d 100644 --- a/functions/integration_test/CMakeLists.txt +++ b/functions/integration_test/CMakeLists.txt @@ -189,6 +189,7 @@ else() # Platform abstraction layer for the desktop integration test. set(FIREBASE_APP_FRAMEWORK_DESKTOP_SRCS src/desktop/desktop_app_framework.cc + src/desktop/desktop_firebase_test_framework.cc ) set(integration_test_target_name "integration_test") diff --git a/installations/integration_test/CMakeLists.txt b/installations/integration_test/CMakeLists.txt index 1bc9bc879e..f849ff4fa1 100644 --- a/installations/integration_test/CMakeLists.txt +++ b/installations/integration_test/CMakeLists.txt @@ -189,6 +189,7 @@ else() # Platform abstraction layer for the desktop integration test. set(FIREBASE_APP_FRAMEWORK_DESKTOP_SRCS src/desktop/desktop_app_framework.cc + src/desktop/desktop_firebase_test_framework.cc ) set(integration_test_target_name "integration_test") diff --git a/remote_config/integration_test/CMakeLists.txt b/remote_config/integration_test/CMakeLists.txt index 6e99d6836e..d34a64fbe9 100644 --- a/remote_config/integration_test/CMakeLists.txt +++ b/remote_config/integration_test/CMakeLists.txt @@ -189,6 +189,7 @@ else() # Platform abstraction layer for the desktop integration test. set(FIREBASE_APP_FRAMEWORK_DESKTOP_SRCS src/desktop/desktop_app_framework.cc + src/desktop/desktop_firebase_test_framework.cc ) set(integration_test_target_name "integration_test") diff --git a/storage/integration_test/CMakeLists.txt b/storage/integration_test/CMakeLists.txt index 71011b2138..c700a0e260 100644 --- a/storage/integration_test/CMakeLists.txt +++ b/storage/integration_test/CMakeLists.txt @@ -189,6 +189,7 @@ else() # Platform abstraction layer for the desktop integration test. set(FIREBASE_APP_FRAMEWORK_DESKTOP_SRCS src/desktop/desktop_app_framework.cc + src/desktop/desktop_firebase_test_framework.cc ) set(integration_test_target_name "integration_test") diff --git a/storage/integration_test/src/integration_test.cc b/storage/integration_test/src/integration_test.cc index 23e3b5b59a..f430de37de 100644 --- a/storage/integration_test/src/integration_test.cc +++ b/storage/integration_test/src/integration_test.cc @@ -118,8 +118,6 @@ class FirebaseStorageTest : public FirebaseTest { // Create a unique working folder and return a reference to it. firebase::storage::StorageReference CreateFolder(); - int64_t GetRemoteTimeInMilliseconds(); - static firebase::App* shared_app_; static firebase::auth::Auth* shared_auth_; @@ -326,30 +324,6 @@ firebase::storage::StorageReference FirebaseStorageTest::CreateFolder() { return storage_->GetReference(kRootNodeName).Child(saved_url_); } -int64_t FirebaseStorageTest::GetRemoteTimeInMilliseconds() { - SignIn(); - - firebase::storage::StorageReference ref = - CreateFolder().Child("timestamp.txt"); - firebase::Future future = - RunWithRetry( - [&]() { return ref.PutBytes("TS00", 4); }); - WaitForCompletionAnyResult(future, "GetRemoteTime_PutBytes"); - if (future.error() == 0 && future.result() != nullptr && - future.result()->creation_time() > 0) { - int64_t timestamp = future.result()->creation_time(); - WaitForCompletionAnyResult(RunWithRetry([&]() { return ref.Delete(); }), - "GetRemoteTime_Delete"); - LogDebug("Got server timestamp: %lld", timestamp); - LogDebug(" Local timestamp: %lld", - app_framework::GetCurrentTimeInMicroseconds() / 1000L); - return timestamp; - } else { - LogWarning("Couldn't get remote timestamp, using local time"); - return app_framework::GetCurrentTimeInMicroseconds() / 1000L; - } -} - // Test cases below. TEST_F(FirebaseStorageTest, TestInitializeAndTerminate) { @@ -523,7 +497,7 @@ TEST_F(FirebaseStorageTest, TestWriteAndReadFileWithCustomMetadata) { ASSERT_NE(metadata, nullptr); // Get the current time to compare to the Timestamp. - int64_t current_time_seconds = GetRemoteTimeInMilliseconds() / 1000L; + int64_t current_time_seconds = GetCurrentTimeInSecondsSinceEpoch(); int64_t updated_time_milliseconds = metadata->updated_time(); int64_t updated_time_seconds = updated_time_milliseconds / 1000L; int64_t time_difference_seconds = diff --git a/testing/sample_framework/src/desktop/desktop_app_framework.cc b/testing/sample_framework/src/desktop/desktop_app_framework.cc index 9f58bd4383..6875baf4f4 100644 --- a/testing/sample_framework/src/desktop/desktop_app_framework.cc +++ b/testing/sample_framework/src/desktop/desktop_app_framework.cc @@ -175,9 +175,11 @@ int64_t GetCurrentTimeInMicroseconds() { now.LowPart = file_time.dwLowDateTime; now.HighPart = file_time.dwHighDateTime; + // Windows file time has a different offset, year 1601 instead of 1970. + now.QuadPart -= 116444736000000000ULL; // Windows file time is expressed in 100s of nanoseconds. - // To convert to microseconds, multiply x10. - return now.QuadPart * 10LL; + // To convert to microseconds, divide by 10. + return now.QuadPart / 10LL; } #endif // defined(_WIN32) diff --git a/testing/test_framework/src/firebase_test_framework.cc b/testing/test_framework/src/firebase_test_framework.cc index 3ca6677a4a..bc6e7e4a55 100644 --- a/testing/test_framework/src/firebase_test_framework.cc +++ b/testing/test_framework/src/firebase_test_framework.cc @@ -14,14 +14,19 @@ #include "firebase_test_framework.h" // NOLINT +#include + #include #include #include +#include #include #include #include "firebase/future.h" +using app_framework::LogDebug; + namespace firebase { namespace internal { // Borrow Firebase's internal Base64 encoder and decoder. @@ -293,6 +298,66 @@ bool FirebaseTest::Base64Decode(const std::string& input, std::string* output) { return ::firebase::internal::Base64Decode(input, output); } +int64_t FirebaseTest::GetCurrentTimeInSecondsSinceEpoch() { +#if defined(ANDROID) || (defined(TARGET_OS_IPHONE) && TARGET_OS_IPHONE) + // Quick and dirty function to retrieve GMT time from worldtimeapi.org + // and parse the very simple JSON response to obtain the "unixtime" value. + // If any step fails, it will return the local time instead. + std::map empty_headers; + int response_code = 0; + std::string response_body; + bool success = + SendHttpGetRequest("https://worldtimeapi.org/api/timezone/GMT", + empty_headers, &response_code, &response_body); + if (!success || response_code != 200 || response_body.empty()) { + LogDebug("GetCurrentTimeInSecondsSinceEpoch: HTTP request failed"); + return (app_framework::GetCurrentTimeInMicroseconds() / 1000000LL); + } + + const char kJsonTag[] = "\"unixtime\":"; + size_t begin = response_body.find(kJsonTag); + if (begin < 0) { + LogDebug( + "GetCurrentTimeInSecondsSinceEpoch: Can't find unixtime JSON field in " + "response: %s", + response_body.c_str()); + return (app_framework::GetCurrentTimeInMicroseconds() / 1000000LL); + } + begin += strlen(kJsonTag); + if (response_body[begin] == ' ') { + // Advance past a single space, if present. + begin++; + } + + size_t end = response_body.find(",", begin); + if (end < 0) end = response_body.find("}", begin); + if (end < 0) { + LogDebug( + "GetCurrentTimeInSecondsSinceEpoch: Can't extract unixtime JSON field " + "from response: %s", + response_body.c_str()); + return (app_framework::GetCurrentTimeInMicroseconds() / 1000000LL); + } + std::string time_str = response_body.substr(begin, end - begin); + int64_t timestamp = std::stoll(time_str); + if (timestamp <= 0) { + LogDebug( + "GetCurrentTimeInSecondsSinceEpoch: Can't parse unixtime JSON value %s", + time_str.c_str()); + return (app_framework::GetCurrentTimeInMicroseconds() / 1000000LL); + } + LogDebug("Got remote timestamp: %" PRId64, timestamp); + return timestamp; +#else + // On desktop, just return the local time since SendHttpGetRequest is not + // implemented. + int64_t time_in_microseconds = + app_framework::GetCurrentTimeInMicroseconds() / 1000000LL; + LogDebug("Got local time: %" PRId64, time_in_microseconds); + return (time_in_microseconds); +#endif +} + class LogTestEventListener : public testing::EmptyTestEventListener { public: void OnTestPartResult( diff --git a/testing/test_framework/src/firebase_test_framework.h b/testing/test_framework/src/firebase_test_framework.h index 443faca7df..af698e3653 100644 --- a/testing/test_framework/src/firebase_test_framework.h +++ b/testing/test_framework/src/firebase_test_framework.h @@ -567,6 +567,11 @@ class FirebaseTest : public testing::Test { static std::string GetDebugDeviceId(); + // Get the current time in seconds since epoch. On desktop, this will use the + // local machine's time. On mobile, it will retrieve UTC time from a server if + // possible, otherwise use the local machine's time. + int64_t GetCurrentTimeInSecondsSinceEpoch(); + firebase::App* app_; static int argc_; static char** argv_;