Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update currentSyncJobStatus for oneTimeSync when syncJobStatus is null #2511

Merged
merged 10 commits into from
Jul 5, 2024
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ package com.google.android.fhir.sync
import android.content.Context
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
import androidx.work.BackoffPolicy
import androidx.work.Constraints
import androidx.work.WorkInfo
import androidx.work.WorkManager
Expand Down Expand Up @@ -104,12 +105,49 @@ class SyncInstrumentedTest {
assertThat(states.last()).isInstanceOf(CurrentSyncJobStatus.Succeeded::class.java)
}

@Test
fun oneTimeSync_currentSyncJobStatusSucceeded_nextCurrentSyncJobStatusShouldBeRunning() {
WorkManagerTestInitHelper.initializeTestWorkManager(context)
val states = mutableListOf<CurrentSyncJobStatus>()
val nextExecutionStates = mutableListOf<CurrentSyncJobStatus>()
runBlocking {
Sync.oneTimeSync<TestSyncWorker>(context = context)
.transformWhile {
states.add(it)
emit(it is CurrentSyncJobStatus.Succeeded)
it !is CurrentSyncJobStatus.Succeeded
}
.shareIn(this, SharingStarted.Eagerly, 5)

Sync.oneTimeSync<TestSyncWorker>(context = context)
.transformWhile {
nextExecutionStates.add(it)
emit(it is CurrentSyncJobStatus.Succeeded)
it !is CurrentSyncJobStatus.Succeeded
}
.shareIn(this, SharingStarted.Eagerly, 5)
}
assertThat(states.first()).isInstanceOf(CurrentSyncJobStatus.Running::class.java)
assertThat(states.last()).isInstanceOf(CurrentSyncJobStatus.Succeeded::class.java)
assertThat(nextExecutionStates.first()).isInstanceOf(CurrentSyncJobStatus.Running::class.java)
}

@Test
fun oneTime_worker_failedSyncState() {
WorkManagerTestInitHelper.initializeTestWorkManager(context)
val states = mutableListOf<CurrentSyncJobStatus>()
runBlocking {
Sync.oneTimeSync<TestSyncWorkerForDownloadFailing>(context = context)
Sync.oneTimeSync<TestSyncWorkerForDownloadFailing>(
context = context,
RetryConfiguration(
BackoffCriteria(
BackoffPolicy.LINEAR,
30,
TimeUnit.SECONDS,
),
0,
),
)
.transformWhile {
states.add(it)
emit(it is CurrentSyncJobStatus.Failed)
Expand All @@ -121,6 +159,53 @@ class SyncInstrumentedTest {
assertThat(states.last()).isInstanceOf(CurrentSyncJobStatus.Failed::class.java)
}

@Test
fun oneTimeSync_currentSyncJobStatusFailed_nextCurrentSyncJobStatusShouldBeRunning() {
WorkManagerTestInitHelper.initializeTestWorkManager(context)
val states = mutableListOf<CurrentSyncJobStatus>()
val nextExecutionStates = mutableListOf<CurrentSyncJobStatus>()
runBlocking {
Sync.oneTimeSync<TestSyncWorkerForDownloadFailing>(
context = context,
RetryConfiguration(
BackoffCriteria(
BackoffPolicy.LINEAR,
30,
TimeUnit.SECONDS,
),
0,
),
)
.transformWhile {
states.add(it)
emit(it is CurrentSyncJobStatus.Failed)
it !is CurrentSyncJobStatus.Failed
}
.shareIn(this, SharingStarted.Eagerly, 5)

Sync.oneTimeSync<TestSyncWorkerForDownloadFailing>(
context = context,
RetryConfiguration(
BackoffCriteria(
BackoffPolicy.LINEAR,
30,
TimeUnit.SECONDS,
),
0,
),
)
.transformWhile {
nextExecutionStates.add(it)
emit(it is CurrentSyncJobStatus.Failed)
it !is CurrentSyncJobStatus.Failed
}
.shareIn(this, SharingStarted.Eagerly, 5)
}
assertThat(states.first()).isInstanceOf(CurrentSyncJobStatus.Running::class.java)
assertThat(states.last()).isInstanceOf(CurrentSyncJobStatus.Failed::class.java)
assertThat(nextExecutionStates.first()).isInstanceOf(CurrentSyncJobStatus.Running::class.java)
}

@Test
fun periodic_worker_periodicSyncState() {
WorkManagerTestInitHelper.initializeTestWorkManager(context)
Expand Down
66 changes: 38 additions & 28 deletions engine/src/main/java/com/google/android/fhir/sync/Sync.kt
santosh-pingle marked this conversation as resolved.
Show resolved Hide resolved
santosh-pingle marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,9 @@ import androidx.work.PeriodicWorkRequest
import androidx.work.WorkInfo
import androidx.work.WorkInfo.State.CANCELLED
import androidx.work.WorkInfo.State.ENQUEUED
import androidx.work.WorkInfo.State.FAILED
import androidx.work.WorkInfo.State.RUNNING
import androidx.work.WorkInfo.State.SUCCEEDED
import androidx.work.WorkManager
import androidx.work.hasKeyWithValueOfType
import com.google.android.fhir.FhirEngineProvider
Expand Down Expand Up @@ -106,7 +108,16 @@ object Sync {
return combineSyncStateForPeriodicSync(context, uniqueWorkName, flow)
}

/** Gets the worker info for the [FhirSyncWorker] */
/**
* Retrieves the work information for a specific unique work name as a flow of pairs containing
* the work state and the corresponding progress data if available.
*
* @param context The application context.
* @param workName The unique name of the work to retrieve information for.
* @return A flow emitting pairs of [WorkInfo.State] and [SyncJobStatus]. The flow will emit only
* when the progress data contains a non-empty key-value map and includes a key of type [String]
* with the name "StateType".
*/
@PublishedApi
internal fun getWorkerInfo(context: Context, workName: String) =
WorkManager.getInstance(context)
Expand Down Expand Up @@ -269,44 +280,43 @@ object Sync {
}

/**
* Only call this API when `syncJobStatusFromWorkManager` is null. Create a [CurrentSyncJobStatus]
* from `syncJobStatusFromDataStore` if it is not null; otherwise, create it from
* [WorkInfo.State].
* Creates terminal states of [CurrentSyncJobStatus] from [syncJobStatusFromDataStore]; and
* intermediate states of [CurrentSyncJobStatus] from [WorkInfo.State].
*
* Note : Only call this API when `syncJobStatusFromWorkManager` is null.
*/
private fun handleNullWorkManagerStatusForOneTimeSync(
workInfoState: WorkInfo.State,
syncJobStatusFromDataStore: SyncJobStatus?,
): CurrentSyncJobStatus =
syncJobStatusFromDataStore?.let {
when (it) {
is SyncJobStatus.Succeeded -> Succeeded(it.timestamp)
is SyncJobStatus.Failed -> Failed(it.timestamp)
else -> error("Inconsistent terminal syncJobStatus : $syncJobStatusFromDataStore")
}
when (workInfoState) {
santosh-pingle marked this conversation as resolved.
Show resolved Hide resolved
ENQUEUED -> Enqueued
RUNNING -> Running(SyncJobStatus.Started())
SUCCEEDED ->
syncJobStatusFromDataStore?.let {
when (it) {
is SyncJobStatus.Succeeded -> Succeeded(it.timestamp)
else -> error("Inconsistent terminal syncJobStatus : $syncJobStatusFromDataStore")
}
}
?: error("Inconsistent terminal syncJobStatus.")
FAILED ->
syncJobStatusFromDataStore?.let {
when (it) {
is SyncJobStatus.Failed -> Failed(it.timestamp)
else -> error("Inconsistent terminal syncJobStatus : $syncJobStatusFromDataStore")
}
}
?: error("Inconsistent terminal syncJobStatus.")
CANCELLED -> Cancelled
else -> error("Inconsistent WorkInfo.State: $workInfoState.")
}
?: when (workInfoState) {
RUNNING -> Running(SyncJobStatus.Started())
ENQUEUED -> Enqueued
CANCELLED -> Cancelled
// syncJobStatusFromDataStore should not be null for SUCCEEDED, FAILED.
else -> error("Inconsistent WorkInfo.State: $workInfoState.")
}

/**
* Only call this API when syncJobStatusFromWorkManager is null. Create a [CurrentSyncJobStatus]
* Only call this API when syncJobStatus From WorkManager is null. Create a [CurrentSyncJobStatus]
* from [WorkInfo.State]. (Note: syncJobStatusFromDataStore is updated as lastSynJobStatus, which
* is the terminalSyncJobStatus.)
*/
private fun handleNullWorkManagerStatusForPeriodicSync(
workInfoState: WorkInfo.State,
): CurrentSyncJobStatus =
when (workInfoState) {
RUNNING -> Running(SyncJobStatus.Started())
ENQUEUED -> Enqueued
CANCELLED -> Cancelled
else -> error("Inconsistent WorkInfo.State in periodic sync : $workInfoState.")
}

private fun handleNullWorkManagerStatusForPeriodicSync(
workInfoState: WorkInfo.State,
syncJobStatusFromDataStore: SyncJobStatus?,
Expand Down
Loading