Skip to content

Commit

Permalink
Update currentSyncJobStatus for oneTimeSync when syncJobStatus is null (
Browse files Browse the repository at this point in the history
#2511)

* currentSyncJobStatus when sync state in workmanager is null.

* unit tests.

* update kdoc

* address review comments.

---------

Co-authored-by: Santosh Pingle <[email protected]>
  • Loading branch information
santosh-pingle and Santosh Pingle authored Jul 5, 2024
1 parent 445cf3f commit cf0eaae
Show file tree
Hide file tree
Showing 2 changed files with 124 additions and 29 deletions.
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
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) {
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

0 comments on commit cf0eaae

Please sign in to comment.