From 03bd7f60227af0abd20bd60ab268bea2e049f4d5 Mon Sep 17 00:00:00 2001 From: a-maurice Date: Tue, 28 Jul 2026 16:44:39 -0700 Subject: [PATCH] Remove old testlab code --- Android/firebase_dependencies.gradle | 1 - .../Android/firebase_dependencies.gradle | 4 - release_build_files/CMakeLists.txt | 1 - .../integration_testing/build_testapps.json | 16 +- testlab/src/android/testlab.cc | 103 ------ testlab/src/android/util.cc | 328 ------------------ testlab/src/android/util.h | 48 --- testlab/src/common/common.cc | 213 ------------ testlab/src/common/common.h | 66 ---- testlab/src/common/scenario_result.fbs | 30 -- testlab/src/desktop/testlab_desktop.cc | 240 ------------- testlab/src/desktop/testlab_macos.h | 33 -- testlab/src/desktop/testlab_macos.mm | 25 -- testlab/src/include/firebase/testlab.h | 109 ------ testlab/src/ios/.clang-format | 2 - testlab/src/ios/custom_results.h | 38 -- testlab/src/ios/custom_results.mm | 124 ------- testlab/src/ios/testlab.mm | 248 ------------- 18 files changed, 1 insertion(+), 1628 deletions(-) delete mode 100644 testlab/src/android/testlab.cc delete mode 100644 testlab/src/android/util.cc delete mode 100644 testlab/src/android/util.h delete mode 100644 testlab/src/common/common.cc delete mode 100644 testlab/src/common/common.h delete mode 100644 testlab/src/common/scenario_result.fbs delete mode 100644 testlab/src/desktop/testlab_desktop.cc delete mode 100644 testlab/src/desktop/testlab_macos.h delete mode 100644 testlab/src/desktop/testlab_macos.mm delete mode 100644 testlab/src/include/firebase/testlab.h delete mode 100644 testlab/src/ios/.clang-format delete mode 100644 testlab/src/ios/custom_results.h delete mode 100644 testlab/src/ios/custom_results.mm delete mode 100644 testlab/src/ios/testlab.mm diff --git a/Android/firebase_dependencies.gradle b/Android/firebase_dependencies.gradle index 59eb331580..55d0f5bb64 100644 --- a/Android/firebase_dependencies.gradle +++ b/Android/firebase_dependencies.gradle @@ -35,7 +35,6 @@ def firebaseDependenciesMap = [ 'performance' : ['com.google.firebase:firebase-perf'], 'remote_config' : ['com.google.firebase:firebase-config'], 'storage' : ['com.google.firebase:firebase-storage'], - 'testlab' : [], 'ump' : ['com.google.android.ump:user-messaging-platform:4.0.0'] ] diff --git a/release_build_files/Android/firebase_dependencies.gradle b/release_build_files/Android/firebase_dependencies.gradle index 5915bf654a..c3dfe97fe1 100644 --- a/release_build_files/Android/firebase_dependencies.gradle +++ b/release_build_files/Android/firebase_dependencies.gradle @@ -35,7 +35,6 @@ def firebaseDependenciesMap = [ 'performance' : ['com.google.firebase:firebase-perf'], 'remote_config' : ['com.google.firebase:firebase-config'], 'storage' : ['com.google.firebase:firebase-storage'], - 'testlab' : [], 'ump' : ['com.google.android.ump:user-messaging-platform:4.0.0'] ] @@ -83,9 +82,6 @@ class Dependencies { def getStorage() { libSet.add('storage') } - def getTestlab() { - libSet.add('testlab') - } def getUmp() { libSet.add('ump') } diff --git a/release_build_files/CMakeLists.txt b/release_build_files/CMakeLists.txt index 0b5f69d9fb..5e4dc9208d 100644 --- a/release_build_files/CMakeLists.txt +++ b/release_build_files/CMakeLists.txt @@ -102,7 +102,6 @@ add_firebase_target(firebase_messaging) add_firebase_target(firebase_performance) add_firebase_target(firebase_remote_config) add_firebase_target(firebase_storage) -add_firebase_target(firebase_testlab) add_firebase_target(firebase_ump) # Auth on Linux desktop has an additional dependency on libsecret, diff --git a/scripts/gha/integration_testing/build_testapps.json b/scripts/gha/integration_testing/build_testapps.json index 058b665b25..6990d0e8d0 100755 --- a/scripts/gha/integration_testing/build_testapps.json +++ b/scripts/gha/integration_testing/build_testapps.json @@ -181,19 +181,6 @@ "firebase.xcframework" ], "provision": "Google_Development.mobileprovision" - }, - { - "name": "test_lab", - "full_name": "FirebaseTestLab", - "bundle_id": "com.google.ios.testlab.testapp", - "ios_target": "integration_test", - "tvos_target": "", - "testapp_path": "testlab/integration_test", - "frameworks": [ - "firebase_testlab.xcframework", - "firebase.xcframework" - ], - "provision": "Google_Development.mobileprovision" } ], "apple_team_id": "REPLACE_ME_TEMP_INVALID_ID", @@ -250,8 +237,7 @@ "'com.google.firebase:firebase-messaging:[0,)'],", "'performance' : ['com.google.firebase:firebase-perf:[0,)'],", "'remote_config' : ['com.google.firebase:firebase-config:[0,)'],", - "'storage' : ['com.google.firebase:firebase-storage:[0,)'],", - "'testlab' : []", + "'storage' : ['com.google.firebase:firebase-storage:[0,)']", "]" ] } diff --git a/testlab/src/android/testlab.cc b/testlab/src/android/testlab.cc deleted file mode 100644 index 3d967d4fff..0000000000 --- a/testlab/src/android/testlab.cc +++ /dev/null @@ -1,103 +0,0 @@ -// Copyright 2019 Google LLC -// -// 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 "testlab/src/include/firebase/testlab.h" - -#include - -#include "app/src/include/firebase/app.h" -#include "app/src/include/firebase/log.h" -#include "app/src/log.h" -#include "app/src/reference_count.h" -#include "app/src/util.h" -#include "app/src/util_android.h" -#include "testlab/src/android/util.h" -#include "testlab/src/common/common.h" - -using firebase::internal::ReferenceCount; -using firebase::internal::ReferenceCountLock; - -namespace firebase { -namespace test_lab { -namespace game_loop { - -static ReferenceCount g_initializer; // NOLINT - -namespace internal { - -// Determine whether the test lab module is initialized. -bool IsInitialized() { return g_initializer.references() > 0; } - -} // namespace internal - -// Initialize the API -void Initialize(const firebase::App& app) { - ReferenceCountLock ref_count(&g_initializer); - if (ref_count.references() != 0) { - LogWarning("Test Lab API already initialized"); - return; - } - ref_count.AddReference(); - LogDebug("Firebase Test Lab API initializing"); - internal::Initialize(&app); -} - -// Clean up the API -void Terminate() { - ReferenceCountLock ref_count(&g_initializer); - if (ref_count.references() == 0) { - LogWarning("Test Lab API was never initialized"); - return; - } - if (ref_count.references() == 1) { - internal::Terminate(); - } - ref_count.RemoveReference(); -} - -// Return the game loop scenario's integer ID, or 0 if no game loop is running -int GetScenario() { - if (!internal::IsInitialized()) return 0; - return internal::GetScenario(); -} - -// Log progress text to the game loop's custom results and device logs -void LogText(const char* format, ...) { - if (GetScenario() == 0) return; - va_list args; - va_start(args, format); - internal::LogText(format, args); - va_end(args); -} - -// Complete the game loop scenario with the specified outcome -void FinishScenario(ScenarioOutcome outcome) { - if (GetScenario() == 0) return; - FILE* result_file = internal::RetrieveCustomResultsFile(); - if (result_file == nullptr) { - LogError("Could not obtain the custom results file"); - } else { - internal::OutputResult(outcome, result_file); - } - internal::CallFinish(); - Terminate(); - // TODO(brandonmorris): This works, but isn't the proper way to exit the app. - // Look into either using ANativeActivity_finish or calling finish() on the - // main thread. - exit(0); -} - -} // namespace game_loop -} // namespace test_lab -} // namespace firebase diff --git a/testlab/src/android/util.cc b/testlab/src/android/util.cc deleted file mode 100644 index 87ef68d75d..0000000000 --- a/testlab/src/android/util.cc +++ /dev/null @@ -1,328 +0,0 @@ -// Copyright 2019 Google LLC -// -// 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 -#include - -#include "app/src/include/firebase/app.h" -#include "app/src/util_android.h" -#include "testlab/src/common/common.h" - -namespace firebase { -namespace test_lab { -namespace game_loop { -namespace internal { - -static const ::firebase::App* g_app = nullptr; -static const char* kFirebaseTestLabAuthority = - "content://com.google.firebase.testlab"; -static const char* kScenarioCol = "scenario"; -static const char* kCustomResultsCol = "customResultUri"; - -std::string* g_custom_result_uri; - -int GetScenario(); -static void InitFromIntent(); -static bool InitFromContentProvider(); -static void GetIntentUri(); - -FILE* GetTempFile(); - -void CreateOrOpenLogFile() { - if (!g_log_file) { - g_log_file = GetTempFile(); - } - if (!g_log_file) { - LogError( - "Could not create a cache directory file for custom results. No custom " - "results will " - "be logged for the duration of the game loop scenario. %s", - strerror(errno)); - } -} - -void Initialize(const ::firebase::App* app) { - g_app = app; - CreateOrOpenLogFile(); - if (!InitFromContentProvider()) { - LogDebug( - "Could not find scenario data from content provider, falling back to " - "intent"); - InitFromIntent(); - } -} - -void Terminate() { - g_app = nullptr; - if (g_custom_result_uri) { - delete g_custom_result_uri; - g_custom_result_uri = nullptr; - } - SetScenario(0); - CloseLogFile(); - TerminateCommon(); -} - -// Creates a global reference to the cursor retrieved by querying the FTL -// content provider. Caller is repsonsible for deleting the reference. -static jobject QueryContentProvider() { - JNIEnv* env = g_app->GetJNIEnv(); - // contentResolver = activity.getContentResolver() - jobject content_resolver = env->CallObjectMethod( - g_app->activity(), - util::activity::GetMethodId(util::activity::kGetContentResolver)); - if (util::CheckAndClearJniExceptions(env) || content_resolver == nullptr) - return nullptr; - jstring authority = env->NewStringUTF(kFirebaseTestLabAuthority); - // authority = Uri.parse(authorityStr) - jobject authority_uri = env->CallStaticObjectMethod( - util::uri::GetClass(), util::uri::GetMethodId(util::uri::kParse), - authority); - util::CheckAndClearJniExceptions(env); - // cursor = contentResolver.query(authority, null, null, null, null) - jobject cursor_local = env->CallObjectMethod( - content_resolver, - util::content_resolver::GetMethodId(util::content_resolver::kQuery), - authority_uri, nullptr, nullptr, nullptr, nullptr); - jobject cursor = env->NewGlobalRef(cursor_local); - env->DeleteLocalRef(cursor_local); - env->DeleteLocalRef(content_resolver); - env->DeleteLocalRef(authority); - env->DeleteLocalRef(authority_uri); - if (util::CheckAndClearJniExceptions(env) || cursor == nullptr) - return nullptr; - return cursor; -} - -static int GetScenarioFromCursor(jobject cursor) { - if (cursor == nullptr) return 0; - JNIEnv* env = g_app->GetJNIEnv(); - // int scenarioCol = cursor.getColumnIndex(SCENARIO_COL); - jstring scenario_col_name = env->NewStringUTF(kScenarioCol); - int scenario_col = env->CallIntMethod( - cursor, util::cursor::GetMethodId(util::cursor::kGetColumnIndex), - scenario_col_name); - env->DeleteLocalRef(scenario_col_name); - if (util::CheckAndClearJniExceptions(env) || scenario_col == -1) return 0; - - // cursor.moveToFirst() - env->CallBooleanMethod(cursor, - util::cursor::GetMethodId(util::cursor::kMoveToFirst)); - util::CheckAndClearJniExceptions(env); - - // cursor.getInt(scenarioCol) - int scenario = env->CallIntMethod( - cursor, util::cursor::GetMethodId(util::cursor::kGetInt), scenario_col); - util::CheckAndClearJniExceptions(env); - LogDebug("Retrieved scenario from the content provider: %d", scenario); - return scenario; -} - -static const char* GetResultsUriFromCursor(jobject cursor) { - if (cursor == nullptr) return nullptr; - JNIEnv* env = g_app->GetJNIEnv(); - // int customResultCol = cursor.getColumnIndex(CUSTOM_RESULT_COL); - jstring custom_result_col_name = env->NewStringUTF(kCustomResultsCol); - int custom_result_col = env->CallIntMethod( - cursor, util::cursor::GetMethodId(util::cursor::kGetColumnIndex), - custom_result_col_name); - env->DeleteLocalRef(custom_result_col_name); - if (util::CheckAndClearJniExceptions(env) || custom_result_col == -1) - return nullptr; - - // cursor.moveToFirst() - env->CallBooleanMethod(cursor, - util::cursor::GetMethodId(util::cursor::kMoveToFirst)); - util::CheckAndClearJniExceptions(env); - - // cursor.getString(customResultCol) - jobject custom_result_str = env->CallObjectMethod( - cursor, util::cursor::GetMethodId(util::cursor::kGetString), - custom_result_col); - util::CheckAndClearJniExceptions(env); - const char* custom_result = - util::JniStringToString(env, custom_result_str).c_str(); - LogDebug("Found the custom result uri string from the content provider: %s", - custom_result); - return custom_result; -} - -// Attempt to initialize game loop scenario data from the Test Lab content -// provider and return whether successful -static bool InitFromContentProvider() { - JNIEnv* env = g_app->GetJNIEnv(); - jobject cursor = QueryContentProvider(); - if (cursor == nullptr) { - LogWarning( - "Firebase Test Lab content provider does not exist or could not be " - "queried."); - return false; - } - - int scenario = GetScenarioFromCursor(cursor); - if (scenario == 0) return false; - SetScenario(scenario); - const char* custom_result = GetResultsUriFromCursor(cursor); - env->DeleteGlobalRef(cursor); - if (custom_result == nullptr) return false; - g_custom_result_uri = new std::string(custom_result); - - return true; -} - -// Helper method to initialize the game loop scenario number -static void InitFromIntent() { - JNIEnv* env = g_app->GetJNIEnv(); - jobject activity = env->NewLocalRef(g_app->activity()); - - // Intent intent = app.getIntent(); - jobject intent = env->CallObjectMethod( - activity, util::activity::GetMethodId(util::activity::kGetIntent)); - if (util::CheckAndClearJniExceptions(env) || !intent) return; - - // scenario = intent.getIntExtra("scenario"); - jstring scenario_key = env->NewStringUTF("scenario"); - int scenario = env->CallIntMethod( - intent, util::intent::GetMethodId(util::intent::kGetIntExtra), - scenario_key, 0); - util::CheckAndClearJniExceptions(env); - env->DeleteLocalRef(intent); - - LogInfo("Received the scenario number %d", scenario); - SetScenario(scenario); - GetIntentUri(); -} - -// Helper method to retrieve the URI data from the intent -static void GetIntentUri() { - JNIEnv* env = g_app->GetJNIEnv(); - jobject intent = env->CallObjectMethod( - g_app->activity(), - util::activity::GetMethodId(util::activity::kGetIntent)); - util::CheckAndClearJniExceptions(env); - jobject uri = env->CallObjectMethod( - intent, util::intent::GetMethodId(util::intent::kGetData)); - env->DeleteLocalRef(intent); - util::CheckAndClearJniExceptions(env); - if (uri == nullptr) { - LogError( - "Intent did not contain a valid file descriptor for the game " - "loop custom results. If you manually set the scenario number, " - "you must also provide a custom results directory or no results " - "will be logged"); - return; - } - jobject uri_str = - env->CallObjectMethod(uri, util::uri::GetMethodId(util::uri::kToString)); - env->DeleteLocalRef(uri); - std::string custom_result_str = util::JniStringToString(env, uri_str); - g_custom_result_uri = new std::string(custom_result_str); -} - -// Helper method to initialize the custom results logging -FILE* RetrieveCustomResultsFile() { - if (g_results_dir != nullptr) { - return OpenCustomResultsFile(GetScenario()); - } - if (g_custom_result_uri == nullptr) { - LogError( - "No URI of a custom results asset were found, no custom results will " - "be logged."); - return nullptr; - } - JNIEnv* env = g_app->GetJNIEnv(); - // activity.getContentResolver() - jobject content_resolver = env->CallObjectMethod( - g_app->activity(), - util::activity::GetMethodId(util::activity::kGetContentResolver)); - - jstring uri_str = env->NewStringUTF(g_custom_result_uri->c_str()); - jobject uri = env->CallStaticObjectMethod( - util::uri::GetClass(), util::uri::GetMethodId(util::uri::kParse), - uri_str); - util::CheckAndClearJniExceptions(env); - env->DeleteLocalRef(uri_str); - - // contentResolver.openAssetFileDescriptor(Uri, String) - jobject asset_file_descriptor = env->CallObjectMethod( - content_resolver, - util::content_resolver::GetMethodId( - util::content_resolver::kOpenAssetFileDescriptor), - uri, env->NewStringUTF("w")); - util::CheckAndClearJniExceptions(env); - env->DeleteLocalRef(uri); - env->DeleteLocalRef(content_resolver); - - // assetFileDescriptor.getParcelFileDescriptor() - jobject parcel_file_descriptor = env->CallObjectMethod( - asset_file_descriptor, - util::asset_file_descriptor::GetMethodId( - util::asset_file_descriptor::kGetParcelFileDescriptor)); - util::CheckAndClearJniExceptions(env); - env->DeleteLocalRef(asset_file_descriptor); - - // parcelFileDescriptor.detachFd() - jint fd = env->CallIntMethod(parcel_file_descriptor, - util::parcel_file_descriptor::GetMethodId( - util::parcel_file_descriptor::kDetachFd)); - util::CheckAndClearJniExceptions(env); - env->DeleteLocalRef(parcel_file_descriptor); - - FILE* log_file = fdopen(fd, "w"); - if (env->ExceptionCheck() || log_file == nullptr) { - LogError( - "Firebase game loop custom results file could not be opened. Any " - "logged results will not appear in the test's custom results."); - } - return log_file; -} - -FILE* GetTempFile() { - JNIEnv* env = g_app->GetJNIEnv(); - // File cache_dir_file = getCacheDir(); - jobject cache_dir_file = env->CallObjectMethod( - g_app->activity(), - util::activity::GetMethodId(util::activity::kGetCacheDir)); - if (util::CheckAndClearJniExceptions(env) || !cache_dir_file) { - LogError("Could not obtain a temporary file"); - return nullptr; - } - - // String cache_dir_path = cache_dir_file.getPath(); - jstring cache_dir_path = static_cast(env->CallObjectMethod( - cache_dir_file, util::file::GetMethodId(util::file::kGetPath))); - std::string cache_dir_string = util::JniStringToString(env, cache_dir_path); - env->DeleteLocalRef(cache_dir_file); - if (util::CheckAndClearJniExceptions(env) || !cache_dir_path) { - LogError("Could not obtain a temporary file"); - return nullptr; - } - - std::string cache_file = cache_dir_string + "gameloopresultstemp.txt"; - return fopen(cache_file.c_str(), "w+"); -} - -void CallFinish() { - JNIEnv* env = g_app->GetJNIEnv(); - jobject activity = env->NewLocalRef(g_app->activity()); - env->CallVoidMethod(activity, - util::activity::GetMethodId(util::activity::kFinish)); - env->DeleteLocalRef(activity); - util::CheckAndClearJniExceptions(env); -} - -} // namespace internal -} // namespace game_loop -} // namespace test_lab -} // namespace firebase diff --git a/testlab/src/android/util.h b/testlab/src/android/util.h deleted file mode 100644 index 5d7175d8a3..0000000000 --- a/testlab/src/android/util.h +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright 2019 Google LLC -// -// 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. - -#ifndef FIREBASE_TESTLAB_SRC_ANDROID_UTIL_H_ -#define FIREBASE_TESTLAB_SRC_ANDROID_UTIL_H_ - -#include - -#include "app/src/include/firebase/app.h" - -namespace firebase { -namespace test_lab { -namespace game_loop { -namespace internal { - -/// Returns true if the Test Lab API has been initialized and a game loop is -/// running -bool IsInitialized(); - -/// Prepares any platform-specific resources associated with the SDK. -void Initialize(const ::firebase::App* app); - -/// Cleans up any platform-specific resources associated with the SDK. -void Terminate(); - -/// Obtains a file handle to the custom results file sent by the intent. -FILE* RetrieveCustomResultsFile(); - -/// Calls finish() on the activity, ending the game loop scenario. -void CallFinish(); - -} // namespace internal -} // namespace game_loop -} // namespace test_lab -} // namespace firebase - -#endif // FIREBASE_TESTLAB_SRC_ANDROID_UTIL_H_ diff --git a/testlab/src/common/common.cc b/testlab/src/common/common.cc deleted file mode 100644 index 42eb8ab5ae..0000000000 --- a/testlab/src/common/common.cc +++ /dev/null @@ -1,213 +0,0 @@ -// Copyright 2019 Google LLC -// -// 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 "testlab/src/common/common.h" - -#include -#include -#include - -#include -#include -#include -#include - -#include "app/src/log.h" -#include "app/src/util.h" -#include "flatbuffers/idl.h" -#include "flatbuffers/util.h" -#include "testlab/scenario_result_generated.h" -#include "testlab/scenario_result_resource.h" -#include "testlab/src/include/firebase/testlab.h" - -// Register the module initializer. -FIREBASE_APP_REGISTER_CALLBACKS( - test_lab, - { - if (app == ::firebase::App::GetInstance()) { - firebase::test_lab::game_loop::Initialize(*app); - } - return kInitResultSuccess; - }, - { - if (app == ::firebase::App::GetInstance()) { - firebase::test_lab::game_loop::Terminate(); - } - }, - false); - -namespace firebase { -namespace test_lab { -namespace game_loop { - -void SetScenario(int scenario_number) { - if (!internal::IsInitialized() || GetScenario() == scenario_number) return; - LogDebug("Resetting scenario number to %d", scenario_number); - internal::ResetLog(); - internal::SetScenario(scenario_number); -} - -void SetResultsDirectory(const char* path) { - internal::SetResultsDirectory(path); -} - -std::string GetResultsDirectory() { return internal::GetResultsDirectory(); } - -namespace internal { - -FILE* g_log_file; -std::string* g_results_dir; - -static const char* kRootType = "ScenarioResult"; - -static int g_scenario = 0; - -void SetScenario(int scenario) { g_scenario = scenario; } -int GetScenario() { return g_scenario; } -std::string GetResultsDirectory() { - if (g_results_dir == nullptr) { - return ""; - } - return *g_results_dir; -} - -std::vector ReadLines(FILE* file); -std::string ScenarioToString(int scenario); - -const char* OutcomeToString( - ::firebase::test_lab::game_loop::ScenarioOutcome outcome) { - switch (outcome) { - case kScenarioOutcomeSuccess: - return "success"; - case kScenarioOutcomeFailure: - return "failure"; - } -} - -void OutputResult(::firebase::test_lab::game_loop::ScenarioOutcome outcome, - FILE* result_file) { - flatbuffers::FlatBufferBuilder builder(1024); - std::string outcome_string = OutcomeToString(outcome); - auto outcome_string_offset = builder.CreateString(outcome_string); - std::rewind(g_log_file); - std::vector logs = ReadLines(g_log_file); - auto text_log_offset = builder.CreateVectorOfStrings(logs); - ScenarioResultBuilder result_builder(builder); - result_builder.add_scenario_number(GetScenario()); - result_builder.add_outcome(outcome_string_offset); - result_builder.add_text_log(text_log_offset); - auto result = result_builder.Finish(); - builder.Finish(result); - flatbuffers::Parser parser; - const char* schema = - reinterpret_cast(scenario_result_resource_data); - parser.Parse(schema); - parser.SetRootType(kRootType); - std::string jsongen; - flatbuffers::GenerateText(parser, builder.GetBufferPointer(), &jsongen); - fprintf(result_file, "%s", jsongen.c_str()); - fflush(result_file); -} - -void LogText(const char* format, va_list args) { - vfprintf(g_log_file, format, args); - fprintf(g_log_file, "\n"); - fflush(g_log_file); - LogMessageV(firebase::kLogLevelDebug, format, args); -} - -void CloseLogFile() { - if (g_log_file) { - fclose(g_log_file); - g_log_file = nullptr; - } -} - -std::vector ReadLines(FILE* file) { - fseek(file, 0, SEEK_END); - auto file_size = ftell(file); - rewind(file); - std::vector buffer(file_size); - if (file_size != fread(&buffer[0], 1, file_size, file)) { - LogError( - "Could not read the custom results log file. Any results logged during " - "the game loop scenario will not be included in the custom results."); - return std::vector(); - } - return TokenizeByCharacter(buffer, '\n'); -} - -std::vector TokenizeByCharacter(std::vector buffer, - char token) { - std::vector tokens; - auto line_begin = buffer.begin(); - auto buffer_end = buffer.end(); - while (line_begin < buffer_end) { - auto line_end = std::find(line_begin, buffer_end, token); - tokens.push_back(std::string(line_begin, line_end)); - line_begin = line_end + 1; - } - return tokens; -} - -void TerminateCommon() { SetResultsDirectory(nullptr); } - -void ResetLog() { - if (g_log_file) { - fclose(g_log_file); - g_log_file = nullptr; - } - CreateOrOpenLogFile(); -} - -void SetResultsDirectory(const char* path) { - if (g_results_dir) { - delete g_results_dir; - g_results_dir = nullptr; - } - if (path && strlen(path)) { - g_results_dir = new std::string(path); - } -} - -FILE* OpenCustomResultsFile(int scenario) { - std::string file_name = - "results_scenario_" + ScenarioToString(scenario) + ".json"; - std::string file_path; - if (g_results_dir != nullptr) { - file_path = *g_results_dir + "/" + file_name; - } else { - file_path = file_name; - } - FILE* file = fopen(file_path.c_str(), "w"); - if (file == nullptr) { - LogError( - "Could not open custom results file at %s. Results for this scenario " - "will not be included: %s", - file_path.c_str(), strerror(errno)); - } - return file; -} - -// std::to_string() isn't supported on Android NDK -std::string ScenarioToString(int scenario) { - std::ostringstream stream; - stream << scenario; - return stream.str(); -} - -} // namespace internal -} // namespace game_loop -} // namespace test_lab -} // namespace firebase diff --git a/testlab/src/common/common.h b/testlab/src/common/common.h deleted file mode 100644 index fecdb32981..0000000000 --- a/testlab/src/common/common.h +++ /dev/null @@ -1,66 +0,0 @@ -// Copyright 2019 Google LLC -// -// 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. - -#ifndef FIREBASE_TESTLAB_SRC_COMMON_COMMON_H_ -#define FIREBASE_TESTLAB_SRC_COMMON_COMMON_H_ - -#include - -#include -#include - -#include "testlab/src/include/firebase/testlab.h" - -namespace firebase { -namespace test_lab { -namespace game_loop { -namespace internal { - -extern FILE* g_log_file; -extern std::string* g_results_dir; - -void SetScenario(int scenario); -int GetScenario(); - -// Formats and produces the custom result file -void OutputResult(::firebase::test_lab::game_loop::ScenarioOutcome outcome, - FILE* result_file); - -// Format and write the scenario's custom results. Does not close the file after -// writing. -void LogText(const char* format, va_list args); - -void TerminateCommon(); - -void CreateOrOpenLogFile(); // Implemented in platform specific module -void CloseLogFile(); -bool IsInitialized(); - -std::vector TokenizeByCharacter(std::vector buffer, - char token); - -void ResetLog(); - -void SetResultsDirectory(const char* path); - -std::string GetResultsDirectory(); - -FILE* OpenCustomResultsFile(int scenario); - -} // namespace internal -} // namespace game_loop -} // namespace test_lab -} // namespace firebase - -#endif // FIREBASE_TESTLAB_SRC_COMMON_COMMON_H_ diff --git a/testlab/src/common/scenario_result.fbs b/testlab/src/common/scenario_result.fbs deleted file mode 100644 index 0020b3d09c..0000000000 --- a/testlab/src/common/scenario_result.fbs +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright 2020 Google LLC -// -// 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. - -// The FlatBuffers schema for game loop result data - -// The namespace is defined to match the internal package. -namespace firebase.test_lab.game_loop.internal; - -// Data table that describes the result of a game loop scenario. -table ScenarioResult { - // The scenario's numeric ID - scenario_number:int; - - // The outcome of the scenario, e.g. success. - outcome:string; - - // Messages logged by the user during the scenario's execution. - text_log:[string]; -} diff --git a/testlab/src/desktop/testlab_desktop.cc b/testlab/src/desktop/testlab_desktop.cc deleted file mode 100644 index 6ea8091bdf..0000000000 --- a/testlab/src/desktop/testlab_desktop.cc +++ /dev/null @@ -1,240 +0,0 @@ -// Copyright 2019 Google LLC -// -// 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 - -#include "app/src/include/firebase/app.h" -#include "app/src/log.h" -#include "app/src/path.h" -#include "app/src/reference_count.h" -#include "flatbuffers/util.h" -#include "testlab/src/common/common.h" -#include "testlab/src/include/firebase/testlab.h" - -#if defined(_WIN32) -// windows.h must be first to define basic Windows types. -// clang-format off -#include // NOLINT -// clang-format on -#include -#include -#else -#include -#include -#endif // defined(_WIN32) - -#if FIREBASE_PLATFORM_OSX -#include "testlab/src/desktop/testlab_macos.h" -#endif - -#if FIREBASE_PLATFORM_WINDOWS -#define mkdir(x, y) _mkdir(x) -#endif // FIREBASE_PLATFORM_WINDOWS - -using firebase::internal::ReferenceCount; -using firebase::internal::ReferenceCountLock; - -namespace firebase { -namespace test_lab { -namespace game_loop { - -static ReferenceCount g_initializer; // NOLINT - -static const char* kScenarioFlagPrefix = "--game_loop_scenario="; -static const char* kResultsDirFlagPrefix = "--game_loop_results_dir="; -static const char* kLogFileName = "firebase-game-loop.log"; - -namespace internal { - -// Determine whether the test lab module is initialized. -bool IsInitialized() { return g_initializer.references() > 0; } -static void ParseCommandLineArgs(); -static FILE* GetCustomResultsFile(); - -} // namespace internal - -void Initialize(const ::firebase::App& app) { - ReferenceCountLock ref_count(&g_initializer); - if (ref_count.references() != 0) { - LogWarning("Test Lab API already initialized"); - return; - } - ref_count.AddReference(); - internal::CreateOrOpenLogFile(); - internal::ParseCommandLineArgs(); -} - -void Terminate() { - ReferenceCountLock ref_count(&g_initializer); - if (ref_count.references() == 0) { - LogWarning("Test Lab API was terminated or never initialized"); - return; - } - if (ref_count.references() == 1) { - internal::SetScenario(0); - internal::CloseLogFile(); - internal::TerminateCommon(); - } - ref_count.RemoveReference(); -} - -int GetScenario() { - if (!internal::IsInitialized()) { - return 0; - } - return internal::GetScenario(); -} - -void LogText(const char* format, ...) { - ReferenceCountLock ref_count(&g_initializer); - if (GetScenario() == 0) return; - va_list args; - va_start(args, format); - internal::LogText(format, args); - va_end(args); -} - -void FinishScenario(::firebase::test_lab::game_loop::ScenarioOutcome outcome) { - if (GetScenario() == 0) return; - FILE* result_file = internal::OpenCustomResultsFile(GetScenario()); - if (result_file != nullptr) { - internal::OutputResult(outcome, result_file); - } - Terminate(); - // TODO(brandonmorris): Find a way to signal a test is complete (e.g. write to - // a file in the results dir or set an env var). -} - -namespace internal { - -static bool NotEmpty(const char* str) { - return str != nullptr && str[0] != '\0'; -} - -static const char* GetTempDir() { - char* temp; -#if defined(_WIN32) - temp = std::getenv("TMP"); - if (NotEmpty(temp)) return temp; - temp = std::getenv("TEMP"); - if (NotEmpty(temp)) return temp; - temp = std::getenv("USERPROFILE"); - if (NotEmpty(temp)) return temp; - // If all else fails, return the current directory - return ""; -#else - temp = std::getenv("TMPDIR"); - if (NotEmpty(temp)) return temp; - temp = std::getenv("TMP"); - if (NotEmpty(temp)) return temp; - temp = std::getenv("TEMP"); - if (NotEmpty(temp)) return temp; - temp = std::getenv("TEMPDIR"); - if (NotEmpty(temp)) return temp; - return "/tmp"; -#endif // defined(_WIN32) -} - -void CreateOrOpenLogFile() { - std::string temp = GetTempDir(); - std::string log_filename = temp + "/" + kLogFileName; - g_log_file = fopen(log_filename.c_str(), "w+"); - if (g_log_file == nullptr) { - LogError( - "Could not open the temporary log file at %s. Any logs from this game " - "loop scenario will not be included in the custom results: %s", - log_filename.c_str(), strerror(errno)); - } -} - -static std::string GetResultFilename() { - if (GetScenario() <= 0) { - return ""; - } - return "results_scenario_" + std::to_string(GetScenario()) + ".json"; -} - -static std::string GetArgumentForPrefix(std::string prefix, - std::vector arguments) { - for (const std::string& argument : arguments) { - if (argument.compare(0, prefix.size(), prefix) == 0) { - return argument.substr(prefix.size()); - } - } - return std::string(); -} - -static void SetScenarioIfNotEmpty(std::string scenario_str) { - if (!scenario_str.empty()) { - int scenario; - flatbuffers::StringToNumber(scenario_str.c_str(), &scenario); - SetScenario(scenario); - } -} - -static std::vector GetCommandLineArgs() { -#if FIREBASE_PLATFORM_WINDOWS - wchar_t** arg_list; - int n_args; - arg_list = CommandLineToArgvW(GetCommandLineW(), &n_args); - std::vector arguments(n_args); - for (int i = 0; i < n_args; i++) { - const wchar_t* arg = arg_list[i]; - int arg_characters = static_cast(wcslen(arg_list[i])); - int size_needed = WideCharToMultiByte(CP_UTF8, 0, arg, arg_characters, NULL, - 0, NULL, NULL); - std::string argument(size_needed, 0); - std::vector buffer(size_needed, 0); - WideCharToMultiByte(CP_UTF8, 0, arg, arg_characters, &buffer[0], - size_needed, NULL, NULL); - arguments[i].append(&buffer[0]); - } - LocalFree(arg_list); - return arguments; -#elif FIREBASE_PLATFORM_OSX - return GetArguments(); -#elif FIREBASE_PLATFORM_LINUX - FILE* proc = fopen("/proc/self/cmdline", "r"); - std::vector buffer; - // /proc "files" aren't real files, so we can't know the total length ahead of - // time and need to read one character at a time. - for (;;) { - int c = getc(proc); - if (c == EOF) break; - buffer.push_back(c); - } - return TokenizeByCharacter(buffer, '\0'); -#else -#warning Game loop command line flags will not be parsed - return std::vector(); -#endif -} - -static void ParseCommandLineArgs() { - std::vector arguments = GetCommandLineArgs(); - std::string scenario_str = - GetArgumentForPrefix(kScenarioFlagPrefix, arguments); - SetScenarioIfNotEmpty(scenario_str); - std::string directory = - GetArgumentForPrefix(kResultsDirFlagPrefix, arguments); - if (!directory.empty()) { - SetResultsDirectory(directory.c_str()); - } -} - -} // namespace internal - -} // namespace game_loop -} // namespace test_lab -} // namespace firebase diff --git a/testlab/src/desktop/testlab_macos.h b/testlab/src/desktop/testlab_macos.h deleted file mode 100644 index 4b4a4b6b44..0000000000 --- a/testlab/src/desktop/testlab_macos.h +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright 2020 Google LLC -// -// 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. - -#ifndef FIREBASE_TESTLAB_SRC_DESKTOP_TESTLAB_MACOS_H_ -#define FIREBASE_TESTLAB_SRC_DESKTOP_TESTLAB_MACOS_H_ - -#include - -namespace firebase { -namespace test_lab { -namespace game_loop { -namespace internal { - -// Get the command line arguments used to launch the running process. -std::vector GetArguments(); - -} // namespace internal -} // namespace game_loop -} // namespace test_lab -} // namespace firebase - -#endif // FIREBASE_TESTLAB_SRC_DESKTOP_TESTLAB_MACOS_H_ diff --git a/testlab/src/desktop/testlab_macos.mm b/testlab/src/desktop/testlab_macos.mm deleted file mode 100644 index f64137bba7..0000000000 --- a/testlab/src/desktop/testlab_macos.mm +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright 2021 Google LLC - -#include -#include - -#include - -namespace firebase { -namespace test_lab { -namespace game_loop { -namespace internal { - -std::vector GetArguments() { - NSArray* args = [[NSProcessInfo processInfo] arguments]; - std::vector arguments(args.count); - for (int i = 0; i < args.count; i++) { - arguments[i] = args[i].UTF8String; - } - return arguments; -} - -} // firebase -} // test_lab -} // game_loop -} // internal diff --git a/testlab/src/include/firebase/testlab.h b/testlab/src/include/firebase/testlab.h deleted file mode 100644 index 6dfdd95732..0000000000 --- a/testlab/src/include/firebase/testlab.h +++ /dev/null @@ -1,109 +0,0 @@ -/* - * Copyright 2019 Google LLC - * - * 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. - */ - -#ifndef FIREBASE_TESTLAB_SRC_INCLUDE_FIREBASE_TESTLAB_H_ -#define FIREBASE_TESTLAB_SRC_INCLUDE_FIREBASE_TESTLAB_H_ - -#include - -#include "firebase/app.h" -#include "firebase/internal/common.h" - -#if !defined(DOXYGEN) && !defined(SWIG) -FIREBASE_APP_REGISTER_CALLBACKS_REFERENCE(test_lab) -#endif // !defined(DOXYGEN) && !defined(SWIG) - -/// @brief Namespace that encompasses all Firebase APIs. -namespace firebase { - -/// @brief Firebase Test Lab API. -/// -/// See the developer guides for general -/// information on using Firebase Test Lab. -/// -/// This library is experimental and is not currently officially supported. -namespace test_lab { -namespace game_loop { - -/// @brief Indicate the outcome of a game loop scenario (e.g. success). -enum ScenarioOutcome { kScenarioOutcomeSuccess, kScenarioOutcomeFailure }; - -/// @brief Initialize the Test Lab Game Loop API. -/// -/// This must be called prior to calling any other methods in the -/// firebase::test_lab::game_loop namespace. -/// -/// @param[in] app Default firebase::App instance. -/// -/// @see firebase::App::GetInstance(). -void Initialize(const firebase::App& app); - -/// @brief Terminate the Test Lab Game Loop API. -/// -/// @note The application will continue to run after calling this method, but -/// any future calls to methods in the firebase::test_lab::game_loop namespace -/// will have no effect unless it is initialized again. -/// -/// @note If this function is called during a game loop, any results logged as -/// part of that game loop scenario will not appear in the scenario's custom -/// results. -/// -/// Cleans up resources associated with the Test Lab Game Loop API. -void Terminate(); - -/// @brief Retrieve the current scenario number of a game loop test. -/// -/// @return A positive integer representing the current game loop scenario, or -/// 0 if not game loop is running. -int GetScenario(); - -/// @brief Record progress of a game loop to the test's custom results. -/// -/// @note These messages also forwarded to the system log at the DEBUG level. -/// -/// @param[in] format Format string of the message to include in the scenario's -/// custom results file. -void LogText(const char* format, ...); - -/// @brief Complete the current game loop scenario and exit the application. -/// -/// @note This method implicitly calls `game_loop::Terminate()` prior to -/// exiting. If no game loop is running, this method has no effect. -/// -/// Finish the current game loop scenario by cleaning up its resources and -/// exiting the application. -void FinishScenario(ScenarioOutcome outcome); - -/// @brief Set the scenario of the currently running test. -/// -/// @note Calling this method and changing the scenario will clear any results -/// for the previous scenario. -void SetScenario(int scenario_number); - -/// @brief Set the directory where custom results will be written to when -/// FinishScenario() is called. -void SetResultsDirectory(const char* path); - -/// @brief The currently set directory where custom results will be written to -/// when FinishScenario() is called. If no directory has been set, this function -/// returns an empty string. -std::string GetResultsDirectory(); - -} // namespace game_loop -} // namespace test_lab -} // namespace firebase - -#endif // FIREBASE_TESTLAB_SRC_INCLUDE_FIREBASE_TESTLAB_H_ diff --git a/testlab/src/ios/.clang-format b/testlab/src/ios/.clang-format deleted file mode 100644 index 9d159247d5..0000000000 --- a/testlab/src/ios/.clang-format +++ /dev/null @@ -1,2 +0,0 @@ -DisableFormat: true -SortIncludes: false diff --git a/testlab/src/ios/custom_results.h b/testlab/src/ios/custom_results.h deleted file mode 100644 index b0bebd8b40..0000000000 --- a/testlab/src/ios/custom_results.h +++ /dev/null @@ -1,38 +0,0 @@ -// Copyright 2019 Google LLC -// -// 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. - -#ifndef FIREBASE_TESTLAB_SRC_IOS_CUSTOM_RESULTS_H_ -#define FIREBASE_TESTLAB_SRC_IOS_CUSTOM_RESULTS_H_ - -#include - -namespace firebase { -namespace test_lab { -namespace game_loop { -namespace internal { - -static const char* kResultsDir = "GameLoopResults"; - -// Creates and returns the custom results file. -FILE* CreateCustomResultsFile(int scenario); - -// Creates and returns the log file for storing intermediate results. -FILE* CreateLogFile(); - -} // namespace internal -} // namespace game_loop -} // namespace test_lab -} // namespace firebase - -#endif // FIREBASE_TESTLAB_SRC_IOS_CUSTOM_RESULTS_H_ diff --git a/testlab/src/ios/custom_results.mm b/testlab/src/ios/custom_results.mm deleted file mode 100644 index 6e80da54f2..0000000000 --- a/testlab/src/ios/custom_results.mm +++ /dev/null @@ -1,124 +0,0 @@ -// Copyright 2019 Google LLC -// -// 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 "testlab/src/ios/custom_results.h" - -#import - -#include -#include -#include "app/src/assert.h" -#include "app/src/util_ios.h" -#include "testlab/src/common/common.h" -#include "testlab/src/include/firebase/testlab.h" - -namespace firebase { -namespace test_lab { -namespace game_loop { -namespace internal { - -static const char* kCustomResultDirErrorMsgFmt = - "Could not create Test Lab's custom results directory. It will not " - "be possible to log test results for Test Lab game loop scenarios. " - "NSFileManager:URLForDirectory returned error %s"; -static const char* kCustomResultFileErrorMsgFmt = - "Test Lab could not create the custom result file. It will not be " - "possible to log test results for Test Lab test cases. " - "NSFileManager:createFileAtPath returned error code %d: %s"; - -static NSURL* GetDocumentsUrl(NSFileManager* manager); -static void LogCustomResultsDirError(NSError* error); -static FILE* OpenFileOrLogError(NSFileManager* manager, NSURL* file_url, const char* mode); - -void CreateOrOpenLogFile() { - if (!g_log_file) { - g_log_file = CreateLogFile(); - } -} - -FILE* CreateCustomResultsFile(int scenario) { - if (g_results_dir != nullptr) { - return OpenCustomResultsFile(scenario); - } - // iOS sandboxes apps' filesystems, so we need to find the documents directory - // through the NSFileManager - NSFileManager* manager = [NSFileManager defaultManager]; - NSURL* documents_url = GetDocumentsUrl(manager); - NSURL* results_dir = [documents_url URLByAppendingPathComponent:@(kResultsDir) isDirectory:YES]; - BOOL results_dir_exists = [manager fileExistsAtPath:results_dir.path]; - if (!results_dir_exists) { - NSError* error; - BOOL create_dir_result = [manager createDirectoryAtURL:results_dir - withIntermediateDirectories:NO - attributes:nil - error:&error]; - if (!create_dir_result) { - LogCustomResultsDirError(error); - return nullptr; - } - } - - // Use the filename that matches the Android results file created by FTL - NSString* result_filename = [NSString stringWithFormat:@"results_scenario_%d.json", scenario]; - NSURL* result_url = [results_dir URLByAppendingPathComponent:result_filename isDirectory:NO]; - return OpenFileOrLogError(manager, result_url, "w"); -} - -FILE* CreateLogFile() { - NSFileManager* manager = [NSFileManager defaultManager]; - NSURL* documents_url = GetDocumentsUrl(manager); - NSURL* log_url = [documents_url URLByAppendingPathComponent:@"firebase-game-loop.log" - isDirectory:NO]; - return OpenFileOrLogError(manager, log_url, "w+"); -} - -static NSURL* GetDocumentsUrl(NSFileManager* manager) { - NSError* error = nil; - NSURL* documents_url = [manager URLForDirectory:NSDocumentDirectory - inDomain:NSUserDomainMask - appropriateForURL:nil - create:NO - error:&error]; - if (documents_url == nil) { - LogCustomResultsDirError(error); - return nullptr; - } - return documents_url; -} - -static void LogCustomResultsDirError(NSError* error) { - NSString* errorMessage = [NSString stringWithFormat:@"%@", error]; - LogError(kCustomResultDirErrorMsgFmt, errorMessage.UTF8String); -} - -static FILE* OpenFileOrLogError(NSFileManager* manager, NSURL* file_url, const char* mode) { - BOOL create_file_result = [manager createFileAtPath:file_url.path contents:nil attributes:nil]; - if (!create_file_result) { - LogError(kCustomResultFileErrorMsgFmt, errno, strerror(errno)); - return nullptr; - } - std::string file_path = file_url.path.UTF8String; - FILE* file = std::fopen(file_path.c_str(), mode); - if (file == nullptr) { - LogError( - "Could not open file %s. Custom results for this scenario may be missing or incomplete", - file_path.c_str()); - } - return file; -} - -} // namespace internal -} // namespace game_loop -} // namespace test_lab -} // namespace firebase diff --git a/testlab/src/ios/testlab.mm b/testlab/src/ios/testlab.mm deleted file mode 100644 index 9d6f92bd8f..0000000000 --- a/testlab/src/ios/testlab.mm +++ /dev/null @@ -1,248 +0,0 @@ -// Copyright 2019 Google LLC -// -// 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 "testlab/src/include/firebase/testlab.h" - -#import -#import - -#include "app/src/include/firebase/app.h" -#include "app/src/include/firebase/internal/mutex.h" -#include "app/src/log.h" -#include "app/src/reference_count.h" -#include "app/src/util_ios.h" -#include "testlab/src/common/common.h" -#include "testlab/src/ios/custom_results.h" - -using firebase::internal::ReferenceCount; -using firebase::internal::ReferenceCountLock; - -namespace firebase { -namespace test_lab { -namespace game_loop { - -static ReferenceCount g_initializer; // NOLINT - -// String constants for iOS game loops -static const char* kFtlScheme = "firebase-game-loop"; -static const char* kFtlCompleteUrl = "firebase-game-loop-complete://"; -static const char* kScenario = "scenario"; - -namespace internal { - -// Determine whether the test lab module is initialized. -bool IsInitialized() { return g_initializer.references() > 0; } - -} // namespace internal - -static ::firebase::util::ClassMethodImplementationCache& SwizzledMethodCache() { - static ::firebase::util::ClassMethodImplementationCache* g_swizzled_method_cache; - return *::firebase::util::ClassMethodImplementationCache::GetCreateCache( - &g_swizzled_method_cache); -} - -// Initialize the game loop API -void Initialize(const ::firebase::App& app) { - ReferenceCountLock ref_count(&g_initializer); - if (ref_count.references() != 0) { - LogWarning("Test Lab API already initialized"); - return; - } - LogDebug("Firebase Test Lab API initializing"); - internal::CreateOrOpenLogFile(); - ref_count.AddReference(); -} - -// Release resources associated with the game loop API -void Terminate() { - ReferenceCountLock ref_count(&g_initializer); - if (ref_count.references() == 0) { - LogWarning("Test Lab API was never initialized"); - return; - } - LogDebug("Terminating the Firebase Test Lab API"); - if (ref_count.references() == 1) { - internal::CloseLogFile(); - internal::TerminateCommon(); - } - ref_count.RemoveReference(); -} - -// Return the current scenario number of the game loop test -int GetScenario() { - if (!internal::IsInitialized()) return 0; - return internal::GetScenario(); -} - -// Log formatted text to the game loop's custom results -void LogText(const char* format, ...) { - ReferenceCountLock ref_count(&g_initializer); - if (GetScenario() == 0) return; - va_list args; - va_start(args, format); - internal::LogText(format, args); - va_end(args); -} - -// End a game loop scenario with an outcome -void FinishScenario(::firebase::test_lab::game_loop::ScenarioOutcome outcome) { - if (GetScenario() == 0) return; - FILE* custom_results_file = internal::CreateCustomResultsFile(GetScenario()); - if (custom_results_file == nullptr) { - LogError("Could not obtain the custom results file"); - } else { - internal::OutputResult(outcome, custom_results_file); - } - Terminate(); - UIApplication* app = [UIApplication sharedApplication]; - [app openURL:[NSURL URLWithString:@(kFtlCompleteUrl)] - options:@{} - completionHandler:^(BOOL success){ - // TODO(brandonmorris): Investigate a graceful way to exit the application, or make a - // special exception within FTL to signify finishing a scenario is not a crash. - }]; -} - -// Retrieve the scenario from the URL if present -static int ParseScenarioFromUrl(NSURL* url) { - if ([url.scheme isEqualToString:(@(kFtlScheme))]) { - NSURLComponents* components = [NSURLComponents componentsWithURL:url - resolvingAgainstBaseURL:YES]; - for (NSURLQueryItem* item in [components queryItems]) { - if ([item.name isEqualToString:@(kScenario)]) { - int scenario = (int)[item.value integerValue]; - NSLog(@"%@", [NSString stringWithFormat:@"Found scenario %d", scenario]); - return scenario > 0 ? scenario : 0; - } - } - } - LogWarning("No game loop scenario could be parsed from the URL scheme"); - return 0; -} - -// Implementation of application:openURL:options that replaces the original -// app delegate's implementation. -static BOOL AppDelegateApplicationOpenURLOptions(id self, SEL selectorValue, - UIApplication* application, NSURL* url, - NSDictionary* options) { - LogDebug("Parsing URL for application:openURL:options:"); - internal::SetScenario(ParseScenarioFromUrl(url)); - - // Some applications / frameworks (like Unity) do not handle nil arguments for url and options - // so create empty objects to prevent them from failing. - if (!url) url = [[NSURL alloc] init]; - if (!options) options = @{}; - - IMP app_delegate_application_open_url_options = - SwizzledMethodCache().GetMethodForObject(self, @selector(application:openURL:options:)); - if (app_delegate_application_open_url_options) { - return ( - (util::AppDelegateApplicationOpenUrlOptionsFunc)app_delegate_application_open_url_options)( - self, selectorValue, application, url, options); - } else if ([self methodForSelector:@selector(forwardInvocation:)] != - [NSObject instanceMethodForSelector:@selector(forwardInvocation:)]) { - NSMethodSignature* signature = [[self class] instanceMethodSignatureForSelector:selectorValue]; - NSInvocation* invocation = [NSInvocation invocationWithMethodSignature:signature]; - [invocation setSelector:selectorValue]; - [invocation setTarget:self]; - [invocation setArgument:&application atIndex:2]; - [invocation setArgument:&url atIndex:3]; - [invocation setArgument:&options atIndex:4]; - [self forwardInvocation:invocation]; - // Read the return value from the invocation. - BOOL ret; - [invocation getReturnValue:&ret]; - return ret; - } - return YES; -} - -static BOOL AppDelegateApplicationOpenUrlSourceApplicationAnnotation(id self, SEL selectorValue, - UIApplication* application, - NSURL* url, - NSString* sourceApplication, - id annotation) { - internal::SetScenario(ParseScenarioFromUrl(url)); - - // Some applications / frameworks (like Unity) do not handle nil arguments for url, - // sourceApplication and annotation, so create empty objects to prevent them from failing. - if (!url) url = [[NSURL alloc] init]; - if (!sourceApplication) sourceApplication = @""; - if (!annotation) annotation = [[NSString alloc] init]; - IMP app_delegate_application_open_url_source_application_annotation = - SwizzledMethodCache().GetMethodForObject( - self, @selector(application:openURL:sourceApplication:annotation:)); - if (app_delegate_application_open_url_source_application_annotation) { - return ((util::AppDelegateApplicationOpenUrlSourceApplicationAnnotationFunc) - app_delegate_application_open_url_source_application_annotation)( - self, selectorValue, application, url, sourceApplication, annotation); - } else if ([self methodForSelector:@selector(forwardInvocation:)] != - [NSObject instanceMethodForSelector:@selector(forwardInvocation:)]) { - NSMethodSignature* signature = [[self class] instanceMethodSignatureForSelector:selectorValue]; - NSInvocation* invocation = [NSInvocation invocationWithMethodSignature:signature]; - [invocation setSelector:selectorValue]; - [invocation setTarget:self]; - [invocation setArgument:&application atIndex:2]; - [invocation setArgument:&url atIndex:3]; - [invocation setArgument:&sourceApplication atIndex:4]; - [invocation setArgument:&annotation atIndex:5]; - [self forwardInvocation:invocation]; - // Read the return value from the invocation. - BOOL ret; - [invocation getReturnValue:&ret]; - return ret; - } - return YES; -} - -// Hook all AppDelegate methods that FTL requires to intercept the game loop -// launch. -// -// The user of the library provides the class which implements AppDelegate protocol so this -// method hooks methods of user's UIApplication class in order to intercept events required for -// FTL. The alternative to this procedure would require the user to implement boilerplate code -// in their UIApplication in order to plumb in FTL. -// -// The following methods are replaced in order to intercept AppDelegate events: -// - (BOOL)application:openURL:sourceApplication:annotation: -// - (BOOL)application:openURL:options: -static void HookAppDelegateMethods(Class clazz) { - Class method_encoding_class = [FIRSAMAppDelegate class]; - auto& method_cache = SwizzledMethodCache(); - // application:openURL:options: is called in preference to - // application:openURL:sourceApplication:annotation: so if the UIApplicationDelegate does not - // implement application:openURL:options:, do not hook it. - method_cache.ReplaceOrAddMethod(clazz, @selector(application:openURL:options:), - (IMP)AppDelegateApplicationOpenURLOptions, method_encoding_class); - method_cache.ReplaceOrAddMethod( - clazz, @selector(application:openURL:sourceApplication:annotation:), - (IMP)AppDelegateApplicationOpenUrlSourceApplicationAnnotation, method_encoding_class); -} - -} // namespace game_loop -} // namespace test_lab -} // namespace firebase - -// Category for UIApplication that is used to hook methods in all classes. -// Category +load() methods are called after all class load methods in each Mach-O -// (see call_load_methods() in -// http://www.opensource.apple.com/source/objc4/objc4-274/runtime/objc-runtime.m) -@implementation UIApplication (FIRFTL) -+ (void)load { - ::firebase::LogDebug("Loading UIApplication FIRFTL category"); - ::firebase::util::ForEachAppDelegateClass(^(Class clazz) { - ::firebase::test_lab::game_loop::HookAppDelegateMethods(clazz); - }); -} -@end