diff --git a/app/BUILD.bazel b/app/BUILD.bazel index 60849bf7ac7..03e885568b6 100644 --- a/app/BUILD.bazel +++ b/app/BUILD.bazel @@ -559,6 +559,7 @@ kt_android_library( "//third_party:androidx_databinding_databinding-common", "//third_party:androidx_databinding_databinding-runtime", "//utility", + "//utility/src/main/java/org/oppia/android/util/enumfilter:enum_filter_util", "//utility/src/main/java/org/oppia/android/util/extensions:context_extensions", "//utility/src/main/java/org/oppia/android/util/logging/firebase:debug_module", "//utility/src/main/java/org/oppia/android/util/math:fraction_parser", @@ -678,6 +679,7 @@ kt_android_library( "//third_party:org_jetbrains_kotlin_kotlin-stdlib-jdk8_jar", "//utility", "//utility/src/main/java/org/oppia/android/util/extensions:bundle_extensions", + "//utility/src/main/java/org/oppia/android/util/logging:current_app_screen_name_intent_decorator", "//utility/src/main/java/org/oppia/android/util/parser/image:image_loader", "//utility/src/main/java/org/oppia/android/util/parser/image:image_parsing_annonations", "//utility/src/main/java/org/oppia/android/util/profile:current_user_profile_id_intent_decorator", @@ -799,7 +801,6 @@ TEST_DEPS = [ "//third_party:androidx_test_espresso_espresso-intents", "//third_party:androidx_test_ext_junit", "//third_party:androidx_test_runner", - "//third_party:androidx_work_work-testing", "//third_party:com_github_bumptech_glide_mocks", "//third_party:com_google_truth_truth", "//third_party:org_jetbrains_kotlin_kotlin-reflect", diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 33f54765791..c2cb7569092 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -16,7 +16,7 @@ android:label="@string/app_name" android:supportsRtl="true" android:theme="@style/OppiaTheme"> - + + android:name="androidx.startup.InitializationProvider" + android:authorities="${applicationId}.androidx-startup" + android:exported="false" + tools:node="merge"> + + diff --git a/app/src/main/java/org/oppia/android/app/application/AbstractOppiaApplication.kt b/app/src/main/java/org/oppia/android/app/application/AbstractOppiaApplication.kt index 2502340e04c..523c25d7e91 100644 --- a/app/src/main/java/org/oppia/android/app/application/AbstractOppiaApplication.kt +++ b/app/src/main/java/org/oppia/android/app/application/AbstractOppiaApplication.kt @@ -5,23 +5,29 @@ import android.app.Application import androidx.appcompat.app.AppCompatActivity import androidx.multidex.MultiDexApplication import androidx.work.Configuration -import androidx.work.WorkManager import com.google.firebase.FirebaseApp +import com.google.firebase.appcheck.AppCheckProviderFactory import com.google.firebase.appcheck.FirebaseAppCheck -import com.google.firebase.appcheck.debug.DebugAppCheckProviderFactory import com.google.firebase.appcheck.playintegrity.PlayIntegrityAppCheckProviderFactory import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.async +import kotlinx.coroutines.launch import org.oppia.android.app.activity.ActivityComponent import org.oppia.android.app.activity.ActivityComponentFactory -import org.oppia.android.app.model.BuildFlavor import org.oppia.android.domain.oppialogger.ApplicationStartupListener -import org.oppia.android.util.extensions.safeForEach -/** The root base [Application] of the Oppia app. */ +/** + * The root base [Application] of the Oppia app. + * + * @param createComponentBuilder the [ApplicationComponent.Builder] used to construct the root + * Dagger class for the implementation's flavor of the app + * @property firebaseAppCheckProviderFactory the [AppCheckProviderFactory] used when initializing + * FirebaseAppCheck. This is defaulted for production use cases, but implementations may choose + * to provide a different one for improved debugging support. + */ abstract class AbstractOppiaApplication( - createComponentBuilder: () -> ApplicationComponent.Builder + createComponentBuilder: () -> ApplicationComponent.Builder, + private val firebaseAppCheckProviderFactory: AppCheckProviderFactory = + PlayIntegrityAppCheckProviderFactory.getInstance() ) : MultiDexApplication(), ActivityComponentFactory, ApplicationInjectorProvider, @@ -42,48 +48,34 @@ abstract class AbstractOppiaApplication( override fun onCreate() { super.onCreate() - // Platform parameter initialization must happen very early since even workers can use both - // feature flags and platform parameters. - component.getPlatformParameterController().loadParametersAsync().invokeOnCompletion { e -> - if (e != null) { - throw Exception("Failed to initialize platform parameters.", e) - } + // Allow startup listeners to early initialize. + val startupListeners = component.getApplicationStartupListeners() + startupListeners.forEach(ApplicationStartupListener::onCreateStarted) - // Continue initializing the startup state. Due to the asynchronous nature of platform - // parameter initialization, this happens after onCreate() finishes at the application level. - // This can introduce some inconsistencies in SplashActivity, though by the time - // SplashActivity completes the following should be fully initialized. - CoroutineScope(Dispatchers.Main).async { - FirebaseApp.initializeApp(applicationContext) - // FirebaseAppCheck protects our API resources from abuse. It works with Firebase - // services, Google Cloud services, and can also be implemented for our own APIs. See - // https://firebase.google.com/docs/app-check for currently supported Firebase products. - // Note that as of this code being checked in, only the app's Firestore usage is affected - // by App Check (Analytics is NOT affected). - if (component.getCurrentBuildFlavor() == BuildFlavor.DEVELOPER) { - FirebaseAppCheck.getInstance().installAppCheckProviderFactory( - DebugAppCheckProviderFactory.getInstance(), - ) - } else { - FirebaseAppCheck.getInstance().installAppCheckProviderFactory( - PlayIntegrityAppCheckProviderFactory.getInstance(), - ) - } - WorkManager.initialize(applicationContext, workManagerConfiguration) - val workManager = WorkManager.getInstance(applicationContext) - component.getAnalyticsStartupListenerStartupListeners().safeForEach { - it.onCreate(workManager) - } - component.getApplicationStartupListeners().forEach(ApplicationStartupListener::onCreate) - }.invokeOnCompletion { - if (it != null) { - throw Exception("Failed to continue application initialization.", it) - } - } + // Initialize high-level third-party systems. Note that WorkManager doesn't need to be + // initialized here because it will automatically initialize itself due to the application being + // a Configuration provider. + FirebaseApp.initializeApp(applicationContext) + // FirebaseAppCheck protects our API resources from abuse. It works with Firebase + // services, Google Cloud services, and can also be implemented for our own APIs. See + // https://firebase.google.com/docs/app-check for currently supported Firebase products. + // Note that as of this code being checked in, only the app's Firestore usage is affected + // by App Check (Analytics is NOT affected). + FirebaseAppCheck.getInstance().installAppCheckProviderFactory(firebaseAppCheckProviderFactory) + + // Kick off a background task to finish startup initialization. Nothing at this stage should be + // startup-state sensitive. It's also fine for parameters to not be fully initialized at this + // point because each app entry point already accounts for potentially uninitialized parameters: + // splash, direct activity recreation, and waking up the app to kick off a worker. Finally, + // since this is using 'launch' any uncaught exceptions should correctly trigger a failure in + // app startup (which is ideal in this case because we can't reliably and safely start the app). + CoroutineScope(component.getBackgroundDispatcher()).launch { + // Wait for parameters to load before running any startup routines that may depend on them. + component.getPlatformParameterController().loadParametersAsync().await() + startupListeners.forEach(ApplicationStartupListener::onCompletedInitialization) } } - override fun getWorkManagerConfiguration(): Configuration { - return component.getWorkManagerConfiguration() - } + override fun getWorkManagerConfiguration(): Configuration = + component.getWorkManagerConfiguration() } diff --git a/app/src/main/java/org/oppia/android/app/application/ApplicationComponent.kt b/app/src/main/java/org/oppia/android/app/application/ApplicationComponent.kt index 4fc1daefb7d..1c92ad5c6c6 100644 --- a/app/src/main/java/org/oppia/android/app/application/ApplicationComponent.kt +++ b/app/src/main/java/org/oppia/android/app/application/ApplicationComponent.kt @@ -4,9 +4,7 @@ import android.app.Application import androidx.work.Configuration import dagger.BindsInstance import org.oppia.android.app.activity.ActivityComponentImpl -import org.oppia.android.app.model.BuildFlavor import org.oppia.android.domain.oppialogger.ApplicationStartupListener -import org.oppia.android.domain.oppialogger.analytics.AnalyticsStartupListener import javax.inject.Provider /** @@ -28,9 +26,5 @@ interface ApplicationComponent : ApplicationInjector { fun getApplicationStartupListeners(): Set - fun getAnalyticsStartupListenerStartupListeners(): Set - fun getWorkManagerConfiguration(): Configuration - - fun getCurrentBuildFlavor(): BuildFlavor } diff --git a/app/src/main/java/org/oppia/android/app/application/BUILD.bazel b/app/src/main/java/org/oppia/android/app/application/BUILD.bazel index 19ef82d522b..5efcf78e8e7 100644 --- a/app/src/main/java/org/oppia/android/app/application/BUILD.bazel +++ b/app/src/main/java/org/oppia/android/app/application/BUILD.bazel @@ -122,6 +122,7 @@ android_library( "//domain/src/main/java/org/oppia/android/domain/oppialogger/logscheduler:metric_log_scheduler_module", "//domain/src/main/java/org/oppia/android/domain/oppialogger/loguploader:worker_module", "//domain/src/main/java/org/oppia/android/domain/platformparameter:prod_module", + "//domain/src/main/java/org/oppia/android/domain/workmanager:work_manager_configuration_module", "//utility/src/main/java/org/oppia/android/util/accessibility:prod_module", "//utility/src/main/java/org/oppia/android/util/caching:asset_prod_module", "//utility/src/main/java/org/oppia/android/util/caching:caching_prod_module", diff --git a/app/src/main/java/org/oppia/android/app/application/alpha/AlphaApplicationComponent.kt b/app/src/main/java/org/oppia/android/app/application/alpha/AlphaApplicationComponent.kt index 9bf42f35695..0bbdfa77b03 100644 --- a/app/src/main/java/org/oppia/android/app/application/alpha/AlphaApplicationComponent.kt +++ b/app/src/main/java/org/oppia/android/app/application/alpha/AlphaApplicationComponent.kt @@ -52,7 +52,6 @@ import org.oppia.android.util.gcsresource.GcsResourceModule import org.oppia.android.util.locale.LocaleProdModule import org.oppia.android.util.logging.LoggerModule import org.oppia.android.util.logging.SyncStatusModule -import org.oppia.android.util.logging.firebase.FirebaseLogUploaderModule import org.oppia.android.util.logging.firebase.LogReportingModule import org.oppia.android.util.logging.performancemetrics.PerformanceMetricsAssessorModule import org.oppia.android.util.logging.performancemetrics.PerformanceMetricsConfigurationsModule @@ -85,7 +84,7 @@ import javax.inject.Singleton RatioInputModule::class, UncaughtExceptionLoggerModule::class, ApplicationStartupListenerModule::class, LogReportWorkerModule::class, WorkManagerConfigurationModule::class, HintsAndSolutionConfigModule::class, - FirebaseLogUploaderModule::class, RetrofitModule::class, RetrofitServiceModule::class, + RetrofitModule::class, RetrofitServiceModule::class, PlatformParameterModule::class, PlatformParameterSingletonModule::class, ExplorationStorageModule::class, DeveloperOptionsModule::class, PlatformParameterSyncUpWorkerModule::class, NetworkConfigProdModule::class, AssetModule::class, diff --git a/app/src/main/java/org/oppia/android/app/application/beta/BetaApplicationComponent.kt b/app/src/main/java/org/oppia/android/app/application/beta/BetaApplicationComponent.kt index 669ec60b44e..985cb3dcc58 100644 --- a/app/src/main/java/org/oppia/android/app/application/beta/BetaApplicationComponent.kt +++ b/app/src/main/java/org/oppia/android/app/application/beta/BetaApplicationComponent.kt @@ -52,7 +52,6 @@ import org.oppia.android.util.gcsresource.GcsResourceModule import org.oppia.android.util.locale.LocaleProdModule import org.oppia.android.util.logging.LoggerModule import org.oppia.android.util.logging.SyncStatusModule -import org.oppia.android.util.logging.firebase.FirebaseLogUploaderModule import org.oppia.android.util.logging.firebase.LogReportingModule import org.oppia.android.util.logging.performancemetrics.PerformanceMetricsAssessorModule import org.oppia.android.util.logging.performancemetrics.PerformanceMetricsConfigurationsModule @@ -85,7 +84,7 @@ import javax.inject.Singleton RatioInputModule::class, UncaughtExceptionLoggerModule::class, ApplicationStartupListenerModule::class, LogReportWorkerModule::class, WorkManagerConfigurationModule::class, HintsAndSolutionConfigModule::class, - FirebaseLogUploaderModule::class, RetrofitModule::class, RetrofitServiceModule::class, + RetrofitModule::class, RetrofitServiceModule::class, PlatformParameterModule::class, PlatformParameterSingletonModule::class, ExplorationStorageModule::class, DeveloperOptionsModule::class, PlatformParameterSyncUpWorkerModule::class, NetworkConfigProdModule::class, AssetModule::class, diff --git a/app/src/main/java/org/oppia/android/app/application/dev/BUILD.bazel b/app/src/main/java/org/oppia/android/app/application/dev/BUILD.bazel index 12f288041cd..b6458c216e7 100644 --- a/app/src/main/java/org/oppia/android/app/application/dev/BUILD.bazel +++ b/app/src/main/java/org/oppia/android/app/application/dev/BUILD.bazel @@ -23,6 +23,8 @@ kt_android_library( "//app/src/main/java/org/oppia/android/app/application:common_application_modules", "//domain/src/main/java/org/oppia/android/domain/auth:auth_module", "//domain/src/main/java/org/oppia/android/domain/platformparameter:debug_module", + "//domain/src/main/java/org/oppia/android/domain/workmanager/debug:debug_module", + "//third_party:com_google_firebase_firebase-common", "//utility/src/main/java/org/oppia/android/util/logging/firebase:debug_module", "//utility/src/main/java/org/oppia/android/util/networking:debug_module", ], diff --git a/app/src/main/java/org/oppia/android/app/application/dev/DeveloperApplicationComponent.kt b/app/src/main/java/org/oppia/android/app/application/dev/DeveloperApplicationComponent.kt index e7b150bf9df..3cd29477abe 100644 --- a/app/src/main/java/org/oppia/android/app/application/dev/DeveloperApplicationComponent.kt +++ b/app/src/main/java/org/oppia/android/app/application/dev/DeveloperApplicationComponent.kt @@ -46,6 +46,7 @@ import org.oppia.android.domain.platformparameter.PlatformParameterSingletonModu import org.oppia.android.domain.platformparameter.syncup.PlatformParameterSyncUpWorkerModule import org.oppia.android.domain.question.QuestionModule import org.oppia.android.domain.workmanager.WorkManagerConfigurationModule +import org.oppia.android.domain.workmanager.debug.DebugWorkerDebugModule import org.oppia.android.util.accessibility.AccessibilityProdModule import org.oppia.android.util.caching.AssetModule import org.oppia.android.util.caching.CachingModule @@ -54,7 +55,6 @@ import org.oppia.android.util.locale.LocaleProdModule import org.oppia.android.util.logging.LoggerModule import org.oppia.android.util.logging.SyncStatusModule import org.oppia.android.util.logging.firebase.DebugLogReportingModule -import org.oppia.android.util.logging.firebase.FirebaseLogUploaderModule import org.oppia.android.util.logging.performancemetrics.PerformanceMetricsAssessorModule import org.oppia.android.util.logging.performancemetrics.PerformanceMetricsConfigurationsModule import org.oppia.android.util.networking.NetworkConnectionDebugUtilModule @@ -87,7 +87,7 @@ import javax.inject.Singleton UncaughtExceptionLoggerModule::class, ApplicationStartupListenerModule::class, LogReportWorkerModule::class, WorkManagerConfigurationModule::class, HintsAndSolutionConfigModule::class, HintsAndSolutionDebugModule::class, - FirebaseLogUploaderModule::class, RetrofitModule::class, RetrofitServiceModule::class, + RetrofitModule::class, RetrofitServiceModule::class, PlatformParameterSingletonModule::class, PlatformParameterDebugModule::class, ExplorationStorageModule::class, DeveloperOptionsStarterModule::class, DeveloperOptionsModule::class, PlatformParameterSyncUpWorkerModule::class, @@ -101,7 +101,7 @@ import javax.inject.Singleton PerformanceMetricsAssessorModule::class, PerformanceMetricsConfigurationsModule::class, DeveloperBuildFlavorModule::class, CpuPerformanceSnapshotterModule::class, ExplorationProgressModule::class, - AuthenticationModule::class + AuthenticationModule::class, DebugWorkerDebugModule::class ] ) interface DeveloperApplicationComponent : ApplicationComponent { diff --git a/app/src/main/java/org/oppia/android/app/application/dev/DeveloperOppiaApplication.kt b/app/src/main/java/org/oppia/android/app/application/dev/DeveloperOppiaApplication.kt index 495a21d7a3e..66cf4a6b62e 100644 --- a/app/src/main/java/org/oppia/android/app/application/dev/DeveloperOppiaApplication.kt +++ b/app/src/main/java/org/oppia/android/app/application/dev/DeveloperOppiaApplication.kt @@ -1,8 +1,10 @@ package org.oppia.android.app.application.dev +import com.google.firebase.appcheck.debug.DebugAppCheckProviderFactory import org.oppia.android.app.application.AbstractOppiaApplication /** The root [AbstractOppiaApplication] for developer builds of the Oppia app. */ class DeveloperOppiaApplication : AbstractOppiaApplication( - DaggerDeveloperApplicationComponent::builder + DaggerDeveloperApplicationComponent::builder, + firebaseAppCheckProviderFactory = DebugAppCheckProviderFactory.getInstance() ) diff --git a/app/src/main/java/org/oppia/android/app/application/ga/GaApplicationComponent.kt b/app/src/main/java/org/oppia/android/app/application/ga/GaApplicationComponent.kt index a8306471f90..84aed99b6ab 100644 --- a/app/src/main/java/org/oppia/android/app/application/ga/GaApplicationComponent.kt +++ b/app/src/main/java/org/oppia/android/app/application/ga/GaApplicationComponent.kt @@ -52,7 +52,6 @@ import org.oppia.android.util.gcsresource.GcsResourceModule import org.oppia.android.util.locale.LocaleProdModule import org.oppia.android.util.logging.LoggerModule import org.oppia.android.util.logging.SyncStatusModule -import org.oppia.android.util.logging.firebase.FirebaseLogUploaderModule import org.oppia.android.util.logging.firebase.LogReportingModule import org.oppia.android.util.logging.performancemetrics.PerformanceMetricsAssessorModule import org.oppia.android.util.logging.performancemetrics.PerformanceMetricsConfigurationsModule @@ -85,7 +84,7 @@ import javax.inject.Singleton RatioInputModule::class, UncaughtExceptionLoggerModule::class, ApplicationStartupListenerModule::class, LogReportWorkerModule::class, WorkManagerConfigurationModule::class, HintsAndSolutionConfigModule::class, - FirebaseLogUploaderModule::class, RetrofitModule::class, RetrofitServiceModule::class, + RetrofitModule::class, RetrofitServiceModule::class, PlatformParameterModule::class, PlatformParameterSingletonModule::class, ExplorationStorageModule::class, DeveloperOptionsModule::class, PlatformParameterSyncUpWorkerModule::class, NetworkConfigProdModule::class, AssetModule::class, diff --git a/app/src/sharedTest/java/org/oppia/android/app/administratorcontrols/AdministratorControlsActivityTest.kt b/app/src/sharedTest/java/org/oppia/android/app/administratorcontrols/AdministratorControlsActivityTest.kt index e98ac3fd0a2..491ace76527 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/administratorcontrols/AdministratorControlsActivityTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/administratorcontrols/AdministratorControlsActivityTest.kt @@ -122,7 +122,6 @@ import org.oppia.android.util.locale.LocaleProdModule import org.oppia.android.util.logging.CurrentAppScreenNameIntentDecorator.extractCurrentAppScreenName import org.oppia.android.util.logging.LoggerModule import org.oppia.android.util.logging.SyncStatusModule -import org.oppia.android.util.logging.firebase.FirebaseLogUploaderModule import org.oppia.android.util.networking.NetworkConnectionDebugUtilModule import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule import org.oppia.android.util.parser.html.HtmlParserEntityTypeModule @@ -1082,7 +1081,6 @@ class AdministratorControlsActivityTest { ExplorationProgressModule::class, ExplorationStorageModule::class, FakeOppiaClockModule::class, - FirebaseLogUploaderModule::class, FractionInputModule::class, GcsResourceModule::class, GlideImageLoaderModule::class, diff --git a/app/src/sharedTest/java/org/oppia/android/app/administratorcontrols/AdministratorControlsFragmentTest.kt b/app/src/sharedTest/java/org/oppia/android/app/administratorcontrols/AdministratorControlsFragmentTest.kt index 1bbb0308c79..0028fc03f6a 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/administratorcontrols/AdministratorControlsFragmentTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/administratorcontrols/AdministratorControlsFragmentTest.kt @@ -107,7 +107,6 @@ import org.oppia.android.util.gcsresource.GcsResourceModule import org.oppia.android.util.locale.LocaleProdModule import org.oppia.android.util.logging.LoggerModule import org.oppia.android.util.logging.SyncStatusModule -import org.oppia.android.util.logging.firebase.FirebaseLogUploaderModule import org.oppia.android.util.networking.NetworkConnectionDebugUtilModule import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule import org.oppia.android.util.parser.html.HtmlParserEntityTypeModule @@ -805,7 +804,6 @@ class AdministratorControlsFragmentTest { ExplorationProgressModule::class, ExplorationStorageModule::class, FakeOppiaClockModule::class, - FirebaseLogUploaderModule::class, FractionInputModule::class, GcsResourceModule::class, GlideImageLoaderModule::class, diff --git a/app/src/sharedTest/java/org/oppia/android/app/administratorcontrols/appversion/AppVersionActivityTest.kt b/app/src/sharedTest/java/org/oppia/android/app/administratorcontrols/appversion/AppVersionActivityTest.kt index a6a5b73ae28..fcbebe45961 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/administratorcontrols/appversion/AppVersionActivityTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/administratorcontrols/appversion/AppVersionActivityTest.kt @@ -99,7 +99,6 @@ import org.oppia.android.util.locale.LocaleProdModule import org.oppia.android.util.logging.CurrentAppScreenNameIntentDecorator.extractCurrentAppScreenName import org.oppia.android.util.logging.LoggerModule import org.oppia.android.util.logging.SyncStatusModule -import org.oppia.android.util.logging.firebase.FirebaseLogUploaderModule import org.oppia.android.util.networking.NetworkConnectionDebugUtilModule import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule import org.oppia.android.util.parser.html.HtmlParserEntityTypeModule @@ -433,7 +432,6 @@ class AppVersionActivityTest { ExplorationProgressModule::class, ExplorationStorageModule::class, FakeOppiaClockModule::class, - FirebaseLogUploaderModule::class, FractionInputModule::class, GcsResourceModule::class, GlideImageLoaderModule::class, diff --git a/app/src/sharedTest/java/org/oppia/android/app/administratorcontrols/learneranalytics/BUILD.bazel b/app/src/sharedTest/java/org/oppia/android/app/administratorcontrols/learneranalytics/BUILD.bazel index 3fc43638bad..60ffac9ae3d 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/administratorcontrols/learneranalytics/BUILD.bazel +++ b/app/src/sharedTest/java/org/oppia/android/app/administratorcontrols/learneranalytics/BUILD.bazel @@ -26,7 +26,6 @@ oppia_android_test( "//testing/src/main/java/org/oppia/android/testing/robolectric:test_module", "//testing/src/main/java/org/oppia/android/testing/threading:test_module", "//testing/src/main/java/org/oppia/android/testing/time:test_module", - "//third_party:androidx_work_work-testing", "//third_party:com_google_truth_truth", "//third_party:junit_junit", "//third_party:org_robolectric_robolectric", @@ -60,6 +59,7 @@ oppia_android_test( "//app/src/main/java/org/oppia/android/app/application:common_application_modules", "//app/src/main/java/org/oppia/android/app/application/testing:testing_build_flavor_module", "//app/src/main/java/org/oppia/android/app/translation/testing:test_module", + "//domain/src/main/java/org/oppia/android/domain/workmanager/testing:oppia_work_manager_test_driver", "//testing", "//testing/src/main/java/org/oppia/android/testing/junit:initialize_default_locale_rule", "//testing/src/main/java/org/oppia/android/testing/logging:event_log_subject", @@ -69,7 +69,6 @@ oppia_android_test( "//testing/src/main/java/org/oppia/android/testing/threading:test_module", "//testing/src/main/java/org/oppia/android/testing/time:test_module", "//third_party:androidx_test_ext_truth", - "//third_party:androidx_work_work-testing", "//third_party:com_google_truth_truth", "//third_party:junit_junit", "//third_party:org_robolectric_robolectric", diff --git a/app/src/sharedTest/java/org/oppia/android/app/administratorcontrols/learneranalytics/ProfileAndDeviceIdActivityTest.kt b/app/src/sharedTest/java/org/oppia/android/app/administratorcontrols/learneranalytics/ProfileAndDeviceIdActivityTest.kt index a2578e44823..21b5bfd007e 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/administratorcontrols/learneranalytics/ProfileAndDeviceIdActivityTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/administratorcontrols/learneranalytics/ProfileAndDeviceIdActivityTest.kt @@ -8,9 +8,6 @@ import androidx.test.core.app.ApplicationProvider import androidx.test.espresso.Espresso.onView import androidx.test.espresso.matcher.ViewMatchers.withId import androidx.test.ext.junit.runners.AndroidJUnit4 -import androidx.work.Configuration -import androidx.work.testing.SynchronousExecutor -import androidx.work.testing.WorkManagerTestInitHelper import com.google.common.truth.Truth.assertThat import dagger.Component import dagger.Module @@ -66,7 +63,6 @@ import org.oppia.android.domain.oppialogger.analytics.ApplicationLifecycleModule import org.oppia.android.domain.oppialogger.analytics.CpuPerformanceSnapshotterModule import org.oppia.android.domain.oppialogger.logscheduler.MetricLogSchedulerModule import org.oppia.android.domain.oppialogger.loguploader.LogReportWorkerModule -import org.oppia.android.domain.oppialogger.loguploader.LogUploadWorkerFactory import org.oppia.android.domain.platformparameter.PlatformParameterSingletonModule import org.oppia.android.domain.question.QuestionModule import org.oppia.android.domain.workmanager.WorkManagerConfigurationModule @@ -87,9 +83,7 @@ import org.oppia.android.util.gcsresource.GcsResourceModule import org.oppia.android.util.locale.LocaleProdModule import org.oppia.android.util.logging.CurrentAppScreenNameIntentDecorator.extractCurrentAppScreenName import org.oppia.android.util.logging.LoggerModule -import org.oppia.android.util.logging.SyncStatusManager import org.oppia.android.util.logging.SyncStatusModule -import org.oppia.android.util.logging.firebase.FirebaseLogUploaderModule import org.oppia.android.util.networking.NetworkConnectionDebugUtil import org.oppia.android.util.networking.NetworkConnectionDebugUtilModule import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule @@ -126,20 +120,12 @@ class ProfileAndDeviceIdActivityTest { @Inject lateinit var oppiaLogger: OppiaLogger @Inject lateinit var oppiaClock: OppiaClock @Inject lateinit var networkConnectionUtil: NetworkConnectionDebugUtil - @Inject lateinit var logUploadWorkerFactory: LogUploadWorkerFactory - @Inject lateinit var syncStatusManager: SyncStatusManager @Before fun setUp() { setUpTestApplicationComponent() testCoroutineDispatchers.registerIdlingResource() profileTestHelper.addOnlyAdminProfile() - - val config = Configuration.Builder() - .setExecutor(SynchronousExecutor()) - .setWorkerFactory(logUploadWorkerFactory) - .build() - WorkManagerTestInitHelper.initializeTestWorkManager(context, config) } @After @@ -222,7 +208,6 @@ class ProfileAndDeviceIdActivityTest { ExplorationProgressModule::class, ExplorationStorageModule::class, FakeOppiaClockModule::class, - FirebaseLogUploaderModule::class, FractionInputModule::class, GcsResourceModule::class, GlideImageLoaderModule::class, diff --git a/app/src/sharedTest/java/org/oppia/android/app/administratorcontrols/learneranalytics/ProfileAndDeviceIdFragmentTest.kt b/app/src/sharedTest/java/org/oppia/android/app/administratorcontrols/learneranalytics/ProfileAndDeviceIdFragmentTest.kt index 3f32e8e4b32..fe5abaa687b 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/administratorcontrols/learneranalytics/ProfileAndDeviceIdFragmentTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/administratorcontrols/learneranalytics/ProfileAndDeviceIdFragmentTest.kt @@ -27,11 +27,7 @@ import androidx.test.espresso.matcher.ViewMatchers.withText import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.ext.truth.content.IntentSubject.assertThat import androidx.work.Configuration -import androidx.work.Data -import androidx.work.OneTimeWorkRequestBuilder -import androidx.work.WorkManager -import androidx.work.testing.SynchronousExecutor -import androidx.work.testing.WorkManagerTestInitHelper +import androidx.work.WorkInfo import com.google.common.truth.Truth.assertThat import dagger.Component import dagger.Module @@ -96,10 +92,10 @@ import org.oppia.android.domain.oppialogger.analytics.LearnerAnalyticsLogger import org.oppia.android.domain.oppialogger.logscheduler.MetricLogSchedulerModule import org.oppia.android.domain.oppialogger.loguploader.LogReportWorkerModule import org.oppia.android.domain.oppialogger.loguploader.LogUploadWorker -import org.oppia.android.domain.oppialogger.loguploader.LogUploadWorkerFactory import org.oppia.android.domain.platformparameter.PlatformParameterSingletonModule import org.oppia.android.domain.question.QuestionModule import org.oppia.android.domain.workmanager.WorkManagerConfigurationModule +import org.oppia.android.domain.workmanager.testing.OppiaWorkManagerTestDriver import org.oppia.android.testing.FakeAnalyticsEventLogger import org.oppia.android.testing.OppiaTestRule import org.oppia.android.testing.TestLogReportingModule @@ -125,7 +121,6 @@ import org.oppia.android.util.locale.LocaleProdModule import org.oppia.android.util.locale.OppiaLocale import org.oppia.android.util.logging.LoggerModule import org.oppia.android.util.logging.SyncStatusManager.SyncStatus -import org.oppia.android.util.logging.firebase.FirebaseLogUploaderModule import org.oppia.android.util.networking.NetworkConnectionDebugUtil import org.oppia.android.util.networking.NetworkConnectionDebugUtilModule import org.oppia.android.util.networking.NetworkConnectionUtil.ProdConnectionStatus @@ -162,11 +157,12 @@ class ProfileAndDeviceIdFragmentTest { @Inject lateinit var oppiaLogger: OppiaLogger @Inject lateinit var fakeOppiaClock: FakeOppiaClock @Inject lateinit var networkConnectionUtil: NetworkConnectionDebugUtil - @Inject lateinit var logUploadWorkerFactory: LogUploadWorkerFactory @Inject lateinit var syncStatusManager: TestSyncStatusManager @Inject lateinit var learnerAnalyticsLogger: LearnerAnalyticsLogger @Inject lateinit var fakeAnalyticsEventLogger: FakeAnalyticsEventLogger @Inject lateinit var machineLocale: OppiaLocale.MachineLocale + @Inject lateinit var configuration: Configuration + @Inject lateinit var testDriver: OppiaWorkManagerTestDriver private val clipboardManager by lazy { context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager @@ -181,12 +177,7 @@ class ProfileAndDeviceIdFragmentTest { Intents.init() testCoroutineDispatchers.registerIdlingResource() profileTestHelper.addOnlyAdminProfile() - - val config = Configuration.Builder() - .setExecutor(SynchronousExecutor()) - .setWorkerFactory(logUploadWorkerFactory) - .build() - WorkManagerTestInitHelper.initializeTestWorkManager(context, config) + testDriver.initializeWorkManager(configuration) } @After @@ -1015,7 +1006,7 @@ class ProfileAndDeviceIdFragmentTest { private fun logAnalyticsEvent(profileId: LegacyProfileId? = null) { learnerAnalyticsLogger.logAppInForeground( - installationId = TEST_INSTALLATION_ID, profileId, learnerId = TEST_LEARNER_ID + TEST_INSTALLATION_ID, profileId, TEST_LEARNER_ID, fakeOppiaClock.getCurrentTimeMs() ) testCoroutineDispatchers.runCurrent() } @@ -1031,15 +1022,10 @@ class ProfileAndDeviceIdFragmentTest { } private fun flushEventWorkerQueue() { - val workManager = WorkManager.getInstance(context) - - val inputData = Data.Builder().putString( - LogUploadWorker.WORKER_CASE_KEY, LogUploadWorker.EVENT_WORKER - ).build() - - val request = OneTimeWorkRequestBuilder().setInputData(inputData).build() - workManager.enqueue(request) - testCoroutineDispatchers.runCurrent() + val monitor = + testDriver.runOneOffWork(LogUploadWorker.WORKER_NAME, LogUploadWorker.Operation.UPLOAD_EVENTS) + // Sanity check to make sure the job succeeded. + assertThat(monitor.state).isEqualTo(WorkInfo.State.SUCCEEDED) } private fun disconnectNetwork() { @@ -1136,7 +1122,6 @@ class ProfileAndDeviceIdFragmentTest { ExplorationProgressModule::class, ExplorationStorageModule::class, FakeOppiaClockModule::class, - FirebaseLogUploaderModule::class, FractionInputModule::class, GcsResourceModule::class, GlideImageLoaderModule::class, diff --git a/app/src/sharedTest/java/org/oppia/android/app/classroom/ClassroomListActivityTest.kt b/app/src/sharedTest/java/org/oppia/android/app/classroom/ClassroomListActivityTest.kt index 00d9ca91b2d..dc7ca09de7d 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/classroom/ClassroomListActivityTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/classroom/ClassroomListActivityTest.kt @@ -79,7 +79,6 @@ import org.oppia.android.util.locale.LocaleProdModule import org.oppia.android.util.logging.CurrentAppScreenNameIntentDecorator.extractCurrentAppScreenName import org.oppia.android.util.logging.LoggerModule import org.oppia.android.util.logging.SyncStatusModule -import org.oppia.android.util.logging.firebase.FirebaseLogUploaderModule import org.oppia.android.util.networking.NetworkConnectionDebugUtilModule import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule import org.oppia.android.util.parser.html.HtmlParserEntityTypeModule @@ -180,7 +179,6 @@ class ClassroomListActivityTest { ExplorationProgressModule::class, ExplorationStorageModule::class, FakeOppiaClockModule::class, - FirebaseLogUploaderModule::class, FractionInputModule::class, GcsResourceModule::class, GlideImageLoaderModule::class, diff --git a/app/src/sharedTest/java/org/oppia/android/app/classroom/ClassroomListFragmentTest.kt b/app/src/sharedTest/java/org/oppia/android/app/classroom/ClassroomListFragmentTest.kt index f013012e344..8440e116496 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/classroom/ClassroomListFragmentTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/classroom/ClassroomListFragmentTest.kt @@ -123,7 +123,6 @@ import org.oppia.android.util.locale.LocaleProdModule import org.oppia.android.util.locale.OppiaLocale import org.oppia.android.util.logging.LoggerModule import org.oppia.android.util.logging.SyncStatusModule -import org.oppia.android.util.logging.firebase.FirebaseLogUploaderModule import org.oppia.android.util.networking.NetworkConnectionDebugUtilModule import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule import org.oppia.android.util.parser.html.HtmlParserEntityTypeModule @@ -1146,7 +1145,6 @@ class ClassroomListFragmentTest { ExplorationProgressModule::class, ExplorationStorageModule::class, FakeOppiaClockModule::class, - FirebaseLogUploaderModule::class, FractionInputModule::class, GcsResourceModule::class, HintsAndSolutionConfigModule::class, diff --git a/app/src/sharedTest/java/org/oppia/android/app/completedstorylist/CompletedStoryListActivityTest.kt b/app/src/sharedTest/java/org/oppia/android/app/completedstorylist/CompletedStoryListActivityTest.kt index b5d377daf65..a718dd420be 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/completedstorylist/CompletedStoryListActivityTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/completedstorylist/CompletedStoryListActivityTest.kt @@ -102,7 +102,6 @@ import org.oppia.android.util.locale.LocaleProdModule import org.oppia.android.util.logging.CurrentAppScreenNameIntentDecorator.extractCurrentAppScreenName import org.oppia.android.util.logging.LoggerModule import org.oppia.android.util.logging.SyncStatusModule -import org.oppia.android.util.logging.firebase.FirebaseLogUploaderModule import org.oppia.android.util.networking.NetworkConnectionDebugUtilModule import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule import org.oppia.android.util.parser.html.HtmlParserEntityTypeModule @@ -523,7 +522,6 @@ class CompletedStoryListActivityTest { ExplorationProgressModule::class, ExplorationStorageModule::class, FakeOppiaClockModule::class, - FirebaseLogUploaderModule::class, FractionInputModule::class, GcsResourceModule::class, GlideImageLoaderModule::class, diff --git a/app/src/sharedTest/java/org/oppia/android/app/customview/LessonThumbnailImageViewTest.kt b/app/src/sharedTest/java/org/oppia/android/app/customview/LessonThumbnailImageViewTest.kt index e4c7c098637..424014bab82 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/customview/LessonThumbnailImageViewTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/customview/LessonThumbnailImageViewTest.kt @@ -83,7 +83,6 @@ import org.oppia.android.util.gcsresource.GcsResourceModule import org.oppia.android.util.locale.LocaleProdModule import org.oppia.android.util.logging.LoggerModule import org.oppia.android.util.logging.SyncStatusModule -import org.oppia.android.util.logging.firebase.FirebaseLogUploaderModule import org.oppia.android.util.networking.NetworkConnectionDebugUtilModule import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule import org.oppia.android.util.parser.html.HtmlParserEntityTypeModule @@ -196,7 +195,6 @@ class LessonThumbnailImageViewTest { ExplorationProgressModule::class, ExplorationStorageModule::class, FakeOppiaClockModule::class, - FirebaseLogUploaderModule::class, FractionInputModule::class, GcsResourceModule::class, HintsAndSolutionConfigModule::class, diff --git a/app/src/sharedTest/java/org/oppia/android/app/customview/interaction/MathExpressionInteractionsViewTest.kt b/app/src/sharedTest/java/org/oppia/android/app/customview/interaction/MathExpressionInteractionsViewTest.kt index 00a2f408c14..1c02366e6a1 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/customview/interaction/MathExpressionInteractionsViewTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/customview/interaction/MathExpressionInteractionsViewTest.kt @@ -100,7 +100,6 @@ import org.oppia.android.util.gcsresource.GcsResourceModule import org.oppia.android.util.locale.LocaleProdModule import org.oppia.android.util.logging.LoggerModule import org.oppia.android.util.logging.SyncStatusModule -import org.oppia.android.util.logging.firebase.FirebaseLogUploaderModule import org.oppia.android.util.networking.NetworkConnectionDebugUtilModule import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule import org.oppia.android.util.parser.html.HtmlParserEntityTypeModule @@ -1801,7 +1800,6 @@ class MathExpressionInteractionsViewTest { ExplorationProgressModule::class, ExplorationStorageModule::class, FakeOppiaClockModule::class, - FirebaseLogUploaderModule::class, FractionInputModule::class, GcsResourceModule::class, GlideImageLoaderModule::class, diff --git a/app/src/sharedTest/java/org/oppia/android/app/databinding/AppCompatCheckBoxBindingAdaptersTest.kt b/app/src/sharedTest/java/org/oppia/android/app/databinding/AppCompatCheckBoxBindingAdaptersTest.kt index ea13aac023f..da9ff948b1c 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/databinding/AppCompatCheckBoxBindingAdaptersTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/databinding/AppCompatCheckBoxBindingAdaptersTest.kt @@ -80,7 +80,6 @@ import org.oppia.android.util.gcsresource.GcsResourceModule import org.oppia.android.util.locale.LocaleProdModule import org.oppia.android.util.logging.LoggerModule import org.oppia.android.util.logging.SyncStatusModule -import org.oppia.android.util.logging.firebase.FirebaseLogUploaderModule import org.oppia.android.util.networking.NetworkConnectionDebugUtilModule import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule import org.oppia.android.util.parser.html.HtmlParserEntityTypeModule @@ -164,7 +163,6 @@ class AppCompatCheckBoxBindingAdaptersTest { ExplorationProgressModule::class, ExplorationStorageModule::class, FakeOppiaClockModule::class, - FirebaseLogUploaderModule::class, FractionInputModule::class, GcsResourceModule::class, HintsAndSolutionConfigModule::class, diff --git a/app/src/sharedTest/java/org/oppia/android/app/databinding/CircularProgressIndicatorAdaptersTest.kt b/app/src/sharedTest/java/org/oppia/android/app/databinding/CircularProgressIndicatorAdaptersTest.kt index e6aa92a5c40..8e56544efa3 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/databinding/CircularProgressIndicatorAdaptersTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/databinding/CircularProgressIndicatorAdaptersTest.kt @@ -86,7 +86,6 @@ import org.oppia.android.util.gcsresource.GcsResourceModule import org.oppia.android.util.locale.LocaleProdModule import org.oppia.android.util.logging.LoggerModule import org.oppia.android.util.logging.SyncStatusModule -import org.oppia.android.util.logging.firebase.FirebaseLogUploaderModule import org.oppia.android.util.math.isApproximatelyEqualTo import org.oppia.android.util.networking.NetworkConnectionDebugUtilModule import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule @@ -313,7 +312,6 @@ class CircularProgressIndicatorAdaptersTest { ExplorationProgressModule::class, ExplorationStorageModule::class, FakeOppiaClockModule::class, - FirebaseLogUploaderModule::class, FractionInputModule::class, GcsResourceModule::class, HintsAndSolutionConfigModule::class, diff --git a/app/src/sharedTest/java/org/oppia/android/app/databinding/ColorBindingAdaptersTest.kt b/app/src/sharedTest/java/org/oppia/android/app/databinding/ColorBindingAdaptersTest.kt index 53efbc86145..cfb8c489269 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/databinding/ColorBindingAdaptersTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/databinding/ColorBindingAdaptersTest.kt @@ -81,7 +81,6 @@ import org.oppia.android.util.gcsresource.GcsResourceModule import org.oppia.android.util.locale.LocaleProdModule import org.oppia.android.util.logging.LoggerModule import org.oppia.android.util.logging.SyncStatusModule -import org.oppia.android.util.logging.firebase.FirebaseLogUploaderModule import org.oppia.android.util.networking.NetworkConnectionDebugUtilModule import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule import org.oppia.android.util.parser.html.HtmlParserEntityTypeModule @@ -178,7 +177,6 @@ class ColorBindingAdaptersTest { ExplorationProgressModule::class, ExplorationStorageModule::class, FakeOppiaClockModule::class, - FirebaseLogUploaderModule::class, FractionInputModule::class, GcsResourceModule::class, HintsAndSolutionConfigModule::class, diff --git a/app/src/sharedTest/java/org/oppia/android/app/databinding/DrawableBindingAdaptersTest.kt b/app/src/sharedTest/java/org/oppia/android/app/databinding/DrawableBindingAdaptersTest.kt index 6760153f7e9..392c332b24f 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/databinding/DrawableBindingAdaptersTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/databinding/DrawableBindingAdaptersTest.kt @@ -85,7 +85,6 @@ import org.oppia.android.util.gcsresource.GcsResourceModule import org.oppia.android.util.locale.LocaleProdModule import org.oppia.android.util.logging.LoggerModule import org.oppia.android.util.logging.SyncStatusModule -import org.oppia.android.util.logging.firebase.FirebaseLogUploaderModule import org.oppia.android.util.networking.NetworkConnectionDebugUtilModule import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule import org.oppia.android.util.parser.html.HtmlParserEntityTypeModule @@ -221,7 +220,6 @@ class DrawableBindingAdaptersTest { ExplorationProgressModule::class, ExplorationStorageModule::class, FakeOppiaClockModule::class, - FirebaseLogUploaderModule::class, FractionInputModule::class, GcsResourceModule::class, HintsAndSolutionConfigModule::class, diff --git a/app/src/sharedTest/java/org/oppia/android/app/databinding/ImageViewBindingAdaptersTest.kt b/app/src/sharedTest/java/org/oppia/android/app/databinding/ImageViewBindingAdaptersTest.kt index 8ea245a0897..6c138de3990 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/databinding/ImageViewBindingAdaptersTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/databinding/ImageViewBindingAdaptersTest.kt @@ -89,7 +89,6 @@ import org.oppia.android.util.gcsresource.GcsResourceModule import org.oppia.android.util.locale.LocaleProdModule import org.oppia.android.util.logging.LoggerModule import org.oppia.android.util.logging.SyncStatusModule -import org.oppia.android.util.logging.firebase.FirebaseLogUploaderModule import org.oppia.android.util.networking.NetworkConnectionDebugUtilModule import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule import org.oppia.android.util.parser.html.HtmlParserEntityTypeModule @@ -276,7 +275,6 @@ class ImageViewBindingAdaptersTest { ExplorationProgressModule::class, ExplorationStorageModule::class, FakeOppiaClockModule::class, - FirebaseLogUploaderModule::class, FractionInputModule::class, GcsResourceModule::class, HintsAndSolutionConfigModule::class, diff --git a/app/src/sharedTest/java/org/oppia/android/app/databinding/MarginBindingAdaptersTest.kt b/app/src/sharedTest/java/org/oppia/android/app/databinding/MarginBindingAdaptersTest.kt index 23251ded40c..2599694fd87 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/databinding/MarginBindingAdaptersTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/databinding/MarginBindingAdaptersTest.kt @@ -93,7 +93,6 @@ import org.oppia.android.util.gcsresource.GcsResourceModule import org.oppia.android.util.locale.LocaleProdModule import org.oppia.android.util.logging.LoggerModule import org.oppia.android.util.logging.SyncStatusModule -import org.oppia.android.util.logging.firebase.FirebaseLogUploaderModule import org.oppia.android.util.networking.NetworkConnectionDebugUtilModule import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule import org.oppia.android.util.parser.html.HtmlParserEntityTypeModule @@ -437,7 +436,6 @@ class MarginBindingAdaptersTest { ExplorationProgressModule::class, ExplorationStorageModule::class, FakeOppiaClockModule::class, - FirebaseLogUploaderModule::class, FractionInputModule::class, GcsResourceModule::class, HintsAndSolutionConfigModule::class, diff --git a/app/src/sharedTest/java/org/oppia/android/app/databinding/StateAssemblerMarginBindingAdaptersTest.kt b/app/src/sharedTest/java/org/oppia/android/app/databinding/StateAssemblerMarginBindingAdaptersTest.kt index 13364012cd4..9d4610b8cd3 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/databinding/StateAssemblerMarginBindingAdaptersTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/databinding/StateAssemblerMarginBindingAdaptersTest.kt @@ -91,7 +91,6 @@ import org.oppia.android.util.gcsresource.GcsResourceModule import org.oppia.android.util.locale.LocaleProdModule import org.oppia.android.util.logging.LoggerModule import org.oppia.android.util.logging.SyncStatusModule -import org.oppia.android.util.logging.firebase.FirebaseLogUploaderModule import org.oppia.android.util.networking.NetworkConnectionDebugUtilModule import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule import org.oppia.android.util.parser.html.HtmlParserEntityTypeModule @@ -527,7 +526,6 @@ class StateAssemblerMarginBindingAdaptersTest { ExplorationProgressModule::class, ExplorationStorageModule::class, FakeOppiaClockModule::class, - FirebaseLogUploaderModule::class, FractionInputModule::class, GcsResourceModule::class, HintsAndSolutionConfigModule::class, diff --git a/app/src/sharedTest/java/org/oppia/android/app/databinding/StateAssemblerPaddingBindingAdaptersTest.kt b/app/src/sharedTest/java/org/oppia/android/app/databinding/StateAssemblerPaddingBindingAdaptersTest.kt index 04252856817..425ce949c6b 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/databinding/StateAssemblerPaddingBindingAdaptersTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/databinding/StateAssemblerPaddingBindingAdaptersTest.kt @@ -89,7 +89,6 @@ import org.oppia.android.util.gcsresource.GcsResourceModule import org.oppia.android.util.locale.LocaleProdModule import org.oppia.android.util.logging.LoggerModule import org.oppia.android.util.logging.SyncStatusModule -import org.oppia.android.util.logging.firebase.FirebaseLogUploaderModule import org.oppia.android.util.networking.NetworkConnectionDebugUtilModule import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule import org.oppia.android.util.parser.html.HtmlParserEntityTypeModule @@ -525,7 +524,6 @@ class StateAssemblerPaddingBindingAdaptersTest { ExplorationProgressModule::class, ExplorationStorageModule::class, FakeOppiaClockModule::class, - FirebaseLogUploaderModule::class, FractionInputModule::class, GcsResourceModule::class, HintsAndSolutionConfigModule::class, diff --git a/app/src/sharedTest/java/org/oppia/android/app/databinding/TextInputLayoutBindingAdaptersTest.kt b/app/src/sharedTest/java/org/oppia/android/app/databinding/TextInputLayoutBindingAdaptersTest.kt index 544165e5afd..02958047dbb 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/databinding/TextInputLayoutBindingAdaptersTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/databinding/TextInputLayoutBindingAdaptersTest.kt @@ -80,7 +80,6 @@ import org.oppia.android.util.gcsresource.GcsResourceModule import org.oppia.android.util.locale.LocaleProdModule import org.oppia.android.util.logging.LoggerModule import org.oppia.android.util.logging.SyncStatusModule -import org.oppia.android.util.logging.firebase.FirebaseLogUploaderModule import org.oppia.android.util.networking.NetworkConnectionDebugUtilModule import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule import org.oppia.android.util.parser.html.HtmlParserEntityTypeModule @@ -207,7 +206,6 @@ class TextInputLayoutBindingAdaptersTest { ExplorationProgressModule::class, ExplorationStorageModule::class, FakeOppiaClockModule::class, - FirebaseLogUploaderModule::class, FractionInputModule::class, GcsResourceModule::class, HintsAndSolutionConfigModule::class, diff --git a/app/src/sharedTest/java/org/oppia/android/app/databinding/TextViewBindingAdaptersTest.kt b/app/src/sharedTest/java/org/oppia/android/app/databinding/TextViewBindingAdaptersTest.kt index b830ce4b831..8885505139b 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/databinding/TextViewBindingAdaptersTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/databinding/TextViewBindingAdaptersTest.kt @@ -83,7 +83,6 @@ import org.oppia.android.util.gcsresource.GcsResourceModule import org.oppia.android.util.locale.LocaleProdModule import org.oppia.android.util.logging.LoggerModule import org.oppia.android.util.logging.SyncStatusModule -import org.oppia.android.util.logging.firebase.FirebaseLogUploaderModule import org.oppia.android.util.networking.NetworkConnectionDebugUtilModule import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule import org.oppia.android.util.parser.html.HtmlParserEntityTypeModule @@ -332,7 +331,6 @@ class TextViewBindingAdaptersTest { ExplorationProgressModule::class, ExplorationStorageModule::class, FakeOppiaClockModule::class, - FirebaseLogUploaderModule::class, FractionInputModule::class, GcsResourceModule::class, HintsAndSolutionConfigModule::class, diff --git a/app/src/sharedTest/java/org/oppia/android/app/databinding/ViewBindingAdaptersTest.kt b/app/src/sharedTest/java/org/oppia/android/app/databinding/ViewBindingAdaptersTest.kt index e4e2bb34b31..6f4d2c92167 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/databinding/ViewBindingAdaptersTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/databinding/ViewBindingAdaptersTest.kt @@ -86,7 +86,6 @@ import org.oppia.android.util.gcsresource.GcsResourceModule import org.oppia.android.util.locale.LocaleProdModule import org.oppia.android.util.logging.LoggerModule import org.oppia.android.util.logging.SyncStatusModule -import org.oppia.android.util.logging.firebase.FirebaseLogUploaderModule import org.oppia.android.util.networking.NetworkConnectionDebugUtilModule import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule import org.oppia.android.util.parser.html.HtmlParserEntityTypeModule @@ -239,7 +238,6 @@ class ViewBindingAdaptersTest { ExplorationProgressModule::class, ExplorationStorageModule::class, FakeOppiaClockModule::class, - FirebaseLogUploaderModule::class, FractionInputModule::class, GcsResourceModule::class, HintsAndSolutionConfigModule::class, diff --git a/app/src/sharedTest/java/org/oppia/android/app/devoptions/DeveloperOptionsActivityTest.kt b/app/src/sharedTest/java/org/oppia/android/app/devoptions/DeveloperOptionsActivityTest.kt index 28a07d19a2d..34b811b441c 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/devoptions/DeveloperOptionsActivityTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/devoptions/DeveloperOptionsActivityTest.kt @@ -108,7 +108,6 @@ import org.oppia.android.util.locale.LocaleProdModule import org.oppia.android.util.logging.CurrentAppScreenNameIntentDecorator.extractCurrentAppScreenName import org.oppia.android.util.logging.LoggerModule import org.oppia.android.util.logging.SyncStatusModule -import org.oppia.android.util.logging.firebase.FirebaseLogUploaderModule import org.oppia.android.util.networking.NetworkConnectionDebugUtilModule import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule import org.oppia.android.util.parser.html.HtmlParserEntityTypeModule @@ -374,7 +373,6 @@ class DeveloperOptionsActivityTest { ExplorationProgressModule::class, ExplorationStorageModule::class, FakeOppiaClockModule::class, - FirebaseLogUploaderModule::class, FractionInputModule::class, GcsResourceModule::class, GlideImageLoaderModule::class, diff --git a/app/src/sharedTest/java/org/oppia/android/app/devoptions/DeveloperOptionsFragmentTest.kt b/app/src/sharedTest/java/org/oppia/android/app/devoptions/DeveloperOptionsFragmentTest.kt index b86dd653162..e32026dc5d2 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/devoptions/DeveloperOptionsFragmentTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/devoptions/DeveloperOptionsFragmentTest.kt @@ -102,7 +102,6 @@ import org.oppia.android.util.gcsresource.GcsResourceModule import org.oppia.android.util.locale.LocaleProdModule import org.oppia.android.util.logging.LoggerModule import org.oppia.android.util.logging.SyncStatusModule -import org.oppia.android.util.logging.firebase.FirebaseLogUploaderModule import org.oppia.android.util.networking.NetworkConnectionDebugUtilModule import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule import org.oppia.android.util.parser.html.HtmlParserEntityTypeModule @@ -688,7 +687,6 @@ class DeveloperOptionsFragmentTest { ExplorationProgressModule::class, ExplorationStorageModule::class, FakeOppiaClockModule::class, - FirebaseLogUploaderModule::class, FractionInputModule::class, GcsResourceModule::class, GlideImageLoaderModule::class, diff --git a/app/src/sharedTest/java/org/oppia/android/app/devoptions/featureflags/FeatureFlagsActivityTest.kt b/app/src/sharedTest/java/org/oppia/android/app/devoptions/featureflags/FeatureFlagsActivityTest.kt index 0c9d69e0502..a746c35360d 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/devoptions/featureflags/FeatureFlagsActivityTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/devoptions/featureflags/FeatureFlagsActivityTest.kt @@ -82,7 +82,6 @@ import org.oppia.android.util.locale.LocaleProdModule import org.oppia.android.util.logging.CurrentAppScreenNameIntentDecorator.extractCurrentAppScreenName import org.oppia.android.util.logging.LoggerModule import org.oppia.android.util.logging.SyncStatusModule -import org.oppia.android.util.logging.firebase.FirebaseLogUploaderModule import org.oppia.android.util.networking.NetworkConnectionDebugUtilModule import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule import org.oppia.android.util.parser.html.HtmlParserEntityTypeModule @@ -176,7 +175,6 @@ class FeatureFlagsActivityTest { ExplorationProgressModule::class, ExplorationStorageModule::class, FakeOppiaClockModule::class, - FirebaseLogUploaderModule::class, FractionInputModule::class, GcsResourceModule::class, GlideImageLoaderModule::class, diff --git a/app/src/sharedTest/java/org/oppia/android/app/devoptions/featureflags/FeatureFlagsFragmentTest.kt b/app/src/sharedTest/java/org/oppia/android/app/devoptions/featureflags/FeatureFlagsFragmentTest.kt index 7ffca968e61..fa30bbad199 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/devoptions/featureflags/FeatureFlagsFragmentTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/devoptions/featureflags/FeatureFlagsFragmentTest.kt @@ -114,7 +114,6 @@ import org.oppia.android.util.gcsresource.GcsResourceModule import org.oppia.android.util.locale.LocaleProdModule import org.oppia.android.util.logging.LoggerModule import org.oppia.android.util.logging.SyncStatusModule -import org.oppia.android.util.logging.firebase.FirebaseLogUploaderModule import org.oppia.android.util.networking.NetworkConnectionDebugUtilModule import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule import org.oppia.android.util.parser.html.HtmlParserEntityTypeModule @@ -1613,7 +1612,6 @@ class FeatureFlagsFragmentTest { ExplorationProgressModule::class, ExplorationStorageModule::class, FakeOppiaClockModule::class, - FirebaseLogUploaderModule::class, FractionInputModule::class, GcsResourceModule::class, GlideImageLoaderModule::class, diff --git a/app/src/sharedTest/java/org/oppia/android/app/devoptions/forcenetworktype/ForceNetworkTypeActivityTest.kt b/app/src/sharedTest/java/org/oppia/android/app/devoptions/forcenetworktype/ForceNetworkTypeActivityTest.kt index 896683ee199..66005845a60 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/devoptions/forcenetworktype/ForceNetworkTypeActivityTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/devoptions/forcenetworktype/ForceNetworkTypeActivityTest.kt @@ -82,7 +82,6 @@ import org.oppia.android.util.locale.LocaleProdModule import org.oppia.android.util.logging.CurrentAppScreenNameIntentDecorator.extractCurrentAppScreenName import org.oppia.android.util.logging.LoggerModule import org.oppia.android.util.logging.SyncStatusModule -import org.oppia.android.util.logging.firebase.FirebaseLogUploaderModule import org.oppia.android.util.networking.NetworkConnectionDebugUtilModule import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule import org.oppia.android.util.parser.html.HtmlParserEntityTypeModule @@ -179,7 +178,6 @@ class ForceNetworkTypeActivityTest { ExplorationProgressModule::class, ExplorationStorageModule::class, FakeOppiaClockModule::class, - FirebaseLogUploaderModule::class, FractionInputModule::class, GcsResourceModule::class, GlideImageLoaderModule::class, diff --git a/app/src/sharedTest/java/org/oppia/android/app/devoptions/forcenetworktype/ForceNetworkTypeFragmentTest.kt b/app/src/sharedTest/java/org/oppia/android/app/devoptions/forcenetworktype/ForceNetworkTypeFragmentTest.kt index 59f854b69c3..87a838444fd 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/devoptions/forcenetworktype/ForceNetworkTypeFragmentTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/devoptions/forcenetworktype/ForceNetworkTypeFragmentTest.kt @@ -87,7 +87,6 @@ import org.oppia.android.util.gcsresource.GcsResourceModule import org.oppia.android.util.locale.LocaleProdModule import org.oppia.android.util.logging.LoggerModule import org.oppia.android.util.logging.SyncStatusModule -import org.oppia.android.util.logging.firebase.FirebaseLogUploaderModule import org.oppia.android.util.networking.NetworkConnectionDebugUtilModule import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule import org.oppia.android.util.parser.html.HtmlParserEntityTypeModule @@ -395,7 +394,6 @@ class ForceNetworkTypeFragmentTest { ExplorationProgressModule::class, ExplorationStorageModule::class, FakeOppiaClockModule::class, - FirebaseLogUploaderModule::class, FractionInputModule::class, GcsResourceModule::class, GlideImageLoaderModule::class, diff --git a/app/src/sharedTest/java/org/oppia/android/app/devoptions/markchapterscompleted/MarkChaptersCompletedActivityTest.kt b/app/src/sharedTest/java/org/oppia/android/app/devoptions/markchapterscompleted/MarkChaptersCompletedActivityTest.kt index 3e1e945f5fe..3633c931222 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/devoptions/markchapterscompleted/MarkChaptersCompletedActivityTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/devoptions/markchapterscompleted/MarkChaptersCompletedActivityTest.kt @@ -92,7 +92,6 @@ import org.oppia.android.util.locale.LocaleProdModule import org.oppia.android.util.logging.CurrentAppScreenNameIntentDecorator.extractCurrentAppScreenName import org.oppia.android.util.logging.LoggerModule import org.oppia.android.util.logging.SyncStatusModule -import org.oppia.android.util.logging.firebase.FirebaseLogUploaderModule import org.oppia.android.util.networking.NetworkConnectionDebugUtilModule import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule import org.oppia.android.util.parser.html.HtmlParserEntityTypeModule @@ -265,7 +264,6 @@ class MarkChaptersCompletedActivityTest { ExplorationProgressModule::class, ExplorationStorageModule::class, FakeOppiaClockModule::class, - FirebaseLogUploaderModule::class, FractionInputModule::class, GcsResourceModule::class, GlideImageLoaderModule::class, diff --git a/app/src/sharedTest/java/org/oppia/android/app/devoptions/markchapterscompleted/MarkChaptersCompletedFragmentTest.kt b/app/src/sharedTest/java/org/oppia/android/app/devoptions/markchapterscompleted/MarkChaptersCompletedFragmentTest.kt index 5ee44efdd94..992b1126ffe 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/devoptions/markchapterscompleted/MarkChaptersCompletedFragmentTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/devoptions/markchapterscompleted/MarkChaptersCompletedFragmentTest.kt @@ -103,7 +103,6 @@ import org.oppia.android.util.gcsresource.GcsResourceModule import org.oppia.android.util.locale.LocaleProdModule import org.oppia.android.util.logging.LoggerModule import org.oppia.android.util.logging.SyncStatusModule -import org.oppia.android.util.logging.firebase.FirebaseLogUploaderModule import org.oppia.android.util.networking.NetworkConnectionDebugUtilModule import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule import org.oppia.android.util.parser.html.HtmlParserEntityTypeModule @@ -1085,7 +1084,6 @@ class MarkChaptersCompletedFragmentTest { ExplorationProgressModule::class, ExplorationStorageModule::class, FakeOppiaClockModule::class, - FirebaseLogUploaderModule::class, FractionInputModule::class, GcsResourceModule::class, GlideImageLoaderModule::class, diff --git a/app/src/sharedTest/java/org/oppia/android/app/devoptions/markstoriescompleted/MarkStoriesCompletedActivityTest.kt b/app/src/sharedTest/java/org/oppia/android/app/devoptions/markstoriescompleted/MarkStoriesCompletedActivityTest.kt index 9ecd1d0f57e..9da19624f12 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/devoptions/markstoriescompleted/MarkStoriesCompletedActivityTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/devoptions/markstoriescompleted/MarkStoriesCompletedActivityTest.kt @@ -82,7 +82,6 @@ import org.oppia.android.util.locale.LocaleProdModule import org.oppia.android.util.logging.CurrentAppScreenNameIntentDecorator.extractCurrentAppScreenName import org.oppia.android.util.logging.LoggerModule import org.oppia.android.util.logging.SyncStatusModule -import org.oppia.android.util.logging.firebase.FirebaseLogUploaderModule import org.oppia.android.util.networking.NetworkConnectionDebugUtilModule import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule import org.oppia.android.util.parser.html.HtmlParserEntityTypeModule @@ -192,7 +191,6 @@ class MarkStoriesCompletedActivityTest { ExplorationProgressModule::class, ExplorationStorageModule::class, FakeOppiaClockModule::class, - FirebaseLogUploaderModule::class, FractionInputModule::class, GcsResourceModule::class, GlideImageLoaderModule::class, diff --git a/app/src/sharedTest/java/org/oppia/android/app/devoptions/markstoriescompleted/MarkStoriesCompletedFragmentTest.kt b/app/src/sharedTest/java/org/oppia/android/app/devoptions/markstoriescompleted/MarkStoriesCompletedFragmentTest.kt index 823d7da3ac2..417958d1007 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/devoptions/markstoriescompleted/MarkStoriesCompletedFragmentTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/devoptions/markstoriescompleted/MarkStoriesCompletedFragmentTest.kt @@ -94,7 +94,6 @@ import org.oppia.android.util.gcsresource.GcsResourceModule import org.oppia.android.util.locale.LocaleProdModule import org.oppia.android.util.logging.LoggerModule import org.oppia.android.util.logging.SyncStatusModule -import org.oppia.android.util.logging.firebase.FirebaseLogUploaderModule import org.oppia.android.util.networking.NetworkConnectionDebugUtilModule import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule import org.oppia.android.util.parser.html.HtmlParserEntityTypeModule @@ -607,7 +606,6 @@ class MarkStoriesCompletedFragmentTest { ExplorationProgressModule::class, ExplorationStorageModule::class, FakeOppiaClockModule::class, - FirebaseLogUploaderModule::class, FractionInputModule::class, GcsResourceModule::class, GlideImageLoaderModule::class, diff --git a/app/src/sharedTest/java/org/oppia/android/app/devoptions/marktopicscompleted/MarkTopicsCompletedActivityTest.kt b/app/src/sharedTest/java/org/oppia/android/app/devoptions/marktopicscompleted/MarkTopicsCompletedActivityTest.kt index 5beaad4d8d9..da64739565e 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/devoptions/marktopicscompleted/MarkTopicsCompletedActivityTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/devoptions/marktopicscompleted/MarkTopicsCompletedActivityTest.kt @@ -82,7 +82,6 @@ import org.oppia.android.util.locale.LocaleProdModule import org.oppia.android.util.logging.CurrentAppScreenNameIntentDecorator.extractCurrentAppScreenName import org.oppia.android.util.logging.LoggerModule import org.oppia.android.util.logging.SyncStatusModule -import org.oppia.android.util.logging.firebase.FirebaseLogUploaderModule import org.oppia.android.util.networking.NetworkConnectionDebugUtilModule import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule import org.oppia.android.util.parser.html.HtmlParserEntityTypeModule @@ -192,7 +191,6 @@ class MarkTopicsCompletedActivityTest { ExplorationProgressModule::class, ExplorationStorageModule::class, FakeOppiaClockModule::class, - FirebaseLogUploaderModule::class, FractionInputModule::class, GcsResourceModule::class, GlideImageLoaderModule::class, diff --git a/app/src/sharedTest/java/org/oppia/android/app/devoptions/marktopicscompleted/MarkTopicsCompletedFragmentTest.kt b/app/src/sharedTest/java/org/oppia/android/app/devoptions/marktopicscompleted/MarkTopicsCompletedFragmentTest.kt index e37998668b5..5ef2c1a74ad 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/devoptions/marktopicscompleted/MarkTopicsCompletedFragmentTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/devoptions/marktopicscompleted/MarkTopicsCompletedFragmentTest.kt @@ -94,7 +94,6 @@ import org.oppia.android.util.gcsresource.GcsResourceModule import org.oppia.android.util.locale.LocaleProdModule import org.oppia.android.util.logging.LoggerModule import org.oppia.android.util.logging.SyncStatusModule -import org.oppia.android.util.logging.firebase.FirebaseLogUploaderModule import org.oppia.android.util.networking.NetworkConnectionDebugUtilModule import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule import org.oppia.android.util.parser.html.HtmlParserEntityTypeModule @@ -586,7 +585,6 @@ class MarkTopicsCompletedFragmentTest { ExplorationProgressModule::class, ExplorationStorageModule::class, FakeOppiaClockModule::class, - FirebaseLogUploaderModule::class, FractionInputModule::class, GcsResourceModule::class, GlideImageLoaderModule::class, diff --git a/app/src/sharedTest/java/org/oppia/android/app/devoptions/mathexpressionparser/MathExpressionParserActivityTest.kt b/app/src/sharedTest/java/org/oppia/android/app/devoptions/mathexpressionparser/MathExpressionParserActivityTest.kt index 1dd92d76b16..fb54bd3c660 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/devoptions/mathexpressionparser/MathExpressionParserActivityTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/devoptions/mathexpressionparser/MathExpressionParserActivityTest.kt @@ -80,7 +80,6 @@ import org.oppia.android.util.locale.LocaleProdModule import org.oppia.android.util.logging.CurrentAppScreenNameIntentDecorator.extractCurrentAppScreenName import org.oppia.android.util.logging.LoggerModule import org.oppia.android.util.logging.SyncStatusModule -import org.oppia.android.util.logging.firebase.FirebaseLogUploaderModule import org.oppia.android.util.networking.NetworkConnectionDebugUtilModule import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule import org.oppia.android.util.parser.html.HtmlParserEntityTypeModule @@ -173,7 +172,6 @@ class MathExpressionParserActivityTest { ExplorationProgressModule::class, ExplorationStorageModule::class, FakeOppiaClockModule::class, - FirebaseLogUploaderModule::class, FractionInputModule::class, GcsResourceModule::class, GlideImageLoaderModule::class, diff --git a/app/src/sharedTest/java/org/oppia/android/app/devoptions/mathexpressionparser/MathExpressionParserFragmentTest.kt b/app/src/sharedTest/java/org/oppia/android/app/devoptions/mathexpressionparser/MathExpressionParserFragmentTest.kt index a8442afc7ed..5d56012c3c6 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/devoptions/mathexpressionparser/MathExpressionParserFragmentTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/devoptions/mathexpressionparser/MathExpressionParserFragmentTest.kt @@ -88,7 +88,6 @@ import org.oppia.android.util.gcsresource.GcsResourceModule import org.oppia.android.util.locale.LocaleProdModule import org.oppia.android.util.logging.LoggerModule import org.oppia.android.util.logging.SyncStatusModule -import org.oppia.android.util.logging.firebase.FirebaseLogUploaderModule import org.oppia.android.util.networking.NetworkConnectionDebugUtilModule import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule import org.oppia.android.util.parser.html.HtmlParserEntityTypeModule @@ -1448,7 +1447,6 @@ class MathExpressionParserFragmentTest { ExplorationProgressModule::class, ExplorationStorageModule::class, FakeOppiaClockModule::class, - FirebaseLogUploaderModule::class, FractionInputModule::class, GcsResourceModule::class, HintsAndSolutionConfigModule::class, diff --git a/app/src/sharedTest/java/org/oppia/android/app/devoptions/platformparameters/PlatformParametersActivityTest.kt b/app/src/sharedTest/java/org/oppia/android/app/devoptions/platformparameters/PlatformParametersActivityTest.kt index e5fa16a9d3b..c87d2a1f7f3 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/devoptions/platformparameters/PlatformParametersActivityTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/devoptions/platformparameters/PlatformParametersActivityTest.kt @@ -82,7 +82,6 @@ import org.oppia.android.util.locale.LocaleProdModule import org.oppia.android.util.logging.CurrentAppScreenNameIntentDecorator.extractCurrentAppScreenName import org.oppia.android.util.logging.LoggerModule import org.oppia.android.util.logging.SyncStatusModule -import org.oppia.android.util.logging.firebase.FirebaseLogUploaderModule import org.oppia.android.util.networking.NetworkConnectionDebugUtilModule import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule import org.oppia.android.util.parser.html.HtmlParserEntityTypeModule @@ -179,7 +178,6 @@ class PlatformParametersActivityTest { ExplorationProgressModule::class, ExplorationStorageModule::class, FakeOppiaClockModule::class, - FirebaseLogUploaderModule::class, FractionInputModule::class, GcsResourceModule::class, GlideImageLoaderModule::class, diff --git a/app/src/sharedTest/java/org/oppia/android/app/devoptions/platformparameters/PlatformParametersFragmentTest.kt b/app/src/sharedTest/java/org/oppia/android/app/devoptions/platformparameters/PlatformParametersFragmentTest.kt index 34bc377e06d..7db35ebc61f 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/devoptions/platformparameters/PlatformParametersFragmentTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/devoptions/platformparameters/PlatformParametersFragmentTest.kt @@ -118,7 +118,6 @@ import org.oppia.android.util.gcsresource.GcsResourceModule import org.oppia.android.util.locale.LocaleProdModule import org.oppia.android.util.logging.LoggerModule import org.oppia.android.util.logging.SyncStatusModule -import org.oppia.android.util.logging.firebase.FirebaseLogUploaderModule import org.oppia.android.util.networking.NetworkConnectionDebugUtilModule import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule import org.oppia.android.util.parser.html.HtmlParserEntityTypeModule @@ -2198,7 +2197,6 @@ class PlatformParametersFragmentTest { ExplorationProgressModule::class, ExplorationStorageModule::class, FakeOppiaClockModule::class, - FirebaseLogUploaderModule::class, FractionInputModule::class, GcsResourceModule::class, GlideImageLoaderModule::class, diff --git a/app/src/sharedTest/java/org/oppia/android/app/devoptions/vieweventlogs/ViewEventLogsActivityTest.kt b/app/src/sharedTest/java/org/oppia/android/app/devoptions/vieweventlogs/ViewEventLogsActivityTest.kt index 40949d7e8bf..18a2f4cd5f3 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/devoptions/vieweventlogs/ViewEventLogsActivityTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/devoptions/vieweventlogs/ViewEventLogsActivityTest.kt @@ -83,7 +83,6 @@ import org.oppia.android.util.logging.CurrentAppScreenNameIntentDecorator.extrac import org.oppia.android.util.logging.LoggerModule import org.oppia.android.util.logging.SyncStatusModule import org.oppia.android.util.logging.firebase.DebugLogReportingModule -import org.oppia.android.util.logging.firebase.FirebaseLogUploaderModule import org.oppia.android.util.logging.performancemetrics.PerformanceMetricsAssessorModule import org.oppia.android.util.logging.performancemetrics.PerformanceMetricsConfigurationsModule import org.oppia.android.util.networking.NetworkConnectionDebugUtilModule @@ -179,7 +178,6 @@ class ViewEventLogsActivityTest { ExplorationProgressModule::class, ExplorationStorageModule::class, FakeOppiaClockModule::class, - FirebaseLogUploaderModule::class, FractionInputModule::class, GcsResourceModule::class, GlideImageLoaderModule::class, diff --git a/app/src/sharedTest/java/org/oppia/android/app/devoptions/vieweventlogs/ViewEventLogsFragmentTest.kt b/app/src/sharedTest/java/org/oppia/android/app/devoptions/vieweventlogs/ViewEventLogsFragmentTest.kt index 811556f8761..ff2ddc8a38b 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/devoptions/vieweventlogs/ViewEventLogsFragmentTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/devoptions/vieweventlogs/ViewEventLogsFragmentTest.kt @@ -105,7 +105,6 @@ import org.oppia.android.util.logging.firebase.DebugAnalyticsEventLogger import org.oppia.android.util.logging.firebase.DebugFirestoreEventLoggerImpl import org.oppia.android.util.logging.firebase.FirebaseAnalyticsEventLogger import org.oppia.android.util.logging.firebase.FirebaseExceptionLogger -import org.oppia.android.util.logging.firebase.FirebaseLogUploaderModule import org.oppia.android.util.logging.firebase.FirestoreEventLogger import org.oppia.android.util.logging.firebase.FirestoreInstanceWrapper import org.oppia.android.util.logging.performancemetrics.PerformanceMetricsAssessorModule @@ -751,7 +750,6 @@ class ViewEventLogsFragmentTest { ExplorationProgressModule::class, ExplorationStorageModule::class, FakeOppiaClockModule::class, - FirebaseLogUploaderModule::class, FractionInputModule::class, GcsResourceModule::class, GlideImageLoaderModule::class, diff --git a/app/src/sharedTest/java/org/oppia/android/app/help/HelpActivityTest.kt b/app/src/sharedTest/java/org/oppia/android/app/help/HelpActivityTest.kt index afcc4eebb83..969134e7591 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/help/HelpActivityTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/help/HelpActivityTest.kt @@ -77,7 +77,6 @@ import org.oppia.android.util.locale.LocaleProdModule import org.oppia.android.util.logging.CurrentAppScreenNameIntentDecorator.extractCurrentAppScreenName import org.oppia.android.util.logging.LoggerModule import org.oppia.android.util.logging.SyncStatusModule -import org.oppia.android.util.logging.firebase.FirebaseLogUploaderModule import org.oppia.android.util.networking.NetworkConnectionDebugUtilModule import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule import org.oppia.android.util.parser.html.HtmlParserEntityTypeModule @@ -167,7 +166,6 @@ class HelpActivityTest { ExplorationProgressModule::class, ExplorationStorageModule::class, FakeOppiaClockModule::class, - FirebaseLogUploaderModule::class, FractionInputModule::class, GcsResourceModule::class, GlideImageLoaderModule::class, diff --git a/app/src/sharedTest/java/org/oppia/android/app/help/HelpFragmentTest.kt b/app/src/sharedTest/java/org/oppia/android/app/help/HelpFragmentTest.kt index 0fcfe739999..ffc2e46a236 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/help/HelpFragmentTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/help/HelpFragmentTest.kt @@ -113,7 +113,6 @@ import org.oppia.android.util.gcsresource.GcsResourceModule import org.oppia.android.util.locale.LocaleProdModule import org.oppia.android.util.logging.LoggerModule import org.oppia.android.util.logging.SyncStatusModule -import org.oppia.android.util.logging.firebase.FirebaseLogUploaderModule import org.oppia.android.util.networking.NetworkConnectionDebugUtilModule import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule import org.oppia.android.util.parser.html.HtmlParserEntityTypeModule @@ -1476,7 +1475,6 @@ class HelpFragmentTest { ExplorationProgressModule::class, ExplorationStorageModule::class, FakeOppiaClockModule::class, - FirebaseLogUploaderModule::class, FractionInputModule::class, GcsResourceModule::class, GlideImageLoaderModule::class, diff --git a/app/src/sharedTest/java/org/oppia/android/app/help/faq/FAQListActivityTest.kt b/app/src/sharedTest/java/org/oppia/android/app/help/faq/FAQListActivityTest.kt index aecccfde05d..7dedd5821c5 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/help/faq/FAQListActivityTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/help/faq/FAQListActivityTest.kt @@ -76,7 +76,6 @@ import org.oppia.android.util.locale.LocaleProdModule import org.oppia.android.util.logging.CurrentAppScreenNameIntentDecorator.extractCurrentAppScreenName import org.oppia.android.util.logging.LoggerModule import org.oppia.android.util.logging.SyncStatusModule -import org.oppia.android.util.logging.firebase.FirebaseLogUploaderModule import org.oppia.android.util.networking.NetworkConnectionDebugUtilModule import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule import org.oppia.android.util.parser.html.HtmlParserEntityTypeModule @@ -158,7 +157,6 @@ class FAQListActivityTest { ExplorationProgressModule::class, ExplorationStorageModule::class, FakeOppiaClockModule::class, - FirebaseLogUploaderModule::class, FractionInputModule::class, GcsResourceModule::class, GlideImageLoaderModule::class, diff --git a/app/src/sharedTest/java/org/oppia/android/app/help/faq/FAQListFragmentTest.kt b/app/src/sharedTest/java/org/oppia/android/app/help/faq/FAQListFragmentTest.kt index f0f5d33260a..f28ffe6c9c7 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/help/faq/FAQListFragmentTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/help/faq/FAQListFragmentTest.kt @@ -93,7 +93,6 @@ import org.oppia.android.util.gcsresource.GcsResourceModule import org.oppia.android.util.locale.LocaleProdModule import org.oppia.android.util.logging.LoggerModule import org.oppia.android.util.logging.SyncStatusModule -import org.oppia.android.util.logging.firebase.FirebaseLogUploaderModule import org.oppia.android.util.networking.NetworkConnectionDebugUtilModule import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule import org.oppia.android.util.parser.html.HtmlParserEntityTypeModule @@ -254,7 +253,6 @@ class FAQListFragmentTest { ExplorationProgressModule::class, ExplorationStorageModule::class, FakeOppiaClockModule::class, - FirebaseLogUploaderModule::class, FractionInputModule::class, GcsResourceModule::class, GlideImageLoaderModule::class, diff --git a/app/src/sharedTest/java/org/oppia/android/app/help/faq/faqsingle/FAQSingleActivityTest.kt b/app/src/sharedTest/java/org/oppia/android/app/help/faq/faqsingle/FAQSingleActivityTest.kt index 51c5fd35a39..ee36dcd503b 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/help/faq/faqsingle/FAQSingleActivityTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/help/faq/faqsingle/FAQSingleActivityTest.kt @@ -87,7 +87,6 @@ import org.oppia.android.util.locale.LocaleProdModule import org.oppia.android.util.logging.CurrentAppScreenNameIntentDecorator.extractCurrentAppScreenName import org.oppia.android.util.logging.LoggerModule import org.oppia.android.util.logging.SyncStatusModule -import org.oppia.android.util.logging.firebase.FirebaseLogUploaderModule import org.oppia.android.util.networking.NetworkConnectionDebugUtilModule import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule import org.oppia.android.util.parser.html.HtmlParser @@ -217,7 +216,6 @@ class FAQSingleActivityTest { ExplorationProgressModule::class, ExplorationStorageModule::class, FakeOppiaClockModule::class, - FirebaseLogUploaderModule::class, FractionInputModule::class, GcsResourceModule::class, GlideImageLoaderModule::class, diff --git a/app/src/sharedTest/java/org/oppia/android/app/help/thirdparty/LicenseListActivityTest.kt b/app/src/sharedTest/java/org/oppia/android/app/help/thirdparty/LicenseListActivityTest.kt index 3a6f5c362fb..83efd6cfdd6 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/help/thirdparty/LicenseListActivityTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/help/thirdparty/LicenseListActivityTest.kt @@ -76,7 +76,6 @@ import org.oppia.android.util.locale.LocaleProdModule import org.oppia.android.util.logging.CurrentAppScreenNameIntentDecorator.extractCurrentAppScreenName import org.oppia.android.util.logging.LoggerModule import org.oppia.android.util.logging.SyncStatusModule -import org.oppia.android.util.logging.firebase.FirebaseLogUploaderModule import org.oppia.android.util.networking.NetworkConnectionDebugUtilModule import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule import org.oppia.android.util.parser.html.HtmlParserEntityTypeModule @@ -162,7 +161,6 @@ class LicenseListActivityTest { ExplorationProgressModule::class, ExplorationStorageModule::class, FakeOppiaClockModule::class, - FirebaseLogUploaderModule::class, FractionInputModule::class, GcsResourceModule::class, GlideImageLoaderModule::class, diff --git a/app/src/sharedTest/java/org/oppia/android/app/help/thirdparty/LicenseListFragmentTest.kt b/app/src/sharedTest/java/org/oppia/android/app/help/thirdparty/LicenseListFragmentTest.kt index a829ccc8cba..b53132ab439 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/help/thirdparty/LicenseListFragmentTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/help/thirdparty/LicenseListFragmentTest.kt @@ -93,7 +93,6 @@ import org.oppia.android.util.gcsresource.GcsResourceModule import org.oppia.android.util.locale.LocaleProdModule import org.oppia.android.util.logging.LoggerModule import org.oppia.android.util.logging.SyncStatusModule -import org.oppia.android.util.logging.firebase.FirebaseLogUploaderModule import org.oppia.android.util.networking.NetworkConnectionDebugUtilModule import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule import org.oppia.android.util.parser.html.HtmlParserEntityTypeModule @@ -400,7 +399,6 @@ class LicenseListFragmentTest { ExplorationProgressModule::class, ExplorationStorageModule::class, FakeOppiaClockModule::class, - FirebaseLogUploaderModule::class, FractionInputModule::class, GcsResourceModule::class, GlideImageLoaderModule::class, diff --git a/app/src/sharedTest/java/org/oppia/android/app/help/thirdparty/LicenseTextViewerActivityTest.kt b/app/src/sharedTest/java/org/oppia/android/app/help/thirdparty/LicenseTextViewerActivityTest.kt index d8b5412a94b..da0a142d07f 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/help/thirdparty/LicenseTextViewerActivityTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/help/thirdparty/LicenseTextViewerActivityTest.kt @@ -84,7 +84,6 @@ import org.oppia.android.util.locale.LocaleProdModule import org.oppia.android.util.logging.CurrentAppScreenNameIntentDecorator.extractCurrentAppScreenName import org.oppia.android.util.logging.LoggerModule import org.oppia.android.util.logging.SyncStatusModule -import org.oppia.android.util.logging.firebase.FirebaseLogUploaderModule import org.oppia.android.util.networking.NetworkConnectionDebugUtilModule import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule import org.oppia.android.util.parser.html.HtmlParserEntityTypeModule @@ -270,7 +269,6 @@ class LicenseTextViewerActivityTest { ExplorationProgressModule::class, ExplorationStorageModule::class, FakeOppiaClockModule::class, - FirebaseLogUploaderModule::class, FractionInputModule::class, GcsResourceModule::class, GlideImageLoaderModule::class, diff --git a/app/src/sharedTest/java/org/oppia/android/app/help/thirdparty/LicenseTextViewerFragmentTest.kt b/app/src/sharedTest/java/org/oppia/android/app/help/thirdparty/LicenseTextViewerFragmentTest.kt index eb39685aa70..f1d62d66051 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/help/thirdparty/LicenseTextViewerFragmentTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/help/thirdparty/LicenseTextViewerFragmentTest.kt @@ -84,7 +84,6 @@ import org.oppia.android.util.gcsresource.GcsResourceModule import org.oppia.android.util.locale.LocaleProdModule import org.oppia.android.util.logging.LoggerModule import org.oppia.android.util.logging.SyncStatusModule -import org.oppia.android.util.logging.firebase.FirebaseLogUploaderModule import org.oppia.android.util.networking.NetworkConnectionDebugUtilModule import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule import org.oppia.android.util.parser.html.HtmlParserEntityTypeModule @@ -383,7 +382,6 @@ class LicenseTextViewerFragmentTest { ExplorationProgressModule::class, ExplorationStorageModule::class, FakeOppiaClockModule::class, - FirebaseLogUploaderModule::class, FractionInputModule::class, GcsResourceModule::class, GlideImageLoaderModule::class, diff --git a/app/src/sharedTest/java/org/oppia/android/app/help/thirdparty/ThirdPartyDependencyListActivityTest.kt b/app/src/sharedTest/java/org/oppia/android/app/help/thirdparty/ThirdPartyDependencyListActivityTest.kt index 1f3b2b2aa6d..bc68dd07705 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/help/thirdparty/ThirdPartyDependencyListActivityTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/help/thirdparty/ThirdPartyDependencyListActivityTest.kt @@ -77,7 +77,6 @@ import org.oppia.android.util.locale.LocaleProdModule import org.oppia.android.util.logging.CurrentAppScreenNameIntentDecorator.extractCurrentAppScreenName import org.oppia.android.util.logging.LoggerModule import org.oppia.android.util.logging.SyncStatusModule -import org.oppia.android.util.logging.firebase.FirebaseLogUploaderModule import org.oppia.android.util.networking.NetworkConnectionDebugUtilModule import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule import org.oppia.android.util.parser.html.HtmlParserEntityTypeModule @@ -165,7 +164,6 @@ class ThirdPartyDependencyListActivityTest { ExplorationProgressModule::class, ExplorationStorageModule::class, FakeOppiaClockModule::class, - FirebaseLogUploaderModule::class, FractionInputModule::class, GcsResourceModule::class, GlideImageLoaderModule::class, diff --git a/app/src/sharedTest/java/org/oppia/android/app/help/thirdparty/ThirdPartyDependencyListFragmentTest.kt b/app/src/sharedTest/java/org/oppia/android/app/help/thirdparty/ThirdPartyDependencyListFragmentTest.kt index c6e57f0af85..3e7711dc522 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/help/thirdparty/ThirdPartyDependencyListFragmentTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/help/thirdparty/ThirdPartyDependencyListFragmentTest.kt @@ -92,7 +92,6 @@ import org.oppia.android.util.gcsresource.GcsResourceModule import org.oppia.android.util.locale.LocaleProdModule import org.oppia.android.util.logging.LoggerModule import org.oppia.android.util.logging.SyncStatusModule -import org.oppia.android.util.logging.firebase.FirebaseLogUploaderModule import org.oppia.android.util.networking.NetworkConnectionDebugUtilModule import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule import org.oppia.android.util.parser.html.HtmlParserEntityTypeModule @@ -508,7 +507,6 @@ class ThirdPartyDependencyListFragmentTest { ExplorationProgressModule::class, ExplorationStorageModule::class, FakeOppiaClockModule::class, - FirebaseLogUploaderModule::class, FractionInputModule::class, GcsResourceModule::class, GlideImageLoaderModule::class, diff --git a/app/src/sharedTest/java/org/oppia/android/app/home/HomeActivityTest.kt b/app/src/sharedTest/java/org/oppia/android/app/home/HomeActivityTest.kt index 211bf66fc2b..341689a15cf 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/home/HomeActivityTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/home/HomeActivityTest.kt @@ -147,7 +147,6 @@ import org.oppia.android.util.locale.LocaleProdModule import org.oppia.android.util.logging.CurrentAppScreenNameIntentDecorator.extractCurrentAppScreenName import org.oppia.android.util.logging.LoggerModule import org.oppia.android.util.logging.SyncStatusModule -import org.oppia.android.util.logging.firebase.FirebaseLogUploaderModule import org.oppia.android.util.networking.NetworkConnectionDebugUtilModule import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule import org.oppia.android.util.parser.html.HtmlParserEntityTypeModule @@ -2222,7 +2221,6 @@ class HomeActivityTest { ExplorationProgressModule::class, ExplorationStorageModule::class, FakeOppiaClockModule::class, - FirebaseLogUploaderModule::class, FractionInputModule::class, GcsResourceModule::class, GlideImageLoaderModule::class, diff --git a/app/src/sharedTest/java/org/oppia/android/app/home/WelcomeViewModelTest.kt b/app/src/sharedTest/java/org/oppia/android/app/home/WelcomeViewModelTest.kt index 3ebbddf4537..93a19bc463e 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/home/WelcomeViewModelTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/home/WelcomeViewModelTest.kt @@ -77,7 +77,6 @@ import org.oppia.android.util.gcsresource.GcsResourceModule import org.oppia.android.util.locale.LocaleProdModule import org.oppia.android.util.logging.LoggerModule import org.oppia.android.util.logging.SyncStatusModule -import org.oppia.android.util.logging.firebase.FirebaseLogUploaderModule import org.oppia.android.util.networking.NetworkConnectionDebugUtilModule import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule import org.oppia.android.util.parser.html.HtmlParserEntityTypeModule @@ -358,7 +357,6 @@ class WelcomeViewModelTest { ExplorationProgressModule::class, ExplorationStorageModule::class, FakeOppiaClockModule::class, - FirebaseLogUploaderModule::class, FractionInputModule::class, GcsResourceModule::class, GlideImageLoaderModule::class, diff --git a/app/src/sharedTest/java/org/oppia/android/app/home/promotedlist/PromotedStoryListViewModelTest.kt b/app/src/sharedTest/java/org/oppia/android/app/home/promotedlist/PromotedStoryListViewModelTest.kt index e25a7f1fd61..0a2f7e7579e 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/home/promotedlist/PromotedStoryListViewModelTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/home/promotedlist/PromotedStoryListViewModelTest.kt @@ -79,7 +79,6 @@ import org.oppia.android.util.gcsresource.GcsResourceModule import org.oppia.android.util.locale.LocaleProdModule import org.oppia.android.util.logging.LoggerModule import org.oppia.android.util.logging.SyncStatusModule -import org.oppia.android.util.logging.firebase.FirebaseLogUploaderModule import org.oppia.android.util.networking.NetworkConnectionDebugUtilModule import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule import org.oppia.android.util.parser.html.HtmlParserEntityTypeModule @@ -371,7 +370,6 @@ class PromotedStoryListViewModelTest { ExplorationProgressModule::class, ExplorationStorageModule::class, FakeOppiaClockModule::class, - FirebaseLogUploaderModule::class, FractionInputModule::class, GcsResourceModule::class, GlideImageLoaderModule::class, diff --git a/app/src/sharedTest/java/org/oppia/android/app/home/promotedlist/PromotedStoryViewModelTest.kt b/app/src/sharedTest/java/org/oppia/android/app/home/promotedlist/PromotedStoryViewModelTest.kt index f8297f8f102..d98edfeef1d 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/home/promotedlist/PromotedStoryViewModelTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/home/promotedlist/PromotedStoryViewModelTest.kt @@ -77,7 +77,6 @@ import org.oppia.android.util.gcsresource.GcsResourceModule import org.oppia.android.util.locale.LocaleProdModule import org.oppia.android.util.logging.LoggerModule import org.oppia.android.util.logging.SyncStatusModule -import org.oppia.android.util.logging.firebase.FirebaseLogUploaderModule import org.oppia.android.util.networking.NetworkConnectionDebugUtilModule import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule import org.oppia.android.util.parser.html.HtmlParserEntityTypeModule @@ -397,7 +396,6 @@ class PromotedStoryViewModelTest { ExplorationProgressModule::class, ExplorationStorageModule::class, FakeOppiaClockModule::class, - FirebaseLogUploaderModule::class, FractionInputModule::class, GcsResourceModule::class, GlideImageLoaderModule::class, diff --git a/app/src/sharedTest/java/org/oppia/android/app/home/recentlyplayed/RecentlyPlayedActivityTest.kt b/app/src/sharedTest/java/org/oppia/android/app/home/recentlyplayed/RecentlyPlayedActivityTest.kt index 30043afaaa8..439ad01947e 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/home/recentlyplayed/RecentlyPlayedActivityTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/home/recentlyplayed/RecentlyPlayedActivityTest.kt @@ -117,7 +117,6 @@ import org.oppia.android.util.locale.LocaleProdModule import org.oppia.android.util.logging.CurrentAppScreenNameIntentDecorator.extractCurrentAppScreenName import org.oppia.android.util.logging.LoggerModule import org.oppia.android.util.logging.SyncStatusModule -import org.oppia.android.util.logging.firebase.FirebaseLogUploaderModule import org.oppia.android.util.networking.NetworkConnectionDebugUtilModule import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule import org.oppia.android.util.parser.html.HtmlParserEntityTypeModule @@ -502,7 +501,6 @@ class RecentlyPlayedActivityTest { ExplorationProgressModule::class, ExplorationStorageTestModule::class, FakeOppiaClockModule::class, - FirebaseLogUploaderModule::class, FractionInputModule::class, GcsResourceModule::class, HintsAndSolutionConfigModule::class, diff --git a/app/src/sharedTest/java/org/oppia/android/app/home/recentlyplayed/RecentlyPlayedFragmentTest.kt b/app/src/sharedTest/java/org/oppia/android/app/home/recentlyplayed/RecentlyPlayedFragmentTest.kt index 01c8c39c4b5..23b490f7698 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/home/recentlyplayed/RecentlyPlayedFragmentTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/home/recentlyplayed/RecentlyPlayedFragmentTest.kt @@ -121,7 +121,6 @@ import org.oppia.android.util.gcsresource.GcsResourceModule import org.oppia.android.util.locale.LocaleProdModule import org.oppia.android.util.logging.LoggerModule import org.oppia.android.util.logging.SyncStatusModule -import org.oppia.android.util.logging.firebase.FirebaseLogUploaderModule import org.oppia.android.util.networking.NetworkConnectionDebugUtilModule import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule import org.oppia.android.util.parser.html.HtmlParserEntityTypeModule @@ -1471,7 +1470,6 @@ class RecentlyPlayedFragmentTest { ExplorationProgressModule::class, ExplorationStorageTestModule::class, FakeOppiaClockModule::class, - FirebaseLogUploaderModule::class, FractionInputModule::class, GcsResourceModule::class, HintsAndSolutionConfigModule::class, diff --git a/app/src/sharedTest/java/org/oppia/android/app/home/topiclist/TopicSummaryViewModelTest.kt b/app/src/sharedTest/java/org/oppia/android/app/home/topiclist/TopicSummaryViewModelTest.kt index e2db4b8eed8..5c691eb7e3d 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/home/topiclist/TopicSummaryViewModelTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/home/topiclist/TopicSummaryViewModelTest.kt @@ -79,7 +79,6 @@ import org.oppia.android.util.gcsresource.GcsResourceModule import org.oppia.android.util.locale.LocaleProdModule import org.oppia.android.util.logging.LoggerModule import org.oppia.android.util.logging.SyncStatusModule -import org.oppia.android.util.logging.firebase.FirebaseLogUploaderModule import org.oppia.android.util.networking.NetworkConnectionDebugUtilModule import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule import org.oppia.android.util.parser.html.HtmlParserEntityTypeModule @@ -385,7 +384,6 @@ class TopicSummaryViewModelTest { ExplorationProgressModule::class, ExplorationStorageModule::class, FakeOppiaClockModule::class, - FirebaseLogUploaderModule::class, FractionInputModule::class, GcsResourceModule::class, GlideImageLoaderModule::class, diff --git a/app/src/sharedTest/java/org/oppia/android/app/mydownloads/MyDownloadsActivityTest.kt b/app/src/sharedTest/java/org/oppia/android/app/mydownloads/MyDownloadsActivityTest.kt index af7e1079a04..0a77b52d0a2 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/mydownloads/MyDownloadsActivityTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/mydownloads/MyDownloadsActivityTest.kt @@ -85,7 +85,6 @@ import org.oppia.android.util.locale.LocaleProdModule import org.oppia.android.util.logging.CurrentAppScreenNameIntentDecorator.extractCurrentAppScreenName import org.oppia.android.util.logging.LoggerModule import org.oppia.android.util.logging.SyncStatusModule -import org.oppia.android.util.logging.firebase.FirebaseLogUploaderModule import org.oppia.android.util.networking.NetworkConnectionDebugUtilModule import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule import org.oppia.android.util.parser.html.HtmlParserEntityTypeModule @@ -192,7 +191,6 @@ class MyDownloadsActivityTest { ExplorationProgressModule::class, ExplorationStorageModule::class, FakeOppiaClockModule::class, - FirebaseLogUploaderModule::class, FractionInputModule::class, GcsResourceModule::class, GlideImageLoaderModule::class, diff --git a/app/src/sharedTest/java/org/oppia/android/app/mydownloads/MyDownloadsFragmentTest.kt b/app/src/sharedTest/java/org/oppia/android/app/mydownloads/MyDownloadsFragmentTest.kt index 8d4570bf5e9..ada59f14bb9 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/mydownloads/MyDownloadsFragmentTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/mydownloads/MyDownloadsFragmentTest.kt @@ -84,7 +84,6 @@ import org.oppia.android.util.gcsresource.GcsResourceModule import org.oppia.android.util.locale.LocaleProdModule import org.oppia.android.util.logging.LoggerModule import org.oppia.android.util.logging.SyncStatusModule -import org.oppia.android.util.logging.firebase.FirebaseLogUploaderModule import org.oppia.android.util.networking.NetworkConnectionDebugUtilModule import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule import org.oppia.android.util.parser.html.HtmlParserEntityTypeModule @@ -233,7 +232,6 @@ class MyDownloadsFragmentTest { ExplorationProgressModule::class, ExplorationStorageModule::class, FakeOppiaClockModule::class, - FirebaseLogUploaderModule::class, FractionInputModule::class, GcsResourceModule::class, GlideImageLoaderModule::class, diff --git a/app/src/sharedTest/java/org/oppia/android/app/notice/BetaNoticeDialogFragmentTest.kt b/app/src/sharedTest/java/org/oppia/android/app/notice/BetaNoticeDialogFragmentTest.kt index b02448d967e..46e0d67cd1c 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/notice/BetaNoticeDialogFragmentTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/notice/BetaNoticeDialogFragmentTest.kt @@ -89,7 +89,6 @@ import org.oppia.android.util.gcsresource.GcsResourceModule import org.oppia.android.util.locale.LocaleProdModule import org.oppia.android.util.logging.LoggerModule import org.oppia.android.util.logging.SyncStatusModule -import org.oppia.android.util.logging.firebase.FirebaseLogUploaderModule import org.oppia.android.util.networking.NetworkConnectionDebugUtilModule import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule import org.oppia.android.util.parser.html.HtmlParserEntityTypeModule @@ -244,7 +243,6 @@ class BetaNoticeDialogFragmentTest { ExplorationProgressModule::class, ExplorationStorageModule::class, FakeOppiaClockModule::class, - FirebaseLogUploaderModule::class, FractionInputModule::class, GcsResourceModule::class, HintsAndSolutionConfigFastShowTestModule::class, diff --git a/app/src/sharedTest/java/org/oppia/android/app/notice/ForcedAppDeprecationNoticeDialogFragmentTest.kt b/app/src/sharedTest/java/org/oppia/android/app/notice/ForcedAppDeprecationNoticeDialogFragmentTest.kt index adb6c2a451e..3dc37a0513e 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/notice/ForcedAppDeprecationNoticeDialogFragmentTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/notice/ForcedAppDeprecationNoticeDialogFragmentTest.kt @@ -88,7 +88,6 @@ import org.oppia.android.util.gcsresource.GcsResourceModule import org.oppia.android.util.locale.LocaleProdModule import org.oppia.android.util.logging.LoggerModule import org.oppia.android.util.logging.SyncStatusModule -import org.oppia.android.util.logging.firebase.FirebaseLogUploaderModule import org.oppia.android.util.networking.NetworkConnectionDebugUtilModule import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule import org.oppia.android.util.parser.html.HtmlParserEntityTypeModule @@ -233,7 +232,6 @@ class ForcedAppDeprecationNoticeDialogFragmentTest { ExplorationProgressModule::class, ExplorationStorageModule::class, FakeOppiaClockModule::class, - FirebaseLogUploaderModule::class, FractionInputModule::class, GcsResourceModule::class, HintsAndSolutionConfigFastShowTestModule::class, diff --git a/app/src/sharedTest/java/org/oppia/android/app/notice/GeneralAvailabilityUpgradeNoticeDialogFragmentTest.kt b/app/src/sharedTest/java/org/oppia/android/app/notice/GeneralAvailabilityUpgradeNoticeDialogFragmentTest.kt index b19641778cd..938d687fff0 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/notice/GeneralAvailabilityUpgradeNoticeDialogFragmentTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/notice/GeneralAvailabilityUpgradeNoticeDialogFragmentTest.kt @@ -89,7 +89,6 @@ import org.oppia.android.util.gcsresource.GcsResourceModule import org.oppia.android.util.locale.LocaleProdModule import org.oppia.android.util.logging.LoggerModule import org.oppia.android.util.logging.SyncStatusModule -import org.oppia.android.util.logging.firebase.FirebaseLogUploaderModule import org.oppia.android.util.networking.NetworkConnectionDebugUtilModule import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule import org.oppia.android.util.parser.html.HtmlParserEntityTypeModule @@ -251,7 +250,6 @@ class GeneralAvailabilityUpgradeNoticeDialogFragmentTest { ExplorationProgressModule::class, ExplorationStorageModule::class, FakeOppiaClockModule::class, - FirebaseLogUploaderModule::class, FractionInputModule::class, GcsResourceModule::class, HintsAndSolutionConfigFastShowTestModule::class, diff --git a/app/src/sharedTest/java/org/oppia/android/app/notice/OptionalAppDeprecationNoticeDialogFragmentTest.kt b/app/src/sharedTest/java/org/oppia/android/app/notice/OptionalAppDeprecationNoticeDialogFragmentTest.kt index 6b35b453df0..a5a92916ac0 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/notice/OptionalAppDeprecationNoticeDialogFragmentTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/notice/OptionalAppDeprecationNoticeDialogFragmentTest.kt @@ -90,7 +90,6 @@ import org.oppia.android.util.gcsresource.GcsResourceModule import org.oppia.android.util.locale.LocaleProdModule import org.oppia.android.util.logging.LoggerModule import org.oppia.android.util.logging.SyncStatusModule -import org.oppia.android.util.logging.firebase.FirebaseLogUploaderModule import org.oppia.android.util.networking.NetworkConnectionDebugUtilModule import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule import org.oppia.android.util.parser.html.HtmlParserEntityTypeModule @@ -242,7 +241,6 @@ class OptionalAppDeprecationNoticeDialogFragmentTest { ExplorationProgressModule::class, ExplorationStorageModule::class, FakeOppiaClockModule::class, - FirebaseLogUploaderModule::class, FractionInputModule::class, GcsResourceModule::class, HintsAndSolutionConfigFastShowTestModule::class, diff --git a/app/src/sharedTest/java/org/oppia/android/app/notice/OsDeprecationNoticeDialogFragmentTest.kt b/app/src/sharedTest/java/org/oppia/android/app/notice/OsDeprecationNoticeDialogFragmentTest.kt index ecc88a6e6c7..b305de33389 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/notice/OsDeprecationNoticeDialogFragmentTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/notice/OsDeprecationNoticeDialogFragmentTest.kt @@ -89,7 +89,6 @@ import org.oppia.android.util.gcsresource.GcsResourceModule import org.oppia.android.util.locale.LocaleProdModule import org.oppia.android.util.logging.LoggerModule import org.oppia.android.util.logging.SyncStatusModule -import org.oppia.android.util.logging.firebase.FirebaseLogUploaderModule import org.oppia.android.util.networking.NetworkConnectionDebugUtilModule import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule import org.oppia.android.util.parser.html.HtmlParserEntityTypeModule @@ -224,7 +223,6 @@ class OsDeprecationNoticeDialogFragmentTest { ExplorationProgressModule::class, ExplorationStorageModule::class, FakeOppiaClockModule::class, - FirebaseLogUploaderModule::class, FractionInputModule::class, GcsResourceModule::class, HintsAndSolutionConfigFastShowTestModule::class, diff --git a/app/src/sharedTest/java/org/oppia/android/app/onboarding/AdminIntroActivityTest.kt b/app/src/sharedTest/java/org/oppia/android/app/onboarding/AdminIntroActivityTest.kt index 5a60ac2d377..b8a9db40fe0 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/onboarding/AdminIntroActivityTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/onboarding/AdminIntroActivityTest.kt @@ -80,7 +80,6 @@ import org.oppia.android.util.locale.LocaleProdModule import org.oppia.android.util.logging.CurrentAppScreenNameIntentDecorator.extractCurrentAppScreenName import org.oppia.android.util.logging.LoggerModule import org.oppia.android.util.logging.SyncStatusModule -import org.oppia.android.util.logging.firebase.FirebaseLogUploaderModule import org.oppia.android.util.networking.NetworkConnectionDebugUtilModule import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule import org.oppia.android.util.parser.html.HtmlParserEntityTypeModule @@ -166,7 +165,6 @@ class AdminIntroActivityTest { ExplorationProgressModule::class, ExplorationStorageModule::class, FakeOppiaClockModule::class, - FirebaseLogUploaderModule::class, FractionInputModule::class, GcsResourceModule::class, GlideImageLoaderModule::class, diff --git a/app/src/sharedTest/java/org/oppia/android/app/onboarding/AdminIntroFragmentTest.kt b/app/src/sharedTest/java/org/oppia/android/app/onboarding/AdminIntroFragmentTest.kt index 66d94ca7d0c..ecdf8671d41 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/onboarding/AdminIntroFragmentTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/onboarding/AdminIntroFragmentTest.kt @@ -93,7 +93,6 @@ import org.oppia.android.util.gcsresource.GcsResourceModule import org.oppia.android.util.locale.LocaleProdModule import org.oppia.android.util.logging.LoggerModule import org.oppia.android.util.logging.SyncStatusModule -import org.oppia.android.util.logging.firebase.FirebaseLogUploaderModule import org.oppia.android.util.networking.NetworkConnectionDebugUtilModule import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule import org.oppia.android.util.parser.html.HtmlParserEntityTypeModule @@ -285,7 +284,6 @@ class AdminIntroFragmentTest { ExplorationProgressModule::class, ExplorationStorageModule::class, FakeOppiaClockModule::class, - FirebaseLogUploaderModule::class, FractionInputModule::class, GcsResourceModule::class, GlideImageLoaderModule::class, diff --git a/app/src/sharedTest/java/org/oppia/android/app/onboarding/CreateProfileActivityTest.kt b/app/src/sharedTest/java/org/oppia/android/app/onboarding/CreateProfileActivityTest.kt index 60b60a20cc3..f2fda85a0f9 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/onboarding/CreateProfileActivityTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/onboarding/CreateProfileActivityTest.kt @@ -80,7 +80,6 @@ import org.oppia.android.util.locale.LocaleProdModule import org.oppia.android.util.logging.CurrentAppScreenNameIntentDecorator.extractCurrentAppScreenName import org.oppia.android.util.logging.LoggerModule import org.oppia.android.util.logging.SyncStatusModule -import org.oppia.android.util.logging.firebase.FirebaseLogUploaderModule import org.oppia.android.util.networking.NetworkConnectionDebugUtilModule import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule import org.oppia.android.util.parser.html.HtmlParserEntityTypeModule @@ -182,7 +181,6 @@ class CreateProfileActivityTest { ExplorationProgressModule::class, ExplorationStorageModule::class, FakeOppiaClockModule::class, - FirebaseLogUploaderModule::class, FractionInputModule::class, GcsResourceModule::class, GlideImageLoaderModule::class, diff --git a/app/src/sharedTest/java/org/oppia/android/app/onboarding/CreateProfileFragmentTest.kt b/app/src/sharedTest/java/org/oppia/android/app/onboarding/CreateProfileFragmentTest.kt index 15804de15a4..a797c197e1d 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/onboarding/CreateProfileFragmentTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/onboarding/CreateProfileFragmentTest.kt @@ -117,7 +117,6 @@ import org.oppia.android.util.gcsresource.GcsResourceModule import org.oppia.android.util.locale.LocaleProdModule import org.oppia.android.util.logging.LoggerModule import org.oppia.android.util.logging.SyncStatusModule -import org.oppia.android.util.logging.firebase.FirebaseLogUploaderModule import org.oppia.android.util.networking.NetworkConnectionDebugUtilModule import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule import org.oppia.android.util.parser.html.HtmlParserEntityTypeModule @@ -762,7 +761,6 @@ class CreateProfileFragmentTest { ExplorationProgressModule::class, ExplorationStorageModule::class, FakeOppiaClockModule::class, - FirebaseLogUploaderModule::class, FractionInputModule::class, GcsResourceModule::class, HintsAndSolutionConfigModule::class, diff --git a/app/src/sharedTest/java/org/oppia/android/app/onboarding/IntroActivityTest.kt b/app/src/sharedTest/java/org/oppia/android/app/onboarding/IntroActivityTest.kt index 73b63bb82f1..743be67f96a 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/onboarding/IntroActivityTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/onboarding/IntroActivityTest.kt @@ -80,7 +80,6 @@ import org.oppia.android.util.locale.LocaleProdModule import org.oppia.android.util.logging.CurrentAppScreenNameIntentDecorator.extractCurrentAppScreenName import org.oppia.android.util.logging.LoggerModule import org.oppia.android.util.logging.SyncStatusModule -import org.oppia.android.util.logging.firebase.FirebaseLogUploaderModule import org.oppia.android.util.networking.NetworkConnectionDebugUtilModule import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule import org.oppia.android.util.parser.html.HtmlParserEntityTypeModule @@ -183,7 +182,6 @@ class IntroActivityTest { ExplorationProgressModule::class, ExplorationStorageModule::class, FakeOppiaClockModule::class, - FirebaseLogUploaderModule::class, FractionInputModule::class, GcsResourceModule::class, GlideImageLoaderModule::class, diff --git a/app/src/sharedTest/java/org/oppia/android/app/onboarding/IntroFragmentTest.kt b/app/src/sharedTest/java/org/oppia/android/app/onboarding/IntroFragmentTest.kt index a5795ec1cdd..74cf1de21fd 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/onboarding/IntroFragmentTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/onboarding/IntroFragmentTest.kt @@ -100,7 +100,6 @@ import org.oppia.android.util.gcsresource.GcsResourceModule import org.oppia.android.util.locale.LocaleProdModule import org.oppia.android.util.logging.LoggerModule import org.oppia.android.util.logging.SyncStatusModule -import org.oppia.android.util.logging.firebase.FirebaseLogUploaderModule import org.oppia.android.util.networking.NetworkConnectionDebugUtilModule import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule import org.oppia.android.util.parser.html.HtmlParserEntityTypeModule @@ -299,7 +298,6 @@ class IntroFragmentTest { ExplorationProgressModule::class, ExplorationStorageModule::class, FakeOppiaClockModule::class, - FirebaseLogUploaderModule::class, FractionInputModule::class, GcsResourceModule::class, GlideImageLoaderModule::class, diff --git a/app/src/sharedTest/java/org/oppia/android/app/onboarding/OnboardingActivityTest.kt b/app/src/sharedTest/java/org/oppia/android/app/onboarding/OnboardingActivityTest.kt index fd87c316498..93968b592f4 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/onboarding/OnboardingActivityTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/onboarding/OnboardingActivityTest.kt @@ -76,7 +76,6 @@ import org.oppia.android.util.locale.LocaleProdModule import org.oppia.android.util.logging.CurrentAppScreenNameIntentDecorator.extractCurrentAppScreenName import org.oppia.android.util.logging.LoggerModule import org.oppia.android.util.logging.SyncStatusModule -import org.oppia.android.util.logging.firebase.FirebaseLogUploaderModule import org.oppia.android.util.networking.NetworkConnectionDebugUtilModule import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule import org.oppia.android.util.parser.html.HtmlParserEntityTypeModule @@ -158,7 +157,6 @@ class OnboardingActivityTest { ExplorationProgressModule::class, ExplorationStorageModule::class, FakeOppiaClockModule::class, - FirebaseLogUploaderModule::class, FractionInputModule::class, GcsResourceModule::class, GlideImageLoaderModule::class, diff --git a/app/src/sharedTest/java/org/oppia/android/app/onboarding/OnboardingFragmentTest.kt b/app/src/sharedTest/java/org/oppia/android/app/onboarding/OnboardingFragmentTest.kt index 4da1a138a40..ccdb361a448 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/onboarding/OnboardingFragmentTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/onboarding/OnboardingFragmentTest.kt @@ -113,7 +113,6 @@ import org.oppia.android.util.gcsresource.GcsResourceModule import org.oppia.android.util.locale.LocaleProdModule import org.oppia.android.util.logging.LoggerModule import org.oppia.android.util.logging.SyncStatusModule -import org.oppia.android.util.logging.firebase.FirebaseLogUploaderModule import org.oppia.android.util.networking.NetworkConnectionDebugUtilModule import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule import org.oppia.android.util.parser.html.HtmlParserEntityTypeModule @@ -1152,7 +1151,6 @@ class OnboardingFragmentTest { ExplorationProgressModule::class, ExplorationStorageModule::class, FakeOppiaClockModule::class, - FirebaseLogUploaderModule::class, FractionInputModule::class, GcsResourceModule::class, GlideImageLoaderModule::class, diff --git a/app/src/sharedTest/java/org/oppia/android/app/onboarding/OnboardingProfileTypeActivityTest.kt b/app/src/sharedTest/java/org/oppia/android/app/onboarding/OnboardingProfileTypeActivityTest.kt index 1fa9ac0e3a8..39ed0f324f1 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/onboarding/OnboardingProfileTypeActivityTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/onboarding/OnboardingProfileTypeActivityTest.kt @@ -78,7 +78,6 @@ import org.oppia.android.util.locale.LocaleProdModule import org.oppia.android.util.logging.CurrentAppScreenNameIntentDecorator.extractCurrentAppScreenName import org.oppia.android.util.logging.LoggerModule import org.oppia.android.util.logging.SyncStatusModule -import org.oppia.android.util.logging.firebase.FirebaseLogUploaderModule import org.oppia.android.util.networking.NetworkConnectionDebugUtilModule import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule import org.oppia.android.util.parser.html.HtmlParserEntityTypeModule @@ -177,7 +176,6 @@ class OnboardingProfileTypeActivityTest { ExplorationProgressModule::class, ExplorationStorageModule::class, FakeOppiaClockModule::class, - FirebaseLogUploaderModule::class, FractionInputModule::class, GcsResourceModule::class, GlideImageLoaderModule::class, diff --git a/app/src/sharedTest/java/org/oppia/android/app/onboarding/OnboardingProfileTypeFragmentTest.kt b/app/src/sharedTest/java/org/oppia/android/app/onboarding/OnboardingProfileTypeFragmentTest.kt index 875006715fb..0bcc58d4760 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/onboarding/OnboardingProfileTypeFragmentTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/onboarding/OnboardingProfileTypeFragmentTest.kt @@ -95,7 +95,6 @@ import org.oppia.android.util.gcsresource.GcsResourceModule import org.oppia.android.util.locale.LocaleProdModule import org.oppia.android.util.logging.LoggerModule import org.oppia.android.util.logging.SyncStatusModule -import org.oppia.android.util.logging.firebase.FirebaseLogUploaderModule import org.oppia.android.util.networking.NetworkConnectionDebugUtilModule import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule import org.oppia.android.util.parser.html.HtmlParserEntityTypeModule @@ -370,7 +369,6 @@ class OnboardingProfileTypeFragmentTest { ExplorationProgressModule::class, ExplorationStorageModule::class, FakeOppiaClockModule::class, - FirebaseLogUploaderModule::class, FractionInputModule::class, GcsResourceModule::class, GlideImageLoaderModule::class, diff --git a/app/src/sharedTest/java/org/oppia/android/app/ongoingtopiclist/OngoingTopicListActivityTest.kt b/app/src/sharedTest/java/org/oppia/android/app/ongoingtopiclist/OngoingTopicListActivityTest.kt index 1dc411ed821..fb8a355a154 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/ongoingtopiclist/OngoingTopicListActivityTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/ongoingtopiclist/OngoingTopicListActivityTest.kt @@ -100,7 +100,6 @@ import org.oppia.android.util.locale.LocaleProdModule import org.oppia.android.util.logging.CurrentAppScreenNameIntentDecorator.extractCurrentAppScreenName import org.oppia.android.util.logging.LoggerModule import org.oppia.android.util.logging.SyncStatusModule -import org.oppia.android.util.logging.firebase.FirebaseLogUploaderModule import org.oppia.android.util.networking.NetworkConnectionDebugUtilModule import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule import org.oppia.android.util.parser.html.HtmlParserEntityTypeModule @@ -470,7 +469,6 @@ class OngoingTopicListActivityTest { ExplorationProgressModule::class, ExplorationStorageModule::class, FakeOppiaClockModule::class, - FirebaseLogUploaderModule::class, FractionInputModule::class, GcsResourceModule::class, GlideImageLoaderModule::class, diff --git a/app/src/sharedTest/java/org/oppia/android/app/options/AppLanguageActivityTest.kt b/app/src/sharedTest/java/org/oppia/android/app/options/AppLanguageActivityTest.kt index 0a3d561327b..ee0dc92371a 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/options/AppLanguageActivityTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/options/AppLanguageActivityTest.kt @@ -77,7 +77,6 @@ import org.oppia.android.util.locale.LocaleProdModule import org.oppia.android.util.logging.CurrentAppScreenNameIntentDecorator.extractCurrentAppScreenName import org.oppia.android.util.logging.LoggerModule import org.oppia.android.util.logging.SyncStatusModule -import org.oppia.android.util.logging.firebase.FirebaseLogUploaderModule import org.oppia.android.util.networking.NetworkConnectionDebugUtilModule import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule import org.oppia.android.util.parser.html.HtmlParserEntityTypeModule @@ -171,7 +170,6 @@ class AppLanguageActivityTest { ExplorationProgressModule::class, ExplorationStorageModule::class, FakeOppiaClockModule::class, - FirebaseLogUploaderModule::class, FractionInputModule::class, GcsResourceModule::class, GlideImageLoaderModule::class, diff --git a/app/src/sharedTest/java/org/oppia/android/app/options/AppLanguageFragmentTest.kt b/app/src/sharedTest/java/org/oppia/android/app/options/AppLanguageFragmentTest.kt index 51fc00bbedd..24874a94b89 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/options/AppLanguageFragmentTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/options/AppLanguageFragmentTest.kt @@ -103,7 +103,6 @@ import org.oppia.android.util.gcsresource.GcsResourceModule import org.oppia.android.util.locale.LocaleProdModule import org.oppia.android.util.logging.LoggerModule import org.oppia.android.util.logging.SyncStatusModule -import org.oppia.android.util.logging.firebase.FirebaseLogUploaderModule import org.oppia.android.util.networking.NetworkConnectionDebugUtilModule import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule import org.oppia.android.util.parser.html.HtmlParserEntityTypeModule @@ -665,7 +664,6 @@ class AppLanguageFragmentTest { ExplorationProgressModule::class, ExplorationStorageModule::class, FakeOppiaClockModule::class, - FirebaseLogUploaderModule::class, FractionInputModule::class, GcsResourceModule::class, GlideImageLoaderModule::class, diff --git a/app/src/sharedTest/java/org/oppia/android/app/options/AudioLanguageActivityTest.kt b/app/src/sharedTest/java/org/oppia/android/app/options/AudioLanguageActivityTest.kt index 92d1623b5f2..1b6974a628d 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/options/AudioLanguageActivityTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/options/AudioLanguageActivityTest.kt @@ -79,7 +79,6 @@ import org.oppia.android.util.locale.LocaleProdModule import org.oppia.android.util.logging.CurrentAppScreenNameIntentDecorator.extractCurrentAppScreenName import org.oppia.android.util.logging.LoggerModule import org.oppia.android.util.logging.SyncStatusModule -import org.oppia.android.util.logging.firebase.FirebaseLogUploaderModule import org.oppia.android.util.networking.NetworkConnectionDebugUtilModule import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule import org.oppia.android.util.parser.html.HtmlParserEntityTypeModule @@ -177,7 +176,6 @@ class AudioLanguageActivityTest { ExplorationProgressModule::class, ExplorationStorageModule::class, FakeOppiaClockModule::class, - FirebaseLogUploaderModule::class, FractionInputModule::class, GcsResourceModule::class, GlideImageLoaderModule::class, diff --git a/app/src/sharedTest/java/org/oppia/android/app/options/AudioLanguageFragmentTest.kt b/app/src/sharedTest/java/org/oppia/android/app/options/AudioLanguageFragmentTest.kt index b0e60ae957c..9805d53081f 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/options/AudioLanguageFragmentTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/options/AudioLanguageFragmentTest.kt @@ -115,7 +115,6 @@ import org.oppia.android.util.gcsresource.GcsResourceModule import org.oppia.android.util.locale.LocaleProdModule import org.oppia.android.util.logging.LoggerModule import org.oppia.android.util.logging.SyncStatusModule -import org.oppia.android.util.logging.firebase.FirebaseLogUploaderModule import org.oppia.android.util.networking.NetworkConnectionDebugUtilModule import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule import org.oppia.android.util.parser.html.HtmlParserEntityTypeModule @@ -814,7 +813,6 @@ class AudioLanguageFragmentTest { ExplorationProgressModule::class, ExplorationStorageModule::class, FakeOppiaClockModule::class, - FirebaseLogUploaderModule::class, FractionInputModule::class, GcsResourceModule::class, GlideImageLoaderModule::class, diff --git a/app/src/sharedTest/java/org/oppia/android/app/options/OptionsActivityTest.kt b/app/src/sharedTest/java/org/oppia/android/app/options/OptionsActivityTest.kt index 0930f010e63..da9b95a0407 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/options/OptionsActivityTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/options/OptionsActivityTest.kt @@ -77,7 +77,6 @@ import org.oppia.android.util.locale.LocaleProdModule import org.oppia.android.util.logging.CurrentAppScreenNameIntentDecorator.extractCurrentAppScreenName import org.oppia.android.util.logging.LoggerModule import org.oppia.android.util.logging.SyncStatusModule -import org.oppia.android.util.logging.firebase.FirebaseLogUploaderModule import org.oppia.android.util.networking.NetworkConnectionDebugUtilModule import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule import org.oppia.android.util.parser.html.HtmlParserEntityTypeModule @@ -167,7 +166,6 @@ class OptionsActivityTest { ExplorationProgressModule::class, ExplorationStorageModule::class, FakeOppiaClockModule::class, - FirebaseLogUploaderModule::class, FractionInputModule::class, GcsResourceModule::class, GlideImageLoaderModule::class, diff --git a/app/src/sharedTest/java/org/oppia/android/app/options/OptionsFragmentTest.kt b/app/src/sharedTest/java/org/oppia/android/app/options/OptionsFragmentTest.kt index a3c0766b584..2cab0e10d1f 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/options/OptionsFragmentTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/options/OptionsFragmentTest.kt @@ -107,7 +107,6 @@ import org.oppia.android.util.gcsresource.GcsResourceModule import org.oppia.android.util.locale.LocaleProdModule import org.oppia.android.util.logging.LoggerModule import org.oppia.android.util.logging.SyncStatusModule -import org.oppia.android.util.logging.firebase.FirebaseLogUploaderModule import org.oppia.android.util.networking.NetworkConnectionDebugUtilModule import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule import org.oppia.android.util.parser.html.HtmlParserEntityTypeModule @@ -696,7 +695,6 @@ class OptionsFragmentTest { ExplorationProgressModule::class, ExplorationStorageModule::class, FakeOppiaClockModule::class, - FirebaseLogUploaderModule::class, FractionInputModule::class, GcsResourceModule::class, GlideImageLoaderModule::class, diff --git a/app/src/sharedTest/java/org/oppia/android/app/options/ReadingTextSizeActivityTest.kt b/app/src/sharedTest/java/org/oppia/android/app/options/ReadingTextSizeActivityTest.kt index 1f61771f14c..76ba2dcc2ac 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/options/ReadingTextSizeActivityTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/options/ReadingTextSizeActivityTest.kt @@ -77,7 +77,6 @@ import org.oppia.android.util.locale.LocaleProdModule import org.oppia.android.util.logging.CurrentAppScreenNameIntentDecorator.extractCurrentAppScreenName import org.oppia.android.util.logging.LoggerModule import org.oppia.android.util.logging.SyncStatusModule -import org.oppia.android.util.logging.firebase.FirebaseLogUploaderModule import org.oppia.android.util.networking.NetworkConnectionDebugUtilModule import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule import org.oppia.android.util.parser.html.HtmlParserEntityTypeModule @@ -166,7 +165,6 @@ class ReadingTextSizeActivityTest { ExplorationProgressModule::class, ExplorationStorageModule::class, FakeOppiaClockModule::class, - FirebaseLogUploaderModule::class, FractionInputModule::class, GcsResourceModule::class, GlideImageLoaderModule::class, diff --git a/app/src/sharedTest/java/org/oppia/android/app/options/ReadingTextSizeFragmentTest.kt b/app/src/sharedTest/java/org/oppia/android/app/options/ReadingTextSizeFragmentTest.kt index d15b6e9b5b5..f6351e88e19 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/options/ReadingTextSizeFragmentTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/options/ReadingTextSizeFragmentTest.kt @@ -93,7 +93,6 @@ import org.oppia.android.util.gcsresource.GcsResourceModule import org.oppia.android.util.locale.LocaleProdModule import org.oppia.android.util.logging.LoggerModule import org.oppia.android.util.logging.SyncStatusModule -import org.oppia.android.util.logging.firebase.FirebaseLogUploaderModule import org.oppia.android.util.networking.NetworkConnectionDebugUtilModule import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule import org.oppia.android.util.parser.html.HtmlParserEntityTypeModule @@ -346,7 +345,6 @@ class ReadingTextSizeFragmentTest { ExplorationProgressModule::class, ExplorationStorageModule::class, FakeOppiaClockModule::class, - FirebaseLogUploaderModule::class, FractionInputModule::class, GcsResourceModule::class, GlideImageLoaderModule::class, diff --git a/app/src/sharedTest/java/org/oppia/android/app/player/audio/AudioFragmentTest.kt b/app/src/sharedTest/java/org/oppia/android/app/player/audio/AudioFragmentTest.kt index 6dc8acd3ef1..953c62f7207 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/player/audio/AudioFragmentTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/player/audio/AudioFragmentTest.kt @@ -109,7 +109,6 @@ import org.oppia.android.util.gcsresource.GcsResourceModule import org.oppia.android.util.locale.LocaleProdModule import org.oppia.android.util.logging.LoggerModule import org.oppia.android.util.logging.SyncStatusModule -import org.oppia.android.util.logging.firebase.FirebaseLogUploaderModule import org.oppia.android.util.networking.NetworkConnectionDebugUtil import org.oppia.android.util.networking.NetworkConnectionDebugUtilModule import org.oppia.android.util.networking.NetworkConnectionUtil.ProdConnectionStatus @@ -678,7 +677,6 @@ class AudioFragmentTest { ExplorationProgressModule::class, ExplorationStorageModule::class, FakeOppiaClockModule::class, - FirebaseLogUploaderModule::class, FractionInputModule::class, GcsResourceModule::class, GlideImageLoaderModule::class, diff --git a/app/src/sharedTest/java/org/oppia/android/app/player/exploration/BUILD.bazel b/app/src/sharedTest/java/org/oppia/android/app/player/exploration/BUILD.bazel index 58c2462df1b..e5b0ff0e737 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/player/exploration/BUILD.bazel +++ b/app/src/sharedTest/java/org/oppia/android/app/player/exploration/BUILD.bazel @@ -93,7 +93,6 @@ oppia_android_test( "//third_party:androidx_test_espresso_espresso-intents", "//third_party:androidx_test_ext_junit", "//third_party:androidx_test_runner", - "//third_party:androidx_work_work-testing", "//third_party:com_github_bumptech_glide_mocks", "//third_party:com_google_truth_truth", "//third_party:junit_junit", diff --git a/app/src/sharedTest/java/org/oppia/android/app/player/exploration/BottomSheetOptionsMenuTest.kt b/app/src/sharedTest/java/org/oppia/android/app/player/exploration/BottomSheetOptionsMenuTest.kt index 20341167cb9..a76cd26941f 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/player/exploration/BottomSheetOptionsMenuTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/player/exploration/BottomSheetOptionsMenuTest.kt @@ -85,7 +85,6 @@ import org.oppia.android.util.gcsresource.GcsResourceModule import org.oppia.android.util.locale.LocaleProdModule import org.oppia.android.util.logging.LoggerModule import org.oppia.android.util.logging.SyncStatusModule -import org.oppia.android.util.logging.firebase.FirebaseLogUploaderModule import org.oppia.android.util.networking.NetworkConnectionDebugUtilModule import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule import org.oppia.android.util.parser.html.HtmlParserEntityTypeModule @@ -187,7 +186,6 @@ class BottomSheetOptionsMenuTest { ExplorationProgressModule::class, ExplorationStorageModule::class, FakeOppiaClockModule::class, - FirebaseLogUploaderModule::class, FractionInputModule::class, GcsResourceModule::class, HintsAndSolutionConfigFastShowTestModule::class, diff --git a/app/src/sharedTest/java/org/oppia/android/app/player/exploration/ExplorationActivityTest.kt b/app/src/sharedTest/java/org/oppia/android/app/player/exploration/ExplorationActivityTest.kt index f132bdbaa34..b0ce8bd9c80 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/player/exploration/ExplorationActivityTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/player/exploration/ExplorationActivityTest.kt @@ -166,7 +166,6 @@ import org.oppia.android.util.locale.LocaleProdModule import org.oppia.android.util.logging.CurrentAppScreenNameIntentDecorator.extractCurrentAppScreenName import org.oppia.android.util.logging.LoggerModule import org.oppia.android.util.logging.SyncStatusModule -import org.oppia.android.util.logging.firebase.FirebaseLogUploaderModule import org.oppia.android.util.networking.NetworkConnectionDebugUtil import org.oppia.android.util.networking.NetworkConnectionDebugUtilModule import org.oppia.android.util.networking.NetworkConnectionUtil.ProdConnectionStatus @@ -2585,7 +2584,6 @@ class ExplorationActivityTest { ExplorationProgressModule::class, ExplorationStorageTestModule::class, FakeOppiaClockModule::class, - FirebaseLogUploaderModule::class, FractionInputModule::class, GcsResourceModule::class, GlideImageLoaderModule::class, diff --git a/app/src/sharedTest/java/org/oppia/android/app/player/state/ImageRegionSelectionInteractionViewTest.kt b/app/src/sharedTest/java/org/oppia/android/app/player/state/ImageRegionSelectionInteractionViewTest.kt index 6ab065641eb..46ebad73dc4 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/player/state/ImageRegionSelectionInteractionViewTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/player/state/ImageRegionSelectionInteractionViewTest.kt @@ -105,7 +105,6 @@ import org.oppia.android.util.gcsresource.GcsResourceModule import org.oppia.android.util.locale.LocaleProdModule import org.oppia.android.util.logging.LoggerModule import org.oppia.android.util.logging.SyncStatusModule -import org.oppia.android.util.logging.firebase.FirebaseLogUploaderModule import org.oppia.android.util.networking.NetworkConnectionDebugUtilModule import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule import org.oppia.android.util.parser.html.HtmlParserEntityTypeModule @@ -465,7 +464,6 @@ class ImageRegionSelectionInteractionViewTest { ExplorationProgressModule::class, ExplorationStorageModule::class, FakeOppiaClockModule::class, - FirebaseLogUploaderModule::class, FractionInputModule::class, GcsResourceModule::class, HintsAndSolutionConfigModule::class, diff --git a/app/src/sharedTest/java/org/oppia/android/app/player/state/StateFragmentTest.kt b/app/src/sharedTest/java/org/oppia/android/app/player/state/StateFragmentTest.kt index a897e3a3a95..0021e15d56f 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/player/state/StateFragmentTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/player/state/StateFragmentTest.kt @@ -191,7 +191,6 @@ import org.oppia.android.util.gcsresource.GcsResourceModule import org.oppia.android.util.locale.LocaleProdModule import org.oppia.android.util.logging.LoggerModule import org.oppia.android.util.logging.SyncStatusModule -import org.oppia.android.util.logging.firebase.FirebaseLogUploaderModule import org.oppia.android.util.networking.NetworkConnectionDebugUtilModule import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule import org.oppia.android.util.parser.html.HtmlParserEntityTypeModule @@ -7125,7 +7124,6 @@ class StateFragmentTest { ExplorationProgressModule::class, ExplorationStorageModule::class, FakeOppiaClockModule::class, - FirebaseLogUploaderModule::class, FractionInputModule::class, GcsResourceModule::class, HintsAndSolutionConfigFastShowTestModule::class, diff --git a/app/src/sharedTest/java/org/oppia/android/app/policies/PoliciesActivityTest.kt b/app/src/sharedTest/java/org/oppia/android/app/policies/PoliciesActivityTest.kt index 5ffc91b2d04..d9c8b90022a 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/policies/PoliciesActivityTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/policies/PoliciesActivityTest.kt @@ -96,7 +96,6 @@ import org.oppia.android.util.locale.LocaleProdModule import org.oppia.android.util.logging.CurrentAppScreenNameIntentDecorator.extractCurrentAppScreenName import org.oppia.android.util.logging.LoggerModule import org.oppia.android.util.logging.SyncStatusModule -import org.oppia.android.util.logging.firebase.FirebaseLogUploaderModule import org.oppia.android.util.networking.NetworkConnectionDebugUtilModule import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule import org.oppia.android.util.parser.html.HtmlParser @@ -274,7 +273,6 @@ class PoliciesActivityTest { ExplorationProgressModule::class, ExplorationStorageModule::class, FakeOppiaClockModule::class, - FirebaseLogUploaderModule::class, FractionInputModule::class, GcsResourceModule::class, HintsAndSolutionConfigModule::class, diff --git a/app/src/sharedTest/java/org/oppia/android/app/policies/PoliciesFragmentTest.kt b/app/src/sharedTest/java/org/oppia/android/app/policies/PoliciesFragmentTest.kt index ac08dc1b0c9..29cd2d7303d 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/policies/PoliciesFragmentTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/policies/PoliciesFragmentTest.kt @@ -108,7 +108,6 @@ import org.oppia.android.util.gcsresource.GcsResourceModule import org.oppia.android.util.locale.LocaleProdModule import org.oppia.android.util.logging.LoggerModule import org.oppia.android.util.logging.SyncStatusModule -import org.oppia.android.util.logging.firebase.FirebaseLogUploaderModule import org.oppia.android.util.networking.NetworkConnectionDebugUtilModule import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule import org.oppia.android.util.parser.html.HtmlParser @@ -348,7 +347,6 @@ class PoliciesFragmentTest { ExplorationProgressModule::class, ExplorationStorageModule::class, FakeOppiaClockModule::class, - FirebaseLogUploaderModule::class, FractionInputModule::class, GcsResourceModule::class, HintsAndSolutionConfigModule::class, diff --git a/app/src/sharedTest/java/org/oppia/android/app/profile/AddProfileActivityTest.kt b/app/src/sharedTest/java/org/oppia/android/app/profile/AddProfileActivityTest.kt index 79239f8b3fe..5a72d9cd233 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/profile/AddProfileActivityTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/profile/AddProfileActivityTest.kt @@ -117,7 +117,6 @@ import org.oppia.android.util.locale.LocaleProdModule import org.oppia.android.util.logging.CurrentAppScreenNameIntentDecorator.extractCurrentAppScreenName import org.oppia.android.util.logging.LoggerModule import org.oppia.android.util.logging.SyncStatusModule -import org.oppia.android.util.logging.firebase.FirebaseLogUploaderModule import org.oppia.android.util.networking.NetworkConnectionDebugUtilModule import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule import org.oppia.android.util.parser.html.HtmlParserEntityTypeModule @@ -1786,7 +1785,6 @@ class AddProfileActivityTest { ExplorationProgressModule::class, ExplorationStorageModule::class, FakeOppiaClockModule::class, - FirebaseLogUploaderModule::class, FractionInputModule::class, GcsResourceModule::class, GlideImageLoaderModule::class, diff --git a/app/src/sharedTest/java/org/oppia/android/app/profile/AdminAuthActivityTest.kt b/app/src/sharedTest/java/org/oppia/android/app/profile/AdminAuthActivityTest.kt index 363c96c24aa..c3ea2c71f42 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/profile/AdminAuthActivityTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/profile/AdminAuthActivityTest.kt @@ -99,7 +99,6 @@ import org.oppia.android.util.locale.LocaleProdModule import org.oppia.android.util.logging.CurrentAppScreenNameIntentDecorator.extractCurrentAppScreenName import org.oppia.android.util.logging.LoggerModule import org.oppia.android.util.logging.SyncStatusModule -import org.oppia.android.util.logging.firebase.FirebaseLogUploaderModule import org.oppia.android.util.networking.NetworkConnectionDebugUtilModule import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule import org.oppia.android.util.parser.html.HtmlParserEntityTypeModule @@ -691,7 +690,6 @@ class AdminAuthActivityTest { ExplorationProgressModule::class, ExplorationStorageModule::class, FakeOppiaClockModule::class, - FirebaseLogUploaderModule::class, FractionInputModule::class, GcsResourceModule::class, GlideImageLoaderModule::class, diff --git a/app/src/sharedTest/java/org/oppia/android/app/profile/AdminPinActivityTest.kt b/app/src/sharedTest/java/org/oppia/android/app/profile/AdminPinActivityTest.kt index 5ce1cf1a453..80a28b3ad21 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/profile/AdminPinActivityTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/profile/AdminPinActivityTest.kt @@ -109,7 +109,6 @@ import org.oppia.android.util.locale.LocaleProdModule import org.oppia.android.util.logging.CurrentAppScreenNameIntentDecorator.extractCurrentAppScreenName import org.oppia.android.util.logging.LoggerModule import org.oppia.android.util.logging.SyncStatusModule -import org.oppia.android.util.logging.firebase.FirebaseLogUploaderModule import org.oppia.android.util.networking.NetworkConnectionDebugUtilModule import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule import org.oppia.android.util.parser.html.HtmlParserEntityTypeModule @@ -1091,7 +1090,6 @@ class AdminPinActivityTest { ExplorationProgressModule::class, ExplorationStorageModule::class, FakeOppiaClockModule::class, - FirebaseLogUploaderModule::class, FractionInputModule::class, GcsResourceModule::class, GlideImageLoaderModule::class, diff --git a/app/src/sharedTest/java/org/oppia/android/app/profile/PinPasswordActivityTest.kt b/app/src/sharedTest/java/org/oppia/android/app/profile/PinPasswordActivityTest.kt index 2abd6eb051a..7f0fc3f90e9 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/profile/PinPasswordActivityTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/profile/PinPasswordActivityTest.kt @@ -110,7 +110,6 @@ import org.oppia.android.util.locale.LocaleProdModule import org.oppia.android.util.logging.CurrentAppScreenNameIntentDecorator.extractCurrentAppScreenName import org.oppia.android.util.logging.LoggerModule import org.oppia.android.util.logging.SyncStatusModule -import org.oppia.android.util.logging.firebase.FirebaseLogUploaderModule import org.oppia.android.util.networking.NetworkConnectionDebugUtilModule import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule import org.oppia.android.util.parser.html.HtmlParserEntityTypeModule @@ -1488,7 +1487,6 @@ class PinPasswordActivityTest { ExplorationProgressModule::class, ExplorationStorageModule::class, FakeOppiaClockModule::class, - FirebaseLogUploaderModule::class, FractionInputModule::class, GcsResourceModule::class, GlideImageLoaderModule::class, diff --git a/app/src/sharedTest/java/org/oppia/android/app/profile/ProfileChooserActivityTest.kt b/app/src/sharedTest/java/org/oppia/android/app/profile/ProfileChooserActivityTest.kt index 600e094ddfd..0e4c2055c35 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/profile/ProfileChooserActivityTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/profile/ProfileChooserActivityTest.kt @@ -77,7 +77,6 @@ import org.oppia.android.util.locale.LocaleProdModule import org.oppia.android.util.logging.CurrentAppScreenNameIntentDecorator.extractCurrentAppScreenName import org.oppia.android.util.logging.LoggerModule import org.oppia.android.util.logging.SyncStatusModule -import org.oppia.android.util.logging.firebase.FirebaseLogUploaderModule import org.oppia.android.util.networking.NetworkConnectionDebugUtilModule import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule import org.oppia.android.util.parser.html.HtmlParserEntityTypeModule @@ -160,7 +159,6 @@ class ProfileChooserActivityTest { ExplorationProgressModule::class, ExplorationStorageModule::class, FakeOppiaClockModule::class, - FirebaseLogUploaderModule::class, FractionInputModule::class, GcsResourceModule::class, GlideImageLoaderModule::class, diff --git a/app/src/sharedTest/java/org/oppia/android/app/profile/ProfileChooserFragmentTest.kt b/app/src/sharedTest/java/org/oppia/android/app/profile/ProfileChooserFragmentTest.kt index 7787824706d..4db77484b7b 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/profile/ProfileChooserFragmentTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/profile/ProfileChooserFragmentTest.kt @@ -116,7 +116,6 @@ import org.oppia.android.util.gcsresource.GcsResourceModule import org.oppia.android.util.locale.LocaleProdModule import org.oppia.android.util.logging.LoggerModule import org.oppia.android.util.logging.SyncStatusModule -import org.oppia.android.util.logging.firebase.FirebaseLogUploaderModule import org.oppia.android.util.networking.NetworkConnectionDebugUtilModule import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule import org.oppia.android.util.parser.html.HtmlParserEntityTypeModule @@ -1696,7 +1695,6 @@ class ProfileChooserFragmentTest { ExplorationProgressModule::class, ExplorationStorageModule::class, FakeOppiaClockModule::class, - FirebaseLogUploaderModule::class, FractionInputModule::class, GcsResourceModule::class, GlideImageLoaderModule::class, diff --git a/app/src/sharedTest/java/org/oppia/android/app/profile/ProfileLoginActivityTest.kt b/app/src/sharedTest/java/org/oppia/android/app/profile/ProfileLoginActivityTest.kt index e404b73e002..c765c51d153 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/profile/ProfileLoginActivityTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/profile/ProfileLoginActivityTest.kt @@ -80,7 +80,6 @@ import org.oppia.android.util.locale.LocaleProdModule import org.oppia.android.util.logging.CurrentAppScreenNameIntentDecorator.extractCurrentAppScreenName import org.oppia.android.util.logging.LoggerModule import org.oppia.android.util.logging.SyncStatusModule -import org.oppia.android.util.logging.firebase.FirebaseLogUploaderModule import org.oppia.android.util.networking.NetworkConnectionDebugUtilModule import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule import org.oppia.android.util.parser.html.HtmlParserEntityTypeModule @@ -170,7 +169,6 @@ class ProfileLoginActivityTest { ExplorationProgressModule::class, ExplorationStorageModule::class, FakeOppiaClockModule::class, - FirebaseLogUploaderModule::class, FractionInputModule::class, GcsResourceModule::class, GlideImageLoaderModule::class, diff --git a/app/src/sharedTest/java/org/oppia/android/app/profile/ProfileLoginFragmentTest.kt b/app/src/sharedTest/java/org/oppia/android/app/profile/ProfileLoginFragmentTest.kt index 16c2b882caa..8591532ce6f 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/profile/ProfileLoginFragmentTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/profile/ProfileLoginFragmentTest.kt @@ -109,7 +109,6 @@ import org.oppia.android.util.gcsresource.GcsResourceModule import org.oppia.android.util.locale.LocaleProdModule import org.oppia.android.util.logging.LoggerModule import org.oppia.android.util.logging.SyncStatusModule -import org.oppia.android.util.logging.firebase.FirebaseLogUploaderModule import org.oppia.android.util.networking.NetworkConnectionDebugUtilModule import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule import org.oppia.android.util.parser.html.HtmlParserEntityTypeModule @@ -863,7 +862,6 @@ class ProfileLoginFragmentTest { ExplorationProgressModule::class, ExplorationStorageModule::class, FakeOppiaClockModule::class, - FirebaseLogUploaderModule::class, FractionInputModule::class, GcsResourceModule::class, GlideImageLoaderModule::class, diff --git a/app/src/sharedTest/java/org/oppia/android/app/profileprogress/ProfilePictureActivityTest.kt b/app/src/sharedTest/java/org/oppia/android/app/profileprogress/ProfilePictureActivityTest.kt index 3eb2c21fdee..3596eacd7a7 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/profileprogress/ProfilePictureActivityTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/profileprogress/ProfilePictureActivityTest.kt @@ -85,7 +85,6 @@ import org.oppia.android.util.locale.LocaleProdModule import org.oppia.android.util.logging.CurrentAppScreenNameIntentDecorator.extractCurrentAppScreenName import org.oppia.android.util.logging.LoggerModule import org.oppia.android.util.logging.SyncStatusModule -import org.oppia.android.util.logging.firebase.FirebaseLogUploaderModule import org.oppia.android.util.networking.NetworkConnectionDebugUtilModule import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule import org.oppia.android.util.parser.html.HtmlParserEntityTypeModule @@ -214,7 +213,6 @@ class ProfilePictureActivityTest { ExplorationProgressModule::class, ExplorationStorageModule::class, FakeOppiaClockModule::class, - FirebaseLogUploaderModule::class, FractionInputModule::class, GcsResourceModule::class, GlideImageLoaderModule::class, diff --git a/app/src/sharedTest/java/org/oppia/android/app/profileprogress/ProfileProgressActivityTest.kt b/app/src/sharedTest/java/org/oppia/android/app/profileprogress/ProfileProgressActivityTest.kt index 1c60de8bea6..5fb776a7cb4 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/profileprogress/ProfileProgressActivityTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/profileprogress/ProfileProgressActivityTest.kt @@ -76,7 +76,6 @@ import org.oppia.android.util.locale.LocaleProdModule import org.oppia.android.util.logging.CurrentAppScreenNameIntentDecorator.extractCurrentAppScreenName import org.oppia.android.util.logging.LoggerModule import org.oppia.android.util.logging.SyncStatusModule -import org.oppia.android.util.logging.firebase.FirebaseLogUploaderModule import org.oppia.android.util.networking.NetworkConnectionDebugUtilModule import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule import org.oppia.android.util.parser.html.HtmlParserEntityTypeModule @@ -165,7 +164,6 @@ class ProfileProgressActivityTest { ExplorationProgressModule::class, ExplorationStorageModule::class, FakeOppiaClockModule::class, - FirebaseLogUploaderModule::class, FractionInputModule::class, GcsResourceModule::class, GlideImageLoaderModule::class, diff --git a/app/src/sharedTest/java/org/oppia/android/app/profileprogress/ProfileProgressFragmentTest.kt b/app/src/sharedTest/java/org/oppia/android/app/profileprogress/ProfileProgressFragmentTest.kt index 5d134d6a06a..82b2720b169 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/profileprogress/ProfileProgressFragmentTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/profileprogress/ProfileProgressFragmentTest.kt @@ -128,7 +128,6 @@ import org.oppia.android.util.logging.EnableFileLog import org.oppia.android.util.logging.GlobalLogLevel import org.oppia.android.util.logging.LogLevel import org.oppia.android.util.logging.SyncStatusModule -import org.oppia.android.util.logging.firebase.FirebaseLogUploaderModule import org.oppia.android.util.networking.NetworkConnectionDebugUtilModule import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule import org.oppia.android.util.parser.html.HtmlParserEntityTypeModule @@ -931,7 +930,6 @@ class ProfileProgressFragmentTest { ExplorationProgressModule::class, ExplorationStorageModule::class, FakeOppiaClockModule::class, - FirebaseLogUploaderModule::class, FractionInputModule::class, GcsResourceModule::class, GlideImageLoaderModule::class, diff --git a/app/src/sharedTest/java/org/oppia/android/app/recyclerview/BindableAdapterTest.kt b/app/src/sharedTest/java/org/oppia/android/app/recyclerview/BindableAdapterTest.kt index bef377f2995..ed867d7403d 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/recyclerview/BindableAdapterTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/recyclerview/BindableAdapterTest.kt @@ -113,7 +113,6 @@ import org.oppia.android.util.gcsresource.GcsResourceModule import org.oppia.android.util.locale.LocaleProdModule import org.oppia.android.util.logging.LoggerModule import org.oppia.android.util.logging.SyncStatusModule -import org.oppia.android.util.logging.firebase.FirebaseLogUploaderModule import org.oppia.android.util.networking.NetworkConnectionDebugUtilModule import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule import org.oppia.android.util.parser.html.HtmlParserEntityTypeModule @@ -748,7 +747,6 @@ class BindableAdapterTest { ExplorationProgressModule::class, ExplorationStorageModule::class, FakeOppiaClockModule::class, - FirebaseLogUploaderModule::class, FractionInputModule::class, GcsResourceModule::class, GlideImageLoaderModule::class, diff --git a/app/src/sharedTest/java/org/oppia/android/app/resumelesson/ResumeLessonActivityTest.kt b/app/src/sharedTest/java/org/oppia/android/app/resumelesson/ResumeLessonActivityTest.kt index b56b7775309..0e21d4b18b9 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/resumelesson/ResumeLessonActivityTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/resumelesson/ResumeLessonActivityTest.kt @@ -98,7 +98,6 @@ import org.oppia.android.util.locale.LocaleProdModule import org.oppia.android.util.logging.CurrentAppScreenNameIntentDecorator.extractCurrentAppScreenName import org.oppia.android.util.logging.LoggerModule import org.oppia.android.util.logging.SyncStatusModule -import org.oppia.android.util.logging.firebase.FirebaseLogUploaderModule import org.oppia.android.util.networking.NetworkConnectionDebugUtilModule import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule import org.oppia.android.util.parser.html.HtmlParserEntityTypeModule @@ -229,7 +228,6 @@ class ResumeLessonActivityTest { ExplorationProgressModule::class, ExplorationStorageModule::class, FakeOppiaClockModule::class, - FirebaseLogUploaderModule::class, FractionInputModule::class, GcsResourceModule::class, HintsAndSolutionConfigModule::class, diff --git a/app/src/sharedTest/java/org/oppia/android/app/resumelesson/ResumeLessonFragmentTest.kt b/app/src/sharedTest/java/org/oppia/android/app/resumelesson/ResumeLessonFragmentTest.kt index c73056df0b7..deaed0e86e6 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/resumelesson/ResumeLessonFragmentTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/resumelesson/ResumeLessonFragmentTest.kt @@ -105,7 +105,6 @@ import org.oppia.android.util.gcsresource.GcsResourceModule import org.oppia.android.util.locale.LocaleProdModule import org.oppia.android.util.logging.LoggerModule import org.oppia.android.util.logging.SyncStatusModule -import org.oppia.android.util.logging.firebase.FirebaseLogUploaderModule import org.oppia.android.util.networking.NetworkConnectionDebugUtilModule import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule import org.oppia.android.util.parser.html.HtmlParserEntityTypeModule @@ -400,7 +399,6 @@ class ResumeLessonFragmentTest { ExplorationProgressModule::class, ExplorationStorageModule::class, FakeOppiaClockModule::class, - FirebaseLogUploaderModule::class, FractionInputModule::class, GcsResourceModule::class, HintsAndSolutionConfigModule::class, diff --git a/app/src/sharedTest/java/org/oppia/android/app/settings/profile/ProfileEditActivityTest.kt b/app/src/sharedTest/java/org/oppia/android/app/settings/profile/ProfileEditActivityTest.kt index f6ad66f41dd..591993e5ba2 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/settings/profile/ProfileEditActivityTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/settings/profile/ProfileEditActivityTest.kt @@ -95,7 +95,6 @@ import org.oppia.android.util.locale.LocaleProdModule import org.oppia.android.util.logging.CurrentAppScreenNameIntentDecorator.extractCurrentAppScreenName import org.oppia.android.util.logging.LoggerModule import org.oppia.android.util.logging.SyncStatusModule -import org.oppia.android.util.logging.firebase.FirebaseLogUploaderModule import org.oppia.android.util.networking.NetworkConnectionDebugUtilModule import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule import org.oppia.android.util.parser.html.HtmlParserEntityTypeModule @@ -374,7 +373,6 @@ class ProfileEditActivityTest { ExplorationProgressModule::class, ExplorationStorageModule::class, FakeOppiaClockModule::class, - FirebaseLogUploaderModule::class, FractionInputModule::class, GcsResourceModule::class, GlideImageLoaderModule::class, diff --git a/app/src/sharedTest/java/org/oppia/android/app/settings/profile/ProfileEditFragmentTest.kt b/app/src/sharedTest/java/org/oppia/android/app/settings/profile/ProfileEditFragmentTest.kt index 5754fd03f2d..7d4641e684f 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/settings/profile/ProfileEditFragmentTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/settings/profile/ProfileEditFragmentTest.kt @@ -109,7 +109,6 @@ import org.oppia.android.util.gcsresource.GcsResourceModule import org.oppia.android.util.locale.LocaleProdModule import org.oppia.android.util.logging.LoggerModule import org.oppia.android.util.logging.SyncStatusModule -import org.oppia.android.util.logging.firebase.FirebaseLogUploaderModule import org.oppia.android.util.networking.NetworkConnectionDebugUtilModule import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule import org.oppia.android.util.parser.html.HtmlParserEntityTypeModule @@ -588,7 +587,6 @@ class ProfileEditFragmentTest { ExplorationProgressModule::class, ExplorationStorageModule::class, FakeOppiaClockModule::class, - FirebaseLogUploaderModule::class, FractionInputModule::class, GcsResourceModule::class, HintsAndSolutionConfigModule::class, diff --git a/app/src/sharedTest/java/org/oppia/android/app/settings/profile/ProfileListActivityTest.kt b/app/src/sharedTest/java/org/oppia/android/app/settings/profile/ProfileListActivityTest.kt index 3790762dbf1..176cea88dcb 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/settings/profile/ProfileListActivityTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/settings/profile/ProfileListActivityTest.kt @@ -76,7 +76,6 @@ import org.oppia.android.util.locale.LocaleProdModule import org.oppia.android.util.logging.CurrentAppScreenNameIntentDecorator.extractCurrentAppScreenName import org.oppia.android.util.logging.LoggerModule import org.oppia.android.util.logging.SyncStatusModule -import org.oppia.android.util.logging.firebase.FirebaseLogUploaderModule import org.oppia.android.util.networking.NetworkConnectionDebugUtilModule import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule import org.oppia.android.util.parser.html.HtmlParserEntityTypeModule @@ -158,7 +157,6 @@ class ProfileListActivityTest { ExplorationProgressModule::class, ExplorationStorageModule::class, FakeOppiaClockModule::class, - FirebaseLogUploaderModule::class, FractionInputModule::class, GcsResourceModule::class, GlideImageLoaderModule::class, diff --git a/app/src/sharedTest/java/org/oppia/android/app/settings/profile/ProfileListFragmentTest.kt b/app/src/sharedTest/java/org/oppia/android/app/settings/profile/ProfileListFragmentTest.kt index 18fc46599c9..f746563daa0 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/settings/profile/ProfileListFragmentTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/settings/profile/ProfileListFragmentTest.kt @@ -94,7 +94,6 @@ import org.oppia.android.util.gcsresource.GcsResourceModule import org.oppia.android.util.locale.LocaleProdModule import org.oppia.android.util.logging.LoggerModule import org.oppia.android.util.logging.SyncStatusModule -import org.oppia.android.util.logging.firebase.FirebaseLogUploaderModule import org.oppia.android.util.networking.NetworkConnectionDebugUtilModule import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule import org.oppia.android.util.parser.html.HtmlParserEntityTypeModule @@ -416,7 +415,6 @@ class ProfileListFragmentTest { ExplorationProgressModule::class, ExplorationStorageModule::class, FakeOppiaClockModule::class, - FirebaseLogUploaderModule::class, FractionInputModule::class, GcsResourceModule::class, GlideImageLoaderModule::class, diff --git a/app/src/sharedTest/java/org/oppia/android/app/settings/profile/ProfileRenameActivityTest.kt b/app/src/sharedTest/java/org/oppia/android/app/settings/profile/ProfileRenameActivityTest.kt index df162f5b5ea..457183b9b84 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/settings/profile/ProfileRenameActivityTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/settings/profile/ProfileRenameActivityTest.kt @@ -79,7 +79,6 @@ import org.oppia.android.util.locale.LocaleProdModule import org.oppia.android.util.logging.CurrentAppScreenNameIntentDecorator.extractCurrentAppScreenName import org.oppia.android.util.logging.LoggerModule import org.oppia.android.util.logging.SyncStatusModule -import org.oppia.android.util.logging.firebase.FirebaseLogUploaderModule import org.oppia.android.util.networking.NetworkConnectionDebugUtilModule import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule import org.oppia.android.util.parser.html.HtmlParserEntityTypeModule @@ -174,7 +173,6 @@ class ProfileRenameActivityTest { ExplorationProgressModule::class, ExplorationStorageModule::class, FakeOppiaClockModule::class, - FirebaseLogUploaderModule::class, FractionInputModule::class, GcsResourceModule::class, GlideImageLoaderModule::class, diff --git a/app/src/sharedTest/java/org/oppia/android/app/settings/profile/ProfileRenameFragmentTest.kt b/app/src/sharedTest/java/org/oppia/android/app/settings/profile/ProfileRenameFragmentTest.kt index 1a1e76d187a..523dfcaf086 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/settings/profile/ProfileRenameFragmentTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/settings/profile/ProfileRenameFragmentTest.kt @@ -96,7 +96,6 @@ import org.oppia.android.util.gcsresource.GcsResourceModule import org.oppia.android.util.locale.LocaleProdModule import org.oppia.android.util.logging.LoggerModule import org.oppia.android.util.logging.SyncStatusModule -import org.oppia.android.util.logging.firebase.FirebaseLogUploaderModule import org.oppia.android.util.networking.NetworkConnectionDebugUtilModule import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule import org.oppia.android.util.parser.html.HtmlParserEntityTypeModule @@ -470,7 +469,6 @@ class ProfileRenameFragmentTest { ExplorationProgressModule::class, ExplorationStorageModule::class, FakeOppiaClockModule::class, - FirebaseLogUploaderModule::class, FractionInputModule::class, GcsResourceModule::class, GlideImageLoaderModule::class, diff --git a/app/src/sharedTest/java/org/oppia/android/app/settings/profile/ProfileResetPinActivityTest.kt b/app/src/sharedTest/java/org/oppia/android/app/settings/profile/ProfileResetPinActivityTest.kt index 4b1b819df78..81ecdd7ca46 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/settings/profile/ProfileResetPinActivityTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/settings/profile/ProfileResetPinActivityTest.kt @@ -79,7 +79,6 @@ import org.oppia.android.util.locale.LocaleProdModule import org.oppia.android.util.logging.CurrentAppScreenNameIntentDecorator.extractCurrentAppScreenName import org.oppia.android.util.logging.LoggerModule import org.oppia.android.util.logging.SyncStatusModule -import org.oppia.android.util.logging.firebase.FirebaseLogUploaderModule import org.oppia.android.util.networking.NetworkConnectionDebugUtilModule import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule import org.oppia.android.util.parser.html.HtmlParserEntityTypeModule @@ -182,7 +181,6 @@ class ProfileResetPinActivityTest { ExplorationProgressModule::class, ExplorationStorageModule::class, FakeOppiaClockModule::class, - FirebaseLogUploaderModule::class, FractionInputModule::class, GcsResourceModule::class, GlideImageLoaderModule::class, diff --git a/app/src/sharedTest/java/org/oppia/android/app/settings/profile/ProfileResetPinFragmentTest.kt b/app/src/sharedTest/java/org/oppia/android/app/settings/profile/ProfileResetPinFragmentTest.kt index 3f09fc33cfd..de9fcb659bc 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/settings/profile/ProfileResetPinFragmentTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/settings/profile/ProfileResetPinFragmentTest.kt @@ -101,7 +101,6 @@ import org.oppia.android.util.gcsresource.GcsResourceModule import org.oppia.android.util.locale.LocaleProdModule import org.oppia.android.util.logging.LoggerModule import org.oppia.android.util.logging.SyncStatusModule -import org.oppia.android.util.logging.firebase.FirebaseLogUploaderModule import org.oppia.android.util.networking.NetworkConnectionDebugUtilModule import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule import org.oppia.android.util.parser.html.HtmlParserEntityTypeModule @@ -921,7 +920,6 @@ class ProfileResetPinFragmentTest { ExplorationProgressModule::class, ExplorationStorageModule::class, FakeOppiaClockModule::class, - FirebaseLogUploaderModule::class, FractionInputModule::class, GcsResourceModule::class, GlideImageLoaderModule::class, diff --git a/app/src/sharedTest/java/org/oppia/android/app/splash/SplashActivityTest.kt b/app/src/sharedTest/java/org/oppia/android/app/splash/SplashActivityTest.kt index bb5a4770037..70e990402d9 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/splash/SplashActivityTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/splash/SplashActivityTest.kt @@ -126,7 +126,6 @@ import org.oppia.android.util.locale.LocaleProdModule import org.oppia.android.util.logging.CurrentAppScreenNameIntentDecorator.extractCurrentAppScreenName import org.oppia.android.util.logging.LoggerModule import org.oppia.android.util.logging.SyncStatusModule -import org.oppia.android.util.logging.firebase.FirebaseLogUploaderModule import org.oppia.android.util.networking.NetworkConnectionDebugUtilModule import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule import org.oppia.android.util.parser.html.HtmlParserEntityTypeModule @@ -1368,7 +1367,6 @@ class SplashActivityTest { ExplorationProgressModule::class, ExplorationStorageModule::class, FakeOppiaClockModule::class, - FirebaseLogUploaderModule::class, FractionInputModule::class, GcsResourceModule::class, GlideImageLoaderModule::class, diff --git a/app/src/sharedTest/java/org/oppia/android/app/spotlight/SpotlightFragmentTest.kt b/app/src/sharedTest/java/org/oppia/android/app/spotlight/SpotlightFragmentTest.kt index f800c432ca9..dac33d4d1fc 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/spotlight/SpotlightFragmentTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/spotlight/SpotlightFragmentTest.kt @@ -89,7 +89,6 @@ import org.oppia.android.util.gcsresource.GcsResourceModule import org.oppia.android.util.locale.LocaleProdModule import org.oppia.android.util.logging.LoggerModule import org.oppia.android.util.logging.SyncStatusModule -import org.oppia.android.util.logging.firebase.FirebaseLogUploaderModule import org.oppia.android.util.networking.NetworkConnectionDebugUtilModule import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule import org.oppia.android.util.parser.html.HtmlParserEntityTypeModule @@ -387,7 +386,6 @@ class SpotlightFragmentTest { ExplorationProgressModule::class, ExplorationStorageModule::class, FakeOppiaClockModule::class, - FirebaseLogUploaderModule::class, FractionInputModule::class, GcsResourceModule::class, HintsAndSolutionConfigModule::class, diff --git a/app/src/sharedTest/java/org/oppia/android/app/story/StoryActivityTest.kt b/app/src/sharedTest/java/org/oppia/android/app/story/StoryActivityTest.kt index 376463851c9..34ea3f5e18f 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/story/StoryActivityTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/story/StoryActivityTest.kt @@ -98,7 +98,6 @@ import org.oppia.android.util.locale.LocaleProdModule import org.oppia.android.util.logging.CurrentAppScreenNameIntentDecorator.extractCurrentAppScreenName import org.oppia.android.util.logging.LoggerModule import org.oppia.android.util.logging.SyncStatusModule -import org.oppia.android.util.logging.firebase.FirebaseLogUploaderModule import org.oppia.android.util.networking.NetworkConnectionDebugUtilModule import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule import org.oppia.android.util.parser.html.HtmlParserEntityTypeModule @@ -251,7 +250,6 @@ class StoryActivityTest { ExplorationProgressModule::class, ExplorationStorageModule::class, FakeOppiaClockModule::class, - FirebaseLogUploaderModule::class, FractionInputModule::class, GcsResourceModule::class, HintsAndSolutionConfigModule::class, diff --git a/app/src/sharedTest/java/org/oppia/android/app/story/StoryFragmentTest.kt b/app/src/sharedTest/java/org/oppia/android/app/story/StoryFragmentTest.kt index 00c2240de8f..185a5ad8546 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/story/StoryFragmentTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/story/StoryFragmentTest.kt @@ -143,7 +143,6 @@ import org.oppia.android.util.gcsresource.GcsResourceModule import org.oppia.android.util.locale.LocaleProdModule import org.oppia.android.util.logging.LoggerModule import org.oppia.android.util.logging.SyncStatusModule -import org.oppia.android.util.logging.firebase.FirebaseLogUploaderModule import org.oppia.android.util.networking.NetworkConnectionDebugUtilModule import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule import org.oppia.android.util.parser.html.HtmlParserEntityTypeModule @@ -1197,7 +1196,6 @@ class StoryFragmentTest { ExplorationProgressModule::class, ExplorationStorageModule::class, FakeOppiaClockModule::class, - FirebaseLogUploaderModule::class, FractionInputModule::class, GcsResourceModule::class, HintsAndSolutionConfigModule::class, diff --git a/app/src/sharedTest/java/org/oppia/android/app/survey/SurveyActivityTest.kt b/app/src/sharedTest/java/org/oppia/android/app/survey/SurveyActivityTest.kt index c1a9faedbc1..8cd425bee0f 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/survey/SurveyActivityTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/survey/SurveyActivityTest.kt @@ -81,7 +81,6 @@ import org.oppia.android.util.locale.LocaleProdModule import org.oppia.android.util.logging.CurrentAppScreenNameIntentDecorator.extractCurrentAppScreenName import org.oppia.android.util.logging.LoggerModule import org.oppia.android.util.logging.SyncStatusModule -import org.oppia.android.util.logging.firebase.FirebaseLogUploaderModule import org.oppia.android.util.networking.NetworkConnectionDebugUtilModule import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule import org.oppia.android.util.parser.html.HtmlParserEntityTypeModule @@ -179,7 +178,6 @@ class SurveyActivityTest { ExplorationProgressModule::class, ExplorationStorageModule::class, FakeOppiaClockModule::class, - FirebaseLogUploaderModule::class, FractionInputModule::class, GcsResourceModule::class, GlideImageLoaderModule::class, diff --git a/app/src/sharedTest/java/org/oppia/android/app/survey/SurveyFragmentTest.kt b/app/src/sharedTest/java/org/oppia/android/app/survey/SurveyFragmentTest.kt index 4fc5421591c..57fd05d0589 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/survey/SurveyFragmentTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/survey/SurveyFragmentTest.kt @@ -109,7 +109,6 @@ import org.oppia.android.util.locale.LocaleProdModule import org.oppia.android.util.logging.CurrentAppScreenNameIntentDecorator.extractCurrentAppScreenName import org.oppia.android.util.logging.LoggerModule import org.oppia.android.util.logging.SyncStatusModule -import org.oppia.android.util.logging.firebase.FirebaseLogUploaderModule import org.oppia.android.util.networking.NetworkConnectionDebugUtilModule import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule import org.oppia.android.util.parser.html.HtmlParserEntityTypeModule @@ -654,7 +653,6 @@ class SurveyFragmentTest { ExplorationProgressModule::class, ExplorationStorageModule::class, FakeOppiaClockModule::class, - FirebaseLogUploaderModule::class, FractionInputModule::class, GcsResourceModule::class, GlideImageLoaderModule::class, diff --git a/app/src/sharedTest/java/org/oppia/android/app/testing/DragDropTestActivityTest.kt b/app/src/sharedTest/java/org/oppia/android/app/testing/DragDropTestActivityTest.kt index 4a31eaf5f63..7e2dae37276 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/testing/DragDropTestActivityTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/testing/DragDropTestActivityTest.kt @@ -89,7 +89,6 @@ import org.oppia.android.util.gcsresource.GcsResourceModule import org.oppia.android.util.locale.LocaleProdModule import org.oppia.android.util.logging.LoggerModule import org.oppia.android.util.logging.SyncStatusModule -import org.oppia.android.util.logging.firebase.FirebaseLogUploaderModule import org.oppia.android.util.networking.NetworkConnectionDebugUtilModule import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule import org.oppia.android.util.parser.html.HtmlParserEntityTypeModule @@ -259,7 +258,6 @@ class DragDropTestActivityTest { ExplorationProgressModule::class, ExplorationStorageModule::class, FakeOppiaClockModule::class, - FirebaseLogUploaderModule::class, FractionInputModule::class, GcsResourceModule::class, GlideImageLoaderModule::class, diff --git a/app/src/sharedTest/java/org/oppia/android/app/testing/FractionInputInteractionViewTestActivityTest.kt b/app/src/sharedTest/java/org/oppia/android/app/testing/FractionInputInteractionViewTestActivityTest.kt index ddbf28116e2..e9ab3613285 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/testing/FractionInputInteractionViewTestActivityTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/testing/FractionInputInteractionViewTestActivityTest.kt @@ -86,7 +86,6 @@ import org.oppia.android.util.gcsresource.GcsResourceModule import org.oppia.android.util.locale.LocaleProdModule import org.oppia.android.util.logging.LoggerModule import org.oppia.android.util.logging.SyncStatusModule -import org.oppia.android.util.logging.firebase.FirebaseLogUploaderModule import org.oppia.android.util.networking.NetworkConnectionDebugUtilModule import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule import org.oppia.android.util.parser.html.HtmlParserEntityTypeModule @@ -570,7 +569,6 @@ class FractionInputInteractionViewTestActivityTest { ExplorationProgressModule::class, ExplorationStorageModule::class, FakeOppiaClockModule::class, - FirebaseLogUploaderModule::class, FractionInputModule::class, GcsResourceModule::class, GlideImageLoaderModule::class, diff --git a/app/src/sharedTest/java/org/oppia/android/app/testing/InputInteractionViewTestActivityTest.kt b/app/src/sharedTest/java/org/oppia/android/app/testing/InputInteractionViewTestActivityTest.kt index b55e0232ca0..44de694e30a 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/testing/InputInteractionViewTestActivityTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/testing/InputInteractionViewTestActivityTest.kt @@ -86,7 +86,6 @@ import org.oppia.android.util.gcsresource.GcsResourceModule import org.oppia.android.util.locale.LocaleProdModule import org.oppia.android.util.logging.LoggerModule import org.oppia.android.util.logging.SyncStatusModule -import org.oppia.android.util.logging.firebase.FirebaseLogUploaderModule import org.oppia.android.util.networking.NetworkConnectionDebugUtilModule import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule import org.oppia.android.util.parser.html.HtmlParserEntityTypeModule @@ -444,7 +443,6 @@ class InputInteractionViewTestActivityTest { ExplorationProgressModule::class, ExplorationStorageModule::class, FakeOppiaClockModule::class, - FirebaseLogUploaderModule::class, FractionInputModule::class, GcsResourceModule::class, GlideImageLoaderModule::class, diff --git a/app/src/sharedTest/java/org/oppia/android/app/testing/NavigationDrawerActivityDebugTest.kt b/app/src/sharedTest/java/org/oppia/android/app/testing/NavigationDrawerActivityDebugTest.kt index 66d00d11aee..648d61ab70f 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/testing/NavigationDrawerActivityDebugTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/testing/NavigationDrawerActivityDebugTest.kt @@ -117,7 +117,6 @@ import org.oppia.android.util.gcsresource.GcsResourceModule import org.oppia.android.util.locale.LocaleProdModule import org.oppia.android.util.logging.LoggerModule import org.oppia.android.util.logging.SyncStatusModule -import org.oppia.android.util.logging.firebase.FirebaseLogUploaderModule import org.oppia.android.util.networking.NetworkConnectionDebugUtilModule import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule import org.oppia.android.util.parser.html.HtmlParserEntityTypeModule @@ -486,7 +485,6 @@ class NavigationDrawerActivityDebugTest { ExplorationProgressModule::class, ExplorationStorageModule::class, FakeOppiaClockModule::class, - FirebaseLogUploaderModule::class, FractionInputModule::class, GcsResourceModule::class, GlideImageLoaderModule::class, diff --git a/app/src/sharedTest/java/org/oppia/android/app/testing/NavigationDrawerActivityProdTest.kt b/app/src/sharedTest/java/org/oppia/android/app/testing/NavigationDrawerActivityProdTest.kt index 22371b138a1..9637d19fde8 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/testing/NavigationDrawerActivityProdTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/testing/NavigationDrawerActivityProdTest.kt @@ -126,7 +126,6 @@ import org.oppia.android.util.gcsresource.GcsResourceModule import org.oppia.android.util.locale.LocaleProdModule import org.oppia.android.util.logging.LoggerModule import org.oppia.android.util.logging.SyncStatusModule -import org.oppia.android.util.logging.firebase.FirebaseLogUploaderModule import org.oppia.android.util.networking.NetworkConnectionDebugUtilModule import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule import org.oppia.android.util.parser.html.HtmlParserEntityTypeModule @@ -1047,7 +1046,6 @@ class NavigationDrawerActivityProdTest { ExplorationProgressModule::class, ExplorationStorageModule::class, FakeOppiaClockModule::class, - FirebaseLogUploaderModule::class, FractionInputModule::class, GcsResourceModule::class, GlideImageLoaderModule::class, diff --git a/app/src/sharedTest/java/org/oppia/android/app/testing/RatioInputInteractionViewTestActivityTest.kt b/app/src/sharedTest/java/org/oppia/android/app/testing/RatioInputInteractionViewTestActivityTest.kt index 1d411d59abc..5b235d66fb7 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/testing/RatioInputInteractionViewTestActivityTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/testing/RatioInputInteractionViewTestActivityTest.kt @@ -93,7 +93,6 @@ import org.oppia.android.util.gcsresource.GcsResourceModule import org.oppia.android.util.locale.LocaleProdModule import org.oppia.android.util.logging.LoggerModule import org.oppia.android.util.logging.SyncStatusModule -import org.oppia.android.util.logging.firebase.FirebaseLogUploaderModule import org.oppia.android.util.networking.NetworkConnectionDebugUtilModule import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule import org.oppia.android.util.parser.html.HtmlParserEntityTypeModule @@ -438,7 +437,6 @@ class RatioInputInteractionViewTestActivityTest { ExplorationProgressModule::class, ExplorationStorageModule::class, FakeOppiaClockModule::class, - FirebaseLogUploaderModule::class, FractionInputModule::class, GcsResourceModule::class, GlideImageLoaderModule::class, diff --git a/app/src/sharedTest/java/org/oppia/android/app/testing/TestFontScaleConfigurationUtilActivityTest.kt b/app/src/sharedTest/java/org/oppia/android/app/testing/TestFontScaleConfigurationUtilActivityTest.kt index b21caa36f17..0d6d0ddf895 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/testing/TestFontScaleConfigurationUtilActivityTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/testing/TestFontScaleConfigurationUtilActivityTest.kt @@ -80,7 +80,6 @@ import org.oppia.android.util.gcsresource.GcsResourceModule import org.oppia.android.util.locale.LocaleProdModule import org.oppia.android.util.logging.LoggerModule import org.oppia.android.util.logging.SyncStatusModule -import org.oppia.android.util.logging.firebase.FirebaseLogUploaderModule import org.oppia.android.util.networking.NetworkConnectionDebugUtilModule import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule import org.oppia.android.util.parser.html.HtmlParserEntityTypeModule @@ -206,7 +205,6 @@ class TestFontScaleConfigurationUtilActivityTest { ExplorationProgressModule::class, ExplorationStorageModule::class, FakeOppiaClockModule::class, - FirebaseLogUploaderModule::class, FractionInputModule::class, GcsResourceModule::class, GlideImageLoaderModule::class, diff --git a/app/src/sharedTest/java/org/oppia/android/app/testing/TextInputInteractionViewTestActivityTest.kt b/app/src/sharedTest/java/org/oppia/android/app/testing/TextInputInteractionViewTestActivityTest.kt index f1b94c3f5b0..83e97bf21c4 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/testing/TextInputInteractionViewTestActivityTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/testing/TextInputInteractionViewTestActivityTest.kt @@ -85,7 +85,6 @@ import org.oppia.android.util.gcsresource.GcsResourceModule import org.oppia.android.util.locale.LocaleProdModule import org.oppia.android.util.logging.LoggerModule import org.oppia.android.util.logging.SyncStatusModule -import org.oppia.android.util.logging.firebase.FirebaseLogUploaderModule import org.oppia.android.util.networking.NetworkConnectionDebugUtilModule import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule import org.oppia.android.util.parser.html.HtmlParserEntityTypeModule @@ -237,7 +236,6 @@ class TextInputInteractionViewTestActivityTest { ExplorationProgressModule::class, ExplorationStorageModule::class, FakeOppiaClockModule::class, - FirebaseLogUploaderModule::class, FractionInputModule::class, GcsResourceModule::class, GlideImageLoaderModule::class, diff --git a/app/src/sharedTest/java/org/oppia/android/app/testing/TopicTestActivityForStoryTest.kt b/app/src/sharedTest/java/org/oppia/android/app/testing/TopicTestActivityForStoryTest.kt index 2cf9d300715..0e837b0c944 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/testing/TopicTestActivityForStoryTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/testing/TopicTestActivityForStoryTest.kt @@ -86,7 +86,6 @@ import org.oppia.android.util.gcsresource.GcsResourceModule import org.oppia.android.util.locale.LocaleProdModule import org.oppia.android.util.logging.LoggerModule import org.oppia.android.util.logging.SyncStatusModule -import org.oppia.android.util.logging.firebase.FirebaseLogUploaderModule import org.oppia.android.util.networking.NetworkConnectionDebugUtilModule import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule import org.oppia.android.util.parser.html.HtmlParserEntityTypeModule @@ -215,7 +214,6 @@ class TopicTestActivityForStoryTest { ExplorationProgressModule::class, ExplorationStorageModule::class, FakeOppiaClockModule::class, - FirebaseLogUploaderModule::class, FractionInputModule::class, GcsResourceModule::class, GlideImageLoaderModule::class, diff --git a/app/src/sharedTest/java/org/oppia/android/app/topic/TopicActivityTest.kt b/app/src/sharedTest/java/org/oppia/android/app/topic/TopicActivityTest.kt index 371d813b999..c3ca96b9ac3 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/topic/TopicActivityTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/topic/TopicActivityTest.kt @@ -98,7 +98,6 @@ import org.oppia.android.util.locale.LocaleProdModule import org.oppia.android.util.logging.CurrentAppScreenNameIntentDecorator.extractCurrentAppScreenName import org.oppia.android.util.logging.LoggerModule import org.oppia.android.util.logging.SyncStatusModule -import org.oppia.android.util.logging.firebase.FirebaseLogUploaderModule import org.oppia.android.util.networking.NetworkConnectionDebugUtilModule import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule import org.oppia.android.util.parser.html.HtmlParserEntityTypeModule @@ -248,7 +247,6 @@ class TopicActivityTest { ExplorationProgressModule::class, ExplorationStorageModule::class, FakeOppiaClockModule::class, - FirebaseLogUploaderModule::class, FractionInputModule::class, GcsResourceModule::class, GlideImageLoaderModule::class, diff --git a/app/src/sharedTest/java/org/oppia/android/app/topic/TopicFragmentTest.kt b/app/src/sharedTest/java/org/oppia/android/app/topic/TopicFragmentTest.kt index 2680d79df3d..1d7fe1d838c 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/topic/TopicFragmentTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/topic/TopicFragmentTest.kt @@ -125,7 +125,6 @@ import org.oppia.android.util.gcsresource.GcsResourceModule import org.oppia.android.util.locale.LocaleProdModule import org.oppia.android.util.logging.LoggerModule import org.oppia.android.util.logging.SyncStatusModule -import org.oppia.android.util.logging.firebase.FirebaseLogUploaderModule import org.oppia.android.util.networking.NetworkConnectionDebugUtilModule import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule import org.oppia.android.util.parser.html.HtmlParserEntityTypeModule @@ -1239,7 +1238,6 @@ class TopicFragmentTest { ExplorationProgressModule::class, ExplorationStorageModule::class, FakeOppiaClockModule::class, - FirebaseLogUploaderModule::class, FractionInputModule::class, GcsResourceModule::class, GlideImageLoaderModule::class, diff --git a/app/src/sharedTest/java/org/oppia/android/app/topic/conceptcard/ConceptCardFragmentTest.kt b/app/src/sharedTest/java/org/oppia/android/app/topic/conceptcard/ConceptCardFragmentTest.kt index 86c2124b035..2ee277b562f 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/topic/conceptcard/ConceptCardFragmentTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/topic/conceptcard/ConceptCardFragmentTest.kt @@ -121,7 +121,6 @@ import org.oppia.android.util.gcsresource.GcsResourceModule import org.oppia.android.util.locale.LocaleProdModule import org.oppia.android.util.logging.LoggerModule import org.oppia.android.util.logging.SyncStatusModule -import org.oppia.android.util.logging.firebase.FirebaseLogUploaderModule import org.oppia.android.util.networking.NetworkConnectionDebugUtilModule import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule import org.oppia.android.util.parser.html.HtmlParserEntityTypeModule @@ -612,7 +611,6 @@ class ConceptCardFragmentTest { ExplorationProgressModule::class, ExplorationStorageModule::class, FakeOppiaClockModule::class, - FirebaseLogUploaderModule::class, FractionInputModule::class, GcsResourceModule::class, GlideImageLoaderModule::class, diff --git a/app/src/sharedTest/java/org/oppia/android/app/topic/info/TopicInfoFragmentTest.kt b/app/src/sharedTest/java/org/oppia/android/app/topic/info/TopicInfoFragmentTest.kt index 13c52a3a492..370a3812608 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/topic/info/TopicInfoFragmentTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/topic/info/TopicInfoFragmentTest.kt @@ -112,7 +112,6 @@ import org.oppia.android.util.gcsresource.GcsResourceModule import org.oppia.android.util.locale.LocaleProdModule import org.oppia.android.util.logging.LoggerModule import org.oppia.android.util.logging.SyncStatusModule -import org.oppia.android.util.logging.firebase.FirebaseLogUploaderModule import org.oppia.android.util.networking.NetworkConnectionDebugUtilModule import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule import org.oppia.android.util.parser.html.HtmlParserEntityTypeModule @@ -563,7 +562,6 @@ class TopicInfoFragmentTest { ExplorationProgressModule::class, ExplorationStorageModule::class, FakeOppiaClockModule::class, - FirebaseLogUploaderModule::class, FractionInputModule::class, GcsResourceModule::class, HintsAndSolutionConfigModule::class, diff --git a/app/src/sharedTest/java/org/oppia/android/app/topic/lessons/TopicLessonsFragmentTest.kt b/app/src/sharedTest/java/org/oppia/android/app/topic/lessons/TopicLessonsFragmentTest.kt index 1015f8ab63d..48d5ac858e4 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/topic/lessons/TopicLessonsFragmentTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/topic/lessons/TopicLessonsFragmentTest.kt @@ -134,7 +134,6 @@ import org.oppia.android.util.gcsresource.GcsResourceModule import org.oppia.android.util.locale.LocaleProdModule import org.oppia.android.util.logging.LoggerModule import org.oppia.android.util.logging.SyncStatusModule -import org.oppia.android.util.logging.firebase.FirebaseLogUploaderModule import org.oppia.android.util.networking.NetworkConnectionDebugUtilModule import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule import org.oppia.android.util.parser.html.HtmlParserEntityTypeModule @@ -1448,7 +1447,6 @@ class TopicLessonsFragmentTest { ExplorationProgressModule::class, ExplorationStorageTestModule::class, FakeOppiaClockModule::class, - FirebaseLogUploaderModule::class, FractionInputModule::class, GcsResourceModule::class, GlideImageLoaderModule::class, diff --git a/app/src/sharedTest/java/org/oppia/android/app/topic/practice/TopicPracticeFragmentTest.kt b/app/src/sharedTest/java/org/oppia/android/app/topic/practice/TopicPracticeFragmentTest.kt index 22ed2c7d9a8..ca7c78c7e9b 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/topic/practice/TopicPracticeFragmentTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/topic/practice/TopicPracticeFragmentTest.kt @@ -110,7 +110,6 @@ import org.oppia.android.util.gcsresource.GcsResourceModule import org.oppia.android.util.locale.LocaleProdModule import org.oppia.android.util.logging.LoggerModule import org.oppia.android.util.logging.SyncStatusModule -import org.oppia.android.util.logging.firebase.FirebaseLogUploaderModule import org.oppia.android.util.networking.NetworkConnectionDebugUtilModule import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule import org.oppia.android.util.parser.html.HtmlParserEntityTypeModule @@ -546,7 +545,6 @@ class TopicPracticeFragmentTest { ExplorationProgressModule::class, ExplorationStorageModule::class, FakeOppiaClockModule::class, - FirebaseLogUploaderModule::class, FractionInputModule::class, GcsResourceModule::class, GlideImageLoaderModule::class, diff --git a/app/src/sharedTest/java/org/oppia/android/app/topic/questionplayer/QuestionPlayerActivityTest.kt b/app/src/sharedTest/java/org/oppia/android/app/topic/questionplayer/QuestionPlayerActivityTest.kt index 5180c5bd1ac..a87238fefdd 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/topic/questionplayer/QuestionPlayerActivityTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/topic/questionplayer/QuestionPlayerActivityTest.kt @@ -146,7 +146,6 @@ import org.oppia.android.util.locale.LocaleProdModule import org.oppia.android.util.logging.CurrentAppScreenNameIntentDecorator.extractCurrentAppScreenName import org.oppia.android.util.logging.LoggerModule import org.oppia.android.util.logging.SyncStatusModule -import org.oppia.android.util.logging.firebase.FirebaseLogUploaderModule import org.oppia.android.util.networking.NetworkConnectionDebugUtilModule import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule import org.oppia.android.util.parser.html.HtmlParserEntityTypeModule @@ -848,7 +847,6 @@ class QuestionPlayerActivityTest { ExplorationProgressModule::class, ExplorationStorageModule::class, FakeOppiaClockModule::class, - FirebaseLogUploaderModule::class, FractionInputModule::class, GcsResourceModule::class, GlideImageLoaderModule::class, diff --git a/app/src/sharedTest/java/org/oppia/android/app/topic/revision/TopicRevisionFragmentTest.kt b/app/src/sharedTest/java/org/oppia/android/app/topic/revision/TopicRevisionFragmentTest.kt index 4a921432221..bdfc788f76b 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/topic/revision/TopicRevisionFragmentTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/topic/revision/TopicRevisionFragmentTest.kt @@ -112,7 +112,6 @@ import org.oppia.android.util.gcsresource.GcsResourceModule import org.oppia.android.util.locale.LocaleProdModule import org.oppia.android.util.logging.LoggerModule import org.oppia.android.util.logging.SyncStatusModule -import org.oppia.android.util.logging.firebase.FirebaseLogUploaderModule import org.oppia.android.util.networking.NetworkConnectionDebugUtilModule import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule import org.oppia.android.util.parser.html.HtmlParserEntityTypeModule @@ -413,7 +412,6 @@ class TopicRevisionFragmentTest { ExplorationProgressModule::class, ExplorationStorageModule::class, FakeOppiaClockModule::class, - FirebaseLogUploaderModule::class, FractionInputModule::class, GcsResourceModule::class, HintsAndSolutionConfigModule::class, diff --git a/app/src/sharedTest/java/org/oppia/android/app/topic/revisioncard/RevisionCardActivityTest.kt b/app/src/sharedTest/java/org/oppia/android/app/topic/revisioncard/RevisionCardActivityTest.kt index 98a55ffaea0..7936d8e8836 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/topic/revisioncard/RevisionCardActivityTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/topic/revisioncard/RevisionCardActivityTest.kt @@ -100,7 +100,6 @@ import org.oppia.android.util.locale.LocaleProdModule import org.oppia.android.util.logging.CurrentAppScreenNameIntentDecorator.extractCurrentAppScreenName import org.oppia.android.util.logging.LoggerModule import org.oppia.android.util.logging.SyncStatusModule -import org.oppia.android.util.logging.firebase.FirebaseLogUploaderModule import org.oppia.android.util.networking.NetworkConnectionDebugUtilModule import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule import org.oppia.android.util.parser.html.HtmlParserEntityTypeModule @@ -475,7 +474,6 @@ class RevisionCardActivityTest { ExplorationProgressModule::class, ExplorationStorageModule::class, FakeOppiaClockModule::class, - FirebaseLogUploaderModule::class, FractionInputModule::class, GcsResourceModule::class, GlideImageLoaderModule::class, diff --git a/app/src/sharedTest/java/org/oppia/android/app/topic/revisioncard/RevisionCardFragmentTest.kt b/app/src/sharedTest/java/org/oppia/android/app/topic/revisioncard/RevisionCardFragmentTest.kt index a22d5109d68..75400998c9a 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/topic/revisioncard/RevisionCardFragmentTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/topic/revisioncard/RevisionCardFragmentTest.kt @@ -128,7 +128,6 @@ import org.oppia.android.util.gcsresource.GcsResourceModule import org.oppia.android.util.locale.LocaleProdModule import org.oppia.android.util.logging.LoggerModule import org.oppia.android.util.logging.SyncStatusModule -import org.oppia.android.util.logging.firebase.FirebaseLogUploaderModule import org.oppia.android.util.networking.NetworkConnectionDebugUtilModule import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule import org.oppia.android.util.parser.html.HtmlParserEntityTypeModule @@ -934,7 +933,6 @@ class RevisionCardFragmentTest { ExplorationProgressModule::class, ExplorationStorageModule::class, FakeOppiaClockModule::class, - FirebaseLogUploaderModule::class, FractionInputModule::class, GcsResourceModule::class, GlideImageLoaderModule::class, diff --git a/app/src/sharedTest/java/org/oppia/android/app/utility/RatioExtensionsTest.kt b/app/src/sharedTest/java/org/oppia/android/app/utility/RatioExtensionsTest.kt index 753fd55c266..dd4e1fd3bb8 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/utility/RatioExtensionsTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/utility/RatioExtensionsTest.kt @@ -75,7 +75,6 @@ import org.oppia.android.util.gcsresource.GcsResourceModule import org.oppia.android.util.locale.LocaleProdModule import org.oppia.android.util.logging.LoggerModule import org.oppia.android.util.logging.SyncStatusModule -import org.oppia.android.util.logging.firebase.FirebaseLogUploaderModule import org.oppia.android.util.networking.NetworkConnectionDebugUtilModule import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule import org.oppia.android.util.parser.html.HtmlParserEntityTypeModule @@ -167,7 +166,6 @@ class RatioExtensionsTest { ExplorationProgressModule::class, ExplorationStorageModule::class, FakeOppiaClockModule::class, - FirebaseLogUploaderModule::class, FractionInputModule::class, GcsResourceModule::class, GlideImageLoaderModule::class, diff --git a/app/src/sharedTest/java/org/oppia/android/app/walkthrough/WalkthroughActivityTest.kt b/app/src/sharedTest/java/org/oppia/android/app/walkthrough/WalkthroughActivityTest.kt index d53b879387a..8750bbda160 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/walkthrough/WalkthroughActivityTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/walkthrough/WalkthroughActivityTest.kt @@ -89,7 +89,6 @@ import org.oppia.android.util.locale.LocaleProdModule import org.oppia.android.util.logging.CurrentAppScreenNameIntentDecorator.extractCurrentAppScreenName import org.oppia.android.util.logging.LoggerModule import org.oppia.android.util.logging.SyncStatusModule -import org.oppia.android.util.logging.firebase.FirebaseLogUploaderModule import org.oppia.android.util.networking.NetworkConnectionDebugUtilModule import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule import org.oppia.android.util.parser.html.HtmlParserEntityTypeModule @@ -251,7 +250,6 @@ class WalkthroughActivityTest { ExplorationProgressModule::class, ExplorationStorageModule::class, FakeOppiaClockModule::class, - FirebaseLogUploaderModule::class, FractionInputModule::class, GcsResourceModule::class, GlideImageLoaderModule::class, diff --git a/app/src/sharedTest/java/org/oppia/android/app/walkthrough/end/WalkthroughFinalFragmentTest.kt b/app/src/sharedTest/java/org/oppia/android/app/walkthrough/end/WalkthroughFinalFragmentTest.kt index 0d9ce68f605..27dfac4e75b 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/walkthrough/end/WalkthroughFinalFragmentTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/walkthrough/end/WalkthroughFinalFragmentTest.kt @@ -97,7 +97,6 @@ import org.oppia.android.util.gcsresource.GcsResourceModule import org.oppia.android.util.locale.LocaleProdModule import org.oppia.android.util.logging.LoggerModule import org.oppia.android.util.logging.SyncStatusModule -import org.oppia.android.util.logging.firebase.FirebaseLogUploaderModule import org.oppia.android.util.networking.NetworkConnectionDebugUtilModule import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule import org.oppia.android.util.parser.html.HtmlParserEntityTypeModule @@ -330,7 +329,6 @@ class WalkthroughFinalFragmentTest { ExplorationProgressModule::class, ExplorationStorageModule::class, FakeOppiaClockModule::class, - FirebaseLogUploaderModule::class, FractionInputModule::class, GcsResourceModule::class, GlideImageLoaderModule::class, diff --git a/app/src/sharedTest/java/org/oppia/android/app/walkthrough/topiclist/WalkthroughTopicListFragmentTest.kt b/app/src/sharedTest/java/org/oppia/android/app/walkthrough/topiclist/WalkthroughTopicListFragmentTest.kt index 182c9c68c59..717addb2be5 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/walkthrough/topiclist/WalkthroughTopicListFragmentTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/walkthrough/topiclist/WalkthroughTopicListFragmentTest.kt @@ -96,7 +96,6 @@ import org.oppia.android.util.gcsresource.GcsResourceModule import org.oppia.android.util.locale.LocaleProdModule import org.oppia.android.util.logging.LoggerModule import org.oppia.android.util.logging.SyncStatusModule -import org.oppia.android.util.logging.firebase.FirebaseLogUploaderModule import org.oppia.android.util.networking.NetworkConnectionDebugUtilModule import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule import org.oppia.android.util.parser.html.HtmlParserEntityTypeModule @@ -306,7 +305,6 @@ class WalkthroughTopicListFragmentTest { ExplorationProgressModule::class, ExplorationStorageModule::class, FakeOppiaClockModule::class, - FirebaseLogUploaderModule::class, FractionInputModule::class, GcsResourceModule::class, HintsAndSolutionConfigModule::class, diff --git a/app/src/sharedTest/java/org/oppia/android/app/walkthrough/welcome/WalkthroughWelcomeFragmentTest.kt b/app/src/sharedTest/java/org/oppia/android/app/walkthrough/welcome/WalkthroughWelcomeFragmentTest.kt index 829b50ea9a7..fc2faa04ac1 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/walkthrough/welcome/WalkthroughWelcomeFragmentTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/walkthrough/welcome/WalkthroughWelcomeFragmentTest.kt @@ -88,7 +88,6 @@ import org.oppia.android.util.gcsresource.GcsResourceModule import org.oppia.android.util.locale.LocaleProdModule import org.oppia.android.util.logging.LoggerModule import org.oppia.android.util.logging.SyncStatusModule -import org.oppia.android.util.logging.firebase.FirebaseLogUploaderModule import org.oppia.android.util.networking.NetworkConnectionDebugUtilModule import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule import org.oppia.android.util.parser.html.HtmlParserEntityTypeModule @@ -219,7 +218,6 @@ class WalkthroughWelcomeFragmentTest { ExplorationProgressModule::class, ExplorationStorageModule::class, FakeOppiaClockModule::class, - FirebaseLogUploaderModule::class, FractionInputModule::class, GcsResourceModule::class, GlideImageLoaderModule::class, diff --git a/app/src/test/java/org/oppia/android/app/activity/ActivityIntentFactoriesTest.kt b/app/src/test/java/org/oppia/android/app/activity/ActivityIntentFactoriesTest.kt index e36a1cbd1ca..390ca929d28 100644 --- a/app/src/test/java/org/oppia/android/app/activity/ActivityIntentFactoriesTest.kt +++ b/app/src/test/java/org/oppia/android/app/activity/ActivityIntentFactoriesTest.kt @@ -79,7 +79,6 @@ import org.oppia.android.util.gcsresource.GcsResourceModule import org.oppia.android.util.locale.LocaleProdModule import org.oppia.android.util.logging.LoggerModule import org.oppia.android.util.logging.SyncStatusModule -import org.oppia.android.util.logging.firebase.FirebaseLogUploaderModule import org.oppia.android.util.networking.NetworkConnectionDebugUtilModule import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule import org.oppia.android.util.parser.html.HtmlParserEntityTypeModule @@ -203,7 +202,6 @@ class ActivityIntentFactoriesTest { ExplorationProgressModule::class, ExplorationStorageModule::class, FakeOppiaClockModule::class, - FirebaseLogUploaderModule::class, FractionInputModule::class, GcsResourceModule::class, GlideImageLoaderModule::class, diff --git a/app/src/test/java/org/oppia/android/app/activity/PlatformParameterInitializationIntegrationTest.kt b/app/src/test/java/org/oppia/android/app/activity/PlatformParameterInitializationIntegrationTest.kt index 6be93f13419..719e1941f4b 100644 --- a/app/src/test/java/org/oppia/android/app/activity/PlatformParameterInitializationIntegrationTest.kt +++ b/app/src/test/java/org/oppia/android/app/activity/PlatformParameterInitializationIntegrationTest.kt @@ -86,7 +86,6 @@ import org.oppia.android.util.gcsresource.GcsResourceModule import org.oppia.android.util.locale.LocaleProdModule import org.oppia.android.util.logging.LoggerModule import org.oppia.android.util.logging.SyncStatusModule -import org.oppia.android.util.logging.firebase.FirebaseLogUploaderModule import org.oppia.android.util.networking.NetworkConnectionDebugUtilModule import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule import org.oppia.android.util.parser.html.HtmlParserEntityTypeModule @@ -266,7 +265,6 @@ class PlatformParameterInitializationIntegrationTest { ExplorationProgressModule::class, ExplorationStorageModule::class, FakeOppiaClockModule::class, - FirebaseLogUploaderModule::class, FractionInputModule::class, GcsResourceModule::class, GlideImageLoaderModule::class, diff --git a/app/src/test/java/org/oppia/android/app/activity/route/ActivityRouterModuleTest.kt b/app/src/test/java/org/oppia/android/app/activity/route/ActivityRouterModuleTest.kt index a9bfc9631dc..80bf01e486b 100644 --- a/app/src/test/java/org/oppia/android/app/activity/route/ActivityRouterModuleTest.kt +++ b/app/src/test/java/org/oppia/android/app/activity/route/ActivityRouterModuleTest.kt @@ -75,7 +75,6 @@ import org.oppia.android.util.gcsresource.GcsResourceModule import org.oppia.android.util.locale.LocaleProdModule import org.oppia.android.util.logging.LoggerModule import org.oppia.android.util.logging.SyncStatusModule -import org.oppia.android.util.logging.firebase.FirebaseLogUploaderModule import org.oppia.android.util.networking.NetworkConnectionDebugUtilModule import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule import org.oppia.android.util.parser.html.HtmlParserEntityTypeModule @@ -143,7 +142,6 @@ class ActivityRouterModuleTest { ExplorationProgressModule::class, ExplorationStorageModule::class, FakeOppiaClockModule::class, - FirebaseLogUploaderModule::class, FractionInputModule::class, GcsResourceModule::class, GlideImageLoaderModule::class, diff --git a/app/src/test/java/org/oppia/android/app/activity/route/ActivityRouterTest.kt b/app/src/test/java/org/oppia/android/app/activity/route/ActivityRouterTest.kt index 7b2a2d1787e..da3ccc86dcb 100644 --- a/app/src/test/java/org/oppia/android/app/activity/route/ActivityRouterTest.kt +++ b/app/src/test/java/org/oppia/android/app/activity/route/ActivityRouterTest.kt @@ -85,7 +85,6 @@ import org.oppia.android.util.gcsresource.GcsResourceModule import org.oppia.android.util.locale.LocaleProdModule import org.oppia.android.util.logging.LoggerModule import org.oppia.android.util.logging.SyncStatusModule -import org.oppia.android.util.logging.firebase.FirebaseLogUploaderModule import org.oppia.android.util.networking.NetworkConnectionDebugUtilModule import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule import org.oppia.android.util.parser.html.HtmlParserEntityTypeModule @@ -198,7 +197,6 @@ class ActivityRouterTest { ExplorationProgressModule::class, ExplorationStorageModule::class, FakeOppiaClockModule::class, - FirebaseLogUploaderModule::class, FractionInputModule::class, GcsResourceModule::class, GlideImageLoaderModule::class, diff --git a/app/src/test/java/org/oppia/android/app/administratorcontrols/AdministratorControlsFragmentTest.kt b/app/src/test/java/org/oppia/android/app/administratorcontrols/AdministratorControlsFragmentTest.kt index 0fc9edbed24..a10f0dbf17c 100644 --- a/app/src/test/java/org/oppia/android/app/administratorcontrols/AdministratorControlsFragmentTest.kt +++ b/app/src/test/java/org/oppia/android/app/administratorcontrols/AdministratorControlsFragmentTest.kt @@ -84,7 +84,6 @@ import org.oppia.android.util.gcsresource.GcsResourceModule import org.oppia.android.util.locale.LocaleProdModule import org.oppia.android.util.logging.LoggerModule import org.oppia.android.util.logging.SyncStatusModule -import org.oppia.android.util.logging.firebase.FirebaseLogUploaderModule import org.oppia.android.util.networking.NetworkConnectionDebugUtilModule import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule import org.oppia.android.util.parser.html.HtmlParserEntityTypeModule @@ -200,7 +199,6 @@ class AdministratorControlsFragmentTest { ExplorationProgressModule::class, ExplorationStorageModule::class, FakeOppiaClockModule::class, - FirebaseLogUploaderModule::class, FractionInputModule::class, GcsResourceModule::class, GlideImageLoaderModule::class, diff --git a/app/src/test/java/org/oppia/android/app/home/HomeActivityLocalTest.kt b/app/src/test/java/org/oppia/android/app/home/HomeActivityLocalTest.kt index ecebe808cb2..117bc542e50 100644 --- a/app/src/test/java/org/oppia/android/app/home/HomeActivityLocalTest.kt +++ b/app/src/test/java/org/oppia/android/app/home/HomeActivityLocalTest.kt @@ -86,7 +86,6 @@ import org.oppia.android.util.gcsresource.GcsResourceModule import org.oppia.android.util.locale.LocaleProdModule import org.oppia.android.util.logging.LoggerModule import org.oppia.android.util.logging.SyncStatusModule -import org.oppia.android.util.logging.firebase.FirebaseLogUploaderModule import org.oppia.android.util.networking.NetworkConnectionDebugUtilModule import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule import org.oppia.android.util.parser.html.HtmlParserEntityTypeModule @@ -380,7 +379,6 @@ class HomeActivityLocalTest { ExplorationProgressModule::class, ExplorationStorageModule::class, FakeOppiaClockModule::class, - FirebaseLogUploaderModule::class, FractionInputModule::class, GcsResourceModule::class, GlideImageLoaderModule::class, diff --git a/app/src/test/java/org/oppia/android/app/options/OptionsFragmentTest.kt b/app/src/test/java/org/oppia/android/app/options/OptionsFragmentTest.kt index dc06170a333..84acc644368 100644 --- a/app/src/test/java/org/oppia/android/app/options/OptionsFragmentTest.kt +++ b/app/src/test/java/org/oppia/android/app/options/OptionsFragmentTest.kt @@ -82,7 +82,6 @@ import org.oppia.android.util.gcsresource.GcsResourceModule import org.oppia.android.util.locale.LocaleProdModule import org.oppia.android.util.logging.LoggerModule import org.oppia.android.util.logging.SyncStatusModule -import org.oppia.android.util.logging.firebase.FirebaseLogUploaderModule import org.oppia.android.util.networking.NetworkConnectionDebugUtilModule import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule import org.oppia.android.util.parser.html.HtmlParserEntityTypeModule @@ -270,7 +269,6 @@ class OptionsFragmentTest { ExplorationProgressModule::class, ExplorationStorageModule::class, FakeOppiaClockModule::class, - FirebaseLogUploaderModule::class, FractionInputModule::class, GcsResourceModule::class, GlideImageLoaderModule::class, diff --git a/app/src/test/java/org/oppia/android/app/parser/FractionParsingUiErrorTest.kt b/app/src/test/java/org/oppia/android/app/parser/FractionParsingUiErrorTest.kt index da342a61488..c1213f406a9 100644 --- a/app/src/test/java/org/oppia/android/app/parser/FractionParsingUiErrorTest.kt +++ b/app/src/test/java/org/oppia/android/app/parser/FractionParsingUiErrorTest.kt @@ -73,7 +73,6 @@ import org.oppia.android.util.gcsresource.GcsResourceModule import org.oppia.android.util.locale.LocaleProdModule import org.oppia.android.util.logging.LoggerModule import org.oppia.android.util.logging.SyncStatusModule -import org.oppia.android.util.logging.firebase.FirebaseLogUploaderModule import org.oppia.android.util.math.FractionParser import org.oppia.android.util.math.FractionParser.FractionParsingError import org.oppia.android.util.networking.NetworkConnectionDebugUtilModule @@ -295,7 +294,6 @@ class FractionParsingUiErrorTest { ExplorationProgressModule::class, ExplorationStorageModule::class, FakeOppiaClockModule::class, - FirebaseLogUploaderModule::class, FractionInputModule::class, GcsResourceModule::class, GlideImageLoaderModule::class, diff --git a/app/src/test/java/org/oppia/android/app/parser/StringToRatioParserTest.kt b/app/src/test/java/org/oppia/android/app/parser/StringToRatioParserTest.kt index bb15da74414..d4b8c4ddbd4 100644 --- a/app/src/test/java/org/oppia/android/app/parser/StringToRatioParserTest.kt +++ b/app/src/test/java/org/oppia/android/app/parser/StringToRatioParserTest.kt @@ -75,7 +75,6 @@ import org.oppia.android.util.gcsresource.GcsResourceModule import org.oppia.android.util.locale.LocaleProdModule import org.oppia.android.util.logging.LoggerModule import org.oppia.android.util.logging.SyncStatusModule -import org.oppia.android.util.logging.firebase.FirebaseLogUploaderModule import org.oppia.android.util.networking.NetworkConnectionDebugUtilModule import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule import org.oppia.android.util.parser.html.HtmlParserEntityTypeModule @@ -297,7 +296,6 @@ class StringToRatioParserTest { ExplorationProgressModule::class, ExplorationStorageModule::class, FakeOppiaClockModule::class, - FirebaseLogUploaderModule::class, FractionInputModule::class, GcsResourceModule::class, GlideImageLoaderModule::class, diff --git a/app/src/test/java/org/oppia/android/app/player/exploration/ExplorationActivityLocalTest.kt b/app/src/test/java/org/oppia/android/app/player/exploration/ExplorationActivityLocalTest.kt index 25d199cf701..73aa54be80f 100644 --- a/app/src/test/java/org/oppia/android/app/player/exploration/ExplorationActivityLocalTest.kt +++ b/app/src/test/java/org/oppia/android/app/player/exploration/ExplorationActivityLocalTest.kt @@ -103,7 +103,6 @@ import org.oppia.android.util.gcsresource.GcsResourceModule import org.oppia.android.util.locale.LocaleProdModule import org.oppia.android.util.logging.LoggerModule import org.oppia.android.util.logging.SyncStatusModule -import org.oppia.android.util.logging.firebase.FirebaseLogUploaderModule import org.oppia.android.util.networking.NetworkConnectionDebugUtilModule import org.oppia.android.util.networking.NetworkConnectionUtil import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule @@ -177,7 +176,7 @@ class ExplorationActivityLocalTest { ) ).use { testCoroutineDispatchers.runCurrent() - val event = fakeAnalyticsEventLogger.getMostRecentEvent() + val event = fakeAnalyticsEventLogger.getOldestEvent() assertThat(event.context.activityContextCase).isEqualTo(OPEN_EXPLORATION_ACTIVITY) assertThat(event.priority).isEqualTo(EventLog.Priority.ESSENTIAL) @@ -550,7 +549,6 @@ class ExplorationActivityLocalTest { ExplorationProgressModule::class, ExplorationStorageModule::class, FakeOppiaClockModule::class, - FirebaseLogUploaderModule::class, FractionInputModule::class, GcsResourceModule::class, GlideImageLoaderModule::class, diff --git a/app/src/test/java/org/oppia/android/app/player/state/StateFragmentLocalTest.kt b/app/src/test/java/org/oppia/android/app/player/state/StateFragmentLocalTest.kt index 5ab762e6c9c..30801538e14 100644 --- a/app/src/test/java/org/oppia/android/app/player/state/StateFragmentLocalTest.kt +++ b/app/src/test/java/org/oppia/android/app/player/state/StateFragmentLocalTest.kt @@ -162,7 +162,6 @@ import org.oppia.android.util.gcsresource.GcsResourceModule import org.oppia.android.util.locale.LocaleProdModule import org.oppia.android.util.logging.LoggerModule import org.oppia.android.util.logging.SyncStatusModule -import org.oppia.android.util.logging.firebase.FirebaseLogUploaderModule import org.oppia.android.util.networking.NetworkConnectionDebugUtilModule import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule import org.oppia.android.util.parser.html.HtmlParserEntityTypeModule @@ -3020,7 +3019,6 @@ class StateFragmentLocalTest { ExplorationProgressModule::class, ExplorationStorageModule::class, FakeOppiaClockModule::class, - FirebaseLogUploaderModule::class, FractionInputModule::class, GcsResourceModule::class, HintsAndSolutionConfigModule::class, diff --git a/app/src/test/java/org/oppia/android/app/profile/ProfileChooserFragmentLocalTest.kt b/app/src/test/java/org/oppia/android/app/profile/ProfileChooserFragmentLocalTest.kt index 984e1c86c0a..9457fd246ce 100644 --- a/app/src/test/java/org/oppia/android/app/profile/ProfileChooserFragmentLocalTest.kt +++ b/app/src/test/java/org/oppia/android/app/profile/ProfileChooserFragmentLocalTest.kt @@ -81,7 +81,6 @@ import org.oppia.android.util.gcsresource.GcsResourceModule import org.oppia.android.util.locale.LocaleProdModule import org.oppia.android.util.logging.LoggerModule import org.oppia.android.util.logging.SyncStatusModule -import org.oppia.android.util.logging.firebase.FirebaseLogUploaderModule import org.oppia.android.util.networking.NetworkConnectionDebugUtilModule import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule import org.oppia.android.util.parser.html.HtmlParserEntityTypeModule @@ -247,7 +246,6 @@ class ProfileChooserFragmentLocalTest { ExplorationProgressModule::class, ExplorationStorageModule::class, FakeOppiaClockModule::class, - FirebaseLogUploaderModule::class, FractionInputModule::class, GcsResourceModule::class, GlideImageLoaderModule::class, diff --git a/app/src/test/java/org/oppia/android/app/story/StoryActivityLocalTest.kt b/app/src/test/java/org/oppia/android/app/story/StoryActivityLocalTest.kt index aff631b9047..c3b99efa9ca 100644 --- a/app/src/test/java/org/oppia/android/app/story/StoryActivityLocalTest.kt +++ b/app/src/test/java/org/oppia/android/app/story/StoryActivityLocalTest.kt @@ -77,7 +77,6 @@ import org.oppia.android.util.gcsresource.GcsResourceModule import org.oppia.android.util.locale.LocaleProdModule import org.oppia.android.util.logging.LoggerModule import org.oppia.android.util.logging.SyncStatusModule -import org.oppia.android.util.logging.firebase.FirebaseLogUploaderModule import org.oppia.android.util.networking.NetworkConnectionDebugUtilModule import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule import org.oppia.android.util.parser.html.HtmlParserEntityTypeModule @@ -173,7 +172,6 @@ class StoryActivityLocalTest { ExplorationProgressModule::class, ExplorationStorageModule::class, FakeOppiaClockModule::class, - FirebaseLogUploaderModule::class, FractionInputModule::class, GcsResourceModule::class, GlideImageLoaderModule::class, diff --git a/app/src/test/java/org/oppia/android/app/testing/CompletedStoryListSpanTest.kt b/app/src/test/java/org/oppia/android/app/testing/CompletedStoryListSpanTest.kt index d1bba286b78..13a9c043f40 100644 --- a/app/src/test/java/org/oppia/android/app/testing/CompletedStoryListSpanTest.kt +++ b/app/src/test/java/org/oppia/android/app/testing/CompletedStoryListSpanTest.kt @@ -77,7 +77,6 @@ import org.oppia.android.util.gcsresource.GcsResourceModule import org.oppia.android.util.locale.LocaleProdModule import org.oppia.android.util.logging.LoggerModule import org.oppia.android.util.logging.SyncStatusModule -import org.oppia.android.util.logging.firebase.FirebaseLogUploaderModule import org.oppia.android.util.networking.NetworkConnectionDebugUtilModule import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule import org.oppia.android.util.parser.html.HtmlParserEntityTypeModule @@ -177,7 +176,6 @@ class CompletedStoryListSpanTest { ExplorationProgressModule::class, ExplorationStorageModule::class, FakeOppiaClockModule::class, - FirebaseLogUploaderModule::class, FractionInputModule::class, GcsResourceModule::class, GlideImageLoaderModule::class, diff --git a/app/src/test/java/org/oppia/android/app/testing/HomeSpanTest.kt b/app/src/test/java/org/oppia/android/app/testing/HomeSpanTest.kt index 628893d980b..9f05654ddea 100644 --- a/app/src/test/java/org/oppia/android/app/testing/HomeSpanTest.kt +++ b/app/src/test/java/org/oppia/android/app/testing/HomeSpanTest.kt @@ -78,7 +78,6 @@ import org.oppia.android.util.gcsresource.GcsResourceModule import org.oppia.android.util.locale.LocaleProdModule import org.oppia.android.util.logging.LoggerModule import org.oppia.android.util.logging.SyncStatusModule -import org.oppia.android.util.logging.firebase.FirebaseLogUploaderModule import org.oppia.android.util.networking.NetworkConnectionDebugUtilModule import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule import org.oppia.android.util.parser.html.HtmlParserEntityTypeModule @@ -193,7 +192,6 @@ class HomeSpanTest { ExplorationProgressModule::class, ExplorationStorageModule::class, FakeOppiaClockModule::class, - FirebaseLogUploaderModule::class, FractionInputModule::class, GcsResourceModule::class, GlideImageLoaderModule::class, diff --git a/app/src/test/java/org/oppia/android/app/testing/OngoingTopicListSpanTest.kt b/app/src/test/java/org/oppia/android/app/testing/OngoingTopicListSpanTest.kt index 76eed12b19a..cfba9f2c60d 100644 --- a/app/src/test/java/org/oppia/android/app/testing/OngoingTopicListSpanTest.kt +++ b/app/src/test/java/org/oppia/android/app/testing/OngoingTopicListSpanTest.kt @@ -78,7 +78,6 @@ import org.oppia.android.util.gcsresource.GcsResourceModule import org.oppia.android.util.locale.LocaleProdModule import org.oppia.android.util.logging.LoggerModule import org.oppia.android.util.logging.SyncStatusModule -import org.oppia.android.util.logging.firebase.FirebaseLogUploaderModule import org.oppia.android.util.networking.NetworkConnectionDebugUtilModule import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule import org.oppia.android.util.parser.html.HtmlParserEntityTypeModule @@ -188,7 +187,6 @@ class OngoingTopicListSpanTest { ExplorationProgressModule::class, ExplorationStorageModule::class, FakeOppiaClockModule::class, - FirebaseLogUploaderModule::class, FractionInputModule::class, GcsResourceModule::class, GlideImageLoaderModule::class, diff --git a/app/src/test/java/org/oppia/android/app/testing/PlatformParameterIntegrationDebugTest.kt b/app/src/test/java/org/oppia/android/app/testing/PlatformParameterIntegrationDebugTest.kt index 2a255bc1315..85cf391031a 100644 --- a/app/src/test/java/org/oppia/android/app/testing/PlatformParameterIntegrationDebugTest.kt +++ b/app/src/test/java/org/oppia/android/app/testing/PlatformParameterIntegrationDebugTest.kt @@ -6,9 +6,6 @@ import androidx.appcompat.app.AppCompatActivity import androidx.test.core.app.ActivityScenario.launch import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 -import androidx.work.Configuration -import androidx.work.impl.utils.SynchronousExecutor -import androidx.work.testing.WorkManagerTestInitHelper import com.google.common.truth.Truth.assertThat import dagger.Component import dagger.Module @@ -82,7 +79,6 @@ import org.oppia.android.domain.platformparameter.PlatformParameterControllerDeb import org.oppia.android.domain.platformparameter.PlatformParameterControllerProdImpl import org.oppia.android.domain.platformparameter.PlatformParameterProcessState import org.oppia.android.domain.platformparameter.PlatformParameterSingletonModule -import org.oppia.android.domain.platformparameter.syncup.PlatformParameterSyncUpWorkerFactory import org.oppia.android.domain.platformparameter.testing.TestPlatformParameterConfigRetriever import org.oppia.android.domain.question.QuestionModule import org.oppia.android.domain.workmanager.WorkManagerConfigurationModule @@ -101,7 +97,6 @@ import org.oppia.android.util.gcsresource.GcsResourceModule import org.oppia.android.util.locale.LocaleProdModule import org.oppia.android.util.logging.LoggerModule import org.oppia.android.util.logging.SyncStatusModule -import org.oppia.android.util.logging.firebase.FirebaseLogUploaderModule import org.oppia.android.util.networking.NetworkConnectionDebugUtilModule import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule import org.oppia.android.util.parser.html.HtmlParserEntityTypeModule @@ -122,7 +117,6 @@ class PlatformParameterIntegrationDebugTest { @get:Rule val initializeDefaultLocaleRule = InitializeDefaultLocaleRule() @Inject lateinit var context: Context @Inject lateinit var testCoroutineDispatchers: TestCoroutineDispatchers - @Inject lateinit var platformParameterSyncUpWorkerFactory: PlatformParameterSyncUpWorkerFactory @Inject lateinit var platformParameterController: PlatformParameterController private companion object { @@ -208,13 +202,8 @@ class PlatformParameterIntegrationDebugTest { @Suppress("DeferredResultUnused") private fun setUpTestEnvironment() { setUpTestApplicationComponent() - val config = Configuration.Builder() - .setExecutor(SynchronousExecutor()) - .setWorkerFactory(platformParameterSyncUpWorkerFactory) - .build() platformParameterController.loadParametersAsync() testCoroutineDispatchers.runCurrent() - WorkManagerTestInitHelper.initializeTestWorkManager(context, config) TestActivity.registerWithPackageManager(context) } @@ -388,7 +377,6 @@ class PlatformParameterIntegrationDebugTest { ExplorationProgressModule::class, ExplorationStorageModule::class, FakeOppiaClockModule::class, - FirebaseLogUploaderModule::class, FractionInputModule::class, GcsResourceModule::class, GlideImageLoaderModule::class, diff --git a/app/src/test/java/org/oppia/android/app/testing/PlatformParameterIntegrationTest.kt b/app/src/test/java/org/oppia/android/app/testing/PlatformParameterIntegrationTest.kt index e6646e60f03..a0bd3cb72b4 100644 --- a/app/src/test/java/org/oppia/android/app/testing/PlatformParameterIntegrationTest.kt +++ b/app/src/test/java/org/oppia/android/app/testing/PlatformParameterIntegrationTest.kt @@ -6,9 +6,6 @@ import androidx.appcompat.app.AppCompatActivity import androidx.test.core.app.ActivityScenario.launch import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 -import androidx.work.Configuration -import androidx.work.impl.utils.SynchronousExecutor -import androidx.work.testing.WorkManagerTestInitHelper import com.google.common.truth.Truth.assertThat import dagger.Component import org.junit.After @@ -60,7 +57,6 @@ import org.oppia.android.domain.oppialogger.analytics.CpuPerformanceSnapshotterM import org.oppia.android.domain.oppialogger.logscheduler.MetricLogSchedulerModule import org.oppia.android.domain.oppialogger.loguploader.LogReportWorkerModule import org.oppia.android.domain.platformparameter.PlatformParameterSingletonModule -import org.oppia.android.domain.platformparameter.syncup.PlatformParameterSyncUpWorkerFactory import org.oppia.android.domain.question.QuestionModule import org.oppia.android.domain.workmanager.WorkManagerConfigurationModule import org.oppia.android.testing.TestLogReportingModule @@ -78,7 +74,6 @@ import org.oppia.android.util.gcsresource.GcsResourceModule import org.oppia.android.util.locale.LocaleProdModule import org.oppia.android.util.logging.LoggerModule import org.oppia.android.util.logging.SyncStatusModule -import org.oppia.android.util.logging.firebase.FirebaseLogUploaderModule import org.oppia.android.util.networking.NetworkConnectionDebugUtilModule import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule import org.oppia.android.util.parser.html.HtmlParserEntityTypeModule @@ -99,17 +94,11 @@ class PlatformParameterIntegrationTest { @Inject lateinit var context: Context @Inject lateinit var testCoroutineDispatchers: TestCoroutineDispatchers - @Inject lateinit var platformParameterSyncUpWorkerFactory: PlatformParameterSyncUpWorkerFactory @Before fun setUp() { setUpTestApplicationComponent() testCoroutineDispatchers.registerIdlingResource() - val config = Configuration.Builder() - .setExecutor(SynchronousExecutor()) - .setWorkerFactory(platformParameterSyncUpWorkerFactory) - .build() - WorkManagerTestInitHelper.initializeTestWorkManager(context, config) TestActivity.registerWithPackageManager(context) } @@ -160,7 +149,6 @@ class PlatformParameterIntegrationTest { ExplorationProgressModule::class, ExplorationStorageModule::class, FakeOppiaClockModule::class, - FirebaseLogUploaderModule::class, FractionInputModule::class, GcsResourceModule::class, GlideImageLoaderModule::class, diff --git a/app/src/test/java/org/oppia/android/app/testing/ProfileChooserSpanTest.kt b/app/src/test/java/org/oppia/android/app/testing/ProfileChooserSpanTest.kt index d4f88045ebe..f7c11756d54 100644 --- a/app/src/test/java/org/oppia/android/app/testing/ProfileChooserSpanTest.kt +++ b/app/src/test/java/org/oppia/android/app/testing/ProfileChooserSpanTest.kt @@ -77,7 +77,6 @@ import org.oppia.android.util.gcsresource.GcsResourceModule import org.oppia.android.util.locale.LocaleProdModule import org.oppia.android.util.logging.LoggerModule import org.oppia.android.util.logging.SyncStatusModule -import org.oppia.android.util.logging.firebase.FirebaseLogUploaderModule import org.oppia.android.util.networking.NetworkConnectionDebugUtilModule import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule import org.oppia.android.util.parser.html.HtmlParserEntityTypeModule @@ -390,7 +389,6 @@ class ProfileChooserSpanTest { ExplorationProgressModule::class, ExplorationStorageModule::class, FakeOppiaClockModule::class, - FirebaseLogUploaderModule::class, FractionInputModule::class, GcsResourceModule::class, GlideImageLoaderModule::class, diff --git a/app/src/test/java/org/oppia/android/app/testing/ProfileProgressSpanCountTest.kt b/app/src/test/java/org/oppia/android/app/testing/ProfileProgressSpanCountTest.kt index 4aac2a2ea12..9f322f991e3 100644 --- a/app/src/test/java/org/oppia/android/app/testing/ProfileProgressSpanCountTest.kt +++ b/app/src/test/java/org/oppia/android/app/testing/ProfileProgressSpanCountTest.kt @@ -77,7 +77,6 @@ import org.oppia.android.util.gcsresource.GcsResourceModule import org.oppia.android.util.locale.LocaleProdModule import org.oppia.android.util.logging.LoggerModule import org.oppia.android.util.logging.SyncStatusModule -import org.oppia.android.util.logging.firebase.FirebaseLogUploaderModule import org.oppia.android.util.networking.NetworkConnectionDebugUtilModule import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule import org.oppia.android.util.parser.html.HtmlParserEntityTypeModule @@ -174,7 +173,6 @@ class ProfileProgressSpanCountTest { ExplorationProgressModule::class, ExplorationStorageModule::class, FakeOppiaClockModule::class, - FirebaseLogUploaderModule::class, FractionInputModule::class, GcsResourceModule::class, GlideImageLoaderModule::class, diff --git a/app/src/test/java/org/oppia/android/app/testing/RecentlyPlayedSpanTest.kt b/app/src/test/java/org/oppia/android/app/testing/RecentlyPlayedSpanTest.kt index 10b51d591e7..ecd86ca63bd 100644 --- a/app/src/test/java/org/oppia/android/app/testing/RecentlyPlayedSpanTest.kt +++ b/app/src/test/java/org/oppia/android/app/testing/RecentlyPlayedSpanTest.kt @@ -83,7 +83,6 @@ import org.oppia.android.util.gcsresource.GcsResourceModule import org.oppia.android.util.locale.LocaleProdModule import org.oppia.android.util.logging.LoggerModule import org.oppia.android.util.logging.SyncStatusModule -import org.oppia.android.util.logging.firebase.FirebaseLogUploaderModule import org.oppia.android.util.networking.NetworkConnectionDebugUtilModule import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule import org.oppia.android.util.parser.html.HtmlParserEntityTypeModule @@ -315,7 +314,6 @@ class RecentlyPlayedSpanTest { ExplorationProgressModule::class, ExplorationStorageModule::class, FakeOppiaClockModule::class, - FirebaseLogUploaderModule::class, FractionInputModule::class, GcsResourceModule::class, GlideImageLoaderModule::class, diff --git a/app/src/test/java/org/oppia/android/app/testing/TopicRevisionSpanTest.kt b/app/src/test/java/org/oppia/android/app/testing/TopicRevisionSpanTest.kt index 6db759defbf..04e29232929 100644 --- a/app/src/test/java/org/oppia/android/app/testing/TopicRevisionSpanTest.kt +++ b/app/src/test/java/org/oppia/android/app/testing/TopicRevisionSpanTest.kt @@ -76,7 +76,6 @@ import org.oppia.android.util.gcsresource.GcsResourceModule import org.oppia.android.util.locale.LocaleProdModule import org.oppia.android.util.logging.LoggerModule import org.oppia.android.util.logging.SyncStatusModule -import org.oppia.android.util.logging.firebase.FirebaseLogUploaderModule import org.oppia.android.util.networking.NetworkConnectionDebugUtilModule import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule import org.oppia.android.util.parser.html.HtmlParserEntityTypeModule @@ -174,7 +173,6 @@ class TopicRevisionSpanTest { ExplorationProgressModule::class, ExplorationStorageModule::class, FakeOppiaClockModule::class, - FirebaseLogUploaderModule::class, FractionInputModule::class, GcsResourceModule::class, GlideImageLoaderModule::class, diff --git a/app/src/test/java/org/oppia/android/app/testing/activity/TestActivityTest.kt b/app/src/test/java/org/oppia/android/app/testing/activity/TestActivityTest.kt index f9fc67462d6..2c09ff1501f 100644 --- a/app/src/test/java/org/oppia/android/app/testing/activity/TestActivityTest.kt +++ b/app/src/test/java/org/oppia/android/app/testing/activity/TestActivityTest.kt @@ -76,7 +76,6 @@ import org.oppia.android.util.gcsresource.GcsResourceModule import org.oppia.android.util.locale.LocaleProdModule import org.oppia.android.util.logging.LoggerModule import org.oppia.android.util.logging.SyncStatusModule -import org.oppia.android.util.logging.firebase.FirebaseLogUploaderModule import org.oppia.android.util.networking.NetworkConnectionDebugUtilModule import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule import org.oppia.android.util.parser.html.HtmlParserEntityTypeModule @@ -253,7 +252,6 @@ class TestActivityTest { ExplorationProgressModule::class, ExplorationStorageModule::class, FakeOppiaClockModule::class, - FirebaseLogUploaderModule::class, FractionInputModule::class, GcsResourceModule::class, GlideImageLoaderModule::class, diff --git a/app/src/test/java/org/oppia/android/app/testing/player/split/PlayerSplitScreenTest.kt b/app/src/test/java/org/oppia/android/app/testing/player/split/PlayerSplitScreenTest.kt index c85e4b6d63c..f966d0d27ed 100644 --- a/app/src/test/java/org/oppia/android/app/testing/player/split/PlayerSplitScreenTest.kt +++ b/app/src/test/java/org/oppia/android/app/testing/player/split/PlayerSplitScreenTest.kt @@ -74,7 +74,6 @@ import org.oppia.android.util.gcsresource.GcsResourceModule import org.oppia.android.util.locale.LocaleProdModule import org.oppia.android.util.logging.LoggerModule import org.oppia.android.util.logging.SyncStatusModule -import org.oppia.android.util.logging.firebase.FirebaseLogUploaderModule import org.oppia.android.util.networking.NetworkConnectionDebugUtilModule import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule import org.oppia.android.util.parser.html.HtmlParserEntityTypeModule @@ -207,7 +206,6 @@ class PlayerSplitScreenTest { ExplorationProgressModule::class, ExplorationStorageModule::class, FakeOppiaClockModule::class, - FirebaseLogUploaderModule::class, FractionInputModule::class, GcsResourceModule::class, GlideImageLoaderModule::class, diff --git a/app/src/test/java/org/oppia/android/app/testing/player/state/StateFragmentAccessibilityTest.kt b/app/src/test/java/org/oppia/android/app/testing/player/state/StateFragmentAccessibilityTest.kt index e21a2cf2f2d..72067e60170 100644 --- a/app/src/test/java/org/oppia/android/app/testing/player/state/StateFragmentAccessibilityTest.kt +++ b/app/src/test/java/org/oppia/android/app/testing/player/state/StateFragmentAccessibilityTest.kt @@ -87,7 +87,6 @@ import org.oppia.android.util.gcsresource.GcsResourceModule import org.oppia.android.util.locale.LocaleProdModule import org.oppia.android.util.logging.LoggerModule import org.oppia.android.util.logging.SyncStatusModule -import org.oppia.android.util.logging.firebase.FirebaseLogUploaderModule import org.oppia.android.util.networking.NetworkConnectionDebugUtilModule import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule import org.oppia.android.util.parser.html.HtmlParserEntityTypeModule @@ -216,7 +215,6 @@ class StateFragmentAccessibilityTest { ExplorationProgressModule::class, ExplorationStorageModule::class, FakeOppiaClockModule::class, - FirebaseLogUploaderModule::class, FractionInputModule::class, GcsResourceModule::class, GlideImageLoaderModule::class, diff --git a/app/src/test/java/org/oppia/android/app/topic/info/TopicInfoFragmentLocalTest.kt b/app/src/test/java/org/oppia/android/app/topic/info/TopicInfoFragmentLocalTest.kt index ba7befee5c9..e37a5266f25 100644 --- a/app/src/test/java/org/oppia/android/app/topic/info/TopicInfoFragmentLocalTest.kt +++ b/app/src/test/java/org/oppia/android/app/topic/info/TopicInfoFragmentLocalTest.kt @@ -76,7 +76,6 @@ import org.oppia.android.util.gcsresource.GcsResourceModule import org.oppia.android.util.locale.LocaleProdModule import org.oppia.android.util.logging.LoggerModule import org.oppia.android.util.logging.SyncStatusModule -import org.oppia.android.util.logging.firebase.FirebaseLogUploaderModule import org.oppia.android.util.networking.NetworkConnectionDebugUtilModule import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule import org.oppia.android.util.parser.html.HtmlParserEntityTypeModule @@ -163,7 +162,6 @@ class TopicInfoFragmentLocalTest { ExplorationProgressModule::class, ExplorationStorageModule::class, FakeOppiaClockModule::class, - FirebaseLogUploaderModule::class, FractionInputModule::class, GcsResourceModule::class, GlideImageLoaderModule::class, diff --git a/app/src/test/java/org/oppia/android/app/topic/lessons/TopicLessonsFragmentLocalTest.kt b/app/src/test/java/org/oppia/android/app/topic/lessons/TopicLessonsFragmentLocalTest.kt index cbba9d19706..4bfa65edf15 100644 --- a/app/src/test/java/org/oppia/android/app/topic/lessons/TopicLessonsFragmentLocalTest.kt +++ b/app/src/test/java/org/oppia/android/app/topic/lessons/TopicLessonsFragmentLocalTest.kt @@ -75,7 +75,6 @@ import org.oppia.android.util.gcsresource.GcsResourceModule import org.oppia.android.util.locale.LocaleProdModule import org.oppia.android.util.logging.LoggerModule import org.oppia.android.util.logging.SyncStatusModule -import org.oppia.android.util.logging.firebase.FirebaseLogUploaderModule import org.oppia.android.util.networking.NetworkConnectionDebugUtilModule import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule import org.oppia.android.util.parser.html.HtmlParserEntityTypeModule @@ -167,7 +166,6 @@ class TopicLessonsFragmentLocalTest { ExplorationProgressModule::class, ExplorationStorageModule::class, FakeOppiaClockModule::class, - FirebaseLogUploaderModule::class, FractionInputModule::class, GcsResourceModule::class, GlideImageLoaderModule::class, diff --git a/app/src/test/java/org/oppia/android/app/topic/questionplayer/QuestionPlayerActivityLocalTest.kt b/app/src/test/java/org/oppia/android/app/topic/questionplayer/QuestionPlayerActivityLocalTest.kt index 1e175150c65..6b82e685b29 100644 --- a/app/src/test/java/org/oppia/android/app/topic/questionplayer/QuestionPlayerActivityLocalTest.kt +++ b/app/src/test/java/org/oppia/android/app/topic/questionplayer/QuestionPlayerActivityLocalTest.kt @@ -105,7 +105,6 @@ import org.oppia.android.util.gcsresource.GcsResourceModule import org.oppia.android.util.locale.LocaleProdModule import org.oppia.android.util.logging.LoggerModule import org.oppia.android.util.logging.SyncStatusModule -import org.oppia.android.util.logging.firebase.FirebaseLogUploaderModule import org.oppia.android.util.networking.NetworkConnectionDebugUtilModule import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule import org.oppia.android.util.parser.html.HtmlParserEntityTypeModule @@ -507,7 +506,6 @@ class QuestionPlayerActivityLocalTest { ExplorationProgressModule::class, ExplorationStorageModule::class, FakeOppiaClockModule::class, - FirebaseLogUploaderModule::class, FractionInputModule::class, GcsResourceModule::class, GlideImageLoaderModule::class, diff --git a/app/src/test/java/org/oppia/android/app/topic/revisioncard/RevisionCardActivityLocalTest.kt b/app/src/test/java/org/oppia/android/app/topic/revisioncard/RevisionCardActivityLocalTest.kt index 6088f007ad7..00af1f0e0d0 100644 --- a/app/src/test/java/org/oppia/android/app/topic/revisioncard/RevisionCardActivityLocalTest.kt +++ b/app/src/test/java/org/oppia/android/app/topic/revisioncard/RevisionCardActivityLocalTest.kt @@ -77,7 +77,6 @@ import org.oppia.android.util.gcsresource.GcsResourceModule import org.oppia.android.util.locale.LocaleProdModule import org.oppia.android.util.logging.LoggerModule import org.oppia.android.util.logging.SyncStatusModule -import org.oppia.android.util.logging.firebase.FirebaseLogUploaderModule import org.oppia.android.util.networking.NetworkConnectionDebugUtilModule import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule import org.oppia.android.util.parser.html.HtmlParserEntityTypeModule @@ -153,7 +152,6 @@ class RevisionCardActivityLocalTest { ExplorationProgressModule::class, ExplorationStorageModule::class, FakeOppiaClockModule::class, - FirebaseLogUploaderModule::class, FractionInputModule::class, GcsResourceModule::class, GlideImageLoaderModule::class, diff --git a/app/src/test/java/org/oppia/android/app/translation/ActivityLanguageLocaleHandlerTest.kt b/app/src/test/java/org/oppia/android/app/translation/ActivityLanguageLocaleHandlerTest.kt index 7dfc65cca28..bba14bd7e57 100644 --- a/app/src/test/java/org/oppia/android/app/translation/ActivityLanguageLocaleHandlerTest.kt +++ b/app/src/test/java/org/oppia/android/app/translation/ActivityLanguageLocaleHandlerTest.kt @@ -90,7 +90,6 @@ import org.oppia.android.util.locale.OppiaLocale import org.oppia.android.util.locale.testing.LocaleTestModule import org.oppia.android.util.logging.LoggerModule import org.oppia.android.util.logging.SyncStatusModule -import org.oppia.android.util.logging.firebase.FirebaseLogUploaderModule import org.oppia.android.util.networking.NetworkConnectionDebugUtilModule import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule import org.oppia.android.util.parser.html.HtmlParserEntityTypeModule @@ -348,7 +347,6 @@ class ActivityLanguageLocaleHandlerTest { ExplorationProgressModule::class, ExplorationStorageModule::class, FakeOppiaClockModule::class, - FirebaseLogUploaderModule::class, FractionInputModule::class, GcsResourceModule::class, GlideImageLoaderModule::class, diff --git a/app/src/test/java/org/oppia/android/app/translation/AppLanguageResourceHandlerTest.kt b/app/src/test/java/org/oppia/android/app/translation/AppLanguageResourceHandlerTest.kt index bcff9c730dc..fed3d352eb1 100644 --- a/app/src/test/java/org/oppia/android/app/translation/AppLanguageResourceHandlerTest.kt +++ b/app/src/test/java/org/oppia/android/app/translation/AppLanguageResourceHandlerTest.kt @@ -88,7 +88,6 @@ import org.oppia.android.util.locale.testing.LocaleTestModule import org.oppia.android.util.locale.testing.TestOppiaBidiFormatter import org.oppia.android.util.logging.LoggerModule import org.oppia.android.util.logging.SyncStatusModule -import org.oppia.android.util.logging.firebase.FirebaseLogUploaderModule import org.oppia.android.util.networking.NetworkConnectionDebugUtilModule import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule import org.oppia.android.util.parser.html.HtmlParserEntityTypeModule @@ -750,7 +749,6 @@ class AppLanguageResourceHandlerTest { ExplorationProgressModule::class, ExplorationStorageModule::class, FakeOppiaClockModule::class, - FirebaseLogUploaderModule::class, FractionInputModule::class, GcsResourceModule::class, GlideImageLoaderModule::class, diff --git a/app/src/test/java/org/oppia/android/app/translation/AppLanguageWatcherMixinTest.kt b/app/src/test/java/org/oppia/android/app/translation/AppLanguageWatcherMixinTest.kt index fe27dc2c8e6..709f20c4906 100644 --- a/app/src/test/java/org/oppia/android/app/translation/AppLanguageWatcherMixinTest.kt +++ b/app/src/test/java/org/oppia/android/app/translation/AppLanguageWatcherMixinTest.kt @@ -88,7 +88,6 @@ import org.oppia.android.util.gcsresource.GcsResourceModule import org.oppia.android.util.locale.testing.LocaleTestModule import org.oppia.android.util.logging.LoggerModule import org.oppia.android.util.logging.SyncStatusModule -import org.oppia.android.util.logging.firebase.FirebaseLogUploaderModule import org.oppia.android.util.networking.NetworkConnectionDebugUtilModule import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule import org.oppia.android.util.parser.html.HtmlParserEntityTypeModule @@ -308,7 +307,6 @@ class AppLanguageWatcherMixinTest { ExplorationProgressModule::class, ExplorationStorageModule::class, FakeOppiaClockModule::class, - FirebaseLogUploaderModule::class, FractionInputModule::class, GcsResourceModule::class, GlideImageLoaderModule::class, diff --git a/app/src/test/java/org/oppia/android/app/utility/datetime/DateTimeUtilTest.kt b/app/src/test/java/org/oppia/android/app/utility/datetime/DateTimeUtilTest.kt index 3722431d16d..77a84c3df2b 100644 --- a/app/src/test/java/org/oppia/android/app/utility/datetime/DateTimeUtilTest.kt +++ b/app/src/test/java/org/oppia/android/app/utility/datetime/DateTimeUtilTest.kt @@ -11,7 +11,6 @@ import dagger.BindsInstance import dagger.Component import dagger.Module import dagger.Provides -import dagger.multibindings.Multibinds import org.junit.Before import org.junit.Rule import org.junit.Test @@ -54,7 +53,6 @@ import org.oppia.android.domain.hintsandsolution.HintsAndSolutionProdModule import org.oppia.android.domain.onboarding.ExpirationMetaDataRetrieverModule import org.oppia.android.domain.oppialogger.LogStorageModule import org.oppia.android.domain.oppialogger.LoggingIdentifierModule -import org.oppia.android.domain.oppialogger.analytics.AnalyticsStartupListener import org.oppia.android.domain.oppialogger.analytics.ApplicationLifecycleModule import org.oppia.android.domain.oppialogger.analytics.CpuPerformanceSnapshotterModule import org.oppia.android.domain.platformparameter.PlatformParameterSingletonModule @@ -205,12 +203,6 @@ class DateTimeUtilTest { fun provideGlobalLogLevel(): LogLevel = LogLevel.VERBOSE } - @Module - interface AnalyticsStartupListenerTestModule { - @Multibinds - fun provideAnalyticsListenerSet(): Set - } - // TODO(#89): Move this to a common test application component. @Singleton @Component( @@ -219,7 +211,6 @@ class DateTimeUtilTest { ActivityRecreatorTestModule::class, ActivityRouterModule::class, AlgebraicExpressionInputModule::class, - AnalyticsStartupListenerTestModule::class, ApplicationLifecycleModule::class, ApplicationModule::class, ApplicationStartupListenerModule::class, diff --git a/app/src/test/java/org/oppia/android/app/utility/math/MathExpressionAccessibilityUtilTest.kt b/app/src/test/java/org/oppia/android/app/utility/math/MathExpressionAccessibilityUtilTest.kt index 2e4b20b5629..9d888887681 100644 --- a/app/src/test/java/org/oppia/android/app/utility/math/MathExpressionAccessibilityUtilTest.kt +++ b/app/src/test/java/org/oppia/android/app/utility/math/MathExpressionAccessibilityUtilTest.kt @@ -98,7 +98,6 @@ import org.oppia.android.util.gcsresource.GcsResourceModule import org.oppia.android.util.locale.testing.LocaleTestModule import org.oppia.android.util.logging.LoggerModule import org.oppia.android.util.logging.SyncStatusModule -import org.oppia.android.util.logging.firebase.FirebaseLogUploaderModule import org.oppia.android.util.math.MathExpressionParser import org.oppia.android.util.math.MathExpressionParser.Companion.ErrorCheckingMode import org.oppia.android.util.math.MathExpressionParser.Companion.ErrorCheckingMode.ALL_ERRORS @@ -1412,7 +1411,6 @@ class MathExpressionAccessibilityUtilTest { ExplorationProgressModule::class, ExplorationStorageModule::class, FakeOppiaClockModule::class, - FirebaseLogUploaderModule::class, FractionInputModule::class, GcsResourceModule::class, GlideImageLoaderModule::class, diff --git a/domain/BUILD.bazel b/domain/BUILD.bazel index 307082d92ee..a45fdacc93f 100755 --- a/domain/BUILD.bazel +++ b/domain/BUILD.bazel @@ -127,10 +127,8 @@ kt_android_library( "//domain/src/main/java/org/oppia/android/domain/classify:classification_result", "//domain/src/main/java/org/oppia/android/domain/oppialogger:oppia_logger", "//domain/src/main/java/org/oppia/android/domain/oppialogger:startup_listener", - "//domain/src/main/java/org/oppia/android/domain/oppialogger/analytics:analytics_startup_listener", + "//domain/src/main/java/org/oppia/android/domain/oppialogger/analytics:application_lifecycle_listener", "//domain/src/main/java/org/oppia/android/domain/oppialogger/exceptions:controller", - "//domain/src/main/java/org/oppia/android/domain/oppialogger/logscheduler:metric_log_scheduling_worker_factory", - "//domain/src/main/java/org/oppia/android/domain/oppialogger/loguploader:worker_factory", "//domain/src/main/java/org/oppia/android/domain/platformparameter:debug_impl", "//domain/src/main/java/org/oppia/android/domain/platformparameter/syncup", "//domain/src/main/java/org/oppia/android/domain/profile:profile_management_controller", @@ -141,6 +139,7 @@ kt_android_library( "//domain/src/main/java/org/oppia/android/domain/util:asset", "//domain/src/main/java/org/oppia/android/domain/util:extensions", "//domain/src/main/java/org/oppia/android/domain/util:retriever", + "//domain/src/main/java/org/oppia/android/domain/workmanager:startup_worker_schedule_readiness_listener", "//model/src/main/proto:exploration_checkpoint_java_proto_lite", "//model/src/main/proto:onboarding_java_proto_lite", "//model/src/main/proto:platform_parameter_java_proto_lite", @@ -153,7 +152,6 @@ kt_android_library( "//utility/src/main/java/org/oppia/android/util/extensions:context_extensions", "//utility/src/main/java/org/oppia/android/util/extensions:string_extensions", "//utility/src/main/java/org/oppia/android/util/logging:event_logger", - "//utility/src/main/java/org/oppia/android/util/logging:log_uploader", "//utility/src/main/java/org/oppia/android/util/math:extensions", "//utility/src/main/java/org/oppia/android/util/math:math_expression_parser", "//utility/src/main/java/org/oppia/android/util/networking:network_connection_util", @@ -203,15 +201,12 @@ TEST_DEPS = [ "//domain/src/main/java/org/oppia/android/domain/oppialogger:prod_module", "//domain/src/main/java/org/oppia/android/domain/oppialogger/analytics:cpu_module", "//domain/src/main/java/org/oppia/android/domain/oppialogger/analytics:prod_module", - "//domain/src/main/java/org/oppia/android/domain/oppialogger/analytics/testing:fake_log_scheduler", "//domain/src/main/java/org/oppia/android/domain/oppialogger/exceptions:logger_module", "//domain/src/main/java/org/oppia/android/domain/oppialogger/exceptions:startup_listener", "//domain/src/main/java/org/oppia/android/domain/oppialogger/logscheduler:metric_log_scheduler_module", - "//domain/src/main/java/org/oppia/android/domain/oppialogger/loguploader:worker_factory", "//domain/src/main/java/org/oppia/android/domain/oppialogger/loguploader:worker_module", "//domain/src/main/java/org/oppia/android/domain/platformparameter:debug_module", "//domain/src/main/java/org/oppia/android/domain/spotlight:spotlight_state_controller", - "//domain/src/main/java/org/oppia/android/domain/testing/oppialogger/loguploader:fake_log_uploader", "//model/src/main/proto:spotlight_java_proto_lite", "//testing", "//testing/src/main/java/org/oppia/android/testing/data:async_result_subject", @@ -225,7 +220,6 @@ TEST_DEPS = [ "//testing/src/main/java/org/oppia/android/testing/time:test_module", "//third_party:androidx_arch_core_core-testing", "//third_party:androidx_test_ext_junit", - "//third_party:androidx_work_work-testing", "//third_party:com_google_truth_extensions_truth-liteproto-extension", "//third_party:com_google_truth_truth", "//third_party:org_jetbrains_kotlin_kotlin-reflect", diff --git a/domain/src/main/java/org/oppia/android/domain/oppialogger/ApplicationStartupListener.kt b/domain/src/main/java/org/oppia/android/domain/oppialogger/ApplicationStartupListener.kt index 1a0b2c91411..02202d262e2 100644 --- a/domain/src/main/java/org/oppia/android/domain/oppialogger/ApplicationStartupListener.kt +++ b/domain/src/main/java/org/oppia/android/domain/oppialogger/ApplicationStartupListener.kt @@ -1,8 +1,53 @@ package org.oppia.android.domain.oppialogger -/** Listener that gets created at application startup. */ +/** + * Monitors the creation lifecycle of the app. + * + * NOTE TO DEVELOPERS: Implementations of this listener run *very* early in the app lifecycle, even + * before platform parameters have been fully initialized. Extreme care must be taken to ensure that + * all injections are either injected as `Provider`s or do not transitively depend on platform + * parameters. Further restrictions about these types of dependencies are covered in the method + * documentation below. + * + * Also, note that the two lifecycle callback methods below are deliberately called on distinct + * thread types (main/background) so care must be taken in implementations to account for this. The + * two methods will never race each other, and are both guaranteed to be called in order and exactly + * once for the lifetime of the application. + */ interface ApplicationStartupListener { - /** Gets called at application creation. */ - fun onCreate() + /** + * Called immediately upon application start. + * + * This function must not interact with any other components that may require state initialization + * in the app, including analytics, `WorkManager`, and especially platform parameters. + * Implementations can use this to perform critical startup self state initialization. If any + * persistence or broad system interaction is needed during startup then that should happen in + * [onCompletedInitialization]. + * + * Note: This is guaranteed to always be called before any other entrypoint logic executes + * including `SplashActivity`, direct activity recreation, and waking up the app to start a + * background worker. + * + * Important: This will be called on the main thread and should never block. + */ + fun onCreateStarted() + + /** + * Called when the application has been initialized sufficiently well to now be safe for broad + * system interaction. + * + * Most critically this will be called after platform parameters have fully initialized making + * broad app system interaction safe. + * + * Note: This may get called after other entry points are called (such as `SplashActivity`, direct + * activity creation, and background worker interaction), so care needs to be taken if this + * listener influences entry point state (it's highly recommended to avoid that if possible). + * + * Most startup initialization logic is expected to go in the implementation of this function to + * help ensure general startup safety. + * + * Important: This will be called on a background thread, NOT the main thread. + */ + fun onCompletedInitialization() } diff --git a/domain/src/main/java/org/oppia/android/domain/oppialogger/BUILD.bazel b/domain/src/main/java/org/oppia/android/domain/oppialogger/BUILD.bazel index ea208554ded..2e7c76e8d98 100644 --- a/domain/src/main/java/org/oppia/android/domain/oppialogger/BUILD.bazel +++ b/domain/src/main/java/org/oppia/android/domain/oppialogger/BUILD.bazel @@ -29,9 +29,7 @@ kt_android_library( kt_android_library( name = "startup_listener", - srcs = [ - "ApplicationStartupListener.kt", - ], + srcs = ["ApplicationStartupListener.kt"], visibility = ["//:oppia_api_visibility"], ) diff --git a/domain/src/main/java/org/oppia/android/domain/oppialogger/analytics/AnalyticsStartupListener.kt b/domain/src/main/java/org/oppia/android/domain/oppialogger/analytics/AnalyticsStartupListener.kt deleted file mode 100644 index b80868a13d7..00000000000 --- a/domain/src/main/java/org/oppia/android/domain/oppialogger/analytics/AnalyticsStartupListener.kt +++ /dev/null @@ -1,15 +0,0 @@ -package org.oppia.android.domain.oppialogger.analytics - -import androidx.work.WorkManager - -/** - * Analytics-specific application startup listener that receives an instance of [WorkManager] to - * perform analytics-specific initialization. - */ -interface AnalyticsStartupListener { - /** - * Called on application creation with the singleton, application-wide [WorkManager] that should - * be used for scheduling background analytics tasks. - */ - fun onCreate(workManager: WorkManager) -} diff --git a/domain/src/main/java/org/oppia/android/domain/oppialogger/analytics/ApplicationLifecycleListener.kt b/domain/src/main/java/org/oppia/android/domain/oppialogger/analytics/ApplicationLifecycleListener.kt index 5cc55000885..e5484dd12e9 100644 --- a/domain/src/main/java/org/oppia/android/domain/oppialogger/analytics/ApplicationLifecycleListener.kt +++ b/domain/src/main/java/org/oppia/android/domain/oppialogger/analytics/ApplicationLifecycleListener.kt @@ -1,6 +1,16 @@ package org.oppia.android.domain.oppialogger.analytics -/** Listener for when the app goes to the foreground or the background. */ +/** + * Listener for when the app goes to the foreground or the background. + * + * Note that implementations may not receive initial signals of app switches due to there being a + * slight delay in app startup between monitoring for foreground/background switches and actually + * reporting them. No switches will be missed but they may not arrive until other entry points in + * the app have already made execution progress (such as the base activity classes). + * + * Care should be taken when using implementations of this listener to synchronize initialization + * state. + */ interface ApplicationLifecycleListener { /** Fired when the app comes to the foreground. */ fun onAppInForeground() diff --git a/domain/src/main/java/org/oppia/android/domain/oppialogger/analytics/ApplicationLifecycleLogger.kt b/domain/src/main/java/org/oppia/android/domain/oppialogger/analytics/ApplicationLifecycleLogger.kt new file mode 100644 index 00000000000..8ec2ff30a28 --- /dev/null +++ b/domain/src/main/java/org/oppia/android/domain/oppialogger/analytics/ApplicationLifecycleLogger.kt @@ -0,0 +1,266 @@ +package org.oppia.android.domain.oppialogger.analytics + +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch +import org.oppia.android.app.model.LegacyProfileId +import org.oppia.android.app.model.ScreenName +import org.oppia.android.app.model.ScreenName.BACKGROUND_SCREEN +import org.oppia.android.app.model.ScreenName.FOREGROUND_SCREEN +import org.oppia.android.domain.oppialogger.LoggingIdentifierController +import org.oppia.android.domain.oppialogger.OppiaLogger +import org.oppia.android.domain.profile.ProfileManagementController +import org.oppia.android.util.logging.performancemetrics.PerformanceMetricsAssessor.AppIconification.APP_IN_BACKGROUND +import org.oppia.android.util.logging.performancemetrics.PerformanceMetricsAssessor.AppIconification.APP_IN_FOREGROUND +import org.oppia.android.util.platformparameter.EnablePerformanceMetricsCollection +import org.oppia.android.util.platformparameter.PlatformParameterValue +import org.oppia.android.util.threading.BackgroundDispatcher +import javax.inject.Inject +import javax.inject.Singleton + +/** Logger for major application and activity lifecycle changes. */ +@Singleton +class ApplicationLifecycleLogger @Inject constructor( + private val loggingIdentifierController: LoggingIdentifierController, + private val learnerAnalyticsLogger: LearnerAnalyticsLogger, + private val profileManagementController: ProfileManagementController, + private val oppiaLogger: OppiaLogger, + private val performanceMetricsLogger: PerformanceMetricsLogger, + private val featureFlagsLogger: FeatureFlagsLogger, + private val performanceMetricsController: PerformanceMetricsController, + private val cpuPerformanceSnapshotter: CpuPerformanceSnapshotter, + @LearnerAnalyticsInactivityLimitMillis private val inactivityLimitMillis: Long, + @BackgroundDispatcher private val backgroundDispatcher: CoroutineDispatcher, + @EnablePerformanceMetricsCollection + private val enablePerformanceMetricsCollection: PlatformParameterValue, + private val analyticsController: AnalyticsController +) { + // TODO(#6190): Add tests for this class. + + private var isStartupLatencyLogged: Boolean = false + private var currentScreen: ScreenName = ScreenName.SCREEN_NAME_UNSPECIFIED + private val foregroundBackgroundRecordKeeper by lazy { ForegroundBackgroundRecordKeeper() } + + /** + * Returns the current active UI screen that's visible to the user. + * + * A few exceptions: + * [BACKGROUND_SCREEN] is returned when the UI is inactive or when the app is backgrounded. + * [FOREGROUND_SCREEN] is never returned. + * [SCREEN_NAME_UNSPECIFIED] is the default value for [currentScreen] and is returned until a + * currentScreen value has been set by the launcher activity's onResume method. + */ + fun getCurrentScreen(): ScreenName = currentScreen + + /** + * Creates logs pertinent to when the application is first opened. + * + * Note that this should be called whenever the application opens including for background cases + * like workers. + * + * @param appStartTimeMillis the system time, in milliseconds, when the application finished + * early initialization + */ + fun recordAppOpened(appStartTimeMillis: Long) { + logApplicationStartupMetrics(appStartTimeMillis) + logAllFeatureFlags(appStartTimeMillis) + if (enablePerformanceMetricsCollection.value) { + cpuPerformanceSnapshotter.initialiseSnapshotter() + } + + analyticsController.listenForConsoleErrorLogs() + analyticsController.listenForNetworkCallLogs() + analyticsController.listenForFailedNetworkCallLogs() + } + + /** + * Creates logs pertinent to when the app is made visible to the user. + * + * @param timestamp the system time, in milliseconds, when the app has been foregrounded + */ + fun recordAppInForeground(timestamp: Long) { + val timeSpentInBackgroundMs = foregroundBackgroundRecordKeeper.recordAppForegrounded(timestamp) + if (timeSpentInBackgroundMs > inactivityLimitMillis) { + loggingIdentifierController.updateSessionId() + } + if (enablePerformanceMetricsCollection.value) { + cpuPerformanceSnapshotter.updateAppIconification(APP_IN_FOREGROUND) + } + performanceMetricsController.setAppInForeground() + logAppLifecycleEventInBackground(learnerAnalyticsLogger::logAppInForeground, timestamp) + } + + /** + * Creates logs pertinent to when the app is made invisible to the user. + * + * Note that this should only be called after the app has been in the foreground (that is, it + * shouldn't ever be called for background-only instantiations of the app such as for workers). + * + * @param timestamp the system time, in milliseconds, when the app has been backgrounded + */ + fun recordAppInBackground(timestamp: Long) { + val timeSpentInForegroundMs = foregroundBackgroundRecordKeeper.recordAppBackgrounded(timestamp) + if (enablePerformanceMetricsCollection.value) { + cpuPerformanceSnapshotter.updateAppIconification(APP_IN_BACKGROUND) + } + performanceMetricsController.setAppInBackground() + logAppLifecycleEventInBackground(learnerAnalyticsLogger::logAppInBackground, timestamp) + logAppInForegroundTime(timeSpentInForegroundMs, timestamp) + } + + /** + * Creates logs pertinent to when the user has resumed using the app after it has been briefly + * sent to the background or paused. + * + * @param currentActivityScreen the [ScreenName] corresponding to the current visible screen + * @param appStartTimeMillis the system time, in milliseconds, when the application finished + * early initialization + * @param timestamp the system time, in milliseconds, when the app approximately was resumed + */ + fun recordActivityResumed( + currentActivityScreen: ScreenName, + appStartTimeMillis: Long, + timestamp: Long + ) { + currentScreen = currentActivityScreen + if (!isStartupLatencyLogged) { + performanceMetricsLogger.logStartupLatency( + timestamp - appStartTimeMillis, + currentScreen, + timestamp + ) + isStartupLatencyLogged = true + } + performanceMetricsLogger.logMemoryUsage(currentScreen, timestamp) + } + + /** Records that the app is currently temporarily paused. */ + fun recordActivityPaused() { + currentScreen = BACKGROUND_SCREEN + } + + private fun logAppLifecycleEventInBackground( + logMethod: (String?, LegacyProfileId?, String?, Long) -> Unit, + timestamp: Long + ) { + CoroutineScope(backgroundDispatcher).launch { + val installationId = loggingIdentifierController.fetchInstallationId() + val profileId = profileManagementController.getCurrentProfileId() + val learnerId = profileManagementController.fetchCurrentLearnerId() + logMethod(installationId, profileId, learnerId, timestamp) + }.invokeOnCompletion { failure -> + if (failure != null) { + oppiaLogger.e( + "ApplicationLifecycleLogger", + "Encountered error while trying to log app lifecycle event.", + failure + ) + } + } + } + + private fun logApplicationStartupMetrics(timestamp: Long) { + CoroutineScope(backgroundDispatcher).launch { + performanceMetricsLogger.logApkSize(currentScreen, timestamp) + performanceMetricsLogger.logStorageUsage(currentScreen, timestamp) + }.invokeOnCompletion { failure -> + if (failure != null) { + oppiaLogger.e( + "ActivityLifecycleObserver", + "Encountered error while trying to log app's performance metrics.", + failure + ) + } + } + } + + private fun logAllFeatureFlags(timestamp: Long) { + CoroutineScope(backgroundDispatcher).launch { + // TODO(#5341): Replace appSessionId generation to the modified Twitter snowflake algorithm. + val appSessionId = loggingIdentifierController.getAppSessionIdFlow().value + featureFlagsLogger.logAllFeatureFlags(appSessionId, timestamp) + }.invokeOnCompletion { failure -> + if (failure != null) { + oppiaLogger.e( + "ActivityLifecycleObserver", + "Encountered error while logging feature flags.", + failure + ) + } + } + } + + private fun logAppInForegroundTime(timeSpentInForegroundMs: Long, timestamp: Long) { + CoroutineScope(backgroundDispatcher).launch { + val sessionId = loggingIdentifierController.getSessionIdFlow().value + val installationId = loggingIdentifierController.fetchInstallationId() + analyticsController.logLowPriorityEvent( + oppiaLogger.createAppInForegroundTimeContext( + installationId = installationId, + appSessionId = sessionId, + foregroundTime = timeSpentInForegroundMs + ), + profileId = null, + timestamp + ) + }.invokeOnCompletion { failure -> + if (failure != null) { + oppiaLogger.e( + "ApplicationLifecycleLogger", + "Encountered error while trying to log app's time in the foreground.", + failure + ) + } + } + } + + private class ForegroundBackgroundRecordKeeper { + private var timestampSinceLastChange: Long? = null + private var currentState: State = State.BACKGROUND // Apps always begin in the background. + + /** + * Records that the app is now in the foreground. + * + * @param timestamp the timestamp at which the app was foregrounded + */ + fun recordAppForegrounded(timestamp: Long): Long { + val lastTimestamp = timestampSinceLastChange + return when (currentState) { + State.FOREGROUND -> error("App is already thought to be in the foreground.") + // A null timestamp means the app just opened so don't return any duration since it's not + // an explicit delay of the user backgrounding the app. + State.BACKGROUND -> if (lastTimestamp != null) timestamp - lastTimestamp else 0L + }.also { + timestampSinceLastChange = timestamp + currentState = State.FOREGROUND + } + } + + /** + * Records that the app is now in the background. + * + * @param timestamp the timestamp at which the app was backgrounded + */ + fun recordAppBackgrounded(timestamp: Long): Long { + val lastTimestamp = timestampSinceLastChange + return when (currentState) { + State.FOREGROUND -> { + if (lastTimestamp != null) { + timestamp - lastTimestamp + } else error("App can't be foregrounded without a time record.") + } + State.BACKGROUND -> error("App is already thought to be in the background.") + }.also { + timestampSinceLastChange = timestamp + currentState = State.BACKGROUND + } + } + + private enum class State { + /** Corresponds to the app being in the foreground. */ + FOREGROUND, + /** Corresponds to the app being in the background. */ + BACKGROUND + } + } +} diff --git a/domain/src/main/java/org/oppia/android/domain/oppialogger/analytics/ApplicationLifecycleObserver.kt b/domain/src/main/java/org/oppia/android/domain/oppialogger/analytics/ApplicationLifecycleObserver.kt index 8e03ef11882..9265fe157ca 100644 --- a/domain/src/main/java/org/oppia/android/domain/oppialogger/analytics/ApplicationLifecycleObserver.kt +++ b/domain/src/main/java/org/oppia/android/domain/oppialogger/analytics/ApplicationLifecycleObserver.kt @@ -9,223 +9,180 @@ import androidx.lifecycle.OnLifecycleEvent import androidx.lifecycle.ProcessLifecycleOwner import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.launch -import org.oppia.android.app.model.LegacyProfileId +import kotlinx.coroutines.ObsoleteCoroutinesApi +import kotlinx.coroutines.channels.Channel +import kotlinx.coroutines.channels.SendChannel +import kotlinx.coroutines.channels.actor import org.oppia.android.app.model.ScreenName -import org.oppia.android.app.model.ScreenName.BACKGROUND_SCREEN -import org.oppia.android.app.model.ScreenName.FOREGROUND_SCREEN import org.oppia.android.domain.oppialogger.ApplicationStartupListener -import org.oppia.android.domain.oppialogger.LoggingIdentifierController -import org.oppia.android.domain.oppialogger.OppiaLogger -import org.oppia.android.domain.profile.ProfileManagementController import org.oppia.android.util.logging.CurrentAppScreenNameIntentDecorator.extractCurrentAppScreenName -import org.oppia.android.util.logging.performancemetrics.PerformanceMetricsAssessor.AppIconification.APP_IN_BACKGROUND -import org.oppia.android.util.logging.performancemetrics.PerformanceMetricsAssessor.AppIconification.APP_IN_FOREGROUND -import org.oppia.android.util.platformparameter.EnablePerformanceMetricsCollection -import org.oppia.android.util.platformparameter.PlatformParameterValue import org.oppia.android.util.system.OppiaClock import org.oppia.android.util.threading.BackgroundDispatcher import javax.inject.Inject +import javax.inject.Provider import javax.inject.Singleton -/** Observer that observes application and activity lifecycle. */ +/** Observer for major application and activity lifecycle changes. */ @Singleton +@OptIn(ObsoleteCoroutinesApi::class) class ApplicationLifecycleObserver @Inject constructor( private val application: Application, private val oppiaClock: OppiaClock, - private val loggingIdentifierController: LoggingIdentifierController, - private val learnerAnalyticsLogger: LearnerAnalyticsLogger, - private val profileManagementController: ProfileManagementController, - private val oppiaLogger: OppiaLogger, - private val performanceMetricsLogger: PerformanceMetricsLogger, - private val featureFlagsLogger: FeatureFlagsLogger, - private val performanceMetricsController: PerformanceMetricsController, - private val cpuPerformanceSnapshotter: CpuPerformanceSnapshotter, - @LearnerAnalyticsInactivityLimitMillis private val inactivityLimitMillis: Long, - @BackgroundDispatcher private val backgroundDispatcher: CoroutineDispatcher, - @EnablePerformanceMetricsCollection - private val enablePerformanceMetricsCollection: PlatformParameterValue, - private val analyticsController: AnalyticsController, - private val applicationLifecycleListeners: Set<@JvmSuppressWildcards ApplicationLifecycleListener> + @BackgroundDispatcher private val backgroundCoroutineDispatcher: CoroutineDispatcher, + private val applicationLifecycleLoggerProvider: Provider, + private val listenersProvider: Provider> ) : ApplicationStartupListener, LifecycleObserver, Application.ActivityLifecycleCallbacks { - - /** - * Timestamp indicating the time of application start-up. It will be used to calculate the - * cold-startup latency of the application. - * - * We're using a large Long value such that the time difference based on any timestamp will be - * negative and thus ignored until the app records initial time during [onCreate]. - */ - private var appStartTimeMillis: Long = Long.MAX_VALUE - - /** - * Returns a boolean flag that makes sure that startup latency is logged only once in the entire - * application lifecycle. - */ - private var isStartupLatencyLogged: Boolean = false - - private var currentScreen: ScreenName = ScreenName.SCREEN_NAME_UNSPECIFIED - - /** - * Returns the current active UI screen that's visible to the user. - * - * A few exceptions: - * [BACKGROUND_SCREEN] is returned when the UI is inactive or when the app is backgrounded. - * [FOREGROUND_SCREEN] is never returned. - * [SCREEN_NAME_UNSPECIFIED] is the default value for [currentScreen] and is returned until a - * currentScreen value has been set by the launcher activity's onResume method. - */ - fun getCurrentScreen(): ScreenName = currentScreen - - /** Returns the time in millis at which the application started. */ - fun getAppStartupTimeMs(): Long = appStartTimeMillis - - override fun onCreate() { - appStartTimeMillis = oppiaClock.getCurrentTimeMs() + private lateinit var commandQueue: SendChannel + + override fun onCreateStarted() { + // Create the command queue so that lifecycle messages can be recorded without loss, then start + // listening for them. Messages cannot be processed immediately since logging requirements and + // other listener callbacks may require dependencies not yet available this early in the app + // lifecycle. + commandQueue = createObserverCommandActor(appStartupTimeMillis = oppiaClock.getCurrentTimeMs()) ProcessLifecycleOwner.get().lifecycle.addObserver(this) application.registerActivityLifecycleCallbacks(this) - logApplicationStartupMetrics() - logAllFeatureFlags() - cpuPerformanceSnapshotter.initialiseSnapshotter() } - // Use a large Long value such that the time difference based on any timestamp will be negative - // and thus ignored until the app goes into the background at least once. - private var firstTimestamp: Long = Long.MAX_VALUE + override fun onCompletedInitialization() { + // The app is fully ready to go so start enable full logging and process any previously missed + // commands. + enqueueMessage(LifecycleChangeMessage::Initialize) + } - /** Occurs when application comes to foreground. */ + /** Lifecycle callback for when the app is brought to the foreground. */ @OnLifecycleEvent(Lifecycle.Event.ON_START) fun onAppInForeground() { - applicationLifecycleListeners.forEach(ApplicationLifecycleListener::onAppInForeground) - val timeDifferenceMs = oppiaClock.getCurrentTimeMs() - firstTimestamp - if (timeDifferenceMs > inactivityLimitMillis) { - loggingIdentifierController.updateSessionId() - } - if (enablePerformanceMetricsCollection.value) { - cpuPerformanceSnapshotter.updateAppIconification(APP_IN_FOREGROUND) - } - performanceMetricsController.setAppInForeground() - logAppLifecycleEventInBackground(learnerAnalyticsLogger::logAppInForeground) - - analyticsController.listenForConsoleErrorLogs() - analyticsController.listenForNetworkCallLogs() - analyticsController.listenForFailedNetworkCallLogs() + enqueueMessage(LifecycleChangeMessage::AppInForeground) } - /** Occurs when application goes to background. */ + /** Lifecycle callback for when the app is brought to the background. */ @OnLifecycleEvent(Lifecycle.Event.ON_STOP) fun onAppInBackground() { - applicationLifecycleListeners.forEach(ApplicationLifecycleListener::onAppInBackground) - firstTimestamp = oppiaClock.getCurrentTimeMs() - if (enablePerformanceMetricsCollection.value) { - cpuPerformanceSnapshotter.updateAppIconification(APP_IN_BACKGROUND) - } - performanceMetricsController.setAppInBackground() - logAppLifecycleEventInBackground(learnerAnalyticsLogger::logAppInBackground) - - logAppInForegroundTime() + enqueueMessage(LifecycleChangeMessage::AppInBackground) } override fun onActivityResumed(activity: Activity) { - currentScreen = activity.intent.extractCurrentAppScreenName() - if (!isStartupLatencyLogged) { - performanceMetricsLogger.logStartupLatency( - getStartupLatencyMillis(appStartTimeMillis), - currentScreen - ) - isStartupLatencyLogged = true - } - performanceMetricsLogger.logMemoryUsage(currentScreen) + val currentScreen = activity.intent.extractCurrentAppScreenName() + enqueueMessage { time -> LifecycleChangeMessage.ActivityResumed(time, currentScreen) } } override fun onActivityPaused(activity: Activity) { - currentScreen = BACKGROUND_SCREEN + enqueueMessage(LifecycleChangeMessage::ActivityPaused) } - private fun logAppLifecycleEventInBackground( - logMethod: (String?, LegacyProfileId?, String?) -> Unit - ) { - CoroutineScope(backgroundDispatcher).launch { - val installationId = loggingIdentifierController.fetchInstallationId() - val profileId = profileManagementController.getCurrentProfileId() - val learnerId = profileManagementController.fetchCurrentLearnerId() - logMethod(installationId, profileId, learnerId) - }.invokeOnCompletion { failure -> - if (failure != null) { - oppiaLogger.e( - "ApplicationLifecycleObserver", - "Encountered error while trying to log app lifecycle event.", - failure - ) + override fun onActivityCreated(activity: Activity, bundle: Bundle?) {} + + override fun onActivityStarted(activity: Activity) {} + + override fun onActivityStopped(activity: Activity) {} + + override fun onActivitySaveInstanceState(activity: Activity, bundle: Bundle) {} + + override fun onActivityDestroyed(activity: Activity) {} + + private fun createObserverCommandActor( + appStartupTimeMillis: Long + ): SendChannel { + lateinit var state: ObserverState + var replayBuffer: MutableSet? = mutableSetOf() + return CoroutineScope(backgroundCoroutineDispatcher).actor(capacity = Channel.UNLIMITED) { + for (message in channel) { + // First check this is a case of a post-initialization message coming in before + // initialization. That's supported by this command queue since it will store them in a + // replay buffer. + val buffer = replayBuffer + when { + message is LifecycleChangeMessage.Initialize -> { + checkNotNull(buffer) { "Attempting to re-initialize command queue." } + + // It's now safe to fetch all dependencies for processing. + state = ObserverState( + appStartupTimeMillis, + applicationLifecycleLoggerProvider.get(), + listenersProvider.get() + ) + + // Process any other messages that came in. + state.processMessage(message) + for (it in buffer) { + check(it !is LifecycleChangeMessage.Initialize) { + "Attempting to re-initialize command queue." + } + state.processMessage(it) + } + buffer.clear() + replayBuffer = null + } + buffer != null -> buffer += message // Queue the message for later. + else -> state.processMessage(message) // Otherwise it can be processed immediately. + } } } } - private fun logApplicationStartupMetrics() { - CoroutineScope(backgroundDispatcher).launch { - performanceMetricsLogger.logApkSize(currentScreen) - performanceMetricsLogger.logStorageUsage(currentScreen) - }.invokeOnCompletion { failure -> - if (failure != null) { - oppiaLogger.e( - "ActivityLifecycleObserver", - "Encountered error while trying to log app's performance metrics.", - failure - ) - } + private fun enqueueMessage(factory: (Long) -> LifecycleChangeMessage) { + // Failures to enqueue lifecycle changes could be catastrophic to internal app state so it's + // almost certainly better to crash than try to recover. It's also expected that such a failure + // should be impossible since the queue is configured to be unlimited. + val message = factory(oppiaClock.getCurrentTimeMs()) + val sendResult = commandQueue.trySend(message) + if (!sendResult.isSuccess) { + throw IllegalStateException( + "Failed to enqueue command to capture lifecycle change: $message.", + sendResult.exceptionOrNull() + ) } } - private fun logAllFeatureFlags() { - CoroutineScope(backgroundDispatcher).launch { - // TODO(#5341): Replace appSessionId generation to the modified Twitter snowflake algorithm. - val appSessionId = loggingIdentifierController.getAppSessionIdFlow().value - featureFlagsLogger.logAllFeatureFlags(appSessionId) - }.invokeOnCompletion { failure -> - if (failure != null) { - oppiaLogger.e( - "ActivityLifecycleObserver", - "Encountered error while logging feature flags.", - failure - ) - } - } + private sealed class LifecycleChangeMessage { + /** The timestamp representing when the lifecycle change occurred. */ + abstract val timestamp: Long + + /** Indicates the application has initialized. */ + data class Initialize(override val timestamp: Long) : LifecycleChangeMessage() + /** Indicates the application has been foregrounded. */ + data class AppInForeground(override val timestamp: Long) : LifecycleChangeMessage() + /** Indicates the application has been backgrounded. */ + data class AppInBackground(override val timestamp: Long) : LifecycleChangeMessage() + /** Indicates the current open activity has been resumed. */ + data class ActivityResumed( + override val timestamp: Long, + val activityScreen: ScreenName + ) : LifecycleChangeMessage() + /** Indicates the current open activity has been paused. */ + data class ActivityPaused(override val timestamp: Long) : LifecycleChangeMessage() } - private fun logAppInForegroundTime() { - CoroutineScope(backgroundDispatcher).launch { - val sessionId = loggingIdentifierController.getSessionIdFlow().value - val installationId = loggingIdentifierController.fetchInstallationId() - val timeInForeground = oppiaClock.getCurrentTimeMs() - appStartTimeMillis - analyticsController.logLowPriorityEvent( - oppiaLogger.createAppInForegroundTimeContext( - installationId = installationId, - appSessionId = sessionId, - foregroundTime = timeInForeground - ), - profileId = null - ) - }.invokeOnCompletion { failure -> - if (failure != null) { - oppiaLogger.e( - "ApplicationLifecycleObserver", - "Encountered error while trying to log app's time in the foreground.", - failure - ) + private class ObserverState( + val appStartupTimeMillis: Long, + val applicationLifecycleLogger: ApplicationLifecycleLogger, + val applicationLifecycleListeners: Set + ) { + /** + * Processes the specified [message] and notifies [applicationLifecycleLogger] of the + * corresponding lifecycle change to ensure that the change can be recorded. + */ + fun processMessage(message: LifecycleChangeMessage) { + when (message) { + is LifecycleChangeMessage.Initialize -> + applicationLifecycleLogger.recordAppOpened(appStartupTimeMillis) + is LifecycleChangeMessage.AppInForeground -> { + applicationLifecycleListeners.forEach(ApplicationLifecycleListener::onAppInForeground) + applicationLifecycleLogger.recordAppInForeground(message.timestamp) + } + is LifecycleChangeMessage.AppInBackground -> { + applicationLifecycleListeners.forEach(ApplicationLifecycleListener::onAppInBackground) + applicationLifecycleLogger.recordAppInBackground(message.timestamp) + } + is LifecycleChangeMessage.ActivityResumed -> { + applicationLifecycleLogger.recordActivityResumed( + message.activityScreen, appStartupTimeMillis, message.timestamp + ) + } + is LifecycleChangeMessage.ActivityPaused -> + applicationLifecycleLogger.recordActivityPaused() } } } - - private fun getStartupLatencyMillis(initialTimestampMillis: Long): Long = - oppiaClock.getCurrentTimeMs() - initialTimestampMillis - - override fun onActivityCreated(activity: Activity, bundle: Bundle?) {} - - override fun onActivityStarted(activity: Activity) {} - - override fun onActivityStopped(activity: Activity) {} - - override fun onActivitySaveInstanceState(activity: Activity, bundle: Bundle) {} - - override fun onActivityDestroyed(activity: Activity) {} } diff --git a/domain/src/main/java/org/oppia/android/domain/oppialogger/analytics/BUILD.bazel b/domain/src/main/java/org/oppia/android/domain/oppialogger/analytics/BUILD.bazel index 733d6558259..4c0d677c9e6 100644 --- a/domain/src/main/java/org/oppia/android/domain/oppialogger/analytics/BUILD.bazel +++ b/domain/src/main/java/org/oppia/android/domain/oppialogger/analytics/BUILD.bazel @@ -4,17 +4,6 @@ Library for providing logging analytics to the Oppia android app. load("@io_bazel_rules_kotlin//kotlin:android.bzl", "kt_android_library") -kt_android_library( - name = "analytics_startup_listener", - srcs = [ - "AnalyticsStartupListener.kt", - ], - visibility = ["//:oppia_api_visibility"], - deps = [ - "//third_party:androidx_work_work-runtime-ktx", - ], -) - kt_android_library( name = "controller", srcs = [ @@ -114,22 +103,29 @@ kt_android_library( ) kt_android_library( - name = "application_lifecycle_observer", - srcs = [ - "ApplicationLifecycleObserver.kt", - ], + name = "application_lifecycle_logger", + srcs = ["ApplicationLifecycleLogger.kt"], visibility = ["//:oppia_api_visibility"], deps = [ - ":application_lifecycle_listener", ":cpu_performance_snapshotter", ":feature_flags_logger", ":learner_analytics_inactivity_limit_millis", ":performance_metrics_controller", "//domain/src/main/java/org/oppia/android/domain/oppialogger:logging_identifier_controller", - "//domain/src/main/java/org/oppia/android/domain/oppialogger:startup_listener", "//domain/src/main/java/org/oppia/android/domain/oppialogger/analytics:performance_metrics_logger", "//domain/src/main/java/org/oppia/android/domain/profile:profile_management_controller", "//model/src/main/proto:screens_java_proto_lite", + ], +) + +kt_android_library( + name = "application_lifecycle_observer", + srcs = ["ApplicationLifecycleObserver.kt"], + deps = [ + ":application_lifecycle_listener", + ":application_lifecycle_logger", + "//domain/src/main/java/org/oppia/android/domain/oppialogger:startup_listener", + "//model/src/main/proto:screens_java_proto_lite", "//third_party:androidx_lifecycle_lifecycle-extensions", "//utility/src/main/java/org/oppia/android/util/logging:current_app_screen_name_intent_decorator", "//utility/src/main/java/org/oppia/android/util/system:oppia_clock", @@ -187,9 +183,7 @@ kt_android_library( kt_android_library( name = "application_lifecycle_listener", - srcs = [ - "ApplicationLifecycleListener.kt", - ], + srcs = ["ApplicationLifecycleListener.kt"], visibility = ["//:oppia_api_visibility"], ) diff --git a/domain/src/main/java/org/oppia/android/domain/oppialogger/analytics/CpuPerformanceSnapshotter.kt b/domain/src/main/java/org/oppia/android/domain/oppialogger/analytics/CpuPerformanceSnapshotter.kt index 7bf3d657058..97297704ee2 100644 --- a/domain/src/main/java/org/oppia/android/domain/oppialogger/analytics/CpuPerformanceSnapshotter.kt +++ b/domain/src/main/java/org/oppia/android/domain/oppialogger/analytics/CpuPerformanceSnapshotter.kt @@ -20,20 +20,26 @@ import org.oppia.android.util.logging.performancemetrics.PerformanceMetricsAsses import org.oppia.android.util.logging.performancemetrics.PerformanceMetricsAssessor.AppIconification.APP_IN_FOREGROUND import org.oppia.android.util.logging.performancemetrics.PerformanceMetricsAssessor.AppIconification.UNINITIALIZED import org.oppia.android.util.logging.performancemetrics.PerformanceMetricsAssessor.CpuSnapshot +import org.oppia.android.util.system.OppiaClock +import org.oppia.android.util.threading.BackgroundDispatcher +import javax.inject.Inject +import javax.inject.Singleton /** * Snapshotter that gracefully and sequentially logs CPU usage across foreground and background * moves of the application. */ -class CpuPerformanceSnapshotter( - private val backgroundCoroutineDispatcher: CoroutineDispatcher, +@Singleton +class CpuPerformanceSnapshotter @Inject constructor( + @BackgroundDispatcher private val backgroundCoroutineDispatcher: CoroutineDispatcher, private val performanceMetricsLogger: PerformanceMetricsLogger, private val consoleLogger: ConsoleLogger, private val exceptionLogger: ExceptionLogger, + private val oppiaClock: OppiaClock, private val performanceMetricsAssessor: PerformanceMetricsAssessor, - private val foregroundCpuLoggingTimePeriodMillis: Long, - private val backgroundCpuLoggingTimePeriodMillis: Long, - private val initialIconificationCutOffTimePeriodMillis: Long + @ForegroundCpuLoggingTimePeriodMillis private val foregroundCpuLoggingTimePeriodMillis: Long, + @BackgroundCpuLoggingTimePeriodMillis private val backgroundCpuLoggingTimePeriodMillis: Long, + @InitialIconificationCutOffTimePeriodMillis private val initialIconCutOffTimePeriodMillis: Long ) { private var currentIconification = UNINITIALIZED @@ -52,7 +58,7 @@ class CpuPerformanceSnapshotter( } isSnapshotterInitialized = true CoroutineScope(backgroundCoroutineDispatcher).launch { - delay(initialIconificationCutOffTimePeriodMillis) + delay(initialIconCutOffTimePeriodMillis) if (currentIconification == UNINITIALIZED) { sendSwitchIconificationCommand(APP_IN_BACKGROUND) } @@ -117,7 +123,8 @@ class CpuPerformanceSnapshotter( is CommandMessage.LogSnapshotDiff -> { performanceMetricsLogger.logCpuUsage( message.screenName, - message.relativeCpuUsage + message.relativeCpuUsage, + oppiaClock.getCurrentTimeMs() ) } } @@ -227,6 +234,6 @@ class CpuPerformanceSnapshotter( private fun AppIconification.getDelay(): Long = when (this) { APP_IN_BACKGROUND -> backgroundCpuLoggingTimePeriodMillis APP_IN_FOREGROUND -> foregroundCpuLoggingTimePeriodMillis - UNINITIALIZED -> initialIconificationCutOffTimePeriodMillis + UNINITIALIZED -> initialIconCutOffTimePeriodMillis } } diff --git a/domain/src/main/java/org/oppia/android/domain/oppialogger/analytics/CpuPerformanceSnapshotterModule.kt b/domain/src/main/java/org/oppia/android/domain/oppialogger/analytics/CpuPerformanceSnapshotterModule.kt index feb8d01eddc..db868d09240 100644 --- a/domain/src/main/java/org/oppia/android/domain/oppialogger/analytics/CpuPerformanceSnapshotterModule.kt +++ b/domain/src/main/java/org/oppia/android/domain/oppialogger/analytics/CpuPerformanceSnapshotterModule.kt @@ -2,40 +2,11 @@ package org.oppia.android.domain.oppialogger.analytics import dagger.Module import dagger.Provides -import kotlinx.coroutines.CoroutineDispatcher -import org.oppia.android.util.logging.ConsoleLogger -import org.oppia.android.util.logging.ExceptionLogger -import org.oppia.android.util.logging.performancemetrics.PerformanceMetricsAssessor -import org.oppia.android.util.threading.BackgroundDispatcher import java.util.concurrent.TimeUnit -import javax.inject.Singleton /** Provides dependencies that are needed for logging CPU usage. */ @Module class CpuPerformanceSnapshotterModule { - - @Singleton - @Provides - fun providesCpuPerformanceSnapshotter( - performanceMetricsLogger: PerformanceMetricsLogger, - consoleLogger: ConsoleLogger, - exceptionLogger: ExceptionLogger, - performanceMetricsAssessor: PerformanceMetricsAssessor, - @BackgroundDispatcher backgroundCoroutineDispatcher: CoroutineDispatcher, - @ForegroundCpuLoggingTimePeriodMillis foregroundCpuLoggingTimePeriodMillis: Long, - @BackgroundCpuLoggingTimePeriodMillis backgroundCpuLoggingTimePeriodMillis: Long, - @InitialIconificationCutOffTimePeriodMillis initialIconificationCutOffTimePeriodMillis: Long - ): CpuPerformanceSnapshotter = CpuPerformanceSnapshotter( - backgroundCoroutineDispatcher, - performanceMetricsLogger, - consoleLogger, - exceptionLogger, - performanceMetricsAssessor, - foregroundCpuLoggingTimePeriodMillis, - backgroundCpuLoggingTimePeriodMillis, - initialIconificationCutOffTimePeriodMillis - ) - @Provides @ForegroundCpuLoggingTimePeriodMillis fun provideForegroundCpuLoggingTimePeriodMillis(): Long = TimeUnit.MINUTES.toMillis(5) diff --git a/domain/src/main/java/org/oppia/android/domain/oppialogger/analytics/FeatureFlagsLogger.kt b/domain/src/main/java/org/oppia/android/domain/oppialogger/analytics/FeatureFlagsLogger.kt index f5320bcf1b1..d2d53668e98 100644 --- a/domain/src/main/java/org/oppia/android/domain/oppialogger/analytics/FeatureFlagsLogger.kt +++ b/domain/src/main/java/org/oppia/android/domain/oppialogger/analytics/FeatureFlagsLogger.kt @@ -46,8 +46,9 @@ class FeatureFlagsLogger @Inject constructor( * loaded (i.e. not too early in the application lifecycle). * * @param appSessionId denotes the id of the current appInForeground session + * @param timestamp the timestamp to associate with the log */ - fun logAllFeatureFlags(appSessionId: String) { + fun logAllFeatureFlags(appSessionId: String, timestamp: Long) { // TODO(#5341): Set the UUID value for this context val featureFlagContext = FeatureFlagListContext.newBuilder() .setAppSessionId(appSessionId) @@ -58,7 +59,8 @@ class FeatureFlagsLogger @Inject constructor( EventLog.Context.newBuilder() .setFeatureFlagListContext(featureFlagContext) .build(), - profileId = null + profileId = null, + timestamp ) } diff --git a/domain/src/main/java/org/oppia/android/domain/oppialogger/analytics/LearnerAnalyticsLogger.kt b/domain/src/main/java/org/oppia/android/domain/oppialogger/analytics/LearnerAnalyticsLogger.kt index be75a5a2641..5336a4640ca 100644 --- a/domain/src/main/java/org/oppia/android/domain/oppialogger/analytics/LearnerAnalyticsLogger.kt +++ b/domain/src/main/java/org/oppia/android/domain/oppialogger/analytics/LearnerAnalyticsLogger.kt @@ -22,6 +22,7 @@ import org.oppia.android.domain.oppialogger.LoggingIdentifierController import org.oppia.android.domain.oppialogger.OppiaLogger import org.oppia.android.domain.oppialogger.analytics.LearnerAnalyticsLogger.BaseLogger.Companion.maybeLogEvent import org.oppia.android.util.math.toAnswerString +import org.oppia.android.util.system.OppiaClock import javax.inject.Inject import javax.inject.Singleton import org.oppia.android.app.model.EventLog.Context as EventContext @@ -42,7 +43,8 @@ import org.oppia.android.app.model.EventLog.Context.Builder as EventBuilder class LearnerAnalyticsLogger @Inject constructor( private val oppiaLogger: OppiaLogger, private val analyticsController: AnalyticsController, - private val loggingIdentifierController: LoggingIdentifierController + private val loggingIdentifierController: LoggingIdentifierController, + private val oppiaClock: OppiaClock ) { /** * The [ExplorationAnalyticsLogger] corresponding to the current play session, or ``null`` if @@ -91,7 +93,8 @@ class LearnerAnalyticsLogger @Inject constructor( exploration.version, oppiaLogger, analyticsController, - loggingIdentifierController + loggingIdentifierController, + oppiaClock ).also { if (!mutableExpAnalyticsLogger.compareAndSet(expect = null, update = it)) { oppiaLogger.w( @@ -126,14 +129,21 @@ class LearnerAnalyticsLogger @Inject constructor( * @param profileId the ID of the Oppia profile currently logged in, or null if none * @param learnerId the personal profile/learner ID corresponding to the new session learner, or * null if not known (which may impact whether the event is logged) + * @param timestamp the timestamp to associate with this log */ - fun logAppInBackground(installationId: String?, profileId: LegacyProfileId?, learnerId: String?) { + fun logAppInBackground( + installationId: String?, + profileId: LegacyProfileId?, + learnerId: String?, + timestamp: Long + ) { val learnerDetailsContext = createLearnerDetailsContextWithIdsPresent(installationId, learnerId) analyticsController.maybeLogEvent( installationId, createAnalyticsEvent(learnerDetailsContext, EventBuilder::setAppInBackgroundContext), profileId, - oppiaLogger + oppiaLogger, + timestamp ) } @@ -145,14 +155,21 @@ class LearnerAnalyticsLogger @Inject constructor( * @param profileId the ID of the Oppia profile currently logged in, or null if none * @param learnerId the personal profile/learner ID corresponding to the new session learner, or * null if not known (which may impact whether the event is logged) + * @param timestamp the timestamp to associate with this log */ - fun logAppInForeground(installationId: String?, profileId: LegacyProfileId?, learnerId: String?) { + fun logAppInForeground( + installationId: String?, + profileId: LegacyProfileId?, + learnerId: String?, + timestamp: Long + ) { val learnerDetailsContext = createLearnerDetailsContextWithIdsPresent(installationId, learnerId) analyticsController.maybeLogEvent( installationId, createAnalyticsEvent(learnerDetailsContext, EventBuilder::setAppInForegroundContext), profileId, - oppiaLogger + oppiaLogger, + timestamp ) } @@ -173,7 +190,8 @@ class LearnerAnalyticsLogger @Inject constructor( installationId, createAnalyticsEvent(learnerDetailsContext, EventBuilder::setDeleteProfileContext), profileId, - oppiaLogger + oppiaLogger, + oppiaClock.getCurrentTimeMs() ) } @@ -195,7 +213,8 @@ class LearnerAnalyticsLogger @Inject constructor( explorationVersion: Int, private val oppiaLogger: OppiaLogger, private val analyticsController: AnalyticsController, - private val loggingIdentifierController: LoggingIdentifierController + private val loggingIdentifierController: LoggingIdentifierController, + private val oppiaClock: OppiaClock ) { /** * The [StateAnalyticsLogger] corresponding to the current, pending state, or null if there is @@ -206,7 +225,7 @@ class LearnerAnalyticsLogger @Inject constructor( private val mutableStateAnalyticsLogger = MutableStateFlow(null) private val baseLogger by lazy { - BaseLogger(oppiaLogger, analyticsController, profileId, installationId) + BaseLogger(oppiaLogger, analyticsController, oppiaClock, profileId, installationId) } private val learnerDetailsContext by lazy { createLearnerDetailsContext(installationId, learnerId) @@ -617,6 +636,7 @@ class LearnerAnalyticsLogger @Inject constructor( internal class BaseLogger internal constructor( private val oppiaLogger: OppiaLogger, private val analyticsController: AnalyticsController, + private val oppiaClock: OppiaClock, private val profileId: LegacyProfileId, private val installationId: String? ) { @@ -640,8 +660,11 @@ class LearnerAnalyticsLogger @Inject constructor( } /** See [AnalyticsController.maybeLogEvent]. */ - fun maybeLogEvent(context: EventContext?) = - analyticsController.maybeLogEvent(installationId, context, profileId, oppiaLogger) + fun maybeLogEvent(context: EventContext?) { + analyticsController.maybeLogEvent( + installationId, context, profileId, oppiaLogger, oppiaClock.getCurrentTimeMs() + ) + } internal companion object { /** @@ -653,16 +676,17 @@ class LearnerAnalyticsLogger @Inject constructor( installId: String?, context: EventContext?, profileId: LegacyProfileId?, - oppiaLogger: OppiaLogger + oppiaLogger: OppiaLogger, + timestamp: Long ) { if (context != null) { - logImportantEvent(context, profileId) + logImportantEvent(context, profileId, timestamp) } else { oppiaLogger.e( "LearnerAnalyticsLogger", "Event is being dropped due to incomplete event (or missing learner ID for profile)." ) - logImportantEvent(createFailedToLogLearnerAnalyticsEvent(installId), profileId) + logImportantEvent(createFailedToLogLearnerAnalyticsEvent(installId), profileId, timestamp) } } } diff --git a/domain/src/main/java/org/oppia/android/domain/oppialogger/analytics/PerformanceMetricsLogger.kt b/domain/src/main/java/org/oppia/android/domain/oppialogger/analytics/PerformanceMetricsLogger.kt index 8592ad19a5f..754f9c37aea 100644 --- a/domain/src/main/java/org/oppia/android/domain/oppialogger/analytics/PerformanceMetricsLogger.kt +++ b/domain/src/main/java/org/oppia/android/domain/oppialogger/analytics/PerformanceMetricsLogger.kt @@ -5,7 +5,6 @@ import org.oppia.android.app.model.ScreenName import org.oppia.android.util.logging.performancemetrics.PerformanceMetricsAssessor import org.oppia.android.util.platformparameter.EnablePerformanceMetricsCollection import org.oppia.android.util.platformparameter.PlatformParameterValue -import org.oppia.android.util.system.OppiaClock import javax.inject.Inject import javax.inject.Singleton @@ -19,7 +18,6 @@ import javax.inject.Singleton class PerformanceMetricsLogger @Inject constructor( private val performanceMetricsController: PerformanceMetricsController, private val performanceMetricsAssessor: PerformanceMetricsAssessor, - private val oppiaClock: OppiaClock, @EnablePerformanceMetricsCollection private val enablePerformanceMetricsCollection: PlatformParameterValue ) { @@ -30,11 +28,12 @@ class PerformanceMetricsLogger @Inject constructor( * application instance. * * @param currentScreen denotes the application screen at which this metric has been logged + * @param timestamp the timestamp to associate with this log */ - fun logApkSize(currentScreen: ScreenName) { + fun logApkSize(currentScreen: ScreenName, timestamp: Long) { if (enablePerformanceMetricsCollection.value) { performanceMetricsController.logLowPriorityMetricEvent( - oppiaClock.getCurrentTimeMs(), + timestamp, currentScreen, createApkSizeLoggableMetric(performanceMetricsAssessor.getApkSize()) ) @@ -47,11 +46,12 @@ class PerformanceMetricsLogger @Inject constructor( * application instance. * * @param currentScreen denotes the application screen at which this metric has been logged + * @param timestamp the timestamp to associate with this log */ - fun logStorageUsage(currentScreen: ScreenName) { + fun logStorageUsage(currentScreen: ScreenName, timestamp: Long) { if (enablePerformanceMetricsCollection.value) { performanceMetricsController.logLowPriorityMetricEvent( - oppiaClock.getCurrentTimeMs(), + timestamp, currentScreen, createStorageUsageLoggableMetric(performanceMetricsAssessor.getUsedStorage()) ) @@ -64,11 +64,12 @@ class PerformanceMetricsLogger @Inject constructor( * * @param startupLatency denotes the startup latency value that'll be logged to Firebase * @param currentScreen denotes the application screen at which this metric has been logged + * @param timestamp the timestamp to associate with this log */ - fun logStartupLatency(startupLatency: Long, currentScreen: ScreenName) { + fun logStartupLatency(startupLatency: Long, currentScreen: ScreenName, timestamp: Long) { if (startupLatency >= 0 && enablePerformanceMetricsCollection.value) { performanceMetricsController.logLowPriorityMetricEvent( - oppiaClock.getCurrentTimeMs(), + timestamp, currentScreen, createStartupLatencyLoggableMetric(startupLatency) ) @@ -81,11 +82,12 @@ class PerformanceMetricsLogger @Inject constructor( * application instance. * * @param currentScreen denotes the application screen at which this metric has been logged + * @param timestamp the timestamp to associate with this log */ - fun logMemoryUsage(currentScreen: ScreenName) { + fun logMemoryUsage(currentScreen: ScreenName, timestamp: Long) { if (enablePerformanceMetricsCollection.value) { performanceMetricsController.logMediumPriorityMetricEvent( - oppiaClock.getCurrentTimeMs(), + timestamp, currentScreen, createMemoryUsageLoggableMetric(performanceMetricsAssessor.getTotalPssUsed()) ) @@ -98,11 +100,12 @@ class PerformanceMetricsLogger @Inject constructor( * application instance. * * @param currentScreen denotes the application screen at which this metric has been logged + * @param timestamp the timestamp to associate with this log */ - fun logNetworkUsage(currentScreen: ScreenName) { + fun logNetworkUsage(currentScreen: ScreenName, timestamp: Long) { if (enablePerformanceMetricsCollection.value) { performanceMetricsController.logHighPriorityMetricEvent( - oppiaClock.getCurrentTimeMs(), + timestamp, currentScreen, createNetworkUsageLoggableMetric( performanceMetricsAssessor.getTotalReceivedBytes(), @@ -119,12 +122,13 @@ class PerformanceMetricsLogger @Inject constructor( * * @param currentScreen denotes the application screen at which this metric has been logged * @param cpuUsage denotes the relative CPU usage of the application which is measured across two - * time-separated points in the application. + * time-separated points in the application + * @param timestamp the timestamp to associate with this log */ - fun logCpuUsage(currentScreen: ScreenName, cpuUsage: Double) { + fun logCpuUsage(currentScreen: ScreenName, cpuUsage: Double, timestamp: Long) { if (enablePerformanceMetricsCollection.value) { performanceMetricsController.logHighPriorityMetricEvent( - oppiaClock.getCurrentTimeMs(), + timestamp, currentScreen, createCpuUsageLoggableMetric(cpuUsage) ) diff --git a/domain/src/main/java/org/oppia/android/domain/oppialogger/analytics/testing/BUILD.bazel b/domain/src/main/java/org/oppia/android/domain/oppialogger/analytics/testing/BUILD.bazel deleted file mode 100644 index fc4d5c7a062..00000000000 --- a/domain/src/main/java/org/oppia/android/domain/oppialogger/analytics/testing/BUILD.bazel +++ /dev/null @@ -1,19 +0,0 @@ -""" -Package for testing utilities for log uploading functionality. -""" - -load("@io_bazel_rules_kotlin//kotlin:android.bzl", "kt_android_library") - -kt_android_library( - name = "fake_log_scheduler", - testonly = True, - srcs = [ - "FakeLogScheduler.kt", - ], - visibility = ["//:oppia_testing_visibility"], - deps = [ - "//third_party:javax_inject_javax_inject", - "//utility/src/main/java/org/oppia/android/util/logging:log_uploader", - "//utility/src/main/java/org/oppia/android/util/logging:metric_log_scheduler", - ], -) diff --git a/domain/src/main/java/org/oppia/android/domain/oppialogger/analytics/testing/FakeLogScheduler.kt b/domain/src/main/java/org/oppia/android/domain/oppialogger/analytics/testing/FakeLogScheduler.kt deleted file mode 100644 index 12973e595fa..00000000000 --- a/domain/src/main/java/org/oppia/android/domain/oppialogger/analytics/testing/FakeLogScheduler.kt +++ /dev/null @@ -1,58 +0,0 @@ -package org.oppia.android.domain.oppialogger.analytics.testing - -import androidx.work.PeriodicWorkRequest -import androidx.work.WorkManager -import org.oppia.android.util.logging.MetricLogScheduler -import java.util.UUID -import javax.inject.Inject -import javax.inject.Singleton - -/** A test specific fake for the log uploader. */ -@Singleton -class FakeLogScheduler @Inject constructor() : MetricLogScheduler { - private val schedulingStorageUsageMetricLoggingRequestIdList = mutableListOf() - private val schedulingPeriodicUiMetricLoggingRequestIdList = mutableListOf() - private val schedulingPeriodicBackgroundMetricsLoggingRequestIdList = mutableListOf() - - override fun enqueueWorkRequestForPeriodicBackgroundMetrics( - workManager: WorkManager, - workRequest: PeriodicWorkRequest - ) { - schedulingPeriodicBackgroundMetricsLoggingRequestIdList.add(workRequest.id) - } - - override fun enqueueWorkRequestForStorageUsage( - workManager: WorkManager, - workRequest: PeriodicWorkRequest - ) { - schedulingStorageUsageMetricLoggingRequestIdList.add(workRequest.id) - } - - override fun enqueueWorkRequestForPeriodicUiMetrics( - workManager: WorkManager, - workRequest: PeriodicWorkRequest - ) { - schedulingPeriodicUiMetricLoggingRequestIdList.add(workRequest.id) - } - - /** - * Returns the most recent work request id that's stored in the - * [schedulingStorageUsageMetricLoggingRequestIdList]. - */ - fun getMostRecentStorageUsageMetricLoggingRequestId() = - schedulingStorageUsageMetricLoggingRequestIdList.last() - - /** - * Returns the most recent work request id that's stored in the - * [schedulingPeriodicUiMetricLoggingRequestIdList]. - */ - fun getMostRecentPeriodicUiMetricLoggingRequestId() = - schedulingPeriodicUiMetricLoggingRequestIdList.last() - - /** - * Returns the most recent work request id that's stored in the - * [schedulingPeriodicBackgroundMetricsLoggingRequestIdList]. - */ - fun getMostRecentPeriodicBackgroundMetricLoggingRequestId() = - schedulingPeriodicBackgroundMetricsLoggingRequestIdList.last() -} diff --git a/domain/src/main/java/org/oppia/android/domain/oppialogger/exceptions/ExceptionsController.kt b/domain/src/main/java/org/oppia/android/domain/oppialogger/exceptions/ExceptionsController.kt index f0912590b25..a8f7294817b 100644 --- a/domain/src/main/java/org/oppia/android/domain/oppialogger/exceptions/ExceptionsController.kt +++ b/domain/src/main/java/org/oppia/android/domain/oppialogger/exceptions/ExceptionsController.kt @@ -11,10 +11,12 @@ import org.oppia.android.util.logging.ExceptionLogger import org.oppia.android.util.networking.NetworkConnectionUtil import org.oppia.android.util.system.OppiaClock import javax.inject.Inject +import javax.inject.Singleton private const val EXCEPTIONS_CONTROLLER = "Exceptions Controller" /** Controller for handling exception logging. */ +@Singleton class ExceptionsController @Inject constructor( private val exceptionLogger: ExceptionLogger, cacheStoreFactory: PersistentCacheStore.Factory, diff --git a/domain/src/main/java/org/oppia/android/domain/oppialogger/exceptions/UncaughtExceptionLoggerStartupListener.kt b/domain/src/main/java/org/oppia/android/domain/oppialogger/exceptions/UncaughtExceptionLoggerStartupListener.kt index bee8f18579a..ae4d12b21f0 100644 --- a/domain/src/main/java/org/oppia/android/domain/oppialogger/exceptions/UncaughtExceptionLoggerStartupListener.kt +++ b/domain/src/main/java/org/oppia/android/domain/oppialogger/exceptions/UncaughtExceptionLoggerStartupListener.kt @@ -3,29 +3,43 @@ package org.oppia.android.domain.oppialogger.exceptions import org.oppia.android.domain.oppialogger.ApplicationStartupListener import org.oppia.android.util.logging.ConsoleLogger import javax.inject.Inject +import javax.inject.Provider /** Handler for catching fatal exceptions before the defaultUncaughtExceptionHandler. */ class UncaughtExceptionLoggerStartupListener @Inject constructor( - private val exceptionsController: ExceptionsController, - private val consoleLogger: ConsoleLogger + private val exceptionsControllerProvider: Provider, + private val consoleLogger: ConsoleLogger // Should be safe for early app access. ) : Thread.UncaughtExceptionHandler, ApplicationStartupListener { - private var existingUncaughtExceptionHandler: Thread.UncaughtExceptionHandler? = null + private var defaultUncaughtExceptionHandler: Thread.UncaughtExceptionHandler? = null + private var canLogExceptions: Boolean = false - /** Sets up the uncaught exception handler to [UncaughtExceptionLoggerStartupListener]. */ - override fun onCreate() { - existingUncaughtExceptionHandler = Thread.currentThread().uncaughtExceptionHandler - Thread.currentThread().uncaughtExceptionHandler = this + override fun onCreateStarted() { + // This should be set up immediately to try and capture exceptions that occur early, but it may + // not be safe to log them until after app initialization completes. + defaultUncaughtExceptionHandler = Thread.getDefaultUncaughtExceptionHandler() + Thread.setDefaultUncaughtExceptionHandler(this) + } + + override fun onCompletedInitialization() { + canLogExceptions = true } - /** Logs an uncaught exception to the [exceptionsController]. */ override fun uncaughtException(thread: Thread, throwable: Throwable) { try { - exceptionsController.logFatalException(Exception(throwable)) + if (canLogExceptions) { + exceptionsControllerProvider.get().logFatalException(Exception(throwable)) + } else { + consoleLogger.e( + "OPPIA_EXCEPTION_HANDLER", + "Skipped logging exception due to app not being fully initialized yet.", + throwable + ) + } } catch (e: Exception) { consoleLogger.e("OPPIA_EXCEPTION_HANDLER", "Problem in logging exception", e) } finally { - existingUncaughtExceptionHandler?.uncaughtException(thread, throwable) + defaultUncaughtExceptionHandler?.uncaughtException(thread, throwable) } } } diff --git a/domain/src/main/java/org/oppia/android/domain/oppialogger/logscheduler/BUILD.bazel b/domain/src/main/java/org/oppia/android/domain/oppialogger/logscheduler/BUILD.bazel index 84b5cafd1dc..4f26940c7a0 100644 --- a/domain/src/main/java/org/oppia/android/domain/oppialogger/logscheduler/BUILD.bazel +++ b/domain/src/main/java/org/oppia/android/domain/oppialogger/logscheduler/BUILD.bazel @@ -5,58 +5,37 @@ Library for providing log scheduling functionality to the Oppia android app. load("@io_bazel_rules_kotlin//kotlin:android.bzl", "kt_android_library") kt_android_library( - name = "performance_metrics_log_scheduler", - srcs = [ - "PerformanceMetricsLogScheduler.kt", - ], + name = "scheduler", + srcs = ["MetricLogSchedulingWorkerScheduler.kt"], + visibility = ["//domain:domain_testing_visibility"], deps = [ ":metric_log_scheduling_worker", - "//third_party:androidx_work_work-runtime-ktx", - "//utility/src/main/java/org/oppia/android/util/logging:metric_log_scheduler", + "//domain/src/main/java/org/oppia/android/domain/workmanager:startup_worker_schedule_readiness_listener", ], ) kt_android_library( name = "metric_log_scheduling_worker", - srcs = [ - "MetricLogSchedulingWorker.kt", - ], + srcs = ["MetricLogSchedulingWorker.kt"], visibility = ["//:oppia_api_visibility"], deps = [ - "//domain/src/main/java/org/oppia/android/domain/oppialogger/analytics:application_lifecycle_observer", + "//domain/src/main/java/org/oppia/android/domain/oppialogger/analytics:application_lifecycle_logger", "//domain/src/main/java/org/oppia/android/domain/oppialogger/analytics:performance_metrics_logger", "//domain/src/main/java/org/oppia/android/domain/oppialogger/exceptions:controller", - "//domain/src/main/java/org/oppia/android/domain/util:extensions", + "//domain/src/main/java/org/oppia/android/domain/workmanager:oppia_worker", "//model/src/main/proto:screens_java_proto_lite", - "//third_party:androidx_work_work-runtime-ktx", - "//third_party:org_jetbrains_kotlinx_kotlinx-coroutines-guava", "//utility/src/main/java/org/oppia/android/util/logging:console_logger", - "//utility/src/main/java/org/oppia/android/util/threading:annotations", - ], -) - -kt_android_library( - name = "metric_log_scheduling_worker_factory", - srcs = [ - "MetricLogSchedulingWorkerFactory.kt", - ], - visibility = ["//domain:__pkg__"], - deps = [ - ":metric_log_scheduling_worker", - "//third_party:androidx_work_work-runtime-ktx", ], ) kt_android_library( name = "metric_log_scheduler_module", - srcs = [ - "MetricLogSchedulerModule.kt", - ], + srcs = ["MetricLogSchedulerModule.kt"], visibility = ["//:oppia_prod_module_visibility"], deps = [ - ":performance_metrics_log_scheduler", + ":scheduler", "//:dagger", - "//domain/src/main/java/org/oppia/android/domain/oppialogger:startup_listener", - "//utility/src/main/java/org/oppia/android/util/logging:metric_log_scheduler", + "//domain/src/main/java/org/oppia/android/domain/workmanager:oppia_worker", + "//domain/src/main/java/org/oppia/android/domain/workmanager:startup_worker_schedule_readiness_listener", ], ) diff --git a/domain/src/main/java/org/oppia/android/domain/oppialogger/logscheduler/MetricLogSchedulerModule.kt b/domain/src/main/java/org/oppia/android/domain/oppialogger/logscheduler/MetricLogSchedulerModule.kt index 7594d6460ec..51a793d37fe 100644 --- a/domain/src/main/java/org/oppia/android/domain/oppialogger/logscheduler/MetricLogSchedulerModule.kt +++ b/domain/src/main/java/org/oppia/android/domain/oppialogger/logscheduler/MetricLogSchedulerModule.kt @@ -2,13 +2,25 @@ package org.oppia.android.domain.oppialogger.logscheduler import dagger.Binds import dagger.Module -import org.oppia.android.util.logging.MetricLogScheduler +import dagger.multibindings.IntoMap +import dagger.multibindings.IntoSet +import dagger.multibindings.StringKey +import org.oppia.android.domain.workmanager.OppiaWorker +import org.oppia.android.domain.workmanager.StartupWorkerScheduleReadinessListener /** Provides metric log scheduler related dependencies. */ @Module -abstract class MetricLogSchedulerModule { +interface MetricLogSchedulerModule { @Binds - abstract fun provideMetricLogScheduler( - performanceMetricLogScheduler: PerformanceMetricsLogScheduler - ): MetricLogScheduler + @IntoSet + fun bindMetricLogSchedulingWorkerScheduler( + scheduler: MetricLogSchedulingWorkerScheduler + ): StartupWorkerScheduleReadinessListener + + @Binds + @IntoMap + @StringKey(MetricLogSchedulingWorker.WORKER_NAME) + fun bindLogUploadWorkerFactoryProvider( + factory: MetricLogSchedulingWorker.Factory + ): OppiaWorker.Factory<*> } diff --git a/domain/src/main/java/org/oppia/android/domain/oppialogger/logscheduler/MetricLogSchedulingWorker.kt b/domain/src/main/java/org/oppia/android/domain/oppialogger/logscheduler/MetricLogSchedulingWorker.kt index 3902ebb74d0..e2d2fe14ba4 100644 --- a/domain/src/main/java/org/oppia/android/domain/oppialogger/logscheduler/MetricLogSchedulingWorker.kt +++ b/domain/src/main/java/org/oppia/android/domain/oppialogger/logscheduler/MetricLogSchedulingWorker.kt @@ -1,100 +1,53 @@ package org.oppia.android.domain.oppialogger.logscheduler -import android.content.Context -import androidx.work.ListenableWorker -import androidx.work.WorkerParameters -import com.google.common.util.concurrent.ListenableFuture -import kotlinx.coroutines.CoroutineDispatcher -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.async -import kotlinx.coroutines.guava.asListenableFuture import org.oppia.android.app.model.ScreenName.BACKGROUND_SCREEN -import org.oppia.android.domain.oppialogger.analytics.ApplicationLifecycleObserver +import org.oppia.android.domain.oppialogger.analytics.ApplicationLifecycleLogger import org.oppia.android.domain.oppialogger.analytics.PerformanceMetricsLogger -import org.oppia.android.domain.util.getStringFromData +import org.oppia.android.domain.workmanager.OppiaWorker import org.oppia.android.util.logging.ConsoleLogger -import org.oppia.android.util.threading.BackgroundDispatcher +import org.oppia.android.util.system.OppiaClock import javax.inject.Inject -/** - * Worker class that generates metric log reports regarding the performance of the application - * and then stores it in in device cache. - */ +/** Worker to log periodic performance metrics. */ class MetricLogSchedulingWorker private constructor( - context: Context, - params: WorkerParameters, private val consoleLogger: ConsoleLogger, private val performanceMetricsLogger: PerformanceMetricsLogger, - private val applicationLifecycleObserver: ApplicationLifecycleObserver, - @BackgroundDispatcher private val backgroundDispatcher: CoroutineDispatcher -) : ListenableWorker(context, params) { - + private val applicationLifecycleLogger: ApplicationLifecycleLogger, + private val oppiaClock: OppiaClock +) : OppiaWorker { companion object { - private const val TAG = "MetricLogSchedulingWorker" - /** - * The key for an input key-value pair for [MetricLogSchedulingWorker] where one of - * [PERIODIC_BACKGROUND_METRIC_WORKER], [PERIODIC_UI_METRIC_WORKER] and [STORAGE_USAGE_WORKER] indicates what - * kind of work to perform. - */ - const val WORKER_CASE_KEY = "metric_log_scheduling_worker_case_key" - /** - * Indicates to [MetricLogSchedulingWorker] that it should schedule logging for periodic - * performance metrics. - */ - const val PERIODIC_BACKGROUND_METRIC_WORKER = "periodic_background_metric_worker" - /** - * Indicates to [MetricLogSchedulingWorker] that it should schedule logging for storage usage - * performance metrics. - */ - const val STORAGE_USAGE_WORKER = "storage_usage_worker" - /** - * Indicates to [MetricLogSchedulingWorker] that it should schedule logging for ui-related - * memory usage performance metrics. - */ - const val PERIODIC_UI_METRIC_WORKER = "periodic_ui_metric_worker" - } - - override fun startWork(): ListenableFuture { - val backgroundScope = CoroutineScope(backgroundDispatcher) - // TODO(#4463): Add withTimeout() to avoid potential hanging. - return backgroundScope.async { - when (inputData.getStringFromData(WORKER_CASE_KEY)) { - PERIODIC_BACKGROUND_METRIC_WORKER -> schedulePeriodicBackgroundMetricLogging() - STORAGE_USAGE_WORKER -> scheduleStorageUsageMetricLogging() - PERIODIC_UI_METRIC_WORKER -> schedulePeriodicUiMetricLogging() - else -> Result.failure() - } - }.asListenableFuture() - } - - private fun schedulePeriodicBackgroundMetricLogging(): Result { - return try { - performanceMetricsLogger.logNetworkUsage(BACKGROUND_SCREEN) - Result.success() - } catch (e: Exception) { - consoleLogger.e(TAG, e.toString(), e) - return Result.failure() - } + /** The unique name used to schedule this worker. */ + const val WORKER_NAME = "MetricLogSchedulingWorker" } - private fun scheduleStorageUsageMetricLogging(): Result { - return try { - performanceMetricsLogger.logStorageUsage(BACKGROUND_SCREEN) - Result.success() - } catch (e: Exception) { - consoleLogger.e(TAG, e.toString(), e) - return Result.failure() - } + /** The operation types supported by [MetricLogSchedulingWorker]. */ + enum class Operation(override val persistentName: String) : OppiaWorker.TaskType { + /** Instructs the worker to log periodic background metrics. */ + SCHEDULE_LOG_PERIODIC_BACKGROUND_METRICS("schedule_log_periodic_background_metrics"), + /** Instructs the worker to log periodic UI metrics. */ + SCHEDULE_LOG_PERIODIC_UI_METRICS("schedule_log_periodic_ui_metrics"), + /** Instructs the worker to log application storage metrics. */ + SCHEDULE_LOG_STORAGE_USAGE_METRICS("schedule_log_storage_usage_metrics") } - private fun schedulePeriodicUiMetricLogging(): Result { + override suspend fun doWork(taskType: Operation): OppiaWorker.Result { + val timestamp = oppiaClock.getCurrentTimeMs() return try { - val currentScreen = applicationLifecycleObserver.getCurrentScreen() - performanceMetricsLogger.logMemoryUsage(currentScreen) - Result.success() + when (taskType) { + Operation.SCHEDULE_LOG_PERIODIC_BACKGROUND_METRICS -> + performanceMetricsLogger.logNetworkUsage(BACKGROUND_SCREEN, timestamp) + Operation.SCHEDULE_LOG_STORAGE_USAGE_METRICS -> + performanceMetricsLogger.logStorageUsage(BACKGROUND_SCREEN, timestamp) + Operation.SCHEDULE_LOG_PERIODIC_UI_METRICS -> { + performanceMetricsLogger.logMemoryUsage( + applicationLifecycleLogger.getCurrentScreen(), timestamp + ) + } + } + OppiaWorker.Result.SUCCESS } catch (e: Exception) { - consoleLogger.e(TAG, e.toString(), e) - return Result.failure() + consoleLogger.e(WORKER_NAME, "Failed operation: ${taskType.persistentName}.", e) + OppiaWorker.Result.FAILURE } } @@ -102,23 +55,17 @@ class MetricLogSchedulingWorker private constructor( class Factory @Inject constructor( private val consoleLogger: ConsoleLogger, private val performanceMetricsLogger: PerformanceMetricsLogger, - private val applicationLifecycleObserver: ApplicationLifecycleObserver, - @BackgroundDispatcher private val backgroundDispatcher: CoroutineDispatcher - ) { - /** - * Returns a new [MetricLogSchedulingWorker]. - * - * This [MetricLogSchedulingWorker] implements the [ListenableWorker] for facilitating metric - * log scheduling. - */ - fun create(context: Context, params: WorkerParameters): ListenableWorker { + private val applicationLifecycleLogger: ApplicationLifecycleLogger, + private val oppiaClock: OppiaClock + ) : OppiaWorker.Factory { + override val supportedTaskTypes: List = Operation.values().toList() + + override fun createWorker(): OppiaWorker { return MetricLogSchedulingWorker( - context, - params, consoleLogger, performanceMetricsLogger, - applicationLifecycleObserver, - backgroundDispatcher + applicationLifecycleLogger, + oppiaClock ) } } diff --git a/domain/src/main/java/org/oppia/android/domain/oppialogger/logscheduler/MetricLogSchedulingWorkerFactory.kt b/domain/src/main/java/org/oppia/android/domain/oppialogger/logscheduler/MetricLogSchedulingWorkerFactory.kt deleted file mode 100644 index c8dae69942e..00000000000 --- a/domain/src/main/java/org/oppia/android/domain/oppialogger/logscheduler/MetricLogSchedulingWorkerFactory.kt +++ /dev/null @@ -1,22 +0,0 @@ -package org.oppia.android.domain.oppialogger.logscheduler - -import android.content.Context -import androidx.work.ListenableWorker -import androidx.work.WorkerFactory -import androidx.work.WorkerParameters -import javax.inject.Inject - -/** Custom [WorkerFactory] for the [MetricLogSchedulingWorker]. */ -class MetricLogSchedulingWorkerFactory @Inject constructor( - private val workerFactory: MetricLogSchedulingWorker.Factory -) : WorkerFactory() { - - /** Returns a new [MetricLogSchedulingWorker] for the given context and parameters. */ - override fun createWorker( - appContext: Context, - workerClassName: String, - workerParameters: WorkerParameters - ): ListenableWorker { - return workerFactory.create(appContext, workerParameters) - } -} diff --git a/domain/src/main/java/org/oppia/android/domain/oppialogger/logscheduler/MetricLogSchedulingWorkerScheduler.kt b/domain/src/main/java/org/oppia/android/domain/oppialogger/logscheduler/MetricLogSchedulingWorkerScheduler.kt new file mode 100644 index 00000000000..f595957c052 --- /dev/null +++ b/domain/src/main/java/org/oppia/android/domain/oppialogger/logscheduler/MetricLogSchedulingWorkerScheduler.kt @@ -0,0 +1,41 @@ +package org.oppia.android.domain.oppialogger.logscheduler + +import org.oppia.android.domain.workmanager.StartupWorkerScheduleReadinessListener +import org.oppia.android.domain.workmanager.WorkManagerScheduler +import org.oppia.android.util.platformparameter.PerformanceMetricsCollectionHighFrequencyTimeIntervalInMinutes +import org.oppia.android.util.platformparameter.PerformanceMetricsCollectionLowFrequencyTimeIntervalInMinutes +import org.oppia.android.util.platformparameter.PlatformParameterValue +import java.util.concurrent.TimeUnit +import javax.inject.Inject + +/** + * Scheduler for [MetricLogSchedulingWorker] operations once startup-bound workers are allowed to + * initialize during application startup. + */ +class MetricLogSchedulingWorkerScheduler @Inject constructor( + @PerformanceMetricsCollectionHighFrequencyTimeIntervalInMinutes + private val performanceMetricsCollectionHighFrequencyTimeInterval: PlatformParameterValue, + @PerformanceMetricsCollectionLowFrequencyTimeIntervalInMinutes + private val performanceMetricCollectionLowFrequencyTimeInterval: PlatformParameterValue +) : StartupWorkerScheduleReadinessListener { + override fun scheduleWork(workManagerScheduler: WorkManagerScheduler) { + workManagerScheduler.schedulePeriodicWorker( + MetricLogSchedulingWorker.WORKER_NAME, + MetricLogSchedulingWorker.Operation.SCHEDULE_LOG_PERIODIC_BACKGROUND_METRICS, + performanceMetricsCollectionHighFrequencyTimeInterval.value.toLong(), + TimeUnit.MINUTES + ) + workManagerScheduler.schedulePeriodicWorker( + MetricLogSchedulingWorker.WORKER_NAME, + MetricLogSchedulingWorker.Operation.SCHEDULE_LOG_PERIODIC_UI_METRICS, + performanceMetricsCollectionHighFrequencyTimeInterval.value.toLong(), + TimeUnit.MINUTES + ) + workManagerScheduler.schedulePeriodicWorker( + MetricLogSchedulingWorker.WORKER_NAME, + MetricLogSchedulingWorker.Operation.SCHEDULE_LOG_STORAGE_USAGE_METRICS, + performanceMetricCollectionLowFrequencyTimeInterval.value.toLong(), + TimeUnit.MINUTES + ) + } +} diff --git a/domain/src/main/java/org/oppia/android/domain/oppialogger/logscheduler/PerformanceMetricsLogScheduler.kt b/domain/src/main/java/org/oppia/android/domain/oppialogger/logscheduler/PerformanceMetricsLogScheduler.kt deleted file mode 100644 index de82dc42dfa..00000000000 --- a/domain/src/main/java/org/oppia/android/domain/oppialogger/logscheduler/PerformanceMetricsLogScheduler.kt +++ /dev/null @@ -1,50 +0,0 @@ -package org.oppia.android.domain.oppialogger.logscheduler - -import androidx.work.ExistingPeriodicWorkPolicy -import androidx.work.PeriodicWorkRequest -import androidx.work.WorkManager -import org.oppia.android.util.logging.MetricLogScheduler -import javax.inject.Inject - -private const val OPPIA_PERIODIC_METRIC_WORK = "OPPIA_PERIODIC_METRIC_WORK" -private const val OPPIA_STORAGE_USAGE_WORK = "OPPIA_STORAGE_USAGE_WORK" -private const val OPPIA_MEMORY_USAGE_WORK = "OPPIA_MEMORY_USAGE_WORK" - -/** - * Enqueues work requests for generating metric log reports for gaining an insight regarding into - * the performance of the application. - */ -class PerformanceMetricsLogScheduler @Inject constructor() : MetricLogScheduler { - override fun enqueueWorkRequestForPeriodicBackgroundMetrics( - workManager: WorkManager, - workRequest: PeriodicWorkRequest - ) { - workManager.enqueueUniquePeriodicWork( - OPPIA_PERIODIC_METRIC_WORK, - ExistingPeriodicWorkPolicy.KEEP, - workRequest - ) - } - - override fun enqueueWorkRequestForStorageUsage( - workManager: WorkManager, - workRequest: PeriodicWorkRequest - ) { - workManager.enqueueUniquePeriodicWork( - OPPIA_STORAGE_USAGE_WORK, - ExistingPeriodicWorkPolicy.KEEP, - workRequest - ) - } - - override fun enqueueWorkRequestForPeriodicUiMetrics( - workManager: WorkManager, - workRequest: PeriodicWorkRequest - ) { - workManager.enqueueUniquePeriodicWork( - OPPIA_MEMORY_USAGE_WORK, - ExistingPeriodicWorkPolicy.KEEP, - workRequest - ) - } -} diff --git a/domain/src/main/java/org/oppia/android/domain/oppialogger/loguploader/BUILD.bazel b/domain/src/main/java/org/oppia/android/domain/oppialogger/loguploader/BUILD.bazel index e240d31b87f..d6203d92662 100644 --- a/domain/src/main/java/org/oppia/android/domain/oppialogger/loguploader/BUILD.bazel +++ b/domain/src/main/java/org/oppia/android/domain/oppialogger/loguploader/BUILD.bazel @@ -5,72 +5,40 @@ Library for providing log uploading functionality to the Oppia android app. load("@io_bazel_rules_kotlin//kotlin:android.bzl", "kt_android_library") kt_android_library( - name = "initializer", - srcs = [ - "LogReportWorkManagerInitializer.kt", - ], - visibility = [ - "//domain:domain_testing_visibility", - ], + name = "scheduler", + srcs = ["LogReportWorkerScheduler.kt"], + visibility = ["//domain:domain_testing_visibility"], deps = [ ":worker", - "//domain/src/main/java/org/oppia/android/domain/oppialogger:startup_listener", - "//domain/src/main/java/org/oppia/android/domain/oppialogger/analytics:analytics_startup_listener", - "//domain/src/main/java/org/oppia/android/domain/oppialogger/logscheduler:metric_log_scheduling_worker", - "//third_party:androidx_work_work-runtime-ktx", - "//utility/src/main/java/org/oppia/android/util/logging:log_uploader", - "//utility/src/main/java/org/oppia/android/util/logging:metric_log_scheduler", + "//domain/src/main/java/org/oppia/android/domain/workmanager:startup_worker_schedule_readiness_listener", ], ) kt_android_library( name = "worker", - srcs = [ - "LogUploadWorker.kt", - ], - visibility = [ - "//domain:domain_testing_visibility", - ], + srcs = ["LogUploadWorker.kt"], + visibility = ["//domain:domain_testing_visibility"], deps = [ "//domain/src/main/java/org/oppia/android/domain/oppialogger/analytics:controller", "//domain/src/main/java/org/oppia/android/domain/oppialogger/analytics:data_controller", "//domain/src/main/java/org/oppia/android/domain/oppialogger/analytics:performance_metrics_controller", "//domain/src/main/java/org/oppia/android/domain/oppialogger/exceptions:controller", - "//domain/src/main/java/org/oppia/android/domain/util:extensions", - "//third_party:androidx_work_work-runtime-ktx", - "//third_party:com_google_guava_guava", - "//third_party:org_jetbrains_kotlinx_kotlinx-coroutines-guava", - "//utility/src/main/java/org/oppia/android/util/extensions:context_extensions", - "//utility/src/main/java/org/oppia/android/util/extensions:iterable_extensions", + "//domain/src/main/java/org/oppia/android/domain/workmanager:oppia_worker", "//utility/src/main/java/org/oppia/android/util/logging:console_logger", "//utility/src/main/java/org/oppia/android/util/logging:event_logger", "//utility/src/main/java/org/oppia/android/util/logging:exception_logger", "//utility/src/main/java/org/oppia/android/util/logging/performancemetrics:performance_metrics_event_logger", - "//utility/src/main/java/org/oppia/android/util/threading:annotations", - ], -) - -kt_android_library( - name = "worker_factory", - srcs = [ - "LogUploadWorkerFactory.kt", - ], - visibility = ["//domain:__pkg__"], - deps = [ - ":worker", - "//third_party:androidx_work_work-runtime-ktx", ], ) kt_android_library( name = "worker_module", - srcs = [ - "LogReportWorkerModule.kt", - ], + srcs = ["LogReportWorkerModule.kt"], visibility = ["//:oppia_prod_module_visibility"], deps = [ - ":initializer", + ":scheduler", "//:dagger", "//domain/src/main/java/org/oppia/android/domain/oppialogger:startup_listener", + "//domain/src/main/java/org/oppia/android/domain/workmanager:oppia_worker", ], ) diff --git a/domain/src/main/java/org/oppia/android/domain/oppialogger/loguploader/LogReportWorkManagerInitializer.kt b/domain/src/main/java/org/oppia/android/domain/oppialogger/loguploader/LogReportWorkManagerInitializer.kt deleted file mode 100644 index ef31a5ab9dc..00000000000 --- a/domain/src/main/java/org/oppia/android/domain/oppialogger/loguploader/LogReportWorkManagerInitializer.kt +++ /dev/null @@ -1,241 +0,0 @@ -package org.oppia.android.domain.oppialogger.loguploader - -import androidx.work.Constraints -import androidx.work.Data -import androidx.work.NetworkType -import androidx.work.PeriodicWorkRequest -import androidx.work.WorkManager -import org.oppia.android.domain.oppialogger.analytics.AnalyticsStartupListener -import org.oppia.android.domain.oppialogger.logscheduler.MetricLogSchedulingWorker -import org.oppia.android.util.logging.LogUploader -import org.oppia.android.util.logging.MetricLogScheduler -import org.oppia.android.util.platformparameter.PerformanceMetricsCollectionHighFrequencyTimeIntervalInMinutes -import org.oppia.android.util.platformparameter.PerformanceMetricsCollectionLowFrequencyTimeIntervalInMinutes -import org.oppia.android.util.platformparameter.PlatformParameterValue -import java.util.UUID -import java.util.concurrent.TimeUnit -import javax.inject.Inject - -/** - * Enqueues unique periodic work requests for uploading events and exceptions to the remote service - * on application creation. - */ -class LogReportWorkManagerInitializer @Inject constructor( - private val logUploader: LogUploader, - private val metricLogScheduler: MetricLogScheduler, - @PerformanceMetricsCollectionHighFrequencyTimeIntervalInMinutes - performanceMetricsCollectionHighFrequencyTimeInterval: PlatformParameterValue, - @PerformanceMetricsCollectionLowFrequencyTimeIntervalInMinutes - performanceMetricCollectionLowFrequencyTimeInterval: PlatformParameterValue -) : AnalyticsStartupListener { - - private val logReportWorkerConstraints = Constraints.Builder() - .setRequiredNetworkType(NetworkType.CONNECTED) - .setRequiresBatteryNotLow(true) - .build() - - private val workerCaseForUploadingEvents: Data = Data.Builder() - .putString( - LogUploadWorker.WORKER_CASE_KEY, - LogUploadWorker.EVENT_WORKER - ) - .build() - - private val workerCaseForUploadingExceptions: Data = Data.Builder() - .putString( - LogUploadWorker.WORKER_CASE_KEY, - LogUploadWorker.EXCEPTION_WORKER - ) - .build() - - private val workerCaseForUploadingPerformanceMetrics: Data = Data.Builder() - .putString( - LogUploadWorker.WORKER_CASE_KEY, - LogUploadWorker.PERFORMANCE_METRICS_WORKER - ) - .build() - - private val workerCaseForUploadingFirestoreData: Data = Data.Builder() - .putString( - LogUploadWorker.WORKER_CASE_KEY, - LogUploadWorker.FIRESTORE_WORKER - ) - .build() - - private val workerCaseForSchedulingPeriodicBackgroundMetricLogs: Data = Data.Builder() - .putString( - MetricLogSchedulingWorker.WORKER_CASE_KEY, - MetricLogSchedulingWorker.PERIODIC_BACKGROUND_METRIC_WORKER - ) - .build() - - private val workerCaseForSchedulingStorageUsageMetricLogs: Data = Data.Builder() - .putString( - MetricLogSchedulingWorker.WORKER_CASE_KEY, - MetricLogSchedulingWorker.STORAGE_USAGE_WORKER - ) - .build() - - private val workerCaseForSchedulingPeriodicUiMetricLogs: Data = Data.Builder() - .putString( - MetricLogSchedulingWorker.WORKER_CASE_KEY, - MetricLogSchedulingWorker.PERIODIC_UI_METRIC_WORKER - ) - .build() - - private val workRequestForUploadingEvents: PeriodicWorkRequest = PeriodicWorkRequest - .Builder(LogUploadWorker::class.java, 6, TimeUnit.HOURS) - .setInputData(workerCaseForUploadingEvents) - .setConstraints(logReportWorkerConstraints) - .build() - - private val workRequestForUploadingExceptions: PeriodicWorkRequest = PeriodicWorkRequest - .Builder(LogUploadWorker::class.java, 6, TimeUnit.HOURS) - .setInputData(workerCaseForUploadingExceptions) - .setConstraints(logReportWorkerConstraints) - .build() - - private val workRequestForUploadingPerformanceMetrics: PeriodicWorkRequest = PeriodicWorkRequest - .Builder(LogUploadWorker::class.java, 6, TimeUnit.HOURS) - .setInputData(workerCaseForUploadingPerformanceMetrics) - .setConstraints(logReportWorkerConstraints) - .build() - - private val workRequestForSchedulingPeriodicBackgroundMetricLogs: PeriodicWorkRequest = - PeriodicWorkRequest.Builder( - MetricLogSchedulingWorker::class.java, - performanceMetricsCollectionHighFrequencyTimeInterval.value.toLong(), - TimeUnit.MINUTES - ) - .setInputData(workerCaseForSchedulingPeriodicBackgroundMetricLogs) - .setConstraints(logReportWorkerConstraints) - .build() - - private val workRequestForSchedulingStorageUsageMetricLogs: PeriodicWorkRequest = - PeriodicWorkRequest.Builder( - MetricLogSchedulingWorker::class.java, - performanceMetricCollectionLowFrequencyTimeInterval.value.toLong(), - TimeUnit.MINUTES - ) - .setInputData(workerCaseForSchedulingStorageUsageMetricLogs) - .setConstraints(logReportWorkerConstraints) - .build() - - private val workRequestForSchedulingPeriodicUiMetricLogs: PeriodicWorkRequest = - PeriodicWorkRequest.Builder( - MetricLogSchedulingWorker::class.java, - performanceMetricsCollectionHighFrequencyTimeInterval.value.toLong(), - TimeUnit.MINUTES - ) - .setInputData(workerCaseForSchedulingPeriodicUiMetricLogs) - .setConstraints(logReportWorkerConstraints) - .build() - - private val workRequestForUploadingFireStoreData: PeriodicWorkRequest = - PeriodicWorkRequest.Builder(LogUploadWorker::class.java, 6, TimeUnit.HOURS) - .setInputData(workerCaseForUploadingFirestoreData) - .setConstraints(logReportWorkerConstraints) - .build() - - override fun onCreate(workManager: WorkManager) { - logUploader.enqueueWorkRequestForEvents(workManager, workRequestForUploadingEvents) - logUploader.enqueueWorkRequestForExceptions(workManager, workRequestForUploadingExceptions) - logUploader.enqueueWorkRequestForPerformanceMetrics( - workManager, - workRequestForUploadingPerformanceMetrics - ) - logUploader.enqueueWorkRequestForFirestore( - workManager, - workRequestForUploadingFireStoreData - ) - metricLogScheduler.enqueueWorkRequestForPeriodicBackgroundMetrics( - workManager, - workRequestForSchedulingPeriodicBackgroundMetricLogs - ) - metricLogScheduler.enqueueWorkRequestForStorageUsage( - workManager, - workRequestForSchedulingStorageUsageMetricLogs - ) - metricLogScheduler.enqueueWorkRequestForPeriodicUiMetrics( - workManager, - workRequestForSchedulingPeriodicUiMetricLogs - ) - } - - /** Returns the worker constraints set for the log reporting work requests. */ - fun getLogReportWorkerConstraints(): Constraints = logReportWorkerConstraints - - /** Returns the [UUID] of the work request that is enqueued for uploading event logs. */ - fun getWorkRequestForEventsId(): UUID = workRequestForUploadingEvents.id - - /** Returns the [UUID] of the work request that is enqueued for uploading exception logs. */ - fun getWorkRequestForExceptionsId(): UUID = workRequestForUploadingExceptions.id - - /** Returns the [UUID] of the work request that is enqueued for uploading performance metrics logs. */ - fun getWorkRequestForPerformanceMetricsId(): UUID = workRequestForUploadingPerformanceMetrics.id - - /** - * Returns the [UUID] of the work request that is enqueued for scheduling memory usage - * performance metrics collection. - */ - fun getWorkRequestForSchedulingPeriodicUiMetricLogsId(): UUID = - workRequestForSchedulingPeriodicUiMetricLogs.id - - /** - * Returns the [UUID] of the work request that is enqueued for scheduling storage usage - * performance metrics collection. - */ - fun getWorkRequestForSchedulingStorageUsageMetricLogsId(): UUID = - workRequestForSchedulingStorageUsageMetricLogs.id - - /** - * Returns the [UUID] of the work request that is enqueued for scheduling periodic performance - * metrics collection. - */ - fun getWorkRequestForSchedulingPeriodicBackgroundPerformanceMetricLogsId(): UUID = - workRequestForSchedulingPeriodicBackgroundMetricLogs.id - - /** Returns the [UUID] of the work request that is enqueued for uploading firestore data. */ - fun getWorkRequestForFirestoreId(): UUID = workRequestForUploadingFireStoreData.id - - /** - * Returns the [Data] that goes into the work request that is enqueued for uploading event logs. - */ - fun getWorkRequestDataForEvents(): Data = workerCaseForUploadingEvents - - /** - * Returns the [Data] that goes into the work request that is enqueued for uploading exception - * logs. - */ - fun getWorkRequestDataForExceptions(): Data = workerCaseForUploadingExceptions - - /** Returns the [Data] that goes into the work request that is enqueued for uploading performance metric logs. */ - fun getWorkRequestDataForPerformanceMetrics(): Data = workerCaseForUploadingPerformanceMetrics - - /** - * Returns the [Data] that goes into the work request that is enqueued for scheduling storage - * usage performance metrics collection. - */ - fun getWorkRequestDataForSchedulingStorageUsageMetricLogs(): Data = - workerCaseForSchedulingStorageUsageMetricLogs - - /** - * Returns the [Data] that goes into the work request that is enqueued for scheduling memory - * usage performance metrics collection. - */ - fun getWorkRequestDataForSchedulingPeriodicUiMetricLogs(): Data = - workerCaseForSchedulingPeriodicUiMetricLogs - - /** - * Returns the [Data] that goes into the work request that is enqueued for scheduling periodic - * performance metrics collection. - */ - fun getWorkRequestDataForSchedulingPeriodicBackgroundPerformanceMetricLogs(): Data = - workerCaseForSchedulingPeriodicBackgroundMetricLogs - - /** - * Returns the [Data] that goes into the work request that is enqueued for uploading firestore - * data. - */ - fun getWorkRequestDataForFirestore(): Data = workerCaseForUploadingFirestoreData -} diff --git a/domain/src/main/java/org/oppia/android/domain/oppialogger/loguploader/LogReportWorkerModule.kt b/domain/src/main/java/org/oppia/android/domain/oppialogger/loguploader/LogReportWorkerModule.kt index 32725e43266..8c65be9bdab 100644 --- a/domain/src/main/java/org/oppia/android/domain/oppialogger/loguploader/LogReportWorkerModule.kt +++ b/domain/src/main/java/org/oppia/android/domain/oppialogger/loguploader/LogReportWorkerModule.kt @@ -2,16 +2,23 @@ package org.oppia.android.domain.oppialogger.loguploader import dagger.Binds import dagger.Module +import dagger.multibindings.IntoMap import dagger.multibindings.IntoSet -import org.oppia.android.domain.oppialogger.analytics.AnalyticsStartupListener +import dagger.multibindings.StringKey +import org.oppia.android.domain.workmanager.OppiaWorker +import org.oppia.android.domain.workmanager.StartupWorkerScheduleReadinessListener /** Provides [LogUploadWorker] related dependencies. */ @Module interface LogReportWorkerModule { - @Binds @IntoSet - fun bindLogReportWorkRequest( - logReportWorkManagerInitializer: LogReportWorkManagerInitializer - ): AnalyticsStartupListener + fun bindLogReportWorkerScheduler( + scheduler: LogReportWorkerScheduler + ): StartupWorkerScheduleReadinessListener + + @Binds + @IntoMap + @StringKey(LogUploadWorker.WORKER_NAME) + fun bindLogUploadWorkerFactoryProvider(factory: LogUploadWorker.Factory): OppiaWorker.Factory<*> } diff --git a/domain/src/main/java/org/oppia/android/domain/oppialogger/loguploader/LogReportWorkerScheduler.kt b/domain/src/main/java/org/oppia/android/domain/oppialogger/loguploader/LogReportWorkerScheduler.kt new file mode 100644 index 00000000000..accbefcb4c4 --- /dev/null +++ b/domain/src/main/java/org/oppia/android/domain/oppialogger/loguploader/LogReportWorkerScheduler.kt @@ -0,0 +1,20 @@ +package org.oppia.android.domain.oppialogger.loguploader + +import org.oppia.android.domain.workmanager.StartupWorkerScheduleReadinessListener +import org.oppia.android.domain.workmanager.WorkManagerScheduler +import java.util.concurrent.TimeUnit +import javax.inject.Inject + +/** + * Scheduler for [LogUploadWorker] operations once startup-bound workers are allowed to initialize + * during application startup. + */ +class LogReportWorkerScheduler @Inject constructor() : StartupWorkerScheduleReadinessListener { + override fun scheduleWork(workManagerScheduler: WorkManagerScheduler) { + for (operation in LogUploadWorker.Operation.values()) { + workManagerScheduler.schedulePeriodicWorker( + LogUploadWorker.WORKER_NAME, operation, repeatInterval = 6, intervalUnit = TimeUnit.HOURS + ) + } + } +} diff --git a/domain/src/main/java/org/oppia/android/domain/oppialogger/loguploader/LogUploadWorker.kt b/domain/src/main/java/org/oppia/android/domain/oppialogger/loguploader/LogUploadWorker.kt index ea8bb8cf2ce..a47ba30a926 100644 --- a/domain/src/main/java/org/oppia/android/domain/oppialogger/loguploader/LogUploadWorker.kt +++ b/domain/src/main/java/org/oppia/android/domain/oppialogger/loguploader/LogUploadWorker.kt @@ -1,118 +1,91 @@ package org.oppia.android.domain.oppialogger.loguploader -import android.content.Context -import androidx.work.ListenableWorker -import androidx.work.WorkerParameters -import com.google.common.util.concurrent.ListenableFuture -import kotlinx.coroutines.CoroutineDispatcher -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.async -import kotlinx.coroutines.guava.asListenableFuture import org.oppia.android.domain.oppialogger.analytics.AnalyticsController import org.oppia.android.domain.oppialogger.analytics.FirestoreDataController import org.oppia.android.domain.oppialogger.analytics.PerformanceMetricsController import org.oppia.android.domain.oppialogger.exceptions.ExceptionsController import org.oppia.android.domain.oppialogger.exceptions.toException -import org.oppia.android.domain.util.getStringFromData -import org.oppia.android.util.extensions.safeForEach +import org.oppia.android.domain.workmanager.OppiaWorker import org.oppia.android.util.logging.ConsoleLogger import org.oppia.android.util.logging.ExceptionLogger import org.oppia.android.util.logging.SyncStatusManager import org.oppia.android.util.logging.performancemetrics.PerformanceMetricsEventLogger -import org.oppia.android.util.threading.BackgroundDispatcher +import org.oppia.android.util.networking.NetworkConnectionUtil +import org.oppia.android.util.networking.NetworkConnectionUtil.ProdConnectionStatus.NONE import javax.inject.Inject -/** Worker class that extracts log reports from the cache store and logs them to the remote service. */ +/** Worker to upload cached analytics (including events, exceptions, and performance metrics). */ class LogUploadWorker private constructor( - context: Context, - params: WorkerParameters, private val analyticsController: AnalyticsController, private val exceptionsController: ExceptionsController, private val performanceMetricsController: PerformanceMetricsController, private val exceptionLogger: ExceptionLogger, - private val dataController: FirestoreDataController, + private val firestoreDataController: FirestoreDataController, private val performanceMetricsEventLogger: PerformanceMetricsEventLogger, private val consoleLogger: ConsoleLogger, private val syncStatusManager: SyncStatusManager, - @BackgroundDispatcher private val backgroundDispatcher: CoroutineDispatcher -) : ListenableWorker(context, params) { - + private val networkConnectionUtil: NetworkConnectionUtil +) : OppiaWorker { companion object { - const val WORKER_CASE_KEY = "worker_case_key" - const val TAG = "LogUploadWorker.tag" - const val EVENT_WORKER = "event_worker" - const val EXCEPTION_WORKER = "exception_worker" - const val PERFORMANCE_METRICS_WORKER = "performance_metrics_worker" - const val FIRESTORE_WORKER = "firestore_worker" + /** The unique name used to schedule this worker. */ + const val WORKER_NAME = "LogUploadWorker" } - override fun startWork(): ListenableFuture { - val backgroundScope = CoroutineScope(backgroundDispatcher) - // TODO(#4463): Add withTimeout() to avoid potential hanging. - return backgroundScope.async { - when (inputData.getStringFromData(WORKER_CASE_KEY)) { - EVENT_WORKER -> uploadEvents() - EXCEPTION_WORKER -> uploadExceptions() - PERFORMANCE_METRICS_WORKER -> uploadPerformanceMetrics() - FIRESTORE_WORKER -> uploadFirestoreData() - else -> Result.failure() - } - }.asListenableFuture() + /** The operation types supported by [LogUploadWorker]. */ + enum class Operation(override val persistentName: String) : OppiaWorker.TaskType { + /** Instructs the worker to upload offline-cached logged analytics events. */ + UPLOAD_EVENTS("upload_events"), + /** Instructs the worker to upload offline-cached logged exceptions. */ + UPLOAD_EXCEPTIONS("upload_exceptions"), + /** Instructs the worker to upload offline-cached logged performance metrics. */ + UPLOAD_PERFORMANCE_METRICS("upload_performance_metrics"), + /** Instructs the worker to upload offline-cached logged Firestore-bound events. */ + UPLOAD_FIRESTORE_DATA("upload_firestore_data") } - /** Extracts exception logs from the cache store and logs them to the remote service. */ - private suspend fun uploadExceptions(): Result { + override suspend fun doWork(taskType: Operation): OppiaWorker.Result { return try { - val exceptionLogs = exceptionsController.getExceptionLogStoreList() - exceptionLogs.let { - for (exceptionLog in it) { - exceptionLogger.logException(exceptionLog.toException()) - exceptionsController.removeFirstExceptionLogFromStore() - } + when (taskType) { + Operation.UPLOAD_EVENTS -> analyticsController.uploadEventLogsAndWait() + Operation.UPLOAD_EXCEPTIONS -> uploadExceptions() + Operation.UPLOAD_PERFORMANCE_METRICS -> uploadPerformanceMetrics() + Operation.UPLOAD_FIRESTORE_DATA -> uploadFirestoreEvents() } - Result.success() + OppiaWorker.Result.SUCCESS } catch (e: Exception) { - consoleLogger.e(TAG, e.toString(), e) - Result.failure() + if (taskType == Operation.UPLOAD_EVENTS) { + syncStatusManager.reportUploadError() + } + consoleLogger.e(WORKER_NAME, "Failed operation: ${taskType.persistentName}.", e) + OppiaWorker.Result.FAILURE } } - /** Extracts event logs from the cache store and logs them to the remote service. */ - private suspend fun uploadEvents(): Result { - return try { - analyticsController.uploadEventLogsAndWait() - Result.success() - } catch (e: Exception) { - syncStatusManager.reportUploadError() - consoleLogger.e(TAG, "Failed to upload events", e) - Result.failure() + private suspend fun uploadExceptions() { + check(networkConnectionUtil.getCurrentConnectionStatus() != NONE) { + "Cannot upload exceptions without internet connectivity." + } + for (exceptionLog in exceptionsController.getExceptionLogStoreList()) { + exceptionLogger.logException(exceptionLog.toException()) + exceptionsController.removeFirstExceptionLogFromStore() } } - /** Extracts performance metric logs from the cache store and logs them to the remote service. */ - private suspend fun uploadPerformanceMetrics(): Result { - return try { - val performanceMetricsLogs = performanceMetricsController.getMetricLogStoreList() - performanceMetricsLogs.safeForEach { performanceMetricsLog -> - performanceMetricsEventLogger.logPerformanceMetric(performanceMetricsLog) - performanceMetricsController.removeFirstMetricLogFromStore() - } - Result.success() - } catch (e: Exception) { - consoleLogger.e(TAG, e.toString(), e) - Result.failure() + private suspend fun uploadPerformanceMetrics() { + check(networkConnectionUtil.getCurrentConnectionStatus() != NONE) { + "Cannot upload performance metrics without internet connectivity." + } + for (performanceMetricsLog in performanceMetricsController.getMetricLogStoreList()) { + performanceMetricsEventLogger.logPerformanceMetric(performanceMetricsLog) + performanceMetricsController.removeFirstMetricLogFromStore() } } - /** Extracts data from offline storage and logs them to the remote service. */ - private suspend fun uploadFirestoreData(): Result { - return try { - dataController.uploadData() - Result.success() - } catch (e: Exception) { - consoleLogger.e(TAG, e.toString(), e) - Result.failure() + private suspend fun uploadFirestoreEvents() { + check(networkConnectionUtil.getCurrentConnectionStatus() != NONE) { + "Cannot upload Firestore events without internet connectivity." } + firestoreDataController.uploadData() } /** Creates an instance of [LogUploadWorker] by properly injecting dependencies. */ @@ -125,12 +98,12 @@ class LogUploadWorker private constructor( private val performanceMetricsEventLogger: PerformanceMetricsEventLogger, private val consoleLogger: ConsoleLogger, private val syncStatusManager: SyncStatusManager, - @BackgroundDispatcher private val backgroundDispatcher: CoroutineDispatcher - ) { - fun create(context: Context, params: WorkerParameters): ListenableWorker { + private val networkConnectionUtil: NetworkConnectionUtil + ) : OppiaWorker.Factory { + override val supportedTaskTypes: List = Operation.values().toList() + + override fun createWorker(): OppiaWorker { return LogUploadWorker( - context, - params, analyticsController, exceptionsController, performanceMetricsController, @@ -139,7 +112,7 @@ class LogUploadWorker private constructor( performanceMetricsEventLogger, consoleLogger, syncStatusManager, - backgroundDispatcher + networkConnectionUtil ) } } diff --git a/domain/src/main/java/org/oppia/android/domain/oppialogger/loguploader/LogUploadWorkerFactory.kt b/domain/src/main/java/org/oppia/android/domain/oppialogger/loguploader/LogUploadWorkerFactory.kt deleted file mode 100644 index 1308e0bf7f5..00000000000 --- a/domain/src/main/java/org/oppia/android/domain/oppialogger/loguploader/LogUploadWorkerFactory.kt +++ /dev/null @@ -1,22 +0,0 @@ -package org.oppia.android.domain.oppialogger.loguploader - -import android.content.Context -import androidx.work.ListenableWorker -import androidx.work.WorkerFactory -import androidx.work.WorkerParameters -import javax.inject.Inject - -/** Custom [WorkerFactory] for the [LogUploadWorker]. */ -class LogUploadWorkerFactory @Inject constructor( - private val workerFactory: LogUploadWorker.Factory -) : WorkerFactory() { - - /** Returns a new [LogUploadWorker] for the given context and parameters. */ - override fun createWorker( - appContext: Context, - workerClassName: String, - workerParameters: WorkerParameters - ): ListenableWorker? { - return workerFactory.create(appContext, workerParameters) - } -} diff --git a/domain/src/main/java/org/oppia/android/domain/oppialogger/survey/BUILD.bazel b/domain/src/main/java/org/oppia/android/domain/oppialogger/survey/BUILD.bazel index 215f85d2212..81e8a490d74 100644 --- a/domain/src/main/java/org/oppia/android/domain/oppialogger/survey/BUILD.bazel +++ b/domain/src/main/java/org/oppia/android/domain/oppialogger/survey/BUILD.bazel @@ -10,7 +10,8 @@ kt_android_library( visibility = ["//:oppia_api_visibility"], deps = [ "//domain", - "//domain/src/main/java/org/oppia/android/domain/oppialogger:oppia_logger", + "//domain/src/main/java/org/oppia/android/domain/oppialogger/analytics:controller", + "//domain/src/main/java/org/oppia/android/domain/oppialogger/analytics:data_controller", "//model/src/main/proto:survey_java_proto_lite", "//third_party:javax_inject_javax_inject", ], diff --git a/domain/src/main/java/org/oppia/android/domain/platformparameter/syncup/BUILD.bazel b/domain/src/main/java/org/oppia/android/domain/platformparameter/syncup/BUILD.bazel index 195399bd015..3c72852f8ec 100644 --- a/domain/src/main/java/org/oppia/android/domain/platformparameter/syncup/BUILD.bazel +++ b/domain/src/main/java/org/oppia/android/domain/platformparameter/syncup/BUILD.bazel @@ -7,21 +7,19 @@ load("@io_bazel_rules_kotlin//kotlin:android.bzl", "kt_android_library") kt_android_library( name = "syncup", srcs = [ - "PlatformParameterSyncUpWorkManagerInitializer.kt", "PlatformParameterSyncUpWorker.kt", - "PlatformParameterSyncUpWorkerFactory.kt", "PlatformParameterSyncUpWorkerModule.kt", + "PlatformParameterSyncUpWorkerScheduler.kt", ], visibility = ["//:oppia_api_visibility"], deps = [ "//:dagger", "//domain/src/main/java/org/oppia/android/domain/oppialogger:oppia_logger", - "//domain/src/main/java/org/oppia/android/domain/oppialogger/analytics:analytics_startup_listener", "//domain/src/main/java/org/oppia/android/domain/oppialogger/exceptions:controller", "//domain/src/main/java/org/oppia/android/domain/platformparameter:controller", "//domain/src/main/java/org/oppia/android/domain/util:extensions", - "//third_party:org_jetbrains_kotlinx_kotlinx-coroutines-guava", + "//domain/src/main/java/org/oppia/android/domain/workmanager:oppia_worker", + "//domain/src/main/java/org/oppia/android/domain/workmanager:startup_worker_schedule_readiness_listener", "//utility", - "//utility/src/main/java/org/oppia/android/util/threading:annotations", ], ) diff --git a/domain/src/main/java/org/oppia/android/domain/platformparameter/syncup/PlatformParameterSyncUpWorkManagerInitializer.kt b/domain/src/main/java/org/oppia/android/domain/platformparameter/syncup/PlatformParameterSyncUpWorkManagerInitializer.kt deleted file mode 100644 index c0e4c0002cb..00000000000 --- a/domain/src/main/java/org/oppia/android/domain/platformparameter/syncup/PlatformParameterSyncUpWorkManagerInitializer.kt +++ /dev/null @@ -1,85 +0,0 @@ -package org.oppia.android.domain.platformparameter.syncup - -import android.annotation.SuppressLint -import androidx.annotation.VisibleForTesting -import androidx.work.Constraints -import androidx.work.Data -import androidx.work.ExistingPeriodicWorkPolicy -import androidx.work.NetworkType -import androidx.work.PeriodicWorkRequest -import androidx.work.WorkManager -import org.oppia.android.domain.oppialogger.analytics.AnalyticsStartupListener -import org.oppia.android.util.platformparameter.PlatformParameterValue -import org.oppia.android.util.platformparameter.SyncUpWorkerTimePeriodHours -import java.util.UUID -import java.util.concurrent.TimeUnit -import javax.inject.Inject - -/** - * Enqueues unique periodic work requests for fetching and caching latest platform parameter values - * from the remote service on application creation. - */ -class PlatformParameterSyncUpWorkManagerInitializer @Inject constructor( - @SyncUpWorkerTimePeriodHours private val workRequestRepeatInterval: PlatformParameterValue -) : AnalyticsStartupListener { - - private val OPPIA_PLATFORM_PARAMETER_WORK_REQUEST_NAME = "OPPIA_PLATFORM_PARAMETER_WORK_REQUEST" - - /** [Constraints] for platform parameter sync up work request. */ - private val platformParameterSyncUpWorkerConstraints = Constraints.Builder() - .setRequiredNetworkType(NetworkType.CONNECTED) - .setRequiresBatteryNotLow(true) - .build() - - /** [Data] for platform parameter sync up work request. */ - private val workerTypeForSyncingPlatformParameters: Data = Data.Builder() - .putString( - PlatformParameterSyncUpWorker.WORKER_TYPE_KEY, - PlatformParameterSyncUpWorker.PLATFORM_PARAMETER_WORKER - ) - .build() - - /** [PeriodicWorkRequest] for platform parameter sync up worker. */ - private val workRequestForSyncingPlatformParameters = - PeriodicWorkRequest.Builder( - PlatformParameterSyncUpWorker::class.java, - workRequestRepeatInterval.value.toLong(), - TimeUnit.HOURS - ) - .addTag(PlatformParameterSyncUpWorker.TAG) - .setInputData(workerTypeForSyncingPlatformParameters) - .setConstraints(platformParameterSyncUpWorkerConstraints) - .build() - - override fun onCreate(workManager: WorkManager) { - workManager.enqueueUniquePeriodicWork( - OPPIA_PLATFORM_PARAMETER_WORK_REQUEST_NAME, - ExistingPeriodicWorkPolicy.KEEP, - workRequestForSyncingPlatformParameters - ) - } - - /** Returns the [UUID] of the work request that is enqueued to sync-up platform parameters. */ - @VisibleForTesting - fun getSyncUpWorkRequestId(): UUID { - return workRequestForSyncingPlatformParameters.id - } - - /** Returns the [Data] that goes into the work request enqueued to sync-up platform parameters. */ - @VisibleForTesting - fun getSyncUpWorkRequestData(): Data { - return workerTypeForSyncingPlatformParameters - } - - /** Returns the time interval of periodic work request enqueued to sync-up platform parameters. */ - @SuppressLint("RestrictedApi") // getWorkSpec is restricted; suppression is fine for tests. - @VisibleForTesting - fun getSyncUpWorkerTimePeriod(): Long { - return workRequestForSyncingPlatformParameters.workSpec.intervalDuration - } - - /** Returns the Worker [Constraints] set for the platform parameter sync-up work requests. */ - fun getSyncUpWorkerConstraints(): Constraints { - return platformParameterSyncUpWorkerConstraints - } -} diff --git a/domain/src/main/java/org/oppia/android/domain/platformparameter/syncup/PlatformParameterSyncUpWorker.kt b/domain/src/main/java/org/oppia/android/domain/platformparameter/syncup/PlatformParameterSyncUpWorker.kt index 1a0a35e38b4..76c4f7575c3 100644 --- a/domain/src/main/java/org/oppia/android/domain/platformparameter/syncup/PlatformParameterSyncUpWorker.kt +++ b/domain/src/main/java/org/oppia/android/domain/platformparameter/syncup/PlatformParameterSyncUpWorker.kt @@ -1,71 +1,54 @@ package org.oppia.android.domain.platformparameter.syncup -import android.content.Context -import androidx.work.ListenableWorker -import androidx.work.WorkerParameters -import com.google.common.util.concurrent.ListenableFuture -import kotlinx.coroutines.CoroutineDispatcher -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.async -import kotlinx.coroutines.guava.asListenableFuture import org.oppia.android.domain.oppialogger.OppiaLogger import org.oppia.android.domain.oppialogger.exceptions.ExceptionsController import org.oppia.android.domain.platformparameter.PlatformParameterController -import org.oppia.android.domain.util.getStringFromData +import org.oppia.android.domain.workmanager.OppiaWorker import org.oppia.android.util.data.AsyncResult -import org.oppia.android.util.threading.BackgroundDispatcher import javax.inject.Inject -/** Worker class that fetches and caches the latest platform parameters from the remote service. */ +/** Worker to fetch and cache the latest platform parameters from the remote service. */ class PlatformParameterSyncUpWorker private constructor( - context: Context, - params: WorkerParameters, private val platformParameterController: PlatformParameterController, private val oppiaLogger: OppiaLogger, - private val exceptionsController: ExceptionsController, - @BackgroundDispatcher private val backgroundDispatcher: CoroutineDispatcher -) : ListenableWorker(context, params) { - + private val exceptionsController: ExceptionsController +) : OppiaWorker { companion object { - /** A Tag for the logs that are associated with PlatformParameterSyncUpWorker. */ - const val TAG = "PlatformParameterWorker.tag" - - /** Value of worker-type associated with this PlatformParameterSyncUpWorker. */ - const val PLATFORM_PARAMETER_WORKER = "platform_parameter_worker" - - /** Key for passing the worker-type as a parameter to PlatformParameterSyncUpWorker. */ - const val WORKER_TYPE_KEY = "worker_type_key" + /** The unique name used to schedule this worker. */ + const val WORKER_NAME = "PlatformParameterSyncUpWorker" } - override fun startWork(): ListenableFuture { - val backgroundScope = CoroutineScope(backgroundDispatcher) - // TODO(#4463): Add withTimeout() to avoid potential hanging. - return backgroundScope.async { - when (inputData.getStringFromData(WORKER_TYPE_KEY)) { - PLATFORM_PARAMETER_WORKER -> refreshPlatformParameters() - else -> Result.failure() - } - }.asListenableFuture() + /** The operation types supported by [PlatformParameterSyncUpWorker]. */ + enum class Operation(override val persistentName: String) : OppiaWorker.TaskType { + /** + * Instructs the worker to refresh platform parameters by downloading the latest values from + * Oppia web. + */ + REFRESH_PLATFORM_PARAMETERS("refresh_platform_parameters") } - /** Extracts platform parameters from the remote service and stores them in the cache store. */ - private suspend fun refreshPlatformParameters(): Result { - // This is valid to do per the contract of the returned DataProvider (there will only ever be - // one result from the provider). - val result = platformParameterController.downloadRemoteParameters().retrieveData() - return when (result) { - is AsyncResult.Pending -> { - oppiaLogger.e(TAG, "Unexpected pending state when downloading remote parameters.") - Result.failure() - } - is AsyncResult.Failure -> { - oppiaLogger.e(TAG, "Failed to fetch platform parameters", result.error) - exceptionsController.logNonFatalException( - IllegalStateException("Failed to fetch platform parameters", result.error) - ) - Result.failure() + override suspend fun doWork(taskType: Operation): OppiaWorker.Result { + return when (taskType) { + Operation.REFRESH_PLATFORM_PARAMETERS -> { + // This is valid to do per the contract of the returned DataProvider (there will only ever + // be one result from the provider). + when (val result = platformParameterController.downloadRemoteParameters().retrieveData()) { + is AsyncResult.Pending -> { + oppiaLogger.e( + WORKER_NAME, "Unexpected pending state when downloading remote parameters." + ) + OppiaWorker.Result.FAILURE + } + is AsyncResult.Failure -> { + oppiaLogger.e(WORKER_NAME, "Failed to fetch platform parameters", result.error) + exceptionsController.logNonFatalException( + IllegalStateException("Failed to fetch platform parameters", result.error) + ) + OppiaWorker.Result.FAILURE + } + is AsyncResult.Success -> OppiaWorker.Result.SUCCESS + } } - is AsyncResult.Success -> Result.Success() } } @@ -73,18 +56,15 @@ class PlatformParameterSyncUpWorker private constructor( class Factory @Inject constructor( private val platformParameterController: PlatformParameterController, private val oppiaLogger: OppiaLogger, - private val exceptionsController: ExceptionsController, - @BackgroundDispatcher private val backgroundDispatcher: CoroutineDispatcher - ) { - /** Returns new instances of [PlatformParameterSyncUpWorker]. */ - fun create(context: Context, params: WorkerParameters): ListenableWorker { + private val exceptionsController: ExceptionsController + ) : OppiaWorker.Factory { + override val supportedTaskTypes: List = Operation.values().toList() + + override fun createWorker(): OppiaWorker { return PlatformParameterSyncUpWorker( - context, - params, platformParameterController, oppiaLogger, - exceptionsController, - backgroundDispatcher + exceptionsController ) } } diff --git a/domain/src/main/java/org/oppia/android/domain/platformparameter/syncup/PlatformParameterSyncUpWorkerFactory.kt b/domain/src/main/java/org/oppia/android/domain/platformparameter/syncup/PlatformParameterSyncUpWorkerFactory.kt deleted file mode 100644 index b0da28ae48d..00000000000 --- a/domain/src/main/java/org/oppia/android/domain/platformparameter/syncup/PlatformParameterSyncUpWorkerFactory.kt +++ /dev/null @@ -1,20 +0,0 @@ -package org.oppia.android.domain.platformparameter.syncup - -import android.content.Context -import androidx.work.ListenableWorker -import androidx.work.WorkerFactory -import androidx.work.WorkerParameters -import javax.inject.Inject - -/** Custom [WorkerFactory] for the [PlatformParameterSyncUpWorker]. */ -class PlatformParameterSyncUpWorkerFactory @Inject constructor( - private val platformParameterSyncUpWorkerFactory: PlatformParameterSyncUpWorker.Factory -) : WorkerFactory() { - override fun createWorker( - context: Context, - workerClassName: String, - workerParameters: WorkerParameters - ): ListenableWorker? { - return platformParameterSyncUpWorkerFactory.create(context, workerParameters) - } -} diff --git a/domain/src/main/java/org/oppia/android/domain/platformparameter/syncup/PlatformParameterSyncUpWorkerModule.kt b/domain/src/main/java/org/oppia/android/domain/platformparameter/syncup/PlatformParameterSyncUpWorkerModule.kt index ad99b3c3ddd..9ad30a8f09e 100644 --- a/domain/src/main/java/org/oppia/android/domain/platformparameter/syncup/PlatformParameterSyncUpWorkerModule.kt +++ b/domain/src/main/java/org/oppia/android/domain/platformparameter/syncup/PlatformParameterSyncUpWorkerModule.kt @@ -2,16 +2,25 @@ package org.oppia.android.domain.platformparameter.syncup import dagger.Binds import dagger.Module +import dagger.multibindings.IntoMap import dagger.multibindings.IntoSet -import org.oppia.android.domain.oppialogger.analytics.AnalyticsStartupListener +import dagger.multibindings.StringKey +import org.oppia.android.domain.workmanager.OppiaWorker +import org.oppia.android.domain.workmanager.StartupWorkerScheduleReadinessListener /** Provides [PlatformParameterSyncUpWorker] related dependencies. */ @Module interface PlatformParameterSyncUpWorkerModule { - @Binds @IntoSet - fun bindLogUploadWorkRequest( - platformParameterSyncUpWorkManagerInitializer: PlatformParameterSyncUpWorkManagerInitializer - ): AnalyticsStartupListener + fun bindPlatformParameterSyncUpWorkerScheduler( + scheduler: PlatformParameterSyncUpWorkerScheduler + ): StartupWorkerScheduleReadinessListener + + @Binds + @IntoMap + @StringKey(PlatformParameterSyncUpWorker.WORKER_NAME) + fun bindLogUploadWorkerFactoryProvider( + factory: PlatformParameterSyncUpWorker.Factory + ): OppiaWorker.Factory<*> } diff --git a/domain/src/main/java/org/oppia/android/domain/platformparameter/syncup/PlatformParameterSyncUpWorkerScheduler.kt b/domain/src/main/java/org/oppia/android/domain/platformparameter/syncup/PlatformParameterSyncUpWorkerScheduler.kt new file mode 100644 index 00000000000..761b2e6c2c4 --- /dev/null +++ b/domain/src/main/java/org/oppia/android/domain/platformparameter/syncup/PlatformParameterSyncUpWorkerScheduler.kt @@ -0,0 +1,25 @@ +package org.oppia.android.domain.platformparameter.syncup + +import org.oppia.android.domain.workmanager.StartupWorkerScheduleReadinessListener +import org.oppia.android.domain.workmanager.WorkManagerScheduler +import org.oppia.android.util.platformparameter.PlatformParameterValue +import org.oppia.android.util.platformparameter.SyncUpWorkerTimePeriodHours +import java.util.concurrent.TimeUnit +import javax.inject.Inject + +/** + * Scheduler for [PlatformParameterSyncUpWorker] operations once startup-bound workers are allowed + * to initialize during application startup. + */ +class PlatformParameterSyncUpWorkerScheduler @Inject constructor( + @SyncUpWorkerTimePeriodHours private val workRequestRepeatInterval: PlatformParameterValue +) : StartupWorkerScheduleReadinessListener { + override fun scheduleWork(workManagerScheduler: WorkManagerScheduler) { + workManagerScheduler.schedulePeriodicWorker( + PlatformParameterSyncUpWorker.WORKER_NAME, + PlatformParameterSyncUpWorker.Operation.REFRESH_PLATFORM_PARAMETERS, + workRequestRepeatInterval.value.toLong(), + TimeUnit.HOURS + ) + } +} diff --git a/domain/src/main/java/org/oppia/android/domain/testing/oppialogger/loguploader/BUILD.bazel b/domain/src/main/java/org/oppia/android/domain/testing/oppialogger/loguploader/BUILD.bazel deleted file mode 100644 index c2714676669..00000000000 --- a/domain/src/main/java/org/oppia/android/domain/testing/oppialogger/loguploader/BUILD.bazel +++ /dev/null @@ -1,18 +0,0 @@ -""" -Package for testing utilities for log uploading functionality. -""" - -load("@io_bazel_rules_kotlin//kotlin:android.bzl", "kt_android_library") - -kt_android_library( - name = "fake_log_uploader", - testonly = True, - srcs = [ - "FakeLogUploader.kt", - ], - visibility = ["//:oppia_testing_visibility"], - deps = [ - "//third_party:javax_inject_javax_inject", - "//utility/src/main/java/org/oppia/android/util/logging:log_uploader", - ], -) diff --git a/domain/src/main/java/org/oppia/android/domain/testing/oppialogger/loguploader/FakeLogUploader.kt b/domain/src/main/java/org/oppia/android/domain/testing/oppialogger/loguploader/FakeLogUploader.kt deleted file mode 100644 index a6a3aae43b1..00000000000 --- a/domain/src/main/java/org/oppia/android/domain/testing/oppialogger/loguploader/FakeLogUploader.kt +++ /dev/null @@ -1,57 +0,0 @@ -package org.oppia.android.domain.testing.oppialogger.loguploader - -import androidx.work.PeriodicWorkRequest -import androidx.work.WorkManager -import org.oppia.android.util.logging.LogUploader -import java.util.UUID -import javax.inject.Inject -import javax.inject.Singleton - -/** A test specific fake for the log uploader. */ -@Singleton -class FakeLogUploader @Inject constructor() : LogUploader { - private val eventRequestIdList = mutableListOf() - private val exceptionRequestIdList = mutableListOf() - private val performanceMetricsRequestIdList = mutableListOf() - private val firestoreRequestIdList = mutableListOf() - - override fun enqueueWorkRequestForEvents( - workManager: WorkManager, - workRequest: PeriodicWorkRequest - ) { - eventRequestIdList.add(workRequest.id) - } - - override fun enqueueWorkRequestForExceptions( - workManager: WorkManager, - workRequest: PeriodicWorkRequest - ) { - exceptionRequestIdList.add(workRequest.id) - } - - override fun enqueueWorkRequestForPerformanceMetrics( - workManager: WorkManager, - workRequest: PeriodicWorkRequest - ) { - performanceMetricsRequestIdList.add(workRequest.id) - } - - override fun enqueueWorkRequestForFirestore( - workManager: WorkManager, - workRequest: PeriodicWorkRequest - ) { - firestoreRequestIdList.add(workRequest.id) - } - - /** Returns the most recent work request id that's stored in the [eventRequestIdList]. */ - fun getMostRecentEventRequestId() = eventRequestIdList.last() - - /** Returns the most recent work request id that's stored in the [exceptionRequestIdList]. */ - fun getMostRecentExceptionRequestId() = exceptionRequestIdList.last() - - /** Returns the most recent work request id that's stored in the [performanceMetricsRequestIdList]. */ - fun getMostRecentPerformanceMetricsRequestId() = performanceMetricsRequestIdList.last() - - /** Returns the most recent work request id that's stored in the [firestoreRequestIdList]. */ - fun getMostRecentFirestoreRequestId() = firestoreRequestIdList.last() -} diff --git a/domain/src/main/java/org/oppia/android/domain/workmanager/BUILD.bazel b/domain/src/main/java/org/oppia/android/domain/workmanager/BUILD.bazel new file mode 100644 index 00000000000..45fdf428490 --- /dev/null +++ b/domain/src/main/java/org/oppia/android/domain/workmanager/BUILD.bazel @@ -0,0 +1,75 @@ +""" +Package for supporting Dagger bindings for work manager worker factories. +""" + +load("@io_bazel_rules_kotlin//kotlin:android.bzl", "kt_android_library") + +kt_android_library( + name = "oppia_worker", + srcs = ["OppiaWorker.kt"], + visibility = ["//:oppia_api_visibility"], + deps = ["//third_party:androidx_work_work-runtime"], +) + +kt_android_library( + name = "startup_worker_schedule_readiness_listener", + srcs = ["StartupWorkerScheduleReadinessListener.kt"], + visibility = ["//:oppia_api_visibility"], + deps = [":work_manager_scheduler"], +) + +kt_android_library( + name = "startup_worker_schedule_readiness_monitor", + srcs = ["StartupWorkerScheduleReadinessMonitor.kt"], + deps = [ + ":startup_worker_schedule_readiness_listener", + ":work_manager_scheduler", + "//domain/src/main/java/org/oppia/android/domain/oppialogger:startup_listener", + ], +) + +kt_android_library( + name = "bootstrap_oppia_worker", + srcs = ["BootstrapOppiaWorker.kt"], + visibility = [ + "//domain:domain_testing_visibility", + "//domain/src/main/java/org/oppia/android/domain/workmanager/testing:__pkg__", + ], + deps = [ + ":oppia_worker", + "//domain/src/main/java/org/oppia/android/domain/platformparameter:controller", + "//domain/src/main/java/org/oppia/android/domain/platformparameter:platform_parameter_controller_injector", + "//domain/src/main/java/org/oppia/android/domain/platformparameter:platform_parameter_controller_injector_provider", + "//domain/src/main/java/org/oppia/android/domain/util:extensions", + "//third_party:androidx_work_work-runtime", + "//third_party:com_google_guava_guava", + "//third_party:javax_inject_javax_inject", + "//third_party:org_jetbrains_kotlinx_kotlinx-coroutines-guava", + "//utility/src/main/java/org/oppia/android/util/logging:console_logger", + "//utility/src/main/java/org/oppia/android/util/threading:dispatcher_injector", + "//utility/src/main/java/org/oppia/android/util/threading:dispatcher_injector_provider", + ], +) + +kt_android_library( + name = "work_manager_configuration_module", + srcs = ["WorkManagerConfigurationModule.kt"], + visibility = ["//:oppia_prod_module_visibility"], + deps = [ + ":bootstrap_oppia_worker", + ":oppia_worker", + ":startup_worker_schedule_readiness_monitor", + "//:dagger", + ], +) + +kt_android_library( + name = "work_manager_scheduler", + srcs = ["WorkManagerScheduler.kt"], + visibility = ["//:oppia_api_visibility"], + deps = [ + ":bootstrap_oppia_worker", + ":oppia_worker", + "//third_party:androidx_work_work-runtime", + ], +) diff --git a/domain/src/main/java/org/oppia/android/domain/workmanager/BootstrapOppiaWorker.kt b/domain/src/main/java/org/oppia/android/domain/workmanager/BootstrapOppiaWorker.kt new file mode 100644 index 00000000000..cdd076c8a79 --- /dev/null +++ b/domain/src/main/java/org/oppia/android/domain/workmanager/BootstrapOppiaWorker.kt @@ -0,0 +1,177 @@ +package org.oppia.android.domain.workmanager + +import android.content.Context +import androidx.work.ListenableWorker +import androidx.work.WorkManager +import androidx.work.WorkerParameters +import com.google.common.util.concurrent.ListenableFuture +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.async +import kotlinx.coroutines.guava.asListenableFuture +import org.oppia.android.domain.platformparameter.PlatformParameterController +import org.oppia.android.domain.platformparameter.PlatformParameterControllerInjectorProvider +import org.oppia.android.domain.util.getStringFromData +import org.oppia.android.util.logging.ConsoleLogger +import org.oppia.android.util.threading.DispatcherInjectorProvider +import javax.inject.Inject +import javax.inject.Provider + +/** + * The root and only [ListenableWorker] that the app uses for all [WorkManager]-manged work. + * + * This worker will automatically locate and find the [OppiaWorker] corresponding to the work + * request and delegate work to it in an Oppia-managed background executor. It also handles worker + * failures, refactors, and worker discontinuity across app releases. + * + * In order to make a new worker, the following needs to be done: + * - [OppiaWorker] must be properly implemented as a new class with a app-unique worker name. + * - The [OppiaWorker]'s [OppiaWorker.Factory] must be bound to the worker name. + * + * At that point, the worker can be scheduled using [WorkManagerScheduler]. An optional + * worker-specific schedule-time callback listener can be created as well by implementing + * [StartupWorkerScheduleReadinessListener] and set-binding it. + * + * There is a special debug worker setup that can be referenced to see how, exactly, to set up a new + * [OppiaWorker], its scheduler, its module, and its tests. This is a fully functioning, + * developer-available implementation of the [OppiaWorker] infrastructure. + * + * One implementation detail: this worker has a private constructor intentionally to prevent + * [WorkManager] from trying to directly construct it (which it will do in some circumstances). That + * failure will encourage the corresponding job to be cancelled. + * + * Implementers of [OppiaWorker] should generally err on the side of rescheduling periodic jobs that + * may get into a bad state since this worker is designed to aggressively self-cancel itself if it + * encounters an unexpected situation (such as an invalid [OppiaWorker.TaskType] which is expected + * if a worker's supported task types change). + */ +class BootstrapOppiaWorker private constructor( + private val appContext: Context, + private val requestedWorkerName: String, + workerParams: WorkerParameters, + private val consoleLogger: ConsoleLogger, + private val oppiaWorkerFactories: Map>> +) : ListenableWorker(appContext, workerParams) { + override fun startWork(): ListenableFuture { + // TODO(#4463): Add withTimeout() to avoid potential hanging. + return CoroutineScope(getBackgroundDispatcher()).async { + startWorkInBackground() + }.asListenableFuture() + } + + private suspend fun startWorkInBackground(): Result { + // Validate if this is even the correct worker. If this fails it's likely due to a custom worker + // that isn't using the bootstrapper or a request to run an old worker that should now be + // migrated to use the bootstrap worker. + if (requestedWorkerName != javaClass.name) { + consoleLogger.e( + "BootstrapOppiaWorker", + "Attempting to bootstrap old or invalid worker class: $requestedWorkerName." + + " Cancelling worker to prevent repeated failures." + ) + WorkManager.getInstance(appContext).cancelWorkById(id) + return Result.failure() + } + + // This may be the very beginning of the app starting up so platform parameters must be fully + // initialized before a worker can even be created to do work (technically even its factory + // cannot be created since a worker's factory directly depends on the worker's required + // dependencies which may transitively require platform parameters to be loaded). + getPlatformParameterController().loadParametersAsync().await() + + // Retrieve the actual worker name being started and the corresponding delegate factory + // provider. These should always be present unless there's an old bootstrap-compatible worker + // that was previously scheduled, but has since been removed or renamed. + val delegatedWorkerName = inputData.getStringFromData(DELEGATED_WORKER_NAME_INPUT_KEY) + val oppiaWorkerFactoryProvider = oppiaWorkerFactories[delegatedWorkerName] + if (delegatedWorkerName == null || oppiaWorkerFactoryProvider == null) { + consoleLogger.e( + "BootstrapOppiaWorker", + "Attempting to bootstrap for an invalid worker delegate: $delegatedWorkerName (no" + + " provider found). Cancelling worker to prevent repeated failures." + ) + WorkManager.getInstance(appContext).cancelWorkById(id) + return Result.failure() + } + + // Construct the factory first, then try to fetch the type of task attempting to be run. + val factory = oppiaWorkerFactoryProvider.get() + val taskTypeNameKey = constructTaskTypeKey(delegatedWorkerName) + val taskTypeName = inputData.getStringFromData(taskTypeNameKey) + + // Delegate execution to a factory helper to simplify type safety. + val result = taskTypeName?.let { factory.doWorkForTaskName(it) } + if (result == null) { + // If the task type has an incompatibility then err on the side of canceling the task in case + // there was some sort if incompatible change in the worker's API. Since the bootstrap + // worker's contract is to encourage rescheduling, any valid worker that reaches a failure= + // here should hopefully self-correct itself with a future reschedule. + consoleLogger.e( + "BootstrapOppiaWorker", + "Encountered invalid task type when trying to prepare worker $delegatedWorkerName:" + + " $taskTypeName. Cancelling worker to prevent repeated failures." + ) + WorkManager.getInstance(appContext).cancelWorkById(id) + return Result.failure() + } + + return when (result) { + OppiaWorker.Result.SUCCESS -> Result.success() + OppiaWorker.Result.FAILURE -> Result.failure() + } + } + + private fun getPlatformParameterController(): PlatformParameterController { + val injectorProvider = appContext as PlatformParameterControllerInjectorProvider + val injector = injectorProvider.getPlatformParameterControllerInjector() + return injector.getPlatformParameterController() + } + + private fun getBackgroundDispatcher(): CoroutineDispatcher { + val injectorProvider = appContext as DispatcherInjectorProvider + val injector = injectorProvider.getDispatcherInjector() + return injector.getBackgroundDispatcher() + } + + companion object { + /** + * The unique worker input key that is used to store the [OppiaWorker] name which will be + * delegated execution. + */ + const val DELEGATED_WORKER_NAME_INPUT_KEY = "BootstrapOppiaWorker.delegated_worker_name" + + /** + * Returns the input key specific to the worker indicated by [workerName] that should be used to + * encode the specific operation to run for the worker. + */ + fun constructTaskTypeKey(workerName: String): String = "$workerName.TASK_TYPE_KEY" + } + + /** + * Factory to create new [BootstrapOppiaWorker]s. + * + * This should never need to be called directly since it's only used to wire the bootstrap worker + * to [WorkManager]. + */ + class Factory @Inject constructor( + private val context: Context, + private val consoleLogger: ConsoleLogger, + private val workerFactories: Map>> + ) { + /** + * Return a new [BootstrapOppiaWorker] for background execution. + * + * @param requestedWorkerName the name of the worker class [WorkManager] wants to run (which is + * expected to be the bootstrap worker's class name) + * @param workerParams the [WorkerParameters] to use during worker execution + */ + fun createBootstrapWorker( + requestedWorkerName: String, + workerParams: WorkerParameters + ): BootstrapOppiaWorker { + return BootstrapOppiaWorker( + context, requestedWorkerName, workerParams, consoleLogger, workerFactories + ) + } + } +} diff --git a/domain/src/main/java/org/oppia/android/domain/workmanager/OppiaWorker.kt b/domain/src/main/java/org/oppia/android/domain/workmanager/OppiaWorker.kt new file mode 100644 index 00000000000..21ec1121ecf --- /dev/null +++ b/domain/src/main/java/org/oppia/android/domain/workmanager/OppiaWorker.kt @@ -0,0 +1,76 @@ +package org.oppia.android.domain.workmanager + +/** + * Represents a worker that's supported to perform arbitrary background operations even when the app + * is not currently running with a foreground activity. + * + * This is especially useful for operations that need to happen periodically (such as updating the + * app's platform parameter values) or upon certain constraints (such as a user's device + * reconnecting to the internet). Job and constraint management are handled by AndroidX's + * `WorkManager` (which delegates internal execution to OS-level schedulers in a cross-API way). + * + * Workers must defined different the [TaskType]s that they support for execution. While workers do + * not need to provide more than one [TaskType] it can be really useful to split up the worker's + * responsibilities into tasks since each one will be a unique scheduled worker in `WorkManager` + * which means it can have its own scheduled interval and constraints. + * + * See [BootstrapOppiaWorker] for specifics on how to implement a new [OppiaWorker]. + */ +interface OppiaWorker { + /** Performs the background work corresponding to [taskType] and returns a [Result]. */ + suspend fun doWork(taskType: T): Result + + /** Represents the result of performing a particular task of background work. */ + enum class Result { + /** Indicates that the background task succeeded. */ + SUCCESS, + /** Indicates that the background task failed. */ + FAILURE + } + + /** Represents a unique bit of work that the worker implementation supports. */ + interface TaskType { + /** + * The unique name corresponding to this task. + * + * **Important**: This name must be able to survive across different obfuscations of the app. + * That means that it cannot just use a built-in generated enum name since those aren't + * guaranteed to be the same across release builds. + * + * The name doesn't need to be stable across job runs (i.e. it can change across app versions), + * but there's a cost in this since it may require a previous scheduled job failing and a new + * schedule needing to replace it. + */ + val persistentName: String + } + + /** Creates new instances of this [OppiaWorker] implementation for the specified [TaskType]. */ + interface Factory { + /** + * The list of [TaskType]s supported by this worker. + * + * This list can change across app builds but it should be stable for the runtime of a single + * app instance. + */ + val supportedTaskTypes: List + + /** Returns a new instance of the [OppiaWorker] implementation. */ + fun createWorker(): OppiaWorker + + /** + * Convenience function for actually creating a worker and running the work corresponding to the + * provided task name. + * + * This ought to never be called directly. It needs to exist to avoid a type safety issue within + * [BootstrapOppiaWorker]'s internal workings. + * + * @param taskName the [TaskType.persistentName] corresponding to the task to run + * @return the [Result] of running the job, or `null` if the provided task name doesn't + * correspond to any known [TaskType]s supported by the [OppiaWorker] + */ + suspend fun doWorkForTaskName(taskName: String): Result? { + val taskType = supportedTaskTypes.find { taskName == it.persistentName } ?: return null + return createWorker().doWork(taskType) + } + } +} diff --git a/domain/src/main/java/org/oppia/android/domain/workmanager/StartupWorkerScheduleReadinessListener.kt b/domain/src/main/java/org/oppia/android/domain/workmanager/StartupWorkerScheduleReadinessListener.kt new file mode 100644 index 00000000000..ee0a64d8f29 --- /dev/null +++ b/domain/src/main/java/org/oppia/android/domain/workmanager/StartupWorkerScheduleReadinessListener.kt @@ -0,0 +1,23 @@ +package org.oppia.android.domain.workmanager + +/** + * Application-bound listener which will receive a prompt to start scheduling workers for periodic + * execution. + * + * Note that implementation bindings should be multi-bound into a set of this listener type in order + * to automatically be included in application startup procedures. + */ +interface StartupWorkerScheduleReadinessListener { + /** + * Called early in application startup to allow periodic work to be scheduled. + * + * Note that no guarantees can be called about when in the application initialization process this + * is called, but it is guaranteed to be called shortly after initialization. Between that and the + * nature of `WorkManager` and Android OS job behaviors, no work that actually synchronizes with + * application startup state should ever be scheduled through this method (or via `WorkManager` at + * all). + * + * This method is guaranteed to be called exactly once per application instance. + */ + fun scheduleWork(workManagerScheduler: WorkManagerScheduler) +} diff --git a/domain/src/main/java/org/oppia/android/domain/workmanager/StartupWorkerScheduleReadinessMonitor.kt b/domain/src/main/java/org/oppia/android/domain/workmanager/StartupWorkerScheduleReadinessMonitor.kt new file mode 100644 index 00000000000..57b34695413 --- /dev/null +++ b/domain/src/main/java/org/oppia/android/domain/workmanager/StartupWorkerScheduleReadinessMonitor.kt @@ -0,0 +1,36 @@ +package org.oppia.android.domain.workmanager + +import org.oppia.android.domain.oppialogger.ApplicationStartupListener +import javax.inject.Inject +import javax.inject.Provider +import javax.inject.Singleton + +/** + * An [ApplicationStartupListener] which monitors for when it's safe to perform `WorkManager` worker + * scheduling using [WorkManagerScheduler]. + * + * This is an automated mechanism class and should never need to be interacted with directly, and + * likely rarely changed. + */ +@Singleton +class StartupWorkerScheduleReadinessMonitor @Inject constructor( + private val workManagerScheduler: WorkManagerScheduler, + private val readinessListenersProvider: + Provider> +) : ApplicationStartupListener { + // TODO(#6189): Add tests for this class. + + override fun onCreateStarted() { + // Do nothing. It's not yet safe to initialize the startup listeners and schedule workers since + // the schedulers themselves may transitively depend on platform parameters. It's also fine to + // wait to schedule these until full initialization since there should be no cross-syncing + // happening. + } + + override fun onCompletedInitialization() { + // Now it's okay to initialize the listener instances and allow them to start scheduling work. + for (startupWorkerScheduleReadinessListener in readinessListenersProvider.get()) { + startupWorkerScheduleReadinessListener.scheduleWork(workManagerScheduler) + } + } +} diff --git a/domain/src/main/java/org/oppia/android/domain/workmanager/WorkManagerConfigurationModule.kt b/domain/src/main/java/org/oppia/android/domain/workmanager/WorkManagerConfigurationModule.kt index 11fa8e5f82f..0cba6fff000 100644 --- a/domain/src/main/java/org/oppia/android/domain/workmanager/WorkManagerConfigurationModule.kt +++ b/domain/src/main/java/org/oppia/android/domain/workmanager/WorkManagerConfigurationModule.kt @@ -1,29 +1,71 @@ package org.oppia.android.domain.workmanager +import android.content.Context import androidx.work.Configuration -import androidx.work.DelegatingWorkerFactory +import androidx.work.ListenableWorker +import androidx.work.WorkerFactory +import androidx.work.WorkerParameters +import dagger.Binds import dagger.Module import dagger.Provides -import org.oppia.android.domain.oppialogger.logscheduler.MetricLogSchedulingWorkerFactory -import org.oppia.android.domain.oppialogger.loguploader.LogUploadWorkerFactory -import org.oppia.android.domain.platformparameter.syncup.PlatformParameterSyncUpWorkerFactory +import dagger.multibindings.IntoSet +import dagger.multibindings.Multibinds +import org.oppia.android.domain.oppialogger.ApplicationStartupListener import javax.inject.Singleton /** Provides [Configuration] for the work manager. */ @Module -class WorkManagerConfigurationModule { +interface WorkManagerConfigurationModule { + @Multibinds + fun bindOppiaWorkerFactories(): Map> - @Singleton - @Provides - fun provideWorkManagerConfiguration( - logUploadWorkerFactory: LogUploadWorkerFactory, - platformParameterSyncUpWorkerFactory: PlatformParameterSyncUpWorkerFactory, - metricLogSchedulingWorkerFactory: MetricLogSchedulingWorkerFactory - ): Configuration { - val delegatingWorkerFactory = DelegatingWorkerFactory() - delegatingWorkerFactory.addFactory(logUploadWorkerFactory) - delegatingWorkerFactory.addFactory(platformParameterSyncUpWorkerFactory) - delegatingWorkerFactory.addFactory(metricLogSchedulingWorkerFactory) - return Configuration.Builder().setWorkerFactory(delegatingWorkerFactory).build() + @Multibinds + fun bindStartupWorkerScheduleReadinessListeners(): Set + + @Binds + @IntoSet + fun bindStartupWorkerScheduleReadinessMonitorAsStartupListener( + monitor: StartupWorkerScheduleReadinessMonitor + ): ApplicationStartupListener + + companion object ConfigurationCreationModule { + @Provides + @Singleton + fun provideWorkManagerConfiguration( + bootstrapWorkerFactory: BootstrapOppiaWorker.Factory + ): Configuration { + // The app uses only one worker factory since all work is bootstrapped. + val workerFactory = object : WorkerFactory() { + override fun createWorker( + appContext: Context, + workerClassName: String, + workerParameters: WorkerParameters + ): ListenableWorker? { + // The only worker the app uses is the bootstrap one, and so all work requests should be + // directed to it even if it's not the worker being requested. This inconsistency can + // happen for old queued jobs before the worker was created and the worker is designed to + // check to make sure the correct class is being requested before proceeding. However, + // there's one set of counter cases: WorkManager's own internal workers route through this + // factory. There's no reliable way to check for those other than validating the class is + // a real class loadable in the app and that it's a ListenableWorker. This combined with a + // static regex content check should prevent any Oppia workers from being ListenableWorker + // directly. + val workerExistsInApk = try { + ListenableWorker::class.java.isAssignableFrom(Class.forName(workerClassName)) + } catch (e: ClassNotFoundException) { false } + if (workerExistsInApk && workerClassName != BootstrapOppiaWorker::class.java.name) { + // Existing workers that aren't the bootstrap worker must be handled through reflection + // since they're almost certainly WorkManager internal workers. Note that the + // ListenableWorker check is necessary since there may be worker classes that previously + // were scheduled as ListenableWorkers but are now run through the bootstrap worker (and + // haven't been renamed). + return null + } + + return bootstrapWorkerFactory.createBootstrapWorker(workerClassName, workerParameters) + } + } + return Configuration.Builder().setWorkerFactory(workerFactory).build() + } } } diff --git a/domain/src/main/java/org/oppia/android/domain/workmanager/WorkManagerScheduler.kt b/domain/src/main/java/org/oppia/android/domain/workmanager/WorkManagerScheduler.kt new file mode 100644 index 00000000000..0599d9f90ac --- /dev/null +++ b/domain/src/main/java/org/oppia/android/domain/workmanager/WorkManagerScheduler.kt @@ -0,0 +1,79 @@ +package org.oppia.android.domain.workmanager + +import android.content.Context +import androidx.work.Constraints +import androidx.work.Data +import androidx.work.ExistingPeriodicWorkPolicy +import androidx.work.NetworkType +import androidx.work.PeriodicWorkRequest +import androidx.work.WorkManager +import java.util.concurrent.TimeUnit +import javax.inject.Inject + +/** + * Helper utility for simplifying scheduling an [OppiaWorker] for background work. + * + * This is meant to only be used by implementations of [StartupWorkerScheduleReadinessListener]. + */ +class WorkManagerScheduler @Inject constructor(context: Context) { + private val workManager: WorkManager by lazy { WorkManager.getInstance(context) } + + /** + * Schedule a recurring background run of an [OppiaWorker]. + * + * Note that, per the contract of [WorkManager], the job may immediately run upon the first time + * it is scheduled. However, if it's been scheduled in a previous run of the app it may not re-run + * unless its time (interval) and other (e.g. requiring network connectivity) constraints are met. + * + * Scheduled workers always expect the device's battery to not be low. + * + * The provided repeat interval will be clamped to no more often than 15 minutes (per + * [WorkManager]'s own internal constraints) and no less often than every 2 weeks. + * + * @param workerName the unique worker name of the [OppiaWorker] + * @param taskType the specific operation to run for the [OppiaWorker] + * @param repeatInterval the time interval at which the worker should run + * @param intervalUnit the [TimeUnit] corresponding to the [repeatInterval] + * @param requireNetworkConnectivity whether the worker requires internet connectivity + */ + fun schedulePeriodicWorker( + workerName: String, + taskType: OppiaWorker.TaskType, + repeatInterval: Long, + intervalUnit: TimeUnit, + requireNetworkConnectivity: Boolean = true + ) { + val workName = "$workerName.${taskType.persistentName}" + val taskTypeKey = BootstrapOppiaWorker.constructTaskTypeKey(workerName) + val inputData = Data.Builder().apply { + putString(BootstrapOppiaWorker.DELEGATED_WORKER_NAME_INPUT_KEY, workerName) + putString(taskTypeKey, taskType.persistentName) + }.build() + val repeatIntervalMs = intervalUnit.toMillis(repeatInterval) + val adjustedIntervalMs = + repeatIntervalMs.coerceIn(MINIMUM_JOB_INTERVAL_MILLIS..MAXIMUM_JOB_INTERVAL_MILLIS) + val request = PeriodicWorkRequest.Builder( + BootstrapOppiaWorker::class.java, adjustedIntervalMs, TimeUnit.MILLISECONDS + ).apply { + addTag(workName) + setConstraints( + Constraints.Builder().apply { + if (requireNetworkConnectivity) { + setRequiredNetworkType(NetworkType.CONNECTED) + } else setRequiredNetworkType(NetworkType.NOT_REQUIRED) + setRequiresBatteryNotLow(true) + }.build() + ) + setInputData(inputData) + }.build() + // Note that UPDATE is used here so that new app versions or platform parameter configurations + // can update the constraints and timed period of jobs. + // TODO(#6115): Use UPDATE here rather than KEEP. + workManager.enqueueUniquePeriodicWork(workName, ExistingPeriodicWorkPolicy.KEEP, request) + } + + private companion object { + private val MINIMUM_JOB_INTERVAL_MILLIS = TimeUnit.MINUTES.toMillis(15) + private val MAXIMUM_JOB_INTERVAL_MILLIS = TimeUnit.DAYS.toMillis(14) + } +} diff --git a/domain/src/main/java/org/oppia/android/domain/workmanager/debug/BUILD.bazel b/domain/src/main/java/org/oppia/android/domain/workmanager/debug/BUILD.bazel new file mode 100644 index 00000000000..1bc4653d354 --- /dev/null +++ b/domain/src/main/java/org/oppia/android/domain/workmanager/debug/BUILD.bazel @@ -0,0 +1,40 @@ +""" +Library for providing a debug work manager worker that can be used for validating the work manager +configuration in the app. +""" + +load("@io_bazel_rules_kotlin//kotlin:android.bzl", "kt_android_library") + +kt_android_library( + name = "scheduler", + srcs = ["DebugWorkerScheduler.kt"], + visibility = ["//domain:domain_testing_visibility"], + deps = [ + ":worker", + "//domain/src/main/java/org/oppia/android/domain/workmanager:startup_worker_schedule_readiness_listener", + ], +) + +kt_android_library( + name = "worker", + srcs = ["DebugWorker.kt"], + visibility = ["//domain:domain_testing_visibility"], + deps = [ + "//domain/src/main/java/org/oppia/android/domain/workmanager:oppia_worker", + "//third_party:androidx_work_work-runtime-ktx", + "//utility/src/main/java/org/oppia/android/util/logging:console_logger", + ], +) + +kt_android_library( + name = "debug_module", + srcs = ["DebugWorkerDebugModule.kt"], + visibility = ["//:oppia_prod_module_visibility"], + deps = [ + ":scheduler", + ":worker", + "//:dagger", + "//domain/src/main/java/org/oppia/android/domain/oppialogger:startup_listener", + "//domain/src/main/java/org/oppia/android/domain/workmanager:oppia_worker", + ], +) diff --git a/domain/src/main/java/org/oppia/android/domain/workmanager/debug/DebugWorker.kt b/domain/src/main/java/org/oppia/android/domain/workmanager/debug/DebugWorker.kt new file mode 100644 index 00000000000..6e5f4a3b0a2 --- /dev/null +++ b/domain/src/main/java/org/oppia/android/domain/workmanager/debug/DebugWorker.kt @@ -0,0 +1,67 @@ +package org.oppia.android.domain.workmanager.debug + +import org.oppia.android.domain.workmanager.OppiaWorker +import org.oppia.android.util.logging.ConsoleLogger +import java.util.concurrent.TimeUnit +import javax.inject.Inject + +/** + * A developer-only [OppiaWorker] to demonstrate how the custom infrastructure works. + * + * This worker simply logs a debug line corresponding to its [Operation] when run. + */ +class DebugWorker private constructor( + private val consoleLogger: ConsoleLogger +) : OppiaWorker { + override suspend fun doWork(taskType: Operation): OppiaWorker.Result { + consoleLogger.d(WORKER_NAME, "Debug worker ran with config: $taskType.") + return OppiaWorker.Result.SUCCESS + } + + /** The operations supported by [DebugWorker]. */ + enum class Operation( + val period: Long, + val periodUnit: TimeUnit, + val requireConnectivity: Boolean + ) : OppiaWorker.TaskType { + /** + * A configuration to demonstrate the worker running every 15 minutes but only when there's + * connectivity. + */ + RUN_EVERY_FIFTEEN_MINUTES_WITH_CONNECTIVITY( + period = 15, periodUnit = TimeUnit.MINUTES, requireConnectivity = true + ), + /** + * A configuration to demonstrate the worker running every 20 minutes even if there's no + * connectivity. + */ + RUN_EVERY_TWENTY_MINUTES_WITH_OR_WITHOUT_CONNECTIVITY( + period = 20, periodUnit = TimeUnit.MINUTES, requireConnectivity = false + ), + /** + * A configuration to demonstrate the worker running every 6 hours even if there's no + * connectivity. + */ + RUN_EVERY_SIX_HOURS_WITH_OR_WITHOUT_CONNECTIVITY( + period = 6, periodUnit = TimeUnit.HOURS, requireConnectivity = false + ); + + // It's safe for this to just be the enum's name because it won't ever be minified since this + // worker only goes in debug builds. + override val persistentName = name + } + + /** An injectable [OppiaWorker.Factor] to build new [DebugWorker]s. */ + class Factory @Inject constructor( + private val consoleLogger: ConsoleLogger + ) : OppiaWorker.Factory { + override val supportedTaskTypes: List = Operation.values().toList() + + override fun createWorker(): OppiaWorker = DebugWorker(consoleLogger) + } + + companion object { + /** The unique name corresponding to this worker. */ + const val WORKER_NAME = "DebugWorker" + } +} diff --git a/domain/src/main/java/org/oppia/android/domain/workmanager/debug/DebugWorkerDebugModule.kt b/domain/src/main/java/org/oppia/android/domain/workmanager/debug/DebugWorkerDebugModule.kt new file mode 100644 index 00000000000..636db0385e1 --- /dev/null +++ b/domain/src/main/java/org/oppia/android/domain/workmanager/debug/DebugWorkerDebugModule.kt @@ -0,0 +1,24 @@ +package org.oppia.android.domain.workmanager.debug + +import dagger.Binds +import dagger.Module +import dagger.multibindings.IntoMap +import dagger.multibindings.IntoSet +import dagger.multibindings.StringKey +import org.oppia.android.domain.workmanager.OppiaWorker +import org.oppia.android.domain.workmanager.StartupWorkerScheduleReadinessListener + +/** Provides the necessary hook-ins for [DebugWorker] to be runnable. */ +@Module +interface DebugWorkerDebugModule { + @Binds + @IntoSet + fun bindDebugWorkerScheduler( + scheduler: DebugWorkerScheduler + ): StartupWorkerScheduleReadinessListener + + @Binds + @IntoMap + @StringKey(DebugWorker.WORKER_NAME) + fun bindDebugWorkerFactoryProvider(factory: DebugWorker.Factory): OppiaWorker.Factory<*> +} diff --git a/domain/src/main/java/org/oppia/android/domain/workmanager/debug/DebugWorkerScheduler.kt b/domain/src/main/java/org/oppia/android/domain/workmanager/debug/DebugWorkerScheduler.kt new file mode 100644 index 00000000000..505419e15a1 --- /dev/null +++ b/domain/src/main/java/org/oppia/android/domain/workmanager/debug/DebugWorkerScheduler.kt @@ -0,0 +1,24 @@ +package org.oppia.android.domain.workmanager.debug + +import org.oppia.android.domain.workmanager.StartupWorkerScheduleReadinessListener +import org.oppia.android.domain.workmanager.WorkManagerScheduler +import org.oppia.android.domain.workmanager.debug.DebugWorker.Companion.WORKER_NAME +import javax.inject.Inject + +/** + * Automatic scheduler for [DebugWorker] to demonstrate how to set up such a scheduler for other + * custom workers. + */ +class DebugWorkerScheduler @Inject constructor() : StartupWorkerScheduleReadinessListener { + override fun scheduleWork(workManagerScheduler: WorkManagerScheduler) { + for (operation in DebugWorker.Operation.values()) { + workManagerScheduler.schedulePeriodicWorker( + WORKER_NAME, + operation, + operation.period, + operation.periodUnit, + requireNetworkConnectivity = operation.requireConnectivity + ) + } + } +} diff --git a/domain/src/main/java/org/oppia/android/domain/workmanager/testing/BUILD.bazel b/domain/src/main/java/org/oppia/android/domain/workmanager/testing/BUILD.bazel new file mode 100644 index 00000000000..adcf7b5a441 --- /dev/null +++ b/domain/src/main/java/org/oppia/android/domain/workmanager/testing/BUILD.bazel @@ -0,0 +1,28 @@ +""" +Test-only utilities for using WorkManager in tests. +""" + +load("@io_bazel_rules_kotlin//kotlin:android.bzl", "kt_android_library") + +kt_android_library( + name = "oppia_work_manager_test_driver", + testonly = True, + srcs = ["OppiaWorkManagerTestDriver.kt"], + visibility = [ + "//app/src/sharedTest/java/org/oppia/android/app/administratorcontrols/learneranalytics:__pkg__", + "//domain:domain_testing_visibility", + ], + deps = [ + "//domain/src/main/java/org/oppia/android/domain/workmanager:bootstrap_oppia_worker", + "//domain/src/main/java/org/oppia/android/domain/workmanager:oppia_worker", + "//testing/src/main/java/org/oppia/android/testing/threading:coroutine_executor_service", + "//testing/src/main/java/org/oppia/android/testing/threading:test_coroutine_dispatchers", + "//testing/src/main/java/org/oppia/android/testing/time:fake_oppia_clock", + "//third_party:androidx_work_work-testing", + "//third_party:com_google_truth_truth", + "//third_party:javax_inject_javax_inject", + "//third_party:org_jetbrains_kotlinx_kotlinx-coroutines-guava", + "//third_party:org_robolectric_robolectric", + "//utility/src/main/java/org/oppia/android/util/threading:annotations", + ], +) diff --git a/domain/src/main/java/org/oppia/android/domain/workmanager/testing/OppiaWorkManagerTestDriver.kt b/domain/src/main/java/org/oppia/android/domain/workmanager/testing/OppiaWorkManagerTestDriver.kt new file mode 100644 index 00000000000..808e7b7dc93 --- /dev/null +++ b/domain/src/main/java/org/oppia/android/domain/workmanager/testing/OppiaWorkManagerTestDriver.kt @@ -0,0 +1,446 @@ +package org.oppia.android.domain.workmanager.testing + +import android.content.Context +import android.util.Log +import androidx.room.InvalidationTracker +import androidx.work.Configuration +import androidx.work.Data +import androidx.work.ExistingPeriodicWorkPolicy.KEEP +import androidx.work.ListenableWorker +import androidx.work.NetworkType +import androidx.work.OneTimeWorkRequest +import androidx.work.PeriodicWorkRequest +import androidx.work.WorkInfo +import androidx.work.WorkManager +import androidx.work.WorkerFactory +import androidx.work.WorkerParameters +import androidx.work.impl.WorkManagerImpl +import androidx.work.impl.model.WorkSpec +import androidx.work.testing.WorkManagerTestInitHelper +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.async +import kotlinx.coroutines.delay +import kotlinx.coroutines.guava.asDeferred +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import org.oppia.android.domain.workmanager.BootstrapOppiaWorker +import org.oppia.android.domain.workmanager.BootstrapOppiaWorker.Companion.DELEGATED_WORKER_NAME_INPUT_KEY +import org.oppia.android.domain.workmanager.BootstrapOppiaWorker.Companion.constructTaskTypeKey +import org.oppia.android.domain.workmanager.OppiaWorker +import org.oppia.android.testing.threading.CoroutineExecutorService +import org.oppia.android.testing.threading.TestCoroutineDispatchers +import org.oppia.android.testing.time.FakeOppiaClock +import org.oppia.android.testing.time.FakeOppiaClock.FakeTimeMode.MODE_UPTIME_MILLIS +import org.oppia.android.util.threading.BackgroundDispatcher +import org.robolectric.shadows.ShadowLog +import java.util.UUID +import java.util.concurrent.CopyOnWriteArraySet +import java.util.concurrent.TimeUnit +import javax.inject.Inject +import javax.inject.Singleton + +/** + * A general-purpose, all-in-one test utility when interacting with [WorkManager] in Oppia tests. + * + * This utility contains a bunch of critical setup and interaction pathways that tests needing to + * run or instead jobs will need to use. This utility is used over direct interactions with + * [WorkManager]'s library to help simplify changes and limitations in the API across versions, and + * because some of the specific operations sometimes require version-sensitive operations (such as + * accessing [WorkManager]'s internals). It's expected that [WorkManager] never needs to be + * interacted with directly in tests, and any missing functionality should be added directly to this + * utility as needed. + * + * Note that [initializeWorkManager] *must* be called before this driver can be interacted with. + * Also, this driver must be used over [androidx.work.testing.TestDriver] and + * [WorkManagerTestInitHelper] since it performs Oppia-specific setup that may need to be manually + * replicated when not using this utility. There's some complex nuance with properly synchronizing + * between a worker and the app's test coroutine dispatchers--make sure to carefully read + * [initializeWorkManager]'s documentation before opting out of its default setup. + */ +@Singleton +@OptIn(ExperimentalCoroutinesApi::class) +class OppiaWorkManagerTestDriver @Inject constructor( + private val context: Context, + private val testCoroutineDispatchers: TestCoroutineDispatchers, + private val bootstrapOppiaWorkerFactory: BootstrapOppiaWorker.Factory, + private val fakeOppiaClock: FakeOppiaClock, + @BackgroundDispatcher private val backgroundDispatcher: CoroutineDispatcher +) { + // TODO(#6191): Add tests for this class. + + private lateinit var workManager: WorkManager + private val workDatabase get() = (workManager as WorkManagerImpl).workDatabase + private val testDriver by lazy { checkNotNull(WorkManagerTestInitHelper.getTestDriver(context)) } + private val mixin by lazy { TestJobSchedulerMixin() } + private val memoizedMonitors by lazy { mutableMapOf() } + + /** + * Initializes [WorkManager] for testing. This must be called before interacting with either + * [WorkManager] production code or with this driver. + * + * Developers should pay particular attention to the arguments below since they can significantly + * change the behaviors of even non-worker tests in a test suite. Most test suites specific to + * verifying workers should be able to rely on the default arguments. + * + * By default this method configures the test to be able to advanced periodic jobs simply by using + * [TestCoroutineDispatchers.advanceTimeBy], though care should be taken to never use + * [TestCoroutineDispatchers.advanceUntilIdle] since periodic jobs will run indefinitely unless + * cancelled. + * + * This method must only be accessed on the main thread. + * + * @param configurationOverride the [Configuration] that [WorkManager] will use, or `null` by + * default. If `null`, a custom [Configuration] will be created that correctly forwards worker + * creation requests to [BootstrapOppiaWorker.Factory] and synchronizes internal [WorkManager] + * execution with the app's test dispatchers. It also increases logging verbosity. This + * default configuration should be used unless a test has specifically special needs. + * @param autoSetUpShadowLogForwarding whether to automatically forward Robolectric [ShadowLog]s + * to the standard [System.out] for easier logging (since worker logcat logs will then appear + * in test error logs). This is enabled by default but, since it changes the behaviors of all + * tests run with this initialization, it can be disabled. It's recommended to keep this + * enabled for easier test debugging when working with workers. + * @param autoSetModeUptimeMillisClockMode whether to change [FakeOppiaClock] to use + * [MODE_UPTIME_MILLIS]. This can drastically change test behaviors so it may not always be + * desired, but it is sometimes necessary for testing certain worker periodic cases. Note that + * this is enabled by default only if the default [Configuration] is used since both are + * required for those periodic cases to work correctly. + */ + fun initializeWorkManager( + configurationOverride: Configuration? = null, + autoSetUpShadowLogForwarding: Boolean = true, + autoSetModeUptimeMillisClockMode: Boolean = configurationOverride == null + ) { + check(!this::workManager.isInitialized) { "Cannot initialize WorkManager more than once." } + val config = configurationOverride ?: createTestOnlyConfiguration() + // TODO(#6115): Use USE_TIME_BASED_SCHEDULING and update Configuration below to sync with + // FakeOppiaClock.getCurrentTimeMs to make TestJobSchedulerMixin redundant. + WorkManagerTestInitHelper.initializeTestWorkManager(context, config) + workManager = WorkManager.getInstance(context) + mixin.startWatchingForWorkers() + + // WorkManager and workers output most their issues issues to logcat, so this ensures those get + // printed to the test log. This leads to a noisier test run but it makes debugging failures + // significantly easier. + if (autoSetUpShadowLogForwarding) ShadowLog.stream = System.out + + // Ensure OppiaClock is synchronized with FakeSystemClock since the latter is used when + // controlling time with dispatchers. + if (autoSetModeUptimeMillisClockMode) fakeOppiaClock.setFakeTimeMode(MODE_UPTIME_MILLIS) + } + + /** + * Looks up the [WorkerMonitor] corresponding to the specific [workerName] and [operation]. + * + * Note that this will throw if no such job has been scheduled yet (either via a production worker + * scheduler or via [runPeriodicWork]). Note that this cannot be used for one-off jobs since they + * are not uniquely identifiable except by their [UUID]. + * + * Note also that the monitor returned here will always be the same monitor for the same unique + * periodic job unless that same job is attempted to be scheduled twice (in which case a monitor + * with [WorkerMonitor.isRedundant] being `true` will be returned). + * + * This method must only be accessed on the main thread. + */ + fun lookUpPeriodicMonitor(workerName: String, operation: OppiaWorker.TaskType): WorkerMonitor { + return lookUpMonitor(lookUpUniqueId(workerName, operation)) + } + + /** + * Runs a one-off Oppia worker corresponding to the specified [workerName] and [operation] by + * default, otherwise runs an unsupported non-[BootstrapOppiaWorker] (for testing purposes). + * + * This method must only be accessed on the main thread. + * + * @param workerName the worker to run, or `null` for an invalid worker + * @param operation the worker task to run, or `null` for an invalid worker + * @param workerClass the invalid worker class to run, or [BootstrapOppiaWorker] + * @return the [WorkerMonitor] that can be used to inspect and observe the job and its results + */ + fun runOneOffWork( + workerName: String?, + operation: OppiaWorker.TaskType?, + workerClass: Class = BootstrapOppiaWorker::class.java + ): WorkerMonitor = enqueueWork(workerName, operation, intervalMins = null, workerClass) + + /** + * Starts a periodic Oppia worker corresponding to the specified [workerName] and [operation] by + * default, otherwise runs an unsupported non-[BootstrapOppiaWorker] (for testing purposes). + * + * The [WorkerMonitor] returned can be used to inspect the latest status of the job based on its + * last run (see [WorkerMonitor.state]. This API is specifically designed to not necessarily track + * each individual run--the test itself should infer that multiple runs occurred by observing a + * worker's output state. + * + * [WorkManager] executes periodic jobs immediately when they are first scheduled and this method + * retains that behavior (or, rather, does not do anything to change it). + * + * This method must only be accessed on the main thread. + * + * @param workerName the worker to run, or `null` for an invalid worker + * @param operation the worker task to run, or `null` for an invalid worker + * @param repeatIntervalMins the minimum number of minutes between worker runs (15 by default) + * @param workerClass the invalid worker class to run, or [BootstrapOppiaWorker] + * @return the [WorkerMonitor] that can be used to inspect and observe the job and its results + */ + fun runPeriodicWork( + workerName: String?, + operation: OppiaWorker.TaskType?, + repeatIntervalMins: Long = 15, + workerClass: Class = BootstrapOppiaWorker::class.java + ): WorkerMonitor = enqueueWork(workerName, operation, repeatIntervalMins, workerClass) + + private fun enqueueWork( + workerName: String?, + operation: OppiaWorker.TaskType?, + intervalMins: Long?, + workerClass: Class + ): WorkerMonitor { + val inputData = + Data.Builder() + .putString(DELEGATED_WORKER_NAME_INPUT_KEY, workerName) + .putString(constructTaskTypeKey(workerName ?: "invalid_worker"), operation?.persistentName) + .build() + val uniqueWorkName = "$workerName.${operation?.persistentName}" + + val id = runInBackground { + if (intervalMins != null) { + PeriodicWorkRequest.Builder(workerClass, intervalMins, TimeUnit.MINUTES) + .setInputData(inputData) + .build() + .also { workManager.enqueueUniquePeriodicWork(uniqueWorkName, KEEP, it) } + } else { + OneTimeWorkRequest.Builder(workerClass) + .setInputData(inputData) + .build() + .also(workManager::enqueue) + }.id + } + + return if (intervalMins != null && workerName != null && operation != null) { + // Periodic jobs may have already been scheduled so check that first. + lookUpMonitor(id, actualId = lookUpUniqueId(workerName, operation)) + } else lookUpMonitor(id) + } + + private fun lookUpMonitor(requestedId: UUID, actualId: UUID = requestedId): WorkerMonitor { + // Use the requested ID so that a new implementation is created for periodic requests that may + // actually be redundant. + return memoizedMonitors.getOrPut(requestedId) { + // Initialize properties that shouldn't change without a new job being scheduled. + val workInfo = runInBackground { lookUpLatestWorkInfoInBackground(actualId) } + val workSpec = lookUpWorkSpec(actualId) + WorkerMonitor( + actualId, + isRedundant = requestedId != actualId, + tags = workInfo.tags.toList(), + isPeriodic = workSpec.isPeriodic, + intervalDurationMs = workSpec.intervalDuration, + requiredNetworkType = workSpec.constraints.requiredNetworkType, + requiresBatteryNotLow = workSpec.constraints.requiresBatteryNotLow() + ) + } + } + + private fun lookUpUniqueId(workerName: String, operation: OppiaWorker.TaskType): UUID { + val workName = "$workerName.${operation.persistentName}" + val id = runInBackground { + workManager.getWorkInfosForUniqueWork(workName).asDeferred().await().singleOrNull()?.id + } + return checkNotNull(id) { "Expected worker ID for unique worker: $workerName, $operation." } + } + + private suspend fun lookUpLatestWorkInfoInBackground(id: UUID): WorkInfo { + return checkNotNull(workManager.getWorkInfoById(id).asDeferred().await()) { + "Expected live WorkInfo to be available for worker: $id." + } + } + + private fun lookUpWorkSpec(id: UUID): WorkSpec { + // This is a very hacky solution since it relies on WorkManagerImpl, but there's no other way to + // access some of the worker's properties (such as its scheduled period) without this. *Some* of + // these can be observed behaviorally, but not all can so it's reasonable to break into + // WorkManager's implementation like this for those specific properties to ensure that + // WorkManager is processing them the way tests expect. + return checkNotNull(workDatabase.workSpecDao().getWorkSpec(id.toString())) { + "Expected database WorkSpec to be available for worker: $id." + } + } + + private fun createTestOnlyConfiguration(): Configuration { + val coroutineExecutorService = CoroutineExecutorService(backgroundDispatcher) + return Configuration.Builder() + .setMinimumLoggingLevel(Log.VERBOSE) + .setExecutor(coroutineExecutorService) + .setTaskExecutor(coroutineExecutorService) + .setWorkerFactory(object : WorkerFactory() { + override fun createWorker( + appContext: Context, + workerClassName: String, + workerParameters: WorkerParameters + ) = bootstrapOppiaWorkerFactory.createBootstrapWorker(workerClassName, workerParameters) + }) + .build() + } + + private fun runInBackground(func: suspend () -> T): T { + val resultDeferred = CoroutineScope(backgroundDispatcher).async { func() } + testCoroutineDispatchers.runCurrent() + assertThat(resultDeferred.isCompleted).isTrue() + return resultDeferred.getCompleted() + } + + /** + * A monitor for observing the results or changed statuses of a previously or future run + * [WorkManager] worker. + * + * All properties and methods of this class must only be accessed on the main thread. + * + * @property id the unique [UUID] corresponding to the specific job being monitored + * @property isRedundant whether this monitor corresponds to a specific periodic work request that + * attempted to reschedule an already-scheduled periodic job. Note that it is generally not + * valid to inspect any of the properties of the monitor other than its [id] if this property + * is `true`. `id` can be used to lookup the correct monitor to use, instead. + * @property tags the list of tags associated with the worker + * @property isPeriodic whether this corresponds to a periodic or one-off job + * @property intervalDurationMs the number of milliseconds to wait between periodic job runs. This + * defaults to `0` for one-off jobs (which is never a valid value for periodic jobs). + * @property requiredNetworkType the [NetworkType] constraint that must be met before the + * monitored job will be allowed to run. See [WorkManager]'s documentation for the default + * value if left undefined. + * @property requiresBatteryNotLow a constraint indicating whether the device (test environment) + * must not have a low battery before the monitored job will be allowed to run. See + * [WorkManager]'s documentation for the default value if left undefined. + */ + inner class WorkerMonitor( + val id: UUID, + val isRedundant: Boolean, + val tags: List, + val isPeriodic: Boolean, + val intervalDurationMs: Long, + val requiredNetworkType: NetworkType, + val requiresBatteryNotLow: Boolean + ) { + /** + * Whether to automatically apply constraints when it's time for the periodic job being + * monitored to run. This is `false` (off) by default until [forceConstraintsMet] is called. + * + * [WorkManager] will not allowed an constrained periodic job to run in its test environment + * unless it both has periodicity to run and its constrains are met (which must be reset each + * time the worker should run). This can be tedious when testing multiple runs in sequence, so + * setting this to true automatically fulfills constrains for the job when it's time to run at + * its next period. + * + * This should not be changed for one-off jobs since it doesn't have any meaning. It also should + * not be changed for redundant ([isRedundant]) jobs since they cannot actually run. + */ + var autoTrackConstraints: Boolean = false + set(value) { + check(isPeriodic) { "It doesn't make sense to set auto-constraints for a one-off job. " } + check(!isRedundant) { "Cannot override auto-tracking on a redundant monitor." } + field = value + hasAutoTrackOverride = true + } + + private var hasAutoTrackOverride = false + private var hasInitializedAutoTrackConstraints = false + + /** + * The current [WorkInfo.State] corresponding to the monitored job. + * + * Note that each time this is accessed the very latest state will be fetched. It may also throw + * an exception if it is accessed for a job that's disappeared from [WorkManager]'s internal + * tracking (which generally should only happen if the test does something unusual or + * [WorkManager] enters a broken state). + */ + val state: WorkInfo.State + get() = runInBackground { lookUpLatestWorkInfoInBackground(id).state } + + /** + * Notifies [WorkManager] that the constraints for this job have been and thus it can be run. + * + * Note that this will automatically enable [autoTrackConstraints] unless it's been explicitly + * set to false or this isn't a periodic job. + * + * Constraints must be met for constrained jobs to run, and [WorkManager] doesn't provide any + * means to enable specific constraints (it's all or nothing other than timing for delayed or + * periodic jobs). The note on periodic jobs is specifically important because calling this is + * not sufficient for a periodic job to run its next cycle--the system clock still needs to be + * advanced. + */ + fun forceConstraintsMet() { + if (!hasInitializedAutoTrackConstraints) { + if (!hasAutoTrackOverride && isPeriodic) autoTrackConstraints = true + hasInitializedAutoTrackConstraints = true + } + testDriver.setAllConstraintsMet(id) + testCoroutineDispatchers.runCurrent() + } + } + + // TODO(#6115): Remove this utility (see the note in initializeWorkManager above for how the + // configuration must change in order for this utility to become obsolete. + /** + * Custom mixin to bridge the gap with older [WorkManager] test libraries to emulate periodic jobs + * by scheduling requests to re-run them at their periodic intervals using background dispatchers + * (to ensure correct interoperability with the test dispatcher framework). + * + * This utility will be able to be removed once a newer test library is used since [WorkManager] + * eventually built this support in (and in a way that can correctly interoperate with the app's + * custom test dispatchers). + */ + private inner class TestJobSchedulerMixin { + private val workDatabase get() = (workManager as WorkManagerImpl).workDatabase + private val trackedWorkers = CopyOnWriteArraySet() + + /** + * Enable the mixin to start monitoring for workers being scheduled since each worker needs to + * be tracked in order for the mixin to know when it's time to reschedule the job. + */ + fun startWatchingForWorkers() { + workDatabase.invalidationTracker.addObserver( + object : InvalidationTracker.Observer("WorkSpec") { + override fun onInvalidated(tables: MutableSet) { + rescanWorkers() + } + }) + rescanWorkers() + } + + private fun rescanWorkers() { + // Check if any workers have changed. + CoroutineScope(backgroundDispatcher).launch { + val workerIds = workDatabase.workSpecDao().allWorkSpecIds.map(UUID::fromString) + withContext(Dispatchers.Main) { + // This is presumably needed to avoid a deadlock since lookUpMonitor can synchronize + // against internal WorkManager state that may not be finished processing. + testCoroutineDispatchers.runCurrent() + val monitors = workerIds.map(::lookUpMonitor) + for (monitor in monitors) { + if (monitor.isPeriodic && monitor.id !in trackedWorkers) { + trackedWorkers += monitor.id + maybeScheduleNextJob(monitor, monitor.state) + } + } + } + } + } + + private fun maybeScheduleNextJob(monitor: WorkerMonitor, latestState: WorkInfo.State) { + if (latestState != WorkInfo.State.CANCELLED) { + CoroutineScope(backgroundDispatcher).launch { + delay(monitor.intervalDurationMs) + if (monitor.autoTrackConstraints) testDriver.setAllConstraintsMet(monitor.id) + testDriver.setPeriodDelayMet(monitor.id) + withContext(Dispatchers.Main) { + // Note that state MUST be retrieved here because it has to be done on the main thread. + maybeScheduleNextJob(monitor, monitor.state) + } + } + } + } + } +} diff --git a/domain/src/test/java/org/oppia/android/domain/exploration/ExplorationActiveTimeControllerTest.kt b/domain/src/test/java/org/oppia/android/domain/exploration/ExplorationActiveTimeControllerTest.kt index 821b90b3ed5..06472d2d05f 100644 --- a/domain/src/test/java/org/oppia/android/domain/exploration/ExplorationActiveTimeControllerTest.kt +++ b/domain/src/test/java/org/oppia/android/domain/exploration/ExplorationActiveTimeControllerTest.kt @@ -521,8 +521,9 @@ class ExplorationActiveTimeControllerTest { } private fun setUpTestApplicationComponent() { - ApplicationProvider.getApplicationContext() - .inject(this) + ApplicationProvider.getApplicationContext().inject(this) + applicationLifecycleObserver.onCreateStarted() + applicationLifecycleObserver.onCompletedInitialization() } // TODO(#89): Move this to a common test application component. diff --git a/domain/src/test/java/org/oppia/android/domain/oppialogger/analytics/ApplicationLifecycleObserverTest.kt b/domain/src/test/java/org/oppia/android/domain/oppialogger/analytics/ApplicationLifecycleObserverTest.kt index 7cc3e738691..fa0ec5d06df 100644 --- a/domain/src/test/java/org/oppia/android/domain/oppialogger/analytics/ApplicationLifecycleObserverTest.kt +++ b/domain/src/test/java/org/oppia/android/domain/oppialogger/analytics/ApplicationLifecycleObserverTest.kt @@ -2,7 +2,11 @@ package org.oppia.android.domain.oppialogger.analytics import android.app.Application import android.content.Context +import android.content.Intent import androidx.appcompat.app.AppCompatActivity +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.ProcessLifecycleOwner +import androidx.lifecycle.ProcessLifecycleOwnerInitializer import androidx.test.core.app.ActivityScenario import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 @@ -35,6 +39,7 @@ import org.oppia.android.app.model.EventLog.Context.ActivityContextCase.APP_IN_F import org.oppia.android.app.model.FeatureFlagId import org.oppia.android.app.model.LegacyProfileId import org.oppia.android.app.model.OppiaMetricLog +import org.oppia.android.app.model.OppiaMetricLog.LoggableMetric.LoggableMetricTypeCase import org.oppia.android.app.model.ScreenName import org.oppia.android.app.model.SyncStatus import org.oppia.android.app.player.state.itemviewmodel.SplitScreenInteractionModule @@ -99,7 +104,6 @@ import org.oppia.android.util.logging.EnableFileLog import org.oppia.android.util.logging.GlobalLogLevel import org.oppia.android.util.logging.LogLevel import org.oppia.android.util.logging.SyncStatusModule -import org.oppia.android.util.logging.firebase.FirebaseLogUploaderModule import org.oppia.android.util.networking.NetworkConnectionDebugUtilModule import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule import org.oppia.android.util.parser.html.HtmlParserEntityTypeModule @@ -110,6 +114,8 @@ import org.robolectric.annotation.Config import org.robolectric.annotation.LooperMode import retrofit2.Retrofit import retrofit2.converter.moshi.MoshiConverterFactory +import java.lang.reflect.Field +import java.lang.reflect.Modifier import java.net.HttpURLConnection import java.util.concurrent.TimeUnit import javax.inject.Inject @@ -138,6 +144,7 @@ class ApplicationLifecycleObserverTest { @Inject lateinit var loggingIdentifierController: LoggingIdentifierController @Inject lateinit var testCoroutineDispatchers: TestCoroutineDispatchers @Inject lateinit var applicationLifecycleObserver: ApplicationLifecycleObserver + @Inject lateinit var applicationLifecycleLogger: ApplicationLifecycleLogger @Inject lateinit var fakeOppiaClock: FakeOppiaClock @Inject lateinit var monitorFactory: DataProviderTestMonitor.Factory @Inject lateinit var fakeAnalyticsEventLogger: FakeAnalyticsEventLogger @@ -166,40 +173,50 @@ class ApplicationLifecycleObserverTest { @After fun tearDown() { TestPlatformParameterModule.reset() + resetProcessLifecycleOwner() } @Test fun testObserver_withDisabledMetricsCollection_doesNotLogAnyEvent() { setUpTestApplicationComponent() - applicationLifecycleObserver.onAppInForeground() - testCoroutineDispatchers.runCurrent() + + runInActivity {} + assertThat(fakePerformanceMetricsEventLogger.noPerformanceMetricsEventsPresent()).isTrue() } @Test fun testObserver_getSessionId_backgroundApp_thenForeground_limitExceeded_sessionIdUpdated() { setUpTestApplicationComponent() - fakeOppiaClock.setFakeTimeMode(FakeOppiaClock.FakeTimeMode.MODE_FIXED_FAKE_TIME) - val sessionIdProvider = loggingIdentifierController.getSessionId() - val firstSessionId = monitorFactory.waitForNextSuccessfulResult(sessionIdProvider) + fakeOppiaClock.setFakeTimeMode(FakeOppiaClock.FakeTimeMode.MODE_UPTIME_MILLIS) + runInActivity { + val sessionIdProvider = loggingIdentifierController.getSessionId() + val firstSessionId = monitorFactory.waitForNextSuccessfulResult(sessionIdProvider) - waitInBackgroundFor(TimeUnit.MINUTES.toMillis(45)) + ensureAppIsInBackground() + testCoroutineDispatchers.advanceTimeBy(TimeUnit.MINUTES.toMillis(45)) + ensureAppIsInForeground() - val latestSessionId = monitorFactory.waitForNextSuccessfulResult(sessionIdProvider) - assertThat(firstSessionId).isNotEqualTo(latestSessionId) + val latestSessionId = monitorFactory.waitForNextSuccessfulResult(sessionIdProvider) + assertThat(firstSessionId).isNotEqualTo(latestSessionId) + } } @Test fun testObserver_getSessionId_backgroundApp_thenForeground_limitNotExceeded_sessionIdUnchanged() { setUpTestApplicationComponent() - fakeOppiaClock.setFakeTimeMode(FakeOppiaClock.FakeTimeMode.MODE_FIXED_FAKE_TIME) - val sessionIdProvider = loggingIdentifierController.getSessionId() - val firstSessionId = monitorFactory.waitForNextSuccessfulResult(sessionIdProvider) + fakeOppiaClock.setFakeTimeMode(FakeOppiaClock.FakeTimeMode.MODE_UPTIME_MILLIS) + runInActivity { + val sessionIdProvider = loggingIdentifierController.getSessionId() + val firstSessionId = monitorFactory.waitForNextSuccessfulResult(sessionIdProvider) - waitInBackgroundFor(TimeUnit.MINUTES.toMillis(15)) + ensureAppIsInBackground() + testCoroutineDispatchers.advanceTimeBy(TimeUnit.MINUTES.toMillis(15)) + ensureAppIsInForeground() - val latestSessionId = monitorFactory.waitForNextSuccessfulResult(sessionIdProvider) - assertThat(firstSessionId).isEqualTo(latestSessionId) + val latestSessionId = monitorFactory.waitForNextSuccessfulResult(sessionIdProvider) + assertThat(firstSessionId).isEqualTo(latestSessionId) + } } @Test @@ -207,14 +224,13 @@ class ApplicationLifecycleObserverTest { setUpTestApplicationWithLearnerStudy() logIntoAnalyticsReadyAdminProfile() - applicationLifecycleObserver.onAppInForeground() - testCoroutineDispatchers.runCurrent() - - val eventLog = fakeAnalyticsEventLogger.getMostRecentEvent() - assertThat(eventLog).isEssentialPriority() - assertThat(eventLog).hasAppInForegroundContextThat { - hasLearnerIdThat().isNotEmpty() - hasInstallationIdThat().isNotEmpty() + runInActivity { + val eventLog = fakeAnalyticsEventLogger.getMostRecentEvent() + assertThat(eventLog).isEssentialPriority() + assertThat(eventLog).hasAppInForegroundContextThat { + hasLearnerIdThat().isNotEmpty() + hasInstallationIdThat().isNotEmpty() + } } } @@ -222,14 +238,13 @@ class ApplicationLifecycleObserverTest { fun testObserver_onAppInForeground_notLoggedIn_studyOn_logsForegroundEventWithoutLearnerId() { setUpTestApplicationWithLearnerStudy() - applicationLifecycleObserver.onAppInForeground() - testCoroutineDispatchers.runCurrent() - - val eventLog = fakeAnalyticsEventLogger.getMostRecentEvent() - assertThat(eventLog).isEssentialPriority() - assertThat(eventLog).hasAppInForegroundContextThat { - hasLearnerIdThat().isEmpty() - hasInstallationIdThat().isNotEmpty() + runInActivity { + val eventLog = fakeAnalyticsEventLogger.getMostRecentEvent() + assertThat(eventLog).isEssentialPriority() + assertThat(eventLog).hasAppInForegroundContextThat { + hasLearnerIdThat().isEmpty() + hasInstallationIdThat().isNotEmpty() + } } } @@ -238,14 +253,15 @@ class ApplicationLifecycleObserverTest { setUpTestApplicationWithLearnerStudy() logIntoAnalyticsReadyAdminProfile() - applicationLifecycleObserver.onAppInBackground() - testCoroutineDispatchers.runCurrent() + runInActivity { + ensureAppIsInBackground() - val eventLog = fakeAnalyticsEventLogger.getMostRecentEvent() - assertThat(eventLog).isEssentialPriority() - assertThat(eventLog).hasAppInBackgroundContextThat { - hasLearnerIdThat().isNotEmpty() - hasInstallationIdThat().isNotEmpty() + val eventLog = fakeAnalyticsEventLogger.getMostRecentEvent() + assertThat(eventLog).isEssentialPriority() + assertThat(eventLog).hasAppInBackgroundContextThat { + hasLearnerIdThat().isNotEmpty() + hasInstallationIdThat().isNotEmpty() + } } } @@ -253,64 +269,66 @@ class ApplicationLifecycleObserverTest { fun testObserver_onAppInBackground_notLoggedIn_studyOn_logsBackgroundEventWithoutLearnerId() { setUpTestApplicationWithLearnerStudy() - applicationLifecycleObserver.onAppInBackground() - testCoroutineDispatchers.runCurrent() - - val eventLog = getOneOfLastTwoEventsLogged(ActivityContextCase.APP_IN_BACKGROUND_CONTEXT) + runInActivity { + ensureAppIsInBackground() - assertThat(eventLog).isEssentialPriority() - assertThat(eventLog).hasAppInBackgroundContextThat { - hasLearnerIdThat().isEmpty() - hasInstallationIdThat().isNotEmpty() + val eventLog = expectAnalyticsEvent { it.context.hasAppInBackgroundContext() } + assertThat(eventLog).isEssentialPriority() + assertThat(eventLog).hasAppInBackgroundContextThat { + hasLearnerIdThat().isEmpty() + hasInstallationIdThat().isNotEmpty() + } } } @Test fun testObserver_onAppInForeground_setsAppInForeground() { setUpTestApplicationComponent() - applicationLifecycleObserver.onAppInForeground() - assertThat(performanceMetricsController.getIsAppInForeground()).isTrue() + runInActivity { + assertThat(performanceMetricsController.getIsAppInForeground()).isTrue() + } } @Test fun testObserver_onAppInBackground_setsAppInBackground() { setUpTestApplicationComponent() - applicationLifecycleObserver.onAppInBackground() - assertThat(performanceMetricsController.getIsAppInForeground()).isFalse() + runInActivity { + ensureAppIsInBackground() + + assertThat(performanceMetricsController.getIsAppInForeground()).isFalse() + } } @Test fun testObserver_getCurrentScreen_verifyInitialValueIsUnspecified() { setUpTestApplicationComponent() - assertThat(applicationLifecycleObserver.getCurrentScreen()) + + assertThat(applicationLifecycleLogger.getCurrentScreen()) .isEqualTo(ScreenName.SCREEN_NAME_UNSPECIFIED) } @Test fun testObserver_onUnspecifiedActivityResume_verifyCurrentScreenReturnsUnspecifiedValue() { setUpTestApplicationComponent() - runWithUnspecifiedLaunchedActivity { - onActivity { activity -> - applicationLifecycleObserver.onActivityResumed(activity) - val currentScreenValue = applicationLifecycleObserver.getCurrentScreen() - assertThat(currentScreenValue).isEqualTo(ScreenName.SCREEN_NAME_UNSPECIFIED) - } + + runInActivity { + val currentScreenValue = applicationLifecycleLogger.getCurrentScreen() + assertThat(currentScreenValue).isEqualTo(ScreenName.SCREEN_NAME_UNSPECIFIED) } } @Test fun testObserver_onCreate_performanceMetricsLoggingWithCorrectDetailsOccurs() { setUpTestApplicationWithPerformanceMetricsCollection() - applicationLifecycleObserver.onCreate() testCoroutineDispatchers.runCurrent() val loggedMetrics = fakePerformanceMetricsEventLogger.getMostRecentPerformanceMetricsEvents(2) assertThat(loggedMetrics[0].loggableMetric.loggableMetricTypeCase) - .isEqualTo(OppiaMetricLog.LoggableMetric.LoggableMetricTypeCase.APK_SIZE_METRIC) + .isEqualTo(LoggableMetricTypeCase.APK_SIZE_METRIC) assertThat(loggedMetrics[1].loggableMetric.loggableMetricTypeCase).isEqualTo( - OppiaMetricLog.LoggableMetric.LoggableMetricTypeCase.STORAGE_USAGE_METRIC + LoggableMetricTypeCase.STORAGE_USAGE_METRIC ) assertThat(loggedMetrics[0].timestampMillis).isEqualTo(TEST_TIMESTAMP_IN_MILLIS_ONE) assertThat(loggedMetrics[1].timestampMillis).isEqualTo(TEST_TIMESTAMP_IN_MILLIS_ONE) @@ -319,148 +337,123 @@ class ApplicationLifecycleObserverTest { @Test fun testObserver_onFirstActivityResume_verifyCurrentScreenReturnsCorrectValue() { setUpTestApplicationComponent() - runWithSpecifiedLaunchedActivity { - onActivity { activity -> - applicationLifecycleObserver.onActivityResumed(activity) - val currentScreenValue = applicationLifecycleObserver.getCurrentScreen() - assertThat(currentScreenValue).isEqualTo(ScreenName.HOME_ACTIVITY) - } + + runInActivityWithScreenName(ScreenName.POLICIES_ACTIVITY) { + val currentScreenValue = applicationLifecycleLogger.getCurrentScreen() + assertThat(currentScreenValue).isEqualTo(ScreenName.POLICIES_ACTIVITY) } } @Test fun testObserver_onFirstActivityResume_logsStartupLatency() { setUpTestApplicationWithPerformanceMetricsCollection() - applicationLifecycleObserver.onCreate() - testCoroutineDispatchers.runCurrent() fakeOppiaClock.setCurrentTimeMs(TEST_TIMESTAMP_IN_MILLIS_TWO) - runWithSpecifiedLaunchedActivity { - onActivity { activity -> - val expectedStartupLatency = TEST_TIMESTAMP_IN_MILLIS_TWO - TEST_TIMESTAMP_IN_MILLIS_ONE - applicationLifecycleObserver.onActivityResumed(activity) - val startupLatencyEvents = - fakePerformanceMetricsEventLogger.getMostRecentPerformanceMetricsEvents(3) - val startupLatencyEvent = startupLatencyEvents[0] - - assertThat(startupLatencyEvent.loggableMetric.loggableMetricTypeCase).isEqualTo( - OppiaMetricLog.LoggableMetric.LoggableMetricTypeCase.STARTUP_LATENCY_METRIC - ) - assertThat(startupLatencyEvent.timestampMillis).isEqualTo(TEST_TIMESTAMP_IN_MILLIS_TWO) - assertThat(startupLatencyEvent.currentScreen).isEqualTo(ScreenName.HOME_ACTIVITY) - assertThat(startupLatencyEvent.loggableMetric.startupLatencyMetric.startupLatencyMillis) - .isEqualTo(expectedStartupLatency) - } + + runInActivityWithScreenName(ScreenName.HOME_ACTIVITY) { + val latencyEvent = expectPerformanceEvent { it.loggableMetric.hasStartupLatencyMetric() } + assertThat(latencyEvent.loggableMetric.loggableMetricTypeCase) + .isEqualTo(LoggableMetricTypeCase.STARTUP_LATENCY_METRIC) + assertThat(latencyEvent.timestampMillis).isEqualTo(TEST_TIMESTAMP_IN_MILLIS_TWO) + assertThat(latencyEvent.currentScreen).isEqualTo(ScreenName.HOME_ACTIVITY) + assertThat(latencyEvent.loggableMetric.startupLatencyMetric.startupLatencyMillis) + .isEqualTo(TEST_TIMESTAMP_IN_MILLIS_TWO - TEST_TIMESTAMP_IN_MILLIS_ONE) } } @Test fun testObserver_onSecondActivityResume_startupLatencyIsLoggedOnce() { setUpTestApplicationWithPerformanceMetricsCollection() - - applicationLifecycleObserver.onCreate() - testCoroutineDispatchers.runCurrent() fakeOppiaClock.setCurrentTimeMs(TEST_TIMESTAMP_IN_MILLIS_TWO) - runWithSpecifiedLaunchedActivity { - onActivity { activity -> - applicationLifecycleObserver.onActivityResumed(activity) - applicationLifecycleObserver.onActivityResumed(activity) - - val loggedStartupLatencyEvents = - fakePerformanceMetricsEventLogger.getMostRecentPerformanceMetricsEvents( - fakePerformanceMetricsEventLogger.getPerformanceMetricsEventListCount() - ).filter { - it.loggableMetric.hasStartupLatencyMetric() - } - - assertThat(loggedStartupLatencyEvents.size).isEqualTo(1) - } - } + + // Start up an activity twice so that it's resumed twice. + runInActivity {} + runInActivity {} + + // The startup latency metric should only be logged once. + val startupEvents = collectAllPerformanceEvents { it.loggableMetric.hasStartupLatencyMetric() } + assertThat(startupEvents).hasSize(1) } @Test fun testObserver_activityResumed_logsMemoryUsage() { setUpTestApplicationWithPerformanceMetricsCollection() - runWithSpecifiedLaunchedActivity { - onActivity { activity -> - applicationLifecycleObserver.onActivityResumed(activity) - - val memoryUsageEvent = - fakePerformanceMetricsEventLogger.getMostRecentPerformanceMetricsEvent() - - assertThat(memoryUsageEvent.loggableMetric.loggableMetricTypeCase).isEqualTo( - OppiaMetricLog.LoggableMetric.LoggableMetricTypeCase.MEMORY_USAGE_METRIC - ) - assertThat(memoryUsageEvent.timestampMillis).isEqualTo(TEST_TIMESTAMP_IN_MILLIS_ONE) - assertThat(memoryUsageEvent.currentScreen).isEqualTo(ScreenName.HOME_ACTIVITY) - } + runInActivityWithScreenName(ScreenName.HOME_ACTIVITY) { + val memoryUsageEvent = expectPerformanceEvent { it.loggableMetric.hasMemoryUsageMetric() } + assertThat(memoryUsageEvent.loggableMetric.loggableMetricTypeCase) + .isEqualTo(LoggableMetricTypeCase.MEMORY_USAGE_METRIC) + assertThat(memoryUsageEvent.timestampMillis).isEqualTo(TEST_TIMESTAMP_IN_MILLIS_ONE) + assertThat(memoryUsageEvent.currentScreen).isEqualTo(ScreenName.HOME_ACTIVITY) } } @Test fun testObserver_activityResumed_activityPaused_currentScreenReturnsBackgroundValue() { setUpTestApplicationComponent() - runWithSpecifiedLaunchedActivity { - onActivity { activity -> - applicationLifecycleObserver.onActivityResumed(activity) - applicationLifecycleObserver.onActivityPaused(activity) - val currentScreen = applicationLifecycleObserver.getCurrentScreen() - assertThat(currentScreen).isEqualTo(ScreenName.BACKGROUND_SCREEN) - } + runInActivity { + ensureActivityIsPaused() + + // The logger should currently be tracking that the background is the current screen. + val currentScreen = applicationLifecycleLogger.getCurrentScreen() + assertThat(currentScreen).isEqualTo(ScreenName.BACKGROUND_SCREEN) } } @Test - fun testObserver_onAppInForeground_logsCpuUsageWithCurrentScreenForeground() { + fun testObserver_onAppInForeground_doesNotLogCpuUsage() { setUpTestApplicationWithPerformanceMetricsCollection() - applicationLifecycleObserver.onCreate() - applicationLifecycleObserver.onAppInForeground() - testCoroutineDispatchers.runCurrent() - testCoroutineDispatchers.advanceTimeBy(foregroundCpuLoggingTimePeriodMillis) - val event = fakePerformanceMetricsEventLogger.getMostRecentPerformanceMetricsEvent() + runInActivity { + val cpuUsageEvents = collectAllPerformanceEvents { it.loggableMetric.hasCpuUsageMetric() } + assertThat(cpuUsageEvents).isEmpty() + } + } - assertThat(event.currentScreen).isEqualTo(ScreenName.FOREGROUND_SCREEN) + @Test + fun testObserver_onAppInForeground_waitLongEnough_logsCpuUsageWithCurrentScreenForeground() { + setUpTestApplicationWithPerformanceMetricsCollection() + + runInActivity { + testCoroutineDispatchers.advanceTimeBy(foregroundCpuLoggingTimePeriodMillis) + + val cpuUsageEvent = expectPerformanceEvent { it.loggableMetric.hasCpuUsageMetric() } + assertThat(cpuUsageEvent.currentScreen).isEqualTo(ScreenName.FOREGROUND_SCREEN) + } } @Test fun testObserver_onAppInBackground_logsCpuUsageWithCurrentScreenBackground() { setUpTestApplicationWithPerformanceMetricsCollection() - applicationLifecycleObserver.onCreate() - applicationLifecycleObserver.onAppInBackground() - testCoroutineDispatchers.runCurrent() - testCoroutineDispatchers.advanceTimeBy(backgroundCpuLoggingTimePeriodMillis) + runInActivity { + ensureAppIsInBackground() - val event = fakePerformanceMetricsEventLogger.getMostRecentPerformanceMetricsEvent() + testCoroutineDispatchers.advanceTimeBy(backgroundCpuLoggingTimePeriodMillis) - assertThat(event.currentScreen).isEqualTo(ScreenName.BACKGROUND_SCREEN) + val event = fakePerformanceMetricsEventLogger.getMostRecentPerformanceMetricsEvent() + assertThat(event.currentScreen).isEqualTo(ScreenName.BACKGROUND_SCREEN) + } } @Test fun testObserver_onAppInForeground_logsAllFeatureFlags() { setUpTestApplicationComponent() - featureFlagsLogger.setFeatureFlagItemMap( mapOf(FeatureFlagId.DOWNLOADS_SUPPORT to testFeatureFlag) ) - // TODO(#5341): Replace appSessionId generation to the modified Twitter snowflake algorithm. val sessionIdProvider = loggingIdentifierController.getAppSessionId() val sessionId = monitorFactory.waitForNextSuccessfulResult(sessionIdProvider) - applicationLifecycleObserver.onCreate() - testCoroutineDispatchers.runCurrent() - testCoroutineDispatchers.advanceTimeBy(foregroundCpuLoggingTimePeriodMillis) - - val eventLog = fakeAnalyticsEventLogger.getMostRecentEvent() - - assertThat(eventLog).hasFeatureFlagContextThat { - hasSessionIdThat().isEqualTo(sessionId) - hasFeatureFlagItemContextThatAtIndex(0) { - hasFeatureFlagIdThat().isEqualTo(FeatureFlagId.DOWNLOADS_SUPPORT) - hasFeatureFlagEnabledStateThat().isEqualTo(false) - hasFeatureFlagSyncStateThat().isEqualTo(SyncStatus.NOT_SYNCED_FROM_SERVER) + runInActivity { + val eventLog = expectAnalyticsEvent { it.context.hasFeatureFlagListContext() } + assertThat(eventLog).hasFeatureFlagContextThat { + hasSessionIdThat().isEqualTo(sessionId) + hasFeatureFlagItemContextThatAtIndex(0) { + hasFeatureFlagIdThat().isEqualTo(FeatureFlagId.DOWNLOADS_SUPPORT) + hasFeatureFlagEnabledStateThat().isEqualTo(false) + hasFeatureFlagSyncStateThat().isEqualTo(SyncStatus.NOT_SYNCED_FROM_SERVER) + } } } } @@ -469,113 +462,86 @@ class ApplicationLifecycleObserverTest { fun testObserver_onAppInForeground_thenInBackground_logsAppInForegroundTime() { setUpTestApplicationComponent() fakeOppiaClock.setFakeTimeMode(FakeOppiaClock.FakeTimeMode.MODE_UPTIME_MILLIS) - - applicationLifecycleObserver.onCreate() - applicationLifecycleObserver.onAppInForeground() - testCoroutineDispatchers.runCurrent() - - val sessionIdProvider = loggingIdentifierController.getSessionId() - val sessionId = monitorFactory.waitForNextSuccessfulResult(sessionIdProvider) - val installationIdProvider = loggingIdentifierController.getInstallationId() - val installationId = monitorFactory.waitForNextSuccessfulResult(installationIdProvider) - - testCoroutineDispatchers.advanceTimeBy(TEST_TIMESTAMP_APP_IN_FOREGROUND_MILLIS) - applicationLifecycleObserver.onAppInBackground() - testCoroutineDispatchers.runCurrent() - - val eventLog = getOneOfLastTwoEventsLogged(APP_IN_FOREGROUND_TIME) - val eventLogContext = eventLog.context - - assertThat(eventLogContext.activityContextCase) - .isEqualTo(APP_IN_FOREGROUND_TIME) - assertThat(eventLogContext.appInForegroundTime.foregroundTime.toLong()) - .isEqualTo(TEST_TIMESTAMP_APP_IN_FOREGROUND_MILLIS) - assertThat(eventLogContext.appInForegroundTime.appSessionId).isEqualTo(sessionId) - assertThat(eventLogContext.appInForegroundTime.installationId).isEqualTo(installationId) + runInActivity { + val sessionIdProvider = loggingIdentifierController.getSessionId() + val sessionId = monitorFactory.waitForNextSuccessfulResult(sessionIdProvider) + val installationIdProvider = loggingIdentifierController.getInstallationId() + val installationId = monitorFactory.waitForNextSuccessfulResult(installationIdProvider) + + testCoroutineDispatchers.advanceTimeBy(TEST_TIMESTAMP_APP_IN_FOREGROUND_MILLIS) + ensureAppIsInBackground() + + val eventLog = expectAnalyticsEvent { it.context.hasAppInForegroundTime() } + val eventLogContext = eventLog.context + assertThat(eventLogContext.activityContextCase).isEqualTo(APP_IN_FOREGROUND_TIME) + // Note that this will actually potentially be bigger because of additional waiting that + // happens for background detection. + assertThat(eventLogContext.appInForegroundTime.foregroundTime.toLong()) + .isGreaterThan(TEST_TIMESTAMP_APP_IN_FOREGROUND_MILLIS) + assertThat(eventLogContext.appInForegroundTime.appSessionId).isEqualTo(sessionId) + assertThat(eventLogContext.appInForegroundTime.installationId).isEqualTo(installationId) + } } @Test fun testObserver_onAppInForeground_onConsoleError_logsConsoleErrors() { setUpTestApplicationComponent() - applicationLifecycleObserver.onCreate() - applicationLifecycleObserver.onAppInForeground() - testCoroutineDispatchers.runCurrent() - - val testTag = "TestObserver" - val testMessage = "Test error message" - - fakeConsoleLogger.e(testTag, testMessage) - testCoroutineDispatchers.runCurrent() - - val eventLog = fakeAnalyticsEventLogger.getMostRecentEvent() - val eventLogContext = eventLog.context + runInActivity { + val testTag = "TestObserver" + val testMessage = "Test error message" + fakeConsoleLogger.e(testTag, testMessage) + testCoroutineDispatchers.runCurrent() - assertThat(eventLogContext.activityContextCase).isEqualTo(ActivityContextCase.CONSOLE_LOG) - assertThat(eventLogContext.consoleLog.fullErrorLog).isEqualTo(testMessage) - assertThat(eventLogContext.consoleLog.logLevel).isEqualTo(LogLevel.ERROR.toString()) - assertThat(eventLogContext.consoleLog.logTag).isEqualTo(testTag) + val eventLog = fakeAnalyticsEventLogger.getMostRecentEvent() + val eventLogContext = eventLog.context + assertThat(eventLogContext.activityContextCase).isEqualTo(ActivityContextCase.CONSOLE_LOG) + assertThat(eventLogContext.consoleLog.fullErrorLog).isEqualTo(testMessage) + assertThat(eventLogContext.consoleLog.logLevel).isEqualTo(LogLevel.ERROR.toString()) + assertThat(eventLogContext.consoleLog.logTag).isEqualTo(testTag) + } } @Test fun testObserver_onAppInForeground_onNetworkCall_logsNetworkCalls() { setUpTestApplicationComponent() setUpRetrofitApiCall() + runInActivity { + mockWebServer.enqueue(MockResponse().setBody(testResponseBody)) + client.newCall(request).execute() + testCoroutineDispatchers.runCurrent() - applicationLifecycleObserver.onCreate() - applicationLifecycleObserver.onAppInForeground() - testCoroutineDispatchers.runCurrent() - - mockWebServer.enqueue(MockResponse().setBody(testResponseBody)) - client.newCall(request).execute() - testCoroutineDispatchers.runCurrent() - - val eventLog = fakeAnalyticsEventLogger.getMostRecentEvent() - val eventLogContext = eventLog.context - val retrofitCallContext = eventLogContext.retrofitCallContext - - assertThat(eventLogContext.activityContextCase) - .isEqualTo(ActivityContextCase.RETROFIT_CALL_CONTEXT) - assertThat(retrofitCallContext.requestUrl).isEqualTo(mockWebServerUrl.toString()) - assertThat(retrofitCallContext.responseStatusCode).isEqualTo(HttpURLConnection.HTTP_OK) + val eventLog = fakeAnalyticsEventLogger.getMostRecentEvent() + val eventLogContext = eventLog.context + val retrofitCallContext = eventLogContext.retrofitCallContext + assertThat(eventLogContext.activityContextCase) + .isEqualTo(ActivityContextCase.RETROFIT_CALL_CONTEXT) + assertThat(retrofitCallContext.requestUrl).isEqualTo(mockWebServerUrl.toString()) + assertThat(retrofitCallContext.responseStatusCode).isEqualTo(HttpURLConnection.HTTP_OK) + } } @Test fun testObserver_onAppInForeground_onNetworkCall_logsFailedNetworkCalls() { setUpTestApplicationComponent() setUpRetrofitApiCall() + runInActivity { + val pageNotFound = HttpURLConnection.HTTP_NOT_FOUND + val mockResponse = MockResponse() + .setResponseCode(pageNotFound) + .setBody(testResponseBody) + mockWebServer.enqueue(mockResponse) + client.newCall(request).execute() + testCoroutineDispatchers.runCurrent() - applicationLifecycleObserver.onCreate() - applicationLifecycleObserver.onAppInForeground() - testCoroutineDispatchers.runCurrent() - - val pageNotFound = HttpURLConnection.HTTP_NOT_FOUND - val mockResponse = MockResponse() - .setResponseCode(pageNotFound) - .setBody(testResponseBody) - - mockWebServer.enqueue(mockResponse) - client.newCall(request).execute() - testCoroutineDispatchers.runCurrent() - - val eventLog = getOneOfLastTwoEventsLogged(ActivityContextCase.RETROFIT_CALL_FAILED_CONTEXT) - val eventLogContext = eventLog.context - val retrofitCallFailedContext = eventLogContext.retrofitCallFailedContext - - assertThat(eventLogContext.activityContextCase) - .isEqualTo(ActivityContextCase.RETROFIT_CALL_FAILED_CONTEXT) - assertThat(retrofitCallFailedContext.requestUrl).isEqualTo(mockWebServerUrl.toString()) - assertThat(retrofitCallFailedContext.responseStatusCode).isEqualTo(pageNotFound) - } - - private fun waitInBackgroundFor(millis: Long) { - applicationLifecycleObserver.onAppInBackground() - testCoroutineDispatchers.runCurrent() - fakeOppiaClock.setCurrentTimeMs(fakeOppiaClock.getCurrentTimeMs() + millis) - testCoroutineDispatchers.advanceTimeBy(millis) - - applicationLifecycleObserver.onAppInForeground() - testCoroutineDispatchers.runCurrent() + val eventLog = expectAnalyticsEvent { it.context.hasRetrofitCallFailedContext() } + val eventLogContext = eventLog.context + val retrofitCallFailedContext = eventLogContext.retrofitCallFailedContext + assertThat(eventLogContext.activityContextCase) + .isEqualTo(ActivityContextCase.RETROFIT_CALL_FAILED_CONTEXT) + assertThat(retrofitCallFailedContext.requestUrl).isEqualTo(mockWebServerUrl.toString()) + assertThat(retrofitCallFailedContext.responseStatusCode).isEqualTo(pageNotFound) + } } private fun logIntoAnalyticsReadyAdminProfile() { @@ -608,29 +574,94 @@ class ApplicationLifecycleObserverTest { ApplicationProvider.getApplicationContext().inject(this) fakeOppiaClock.setFakeTimeMode(FakeOppiaClock.FakeTimeMode.MODE_FIXED_FAKE_TIME) fakeOppiaClock.setCurrentTimeMs(TEST_TIMESTAMP_IN_MILLIS_ONE) + + // This will be called very early in the lifecycle and must be done for the observer to work. + initializeProcessLifecycleOwner() + applicationLifecycleObserver.onCreateStarted() + applicationLifecycleObserver.onCompletedInitialization() + } + + private fun initializeProcessLifecycleOwner() { + // Hacky way to force ProcessLifecycleOwner to initialize itself and start listening for events. + ProcessLifecycleOwnerInitializer().also { it.attachInfo(context, /* info= */ null) }.onCreate() + } + + private fun resetProcessLifecycleOwner() { + // TODO(#6187): Replace this with a safer hack that doesn't require forcing non-final. + // A *VERY* hacky way to force ProcessLifecycleOwner to reset state by recreating its internal + // (static) singleton that would otherwise share and leak state across test boundaries. Upgrades + // to the Java version may make the 'final' override fail, but if that happens then the + // lifecycle package can be updated to a version that has a non-final instance (or an alternate + // utility could be used, instead). + val lifecycleOwnerClass = ProcessLifecycleOwner::class.java + val constructor = lifecycleOwnerClass.getDeclaredConstructor().also { it.isAccessible = true } + val fieldModifiers = + Field::class.java.getDeclaredField("modifiers").also { it.isAccessible = true } + val instanceField = lifecycleOwnerClass.getDeclaredField("sInstance").also { + it.isAccessible = true + fieldModifiers.set(it, it.modifiers and Modifier.FINAL.inv()) + } + instanceField.set(null, constructor.newInstance()) } - private fun runWithSpecifiedLaunchedActivity( + private fun runInActivityWithScreenName( + screenName: ScreenName, testBlock: ActivityScenario.() -> Unit ) { - val intent = TestActivity.createIntent(context).apply { - decorateWithScreenName(ScreenName.HOME_ACTIVITY) - } - ActivityScenario.launch(intent).use { scenario -> - testCoroutineDispatchers.runCurrent() - scenario.testBlock() - } + runInActivity( + TestActivity.createIntent(context).apply { decorateWithScreenName(screenName) }, testBlock + ) } - private fun runWithUnspecifiedLaunchedActivity( + private fun runInActivity( + intent: Intent = TestActivity.createIntent(context), testBlock: ActivityScenario.() -> Unit ) { - ActivityScenario.launch(TestActivity.createIntent(context)).use { scenario -> + ActivityScenario.launch(intent).use { scenario -> testCoroutineDispatchers.runCurrent() scenario.testBlock() } } + private fun ActivityScenario.ensureAppIsInForeground() { + updateStateAndWait(Lifecycle.State.RESUMED) + } + + private fun ActivityScenario.ensureAppIsInBackground() { + updateStateAndWait(Lifecycle.State.CREATED) + } + + private fun ActivityScenario.ensureActivityIsPaused() { + updateStateAndWait(Lifecycle.State.STARTED) + } + + private fun ActivityScenario.updateStateAndWait(state: Lifecycle.State) { + moveToState(state) + + // ProcessLifecycleOwner uses a timer before firing the background event and that must be + // fully processed before time can be advanced (otherwise time will be advanced before the app + // is fully in the background). Wait for a little bit for that to process. This is a timing + // quirk with how the coroutine dispatcher time synchronization behaves. + testCoroutineDispatchers.advanceTimeBy(TimeUnit.SECONDS.toMillis(1)) + } + + private fun expectAnalyticsEvent(predicate: (EventLog) -> Boolean): EventLog { + val eventCount = fakeAnalyticsEventLogger.getEventListCount() + val events = fakeAnalyticsEventLogger.getMostRecentEvents(eventCount) + return events.firstOrNull(predicate) ?: error("Expected to find event.") + } + + private fun expectPerformanceEvent(predicate: (OppiaMetricLog) -> Boolean): OppiaMetricLog = + collectAllPerformanceEvents(predicate).firstOrNull() ?: error("Expected to find event.") + + private fun collectAllPerformanceEvents( + predicate: (OppiaMetricLog) -> Boolean + ): List { + val eventCount = fakePerformanceMetricsEventLogger.getPerformanceMetricsEventListCount() + val events = fakePerformanceMetricsEventLogger.getMostRecentPerformanceMetricsEvents(eventCount) + return events.filter(predicate) + } + private fun setUpRetrofitApiCall() { mockWebServerUrl = mockWebServer.url(testUrl) client = OkHttpClient.Builder() @@ -647,13 +678,6 @@ class ApplicationLifecycleObserverTest { .build() } - private fun getOneOfLastTwoEventsLogged( - wantedContext: ActivityContextCase - ): EventLog { - val events = fakeAnalyticsEventLogger.getMostRecentEvents(2) - return if (events[0].context.activityContextCase == wantedContext) events[0] else events[1] - } - // TODO(#89): Move this to a common test application component. @Module class TestModule { @@ -704,7 +728,6 @@ class ApplicationLifecycleObserverTest { ExplorationProgressModule::class, ExplorationStorageModule::class, FakeOppiaClockModule::class, - FirebaseLogUploaderModule::class, FractionInputModule::class, GcsResourceModule::class, HintsAndSolutionConfigModule::class, diff --git a/domain/src/test/java/org/oppia/android/domain/oppialogger/analytics/CpuPerformanceSnapshotterTest.kt b/domain/src/test/java/org/oppia/android/domain/oppialogger/analytics/CpuPerformanceSnapshotterTest.kt index c5679986c08..e4b35da2e20 100644 --- a/domain/src/test/java/org/oppia/android/domain/oppialogger/analytics/CpuPerformanceSnapshotterTest.kt +++ b/domain/src/test/java/org/oppia/android/domain/oppialogger/analytics/CpuPerformanceSnapshotterTest.kt @@ -67,8 +67,6 @@ class CpuPerformanceSnapshotterTest { @Inject lateinit var testCoroutineDispatchers: TestCoroutineDispatchers @Inject - lateinit var applicationLifecycleObserver: ApplicationLifecycleObserver - @Inject lateinit var fakePerformanceMetricAssessor: FakePerformanceMetricAssessor @Inject lateinit var fakeOppiaClock: FakeOppiaClock @@ -182,7 +180,9 @@ class CpuPerformanceSnapshotterTest { val timePeriodLessThanFgCpuLoggingTimePeriod = foregroundCpuLoggingTimePeriodMillis - 1000 testCoroutineDispatchers.advanceTimeBy(timePeriodLessThanFgCpuLoggingTimePeriod) - assertThat(fakePerformanceMetricsEventLogger.noPerformanceMetricsEventsPresent()).isTrue() + val eventCount = fakePerformanceMetricsEventLogger.getPerformanceMetricsEventListCount() + val events = fakePerformanceMetricsEventLogger.getMostRecentPerformanceMetricsEvents(eventCount) + assertThat(events).isEmpty() } @Test @@ -218,7 +218,9 @@ class CpuPerformanceSnapshotterTest { val timePeriodLessThanBgCpuLoggingTimePeriod = backgroundCpuLoggingTimePeriodMillis - 1000 testCoroutineDispatchers.advanceTimeBy(timePeriodLessThanBgCpuLoggingTimePeriod) - assertThat(fakePerformanceMetricsEventLogger.noPerformanceMetricsEventsPresent()).isTrue() + val eventCount = fakePerformanceMetricsEventLogger.getPerformanceMetricsEventListCount() + val events = fakePerformanceMetricsEventLogger.getMostRecentPerformanceMetricsEvents(eventCount) + assertThat(events).isEmpty() } @Test @@ -262,76 +264,75 @@ class CpuPerformanceSnapshotterTest { cpuPerformanceSnapshotter.updateAppIconification(APP_IN_FOREGROUND) testCoroutineDispatchers.runCurrent() - assertThat(fakePerformanceMetricsEventLogger.noPerformanceMetricsEventsPresent()).isTrue() + val eventCount = fakePerformanceMetricsEventLogger.getPerformanceMetricsEventListCount() + val events = fakePerformanceMetricsEventLogger.getMostRecentPerformanceMetricsEvents(eventCount) + assertThat(events).isEmpty() } @Test - fun testSnapshotter_onCreate_moveToForegroundBeforeCutOff_logsCpuUsageInForegroundAfterDelay() { - applicationLifecycleObserver.onCreate() + fun testSnapshotter_inited_moveToForegroundBeforeCutOff_logsCpuUsageInForegroundAfterDelay() { + cpuPerformanceSnapshotter.initialiseSnapshotter() testCoroutineDispatchers.runCurrent() // Clearing up all app startup performance metrics: apk size and storage usage. fakePerformanceMetricsEventLogger.clearAllPerformanceMetricsEvents() testCoroutineDispatchers.advanceTimeBy(1000) - applicationLifecycleObserver.onAppInForeground() + cpuPerformanceSnapshotter.updateAppIconification(APP_IN_FOREGROUND) testCoroutineDispatchers.runCurrent() testCoroutineDispatchers.advanceTimeBy(foregroundCpuLoggingTimePeriodMillis) val count = fakePerformanceMetricsEventLogger.getPerformanceMetricsEventListCount() - val event = fakePerformanceMetricsEventLogger.getMostRecentPerformanceMetricsEvent() - - assertThat(count).isEqualTo(1) - assertThat(event.currentScreen).isEqualTo(ScreenName.FOREGROUND_SCREEN) + val events = fakePerformanceMetricsEventLogger.getMostRecentPerformanceMetricsEvents(count) + assertThat(events).hasSize(1) + assertThat(events.single().currentScreen).isEqualTo(ScreenName.FOREGROUND_SCREEN) } @Test fun testSnapshotter_moveToFgBeforeCutOff_moveToBgBeforeDelayEnds_logsInFg_logsInBgAfterDelay() { - applicationLifecycleObserver.onCreate() + cpuPerformanceSnapshotter.initialiseSnapshotter() testCoroutineDispatchers.runCurrent() // Clearing up all app startup performance metrics: apk size and storage usage. fakePerformanceMetricsEventLogger.clearAllPerformanceMetricsEvents() testCoroutineDispatchers.advanceTimeBy(1000) - applicationLifecycleObserver.onAppInForeground() + cpuPerformanceSnapshotter.updateAppIconification(APP_IN_FOREGROUND) testCoroutineDispatchers.runCurrent() testCoroutineDispatchers.advanceTimeBy(TimeUnit.MINUTES.toMillis(2)) - applicationLifecycleObserver.onAppInBackground() + cpuPerformanceSnapshotter.updateAppIconification(APP_IN_BACKGROUND) testCoroutineDispatchers.runCurrent() testCoroutineDispatchers.advanceTimeBy(backgroundCpuLoggingTimePeriodMillis) val count = fakePerformanceMetricsEventLogger.getPerformanceMetricsEventListCount() val events = fakePerformanceMetricsEventLogger.getMostRecentPerformanceMetricsEvents(count) - - assertThat(count).isEqualTo(2) + assertThat(events).hasSize(2) assertThat(events[0].currentScreen).isEqualTo(ScreenName.FOREGROUND_SCREEN) assertThat(events[1].currentScreen).isEqualTo(ScreenName.BACKGROUND_SCREEN) } @Test fun testSnapshotter_moveToFgBeforeCutOff_moveToBgAfterFirstDelay_logsCpuWithCorrectIcon() { - applicationLifecycleObserver.onCreate() + cpuPerformanceSnapshotter.initialiseSnapshotter() testCoroutineDispatchers.runCurrent() // Clearing up all app startup performance metrics: apk size and storage usage. fakePerformanceMetricsEventLogger.clearAllPerformanceMetricsEvents() testCoroutineDispatchers.advanceTimeBy(1000) - applicationLifecycleObserver.onAppInForeground() + cpuPerformanceSnapshotter.updateAppIconification(APP_IN_FOREGROUND) testCoroutineDispatchers.runCurrent() testCoroutineDispatchers.advanceTimeBy(foregroundCpuLoggingTimePeriodMillis) testCoroutineDispatchers.advanceTimeBy(TimeUnit.MINUTES.toMillis(1)) - applicationLifecycleObserver.onAppInBackground() + cpuPerformanceSnapshotter.updateAppIconification(APP_IN_BACKGROUND) testCoroutineDispatchers.runCurrent() testCoroutineDispatchers.advanceTimeBy(backgroundCpuLoggingTimePeriodMillis) val count = fakePerformanceMetricsEventLogger.getPerformanceMetricsEventListCount() val events = fakePerformanceMetricsEventLogger.getMostRecentPerformanceMetricsEvents(count) - - assertThat(count).isEqualTo(3) + assertThat(events).hasSize(3) assertThat(events[0].currentScreen).isEqualTo(ScreenName.FOREGROUND_SCREEN) assertThat(events[1].currentScreen).isEqualTo(ScreenName.FOREGROUND_SCREEN) assertThat(events[2].currentScreen).isEqualTo(ScreenName.BACKGROUND_SCREEN) } @Test - fun testSnapshotter_onCreate_setsIconificationToBgAfterCutOff_logsCpuInBgAfterCorrectDelay() { - applicationLifecycleObserver.onCreate() + fun testSnapshotter_inited_setsIconificationToBgAfterCutOff_logsCpuInBgAfterCorrectDelay() { + cpuPerformanceSnapshotter.initialiseSnapshotter() testCoroutineDispatchers.runCurrent() // Clearing up all app startup performance metrics: apk size and storage usage. fakePerformanceMetricsEventLogger.clearAllPerformanceMetricsEvents() @@ -341,33 +342,31 @@ class CpuPerformanceSnapshotterTest { val count = fakePerformanceMetricsEventLogger.getPerformanceMetricsEventListCount() val events = fakePerformanceMetricsEventLogger.getMostRecentPerformanceMetricsEvents(count) - - assertThat(count).isEqualTo(1) + assertThat(events).hasSize(1) assertThat(events[0].currentScreen).isEqualTo(ScreenName.BACKGROUND_SCREEN) } @Test - fun testSnapshotter_onCreate_setsIconToBgAfterCutOff_moveToFgAndLogsCpuInBg_logsCpuInFg() { - applicationLifecycleObserver.onCreate() + fun testSnapshotter_inited_setsIconToBgAfterCutOff_moveToFgAndLogsCpuInBg_logsCpuInFg() { + cpuPerformanceSnapshotter.initialiseSnapshotter() testCoroutineDispatchers.runCurrent() // Clearing up all app startup performance metrics: apk size and storage usage. fakePerformanceMetricsEventLogger.clearAllPerformanceMetricsEvents() testCoroutineDispatchers.advanceTimeBy(TimeUnit.MINUTES.toMillis(5)) - applicationLifecycleObserver.onAppInForeground() + cpuPerformanceSnapshotter.updateAppIconification(APP_IN_FOREGROUND) testCoroutineDispatchers.runCurrent() testCoroutineDispatchers.advanceTimeBy(foregroundCpuLoggingTimePeriodMillis) val count = fakePerformanceMetricsEventLogger.getPerformanceMetricsEventListCount() val events = fakePerformanceMetricsEventLogger.getMostRecentPerformanceMetricsEvents(count) - - assertThat(count).isEqualTo(2) + assertThat(events).hasSize(2) assertThat(events[0].currentScreen).isEqualTo(ScreenName.BACKGROUND_SCREEN) assertThat(events[1].currentScreen).isEqualTo(ScreenName.FOREGROUND_SCREEN) } @Test fun testSnapshotter_setsIconToBgAfterCutOff_logsCpuInBg_moveToFgAndLogsCpuInBg_logsCpuInFg() { - applicationLifecycleObserver.onCreate() + cpuPerformanceSnapshotter.initialiseSnapshotter() testCoroutineDispatchers.runCurrent() // Clearing up all app startup performance metrics: apk size and storage usage. fakePerformanceMetricsEventLogger.clearAllPerformanceMetricsEvents() @@ -376,14 +375,13 @@ class CpuPerformanceSnapshotterTest { 3 ) ) - applicationLifecycleObserver.onAppInForeground() + cpuPerformanceSnapshotter.updateAppIconification(APP_IN_FOREGROUND) testCoroutineDispatchers.runCurrent() testCoroutineDispatchers.advanceTimeBy(foregroundCpuLoggingTimePeriodMillis) val count = fakePerformanceMetricsEventLogger.getPerformanceMetricsEventListCount() val events = fakePerformanceMetricsEventLogger.getMostRecentPerformanceMetricsEvents(count) - - assertThat(count).isEqualTo(3) + assertThat(events).hasSize(3) assertThat(events[0].currentScreen).isEqualTo(ScreenName.BACKGROUND_SCREEN) assertThat(events[1].currentScreen).isEqualTo(ScreenName.BACKGROUND_SCREEN) assertThat(events[2].currentScreen).isEqualTo(ScreenName.FOREGROUND_SCREEN) @@ -392,23 +390,32 @@ class CpuPerformanceSnapshotterTest { @Test fun testSnapshotter_updateIconification_withoutInitialization_doesNotLogAnyCpuUsage() { cpuPerformanceSnapshotter.updateAppIconification(APP_IN_BACKGROUND) + testCoroutineDispatchers.runCurrent() - assertThat(fakePerformanceMetricsEventLogger.noPerformanceMetricsEventsPresent()).isTrue() + val eventCount = fakePerformanceMetricsEventLogger.getPerformanceMetricsEventListCount() + val events = fakePerformanceMetricsEventLogger.getMostRecentPerformanceMetricsEvents(eventCount) + assertThat(events).isEmpty() } @Test fun testSnapshotter_updateToUninitializedIcon_withoutInitializingSnapshotter_noCpuLogExists() { cpuPerformanceSnapshotter.updateAppIconification(UNINITIALIZED) + testCoroutineDispatchers.runCurrent() - assertThat(fakePerformanceMetricsEventLogger.noPerformanceMetricsEventsPresent()).isTrue() + val eventCount = fakePerformanceMetricsEventLogger.getPerformanceMetricsEventListCount() + val events = fakePerformanceMetricsEventLogger.getMostRecentPerformanceMetricsEvents(eventCount) + assertThat(events).isEmpty() } @Test fun testSnapshotter_updateToUninitializedIcon_withInitializedSnapshotter_noCpuLogExists() { cpuPerformanceSnapshotter.initialiseSnapshotter() cpuPerformanceSnapshotter.updateAppIconification(UNINITIALIZED) + testCoroutineDispatchers.runCurrent() - assertThat(fakePerformanceMetricsEventLogger.noPerformanceMetricsEventsPresent()).isTrue() + val eventCount = fakePerformanceMetricsEventLogger.getPerformanceMetricsEventListCount() + val events = fakePerformanceMetricsEventLogger.getMostRecentPerformanceMetricsEvents(eventCount) + assertThat(events).isEmpty() } @Test @@ -417,7 +424,9 @@ class CpuPerformanceSnapshotterTest { initialIconificationCutOffTimePeriodMillis + backgroundCpuLoggingTimePeriodMillis ) - assertThat(fakePerformanceMetricsEventLogger.noPerformanceMetricsEventsPresent()).isTrue() + val eventCount = fakePerformanceMetricsEventLogger.getPerformanceMetricsEventListCount() + val events = fakePerformanceMetricsEventLogger.getMostRecentPerformanceMetricsEvents(eventCount) + assertThat(events).isEmpty() } @Test @@ -426,12 +435,15 @@ class CpuPerformanceSnapshotterTest { testCoroutineDispatchers.runCurrent() testCoroutineDispatchers.advanceTimeBy(foregroundCpuLoggingTimePeriodMillis) - assertThat(fakePerformanceMetricsEventLogger.noPerformanceMetricsEventsPresent()).isTrue() + val eventCount = fakePerformanceMetricsEventLogger.getPerformanceMetricsEventListCount() + val events = fakePerformanceMetricsEventLogger.getMostRecentPerformanceMetricsEvents(eventCount) + assertThat(events).isEmpty() } @Test fun testSnapshotter_initializeOnce_initializeAgain_throwsErrorOnReinitialization() { cpuPerformanceSnapshotter.initialiseSnapshotter() + val exception = assertThrows() { cpuPerformanceSnapshotter.initialiseSnapshotter() } diff --git a/domain/src/test/java/org/oppia/android/domain/oppialogger/analytics/FeatureFlagsLoggerTest.kt b/domain/src/test/java/org/oppia/android/domain/oppialogger/analytics/FeatureFlagsLoggerTest.kt index 3bac46f37f0..2a2f4114131 100644 --- a/domain/src/test/java/org/oppia/android/domain/oppialogger/analytics/FeatureFlagsLoggerTest.kt +++ b/domain/src/test/java/org/oppia/android/domain/oppialogger/analytics/FeatureFlagsLoggerTest.kt @@ -30,6 +30,7 @@ import org.oppia.android.testing.platformparameter.TestPlatformParameterModule import org.oppia.android.testing.robolectric.RobolectricModule import org.oppia.android.testing.threading.TestCoroutineDispatchers import org.oppia.android.testing.threading.TestDispatcherModule +import org.oppia.android.testing.time.FakeOppiaClock import org.oppia.android.testing.time.FakeOppiaClockModule import org.oppia.android.util.caching.AssetModule import org.oppia.android.util.data.DataProvidersInjector @@ -62,6 +63,7 @@ class FeatureFlagsLoggerTest { @Inject lateinit var testCoroutineDispatchers: TestCoroutineDispatchers @Inject lateinit var featureFlagsLogger: FeatureFlagsLogger @Inject lateinit var fakeAnalyticsEventLogger: FakeAnalyticsEventLogger + @Inject lateinit var fakeOppiaClock: FakeOppiaClock @field:[Inject EnableDownloadsSupport] lateinit var testFeatureFlag: PlatformParameterValue @@ -81,7 +83,7 @@ class FeatureFlagsLoggerTest { @Test fun testLogFeatureFlags_logFeatureFlags_hasEmptyUserUuid() { // TODO(#5341): The user UUID is not set in this test context and is expected to be empty. - featureFlagsLogger.logAllFeatureFlags(TEST_SESSION_ID) + featureFlagsLogger.logAllFeatureFlags(TEST_SESSION_ID, fakeOppiaClock.getCurrentTimeMs()) testCoroutineDispatchers.runCurrent() val eventLog = fakeAnalyticsEventLogger.getMostRecentEvent() @@ -92,7 +94,7 @@ class FeatureFlagsLoggerTest { @Test fun testLogFeatureFlags_logFeatureFlags_hasCorrectSessionId() { - featureFlagsLogger.logAllFeatureFlags(TEST_SESSION_ID) + featureFlagsLogger.logAllFeatureFlags(TEST_SESSION_ID, fakeOppiaClock.getCurrentTimeMs()) testCoroutineDispatchers.runCurrent() val eventLog = fakeAnalyticsEventLogger.getMostRecentEvent() @@ -106,7 +108,7 @@ class FeatureFlagsLoggerTest { featureFlagsLogger.setFeatureFlagItemMap( mapOf(FeatureFlagId.DOWNLOADS_SUPPORT to testFeatureFlag) ) - featureFlagsLogger.logAllFeatureFlags(TEST_SESSION_ID) + featureFlagsLogger.logAllFeatureFlags(TEST_SESSION_ID, fakeOppiaClock.getCurrentTimeMs()) testCoroutineDispatchers.runCurrent() @@ -125,7 +127,7 @@ class FeatureFlagsLoggerTest { featureFlagsLogger.setFeatureFlagItemMap( mapOf(FeatureFlagId.NPS_SURVEY to testFeatureFlagWithEnabledDefault) ) - featureFlagsLogger.logAllFeatureFlags(TEST_SESSION_ID) + featureFlagsLogger.logAllFeatureFlags(TEST_SESSION_ID, fakeOppiaClock.getCurrentTimeMs()) testCoroutineDispatchers.runCurrent() @@ -143,7 +145,7 @@ class FeatureFlagsLoggerTest { fun testLogFeatureFlags_correctNumberOfFeatureFlagsIsLogged() { val expectedFeatureFlagCount = 15 - featureFlagsLogger.logAllFeatureFlags(TEST_SESSION_ID) + featureFlagsLogger.logAllFeatureFlags(TEST_SESSION_ID, fakeOppiaClock.getCurrentTimeMs()) testCoroutineDispatchers.runCurrent() val eventLog = fakeAnalyticsEventLogger.getMostRecentEvent() @@ -169,7 +171,7 @@ class FeatureFlagsLoggerTest { @Iteration("topic_info_tab", "index=13", "flagId=TOPIC_INFO_TAB") @Iteration("topic_practice_tab", "index=14", "flagId=TOPIC_PRACTICE_TAB") fun testLogFeatureFlags_allFeatureFlagIdsAreLogged() { - featureFlagsLogger.logAllFeatureFlags(TEST_SESSION_ID) + featureFlagsLogger.logAllFeatureFlags(TEST_SESSION_ID, fakeOppiaClock.getCurrentTimeMs()) testCoroutineDispatchers.runCurrent() diff --git a/domain/src/test/java/org/oppia/android/domain/oppialogger/analytics/LearnerAnalyticsLoggerTest.kt b/domain/src/test/java/org/oppia/android/domain/oppialogger/analytics/LearnerAnalyticsLoggerTest.kt index dd139a46e81..161a54624db 100644 --- a/domain/src/test/java/org/oppia/android/domain/oppialogger/analytics/LearnerAnalyticsLoggerTest.kt +++ b/domain/src/test/java/org/oppia/android/domain/oppialogger/analytics/LearnerAnalyticsLoggerTest.kt @@ -59,6 +59,7 @@ import org.oppia.android.testing.platformparameter.TestPlatformParameterModule import org.oppia.android.testing.robolectric.RobolectricModule import org.oppia.android.testing.threading.TestCoroutineDispatchers import org.oppia.android.testing.threading.TestDispatcherModule +import org.oppia.android.testing.time.FakeOppiaClock import org.oppia.android.testing.time.FakeOppiaClockModule import org.oppia.android.util.caching.AssetModule import org.oppia.android.util.caching.testing.CachingTestModule @@ -94,25 +95,17 @@ class LearnerAnalyticsLoggerTest { private const val DEFAULT_INITIAL_SESSION_ID = "ab4532d6-476c-3727-bc5a-ad84e5dae60f" } - @Inject - lateinit var learnerAnalyticsLogger: LearnerAnalyticsLogger - @Inject - lateinit var explorationDataController: ExplorationDataController - @Inject - lateinit var monitorFactory: DataProviderTestMonitor.Factory - @Inject - lateinit var fakeAnalyticsEventLogger: FakeAnalyticsEventLogger - @Inject - lateinit var testCoroutineDispatchers: TestCoroutineDispatchers - - @Parameter - lateinit var iid: String - @Parameter - lateinit var lid: String - @Parameter - lateinit var eid: String - @Parameter - lateinit var elid: String + @Inject lateinit var learnerAnalyticsLogger: LearnerAnalyticsLogger + @Inject lateinit var explorationDataController: ExplorationDataController + @Inject lateinit var monitorFactory: DataProviderTestMonitor.Factory + @Inject lateinit var fakeAnalyticsEventLogger: FakeAnalyticsEventLogger + @Inject lateinit var testCoroutineDispatchers: TestCoroutineDispatchers + @Inject lateinit var fakeOppiaClock: FakeOppiaClock + + @Parameter lateinit var iid: String + @Parameter lateinit var lid: String + @Parameter lateinit var eid: String + @Parameter lateinit var elid: String private val learnerIdParameter: String? get() = lid.takeIf { it != "null" } private val installIdParameter: String? get() = iid.takeIf { it != "null" } @@ -235,7 +228,9 @@ class LearnerAnalyticsLoggerTest { @Test fun testLogAppInBackground_noOngoingSession_logsEventWithIds() { - learnerAnalyticsLogger.logAppInBackground(TEST_INSTALL_ID, profileId, TEST_LEARNER_ID) + learnerAnalyticsLogger.logAppInBackground( + TEST_INSTALL_ID, profileId, TEST_LEARNER_ID, fakeOppiaClock.getCurrentTimeMs() + ) testCoroutineDispatchers.runCurrent() val event = fakeAnalyticsEventLogger.getMostRecentEvent() @@ -251,7 +246,9 @@ class LearnerAnalyticsLoggerTest { learnerAnalyticsLogger.beginExploration(loadExploration(TEST_EXPLORATION_ID_5)) testCoroutineDispatchers.runCurrent() - learnerAnalyticsLogger.logAppInBackground(TEST_INSTALL_ID, profileId, TEST_LEARNER_ID) + learnerAnalyticsLogger.logAppInBackground( + TEST_INSTALL_ID, profileId, TEST_LEARNER_ID, fakeOppiaClock.getCurrentTimeMs() + ) testCoroutineDispatchers.runCurrent() val event = fakeAnalyticsEventLogger.getMostRecentEvent() @@ -264,7 +261,9 @@ class LearnerAnalyticsLoggerTest { @Test fun testLogAppInBackground_withoutInstallationId_logsEventWithoutInstallationId() { - learnerAnalyticsLogger.logAppInBackground(installationId = null, profileId, TEST_LEARNER_ID) + learnerAnalyticsLogger.logAppInBackground( + installationId = null, profileId, TEST_LEARNER_ID, fakeOppiaClock.getCurrentTimeMs() + ) testCoroutineDispatchers.runCurrent() assertThat(fakeAnalyticsEventLogger.getMostRecentEvent()).hasAppInBackgroundContextThat { @@ -275,7 +274,9 @@ class LearnerAnalyticsLoggerTest { @Test fun testLogAppInBackground_withoutLearnerId_logsEventWithoutLearnerId() { - learnerAnalyticsLogger.logAppInBackground(TEST_INSTALL_ID, profileId, learnerId = null) + learnerAnalyticsLogger.logAppInBackground( + TEST_INSTALL_ID, profileId, learnerId = null, fakeOppiaClock.getCurrentTimeMs() + ) testCoroutineDispatchers.runCurrent() assertThat(fakeAnalyticsEventLogger.getMostRecentEvent()).hasAppInBackgroundContextThat { @@ -286,7 +287,9 @@ class LearnerAnalyticsLoggerTest { @Test fun testLogAppInBackground_withoutIds_logsEventWithoutIds() { - learnerAnalyticsLogger.logAppInBackground(installationId = null, profileId, learnerId = null) + learnerAnalyticsLogger.logAppInBackground( + installationId = null, profileId, learnerId = null, fakeOppiaClock.getCurrentTimeMs() + ) testCoroutineDispatchers.runCurrent() val event = fakeAnalyticsEventLogger.getMostRecentEvent() @@ -295,7 +298,9 @@ class LearnerAnalyticsLoggerTest { @Test fun testLogAppInForeground_noOngoingSession_logsEventWithIds() { - learnerAnalyticsLogger.logAppInForeground(TEST_INSTALL_ID, profileId, TEST_LEARNER_ID) + learnerAnalyticsLogger.logAppInForeground( + TEST_INSTALL_ID, profileId, TEST_LEARNER_ID, fakeOppiaClock.getCurrentTimeMs() + ) testCoroutineDispatchers.runCurrent() val event = fakeAnalyticsEventLogger.getMostRecentEvent() @@ -311,7 +316,9 @@ class LearnerAnalyticsLoggerTest { learnerAnalyticsLogger.beginExploration(loadExploration(TEST_EXPLORATION_ID_5)) testCoroutineDispatchers.runCurrent() - learnerAnalyticsLogger.logAppInForeground(TEST_INSTALL_ID, profileId, TEST_LEARNER_ID) + learnerAnalyticsLogger.logAppInForeground( + TEST_INSTALL_ID, profileId, TEST_LEARNER_ID, fakeOppiaClock.getCurrentTimeMs() + ) testCoroutineDispatchers.runCurrent() val event = fakeAnalyticsEventLogger.getMostRecentEvent() @@ -324,7 +331,9 @@ class LearnerAnalyticsLoggerTest { @Test fun testLogAppInForeground_withoutInstallationId_logsEventWithoutInstallationId() { - learnerAnalyticsLogger.logAppInForeground(installationId = null, profileId, TEST_LEARNER_ID) + learnerAnalyticsLogger.logAppInForeground( + installationId = null, profileId, TEST_LEARNER_ID, fakeOppiaClock.getCurrentTimeMs() + ) testCoroutineDispatchers.runCurrent() assertThat(fakeAnalyticsEventLogger.getMostRecentEvent()).hasAppInForegroundContextThat { @@ -335,7 +344,9 @@ class LearnerAnalyticsLoggerTest { @Test fun testLogAppInForeground_withoutLearnerId_logsEventWithoutLearnerId() { - learnerAnalyticsLogger.logAppInForeground(TEST_INSTALL_ID, profileId, learnerId = null) + learnerAnalyticsLogger.logAppInForeground( + TEST_INSTALL_ID, profileId, learnerId = null, fakeOppiaClock.getCurrentTimeMs() + ) testCoroutineDispatchers.runCurrent() assertThat(fakeAnalyticsEventLogger.getMostRecentEvent()).hasAppInForegroundContextThat { @@ -346,7 +357,9 @@ class LearnerAnalyticsLoggerTest { @Test fun testLogAppInForeground_withoutIds_logsEventWithoutIds() { - learnerAnalyticsLogger.logAppInForeground(installationId = null, profileId, learnerId = null) + learnerAnalyticsLogger.logAppInForeground( + installationId = null, profileId, learnerId = null, fakeOppiaClock.getCurrentTimeMs() + ) testCoroutineDispatchers.runCurrent() val event = fakeAnalyticsEventLogger.getMostRecentEvent() diff --git a/domain/src/test/java/org/oppia/android/domain/oppialogger/analytics/PerformanceMetricsLoggerTest.kt b/domain/src/test/java/org/oppia/android/domain/oppialogger/analytics/PerformanceMetricsLoggerTest.kt index 8b67112ec69..bf09ab8c750 100644 --- a/domain/src/test/java/org/oppia/android/domain/oppialogger/analytics/PerformanceMetricsLoggerTest.kt +++ b/domain/src/test/java/org/oppia/android/domain/oppialogger/analytics/PerformanceMetricsLoggerTest.kt @@ -101,12 +101,16 @@ class PerformanceMetricsLoggerTest { fun testLogger_inDefaultState_logPerformanceMetrics_verifyNoMetricsAreLogged() { setUpApplicationInDefaultMode() performanceMetricsLogger.apply { - logApkSize(SCREEN_NAME_UNSPECIFIED) - logStorageUsage(SCREEN_NAME_UNSPECIFIED) - logMemoryUsage(SCREEN_NAME_UNSPECIFIED) - logNetworkUsage(SCREEN_NAME_UNSPECIFIED) - logCpuUsage(SCREEN_NAME_UNSPECIFIED, TEST_CPU_USAGE) - logStartupLatency(TEST_STARTUP_LATENCY_IN_MILLISECONDS, SCREEN_NAME_UNSPECIFIED) + logApkSize(SCREEN_NAME_UNSPECIFIED, fakeOppiaClock.getCurrentTimeMs()) + logStorageUsage(SCREEN_NAME_UNSPECIFIED, fakeOppiaClock.getCurrentTimeMs()) + logMemoryUsage(SCREEN_NAME_UNSPECIFIED, fakeOppiaClock.getCurrentTimeMs()) + logNetworkUsage(SCREEN_NAME_UNSPECIFIED, fakeOppiaClock.getCurrentTimeMs()) + logCpuUsage(SCREEN_NAME_UNSPECIFIED, TEST_CPU_USAGE, fakeOppiaClock.getCurrentTimeMs()) + logStartupLatency( + TEST_STARTUP_LATENCY_IN_MILLISECONDS, + SCREEN_NAME_UNSPECIFIED, + fakeOppiaClock.getCurrentTimeMs() + ) } assertThat(fakePerformanceMetricsEventLogger.noPerformanceMetricsEventsPresent()).isTrue() @@ -119,7 +123,7 @@ class PerformanceMetricsLoggerTest { val memoryTier = fakePerformanceMetricAssessor.getDeviceMemoryTier() val storageTier = fakePerformanceMetricAssessor.getDeviceStorageTier() val isAppInForeground = performanceMetricsController.getIsAppInForeground() - performanceMetricsLogger.logApkSize(SCREEN_NAME_UNSPECIFIED) + performanceMetricsLogger.logApkSize(SCREEN_NAME_UNSPECIFIED, fakeOppiaClock.getCurrentTimeMs()) val loggedEvent = fakePerformanceMetricsEventLogger.getMostRecentPerformanceMetricsEvent() assertThat(loggedEvent.timestampMillis).isEqualTo(TEST_TIMESTAMP) @@ -139,7 +143,9 @@ class PerformanceMetricsLoggerTest { val storageTier = fakePerformanceMetricAssessor.getDeviceStorageTier() val isAppInForeground = performanceMetricsController.getIsAppInForeground() val storageUsage = fakePerformanceMetricAssessor.getUsedStorage() - performanceMetricsLogger.logStorageUsage(SCREEN_NAME_UNSPECIFIED) + performanceMetricsLogger.logStorageUsage( + SCREEN_NAME_UNSPECIFIED, fakeOppiaClock.getCurrentTimeMs() + ) val loggedEvent = fakePerformanceMetricsEventLogger.getMostRecentPerformanceMetricsEvent() assertThat(loggedEvent.timestampMillis).isEqualTo(TEST_TIMESTAMP) @@ -161,7 +167,9 @@ class PerformanceMetricsLoggerTest { val memoryTier = fakePerformanceMetricAssessor.getDeviceMemoryTier() val storageTier = fakePerformanceMetricAssessor.getDeviceStorageTier() val isAppInForeground = performanceMetricsController.getIsAppInForeground() - performanceMetricsLogger.logMemoryUsage(SCREEN_NAME_UNSPECIFIED) + performanceMetricsLogger.logMemoryUsage( + SCREEN_NAME_UNSPECIFIED, fakeOppiaClock.getCurrentTimeMs() + ) val loggedEvent = fakePerformanceMetricsEventLogger.getMostRecentPerformanceMetricsEvent() assertThat(loggedEvent.timestampMillis).isEqualTo(TEST_TIMESTAMP) @@ -182,7 +190,8 @@ class PerformanceMetricsLoggerTest { val isAppInForeground = performanceMetricsController.getIsAppInForeground() performanceMetricsLogger.logStartupLatency( TEST_STARTUP_LATENCY_IN_MILLISECONDS, - SCREEN_NAME_UNSPECIFIED + SCREEN_NAME_UNSPECIFIED, + fakeOppiaClock.getCurrentTimeMs() ) val loggedEvent = fakePerformanceMetricsEventLogger.getMostRecentPerformanceMetricsEvent() @@ -208,7 +217,8 @@ class PerformanceMetricsLoggerTest { val isAppInForeground = performanceMetricsController.getIsAppInForeground() performanceMetricsLogger.logCpuUsage( SCREEN_NAME_UNSPECIFIED, - TEST_CPU_USAGE + TEST_CPU_USAGE, + fakeOppiaClock.getCurrentTimeMs() ) val loggedEvent = fakePerformanceMetricsEventLogger.getMostRecentPerformanceMetricsEvent() @@ -230,7 +240,9 @@ class PerformanceMetricsLoggerTest { val memoryTier = fakePerformanceMetricAssessor.getDeviceMemoryTier() val storageTier = fakePerformanceMetricAssessor.getDeviceStorageTier() val isAppInForeground = performanceMetricsController.getIsAppInForeground() - performanceMetricsLogger.logNetworkUsage(SCREEN_NAME_UNSPECIFIED) + performanceMetricsLogger.logNetworkUsage( + SCREEN_NAME_UNSPECIFIED, fakeOppiaClock.getCurrentTimeMs() + ) val loggedEvent = fakePerformanceMetricsEventLogger.getMostRecentPerformanceMetricsEvent() assertThat(loggedEvent.timestampMillis).isEqualTo(TEST_TIMESTAMP) @@ -248,12 +260,14 @@ class PerformanceMetricsLoggerTest { fun testLogger_inDefaultState_tryToLogMultipleEvents_doesNotLogAnyPerformanceMetric() { setUpApplicationInDefaultMode() performanceMetricsLogger.apply { - logApkSize(HOME_ACTIVITY) - logCpuUsage(HOME_ACTIVITY, TEST_CPU_USAGE) - logNetworkUsage(HOME_ACTIVITY) - logStartupLatency(TEST_STARTUP_LATENCY_IN_MILLISECONDS, HOME_ACTIVITY) - logMemoryUsage(HOME_ACTIVITY) - logStorageUsage(HOME_ACTIVITY) + logApkSize(HOME_ACTIVITY, fakeOppiaClock.getCurrentTimeMs()) + logCpuUsage(HOME_ACTIVITY, TEST_CPU_USAGE, fakeOppiaClock.getCurrentTimeMs()) + logNetworkUsage(HOME_ACTIVITY, fakeOppiaClock.getCurrentTimeMs()) + logStartupLatency( + TEST_STARTUP_LATENCY_IN_MILLISECONDS, HOME_ACTIVITY, fakeOppiaClock.getCurrentTimeMs() + ) + logMemoryUsage(HOME_ACTIVITY, fakeOppiaClock.getCurrentTimeMs()) + logStorageUsage(HOME_ACTIVITY, fakeOppiaClock.getCurrentTimeMs()) } assertThat(fakePerformanceMetricsEventLogger.noPerformanceMetricsEventsPresent()).isTrue() diff --git a/domain/src/test/java/org/oppia/android/domain/oppialogger/analytics/testing/BUILD.bazel b/domain/src/test/java/org/oppia/android/domain/oppialogger/analytics/testing/BUILD.bazel deleted file mode 100644 index 533770e2c80..00000000000 --- a/domain/src/test/java/org/oppia/android/domain/oppialogger/analytics/testing/BUILD.bazel +++ /dev/null @@ -1,38 +0,0 @@ -""" -Tests for test-only app analytics logging support components. -""" - -load("//:oppia_android_test.bzl", "oppia_android_test") - -oppia_android_test( - name = "FakeLogSchedulerTest", - srcs = ["FakeLogSchedulerTest.kt"], - custom_package = "org.oppia.android.domain.oppialogger.analytics.testing", - test_class = "org.oppia.android.domain.oppialogger.analytics.testing.FakeLogSchedulerTest", - test_manifest = "//domain:test_manifest", - deps = [ - "//:dagger", - "//domain/src/main/java/org/oppia/android/domain/oppialogger/analytics:cpu_module", - "//domain/src/main/java/org/oppia/android/domain/oppialogger/analytics:prod_module", - "//domain/src/main/java/org/oppia/android/domain/oppialogger/analytics/testing:fake_log_scheduler", - "//testing", - "//testing/src/main/java/org/oppia/android/testing/data:data_provider_test_monitor", - "//testing/src/main/java/org/oppia/android/testing/platformparameter:test_module", - "//testing/src/main/java/org/oppia/android/testing/robolectric:test_module", - "//testing/src/main/java/org/oppia/android/testing/threading:test_coroutine_dispatchers", - "//testing/src/main/java/org/oppia/android/testing/threading:test_module", - "//testing/src/main/java/org/oppia/android/testing/time:test_module", - "//third_party:androidx_test_ext_junit", - "//third_party:androidx_work_work-testing", - "//third_party:com_google_truth_truth", - "//third_party:junit_junit", - "//third_party:org_mockito_mockito-core", - "//third_party:org_robolectric_robolectric", - "//third_party:robolectric_android-all", - "//utility/src/main/java/org/oppia/android/util/caching:asset_prod_module", - "//utility/src/main/java/org/oppia/android/util/locale:prod_module", - "//utility/src/main/java/org/oppia/android/util/logging/performancemetrics:performance_metrics_configurations_module", - "//utility/src/main/java/org/oppia/android/util/networking:debug_module", - "//utility/src/main/java/org/oppia/android/util/networking:prod_module", - ], -) diff --git a/domain/src/test/java/org/oppia/android/domain/oppialogger/analytics/testing/FakeLogSchedulerTest.kt b/domain/src/test/java/org/oppia/android/domain/oppialogger/analytics/testing/FakeLogSchedulerTest.kt deleted file mode 100644 index 216221a986a..00000000000 --- a/domain/src/test/java/org/oppia/android/domain/oppialogger/analytics/testing/FakeLogSchedulerTest.kt +++ /dev/null @@ -1,222 +0,0 @@ -package org.oppia.android.domain.oppialogger.analytics.testing - -import android.app.Application -import android.content.Context -import androidx.test.core.app.ApplicationProvider -import androidx.test.ext.junit.runners.AndroidJUnit4 -import androidx.work.Configuration -import androidx.work.Data -import androidx.work.PeriodicWorkRequestBuilder -import androidx.work.WorkManager -import androidx.work.testing.SynchronousExecutor -import androidx.work.testing.WorkManagerTestInitHelper -import com.google.common.truth.Truth.assertThat -import dagger.Binds -import dagger.BindsInstance -import dagger.Component -import dagger.Module -import dagger.Provides -import org.junit.Before -import org.junit.Test -import org.junit.runner.RunWith -import org.oppia.android.domain.oppialogger.EventLogStorageCacheSize -import org.oppia.android.domain.oppialogger.ExceptionLogStorageCacheSize -import org.oppia.android.domain.oppialogger.LoggingIdentifierModule -import org.oppia.android.domain.oppialogger.PerformanceMetricsLogStorageCacheSize -import org.oppia.android.domain.oppialogger.analytics.ApplicationLifecycleModule -import org.oppia.android.domain.oppialogger.analytics.CpuPerformanceSnapshotterModule -import org.oppia.android.domain.oppialogger.logscheduler.MetricLogSchedulingWorker -import org.oppia.android.domain.oppialogger.logscheduler.MetricLogSchedulingWorkerFactory -import org.oppia.android.domain.oppialogger.loguploader.LogUploadWorker -import org.oppia.android.domain.platformparameter.PlatformParameterSingletonModule -import org.oppia.android.testing.TestLogReportingModule -import org.oppia.android.testing.platformparameter.TestPlatformParameterModule -import org.oppia.android.testing.robolectric.RobolectricModule -import org.oppia.android.testing.threading.TestDispatcherModule -import org.oppia.android.util.caching.AssetModule -import org.oppia.android.util.data.DataProvidersInjector -import org.oppia.android.util.data.DataProvidersInjectorProvider -import org.oppia.android.util.locale.LocaleProdModule -import org.oppia.android.util.logging.LoggerModule -import org.oppia.android.util.logging.MetricLogScheduler -import org.oppia.android.util.logging.SyncStatusModule -import org.oppia.android.util.logging.performancemetrics.PerformanceMetricsConfigurationsModule -import org.oppia.android.util.networking.NetworkConnectionUtilProdModule -import org.oppia.android.util.system.OppiaClockModule -import org.robolectric.annotation.Config -import org.robolectric.annotation.LooperMode -import java.util.concurrent.TimeUnit -import javax.inject.Inject -import javax.inject.Singleton - -/** Tests for [FakeLogScheduler]. */ -// FunctionName: test names are conventionally named with underscores. -@Suppress("FunctionName") -@RunWith(AndroidJUnit4::class) -@LooperMode(LooperMode.Mode.PAUSED) -@Config(application = FakeLogSchedulerTest.TestApplication::class) -class FakeLogSchedulerTest { - - @Inject - lateinit var fakeLogScheduler: FakeLogScheduler - - @Inject - lateinit var workerFactory: MetricLogSchedulingWorkerFactory - - @Inject - lateinit var context: Context - - @Before - fun setUp() { - setUpTestApplicationComponent() - val config = Configuration.Builder() - .setExecutor(SynchronousExecutor()) - .setWorkerFactory(workerFactory) - .build() - WorkManagerTestInitHelper.initializeTestWorkManager(context, config) - } - - @Test - fun testFakeScheduler_scheduleStorageLogging_verifyScheduling() { - val workManager = WorkManager.getInstance(context) - - val inputData = Data.Builder().putString( - MetricLogSchedulingWorker.WORKER_CASE_KEY, - MetricLogSchedulingWorker.STORAGE_USAGE_WORKER - ).build() - - val request = PeriodicWorkRequestBuilder(10, TimeUnit.SECONDS) - .setInputData(inputData) - .build() - - fakeLogScheduler.enqueueWorkRequestForStorageUsage( - workManager, - request - ) - - assertThat(fakeLogScheduler.getMostRecentStorageUsageMetricLoggingRequestId()) - .isEqualTo(request.id) - } - - @Test - fun testFakeScheduler_schedulePeriodicBackgroundMetricsLogging_verifyScheduling() { - val workManager = WorkManager.getInstance(context) - - val inputData = Data.Builder().putString( - MetricLogSchedulingWorker.WORKER_CASE_KEY, - MetricLogSchedulingWorker.PERIODIC_BACKGROUND_METRIC_WORKER - ).build() - - val request = PeriodicWorkRequestBuilder(10, TimeUnit.SECONDS) - .setInputData(inputData) - .build() - - fakeLogScheduler.enqueueWorkRequestForPeriodicBackgroundMetrics( - workManager, - request - ) - - assertThat(fakeLogScheduler.getMostRecentPeriodicBackgroundMetricLoggingRequestId()) - .isEqualTo(request.id) - } - - @Test - fun testFakeScheduler_schedulePeriodicUiMetricsLogging_verifyScheduling() { - val workManager = WorkManager.getInstance(context) - - val inputData = Data.Builder().putString( - MetricLogSchedulingWorker.WORKER_CASE_KEY, - MetricLogSchedulingWorker.PERIODIC_UI_METRIC_WORKER - ).build() - - val request = PeriodicWorkRequestBuilder(10, TimeUnit.SECONDS) - .setInputData(inputData) - .build() - - fakeLogScheduler.enqueueWorkRequestForPeriodicUiMetrics( - workManager, - request - ) - - assertThat(fakeLogScheduler.getMostRecentPeriodicUiMetricLoggingRequestId()) - .isEqualTo(request.id) - } - - private fun setUpTestApplicationComponent() { - ApplicationProvider.getApplicationContext().inject(this) - } - - // TODO(#89): Move this to a common test application component. - @Module - interface TestModule { - @Binds - fun provideContext(application: Application): Context - - @Binds - fun bindMetricLogScheduler(fakeLogScheduler: FakeLogScheduler): MetricLogScheduler - } - - @Module - class TestLogStorageModule { - - @Provides - @EventLogStorageCacheSize - fun provideEventLogStorageCacheSize(): Int = 2 - - @Provides - @ExceptionLogStorageCacheSize - fun provideExceptionLogStorageSize(): Int = 2 - - @Provides - @PerformanceMetricsLogStorageCacheSize - fun providePerformanceMetricsLogStorageCacheSize(): Int = 2 - } - - // TODO(#89): Move this to a common test application component. - @Singleton - @Component( - modules = [ - ApplicationLifecycleModule::class, - AssetModule::class, - CpuPerformanceSnapshotterModule::class, - LocaleProdModule::class, - LoggerModule::class, - LoggingIdentifierModule::class, - NetworkConnectionUtilProdModule::class, - OppiaClockModule::class, - PerformanceMetricsConfigurationsModule::class, - PlatformParameterSingletonModule::class, - RobolectricModule::class, - SyncStatusModule::class, - TestDispatcherModule::class, - TestLogReportingModule::class, - TestLogStorageModule::class, - TestModule::class, - TestPlatformParameterModule::class - ] - ) - interface TestApplicationComponent : DataProvidersInjector { - @Component.Builder - interface Builder { - @BindsInstance - fun setApplication(application: Application): Builder - fun build(): TestApplicationComponent - } - - fun inject(test: FakeLogSchedulerTest) - } - - class TestApplication : Application(), DataProvidersInjectorProvider { - private val component: TestApplicationComponent by lazy { - DaggerFakeLogSchedulerTest_TestApplicationComponent.builder() - .setApplication(this) - .build() - } - - fun inject(test: FakeLogSchedulerTest) { - component.inject(test) - } - - override fun getDataProvidersInjector(): DataProvidersInjector = component - } -} diff --git a/domain/src/test/java/org/oppia/android/domain/oppialogger/exceptions/UncaughtExceptionLoggerStartupListenerTest.kt b/domain/src/test/java/org/oppia/android/domain/oppialogger/exceptions/UncaughtExceptionLoggerStartupListenerTest.kt index 9540d7a7662..980b2728457 100644 --- a/domain/src/test/java/org/oppia/android/domain/oppialogger/exceptions/UncaughtExceptionLoggerStartupListenerTest.kt +++ b/domain/src/test/java/org/oppia/android/domain/oppialogger/exceptions/UncaughtExceptionLoggerStartupListenerTest.kt @@ -50,19 +50,19 @@ class UncaughtExceptionLoggerStartupListenerTest { @Before fun setUp() { setUpTestApplicationComponent() + uncaughtExceptionStartupListener.onCreateStarted() + uncaughtExceptionStartupListener.onCompletedInitialization() } @Test fun testHandler_throwException_withNoNetwork_verifyLogInCache() { networkConnectionUtil.setCurrentConnectionStatus(NONE) - val exceptionThrown = Exception("TEST") - uncaughtExceptionStartupListener.uncaughtException( - Thread.currentThread(), - exceptionThrown - ) - val cachedExceptions = exceptionsController.getExceptionLogStore() + val crashThread = Thread { throw Exception("TEST") } + crashThread.start() + crashThread.join() + val cachedExceptions = exceptionsController.getExceptionLogStore() val exceptionLogs = monitorFactory.waitForNextSuccessfulResult(cachedExceptions) val exception = exceptionLogs.getExceptionLog(0).toException() assertThat(exception.message).matches("java.lang.Exception: TEST") @@ -70,15 +70,15 @@ class UncaughtExceptionLoggerStartupListenerTest { @Test fun testHandler_throwException_withNetwork_verifyLogToRemoteService() { - val exceptionThrown = Exception("TEST") - uncaughtExceptionStartupListener.uncaughtException( - Thread.currentThread(), - exceptionThrown - ) + val crashThread = Thread { throw Exception("TEST") } + crashThread.start() + crashThread.join() val exceptionCaught = fakeExceptionLogger.getMostRecentException() assertThat(exceptionCaught).hasMessageThat().matches("java.lang.Exception: TEST") - assertThat(exceptionCaught.cause).isEqualTo(Exception(exceptionThrown).cause) + // Note: When throwing across threads, cause validation might require checking the exact instance + // or string matching depending on how the handler wraps exceptions. + assertThat(exceptionCaught.cause?.message).isEqualTo("TEST") } private fun setUpTestApplicationComponent() { diff --git a/domain/src/test/java/org/oppia/android/domain/oppialogger/logscheduler/BUILD.bazel b/domain/src/test/java/org/oppia/android/domain/oppialogger/logscheduler/BUILD.bazel new file mode 100644 index 00000000000..3b2c0e6d975 --- /dev/null +++ b/domain/src/test/java/org/oppia/android/domain/oppialogger/logscheduler/BUILD.bazel @@ -0,0 +1,124 @@ +""" +Tests for the library that provides log scheduling functionality. +""" + +load("//:oppia_android_test.bzl", "oppia_android_test") + +oppia_android_test( + name = "MetricLogSchedulerModuleTest", + srcs = ["MetricLogSchedulerModuleTest.kt"], + custom_package = "org.oppia.android.domain.oppialogger.logscheduler", + test_class = "org.oppia.android.domain.oppialogger.logscheduler.MetricLogSchedulerModuleTest", + test_manifest = "//domain:test_manifest", + deps = [ + "//:dagger", + "//domain/src/main/java/org/oppia/android/domain/oppialogger:prod_module", + "//domain/src/main/java/org/oppia/android/domain/oppialogger/analytics:cpu_module", + "//domain/src/main/java/org/oppia/android/domain/oppialogger/analytics:prod_module", + "//domain/src/main/java/org/oppia/android/domain/oppialogger/logscheduler:metric_log_scheduler_module", + "//domain/src/main/java/org/oppia/android/domain/platformparameter:prod_module", + "//testing", + "//testing/src/main/java/org/oppia/android/testing/firebase:test_authentication_module", + "//testing/src/main/java/org/oppia/android/testing/logging:sync_status_test_module", + "//testing/src/main/java/org/oppia/android/testing/platformparameter:test_module", + "//testing/src/main/java/org/oppia/android/testing/robolectric:test_module", + "//testing/src/main/java/org/oppia/android/testing/threading:test_module", + "//testing/src/main/java/org/oppia/android/testing/time:test_module", + "//third_party:androidx_test_ext_junit", + "//third_party:com_google_truth_truth", + "//third_party:junit_junit", + "//third_party:org_robolectric_robolectric", + "//third_party:robolectric_android-all", + "//utility/src/main/java/org/oppia/android/util/caching:asset_prod_module", + "//utility/src/main/java/org/oppia/android/util/locale:prod_module", + "//utility/src/main/java/org/oppia/android/util/logging:prod_module", + "//utility/src/main/java/org/oppia/android/util/logging/firebase:debug_module", + "//utility/src/main/java/org/oppia/android/util/logging/performancemetrics:performance_metrics_configurations_module", + "//utility/src/main/java/org/oppia/android/util/networking:debug_module", + ], +) + +oppia_android_test( + name = "MetricLogSchedulingWorkerSchedulerTest", + srcs = ["MetricLogSchedulingWorkerSchedulerTest.kt"], + custom_package = "org.oppia.android.domain.oppialogger.logscheduler", + test_class = "org.oppia.android.domain.oppialogger.logscheduler.MetricLogSchedulingWorkerSchedulerTest", + test_manifest = "//domain:test_manifest", + deps = [ + "//:dagger", + "//domain/src/main/java/org/oppia/android/domain/oppialogger:prod_module", + "//domain/src/main/java/org/oppia/android/domain/oppialogger/analytics:cpu_module", + "//domain/src/main/java/org/oppia/android/domain/oppialogger/analytics:prod_module", + "//domain/src/main/java/org/oppia/android/domain/oppialogger/logscheduler:metric_log_scheduler_module", + "//domain/src/main/java/org/oppia/android/domain/oppialogger/logscheduler:metric_log_scheduling_worker", + "//domain/src/main/java/org/oppia/android/domain/oppialogger/logscheduler:scheduler", + "//domain/src/main/java/org/oppia/android/domain/platformparameter:prod_module", + "//domain/src/main/java/org/oppia/android/domain/workmanager:oppia_worker", + "//domain/src/main/java/org/oppia/android/domain/workmanager:work_manager_scheduler", + "//domain/src/main/java/org/oppia/android/domain/workmanager/testing:oppia_work_manager_test_driver", + "//testing", + "//testing/src/main/java/org/oppia/android/testing/firebase:test_authentication_module", + "//testing/src/main/java/org/oppia/android/testing/logging:sync_status_test_module", + "//testing/src/main/java/org/oppia/android/testing/platformparameter:test_module", + "//testing/src/main/java/org/oppia/android/testing/robolectric:test_module", + "//testing/src/main/java/org/oppia/android/testing/threading:test_coroutine_dispatchers", + "//testing/src/main/java/org/oppia/android/testing/threading:test_module", + "//testing/src/main/java/org/oppia/android/testing/time:test_module", + "//third_party:androidx_test_ext_junit", + "//third_party:androidx_work_work-runtime", + "//third_party:com_google_truth_truth", + "//third_party:junit_junit", + "//third_party:org_robolectric_robolectric", + "//third_party:robolectric_android-all", + "//utility/src/main/java/org/oppia/android/util/caching:asset_prod_module", + "//utility/src/main/java/org/oppia/android/util/locale:prod_module", + "//utility/src/main/java/org/oppia/android/util/logging:prod_module", + "//utility/src/main/java/org/oppia/android/util/logging/firebase:debug_module", + "//utility/src/main/java/org/oppia/android/util/logging/performancemetrics:performance_metrics_configurations_module", + "//utility/src/main/java/org/oppia/android/util/networking:debug_module", + ], +) + +oppia_android_test( + name = "MetricLogSchedulingWorkerTest", + srcs = ["MetricLogSchedulingWorkerTest.kt"], + custom_package = "org.oppia.android.domain.oppialogger.logscheduler", + test_class = "org.oppia.android.domain.oppialogger.logscheduler.MetricLogSchedulingWorkerTest", + test_manifest = "//domain:test_manifest", + deps = [ + "//:dagger", + "//domain/src/main/java/org/oppia/android/domain/oppialogger:prod_module", + "//domain/src/main/java/org/oppia/android/domain/oppialogger/analytics:cpu_module", + "//domain/src/main/java/org/oppia/android/domain/oppialogger/analytics:prod_module", + "//domain/src/main/java/org/oppia/android/domain/oppialogger/logscheduler:metric_log_scheduler_module", + "//domain/src/main/java/org/oppia/android/domain/oppialogger/logscheduler:metric_log_scheduling_worker", + "//domain/src/main/java/org/oppia/android/domain/oppialogger/loguploader:worker", + "//domain/src/main/java/org/oppia/android/domain/oppialogger/loguploader:worker_module", + "//domain/src/main/java/org/oppia/android/domain/platformparameter:prod_module", + "//domain/src/main/java/org/oppia/android/domain/workmanager:oppia_worker", + "//domain/src/main/java/org/oppia/android/domain/workmanager:work_manager_configuration_module", + "//domain/src/main/java/org/oppia/android/domain/workmanager:work_manager_scheduler", + "//domain/src/main/java/org/oppia/android/domain/workmanager/testing:oppia_work_manager_test_driver", + "//testing", + "//testing/src/main/java/org/oppia/android/testing/firebase:test_authentication_module", + "//testing/src/main/java/org/oppia/android/testing/logging:sync_status_test_module", + "//testing/src/main/java/org/oppia/android/testing/platformparameter:test_module", + "//testing/src/main/java/org/oppia/android/testing/robolectric:test_module", + "//testing/src/main/java/org/oppia/android/testing/threading:test_coroutine_dispatchers", + "//testing/src/main/java/org/oppia/android/testing/threading:test_module", + "//testing/src/main/java/org/oppia/android/testing/time:test_module", + "//third_party:androidx_test_ext_junit", + "//third_party:androidx_work_work-runtime", + "//third_party:com_google_truth_truth", + "//third_party:junit_junit", + "//third_party:org_robolectric_robolectric", + "//third_party:robolectric_android-all", + "//utility/src/main/java/org/oppia/android/util/caching:asset_prod_module", + "//utility/src/main/java/org/oppia/android/util/locale:prod_module", + "//utility/src/main/java/org/oppia/android/util/logging:prod_module", + "//utility/src/main/java/org/oppia/android/util/logging/firebase:debug_module", + "//utility/src/main/java/org/oppia/android/util/logging/performancemetrics:performance_metrics_assessor_module", + "//utility/src/main/java/org/oppia/android/util/logging/performancemetrics:performance_metrics_configurations_module", + "//utility/src/main/java/org/oppia/android/util/networking:debug_module", + ], +) diff --git a/domain/src/test/java/org/oppia/android/domain/oppialogger/logscheduler/MetricLogSchedulerModuleTest.kt b/domain/src/test/java/org/oppia/android/domain/oppialogger/logscheduler/MetricLogSchedulerModuleTest.kt index 35fe7ad80a1..0808c155888 100644 --- a/domain/src/test/java/org/oppia/android/domain/oppialogger/logscheduler/MetricLogSchedulerModuleTest.kt +++ b/domain/src/test/java/org/oppia/android/domain/oppialogger/logscheduler/MetricLogSchedulerModuleTest.kt @@ -12,26 +12,46 @@ import dagger.Module import org.junit.Before import org.junit.Test import org.junit.runner.RunWith +import org.oppia.android.domain.oppialogger.LogStorageModule +import org.oppia.android.domain.oppialogger.LoggingIdentifierModule +import org.oppia.android.domain.oppialogger.analytics.ApplicationLifecycleModule +import org.oppia.android.domain.oppialogger.analytics.CpuPerformanceSnapshotterModule +import org.oppia.android.domain.platformparameter.PlatformParameterControllerInjector +import org.oppia.android.domain.platformparameter.PlatformParameterControllerInjectorProvider +import org.oppia.android.domain.platformparameter.PlatformParameterSingletonModule +import org.oppia.android.domain.workmanager.OppiaWorker +import org.oppia.android.domain.workmanager.StartupWorkerScheduleReadinessListener +import org.oppia.android.testing.TestLogReportingModule +import org.oppia.android.testing.firebase.TestAuthenticationModule +import org.oppia.android.testing.logging.SyncStatusTestModule +import org.oppia.android.testing.platformparameter.TestPlatformParameterModule import org.oppia.android.testing.robolectric.RobolectricModule +import org.oppia.android.testing.threading.TestDispatcherModule import org.oppia.android.testing.time.FakeOppiaClockModule +import org.oppia.android.util.caching.AssetModule +import org.oppia.android.util.data.DataProvidersInjector +import org.oppia.android.util.data.DataProvidersInjectorProvider import org.oppia.android.util.locale.LocaleProdModule import org.oppia.android.util.logging.LoggerModule -import org.oppia.android.util.logging.MetricLogScheduler +import org.oppia.android.util.logging.performancemetrics.PerformanceMetricsConfigurationsModule +import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule +import org.oppia.android.util.threading.DispatcherInjector +import org.oppia.android.util.threading.DispatcherInjectorProvider import org.robolectric.annotation.Config import org.robolectric.annotation.LooperMode import javax.inject.Inject import javax.inject.Singleton /** Tests for [MetricLogSchedulerModule]. */ -// FunctionName: test names are conventionally named with underscores. -@Suppress("FunctionName") +@Suppress("FunctionName") // FunctionName: test names are conventionally named with underscores. @RunWith(AndroidJUnit4::class) @LooperMode(LooperMode.Mode.PAUSED) -@Config(manifest = Config.NONE) +@Config(application = MetricLogSchedulerModuleTest.TestApplication::class) class MetricLogSchedulerModuleTest { - + @Inject lateinit var context: Context @Inject - lateinit var metricLogScheduler: MetricLogScheduler + lateinit var readinessListeners: Set<@JvmSuppressWildcards StartupWorkerScheduleReadinessListener> + @Inject lateinit var factories: Map> @Before fun setUp() { @@ -39,15 +59,23 @@ class MetricLogSchedulerModuleTest { } @Test - fun testModule_injectsProductionImplementationOfMetricLogScheduler() { - assertThat(metricLogScheduler).isInstanceOf(PerformanceMetricsLogScheduler::class.java) + fun testInjection_bindsMetricLogWorkerSchedulerIntoReadinessListenerSet() { + // The main test is that the test suite builds. + val metricLogWorkerSchedulers = + readinessListeners.filterIsInstance() + assertThat(readinessListeners).isNotEmpty() + assertThat(metricLogWorkerSchedulers).hasSize(1) + } + + @Test + fun testInjection_bindsMetricLogWorkerFactoryByWorkerName() { + assertThat(factories).containsKey(MetricLogSchedulingWorker.WORKER_NAME) + assertThat(factories[MetricLogSchedulingWorker.WORKER_NAME]) + .isInstanceOf(MetricLogSchedulingWorker.Factory::class.java) } private fun setUpTestApplicationComponent() { - DaggerMetricLogSchedulerModuleTest_TestApplicationComponent.builder() - .setApplication(ApplicationProvider.getApplicationContext()) - .build() - .inject(this) + ApplicationProvider.getApplicationContext().inject(this) } // TODO(#89): Move this to a common test application component. @@ -61,15 +89,29 @@ class MetricLogSchedulerModuleTest { @Singleton @Component( modules = [ - FakeOppiaClockModule::class, + TestModule::class, + MetricLogSchedulerModule::class, LocaleProdModule::class, + FakeOppiaClockModule::class, LoggerModule::class, - MetricLogSchedulerModule::class, RobolectricModule::class, - TestModule::class + TestDispatcherModule::class, + TestPlatformParameterModule::class, + ApplicationLifecycleModule::class, + AssetModule::class, + CpuPerformanceSnapshotterModule::class, + LogStorageModule::class, + LoggingIdentifierModule::class, + NetworkConnectionUtilDebugModule::class, + PerformanceMetricsConfigurationsModule::class, + PlatformParameterSingletonModule::class, + SyncStatusTestModule::class, + TestLogReportingModule::class, + TestAuthenticationModule::class ] ) - interface TestApplicationComponent { + interface TestApplicationComponent : + DataProvidersInjector, DispatcherInjector, PlatformParameterControllerInjector { @Component.Builder interface Builder { @BindsInstance @@ -78,6 +120,26 @@ class MetricLogSchedulerModuleTest { fun build(): TestApplicationComponent } - fun inject(metricLogSchedulerModuleTest: MetricLogSchedulerModuleTest) + fun inject(test: MetricLogSchedulerModuleTest) + } + + class TestApplication : + Application(), + DataProvidersInjectorProvider, + DispatcherInjectorProvider, + PlatformParameterControllerInjectorProvider { + private val component: TestApplicationComponent by lazy { + DaggerMetricLogSchedulerModuleTest_TestApplicationComponent.builder() + .setApplication(this) + .build() + } + + fun inject(test: MetricLogSchedulerModuleTest) { + component.inject(test) + } + + override fun getDataProvidersInjector() = component + override fun getPlatformParameterControllerInjector() = component + override fun getDispatcherInjector(): DispatcherInjector = component } } diff --git a/domain/src/test/java/org/oppia/android/domain/oppialogger/logscheduler/MetricLogSchedulingWorkerSchedulerTest.kt b/domain/src/test/java/org/oppia/android/domain/oppialogger/logscheduler/MetricLogSchedulingWorkerSchedulerTest.kt new file mode 100644 index 00000000000..234f931e65f --- /dev/null +++ b/domain/src/test/java/org/oppia/android/domain/oppialogger/logscheduler/MetricLogSchedulingWorkerSchedulerTest.kt @@ -0,0 +1,273 @@ +package org.oppia.android.domain.oppialogger.logscheduler + +import android.app.Application +import android.content.Context +import androidx.test.core.app.ApplicationProvider +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.work.NetworkType.CONNECTED +import androidx.work.WorkInfo +import com.google.common.truth.Truth.assertThat +import com.google.firebase.FirebaseApp +import dagger.Binds +import dagger.BindsInstance +import dagger.Component +import dagger.Module +import kotlinx.coroutines.CoroutineDispatcher +import org.junit.After +import org.junit.Test +import org.junit.runner.RunWith +import org.oppia.android.app.model.OppiaMetricLog.LoggableMetric.LoggableMetricTypeCase.MEMORY_USAGE_METRIC +import org.oppia.android.app.model.OppiaMetricLog.LoggableMetric.LoggableMetricTypeCase.NETWORK_USAGE_METRIC +import org.oppia.android.app.model.OppiaMetricLog.LoggableMetric.LoggableMetricTypeCase.STORAGE_USAGE_METRIC +import org.oppia.android.domain.oppialogger.LogStorageModule +import org.oppia.android.domain.oppialogger.LoggingIdentifierModule +import org.oppia.android.domain.oppialogger.analytics.ApplicationLifecycleModule +import org.oppia.android.domain.oppialogger.analytics.CpuPerformanceSnapshotterModule +import org.oppia.android.domain.oppialogger.logscheduler.MetricLogSchedulingWorker.Companion.WORKER_NAME +import org.oppia.android.domain.oppialogger.logscheduler.MetricLogSchedulingWorker.Operation.SCHEDULE_LOG_PERIODIC_BACKGROUND_METRICS +import org.oppia.android.domain.oppialogger.logscheduler.MetricLogSchedulingWorker.Operation.SCHEDULE_LOG_PERIODIC_UI_METRICS +import org.oppia.android.domain.oppialogger.logscheduler.MetricLogSchedulingWorker.Operation.SCHEDULE_LOG_STORAGE_USAGE_METRICS +import org.oppia.android.domain.platformparameter.PlatformParameterControllerInjector +import org.oppia.android.domain.platformparameter.PlatformParameterControllerInjectorProvider +import org.oppia.android.domain.platformparameter.PlatformParameterSingletonModule +import org.oppia.android.domain.workmanager.WorkManagerScheduler +import org.oppia.android.domain.workmanager.testing.OppiaWorkManagerTestDriver +import org.oppia.android.testing.FakePerformanceMetricsEventLogger +import org.oppia.android.testing.TestLogReportingModule +import org.oppia.android.testing.firebase.TestAuthenticationModule +import org.oppia.android.testing.logging.SyncStatusTestModule +import org.oppia.android.testing.platformparameter.TestPlatformParameterModule +import org.oppia.android.testing.platformparameter.TestPlatformParameterModule.Companion.forcePerformanceMetricsCollectionHighFrequencyTimeIntervalInMinutes +import org.oppia.android.testing.platformparameter.TestPlatformParameterModule.Companion.forcePerformanceMetricsCollectionLowFrequencyTimeIntervalInMinutes +import org.oppia.android.testing.robolectric.RobolectricModule +import org.oppia.android.testing.threading.TestCoroutineDispatchers +import org.oppia.android.testing.threading.TestDispatcherModule +import org.oppia.android.testing.time.FakeOppiaClockModule +import org.oppia.android.util.caching.AssetModule +import org.oppia.android.util.data.DataProvidersInjector +import org.oppia.android.util.data.DataProvidersInjectorProvider +import org.oppia.android.util.locale.LocaleProdModule +import org.oppia.android.util.logging.LoggerModule +import org.oppia.android.util.logging.performancemetrics.PerformanceMetricsConfigurationsModule +import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule +import org.oppia.android.util.threading.BackgroundDispatcher +import org.oppia.android.util.threading.DispatcherInjector +import org.oppia.android.util.threading.DispatcherInjectorProvider +import org.robolectric.annotation.Config +import org.robolectric.annotation.LooperMode +import java.util.concurrent.TimeUnit +import javax.inject.Inject +import javax.inject.Singleton + +/** Tests for [MetricLogSchedulingWorkerScheduler]. */ +@Suppress("FunctionName") // FunctionName: test names are conventionally named with underscores. +@RunWith(AndroidJUnit4::class) +@LooperMode(LooperMode.Mode.PAUSED) +@Config(application = MetricLogSchedulingWorkerSchedulerTest.TestApplication::class) +class MetricLogSchedulingWorkerSchedulerTest { + @Inject lateinit var context: Context + @Inject lateinit var testCoroutineDispatchers: TestCoroutineDispatchers + @Inject lateinit var workManagerScheduler: WorkManagerScheduler + @Inject lateinit var metricLogSchedulingWorkerScheduler: MetricLogSchedulingWorkerScheduler + @Inject lateinit var fakePerformanceMetricsEventLogger: FakePerformanceMetricsEventLogger + @Inject lateinit var testDriver: OppiaWorkManagerTestDriver + @field:[Inject BackgroundDispatcher] lateinit var backgroundDispatcher: CoroutineDispatcher + + @After + fun tearDown() { + TestPlatformParameterModule.reset() + } + + @Test + fun testMetricLogSchedulingWorker_hasThreeOperationTypes() { + setUpTestApplicationComponent() + + // A change detector test that, if failing, means that other tests in this suite need to be + // updated to ensure that the new operation type is properly verified. + assertThat(MetricLogSchedulingWorker.Operation.values().toList()).containsExactly( + SCHEDULE_LOG_PERIODIC_BACKGROUND_METRICS, + SCHEDULE_LOG_PERIODIC_UI_METRICS, + SCHEDULE_LOG_STORAGE_USAGE_METRICS + ) + } + + @Test + fun testScheduleWork_schedulesMetricLogSchedulingWorkerForPeriodicBackgroundMetrics() { + forcePerformanceMetricsCollectionHighFrequencyTimeIntervalInMinutes(100) + forcePerformanceMetricsCollectionLowFrequencyTimeIntervalInMinutes(200) + setUpTestApplicationComponent() + initializeDependencies() + + metricLogSchedulingWorkerScheduler.scheduleWork(workManagerScheduler) + + // Verify that the job was scheduled correctly and uses the high-frequency time. + val monitor = + testDriver.lookUpPeriodicMonitor(WORKER_NAME, SCHEDULE_LOG_PERIODIC_BACKGROUND_METRICS) + assertThat(monitor.state).isEqualTo(WorkInfo.State.ENQUEUED) + assertThat(monitor.intervalDurationMs).isEqualTo(TimeUnit.MINUTES.toMillis(100)) + assertThat(monitor.requiredNetworkType).isEqualTo(CONNECTED) + } + + @Test + fun testScheduleWork_schedulesMetricLogSchedulingWorkerForPeriodicUiMetrics() { + forcePerformanceMetricsCollectionHighFrequencyTimeIntervalInMinutes(300) + forcePerformanceMetricsCollectionLowFrequencyTimeIntervalInMinutes(400) + setUpTestApplicationComponent() + initializeDependencies() + + metricLogSchedulingWorkerScheduler.scheduleWork(workManagerScheduler) + + // Verify that the job was scheduled correctly and uses the high-frequency time. + val monitor = testDriver.lookUpPeriodicMonitor(WORKER_NAME, SCHEDULE_LOG_PERIODIC_UI_METRICS) + assertThat(monitor.state).isEqualTo(WorkInfo.State.ENQUEUED) + assertThat(monitor.intervalDurationMs).isEqualTo(TimeUnit.MINUTES.toMillis(300)) + assertThat(monitor.requiredNetworkType).isEqualTo(CONNECTED) + } + + @Test + fun testScheduleWork_schedulesMetricLogSchedulingWorkerForStorageUsageMetrics() { + forcePerformanceMetricsCollectionHighFrequencyTimeIntervalInMinutes(500) + forcePerformanceMetricsCollectionLowFrequencyTimeIntervalInMinutes(600) + setUpTestApplicationComponent() + initializeDependencies() + + metricLogSchedulingWorkerScheduler.scheduleWork(workManagerScheduler) + + // Verify that the job was scheduled correctly and uses the low-frequency time.s + val monitor = testDriver.lookUpPeriodicMonitor(WORKER_NAME, SCHEDULE_LOG_STORAGE_USAGE_METRICS) + assertThat(monitor.state).isEqualTo(WorkInfo.State.ENQUEUED) + assertThat(monitor.intervalDurationMs).isEqualTo(TimeUnit.MINUTES.toMillis(600)) + assertThat(monitor.requiredNetworkType).isEqualTo(CONNECTED) + } + + @Test + fun testScheduleWork_constraintsMet_runsThreeJobs() { + TestPlatformParameterModule.forceEnablePerformanceMetricsCollection(true) + forcePerformanceMetricsCollectionHighFrequencyTimeIntervalInMinutes(100) + forcePerformanceMetricsCollectionLowFrequencyTimeIntervalInMinutes(150) + setUpTestApplicationComponent() + initializeDependencies() + metricLogSchedulingWorkerScheduler.scheduleWork(workManagerScheduler) + + forceConstraintsMet(SCHEDULE_LOG_PERIODIC_BACKGROUND_METRICS) + forceConstraintsMet(SCHEDULE_LOG_PERIODIC_UI_METRICS) + forceConstraintsMet(SCHEDULE_LOG_STORAGE_USAGE_METRICS) + testCoroutineDispatchers.runCurrent() + + // Order cannot be easily checked here, so just verify that they ran. + val logCount = fakePerformanceMetricsEventLogger.getPerformanceMetricsEventListCount() + assertThat(logCount).isEqualTo(3) + val loggedEvents = fakePerformanceMetricsEventLogger.getMostRecentPerformanceMetricsEvents(3) + assertThat(loggedEvents.map { it.loggableMetric.loggableMetricTypeCase }) + .containsExactly(NETWORK_USAGE_METRIC, MEMORY_USAGE_METRIC, STORAGE_USAGE_METRIC) + } + + @Test + fun testScheduleWork_constraintsMet_fifteenAndSixtyMinIncrements_wait35_runsSevenJobs() { + TestPlatformParameterModule.forceEnablePerformanceMetricsCollection(true) + forcePerformanceMetricsCollectionHighFrequencyTimeIntervalInMinutes(15) + forcePerformanceMetricsCollectionLowFrequencyTimeIntervalInMinutes(60) + setUpTestApplicationComponent() + initializeDependencies() + metricLogSchedulingWorkerScheduler.scheduleWork(workManagerScheduler) + + forceConstraintsMet(SCHEDULE_LOG_PERIODIC_BACKGROUND_METRICS) + forceConstraintsMet(SCHEDULE_LOG_PERIODIC_UI_METRICS) + forceConstraintsMet(SCHEDULE_LOG_STORAGE_USAGE_METRICS) + testCoroutineDispatchers.advanceTimeBy(TimeUnit.MINUTES.toMillis(35)) + + // Advancing 35 minutes means the high frequency jobs (2) each ran 3 times (they run immediately + // upon being scheduled), and the low frequency job (1) only ran 1 time since it was scheduled. + val logCount = fakePerformanceMetricsEventLogger.getPerformanceMetricsEventListCount() + assertThat(logCount).isEqualTo(7) + val loggedEvents = fakePerformanceMetricsEventLogger.getMostRecentPerformanceMetricsEvents(7) + assertThat(loggedEvents.map { it.loggableMetric.loggableMetricTypeCase }) + .containsExactly( + NETWORK_USAGE_METRIC, + NETWORK_USAGE_METRIC, + NETWORK_USAGE_METRIC, + MEMORY_USAGE_METRIC, + MEMORY_USAGE_METRIC, + MEMORY_USAGE_METRIC, + STORAGE_USAGE_METRIC + ) + } + + private fun forceConstraintsMet(taskType: MetricLogSchedulingWorker.Operation) { + testDriver.lookUpPeriodicMonitor(WORKER_NAME, taskType).forceConstraintsMet() + } + + private fun initializeDependencies() { + FirebaseApp.initializeApp(context) + testDriver.initializeWorkManager() + } + + private fun setUpTestApplicationComponent() { + ApplicationProvider.getApplicationContext().inject(this) + } + + // TODO(#89): Move this to a common test application component. + @Module + interface TestModule { + @Binds + fun provideContext(application: Application): Context + } + + // TODO(#89): Move this to a common test application component. + @Singleton + @Component( + modules = [ + ApplicationLifecycleModule::class, + AssetModule::class, + CpuPerformanceSnapshotterModule::class, + FakeOppiaClockModule::class, + LocaleProdModule::class, + LogStorageModule::class, + LoggerModule::class, + LoggingIdentifierModule::class, + MetricLogSchedulerModule::class, + NetworkConnectionUtilDebugModule::class, + PerformanceMetricsConfigurationsModule::class, + PlatformParameterSingletonModule::class, + RobolectricModule::class, + SyncStatusTestModule::class, + TestDispatcherModule::class, + TestLogReportingModule::class, + TestModule::class, + TestAuthenticationModule::class, + TestPlatformParameterModule::class + ] + ) + interface TestApplicationComponent : + DataProvidersInjector, DispatcherInjector, PlatformParameterControllerInjector { + @Component.Builder + interface Builder { + @BindsInstance + fun setApplication(application: Application): Builder + + fun build(): TestApplicationComponent + } + + fun inject(metricLogSchedulingWorkerSchedulerTest: MetricLogSchedulingWorkerSchedulerTest) + } + + class TestApplication : + Application(), + DataProvidersInjectorProvider, + DispatcherInjectorProvider, + PlatformParameterControllerInjectorProvider { + private val component: TestApplicationComponent by lazy { + DaggerMetricLogSchedulingWorkerSchedulerTest_TestApplicationComponent.builder() + .setApplication(this) + .build() + } + + fun inject(metricLogSchedulingWorkerSchedulerTest: MetricLogSchedulingWorkerSchedulerTest) { + component.inject(metricLogSchedulingWorkerSchedulerTest) + } + + override fun getDataProvidersInjector() = component + override fun getPlatformParameterControllerInjector() = component + override fun getDispatcherInjector(): DispatcherInjector = component + } +} diff --git a/domain/src/test/java/org/oppia/android/domain/oppialogger/logscheduler/MetricLogSchedulingWorkerTest.kt b/domain/src/test/java/org/oppia/android/domain/oppialogger/logscheduler/MetricLogSchedulingWorkerTest.kt index 2c1a4a02c21..009429cec99 100644 --- a/domain/src/test/java/org/oppia/android/domain/oppialogger/logscheduler/MetricLogSchedulingWorkerTest.kt +++ b/domain/src/test/java/org/oppia/android/domain/oppialogger/logscheduler/MetricLogSchedulingWorkerTest.kt @@ -2,45 +2,45 @@ package org.oppia.android.domain.oppialogger.logscheduler import android.app.Application import android.content.Context +import android.util.Log import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 -import androidx.test.platform.app.InstrumentationRegistry import androidx.work.Configuration -import androidx.work.Data -import androidx.work.OneTimeWorkRequest -import androidx.work.OneTimeWorkRequestBuilder import androidx.work.WorkInfo -import androidx.work.WorkManager -import androidx.work.testing.SynchronousExecutor -import androidx.work.testing.WorkManagerTestInitHelper import com.google.common.truth.Truth.assertThat -import dagger.Binds +import com.google.firebase.FirebaseApp import dagger.BindsInstance import dagger.Component import dagger.Module import dagger.Provides +import kotlinx.coroutines.CoroutineDispatcher import org.junit.After -import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.oppia.android.app.model.OppiaMetricLog.LoggableMetric.LoggableMetricTypeCase.MEMORY_USAGE_METRIC import org.oppia.android.app.model.OppiaMetricLog.LoggableMetric.LoggableMetricTypeCase.NETWORK_USAGE_METRIC import org.oppia.android.app.model.OppiaMetricLog.LoggableMetric.LoggableMetricTypeCase.STORAGE_USAGE_METRIC -import org.oppia.android.domain.oppialogger.EventLogStorageCacheSize -import org.oppia.android.domain.oppialogger.ExceptionLogStorageCacheSize +import org.oppia.android.app.model.ScreenName +import org.oppia.android.domain.oppialogger.LogStorageModule import org.oppia.android.domain.oppialogger.LoggingIdentifierModule -import org.oppia.android.domain.oppialogger.OppiaLogger -import org.oppia.android.domain.oppialogger.PerformanceMetricsLogStorageCacheSize +import org.oppia.android.domain.oppialogger.analytics.ApplicationLifecycleLogger import org.oppia.android.domain.oppialogger.analytics.ApplicationLifecycleModule import org.oppia.android.domain.oppialogger.analytics.CpuPerformanceSnapshotterModule -import org.oppia.android.domain.oppialogger.analytics.PerformanceMetricsController +import org.oppia.android.domain.oppialogger.logscheduler.MetricLogSchedulingWorker.Companion.WORKER_NAME +import org.oppia.android.domain.oppialogger.logscheduler.MetricLogSchedulingWorker.Operation.SCHEDULE_LOG_PERIODIC_BACKGROUND_METRICS +import org.oppia.android.domain.oppialogger.logscheduler.MetricLogSchedulingWorker.Operation.SCHEDULE_LOG_PERIODIC_UI_METRICS +import org.oppia.android.domain.oppialogger.logscheduler.MetricLogSchedulingWorker.Operation.SCHEDULE_LOG_STORAGE_USAGE_METRICS import org.oppia.android.domain.oppialogger.loguploader.LogReportWorkerModule import org.oppia.android.domain.oppialogger.loguploader.LogUploadWorker +import org.oppia.android.domain.oppialogger.loguploader.LogUploadWorker.Operation.UPLOAD_PERFORMANCE_METRICS +import org.oppia.android.domain.platformparameter.PlatformParameterControllerInjector +import org.oppia.android.domain.platformparameter.PlatformParameterControllerInjectorProvider import org.oppia.android.domain.platformparameter.PlatformParameterSingletonModule -import org.oppia.android.domain.testing.oppialogger.loguploader.FakeLogUploader -import org.oppia.android.testing.FakeAnalyticsEventLogger -import org.oppia.android.testing.FakeExceptionLogger +import org.oppia.android.domain.workmanager.WorkManagerConfigurationModule +import org.oppia.android.domain.workmanager.testing.OppiaWorkManagerTestDriver import org.oppia.android.testing.FakePerformanceMetricsEventLogger +import org.oppia.android.testing.TestLogReportingModule +import org.oppia.android.testing.firebase.TestAuthenticationModule import org.oppia.android.testing.logging.SyncStatusTestModule import org.oppia.android.testing.platformparameter.TestPlatformParameterModule import org.oppia.android.testing.robolectric.RobolectricModule @@ -48,222 +48,371 @@ import org.oppia.android.testing.threading.TestCoroutineDispatchers import org.oppia.android.testing.threading.TestDispatcherModule import org.oppia.android.testing.time.FakeOppiaClockModule import org.oppia.android.util.caching.AssetModule -import org.oppia.android.util.data.DataProviders import org.oppia.android.util.data.DataProvidersInjector import org.oppia.android.util.data.DataProvidersInjectorProvider import org.oppia.android.util.locale.LocaleProdModule -import org.oppia.android.util.logging.AnalyticsEventLogger -import org.oppia.android.util.logging.ExceptionLogger -import org.oppia.android.util.logging.LogUploader import org.oppia.android.util.logging.LoggerModule -import org.oppia.android.util.logging.performancemetrics.PerformanceMetricsAssessorModule import org.oppia.android.util.logging.performancemetrics.PerformanceMetricsConfigurationsModule -import org.oppia.android.util.logging.performancemetrics.PerformanceMetricsEventLogger import org.oppia.android.util.networking.NetworkConnectionDebugUtil +import org.oppia.android.util.networking.NetworkConnectionUtil.ProdConnectionStatus.CELLULAR +import org.oppia.android.util.networking.NetworkConnectionUtil.ProdConnectionStatus.NONE import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule +import org.oppia.android.util.threading.BackgroundDispatcher +import org.oppia.android.util.threading.DispatcherInjector +import org.oppia.android.util.threading.DispatcherInjectorProvider import org.robolectric.annotation.Config import org.robolectric.annotation.LooperMode +import org.robolectric.shadows.ShadowLog import javax.inject.Inject import javax.inject.Singleton -private const val INCORRECT_WORKER_CASE = "incorrect_worker_case" - /** Tests for [MetricLogSchedulingWorker]. */ -// FunctionName: test names are conventionally named with underscores. -@Suppress("FunctionName") +@Suppress("FunctionName") // FunctionName: test names are conventionally named with underscores. @RunWith(AndroidJUnit4::class) @LooperMode(LooperMode.Mode.PAUSED) @Config(application = MetricLogSchedulingWorkerTest.TestApplication::class) class MetricLogSchedulingWorkerTest { - @Inject - lateinit var networkConnectionUtil: NetworkConnectionDebugUtil + @Inject lateinit var context: Context + @Inject lateinit var testCoroutineDispatchers: TestCoroutineDispatchers + @Inject lateinit var configuration: Configuration + @Inject lateinit var testDriver: OppiaWorkManagerTestDriver + @Inject lateinit var fakePerformanceMetricsEventLogger: FakePerformanceMetricsEventLogger + @Inject lateinit var applicationLifecycleLogger: ApplicationLifecycleLogger + @Inject lateinit var networkConnectionUtil: NetworkConnectionDebugUtil + @field:[Inject BackgroundDispatcher] lateinit var backgroundDispatcher: CoroutineDispatcher + + @After + fun tearDown() { + TestPlatformParameterModule.reset() + } - @Inject - lateinit var fakePerformanceMetricsEventLogger: FakePerformanceMetricsEventLogger + /* Tests for SCHEDULE_LOG_PERIODIC_BACKGROUND_METRICS. */ - @Inject - lateinit var oppiaLogger: OppiaLogger + @Test + fun testWorker_scheduleLogPeriodicBackgroundMetrics_logsNetworkUsageMetricsInBackground() { + TestPlatformParameterModule.forceEnablePerformanceMetricsCollection(true) + setUpTestApplicationComponent() + initializeDependencies() - @Inject - lateinit var performanceMetricsController: PerformanceMetricsController + val monitor = testDriver.runOneOffWork(WORKER_NAME, SCHEDULE_LOG_PERIODIC_BACKGROUND_METRICS) - @Inject - lateinit var metricLogSchedulingWorkerFactory: MetricLogSchedulingWorkerFactory + // Verify that the job succeeded, was the only job to log something, and logged correctly. + val logCount = fakePerformanceMetricsEventLogger.getPerformanceMetricsEventListCount() + val loggedEvent = fakePerformanceMetricsEventLogger.getMostRecentPerformanceMetricsEvent() + assertThat(monitor.state).isEqualTo(WorkInfo.State.SUCCEEDED) + assertThat(logCount).isEqualTo(1) + assertThat(loggedEvent.loggableMetric.loggableMetricTypeCase).isEqualTo(NETWORK_USAGE_METRIC) + assertThat(loggedEvent.currentScreen).isEqualTo(ScreenName.BACKGROUND_SCREEN) + } - @Inject - lateinit var dataProviders: DataProviders + @Test + fun testWorker_scheduleLogPeriodicBackgroundMetrics_perfMetricsOff_succeedsAndLogsNothing() { + TestPlatformParameterModule.forceEnablePerformanceMetricsCollection(false) + setUpTestApplicationComponent() + initializeDependencies() - @Inject - lateinit var testCoroutineDispatchers: TestCoroutineDispatchers + val monitor = testDriver.runOneOffWork(WORKER_NAME, SCHEDULE_LOG_PERIODIC_BACKGROUND_METRICS) - private lateinit var context: Context + // The job should succeed but nothing will be logged since performance metrics are disabled. + val logCount = fakePerformanceMetricsEventLogger.getPerformanceMetricsEventListCount() + assertThat(monitor.state).isEqualTo(WorkInfo.State.SUCCEEDED) + assertThat(logCount).isEqualTo(0) + } - @Before - fun setUp() { + @Test + fun testWorker_scheduleLogPeriodicBackgroundMetrics_hasFailure_failsAndLogsError() { TestPlatformParameterModule.forceEnablePerformanceMetricsCollection(true) setUpTestApplicationComponent() - context = InstrumentationRegistry.getInstrumentation().targetContext - val config = Configuration.Builder() - .setExecutor(SynchronousExecutor()) - .setWorkerFactory(metricLogSchedulingWorkerFactory) - .build() - WorkManagerTestInitHelper.initializeTestWorkManager(context, config) + initializeDependencies() + fakePerformanceMetricsEventLogger.setFailure(Exception("Forced failure.")) + + val monitor = testDriver.runOneOffWork(WORKER_NAME, SCHEDULE_LOG_PERIODIC_BACKGROUND_METRICS) + + // Verify that the job failed and an error was logged due to an underlying event logger failure. + val logCount = fakePerformanceMetricsEventLogger.getPerformanceMetricsEventListCount() + val failureLine = fetchSingleWorkerErrorLog() + assertThat(monitor.state).isEqualTo(WorkInfo.State.FAILED) + assertThat(logCount).isEqualTo(0) + assertThat(failureLine).contains("Failed operation: schedule_log_periodic_background_metrics.") + assertThat(failureLine).contains("java.lang.Exception: Forced failure.") } - @After - fun tearDown() { - TestPlatformParameterModule.reset() + @Test + fun testWorker_scheduleLogPeriodicBackgroundMetrics_loseConnectivity_succeedsButUploadsNone() { + TestPlatformParameterModule.forceEnablePerformanceMetricsCollection(true) + setUpTestApplicationComponent() + initializeDependencies() + + // Run the job with connectivity disabled. This is slightly contrived because technically the + // job wouldn't start, but it has the rough effect of simulating connectivity being lost after + // the job kicks off. + forceNetworkConnectivityOff() + val monitor = testDriver.runOneOffWork(WORKER_NAME, SCHEDULE_LOG_PERIODIC_BACKGROUND_METRICS) + + // The job should success but it won't log anything because, due to lost connectivity, events + // should be cached for the next run. + val logCount = fakePerformanceMetricsEventLogger.getPerformanceMetricsEventListCount() + assertThat(monitor.state).isEqualTo(WorkInfo.State.SUCCEEDED) + assertThat(logCount).isEqualTo(0) } + // Cross-worker integration test validate a legitimate workflow. @Test - fun testWorker_enqueueRequest_verifyStorageUsagePerformanceMetricLogging() { - val workManager = WorkManager.getInstance(ApplicationProvider.getApplicationContext()) + fun testWorker_scheduleLogPeriodicBackgroundMetrics_thenRunUploadJob_uploadsLogs() { + TestPlatformParameterModule.forceEnablePerformanceMetricsCollection(true) + setUpTestApplicationComponent() + initializeDependencies() + // Run the job with connectivity disabled. This is slightly contrived because technically the + // job wouldn't start, but it has the rough effect of simulating connectivity being lost after + // the job kicks off. + forceNetworkConnectivityOff() + testDriver.runOneOffWork(WORKER_NAME, SCHEDULE_LOG_PERIODIC_BACKGROUND_METRICS) + + forceNetworkConnectivityToCellular() + testDriver.runOneOffWork(LogUploadWorker.WORKER_NAME, UPLOAD_PERFORMANCE_METRICS) + + // Running the upload worker after the previous worker cached its results should upload logs. + val logCount = fakePerformanceMetricsEventLogger.getPerformanceMetricsEventListCount() + val loggedEvent = fakePerformanceMetricsEventLogger.getMostRecentPerformanceMetricsEvent() + assertThat(logCount).isEqualTo(1) + assertThat(loggedEvent.loggableMetric.loggableMetricTypeCase).isEqualTo(NETWORK_USAGE_METRIC) + assertThat(loggedEvent.currentScreen).isEqualTo(ScreenName.BACKGROUND_SCREEN) + } - val inputData = Data.Builder().putString( - MetricLogSchedulingWorker.WORKER_CASE_KEY, - MetricLogSchedulingWorker.STORAGE_USAGE_WORKER - ).build() + /* Tests for SCHEDULE_LOG_PERIODIC_UI_METRICS. */ + + @Test + fun testWorker_scheduleLogPeriodicUiMetrics_homeScreen_logsNetworkUsageMetricsInHomeScreen() { + TestPlatformParameterModule.forceEnablePerformanceMetricsCollection(true) + setUpTestApplicationComponent() + initializeDependencies() + simulateResumeHomeActivity() - val request: OneTimeWorkRequest = OneTimeWorkRequestBuilder() - .setInputData(inputData) - .build() + val monitor = testDriver.runOneOffWork(WORKER_NAME, SCHEDULE_LOG_PERIODIC_UI_METRICS) - workManager.enqueue(request) - testCoroutineDispatchers.runCurrent() - val workInfo = workManager.getWorkInfoById(request.id) + // Verify that the job succeeded, was the only job to log something, and logged correctly. + val logCount = fakePerformanceMetricsEventLogger.getPerformanceMetricsEventListCount() val loggedEvent = fakePerformanceMetricsEventLogger.getMostRecentPerformanceMetricsEvent() - - assertThat(workInfo.get().state).isEqualTo(WorkInfo.State.SUCCEEDED) - assertThat(loggedEvent.loggableMetric.loggableMetricTypeCase).isEqualTo(STORAGE_USAGE_METRIC) + assertThat(monitor.state).isEqualTo(WorkInfo.State.SUCCEEDED) + assertThat(logCount).isEqualTo(1) + assertThat(loggedEvent.loggableMetric.loggableMetricTypeCase).isEqualTo(MEMORY_USAGE_METRIC) + assertThat(loggedEvent.currentScreen).isEqualTo(ScreenName.HOME_ACTIVITY) } @Test - fun testWorker_enqueueRequest_verifyPeriodicBackgroundPerformanceMetricsLogging() { - val workManager = WorkManager.getInstance(ApplicationProvider.getApplicationContext()) - - val inputData = Data.Builder().putString( - MetricLogSchedulingWorker.WORKER_CASE_KEY, - MetricLogSchedulingWorker.PERIODIC_BACKGROUND_METRIC_WORKER - ).build() - - val request: OneTimeWorkRequest = OneTimeWorkRequestBuilder() - .setInputData(inputData) - .build() + fun testWorker_scheduleLogPeriodicUiMetrics_homeScreen_perfMetricsOff_succeedsAndLogsNothing() { + TestPlatformParameterModule.forceEnablePerformanceMetricsCollection(false) + setUpTestApplicationComponent() + initializeDependencies() + simulateResumeHomeActivity() - workManager.enqueue(request) - testCoroutineDispatchers.runCurrent() - val workInfo = workManager.getWorkInfoById(request.id) - val loggedEvent = fakePerformanceMetricsEventLogger.getMostRecentPerformanceMetricsEvent() + val monitor = testDriver.runOneOffWork(WORKER_NAME, SCHEDULE_LOG_PERIODIC_UI_METRICS) - assertThat(workInfo.get().state).isEqualTo(WorkInfo.State.SUCCEEDED) - assertThat(loggedEvent.loggableMetric.loggableMetricTypeCase).isEqualTo(NETWORK_USAGE_METRIC) + // The job should succeed but nothing will be logged since performance metrics are disabled. + val logCount = fakePerformanceMetricsEventLogger.getPerformanceMetricsEventListCount() + assertThat(monitor.state).isEqualTo(WorkInfo.State.SUCCEEDED) + assertThat(logCount).isEqualTo(0) } @Test - fun testWorker_enqueueRequest_verifyPeriodicUiPerformanceMetricsLogging() { - val workManager = WorkManager.getInstance(ApplicationProvider.getApplicationContext()) - - val inputData = Data.Builder().putString( - MetricLogSchedulingWorker.WORKER_CASE_KEY, - MetricLogSchedulingWorker.PERIODIC_UI_METRIC_WORKER - ).build() + fun testWorker_scheduleLogPeriodicUiMetrics_homeScreen_hasFailure_failsAndLogsError() { + TestPlatformParameterModule.forceEnablePerformanceMetricsCollection(true) + setUpTestApplicationComponent() + initializeDependencies() + simulateResumeHomeActivity() + fakePerformanceMetricsEventLogger.setFailure(Exception("Forced failure.")) + + val monitor = testDriver.runOneOffWork(WORKER_NAME, SCHEDULE_LOG_PERIODIC_UI_METRICS) + + // Verify that the job failed and an error was logged due to an underlying event logger failure. + val logCount = fakePerformanceMetricsEventLogger.getPerformanceMetricsEventListCount() + val failureLine = fetchSingleWorkerErrorLog() + assertThat(monitor.state).isEqualTo(WorkInfo.State.FAILED) + assertThat(logCount).isEqualTo(0) + assertThat(failureLine).contains("Failed operation: schedule_log_periodic_ui_metrics.") + assertThat(failureLine).contains("java.lang.Exception: Forced failure.") + } - val request: OneTimeWorkRequest = OneTimeWorkRequestBuilder() - .setInputData(inputData) - .build() + @Test + fun testWorker_scheduleLogPeriodicUiMetrics_homeScreen_loseConnectivity_succeedsButUploadsNone() { + TestPlatformParameterModule.forceEnablePerformanceMetricsCollection(true) + setUpTestApplicationComponent() + initializeDependencies() + simulateResumeHomeActivity() + + // Run the job with connectivity disabled. This is slightly contrived because technically the + // job wouldn't start, but it has the rough effect of simulating connectivity being lost after + // the job kicks off. + forceNetworkConnectivityOff() + val monitor = testDriver.runOneOffWork(WORKER_NAME, SCHEDULE_LOG_PERIODIC_UI_METRICS) + + // The job should success but it won't log anything because, due to lost connectivity, events + // should be cached for the next run. + val logCount = fakePerformanceMetricsEventLogger.getPerformanceMetricsEventListCount() + assertThat(monitor.state).isEqualTo(WorkInfo.State.SUCCEEDED) + assertThat(logCount).isEqualTo(0) + } - workManager.enqueue(request) - testCoroutineDispatchers.runCurrent() - val workInfo = workManager.getWorkInfoById(request.id) + // Cross-worker integration test validate a legitimate workflow. + @Test + fun testWorker_scheduleLogPeriodicUiMetrics_loseConnectivity_thenRunUploadJob_uploadsLogs() { + TestPlatformParameterModule.forceEnablePerformanceMetricsCollection(true) + setUpTestApplicationComponent() + initializeDependencies() + simulateResumeHomeActivity() + // Run the job with connectivity disabled. This is slightly contrived because technically the + // job wouldn't start, but it has the rough effect of simulating connectivity being lost after + // the job kicks off. + forceNetworkConnectivityOff() + testDriver.runOneOffWork(WORKER_NAME, SCHEDULE_LOG_PERIODIC_UI_METRICS) + + forceNetworkConnectivityToCellular() + testDriver.runOneOffWork(LogUploadWorker.WORKER_NAME, UPLOAD_PERFORMANCE_METRICS) + + // Running the upload worker after the previous worker cached its results should upload logs. + val logCount = fakePerformanceMetricsEventLogger.getPerformanceMetricsEventListCount() val loggedEvent = fakePerformanceMetricsEventLogger.getMostRecentPerformanceMetricsEvent() - - assertThat(workInfo.get().state).isEqualTo(WorkInfo.State.SUCCEEDED) + assertThat(logCount).isEqualTo(1) assertThat(loggedEvent.loggableMetric.loggableMetricTypeCase).isEqualTo(MEMORY_USAGE_METRIC) + assertThat(loggedEvent.currentScreen).isEqualTo(ScreenName.HOME_ACTIVITY) } - @Test - fun testWorker_enqueueRequest_writeFails_verifyFailure() { - val workManager = WorkManager.getInstance(ApplicationProvider.getApplicationContext()) + /* Tests for SCHEDULE_LOG_STORAGE_USAGE_METRICS. */ - val request: OneTimeWorkRequest = OneTimeWorkRequestBuilder() - .build() + @Test + fun testWorker_scheduleLogStorageUsageMetrics_logsStorageUsageMetricsInBackground() { + TestPlatformParameterModule.forceEnablePerformanceMetricsCollection(true) + setUpTestApplicationComponent() + initializeDependencies() - workManager.enqueue(request) - testCoroutineDispatchers.runCurrent() - val workInfo = workManager.getWorkInfoById(request.id) + val monitor = testDriver.runOneOffWork(WORKER_NAME, SCHEDULE_LOG_STORAGE_USAGE_METRICS) - assertThat(workInfo.get().state).isEqualTo(WorkInfo.State.FAILED) - assertThat(fakePerformanceMetricsEventLogger.noPerformanceMetricsEventsPresent()).isTrue() + // Verify that the job succeeded, was the only job to log something, and logged correctly. + val logCount = fakePerformanceMetricsEventLogger.getPerformanceMetricsEventListCount() + val loggedEvent = fakePerformanceMetricsEventLogger.getMostRecentPerformanceMetricsEvent() + assertThat(monitor.state).isEqualTo(WorkInfo.State.SUCCEEDED) + assertThat(logCount).isEqualTo(1) + assertThat(loggedEvent.loggableMetric.loggableMetricTypeCase).isEqualTo(STORAGE_USAGE_METRIC) + assertThat(loggedEvent.currentScreen).isEqualTo(ScreenName.BACKGROUND_SCREEN) } @Test - fun testScheduler_enqueueRequestForIncorrectWorkerCase_verifyWorkRequestReturnsFailureResult() { - val workManager = WorkManager.getInstance(ApplicationProvider.getApplicationContext()) - - val inputData: Data = Data.Builder() - .putString( - MetricLogSchedulingWorker.WORKER_CASE_KEY, - INCORRECT_WORKER_CASE - ).build() + fun testWorker_scheduleLogStorageUsageMetrics_perfMetricsOff_succeedsAndLogsNothing() { + TestPlatformParameterModule.forceEnablePerformanceMetricsCollection(false) + setUpTestApplicationComponent() + initializeDependencies() - val request = OneTimeWorkRequestBuilder() - .setInputData(inputData) - .build() + val monitor = testDriver.runOneOffWork(WORKER_NAME, SCHEDULE_LOG_STORAGE_USAGE_METRICS) - workManager.enqueue(request) - testCoroutineDispatchers.runCurrent() - val workInfo = workManager.getWorkInfoById(request.id) + // The job should succeed but nothing will be logged since performance metrics are disabled. + val logCount = fakePerformanceMetricsEventLogger.getPerformanceMetricsEventListCount() + assertThat(monitor.state).isEqualTo(WorkInfo.State.SUCCEEDED) + assertThat(logCount).isEqualTo(0) + } - assertThat(workInfo.get().state).isEqualTo(WorkInfo.State.FAILED) + @Test + fun testWorker_scheduleLogStorageUsageMetrics_hasFailure_failsAndLogsError() { + TestPlatformParameterModule.forceEnablePerformanceMetricsCollection(true) + setUpTestApplicationComponent() + initializeDependencies() + fakePerformanceMetricsEventLogger.setFailure(Exception("Forced failure.")) + + val monitor = testDriver.runOneOffWork(WORKER_NAME, SCHEDULE_LOG_STORAGE_USAGE_METRICS) + + // Verify that the job failed and an error was logged due to an underlying event logger failure. + val logCount = fakePerformanceMetricsEventLogger.getPerformanceMetricsEventListCount() + val failureLine = fetchSingleWorkerErrorLog() + assertThat(monitor.state).isEqualTo(WorkInfo.State.FAILED) + assertThat(logCount).isEqualTo(0) + assertThat(failureLine).contains("Failed operation: schedule_log_storage_usage_metrics.") + assertThat(failureLine).contains("java.lang.Exception: Forced failure.") } - private fun setUpTestApplicationComponent() { - DaggerMetricLogSchedulingWorkerTest_TestApplicationComponent.builder() - .setApplication(ApplicationProvider.getApplicationContext()) - .build() - .inject(this) + @Test + fun testWorker_scheduleLogStorageUsageMetrics_loseConnectivity_succeedsButUploadsNone() { + TestPlatformParameterModule.forceEnablePerformanceMetricsCollection(true) + setUpTestApplicationComponent() + initializeDependencies() + + // Run the job with connectivity disabled. This is slightly contrived because technically the + // job wouldn't start, but it has the rough effect of simulating connectivity being lost after + // the job kicks off. + forceNetworkConnectivityOff() + val monitor = testDriver.runOneOffWork(WORKER_NAME, SCHEDULE_LOG_STORAGE_USAGE_METRICS) + + // The job should success but it won't log anything because, due to lost connectivity, events + // should be cached for the next run. + val logCount = fakePerformanceMetricsEventLogger.getPerformanceMetricsEventListCount() + assertThat(monitor.state).isEqualTo(WorkInfo.State.SUCCEEDED) + assertThat(logCount).isEqualTo(0) } - // TODO(#89): Move this to a common test application component. - @Module - class TestModule { - @Provides - fun provideContext(application: Application): Context = application + // Cross-worker integration test validate a legitimate workflow. + @Test + fun testWorker_scheduleLogStorageUsageMetrics_thenRunUploadJob_uploadsLogs() { + TestPlatformParameterModule.forceEnablePerformanceMetricsCollection(true) + setUpTestApplicationComponent() + initializeDependencies() + // Run the job with connectivity disabled. This is slightly contrived because technically the + // job wouldn't start, but it has the rough effect of simulating connectivity being lost after + // the job kicks off. + forceNetworkConnectivityOff() + testDriver.runOneOffWork(WORKER_NAME, SCHEDULE_LOG_STORAGE_USAGE_METRICS) + + forceNetworkConnectivityToCellular() + testDriver.runOneOffWork(LogUploadWorker.WORKER_NAME, UPLOAD_PERFORMANCE_METRICS) + + // Running the upload worker after the previous worker cached its results should upload logs. + val logCount = fakePerformanceMetricsEventLogger.getPerformanceMetricsEventListCount() + val loggedEvent = fakePerformanceMetricsEventLogger.getMostRecentPerformanceMetricsEvent() + assertThat(logCount).isEqualTo(1) + assertThat(loggedEvent.loggableMetric.loggableMetricTypeCase).isEqualTo(STORAGE_USAGE_METRIC) + assertThat(loggedEvent.currentScreen).isEqualTo(ScreenName.BACKGROUND_SCREEN) + } - @Provides - fun bindFakeEventLogger(fakeAnalyticsEventLogger: FakeAnalyticsEventLogger): - AnalyticsEventLogger = fakeAnalyticsEventLogger + private fun simulateResumeHomeActivity() { + // Simulate being on a specific screen (though clear any logs from the lifecycle logger to avoid + // interfering with validating the worker). + applicationLifecycleLogger.recordActivityResumed(ScreenName.HOME_ACTIVITY, 0L, 0L) + fakePerformanceMetricsEventLogger.clearAllPerformanceMetricsEvents() + } - @Provides - fun bindFakeExceptionLogger(fakeLogger: FakeExceptionLogger): ExceptionLogger = fakeLogger + private fun forceNetworkConnectivityOff() { + networkConnectionUtil.setCurrentConnectionStatus(NONE) + } - @Provides - fun bindFakePerformanceMetricsEventLogger( - fakePerformanceMetricsEventLogger: FakePerformanceMetricsEventLogger - ): PerformanceMetricsEventLogger = fakePerformanceMetricsEventLogger + private fun forceNetworkConnectivityToCellular() { + networkConnectionUtil.setCurrentConnectionStatus(CELLULAR) } - @Module - class TestLogStorageModule { + private fun initializeDependencies() { + FirebaseApp.initializeApp(context) + testDriver.initializeWorkManager(configuration) + } - @Provides - @EventLogStorageCacheSize - fun provideEventLogStorageCacheSize(): Int = 2 + private fun setUpTestApplicationComponent() { + ApplicationProvider.getApplicationContext().inject(this) + } - @Provides - @ExceptionLogStorageCacheSize - fun provideExceptionLogStorageSize(): Int = 2 + private fun fetchSingleWorkerErrorLog(): String { + val errorLogs = fetchWorkerErrorLogs() + assertThat(errorLogs).hasSize(1) + return errorLogs.single() + } - @Provides - @PerformanceMetricsLogStorageCacheSize - fun providePerformanceMetricsLogStorageCacheSize(): Int = 2 + private fun fetchWorkerErrorLogs(): List { + // Extract all logs from the bootstrap worker and validate they are each errors before returning + // the logged message lines. + return ShadowLog.getLogs().filter { it.tag == WORKER_NAME }.onEach { + assertThat(it.type).isEqualTo(Log.ERROR) + }.map(ShadowLog.LogItem::msg) } + // TODO(#89): Move this to a common test application component. @Module - interface TestFirebaseLogUploaderModule { - - @Binds - fun bindsFakeLogUploader(fakeLogUploader: FakeLogUploader): LogUploader + class TestModule { + @Provides + fun provideContext(application: Application): Context = application } // TODO(#89): Move this to a common test application component. @@ -275,23 +424,26 @@ class MetricLogSchedulingWorkerTest { CpuPerformanceSnapshotterModule::class, FakeOppiaClockModule::class, LocaleProdModule::class, - LogReportWorkerModule::class, LoggerModule::class, LoggingIdentifierModule::class, NetworkConnectionUtilDebugModule::class, - PerformanceMetricsAssessorModule::class, PerformanceMetricsConfigurationsModule::class, PlatformParameterSingletonModule::class, RobolectricModule::class, SyncStatusTestModule::class, TestDispatcherModule::class, - TestFirebaseLogUploaderModule::class, - TestLogStorageModule::class, TestModule::class, - TestPlatformParameterModule::class + TestAuthenticationModule::class, + LogStorageModule::class, + TestLogReportingModule::class, + WorkManagerConfigurationModule::class, + TestPlatformParameterModule::class, + MetricLogSchedulerModule::class, + LogReportWorkerModule::class ] ) - interface TestApplicationComponent : DataProvidersInjector { + interface TestApplicationComponent : + DataProvidersInjector, DispatcherInjector, PlatformParameterControllerInjector { @Component.Builder interface Builder { @BindsInstance @@ -302,7 +454,11 @@ class MetricLogSchedulingWorkerTest { fun inject(metricLogSchedulingWorkerTest: MetricLogSchedulingWorkerTest) } - class TestApplication : Application(), DataProvidersInjectorProvider { + class TestApplication : + Application(), + DataProvidersInjectorProvider, + DispatcherInjectorProvider, + PlatformParameterControllerInjectorProvider { private val component: TestApplicationComponent by lazy { DaggerMetricLogSchedulingWorkerTest_TestApplicationComponent.builder() .setApplication(this) @@ -313,6 +469,8 @@ class MetricLogSchedulingWorkerTest { component.inject(metricLogSchedulingWorkerTest) } - override fun getDataProvidersInjector(): DataProvidersInjector = component + override fun getDataProvidersInjector() = component + override fun getDispatcherInjector() = component + override fun getPlatformParameterControllerInjector() = component } } diff --git a/domain/src/test/java/org/oppia/android/domain/oppialogger/logscheduler/PerformanceMetricsLogSchedulerTest.kt b/domain/src/test/java/org/oppia/android/domain/oppialogger/logscheduler/PerformanceMetricsLogSchedulerTest.kt deleted file mode 100644 index 96815794e31..00000000000 --- a/domain/src/test/java/org/oppia/android/domain/oppialogger/logscheduler/PerformanceMetricsLogSchedulerTest.kt +++ /dev/null @@ -1,221 +0,0 @@ -package org.oppia.android.domain.oppialogger.logscheduler - -import android.app.Application -import android.content.Context -import androidx.test.core.app.ApplicationProvider -import androidx.test.ext.junit.runners.AndroidJUnit4 -import androidx.work.Configuration -import androidx.work.Data -import androidx.work.PeriodicWorkRequest -import androidx.work.WorkInfo -import androidx.work.WorkManager -import androidx.work.testing.SynchronousExecutor -import androidx.work.testing.WorkManagerTestInitHelper -import com.google.common.truth.Truth.assertThat -import dagger.Binds -import dagger.BindsInstance -import dagger.Component -import dagger.Module -import org.junit.Before -import org.junit.Test -import org.junit.runner.RunWith -import org.oppia.android.domain.oppialogger.LogStorageModule -import org.oppia.android.domain.oppialogger.LoggingIdentifierModule -import org.oppia.android.domain.oppialogger.analytics.ApplicationLifecycleModule -import org.oppia.android.domain.oppialogger.analytics.CpuPerformanceSnapshotterModule -import org.oppia.android.domain.oppialogger.loguploader.LogReportWorkerModule -import org.oppia.android.domain.platformparameter.PlatformParameterSingletonModule -import org.oppia.android.testing.TestLogReportingModule -import org.oppia.android.testing.logging.SyncStatusTestModule -import org.oppia.android.testing.platformparameter.TestPlatformParameterModule -import org.oppia.android.testing.robolectric.RobolectricModule -import org.oppia.android.testing.threading.TestCoroutineDispatchers -import org.oppia.android.testing.threading.TestDispatcherModule -import org.oppia.android.testing.time.FakeOppiaClockModule -import org.oppia.android.util.caching.AssetModule -import org.oppia.android.util.data.DataProvidersInjector -import org.oppia.android.util.data.DataProvidersInjectorProvider -import org.oppia.android.util.locale.LocaleProdModule -import org.oppia.android.util.logging.LoggerModule -import org.oppia.android.util.logging.performancemetrics.PerformanceMetricsConfigurationsModule -import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule -import org.robolectric.annotation.Config -import org.robolectric.annotation.LooperMode -import java.util.concurrent.TimeUnit -import javax.inject.Inject -import javax.inject.Singleton - -/** Tests for [PerformanceMetricsLogScheduler]. */ -// FunctionName: test names are conventionally named with underscores. -@Suppress("FunctionName") -@RunWith(AndroidJUnit4::class) -@LooperMode(LooperMode.Mode.PAUSED) -@Config(application = PerformanceMetricsLogSchedulerTest.TestApplication::class) -class PerformanceMetricsLogSchedulerTest { - - @Inject - lateinit var performanceMetricsLogScheduler: PerformanceMetricsLogScheduler - - @Inject - lateinit var metricLogSchedulingWorkerFactory: MetricLogSchedulingWorkerFactory - - @Inject - lateinit var testCoroutineDispatchers: TestCoroutineDispatchers - - @Inject - lateinit var context: Context - - private lateinit var workManager: WorkManager - - private val workerCaseForSchedulingPeriodicBackgroundMetricLogs: Data = Data.Builder() - .putString( - MetricLogSchedulingWorker.WORKER_CASE_KEY, - MetricLogSchedulingWorker.PERIODIC_BACKGROUND_METRIC_WORKER - ) - .build() - - private val workerCaseForSchedulingStorageUsageMetricLogs: Data = Data.Builder() - .putString( - MetricLogSchedulingWorker.WORKER_CASE_KEY, - MetricLogSchedulingWorker.STORAGE_USAGE_WORKER - ) - .build() - - private val workerCaseForSchedulingPeriodicUiMetricLogs: Data = Data.Builder() - .putString( - MetricLogSchedulingWorker.WORKER_CASE_KEY, - MetricLogSchedulingWorker.PERIODIC_UI_METRIC_WORKER - ) - .build() - - @Before - fun setUp() { - setUpTestApplicationComponent() - val config = Configuration.Builder() - .setExecutor(SynchronousExecutor()) - .setWorkerFactory(metricLogSchedulingWorkerFactory) - .build() - WorkManagerTestInitHelper.initializeTestWorkManager(context, config) - } - - @Test - fun testScheduler_enqueueRequestForPeriodicBackgroundMetrics_workRequestGetsEnqueued() { - val workManager = WorkManager.getInstance(ApplicationProvider.getApplicationContext()) - val request = PeriodicWorkRequest - .Builder(MetricLogSchedulingWorker::class.java, 15, TimeUnit.MINUTES) - .setInputData(workerCaseForSchedulingPeriodicBackgroundMetricLogs) - .build() - - performanceMetricsLogScheduler.enqueueWorkRequestForPeriodicBackgroundMetrics( - workManager, - request - ) - testCoroutineDispatchers.runCurrent() - val workInfo = workManager.getWorkInfoById(request.id) - - assertThat(workInfo.get().state).isEqualTo(WorkInfo.State.ENQUEUED) - } - - @Test - fun testScheduler_enqueueRequestForPeriodicUiMetric_workRequestGetsEnqueued() { - val workManager = WorkManager.getInstance(ApplicationProvider.getApplicationContext()) - - val request = PeriodicWorkRequest - .Builder(MetricLogSchedulingWorker::class.java, 15, TimeUnit.MINUTES) - .setInputData(workerCaseForSchedulingPeriodicUiMetricLogs) - .build() - - performanceMetricsLogScheduler.enqueueWorkRequestForPeriodicUiMetrics( - workManager, - request - ) - testCoroutineDispatchers.runCurrent() - val workInfo = workManager.getWorkInfoById(request.id) - - assertThat(workInfo.get().state).isEqualTo(WorkInfo.State.ENQUEUED) - } - - @Test - fun testScheduler_enqueueRequestForStorageMetric_workRequestGetsEnqueued() { - val workManager = WorkManager.getInstance(ApplicationProvider.getApplicationContext()) - - val request = PeriodicWorkRequest - .Builder(MetricLogSchedulingWorker::class.java, 15, TimeUnit.MINUTES) - .setInputData(workerCaseForSchedulingStorageUsageMetricLogs) - .build() - - performanceMetricsLogScheduler.enqueueWorkRequestForStorageUsage( - workManager, - request - ) - testCoroutineDispatchers.runCurrent() - val workInfo = workManager.getWorkInfoById(request.id) - - assertThat(workInfo.get().state).isEqualTo(WorkInfo.State.ENQUEUED) - } - - private fun setUpTestApplicationComponent() { - DaggerPerformanceMetricsLogSchedulerTest_TestApplicationComponent.builder() - .setApplication(ApplicationProvider.getApplicationContext()) - .build() - .inject(this) - } - - // TODO(#89): Move this to a common test application component. - @Module - interface TestModule { - @Binds - fun provideContext(application: Application): Context - } - - // TODO(#89): Move this to a common test application component. - @Singleton - @Component( - modules = [ - ApplicationLifecycleModule::class, - AssetModule::class, - CpuPerformanceSnapshotterModule::class, - FakeOppiaClockModule::class, - LocaleProdModule::class, - LogReportWorkerModule::class, - LogStorageModule::class, - LoggerModule::class, - LoggingIdentifierModule::class, - MetricLogSchedulerModule::class, - NetworkConnectionUtilDebugModule::class, - PerformanceMetricsConfigurationsModule::class, - PlatformParameterSingletonModule::class, - RobolectricModule::class, - SyncStatusTestModule::class, - TestDispatcherModule::class, - TestLogReportingModule::class, - TestModule::class, - TestPlatformParameterModule::class - ] - ) - interface TestApplicationComponent : DataProvidersInjector { - @Component.Builder - interface Builder { - @BindsInstance - fun setApplication(application: Application): Builder - - fun build(): TestApplicationComponent - } - - fun inject(performanceMetricsLogSchedulerTest: PerformanceMetricsLogSchedulerTest) - } - - class TestApplication : Application(), DataProvidersInjectorProvider { - private val component: TestApplicationComponent by lazy { - DaggerPerformanceMetricsLogSchedulerTest_TestApplicationComponent.builder() - .setApplication(this) - .build() - } - - fun inject(performanceMetricsLogSchedulerTest: PerformanceMetricsLogSchedulerTest) { - component.inject(performanceMetricsLogSchedulerTest) - } - - override fun getDataProvidersInjector(): DataProvidersInjector = component - } -} diff --git a/domain/src/test/java/org/oppia/android/domain/oppialogger/loguploader/BUILD.bazel b/domain/src/test/java/org/oppia/android/domain/oppialogger/loguploader/BUILD.bazel index 23ec337da67..879c675dfd1 100644 --- a/domain/src/test/java/org/oppia/android/domain/oppialogger/loguploader/BUILD.bazel +++ b/domain/src/test/java/org/oppia/android/domain/oppialogger/loguploader/BUILD.bazel @@ -5,35 +5,77 @@ Tests for app-wide log uploading functionality. load("//:oppia_android_test.bzl", "oppia_android_test") oppia_android_test( - name = "LogReportWorkManagerInitializerTest", - srcs = ["LogReportWorkManagerInitializerTest.kt"], + name = "LogReportWorkerModuleTest", + srcs = ["LogReportWorkerModuleTest.kt"], custom_package = "org.oppia.android.domain.oppialogger.loguploader", - test_class = "org.oppia.android.domain.oppialogger.loguploader.LogReportWorkManagerInitializerTest", + test_class = "org.oppia.android.domain.oppialogger.loguploader.LogReportWorkerModuleTest", + test_manifest = "//domain:test_manifest", + deps = [ + "//:dagger", + "//domain/src/main/java/org/oppia/android/domain/oppialogger:prod_module", + "//domain/src/main/java/org/oppia/android/domain/oppialogger/analytics:cpu_module", + "//domain/src/main/java/org/oppia/android/domain/oppialogger/analytics:prod_module", + "//domain/src/main/java/org/oppia/android/domain/oppialogger/loguploader:worker_module", + "//domain/src/main/java/org/oppia/android/domain/platformparameter:prod_module", + "//testing", + "//testing/src/main/java/org/oppia/android/testing/firebase:test_authentication_module", + "//testing/src/main/java/org/oppia/android/testing/logging:sync_status_test_module", + "//testing/src/main/java/org/oppia/android/testing/platformparameter:test_module", + "//testing/src/main/java/org/oppia/android/testing/robolectric:test_module", + "//testing/src/main/java/org/oppia/android/testing/threading:test_module", + "//testing/src/main/java/org/oppia/android/testing/time:test_module", + "//third_party:androidx_test_ext_junit", + "//third_party:com_google_truth_truth", + "//third_party:junit_junit", + "//third_party:org_robolectric_robolectric", + "//third_party:robolectric_android-all", + "//utility/src/main/java/org/oppia/android/util/caching:asset_prod_module", + "//utility/src/main/java/org/oppia/android/util/locale:prod_module", + "//utility/src/main/java/org/oppia/android/util/logging:prod_module", + "//utility/src/main/java/org/oppia/android/util/logging/firebase:debug_module", + "//utility/src/main/java/org/oppia/android/util/logging/performancemetrics:performance_metrics_configurations_module", + "//utility/src/main/java/org/oppia/android/util/networking:debug_module", + ], +) + +oppia_android_test( + name = "LogReportWorkerSchedulerTest", + srcs = ["LogReportWorkerSchedulerTest.kt"], + custom_package = "org.oppia.android.domain.oppialogger.loguploader", + test_class = "org.oppia.android.domain.oppialogger.loguploader.LogReportWorkerSchedulerTest", test_manifest = "//domain:test_manifest", deps = [ "//:dagger", "//domain", + "//domain/src/main/java/org/oppia/android/domain/oppialogger:prod_module", "//domain/src/main/java/org/oppia/android/domain/oppialogger/analytics:cpu_module", "//domain/src/main/java/org/oppia/android/domain/oppialogger/analytics:prod_module", - "//domain/src/main/java/org/oppia/android/domain/oppialogger/analytics/testing:fake_log_scheduler", - "//domain/src/main/java/org/oppia/android/domain/oppialogger/loguploader:initializer", + "//domain/src/main/java/org/oppia/android/domain/oppialogger/loguploader:scheduler", "//domain/src/main/java/org/oppia/android/domain/oppialogger/loguploader:worker", "//domain/src/main/java/org/oppia/android/domain/oppialogger/loguploader:worker_module", - "//domain/src/main/java/org/oppia/android/domain/testing/oppialogger/loguploader:fake_log_uploader", + "//domain/src/main/java/org/oppia/android/domain/platformparameter:prod_module", + "//domain/src/main/java/org/oppia/android/domain/workmanager:oppia_worker", + "//domain/src/main/java/org/oppia/android/domain/workmanager:work_manager_scheduler", + "//domain/src/main/java/org/oppia/android/domain/workmanager/testing:oppia_work_manager_test_driver", "//testing", + "//testing/src/main/java/org/oppia/android/testing/firebase:test_authentication_module", + "//testing/src/main/java/org/oppia/android/testing/logging:sync_status_test_module", "//testing/src/main/java/org/oppia/android/testing/platformparameter:test_module", "//testing/src/main/java/org/oppia/android/testing/robolectric:test_module", + "//testing/src/main/java/org/oppia/android/testing/threading:test_coroutine_dispatchers", "//testing/src/main/java/org/oppia/android/testing/threading:test_module", "//testing/src/main/java/org/oppia/android/testing/time:test_module", "//third_party:androidx_test_ext_junit", - "//third_party:androidx_work_work-testing", + "//third_party:androidx_work_work-runtime", "//third_party:com_google_truth_truth", "//third_party:junit_junit", "//third_party:org_robolectric_robolectric", "//third_party:robolectric_android-all", "//utility/src/main/java/org/oppia/android/util/caching:asset_prod_module", "//utility/src/main/java/org/oppia/android/util/locale:prod_module", - "//utility/src/main/java/org/oppia/android/util/logging:metric_log_scheduler", + "//utility/src/main/java/org/oppia/android/util/logging:prod_module", + "//utility/src/main/java/org/oppia/android/util/logging/firebase:debug_module", + "//utility/src/main/java/org/oppia/android/util/logging/performancemetrics:performance_metrics_configurations_module", "//utility/src/main/java/org/oppia/android/util/networking:debug_module", "//utility/src/main/java/org/oppia/android/util/networking:network_connection_debug_util", ], @@ -48,26 +90,36 @@ oppia_android_test( deps = [ "//:dagger", "//domain", + "//domain/src/main/java/org/oppia/android/domain/oppialogger:prod_module", + "//domain/src/main/java/org/oppia/android/domain/oppialogger/analytics:cpu_module", "//domain/src/main/java/org/oppia/android/domain/oppialogger/analytics:prod_module", "//domain/src/main/java/org/oppia/android/domain/oppialogger/loguploader:worker", "//domain/src/main/java/org/oppia/android/domain/oppialogger/loguploader:worker_module", - "//domain/src/main/java/org/oppia/android/domain/testing/oppialogger/loguploader:fake_log_uploader", + "//domain/src/main/java/org/oppia/android/domain/platformparameter:prod_module", + "//domain/src/main/java/org/oppia/android/domain/workmanager:oppia_worker", + "//domain/src/main/java/org/oppia/android/domain/workmanager:work_manager_configuration_module", + "//domain/src/main/java/org/oppia/android/domain/workmanager:work_manager_scheduler", + "//domain/src/main/java/org/oppia/android/domain/workmanager/testing:oppia_work_manager_test_driver", "//testing", + "//testing/src/main/java/org/oppia/android/testing/firebase:test_authentication_module", + "//testing/src/main/java/org/oppia/android/testing/logging:event_log_subject", "//testing/src/main/java/org/oppia/android/testing/logging:sync_status_test_module", "//testing/src/main/java/org/oppia/android/testing/logging:test_sync_status_manager", "//testing/src/main/java/org/oppia/android/testing/platformparameter:test_module", "//testing/src/main/java/org/oppia/android/testing/robolectric:test_module", + "//testing/src/main/java/org/oppia/android/testing/threading:test_coroutine_dispatchers", "//testing/src/main/java/org/oppia/android/testing/threading:test_module", "//testing/src/main/java/org/oppia/android/testing/time:test_module", "//third_party:androidx_test_ext_junit", - "//third_party:androidx_work_work-testing", + "//third_party:androidx_work_work-runtime", "//third_party:com_google_truth_truth", "//third_party:junit_junit", "//third_party:org_robolectric_robolectric", "//third_party:robolectric_android-all", "//utility/src/main/java/org/oppia/android/util/caching:asset_prod_module", "//utility/src/main/java/org/oppia/android/util/locale:prod_module", - "//utility/src/main/java/org/oppia/android/util/logging/performancemetrics:performance_metrics_assessor_module", + "//utility/src/main/java/org/oppia/android/util/logging:prod_module", + "//utility/src/main/java/org/oppia/android/util/logging/firebase:debug_module", "//utility/src/main/java/org/oppia/android/util/logging/performancemetrics:performance_metrics_configurations_module", "//utility/src/main/java/org/oppia/android/util/networking:debug_module", "//utility/src/main/java/org/oppia/android/util/networking:network_connection_debug_util", diff --git a/domain/src/test/java/org/oppia/android/domain/oppialogger/loguploader/LogReportWorkManagerInitializerTest.kt b/domain/src/test/java/org/oppia/android/domain/oppialogger/loguploader/LogReportWorkManagerInitializerTest.kt deleted file mode 100644 index ef92acaf13c..00000000000 --- a/domain/src/test/java/org/oppia/android/domain/oppialogger/loguploader/LogReportWorkManagerInitializerTest.kt +++ /dev/null @@ -1,365 +0,0 @@ -package org.oppia.android.domain.oppialogger.loguploader - -import android.app.Application -import android.content.Context -import androidx.test.core.app.ApplicationProvider -import androidx.test.ext.junit.runners.AndroidJUnit4 -import androidx.test.platform.app.InstrumentationRegistry -import androidx.work.Configuration -import androidx.work.Constraints -import androidx.work.Data -import androidx.work.DelegatingWorkerFactory -import androidx.work.NetworkType -import androidx.work.WorkManager -import androidx.work.testing.SynchronousExecutor -import androidx.work.testing.WorkManagerTestInitHelper -import com.google.common.truth.Truth.assertThat -import dagger.Binds -import dagger.BindsInstance -import dagger.Component -import dagger.Module -import dagger.Provides -import org.junit.Before -import org.junit.Test -import org.junit.runner.RunWith -import org.oppia.android.domain.oppialogger.EventLogStorageCacheSize -import org.oppia.android.domain.oppialogger.ExceptionLogStorageCacheSize -import org.oppia.android.domain.oppialogger.FirestoreLogStorageCacheSize -import org.oppia.android.domain.oppialogger.LoggingIdentifierModule -import org.oppia.android.domain.oppialogger.OppiaLogger -import org.oppia.android.domain.oppialogger.PerformanceMetricsLogStorageCacheSize -import org.oppia.android.domain.oppialogger.analytics.ApplicationLifecycleModule -import org.oppia.android.domain.oppialogger.analytics.CpuPerformanceSnapshotterModule -import org.oppia.android.domain.oppialogger.analytics.testing.FakeLogScheduler -import org.oppia.android.domain.oppialogger.exceptions.ExceptionsController -import org.oppia.android.domain.oppialogger.logscheduler.MetricLogSchedulingWorker -import org.oppia.android.domain.oppialogger.logscheduler.MetricLogSchedulingWorkerFactory -import org.oppia.android.domain.platformparameter.PlatformParameterSingletonModule -import org.oppia.android.domain.testing.oppialogger.loguploader.FakeLogUploader -import org.oppia.android.testing.FakeExceptionLogger -import org.oppia.android.testing.TestLogReportingModule -import org.oppia.android.testing.firebase.TestAuthenticationModule -import org.oppia.android.testing.platformparameter.TestPlatformParameterModule -import org.oppia.android.testing.robolectric.RobolectricModule -import org.oppia.android.testing.threading.TestCoroutineDispatchers -import org.oppia.android.testing.threading.TestDispatcherModule -import org.oppia.android.testing.time.FakeOppiaClockModule -import org.oppia.android.util.caching.AssetModule -import org.oppia.android.util.data.DataProviders -import org.oppia.android.util.data.DataProvidersInjector -import org.oppia.android.util.data.DataProvidersInjectorProvider -import org.oppia.android.util.locale.LocaleProdModule -import org.oppia.android.util.logging.LogUploader -import org.oppia.android.util.logging.LoggerModule -import org.oppia.android.util.logging.MetricLogScheduler -import org.oppia.android.util.logging.SyncStatusModule -import org.oppia.android.util.networking.NetworkConnectionDebugUtil -import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule -import org.robolectric.annotation.Config -import org.robolectric.annotation.LooperMode -import javax.inject.Inject -import javax.inject.Singleton - -@RunWith(AndroidJUnit4::class) -@LooperMode(LooperMode.Mode.PAUSED) -@Config(application = LogReportWorkManagerInitializerTest.TestApplication::class) -class LogReportWorkManagerInitializerTest { - - @Inject - lateinit var logUploadWorkerFactory: LogUploadWorkerFactory - - @Inject - lateinit var metricLogSchedulingWorkerFactory: MetricLogSchedulingWorkerFactory - - @Inject - lateinit var logReportWorkManagerInitializer: LogReportWorkManagerInitializer - - @Inject - lateinit var exceptionsController: ExceptionsController - - @Inject - lateinit var networkConnectionUtil: NetworkConnectionDebugUtil - - @Inject - lateinit var fakeExceptionLogger: FakeExceptionLogger - - @Inject - lateinit var dataProviders: DataProviders - - @Inject - lateinit var oppiaLogger: OppiaLogger - - @Inject - lateinit var fakeLogUploader: FakeLogUploader - - @Inject - lateinit var fakeLogScheduler: FakeLogScheduler - - @Inject - lateinit var testCoroutineDispatchers: TestCoroutineDispatchers - - private lateinit var context: Context - - @Before - fun setUp() { - setUpTestApplicationComponent() - context = InstrumentationRegistry.getInstrumentation().targetContext - - val delegatingWorkerFactory = DelegatingWorkerFactory() - delegatingWorkerFactory.addFactory(logUploadWorkerFactory) - delegatingWorkerFactory.addFactory(metricLogSchedulingWorkerFactory) - - val config = Configuration.Builder() - .setExecutor(SynchronousExecutor()) - .setWorkerFactory(delegatingWorkerFactory) - .build() - WorkManagerTestInitHelper.initializeTestWorkManager(context, config) - } - - @Test - fun testWorkRequest_onCreate_enqueuesRequest_verifyRequestId() { - logReportWorkManagerInitializer.onCreate(WorkManager.getInstance(context)) - testCoroutineDispatchers.runCurrent() - - val enqueuedEventWorkRequestId = logReportWorkManagerInitializer.getWorkRequestForEventsId() - val enqueuedExceptionWorkRequestId = - logReportWorkManagerInitializer.getWorkRequestForExceptionsId() - val enqueuedPerformanceMetricsWorkRequestId = - logReportWorkManagerInitializer.getWorkRequestForPerformanceMetricsId() - val enqueuedSchedulingStorageUsageMetricWorkRequestId = - logReportWorkManagerInitializer.getWorkRequestForSchedulingStorageUsageMetricLogsId() - val enqueuedSchedulingPeriodicUiMetricWorkRequestId = - logReportWorkManagerInitializer.getWorkRequestForSchedulingPeriodicUiMetricLogsId() - val enqueuedSchedulingPeriodicBackgroundPerformanceMetricWorkRequestId = - logReportWorkManagerInitializer - .getWorkRequestForSchedulingPeriodicBackgroundPerformanceMetricLogsId() - val enqueuedFirestoreWorkRequestId = - logReportWorkManagerInitializer.getWorkRequestForFirestoreId() - - assertThat(fakeLogUploader.getMostRecentEventRequestId()).isEqualTo(enqueuedEventWorkRequestId) - assertThat(fakeLogUploader.getMostRecentExceptionRequestId()).isEqualTo( - enqueuedExceptionWorkRequestId - ) - assertThat(fakeLogUploader.getMostRecentPerformanceMetricsRequestId()).isEqualTo( - enqueuedPerformanceMetricsWorkRequestId - ) - assertThat(fakeLogScheduler.getMostRecentStorageUsageMetricLoggingRequestId()).isEqualTo( - enqueuedSchedulingStorageUsageMetricWorkRequestId - ) - assertThat(fakeLogScheduler.getMostRecentPeriodicUiMetricLoggingRequestId()).isEqualTo( - enqueuedSchedulingPeriodicUiMetricWorkRequestId - ) - assertThat(fakeLogScheduler.getMostRecentPeriodicBackgroundMetricLoggingRequestId()).isEqualTo( - enqueuedSchedulingPeriodicBackgroundPerformanceMetricWorkRequestId - ) - assertThat(fakeLogUploader.getMostRecentFirestoreRequestId()).isEqualTo( - enqueuedFirestoreWorkRequestId - ) - } - - @Test - fun testWorkRequest_verifyWorkerConstraints() { - val workerConstraints = Constraints.Builder() - .setRequiredNetworkType(NetworkType.CONNECTED) - .setRequiresBatteryNotLow(true) - .build() - - val logUploadingWorkRequestConstraints = - logReportWorkManagerInitializer.getLogReportWorkerConstraints() - - assertThat(logUploadingWorkRequestConstraints).isEqualTo(workerConstraints) - } - - @Test - fun testWorkRequest_verifyWorkRequestDataForEvents() { - val workerCaseForUploadingEvents: Data = Data.Builder() - .putString( - LogUploadWorker.WORKER_CASE_KEY, - LogUploadWorker.EVENT_WORKER - ) - .build() - - assertThat(logReportWorkManagerInitializer.getWorkRequestDataForEvents()).isEqualTo( - workerCaseForUploadingEvents - ) - } - - @Test - fun testWorkRequest_verifyWorkRequestDataForExceptions() { - val workerCaseForUploadingExceptions: Data = Data.Builder() - .putString( - LogUploadWorker.WORKER_CASE_KEY, - LogUploadWorker.EXCEPTION_WORKER - ) - .build() - - assertThat( - logReportWorkManagerInitializer.getWorkRequestDataForExceptions() - ).isEqualTo(workerCaseForUploadingExceptions) - } - - @Test - fun testWorkRequest_verifyWorkRequestDataForPerformanceMetrics() { - val workerCaseForUploadingPerformanceMetrics: Data = Data.Builder() - .putString( - LogUploadWorker.WORKER_CASE_KEY, - LogUploadWorker.PERFORMANCE_METRICS_WORKER - ) - .build() - - assertThat(logReportWorkManagerInitializer.getWorkRequestDataForPerformanceMetrics()).isEqualTo( - workerCaseForUploadingPerformanceMetrics - ) - } - - @Test - fun testWorkRequest_verifyWorkRequestData_forSchedulingStorageUsageMetricLogs() { - val workerCaseForSchedulingStorageUsageMetricLogs: Data = Data.Builder() - .putString( - MetricLogSchedulingWorker.WORKER_CASE_KEY, - MetricLogSchedulingWorker.STORAGE_USAGE_WORKER - ) - .build() - - assertThat( - logReportWorkManagerInitializer.getWorkRequestDataForSchedulingStorageUsageMetricLogs() - ).isEqualTo(workerCaseForSchedulingStorageUsageMetricLogs) - } - - @Test - fun testWorkRequest_verifyWorkRequestData_forSchedulingPeriodicBackgroundPerformanceMetricLogs() { - val workerCaseForSchedulingPeriodicPerformanceMetricLogs: Data = Data.Builder() - .putString( - MetricLogSchedulingWorker.WORKER_CASE_KEY, - MetricLogSchedulingWorker.PERIODIC_BACKGROUND_METRIC_WORKER - ) - .build() - - assertThat( - logReportWorkManagerInitializer - .getWorkRequestDataForSchedulingPeriodicBackgroundPerformanceMetricLogs() - ).isEqualTo(workerCaseForSchedulingPeriodicPerformanceMetricLogs) - } - - @Test - fun testWorkRequest_verifyWorkRequestData_forSchedulingPeriodicUiMetricLogs() { - val workerCaseForSchedulingMemoryUsageMetricLogs: Data = Data.Builder() - .putString( - MetricLogSchedulingWorker.WORKER_CASE_KEY, - MetricLogSchedulingWorker.PERIODIC_UI_METRIC_WORKER - ) - .build() - - assertThat( - logReportWorkManagerInitializer.getWorkRequestDataForSchedulingPeriodicUiMetricLogs() - ).isEqualTo(workerCaseForSchedulingMemoryUsageMetricLogs) - } - - @Test - fun testWorkRequest_verifyWorkRequestData_forSchedulingFirestoreUpload() { - val workerCaseForUploadingFirestoreData: Data = Data.Builder() - .putString( - LogUploadWorker.WORKER_CASE_KEY, - LogUploadWorker.FIRESTORE_WORKER - ) - .build() - - assertThat( - logReportWorkManagerInitializer.getWorkRequestDataForFirestore() - ).isEqualTo(workerCaseForUploadingFirestoreData) - } - - private fun setUpTestApplicationComponent() { - ApplicationProvider.getApplicationContext().inject(this) - } - - // TODO(#89): Move this to a common test application component. - @Module - class TestModule { - @Provides - @Singleton - fun provideContext(application: Application): Context { - return application - } - } - - @Module - class TestLogStorageModule { - - @Provides - @EventLogStorageCacheSize - fun provideEventLogStorageCacheSize(): Int = 2 - - @Provides - @ExceptionLogStorageCacheSize - fun provideExceptionLogStorageSize(): Int = 2 - - @Provides - @PerformanceMetricsLogStorageCacheSize - fun providePerformanceMetricsLogStorageCacheSize(): Int = 2 - - @Provides - @FirestoreLogStorageCacheSize - fun provideFirestoreLogStorageCacheSize(): Int = 2 - } - - @Module - interface TestFirebaseLogUploaderModule { - - @Binds - fun bindsFakeLogUploader(fakeLogUploader: FakeLogUploader): LogUploader - - @Binds - fun bindsFakeLogScheduler(fakeLogScheduler: FakeLogScheduler): MetricLogScheduler - } - - // TODO(#89): Move this to a common test application component. - @Singleton - @Component( - modules = [ - ApplicationLifecycleModule::class, - AssetModule::class, - CpuPerformanceSnapshotterModule::class, - FakeOppiaClockModule::class, - LocaleProdModule::class, - LogReportWorkerModule::class, - LoggerModule::class, - LoggingIdentifierModule::class, - NetworkConnectionUtilDebugModule::class, - PlatformParameterSingletonModule::class, - RobolectricModule::class, - SyncStatusModule::class, - TestAuthenticationModule::class, - TestDispatcherModule::class, - TestFirebaseLogUploaderModule::class, - TestLogReportingModule::class, - TestLogStorageModule::class, - TestModule::class, - TestPlatformParameterModule::class - ] - ) - interface TestApplicationComponent : DataProvidersInjector { - @Component.Builder - interface Builder { - @BindsInstance - fun setApplication(application: Application): Builder - fun build(): TestApplicationComponent - } - - fun inject(logUploadWorkRequestTest: LogReportWorkManagerInitializerTest) - } - - class TestApplication : Application(), DataProvidersInjectorProvider { - private val component: TestApplicationComponent by lazy { - DaggerLogReportWorkManagerInitializerTest_TestApplicationComponent.builder() - .setApplication(this) - .build() - } - - fun inject(test: LogReportWorkManagerInitializerTest) { - component.inject(test) - } - - override fun getDataProvidersInjector() = component - } -} diff --git a/domain/src/test/java/org/oppia/android/domain/oppialogger/loguploader/LogReportWorkerModuleTest.kt b/domain/src/test/java/org/oppia/android/domain/oppialogger/loguploader/LogReportWorkerModuleTest.kt new file mode 100644 index 00000000000..a0a0c12eb1a --- /dev/null +++ b/domain/src/test/java/org/oppia/android/domain/oppialogger/loguploader/LogReportWorkerModuleTest.kt @@ -0,0 +1,144 @@ +package org.oppia.android.domain.oppialogger.loguploader + +import android.app.Application +import android.content.Context +import androidx.test.core.app.ApplicationProvider +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.google.common.truth.Truth.assertThat +import dagger.Binds +import dagger.BindsInstance +import dagger.Component +import dagger.Module +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.oppia.android.domain.oppialogger.LogStorageModule +import org.oppia.android.domain.oppialogger.LoggingIdentifierModule +import org.oppia.android.domain.oppialogger.analytics.ApplicationLifecycleModule +import org.oppia.android.domain.oppialogger.analytics.CpuPerformanceSnapshotterModule +import org.oppia.android.domain.platformparameter.PlatformParameterControllerInjector +import org.oppia.android.domain.platformparameter.PlatformParameterControllerInjectorProvider +import org.oppia.android.domain.platformparameter.PlatformParameterSingletonModule +import org.oppia.android.domain.workmanager.OppiaWorker +import org.oppia.android.domain.workmanager.StartupWorkerScheduleReadinessListener +import org.oppia.android.testing.TestLogReportingModule +import org.oppia.android.testing.firebase.TestAuthenticationModule +import org.oppia.android.testing.logging.SyncStatusTestModule +import org.oppia.android.testing.platformparameter.TestPlatformParameterModule +import org.oppia.android.testing.robolectric.RobolectricModule +import org.oppia.android.testing.threading.TestDispatcherModule +import org.oppia.android.testing.time.FakeOppiaClockModule +import org.oppia.android.util.caching.AssetModule +import org.oppia.android.util.data.DataProvidersInjector +import org.oppia.android.util.data.DataProvidersInjectorProvider +import org.oppia.android.util.locale.LocaleProdModule +import org.oppia.android.util.logging.LoggerModule +import org.oppia.android.util.logging.performancemetrics.PerformanceMetricsConfigurationsModule +import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule +import org.oppia.android.util.threading.DispatcherInjector +import org.oppia.android.util.threading.DispatcherInjectorProvider +import org.robolectric.annotation.Config +import org.robolectric.annotation.LooperMode +import javax.inject.Inject +import javax.inject.Singleton + +/** Tests for [LogReportWorkerModule]. */ +@Suppress("FunctionName") // FunctionName: test names are conventionally named with underscores. +@RunWith(AndroidJUnit4::class) +@LooperMode(LooperMode.Mode.PAUSED) +@Config(application = LogReportWorkerModuleTest.TestApplication::class) +class LogReportWorkerModuleTest { + @Inject lateinit var context: Context + @Inject + lateinit var readinessListeners: Set<@JvmSuppressWildcards StartupWorkerScheduleReadinessListener> + @Inject lateinit var factories: Map> + + @Before + fun setUp() { + setUpTestApplicationComponent() + } + + @Test + fun testInjection_bindsLogUploadWorkerSchedulerIntoReadinessListenerSet() { + // The main test is that the test suite builds. + val logReportWorkerSchedulers = readinessListeners.filterIsInstance() + assertThat(readinessListeners).isNotEmpty() + assertThat(logReportWorkerSchedulers).hasSize(1) + } + + @Test + fun testInjection_bindsLogUploadWorkerFactoryByWorkerName() { + assertThat(factories).containsKey(LogUploadWorker.WORKER_NAME) + assertThat(factories[LogUploadWorker.WORKER_NAME]) + .isInstanceOf(LogUploadWorker.Factory::class.java) + } + + private fun setUpTestApplicationComponent() { + ApplicationProvider.getApplicationContext().inject(this) + } + + // TODO(#89): Move this to a common test application component. + @Module + interface TestModule { + @Binds + fun provideContext(application: Application): Context + } + + // TODO(#89): Move this to a common test application component. + @Singleton + @Component( + modules = [ + TestModule::class, + LogReportWorkerModule::class, + LocaleProdModule::class, + FakeOppiaClockModule::class, + LoggerModule::class, + RobolectricModule::class, + TestDispatcherModule::class, + TestPlatformParameterModule::class, + ApplicationLifecycleModule::class, + AssetModule::class, + CpuPerformanceSnapshotterModule::class, + LogStorageModule::class, + LoggingIdentifierModule::class, + NetworkConnectionUtilDebugModule::class, + PerformanceMetricsConfigurationsModule::class, + PlatformParameterSingletonModule::class, + SyncStatusTestModule::class, + TestLogReportingModule::class, + TestAuthenticationModule::class + ] + ) + interface TestApplicationComponent : + DataProvidersInjector, DispatcherInjector, PlatformParameterControllerInjector { + @Component.Builder + interface Builder { + @BindsInstance + fun setApplication(application: Application): Builder + + fun build(): TestApplicationComponent + } + + fun inject(test: LogReportWorkerModuleTest) + } + + class TestApplication : + Application(), + DataProvidersInjectorProvider, + DispatcherInjectorProvider, + PlatformParameterControllerInjectorProvider { + private val component: TestApplicationComponent by lazy { + DaggerLogReportWorkerModuleTest_TestApplicationComponent.builder() + .setApplication(this) + .build() + } + + fun inject(test: LogReportWorkerModuleTest) { + component.inject(test) + } + + override fun getDataProvidersInjector() = component + override fun getPlatformParameterControllerInjector() = component + override fun getDispatcherInjector(): DispatcherInjector = component + } +} diff --git a/domain/src/test/java/org/oppia/android/domain/oppialogger/loguploader/LogReportWorkerSchedulerTest.kt b/domain/src/test/java/org/oppia/android/domain/oppialogger/loguploader/LogReportWorkerSchedulerTest.kt new file mode 100644 index 00000000000..ed6ea525220 --- /dev/null +++ b/domain/src/test/java/org/oppia/android/domain/oppialogger/loguploader/LogReportWorkerSchedulerTest.kt @@ -0,0 +1,366 @@ +package org.oppia.android.domain.oppialogger.loguploader + +import android.app.Application +import android.content.Context +import android.util.Log +import androidx.test.core.app.ApplicationProvider +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.work.NetworkType.CONNECTED +import androidx.work.WorkInfo +import com.google.common.truth.Truth.assertThat +import com.google.firebase.FirebaseApp +import dagger.BindsInstance +import dagger.Component +import dagger.Module +import dagger.Provides +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import org.junit.After +import org.junit.Test +import org.junit.runner.RunWith +import org.oppia.android.app.model.EventLog +import org.oppia.android.app.model.OppiaMetricLog +import org.oppia.android.app.model.OppiaMetricLog.Priority.HIGH_PRIORITY +import org.oppia.android.app.model.ScreenName.HOME_ACTIVITY +import org.oppia.android.domain.oppialogger.LogStorageModule +import org.oppia.android.domain.oppialogger.LoggingIdentifierModule +import org.oppia.android.domain.oppialogger.OppiaLogger +import org.oppia.android.domain.oppialogger.analytics.AnalyticsController +import org.oppia.android.domain.oppialogger.analytics.ApplicationLifecycleModule +import org.oppia.android.domain.oppialogger.analytics.CpuPerformanceSnapshotterModule +import org.oppia.android.domain.oppialogger.analytics.FirestoreDataController +import org.oppia.android.domain.oppialogger.analytics.PerformanceMetricsController +import org.oppia.android.domain.oppialogger.exceptions.ExceptionsController +import org.oppia.android.domain.oppialogger.loguploader.LogUploadWorker.Companion.WORKER_NAME +import org.oppia.android.domain.oppialogger.loguploader.LogUploadWorker.Operation.UPLOAD_EVENTS +import org.oppia.android.domain.oppialogger.loguploader.LogUploadWorker.Operation.UPLOAD_EXCEPTIONS +import org.oppia.android.domain.oppialogger.loguploader.LogUploadWorker.Operation.UPLOAD_FIRESTORE_DATA +import org.oppia.android.domain.oppialogger.loguploader.LogUploadWorker.Operation.UPLOAD_PERFORMANCE_METRICS +import org.oppia.android.domain.platformparameter.PlatformParameterControllerInjector +import org.oppia.android.domain.platformparameter.PlatformParameterControllerInjectorProvider +import org.oppia.android.domain.platformparameter.PlatformParameterSingletonModule +import org.oppia.android.domain.workmanager.WorkManagerScheduler +import org.oppia.android.domain.workmanager.testing.OppiaWorkManagerTestDriver +import org.oppia.android.testing.FakeAnalyticsEventLogger +import org.oppia.android.testing.FakeExceptionLogger +import org.oppia.android.testing.FakeFirestoreEventLogger +import org.oppia.android.testing.FakePerformanceMetricsEventLogger +import org.oppia.android.testing.TestLogReportingModule +import org.oppia.android.testing.firebase.TestAuthenticationModule +import org.oppia.android.testing.logging.SyncStatusTestModule +import org.oppia.android.testing.platformparameter.TestPlatformParameterModule +import org.oppia.android.testing.robolectric.RobolectricModule +import org.oppia.android.testing.threading.TestCoroutineDispatchers +import org.oppia.android.testing.threading.TestDispatcherModule +import org.oppia.android.testing.time.FakeOppiaClockModule +import org.oppia.android.util.caching.AssetModule +import org.oppia.android.util.data.DataProvidersInjector +import org.oppia.android.util.data.DataProvidersInjectorProvider +import org.oppia.android.util.locale.LocaleProdModule +import org.oppia.android.util.logging.LoggerModule +import org.oppia.android.util.networking.NetworkConnectionDebugUtil +import org.oppia.android.util.networking.NetworkConnectionUtil.ProdConnectionStatus.CELLULAR +import org.oppia.android.util.networking.NetworkConnectionUtil.ProdConnectionStatus.NONE +import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule +import org.oppia.android.util.system.OppiaClock +import org.oppia.android.util.threading.BackgroundDispatcher +import org.oppia.android.util.threading.DispatcherInjector +import org.oppia.android.util.threading.DispatcherInjectorProvider +import org.robolectric.annotation.Config +import org.robolectric.annotation.LooperMode +import org.robolectric.shadows.ShadowLog +import java.util.concurrent.TimeUnit +import javax.inject.Inject +import javax.inject.Singleton + +/** Tests for [LogReportWorkerScheduler]. */ +@RunWith(AndroidJUnit4::class) +@LooperMode(LooperMode.Mode.PAUSED) +@Config(application = LogReportWorkerSchedulerTest.TestApplication::class) +// FunctionName: test names are conventionally named with underscores. +// SameParameterValue: tests should have specific context included/excluded for readability. +@Suppress("FunctionName", "SameParameterValue") +class LogReportWorkerSchedulerTest { + @Inject lateinit var context: Context + @Inject lateinit var testCoroutineDispatchers: TestCoroutineDispatchers + @Inject lateinit var workManagerScheduler: WorkManagerScheduler + @Inject lateinit var logReportWorkerScheduler: LogReportWorkerScheduler + @Inject lateinit var testDriver: OppiaWorkManagerTestDriver + @Inject lateinit var oppiaLogger: OppiaLogger + @Inject lateinit var oppiaClock: OppiaClock + @Inject lateinit var analyticsController: AnalyticsController + @Inject lateinit var exceptionsController: ExceptionsController + @Inject lateinit var performanceMetricsController: PerformanceMetricsController + @Inject lateinit var firestoreDataController: FirestoreDataController + @Inject lateinit var networkConnectionUtil: NetworkConnectionDebugUtil + @Inject lateinit var fakeAnalyticsEventLogger: FakeAnalyticsEventLogger + @Inject lateinit var fakeExceptionLogger: FakeExceptionLogger + @Inject lateinit var fakePerformanceMetricsEventLogger: FakePerformanceMetricsEventLogger + @Inject lateinit var fakeFirestoreEventLogger: FakeFirestoreEventLogger + @field:[Inject BackgroundDispatcher] lateinit var backgroundDispatcher: CoroutineDispatcher + + @After + fun tearDown() { + TestPlatformParameterModule.reset() + } + + @Test + fun testLogUploadWorker_hasFourOperationTypes() { + setUpTestApplicationComponent() + + // A change detector test that, if failing, means that other tests in this suite need to be + // updated to ensure that the new operation type is properly verified. + assertThat(LogUploadWorker.Operation.values().toList()).containsExactly( + UPLOAD_EVENTS, UPLOAD_EXCEPTIONS, UPLOAD_PERFORMANCE_METRICS, UPLOAD_FIRESTORE_DATA + ) + } + + @Test + fun testScheduleWork_schedulesLogUploadWorkerToUploadEvents() { + setUpTestApplicationComponent() + initializeDependencies() + + logReportWorkerScheduler.scheduleWork(workManagerScheduler) + + // Verify that the job was scheduled correctly. + val monitor = testDriver.lookUpPeriodicMonitor(WORKER_NAME, UPLOAD_EVENTS) + assertThat(monitor.state).isEqualTo(WorkInfo.State.ENQUEUED) + assertThat(monitor.intervalDurationMs).isEqualTo(TimeUnit.HOURS.toMillis(6)) + assertThat(monitor.requiredNetworkType).isEqualTo(CONNECTED) + } + + @Test + fun testScheduleWork_schedulesLogUploadWorkerToUploadExceptions() { + setUpTestApplicationComponent() + initializeDependencies() + + logReportWorkerScheduler.scheduleWork(workManagerScheduler) + + // Verify that the job was scheduled correctly. + val monitor = testDriver.lookUpPeriodicMonitor(WORKER_NAME, UPLOAD_EXCEPTIONS) + assertThat(monitor.state).isEqualTo(WorkInfo.State.ENQUEUED) + assertThat(monitor.intervalDurationMs).isEqualTo(TimeUnit.HOURS.toMillis(6)) + assertThat(monitor.requiredNetworkType).isEqualTo(CONNECTED) + } + + @Test + fun testScheduleWork_schedulesLogUploadWorkerToUploadPerformanceMetrics() { + setUpTestApplicationComponent() + initializeDependencies() + + logReportWorkerScheduler.scheduleWork(workManagerScheduler) + + // Verify that the job was scheduled correctly. + val monitor = testDriver.lookUpPeriodicMonitor(WORKER_NAME, UPLOAD_PERFORMANCE_METRICS) + assertThat(monitor.state).isEqualTo(WorkInfo.State.ENQUEUED) + assertThat(monitor.intervalDurationMs).isEqualTo(TimeUnit.HOURS.toMillis(6)) + assertThat(monitor.requiredNetworkType).isEqualTo(CONNECTED) + } + + @Test + fun testScheduleWork_schedulesLogUploadWorkerToUploadFirestoreData() { + setUpTestApplicationComponent() + initializeDependencies() + + logReportWorkerScheduler.scheduleWork(workManagerScheduler) + + // Verify that the job was scheduled correctly. + val monitor = testDriver.lookUpPeriodicMonitor(WORKER_NAME, UPLOAD_FIRESTORE_DATA) + assertThat(monitor.state).isEqualTo(WorkInfo.State.ENQUEUED) + assertThat(monitor.intervalDurationMs).isEqualTo(TimeUnit.HOURS.toMillis(6)) + assertThat(monitor.requiredNetworkType).isEqualTo(CONNECTED) + } + + @Test + fun testScheduleWork_constraintsMet_runsFourJobs() { + setUpTestApplicationComponent() + initializeDependencies() + logOneEventPerType() + logReportWorkerScheduler.scheduleWork(workManagerScheduler) + + forceConstraintsMet(UPLOAD_EVENTS) + forceConstraintsMet(UPLOAD_EXCEPTIONS) + forceConstraintsMet(UPLOAD_PERFORMANCE_METRICS) + forceConstraintsMet(UPLOAD_FIRESTORE_DATA) + testCoroutineDispatchers.runCurrent() + + // All of the jobs should've run and uploading the logged metrics. + assertThat(fetchWorkerErrorLogs()).isEmpty() // No job failures should have occurred. + assertThat(fakeAnalyticsEventLogger.getEventListCount()).isEqualTo(1) + assertThat(fakeExceptionLogger.getExceptionCount()).isEqualTo(1) + assertThat(fakePerformanceMetricsEventLogger.getPerformanceMetricsEventListCount()).isEqualTo(1) + assertThat(fakeFirestoreEventLogger.getEventListCount()).isEqualTo(1) + } + + @Test + fun testScheduleWork_constraintsMet_tenHoursElapsed_runsEightJobs() { + setUpTestApplicationComponent() + initializeDependencies() + logOneEventPerType() // Log events right away for the immediate job runs. + logOneEventEveryHourIsh() + logReportWorkerScheduler.scheduleWork(workManagerScheduler) + + forceConstraintsMet(UPLOAD_EVENTS) + forceConstraintsMet(UPLOAD_EXCEPTIONS) + forceConstraintsMet(UPLOAD_PERFORMANCE_METRICS) + forceConstraintsMet(UPLOAD_FIRESTORE_DATA) + testCoroutineDispatchers.advanceTimeBy(TimeUnit.HOURS.toMillis(10)) + + // There are initially 1 set of events logged per job type, then one every hour. However, the + // jobs are scheduled to run every 6 hours (with 1 running immediately). This means although 11 + // total events will be cached for logging, only 7 will actually be uploaded across the 2 runs. + assertThat(fetchWorkerErrorLogs()).isEmpty() // No job failures should have occurred. + assertThat(fakeAnalyticsEventLogger.getEventListCount()).isEqualTo(7) + assertThat(fakeExceptionLogger.getExceptionCount()).isEqualTo(7) + assertThat(fakePerformanceMetricsEventLogger.getPerformanceMetricsEventListCount()).isEqualTo(7) + assertThat(fakeFirestoreEventLogger.getEventListCount()).isEqualTo(7) + } + + private fun logOneEventPerType() { + networkConnectionUtil.setCurrentConnectionStatus(NONE) // For offline caching. + analyticsController.logImportantEvent(oppiaLogger.createOpenHomeContext(), profileId = null) + exceptionsController.logNonFatalException(Exception("test")) + performanceMetricsController.logPerformanceMetricsEvent( + oppiaClock.getCurrentTimeMs(), + HOME_ACTIVITY, + createStartupLatencyMetric(startMs = 123), + HIGH_PRIORITY + ) + firestoreDataController.logEvent( + createOptionalSurveyResponseContext("feedback 1", "test_survey_id1"), profileId = null + ) + + // Note that the order is important here. Logs must be synchronized before restoring + // connectivity to ensure that they're cached and not immediately uploaded. This also means that + // this helper method cannot be called except on the main thread. + testCoroutineDispatchers.runCurrent() + networkConnectionUtil.setCurrentConnectionStatus(CELLULAR) + } + + private fun logOneEventEveryHourIsh() { + CoroutineScope(backgroundDispatcher).launch { + // Note that the '- 1' here is very important. The logs need to be logged just shy of the hour + // mark to make sure that logOneEventPerType() can restore network connectivity BEFORE the + // job has time to run otherwise it will run in non-connectivity mode and fail. + delay(TimeUnit.HOURS.toMillis(1) - 1) + // Hop to main thread so that coroutines can be synchronized. + withContext(Dispatchers.Main) { + logOneEventPerType() + logOneEventEveryHourIsh() + } + } + } + + private fun forceConstraintsMet(taskType: LogUploadWorker.Operation) { + testDriver.lookUpPeriodicMonitor(WORKER_NAME, taskType).forceConstraintsMet() + } + + private fun createStartupLatencyMetric(startMs: Long): OppiaMetricLog.LoggableMetric { + return OppiaMetricLog.LoggableMetric.newBuilder().apply { + startupLatencyMetric = OppiaMetricLog.StartupLatencyMetric.newBuilder().apply { + startupLatencyMillis = startMs + }.build() + }.build() + } + + private fun createOptionalSurveyResponseContext( + feedback: String, + surveyId: String + ): EventLog.Context { + return EventLog.Context.newBuilder().apply { + optionalResponse = EventLog.OptionalSurveyResponseContext.newBuilder().apply { + feedbackAnswer = feedback + surveyDetails = EventLog.SurveyResponseContext.newBuilder().apply { + this.surveyId = surveyId + }.build() + }.build() + }.build() + } + + private fun fetchWorkerErrorLogs(): List { + // Extract all logs from the bootstrap worker and validate they are each errors before returning + // the logged message lines. + return ShadowLog.getLogs().filter { it.tag == WORKER_NAME }.onEach { + assertThat(it.type).isEqualTo(Log.ERROR) + }.map(ShadowLog.LogItem::msg) + } + + private fun initializeDependencies() { + FirebaseApp.initializeApp(context) + testDriver.initializeWorkManager() + } + + private fun setUpTestApplicationComponent() { + ApplicationProvider.getApplicationContext().inject(this) + } + + // TODO(#89): Move this to a common test application component. + @Module + class TestModule { + @Provides + @Singleton + fun provideContext(application: Application): Context { + return application + } + } + + // TODO(#89): Move this to a common test application component. + @Singleton + @Component( + modules = [ + ApplicationLifecycleModule::class, + AssetModule::class, + CpuPerformanceSnapshotterModule::class, + FakeOppiaClockModule::class, + LocaleProdModule::class, + LogStorageModule::class, + LoggerModule::class, + LoggingIdentifierModule::class, + LogReportWorkerModule::class, + NetworkConnectionUtilDebugModule::class, + PlatformParameterSingletonModule::class, + RobolectricModule::class, + SyncStatusTestModule::class, + TestDispatcherModule::class, + TestLogReportingModule::class, + TestModule::class, + TestAuthenticationModule::class, + TestPlatformParameterModule::class + ] + ) + interface TestApplicationComponent : + DataProvidersInjector, DispatcherInjector, PlatformParameterControllerInjector { + @Component.Builder + interface Builder { + @BindsInstance + fun setApplication(application: Application): Builder + fun build(): TestApplicationComponent + } + + fun inject(logUploadWorkRequestTest: LogReportWorkerSchedulerTest) + } + + class TestApplication : + Application(), + DataProvidersInjectorProvider, + DispatcherInjectorProvider, + PlatformParameterControllerInjectorProvider { + private val component: TestApplicationComponent by lazy { + DaggerLogReportWorkerSchedulerTest_TestApplicationComponent.builder() + .setApplication(this) + .build() + } + + fun inject(test: LogReportWorkerSchedulerTest) { + component.inject(test) + } + + override fun getDataProvidersInjector() = component + override fun getDispatcherInjector() = component + override fun getPlatformParameterControllerInjector() = component + } +} diff --git a/domain/src/test/java/org/oppia/android/domain/oppialogger/loguploader/LogUploadWorkerTest.kt b/domain/src/test/java/org/oppia/android/domain/oppialogger/loguploader/LogUploadWorkerTest.kt index cad34cf8422..9634d3fdddd 100644 --- a/domain/src/test/java/org/oppia/android/domain/oppialogger/loguploader/LogUploadWorkerTest.kt +++ b/domain/src/test/java/org/oppia/android/domain/oppialogger/loguploader/LogUploadWorkerTest.kt @@ -2,628 +2,786 @@ package org.oppia.android.domain.oppialogger.loguploader import android.app.Application import android.content.Context +import android.util.Log import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 -import androidx.test.platform.app.InstrumentationRegistry import androidx.work.Configuration -import androidx.work.Data -import androidx.work.OneTimeWorkRequest -import androidx.work.OneTimeWorkRequestBuilder import androidx.work.WorkInfo -import androidx.work.WorkManager -import androidx.work.testing.SynchronousExecutor -import androidx.work.testing.WorkManagerTestInitHelper import com.google.common.truth.Truth.assertThat -import dagger.Binds +import com.google.firebase.FirebaseApp import dagger.BindsInstance import dagger.Component import dagger.Module import dagger.Provides +import kotlinx.coroutines.CoroutineDispatcher import org.junit.After import org.junit.Test import org.junit.runner.RunWith -import org.mockito.Mockito.`when` -import org.mockito.Mockito.mock -import org.mockito.Mockito.reset import org.oppia.android.app.model.EventLog +import org.oppia.android.app.model.EventLog.Context.ActivityContextCase.OPTIONAL_RESPONSE import org.oppia.android.app.model.OppiaMetricLog -import org.oppia.android.app.model.ScreenName.SCREEN_NAME_UNSPECIFIED -import org.oppia.android.domain.oppialogger.EventLogStorageCacheSize -import org.oppia.android.domain.oppialogger.ExceptionLogStorageCacheSize -import org.oppia.android.domain.oppialogger.FirestoreLogStorageCacheSize +import org.oppia.android.app.model.OppiaMetricLog.LoggableMetric +import org.oppia.android.app.model.OppiaMetricLog.LoggableMetric.LoggableMetricTypeCase.APK_SIZE_METRIC +import org.oppia.android.app.model.OppiaMetricLog.LoggableMetric.LoggableMetricTypeCase.STARTUP_LATENCY_METRIC +import org.oppia.android.app.model.OppiaMetricLog.LoggableMetric.LoggableMetricTypeCase.STORAGE_USAGE_METRIC +import org.oppia.android.app.model.OppiaMetricLog.Priority +import org.oppia.android.app.model.OppiaMetricLog.Priority.HIGH_PRIORITY +import org.oppia.android.app.model.OppiaMetricLog.Priority.LOW_PRIORITY +import org.oppia.android.app.model.ScreenName +import org.oppia.android.app.model.ScreenName.BACKGROUND_SCREEN +import org.oppia.android.app.model.ScreenName.HOME_ACTIVITY +import org.oppia.android.domain.oppialogger.LogStorageModule import org.oppia.android.domain.oppialogger.LoggingIdentifierModule import org.oppia.android.domain.oppialogger.OppiaLogger -import org.oppia.android.domain.oppialogger.PerformanceMetricsLogStorageCacheSize import org.oppia.android.domain.oppialogger.analytics.AnalyticsController import org.oppia.android.domain.oppialogger.analytics.ApplicationLifecycleModule +import org.oppia.android.domain.oppialogger.analytics.CpuPerformanceSnapshotterModule import org.oppia.android.domain.oppialogger.analytics.FirestoreDataController import org.oppia.android.domain.oppialogger.analytics.PerformanceMetricsController import org.oppia.android.domain.oppialogger.exceptions.ExceptionsController +import org.oppia.android.domain.oppialogger.loguploader.LogUploadWorker.Companion.WORKER_NAME +import org.oppia.android.domain.oppialogger.loguploader.LogUploadWorker.Operation.UPLOAD_EVENTS +import org.oppia.android.domain.oppialogger.loguploader.LogUploadWorker.Operation.UPLOAD_EXCEPTIONS +import org.oppia.android.domain.oppialogger.loguploader.LogUploadWorker.Operation.UPLOAD_FIRESTORE_DATA +import org.oppia.android.domain.oppialogger.loguploader.LogUploadWorker.Operation.UPLOAD_PERFORMANCE_METRICS +import org.oppia.android.domain.platformparameter.PlatformParameterControllerInjector +import org.oppia.android.domain.platformparameter.PlatformParameterControllerInjectorProvider import org.oppia.android.domain.platformparameter.PlatformParameterSingletonModule -import org.oppia.android.domain.testing.oppialogger.loguploader.FakeLogUploader +import org.oppia.android.domain.workmanager.WorkManagerConfigurationModule +import org.oppia.android.domain.workmanager.testing.OppiaWorkManagerTestDriver import org.oppia.android.testing.FakeAnalyticsEventLogger import org.oppia.android.testing.FakeExceptionLogger import org.oppia.android.testing.FakeFirestoreEventLogger import org.oppia.android.testing.FakePerformanceMetricsEventLogger -import org.oppia.android.testing.data.DataProviderTestMonitor +import org.oppia.android.testing.TestLogReportingModule import org.oppia.android.testing.firebase.TestAuthenticationModule +import org.oppia.android.testing.logging.EventLogSubject.Companion.assertThat import org.oppia.android.testing.logging.SyncStatusTestModule import org.oppia.android.testing.logging.TestSyncStatusManager -import org.oppia.android.testing.mockito.anyOrNull import org.oppia.android.testing.platformparameter.TestPlatformParameterModule import org.oppia.android.testing.robolectric.RobolectricModule import org.oppia.android.testing.threading.TestCoroutineDispatchers import org.oppia.android.testing.threading.TestDispatcherModule import org.oppia.android.testing.time.FakeOppiaClockModule import org.oppia.android.util.caching.AssetModule -import org.oppia.android.util.data.DataProviders import org.oppia.android.util.data.DataProvidersInjector import org.oppia.android.util.data.DataProvidersInjectorProvider import org.oppia.android.util.locale.LocaleProdModule -import org.oppia.android.util.logging.AnalyticsEventLogger -import org.oppia.android.util.logging.ExceptionLogger -import org.oppia.android.util.logging.LogUploader import org.oppia.android.util.logging.LoggerModule +import org.oppia.android.util.logging.SyncStatusManager.SyncStatus import org.oppia.android.util.logging.SyncStatusManager.SyncStatus.DATA_UPLOADED import org.oppia.android.util.logging.SyncStatusManager.SyncStatus.DATA_UPLOADING import org.oppia.android.util.logging.SyncStatusManager.SyncStatus.INITIAL_UNKNOWN import org.oppia.android.util.logging.SyncStatusManager.SyncStatus.NO_CONNECTIVITY import org.oppia.android.util.logging.SyncStatusManager.SyncStatus.UPLOAD_ERROR -import org.oppia.android.util.logging.firebase.FirestoreEventLogger -import org.oppia.android.util.logging.performancemetrics.PerformanceMetricsAssessorModule import org.oppia.android.util.logging.performancemetrics.PerformanceMetricsConfigurationsModule -import org.oppia.android.util.logging.performancemetrics.PerformanceMetricsEventLogger import org.oppia.android.util.networking.NetworkConnectionDebugUtil -import org.oppia.android.util.networking.NetworkConnectionUtil.ProdConnectionStatus.LOCAL -import org.oppia.android.util.networking.NetworkConnectionUtil.ProdConnectionStatus.NONE +import org.oppia.android.util.networking.NetworkConnectionUtil.ProdConnectionStatus import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule +import org.oppia.android.util.system.OppiaClock +import org.oppia.android.util.threading.BackgroundDispatcher +import org.oppia.android.util.threading.DispatcherInjector +import org.oppia.android.util.threading.DispatcherInjectorProvider import org.robolectric.annotation.Config import org.robolectric.annotation.LooperMode +import org.robolectric.shadows.ShadowLog import javax.inject.Inject -import javax.inject.Qualifier import javax.inject.Singleton -private const val TEST_TIMESTAMP = 1556094120000 -private const val TEST_TOPIC_ID = "test_topicId" -private const val TEST_APK_SIZE = Long.MAX_VALUE +private const val TEST_TOPIC_ID1 = "test_topic_id1" +private const val TEST_TOPIC_ID2 = "test_topic_id2" /** Tests for [LogUploadWorker]. */ // FunctionName: test names are conventionally named with underscores. -@Suppress("FunctionName") +// SameParameterValue: tests should have specific context included/excluded for readability. +@Suppress("FunctionName", "SameParameterValue") @RunWith(AndroidJUnit4::class) @LooperMode(LooperMode.Mode.PAUSED) @Config(application = LogUploadWorkerTest.TestApplication::class) class LogUploadWorkerTest { + @Inject lateinit var context: Context + @Inject lateinit var testCoroutineDispatchers: TestCoroutineDispatchers + @Inject lateinit var configuration: Configuration + @Inject lateinit var testDriver: OppiaWorkManagerTestDriver + @Inject lateinit var oppiaLogger: OppiaLogger + @Inject lateinit var oppiaClock: OppiaClock + @Inject lateinit var analyticsController: AnalyticsController + @Inject lateinit var exceptionsController: ExceptionsController + @Inject lateinit var performanceMetricsController: PerformanceMetricsController + @Inject lateinit var firestoreDataController: FirestoreDataController @Inject lateinit var networkConnectionUtil: NetworkConnectionDebugUtil @Inject lateinit var fakeAnalyticsEventLogger: FakeAnalyticsEventLogger @Inject lateinit var fakeExceptionLogger: FakeExceptionLogger @Inject lateinit var fakePerformanceMetricsEventLogger: FakePerformanceMetricsEventLogger @Inject lateinit var fakeFirestoreEventLogger: FakeFirestoreEventLogger - @Inject lateinit var oppiaLogger: OppiaLogger - @Inject lateinit var analyticsController: AnalyticsController - @Inject lateinit var dataController: FirestoreDataController - @Inject lateinit var exceptionsController: ExceptionsController - @Inject lateinit var performanceMetricsController: PerformanceMetricsController - @Inject lateinit var logUploadWorkerFactory: LogUploadWorkerFactory - @Inject lateinit var dataProviders: DataProviders - @Inject lateinit var testCoroutineDispatchers: TestCoroutineDispatchers @Inject lateinit var testSyncStatusManager: TestSyncStatusManager - @Inject lateinit var monitorFactory: DataProviderTestMonitor.Factory - @field:[Inject MockEventLogger] lateinit var mockAnalyticsEventLogger: AnalyticsEventLogger - @field:[Inject MockFirestoreEventLogger] - lateinit var mockFirestoreEventLogger: FirestoreEventLogger - - private lateinit var context: Context - - private val eventLogTopicContext = EventLog.newBuilder() - .setContext( - EventLog.Context.newBuilder() - .setOpenInfoTab( - EventLog.TopicContext.newBuilder() - .setTopicId(TEST_TOPIC_ID) - .build() - ) - .build() - ) - .setPriority(EventLog.Priority.ESSENTIAL) - .setTimestamp(TEST_TIMESTAMP) - .build() - - private val apkSizeTestLoggableMetric = OppiaMetricLog.LoggableMetric.newBuilder() - .setApkSizeMetric( - OppiaMetricLog.ApkSizeMetric.newBuilder() - .setApkSizeBytes(TEST_APK_SIZE) - .build() - ).build() + @field:[Inject BackgroundDispatcher] lateinit var backgroundDispatcher: CoroutineDispatcher - private val exception = Exception("TEST") + private val testException1 = Exception("Test 1") + private val testException2 = Exception("Test 2") + private val testException3 = Exception("Test 3") @After fun tearDown() { TestPlatformParameterModule.reset() } + /* Tests for UPLOAD_EVENTS. */ + @Test - fun testWorker_logEvent_withoutNetwork_enqueueRequest_verifyFailed() { + fun testWorker_scheduleUploadEvents_sanityCheck_doNotRun_noLogsUploaded() { setUpTestApplicationComponent() - networkConnectionUtil.setCurrentConnectionStatus(NONE) - analyticsController.logImportantEvent( - oppiaLogger.createOpenInfoTabContext(TEST_TOPIC_ID), - profileId = null, - eventLogTopicContext.timestamp - ) - testCoroutineDispatchers.runCurrent() - - val workManager = WorkManager.getInstance(ApplicationProvider.getApplicationContext()) - - val inputData = Data.Builder().putString( - LogUploadWorker.WORKER_CASE_KEY, - LogUploadWorker.EVENT_WORKER - ).build() + initializeDependencies() + forceNetworkConnectivityOff() // For offline caching. - val request: OneTimeWorkRequest = OneTimeWorkRequestBuilder() - .setInputData(inputData) - .build() - - workManager.enqueue(request) - testCoroutineDispatchers.runCurrent() - val workInfo = workManager.getWorkInfoById(request.id) + logImportantEvent(oppiaLogger.createOpenHomeContext()) + logLowPriorityEvent(oppiaLogger.createOpenPracticeTabContext(TEST_TOPIC_ID1)) + logImportantEvent(oppiaLogger.createOpenInfoTabContext(TEST_TOPIC_ID2)) - // The enqueue should fail since the worker shouldn't be running when there's no network - // connectivity. - assertThat(workInfo.get().state).isEqualTo(WorkInfo.State.FAILED) + // Sanity check: ensure that logs do not automatically upload without the job running (otherwise + // the other tests in this test suite are moot). + assertThat(fakeAnalyticsEventLogger.getEventListCount()).isEqualTo(0) + assertThat(getLatestSyncStatus()).isEqualTo(NO_CONNECTIVITY) } @Test - fun testWorker_logEvent_withNetwork_enqueueRequest_verifySuccess() { + fun testWorker_scheduleUploadEvents_noEvents_uploadsNothing() { setUpTestApplicationComponent() - networkConnectionUtil.setCurrentConnectionStatus(NONE) - analyticsController.logImportantEvent( - oppiaLogger.createOpenInfoTabContext(TEST_TOPIC_ID), - profileId = null, - eventLogTopicContext.timestamp - ) - networkConnectionUtil.setCurrentConnectionStatus(LOCAL) - testCoroutineDispatchers.runCurrent() - - val workManager = WorkManager.getInstance(ApplicationProvider.getApplicationContext()) + initializeDependencies() - val inputData = Data.Builder().putString( - LogUploadWorker.WORKER_CASE_KEY, - LogUploadWorker.EVENT_WORKER - ).build() + forceNetworkConnectivityToCellular() // For the job to run. + val monitor = testDriver.runOneOffWork(WORKER_NAME, UPLOAD_EVENTS) - val request: OneTimeWorkRequest = OneTimeWorkRequestBuilder() - .setInputData(inputData) - .build() + // Verify that the job succeeded and there was nothing to upload. Sync status will also update + // since the attempt to upload finished. + val logCount = fakeAnalyticsEventLogger.getEventListCount() + assertThat(monitor.state).isEqualTo(WorkInfo.State.SUCCEEDED) + assertThat(logCount).isEqualTo(0) + assertThat(getLatestSyncStatus()).isEqualTo(DATA_UPLOADED) + } - workManager.enqueue(request) - testCoroutineDispatchers.runCurrent() - val workInfo = workManager.getWorkInfoById(request.id) + @Test + fun testWorker_scheduleUploadEvents_severalEvents_uploadsAllEvents() { + setUpTestApplicationComponent() + initializeDependencies() + forceNetworkConnectivityOff() // For offline caching. + logImportantEvent(oppiaLogger.createOpenHomeContext()) + logLowPriorityEvent(oppiaLogger.createOpenPracticeTabContext(TEST_TOPIC_ID1)) + logImportantEvent(oppiaLogger.createOpenInfoTabContext(TEST_TOPIC_ID2)) + + forceNetworkConnectivityToCellular() // For the job to run. + val monitor = testDriver.runOneOffWork(WORKER_NAME, UPLOAD_EVENTS) + + // Verify that the job succeeded, was the only job to upload something, and uploaded correctly. + val logCount = fakeAnalyticsEventLogger.getEventListCount() + val events = fakeAnalyticsEventLogger.getMostRecentEvents(3) + assertThat(monitor.state).isEqualTo(WorkInfo.State.SUCCEEDED) + assertThat(logCount).isEqualTo(3) + assertThat(events[0]).hasOpenHomeContext() + assertThat(events[0]).isEssentialPriority() + assertThat(events[1]).hasOpenPracticeTabContextThat().hasTopicIdThat().isEqualTo(TEST_TOPIC_ID1) + assertThat(events[1]).isOptionalPriority() + assertThat(events[2]).hasOpenInfoTabContextThat().hasTopicIdThat().isEqualTo(TEST_TOPIC_ID2) + assertThat(events[2]).isEssentialPriority() + assertThat(getLatestSyncStatus()).isEqualTo(DATA_UPLOADED) + assertThat(testSyncStatusManager.getSyncStatuses()) + .containsExactly(INITIAL_UNKNOWN, NO_CONNECTIVITY, DATA_UPLOADING, DATA_UPLOADED) + .inOrder() + } - assertThat(workInfo.get().state).isEqualTo(WorkInfo.State.SUCCEEDED) - assertThat(fakeAnalyticsEventLogger.getMostRecentEvent()).isEqualTo(eventLogTopicContext) + @Test + fun testWorker_scheduleUploadEvents_severalEvents_runAgain_uploadsNothing() { + setUpTestApplicationComponent() + initializeDependencies() + forceNetworkConnectivityOff() // For offline caching. + logImportantEvent(oppiaLogger.createOpenHomeContext()) + logLowPriorityEvent(oppiaLogger.createOpenPracticeTabContext(TEST_TOPIC_ID1)) + logImportantEvent(oppiaLogger.createOpenInfoTabContext(TEST_TOPIC_ID2)) + forceNetworkConnectivityToCellular() // For the job to run. + testDriver.runOneOffWork(WORKER_NAME, UPLOAD_EVENTS) + fakeAnalyticsEventLogger.clearAllEvents() // Reset for next job. + + // Run a second time. + val monitor = testDriver.runOneOffWork(WORKER_NAME, UPLOAD_EVENTS) + + // Verify nothing uploaded (since everything should've uploaded during the first job). + val logCount = fakeAnalyticsEventLogger.getEventListCount() + assertThat(monitor.state).isEqualTo(WorkInfo.State.SUCCEEDED) + assertThat(logCount).isEqualTo(0) } @Test - fun testWorker_logEvent_withoutNetwork_enqueueRequest_writeFails_verifyFailure() { + fun testWorker_scheduleUploadEvents_hasFailure_failsAndLogsErrorAndReportsSyncStatus() { setUpTestApplicationComponent() - networkConnectionUtil.setCurrentConnectionStatus(NONE) - analyticsController.logImportantEvent( - oppiaLogger.createOpenInfoTabContext(TEST_TOPIC_ID), - profileId = null, - eventLogTopicContext.timestamp - ) - testCoroutineDispatchers.runCurrent() + initializeDependencies() + forceNetworkConnectivityOff() // For offline caching. + logImportantEvent(oppiaLogger.createOpenHomeContext()) + fakeAnalyticsEventLogger.setFailure(Exception("Forced failure.")) + + forceNetworkConnectivityToCellular() // For the job to run. + val monitor = testDriver.runOneOffWork(WORKER_NAME, UPLOAD_EVENTS) + + // Verify that the job failed and an error was logged due to an underlying failure. + val logCount = fakeAnalyticsEventLogger.getEventListCount() + val failureLine = fetchSingleWorkerErrorLog() + assertThat(monitor.state).isEqualTo(WorkInfo.State.FAILED) + assertThat(logCount).isEqualTo(0) + assertThat(failureLine).contains("Failed operation: upload_events.") + assertThat(failureLine).contains("java.lang.Exception: Forced failure.") + assertThat(getLatestSyncStatus()).isEqualTo(UPLOAD_ERROR) + } - val workManager = WorkManager.getInstance(ApplicationProvider.getApplicationContext()) + @Test + fun testWorker_scheduleUploadEvents_losesConnectivity_failsAndLogsErrorAndReportsSyncStatus() { + setUpTestApplicationComponent() + initializeDependencies() + forceNetworkConnectivityOff() // For offline caching. + logImportantEvent(oppiaLogger.createOpenHomeContext()) + + // Run the job with connectivity disabled. This is slightly contrived because technically the + // job wouldn't start, but it has the rough effect of simulating connectivity being lost after + // the job kicks off. + val monitor = testDriver.runOneOffWork(WORKER_NAME, UPLOAD_EVENTS) + + // Verify that the job failed and an error was logged due to an underlying failure. + val logCount = fakeAnalyticsEventLogger.getEventListCount() + val failureLine = fetchSingleWorkerErrorLog() + assertThat(monitor.state).isEqualTo(WorkInfo.State.FAILED) + assertThat(logCount).isEqualTo(0) + assertThat(failureLine).contains("Failed operation: upload_events.") + assertThat(failureLine).contains("Cannot upload events without internet connectivity.") + assertThat(getLatestSyncStatus()).isEqualTo(NO_CONNECTIVITY) + } - val inputData = Data.Builder().putString( - LogUploadWorker.WORKER_CASE_KEY, - LogUploadWorker.EVENT_WORKER - ).build() + /* Tests for UPLOAD_EXCEPTIONS. */ - val request: OneTimeWorkRequest = OneTimeWorkRequestBuilder() - .setInputData(inputData) - .build() + @Test + fun testWorker_scheduleUploadExceptions_sanityCheck_doNotRun_noExceptionsUploaded() { + setUpTestApplicationComponent() + initializeDependencies() + forceNetworkConnectivityOff() // For offline caching. - setUpEventLoggerToFail() - workManager.enqueue(request) - testCoroutineDispatchers.runCurrent() - val workInfo = workManager.getWorkInfoById(request.id) + logNonFatalException(testException1) + logFatalException(testException2) + logNonFatalException(testException3) - assertThat(workInfo.get().state).isEqualTo(WorkInfo.State.FAILED) - assertThat(fakeAnalyticsEventLogger.noEventsPresent()).isTrue() + // Sanity check: ensure that logs do not automatically upload without the job running (otherwise + // the other tests in this test suite are moot). + assertThat(fakeExceptionLogger.getExceptionCount()).isEqualTo(0) + expectSyncStatusToBeUnchanged() } @Test - fun testWorker_logException_withoutNetwork_enqueueRequest_verifySuccess() { + fun testWorker_scheduleUploadExceptions_noExceptions_uploadsNothing() { setUpTestApplicationComponent() - networkConnectionUtil.setCurrentConnectionStatus(NONE) - exceptionsController.logNonFatalException(exception, TEST_TIMESTAMP) - testCoroutineDispatchers.runCurrent() + initializeDependencies() - val workManager = WorkManager.getInstance(ApplicationProvider.getApplicationContext()) + forceNetworkConnectivityToCellular() // For the job to run. + val monitor = testDriver.runOneOffWork(WORKER_NAME, UPLOAD_EXCEPTIONS) - val inputData = Data.Builder().putString( - LogUploadWorker.WORKER_CASE_KEY, - LogUploadWorker.EXCEPTION_WORKER - ).build() + // Verify that the job succeeded and there was nothing to upload. Sync status will also update + // since the attempt to upload finished. + val logCount = fakeExceptionLogger.getExceptionCount() + assertThat(monitor.state).isEqualTo(WorkInfo.State.SUCCEEDED) + assertThat(logCount).isEqualTo(0) + expectSyncStatusToBeUnchanged() + } - val request: OneTimeWorkRequest = OneTimeWorkRequestBuilder() - .setInputData(inputData) - .build() - workManager.enqueue(request) - testCoroutineDispatchers.runCurrent() + @Test + fun testWorker_scheduleUploadExceptions_severalExceptions_uploadsAllExceptions() { + setUpTestApplicationComponent() + initializeDependencies() + forceNetworkConnectivityOff() // For offline caching. + logNonFatalException(testException1) + logFatalException(testException2) + logNonFatalException(testException3) + + forceNetworkConnectivityToCellular() // For the job to run. + val monitor = testDriver.runOneOffWork(WORKER_NAME, UPLOAD_EXCEPTIONS) + + // Verify that the job succeeded, was the only job to upload something, and uploaded correctly. + val logCount = fakeExceptionLogger.getExceptionCount() + val exceptions = fakeExceptionLogger.getMostRecentExceptions(3) + assertThat(monitor.state).isEqualTo(WorkInfo.State.SUCCEEDED) + assertThat(logCount).isEqualTo(3) + assertThat(exceptions[0]).hasMessageThat().isEqualTo("Test 1") + assertThat(exceptions[1]).hasMessageThat().isEqualTo("Test 2") + assertThat(exceptions[2]).hasMessageThat().isEqualTo("Test 3") + assertThat(exceptions[0].toComparableTrace()).isEqualTo(testException1.toComparableTrace()) + assertThat(exceptions[1].toComparableTrace()).isEqualTo(testException2.toComparableTrace()) + assertThat(exceptions[2].toComparableTrace()).isEqualTo(testException3.toComparableTrace()) + expectSyncStatusToBeUnchanged() + } - val workInfo = workManager.getWorkInfoById(request.id) - val loggedException = fakeExceptionLogger.getMostRecentException() - val loggedExceptionStackTraceElems = loggedException.stackTrace.extractRelevantDetails() - val expectedExceptionStackTraceElems = exception.stackTrace.extractRelevantDetails() - assertThat(workInfo.get().state).isEqualTo(WorkInfo.State.SUCCEEDED) - assertThat(loggedException.message).isEqualTo("TEST") - assertThat(loggedException.cause).isEqualTo(null) - // The following can't be an exact match for the stack trace since new properties are added to - // stack trace elements in newer versions of Java (such as module name). - assertThat(loggedExceptionStackTraceElems).isEqualTo(expectedExceptionStackTraceElems) + @Test + fun testWorker_scheduleUploadExceptions_severalExceptions_runAgain_uploadsNothing() { + setUpTestApplicationComponent() + initializeDependencies() + forceNetworkConnectivityOff() // For offline caching. + logNonFatalException(testException1) + logFatalException(testException2) + logNonFatalException(testException3) + forceNetworkConnectivityToCellular() // For the job to run. + testDriver.runOneOffWork(WORKER_NAME, UPLOAD_EXCEPTIONS) + fakeExceptionLogger.clearAllExceptions() // Reset for next job. + + // Run a second time. + val monitor = testDriver.runOneOffWork(WORKER_NAME, UPLOAD_EXCEPTIONS) + + // Verify nothing uploaded (since everything should've uploaded during the first job). + val logCount = fakeExceptionLogger.getExceptionCount() + assertThat(monitor.state).isEqualTo(WorkInfo.State.SUCCEEDED) + assertThat(logCount).isEqualTo(0) + expectSyncStatusToBeUnchanged() } @Test - fun testWorker_logPerformanceMetric_withoutNetwork_enqueueRequest_verifySuccess() { + fun testWorker_scheduleUploadExceptions_hasFailure_failsAndLogsError() { setUpTestApplicationComponent() - networkConnectionUtil.setCurrentConnectionStatus(NONE) - performanceMetricsController.logPerformanceMetricsEvent( - TEST_TIMESTAMP, - SCREEN_NAME_UNSPECIFIED, - apkSizeTestLoggableMetric, - OppiaMetricLog.Priority.LOW_PRIORITY - ) - testCoroutineDispatchers.runCurrent() + initializeDependencies() + forceNetworkConnectivityOff() // For offline caching. + logNonFatalException(testException1) + fakeExceptionLogger.setFailure(Exception("Forced failure.")) + + forceNetworkConnectivityToCellular() // For the job to run. + val monitor = testDriver.runOneOffWork(WORKER_NAME, UPLOAD_EXCEPTIONS) + + // Verify that the job failed and an error was logged due to an underlying failure. + val logCount = fakeExceptionLogger.getExceptionCount() + val failureLine = fetchSingleWorkerErrorLog() + assertThat(monitor.state).isEqualTo(WorkInfo.State.FAILED) + assertThat(logCount).isEqualTo(0) + assertThat(failureLine).contains("Failed operation: upload_exceptions.") + assertThat(failureLine).contains("java.lang.Exception: Forced failure.") + expectSyncStatusToBeUnchanged() + } - val workManager = WorkManager.getInstance(ApplicationProvider.getApplicationContext()) + @Test + fun testWorker_scheduleUploadExceptions_losesConnectivity_failsAndLogsError() { + setUpTestApplicationComponent() + initializeDependencies() + forceNetworkConnectivityOff() // For offline caching. + logNonFatalException(testException1) + + // Run the job with connectivity disabled. This is slightly contrived because technically the + // job wouldn't start, but it has the rough effect of simulating connectivity being lost after + // the job kicks off. + val monitor = testDriver.runOneOffWork(WORKER_NAME, UPLOAD_EXCEPTIONS) + + // Verify that the job failed and an error was logged due to an underlying failure. + val logCount = fakeExceptionLogger.getExceptionCount() + val failureLine = fetchSingleWorkerErrorLog() + assertThat(monitor.state).isEqualTo(WorkInfo.State.FAILED) + assertThat(logCount).isEqualTo(0) + assertThat(failureLine).contains("Failed operation: upload_exceptions.") + assertThat(failureLine).contains("Cannot upload exceptions without internet connectivity.") + expectSyncStatusToBeUnchanged() + } - val inputData = Data.Builder().putString( - LogUploadWorker.WORKER_CASE_KEY, - LogUploadWorker.PERFORMANCE_METRICS_WORKER - ).build() + /* Tests for UPLOAD_PERFORMANCE_METRICS. */ - val request: OneTimeWorkRequest = OneTimeWorkRequestBuilder() - .setInputData(inputData) - .build() - workManager.enqueue(request) - testCoroutineDispatchers.runCurrent() + @Test + fun testWorker_scheduleUploadPerfMetrics_sanityCheck_doNotRun_noMetricsUploaded() { + setUpTestApplicationComponent() + initializeDependencies() + forceNetworkConnectivityOff() // For offline caching. - val workInfo = workManager.getWorkInfoById(request.id) - val loggedPerformanceMetric = - fakePerformanceMetricsEventLogger.getMostRecentPerformanceMetricsEvent() - assertThat(workInfo.get().state).isEqualTo(WorkInfo.State.SUCCEEDED) - assertThat(loggedPerformanceMetric.loggableMetric.loggableMetricTypeCase).isEqualTo( - OppiaMetricLog.LoggableMetric.LoggableMetricTypeCase.APK_SIZE_METRIC - ) - assertThat(loggedPerformanceMetric.currentScreen).isEqualTo( - SCREEN_NAME_UNSPECIFIED - ) - assertThat(loggedPerformanceMetric.priority).isEqualTo(OppiaMetricLog.Priority.LOW_PRIORITY) - assertThat(loggedPerformanceMetric.timestampMillis).isEqualTo(TEST_TIMESTAMP) - assertThat(loggedPerformanceMetric.loggableMetric.apkSizeMetric.apkSizeBytes).isEqualTo( - TEST_APK_SIZE - ) + logPerformanceMetric(createStartupLatencyMetric(startMs = 123), HOME_ACTIVITY, HIGH_PRIORITY) + logPerformanceMetric(createApkSizeMetric(sizeBytes = 456), BACKGROUND_SCREEN, LOW_PRIORITY) + logPerformanceMetric(createStorageSizeMetric(sizeBytes = 789), BACKGROUND_SCREEN, LOW_PRIORITY) + + // Sanity check: ensure that logs do not automatically upload without the job running (otherwise + // the other tests in this test suite are moot). + assertThat(fakePerformanceMetricsEventLogger.getPerformanceMetricsEventListCount()).isEqualTo(0) + expectSyncStatusToBeUnchanged() } @Test - fun testWorker_logEvent_withNetwork_enqueueRequest_studyOn_verifySyncStatusesHasSuccess() { - setUpTestApplicationComponent(enableLearnerStudyAnalytics = true) - networkConnectionUtil.setCurrentConnectionStatus(NONE) - analyticsController.logImportantEvent( - oppiaLogger.createOpenInfoTabContext(TEST_TOPIC_ID), - profileId = null, - eventLogTopicContext.timestamp - ) - networkConnectionUtil.setCurrentConnectionStatus(LOCAL) - testCoroutineDispatchers.runCurrent() - - val workManager = WorkManager.getInstance(ApplicationProvider.getApplicationContext()) + fun testWorker_scheduleUploadPerfMetrics_noMetrics_uploadsNothing() { + setUpTestApplicationComponent() + initializeDependencies() - val inputData = Data.Builder().putString( - LogUploadWorker.WORKER_CASE_KEY, - LogUploadWorker.EVENT_WORKER - ).build() + forceNetworkConnectivityToCellular() // For the job to run. + val monitor = testDriver.runOneOffWork(WORKER_NAME, UPLOAD_PERFORMANCE_METRICS) - val request: OneTimeWorkRequest = OneTimeWorkRequestBuilder() - .setInputData(inputData) - .build() + // Verify that the job succeeded and there was nothing to upload. Sync status will also update + // since the attempt to upload finished. + val logCount = fakePerformanceMetricsEventLogger.getPerformanceMetricsEventListCount() + assertThat(monitor.state).isEqualTo(WorkInfo.State.SUCCEEDED) + assertThat(logCount).isEqualTo(0) + expectSyncStatusToBeUnchanged() + } - workManager.enqueue(request) - testCoroutineDispatchers.runCurrent() + @Test + fun testWorker_scheduleUploadPerfMetrics_severalMetrics_uploadsAllMetrics() { + setUpTestApplicationComponent() + initializeDependencies() + forceNetworkConnectivityOff() // For offline caching. + logPerformanceMetric(createStartupLatencyMetric(startMs = 123), HOME_ACTIVITY, HIGH_PRIORITY) + logPerformanceMetric(createApkSizeMetric(sizeBytes = 456), BACKGROUND_SCREEN, LOW_PRIORITY) + logPerformanceMetric(createStorageSizeMetric(sizeBytes = 789), BACKGROUND_SCREEN, LOW_PRIORITY) + + forceNetworkConnectivityToCellular() // For the job to run. + val monitor = testDriver.runOneOffWork(WORKER_NAME, UPLOAD_PERFORMANCE_METRICS) + + // Verify that the job succeeded, was the only job to upload something, and uploaded correctly. + val logCount = fakePerformanceMetricsEventLogger.getPerformanceMetricsEventListCount() + assertThat(monitor.state).isEqualTo(WorkInfo.State.SUCCEEDED) + assertThat(logCount).isEqualTo(3) + val events = fakePerformanceMetricsEventLogger.getMostRecentPerformanceMetricsEvents(3) + val (event1, event2, event3) = events + assertThat(event1.loggableMetric.loggableMetricTypeCase).isEqualTo(STARTUP_LATENCY_METRIC) + assertThat(event1.currentScreen).isEqualTo(HOME_ACTIVITY) + assertThat(event1.priority).isEqualTo(HIGH_PRIORITY) + assertThat(event1.loggableMetric.startupLatencyMetric.startupLatencyMillis).isEqualTo(123) + assertThat(event2.loggableMetric.loggableMetricTypeCase).isEqualTo(APK_SIZE_METRIC) + assertThat(event2.currentScreen).isEqualTo(BACKGROUND_SCREEN) + assertThat(event2.priority).isEqualTo(LOW_PRIORITY) + assertThat(event2.loggableMetric.apkSizeMetric.apkSizeBytes).isEqualTo(456) + assertThat(event3.loggableMetric.loggableMetricTypeCase).isEqualTo(STORAGE_USAGE_METRIC) + assertThat(event3.currentScreen).isEqualTo(BACKGROUND_SCREEN) + assertThat(event3.priority).isEqualTo(LOW_PRIORITY) + assertThat(event3.loggableMetric.storageUsageMetric.storageUsageBytes).isEqualTo(789) + expectSyncStatusToBeUnchanged() + } - val currentStatus = - monitorFactory.waitForNextSuccessfulResult(testSyncStatusManager.getSyncStatus()) - val statusList = testSyncStatusManager.getSyncStatuses() - assertThat(statusList) - .containsAtLeast(INITIAL_UNKNOWN, DATA_UPLOADED, DATA_UPLOADING, DATA_UPLOADED) - .inOrder() - assertThat(currentStatus).isEqualTo(DATA_UPLOADED) + @Test + fun testWorker_scheduleUploadPerfMetrics_severalMetrics_runAgain_uploadsNothing() { + setUpTestApplicationComponent() + initializeDependencies() + forceNetworkConnectivityOff() // For offline caching. + logPerformanceMetric(createStartupLatencyMetric(startMs = 123), HOME_ACTIVITY, HIGH_PRIORITY) + logPerformanceMetric(createApkSizeMetric(sizeBytes = 456), BACKGROUND_SCREEN, LOW_PRIORITY) + logPerformanceMetric(createStorageSizeMetric(sizeBytes = 789), BACKGROUND_SCREEN, LOW_PRIORITY) + forceNetworkConnectivityToCellular() // For the job to run. + testDriver.runOneOffWork(WORKER_NAME, UPLOAD_PERFORMANCE_METRICS) + fakePerformanceMetricsEventLogger.clearAllPerformanceMetricsEvents() // Reset for next job. + + // Run a second time. + val monitor = testDriver.runOneOffWork(WORKER_NAME, UPLOAD_PERFORMANCE_METRICS) + + // Verify nothing uploaded (since everything should've uploaded during the first job). + val logCount = fakePerformanceMetricsEventLogger.getPerformanceMetricsEventListCount() + assertThat(monitor.state).isEqualTo(WorkInfo.State.SUCCEEDED) + assertThat(logCount).isEqualTo(0) + expectSyncStatusToBeUnchanged() } @Test - fun testWorker_logEvent_withoutNetwork_enqueueRequest_studyOn_verifySyncStatusesHasFailed() { - setUpTestApplicationComponent(enableLearnerStudyAnalytics = true) - networkConnectionUtil.setCurrentConnectionStatus(NONE) - analyticsController.logImportantEvent( - oppiaLogger.createOpenInfoTabContext(TEST_TOPIC_ID), - profileId = null, - eventLogTopicContext.timestamp - ) - testCoroutineDispatchers.runCurrent() + fun testWorker_scheduleUploadPerfMetrics_hasFailure_failsAndLogsError() { + setUpTestApplicationComponent() + initializeDependencies() + forceNetworkConnectivityOff() // For offline caching. + logPerformanceMetric(createStartupLatencyMetric(startMs = 123), HOME_ACTIVITY, HIGH_PRIORITY) + fakePerformanceMetricsEventLogger.setFailure(Exception("Forced failure.")) + + forceNetworkConnectivityToCellular() // For the job to run. + val monitor = testDriver.runOneOffWork(WORKER_NAME, UPLOAD_PERFORMANCE_METRICS) + + // Verify that the job failed and an error was logged due to an underlying failure. + val logCount = fakePerformanceMetricsEventLogger.getPerformanceMetricsEventListCount() + val failureLine = fetchSingleWorkerErrorLog() + assertThat(monitor.state).isEqualTo(WorkInfo.State.FAILED) + assertThat(logCount).isEqualTo(0) + assertThat(failureLine).contains("Failed operation: upload_performance_metrics.") + assertThat(failureLine).contains("java.lang.Exception: Forced failure.") + expectSyncStatusToBeUnchanged() + } - val workManager = WorkManager.getInstance(ApplicationProvider.getApplicationContext()) + @Test + fun testWorker_scheduleUploadPerfMetrics_losesConnectivity_failsAndLogsError() { + setUpTestApplicationComponent() + initializeDependencies() + forceNetworkConnectivityOff() // For offline caching. + logPerformanceMetric(createStartupLatencyMetric(startMs = 123), HOME_ACTIVITY, HIGH_PRIORITY) + + // Run the job with connectivity disabled. This is slightly contrived because technically the + // job wouldn't start, but it has the rough effect of simulating connectivity being lost after + // the job kicks off. + val monitor = testDriver.runOneOffWork(WORKER_NAME, UPLOAD_PERFORMANCE_METRICS) + + // Verify that the job failed and an error was logged due to an underlying failure. + val logCount = fakePerformanceMetricsEventLogger.getPerformanceMetricsEventListCount() + val failure = fetchSingleWorkerErrorLog() + assertThat(monitor.state).isEqualTo(WorkInfo.State.FAILED) + assertThat(logCount).isEqualTo(0) + assertThat(failure).contains("Failed operation: upload_performance_metrics.") + assertThat(failure).contains("Cannot upload performance metrics without internet connectivity.") + expectSyncStatusToBeUnchanged() + } - val inputData = Data.Builder().putString( - LogUploadWorker.WORKER_CASE_KEY, - LogUploadWorker.EVENT_WORKER - ).build() + /* Tests for UPLOAD_FIRESTORE_DATA. */ - val request: OneTimeWorkRequest = OneTimeWorkRequestBuilder() - .setInputData(inputData) - .build() + @Test + fun testWorker_scheduleUploadFirestoreData_sanityCheck_doNotRun_noEventsUploaded() { + setUpTestApplicationComponent() + initializeDependencies() + forceNetworkConnectivityOff() // For offline caching. - workManager.enqueue(request) - testCoroutineDispatchers.runCurrent() + logFirestoreEvent(createOptionalSurveyResponseContext("feedback 1", "test_survey_id1")) + logFirestoreEvent(createOptionalSurveyResponseContext("feedback 2", "test_survey_id2")) + logFirestoreEvent(createOptionalSurveyResponseContext("feedback 3", "test_survey_id3")) - val currentStatus = - monitorFactory.waitForNextSuccessfulResult(testSyncStatusManager.getSyncStatus()) - val statusList = testSyncStatusManager.getSyncStatuses() - // The operation should fail since there's no internet connectivity with which to upload the - // events. It's not valid to try. - assertThat(statusList) - .containsAtLeast(INITIAL_UNKNOWN, DATA_UPLOADING, UPLOAD_ERROR, NO_CONNECTIVITY) - .inOrder() - assertThat(currentStatus).isEqualTo(NO_CONNECTIVITY) + // Sanity check: ensure that logs do not automatically upload without the job running (otherwise + // the other tests in this test suite are moot). + assertThat(fakeFirestoreEventLogger.getEventListCount()).isEqualTo(0) + expectSyncStatusToBeUnchanged() } @Test - fun testWorker_logEvent_noNetwork_enqueueRequest_writeFails_studyOn_verifyHasFailedSyncStatus() { - setUpTestApplicationComponent(enableLearnerStudyAnalytics = true) - networkConnectionUtil.setCurrentConnectionStatus(NONE) - analyticsController.logImportantEvent( - oppiaLogger.createOpenInfoTabContext(TEST_TOPIC_ID), - profileId = null, - eventLogTopicContext.timestamp - ) - testCoroutineDispatchers.runCurrent() - - val workManager = WorkManager.getInstance(ApplicationProvider.getApplicationContext()) + fun testWorker_scheduleUploadFirestoreData_noEvents_uploadsNothing() { + setUpTestApplicationComponent() + initializeDependencies() - val inputData = Data.Builder().putString( - LogUploadWorker.WORKER_CASE_KEY, - LogUploadWorker.EVENT_WORKER - ).build() + forceNetworkConnectivityToCellular() // For the job to run. + val monitor = testDriver.runOneOffWork(WORKER_NAME, UPLOAD_FIRESTORE_DATA) - val request: OneTimeWorkRequest = OneTimeWorkRequestBuilder() - .setInputData(inputData) - .build() + // Verify that the job succeeded and there was nothing to upload. Sync status will also update + // since the attempt to upload finished. + val logCount = fakeFirestoreEventLogger.getEventListCount() + assertThat(monitor.state).isEqualTo(WorkInfo.State.SUCCEEDED) + assertThat(logCount).isEqualTo(0) + expectSyncStatusToBeUnchanged() + } - setUpEventLoggerToFail() - workManager.enqueue(request) - testCoroutineDispatchers.runCurrent() + @Test + fun testWorker_scheduleUploadFirestoreData_severalEvents_uploadsAllEvents() { + setUpTestApplicationComponent() + initializeDependencies() + forceNetworkConnectivityOff() // For offline caching. + logFirestoreEvent(createOptionalSurveyResponseContext("feedback 1", "test_survey_id1")) + logFirestoreEvent(createOptionalSurveyResponseContext("feedback 2", "test_survey_id2")) + logFirestoreEvent(createOptionalSurveyResponseContext("feedback 3", "test_survey_id3")) + + forceNetworkConnectivityToCellular() // For the job to run. + val monitor = testDriver.runOneOffWork(WORKER_NAME, UPLOAD_FIRESTORE_DATA) + + // Verify that the job succeeded, was the only job to upload something, and uploaded correctly. + val logCount = fakeFirestoreEventLogger.getEventListCount() + val events = fakeFirestoreEventLogger.getMostRecentEvents(3) + assertThat(monitor.state).isEqualTo(WorkInfo.State.SUCCEEDED) + assertThat(logCount).isEqualTo(3) + val (event1, event2, event3) = events + assertThat(event1.priority).isEqualTo(EventLog.Priority.ESSENTIAL) + assertThat(event1.context.activityContextCase).isEqualTo(OPTIONAL_RESPONSE) + assertThat(event1.context.optionalResponse.feedbackAnswer).isEqualTo("feedback 1") + assertThat(event1.context.optionalResponse.surveyDetails.surveyId).isEqualTo("test_survey_id1") + assertThat(event2.priority).isEqualTo(EventLog.Priority.ESSENTIAL) + assertThat(event2.context.activityContextCase).isEqualTo(OPTIONAL_RESPONSE) + assertThat(event2.context.optionalResponse.feedbackAnswer).isEqualTo("feedback 2") + assertThat(event2.context.optionalResponse.surveyDetails.surveyId).isEqualTo("test_survey_id2") + assertThat(event3.priority).isEqualTo(EventLog.Priority.ESSENTIAL) + assertThat(event3.context.activityContextCase).isEqualTo(OPTIONAL_RESPONSE) + assertThat(event3.context.optionalResponse.feedbackAnswer).isEqualTo("feedback 3") + assertThat(event3.context.optionalResponse.surveyDetails.surveyId).isEqualTo("test_survey_id3") + expectSyncStatusToBeUnchanged() + } - // Note that "no connectivity" is the last item because it takes priority over an error. - val statusList = testSyncStatusManager.getSyncStatuses() - val currentStatus = - monitorFactory.waitForNextSuccessfulResult(testSyncStatusManager.getSyncStatus()) - assertThat(statusList) - .containsAtLeast(INITIAL_UNKNOWN, DATA_UPLOADING, UPLOAD_ERROR, NO_CONNECTIVITY) - .inOrder() - assertThat(currentStatus).isEqualTo(NO_CONNECTIVITY) + @Test + fun testWorker_scheduleUploadFirestoreData_severalEvents_runAgain_uploadsNothing() { + setUpTestApplicationComponent() + initializeDependencies() + forceNetworkConnectivityOff() // For offline caching. + logFirestoreEvent(createOptionalSurveyResponseContext("feedback 1", "test_survey_id1")) + logFirestoreEvent(createOptionalSurveyResponseContext("feedback 2", "test_survey_id2")) + logFirestoreEvent(createOptionalSurveyResponseContext("feedback 3", "test_survey_id3")) + forceNetworkConnectivityToCellular() // For the job to run. + testDriver.runOneOffWork(WORKER_NAME, UPLOAD_FIRESTORE_DATA) + fakeFirestoreEventLogger.clearAllEvents() // Reset for next job. + + // Run a second time. + val monitor = testDriver.runOneOffWork(WORKER_NAME, UPLOAD_FIRESTORE_DATA) + + // Verify nothing uploaded (since everything should've uploaded during the first job). + val logCount = fakeFirestoreEventLogger.getEventListCount() + assertThat(monitor.state).isEqualTo(WorkInfo.State.SUCCEEDED) + assertThat(logCount).isEqualTo(0) + expectSyncStatusToBeUnchanged() } @Test - fun testWorker_logFirestoreEvent_withNetwork_enqueueRequest_verifySuccess() { + fun testWorker_scheduleUploadFirestoreData_hasFailure_failsAndLogsError() { setUpTestApplicationComponent() - networkConnectionUtil.setCurrentConnectionStatus(NONE) - dataController.logEvent( - createOptionalSurveyResponseContext(), - profileId = null, - 1556094120000 - ) - networkConnectionUtil.setCurrentConnectionStatus(LOCAL) - testCoroutineDispatchers.runCurrent() + initializeDependencies() + forceNetworkConnectivityOff() // For offline caching. + logFirestoreEvent(createOptionalSurveyResponseContext("feedback 1", "test_survey_id1")) + fakeFirestoreEventLogger.setFailure(Exception("Forced failure.")) + + forceNetworkConnectivityToCellular() // For the job to run. + val monitor = testDriver.runOneOffWork(WORKER_NAME, UPLOAD_FIRESTORE_DATA) + + // Verify that the job failed and an error was logged due to an underlying failure. + val logCount = fakeFirestoreEventLogger.getEventListCount() + val failureLine = fetchSingleWorkerErrorLog() + assertThat(monitor.state).isEqualTo(WorkInfo.State.FAILED) + assertThat(logCount).isEqualTo(0) + assertThat(failureLine).contains("Failed operation: upload_firestore_data.") + assertThat(failureLine).contains("java.lang.Exception: Forced failure.") + expectSyncStatusToBeUnchanged() + } - val workManager = WorkManager.getInstance(ApplicationProvider.getApplicationContext()) + @Test + fun testWorker_scheduleUploadFirestoreData_losesConnectivity_failsAndLogsError() { + setUpTestApplicationComponent() + initializeDependencies() + forceNetworkConnectivityOff() // For offline caching. + logFirestoreEvent(createOptionalSurveyResponseContext("feedback 1", "test_survey_id1")) + + // Run the job with connectivity disabled. This is slightly contrived because technically the + // job wouldn't start, but it has the rough effect of simulating connectivity being lost after + // the job kicks off. + val monitor = testDriver.runOneOffWork(WORKER_NAME, UPLOAD_FIRESTORE_DATA) + + // Verify that the job failed and an error was logged due to an underlying failure. + val logCount = fakeFirestoreEventLogger.getEventListCount() + val failLine = fetchSingleWorkerErrorLog() + assertThat(monitor.state).isEqualTo(WorkInfo.State.FAILED) + assertThat(logCount).isEqualTo(0) + assertThat(failLine).contains("Failed operation: upload_firestore_data.") + assertThat(failLine).contains("Cannot upload Firestore events without internet connectivity.") + expectSyncStatusToBeUnchanged() + } - val inputData = Data.Builder().putString( - LogUploadWorker.WORKER_CASE_KEY, - LogUploadWorker.FIRESTORE_WORKER - ).build() + private fun forceNetworkConnectivityOff() { + networkConnectionUtil.setCurrentConnectionStatus(ProdConnectionStatus.NONE) + } - val request: OneTimeWorkRequest = OneTimeWorkRequestBuilder() - .setInputData(inputData) - .build() + private fun forceNetworkConnectivityToCellular() { + networkConnectionUtil.setCurrentConnectionStatus(ProdConnectionStatus.CELLULAR) + } - workManager.enqueue(request) + private fun logImportantEvent(eventContext: EventLog.Context) { + analyticsController.logImportantEvent(eventContext, profileId = null) testCoroutineDispatchers.runCurrent() - val workInfo = workManager.getWorkInfoById(request.id) - - assertThat(workInfo.get().state).isEqualTo(WorkInfo.State.SUCCEEDED) - assertThat(fakeFirestoreEventLogger.getMostRecentEvent()).isEqualTo( - optionalSurveyResponseEventLog - ) } - @Test - fun testWorker_logFirestoreEvent_withoutNetwork_enqueueRequest_writeFails_verifyFailure() { - setUpTestApplicationComponent() - networkConnectionUtil.setCurrentConnectionStatus(NONE) - dataController.logEvent( - createOptionalSurveyResponseContext(), - profileId = null, - 1556094120000 - ) + private fun logLowPriorityEvent(eventContext: EventLog.Context) { + analyticsController.logLowPriorityEvent(eventContext, profileId = null) testCoroutineDispatchers.runCurrent() + } - val workManager = WorkManager.getInstance(ApplicationProvider.getApplicationContext()) + private fun logFatalException(exception: Exception) { + exceptionsController.logFatalException(exception) + testCoroutineDispatchers.runCurrent() + } - val inputData = Data.Builder().putString( - LogUploadWorker.WORKER_CASE_KEY, - LogUploadWorker.FIRESTORE_WORKER - ).build() + private fun logNonFatalException(exception: Exception) { + exceptionsController.logNonFatalException(exception) + testCoroutineDispatchers.runCurrent() + } - val request: OneTimeWorkRequest = OneTimeWorkRequestBuilder() - .setInputData(inputData) - .build() + private fun logPerformanceMetric( + loggableMetric: LoggableMetric, + screenName: ScreenName, + priority: Priority + ) { + performanceMetricsController.logPerformanceMetricsEvent( + oppiaClock.getCurrentTimeMs(), screenName, loggableMetric, priority + ) + testCoroutineDispatchers.runCurrent() + } - setUpFirestoreEventLoggerToFail() - workManager.enqueue(request) + private fun logFirestoreEvent(eventLogContext: EventLog.Context) { + firestoreDataController.logEvent(eventLogContext, profileId = null) testCoroutineDispatchers.runCurrent() - val workInfo = workManager.getWorkInfoById(request.id) + } - assertThat(workInfo.get().state).isEqualTo(WorkInfo.State.FAILED) - assertThat(fakeFirestoreEventLogger.noEventsPresent()).isTrue() + private fun createStartupLatencyMetric(startMs: Long): LoggableMetric { + return LoggableMetric.newBuilder().apply { + startupLatencyMetric = OppiaMetricLog.StartupLatencyMetric.newBuilder().apply { + startupLatencyMillis = startMs + }.build() + }.build() } - private val optionalSurveyResponseEventLog = EventLog.newBuilder().apply { - this.context = createOptionalSurveyResponseContext() - this.timestamp = TEST_TIMESTAMP - this.priority = EventLog.Priority.ESSENTIAL + private fun createApkSizeMetric(sizeBytes: Long): LoggableMetric { + return LoggableMetric.newBuilder().apply { + apkSizeMetric = OppiaMetricLog.ApkSizeMetric.newBuilder().apply { + apkSizeBytes = sizeBytes + }.build() + }.build() } - .build() - private fun createOptionalSurveyResponseContext(): EventLog.Context { - return EventLog.Context.newBuilder() - .setOptionalResponse( - EventLog.OptionalSurveyResponseContext.newBuilder() - .setFeedbackAnswer("answer") - .setSurveyDetails( - createSurveyResponseContext() - ) - ) - .build() + private fun createStorageSizeMetric(sizeBytes: Long): LoggableMetric { + return LoggableMetric.newBuilder().apply { + storageUsageMetric = OppiaMetricLog.StorageUsageMetric.newBuilder().apply { + storageUsageBytes = sizeBytes + }.build() + }.build() + } + + private fun createOptionalSurveyResponseContext( + feedback: String, + surveyId: String + ): EventLog.Context { + return EventLog.Context.newBuilder().apply { + optionalResponse = EventLog.OptionalSurveyResponseContext.newBuilder().apply { + feedbackAnswer = feedback + surveyDetails = EventLog.SurveyResponseContext.newBuilder().apply { + this.surveyId = surveyId + }.build() + }.build() + }.build() } - private fun createSurveyResponseContext(): EventLog.SurveyResponseContext { - return EventLog.SurveyResponseContext.newBuilder() - .setSurveyId("test_survey_id") - .build() + private fun fetchSingleWorkerErrorLog(): String { + val errorLogs = fetchWorkerErrorLogs() + assertThat(errorLogs).hasSize(1) + return errorLogs.single() } - private fun setUpEventLoggerToFail() { - // Simulate the log attempt itself failing during the job. Note that the reset is necessary here - // to remove the default stubbing for the mock so that it can properly trigger a failure. - reset(mockAnalyticsEventLogger) - `when`(mockAnalyticsEventLogger.logEvent(anyOrNull())) - .thenThrow(IllegalStateException("Failure.")) + private fun fetchWorkerErrorLogs(): List { + // Extract all logs from the bootstrap worker and validate they are each errors before returning + // the logged message lines. + return ShadowLog.getLogs().filter { it.tag == WORKER_NAME }.onEach { + assertThat(it.type).isEqualTo(Log.ERROR) + }.map(ShadowLog.LogItem::msg) } - private fun setUpFirestoreEventLoggerToFail() { - // Simulate the log attempt itself failing during the job. Note that the reset is necessary here - // to remove the default stubbing for the mock so that it can properly trigger a failure. - reset(mockFirestoreEventLogger) - `when`(mockFirestoreEventLogger.uploadEvent(anyOrNull())) - .thenThrow(IllegalStateException("Failure.")) + private fun expectSyncStatusToBeUnchanged() { + // This is useful for tests that should never be changing sync status (basically everything + // except upload events). + assertThat(getLatestSyncStatus()).isEqualTo(INITIAL_UNKNOWN) } + private fun getLatestSyncStatus(): SyncStatus = testSyncStatusManager.getSyncStatuses().last() + /** * Returns a list of lists of each relevant element of a [StackTraceElement] to be used for * comparison in a way that's consistent across JDK versions. */ - private fun Array.extractRelevantDetails(): List> = - map { elem -> listOf(elem.fileName, elem.methodName, elem.lineNumber, elem.className) } + private fun Exception.toComparableTrace(): List { + return stackTrace.map { + StackTraceElement( + it.fileName ?: "", // Match ExceptionsController behavior when a null file name happens. + checkNotNull(it.methodName), + checkNotNull(it.lineNumber), + checkNotNull(it.className) + ) + } + } - private fun setUpTestApplicationComponent(enableLearnerStudyAnalytics: Boolean = false) { - TestPlatformParameterModule.forceEnableLearnerStudyAnalytics(enableLearnerStudyAnalytics) - ApplicationProvider.getApplicationContext().inject(this) - context = InstrumentationRegistry.getInstrumentation().targetContext - val config = Configuration.Builder() - .setExecutor(SynchronousExecutor()) - .setWorkerFactory(logUploadWorkerFactory) - .build() - WorkManagerTestInitHelper.initializeTestWorkManager(context, config) + private fun initializeDependencies() { + FirebaseApp.initializeApp(context) + testDriver.initializeWorkManager(configuration) } - @Qualifier - annotation class MockEventLogger + private fun setUpTestApplicationComponent() { + ApplicationProvider.getApplicationContext().inject(this) + } - @Qualifier - annotation class MockFirestoreEventLogger + private data class StackTraceElement( + val fileName: String, + val methodName: String, + val lineNumber: Int, + val className: String + ) // TODO(#89): Move this to a common test application component. @Module class TestModule { @Provides fun provideContext(application: Application): Context = application - - @Provides - @Singleton - @MockEventLogger - fun bindMockEventLogger(fakeAnalyticsLogger: FakeAnalyticsEventLogger): AnalyticsEventLogger { - return mock(AnalyticsEventLogger::class.java).also { - `when`(it.logEvent(anyOrNull())).then { answer -> - fakeAnalyticsLogger.logEvent( - answer.getArgument(/* index= */ 0, /* clazz= */ EventLog::class.java) - ) - return@then null - } - } - } - - @Provides - @Singleton - @MockFirestoreEventLogger - fun bindMockFirestoreEventLogger(fakeFirestoreLogger: FakeFirestoreEventLogger): - FirestoreEventLogger { - return mock(FirestoreEventLogger::class.java).also { - `when`(it.uploadEvent(anyOrNull())).then { answer -> - fakeFirestoreLogger.uploadEvent( - answer.getArgument(/* index= */ 0, /* clazz= */ EventLog::class.java) - ) - return@then null - } - } - } - - @Provides - fun bindFakeEventLogger(@MockEventLogger delegate: AnalyticsEventLogger): - AnalyticsEventLogger = delegate - - @Provides - fun bindFakeExceptionLogger(fakeLogger: FakeExceptionLogger): ExceptionLogger = fakeLogger - - @Provides - fun bindFakePerformanceMetricsLogger( - fakePerformanceMetricsEventLogger: FakePerformanceMetricsEventLogger - ): PerformanceMetricsEventLogger = fakePerformanceMetricsEventLogger - - @Provides - fun bindFakeFirestoreEventLogger( - @MockFirestoreEventLogger delegate: FirestoreEventLogger - ): FirestoreEventLogger = delegate - } - - @Module - class TestLogStorageModule { - - @Provides - @EventLogStorageCacheSize - fun provideEventLogStorageCacheSize(): Int = 2 - - @Provides - @ExceptionLogStorageCacheSize - fun provideExceptionLogStorageSize(): Int = 2 - - @Provides - @PerformanceMetricsLogStorageCacheSize - fun providePerformanceMetricsLogStorageCacheSize(): Int = 2 - - @Provides - @FirestoreLogStorageCacheSize - fun provideFirestoreLogStorageCacheSize(): Int = 2 - } - - @Module - interface TestFirebaseLogUploaderModule { - - @Binds - fun bindsFakeLogUploader(fakeLogUploader: FakeLogUploader): LogUploader } // TODO(#89): Move this to a common test application component. @@ -638,20 +796,22 @@ class LogUploadWorkerTest { LoggerModule::class, LoggingIdentifierModule::class, NetworkConnectionUtilDebugModule::class, - PerformanceMetricsAssessorModule::class, PerformanceMetricsConfigurationsModule::class, PlatformParameterSingletonModule::class, RobolectricModule::class, SyncStatusTestModule::class, TestAuthenticationModule::class, TestDispatcherModule::class, - TestFirebaseLogUploaderModule::class, - TestLogStorageModule::class, TestModule::class, - TestPlatformParameterModule::class + TestPlatformParameterModule::class, + CpuPerformanceSnapshotterModule::class, + LogStorageModule::class, + TestLogReportingModule::class, + WorkManagerConfigurationModule::class ] ) - interface TestApplicationComponent : DataProvidersInjector { + interface TestApplicationComponent : + DataProvidersInjector, DispatcherInjector, PlatformParameterControllerInjector { @Component.Builder interface Builder { @BindsInstance @@ -662,7 +822,11 @@ class LogUploadWorkerTest { fun inject(logUploadWorkerTest: LogUploadWorkerTest) } - class TestApplication : Application(), DataProvidersInjectorProvider { + class TestApplication : + Application(), + DataProvidersInjectorProvider, + DispatcherInjectorProvider, + PlatformParameterControllerInjectorProvider { private val component: TestApplicationComponent by lazy { DaggerLogUploadWorkerTest_TestApplicationComponent.builder() .setApplication(this) @@ -673,6 +837,8 @@ class LogUploadWorkerTest { component.inject(logUploadWorkerTest) } - override fun getDataProvidersInjector(): DataProvidersInjector = component + override fun getDataProvidersInjector() = component + override fun getDispatcherInjector() = component + override fun getPlatformParameterControllerInjector() = component } } diff --git a/domain/src/test/java/org/oppia/android/domain/platformparameter/syncup/BUILD.bazel b/domain/src/test/java/org/oppia/android/domain/platformparameter/syncup/BUILD.bazel new file mode 100644 index 00000000000..530e3cb0673 --- /dev/null +++ b/domain/src/test/java/org/oppia/android/domain/platformparameter/syncup/BUILD.bazel @@ -0,0 +1,128 @@ +""" +Tests for library that provides support for downloading platform parameter states from Oppia web. +""" + +load("//:oppia_android_test.bzl", "oppia_android_test") + +oppia_android_test( + name = "PlatformParameterSyncUpWorkerModuleTest", + srcs = ["PlatformParameterSyncUpWorkerModuleTest.kt"], + custom_package = "org.oppia.android.domain.platformparameter.syncup", + test_class = "org.oppia.android.domain.platformparameter.syncup.PlatformParameterSyncUpWorkerModuleTest", + test_manifest = "//domain:test_manifest", + deps = [ + "//:dagger", + "//domain/src/main/java/org/oppia/android/domain/oppialogger:prod_module", + "//domain/src/main/java/org/oppia/android/domain/oppialogger/analytics:cpu_module", + "//domain/src/main/java/org/oppia/android/domain/oppialogger/analytics:prod_module", + "//domain/src/main/java/org/oppia/android/domain/platformparameter:prod_module", + "//domain/src/main/java/org/oppia/android/domain/platformparameter/syncup", + "//testing", + "//testing/src/main/java/org/oppia/android/testing/firebase:test_authentication_module", + "//testing/src/main/java/org/oppia/android/testing/logging:sync_status_test_module", + "//testing/src/main/java/org/oppia/android/testing/platformparameter:test_module", + "//testing/src/main/java/org/oppia/android/testing/robolectric:test_module", + "//testing/src/main/java/org/oppia/android/testing/threading:test_module", + "//testing/src/main/java/org/oppia/android/testing/time:test_module", + "//third_party:androidx_test_ext_junit", + "//third_party:com_google_truth_truth", + "//third_party:junit_junit", + "//third_party:org_robolectric_robolectric", + "//third_party:robolectric_android-all", + "//utility/src/main/java/org/oppia/android/util/caching:asset_prod_module", + "//utility/src/main/java/org/oppia/android/util/locale:prod_module", + "//utility/src/main/java/org/oppia/android/util/logging:prod_module", + "//utility/src/main/java/org/oppia/android/util/logging/firebase:debug_module", + "//utility/src/main/java/org/oppia/android/util/logging/performancemetrics:performance_metrics_configurations_module", + "//utility/src/main/java/org/oppia/android/util/networking:debug_module", + ], +) + +oppia_android_test( + name = "PlatformParameterSyncUpWorkerSchedulerTest", + srcs = ["PlatformParameterSyncUpWorkerSchedulerTest.kt"], + custom_package = "org.oppia.android.domain.platformparameter.syncup", + test_class = "org.oppia.android.domain.platformparameter.syncup.PlatformParameterSyncUpWorkerSchedulerTest", + test_manifest = "//domain:test_manifest", + deps = [ + "//:dagger", + "//data/src/main/java/org/oppia/android/data/backends/gae:retrofit_module", + "//data/src/main/java/org/oppia/android/data/backends/gae/testing:network_config_test_module", + "//domain/src/main/java/org/oppia/android/domain/oppialogger:prod_module", + "//domain/src/main/java/org/oppia/android/domain/oppialogger/analytics:cpu_module", + "//domain/src/main/java/org/oppia/android/domain/oppialogger/analytics:prod_module", + "//domain/src/main/java/org/oppia/android/domain/platformparameter:prod_module", + "//domain/src/main/java/org/oppia/android/domain/platformparameter/syncup", + "//domain/src/main/java/org/oppia/android/domain/workmanager:oppia_worker", + "//domain/src/main/java/org/oppia/android/domain/workmanager:work_manager_scheduler", + "//domain/src/main/java/org/oppia/android/domain/workmanager/testing:oppia_work_manager_test_driver", + "//testing", + "//testing/src/main/java/org/oppia/android/testing/firebase:test_authentication_module", + "//testing/src/main/java/org/oppia/android/testing/logging:sync_status_test_module", + "//testing/src/main/java/org/oppia/android/testing/platformparameter:test_module", + "//testing/src/main/java/org/oppia/android/testing/robolectric:test_module", + "//testing/src/main/java/org/oppia/android/testing/threading:test_coroutine_dispatchers", + "//testing/src/main/java/org/oppia/android/testing/threading:test_module", + "//testing/src/main/java/org/oppia/android/testing/time:test_module", + "//third_party:androidx_test_ext_junit", + "//third_party:androidx_work_work-runtime", + "//third_party:com_google_truth_truth", + "//third_party:junit_junit", + "//third_party:org_robolectric_robolectric", + "//third_party:robolectric_android-all", + "//utility/src/main/java/org/oppia/android/util/caching:asset_prod_module", + "//utility/src/main/java/org/oppia/android/util/locale:prod_module", + "//utility/src/main/java/org/oppia/android/util/logging:prod_module", + "//utility/src/main/java/org/oppia/android/util/logging/firebase:debug_module", + "//utility/src/main/java/org/oppia/android/util/logging/performancemetrics:performance_metrics_configurations_module", + "//utility/src/main/java/org/oppia/android/util/networking:debug_module", + "//utility/src/main/java/org/oppia/android/util/networking:debug_util_module", + ], +) + +oppia_android_test( + name = "PlatformParameterSyncUpWorkerTest", + srcs = ["PlatformParameterSyncUpWorkerTest.kt"], + custom_package = "org.oppia.android.domain.platformparameter.syncup", + test_class = "org.oppia.android.domain.platformparameter.syncup.PlatformParameterSyncUpWorkerTest", + test_manifest = "//domain:test_manifest", + deps = [ + "//:dagger", + "//data/src/main/java/org/oppia/android/data/backends/gae:retrofit_module", + "//data/src/main/java/org/oppia/android/data/backends/gae/testing:network_config_test_module", + "//data/src/main/java/org/oppia/android/data/backends/gae/testing:platform_parameter_service_test_orchestrator", + "//domain/src/main/java/org/oppia/android/domain/oppialogger:prod_module", + "//domain/src/main/java/org/oppia/android/domain/oppialogger/analytics:cpu_module", + "//domain/src/main/java/org/oppia/android/domain/oppialogger/analytics:prod_module", + "//domain/src/main/java/org/oppia/android/domain/oppialogger/loguploader:worker", + "//domain/src/main/java/org/oppia/android/domain/oppialogger/loguploader:worker_module", + "//domain/src/main/java/org/oppia/android/domain/platformparameter:prod_module", + "//domain/src/main/java/org/oppia/android/domain/platformparameter/syncup", + "//domain/src/main/java/org/oppia/android/domain/workmanager:oppia_worker", + "//domain/src/main/java/org/oppia/android/domain/workmanager:work_manager_configuration_module", + "//domain/src/main/java/org/oppia/android/domain/workmanager:work_manager_scheduler", + "//domain/src/main/java/org/oppia/android/domain/workmanager/testing:oppia_work_manager_test_driver", + "//testing", + "//testing/src/main/java/org/oppia/android/testing/firebase:test_authentication_module", + "//testing/src/main/java/org/oppia/android/testing/logging:sync_status_test_module", + "//testing/src/main/java/org/oppia/android/testing/platformparameter:test_module", + "//testing/src/main/java/org/oppia/android/testing/robolectric:test_module", + "//testing/src/main/java/org/oppia/android/testing/threading:test_coroutine_dispatchers", + "//testing/src/main/java/org/oppia/android/testing/threading:test_module", + "//testing/src/main/java/org/oppia/android/testing/time:test_module", + "//third_party:androidx_test_ext_junit", + "//third_party:androidx_work_work-runtime", + "//third_party:com_google_truth_truth", + "//third_party:junit_junit", + "//third_party:org_robolectric_robolectric", + "//third_party:robolectric_android-all", + "//utility/src/main/java/org/oppia/android/util/caching:asset_prod_module", + "//utility/src/main/java/org/oppia/android/util/locale:prod_module", + "//utility/src/main/java/org/oppia/android/util/logging:prod_module", + "//utility/src/main/java/org/oppia/android/util/logging/firebase:debug_module", + "//utility/src/main/java/org/oppia/android/util/logging/performancemetrics:performance_metrics_assessor_module", + "//utility/src/main/java/org/oppia/android/util/logging/performancemetrics:performance_metrics_configurations_module", + "//utility/src/main/java/org/oppia/android/util/networking:debug_module", + "//utility/src/main/java/org/oppia/android/util/networking:debug_util_module", + ], +) diff --git a/domain/src/test/java/org/oppia/android/domain/platformparameter/syncup/PlatformParameterSyncUpWorkManagerInitializerTest.kt b/domain/src/test/java/org/oppia/android/domain/platformparameter/syncup/PlatformParameterSyncUpWorkManagerInitializerTest.kt deleted file mode 100644 index b9598206c8f..00000000000 --- a/domain/src/test/java/org/oppia/android/domain/platformparameter/syncup/PlatformParameterSyncUpWorkManagerInitializerTest.kt +++ /dev/null @@ -1,197 +0,0 @@ -package org.oppia.android.domain.platformparameter.syncup - -import android.app.Application -import android.content.Context -import androidx.test.core.app.ApplicationProvider -import androidx.test.ext.junit.runners.AndroidJUnit4 -import androidx.work.Configuration -import androidx.work.Constraints -import androidx.work.Data -import androidx.work.NetworkType -import androidx.work.WorkManager -import androidx.work.testing.SynchronousExecutor -import androidx.work.testing.WorkManagerTestInitHelper -import com.google.common.truth.Truth.assertThat -import dagger.BindsInstance -import dagger.Component -import dagger.Module -import dagger.Provides -import org.junit.Before -import org.junit.Test -import org.junit.runner.RunWith -import org.oppia.android.data.backends.gae.RetrofitModule -import org.oppia.android.data.backends.gae.RetrofitServiceModule -import org.oppia.android.data.backends.gae.testing.NetworkConfigTestModule -import org.oppia.android.domain.oppialogger.LogStorageModule -import org.oppia.android.domain.oppialogger.LoggingIdentifierModule -import org.oppia.android.domain.oppialogger.analytics.ApplicationLifecycleModule -import org.oppia.android.domain.platformparameter.PlatformParameterSingletonModule -import org.oppia.android.testing.TestLogReportingModule -import org.oppia.android.testing.platformparameter.TestPlatformParameterModule -import org.oppia.android.testing.robolectric.RobolectricModule -import org.oppia.android.testing.threading.TestCoroutineDispatchers -import org.oppia.android.testing.threading.TestDispatcherModule -import org.oppia.android.testing.time.FakeOppiaClockModule -import org.oppia.android.util.caching.AssetModule -import org.oppia.android.util.locale.LocaleProdModule -import org.oppia.android.util.logging.EnableConsoleLog -import org.oppia.android.util.logging.EnableFileLog -import org.oppia.android.util.logging.GlobalLogLevel -import org.oppia.android.util.logging.LogLevel -import org.oppia.android.util.logging.SyncStatusModule -import org.oppia.android.util.networking.NetworkConnectionDebugUtilModule -import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule -import org.oppia.android.util.platformparameter.SYNC_UP_WORKER_TIME_PERIOD_IN_HOURS_DEFAULT_VALUE -import org.robolectric.annotation.Config -import org.robolectric.annotation.LooperMode -import java.util.concurrent.TimeUnit -import javax.inject.Inject -import javax.inject.Singleton - -/** Tests for [PlatformParameterSyncUpWorkManagerInitializer]. */ -@RunWith(AndroidJUnit4::class) -@LooperMode(LooperMode.Mode.PAUSED) -@Config(manifest = Config.NONE) -class PlatformParameterSyncUpWorkManagerInitializerTest { - - @Inject - lateinit var syncUpWorkManagerInitializer: PlatformParameterSyncUpWorkManagerInitializer - - @Inject - lateinit var platformParameterSyncUpWorkerFactory: PlatformParameterSyncUpWorkerFactory - - @Inject - lateinit var testCoroutineDispatchers: TestCoroutineDispatchers - - @Inject - lateinit var context: Context - - @Before - fun setup() { - setUpTestApplicationComponent() - val config = Configuration.Builder() - .setExecutor(SynchronousExecutor()) - .setWorkerFactory(platformParameterSyncUpWorkerFactory) - .build() - WorkManagerTestInitHelper.initializeTestWorkManager(context, config) - } - - @Test - fun testWorkRequest_onCreate_enqueuesRequest_verifyRequestId() { - val workManager = WorkManager.getInstance(context) - syncUpWorkManagerInitializer.onCreate(workManager) - testCoroutineDispatchers.runCurrent() - - val enqueuedSyncUpWorkRequestId = syncUpWorkManagerInitializer.getSyncUpWorkRequestId() - - // Get all the WorkRequestInfo which have been tagged with "PlatformParameterSyncUpWorker.TAG" - val workInfoList = workManager.getWorkInfosByTag(PlatformParameterSyncUpWorker.TAG).get() - // There should be only one such work request having "PlatformParameterSyncUpWorker.TAG" tag - assertThat(workInfoList.size).isEqualTo(1) - // Match the ID of this work request with the ID of another work request which was enqueued by - // PlatformParameterSyncUpWorkManagerInitializer - assertThat(enqueuedSyncUpWorkRequestId).isEqualTo(workInfoList[0].id) - } - - @Test - fun testWorkRequest_verifyWorkerConstraints() { - val workerConstraints = Constraints.Builder() - .setRequiredNetworkType(NetworkType.CONNECTED) - .setRequiresBatteryNotLow(true) - .build() - - val syncUpWorkRequestConstraints = syncUpWorkManagerInitializer.getSyncUpWorkerConstraints() - assertThat(syncUpWorkRequestConstraints).isEqualTo(workerConstraints) - } - - @Test - fun testWorkRequest_verifyWorkRequestData() { - val workerTypeForSyncingUpParameters = Data.Builder().putString( - PlatformParameterSyncUpWorker.WORKER_TYPE_KEY, - PlatformParameterSyncUpWorker.PLATFORM_PARAMETER_WORKER - ).build() - - val syncUpWorkRequestData = syncUpWorkManagerInitializer.getSyncUpWorkRequestData() - - assertThat(syncUpWorkRequestData).isEqualTo(workerTypeForSyncingUpParameters) - } - - @Test - fun testWorkRequest_verifyWorkRequestPeriodicity() { - syncUpWorkManagerInitializer.onCreate(WorkManager.getInstance(context)) - testCoroutineDispatchers.runCurrent() - - val syncUpWorkerTimePeriodInMs = syncUpWorkManagerInitializer.getSyncUpWorkerTimePeriod() - val syncUpWorkerTimePeriodInHours = TimeUnit.MILLISECONDS.toHours(syncUpWorkerTimePeriodInMs) - - assertThat(syncUpWorkerTimePeriodInHours).isEqualTo( - SYNC_UP_WORKER_TIME_PERIOD_IN_HOURS_DEFAULT_VALUE - ) - } - - private fun setUpTestApplicationComponent() { - DaggerPlatformParameterSyncUpWorkManagerInitializerTest_TestApplicationComponent.builder() - .setApplication(ApplicationProvider.getApplicationContext()) - .build() - .inject(this) - } - - // TODO(#89): Move this to a common test application component. - @Module - class TestModule { - @Provides - @Singleton - fun provideContext(application: Application): Context { - return application - } - - // TODO(#59): Either isolate these to their own shared test module, or use the real logging - // module in tests to avoid needing to specify these settings for tests. - @EnableConsoleLog - @Provides - fun provideEnableConsoleLog(): Boolean = true - - @EnableFileLog - @Provides - fun provideEnableFileLog(): Boolean = false - - @GlobalLogLevel - @Provides - fun provideGlobalLogLevel(): LogLevel = LogLevel.VERBOSE - } - - // TODO(#89): Move this to a common test application component. - @Singleton - @Component( - modules = [ - ApplicationLifecycleModule::class, - AssetModule::class, - FakeOppiaClockModule::class, - LocaleProdModule::class, - LogStorageModule::class, - LoggingIdentifierModule::class, - NetworkConfigTestModule::class, - NetworkConnectionDebugUtilModule::class, - NetworkConnectionUtilDebugModule::class, - TestPlatformParameterModule::class, - PlatformParameterSingletonModule::class, - RetrofitModule::class, - RetrofitServiceModule::class, - RobolectricModule::class, - SyncStatusModule::class, - TestDispatcherModule::class, - TestLogReportingModule::class, - TestModule::class - ] - ) - interface TestApplicationComponent { - @Component.Builder - interface Builder { - @BindsInstance - fun setApplication(application: Application): Builder - fun build(): TestApplicationComponent - } - - fun inject(platformParameterSyncUpWorkerTest: PlatformParameterSyncUpWorkManagerInitializerTest) - } -} diff --git a/domain/src/test/java/org/oppia/android/domain/platformparameter/syncup/PlatformParameterSyncUpWorkerModuleTest.kt b/domain/src/test/java/org/oppia/android/domain/platformparameter/syncup/PlatformParameterSyncUpWorkerModuleTest.kt new file mode 100644 index 00000000000..31bd849eae7 --- /dev/null +++ b/domain/src/test/java/org/oppia/android/domain/platformparameter/syncup/PlatformParameterSyncUpWorkerModuleTest.kt @@ -0,0 +1,145 @@ +package org.oppia.android.domain.platformparameter.syncup + +import android.app.Application +import android.content.Context +import androidx.test.core.app.ApplicationProvider +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.google.common.truth.Truth.assertThat +import dagger.Binds +import dagger.BindsInstance +import dagger.Component +import dagger.Module +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.oppia.android.domain.oppialogger.LogStorageModule +import org.oppia.android.domain.oppialogger.LoggingIdentifierModule +import org.oppia.android.domain.oppialogger.analytics.ApplicationLifecycleModule +import org.oppia.android.domain.oppialogger.analytics.CpuPerformanceSnapshotterModule +import org.oppia.android.domain.platformparameter.PlatformParameterControllerInjector +import org.oppia.android.domain.platformparameter.PlatformParameterControllerInjectorProvider +import org.oppia.android.domain.platformparameter.PlatformParameterSingletonModule +import org.oppia.android.domain.workmanager.OppiaWorker +import org.oppia.android.domain.workmanager.StartupWorkerScheduleReadinessListener +import org.oppia.android.testing.TestLogReportingModule +import org.oppia.android.testing.firebase.TestAuthenticationModule +import org.oppia.android.testing.logging.SyncStatusTestModule +import org.oppia.android.testing.platformparameter.TestPlatformParameterModule +import org.oppia.android.testing.robolectric.RobolectricModule +import org.oppia.android.testing.threading.TestDispatcherModule +import org.oppia.android.testing.time.FakeOppiaClockModule +import org.oppia.android.util.caching.AssetModule +import org.oppia.android.util.data.DataProvidersInjector +import org.oppia.android.util.data.DataProvidersInjectorProvider +import org.oppia.android.util.locale.LocaleProdModule +import org.oppia.android.util.logging.LoggerModule +import org.oppia.android.util.logging.performancemetrics.PerformanceMetricsConfigurationsModule +import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule +import org.oppia.android.util.threading.DispatcherInjector +import org.oppia.android.util.threading.DispatcherInjectorProvider +import org.robolectric.annotation.Config +import org.robolectric.annotation.LooperMode +import javax.inject.Inject +import javax.inject.Singleton + +/** Tests for [PlatformParameterSyncUpWorkerModule]. */ +@Suppress("FunctionName") // FunctionName: test names are conventionally named with underscores. +@RunWith(AndroidJUnit4::class) +@LooperMode(LooperMode.Mode.PAUSED) +@Config(application = PlatformParameterSyncUpWorkerModuleTest.TestApplication::class) +class PlatformParameterSyncUpWorkerModuleTest { + @Inject lateinit var context: Context + @Inject + lateinit var readinessListeners: Set<@JvmSuppressWildcards StartupWorkerScheduleReadinessListener> + @Inject lateinit var factories: Map> + + @Before + fun setUp() { + setUpTestApplicationComponent() + } + + @Test + fun testInjection_bindsPlatformParameterSyncUpWorkerSchedulerIntoReadinessListenerSet() { + // The main test is that the test suite builds. + val platformParameterSyncUpWorkerSchedulers = + readinessListeners.filterIsInstance() + assertThat(readinessListeners).isNotEmpty() + assertThat(platformParameterSyncUpWorkerSchedulers).hasSize(1) + } + + @Test + fun testInjection_bindsPlatformParameterSyncUpWorkerFactoryByWorkerName() { + assertThat(factories).containsKey(PlatformParameterSyncUpWorker.WORKER_NAME) + assertThat(factories[PlatformParameterSyncUpWorker.WORKER_NAME]) + .isInstanceOf(PlatformParameterSyncUpWorker.Factory::class.java) + } + + private fun setUpTestApplicationComponent() { + ApplicationProvider.getApplicationContext().inject(this) + } + + // TODO(#89): Move this to a common test application component. + @Module + interface TestModule { + @Binds + fun provideContext(application: Application): Context + } + + // TODO(#89): Move this to a common test application component. + @Singleton + @Component( + modules = [ + TestModule::class, + PlatformParameterSyncUpWorkerModule::class, + LocaleProdModule::class, + FakeOppiaClockModule::class, + LoggerModule::class, + RobolectricModule::class, + TestDispatcherModule::class, + TestPlatformParameterModule::class, + ApplicationLifecycleModule::class, + AssetModule::class, + CpuPerformanceSnapshotterModule::class, + LogStorageModule::class, + LoggingIdentifierModule::class, + NetworkConnectionUtilDebugModule::class, + PerformanceMetricsConfigurationsModule::class, + PlatformParameterSingletonModule::class, + SyncStatusTestModule::class, + TestLogReportingModule::class, + TestAuthenticationModule::class + ] + ) + interface TestApplicationComponent : + DataProvidersInjector, DispatcherInjector, PlatformParameterControllerInjector { + @Component.Builder + interface Builder { + @BindsInstance + fun setApplication(application: Application): Builder + + fun build(): TestApplicationComponent + } + + fun inject(test: PlatformParameterSyncUpWorkerModuleTest) + } + + class TestApplication : + Application(), + DataProvidersInjectorProvider, + DispatcherInjectorProvider, + PlatformParameterControllerInjectorProvider { + private val component: TestApplicationComponent by lazy { + DaggerPlatformParameterSyncUpWorkerModuleTest_TestApplicationComponent.builder() + .setApplication(this) + .build() + } + + fun inject(test: PlatformParameterSyncUpWorkerModuleTest) { + component.inject(test) + } + + override fun getDataProvidersInjector() = component + override fun getPlatformParameterControllerInjector() = component + override fun getDispatcherInjector(): DispatcherInjector = component + } +} diff --git a/domain/src/test/java/org/oppia/android/domain/platformparameter/syncup/PlatformParameterSyncUpWorkerSchedulerTest.kt b/domain/src/test/java/org/oppia/android/domain/platformparameter/syncup/PlatformParameterSyncUpWorkerSchedulerTest.kt new file mode 100644 index 00000000000..af161e1915c --- /dev/null +++ b/domain/src/test/java/org/oppia/android/domain/platformparameter/syncup/PlatformParameterSyncUpWorkerSchedulerTest.kt @@ -0,0 +1,171 @@ +package org.oppia.android.domain.platformparameter.syncup + +import android.app.Application +import android.content.Context +import androidx.test.core.app.ApplicationProvider +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.work.NetworkType.CONNECTED +import androidx.work.WorkInfo +import com.google.common.truth.Truth.assertThat +import dagger.Binds +import dagger.BindsInstance +import dagger.Component +import dagger.Module +import kotlinx.coroutines.CoroutineDispatcher +import org.junit.After +import org.junit.Test +import org.junit.runner.RunWith +import org.oppia.android.data.backends.gae.RetrofitModule +import org.oppia.android.data.backends.gae.RetrofitServiceModule +import org.oppia.android.data.backends.gae.testing.NetworkConfigTestModule +import org.oppia.android.domain.oppialogger.LogStorageModule +import org.oppia.android.domain.oppialogger.LoggingIdentifierModule +import org.oppia.android.domain.oppialogger.analytics.ApplicationLifecycleModule +import org.oppia.android.domain.platformparameter.PlatformParameterControllerInjector +import org.oppia.android.domain.platformparameter.PlatformParameterControllerInjectorProvider +import org.oppia.android.domain.platformparameter.PlatformParameterSingletonModule +import org.oppia.android.domain.platformparameter.syncup.PlatformParameterSyncUpWorker.Companion.WORKER_NAME +import org.oppia.android.domain.platformparameter.syncup.PlatformParameterSyncUpWorker.Operation.REFRESH_PLATFORM_PARAMETERS +import org.oppia.android.domain.workmanager.WorkManagerScheduler +import org.oppia.android.domain.workmanager.testing.OppiaWorkManagerTestDriver +import org.oppia.android.testing.TestLogReportingModule +import org.oppia.android.testing.platformparameter.TestPlatformParameterModule +import org.oppia.android.testing.robolectric.RobolectricModule +import org.oppia.android.testing.threading.TestCoroutineDispatchers +import org.oppia.android.testing.threading.TestDispatcherModule +import org.oppia.android.testing.time.FakeOppiaClockModule +import org.oppia.android.util.caching.AssetModule +import org.oppia.android.util.data.DataProvidersInjector +import org.oppia.android.util.data.DataProvidersInjectorProvider +import org.oppia.android.util.locale.LocaleProdModule +import org.oppia.android.util.logging.LoggerModule +import org.oppia.android.util.logging.SyncStatusModule +import org.oppia.android.util.networking.NetworkConnectionDebugUtilModule +import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule +import org.oppia.android.util.threading.BackgroundDispatcher +import org.oppia.android.util.threading.DispatcherInjector +import org.oppia.android.util.threading.DispatcherInjectorProvider +import org.robolectric.annotation.Config +import org.robolectric.annotation.LooperMode +import java.util.concurrent.TimeUnit +import javax.inject.Inject +import javax.inject.Singleton + +/** Tests for [PlatformParameterSyncUpWorkerScheduler]. */ +@Suppress("FunctionName") // FunctionName: test names are conventionally named with underscores. +@RunWith(AndroidJUnit4::class) +@LooperMode(LooperMode.Mode.PAUSED) +@Config(application = PlatformParameterSyncUpWorkerSchedulerTest.TestApplication::class) +class PlatformParameterSyncUpWorkerSchedulerTest { + @Inject lateinit var context: Context + @Inject lateinit var testCoroutineDispatchers: TestCoroutineDispatchers + @Inject lateinit var workManagerScheduler: WorkManagerScheduler + @Inject lateinit var scheduler: PlatformParameterSyncUpWorkerScheduler + @Inject lateinit var testDriver: OppiaWorkManagerTestDriver + @field:[Inject BackgroundDispatcher] lateinit var backgroundDispatcher: CoroutineDispatcher + + @After + fun tearDown() { + TestPlatformParameterModule.reset() + } + + // TODO(#5835): Finish the tests for this suite. + + @Test + fun testPlatformParameterSyncUpWorker_hasOneOperationType() { + setUpTestApplicationComponent() + + // A change detector test that, if failing, means that other tests in this suite need to be + // updated to ensure that the new operation type is properly verified. + val operations = PlatformParameterSyncUpWorker.Operation.values().toList() + assertThat(operations).containsExactly(REFRESH_PLATFORM_PARAMETERS) + } + + @Test + fun testScheduleWork_schedulesPlatformParameterSyncUpWorkerForRefreshPlatformParameters() { + TestPlatformParameterModule.forceSyncUpWorkerTimePeriodInHours(2) + setUpTestApplicationComponent() + initializeDependencies() + + scheduler.scheduleWork(workManagerScheduler) + + // Verify that the job was scheduled correctly and uses the high-frequency time. + val monitor = testDriver.lookUpPeriodicMonitor(WORKER_NAME, REFRESH_PLATFORM_PARAMETERS) + assertThat(monitor.state).isEqualTo(WorkInfo.State.ENQUEUED) + assertThat(monitor.intervalDurationMs).isEqualTo(TimeUnit.HOURS.toMillis(2)) + assertThat(monitor.requiredNetworkType).isEqualTo(CONNECTED) + } + + private fun initializeDependencies() { + testDriver.initializeWorkManager() + } + + private fun setUpTestApplicationComponent() { + ApplicationProvider.getApplicationContext().inject(this) + } + + // TODO(#89): Move this to a common test application component. + @Module + interface TestModule { + @Binds + fun bindContext(application: Application): Context + } + + // TODO(#89): Move this to a common test application component. + @Singleton + @Component( + modules = [ + ApplicationLifecycleModule::class, + AssetModule::class, + FakeOppiaClockModule::class, + LocaleProdModule::class, + LogStorageModule::class, + LoggingIdentifierModule::class, + NetworkConfigTestModule::class, + NetworkConnectionDebugUtilModule::class, + NetworkConnectionUtilDebugModule::class, + TestPlatformParameterModule::class, + PlatformParameterSingletonModule::class, + RetrofitModule::class, + RetrofitServiceModule::class, + RobolectricModule::class, + SyncStatusModule::class, + TestDispatcherModule::class, + TestLogReportingModule::class, + TestModule::class, + LoggerModule::class, + PlatformParameterSyncUpWorkerModule::class + ] + ) + interface TestApplicationComponent : + DataProvidersInjector, DispatcherInjector, PlatformParameterControllerInjector { + @Component.Builder + interface Builder { + @BindsInstance + fun setApplication(application: Application): Builder + fun build(): TestApplicationComponent + } + + fun inject(test: PlatformParameterSyncUpWorkerSchedulerTest) + } + + class TestApplication : + Application(), + DataProvidersInjectorProvider, + DispatcherInjectorProvider, + PlatformParameterControllerInjectorProvider { + private val component: TestApplicationComponent by lazy { + DaggerPlatformParameterSyncUpWorkerSchedulerTest_TestApplicationComponent.builder() + .setApplication(this) + .build() + } + + fun inject(test: PlatformParameterSyncUpWorkerSchedulerTest) { + component.inject(test) + } + + override fun getDataProvidersInjector() = component + override fun getPlatformParameterControllerInjector() = component + override fun getDispatcherInjector(): DispatcherInjector = component + } +} diff --git a/domain/src/test/java/org/oppia/android/domain/platformparameter/syncup/PlatformParameterSyncUpWorkerTest.kt b/domain/src/test/java/org/oppia/android/domain/platformparameter/syncup/PlatformParameterSyncUpWorkerTest.kt index 8e6fd6ecf0c..9153c16bd64 100644 --- a/domain/src/test/java/org/oppia/android/domain/platformparameter/syncup/PlatformParameterSyncUpWorkerTest.kt +++ b/domain/src/test/java/org/oppia/android/domain/platformparameter/syncup/PlatformParameterSyncUpWorkerTest.kt @@ -5,19 +5,14 @@ import android.content.Context import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.work.Configuration -import androidx.work.Data -import androidx.work.OneTimeWorkRequest -import androidx.work.OneTimeWorkRequestBuilder import androidx.work.WorkInfo -import androidx.work.WorkManager -import androidx.work.testing.SynchronousExecutor -import androidx.work.testing.WorkManagerTestInitHelper import com.google.common.truth.Truth.assertThat +import dagger.Binds import dagger.BindsInstance import dagger.Component import dagger.Module -import dagger.Provides -import org.junit.Before +import kotlinx.coroutines.CoroutineDispatcher +import org.junit.After import org.junit.Test import org.junit.runner.RunWith import org.oppia.android.data.backends.gae.RetrofitModule @@ -27,10 +22,15 @@ import org.oppia.android.data.backends.gae.testing.PlatformParameterServiceTestO import org.oppia.android.domain.oppialogger.LogStorageModule import org.oppia.android.domain.oppialogger.LoggingIdentifierModule import org.oppia.android.domain.oppialogger.analytics.ApplicationLifecycleModule -import org.oppia.android.domain.platformparameter.PlatformParameterModule -import org.oppia.android.testing.FakeExceptionLogger +import org.oppia.android.domain.platformparameter.PlatformParameterControllerInjector +import org.oppia.android.domain.platformparameter.PlatformParameterControllerInjectorProvider +import org.oppia.android.domain.platformparameter.PlatformParameterSingletonModule +import org.oppia.android.domain.platformparameter.syncup.PlatformParameterSyncUpWorker.Companion.WORKER_NAME +import org.oppia.android.domain.platformparameter.syncup.PlatformParameterSyncUpWorker.Operation.REFRESH_PLATFORM_PARAMETERS +import org.oppia.android.domain.workmanager.WorkManagerConfigurationModule +import org.oppia.android.domain.workmanager.testing.OppiaWorkManagerTestDriver import org.oppia.android.testing.TestLogReportingModule -import org.oppia.android.testing.data.DataProviderTestMonitor +import org.oppia.android.testing.platformparameter.TestPlatformParameterModule import org.oppia.android.testing.robolectric.RobolectricModule import org.oppia.android.testing.threading.TestCoroutineDispatchers import org.oppia.android.testing.threading.TestDispatcherModule @@ -39,13 +39,14 @@ import org.oppia.android.util.caching.AssetModule import org.oppia.android.util.data.DataProvidersInjector import org.oppia.android.util.data.DataProvidersInjectorProvider import org.oppia.android.util.locale.LocaleProdModule -import org.oppia.android.util.logging.EnableConsoleLog -import org.oppia.android.util.logging.EnableFileLog -import org.oppia.android.util.logging.GlobalLogLevel -import org.oppia.android.util.logging.LogLevel +import org.oppia.android.util.logging.LoggerModule import org.oppia.android.util.logging.SyncStatusModule +import org.oppia.android.util.networking.NetworkConnectionDebugUtil import org.oppia.android.util.networking.NetworkConnectionDebugUtilModule import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule +import org.oppia.android.util.threading.BackgroundDispatcher +import org.oppia.android.util.threading.DispatcherInjector +import org.oppia.android.util.threading.DispatcherInjectorProvider import org.robolectric.annotation.Config import org.robolectric.annotation.LooperMode import javax.inject.Inject @@ -56,52 +57,39 @@ import javax.inject.Singleton @Suppress("FunctionName") @RunWith(AndroidJUnit4::class) @LooperMode(LooperMode.Mode.PAUSED) -@Config( - application = PlatformParameterSyncUpWorkerTest.TestApplication::class, - manifest = Config.NONE -) +@Config(application = PlatformParameterSyncUpWorkerTest.TestApplication::class) class PlatformParameterSyncUpWorkerTest { - @Inject lateinit var platformParameterSyncUpWorkerFactory: PlatformParameterSyncUpWorkerFactory - @Inject lateinit var testCoroutineDispatchers: TestCoroutineDispatchers @Inject lateinit var context: Context - @Inject lateinit var fakeExceptionLogger: FakeExceptionLogger - @Inject lateinit var monitorFactory: DataProviderTestMonitor.Factory + @Inject lateinit var testCoroutineDispatchers: TestCoroutineDispatchers + @Inject lateinit var configuration: Configuration + @Inject lateinit var testDriver: OppiaWorkManagerTestDriver + @Inject lateinit var networkConnectionUtil: NetworkConnectionDebugUtil @Inject lateinit var serviceOrchestrator: PlatformParameterServiceTestOrchestrator + @field:[Inject BackgroundDispatcher] lateinit var backgroundDispatcher: CoroutineDispatcher - @Before - fun setup() { - setUpTestApplicationComponent() - val config = Configuration.Builder() - .setExecutor(SynchronousExecutor()) - .setWorkerFactory(platformParameterSyncUpWorkerFactory) - .build() - WorkManagerTestInitHelper.initializeTestWorkManager(context, config) + @After + fun tearDown() { + TestPlatformParameterModule.reset() } - // TODO(#5835): Fix and finish the tests for this suite. + // TODO(#5835): Finish the tests for this suite. @Test - fun testSyncUpWorker_databaseIsEmpty_failsToLoadParameters() { - serviceOrchestrator.setNextResponseAsSuccess() - - val workManager = WorkManager.getInstance(context) - - val inputData = Data.Builder().putString( - PlatformParameterSyncUpWorker.WORKER_TYPE_KEY, - PlatformParameterSyncUpWorker.PLATFORM_PARAMETER_WORKER - ).build() + fun testWorker_scheduleRefreshPlatformParameters_succeeds() { + setUpTestApplicationComponent() + initializeDependencies() + serviceOrchestrator.setNextResponseAsServerError() - val request: OneTimeWorkRequest = OneTimeWorkRequestBuilder() - .setInputData(inputData) - .build() + val monitor = testDriver.runOneOffWork(WORKER_NAME, REFRESH_PLATFORM_PARAMETERS) - // Enqueue the Work Request to fetch and cache the Platform Parameters from Remote Service. - workManager.enqueue(request) - testCoroutineDispatchers.runCurrent() + // Verify that running the job succeeds even though it doesn't actually have an effect yet. Note + // that this is specifically verifiable because the orchestrator setup above should trigger a + // failure in the job if it actually made an attempt to call through to the server for syncing. + assertThat(monitor.state).isEqualTo(WorkInfo.State.SUCCEEDED) + } - // Note that this fails because the test isn't yet set up correctly. - val workInfo = workManager.getWorkInfoById(request.id) - assertThat(workInfo.get().state).isEqualTo(WorkInfo.State.FAILED) + private fun initializeDependencies() { + testDriver.initializeWorkManager(configuration) } private fun setUpTestApplicationComponent() { @@ -110,26 +98,9 @@ class PlatformParameterSyncUpWorkerTest { // TODO(#89): Move this to a common test application component. @Module - class TestModule { - @Provides - @Singleton - fun provideContext(application: Application): Context { - return application - } - - // TODO(#59): Either isolate these to their own shared test module, or use the real logging - // module in tests to avoid needing to specify these settings for tests. - @EnableConsoleLog - @Provides - fun provideEnableConsoleLog(): Boolean = true - - @EnableFileLog - @Provides - fun provideEnableFileLog(): Boolean = false - - @GlobalLogLevel - @Provides - fun provideGlobalLogLevel(): LogLevel = LogLevel.VERBOSE + interface TestModule { + @Binds + fun bindContext(application: Application): Context } // TODO(#89): Move this to a common test application component. @@ -145,17 +116,22 @@ class PlatformParameterSyncUpWorkerTest { NetworkConfigTestModule::class, NetworkConnectionDebugUtilModule::class, NetworkConnectionUtilDebugModule::class, - PlatformParameterModule::class, RetrofitModule::class, RetrofitServiceModule::class, RobolectricModule::class, SyncStatusModule::class, TestDispatcherModule::class, TestLogReportingModule::class, - TestModule::class + TestModule::class, + LoggerModule::class, + PlatformParameterSingletonModule::class, + WorkManagerConfigurationModule::class, + TestPlatformParameterModule::class, + PlatformParameterSyncUpWorkerModule::class ] ) - interface TestApplicationComponent : DataProvidersInjector { + interface TestApplicationComponent : + DataProvidersInjector, DispatcherInjector, PlatformParameterControllerInjector { @Component.Builder interface Builder { @BindsInstance @@ -166,8 +142,12 @@ class PlatformParameterSyncUpWorkerTest { fun inject(platformParameterSyncUpWorkerTest: PlatformParameterSyncUpWorkerTest) } - class TestApplication : Application(), DataProvidersInjectorProvider { - private val component: PlatformParameterSyncUpWorkerTest.TestApplicationComponent by lazy { + class TestApplication : + Application(), + DataProvidersInjectorProvider, + DispatcherInjectorProvider, + PlatformParameterControllerInjectorProvider { + private val component: TestApplicationComponent by lazy { DaggerPlatformParameterSyncUpWorkerTest_TestApplicationComponent.builder() .setApplication(this) .build() @@ -177,10 +157,8 @@ class PlatformParameterSyncUpWorkerTest { component.inject(platformParameterSyncUpWorkerTest) } - public override fun attachBaseContext(base: Context?) { - super.attachBaseContext(base) - } - - override fun getDataProvidersInjector(): DataProvidersInjector = component + override fun getDataProvidersInjector() = component + override fun getPlatformParameterControllerInjector() = component + override fun getDispatcherInjector(): DispatcherInjector = component } } diff --git a/domain/src/test/java/org/oppia/android/domain/workmanager/BUILD.bazel b/domain/src/test/java/org/oppia/android/domain/workmanager/BUILD.bazel new file mode 100644 index 00000000000..2d2d832e608 --- /dev/null +++ b/domain/src/test/java/org/oppia/android/domain/workmanager/BUILD.bazel @@ -0,0 +1,89 @@ +""" +Tests for WorkManager domain services & definitions. +""" + +load("//:oppia_android_test.bzl", "oppia_android_test") + +oppia_android_test( + name = "BootstrapOppiaWorkerTest", + srcs = ["BootstrapOppiaWorkerTest.kt"], + custom_package = "org.oppia.android.domain.workmanager", + test_class = "org.oppia.android.domain.workmanager.BootstrapOppiaWorkerTest", + test_manifest = "//domain:test_manifest", + deps = [ + "//:dagger", + "//domain/src/main/java/org/oppia/android/domain/workmanager:bootstrap_oppia_worker", + "//domain/src/main/java/org/oppia/android/domain/workmanager:oppia_worker", + "//domain/src/main/java/org/oppia/android/domain/workmanager/testing:oppia_work_manager_test_driver", + "//testing/src/main/java/org/oppia/android/testing/platformparameter:test_module", + "//testing/src/main/java/org/oppia/android/testing/robolectric:test_module", + "//testing/src/main/java/org/oppia/android/testing/threading:test_coroutine_dispatchers", + "//testing/src/main/java/org/oppia/android/testing/threading:test_module", + "//testing/src/main/java/org/oppia/android/testing/time:test_module", + "//third_party:androidx_test_ext_junit", + "//third_party:androidx_work_work-runtime", + "//third_party:com_google_truth_truth", + "//third_party:junit_junit", + "//third_party:org_robolectric_robolectric", + "//third_party:robolectric_android-all", + "//utility/src/main/java/org/oppia/android/util/caching:asset_prod_module", + "//utility/src/main/java/org/oppia/android/util/locale:prod_module", + "//utility/src/main/java/org/oppia/android/util/logging:prod_module", + "//utility/src/main/java/org/oppia/android/util/logging/firebase:debug_module", + ], +) + +oppia_android_test( + name = "WorkManagerConfigurationModuleTest", + srcs = ["WorkManagerConfigurationModuleTest.kt"], + custom_package = "org.oppia.android.domain.workmanager", + test_class = "org.oppia.android.domain.workmanager.WorkManagerConfigurationModuleTest", + test_manifest = "//domain:test_manifest", + deps = [ + "//:dagger", + "//domain/src/main/java/org/oppia/android/domain/workmanager:work_manager_configuration_module", + "//testing/src/main/java/org/oppia/android/testing/platformparameter:test_module", + "//testing/src/main/java/org/oppia/android/testing/robolectric:test_module", + "//testing/src/main/java/org/oppia/android/testing/threading:test_module", + "//testing/src/main/java/org/oppia/android/testing/time:test_module", + "//third_party:androidx_test_ext_junit", + "//third_party:com_google_truth_truth", + "//third_party:junit_junit", + "//third_party:org_robolectric_robolectric", + "//third_party:robolectric_android-all", + "//utility/src/main/java/org/oppia/android/util/caching:asset_prod_module", + "//utility/src/main/java/org/oppia/android/util/locale:prod_module", + "//utility/src/main/java/org/oppia/android/util/logging:prod_module", + "//utility/src/main/java/org/oppia/android/util/logging/firebase:debug_module", + ], +) + +oppia_android_test( + name = "WorkManagerSchedulerTest", + srcs = ["WorkManagerSchedulerTest.kt"], + custom_package = "org.oppia.android.domain.workmanager", + test_class = "org.oppia.android.domain.workmanager.WorkManagerSchedulerTest", + test_manifest = "//domain:test_manifest", + deps = [ + "//:dagger", + "//domain/src/main/java/org/oppia/android/domain/workmanager:bootstrap_oppia_worker", + "//domain/src/main/java/org/oppia/android/domain/workmanager:oppia_worker", + "//domain/src/main/java/org/oppia/android/domain/workmanager:work_manager_scheduler", + "//domain/src/main/java/org/oppia/android/domain/workmanager/testing:oppia_work_manager_test_driver", + "//testing/src/main/java/org/oppia/android/testing/platformparameter:test_module", + "//testing/src/main/java/org/oppia/android/testing/robolectric:test_module", + "//testing/src/main/java/org/oppia/android/testing/threading:test_coroutine_dispatchers", + "//testing/src/main/java/org/oppia/android/testing/threading:test_module", + "//testing/src/main/java/org/oppia/android/testing/time:test_module", + "//third_party:androidx_test_ext_junit", + "//third_party:androidx_work_work-runtime", + "//third_party:com_google_truth_truth", + "//third_party:junit_junit", + "//third_party:org_robolectric_robolectric", + "//third_party:robolectric_android-all", + "//utility/src/main/java/org/oppia/android/util/caching:asset_prod_module", + "//utility/src/main/java/org/oppia/android/util/locale:prod_module", + "//utility/src/main/java/org/oppia/android/util/logging:prod_module", + "//utility/src/main/java/org/oppia/android/util/logging/firebase:debug_module", + ], +) diff --git a/domain/src/test/java/org/oppia/android/domain/workmanager/BootstrapOppiaWorkerTest.kt b/domain/src/test/java/org/oppia/android/domain/workmanager/BootstrapOppiaWorkerTest.kt new file mode 100644 index 00000000000..f27e93d0130 --- /dev/null +++ b/domain/src/test/java/org/oppia/android/domain/workmanager/BootstrapOppiaWorkerTest.kt @@ -0,0 +1,815 @@ +package org.oppia.android.domain.workmanager + +import android.app.Application +import android.content.Context +import android.util.Log +import androidx.test.core.app.ApplicationProvider +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.work.ListenableWorker +import androidx.work.WorkInfo +import androidx.work.WorkerParameters +import com.google.common.truth.Truth.assertThat +import com.google.common.util.concurrent.ListenableFuture +import com.google.firebase.FirebaseApp +import dagger.Binds +import dagger.BindsInstance +import dagger.Component +import dagger.Module +import dagger.multibindings.IntoMap +import dagger.multibindings.StringKey +import kotlinx.coroutines.CoroutineDispatcher +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.oppia.android.domain.platformparameter.PlatformParameterControllerInjector +import org.oppia.android.domain.platformparameter.PlatformParameterControllerInjectorProvider +import org.oppia.android.domain.workmanager.BootstrapOppiaWorkerTest.MockAlternatingFailingWorker.TaskType.ALTERNATING_TASK +import org.oppia.android.domain.workmanager.BootstrapOppiaWorkerTest.MockFailingOppiaWorker.TaskType.FAILING_WORKER1_TASK1 +import org.oppia.android.domain.workmanager.BootstrapOppiaWorkerTest.MockOppiaWorker1.TaskType.WORKER1_TASK1 +import org.oppia.android.domain.workmanager.BootstrapOppiaWorkerTest.MockOppiaWorker1.TaskType.WORKER1_TASK2 +import org.oppia.android.domain.workmanager.BootstrapOppiaWorkerTest.MockOppiaWorker1.TaskType.WORKER1_TASK3 +import org.oppia.android.domain.workmanager.BootstrapOppiaWorkerTest.MockOppiaWorker2.TaskType.WORKER2_TASK1 +import org.oppia.android.domain.workmanager.BootstrapOppiaWorkerTest.MockPlatformParamOppiaWorker.TaskType.FETCH_PLATFORM_PARAMETER +import org.oppia.android.domain.workmanager.BootstrapOppiaWorkerTest.MockSuccessThenFailWorker.TaskType.MAYBE_SUCCEED_TASK +import org.oppia.android.domain.workmanager.BootstrapOppiaWorkerTest.MockThrowingOppiaWorker.TaskType.FAILING_WORKER2_TASK1 +import org.oppia.android.domain.workmanager.testing.OppiaWorkManagerTestDriver +import org.oppia.android.testing.platformparameter.TestPlatformParameterModule +import org.oppia.android.testing.robolectric.RobolectricModule +import org.oppia.android.testing.threading.TestCoroutineDispatchers +import org.oppia.android.testing.threading.TestDispatcherModule +import org.oppia.android.testing.time.FakeOppiaClockModule +import org.oppia.android.util.caching.AssetModule +import org.oppia.android.util.locale.LocaleProdModule +import org.oppia.android.util.logging.LoggerModule +import org.oppia.android.util.logging.firebase.DebugLogReportingModule +import org.oppia.android.util.platformparameter.PlatformParameterValue +import org.oppia.android.util.platformparameter.SplashScreenWelcomeMsg +import org.oppia.android.util.threading.BackgroundDispatcher +import org.oppia.android.util.threading.DispatcherInjector +import org.oppia.android.util.threading.DispatcherInjectorProvider +import org.robolectric.annotation.Config +import org.robolectric.annotation.LooperMode +import org.robolectric.shadows.ShadowLog +import java.util.concurrent.CopyOnWriteArrayList +import java.util.concurrent.TimeUnit +import javax.inject.Inject +import javax.inject.Provider +import javax.inject.Singleton + +/** Tests for [BootstrapOppiaWorker]. */ +@RunWith(AndroidJUnit4::class) +@LooperMode(LooperMode.Mode.PAUSED) +@Config(application = BootstrapOppiaWorkerTest.TestApplication::class) +@Suppress("FunctionName") // FunctionName: test names are conventionally named with underscores. +class BootstrapOppiaWorkerTest { + @Inject lateinit var context: Context + @Inject lateinit var testCoroutineDispatchers: TestCoroutineDispatchers + @Inject lateinit var testDriver: OppiaWorkManagerTestDriver + @field:[Inject BackgroundDispatcher] lateinit var backgroundDispatcher: CoroutineDispatcher + + @Before + fun setUp() { + setUpTestApplicationComponent() + FirebaseApp.initializeApp(context) + testDriver.initializeWorkManager() + + // Reset static state between tests. + workerResults.clear() + } + + @Test + fun testWorker_oneOff_differentWorkerClass_cancelsAndLogsError() { + val monitor = + testDriver.runOneOffWork( + workerName = null, operation = null, RealListenableWorker::class.java + ) + + val errorLogLine = fetchSingleBootstrapWorkerErrorLog() + assertThat(monitor.state).isEqualTo(WorkInfo.State.CANCELLED) + assertThat(errorLogLine).contains("Attempting to bootstrap old or invalid worker class") + assertThat(errorLogLine).contains("BootstrapOppiaWorkerTest\$RealListenableWorker") + } + + @Test + fun testWorker_oneOff_missingWorkerName_cancelsAndLogsError() { + // Run BootstrapOppiaWorker with no input data (which means no worker name). + val monitor = testDriver.runOneOffWork(workerName = null, operation = null) + + val errorLogLine = fetchSingleBootstrapWorkerErrorLog() + assertThat(monitor.state).isEqualTo(WorkInfo.State.CANCELLED) + assertThat(errorLogLine).contains("Attempting to bootstrap for an invalid worker delegate") + assertThat(errorLogLine).contains("null (no provider found)") + } + + @Test + fun testWorker_oneOff_missingTaskTypeKey_cancelsAndLogsError() { + val monitor = + testDriver.runOneOffWork(workerName = MockOppiaWorker1.WORKER_NAME, operation = null) + + val errorLogLine = fetchSingleBootstrapWorkerErrorLog() + assertThat(monitor.state).isEqualTo(WorkInfo.State.CANCELLED) + assertThat(errorLogLine).contains("Encountered invalid task type when trying to prepare worker") + assertThat(errorLogLine).contains("MockOppiaWorker1: null") + } + + @Test + fun testWorker_oneOff_invalidWorkerName_cancelsAndLogsError() { + val monitor = testDriver.runOneOffWork(workerName = "invalid_worker_name", operation = null) + + val errorLogLine = fetchSingleBootstrapWorkerErrorLog() + assertThat(monitor.state).isEqualTo(WorkInfo.State.CANCELLED) + assertThat(errorLogLine).contains("Attempting to bootstrap for an invalid worker delegate") + assertThat(errorLogLine).contains("invalid_worker_name (no provider found)") + } + + @Test + fun testWorker_oneOff_invalidTaskType_cancelsAndLogsError() { + val monitor = + testDriver.runOneOffWork( + workerName = MockOppiaWorker1.WORKER_NAME, + operation = MockOppiaWorker2.TaskType.WORKER2_TASK1 + ) + + val errorLogLine = fetchSingleBootstrapWorkerErrorLog() + assertThat(monitor.state).isEqualTo(WorkInfo.State.CANCELLED) + assertThat(errorLogLine).contains("Encountered invalid task type when trying to prepare worker") + assertThat(errorLogLine).contains("MockOppiaWorker1: worker2_task1") + } + + @Test + fun testWorker_oneOff_worker1_taskType1_succeedsWithNoLoggedErrors() { + val monitor = testDriver.runOneOffWork(MockOppiaWorker1.WORKER_NAME, WORKER1_TASK1) + + assertThat(monitor.state).isEqualTo(WorkInfo.State.SUCCEEDED) + assertThat(fetchBootstrapWorkerErrorLogs()).isEmpty() + assertThat(fetchSingleWorkerResult()).isEqualTo("Ran MockOppiaWorker1 for task: worker1_task1") + } + + @Test + fun testWorker_oneOff_worker1_taskType2_succeedsWithNoLoggedErrors() { + val monitor = testDriver.runOneOffWork(MockOppiaWorker1.WORKER_NAME, WORKER1_TASK2) + + assertThat(monitor.state).isEqualTo(WorkInfo.State.SUCCEEDED) + assertThat(fetchBootstrapWorkerErrorLogs()).isEmpty() + assertThat(fetchSingleWorkerResult()).isEqualTo("Ran MockOppiaWorker1 for task: worker1_task2") + } + + @Test + fun testWorker_oneOff_worker2_taskType1_succeedsWithNoLoggedErrors() { + val monitor = testDriver.runOneOffWork(MockOppiaWorker2.WORKER_NAME, WORKER2_TASK1) + + assertThat(monitor.state).isEqualTo(WorkInfo.State.SUCCEEDED) + assertThat(fetchBootstrapWorkerErrorLogs()).isEmpty() + assertThat(fetchSingleWorkerResult()).isEqualTo("Ran MockOppiaWorker2 for task: worker2_task1") + } + + @Test + fun testWorker_oneOff_failingWorker_failsWithNoLoggedErrors() { + val monitor = + testDriver.runOneOffWork(MockFailingOppiaWorker.WORKER_NAME, FAILING_WORKER1_TASK1) + + assertThat(monitor.state).isEqualTo(WorkInfo.State.FAILED) + // A standard job failure shouldn't log bootstrap worker errors. + assertThat(fetchBootstrapWorkerErrorLogs()).isEmpty() + assertThat(fetchSingleWorkerResult()) + .isEqualTo("Ran MockFailingOppiaWorker for task: failing_worker1_task1") + } + + @Test + fun testWorker_oneOff_throwingWorker_failsWithNoLoggedErrors() { + val monitor = + testDriver.runOneOffWork(MockThrowingOppiaWorker.WORKER_NAME, FAILING_WORKER2_TASK1) + + assertThat(monitor.state).isEqualTo(WorkInfo.State.FAILED) + // A standard job failure shouldn't log bootstrap worker errors. + assertThat(fetchBootstrapWorkerErrorLogs()).isEmpty() + assertThat(fetchSingleWorkerResult()).isEqualTo("Pre-throw in MockThrowingOppiaWorker") + } + + @Test + fun testWorker_oneOff_workerWithPlatformParameters_canInjectParametersSuccessfully() { + val monitor = + testDriver.runOneOffWork(MockPlatformParamOppiaWorker.WORKER_NAME, FETCH_PLATFORM_PARAMETER) + + assertThat(monitor.state).isEqualTo(WorkInfo.State.SUCCEEDED) + assertThat(fetchBootstrapWorkerErrorLogs()).isEmpty() + assertThat(fetchSingleWorkerResult()).isEqualTo("Fetched platform parameter value: 'false'") + } + + @Test + fun testWorker_oneOff_sameWorkerTwice_sameParameters_bothSucceed() { + val monitor1 = testDriver.runOneOffWork(MockOppiaWorker1.WORKER_NAME, WORKER1_TASK1) + val monitor2 = testDriver.runOneOffWork(MockOppiaWorker1.WORKER_NAME, WORKER1_TASK1) + + assertThat(monitor1.state).isEqualTo(WorkInfo.State.SUCCEEDED) + assertThat(monitor2.state).isEqualTo(WorkInfo.State.SUCCEEDED) + assertThat(fetchBootstrapWorkerErrorLogs()).isEmpty() + assertThat(workerResults).containsExactly( + "Ran MockOppiaWorker1 for task: worker1_task1", + "Ran MockOppiaWorker1 for task: worker1_task1" + ).inOrder() + } + + @Test + fun testWorker_oneOff_sameWorkerTwice_differentParameters_bothSucceed() { + val monitor1 = testDriver.runOneOffWork(MockOppiaWorker1.WORKER_NAME, WORKER1_TASK1) + val monitor2 = testDriver.runOneOffWork(MockOppiaWorker1.WORKER_NAME, WORKER1_TASK2) + + assertThat(monitor1.state).isEqualTo(WorkInfo.State.SUCCEEDED) + assertThat(monitor2.state).isEqualTo(WorkInfo.State.SUCCEEDED) + assertThat(fetchBootstrapWorkerErrorLogs()).isEmpty() + assertThat(workerResults).containsExactly( + "Ran MockOppiaWorker1 for task: worker1_task1", + "Ran MockOppiaWorker1 for task: worker1_task2" + ).inOrder() + } + + @Test + fun testWorker_oneOff_differentWorkers_sameTime_bothSucceed() { + val monitor1 = testDriver.runOneOffWork(MockOppiaWorker1.WORKER_NAME, WORKER1_TASK1) + val monitor2 = testDriver.runOneOffWork(MockOppiaWorker2.WORKER_NAME, WORKER2_TASK1) + + assertThat(monitor1.state).isEqualTo(WorkInfo.State.SUCCEEDED) + assertThat(monitor2.state).isEqualTo(WorkInfo.State.SUCCEEDED) + assertThat(fetchBootstrapWorkerErrorLogs()).isEmpty() + assertThat(workerResults).containsExactly( + "Ran MockOppiaWorker1 for task: worker1_task1", + "Ran MockOppiaWorker2 for task: worker2_task1" + ).inOrder() + } + + @Test + fun testWorker_periodic_differentWorkerClass_cancelsAndLogsError() { + val monitor = + testDriver.runPeriodicWork( + workerName = null, operation = null, repeatIntervalMins = 15, + RealListenableWorker::class.java + ) + + val errorLogLine = fetchSingleBootstrapWorkerErrorLog() + assertThat(monitor.state).isEqualTo(WorkInfo.State.CANCELLED) + assertThat(errorLogLine).contains("Attempting to bootstrap old or invalid worker class") + assertThat(errorLogLine).contains("BootstrapOppiaWorkerTest\$RealListenableWorker") + } + + @Test + fun testWorker_periodic_missingWorkerName_cancelsAndLogsError() { + // Run BootstrapOppiaWorker with no input data (which means no worker name). + val monitor = testDriver.runPeriodicWork(workerName = null, operation = null) + + val errorLogLine = fetchSingleBootstrapWorkerErrorLog() + assertThat(monitor.state).isEqualTo(WorkInfo.State.CANCELLED) + assertThat(errorLogLine).contains("Attempting to bootstrap for an invalid worker delegate") + assertThat(errorLogLine).contains("null (no provider found)") + } + + @Test + fun testWorker_periodic_missingTaskTypeKey_cancelsAndLogsError() { + val monitor = + testDriver.runPeriodicWork(workerName = MockOppiaWorker1.WORKER_NAME, operation = null) + + val errorLogLine = fetchSingleBootstrapWorkerErrorLog() + assertThat(monitor.state).isEqualTo(WorkInfo.State.CANCELLED) + assertThat(errorLogLine).contains("Encountered invalid task type when trying to prepare worker") + assertThat(errorLogLine).contains("MockOppiaWorker1: null") + } + + @Test + fun testWorker_periodic_invalidWorkerName_cancelsAndLogsError() { + val monitor = testDriver.runOneOffWork(workerName = "invalid_worker_name", operation = null) + + val errorLogLine = fetchSingleBootstrapWorkerErrorLog() + assertThat(monitor.state).isEqualTo(WorkInfo.State.CANCELLED) + assertThat(errorLogLine).contains("Attempting to bootstrap for an invalid worker delegate") + assertThat(errorLogLine).contains("invalid_worker_name (no provider found)") + } + + @Test + fun testWorker_periodic_invalidTaskType_cancelsAndLogsError() { + val monitor = + testDriver.runOneOffWork( + workerName = MockOppiaWorker1.WORKER_NAME, + operation = MockOppiaWorker2.TaskType.WORKER2_TASK1 + ) + + val errorLogLine = fetchSingleBootstrapWorkerErrorLog() + assertThat(monitor.state).isEqualTo(WorkInfo.State.CANCELLED) + assertThat(errorLogLine).contains("Encountered invalid task type when trying to prepare worker") + assertThat(errorLogLine).contains("MockOppiaWorker1: worker2_task1") + } + + @Test + fun testWorker_periodic_worker1_taskType1_succeedsWithNoLoggedErrorsAndReEnqueues() { + val monitor = testDriver.runPeriodicWork(MockOppiaWorker1.WORKER_NAME, WORKER1_TASK1) + + assertThat(monitor.state).isEqualTo(WorkInfo.State.ENQUEUED) + assertThat(fetchBootstrapWorkerErrorLogs()).isEmpty() + assertThat(fetchSingleWorkerResult()).isEqualTo("Ran MockOppiaWorker1 for task: worker1_task1") + } + + @Test + fun testWorker_periodic_worker1_taskType2_succeedsWithNoLoggedErrorsAndReEnqueues() { + val monitor = testDriver.runPeriodicWork(MockOppiaWorker1.WORKER_NAME, WORKER1_TASK2) + + assertThat(monitor.state).isEqualTo(WorkInfo.State.ENQUEUED) + assertThat(fetchBootstrapWorkerErrorLogs()).isEmpty() + assertThat(fetchSingleWorkerResult()).isEqualTo("Ran MockOppiaWorker1 for task: worker1_task2") + } + + @Test + fun testWorker_periodic_worker2_taskType1_succeedsWithNoLoggedErrorsAndReEnqueues() { + val monitor = testDriver.runPeriodicWork(MockOppiaWorker2.WORKER_NAME, WORKER2_TASK1) + + assertThat(monitor.state).isEqualTo(WorkInfo.State.ENQUEUED) + assertThat(fetchBootstrapWorkerErrorLogs()).isEmpty() + assertThat(fetchSingleWorkerResult()).isEqualTo("Ran MockOppiaWorker2 for task: worker2_task1") + } + + @Test + fun testWorker_periodic_failingWorker_failsWithNoLoggedErrorsAndReEnqueues() { + val monitor = + testDriver.runPeriodicWork(MockFailingOppiaWorker.WORKER_NAME, FAILING_WORKER1_TASK1) + + // Standard job failures should re-enqueue and result in no bootstrap worker errors. + assertThat(monitor.state).isEqualTo(WorkInfo.State.ENQUEUED) + assertThat(fetchBootstrapWorkerErrorLogs()).isEmpty() + assertThat(fetchSingleWorkerResult()) + .isEqualTo("Ran MockFailingOppiaWorker for task: failing_worker1_task1") + } + + @Test + fun testWorker_periodic_throwingWorker_failsWithNoLoggedErrorsAndReEnqueues() { + val monitor = + testDriver.runPeriodicWork(MockThrowingOppiaWorker.WORKER_NAME, FAILING_WORKER2_TASK1) + + // Standard job failures should re-enqueue and result in no bootstrap worker errors. + assertThat(monitor.state).isEqualTo(WorkInfo.State.ENQUEUED) + assertThat(fetchBootstrapWorkerErrorLogs()).isEmpty() + assertThat(fetchSingleWorkerResult()).isEqualTo("Pre-throw in MockThrowingOppiaWorker") + } + + @Test + fun testWorker_periodic_workerWithPlatformParameters_canInjectParametersSuccessfully() { + val monitor = + testDriver.runPeriodicWork(MockPlatformParamOppiaWorker.WORKER_NAME, FETCH_PLATFORM_PARAMETER) + + assertThat(monitor.state).isEqualTo(WorkInfo.State.ENQUEUED) + assertThat(fetchBootstrapWorkerErrorLogs()).isEmpty() + assertThat(fetchSingleWorkerResult()).isEqualTo("Fetched platform parameter value: 'false'") + } + + @Test + fun testWorker_periodic_sameWorkerTwice_sameParameters_onlyFirstRunsAndIsReEnqueued() { + val monitor1 = testDriver.runPeriodicWork(MockOppiaWorker1.WORKER_NAME, WORKER1_TASK1) + val monitor2 = testDriver.runPeriodicWork(MockOppiaWorker1.WORKER_NAME, WORKER1_TASK1) + + // Since the work name is based on the worker and task type, attempting to schedule the same job + // twice will only result in the first one being scheduled. Both work infos will actually + // represent the same work request (actualId) but will differ in requestedId due to the + // redundant schedule. + assertThat(monitor1.state).isEqualTo(WorkInfo.State.ENQUEUED) + assertThat(monitor1.isRedundant).isFalse() // Request went through. + assertThat(monitor2.isRedundant).isTrue() // Request failed. + assertThat(monitor2.id).isEqualTo(monitor1.id) // Both requests are the same. + assertThat(fetchBootstrapWorkerErrorLogs()).isEmpty() + assertThat(fetchSingleWorkerResult()).isEqualTo("Ran MockOppiaWorker1 for task: worker1_task1") + } + + @Test + fun testWorker_periodic_sameWorkerTwice_differentParameters_bothSucceedAndReEnqueue() { + val monitor1 = testDriver.runPeriodicWork(MockOppiaWorker1.WORKER_NAME, WORKER1_TASK1) + val monitor2 = testDriver.runPeriodicWork(MockOppiaWorker1.WORKER_NAME, WORKER1_TASK2) + + assertThat(monitor1.state).isEqualTo(WorkInfo.State.ENQUEUED) + assertThat(monitor2.state).isEqualTo(WorkInfo.State.ENQUEUED) + assertThat(fetchBootstrapWorkerErrorLogs()).isEmpty() + assertThat(workerResults).containsExactly( + "Ran MockOppiaWorker1 for task: worker1_task1", + "Ran MockOppiaWorker1 for task: worker1_task2" + ).inOrder() + } + + @Test + fun testWorker_periodic_differentWorkers_sameTime_bothSucceedAndReEnqueue() { + val monitor1 = testDriver.runPeriodicWork(MockOppiaWorker1.WORKER_NAME, WORKER1_TASK1) + val monitor2 = testDriver.runPeriodicWork(MockOppiaWorker2.WORKER_NAME, WORKER2_TASK1) + + assertThat(monitor1.state).isEqualTo(WorkInfo.State.ENQUEUED) + assertThat(monitor2.state).isEqualTo(WorkInfo.State.ENQUEUED) + assertThat(fetchBootstrapWorkerErrorLogs()).isEmpty() + assertThat(workerResults).containsExactly( + "Ran MockOppiaWorker1 for task: worker1_task1", + "Ran MockOppiaWorker2 for task: worker2_task1" + ).inOrder() + } + + @Test + fun testWorker_periodic_waitTwoPeriods_workerRunsThreeTimesAndReEnqueues() { + val monitor = testDriver.runPeriodicWork(MockOppiaWorker1.WORKER_NAME, WORKER1_TASK1) + + testCoroutineDispatchers.advanceTimeBy(TimeUnit.MINUTES.toMillis(35)) + + // The worker should have run 3 times since it's scheduled to run every 15 minutes and it will + // run immediately when scheduled since constraints are met. + assertThat(monitor.state).isEqualTo(WorkInfo.State.ENQUEUED) + assertThat(fetchBootstrapWorkerErrorLogs()).isEmpty() + assertThat(workerResults).containsExactly( + "Ran MockOppiaWorker1 for task: worker1_task1", + "Ran MockOppiaWorker1 for task: worker1_task1", + "Ran MockOppiaWorker1 for task: worker1_task1" + ).inOrder() + } + + @Test + fun testWorker_periodic_intermittentSuccessesAndThrownErrors_keepsWorkerEnqueued() { + val monitor = + testDriver.runPeriodicWork(MockAlternatingFailingWorker.WORKER_NAME, ALTERNATING_TASK) + + testCoroutineDispatchers.advanceTimeBy(TimeUnit.MINUTES.toMillis(35)) + + // The worker should have run 3 times and alternated between successes and failures, but + // continue to run and be enqueued. + assertThat(monitor.state).isEqualTo(WorkInfo.State.ENQUEUED) + assertThat(fetchBootstrapWorkerErrorLogs()).isEmpty() + assertThat(workerResults).containsExactly( + "Ran MockAlternatingFailingWorker. Fail this time: false", + "Ran MockAlternatingFailingWorker. Fail this time: true", + "Ran MockAlternatingFailingWorker. Fail this time: false" + ).inOrder() + } + + @Test + fun testWorker_periodic_successfulThenIncompatibleWorker_logsErrorAndCancelsJob() { + val monitor = + testDriver.runPeriodicWork(MockSuccessThenFailWorker.WORKER_NAME, MAYBE_SUCCEED_TASK) + + // Advance the clock enough for the job to run 4 times. + testCoroutineDispatchers.advanceTimeBy(TimeUnit.MINUTES.toMillis(55)) + + // The worker should have run twice. The first is a success, but the second simulates a code + // change where the job may no longer accept the same task type that was previously periodically + // scheduled. This should result in a hard stop and cancellation of the job, plus logged errors. + val errorLogLine = fetchSingleBootstrapWorkerErrorLog() + assertThat(monitor.state).isEqualTo(WorkInfo.State.CANCELLED) + assertThat(errorLogLine).contains("Encountered invalid task type when trying to prepare worker") + assertThat(errorLogLine).contains("MockSuccessThenFailWorker: maybe_succeed_task") + assertThat(fetchSingleWorkerResult()).isEqualTo("Ran MockSuccessThenFailWorker") + } + + @Test + fun testWorker_multiplePeriodicAndOneOffWorkers_differentRates_allRunInOrderWithNoErrors() { + val monitor1 = + testDriver.runPeriodicWork( + MockOppiaWorker1.WORKER_NAME, WORKER1_TASK1, repeatIntervalMins = 15 + ) + val monitor2 = testDriver.runOneOffWork(MockOppiaWorker1.WORKER_NAME, WORKER1_TASK2) + val monitor3 = + testDriver.runPeriodicWork( + MockOppiaWorker2.WORKER_NAME, WORKER2_TASK1, repeatIntervalMins = 31 + ) + // Advance the clock enough to run job 1 three times and job 2 twice. + testCoroutineDispatchers.advanceTimeBy(TimeUnit.MINUTES.toMillis(35)) + val monitor4 = testDriver.runOneOffWork(MockOppiaWorker1.WORKER_NAME, WORKER1_TASK3) + + // Advance enough for job 1 to run once more. + testCoroutineDispatchers.advanceTimeBy(TimeUnit.MINUTES.toMillis(12)) + + assertThat(monitor1.state).isEqualTo(WorkInfo.State.ENQUEUED) + assertThat(monitor2.state).isEqualTo(WorkInfo.State.SUCCEEDED) + assertThat(monitor3.state).isEqualTo(WorkInfo.State.ENQUEUED) + assertThat(monitor4.state).isEqualTo(WorkInfo.State.SUCCEEDED) + assertThat(fetchBootstrapWorkerErrorLogs()).isEmpty() + assertThat(workerResults).containsExactly( + "Ran MockOppiaWorker1 for task: worker1_task1", // Repeat worker 1 run 1. + "Ran MockOppiaWorker1 for task: worker1_task2", // One-off worker 1. + "Ran MockOppiaWorker2 for task: worker2_task1", // Repeat worker 2 run 1. + "Ran MockOppiaWorker1 for task: worker1_task1", // Repeat worker 1 run 2. + "Ran MockOppiaWorker1 for task: worker1_task1", // Repeat worker 1 run 3. + "Ran MockOppiaWorker2 for task: worker2_task1", // Repeat worker 2 run 2. + "Ran MockOppiaWorker1 for task: worker1_task3", // One-off worker 2. + "Ran MockOppiaWorker1 for task: worker1_task1", // Repeat worker 1 run 4. + ).inOrder() + } + + private fun fetchBootstrapWorkerErrorLogs(): List { + // Extract all logs from the bootstrap worker and validate they are each errors before returning + // the logged message lines. + return ShadowLog.getLogs().filter { it.tag == "BootstrapOppiaWorker" }.map { + assertThat(it.type).isEqualTo(Log.ERROR) + return@map it.msg + } + } + + private fun fetchSingleBootstrapWorkerErrorLog(): String { + val errorLogs = fetchBootstrapWorkerErrorLogs() + assertThat(errorLogs).hasSize(1) + return errorLogs.single() + } + + private fun fetchSingleWorkerResult(): String { + assertThat(workerResults).hasSize(1) + return workerResults.single() + } + + private fun setUpTestApplicationComponent() { + ApplicationProvider.getApplicationContext().inject(this) + } + + companion object { + val workerResults = CopyOnWriteArrayList() + } + + @Module + interface TestModule { + @Binds fun bindApplicationContext(application: Application): Context + } + + @Module + interface TestWorkManagerModule { + @Binds + @IntoMap + @StringKey(MockOppiaWorker1.WORKER_NAME) + fun provideMockOppiaWorker1Factory( + factory: MockOppiaWorker1.Factory + ): OppiaWorker.Factory<*> + + @Binds + @IntoMap + @StringKey(MockOppiaWorker2.WORKER_NAME) + fun provideMockOppiaWorker2Factory( + factory: MockOppiaWorker2.Factory + ): OppiaWorker.Factory<*> + + @Binds + @IntoMap + @StringKey(MockPlatformParamOppiaWorker.WORKER_NAME) + fun provideMockPlatformParamOppiaWorkerFactory( + factory: MockPlatformParamOppiaWorker.Factory + ): OppiaWorker.Factory<*> + + @Binds + @IntoMap + @StringKey(MockFailingOppiaWorker.WORKER_NAME) + fun provideMockFailingOppiaWorkerFactory( + factory: MockFailingOppiaWorker.Factory + ): OppiaWorker.Factory<*> + + @Binds + @IntoMap + @StringKey(MockThrowingOppiaWorker.WORKER_NAME) + fun provideMockThrowingOppiaWorkerFactory( + factory: MockThrowingOppiaWorker.Factory + ): OppiaWorker.Factory<*> + + @Binds + @IntoMap + @StringKey(MockAlternatingFailingWorker.WORKER_NAME) + fun provideMockAlternatingFailingWorkerFactory( + factory: MockAlternatingFailingWorker.Factory + ): OppiaWorker.Factory<*> + + @Binds + @IntoMap + @StringKey(MockSuccessThenFailWorker.WORKER_NAME) + fun provideMockSuccessThenFailWorkerFactory( + factory: MockSuccessThenFailWorker.Factory + ): OppiaWorker.Factory<*> + } + + // TODO(#89): Move this to a common test application component. + @Singleton + @Component( + modules = [ + TestModule::class, RobolectricModule::class, TestDispatcherModule::class, + FakeOppiaClockModule::class, TestWorkManagerModule::class, + LocaleProdModule::class, + LoggerModule::class, + TestPlatformParameterModule::class, + AssetModule::class, + DebugLogReportingModule::class + ] + ) + interface TestApplicationComponent : DispatcherInjector, PlatformParameterControllerInjector { + @Component.Builder + interface Builder { + @BindsInstance + fun setApplication(application: Application): Builder + fun build(): TestApplicationComponent + } + + fun inject(test: BootstrapOppiaWorkerTest) + } + + class TestApplication : + Application(), DispatcherInjectorProvider, PlatformParameterControllerInjectorProvider { + private val component: TestApplicationComponent by lazy { + DaggerBootstrapOppiaWorkerTest_TestApplicationComponent.builder() + .setApplication(this) + .build() + } + + fun inject(test: BootstrapOppiaWorkerTest) { + component.inject(test) + } + + override fun getDispatcherInjector() = component + override fun getPlatformParameterControllerInjector() = component + } + + class MockOppiaWorker1 private constructor() : OppiaWorker { + enum class TaskType(override val persistentName: String) : OppiaWorker.TaskType { + WORKER1_TASK1("worker1_task1"), + WORKER1_TASK2("worker1_task2"), + WORKER1_TASK3("worker1_task3") + } + + override suspend fun doWork(taskType: TaskType): OppiaWorker.Result { + workerResults += "Ran MockOppiaWorker1 for task: ${taskType.persistentName}" + return OppiaWorker.Result.SUCCESS + } + + class Factory @Inject constructor() : OppiaWorker.Factory { + override val supportedTaskTypes: List = TaskType.values().toList() + + override fun createWorker(): OppiaWorker { + return MockOppiaWorker1() + } + } + + companion object { + const val WORKER_NAME = "MockOppiaWorker1" + } + } + + class MockOppiaWorker2 private constructor() : OppiaWorker { + enum class TaskType(override val persistentName: String) : OppiaWorker.TaskType { + WORKER2_TASK1("worker2_task1") + } + + override suspend fun doWork(taskType: TaskType): OppiaWorker.Result { + workerResults += "Ran MockOppiaWorker2 for task: ${taskType.persistentName}" + return OppiaWorker.Result.SUCCESS + } + + class Factory @Inject constructor() : OppiaWorker.Factory { + override val supportedTaskTypes: List = TaskType.values().toList() + + override fun createWorker(): OppiaWorker { + return MockOppiaWorker2() + } + } + + companion object { + const val WORKER_NAME = "MockOppiaWorker2" + } + } + + class MockPlatformParamOppiaWorker private constructor( + private val splashScreenWelcomeMsgParam: Provider> + ) : OppiaWorker { + enum class TaskType(override val persistentName: String) : OppiaWorker.TaskType { + FETCH_PLATFORM_PARAMETER("fetch_platform_parameter") + } + + override suspend fun doWork(taskType: TaskType): OppiaWorker.Result { + val splashScreenWelcomeMsgValue = splashScreenWelcomeMsgParam.get().value + workerResults += "Fetched platform parameter value: '$splashScreenWelcomeMsgValue'" + return OppiaWorker.Result.SUCCESS + } + + class Factory @Inject constructor( + @SplashScreenWelcomeMsg + private val splashScreenWelcomeMsgParam: Provider> + ) : OppiaWorker.Factory { + override val supportedTaskTypes: List = TaskType.values().toList() + + override fun createWorker(): OppiaWorker { + return MockPlatformParamOppiaWorker(splashScreenWelcomeMsgParam) + } + } + + companion object { + const val WORKER_NAME = "MockPlatformParamOppiaWorker" + } + } + + class MockFailingOppiaWorker private constructor() : + OppiaWorker { + enum class TaskType(override val persistentName: String) : OppiaWorker.TaskType { + FAILING_WORKER1_TASK1("failing_worker1_task1") + } + + override suspend fun doWork(taskType: TaskType): OppiaWorker.Result { + workerResults += "Ran MockFailingOppiaWorker for task: ${taskType.persistentName}" + return OppiaWorker.Result.FAILURE + } + + class Factory @Inject constructor() : OppiaWorker.Factory { + override val supportedTaskTypes: List = TaskType.values().toList() + + override fun createWorker(): OppiaWorker { + return MockFailingOppiaWorker() + } + } + + companion object { + const val WORKER_NAME = "MockFailingOppiaWorker" + } + } + + class MockThrowingOppiaWorker private constructor() : + OppiaWorker { + enum class TaskType(override val persistentName: String) : OppiaWorker.TaskType { + FAILING_WORKER2_TASK1("failing_worker2_task1") + } + + override suspend fun doWork(taskType: TaskType): OppiaWorker.Result { + workerResults += "Pre-throw in MockThrowingOppiaWorker" + error("Catastrophic failure.") + } + + class Factory @Inject constructor() : OppiaWorker.Factory { + override val supportedTaskTypes: List = TaskType.values().toList() + + override fun createWorker(): OppiaWorker { + return MockThrowingOppiaWorker() + } + } + + companion object { + const val WORKER_NAME = "MockThrowingOppiaWorker" + } + } + + class MockAlternatingFailingWorker private constructor() : + OppiaWorker { + enum class TaskType(override val persistentName: String) : OppiaWorker.TaskType { + ALTERNATING_TASK("alternating_tasks") + } + + override suspend fun doWork(taskType: TaskType): OppiaWorker.Result { + workerResults += "Ran MockAlternatingFailingWorker. Fail this time: $failThisTime" + val shouldThrow = failThisTime + failThisTime = !failThisTime + if (shouldThrow) error("Catastrophic failure.") + return OppiaWorker.Result.SUCCESS + } + + class Factory @Inject constructor() : OppiaWorker.Factory { + override val supportedTaskTypes: List = TaskType.values().toList() + + override fun createWorker(): OppiaWorker { + return MockAlternatingFailingWorker() + } + } + + companion object { + const val WORKER_NAME = "MockAlternatingFailingWorker" + + private var failThisTime = false + } + } + + class MockSuccessThenFailWorker private constructor() : + OppiaWorker { + enum class TaskType(override val persistentName: String) : OppiaWorker.TaskType { + MAYBE_SUCCEED_TASK("maybe_succeed_task") + } + + override suspend fun doWork(taskType: TaskType): OppiaWorker.Result { + workerResults += "Ran MockSuccessThenFailWorker" + hasIncompatibility = true + return OppiaWorker.Result.SUCCESS + } + + class Factory @Inject constructor() : OppiaWorker.Factory { + override val supportedTaskTypes: List = TaskType.values().toList() + + override fun createWorker(): OppiaWorker { + return MockSuccessThenFailWorker() + } + + override suspend fun doWorkForTaskName(taskName: String): OppiaWorker.Result? { + // Simulate a "later" run of the job (e.g. in the next process run) not having the same + // tasks supported. + if (hasIncompatibility) return null + return super.doWorkForTaskName(taskName) + } + } + + companion object { + const val WORKER_NAME = "MockSuccessThenFailWorker" + + private var hasIncompatibility = false + } + } + + private class RealListenableWorker( + appContext: Context, + workerParams: WorkerParameters + ) : ListenableWorker(appContext, workerParams) { + override fun startWork(): ListenableFuture { + error("Not implemented for test.") + } + } +} diff --git a/domain/src/test/java/org/oppia/android/domain/workmanager/WorkManagerConfigurationModuleTest.kt b/domain/src/test/java/org/oppia/android/domain/workmanager/WorkManagerConfigurationModuleTest.kt new file mode 100644 index 00000000000..0438999b853 --- /dev/null +++ b/domain/src/test/java/org/oppia/android/domain/workmanager/WorkManagerConfigurationModuleTest.kt @@ -0,0 +1,218 @@ +package org.oppia.android.domain.workmanager + +import android.app.Application +import android.content.Context +import androidx.test.core.app.ApplicationProvider +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.work.Configuration +import androidx.work.Data +import androidx.work.ListenableWorker +import androidx.work.WorkerParameters +import androidx.work.impl.utils.taskexecutor.TaskExecutor +import com.google.common.truth.Truth.assertThat +import com.google.common.util.concurrent.ListenableFuture +import com.google.common.util.concurrent.MoreExecutors.directExecutor +import com.google.firebase.FirebaseApp +import dagger.Binds +import dagger.BindsInstance +import dagger.Component +import dagger.Module +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.oppia.android.domain.oppialogger.ApplicationStartupListener +import org.oppia.android.domain.platformparameter.PlatformParameterControllerInjector +import org.oppia.android.domain.platformparameter.PlatformParameterControllerInjectorProvider +import org.oppia.android.testing.platformparameter.TestPlatformParameterModule +import org.oppia.android.testing.robolectric.RobolectricModule +import org.oppia.android.testing.threading.TestDispatcherModule +import org.oppia.android.testing.time.FakeOppiaClockModule +import org.oppia.android.util.caching.AssetModule +import org.oppia.android.util.locale.LocaleProdModule +import org.oppia.android.util.logging.LoggerModule +import org.oppia.android.util.logging.firebase.DebugLogReportingModule +import org.oppia.android.util.threading.DispatcherInjector +import org.oppia.android.util.threading.DispatcherInjectorProvider +import org.robolectric.annotation.Config +import org.robolectric.annotation.LooperMode +import java.util.UUID +import javax.inject.Inject +import javax.inject.Singleton + +/** Tests for [WorkManagerConfigurationModule]. */ +@RunWith(AndroidJUnit4::class) +@LooperMode(LooperMode.Mode.PAUSED) +@Config(application = WorkManagerConfigurationModuleTest.TestApplication::class) +@Suppress("FunctionName") // FunctionName: test names are conventionally named with underscores. +class WorkManagerConfigurationModuleTest { + @Inject lateinit var context: Context + @Inject lateinit var factories: Map> + @Inject + lateinit var readinessListeners: Set<@JvmSuppressWildcards StartupWorkerScheduleReadinessListener> + @Inject lateinit var appStartupListeners: Set<@JvmSuppressWildcards ApplicationStartupListener> + @Inject lateinit var configuration: Configuration + + @Before + fun setUp() { + setUpTestApplicationComponent() + FirebaseApp.initializeApp(context) + } + + @Test + fun testInjection_bindsOppiaWorkersMap() { + // The module should be setting up a multibinding that then defaults to an empty map to ensure + // injection. Note that the real test here is that test suite actually builds. + assertThat(factories).isEmpty() + } + + @Test + fun testInjection_bindsStartupWorkerScheduleReadinessListenerSet() { + // The module should be setting up a multibinding that then defaults to an empty set to ensure + // injection. Note that the real test here is that test suite actually builds. + assertThat(readinessListeners).isEmpty() + } + + @Test + fun testInjection_bindsStartupWorkerScheduleReadinessMonitorAsApplicationStartupListener() { + val monitors = appStartupListeners.filterIsInstance() + assertThat(appStartupListeners).isNotEmpty() + assertThat(monitors).hasSize(1) + } + + @Test + fun testWorkManagerConfiguration_setsCustomWorkerFactory() { + // Note that an implied test here is that the test suite actually builds. + val workerFactoryClassPackage = configuration.workerFactory.javaClass.packageName + assertThat(workerFactoryClassPackage).startsWith("org.oppia.android") + } + + @Test + fun testConfiguredWorkerFactory_createWorker_workerNotInClassPath_returnsBootstrapWorker() { + val workerFactory = configuration.workerFactory + + val worker = + workerFactory.createWorker(context, "org.oppia.android.FakeWorker", createWorkerParameters()) + + // The custom factory defaults to BootstrapOppiaWorker to handle non-existent workers (which may + // happen if the old versions of the workers are scheduled). + assertThat(worker).isInstanceOf(BootstrapOppiaWorker::class.java) + } + + @Test + fun testConfiguredWorkerFactory_createWorker_workerNotListenableWorker_returnsBootstrapWorker() { + val workerFactory = configuration.workerFactory + + val worker = + workerFactory.createWorker(context, TestModule::class.java.name, createWorkerParameters()) + + // The custom factory defaults to BootstrapOppiaWorker to handle invalid workers. + assertThat(worker).isInstanceOf(BootstrapOppiaWorker::class.java) + } + + @Test + fun testConfiguredWorkerFactory_createWorker_workerIsNotBootstrapWorker_returnsNull() { + val workerFactory = configuration.workerFactory + + val worker = + workerFactory.createWorker( + context, RealListenableWorker::class.java.name, createWorkerParameters() + ) + + // If a real worker is encountered (such as one of WorkManager's) then the bootstrap worker + // should not be used. + assertThat(worker).isNull() + } + + @Test + fun testConfiguredWorkerFactory_createWorker_workerIsBootstrapWorker_returnsBootstrapWorker() { + val workerFactory = configuration.workerFactory + + val worker = + workerFactory.createWorker( + context, BootstrapOppiaWorker::class.java.name, createWorkerParameters() + ) + + // A valid request to create a bootstrap worker should return one of those workers. + assertThat(worker).isInstanceOf(BootstrapOppiaWorker::class.java) + } + + private fun createWorkerParameters(): WorkerParameters { + val taskExecutor = object : TaskExecutor { + override fun postToMainThread(runnable: Runnable?) = error("Not used.") + override fun getMainThreadExecutor() = error("Not used.") + override fun executeOnBackgroundThread(runnable: Runnable?) = error("Not used.") + override fun getBackgroundExecutor() = error("Not used.") + } + return WorkerParameters( + /* id= */ UUID.randomUUID(), + /* inputData= */ Data.EMPTY, + /* tags= */ emptyList(), + /* runtimeExtras= */ WorkerParameters.RuntimeExtras(), + /* runAttemptCount= */ 0, + /* backgroundExecutor= */ directExecutor(), + /* workTaskExecutor= */ taskExecutor, + /* workerFactory= */ configuration.workerFactory, + /* progressUpdater= */ { _, _, _ -> error("Not used.") }, + /* foregroundUpdater= */ { _, _, _ -> error("Not used.") } + ) + } + + private fun setUpTestApplicationComponent() { + ApplicationProvider.getApplicationContext().inject(this) + } + + @Module + interface TestModule { + @Binds fun bindApplicationContext(application: Application): Context + } + + // TODO(#89): Move this to a common test application component. + @Singleton + @Component( + modules = [ + TestModule::class, RobolectricModule::class, TestDispatcherModule::class, + FakeOppiaClockModule::class, + WorkManagerConfigurationModule::class, + LocaleProdModule::class, + LoggerModule::class, + TestPlatformParameterModule::class, + AssetModule::class, + DebugLogReportingModule::class + ] + ) + interface TestApplicationComponent : DispatcherInjector, PlatformParameterControllerInjector { + @Component.Builder + interface Builder { + @BindsInstance + fun setApplication(application: Application): Builder + fun build(): TestApplicationComponent + } + + fun inject(test: WorkManagerConfigurationModuleTest) + } + + class TestApplication : + Application(), DispatcherInjectorProvider, PlatformParameterControllerInjectorProvider { + private val component: TestApplicationComponent by lazy { + DaggerWorkManagerConfigurationModuleTest_TestApplicationComponent.builder() + .setApplication(this) + .build() + } + + fun inject(test: WorkManagerConfigurationModuleTest) { + component.inject(test) + } + + override fun getDispatcherInjector() = component + override fun getPlatformParameterControllerInjector() = component + } + + private class RealListenableWorker( + appContext: Context, + workerParams: WorkerParameters + ) : ListenableWorker(appContext, workerParams) { + override fun startWork(): ListenableFuture { + error("Not implemented for test.") + } + } +} diff --git a/domain/src/test/java/org/oppia/android/domain/workmanager/WorkManagerSchedulerTest.kt b/domain/src/test/java/org/oppia/android/domain/workmanager/WorkManagerSchedulerTest.kt new file mode 100644 index 00000000000..0913b6e100c --- /dev/null +++ b/domain/src/test/java/org/oppia/android/domain/workmanager/WorkManagerSchedulerTest.kt @@ -0,0 +1,474 @@ +package org.oppia.android.domain.workmanager + +import android.app.Application +import android.content.Context +import androidx.test.core.app.ApplicationProvider +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.work.NetworkType.CONNECTED +import androidx.work.NetworkType.NOT_REQUIRED +import androidx.work.WorkInfo +import com.google.common.truth.Truth.assertThat +import com.google.firebase.FirebaseApp +import dagger.Binds +import dagger.BindsInstance +import dagger.Component +import dagger.Module +import dagger.multibindings.IntoMap +import dagger.multibindings.StringKey +import kotlinx.coroutines.CoroutineDispatcher +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.oppia.android.domain.platformparameter.PlatformParameterControllerInjector +import org.oppia.android.domain.platformparameter.PlatformParameterControllerInjectorProvider +import org.oppia.android.domain.workmanager.WorkManagerSchedulerTest.MockOppiaWorker1.TaskType.WORKER1_TASK1 +import org.oppia.android.domain.workmanager.testing.OppiaWorkManagerTestDriver +import org.oppia.android.testing.platformparameter.TestPlatformParameterModule +import org.oppia.android.testing.robolectric.RobolectricModule +import org.oppia.android.testing.threading.TestCoroutineDispatchers +import org.oppia.android.testing.threading.TestDispatcherModule +import org.oppia.android.testing.time.FakeOppiaClockModule +import org.oppia.android.util.caching.AssetModule +import org.oppia.android.util.locale.LocaleProdModule +import org.oppia.android.util.logging.LoggerModule +import org.oppia.android.util.logging.firebase.DebugLogReportingModule +import org.oppia.android.util.threading.BackgroundDispatcher +import org.oppia.android.util.threading.DispatcherInjector +import org.oppia.android.util.threading.DispatcherInjectorProvider +import org.robolectric.annotation.Config +import org.robolectric.annotation.LooperMode +import java.util.concurrent.CopyOnWriteArrayList +import java.util.concurrent.TimeUnit +import javax.inject.Inject +import javax.inject.Singleton + +/** Tests for [WorkManagerScheduler]. */ +@RunWith(AndroidJUnit4::class) +@LooperMode(LooperMode.Mode.PAUSED) +@Config(application = WorkManagerSchedulerTest.TestApplication::class) +@Suppress("FunctionName") // FunctionName: test names are conventionally named with underscores. +class WorkManagerSchedulerTest { + @Inject lateinit var context: Context + @Inject lateinit var testCoroutineDispatchers: TestCoroutineDispatchers + @Inject lateinit var workManagerScheduler: WorkManagerScheduler + @Inject lateinit var testDriver: OppiaWorkManagerTestDriver + @field:[Inject BackgroundDispatcher] lateinit var backgroundDispatcher: CoroutineDispatcher + + @Before + fun setUp() { + setUpTestApplicationComponent() + FirebaseApp.initializeApp(context) + testDriver.initializeWorkManager() + + // Reset static state between tests. + workerResults.clear() + } + + @Test + fun testSchedulePeriodicWorker_invalidWorkerName_cancelsWorker() { + val monitor = schedulePeriodicWorker( + "invalid_worker_name", + WORKER1_TASK1, + repeatInterval = 15, + intervalUnit = TimeUnit.MINUTES + ) + + monitor.forceConstraintsMet() + + assertThat(workerResults).isEmpty() + assertThat(monitor.state).isEqualTo(WorkInfo.State.CANCELLED) + } + + @Test + fun testSchedulePeriodicWorker_invalidTaskType_doesNotRunAndCancelsWorker() { + // Use a task type from a different worker since it won't be valid for this one. + val monitor = schedulePeriodicWorker( + MockOppiaWorker1.WORKER_NAME, + MockOppiaWorker2.TaskType.WORKER2_TASK1, + repeatInterval = 15, + intervalUnit = TimeUnit.MINUTES + ) + + monitor.forceConstraintsMet() + + assertThat(workerResults).isEmpty() + assertThat(monitor.state).isEqualTo(WorkInfo.State.CANCELLED) + } + + @Test + fun testSchedulePeriodicWorker_validWorker_hasWorkerNameInTag() { + val monitor = schedulePeriodicWorker( + MockOppiaWorker1.WORKER_NAME, + WORKER1_TASK1, + repeatInterval = 15, + intervalUnit = TimeUnit.MINUTES + ) + + assertThat(monitor.tags).hasSize(2) + assertThat(monitor.tags[0]).isEqualTo(BootstrapOppiaWorker::class.java.name) + assertThat(monitor.tags[1]).isEqualTo("MockOppiaWorker1.worker1_task1") + } + + @Test + fun testSchedulePeriodicWorker_scheduledForOneSecondPeriod_isClampedToFifteenMinutes() { + val monitor = schedulePeriodicWorker( + MockOppiaWorker1.WORKER_NAME, + WORKER1_TASK1, + repeatInterval = 1, + intervalUnit = TimeUnit.SECONDS + ) + + assertThat(monitor.intervalDurationMs).isEqualTo(TimeUnit.MINUTES.toMillis(15)) + } + + @Test + fun testSchedulePeriodicWorker_scheduledForThreeHundredDays_isClampedToTwoWeeks() { + val monitor = schedulePeriodicWorker( + MockOppiaWorker1.WORKER_NAME, + WORKER1_TASK1, + repeatInterval = 300, + intervalUnit = TimeUnit.DAYS + ) + + assertThat(monitor.intervalDurationMs).isEqualTo(TimeUnit.DAYS.toMillis(14)) + } + + @Test + fun testSchedulePeriodicWorker_validWorker_hasNoLowBatteryConstraint() { + val monitor = schedulePeriodicWorker( + MockOppiaWorker1.WORKER_NAME, + WORKER1_TASK1, + repeatInterval = 15, + intervalUnit = TimeUnit.MINUTES + ) + + assertThat(monitor.requiresBatteryNotLow).isTrue() + } + + @Test + fun testSchedulePeriodicWorker_validWorker_defaultConnectivityArg_hasConnectedNetworkType() { + workManagerScheduler.schedulePeriodicWorker( + MockOppiaWorker1.WORKER_NAME, + WORKER1_TASK1, + repeatInterval = 15, + intervalUnit = TimeUnit.MINUTES + ) + + // The scheduler assumes network connectivity is required by default. + val monitor = testDriver.lookUpPeriodicMonitor(MockOppiaWorker1.WORKER_NAME, WORKER1_TASK1) + assertThat(monitor.requiredNetworkType).isEqualTo(CONNECTED) + } + + @Test + fun testSchedulePeriodicWorker_validWorker_doNotRequireConnectivity_hasNotRequiredNetworkType() { + workManagerScheduler.schedulePeriodicWorker( + MockOppiaWorker1.WORKER_NAME, + WORKER1_TASK1, + repeatInterval = 15, + intervalUnit = TimeUnit.MINUTES, + requireNetworkConnectivity = false + ) + + val monitor = testDriver.lookUpPeriodicMonitor(MockOppiaWorker1.WORKER_NAME, WORKER1_TASK1) + assertThat(monitor.requiredNetworkType).isEqualTo(NOT_REQUIRED) + } + + @Test + fun testSchedulePeriodicWorker_validWorker_requireConnectivity_hasConnectedNetworkType() { + workManagerScheduler.schedulePeriodicWorker( + MockOppiaWorker1.WORKER_NAME, + WORKER1_TASK1, + repeatInterval = 15, + intervalUnit = TimeUnit.MINUTES, + requireNetworkConnectivity = true + ) + + val monitor = testDriver.lookUpPeriodicMonitor(MockOppiaWorker1.WORKER_NAME, WORKER1_TASK1) + assertThat(monitor.requiredNetworkType).isEqualTo(CONNECTED) + } + + @Test + fun testSchedulePeriodicWorker_scheduledTwice_diffConstraints_keepsIdAndConstraints() { + workManagerScheduler.schedulePeriodicWorker( + MockOppiaWorker1.WORKER_NAME, + WORKER1_TASK1, + repeatInterval = 15, + intervalUnit = TimeUnit.MINUTES, + requireNetworkConnectivity = false + ) + val monitor1 = testDriver.lookUpPeriodicMonitor(MockOppiaWorker1.WORKER_NAME, WORKER1_TASK1) + workManagerScheduler.schedulePeriodicWorker( + MockOppiaWorker1.WORKER_NAME, + WORKER1_TASK1, + repeatInterval = 15, + intervalUnit = TimeUnit.MINUTES, + requireNetworkConnectivity = true + ) + val monitor2 = testDriver.lookUpPeriodicMonitor(MockOppiaWorker1.WORKER_NAME, WORKER1_TASK1) + + // TODO(#6115): Update this to check for CONNECTED rather than NOT_REQUIRED once the scheduler + // can use UPDATE as its replacement policy. + // Because the scheduler uses ExistingPeriodicWorkPolicy.KEEP the existing worker should not + // be canceled, its UUID should remain unchanged, and its constraints should be the same. + assertThat(monitor1.id).isEqualTo(monitor2.id) + assertThat(monitor1.requiredNetworkType).isEqualTo(NOT_REQUIRED) + } + + @Test + fun testSchedulePeriodicWorker_validWorker_withConstraintsUnmet_doesNotRun() { + val monitor = schedulePeriodicWorker( + MockOppiaWorker1.WORKER_NAME, + WORKER1_TASK1, + repeatInterval = 15, + intervalUnit = TimeUnit.MINUTES + ) + + testCoroutineDispatchers.runCurrent() + + assertThat(workerResults).isEmpty() + assertThat(monitor.state).isEqualTo(WorkInfo.State.ENQUEUED) + } + + @Test + fun testSchedulePeriodicWorker_validWorker_withConstraintsMet_isRunAndReEnqueued() { + val monitor = schedulePeriodicWorker( + MockOppiaWorker1.WORKER_NAME, + WORKER1_TASK1, + repeatInterval = 15, + intervalUnit = TimeUnit.MINUTES + ) + + monitor.forceConstraintsMet() + + assertThat(workerResults).hasSize(1) + assertThat(workerResults.single()).isEqualTo("Ran MockOppiaWorker1 for task: worker1_task1") + assertThat(monitor.state).isEqualTo(WorkInfo.State.ENQUEUED) + } + + @Test + fun testSchedulePeriodicWorker_validWorker_withConstraintsMetThenUnmet_afterDelay_runsOnce() { + val monitor = schedulePeriodicWorker( + MockOppiaWorker1.WORKER_NAME, + WORKER1_TASK1, + repeatInterval = 15, + intervalUnit = TimeUnit.MINUTES + ) + + monitor.autoTrackConstraints = false + monitor.forceConstraintsMet() + testCoroutineDispatchers.advanceTimeBy(TimeUnit.MINUTES.toMillis(20)) + + // If the constraints aren't met for the second run then the worker only runs once. + assertThat(workerResults).hasSize(1) + assertThat(workerResults.single()).isEqualTo("Ran MockOppiaWorker1 for task: worker1_task1") + assertThat(monitor.state).isEqualTo(WorkInfo.State.ENQUEUED) + } + + @Test + fun testSchedulePeriodicWorker_validWorker_withConstraintsUnmetThenMet_afterDelay_runsOnce() { + val monitor = schedulePeriodicWorker( + MockOppiaWorker1.WORKER_NAME, + WORKER1_TASK1, + repeatInterval = 15, + intervalUnit = TimeUnit.MINUTES + ) + + testCoroutineDispatchers.advanceTimeBy(TimeUnit.MINUTES.toMillis(20)) + monitor.forceConstraintsMet() + + // If the constraints are only met after a certain delay, the worker will run once rather than + // multiple times to "catch up." + assertThat(workerResults).hasSize(1) + assertThat(workerResults.single()).isEqualTo("Ran MockOppiaWorker1 for task: worker1_task1") + assertThat(monitor.state).isEqualTo(WorkInfo.State.ENQUEUED) + } + + @Test + fun testSchedulePeriodicWorker_validWorker_withConstraintsMet_afterDelay_runsTwice() { + val monitor = schedulePeriodicWorker( + MockOppiaWorker1.WORKER_NAME, + WORKER1_TASK1, + repeatInterval = 15, + intervalUnit = TimeUnit.MINUTES + ) + + monitor.forceConstraintsMet() + testCoroutineDispatchers.advanceTimeBy(TimeUnit.MINUTES.toMillis(20)) + + // If time elapses sufficiently for the job to re-run and its constraints are satisfied at that + // time, then it should run again. + assertThat(workerResults).containsExactly( + "Ran MockOppiaWorker1 for task: worker1_task1", + "Ran MockOppiaWorker1 for task: worker1_task1" + ).inOrder() + assertThat(monitor.state).isEqualTo(WorkInfo.State.ENQUEUED) + } + + @Test + fun testSchedulePeriodicWorker_multipleWorkers_withDifferentTimes_constraintsMet_runsEach() { + val monitor1 = schedulePeriodicWorker( + MockOppiaWorker1.WORKER_NAME, + WORKER1_TASK1, + repeatInterval = 15, + intervalUnit = TimeUnit.MINUTES + ) + val monitor2 = schedulePeriodicWorker( + MockOppiaWorker2.WORKER_NAME, + MockOppiaWorker2.TaskType.WORKER2_TASK1, + repeatInterval = 20, + intervalUnit = TimeUnit.MINUTES + ) + + // Simulate the constraints being continually met for both jobs, but note that the two 18 minute + // steps will have different behaviors between the two. The 15 minute job will get one run at + // each 18 minute step, but the 20 minute job will only run one more time. + monitor1.forceConstraintsMet() + monitor2.forceConstraintsMet() + testCoroutineDispatchers.advanceTimeBy(TimeUnit.MINUTES.toMillis(18)) + testCoroutineDispatchers.advanceTimeBy(TimeUnit.MINUTES.toMillis(18)) + + // Note that order isn't checked here because exact execution order isn't guaranteed by + // WorkManager when two workers are scheduled at the same time. + assertThat(workerResults).containsExactly( + "Ran MockOppiaWorker1 for task: worker1_task1", + "Ran MockOppiaWorker2 for task: worker2_task1", + "Ran MockOppiaWorker1 for task: worker1_task1", + "Ran MockOppiaWorker1 for task: worker1_task1", + "Ran MockOppiaWorker2 for task: worker2_task1" + ) + // If time elapses sufficiently for the job to re-run and its constraints are satisfied at that + // time, then it should run again. + assertThat(monitor1.state).isEqualTo(WorkInfo.State.ENQUEUED) + assertThat(monitor2.state).isEqualTo(WorkInfo.State.ENQUEUED) + } + + private fun schedulePeriodicWorker( + workerName: String, + taskType: OppiaWorker.TaskType, + repeatInterval: Long, + intervalUnit: TimeUnit + ): OppiaWorkManagerTestDriver.WorkerMonitor { + workManagerScheduler.schedulePeriodicWorker( + workerName, taskType, repeatInterval, intervalUnit + ) + return testDriver.lookUpPeriodicMonitor(workerName, taskType) + } + + private fun setUpTestApplicationComponent() { + ApplicationProvider.getApplicationContext().inject(this) + } + + companion object { + val workerResults = CopyOnWriteArrayList() + } + + @Module + interface TestModule { + @Binds fun bindApplicationContext(application: Application): Context + } + + @Module + interface TestWorkManagerModule { + @Binds + @IntoMap + @StringKey(MockOppiaWorker1.WORKER_NAME) + fun provideMockOppiaWorker1Factory( + factory: MockOppiaWorker1.Factory + ): OppiaWorker.Factory<*> + + @Binds + @IntoMap + @StringKey(MockOppiaWorker2.WORKER_NAME) + fun provideMockOppiaWorker2Factory( + factory: MockOppiaWorker2.Factory + ): OppiaWorker.Factory<*> + } + + // TODO(#89): Move this to a common test application component. + @Singleton + @Component( + modules = [ + TestModule::class, RobolectricModule::class, TestDispatcherModule::class, + FakeOppiaClockModule::class, TestWorkManagerModule::class, + LocaleProdModule::class, + LoggerModule::class, + TestPlatformParameterModule::class, + AssetModule::class, + DebugLogReportingModule::class + ] + ) + interface TestApplicationComponent : DispatcherInjector, PlatformParameterControllerInjector { + @Component.Builder + interface Builder { + @BindsInstance + fun setApplication(application: Application): Builder + fun build(): TestApplicationComponent + } + + fun inject(test: WorkManagerSchedulerTest) + } + + class TestApplication : + Application(), DispatcherInjectorProvider, PlatformParameterControllerInjectorProvider { + private val component: TestApplicationComponent by lazy { + DaggerWorkManagerSchedulerTest_TestApplicationComponent.builder() + .setApplication(this) + .build() + } + + fun inject(test: WorkManagerSchedulerTest) { + component.inject(test) + } + + override fun getDispatcherInjector() = component + override fun getPlatformParameterControllerInjector() = component + } + + class MockOppiaWorker1 private constructor() : OppiaWorker { + enum class TaskType(override val persistentName: String) : OppiaWorker.TaskType { + WORKER1_TASK1("worker1_task1"), + WORKER1_TASK2("worker1_task2"), + WORKER1_TASK3("worker1_task3") + } + + override suspend fun doWork(taskType: TaskType): OppiaWorker.Result { + workerResults += "Ran MockOppiaWorker1 for task: ${taskType.persistentName}" + return OppiaWorker.Result.SUCCESS + } + + class Factory @Inject constructor() : OppiaWorker.Factory { + override val supportedTaskTypes: List = TaskType.values().toList() + + override fun createWorker(): OppiaWorker { + return MockOppiaWorker1() + } + } + + companion object { + const val WORKER_NAME = "MockOppiaWorker1" + } + } + + class MockOppiaWorker2 private constructor() : OppiaWorker { + enum class TaskType(override val persistentName: String) : OppiaWorker.TaskType { + WORKER2_TASK1("worker2_task1") + } + + override suspend fun doWork(taskType: TaskType): OppiaWorker.Result { + workerResults += "Ran MockOppiaWorker2 for task: ${taskType.persistentName}" + return OppiaWorker.Result.SUCCESS + } + + class Factory @Inject constructor() : OppiaWorker.Factory { + override val supportedTaskTypes: List = TaskType.values().toList() + + override fun createWorker(): OppiaWorker { + return MockOppiaWorker2() + } + } + + companion object { + const val WORKER_NAME = "MockOppiaWorker2" + } + } +} diff --git a/domain/src/test/java/org/oppia/android/domain/workmanager/debug/BUILD.bazel b/domain/src/test/java/org/oppia/android/domain/workmanager/debug/BUILD.bazel new file mode 100644 index 00000000000..6c2d80f2c67 --- /dev/null +++ b/domain/src/test/java/org/oppia/android/domain/workmanager/debug/BUILD.bazel @@ -0,0 +1,92 @@ +""" +Tests for the debug work manager worker that can be used for validating the work manager +configuration in the app. +""" + +load("//:oppia_android_test.bzl", "oppia_android_test") + +oppia_android_test( + name = "DebugWorkerDebugModuleTest", + srcs = ["DebugWorkerDebugModuleTest.kt"], + custom_package = "org.oppia.android.domain.debug", + test_class = "org.oppia.android.domain.workmanager.debug.DebugWorkerDebugModuleTest", + test_manifest = "//domain:test_manifest", + deps = [ + "//:dagger", + "//domain/src/main/java/org/oppia/android/domain/workmanager/debug:debug_module", + "//testing/src/main/java/org/oppia/android/testing/robolectric:test_module", + "//testing/src/main/java/org/oppia/android/testing/threading:test_module", + "//testing/src/main/java/org/oppia/android/testing/time:test_module", + "//third_party:androidx_test_ext_junit", + "//third_party:com_google_truth_truth", + "//third_party:junit_junit", + "//third_party:org_robolectric_robolectric", + "//third_party:robolectric_android-all", + "//utility/src/main/java/org/oppia/android/util/locale:prod_module", + "//utility/src/main/java/org/oppia/android/util/logging:prod_module", + ], +) + +oppia_android_test( + name = "DebugWorkerSchedulerTest", + srcs = ["DebugWorkerSchedulerTest.kt"], + custom_package = "org.oppia.android.domain.debug", + test_class = "org.oppia.android.domain.workmanager.debug.DebugWorkerSchedulerTest", + test_manifest = "//domain:test_manifest", + deps = [ + "//:dagger", + "//domain/src/main/java/org/oppia/android/domain/workmanager:oppia_worker", + "//domain/src/main/java/org/oppia/android/domain/workmanager:work_manager_configuration_module", + "//domain/src/main/java/org/oppia/android/domain/workmanager:work_manager_scheduler", + "//domain/src/main/java/org/oppia/android/domain/workmanager/debug:debug_module", + "//domain/src/main/java/org/oppia/android/domain/workmanager/debug:scheduler", + "//domain/src/main/java/org/oppia/android/domain/workmanager/debug:worker", + "//domain/src/main/java/org/oppia/android/domain/workmanager/testing:oppia_work_manager_test_driver", + "//testing/src/main/java/org/oppia/android/testing/platformparameter:test_module", + "//testing/src/main/java/org/oppia/android/testing/robolectric:test_module", + "//testing/src/main/java/org/oppia/android/testing/threading:test_coroutine_dispatchers", + "//testing/src/main/java/org/oppia/android/testing/threading:test_module", + "//testing/src/main/java/org/oppia/android/testing/time:test_module", + "//third_party:androidx_test_ext_junit", + "//third_party:androidx_work_work-runtime", + "//third_party:com_google_truth_truth", + "//third_party:junit_junit", + "//third_party:org_robolectric_robolectric", + "//third_party:robolectric_android-all", + "//utility/src/main/java/org/oppia/android/util/caching:asset_prod_module", + "//utility/src/main/java/org/oppia/android/util/locale:prod_module", + "//utility/src/main/java/org/oppia/android/util/logging:prod_module", + "//utility/src/main/java/org/oppia/android/util/logging/firebase:debug_module", + ], +) + +oppia_android_test( + name = "DebugWorkerTest", + srcs = ["DebugWorkerTest.kt"], + custom_package = "org.oppia.android.domain.debug", + test_class = "org.oppia.android.domain.workmanager.debug.DebugWorkerTest", + test_manifest = "//domain:test_manifest", + deps = [ + "//:dagger", + "//domain/src/main/java/org/oppia/android/domain/workmanager:oppia_worker", + "//domain/src/main/java/org/oppia/android/domain/workmanager:work_manager_configuration_module", + "//domain/src/main/java/org/oppia/android/domain/workmanager/debug:debug_module", + "//domain/src/main/java/org/oppia/android/domain/workmanager/debug:worker", + "//domain/src/main/java/org/oppia/android/domain/workmanager/testing:oppia_work_manager_test_driver", + "//testing/src/main/java/org/oppia/android/testing/platformparameter:test_module", + "//testing/src/main/java/org/oppia/android/testing/robolectric:test_module", + "//testing/src/main/java/org/oppia/android/testing/threading:test_coroutine_dispatchers", + "//testing/src/main/java/org/oppia/android/testing/threading:test_module", + "//testing/src/main/java/org/oppia/android/testing/time:test_module", + "//third_party:androidx_test_ext_junit", + "//third_party:androidx_work_work-runtime", + "//third_party:com_google_truth_truth", + "//third_party:junit_junit", + "//third_party:org_robolectric_robolectric", + "//third_party:robolectric_android-all", + "//utility/src/main/java/org/oppia/android/util/caching:asset_prod_module", + "//utility/src/main/java/org/oppia/android/util/locale:prod_module", + "//utility/src/main/java/org/oppia/android/util/logging:prod_module", + "//utility/src/main/java/org/oppia/android/util/logging/firebase:debug_module", + ], +) diff --git a/domain/src/test/java/org/oppia/android/domain/workmanager/debug/DebugWorkerDebugModuleTest.kt b/domain/src/test/java/org/oppia/android/domain/workmanager/debug/DebugWorkerDebugModuleTest.kt new file mode 100644 index 00000000000..7f98e252790 --- /dev/null +++ b/domain/src/test/java/org/oppia/android/domain/workmanager/debug/DebugWorkerDebugModuleTest.kt @@ -0,0 +1,100 @@ +package org.oppia.android.domain.workmanager.debug + +import android.app.Application +import android.content.Context +import androidx.test.core.app.ApplicationProvider +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.google.common.truth.Truth.assertThat +import dagger.Binds +import dagger.BindsInstance +import dagger.Component +import dagger.Module +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.oppia.android.domain.workmanager.OppiaWorker +import org.oppia.android.domain.workmanager.StartupWorkerScheduleReadinessListener +import org.oppia.android.testing.robolectric.RobolectricModule +import org.oppia.android.testing.threading.TestDispatcherModule +import org.oppia.android.testing.time.FakeOppiaClockModule +import org.oppia.android.util.locale.LocaleProdModule +import org.oppia.android.util.logging.LoggerModule +import org.robolectric.annotation.Config +import org.robolectric.annotation.LooperMode +import javax.inject.Inject +import javax.inject.Singleton + +/** Tests for [DebugWorkerDebugModule]. */ +@RunWith(AndroidJUnit4::class) +@LooperMode(LooperMode.Mode.PAUSED) +@Config(application = DebugWorkerDebugModuleTest.TestApplication::class) +@Suppress("FunctionName") // FunctionName: test names are conventionally named with underscores. +class DebugWorkerDebugModuleTest { + @Inject lateinit var context: Context + @Inject + lateinit var readinessListeners: Set<@JvmSuppressWildcards StartupWorkerScheduleReadinessListener> + @Inject lateinit var factories: Map> + + @Before + fun setUp() { + setUpTestApplicationComponent() + } + + @Test + fun testInjection_bindsDebugWorkerSchedulerIntoReadinessListenerSet() { + // The main test is that the test suite builds. + val debugWorkerSchedulers = readinessListeners.filterIsInstance() + assertThat(readinessListeners).isNotEmpty() + assertThat(debugWorkerSchedulers).hasSize(1) + } + + @Test + fun testInjection_bindsDebugWorkerFactoryByWorkerName() { + assertThat(factories).containsKey(DebugWorker.WORKER_NAME) + assertThat(factories[DebugWorker.WORKER_NAME]).isInstanceOf(DebugWorker.Factory::class.java) + } + + private fun setUpTestApplicationComponent() { + ApplicationProvider.getApplicationContext().inject(this) + } + + @Module + interface TestModule { + @Binds fun bindApplicationContext(application: Application): Context + } + + // TODO(#89): Move this to a common test application component. + @Singleton + @Component( + modules = [ + TestModule::class, + DebugWorkerDebugModule::class, + LocaleProdModule::class, + FakeOppiaClockModule::class, + LoggerModule::class, + RobolectricModule::class, TestDispatcherModule::class, + ] + ) + interface TestApplicationComponent { + @Component.Builder + interface Builder { + @BindsInstance + fun setApplication(application: Application): Builder + fun build(): TestApplicationComponent + } + + fun inject(test: DebugWorkerDebugModuleTest) + } + + class TestApplication : Application() { + private val component: TestApplicationComponent by lazy { + DaggerDebugWorkerDebugModuleTest_TestApplicationComponent.builder() + .setApplication(this) + .build() + } + + fun inject(test: DebugWorkerDebugModuleTest) { + component.inject(test) + } + } +} diff --git a/domain/src/test/java/org/oppia/android/domain/workmanager/debug/DebugWorkerSchedulerTest.kt b/domain/src/test/java/org/oppia/android/domain/workmanager/debug/DebugWorkerSchedulerTest.kt new file mode 100644 index 00000000000..64047da19f4 --- /dev/null +++ b/domain/src/test/java/org/oppia/android/domain/workmanager/debug/DebugWorkerSchedulerTest.kt @@ -0,0 +1,207 @@ +package org.oppia.android.domain.workmanager.debug + +import android.app.Application +import android.content.Context +import android.util.Log +import androidx.test.core.app.ApplicationProvider +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.work.Configuration +import androidx.work.NetworkType.CONNECTED +import androidx.work.NetworkType.NOT_REQUIRED +import androidx.work.WorkInfo +import com.google.common.truth.Truth.assertThat +import com.google.firebase.FirebaseApp +import dagger.Binds +import dagger.BindsInstance +import dagger.Component +import dagger.Module +import kotlinx.coroutines.CoroutineDispatcher +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.oppia.android.domain.platformparameter.PlatformParameterControllerInjector +import org.oppia.android.domain.platformparameter.PlatformParameterControllerInjectorProvider +import org.oppia.android.domain.workmanager.WorkManagerConfigurationModule +import org.oppia.android.domain.workmanager.WorkManagerScheduler +import org.oppia.android.domain.workmanager.debug.DebugWorker.Companion.WORKER_NAME +import org.oppia.android.domain.workmanager.debug.DebugWorker.Operation.RUN_EVERY_FIFTEEN_MINUTES_WITH_CONNECTIVITY +import org.oppia.android.domain.workmanager.debug.DebugWorker.Operation.RUN_EVERY_SIX_HOURS_WITH_OR_WITHOUT_CONNECTIVITY +import org.oppia.android.domain.workmanager.debug.DebugWorker.Operation.RUN_EVERY_TWENTY_MINUTES_WITH_OR_WITHOUT_CONNECTIVITY +import org.oppia.android.domain.workmanager.testing.OppiaWorkManagerTestDriver +import org.oppia.android.testing.platformparameter.TestPlatformParameterModule +import org.oppia.android.testing.robolectric.RobolectricModule +import org.oppia.android.testing.threading.TestCoroutineDispatchers +import org.oppia.android.testing.threading.TestDispatcherModule +import org.oppia.android.testing.time.FakeOppiaClockModule +import org.oppia.android.util.caching.AssetModule +import org.oppia.android.util.locale.LocaleProdModule +import org.oppia.android.util.logging.LoggerModule +import org.oppia.android.util.logging.firebase.DebugLogReportingModule +import org.oppia.android.util.threading.BackgroundDispatcher +import org.oppia.android.util.threading.DispatcherInjector +import org.oppia.android.util.threading.DispatcherInjectorProvider +import org.robolectric.annotation.Config +import org.robolectric.annotation.LooperMode +import org.robolectric.shadows.ShadowLog +import java.util.concurrent.TimeUnit +import javax.inject.Inject +import javax.inject.Singleton + +/** Tests for [DebugWorkerScheduler]. */ +@RunWith(AndroidJUnit4::class) +@LooperMode(LooperMode.Mode.PAUSED) +@Config(application = DebugWorkerSchedulerTest.TestApplication::class) +@Suppress("FunctionName") // FunctionName: test names are conventionally named with underscores. +class DebugWorkerSchedulerTest { + @Inject lateinit var context: Context + @Inject lateinit var testCoroutineDispatchers: TestCoroutineDispatchers + @Inject lateinit var configuration: Configuration + @Inject lateinit var workManagerScheduler: WorkManagerScheduler + @Inject lateinit var debugWorkerScheduler: DebugWorkerScheduler + @Inject lateinit var testDriver: OppiaWorkManagerTestDriver + + @field:[Inject BackgroundDispatcher] + lateinit var backgroundDispatcher: CoroutineDispatcher + + @Before + fun setUp() { + setUpTestApplicationComponent() + FirebaseApp.initializeApp(context) + testDriver.initializeWorkManager(configuration) + } + + @Test + fun testDebugWorker_hasThreeOperationTypes() { + // A change detector test that, if failing, means that other tests in this suite need to be + // updated to ensure that the new operation type is properly verified. + assertThat(DebugWorker.Operation.values().toList()).containsExactly( + RUN_EVERY_FIFTEEN_MINUTES_WITH_CONNECTIVITY, + RUN_EVERY_TWENTY_MINUTES_WITH_OR_WITHOUT_CONNECTIVITY, + RUN_EVERY_SIX_HOURS_WITH_OR_WITHOUT_CONNECTIVITY + ) + } + + @Test + fun testScheduleWork_schedulesDebugWorkerToRunEveryFifteenMinsWithConnectivity() { + debugWorkerScheduler.scheduleWork(workManagerScheduler) + + // Check what's been scheduled. One of the workers should be a DebugWorker that runs every 15 + // minutes but only if there's internet connectivity. + val monitor = + testDriver.lookUpPeriodicMonitor(WORKER_NAME, RUN_EVERY_FIFTEEN_MINUTES_WITH_CONNECTIVITY) + assertThat(monitor.state).isEqualTo(WorkInfo.State.ENQUEUED) + assertThat(monitor.intervalDurationMs).isEqualTo(TimeUnit.MINUTES.toMillis(15)) + assertThat(monitor.requiredNetworkType).isEqualTo(CONNECTED) + } + + @Test + fun testScheduleWork_schedulesDebugWorkerToRunEveryTwentyMinsWithOrWithoutConnectivity() { + debugWorkerScheduler.scheduleWork(workManagerScheduler) + + // Check what's been scheduled. One of the workers should be a DebugWorker that runs every 15 + // minutes but only if there's internet connectivity. + val monitor = + testDriver.lookUpPeriodicMonitor( + WORKER_NAME, RUN_EVERY_TWENTY_MINUTES_WITH_OR_WITHOUT_CONNECTIVITY + ) + assertThat(monitor.state).isEqualTo(WorkInfo.State.ENQUEUED) + assertThat(monitor.intervalDurationMs).isEqualTo(TimeUnit.MINUTES.toMillis(20)) + assertThat(monitor.requiredNetworkType).isEqualTo(NOT_REQUIRED) + } + + @Test + fun testScheduleWork_schedulesDebugWorkerToRunEverySixHoursWithOrWithoutConnectivity() { + debugWorkerScheduler.scheduleWork(workManagerScheduler) + + // Check what's been scheduled. One of the workers should be a DebugWorker that runs every 15 + // minutes but only if there's internet connectivity. + val monitor = + testDriver.lookUpPeriodicMonitor( + WORKER_NAME, RUN_EVERY_SIX_HOURS_WITH_OR_WITHOUT_CONNECTIVITY + ) + assertThat(monitor.state).isEqualTo(WorkInfo.State.ENQUEUED) + assertThat(monitor.intervalDurationMs).isEqualTo(TimeUnit.HOURS.toMillis(6)) + assertThat(monitor.requiredNetworkType).isEqualTo(NOT_REQUIRED) + } + + @Test + fun testScheduleWork_constraintsMet_runsThreeJobs() { + debugWorkerScheduler.scheduleWork(workManagerScheduler) + + forceConstraintsMet(RUN_EVERY_FIFTEEN_MINUTES_WITH_CONNECTIVITY) + forceConstraintsMet(RUN_EVERY_TWENTY_MINUTES_WITH_OR_WITHOUT_CONNECTIVITY) + forceConstraintsMet(RUN_EVERY_SIX_HOURS_WITH_OR_WITHOUT_CONNECTIVITY) + testCoroutineDispatchers.runCurrent() + + // Order cannot be easily checked here, so just verify that they ran. + assertThat(fetchDebugWorkerDebugLogs()).containsExactly( + "Debug worker ran with config: RUN_EVERY_FIFTEEN_MINUTES_WITH_CONNECTIVITY.", + "Debug worker ran with config: RUN_EVERY_TWENTY_MINUTES_WITH_OR_WITHOUT_CONNECTIVITY.", + "Debug worker ran with config: RUN_EVERY_SIX_HOURS_WITH_OR_WITHOUT_CONNECTIVITY." + ) + } + + private fun forceConstraintsMet(taskType: DebugWorker.Operation) { + testDriver.lookUpPeriodicMonitor(WORKER_NAME, taskType).forceConstraintsMet() + } + + private fun fetchDebugWorkerDebugLogs(): List { + // Extract all logs from the bootstrap worker and validate they are each errors before returning + // the logged message lines. + return ShadowLog.getLogs().filter { it.tag == WORKER_NAME }.map { + assertThat(it.type).isEqualTo(Log.DEBUG) + return@map it.msg + } + } + private fun setUpTestApplicationComponent() { + ApplicationProvider.getApplicationContext().inject(this) + } + + @Module + interface TestModule { + @Binds + fun bindApplicationContext(application: Application): Context + } + + // TODO(#89): Move this to a common test application component. + @Singleton + @Component( + modules = [ + TestModule::class, RobolectricModule::class, TestDispatcherModule::class, + FakeOppiaClockModule::class, + DebugWorkerDebugModule::class, + WorkManagerConfigurationModule::class, + LocaleProdModule::class, + LoggerModule::class, + TestPlatformParameterModule::class, + AssetModule::class, + DebugLogReportingModule::class + ] + ) + interface TestApplicationComponent : DispatcherInjector, PlatformParameterControllerInjector { + @Component.Builder + interface Builder { + @BindsInstance + fun setApplication(application: Application): Builder + fun build(): TestApplicationComponent + } + + fun inject(test: DebugWorkerSchedulerTest) + } + + class TestApplication : + Application(), DispatcherInjectorProvider, PlatformParameterControllerInjectorProvider { + private val component: TestApplicationComponent by lazy { + DaggerDebugWorkerSchedulerTest_TestApplicationComponent.builder() + .setApplication(this) + .build() + } + + fun inject(test: DebugWorkerSchedulerTest) { + component.inject(test) + } + + override fun getDispatcherInjector() = component + override fun getPlatformParameterControllerInjector() = component + } +} diff --git a/domain/src/test/java/org/oppia/android/domain/workmanager/debug/DebugWorkerTest.kt b/domain/src/test/java/org/oppia/android/domain/workmanager/debug/DebugWorkerTest.kt new file mode 100644 index 00000000000..e2066989620 --- /dev/null +++ b/domain/src/test/java/org/oppia/android/domain/workmanager/debug/DebugWorkerTest.kt @@ -0,0 +1,165 @@ +package org.oppia.android.domain.workmanager.debug + +import android.app.Application +import android.content.Context +import android.util.Log +import androidx.test.core.app.ApplicationProvider +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.work.Configuration +import androidx.work.WorkInfo +import com.google.common.truth.Truth.assertThat +import com.google.firebase.FirebaseApp +import dagger.Binds +import dagger.BindsInstance +import dagger.Component +import dagger.Module +import kotlinx.coroutines.CoroutineDispatcher +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.oppia.android.domain.platformparameter.PlatformParameterControllerInjector +import org.oppia.android.domain.platformparameter.PlatformParameterControllerInjectorProvider +import org.oppia.android.domain.workmanager.WorkManagerConfigurationModule +import org.oppia.android.domain.workmanager.debug.DebugWorker.Companion.WORKER_NAME +import org.oppia.android.domain.workmanager.debug.DebugWorker.Operation.RUN_EVERY_FIFTEEN_MINUTES_WITH_CONNECTIVITY +import org.oppia.android.domain.workmanager.debug.DebugWorker.Operation.RUN_EVERY_SIX_HOURS_WITH_OR_WITHOUT_CONNECTIVITY +import org.oppia.android.domain.workmanager.debug.DebugWorker.Operation.RUN_EVERY_TWENTY_MINUTES_WITH_OR_WITHOUT_CONNECTIVITY +import org.oppia.android.domain.workmanager.testing.OppiaWorkManagerTestDriver +import org.oppia.android.testing.platformparameter.TestPlatformParameterModule +import org.oppia.android.testing.robolectric.RobolectricModule +import org.oppia.android.testing.threading.TestCoroutineDispatchers +import org.oppia.android.testing.threading.TestDispatcherModule +import org.oppia.android.testing.time.FakeOppiaClockModule +import org.oppia.android.util.caching.AssetModule +import org.oppia.android.util.locale.LocaleProdModule +import org.oppia.android.util.logging.LoggerModule +import org.oppia.android.util.logging.firebase.DebugLogReportingModule +import org.oppia.android.util.threading.BackgroundDispatcher +import org.oppia.android.util.threading.DispatcherInjector +import org.oppia.android.util.threading.DispatcherInjectorProvider +import org.robolectric.annotation.Config +import org.robolectric.annotation.LooperMode +import org.robolectric.shadows.ShadowLog +import javax.inject.Inject +import javax.inject.Singleton + +/** Tests for [DebugWorker]. */ +@RunWith(AndroidJUnit4::class) +@LooperMode(LooperMode.Mode.PAUSED) +@Config(application = DebugWorkerTest.TestApplication::class) +@Suppress("FunctionName") // FunctionName: test names are conventionally named with underscores. +class DebugWorkerTest { + @Inject lateinit var context: Context + @Inject lateinit var testCoroutineDispatchers: TestCoroutineDispatchers + @Inject lateinit var configuration: Configuration + @Inject lateinit var testDriver: OppiaWorkManagerTestDriver + @field:[Inject BackgroundDispatcher] lateinit var backgroundDispatcher: CoroutineDispatcher + + @Before + fun setUp() { + setUpTestApplicationComponent() + FirebaseApp.initializeApp(context) + testDriver.initializeWorkManager(configuration) + } + + @Test + fun testWorker_runEveryFifteenMinsWithConnectivity_oneOff_printsLogAndSucceeds() { + val monitor = testDriver.runOneOffWork(WORKER_NAME, RUN_EVERY_FIFTEEN_MINUTES_WITH_CONNECTIVITY) + + val debugLine = fetchSingleDebugWorkerDebugLog() + assertThat(monitor.state).isEqualTo(WorkInfo.State.SUCCEEDED) + assertThat(debugLine) + .isEqualTo("Debug worker ran with config: RUN_EVERY_FIFTEEN_MINUTES_WITH_CONNECTIVITY.") + } + + @Test + fun testWorker_runEveryTwentyMinsMinsWithOrWithoutConnectivity_oneOff_printsLogAndSucceeds() { + val monitor = + testDriver.runOneOffWork(WORKER_NAME, RUN_EVERY_TWENTY_MINUTES_WITH_OR_WITHOUT_CONNECTIVITY) + + val debugLine = fetchSingleDebugWorkerDebugLog() + assertThat(monitor.state).isEqualTo(WorkInfo.State.SUCCEEDED) + assertThat(debugLine) + .isEqualTo( + "Debug worker ran with config:" + + " RUN_EVERY_TWENTY_MINUTES_WITH_OR_WITHOUT_CONNECTIVITY." + ) + } + + @Test + fun testWorker_runEverySixHoursWithOrWithoutConnectivity_oneOff_printsLogAndSucceeds() { + val monitor = + testDriver.runOneOffWork(WORKER_NAME, RUN_EVERY_SIX_HOURS_WITH_OR_WITHOUT_CONNECTIVITY) + + val debugLine = fetchSingleDebugWorkerDebugLog() + assertThat(monitor.state).isEqualTo(WorkInfo.State.SUCCEEDED) + assertThat(debugLine) + .isEqualTo("Debug worker ran with config: RUN_EVERY_SIX_HOURS_WITH_OR_WITHOUT_CONNECTIVITY.") + } + + private fun fetchDebugWorkerDebugLogs(): List { + // Extract all logs from the bootstrap worker and validate they are each errors before returning + // the logged message lines. + return ShadowLog.getLogs().filter { it.tag == WORKER_NAME }.map { + assertThat(it.type).isEqualTo(Log.DEBUG) + return@map it.msg + } + } + + private fun fetchSingleDebugWorkerDebugLog(): String { + val debugLogs = fetchDebugWorkerDebugLogs() + assertThat(debugLogs).hasSize(1) + return debugLogs.single() + } + + private fun setUpTestApplicationComponent() { + ApplicationProvider.getApplicationContext().inject(this) + } + + @Module + interface TestModule { + @Binds fun bindApplicationContext(application: Application): Context + } + + // TODO(#89): Move this to a common test application component. + @Singleton + @Component( + modules = [ + TestModule::class, RobolectricModule::class, TestDispatcherModule::class, + FakeOppiaClockModule::class, + DebugWorkerDebugModule::class, + WorkManagerConfigurationModule::class, + LocaleProdModule::class, + LoggerModule::class, + TestPlatformParameterModule::class, + AssetModule::class, + DebugLogReportingModule::class + ] + ) + interface TestApplicationComponent : DispatcherInjector, PlatformParameterControllerInjector { + @Component.Builder + interface Builder { + @BindsInstance + fun setApplication(application: Application): Builder + fun build(): TestApplicationComponent + } + + fun inject(test: DebugWorkerTest) + } + + class TestApplication : + Application(), DispatcherInjectorProvider, PlatformParameterControllerInjectorProvider { + private val component: TestApplicationComponent by lazy { + DaggerDebugWorkerTest_TestApplicationComponent.builder() + .setApplication(this) + .build() + } + + fun inject(test: DebugWorkerTest) { + component.inject(test) + } + + override fun getDispatcherInjector() = component + override fun getPlatformParameterControllerInjector() = component + } +} diff --git a/instrumentation/src/java/org/oppia/android/instrumentation/application/TestApplicationComponent.kt b/instrumentation/src/java/org/oppia/android/instrumentation/application/TestApplicationComponent.kt index 5f07d83d33f..accb4d7c0e2 100644 --- a/instrumentation/src/java/org/oppia/android/instrumentation/application/TestApplicationComponent.kt +++ b/instrumentation/src/java/org/oppia/android/instrumentation/application/TestApplicationComponent.kt @@ -53,7 +53,6 @@ import org.oppia.android.util.locale.LocaleProdModule import org.oppia.android.util.logging.LoggerModule import org.oppia.android.util.logging.SyncStatusModule import org.oppia.android.util.logging.firebase.DebugLogReportingModule -import org.oppia.android.util.logging.firebase.FirebaseLogUploaderModule import org.oppia.android.util.logging.performancemetrics.PerformanceMetricsAssessorModule import org.oppia.android.util.logging.performancemetrics.PerformanceMetricsConfigurationsModule import org.oppia.android.util.networking.NetworkConnectionDebugUtilModule @@ -88,7 +87,7 @@ import javax.inject.Singleton UncaughtExceptionLoggerModule::class, ApplicationStartupListenerModule::class, LogReportWorkerModule::class, WorkManagerConfigurationModule::class, HintsAndSolutionConfigModule::class, HintsAndSolutionProdModule::class, - FirebaseLogUploaderModule::class, RetrofitModule::class, RetrofitServiceModule::class, + RetrofitModule::class, RetrofitServiceModule::class, PlatformParameterModule::class, PlatformParameterSingletonModule::class, ExplorationStorageModule::class, DeveloperOptionsStarterModule::class, DeveloperOptionsModule::class, PlatformParameterSyncUpWorkerModule::class, diff --git a/scripts/assets/file_content_validation_checks.textproto b/scripts/assets/file_content_validation_checks.textproto index 4eab5010dec..4b5ea80e036 100644 --- a/scripts/assets/file_content_validation_checks.textproto +++ b/scripts/assets/file_content_validation_checks.textproto @@ -22,12 +22,6 @@ file_content_checks { exempted_file_name: "utility/src/main/java/org/oppia/android/util/accessibility/FakeAccessibilityService.kt" exempted_file_name: "utility/src/test/java/org/oppia/android/util/accessibility/FakeAccessibilityServiceTest.kt" } -file_content_checks { - file_path_regex: ".+?\\.kt" - prohibited_content_regex: "CoroutineWorker" - failure_message: "For stable tests, prefer using ListenableWorker with an Oppia-managed dispatcher." - exempted_file_name: "scripts/src/javatests/org/oppia/android/scripts/regex/RegexPatternValidationCheckTest.kt" -} file_content_checks { file_path_regex: ".+?\\.kt" prohibited_content_regex: "SettableFuture" @@ -448,10 +442,48 @@ file_content_checks { } file_content_checks { file_path_regex: ".+?\\.kt$" - prohibited_content_regex: "WorkManager.getInstance" - failure_message: "Use AnalyticsStartupListener to retrieve an instance of WorkManager rather than fetching one using getInstance (as the latter may create a WorkManager if one isn't already present, and the application may intend to disable it)." - exempted_file_name: "app/src/main/java/org/oppia/android/app/application/AbstractOppiaApplication.kt" - exempted_file_patterns: ".+?Test\\.kt" + prohibited_content_regex: "CoroutineWorker" + failure_message: "Never use implement a CoroutineWorker directly. Instead, create a new OppiaWorker and rely on BootstrapOppiaWorker for direct interaction with WorkManager. See the WorkManager wiki page for more." + exempted_file_name: "scripts/src/javatests/org/oppia/android/scripts/regex/RegexPatternValidationCheckTest.kt" +} +file_content_checks { + file_path_regex: ".+?\\.kt$" + prohibited_content_regex: "androidx.work.ListenableWorker" + failure_message: "Never use implement a ListenableWorker directly. Instead, create a new OppiaWorker and rely on BootstrapOppiaWorker for direct interaction with WorkManager. See the WorkManager wiki page for more." + exempted_file_name: "domain/src/main/java/org/oppia/android/domain/workmanager/BootstrapOppiaWorker.kt" + exempted_file_name: "domain/src/main/java/org/oppia/android/domain/workmanager/WorkManagerConfigurationModule.kt" + exempted_file_name: "scripts/src/javatests/org/oppia/android/scripts/regex/RegexPatternValidationCheckTest.kt" + exempted_file_name: "domain/src/main/java/org/oppia/android/domain/workmanager/testing/OppiaWorkManagerTestDriver.kt" + exempted_file_name: "domain/src/test/java/org/oppia/android/domain/workmanager/BootstrapOppiaWorkerTest.kt" + exempted_file_name: "domain/src/test/java/org/oppia/android/domain/workmanager/WorkManagerConfigurationModuleTest.kt" +} +file_content_checks { + file_path_regex: ".+?\\.kt$" + prohibited_content_regex: "androidx.work.testing.TestDriver" + failure_message: "Never use WorkManager's TestDriver directly. Instead, use OppiaWorkManagerTestDriver. See the WorkManager wiki page for more." + exempted_file_name: "scripts/src/javatests/org/oppia/android/scripts/regex/RegexPatternValidationCheckTest.kt" + exempted_file_name: "domain/src/main/java/org/oppia/android/domain/workmanager/testing/OppiaWorkManagerTestDriver.kt" +} +file_content_checks { + file_path_regex: ".+?\\.kt$" + prohibited_content_regex: "WorkManagerTestInitHelper.getTestDriver" + failure_message: "Never use WorkManager's TestDriver directly. Instead, use OppiaWorkManagerTestDriver. See the WorkManager wiki page for more." + exempted_file_name: "scripts/src/javatests/org/oppia/android/scripts/regex/RegexPatternValidationCheckTest.kt" + exempted_file_name: "domain/src/main/java/org/oppia/android/domain/workmanager/testing/OppiaWorkManagerTestDriver.kt" +} +file_content_checks { + file_path_regex: ".+?\\.kt$" + prohibited_content_regex: "androidx.work.testing.WorkManagerTestInitHelper" + failure_message: "Never use WorkManagerTestInitHelper directly. Instead, use OppiaWorkManagerTestDriver. See the WorkManager wiki page for more." + exempted_file_name: "scripts/src/javatests/org/oppia/android/scripts/regex/RegexPatternValidationCheckTest.kt" + exempted_file_name: "domain/src/main/java/org/oppia/android/domain/workmanager/testing/OppiaWorkManagerTestDriver.kt" +} +file_content_checks { + file_path_regex: ".+?\\.kt$" + prohibited_content_regex: "androidx.work.WorkManager" + failure_message: "Never use WorkManager directly. For production code use OppiaWorker and WorkManagerScheduler. For tests use OppiaWorkManagerTestDriver. See the WorkManager wiki page for more." + exempted_file_name: "scripts/src/javatests/org/oppia/android/scripts/regex/RegexPatternValidationCheckTest.kt" + exempted_file_patterns: "domain/src/main/java/org/oppia/android/domain/workmanager/.+" } file_content_checks { file_path_regex: ".+?\\.kt$" diff --git a/scripts/assets/kdoc_validity_exemptions.textproto b/scripts/assets/kdoc_validity_exemptions.textproto index 7ecc196a8c9..8247094b410 100644 --- a/scripts/assets/kdoc_validity_exemptions.textproto +++ b/scripts/assets/kdoc_validity_exemptions.textproto @@ -1,7 +1,6 @@ exempted_file_path: "app/src/main/java/org/oppia/android/app/activity/ActivityComponentImpl.kt" exempted_file_path: "app/src/main/java/org/oppia/android/app/application/ApplicationComponent.kt" exempted_file_path: "app/src/main/java/org/oppia/android/app/application/ApplicationInjectorProvider.kt" -exempted_file_path: "app/src/main/java/org/oppia/android/app/application/ApplicationStartupListenerModule.kt" exempted_file_path: "app/src/main/java/org/oppia/android/app/administratorcontrols/RouteToProfileListListener.kt" exempted_file_path: "app/src/main/java/org/oppia/android/app/notice/AutomaticAppDeprecationNoticeDialogFragmentPresenter.kt" exempted_file_path: "app/src/main/java/org/oppia/android/app/devoptions/DeveloperOptionsActivityPresenter.kt" @@ -276,11 +275,9 @@ exempted_file_path: "domain/src/main/java/org/oppia/android/domain/audio/Cellula exempted_file_path: "domain/src/main/java/org/oppia/android/domain/classify/ClassificationResult.kt" exempted_file_path: "domain/src/main/java/org/oppia/android/domain/classify/rules/GenericRuleClassifier.kt" exempted_file_path: "domain/src/main/java/org/oppia/android/domain/classify/rules/InteractionObjectTypeExtractorRepository.kt" -exempted_file_path: "domain/src/main/java/org/oppia/android/domain/classify/rules/continueinteraction/ContinueModule.kt" exempted_file_path: "domain/src/main/java/org/oppia/android/domain/exploration/ExplorationProgress.kt" exempted_file_path: "domain/src/main/java/org/oppia/android/domain/exploration/lightweightcheckpointing/ExplorationCheckpointController.kt" exempted_file_path: "domain/src/main/java/org/oppia/android/domain/oppialogger/LogStorageModule.kt" -exempted_file_path: "domain/src/main/java/org/oppia/android/domain/oppialogger/loguploader/LogUploadWorker.kt" exempted_file_path: "domain/src/main/java/org/oppia/android/domain/question/QuestionAssessmentProgress.kt" exempted_file_path: "domain/src/main/java/org/oppia/android/domain/question/QuestionConstantsProvider.kt" exempted_file_path: "domain/src/main/java/org/oppia/android/domain/question/QuestionTrainingConstantsProvider.kt" diff --git a/scripts/assets/test_file_exemptions.textproto b/scripts/assets/test_file_exemptions.textproto index 16cb1cfba17..26179ae00c9 100644 --- a/scripts/assets/test_file_exemptions.textproto +++ b/scripts/assets/test_file_exemptions.textproto @@ -3843,16 +3843,28 @@ test_file_exemption { exempted_file_path: "domain/src/main/java/org/oppia/android/domain/oppialogger/OppiaLogger.kt" source_file_is_incompatible_with_code_coverage: true } +test_file_exemption { + exempted_file_path: "domain/src/main/java/org/oppia/android/domain/workmanager/StartupWorkerScheduleReadinessListener.kt" + test_file_not_required: true +} +test_file_exemption { + exempted_file_path: "domain/src/main/java/org/oppia/android/domain/workmanager/StartupWorkerScheduleReadinessMonitor.kt" + test_file_not_required: true +} +test_file_exemption { + exempted_file_path: "domain/src/main/java/org/oppia/android/domain/workmanager/debug/DebugWorkerDebugModule.kt" + source_file_is_incompatible_with_code_coverage: true +} test_file_exemption { exempted_file_path: "domain/src/main/java/org/oppia/android/domain/oppialogger/analytics/AnalyticsController.kt" override_min_coverage_percent_required: 68 } test_file_exemption { - exempted_file_path: "domain/src/main/java/org/oppia/android/domain/oppialogger/analytics/AnalyticsStartupListener.kt" + exempted_file_path: "domain/src/main/java/org/oppia/android/domain/oppialogger/analytics/ApplicationLifecycleListener.kt" test_file_not_required: true } test_file_exemption { - exempted_file_path: "domain/src/main/java/org/oppia/android/domain/oppialogger/analytics/ApplicationLifecycleListener.kt" + exempted_file_path: "domain/src/main/java/org/oppia/android/domain/oppialogger/analytics/ApplicationLifecycleLogger.kt" test_file_not_required: true } test_file_exemption { @@ -3875,6 +3887,10 @@ test_file_exemption { exempted_file_path: "domain/src/main/java/org/oppia/android/domain/oppialogger/analytics/LearnerAnalyticsLogger.kt" source_file_is_incompatible_with_code_coverage: true } +test_file_exemption { + exempted_file_path: "domain/src/main/java/org/oppia/android/domain/workmanager/testing/OppiaWorkManagerTestDriver.kt" + test_file_not_required: true +} test_file_exemption { exempted_file_path: "domain/src/main/java/org/oppia/android/domain/oppialogger/exceptions/ExceptionsController.kt" source_file_is_incompatible_with_code_coverage: true @@ -3891,26 +3907,10 @@ test_file_exemption { exempted_file_path: "domain/src/main/java/org/oppia/android/domain/oppialogger/logscheduler/MetricLogSchedulerModule.kt" source_file_is_incompatible_with_code_coverage: true } -test_file_exemption { - exempted_file_path: "domain/src/main/java/org/oppia/android/domain/oppialogger/logscheduler/MetricLogSchedulingWorker.kt" - source_file_is_incompatible_with_code_coverage: true -} -test_file_exemption { - exempted_file_path: "domain/src/main/java/org/oppia/android/domain/oppialogger/logscheduler/MetricLogSchedulingWorkerFactory.kt" - test_file_not_required: true -} -test_file_exemption { - exempted_file_path: "domain/src/main/java/org/oppia/android/domain/oppialogger/logscheduler/PerformanceMetricsLogScheduler.kt" - source_file_is_incompatible_with_code_coverage: true -} test_file_exemption { exempted_file_path: "domain/src/main/java/org/oppia/android/domain/oppialogger/loguploader/LogReportWorkerModule.kt" test_file_not_required: true } -test_file_exemption { - exempted_file_path: "domain/src/main/java/org/oppia/android/domain/oppialogger/loguploader/LogUploadWorkerFactory.kt" - test_file_not_required: true -} test_file_exemption { exempted_file_path: "domain/src/main/java/org/oppia/android/domain/oppialogger/survey/SurveyEventsLogger.kt" source_file_is_incompatible_with_code_coverage: true @@ -3987,18 +3987,6 @@ test_file_exemption { exempted_file_path: "domain/src/main/java/org/oppia/android/domain/platformparameter/testing/TestPlatformParameterConfigRetriever.kt" test_file_not_required: true } -test_file_exemption { - exempted_file_path: "domain/src/main/java/org/oppia/android/domain/platformparameter/syncup/PlatformParameterSyncUpWorkManagerInitializer.kt" - source_file_is_incompatible_with_code_coverage: true -} -test_file_exemption { - exempted_file_path: "domain/src/main/java/org/oppia/android/domain/platformparameter/syncup/PlatformParameterSyncUpWorker.kt" - source_file_is_incompatible_with_code_coverage: true -} -test_file_exemption { - exempted_file_path: "domain/src/main/java/org/oppia/android/domain/platformparameter/syncup/PlatformParameterSyncUpWorkerFactory.kt" - test_file_not_required: true -} test_file_exemption { exempted_file_path: "domain/src/main/java/org/oppia/android/domain/platformparameter/syncup/PlatformParameterSyncUpWorkerModule.kt" test_file_not_required: true @@ -4067,10 +4055,6 @@ test_file_exemption { exempted_file_path: "domain/src/main/java/org/oppia/android/domain/survey/SurveyQuestionGraph.kt" test_file_not_required: true } -test_file_exemption { - exempted_file_path: "domain/src/main/java/org/oppia/android/domain/testing/oppialogger/loguploader/FakeLogUploader.kt" - test_file_not_required: true -} test_file_exemption { exempted_file_path: "domain/src/main/java/org/oppia/android/domain/topic/ConceptCardRetriever.kt" test_file_not_required: true @@ -4112,7 +4096,7 @@ test_file_exemption { test_file_not_required: true } test_file_exemption { - exempted_file_path: "domain/src/main/java/org/oppia/android/domain/workmanager/WorkManagerConfigurationModule.kt" + exempted_file_path: "domain/src/main/java/org/oppia/android/domain/workmanager/OppiaWorker.kt" test_file_not_required: true } test_file_exemption { @@ -4551,10 +4535,6 @@ test_file_exemption { exempted_file_path: "utility/src/main/java/org/oppia/android/util/logging/LogLevel.kt" test_file_not_required: true } -test_file_exemption { - exempted_file_path: "utility/src/main/java/org/oppia/android/util/logging/LogUploader.kt" - test_file_not_required: true -} test_file_exemption { exempted_file_path: "utility/src/main/java/org/oppia/android/util/logging/LoggerModule.kt" test_file_not_required: true @@ -4563,10 +4543,6 @@ test_file_exemption { exempted_file_path: "utility/src/main/java/org/oppia/android/util/logging/LoggingAnnotations.kt" test_file_not_required: true } -test_file_exemption { - exempted_file_path: "utility/src/main/java/org/oppia/android/util/logging/MetricLogScheduler.kt" - test_file_not_required: true -} test_file_exemption { exempted_file_path: "utility/src/main/java/org/oppia/android/util/logging/SyncStatusManager.kt" test_file_not_required: true @@ -4591,14 +4567,6 @@ test_file_exemption { exempted_file_path: "utility/src/main/java/org/oppia/android/util/logging/firebase/FirebaseExceptionLogger.kt" test_file_not_required: true } -test_file_exemption { - exempted_file_path: "utility/src/main/java/org/oppia/android/util/logging/firebase/FirebaseLogUploader.kt" - test_file_not_required: true -} -test_file_exemption { - exempted_file_path: "utility/src/main/java/org/oppia/android/util/logging/firebase/FirebaseLogUploaderModule.kt" - test_file_not_required: true -} test_file_exemption { exempted_file_path: "utility/src/main/java/org/oppia/android/util/logging/firebase/FirestoreEventLogger.kt" test_file_not_required: true diff --git a/scripts/src/java/org/oppia/android/scripts/docs/KdocValidityCheck.kt b/scripts/src/java/org/oppia/android/scripts/docs/KdocValidityCheck.kt index 5aed8d34847..52b61785fbc 100644 --- a/scripts/src/java/org/oppia/android/scripts/docs/KdocValidityCheck.kt +++ b/scripts/src/java/org/oppia/android/scripts/docs/KdocValidityCheck.kt @@ -70,7 +70,8 @@ fun main(vararg args: String) { "AfterClass", "Inject", "Provides", - "Binds" + "Binds", + "Multibinds" ) // A list of all the files to be exempted for this check. diff --git a/scripts/src/javatests/org/oppia/android/scripts/docs/KdocValidityCheckTest.kt b/scripts/src/javatests/org/oppia/android/scripts/docs/KdocValidityCheckTest.kt index 9fb44afa3fd..c52e16e9999 100644 --- a/scripts/src/javatests/org/oppia/android/scripts/docs/KdocValidityCheckTest.kt +++ b/scripts/src/javatests/org/oppia/android/scripts/docs/KdocValidityCheckTest.kt @@ -253,6 +253,21 @@ class KdocValidityCheckTest { assertThat(outContent.toString().trim()).isEqualTo(KDOC_CHECK_PASSED_OUTPUT_INDICATOR) } + @Test + fun testKdoc_functionWithMultibindsAnnotation_withoutKdoc_checkShouldPass() { + val testContent = + """ + @Multibinds + fun provideContext(application: Application): Context + """.trimIndent() + val tempFile = tempFolder.newFile("testfiles/TempFile.kt") + tempFile.writeText(testContent) + + runScript() + + assertThat(outContent.toString().trim()).isEqualTo(KDOC_CHECK_PASSED_OUTPUT_INDICATOR) + } + @Test fun testKdoc_testFile_withoutKdoc_checkShouldPass() { val testContent = diff --git a/scripts/src/javatests/org/oppia/android/scripts/regex/RegexPatternValidationCheckTest.kt b/scripts/src/javatests/org/oppia/android/scripts/regex/RegexPatternValidationCheckTest.kt index 717b024d05c..745f6d35b34 100644 --- a/scripts/src/javatests/org/oppia/android/scripts/regex/RegexPatternValidationCheckTest.kt +++ b/scripts/src/javatests/org/oppia/android/scripts/regex/RegexPatternValidationCheckTest.kt @@ -24,8 +24,6 @@ class RegexPatternValidationCheckTest { "aren't supported by the project configuration)." private val supportLibraryUsageErrorMessage = "AndroidX should be used instead of the support library" - private val coroutineWorkerUsageErrorMessage = - "For stable tests, prefer using ListenableWorker with an Oppia-managed dispatcher." private val announceForAccessibilityUsageErrorMessage = "Please use AccessibilityService instead." private val announceForAccessibilityForViewUsageErrorMessage = @@ -179,10 +177,27 @@ class RegexPatternValidationCheckTest { private val doesNotReferenceColorFromComponentColorInKotlinFiles = "Only colors from component_colors.xml may be used in Kotlin Files (Activities, Fragments, " + "Views and Presenters)." - private val doesNotUseWorkManagerGetInstance = - "Use AnalyticsStartupListener to retrieve an instance of WorkManager rather than fetching one" + - " using getInstance (as the latter may create a WorkManager if one isn't already present, " + - "and the application may intend to disable it)." + private val coroutineWorkerUsageErrorMessage = + "Never use implement a CoroutineWorker directly. Instead, create a new OppiaWorker and rely " + + "on BootstrapOppiaWorker for direct interaction with WorkManager. See the WorkManager wiki " + + "page for more." + private val doNotUseListenableWorker = + "Never use implement a ListenableWorker directly. Instead, create a new OppiaWorker and rely " + + "on BootstrapOppiaWorker for direct interaction with WorkManager. See the WorkManager wiki " + + "page for more." + private val doNotUseWorkManagerTestDriver = + "Never use WorkManager's TestDriver directly. Instead, use OppiaWorkManagerTestDriver. See " + + "the WorkManager wiki page for more." + private val doNotUseWorkManagerGetTestDriver = + "Never use WorkManager's TestDriver directly. Instead, use OppiaWorkManagerTestDriver. See " + + "the WorkManager wiki page for more." + private val doNotUseWorkManagerTestInitHelper = + "Never use WorkManagerTestInitHelper directly. Instead, use OppiaWorkManagerTestDriver. See " + + "the WorkManager wiki page for more." + private val doNotUseWorkManager = + "Never use WorkManager directly. For production code use OppiaWorker and " + + "WorkManagerScheduler. For tests use OppiaWorkManagerTestDriver. See the WorkManager wiki " + + "page for more." private val doesNotUsePostOrPostDelayed = "Prefer avoiding post() and postDelayed() methods as they can can lead to subtle and " + "difficult-to-debug crashes. Prefer using LifecycleSafeTimerFactory for most cases when " + @@ -356,24 +371,6 @@ class RegexPatternValidationCheckTest { ) } - @Test - fun testFileContent_coroutineWorker_fileContentIsNotCorrect() { - val prohibitedContent = ") : CoroutineWorker(context, params) {" - val fileContainsSupportLibraryImport = tempFolder.newFile("testfiles/TestFile.kt") - fileContainsSupportLibraryImport.writeText(prohibitedContent) - - val exception = assertThrows() { runScript() } - - assertThat(exception).hasMessageThat().contains(REGEX_CHECK_FAILED_OUTPUT_INDICATOR) - assertThat(outContent.toString().trim()) - .isEqualTo( - """ - TestFile.kt:1: $coroutineWorkerUsageErrorMessage - $wikiReferenceNote - """.trimIndent() - ) - } - @Test fun testFileContent_announceForAccessibilityUsageErrorMessage_fileContentIsNotCorrect() { val prohibitedContent = "announceForAccessibility(" @@ -2391,23 +2388,134 @@ class RegexPatternValidationCheckTest { } @Test - fun testFileContent_referenceGetInstance_fileContentIsNotCorrect() { + fun testFileContent_coroutineWorker_fileContentIsNotCorrect() { + val prohibitedContent = ") : CoroutineWorker(context, params) {" + val fileContainsSupportLibraryImport = tempFolder.newFile("testfiles/TestFile.kt") + fileContainsSupportLibraryImport.writeText(prohibitedContent) + + val exception = assertThrows() { runScript() } + + assertThat(exception).hasMessageThat().contains(REGEX_CHECK_FAILED_OUTPUT_INDICATOR) + assertThat(outContent.toString().trim()) + .isEqualTo( + """ + TestFile.kt:1: $coroutineWorkerUsageErrorMessage + $wikiReferenceNote + """.trimIndent() + ) + } + + @Test + fun testFileContent_referenceListenableWorker_fileContentIsNotCorrect() { val prohibitedContent = """ - val workManager = WorkManager.getInstance(context) + import androidx.work.ListenableWorker + class ProhibitedWorker: ListenableWorker() """.trimIndent() tempFolder.newFolder("testfiles", "app", "src", "main", "java", "org", "oppia", "android") val stringFilePath = "app/src/main/java/org/oppia/android/SomeInitializer.kt" tempFolder.newFile("testfiles/$stringFilePath").writeText(prohibitedContent) - val exception = assertThrows() { runScript() } + val exception = assertThrows { runScript() } + + // Verify that all patterns are properly detected & prohibited. + assertThat(exception).hasMessageThat().contains(REGEX_CHECK_FAILED_OUTPUT_INDICATOR) + assertThat(outContent.toString().trim()) + .isEqualTo( + """ + $stringFilePath:1: $doNotUseListenableWorker + $wikiReferenceNote + """.trimIndent() + ) + } + + @Test + fun testFileContent_referenceTestDriver_fileContentIsNotCorrect() { + val prohibitedContent = + """ + import androidx.work.testing.TestDriver + """.trimIndent() + tempFolder.newFolder("testfiles", "app", "src", "main", "java", "org", "oppia", "android") + val stringFilePath = "app/src/main/java/org/oppia/android/SomeInitializer.kt" + tempFolder.newFile("testfiles/$stringFilePath").writeText(prohibitedContent) + + val exception = assertThrows { runScript() } + + // Verify that all patterns are properly detected & prohibited. + assertThat(exception).hasMessageThat().contains(REGEX_CHECK_FAILED_OUTPUT_INDICATOR) + assertThat(outContent.toString().trim()) + .isEqualTo( + """ + $stringFilePath:1: $doNotUseWorkManagerTestDriver + $wikiReferenceNote + """.trimIndent() + ) + } + + @Test + fun testFileContent_referenceGetTestDriver_fileContentIsNotCorrect() { + val prohibitedContent = + """ + WorkManagerTestInitHelper.getTestDriver(context) + """.trimIndent() + tempFolder.newFolder("testfiles", "app", "src", "main", "java", "org", "oppia", "android") + val stringFilePath = "app/src/main/java/org/oppia/android/SomeInitializer.kt" + tempFolder.newFile("testfiles/$stringFilePath").writeText(prohibitedContent) + + val exception = assertThrows { runScript() } + + // Verify that all patterns are properly detected & prohibited. + assertThat(exception).hasMessageThat().contains(REGEX_CHECK_FAILED_OUTPUT_INDICATOR) + assertThat(outContent.toString().trim()) + .isEqualTo( + """ + $stringFilePath:1: $doNotUseWorkManagerGetTestDriver + $wikiReferenceNote + """.trimIndent() + ) + } + + @Test + fun testFileContent_referenceWorkManagerTestInitHelper_fileContentIsNotCorrect() { + val prohibitedContent = + """ + import androidx.work.testing.WorkManagerTestInitHelper + """.trimIndent() + tempFolder.newFolder("testfiles", "app", "src", "main", "java", "org", "oppia", "android") + val stringFilePath = "app/src/main/java/org/oppia/android/SomeInitializer.kt" + tempFolder.newFile("testfiles/$stringFilePath").writeText(prohibitedContent) + + val exception = assertThrows { runScript() } + + // Verify that all patterns are properly detected & prohibited. + assertThat(exception).hasMessageThat().contains(REGEX_CHECK_FAILED_OUTPUT_INDICATOR) + assertThat(outContent.toString().trim()) + .isEqualTo( + """ + $stringFilePath:1: $doNotUseWorkManagerTestInitHelper + $wikiReferenceNote + """.trimIndent() + ) + } + + @Test + fun testFileContent_referenceWorkManager_fileContentIsNotCorrect() { + val prohibitedContent = + """ + import androidx.work.WorkManager + """.trimIndent() + tempFolder.newFolder("testfiles", "app", "src", "main", "java", "org", "oppia", "android") + val stringFilePath = "app/src/main/java/org/oppia/android/SomeInitializer.kt" + tempFolder.newFile("testfiles/$stringFilePath").writeText(prohibitedContent) + + val exception = assertThrows { runScript() } // Verify that all patterns are properly detected & prohibited. assertThat(exception).hasMessageThat().contains(REGEX_CHECK_FAILED_OUTPUT_INDICATOR) assertThat(outContent.toString().trim()) .isEqualTo( """ - $stringFilePath:1: $doesNotUseWorkManagerGetInstance + $stringFilePath:1: $doNotUseWorkManager $wikiReferenceNote """.trimIndent() ) diff --git a/testing/BUILD.bazel b/testing/BUILD.bazel index cf7746c1ad8..fe7135f28bc 100644 --- a/testing/BUILD.bazel +++ b/testing/BUILD.bazel @@ -62,6 +62,8 @@ kt_android_library( "//third_party:org_mockito_mockito-core", "//third_party:org_robolectric_robolectric", "//utility", + "//utility/src/main/java/org/oppia/android/util/logging/firebase:firestore_logger", + "//utility/src/main/java/org/oppia/android/util/logging/firebase:firestore_wrapper", "//utility/src/main/java/org/oppia/android/util/logging/performancemetrics:performance_metrics_assessor", "//utility/src/main/java/org/oppia/android/util/logging/performancemetrics:performance_metrics_event_logger", "//utility/src/main/java/org/oppia/android/util/parser/image:glide_image_loader_module", diff --git a/testing/src/main/java/org/oppia/android/testing/FakeAnalyticsEventLogger.kt b/testing/src/main/java/org/oppia/android/testing/FakeAnalyticsEventLogger.kt index ef425464ca3..2b6b79b7655 100644 --- a/testing/src/main/java/org/oppia/android/testing/FakeAnalyticsEventLogger.kt +++ b/testing/src/main/java/org/oppia/android/testing/FakeAnalyticsEventLogger.kt @@ -10,11 +10,22 @@ import javax.inject.Singleton @Singleton class FakeAnalyticsEventLogger @Inject constructor() : AnalyticsEventLogger { private val eventList = CopyOnWriteArrayList() + private var failureToThrow: Exception? = null override fun logEvent(eventLog: EventLog) { + val possibleFailure = failureToThrow + if (possibleFailure != null) throw possibleFailure eventList.add(eventLog) } + /** + * Sets an [Exception] to throw the next time(s) [logEvent] is called, or `null` if the failure + * mode should be cleared. + */ + fun setFailure(failure: Exception?) { + failureToThrow = failure + } + /** Returns the oldest event that's been logged. */ fun getOldestEvent(): EventLog = eventList.first() diff --git a/testing/src/main/java/org/oppia/android/testing/FakeExceptionLogger.kt b/testing/src/main/java/org/oppia/android/testing/FakeExceptionLogger.kt index 5a3469f70fe..f61ee06981f 100644 --- a/testing/src/main/java/org/oppia/android/testing/FakeExceptionLogger.kt +++ b/testing/src/main/java/org/oppia/android/testing/FakeExceptionLogger.kt @@ -9,14 +9,28 @@ import kotlin.Exception @Singleton class FakeExceptionLogger @Inject constructor() : ExceptionLogger { private val exceptionList = mutableListOf() + private var failureToThrow: Exception? = null override fun logException(exception: Exception) { + val possibleFailure = failureToThrow + if (possibleFailure != null) throw possibleFailure exceptionList.add(exception) } + /** + * Sets an [Exception] to throw the next time(s) [logException] is called, or `null` if the + * failure mode should be cleared. + */ + fun setFailure(failure: Exception?) { + failureToThrow = failure + } + /** Returns the most recently logged exception. */ fun getMostRecentException(): Exception = exceptionList.last() + /** Returns the most recently logged exceptions. */ + fun getMostRecentExceptions(count: Int): List = exceptionList.takeLast(count) + /** Clears all the exceptions that are currently logged. */ fun clearAllExceptions() = exceptionList.clear() @@ -25,4 +39,7 @@ class FakeExceptionLogger @Inject constructor() : ExceptionLogger { /** Returns true if there are no exceptions logged. */ fun noExceptionsPresent(): Boolean = exceptionList.isEmpty() + + /** Returns the number of exceptions logged to date (and not cleared by [clearAllExceptions]). */ + fun getExceptionCount(): Int = exceptionList.size } diff --git a/testing/src/main/java/org/oppia/android/testing/FakeFirestoreEventLogger.kt b/testing/src/main/java/org/oppia/android/testing/FakeFirestoreEventLogger.kt index b2a16c85704..3d882dfbeeb 100644 --- a/testing/src/main/java/org/oppia/android/testing/FakeFirestoreEventLogger.kt +++ b/testing/src/main/java/org/oppia/android/testing/FakeFirestoreEventLogger.kt @@ -10,11 +10,22 @@ import javax.inject.Singleton @Singleton class FakeFirestoreEventLogger @Inject constructor() : FirestoreEventLogger { private val eventList = CopyOnWriteArrayList() + private var failureToThrow: Exception? = null override fun uploadEvent(eventLog: EventLog) { + val possibleFailure = failureToThrow + if (possibleFailure != null) throw possibleFailure eventList.add(eventLog) } + /** + * Sets an [Exception] to throw the next time(s) [uploadEvent] is called, or `null` if the + * failure mode should be cleared. + */ + fun setFailure(failure: Exception?) { + failureToThrow = failure + } + /** Returns the oldest event that's been logged. */ fun getOldestEvent(): EventLog = eventList.first() diff --git a/testing/src/main/java/org/oppia/android/testing/FakePerformanceMetricsEventLogger.kt b/testing/src/main/java/org/oppia/android/testing/FakePerformanceMetricsEventLogger.kt index c48666eb680..30c39cab1c5 100644 --- a/testing/src/main/java/org/oppia/android/testing/FakePerformanceMetricsEventLogger.kt +++ b/testing/src/main/java/org/oppia/android/testing/FakePerformanceMetricsEventLogger.kt @@ -10,11 +10,22 @@ import javax.inject.Singleton @Singleton class FakePerformanceMetricsEventLogger @Inject constructor() : PerformanceMetricsEventLogger { private val performanceMetricsEventList = CopyOnWriteArrayList() + private var failureToThrow: Exception? = null override fun logPerformanceMetric(oppiaMetricLog: OppiaMetricLog) { + val possibleFailure = failureToThrow + if (possibleFailure != null) throw possibleFailure performanceMetricsEventList.add(oppiaMetricLog) } + /** + * Sets an [Exception] to throw the next time(s) [logPerformanceMetric] is called, or `null` if + * the failure mode should be cleared. + */ + fun setFailure(failure: Exception?) { + failureToThrow = failure + } + /** Returns the oldest event that's been logged. */ fun getOldestPerformanceMetricsEvent(): OppiaMetricLog = performanceMetricsEventList.first() diff --git a/testing/src/main/java/org/oppia/android/testing/platformparameter/TestPlatformParameterModule.kt b/testing/src/main/java/org/oppia/android/testing/platformparameter/TestPlatformParameterModule.kt index 8fe3503e6c3..e3d78b59650 100644 --- a/testing/src/main/java/org/oppia/android/testing/platformparameter/TestPlatformParameterModule.kt +++ b/testing/src/main/java/org/oppia/android/testing/platformparameter/TestPlatformParameterModule.kt @@ -1,7 +1,9 @@ package org.oppia.android.testing.platformparameter +import android.os.Looper import dagger.Module import dagger.Provides +import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Deferred import kotlinx.coroutines.Dispatchers @@ -20,6 +22,9 @@ import org.oppia.android.app.model.FeatureFlagId.PERFORMANCE_METRICS_COLLECTION import org.oppia.android.app.model.FeatureFlagId.SPOTLIGHT_UI import org.oppia.android.app.model.FeatureFlagId.TOPIC_INFO_TAB import org.oppia.android.app.model.FeatureFlagId.TOPIC_PRACTICE_TAB +import org.oppia.android.app.model.PlatformParameterId.PERFORMANCE_METRICS_COLLECTION_HIGH_FREQUENCY_TIME_INTERVAL_IN_MINUTES +import org.oppia.android.app.model.PlatformParameterId.PERFORMANCE_METRICS_COLLECTION_LOW_FREQUENCY_TIME_INTERVAL_IN_MINUTES +import org.oppia.android.app.model.PlatformParameterId.SYNC_UP_WORKER_TIME_PERIOD_IN_HOURS import org.oppia.android.domain.platformparameter.FeatureFlagBindingModule import org.oppia.android.domain.platformparameter.FeatureFlagsMapBindingModule import org.oppia.android.domain.platformparameter.PlatformParameterBindingModule @@ -57,21 +62,38 @@ class TestPlatformParameterModule { ): PlatformParameterController { val prodController = factory.create(processState) return object : PlatformParameterController { + private var isLoaded = false + override fun loadParametersAsync(): Deferred { // Calling code can be blocking which means the returned deferred must run immediately, - // hence the use of the Unconfined dispatcher. - return CoroutineScope(Dispatchers.Unconfined).async { - // TODO(#5835): Remove this blocking hack to ensure tests are properly inited for params. - val loadResult = prodController.loadParametersAsync() - testCoroutineDispatchers.runCurrent() - check(loadResult.isCompleted) { "Expected parameter loading to have finished." } + // hence the use of the dispatcher. The Main dispatcher must be used due to the runCurrent() + // call, however, since otherwise it can contend with tests trying to synchronize state and + // then deadlock. Additional work must be done to ensure that the thread hop *doesn't* + // happen if the main thread is already being used (since that could then cause a deadlock). + if (isLoaded) { + // This is a slight hack to allow the PlatformParameterProcessState injector to return + // immediately if parameters are already loaded. This is needed in more complex + // cross-thread initialization cases such as for background workers. + return CompletableDeferred(Unit) } + val dispatcher = if (Looper.getMainLooper().thread == Thread.currentThread()) { + Dispatchers.Unconfined // Run immediately on the current (main) thread. + } else Dispatchers.Main // Defer to the main thread. + return CoroutineScope(dispatcher).async { loadParamsAndRunTestDispatchersOnMainThread() } } override fun getParameterInitializationStatus() = prodController.getParameterInitializationStatus() override fun downloadRemoteParameters() = prodController.downloadRemoteParameters() + + private fun loadParamsAndRunTestDispatchersOnMainThread() { + // TODO(#5835): Remove this blocking hack to ensure tests are properly inited for params. + val loadResult = prodController.loadParametersAsync() + testCoroutineDispatchers.runCurrent() + check(loadResult.isCompleted) { "Expected parameter loading to have finished." } + isLoaded = true + } } } @@ -86,9 +108,15 @@ class TestPlatformParameterModule { platformParameterController: PlatformParameterController, testCoroutineDispatchers: TestCoroutineDispatchers ): PlatformParameterProcessState { - // TODO(#5835): Remove this blocking hack to ensure tests are properly inited for params. + // TODO(#5835): Remove this blocking hack to ensure tests are properly inited for params. Make + // sure to double check parameter loading in BootstrapOppiaWorker. Commenting out that line + // should trigger test failures once this mechanism is cleaned up. If it doesn't then work will + // be needed in BootstrapOppiaWorkerTest to ensure that line is properly tested (since removing + // it can cause catastrophic problems in deployed apps--it MUST be tested). val loadDeferred = platformParameterController.loadParametersAsync() - testCoroutineDispatchers.runCurrent() + if (!loadDeferred.isCompleted) { + testCoroutineDispatchers.runCurrent() + } check(loadDeferred.isCompleted) { "Expected parameter loading to have finished." } return processState } @@ -150,6 +178,24 @@ class TestPlatformParameterModule { TestPlatformParameterConfigRetriever.setFlagOverride(TOPIC_PRACTICE_TAB, value) } + fun forcePerformanceMetricsCollectionHighFrequencyTimeIntervalInMinutes(value: Int) { + TestPlatformParameterConfigRetriever.setParameterOverride( + PERFORMANCE_METRICS_COLLECTION_HIGH_FREQUENCY_TIME_INTERVAL_IN_MINUTES, value + ) + } + + fun forcePerformanceMetricsCollectionLowFrequencyTimeIntervalInMinutes(value: Int) { + TestPlatformParameterConfigRetriever.setParameterOverride( + PERFORMANCE_METRICS_COLLECTION_LOW_FREQUENCY_TIME_INTERVAL_IN_MINUTES, value + ) + } + + fun forceSyncUpWorkerTimePeriodInHours(value: Int) { + TestPlatformParameterConfigRetriever.setParameterOverride( + SYNC_UP_WORKER_TIME_PERIOD_IN_HOURS, value + ) + } + fun reset() { TestPlatformParameterConfigRetriever.reset() } diff --git a/testing/src/main/java/org/oppia/android/testing/threading/BUILD.bazel b/testing/src/main/java/org/oppia/android/testing/threading/BUILD.bazel index 62f8efe71ff..5481fd207d7 100644 --- a/testing/src/main/java/org/oppia/android/testing/threading/BUILD.bazel +++ b/testing/src/main/java/org/oppia/android/testing/threading/BUILD.bazel @@ -86,6 +86,7 @@ kt_android_library( ], visibility = [ "//app:__subpackages__", + "//domain/src/main/java/org/oppia/android/domain/workmanager/testing:__pkg__", "//testing/src/test/java/org/oppia/android/testing/threading:__pkg__", ], deps = [ diff --git a/testing/src/test/java/org/oppia/android/testing/FakeAnalyticsEventLoggerTest.kt b/testing/src/test/java/org/oppia/android/testing/FakeAnalyticsEventLoggerTest.kt index 047d7fb86d7..e4d7128d5c9 100644 --- a/testing/src/test/java/org/oppia/android/testing/FakeAnalyticsEventLoggerTest.kt +++ b/testing/src/test/java/org/oppia/android/testing/FakeAnalyticsEventLoggerTest.kt @@ -62,6 +62,37 @@ class FakeAnalyticsEventLoggerTest { assertThat(event.priority).isEqualTo(Priority.OPTIONAL) } + @Test + fun testFakeEventLogger_logEventTwice_withFailureSet_throwsExceptionForBoth() { + fakeAnalyticsEventLogger.setFailure(Exception("Forced failure.")) + + assertThrows { analyticsEventLogger.logEvent(eventLog1) } + val exception2 = assertThrows { analyticsEventLogger.logEvent(eventLog2) } + + assertThat(exception2).hasMessageThat().isEqualTo("Forced failure.") + } + + @Test + fun testFakeEventLogger_logEventTwice_withNewFailureSet_throwsLatestException() { + fakeAnalyticsEventLogger.setFailure(Exception("Forced failure.")) + fakeAnalyticsEventLogger.setFailure(Exception("Forced failure2.")) + + val exception = assertThrows { analyticsEventLogger.logEvent(eventLog1) } + + assertThat(exception).hasMessageThat().isEqualTo("Forced failure2.") + } + + @Test + fun testFakeEventLogger_logEventTwice_withFailureSetToNull_doesNotThrowException() { + fakeAnalyticsEventLogger.setFailure(Exception("Forced failure.")) + fakeAnalyticsEventLogger.setFailure(null) // Reset. + + analyticsEventLogger.logEvent(eventLog1) + + val event = fakeAnalyticsEventLogger.getMostRecentEvent() + assertThat(event).isEqualTo(eventLog1) + } + @Test fun testFakeEventLogger_logEvent_clearAllEvents_logEventAgain_returnsLatestEvent() { analyticsEventLogger.logEvent(eventLog1) diff --git a/testing/src/test/java/org/oppia/android/testing/FakeExceptionLoggerTest.kt b/testing/src/test/java/org/oppia/android/testing/FakeExceptionLoggerTest.kt index f8c549b45db..703041f9681 100644 --- a/testing/src/test/java/org/oppia/android/testing/FakeExceptionLoggerTest.kt +++ b/testing/src/test/java/org/oppia/android/testing/FakeExceptionLoggerTest.kt @@ -56,6 +56,38 @@ class FakeExceptionLoggerTest { assertThat(exception).hasMessageThat().contains("Second Exception") } + @Test + fun testFakeExceptionLogger_logExceptionTwice_withFailureSet_throwsExceptionForBoth() { + fakeExceptionLogger.setFailure(Exception("Forced failure.")) + + assertThrows { exceptionLogger.logException(exception1) } + val exception2 = assertThrows { exceptionLogger.logException(exception2) } + + assertThat(exception2).hasMessageThat().isEqualTo("Forced failure.") + } + + @Test + fun testFakeExceptionLogger_logExceptionTwice_withNewFailureSet_throwsLatestException() { + fakeExceptionLogger.setFailure(Exception("Forced failure.")) + fakeExceptionLogger.setFailure(Exception("Forced failure2.")) + + val exception = assertThrows { exceptionLogger.logException(exception1) } + + assertThat(exception).hasMessageThat().isEqualTo("Forced failure2.") + } + + @Test + fun testFakeExceptionLogger_logExceptionTwice_withFailureSetToNull_doesNotThrowException() { + fakeExceptionLogger.setFailure(Exception("Forced failure.")) + fakeExceptionLogger.setFailure(null) // Reset. + + exceptionLogger.logException(exception1) + + val exception = fakeExceptionLogger.getMostRecentException() + assertThat(exception).isInstanceOf(IllegalStateException::class.java) + assertThat(exception).hasMessageThat().contains("First Exception") + } + @Test fun testFakeExceptionLogger_logException_clearList_logExceptionAgain_returnsLatestException() { exceptionLogger.logException(exception1) @@ -140,6 +172,136 @@ class FakeExceptionLoggerTest { assertThat(exceptionLogStatus2).isTrue() } + @Test + fun testFakeExceptionLogger_getMostRecentExceptions_two_noExceptionsLogged_returnsEmptyList() { + val mostRecentExceptions = fakeExceptionLogger.getMostRecentExceptions(count = 2) + + assertThat(mostRecentExceptions).isEmpty() + } + + @Test + fun testFakeExceptionLogger_getMostRecentExceptions_two_oneExceptionLogged_returnsOneItemList() { + exceptionLogger.logException(exception1) + + val mostRecentExceptions = fakeExceptionLogger.getMostRecentExceptions(count = 2) + + assertThat(mostRecentExceptions).hasSize(1) + assertThat(mostRecentExceptions.single()).hasMessageThat().isEqualTo("First Exception") + } + + @Test + fun testFakeExceptionLogger_getMostRecentExceptions_two_twoExceptionsLogged_returnsInOrder() { + exceptionLogger.logException(exception2) + exceptionLogger.logException(exception1) + + val mostRecentExceptions = fakeExceptionLogger.getMostRecentExceptions(count = 2) + + val messages = mostRecentExceptions.map { it.message } + assertThat(messages).containsExactly("Second Exception", "First Exception").inOrder() + } + + @Test + fun testFakeExceptionLogger_getMostRecentExceptions_one_twoExceptionsLogged_returnsLatest() { + exceptionLogger.logException(exception2) + exceptionLogger.logException(exception1) + + val mostRecentExceptions = fakeExceptionLogger.getMostRecentExceptions(count = 1) + + assertThat(mostRecentExceptions).hasSize(1) + assertThat(mostRecentExceptions.single()).hasMessageThat().isEqualTo("First Exception") + } + + @Test + fun testFakeExceptionLogger_getMostRecentExceptions_zero_twoExceptionsLogged_returnsEmptyList() { + exceptionLogger.logException(exception2) + exceptionLogger.logException(exception1) + + val mostRecentExceptions = fakeExceptionLogger.getMostRecentExceptions(count = 0) + + assertThat(mostRecentExceptions).isEmpty() + } + + @Test + fun testFakeExceptionLogger_getMostRecentExceptions_negative_twoExceptionsLogged_throws() { + exceptionLogger.logException(exception2) + exceptionLogger.logException(exception1) + + assertThrows { + fakeExceptionLogger.getMostRecentExceptions(count = -1) + } + } + + @Test + fun testFakeExceptionLogger_getMostRecentExceptions_two_twoLogged_cleared_returnsEmpty() { + exceptionLogger.logException(exception2) + exceptionLogger.logException(exception1) + fakeExceptionLogger.clearAllExceptions() + + val mostRecentExceptions = fakeExceptionLogger.getMostRecentExceptions(count = 2) + + assertThat(mostRecentExceptions).isEmpty() + } + + @Test + fun testFakeExceptionLogger_getMostRecentExceptions_two_logged_cleared_logged_returnsLatest() { + exceptionLogger.logException(exception1) + fakeExceptionLogger.clearAllExceptions() + exceptionLogger.logException(exception2) + + val mostRecentExceptions = fakeExceptionLogger.getMostRecentExceptions(count = 2) + + assertThat(mostRecentExceptions).hasSize(1) + assertThat(mostRecentExceptions.single()).hasMessageThat().isEqualTo("Second Exception") + } + + @Test + fun testFakeExceptionLogger_nothingLogged_countReturnsZero() { + val count = fakeExceptionLogger.getExceptionCount() + + assertThat(count).isEqualTo(0) + } + + @Test + fun testFakeExceptionLogger_logException_countReturnsOne() { + exceptionLogger.logException(exception1) + + val count = fakeExceptionLogger.getExceptionCount() + + assertThat(count).isEqualTo(1) + } + + @Test + fun testFakeExceptionLogger_logExceptionTwice_countReturnsTwo() { + exceptionLogger.logException(exception1) + exceptionLogger.logException(exception2) + + val count = fakeExceptionLogger.getExceptionCount() + + assertThat(count).isEqualTo(2) + } + + @Test + fun testFakeExceptionLogger_logExceptionTwice_clear_countReturnsZero() { + exceptionLogger.logException(exception1) + exceptionLogger.logException(exception2) + fakeExceptionLogger.clearAllExceptions() + + val count = fakeExceptionLogger.getExceptionCount() + + assertThat(count).isEqualTo(0) + } + + @Test + fun testFakeExceptionLogger_logException_clear_logOneMore_countReturnsOne() { + exceptionLogger.logException(exception1) + fakeExceptionLogger.clearAllExceptions() + exceptionLogger.logException(exception2) + + val count = fakeExceptionLogger.getExceptionCount() + + assertThat(count).isEqualTo(1) + } + private fun setUpTestApplicationComponent() { DaggerFakeExceptionLoggerTest_TestApplicationComponent.builder() .setApplication(ApplicationProvider.getApplicationContext()) diff --git a/testing/src/test/java/org/oppia/android/testing/FakeFirestoreEventLoggerTest.kt b/testing/src/test/java/org/oppia/android/testing/FakeFirestoreEventLoggerTest.kt index 94ca8628ebf..00d3c1ee788 100644 --- a/testing/src/test/java/org/oppia/android/testing/FakeFirestoreEventLoggerTest.kt +++ b/testing/src/test/java/org/oppia/android/testing/FakeFirestoreEventLoggerTest.kt @@ -47,7 +47,7 @@ class FakeFirestoreEventLoggerTest { } @Test - fun testfakeEventLogger_logEvent_returnsEvent() { + fun testFakeEventLogger_logEvent_returnsEvent() { eventLogger.uploadEvent(eventLog1) val event = fakeEventLogger.getMostRecentEvent() @@ -56,7 +56,7 @@ class FakeFirestoreEventLoggerTest { } @Test - fun testfakeEventLogger_logEventTwice_returnsLatestEvent() { + fun testFakeEventLogger_logEventTwice_returnsLatestEvent() { eventLogger.uploadEvent(eventLog1) eventLogger.uploadEvent(eventLog2) val event = fakeEventLogger.getMostRecentEvent() @@ -66,7 +66,39 @@ class FakeFirestoreEventLoggerTest { } @Test - fun testfakeEventLogger_logEvent_clearAllEvents_logEventAgain_returnsLatestEvent() { + fun testFakeEventLogger_logEventTwice_withFailureSet_throwsExceptionForBoth() { + fakeEventLogger.setFailure(Exception("Forced failure.")) + + assertThrows { eventLogger.uploadEvent(eventLog1) } + val exception2 = assertThrows { eventLogger.uploadEvent(eventLog2) } + + assertThat(exception2).hasMessageThat().isEqualTo("Forced failure.") + } + + @Test + fun testFakeEventLogger_logEventTwice_withNewFailureSet_throwsLatestException() { + fakeEventLogger.setFailure(Exception("Forced failure.")) + fakeEventLogger.setFailure(Exception("Forced failure2.")) + + val exception = assertThrows { eventLogger.uploadEvent(eventLog1) } + + assertThat(exception).hasMessageThat().isEqualTo("Forced failure2.") + } + + @Test + fun testFakeEventLogger_logEventTwice_withFailureSetToNull_doesNotThrowException() { + fakeEventLogger.setFailure(Exception("Forced failure.")) + fakeEventLogger.setFailure(null) // Reset. + + eventLogger.uploadEvent(eventLog1) + + val event = fakeEventLogger.getMostRecentEvent() + assertThat(event).isEqualTo(eventLog1) + assertThat(event.priority).isEqualTo(Priority.ESSENTIAL) + } + + @Test + fun testFakeEventLogger_logEvent_clearAllEvents_logEventAgain_returnsLatestEvent() { eventLogger.uploadEvent(eventLog1) fakeEventLogger.clearAllEvents() eventLogger.uploadEvent(eventLog2) @@ -77,12 +109,12 @@ class FakeFirestoreEventLoggerTest { } @Test - fun testfakeEventLogger_logNothing_getMostRecent_returnsFailure() { + fun testFakeEventLogger_logNothing_getMostRecent_returnsFailure() { assertThrows() { fakeEventLogger.getMostRecentEvent() } } @Test - fun testfakeEventLogger_logEvent_clearAllEvents_getMostRecent_returnsFailure() { + fun testFakeEventLogger_logEvent_clearAllEvents_getMostRecent_returnsFailure() { eventLogger.uploadEvent(eventLog1) fakeEventLogger.clearAllEvents() @@ -94,7 +126,7 @@ class FakeFirestoreEventLoggerTest { } @Test - fun testfakeEventLogger_clearAllEvents_returnsEmptyList() { + fun testFakeEventLogger_clearAllEvents_returnsEmptyList() { fakeEventLogger.clearAllEvents() val isListEmpty = fakeEventLogger.noEventsPresent() @@ -102,7 +134,7 @@ class FakeFirestoreEventLoggerTest { } @Test - fun testfakeEventLogger_logEvent_clearAllEvents_returnsEmptyList() { + fun testFakeEventLogger_logEvent_clearAllEvents_returnsEmptyList() { eventLogger.uploadEvent(eventLog1) fakeEventLogger.clearAllEvents() val isListEmpty = fakeEventLogger.noEventsPresent() @@ -111,7 +143,7 @@ class FakeFirestoreEventLoggerTest { } @Test - fun testfakeEventLogger_logMultipleEvents_clearAllEvents_returnsEmptyList() { + fun testFakeEventLogger_logMultipleEvents_clearAllEvents_returnsEmptyList() { eventLogger.uploadEvent(eventLog1) eventLogger.uploadEvent(eventLog2) fakeEventLogger.clearAllEvents() @@ -121,7 +153,7 @@ class FakeFirestoreEventLoggerTest { } @Test - fun testfakeEventLogger_logEvent_returnsNonEmptyList() { + fun testFakeEventLogger_logEvent_returnsNonEmptyList() { eventLogger.uploadEvent(eventLog1) val isListEmpty = fakeEventLogger.noEventsPresent() @@ -129,7 +161,7 @@ class FakeFirestoreEventLoggerTest { } @Test - fun testfakeEventLogger_logMultipleEvents_returnsNonEmptyList() { + fun testFakeEventLogger_logMultipleEvents_returnsNonEmptyList() { eventLogger.uploadEvent(eventLog1) eventLogger.uploadEvent(eventLog2) diff --git a/testing/src/test/java/org/oppia/android/testing/FakePerformanceMetricsEventLoggerTest.kt b/testing/src/test/java/org/oppia/android/testing/FakePerformanceMetricsEventLoggerTest.kt index a75aafaf7c6..e68d897a2ec 100644 --- a/testing/src/test/java/org/oppia/android/testing/FakePerformanceMetricsEventLoggerTest.kt +++ b/testing/src/test/java/org/oppia/android/testing/FakePerformanceMetricsEventLoggerTest.kt @@ -65,6 +65,53 @@ class FakePerformanceMetricsEventLoggerTest { assertThat(event.priority).isEqualTo(MEDIUM_PRIORITY) } + @Test + fun testFakeMetricsEventLogger_logMetric_withFailureSet_throwsException() { + fakePerformanceMetricsEventLogger.setFailure(Exception("Forced failure.")) + + val exception = assertThrows { + performanceMetricsEventLogger.logPerformanceMetric(metricLog1) + } + + assertThat(exception).hasMessageThat().isEqualTo("Forced failure.") + } + + @Test + fun testFakeMetricsEventLogger_logMetricTwice_withFailureSet_throwsExceptionForBoth() { + fakePerformanceMetricsEventLogger.setFailure(Exception("Forced failure.")) + + assertThrows { performanceMetricsEventLogger.logPerformanceMetric(metricLog1) } + val exception2 = assertThrows { + performanceMetricsEventLogger.logPerformanceMetric(metricLog1) + } + + assertThat(exception2).hasMessageThat().isEqualTo("Forced failure.") + } + + @Test + fun testFakeMetricsEventLogger_logMetric_withNewFailureSet_throwsLatestException() { + fakePerformanceMetricsEventLogger.setFailure(Exception("Forced failure.")) + fakePerformanceMetricsEventLogger.setFailure(Exception("Forced failure2.")) + + val exception = assertThrows { + performanceMetricsEventLogger.logPerformanceMetric(metricLog1) + } + + assertThat(exception).hasMessageThat().isEqualTo("Forced failure2.") + } + + @Test + fun testFakeMetricsEventLogger_logMetric_withFailureSetToNull_doesNotThrowException() { + fakePerformanceMetricsEventLogger.setFailure(Exception("Forced failure.")) + fakePerformanceMetricsEventLogger.setFailure(null) // Reset. + + performanceMetricsEventLogger.logPerformanceMetric(metricLog1) + + val event = fakePerformanceMetricsEventLogger.getMostRecentPerformanceMetricsEvent() + assertThat(event).isEqualTo(metricLog1) + assertThat(event.priority).isEqualTo(HIGH_PRIORITY) + } + @Test fun testFakeMetricsEventLogger_logEvent_clearAllEvents_logEventAgain_returnsLatestEvent() { performanceMetricsEventLogger.logPerformanceMetric(metricLog1) diff --git a/testing/src/test/java/org/oppia/android/testing/firebase/BUILD.bazel b/testing/src/test/java/org/oppia/android/testing/firebase/BUILD.bazel index 6eeeb67df1b..39f79207767 100644 --- a/testing/src/test/java/org/oppia/android/testing/firebase/BUILD.bazel +++ b/testing/src/test/java/org/oppia/android/testing/firebase/BUILD.bazel @@ -41,5 +41,6 @@ oppia_android_test( "//third_party:junit_junit", "//third_party:org_robolectric_robolectric", "//third_party:robolectric_android-all", + "//utility/src/main/java/org/oppia/android/util/logging/firebase:debug_module", ], ) diff --git a/testing/src/test/java/org/oppia/android/testing/junit/InitializeDefaultLocaleRuleCustomContextTest.kt b/testing/src/test/java/org/oppia/android/testing/junit/InitializeDefaultLocaleRuleCustomContextTest.kt index 32ddab65040..a238c065210 100644 --- a/testing/src/test/java/org/oppia/android/testing/junit/InitializeDefaultLocaleRuleCustomContextTest.kt +++ b/testing/src/test/java/org/oppia/android/testing/junit/InitializeDefaultLocaleRuleCustomContextTest.kt @@ -72,7 +72,6 @@ import org.oppia.android.util.gcsresource.GcsResourceModule import org.oppia.android.util.locale.LocaleProdModule import org.oppia.android.util.logging.LoggerModule import org.oppia.android.util.logging.SyncStatusModule -import org.oppia.android.util.logging.firebase.FirebaseLogUploaderModule import org.oppia.android.util.networking.NetworkConnectionDebugUtilModule import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule import org.oppia.android.util.parser.html.HtmlParserEntityTypeModule @@ -266,7 +265,6 @@ class InitializeDefaultLocaleRuleCustomContextTest { ExplorationProgressModule::class, ExplorationStorageModule::class, FakeOppiaClockModule::class, - FirebaseLogUploaderModule::class, FractionInputModule::class, GcsResourceModule::class, GlideImageLoaderModule::class, diff --git a/testing/src/test/java/org/oppia/android/testing/junit/InitializeDefaultLocaleRuleOmissionTest.kt b/testing/src/test/java/org/oppia/android/testing/junit/InitializeDefaultLocaleRuleOmissionTest.kt index ec8f084ac02..084f1ce973a 100644 --- a/testing/src/test/java/org/oppia/android/testing/junit/InitializeDefaultLocaleRuleOmissionTest.kt +++ b/testing/src/test/java/org/oppia/android/testing/junit/InitializeDefaultLocaleRuleOmissionTest.kt @@ -70,7 +70,6 @@ import org.oppia.android.util.gcsresource.GcsResourceModule import org.oppia.android.util.locale.LocaleProdModule import org.oppia.android.util.logging.LoggerModule import org.oppia.android.util.logging.SyncStatusModule -import org.oppia.android.util.logging.firebase.FirebaseLogUploaderModule import org.oppia.android.util.networking.NetworkConnectionDebugUtilModule import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule import org.oppia.android.util.parser.html.HtmlParserEntityTypeModule @@ -142,7 +141,6 @@ class InitializeDefaultLocaleRuleOmissionTest { ExplorationProgressModule::class, ExplorationStorageModule::class, FakeOppiaClockModule::class, - FirebaseLogUploaderModule::class, FractionInputModule::class, GcsResourceModule::class, GlideImageLoaderModule::class, diff --git a/testing/src/test/java/org/oppia/android/testing/junit/InitializeDefaultLocaleRuleTest.kt b/testing/src/test/java/org/oppia/android/testing/junit/InitializeDefaultLocaleRuleTest.kt index 2fd8e767d20..338c5c0b6fb 100644 --- a/testing/src/test/java/org/oppia/android/testing/junit/InitializeDefaultLocaleRuleTest.kt +++ b/testing/src/test/java/org/oppia/android/testing/junit/InitializeDefaultLocaleRuleTest.kt @@ -77,7 +77,6 @@ import org.oppia.android.util.gcsresource.GcsResourceModule import org.oppia.android.util.locale.LocaleProdModule import org.oppia.android.util.logging.LoggerModule import org.oppia.android.util.logging.SyncStatusModule -import org.oppia.android.util.logging.firebase.FirebaseLogUploaderModule import org.oppia.android.util.networking.NetworkConnectionDebugUtilModule import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule import org.oppia.android.util.parser.html.HtmlParserEntityTypeModule @@ -290,7 +289,6 @@ class InitializeDefaultLocaleRuleTest { ExplorationProgressModule::class, ExplorationStorageModule::class, FakeOppiaClockModule::class, - FirebaseLogUploaderModule::class, FractionInputModule::class, GcsResourceModule::class, GlideImageLoaderModule::class, diff --git a/utility/BUILD.bazel b/utility/BUILD.bazel index 79dc5a5a773..cd6c7b4df0a 100644 --- a/utility/BUILD.bazel +++ b/utility/BUILD.bazel @@ -121,7 +121,6 @@ TEST_DEPS = [ "//third_party:androidx_arch_core_core-testing", "//third_party:androidx_test_ext_junit", "//third_party:androidx_test_ext_truth", - "//third_party:androidx_work_work-testing", "//third_party:com_google_truth_extensions_truth-liteproto-extension", "//third_party:com_google_truth_truth", "//third_party:org_jetbrains_kotlin_kotlin-reflect", diff --git a/utility/src/main/java/org/oppia/android/util/logging/BUILD.bazel b/utility/src/main/java/org/oppia/android/util/logging/BUILD.bazel index de80cc82f38..29f8bfa3ce0 100644 --- a/utility/src/main/java/org/oppia/android/util/logging/BUILD.bazel +++ b/utility/src/main/java/org/oppia/android/util/logging/BUILD.bazel @@ -115,6 +115,7 @@ kt_android_library( srcs = [ "EventTypeToHumanReadableNameConverter.kt", ], + visibility = ["//utility:utility_testing_visibility"], deps = [ "//model/src/main/proto:event_logger_java_proto_lite", "//third_party:javax_inject_javax_inject", @@ -137,30 +138,6 @@ kt_android_library( ], ) -kt_android_library( - name = "log_uploader", - srcs = [ - "LogUploader.kt", - ], - visibility = ["//:oppia_api_visibility"], - deps = [ - "//third_party:androidx_work_work-runtime", - "//third_party:androidx_work_work-runtime-ktx", - ], -) - -kt_android_library( - name = "metric_log_scheduler", - srcs = [ - "MetricLogScheduler.kt", - ], - visibility = ["//:oppia_api_visibility"], - deps = [ - "//third_party:androidx_work_work-runtime", - "//third_party:androidx_work_work-runtime-ktx", - ], -) - kt_android_library( name = "sync_status_manager", srcs = [ diff --git a/utility/src/main/java/org/oppia/android/util/logging/LogUploader.kt b/utility/src/main/java/org/oppia/android/util/logging/LogUploader.kt deleted file mode 100644 index 0c337ca71a6..00000000000 --- a/utility/src/main/java/org/oppia/android/util/logging/LogUploader.kt +++ /dev/null @@ -1,26 +0,0 @@ -package org.oppia.android.util.logging - -import androidx.work.PeriodicWorkRequest -import androidx.work.WorkManager - -/** Uploader for uploading events and exceptions to the remote service. */ -interface LogUploader { - - /** Enqueues a [workRequest] using the [workManager] for uploading event logs that are stored in the cache store. */ - fun enqueueWorkRequestForEvents(workManager: WorkManager, workRequest: PeriodicWorkRequest) - - /** Enqueues a [workRequest] using the [workManager] for uploading exception logs that are stored in the cache store. */ - fun enqueueWorkRequestForExceptions(workManager: WorkManager, workRequest: PeriodicWorkRequest) - - /** Enqueues a [workRequest] using the [workManager] for uploading performance metrics logs that are stored in the cache store. */ - fun enqueueWorkRequestForPerformanceMetrics( - workManager: WorkManager, - workRequest: PeriodicWorkRequest - ) - - /** Enqueues a [workRequest] using the [workManager] for uploading event logs that are meant for Firestore. */ - fun enqueueWorkRequestForFirestore( - workManager: WorkManager, - workRequest: PeriodicWorkRequest - ) -} diff --git a/utility/src/main/java/org/oppia/android/util/logging/MetricLogScheduler.kt b/utility/src/main/java/org/oppia/android/util/logging/MetricLogScheduler.kt deleted file mode 100644 index 463179cf15a..00000000000 --- a/utility/src/main/java/org/oppia/android/util/logging/MetricLogScheduler.kt +++ /dev/null @@ -1,31 +0,0 @@ -package org.oppia.android.util.logging - -import androidx.work.PeriodicWorkRequest -import androidx.work.WorkManager - -/** Scheduler for scheduling metric log reports related to the performance of the application. */ -interface MetricLogScheduler { - /** - * Enqueues a [workRequest] using the [workManager] for scheduling metric collection of periodic - * metrics like network and cpu usage. - */ - fun enqueueWorkRequestForPeriodicBackgroundMetrics( - workManager: WorkManager, - workRequest: PeriodicWorkRequest - ) - - /** - * Enqueues a [workRequest] using the [workManager] for scheduling metric collection of storage - * usage of the application on the current device. - */ - fun enqueueWorkRequestForStorageUsage(workManager: WorkManager, workRequest: PeriodicWorkRequest) - - /** - * Enqueues a [workRequest] using the [workManager] for scheduling metric collection of periodic - * ui metrics like memory usage. - */ - fun enqueueWorkRequestForPeriodicUiMetrics( - workManager: WorkManager, - workRequest: PeriodicWorkRequest - ) -} diff --git a/utility/src/main/java/org/oppia/android/util/logging/firebase/BUILD.bazel b/utility/src/main/java/org/oppia/android/util/logging/firebase/BUILD.bazel index cb3df472c2e..b4c4066f1fe 100644 --- a/utility/src/main/java/org/oppia/android/util/logging/firebase/BUILD.bazel +++ b/utility/src/main/java/org/oppia/android/util/logging/firebase/BUILD.bazel @@ -19,7 +19,6 @@ kt_android_library( name = "prod_impl", srcs = [ "FirebaseAnalyticsEventLogger.kt", - "FirebaseLogUploader.kt", ], deps = [ "//model/src/main/proto:event_logger_java_proto_lite", @@ -30,7 +29,6 @@ kt_android_library( "//third_party:javax_inject_javax_inject", "//utility/src/main/java/org/oppia/android/util/logging:event_bundle_creator", "//utility/src/main/java/org/oppia/android/util/logging:event_logger", - "//utility/src/main/java/org/oppia/android/util/logging:log_uploader", "//utility/src/main/java/org/oppia/android/util/logging:sync_status_manager", "//utility/src/main/java/org/oppia/android/util/logging/performancemetrics:performance_metrics_event_logger", "//utility/src/main/java/org/oppia/android/util/networking:network_connection_util", @@ -40,7 +38,6 @@ kt_android_library( kt_android_library( name = "prod_module", srcs = [ - "FirebaseLogUploaderModule.kt", "LogReportingModule.kt", ], visibility = ["//:oppia_prod_module_visibility"], @@ -54,7 +51,6 @@ kt_android_library( "//third_party:com_google_firebase_firebase-crashlytics", "//third_party:javax_inject_javax_inject", "//utility/src/main/java/org/oppia/android/util/logging:event_bundle_creator", - "//utility/src/main/java/org/oppia/android/util/logging:log_uploader", ], ) @@ -135,6 +131,7 @@ kt_android_library( "FirestoreInstance.kt", "FirestoreInstanceWrapper.kt", ], + visibility = ["//testing:__subpackages__"], deps = [ "//third_party:com_google_firebase_firebase-firestore-ktx", "//third_party:org_jetbrains_kotlinx_kotlinx-coroutines-core", diff --git a/utility/src/main/java/org/oppia/android/util/logging/firebase/FirebaseLogUploader.kt b/utility/src/main/java/org/oppia/android/util/logging/firebase/FirebaseLogUploader.kt deleted file mode 100644 index 40316ac0397..00000000000 --- a/utility/src/main/java/org/oppia/android/util/logging/firebase/FirebaseLogUploader.kt +++ /dev/null @@ -1,61 +0,0 @@ -package org.oppia.android.util.logging.firebase - -import androidx.work.ExistingPeriodicWorkPolicy -import androidx.work.PeriodicWorkRequest -import androidx.work.WorkManager -import org.oppia.android.util.logging.LogUploader -import javax.inject.Inject - -private const val OPPIA_EVENT_WORK = "OPPIA_EVENT_WORK_REQUEST" -private const val OPPIA_EXCEPTION_WORK = "OPPIA_EXCEPTION_WORK_REQUEST" -private const val OPPIA_PERFORMANCE_METRICS_WORK = "OPPIA_PERFORMANCE_METRICS_WORK" -private const val OPPIA_FIRESTORE_WORK = "OPPIA_FIRESTORE_WORK_REQUEST" - -/** Enqueues work requests for uploading stored event/exception logs to the remote service. */ -class FirebaseLogUploader @Inject constructor() : - LogUploader { - - override fun enqueueWorkRequestForEvents( - workManager: WorkManager, - workRequest: PeriodicWorkRequest - ) { - workManager.enqueueUniquePeriodicWork( - OPPIA_EVENT_WORK, - ExistingPeriodicWorkPolicy.KEEP, - workRequest - ) - } - - override fun enqueueWorkRequestForExceptions( - workManager: WorkManager, - workRequest: PeriodicWorkRequest - ) { - workManager.enqueueUniquePeriodicWork( - OPPIA_EXCEPTION_WORK, - ExistingPeriodicWorkPolicy.KEEP, - workRequest - ) - } - - override fun enqueueWorkRequestForPerformanceMetrics( - workManager: WorkManager, - workRequest: PeriodicWorkRequest - ) { - workManager.enqueueUniquePeriodicWork( - OPPIA_PERFORMANCE_METRICS_WORK, - ExistingPeriodicWorkPolicy.KEEP, - workRequest - ) - } - - override fun enqueueWorkRequestForFirestore( - workManager: WorkManager, - workRequest: PeriodicWorkRequest - ) { - workManager.enqueueUniquePeriodicWork( - OPPIA_FIRESTORE_WORK, - ExistingPeriodicWorkPolicy.KEEP, - workRequest - ) - } -} diff --git a/utility/src/main/java/org/oppia/android/util/logging/firebase/FirebaseLogUploaderModule.kt b/utility/src/main/java/org/oppia/android/util/logging/firebase/FirebaseLogUploaderModule.kt deleted file mode 100644 index 108b6d8ca9e..00000000000 --- a/utility/src/main/java/org/oppia/android/util/logging/firebase/FirebaseLogUploaderModule.kt +++ /dev/null @@ -1,12 +0,0 @@ -package org.oppia.android.util.logging.firebase - -import dagger.Binds -import dagger.Module -import org.oppia.android.util.logging.LogUploader - -/** Provides Log Uploader related dependencies. */ -@Module -interface FirebaseLogUploaderModule { - @Binds - fun bindFirebaseLogUploader(firebaseLogUploader: FirebaseLogUploader): LogUploader -} diff --git a/utility/src/test/java/org/oppia/android/util/logging/BUILD.bazel b/utility/src/test/java/org/oppia/android/util/logging/BUILD.bazel index d58bc28f9da..f9036fbcc61 100644 --- a/utility/src/test/java/org/oppia/android/util/logging/BUILD.bazel +++ b/utility/src/test/java/org/oppia/android/util/logging/BUILD.bazel @@ -133,5 +133,6 @@ oppia_android_test( "//third_party:com_google_truth_truth", "//third_party:org_robolectric_robolectric", "//third_party:robolectric_android-all", + "//utility/src/main/java/org/oppia/android/util/logging:event_type_to_human_readable_name_converter", ], ) diff --git a/utility/src/test/java/org/oppia/android/util/parser/html/HtmlParserTest.kt b/utility/src/test/java/org/oppia/android/util/parser/html/HtmlParserTest.kt index 327fa9da92d..d0fd9f32ec2 100644 --- a/utility/src/test/java/org/oppia/android/util/parser/html/HtmlParserTest.kt +++ b/utility/src/test/java/org/oppia/android/util/parser/html/HtmlParserTest.kt @@ -119,7 +119,6 @@ import org.oppia.android.util.locale.OppiaBidiFormatter import org.oppia.android.util.locale.OppiaLocale import org.oppia.android.util.logging.LoggerModule import org.oppia.android.util.logging.SyncStatusModule -import org.oppia.android.util.logging.firebase.FirebaseLogUploaderModule import org.oppia.android.util.networking.NetworkConnectionDebugUtilModule import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule import org.oppia.android.util.parser.image.ImageParsingModule @@ -1179,7 +1178,6 @@ class HtmlParserTest { ExplorationProgressModule::class, ExplorationStorageModule::class, FakeOppiaClockModule::class, - FirebaseLogUploaderModule::class, FractionInputModule::class, GcsResourceModule::class, HintsAndSolutionConfigModule::class, diff --git a/utility/src/test/java/org/oppia/android/util/parser/html/ListItemLeadingMarginSpanTest.kt b/utility/src/test/java/org/oppia/android/util/parser/html/ListItemLeadingMarginSpanTest.kt index faa5692afaa..99442bda956 100644 --- a/utility/src/test/java/org/oppia/android/util/parser/html/ListItemLeadingMarginSpanTest.kt +++ b/utility/src/test/java/org/oppia/android/util/parser/html/ListItemLeadingMarginSpanTest.kt @@ -102,7 +102,6 @@ import org.oppia.android.util.locale.OppiaBidiFormatter import org.oppia.android.util.locale.OppiaLocale import org.oppia.android.util.logging.LoggerModule import org.oppia.android.util.logging.SyncStatusModule -import org.oppia.android.util.logging.firebase.FirebaseLogUploaderModule import org.oppia.android.util.networking.NetworkConnectionDebugUtilModule import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule import org.oppia.android.util.parser.image.ImageParsingModule @@ -1100,7 +1099,6 @@ class ListItemLeadingMarginSpanTest { ExplorationProgressModule::class, ExplorationStorageModule::class, FakeOppiaClockModule::class, - FirebaseLogUploaderModule::class, FractionInputModule::class, GcsResourceModule::class, HintsAndSolutionConfigModule::class, diff --git a/wiki/Work-Manager.md b/wiki/Work-Manager.md index ce9628d217f..a54b97221bf 100644 --- a/wiki/Work-Manager.md +++ b/wiki/Work-Manager.md @@ -1,170 +1,140 @@ ## Table of Contents -- [What is WorkManager?](#what-is-workmanager) -- [Its Usage in Oppia Android](#its-usage-in-oppia-android) -- [How to use WorkManager](#how-to-use-workmanager) -- [Writing tests with WorkManager](#writing-tests-with-workmanager) +- [What is `WorkManager`?](#what-is-workmanager) +- [When to use `WorkManager`](#when-to-use-workmanager) +- [How to use `WorkManager`](#how-to-use-workmanager) +- [Writing tests with `WorkManager`](#writing-tests-with-workmanager) +- [Debugging `WorkManager`](#debugging-workmanager) # What is WorkManager? -WorkManager is part of Android Jetpack and an Architecture Component for background work that needs a combination of opportunistic and guaranteed execution. Opportunistic execution means that WorkManager will do your background work as soon as it can. Guaranteed execution means that WorkManager will take care of the logic to start your work under a variety of situations, even if you navigate away from your app. +`WorkManager` is part of Android Jetpack and an Architecture Component for background work that needs a combination of opportunistic and guaranteed execution. Opportunistic execution means that `WorkManager` will do your background work as soon as it can. Guaranteed execution means that `WorkManager` will take care of the logic to start your work under a variety of situations, even if you navigate away from your app. -WorkManager is an incredibly flexible library that has many additional benefits. These include: +`WorkManager` is an incredibly flexible library that has many additional benefits. These include: - Support for both asynchronous one-off and periodic tasks - Support for constraints such as network conditions, storage space, and charging status - Chaining of complex work requests, including running work in parallel - Output from one work request used as input for the next -- Handling API level compatibility back to API level 14 (see note) +- Handling API level compatibility back to API level 14 - Working with or without Google Play services - Following system health best practices -- LiveData support to easily display work request state in UI -The WorkManager library is a good choice for tasks that are useful to complete, even if the user navigates away from the particular screen or your app. Some examples of tasks that are a good use of WorkManager: +The `WorkManager` library is a good choice for tasks that are useful to complete, even if the user navigates away from the particular screen or your app. Some examples of tasks that are a good use of `WorkManager`: - Uploading logs -- Applying filters to images and saving the image - Periodically syncing local data with the network -WorkManager offers guaranteed execution, and not all tasks require that. As such, it is not a catch-all for running every task off of the main thread. +`WorkManager` offers guaranteed execution, and not all tasks require that. As such, it is not a catch-all for running every task off of the main thread. -# Its Usage in Oppia Android -There are a few WorkManager classes you need to know about: +# When to use WorkManager +`WorkManager` is used for long-running periodic tasks such as collecting or uploading analytics, and synchronizing platform parameters with the Oppia backend. These tasks can't be performed in any other way because they need to be able to run even when the user isn't actively using the device (particularly in the case of platform parameters which we want to synchronize for the next app open). Thus, only work that needs to be able to happen even when the app is closed, or under very specific network situations (like the device connecting to cellular or wifi triggering the app needing to do something) should use `WorkManager`. -- `Worker`: This is where you put the code for the actual work you want to perform in the background. You'll extend this class and override the doWork() method. -- `WorkRequest`: This represents a request to do some work. You'll pass in your Worker as part of creating your WorkRequest. When making the WorkRequest you can also specify things like Constraints on when the Worker should run. There are two types of work supported by WorkManager: OneTimeWorkRequest and PeriodicWorkRequest. -- `WorkManager`: This class actually schedules your WorkRequest and makes it run. It schedules WorkRequests in a way that spreads out the load on system resources, while honoring the constraints you specify. +Other types of background tasks (those are, tasks done off the main thread) are all handled using a background coroutine dispatcher and are generally coerced into a specific data flow pattern called `DataProvider`s. For more context on those, see the corresponding [wiki page](https://github.com/oppia/oppia-android/wiki/DataProvider-&-LiveData). +# How to use WorkManager +The Oppia Android app implements exactly one worker: `BootstrapOppiaWorker`. This worker is responsible for several things: +- Determining if an invalid worker is trying to be run (such as an old worker that's no longer valid) and, if so, cancelling it. +- Identifying the specific type of work a worker wants to do. +- Deferring worker construction until platform parameters are guaranteed to be loaded (so that both workers and their factories can depend on platform parameters either directly or via their other dependencies). +- Delegating work to an actual defined `OppiaWorker` implementation. -In Oppia we are using WorkManager in two scenarios : -- To upload cached Logs (for Analytics) over FirebaseAnalytics whenever data connection and battery requirements are met. This was implemented by @Sarthak2601 during GSoC'20, for more details you can go through the [proposal idea](https://github.com/oppia/oppia-web-developer-docs/blob/develop/pdfs/GSoC2020SarthakAgarwal.pdf) -- To sync up the PlatformParameters from OppiaBackend whenever the app starts and the data + battery requirements are met. This was implemented by @ARJUPTA during GSoC'21, for more details you can go through the [proposal idea](https://github.com/oppia/oppia-web-developer-docs/blob/develop/pdfs/GSoC2021ArjunGupta.pdf) +With this architecture in place, you'll never need to directly implement a `ListenableWorker`. Instead, you must do the following: +- Introduce a new `OppiaWorker` implementation (along with its factory and task type). +- Introduce a scheduler class that can automatically schedule the new worker for periodic work. +- Introduce a module that creates the necessary Dagger bindings for both the worker and schedule to automatically be used. Note that this includes adding the new module to the corresponding application component classes (e.g. `DeveloperApplicationComponent`) so that the bindings are enabled. -# How to use WorkManager -If you want to introduce a new feature or any change to the existing WorkManager implementation in oppia-android, here is the basic structure of files you need to keep in mind : - -1. Start with creating a Worker class (we have used `ListenableWorker` till now everywhere) for eg - MyFeatureWorker. - - ``` - class LogUploadWorker private constructor( - context: Context, - params: WorkerParameters, - ... - @BackgroundDispatcher private val backgroundDispatcher: CoroutineDispatcher - ) : ListenableWorker(context, params) { - override fun startWork(): ListenableFuture { - val backgroundScope = CoroutineScope(backgroundDispatcher) - val result = backgroundScope.async {...} - return if(checkWorkDone(result)) Result.success() else Result.failure() - } - } - ``` - -2. Then after implementing all the functionality in MyFeatureWorker, create a custom WorkerFactory class (for eg- MyFeatureWorkerFactory) so that we can provide any extra parameters if needed. - - ``` - class LogUploadWorkerFactory @Inject constructor( - private val workerFactory: LogUploadWorker.Factory - ) : WorkerFactory() { - - /** Returns a new [LogUploadWorker] for the given context and parameters. */ - override fun createWorker( - appContext: Context, - workerClassName: String, - workerParameters: WorkerParameters - ): ListenableWorker? { - return workerFactory.create(appContext, workerParameters) - } - } - ``` - -3. Provide an instance of this WorkerFactory class in the `WorkManagerConfigurationModule` so that a singular work manager configuration can be made for the entire app. - - ``` - @Module - class WorkManagerConfigurationModule { - - @Singleton - @Provides - fun provideWorkManagerConfiguration( - logUploadWorkerFactory: LogUploadWorkerFactory, - platformParameterSyncUpWorkerFactory: PlatformParameterSyncUpWorkerFactory - ): Configuration { - val delegatingWorkerFactory = DelegatingWorkerFactory() - delegatingWorkerFactory.addFactory(logUploadWorkerFactory) - delegatingWorkerFactory.addFactory(platformParameterSyncUpWorkerFactory) - return Configuration.Builder().setWorkerFactory(delegatingWorkerFactory).build() - } - } - ``` - -4. After all these steps create an Initializer class (for eg- MyFeatureWorkerInitializer) that will prepare and enqueue a WorkRequest for you at the time when app starts. - - ``` - @Singleton - class LogUploadWorkManagerInitializer @Inject constructor( - private val context: Context, - private val logUploader: LogUploader - ) : ApplicationStartupListener { - override fun onCreate() { - val workManager = WorkManager.getInstance(context) - logUploader.enqueueWorkRequestForEvents(workManager, workRequestForUploadingEvents) - logUploader.enqueueWorkRequestForExceptions(workManager, workRequestForUploadingExceptions) - } - } - ``` - -**Note** - All the parts of WorkManager implementation entirely lie in the domain layer, but there are few functionalities that you may need to acquire from other layers for eg- if you need to make a network request you would probably need to interact with data layer also. +That's it! From there you can customize which tasks the job can do and adapt the work and scheduling accordingly. It's recommended to look at existing implementations for how they're set up, but the simplest is the debug worker which is designed to specifically analyze `WorkManager` behavior and can be seen in the [`org.oppia.android.domain.workmanager.debug` package](https://github.com/oppia/oppia-android/tree/develop/domain/src/main/java/org/oppia/android/domain/workmanager/debug). # Writing tests with WorkManager -For writing any test with WorkManager you will need to interact with -- *WorkManagerTestInitHelper* so that you can emulate the enquing and running of WorkRequests. - - ``` - @Before - fun setUp() { - setUpTestApplicationComponent() - context = InstrumentationRegistry.getInstrumentation().targetContext - val config = Configuration.Builder() - .setExecutor(SynchronousExecutor()) - .setWorkerFactory(logUploadWorkerFactory) - .build() - WorkManagerTestInitHelper.initializeTestWorkManager(context, config) - } - ``` -- *TestCoroutinesDispatcher* so that you can block the code execution up untill WorkRequest(s) are running. (ie. working with suspend functions) -- You might also need to introduce some fakes so that you can make sure the entities (object, classes, varaibles & constants etc.) over which you MyFeatureWorker depends doesn't have any bugs. - -Here is an exemplar test that is using WorkManager to enqueue a WorkRequest with any inputData (if needed). After we enqueue a request, the next step is to wait until its execution is completed and for that we are using testCoroutineDispatchers - + +Writing worker tests generally means testing one of three things: +- The actual worker (i.e. the class that implements `OppiaWorker`). +- Testing the class that automatically schedules the worker on application startup (i.e. the class that implements `StartupWorkerScheduleReadinessListener`). +- Testing the Dagger module that correctly binds both the worker and the scheduler. + +For all three of these, look at the following references (plus their tested class implementations): +- [`DebugWorkerTest`](https://github.com/oppia/oppia-android/blob/develop/domain/src/test/java/org/oppia/android/domain/workmanager/debug/DebugWorkerTest.kt) +- [`DebugWorkerSchedulerTest`](https://github.com/oppia/oppia-android/blob/develop/domain/src/test/java/org/oppia/android/domain/workmanager/debug/DebugWorkerSchedulerTest.kt) +- [`DebugWorkerDebugModuleTest`](https://github.com/oppia/oppia-android/blob/develop/domain/src/test/java/org/oppia/android/domain/workmanager/debug/DebugWorkerDebugModuleTest.kt) + +These classes are specifically designed to serve as examples for creating and testing Oppia workers. + +At a higher level, tests never interact with `WorkManager` directly. Instead, they make use of [`OppiaWorkManagerTestDriver`](https://github.com/oppia/oppia-android/blob/develop/domain/src/main/java/org/oppia/android/domain/workmanager/testing/OppiaWorkManagerTestDriver.kt) to do all of: +- Initializing the test so that it can properly integrate with `WorkManager`. +- Running `OppiaWorker`s as either one-off or periodic jobs. +- Inspect the latest state of a job. +- Force job constraints to test constrained scenarios (such as when internet connectivity is required). + +Cumulatively, these provide all the necessary functionality for testing both workers and worker schedulers. Validating the module is done using standard module testing techniques. + +Setting up the test environment properly is highly complex which is why the helper must be used (and, in fact, file content checks prohibit using `WorkManager` or its testing utilities directly). + +# Debugging WorkManager + +Verifying that `WorkManager` is behaving correctly can be difficult since it relies heavily on OS constraints and thus cannot necessarily guarantee certain behaviors, especially for periodic tasks. Here are some important aspects to note when trying to determine if a worker is running correctly: +- `WorkManager` won't allow periodic tasks to run more frequently than 15 minutes unless they are explicitly initiated with a one-time work request. +- Periodic tasks will run immediately upon opening the app if their constraints are met and they haven't yet run within their configured time period. +- Tasks will run even if the app is closed and this can be validated by force closing the app using: `adb shell am force-stop org.oppia.android` +- There's a debug worker that's configured to run every 15 minutes, 20 minutes, and 6 hours (to align with other long periodic workers). This only runs in `//:oppia_dev` builds but simply opening the developer app at least once and watching ADB (for debug logs) should provide a basis to verify that `WorkManager` is actually running. +- You can use tests to verify that your worker will run when expected. See `DebugWorkerTest` for an example of how to test periodic workers. +- Finally, you can actually force a periodic task to run through ADB (see below). + +**Important**: Many of the following commands may require using a root ADB shell. You can enable that using `adb root` but it may not be available on all Android devices. It should always be available for emulators so long as they're running a developer version of the Android OS (make sure you aren't using an image that has Google Play, but you may need Google APIs for testing certain jobs like Firebase analytics). + +To force a specific worker to run we must first figure out what's currently enqueued. To do that run the following ADB command: + +```sh +adb shell am broadcast -a "androidx.work.diagnostics.REQUEST_DIAGNOSTICS" org.oppia.android -f 32 +``` + +(Note that the `-f 32` will allow the diagnostic request to wake the app if it was force stopped or never opened). + +View logcat for info logs. You should see something like the following: + ``` - @Test - fun testWorker_logEvent_withoutNetwork_enqueueRequest_verifySuccess() { - networkConnectionUtil.setCurrentConnectionStatus(NONE) - analyticsController.logTransitionEvent( - eventLogTopicContext.timestamp, - eventLogTopicContext.actionName, - oppiaLogger.createTopicContext(TEST_TOPIC_ID) - ) - - val workManager = WorkManager.getInstance(ApplicationProvider.getApplicationContext()) - - val inputData = Data.Builder().putString( - LogUploadWorker.WORKER_CASE_KEY, - LogUploadWorker.EVENT_WORKER - ).build() - - val request: OneTimeWorkRequest = OneTimeWorkRequestBuilder() - .setInputData(inputData) - .build() - - workManager.enqueue(request) - testCoroutineDispatchers.runCurrent() - val workInfo = workManager.getWorkInfoById(request.id) - - assertThat(workInfo.get().state).isEqualTo(WorkInfo.State.SUCCEEDED) - assertThat(fakeEventLogger.getMostRecentEvent()).isEqualTo(eventLogTopicContext) - } +Enqueued work: +Id Class Name Job Id State Unique Name Tags +8126cd00-2c35-4704-8239-77abda3dd12e org.oppia.android.domain.workmanager.BootstrapOppiaWorker 2109 ENQUEUED DebugWorker.RUN_EVERY_SIX_HOURS_WITH_OR_WITHOUT_CONNECTIVITY DebugWorker.RUN_EVERY_SIX_HOURS_WITH_OR_WITHOUT_CONNECTIVITY,org.oppia.android.domain.workmanager.BootstrapOppiaWorker +e7b9b00c-e742-4688-b004-14cf07698405 org.oppia.android.domain.workmanager.BootstrapOppiaWorker 2110 ENQUEUED PlatformParameterSyncUpWorker.refresh_platform_parameters org.oppia.android.domain.workmanager.BootstrapOppiaWorker,PlatformParameterSyncUpWorker.refresh_platform_parameters +240ffbc1-3001-478b-947b-3549f9e91007 org.oppia.android.domain.workmanager.BootstrapOppiaWorker 2112 ENQUEUED LogUploadWorker.upload_performance_metrics org.oppia.android.domain.workmanager.BootstrapOppiaWorker,LogUploadWorker.upload_performance_metrics +21048af0-8b1b-4c85-899e-c252ecd4c2fa org.oppia.android.domain.workmanager.BootstrapOppiaWorker 2113 ENQUEUED LogUploadWorker.upload_exceptions org.oppia.android.domain.workmanager.BootstrapOppiaWorker,LogUploadWorker.upload_exceptions +88be6627-bc44-45ab-993f-9dcb9b6a756d org.oppia.android.domain.workmanager.BootstrapOppiaWorker 2114 ENQUEUED LogUploadWorker.upload_firestore_data org.oppia.android.domain.workmanager.BootstrapOppiaWorker,LogUploadWorker.upload_firestore_data +9aecbfd4-405c-4f29-9467-a6e434350ded org.oppia.android.domain.workmanager.BootstrapOppiaWorker 2115 ENQUEUED LogUploadWorker.upload_events org.oppia.android.domain.workmanager.BootstrapOppiaWorker,LogUploadWorker.upload_events +9de8273f-6c12-4684-94a3-a75b4b36aa0b org.oppia.android.domain.workmanager.BootstrapOppiaWorker 2123 ENQUEUED DebugWorker.RUN_EVERY_TWENTY_MINUTES_WITH_OR_WITHOUT_CONNECTIVITY org.oppia.android.domain.workmanager.BootstrapOppiaWorker,DebugWorker.RUN_EVERY_TWENTY_MINUTES_WITH_OR_WITHOUT_CONNECTIVITY +9e070daa-c323-499f-b45f-00d2932f8809 org.oppia.android.domain.workmanager.BootstrapOppiaWorker 2131 ENQUEUED DebugWorker.RUN_EVERY_FIFTEEN_MINUTES_WITH_CONNECTIVITY org.oppia.android.domain.workmanager.BootstrapOppiaWorker,DebugWorker.RUN_EVERY_FIFTEEN_MINUTES_WITH_CONNECTIVITY ``` -In Oppia we write tests for both the Worker and its Initializer class. You can take a reference for the same from these files: +This forces `WorkManager` to reconcile its internal state which will automatically kick off stale jobs. We can leverage that to force a job that's run within its recent time period to run again by making `WorkManager` forget about it. This even bypasses its own internal 15 minute limit. The following command can be used to achieve this: -Worker Tests - *[PlatformParameterSyncUpWorkerTest](https://github.com/oppia/oppia-android/blob/develop/domain/src/test/java/org/oppia/android/domain/platformparameter/syncup/PlatformParameterSyncUpWorkerTest.kt) OR [LogUploadWorkerTest](https://github.com/oppia/oppia-android/blob/develop/domain/src/test/java/org/oppia/android/domain/oppialogger/loguploader/LogUploadWorkerTest.kt)* +```sh +adb shell "run-as org.oppia.android sqlite3 no_backup/androidx.work.workdb \"UPDATE WorkSpec SET period_start_time = 0 WHERE id = ''; DELETE FROM SystemIdInfo WHERE work_spec_id = '';\"" +``` + +`` is updated using one of the IDs in the diagnostics table above, for example `9e070daa-c323-499f-b45f-00d2932f8809` would correspond to the periodic 15-minute `DebugWorker` job. These IDs are long-lived unlike the job IDs above. + +Once this command finishes you can re-run the diagnostics command above and the corresponding job should run. + +For simplicity, here are Bash functions that take either a parameter of the worker's unique name (e.g. 'DebugWorker.RUN_EVERY_FIFTEEN_MINUTES_WITH_CONNECTIVITY') or the worker's ID performs all of the necessary commands: + +```bash +function PrintOppiaWorkerDiagnostics() { + adb shell am broadcast -a "androidx.work.diagnostics.REQUEST_DIAGNOSTICS" org.oppia.android -f 32 +} + +function ForceRunOppiaJobById() { + echo "Attempting to force run Oppia worker with ID: $1" + local worker_id="$1" + adb shell "run-as org.oppia.android sqlite3 no_backup/androidx.work.workdb \"UPDATE WorkSpec SET period_start_time = 0 WHERE id = '$worker_id'; DELETE FROM SystemIdInfo WHERE work_spec_id = '$worker_id';\"" + PrintOppiaWorkerDiagnostics +} + +function ForceRunOppiaJobWithUniqueName() { + local unique_name="$1" + echo "Attempting to find an Oppia worker with unique name: $unique_name" + PrintOppiaWorkerDiagnostics + local app_pid=$(adb shell pidof -s org.oppia.android) + local worker_id=$(adb logcat --pid=$app_pid -d | grep "WM-DiagnosticsWrkr" | grep "$unique_name" | tail -1 | awk '{for(i=1;i<=NF;i++) if($i=="WM-DiagnosticsWrkr:") {print $(i+1); exit}}') + echo "Worker has unique ID: $worker_id" + ForceRunOppiaJobById $worker_id +} +``` -Initializer Tests - *[PlatformParameterSyncUpWorkManagerInitializerTest](https://github.com/oppia/oppia-android/blob/develop/domain/src/test/java/org/oppia/android/domain/platformparameter/syncup/PlatformParameterSyncUpWorkManagerInitializerTest.kt) OR [LogUploadWorkManagerInitializerTest](https://github.com/oppia/oppia-android/blob/develop/domain/src/test/java/org/oppia/android/domain/oppialogger/loguploader/LogReportWorkManagerInitializerTest.kt)* \ No newline at end of file +Caveat: these may only run successfully if there's exactly one device available.