Use a minimal job spec representation in memory.
This commit is contained in:
parent
eb59afc33c
commit
973dc72cfa
5 changed files with 340 additions and 142 deletions
|
@ -13,6 +13,7 @@ import org.signal.core.util.delete
|
|||
import org.signal.core.util.insertInto
|
||||
import org.signal.core.util.logging.Log
|
||||
import org.signal.core.util.readToList
|
||||
import org.signal.core.util.readToSingleObject
|
||||
import org.signal.core.util.requireBlob
|
||||
import org.signal.core.util.requireBoolean
|
||||
import org.signal.core.util.requireInt
|
||||
|
@ -29,6 +30,7 @@ import org.thoughtcrime.securesms.jobmanager.persistence.ConstraintSpec
|
|||
import org.thoughtcrime.securesms.jobmanager.persistence.DependencySpec
|
||||
import org.thoughtcrime.securesms.jobmanager.persistence.FullSpec
|
||||
import org.thoughtcrime.securesms.jobmanager.persistence.JobSpec
|
||||
import org.thoughtcrime.securesms.jobs.MinimalJobSpec
|
||||
|
||||
class JobDatabase(
|
||||
application: Application,
|
||||
|
@ -183,6 +185,43 @@ class JobDatabase(
|
|||
|
||||
@Synchronized
|
||||
fun getAllJobSpecs(): List<JobSpec> {
|
||||
return readableDatabase
|
||||
.select()
|
||||
.from(Jobs.TABLE_NAME)
|
||||
.orderBy("${Jobs.CREATE_TIME}, ${Jobs.ID} ASC")
|
||||
.run()
|
||||
.readToList { cursor ->
|
||||
jobSpecFromCursor(cursor)
|
||||
}
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
fun getOldestJobSpecs(limit: Int): List<JobSpec> {
|
||||
return readableDatabase
|
||||
.select()
|
||||
.from(Jobs.TABLE_NAME)
|
||||
.orderBy("${Jobs.CREATE_TIME}, ${Jobs.ID} ASC")
|
||||
.limit(limit)
|
||||
.run()
|
||||
.readToList { cursor ->
|
||||
jobSpecFromCursor(cursor)
|
||||
}
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
fun getJobSpec(id: String): JobSpec? {
|
||||
return readableDatabase
|
||||
.select()
|
||||
.from(Jobs.TABLE_NAME)
|
||||
.where("${Jobs.JOB_SPEC_ID} = ?", id)
|
||||
.run()
|
||||
.readToSingleObject {
|
||||
jobSpecFromCursor(it)
|
||||
}
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
fun getAllMinimalJobSpecs(): List<MinimalJobSpec> {
|
||||
val columns = arrayOf(
|
||||
Jobs.ID,
|
||||
Jobs.JOB_SPEC_ID,
|
||||
|
@ -191,18 +230,23 @@ class JobDatabase(
|
|||
Jobs.CREATE_TIME,
|
||||
Jobs.LAST_RUN_ATTEMPT_TIME,
|
||||
Jobs.NEXT_BACKOFF_INTERVAL,
|
||||
Jobs.RUN_ATTEMPT,
|
||||
Jobs.MAX_ATTEMPTS,
|
||||
Jobs.LIFESPAN,
|
||||
Jobs.SERIALIZED_DATA,
|
||||
Jobs.SERIALIZED_INPUT_DATA,
|
||||
Jobs.IS_RUNNING,
|
||||
Jobs.PRIORITY
|
||||
)
|
||||
return readableDatabase
|
||||
.query(Jobs.TABLE_NAME, columns, null, null, null, null, "${Jobs.CREATE_TIME}, ${Jobs.ID} ASC")
|
||||
.readToList { cursor ->
|
||||
jobSpecFromCursor(cursor)
|
||||
MinimalJobSpec(
|
||||
id = cursor.requireNonNullString(Jobs.JOB_SPEC_ID),
|
||||
factoryKey = cursor.requireNonNullString(Jobs.FACTORY_KEY),
|
||||
queueKey = cursor.requireNonNullString(Jobs.QUEUE_KEY),
|
||||
createTime = cursor.requireLong(Jobs.CREATE_TIME),
|
||||
lastRunAttemptTime = cursor.requireLong(Jobs.LAST_RUN_ATTEMPT_TIME),
|
||||
nextBackoffInterval = cursor.requireLong(Jobs.NEXT_BACKOFF_INTERVAL),
|
||||
priority = cursor.requireInt(Jobs.PRIORITY),
|
||||
isRunning = cursor.requireBoolean(Jobs.IS_RUNNING),
|
||||
isMemoryOnly = false
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package org.thoughtcrime.securesms.jobs
|
||||
|
||||
import androidx.annotation.VisibleForTesting
|
||||
import org.thoughtcrime.securesms.database.JobDatabase
|
||||
import org.thoughtcrime.securesms.jobmanager.Job
|
||||
import org.thoughtcrime.securesms.jobmanager.persistence.ConstraintSpec
|
||||
|
@ -7,33 +8,43 @@ import org.thoughtcrime.securesms.jobmanager.persistence.DependencySpec
|
|||
import org.thoughtcrime.securesms.jobmanager.persistence.FullSpec
|
||||
import org.thoughtcrime.securesms.jobmanager.persistence.JobSpec
|
||||
import org.thoughtcrime.securesms.jobmanager.persistence.JobStorage
|
||||
import org.thoughtcrime.securesms.util.LRUCache
|
||||
import java.util.TreeSet
|
||||
|
||||
class FastJobStorage(private val jobDatabase: JobDatabase) : JobStorage {
|
||||
|
||||
// TODO [job] We need a new jobspec that has no data (for space efficiency), and ideally no other random stuff that we don't need for filtering
|
||||
companion object {
|
||||
private const val JOB_CACHE_LIMIT = 1000
|
||||
}
|
||||
|
||||
private val jobs: MutableList<JobSpec> = mutableListOf()
|
||||
private val jobSpecCache: LRUCache<String, JobSpec> = LRUCache(JOB_CACHE_LIMIT)
|
||||
|
||||
private val jobs: MutableList<MinimalJobSpec> = mutableListOf()
|
||||
|
||||
// TODO [job] Rather than duplicate what is likely the same handful of constraints over and over, we should somehow re-use instances
|
||||
private val constraintsByJobId: MutableMap<String, MutableList<ConstraintSpec>> = mutableMapOf()
|
||||
private val dependenciesByJobId: MutableMap<String, MutableList<DependencySpec>> = mutableMapOf()
|
||||
|
||||
private val eligibleJobs: TreeSet<JobSpec> = TreeSet(EligibleJobComparator)
|
||||
private val migrationJobs: TreeSet<JobSpec> = TreeSet(compareBy { it.createTime })
|
||||
private val mostEligibleJobForQueue: MutableMap<String, JobSpec> = hashMapOf()
|
||||
private val eligibleJobs: TreeSet<MinimalJobSpec> = TreeSet(EligibleJobComparator)
|
||||
private val migrationJobs: TreeSet<MinimalJobSpec> = TreeSet(compareBy { it.createTime })
|
||||
private val mostEligibleJobForQueue: MutableMap<String, MinimalJobSpec> = hashMapOf()
|
||||
|
||||
@Synchronized
|
||||
override fun init() {
|
||||
jobs += jobDatabase.getAllJobSpecs()
|
||||
jobs += jobDatabase.getAllMinimalJobSpecs()
|
||||
|
||||
for (job in jobs) {
|
||||
if (job.queueKey == Job.Parameters.MIGRATION_QUEUE_KEY) {
|
||||
migrationJobs += job
|
||||
} else {
|
||||
// TODO [job] Because we're using a TreeSet, this operation becomes n*log(n). Ideal complexity for a sort, but think more about whether a bulk sort would be better.
|
||||
placeJobInEligibleList(job)
|
||||
}
|
||||
}
|
||||
|
||||
jobDatabase.getOldestJobSpecs(JOB_CACHE_LIMIT).forEach {
|
||||
jobSpecCache[it.id] = it
|
||||
}
|
||||
|
||||
for (constraintSpec in jobDatabase.getAllConstraintSpecs()) {
|
||||
val jobConstraints: MutableList<ConstraintSpec> = constraintsByJobId.getOrPut(constraintSpec.jobSpecId) { mutableListOf() }
|
||||
jobConstraints += constraintSpec
|
||||
|
@ -54,12 +65,14 @@ class FastJobStorage(private val jobDatabase: JobDatabase) : JobStorage {
|
|||
}
|
||||
|
||||
for (fullSpec in fullSpecs) {
|
||||
jobs += fullSpec.jobSpec
|
||||
val minimalJobSpec = fullSpec.jobSpec.toMinimalJobSpec()
|
||||
jobs += minimalJobSpec
|
||||
jobSpecCache[fullSpec.jobSpec.id] = fullSpec.jobSpec
|
||||
|
||||
if (fullSpec.jobSpec.queueKey == Job.Parameters.MIGRATION_QUEUE_KEY) {
|
||||
migrationJobs += fullSpec.jobSpec
|
||||
migrationJobs += minimalJobSpec
|
||||
} else {
|
||||
placeJobInEligibleList(fullSpec.jobSpec)
|
||||
placeJobInEligibleList(minimalJobSpec)
|
||||
}
|
||||
|
||||
constraintsByJobId[fullSpec.jobSpec.id] = fullSpec.constraintSpecs.toMutableList()
|
||||
|
@ -69,21 +82,21 @@ class FastJobStorage(private val jobDatabase: JobDatabase) : JobStorage {
|
|||
|
||||
@Synchronized
|
||||
override fun getJobSpec(id: String): JobSpec? {
|
||||
return jobs.firstOrNull { it.id == id }
|
||||
return jobs.firstOrNull { it.id == id }?.toJobSpec()
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
override fun getAllJobSpecs(): List<JobSpec> {
|
||||
// TODO [job] this will have to change
|
||||
return ArrayList(jobs)
|
||||
return jobDatabase.getAllJobSpecs()
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
override fun getPendingJobsWithNoDependenciesInCreatedOrder(currentTime: Long): List<JobSpec> {
|
||||
val migrationJob: JobSpec? = migrationJobs.firstOrNull()
|
||||
val migrationJob: MinimalJobSpec? = migrationJobs.firstOrNull()
|
||||
|
||||
return if (migrationJob != null && !migrationJob.isRunning && migrationJob.hasEligibleRunTime(currentTime)) {
|
||||
listOf(migrationJob)
|
||||
listOf(migrationJob.toJobSpec())
|
||||
} else if (migrationJob != null) {
|
||||
emptyList()
|
||||
} else {
|
||||
|
@ -95,15 +108,16 @@ class FastJobStorage(private val jobDatabase: JobDatabase) : JobStorage {
|
|||
}
|
||||
.filterNot { it.isRunning }
|
||||
.filter { job -> job.hasEligibleRunTime(currentTime) }
|
||||
.map { it.toJobSpec() }
|
||||
.toList()
|
||||
|
||||
// Note: The priority sort at the end is safe because it's stable. That means that within jobs with the same priority, they will still be sorted by createTime.
|
||||
}
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
override fun getJobsInQueue(queue: String): List<JobSpec> {
|
||||
return jobs.filter { it.queueKey == queue }
|
||||
return jobs
|
||||
.filter { it.queueKey == queue }
|
||||
.map { it.toJobSpec() }
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
|
@ -130,9 +144,10 @@ class FastJobStorage(private val jobDatabase: JobDatabase) : JobStorage {
|
|||
val job: JobSpec? = getJobSpec(id)
|
||||
if (job == null || !job.isMemoryOnly) {
|
||||
jobDatabase.markJobAsRunning(id, currentTime)
|
||||
// Don't need to update jobSpecCache because all changed fields are in the min spec
|
||||
}
|
||||
|
||||
updateJobsInMemory(
|
||||
updateCachedJobSpecs(
|
||||
filter = { it.id == id },
|
||||
transformer = { jobSpec ->
|
||||
jobSpec.copy(
|
||||
|
@ -149,46 +164,35 @@ class FastJobStorage(private val jobDatabase: JobDatabase) : JobStorage {
|
|||
val job = getJobSpec(id)
|
||||
if (job == null || !job.isMemoryOnly) {
|
||||
jobDatabase.updateJobAfterRetry(id, currentTime, runAttempt, nextBackoffInterval, serializedData)
|
||||
|
||||
// Note: All other fields are accounted for in the min spec. We only need to update from disk if serialized data changes.
|
||||
val cached = jobSpecCache[id]
|
||||
if (cached != null && !cached.serializedData.contentEquals(serializedData)) {
|
||||
jobDatabase.getJobSpec(id)?.let {
|
||||
jobSpecCache[id] = it
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
updateJobsInMemory(
|
||||
updateCachedJobSpecs(
|
||||
filter = { it.id == id },
|
||||
transformer = { jobSpec ->
|
||||
jobSpec.copy(
|
||||
isRunning = false,
|
||||
runAttempt = runAttempt,
|
||||
lastRunAttemptTime = currentTime,
|
||||
nextBackoffInterval = nextBackoffInterval,
|
||||
serializedData = serializedData
|
||||
nextBackoffInterval = nextBackoffInterval
|
||||
)
|
||||
},
|
||||
singleUpdate = true
|
||||
)
|
||||
}
|
||||
|
||||
private fun updateJobsInMemory(filter: (JobSpec) -> Boolean, transformer: (JobSpec) -> JobSpec, singleUpdate: Boolean = false) {
|
||||
val iterator = jobs.listIterator()
|
||||
|
||||
while (iterator.hasNext()) {
|
||||
val current = iterator.next()
|
||||
|
||||
if (filter(current)) {
|
||||
val updated = transformer(current)
|
||||
iterator.set(updated)
|
||||
replaceJobInEligibleList(current, updated)
|
||||
|
||||
if (singleUpdate) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
override fun updateAllJobsToBePending() {
|
||||
jobDatabase.updateAllJobsToBePending()
|
||||
// Don't need to update jobSpecCache because all changed fields are in the min spec
|
||||
|
||||
updateJobsInMemory(
|
||||
updateCachedJobSpecs(
|
||||
filter = { it.isRunning },
|
||||
transformer = { jobSpec ->
|
||||
jobSpec.copy(
|
||||
|
@ -210,12 +214,18 @@ class FastJobStorage(private val jobDatabase: JobDatabase) : JobStorage {
|
|||
jobDatabase.updateJobs(durable)
|
||||
}
|
||||
|
||||
val updatesById: Map<String, JobSpec> = jobSpecs.associateBy { it.id }
|
||||
val updatesById: Map<String, MinimalJobSpec> = jobSpecs
|
||||
.map { it.toMinimalJobSpec() }
|
||||
.associateBy { it.id }
|
||||
|
||||
updateJobsInMemory(
|
||||
updateCachedJobSpecs(
|
||||
filter = { updatesById.containsKey(it.id) },
|
||||
transformer = { updatesById.getValue(it.id) }
|
||||
)
|
||||
|
||||
for (update in jobSpecs) {
|
||||
jobSpecCache[update.id] = update
|
||||
}
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
|
@ -228,18 +238,24 @@ class FastJobStorage(private val jobDatabase: JobDatabase) : JobStorage {
|
|||
val jobsToDelete: Set<JobSpec> = jobIds
|
||||
.mapNotNull { getJobSpec(it) }
|
||||
.toSet()
|
||||
|
||||
val durableJobIdsToDelete: List<String> = jobsToDelete
|
||||
.filterNot { it.isMemoryOnly }
|
||||
.map { it.id }
|
||||
|
||||
val minimalJobsToDelete: Set<MinimalJobSpec> = jobsToDelete
|
||||
.map { it.toMinimalJobSpec() }
|
||||
.toSet()
|
||||
|
||||
if (durableJobIdsToDelete.isNotEmpty()) {
|
||||
jobDatabase.deleteJobs(durableJobIdsToDelete)
|
||||
}
|
||||
|
||||
val deleteIds: Set<String> = jobIds.toSet()
|
||||
jobs.removeIf { deleteIds.contains(it.id) }
|
||||
eligibleJobs.removeAll(jobsToDelete)
|
||||
migrationJobs.removeAll(jobsToDelete)
|
||||
jobSpecCache.keys.removeAll(deleteIds)
|
||||
eligibleJobs.removeAll(minimalJobsToDelete)
|
||||
migrationJobs.removeAll(minimalJobsToDelete)
|
||||
|
||||
for (jobId in jobIds) {
|
||||
constraintsByJobId.remove(jobId)
|
||||
|
@ -289,8 +305,44 @@ class FastJobStorage(private val jobDatabase: JobDatabase) : JobStorage {
|
|||
return dependenciesByJobId.values.flatten()
|
||||
}
|
||||
|
||||
private fun placeJobInEligibleList(job: JobSpec) {
|
||||
var jobToPlace: JobSpec? = job
|
||||
private fun updateCachedJobSpecs(filter: (MinimalJobSpec) -> Boolean, transformer: (MinimalJobSpec) -> MinimalJobSpec, singleUpdate: Boolean = false) {
|
||||
val iterator = jobs.listIterator()
|
||||
|
||||
while (iterator.hasNext()) {
|
||||
val current = iterator.next()
|
||||
|
||||
if (filter(current)) {
|
||||
val updated = transformer(current)
|
||||
iterator.set(updated)
|
||||
replaceJobInEligibleList(current, updated)
|
||||
|
||||
jobSpecCache.remove(current.id)?.let { currentJobSpec ->
|
||||
val updatedJobSpec = currentJobSpec.copy(
|
||||
id = updated.id,
|
||||
factoryKey = updated.factoryKey,
|
||||
queueKey = updated.queueKey,
|
||||
createTime = updated.createTime,
|
||||
lastRunAttemptTime = updated.lastRunAttemptTime,
|
||||
nextBackoffInterval = updated.nextBackoffInterval,
|
||||
priority = updated.priority,
|
||||
isRunning = updated.isRunning,
|
||||
isMemoryOnly = updated.isMemoryOnly
|
||||
)
|
||||
jobSpecCache[updatedJobSpec.id] = updatedJobSpec
|
||||
}
|
||||
|
||||
if (singleUpdate) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Heart of a lot of the in-memory job management. Will ensure that we have an up-to-date list of eligible jobs in sorted order.
|
||||
*/
|
||||
private fun placeJobInEligibleList(job: MinimalJobSpec) {
|
||||
var jobToPlace: MinimalJobSpec? = job
|
||||
|
||||
if (job.queueKey != null) {
|
||||
val existingJobInQueue = mostEligibleJobForQueue[job.queueKey]
|
||||
|
@ -320,7 +372,10 @@ class FastJobStorage(private val jobDatabase: JobDatabase) : JobStorage {
|
|||
eligibleJobs += jobToPlace
|
||||
}
|
||||
|
||||
private fun replaceJobInEligibleList(current: JobSpec?, updated: JobSpec?) {
|
||||
/**
|
||||
* Replaces a job in the eligible list with an updated version of the job.
|
||||
*/
|
||||
private fun replaceJobInEligibleList(current: MinimalJobSpec?, updated: MinimalJobSpec?) {
|
||||
if (current == null || updated == null) {
|
||||
return
|
||||
}
|
||||
|
@ -372,7 +427,7 @@ class FastJobStorage(private val jobDatabase: JobDatabase) : JobStorage {
|
|||
/**
|
||||
* Whether or not the job's eligible to be run based off of it's [Job.nextBackoffInterval] and other properties.
|
||||
*/
|
||||
private fun JobSpec.hasEligibleRunTime(currentTime: Long): Boolean {
|
||||
private fun MinimalJobSpec.hasEligibleRunTime(currentTime: Long): Boolean {
|
||||
return this.lastRunAttemptTime > currentTime || (this.lastRunAttemptTime + this.nextBackoffInterval) < currentTime
|
||||
}
|
||||
|
||||
|
@ -383,12 +438,23 @@ class FastJobStorage(private val jobDatabase: JobDatabase) : JobStorage {
|
|||
.filter { it.dependsOnJobId == jobSpecId }
|
||||
}
|
||||
|
||||
private object EligibleJobComparator : Comparator<JobSpec> {
|
||||
override fun compare(o1: JobSpec, o2: JobSpec): Int {
|
||||
/**
|
||||
* Converts a [MinimalJobSpec] to a [JobSpec]. We prefer using the cache, but if it's not found, we'll hit the database.
|
||||
* We consider this a "recent access" and will cache it for future use.
|
||||
*/
|
||||
private fun MinimalJobSpec.toJobSpec(): JobSpec {
|
||||
return jobSpecCache.getOrPut(this.id) {
|
||||
jobDatabase.getJobSpec(this.id) ?: throw IllegalArgumentException("JobSpec not found for id: $id")
|
||||
}
|
||||
}
|
||||
|
||||
private object EligibleJobComparator : Comparator<MinimalJobSpec> {
|
||||
override fun compare(o1: MinimalJobSpec, o2: MinimalJobSpec): Int {
|
||||
// We want to sort by priority descending, then createTime ascending
|
||||
|
||||
// CAUTION: This is used by a TreeSet, so it must be consistent with equals.
|
||||
// If this compare function says two objects are equal, then only one will be allowed in the set!
|
||||
// This is why the last step is to compare the IDs.
|
||||
return when {
|
||||
o1.priority > o2.priority -> -1
|
||||
o1.priority < o2.priority -> 1
|
||||
|
@ -398,14 +464,22 @@ class FastJobStorage(private val jobDatabase: JobDatabase) : JobStorage {
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private data class MinimalJobSpec(
|
||||
val id: String,
|
||||
val factoryKey: String,
|
||||
val queueKey: String?,
|
||||
val createTime: Long,
|
||||
val priority: Int,
|
||||
val isRunning: Boolean,
|
||||
val isMemoryOnly: Boolean
|
||||
/**
|
||||
* Converts a [JobSpec] to a [MinimalJobSpec], which is just a matter of trimming off unnecessary properties.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
fun JobSpec.toMinimalJobSpec(): MinimalJobSpec {
|
||||
return MinimalJobSpec(
|
||||
id = this.id,
|
||||
factoryKey = this.factoryKey,
|
||||
queueKey = this.queueKey,
|
||||
createTime = this.createTime,
|
||||
lastRunAttemptTime = this.lastRunAttemptTime,
|
||||
nextBackoffInterval = this.nextBackoffInterval,
|
||||
priority = this.priority,
|
||||
isRunning = this.isRunning,
|
||||
isMemoryOnly = this.isMemoryOnly
|
||||
)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
/*
|
||||
* Copyright 2024 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.securesms.jobs
|
||||
|
||||
/**
|
||||
* A smaller version of [org.thoughtcrime.securesms.jobmanager.persistence.JobSpec] that contains on the the data we need
|
||||
* to sort and pick jobs in [FastJobStorage].
|
||||
*/
|
||||
data class MinimalJobSpec(
|
||||
val id: String,
|
||||
val factoryKey: String,
|
||||
val queueKey: String?,
|
||||
val createTime: Long,
|
||||
val lastRunAttemptTime: Long,
|
||||
val nextBackoffInterval: Long,
|
||||
val priority: Int,
|
||||
val isRunning: Boolean,
|
||||
val isMemoryOnly: Boolean
|
||||
)
|
|
@ -1,7 +1,9 @@
|
|||
package org.thoughtcrime.securesms.jobs
|
||||
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import io.mockk.verify
|
||||
import org.junit.Test
|
||||
import org.mockito.Mockito
|
||||
import org.thoughtcrime.securesms.assertIs
|
||||
import org.thoughtcrime.securesms.database.JobDatabase
|
||||
import org.thoughtcrime.securesms.jobmanager.Job
|
||||
|
@ -15,7 +17,7 @@ import java.nio.charset.Charset
|
|||
class FastJobStorageTest {
|
||||
@Test
|
||||
fun `init - all stored data available`() {
|
||||
val subject = FastJobStorage(fixedDataDatabase(DataSet1.FULL_SPECS))
|
||||
val subject = FastJobStorage(mockDatabase(DataSet1.FULL_SPECS))
|
||||
subject.init()
|
||||
|
||||
DataSet1.assertJobsMatch(subject.allJobSpecs)
|
||||
|
@ -25,7 +27,7 @@ class FastJobStorageTest {
|
|||
|
||||
@Test
|
||||
fun `init - removes circular dependencies`() {
|
||||
val subject = FastJobStorage(fixedDataDatabase(DataSetCircularDependency.FULL_SPECS))
|
||||
val subject = FastJobStorage(mockDatabase(DataSetCircularDependency.FULL_SPECS))
|
||||
subject.init()
|
||||
|
||||
DataSetCircularDependency.assertJobsMatch(subject.allJobSpecs)
|
||||
|
@ -35,27 +37,27 @@ class FastJobStorageTest {
|
|||
|
||||
@Test
|
||||
fun `insertJobs - writes to database`() {
|
||||
val database = noopDatabase()
|
||||
val database = mockDatabase()
|
||||
val subject = FastJobStorage(database)
|
||||
|
||||
subject.insertJobs(DataSet1.FULL_SPECS)
|
||||
|
||||
Mockito.verify(database).insertJobs(DataSet1.FULL_SPECS)
|
||||
verify { database.insertJobs(DataSet1.FULL_SPECS) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `insertJobs - memory-only job does not write to database`() {
|
||||
val database = noopDatabase()
|
||||
val database = mockDatabase()
|
||||
val subject = FastJobStorage(database)
|
||||
|
||||
subject.insertJobs(DataSetMemory.FULL_SPECS)
|
||||
|
||||
Mockito.verify(database, Mockito.times(0)).insertJobs(DataSet1.FULL_SPECS)
|
||||
verify(exactly = 0) { database.insertJobs(DataSet1.FULL_SPECS) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `insertJobs - data can be found`() {
|
||||
val subject = FastJobStorage(noopDatabase())
|
||||
val subject = FastJobStorage(mockDatabase())
|
||||
subject.insertJobs(DataSet1.FULL_SPECS)
|
||||
DataSet1.assertJobsMatch(subject.allJobSpecs)
|
||||
DataSet1.assertConstraintsMatch(subject.allConstraintSpecs)
|
||||
|
@ -64,7 +66,7 @@ class FastJobStorageTest {
|
|||
|
||||
@Test
|
||||
fun `insertJobs - individual job can be found`() {
|
||||
val subject = FastJobStorage(noopDatabase())
|
||||
val subject = FastJobStorage(mockDatabase())
|
||||
subject.insertJobs(DataSet1.FULL_SPECS)
|
||||
|
||||
subject.getJobSpec(DataSet1.JOB_1.id) assertIs DataSet1.JOB_1
|
||||
|
@ -73,10 +75,10 @@ class FastJobStorageTest {
|
|||
|
||||
@Test
|
||||
fun `updateAllJobsToBePending - writes to database`() {
|
||||
val database = noopDatabase()
|
||||
val database = mockDatabase()
|
||||
val subject = FastJobStorage(database)
|
||||
subject.updateAllJobsToBePending()
|
||||
Mockito.verify(database).updateAllJobsToBePending()
|
||||
verify { database.updateAllJobsToBePending() }
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -84,7 +86,7 @@ class FastJobStorageTest {
|
|||
val fullSpec1 = FullSpec(jobSpec(id = "1", factoryKey = "f1", isRunning = true), emptyList(), emptyList())
|
||||
val fullSpec2 = FullSpec(jobSpec(id = "2", factoryKey = "f2", isRunning = true), emptyList(), emptyList())
|
||||
|
||||
val subject = FastJobStorage(fixedDataDatabase(listOf(fullSpec1, fullSpec2)))
|
||||
val subject = FastJobStorage(mockDatabase(listOf(fullSpec1, fullSpec2)))
|
||||
subject.init()
|
||||
subject.updateAllJobsToBePending()
|
||||
|
||||
|
@ -94,26 +96,26 @@ class FastJobStorageTest {
|
|||
|
||||
@Test
|
||||
fun `updateJobs - writes to database`() {
|
||||
val database = fixedDataDatabase(DataSet1.FULL_SPECS)
|
||||
val database = mockDatabase(DataSet1.FULL_SPECS)
|
||||
val jobs = listOf(jobSpec(id = "id1", factoryKey = "f1"))
|
||||
|
||||
val subject = FastJobStorage(database)
|
||||
subject.init()
|
||||
subject.updateJobs(jobs)
|
||||
|
||||
Mockito.verify(database).updateJobs(jobs)
|
||||
verify { database.updateJobs(jobs) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `updateJobs - memory-only job does not write to database`() {
|
||||
val database = fixedDataDatabase(DataSetMemory.FULL_SPECS)
|
||||
val database = mockDatabase(DataSetMemory.FULL_SPECS)
|
||||
val jobs = listOf(jobSpec(id = "id1", factoryKey = "f1"))
|
||||
|
||||
val subject = FastJobStorage(database)
|
||||
subject.init()
|
||||
subject.updateJobs(jobs)
|
||||
|
||||
Mockito.verify(database, Mockito.times(0)).updateJobs(jobs)
|
||||
verify(exactly = 0) { database.updateJobs(jobs) }
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -153,7 +155,7 @@ class FastJobStorageTest {
|
|||
isMemoryOnly = false
|
||||
)
|
||||
|
||||
val subject = FastJobStorage(fixedDataDatabase(listOf(fullSpec1, fullSpec2, fullSpec3)))
|
||||
val subject = FastJobStorage(mockDatabase(listOf(fullSpec1, fullSpec2, fullSpec3)))
|
||||
subject.init()
|
||||
subject.updateJobs(listOf(update1, update2))
|
||||
|
||||
|
@ -164,19 +166,19 @@ class FastJobStorageTest {
|
|||
|
||||
@Test
|
||||
fun `markJobAsRunning - writes to database`() {
|
||||
val database = fixedDataDatabase(DataSet1.FULL_SPECS)
|
||||
val database = mockDatabase(DataSet1.FULL_SPECS)
|
||||
|
||||
val subject = FastJobStorage(database)
|
||||
subject.init()
|
||||
|
||||
subject.markJobAsRunning(id = "id1", currentTime = 42)
|
||||
|
||||
Mockito.verify(database).markJobAsRunning(id = "id1", currentTime = 42)
|
||||
verify { database.markJobAsRunning(id = "id1", currentTime = 42) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `markJobAsRunning - state updated`() {
|
||||
val subject = FastJobStorage(fixedDataDatabase(DataSet1.FULL_SPECS))
|
||||
val subject = FastJobStorage(mockDatabase(DataSet1.FULL_SPECS))
|
||||
subject.init()
|
||||
|
||||
subject.markJobAsRunning(id = DataSet1.JOB_1.id, currentTime = 42)
|
||||
|
@ -187,7 +189,7 @@ class FastJobStorageTest {
|
|||
|
||||
@Test
|
||||
fun `updateJobAfterRetry - writes to database`() {
|
||||
val database = fixedDataDatabase(DataSet1.FULL_SPECS)
|
||||
val database = mockDatabase(DataSet1.FULL_SPECS)
|
||||
|
||||
val subject = FastJobStorage(database)
|
||||
subject.init()
|
||||
|
@ -200,12 +202,12 @@ class FastJobStorageTest {
|
|||
serializedData = "a".toByteArray()
|
||||
)
|
||||
|
||||
Mockito.verify(database).updateJobAfterRetry(id = "id1", currentTime = 0, runAttempt = 1, nextBackoffInterval = 10, serializedData = "a".toByteArray())
|
||||
verify { database.updateJobAfterRetry(id = "id1", currentTime = 0, runAttempt = 1, nextBackoffInterval = 10, serializedData = "a".toByteArray()) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `updateJobAfterRetry - memory-only job does not write to database`() {
|
||||
val database = fixedDataDatabase(DataSetMemory.FULL_SPECS)
|
||||
val database = mockDatabase(DataSetMemory.FULL_SPECS)
|
||||
|
||||
val subject = FastJobStorage(database)
|
||||
subject.init()
|
||||
|
@ -218,14 +220,14 @@ class FastJobStorageTest {
|
|||
serializedData = "a".toByteArray()
|
||||
)
|
||||
|
||||
Mockito.verify(database, Mockito.times(0)).updateJobAfterRetry(id = "id1", currentTime = 0, runAttempt = 1, nextBackoffInterval = 10, serializedData = "a".toByteArray())
|
||||
verify(exactly = 0) { database.updateJobAfterRetry(id = "id1", currentTime = 0, runAttempt = 1, nextBackoffInterval = 10, serializedData = "a".toByteArray()) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `updateJobAfterRetry - state updated`() {
|
||||
val fullSpec = FullSpec(jobSpec(id = "1", factoryKey = "f1", isRunning = true), emptyList(), emptyList())
|
||||
|
||||
val subject = FastJobStorage(fixedDataDatabase(listOf(fullSpec)))
|
||||
val subject = FastJobStorage(mockDatabase(listOf(fullSpec)))
|
||||
subject.init()
|
||||
|
||||
subject.updateJobAfterRetry(
|
||||
|
@ -250,7 +252,7 @@ class FastJobStorageTest {
|
|||
val fullSpec1 = FullSpec(jobSpec(id = "1", factoryKey = "f1", queueKey = "q", isRunning = true), emptyList(), emptyList())
|
||||
val fullSpec2 = FullSpec(jobSpec(id = "2", factoryKey = "f2", queueKey = "q"), emptyList(), emptyList())
|
||||
|
||||
val subject = FastJobStorage(fixedDataDatabase(listOf(fullSpec1, fullSpec2)))
|
||||
val subject = FastJobStorage(mockDatabase(listOf(fullSpec1, fullSpec2)))
|
||||
subject.init()
|
||||
|
||||
subject.getPendingJobsWithNoDependenciesInCreatedOrder(1).size assertIs 0
|
||||
|
@ -260,7 +262,7 @@ class FastJobStorageTest {
|
|||
fun `getPendingJobsWithNoDependenciesInCreatedOrder - none when all jobs are running`() {
|
||||
val fullSpec = FullSpec(jobSpec(id = "1", factoryKey = "f1", queueKey = "q", isRunning = true), emptyList(), emptyList())
|
||||
|
||||
val subject = FastJobStorage(fixedDataDatabase(listOf(fullSpec)))
|
||||
val subject = FastJobStorage(mockDatabase(listOf(fullSpec)))
|
||||
subject.init()
|
||||
|
||||
subject.getPendingJobsWithNoDependenciesInCreatedOrder(10).size assertIs 0
|
||||
|
@ -271,7 +273,7 @@ class FastJobStorageTest {
|
|||
val currentTime = 0L
|
||||
val fullSpec = FullSpec(jobSpec(id = "1", factoryKey = "f1", queueKey = "q", lastRunAttemptTime = 0, nextBackoffInterval = 10), emptyList(), emptyList())
|
||||
|
||||
val subject = FastJobStorage(fixedDataDatabase(listOf(fullSpec)))
|
||||
val subject = FastJobStorage(mockDatabase(listOf(fullSpec)))
|
||||
subject.init()
|
||||
|
||||
subject.getPendingJobsWithNoDependenciesInCreatedOrder(currentTime).size assertIs 0
|
||||
|
@ -282,7 +284,7 @@ class FastJobStorageTest {
|
|||
val fullSpec1 = FullSpec(jobSpec(id = "1", factoryKey = "f1", isRunning = true), emptyList(), emptyList())
|
||||
val fullSpec2 = FullSpec(jobSpec(id = "2", factoryKey = "f2"), emptyList(), listOf(DependencySpec("2", "1", false)))
|
||||
|
||||
val subject = FastJobStorage(fixedDataDatabase(listOf(fullSpec1, fullSpec2)))
|
||||
val subject = FastJobStorage(mockDatabase(listOf(fullSpec1, fullSpec2)))
|
||||
subject.init()
|
||||
|
||||
subject.getPendingJobsWithNoDependenciesInCreatedOrder(0).size assertIs 0
|
||||
|
@ -292,7 +294,7 @@ class FastJobStorageTest {
|
|||
fun `getPendingJobsWithNoDependenciesInCreatedOrder - single eligible job`() {
|
||||
val fullSpec = FullSpec(jobSpec(id = "1", factoryKey = "f1", queueKey = "q"), emptyList(), emptyList())
|
||||
|
||||
val subject = FastJobStorage(fixedDataDatabase(listOf(fullSpec)))
|
||||
val subject = FastJobStorage(mockDatabase(listOf(fullSpec)))
|
||||
subject.init()
|
||||
|
||||
subject.getPendingJobsWithNoDependenciesInCreatedOrder(10).size assertIs 1
|
||||
|
@ -303,7 +305,7 @@ class FastJobStorageTest {
|
|||
val fullSpec1 = FullSpec(jobSpec(id = "1", factoryKey = "f1"), emptyList(), emptyList())
|
||||
val fullSpec2 = FullSpec(jobSpec(id = "2", factoryKey = "f2"), emptyList(), emptyList())
|
||||
|
||||
val subject = FastJobStorage(fixedDataDatabase(listOf(fullSpec1, fullSpec2)))
|
||||
val subject = FastJobStorage(mockDatabase(listOf(fullSpec1, fullSpec2)))
|
||||
subject.init()
|
||||
|
||||
subject.getPendingJobsWithNoDependenciesInCreatedOrder(10).size assertIs 2
|
||||
|
@ -314,7 +316,7 @@ class FastJobStorageTest {
|
|||
val fullSpec1 = FullSpec(jobSpec(id = "1", factoryKey = "f1", isRunning = true), emptyList(), emptyList())
|
||||
val fullSpec2 = FullSpec(jobSpec(id = "2", factoryKey = "f2"), emptyList(), emptyList())
|
||||
|
||||
val subject = FastJobStorage(fixedDataDatabase(listOf(fullSpec1, fullSpec2)))
|
||||
val subject = FastJobStorage(mockDatabase(listOf(fullSpec1, fullSpec2)))
|
||||
subject.init()
|
||||
|
||||
val jobs = subject.getPendingJobsWithNoDependenciesInCreatedOrder(10)
|
||||
|
@ -327,7 +329,7 @@ class FastJobStorageTest {
|
|||
val fullSpec1 = FullSpec(jobSpec(id = "1", factoryKey = "f1", queueKey = "q"), emptyList(), emptyList())
|
||||
val fullSpec2 = FullSpec(jobSpec(id = "2", factoryKey = "f2", queueKey = "q"), emptyList(), emptyList())
|
||||
|
||||
val subject = FastJobStorage(fixedDataDatabase(listOf(fullSpec1, fullSpec2)))
|
||||
val subject = FastJobStorage(mockDatabase(listOf(fullSpec1, fullSpec2)))
|
||||
subject.init()
|
||||
|
||||
val jobs = subject.getPendingJobsWithNoDependenciesInCreatedOrder(10)
|
||||
|
@ -341,7 +343,7 @@ class FastJobStorageTest {
|
|||
val fullSpec2 = FullSpec(jobSpec(id = "2", factoryKey = "f2", queueKey = "q", createTime = 2, priority = Job.Parameters.PRIORITY_HIGH), emptyList(), emptyList())
|
||||
val fullSpec3 = FullSpec(jobSpec(id = "3", factoryKey = "f3", queueKey = "q", createTime = 3, priority = Job.Parameters.PRIORITY_DEFAULT), emptyList(), emptyList())
|
||||
|
||||
val subject = FastJobStorage(fixedDataDatabase(listOf(fullSpec1, fullSpec2, fullSpec3)))
|
||||
val subject = FastJobStorage(mockDatabase(listOf(fullSpec1, fullSpec2, fullSpec3)))
|
||||
subject.init()
|
||||
|
||||
val jobs = subject.getPendingJobsWithNoDependenciesInCreatedOrder(10)
|
||||
|
@ -361,7 +363,7 @@ class FastJobStorageTest {
|
|||
val fullSpec8 = FullSpec(jobSpec(id = "8", factoryKey = "f8", queueKey = null, createTime = 8, priority = Job.Parameters.PRIORITY_LOW), emptyList(), emptyList())
|
||||
val fullSpec9 = FullSpec(jobSpec(id = "9", factoryKey = "f9", queueKey = null, createTime = 9, priority = Job.Parameters.PRIORITY_DEFAULT), emptyList(), emptyList())
|
||||
|
||||
val subject = FastJobStorage(fixedDataDatabase(listOf(fullSpec1, fullSpec2, fullSpec3, fullSpec4, fullSpec5, fullSpec6, fullSpec7, fullSpec8, fullSpec9)))
|
||||
val subject = FastJobStorage(mockDatabase(listOf(fullSpec1, fullSpec2, fullSpec3, fullSpec4, fullSpec5, fullSpec6, fullSpec7, fullSpec8, fullSpec9)))
|
||||
subject.init()
|
||||
|
||||
val jobs = subject.getPendingJobsWithNoDependenciesInCreatedOrder(10)
|
||||
|
@ -380,7 +382,7 @@ class FastJobStorageTest {
|
|||
|
||||
val fullSpec1 = FullSpec(jobSpec(id = "1", factoryKey = "f1", queueKey = "q", lastRunAttemptTime = 100, nextBackoffInterval = 5), emptyList(), emptyList())
|
||||
|
||||
val subject = FastJobStorage(fixedDataDatabase(listOf(fullSpec1)))
|
||||
val subject = FastJobStorage(mockDatabase(listOf(fullSpec1)))
|
||||
subject.init()
|
||||
|
||||
val jobs = subject.getPendingJobsWithNoDependenciesInCreatedOrder(currentTime)
|
||||
|
@ -393,7 +395,7 @@ class FastJobStorageTest {
|
|||
val plainSpec = FullSpec(jobSpec(id = "1", factoryKey = "f1", queueKey = "q", createTime = 0), emptyList(), emptyList())
|
||||
val migrationSpec = FullSpec(jobSpec(id = "2", factoryKey = "f2", queueKey = Job.Parameters.MIGRATION_QUEUE_KEY, createTime = 5), emptyList(), emptyList())
|
||||
|
||||
val subject = FastJobStorage(fixedDataDatabase(listOf(plainSpec, migrationSpec)))
|
||||
val subject = FastJobStorage(mockDatabase(listOf(plainSpec, migrationSpec)))
|
||||
subject.init()
|
||||
|
||||
val jobs = subject.getPendingJobsWithNoDependenciesInCreatedOrder(10)
|
||||
|
@ -406,7 +408,7 @@ class FastJobStorageTest {
|
|||
val plainSpec = FullSpec(jobSpec(id = "1", factoryKey = "f1", queueKey = "q", createTime = 0), emptyList(), emptyList())
|
||||
val migrationSpec = FullSpec(jobSpec(id = "2", factoryKey = "f2", queueKey = Job.Parameters.MIGRATION_QUEUE_KEY, createTime = 5, isRunning = true), emptyList(), emptyList())
|
||||
|
||||
val subject = FastJobStorage(fixedDataDatabase(listOf(plainSpec, migrationSpec)))
|
||||
val subject = FastJobStorage(mockDatabase(listOf(plainSpec, migrationSpec)))
|
||||
subject.init()
|
||||
|
||||
val jobs = subject.getPendingJobsWithNoDependenciesInCreatedOrder(10)
|
||||
|
@ -418,7 +420,7 @@ class FastJobStorageTest {
|
|||
val migrationSpec1 = FullSpec(jobSpec(id = "1", factoryKey = "f1", queueKey = Job.Parameters.MIGRATION_QUEUE_KEY, createTime = 0, isRunning = true), emptyList(), emptyList())
|
||||
val migrationSpec2 = FullSpec(jobSpec(id = "2", factoryKey = "f2", queueKey = Job.Parameters.MIGRATION_QUEUE_KEY, createTime = 5), emptyList(), emptyList())
|
||||
|
||||
val subject = FastJobStorage(fixedDataDatabase(listOf(migrationSpec1, migrationSpec2)))
|
||||
val subject = FastJobStorage(mockDatabase(listOf(migrationSpec1, migrationSpec2)))
|
||||
subject.init()
|
||||
|
||||
val jobs = subject.getPendingJobsWithNoDependenciesInCreatedOrder(10)
|
||||
|
@ -430,7 +432,7 @@ class FastJobStorageTest {
|
|||
val migrationSpec1 = FullSpec(jobSpec(id = "1", factoryKey = "f1", queueKey = Job.Parameters.MIGRATION_QUEUE_KEY, createTime = 0), emptyList(), emptyList())
|
||||
val migrationSpec2 = FullSpec(jobSpec(id = "2", factoryKey = "f2", queueKey = Job.Parameters.MIGRATION_QUEUE_KEY, createTime = 5), emptyList(), emptyList())
|
||||
|
||||
val subject = FastJobStorage(fixedDataDatabase(listOf(migrationSpec1, migrationSpec2)))
|
||||
val subject = FastJobStorage(mockDatabase(listOf(migrationSpec1, migrationSpec2)))
|
||||
subject.init()
|
||||
|
||||
val jobs = subject.getPendingJobsWithNoDependenciesInCreatedOrder(10)
|
||||
|
@ -445,7 +447,7 @@ class FastJobStorageTest {
|
|||
val migrationSpec1 = FullSpec(jobSpec(id = "1", factoryKey = "f1", queueKey = Job.Parameters.MIGRATION_QUEUE_KEY, createTime = 0, lastRunAttemptTime = 0, nextBackoffInterval = 999), emptyList(), emptyList())
|
||||
val migrationSpec2 = FullSpec(jobSpec(id = "2", factoryKey = "f2", queueKey = Job.Parameters.MIGRATION_QUEUE_KEY, createTime = 5, lastRunAttemptTime = 0, nextBackoffInterval = 0), emptyList(), emptyList())
|
||||
|
||||
val subject = FastJobStorage(fixedDataDatabase(listOf(migrationSpec1, migrationSpec2)))
|
||||
val subject = FastJobStorage(mockDatabase(listOf(migrationSpec1, migrationSpec2)))
|
||||
subject.init()
|
||||
|
||||
val jobs = subject.getPendingJobsWithNoDependenciesInCreatedOrder(currentTime)
|
||||
|
@ -454,7 +456,7 @@ class FastJobStorageTest {
|
|||
|
||||
@Test
|
||||
fun `getPendingJobsWithNoDependenciesInCreatedOrder - after deleted, no longer is in eligible list`() {
|
||||
val subject = FastJobStorage(fixedDataDatabase(DataSet1.FULL_SPECS))
|
||||
val subject = FastJobStorage(mockDatabase(DataSet1.FULL_SPECS))
|
||||
subject.init()
|
||||
|
||||
var jobs = subject.getPendingJobsWithNoDependenciesInCreatedOrder(100)
|
||||
|
@ -468,7 +470,7 @@ class FastJobStorageTest {
|
|||
|
||||
@Test
|
||||
fun `getPendingJobsWithNoDependenciesInCreatedOrder - after marked running, no longer is in eligible list`() {
|
||||
val subject = FastJobStorage(fixedDataDatabase(DataSet1.FULL_SPECS))
|
||||
val subject = FastJobStorage(mockDatabase(DataSet1.FULL_SPECS))
|
||||
subject.init()
|
||||
|
||||
var jobs = subject.getPendingJobsWithNoDependenciesInCreatedOrder(100)
|
||||
|
@ -482,7 +484,7 @@ class FastJobStorageTest {
|
|||
|
||||
@Test
|
||||
fun `getPendingJobsWithNoDependenciesInCreatedOrder - after updateJobAfterRetry to be invalid, no longer is in eligible list`() {
|
||||
val subject = FastJobStorage(fixedDataDatabase(DataSet1.FULL_SPECS))
|
||||
val subject = FastJobStorage(mockDatabase(DataSet1.FULL_SPECS))
|
||||
subject.init()
|
||||
|
||||
var jobs = subject.getPendingJobsWithNoDependenciesInCreatedOrder(100)
|
||||
|
@ -496,7 +498,7 @@ class FastJobStorageTest {
|
|||
|
||||
@Test
|
||||
fun `getPendingJobsWithNoDependenciesInCreatedOrder - after invalid then marked pending, is in eligible list`() {
|
||||
val subject = FastJobStorage(fixedDataDatabase(DataSet1.FULL_SPECS))
|
||||
val subject = FastJobStorage(mockDatabase(DataSet1.FULL_SPECS))
|
||||
subject.init()
|
||||
|
||||
subject.markJobAsRunning("id1", 1)
|
||||
|
@ -511,7 +513,7 @@ class FastJobStorageTest {
|
|||
|
||||
@Test
|
||||
fun `getPendingJobsWithNoDependenciesInCreatedOrder - after updateJobs to be invalid, no longer is in eligible list`() {
|
||||
val subject = FastJobStorage(fixedDataDatabase(DataSet1.FULL_SPECS))
|
||||
val subject = FastJobStorage(mockDatabase(DataSet1.FULL_SPECS))
|
||||
subject.init()
|
||||
|
||||
var jobs = subject.getPendingJobsWithNoDependenciesInCreatedOrder(100)
|
||||
|
@ -525,7 +527,7 @@ class FastJobStorageTest {
|
|||
|
||||
@Test
|
||||
fun `getPendingJobsWithNoDependenciesInCreatedOrder - newly-inserted higher-priority job in queue replaces old`() {
|
||||
val subject = FastJobStorage(fixedDataDatabase(DataSet1.FULL_SPECS))
|
||||
val subject = FastJobStorage(mockDatabase(DataSet1.FULL_SPECS))
|
||||
subject.init()
|
||||
|
||||
var jobs = subject.getPendingJobsWithNoDependenciesInCreatedOrder(100)
|
||||
|
@ -541,7 +543,7 @@ class FastJobStorageTest {
|
|||
|
||||
@Test
|
||||
fun `getPendingJobsWithNoDependenciesInCreatedOrder - updating job to have a higher priority replaces lower priority in queue`() {
|
||||
val subject = FastJobStorage(fixedDataDatabase(DataSet1.FULL_SPECS))
|
||||
val subject = FastJobStorage(mockDatabase(DataSet1.FULL_SPECS))
|
||||
subject.init()
|
||||
|
||||
val lowerPriorityJob = DataSet1.JOB_1.copy(id = "id-bigboi", priority = Job.Parameters.PRIORITY_LOW)
|
||||
|
@ -561,7 +563,7 @@ class FastJobStorageTest {
|
|||
|
||||
@Test
|
||||
fun `getPendingJobsWithNoDependenciesInCreatedOrder - updating job to have an older createTime replaces newer in queue`() {
|
||||
val subject = FastJobStorage(fixedDataDatabase(DataSet1.FULL_SPECS))
|
||||
val subject = FastJobStorage(mockDatabase(DataSet1.FULL_SPECS))
|
||||
subject.init()
|
||||
|
||||
val newerJob = DataSet1.JOB_1.copy(id = "id-bigboi", createTime = 1000)
|
||||
|
@ -581,7 +583,7 @@ class FastJobStorageTest {
|
|||
|
||||
@Test
|
||||
fun `deleteJobs - writes to database`() {
|
||||
val database = fixedDataDatabase(DataSet1.FULL_SPECS)
|
||||
val database = mockDatabase(DataSet1.FULL_SPECS)
|
||||
val ids: List<String> = listOf("id1", "id2")
|
||||
|
||||
val subject = FastJobStorage(database)
|
||||
|
@ -589,12 +591,12 @@ class FastJobStorageTest {
|
|||
|
||||
subject.deleteJobs(ids)
|
||||
|
||||
Mockito.verify(database).deleteJobs(ids)
|
||||
verify { database.deleteJobs(ids) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `deleteJobs - memory-only job does not write to database`() {
|
||||
val database = fixedDataDatabase(DataSetMemory.FULL_SPECS)
|
||||
val database = mockDatabase(DataSetMemory.FULL_SPECS)
|
||||
val ids = listOf("id1")
|
||||
|
||||
val subject = FastJobStorage(database)
|
||||
|
@ -602,12 +604,12 @@ class FastJobStorageTest {
|
|||
|
||||
subject.deleteJobs(ids)
|
||||
|
||||
Mockito.verify(database, Mockito.times(0)).deleteJobs(ids)
|
||||
verify(exactly = 0) { database.deleteJobs(ids) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `deleteJobs - deletes all relevant pieces`() {
|
||||
val subject = FastJobStorage(fixedDataDatabase(DataSet1.FULL_SPECS))
|
||||
val subject = FastJobStorage(mockDatabase(DataSet1.FULL_SPECS))
|
||||
subject.init()
|
||||
|
||||
subject.deleteJobs(listOf("id1"))
|
||||
|
@ -622,11 +624,12 @@ class FastJobStorageTest {
|
|||
constraints.size assertIs 1
|
||||
constraints[0] assertIs DataSet1.CONSTRAINT_2
|
||||
dependencies.size assertIs 1
|
||||
subject.getJobSpec("id1") assertIs null
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `getDependencySpecsThatDependOnJob - start of chain`() {
|
||||
val subject = FastJobStorage(fixedDataDatabase(DataSet1.FULL_SPECS))
|
||||
val subject = FastJobStorage(mockDatabase(DataSet1.FULL_SPECS))
|
||||
subject.init()
|
||||
|
||||
val result = subject.getDependencySpecsThatDependOnJob("id1")
|
||||
|
@ -637,7 +640,7 @@ class FastJobStorageTest {
|
|||
|
||||
@Test
|
||||
fun `getDependencySpecsThatDependOnJob - mid-chain`() {
|
||||
val subject = FastJobStorage(fixedDataDatabase(DataSet1.FULL_SPECS))
|
||||
val subject = FastJobStorage(mockDatabase(DataSet1.FULL_SPECS))
|
||||
subject.init()
|
||||
|
||||
val result = subject.getDependencySpecsThatDependOnJob("id2")
|
||||
|
@ -647,7 +650,7 @@ class FastJobStorageTest {
|
|||
|
||||
@Test
|
||||
fun `getDependencySpecsThatDependOnJob - end of chain`() {
|
||||
val subject = FastJobStorage(fixedDataDatabase(DataSet1.FULL_SPECS))
|
||||
val subject = FastJobStorage(mockDatabase(DataSet1.FULL_SPECS))
|
||||
subject.init()
|
||||
|
||||
val result = subject.getDependencySpecsThatDependOnJob("id3")
|
||||
|
@ -656,7 +659,7 @@ class FastJobStorageTest {
|
|||
|
||||
@Test
|
||||
fun `getJobsInQueue - empty`() {
|
||||
val subject = FastJobStorage(fixedDataDatabase(DataSet1.FULL_SPECS))
|
||||
val subject = FastJobStorage(mockDatabase(DataSet1.FULL_SPECS))
|
||||
subject.init()
|
||||
|
||||
val result = subject.getJobsInQueue("x")
|
||||
|
@ -665,7 +668,7 @@ class FastJobStorageTest {
|
|||
|
||||
@Test
|
||||
fun `getJobsInQueue - single job`() {
|
||||
val subject = FastJobStorage(fixedDataDatabase(DataSet1.FULL_SPECS))
|
||||
val subject = FastJobStorage(mockDatabase(DataSet1.FULL_SPECS))
|
||||
subject.init()
|
||||
|
||||
val result = subject.getJobsInQueue("q1")
|
||||
|
@ -675,7 +678,7 @@ class FastJobStorageTest {
|
|||
|
||||
@Test
|
||||
fun `getJobCountForFactory - general`() {
|
||||
val subject = FastJobStorage(fixedDataDatabase(DataSet1.FULL_SPECS))
|
||||
val subject = FastJobStorage(mockDatabase(DataSet1.FULL_SPECS))
|
||||
subject.init()
|
||||
|
||||
subject.getJobCountForFactory("f1") assertIs 1
|
||||
|
@ -684,7 +687,7 @@ class FastJobStorageTest {
|
|||
|
||||
@Test
|
||||
fun `getJobCountForFactoryAndQueue - general`() {
|
||||
val subject = FastJobStorage(fixedDataDatabase(DataSet1.FULL_SPECS))
|
||||
val subject = FastJobStorage(mockDatabase(DataSet1.FULL_SPECS))
|
||||
subject.init()
|
||||
|
||||
subject.getJobCountForFactoryAndQueue("f1", "q1") assertIs 1
|
||||
|
@ -694,7 +697,7 @@ class FastJobStorageTest {
|
|||
|
||||
@Test
|
||||
fun `areQueuesEmpty - all non-empty`() {
|
||||
val subject = FastJobStorage(fixedDataDatabase(DataSet1.FULL_SPECS))
|
||||
val subject = FastJobStorage(mockDatabase(DataSet1.FULL_SPECS))
|
||||
subject.init()
|
||||
|
||||
subject.areQueuesEmpty(TestHelpers.setOf("q1")) assertIs false
|
||||
|
@ -703,7 +706,7 @@ class FastJobStorageTest {
|
|||
|
||||
@Test
|
||||
fun `areQueuesEmpty - mixed empty`() {
|
||||
val subject = FastJobStorage(fixedDataDatabase(DataSet1.FULL_SPECS))
|
||||
val subject = FastJobStorage(mockDatabase(DataSet1.FULL_SPECS))
|
||||
subject.init()
|
||||
|
||||
subject.areQueuesEmpty(TestHelpers.setOf("q1", "q5")) assertIs false
|
||||
|
@ -711,27 +714,78 @@ class FastJobStorageTest {
|
|||
|
||||
@Test
|
||||
fun `areQueuesEmpty - queue does not exist`() {
|
||||
val subject = FastJobStorage(fixedDataDatabase(DataSet1.FULL_SPECS))
|
||||
val subject = FastJobStorage(mockDatabase(DataSet1.FULL_SPECS))
|
||||
subject.init()
|
||||
|
||||
subject.areQueuesEmpty(TestHelpers.setOf("q4")) assertIs true
|
||||
subject.areQueuesEmpty(TestHelpers.setOf("q4", "q5")) assertIs true
|
||||
}
|
||||
|
||||
private fun noopDatabase(): JobDatabase {
|
||||
val database = Mockito.mock(JobDatabase::class.java)
|
||||
Mockito.`when`(database.getAllJobSpecs()).thenReturn(emptyList())
|
||||
Mockito.`when`(database.getAllConstraintSpecs()).thenReturn(emptyList())
|
||||
Mockito.`when`(database.getAllDependencySpecs()).thenReturn(emptyList())
|
||||
return database
|
||||
}
|
||||
private fun mockDatabase(fullSpecs: List<FullSpec> = emptyList()): JobDatabase {
|
||||
val jobs = fullSpecs.map { it.jobSpec }.toMutableList()
|
||||
val constraints = fullSpecs.map { it.constraintSpecs }.flatten().toMutableList()
|
||||
val dependencies = fullSpecs.map { it.dependencySpecs }.flatten().toMutableList()
|
||||
|
||||
private fun fixedDataDatabase(fullSpecs: List<FullSpec>): JobDatabase {
|
||||
val database = Mockito.mock(JobDatabase::class.java)
|
||||
Mockito.`when`(database.getAllJobSpecs()).thenReturn(fullSpecs.map { it.jobSpec })
|
||||
Mockito.`when`(database.getAllConstraintSpecs()).thenReturn(fullSpecs.map { it.constraintSpecs }.flatten())
|
||||
Mockito.`when`(database.getAllDependencySpecs()).thenReturn(fullSpecs.map { it.dependencySpecs }.flatten())
|
||||
return database
|
||||
val mock = mockk<JobDatabase>(relaxed = true)
|
||||
every { mock.getAllJobSpecs() } returns jobs
|
||||
every { mock.getAllMinimalJobSpecs() } returns jobs.map { it.toMinimalJobSpec() }
|
||||
every { mock.getOldestJobSpecs(any()) } answers { jobs.sortedBy { it.createTime }.take(firstArg()) }
|
||||
every { mock.getAllConstraintSpecs() } returns constraints
|
||||
every { mock.getAllDependencySpecs() } returns dependencies
|
||||
every { mock.getJobSpec(any()) } answers { jobs.first { it.id == firstArg() } }
|
||||
every { mock.insertJobs(any()) } answers {
|
||||
val inserts: List<FullSpec> = firstArg()
|
||||
for (insert in inserts) {
|
||||
jobs += insert.jobSpec
|
||||
constraints += insert.constraintSpecs
|
||||
dependencies += insert.dependencySpecs
|
||||
}
|
||||
}
|
||||
every { mock.deleteJobs(any()) } answers {
|
||||
val ids: List<String> = firstArg()
|
||||
jobs.removeIf { ids.contains(it.id) }
|
||||
constraints.removeIf { ids.contains(it.jobSpecId) }
|
||||
dependencies.removeIf { ids.contains(it.jobId) || ids.contains(it.dependsOnJobId) }
|
||||
}
|
||||
every { mock.updateJobs(any()) } answers {
|
||||
val updates: List<JobSpec> = firstArg()
|
||||
for (update in updates) {
|
||||
jobs.removeIf { it.id == update.id }
|
||||
jobs += update
|
||||
}
|
||||
}
|
||||
every { mock.updateAllJobsToBePending() } answers {
|
||||
val iterator = jobs.listIterator()
|
||||
while (iterator.hasNext()) {
|
||||
val job = iterator.next()
|
||||
iterator.set(job.copy(isRunning = false))
|
||||
}
|
||||
}
|
||||
every { mock.updateJobAfterRetry(any(), any(), any(), any(), any()) } answers {
|
||||
val id = args[0] as String
|
||||
val currentTime = args[1] as Long
|
||||
val runAttempt = args[2] as Int
|
||||
val nextBackoffInterval = args[3] as Long
|
||||
val serializedData = args[4] as ByteArray?
|
||||
|
||||
val iterator = jobs.listIterator()
|
||||
while (iterator.hasNext()) {
|
||||
val job = iterator.next()
|
||||
if (job.id == id) {
|
||||
iterator.set(
|
||||
job.copy(
|
||||
isRunning = false,
|
||||
runAttempt = runAttempt,
|
||||
lastRunAttemptTime = currentTime,
|
||||
nextBackoffInterval = nextBackoffInterval,
|
||||
serializedData = serializedData
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return mock
|
||||
}
|
||||
|
||||
private fun jobSpec(
|
||||
|
|
|
@ -202,6 +202,10 @@ class SelectBuilderPart2(
|
|||
return SelectBuilderPart3(db, columns, tableName, where, whereArgs)
|
||||
}
|
||||
|
||||
fun orderBy(orderBy: String): SelectBuilderPart4a {
|
||||
return SelectBuilderPart4a(db, columns, tableName, "", arrayOf(), orderBy)
|
||||
}
|
||||
|
||||
fun run(): Cursor {
|
||||
return db.query(
|
||||
SupportSQLiteQueryBuilder
|
||||
|
|
Loading…
Add table
Reference in a new issue