diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobmanager/persistence/ConstraintSpec.java b/app/src/main/java/org/thoughtcrime/securesms/jobmanager/persistence/ConstraintSpec.java deleted file mode 100644 index 1339a25f2c..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/jobmanager/persistence/ConstraintSpec.java +++ /dev/null @@ -1,51 +0,0 @@ -package org.thoughtcrime.securesms.jobmanager.persistence; - -import androidx.annotation.NonNull; - -import java.util.Locale; -import java.util.Objects; - -public final class ConstraintSpec { - - private final String jobSpecId; - private final String factoryKey; - private final boolean memoryOnly; - - public ConstraintSpec(@NonNull String jobSpecId, @NonNull String factoryKey, boolean memoryOnly) { - this.jobSpecId = jobSpecId; - this.factoryKey = factoryKey; - this.memoryOnly = memoryOnly; - } - - public String getJobSpecId() { - return jobSpecId; - } - - public String getFactoryKey() { - return factoryKey; - } - - public boolean isMemoryOnly() { - return memoryOnly; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - ConstraintSpec that = (ConstraintSpec) o; - return Objects.equals(jobSpecId, that.jobSpecId) && - Objects.equals(factoryKey, that.factoryKey) && - memoryOnly == that.memoryOnly; - } - - @Override - public int hashCode() { - return Objects.hash(jobSpecId, factoryKey, memoryOnly); - } - - @Override - public @NonNull String toString() { - return String.format(Locale.US, "jobSpecId: JOB::%s | factoryKey: %s | memoryOnly: %b", jobSpecId, factoryKey, memoryOnly); - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobmanager/persistence/ConstraintSpec.kt b/app/src/main/java/org/thoughtcrime/securesms/jobmanager/persistence/ConstraintSpec.kt new file mode 100644 index 0000000000..cdcccfb890 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/jobmanager/persistence/ConstraintSpec.kt @@ -0,0 +1,11 @@ +package org.thoughtcrime.securesms.jobmanager.persistence + +data class ConstraintSpec( + val jobSpecId: String, + val factoryKey: String, + val isMemoryOnly: Boolean +) { + override fun toString(): String { + return "jobSpecId: JOB::$jobSpecId | factoryKey: $factoryKey | memoryOnly: $isMemoryOnly" + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobmanager/persistence/DependencySpec.kt b/app/src/main/java/org/thoughtcrime/securesms/jobmanager/persistence/DependencySpec.kt index 2285a3c934..60522e2b3a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobmanager/persistence/DependencySpec.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/jobmanager/persistence/DependencySpec.kt @@ -2,7 +2,7 @@ package org.thoughtcrime.securesms.jobmanager.persistence import java.util.Locale -class DependencySpec( +data class DependencySpec( val jobId: String, val dependsOnJobId: String, val isMemoryOnly: Boolean diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobmanager/persistence/FullSpec.java b/app/src/main/java/org/thoughtcrime/securesms/jobmanager/persistence/FullSpec.java deleted file mode 100644 index 03169f141e..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/jobmanager/persistence/FullSpec.java +++ /dev/null @@ -1,53 +0,0 @@ -package org.thoughtcrime.securesms.jobmanager.persistence; - -import androidx.annotation.NonNull; - -import java.util.List; -import java.util.Objects; - -public final class FullSpec { - - private final JobSpec jobSpec; - private final List constraintSpecs; - private final List dependencySpecs; - - public FullSpec(@NonNull JobSpec jobSpec, - @NonNull List constraintSpecs, - @NonNull List dependencySpecs) - { - this.jobSpec = jobSpec; - this.constraintSpecs = constraintSpecs; - this.dependencySpecs = dependencySpecs; - } - - public @NonNull JobSpec getJobSpec() { - return jobSpec; - } - - public @NonNull List getConstraintSpecs() { - return constraintSpecs; - } - - public @NonNull List getDependencySpecs() { - return dependencySpecs; - } - - public boolean isMemoryOnly() { - return jobSpec.isMemoryOnly(); - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - FullSpec fullSpec = (FullSpec) o; - return Objects.equals(jobSpec, fullSpec.jobSpec) && - Objects.equals(constraintSpecs, fullSpec.constraintSpecs) && - Objects.equals(dependencySpecs, fullSpec.dependencySpecs); - } - - @Override - public int hashCode() { - return Objects.hash(jobSpec, constraintSpecs, dependencySpecs); - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobmanager/persistence/FullSpec.kt b/app/src/main/java/org/thoughtcrime/securesms/jobmanager/persistence/FullSpec.kt new file mode 100644 index 0000000000..32c21e2955 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/jobmanager/persistence/FullSpec.kt @@ -0,0 +1,10 @@ +package org.thoughtcrime.securesms.jobmanager.persistence + +data class FullSpec( + val jobSpec: JobSpec, + val constraintSpecs: List, + val dependencySpecs: List +) { + val isMemoryOnly: Boolean + get() = jobSpec.isMemoryOnly +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/FastJobStorage.kt b/app/src/main/java/org/thoughtcrime/securesms/jobs/FastJobStorage.kt index cfa7c9ced2..c686cd1a71 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/FastJobStorage.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/FastJobStorage.kt @@ -39,8 +39,8 @@ class FastJobStorage(private val jobDatabase: JobDatabase) : JobStorage { for (fullSpec in fullSpecs) { jobs += fullSpec.jobSpec - constraintsByJobId[fullSpec.jobSpec.id] = fullSpec.constraintSpecs - dependenciesByJobId[fullSpec.jobSpec.id] = fullSpec.dependencySpecs + constraintsByJobId[fullSpec.jobSpec.id] = fullSpec.constraintSpecs.toMutableList() + dependenciesByJobId[fullSpec.jobSpec.id] = fullSpec.dependencySpecs.toMutableList() } } diff --git a/app/src/test/java/org/thoughtcrime/securesms/jobs/FastJobStorageTest.java b/app/src/test/java/org/thoughtcrime/securesms/jobs/FastJobStorageTest.java deleted file mode 100644 index 766242a164..0000000000 --- a/app/src/test/java/org/thoughtcrime/securesms/jobs/FastJobStorageTest.java +++ /dev/null @@ -1,684 +0,0 @@ -package org.thoughtcrime.securesms.jobs; - -import androidx.annotation.NonNull; - -import com.annimon.stream.Stream; - -import org.junit.Test; -import org.thoughtcrime.securesms.database.JobDatabase; -import org.thoughtcrime.securesms.jobmanager.Job; -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 java.util.Arrays; -import java.util.Collections; -import java.util.List; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; -import static org.thoughtcrime.securesms.testutil.TestHelpers.setOf; - -public class FastJobStorageTest { - - @Test - public void init_allStoredDataAvailable() { - FastJobStorage subject = new FastJobStorage(fixedDataDatabase(DataSet1.FULL_SPECS)); - - subject.init(); - - DataSet1.assertJobsMatch(subject.getAllJobSpecs()); - DataSet1.assertConstraintsMatch(subject.getAllConstraintSpecs()); - DataSet1.assertDependenciesMatch(subject.getAllDependencySpecs()); - } - - @Test - public void init_removesCircularDependencies() { - FastJobStorage subject = new FastJobStorage(fixedDataDatabase(DataSetCircularDependency.FULL_SPECS)); - - subject.init(); - - DataSetCircularDependency.assertJobsMatch(subject.getAllJobSpecs()); - DataSetCircularDependency.assertConstraintsMatch(subject.getAllConstraintSpecs()); - DataSetCircularDependency.assertDependenciesMatch(subject.getAllDependencySpecs()); - } - - @Test - public void insertJobs_writesToDatabase() { - JobDatabase database = noopDatabase(); - FastJobStorage subject = new FastJobStorage(database); - - subject.insertJobs(DataSet1.FULL_SPECS); - - verify(database).insertJobs(DataSet1.FULL_SPECS); - } - - @Test - public void insertJobs_memoryOnlyJob_doesNotWriteToDatabase() { - JobDatabase database = noopDatabase(); - FastJobStorage subject = new FastJobStorage(database); - - subject.insertJobs(DataSetMemory.FULL_SPECS); - - verify(database, times(0)).insertJobs(DataSet1.FULL_SPECS); - } - - @Test - public void insertJobs_dataCanBeFound() { - FastJobStorage subject = new FastJobStorage(noopDatabase()); - - subject.insertJobs(DataSet1.FULL_SPECS); - - DataSet1.assertJobsMatch(subject.getAllJobSpecs()); - DataSet1.assertConstraintsMatch(subject.getAllConstraintSpecs()); - DataSet1.assertDependenciesMatch(subject.getAllDependencySpecs()); - } - - @Test - public void insertJobs_individualJobCanBeFound() { - FastJobStorage subject = new FastJobStorage(noopDatabase()); - - subject.insertJobs(DataSet1.FULL_SPECS); - - assertEquals(DataSet1.JOB_1, subject.getJobSpec(DataSet1.JOB_1.getId())); - assertEquals(DataSet1.JOB_2, subject.getJobSpec(DataSet1.JOB_2.getId())); - } - - @Test - public void updateAllJobsToBePending_writesToDatabase() { - JobDatabase database = noopDatabase(); - FastJobStorage subject = new FastJobStorage(database); - - subject.updateAllJobsToBePending(); - - verify(database).updateAllJobsToBePending(); - } - - @Test - public void updateAllJobsToBePending_allArePending() { - FullSpec fullSpec1 = new FullSpec(new JobSpec("1", "f1", null, 1, 1, 1, 1, 1, null, null, true, false), - Collections.emptyList(), - Collections.emptyList()); - FullSpec fullSpec2 = new FullSpec(new JobSpec("2", "f2", null, 1, 1, 1, 1, 1, null, null, true, false), - Collections.emptyList(), - Collections.emptyList()); - - FastJobStorage subject = new FastJobStorage(fixedDataDatabase(Arrays.asList(fullSpec1, fullSpec2))); - - subject.init(); - subject.updateAllJobsToBePending(); - - assertFalse(subject.getJobSpec("1").isRunning()); - assertFalse(subject.getJobSpec("2").isRunning()); - } - - @Test - public void updateJobs_writesToDatabase() { - JobDatabase database = fixedDataDatabase(DataSet1.FULL_SPECS); - FastJobStorage subject = new FastJobStorage(database); - List jobs = Collections.singletonList(new JobSpec("id1", "f1", null, 1, 1, 1, 1, 1, null, null, false, false)); - - subject.init(); - subject.updateJobs(jobs); - - verify(database).updateJobs(jobs); - } - - @Test - public void updateJobs_memoryOnly_doesNotWriteToDatabase() { - JobDatabase database = fixedDataDatabase(DataSetMemory.FULL_SPECS); - FastJobStorage subject = new FastJobStorage(database); - List jobs = Collections.singletonList(new JobSpec("id1", "f1", null, 1, 1, 1, 1, 1, null, null, false, false)); - - subject.init(); - subject.updateJobs(jobs); - - verify(database, times(0)).updateJobs(jobs); - } - - @Test - public void updateJobs_updatesAllFields() { - FullSpec fullSpec1 = new FullSpec(new JobSpec("1", "f1", null, 1, 1, 1, 1, 1, null, null, false, false), - Collections.emptyList(), - Collections.emptyList()); - FullSpec fullSpec2 = new FullSpec(new JobSpec("2", "f2", null, 1, 1, 1, 1, 1, null, null, false, false), - Collections.emptyList(), - Collections.emptyList()); - FullSpec fullSpec3 = new FullSpec(new JobSpec("3", "f3", null, 1, 1, 1, 1, 1, null, null, false, false), - Collections.emptyList(), - Collections.emptyList()); - - FastJobStorage subject = new FastJobStorage(fixedDataDatabase(Arrays.asList(fullSpec1, fullSpec2, fullSpec3))); - - JobSpec update1 = new JobSpec("1", "g1", "q1", 2, 2, 2, 2, 2, "abc".getBytes(), null, true, false); - JobSpec update2 = new JobSpec("2", "g2", "q2", 3, 3, 3, 3, 3, "def".getBytes(), "ghi".getBytes(), true, false); - - subject.init(); - subject.updateJobs(Arrays.asList(update1, update2)); - - assertEquals(update1, subject.getJobSpec("1")); - assertEquals(update2, subject.getJobSpec("2")); - assertEquals(fullSpec3.getJobSpec(), subject.getJobSpec("3")); - } - - @Test - public void updateJobRunningState_writesToDatabase() { - JobDatabase database = fixedDataDatabase(DataSet1.FULL_SPECS); - FastJobStorage subject = new FastJobStorage(database); - - subject.init(); - subject.updateJobRunningState("id1", true); - - verify(database).updateJobRunningState("id1", true); - } - - @Test - public void updateJobRunningState_stateUpdated() { - FastJobStorage subject = new FastJobStorage(fixedDataDatabase(DataSet1.FULL_SPECS)); - subject.init(); - - subject.updateJobRunningState(DataSet1.JOB_1.getId(), true); - assertTrue(subject.getJobSpec(DataSet1.JOB_1.getId()).isRunning()); - - subject.updateJobRunningState(DataSet1.JOB_1.getId(), false); - assertFalse(subject.getJobSpec(DataSet1.JOB_1.getId()).isRunning()); - } - - @Test - public void updateJobAfterRetry_writesToDatabase() { - JobDatabase database = fixedDataDatabase(DataSet1.FULL_SPECS); - FastJobStorage subject = new FastJobStorage(database); - - subject.init(); - subject.updateJobAfterRetry("id1", true, 1, 10, "a".getBytes()); - - verify(database).updateJobAfterRetry("id1", true, 1, 10, "a".getBytes()); - } - - @Test - public void updateJobAfterRetry_memoryOnly_doesNotWriteToDatabase() { - JobDatabase database = fixedDataDatabase(DataSetMemory.FULL_SPECS); - FastJobStorage subject = new FastJobStorage(database); - - subject.init(); - subject.updateJobAfterRetry("id1", true, 1, 10, "a".getBytes()); - - verify(database, times(0)).updateJobAfterRetry("id1", true, 1, 10, "a".getBytes()); - } - - @Test - public void updateJobAfterRetry_stateUpdated() { - FullSpec fullSpec = new FullSpec(new JobSpec("1", "f1", null, 0, 0, 0, 3, -1, null, null, true, false), - Collections.emptyList(), - Collections.emptyList()); - - FastJobStorage subject = new FastJobStorage(fixedDataDatabase(Collections.singletonList(fullSpec))); - - subject.init(); - subject.updateJobAfterRetry("1", false, 1, 10, "a".getBytes()); - - JobSpec job = subject.getJobSpec("1"); - - assertNotNull(job); - assertFalse(job.isRunning()); - assertEquals(1, job.getRunAttempt()); - assertEquals(10, job.getNextRunAttemptTime()); - assertEquals("a", new String(job.getSerializedData())); - } - - @Test - public void getPendingJobsWithNoDependenciesInCreatedOrder_noneWhenEarlierItemInQueueInRunning() { - FullSpec fullSpec1 = new FullSpec(new JobSpec("1", "f1", "q", 0, 0, 0, 0, -1, null, null, true, false), - Collections.emptyList(), - Collections.emptyList()); - FullSpec fullSpec2 = new FullSpec(new JobSpec("2", "f2", "q", 0, 0, 0, 0, -1, null, null, false, false), - Collections.emptyList(), - Collections.emptyList()); - - FastJobStorage subject = new FastJobStorage(fixedDataDatabase(Arrays.asList(fullSpec1, fullSpec2))); - subject.init(); - - assertEquals(0, subject.getPendingJobsWithNoDependenciesInCreatedOrder(1).size()); - } - - @Test - public void getPendingJobsWithNoDependenciesInCreatedOrder_noneWhenAllJobsAreRunning() { - FullSpec fullSpec = new FullSpec(new JobSpec("1", "f1", "q", 0, 0, 0, 0, -1, null, null, true, false), - Collections.emptyList(), - Collections.emptyList()); - - FastJobStorage subject = new FastJobStorage(fixedDataDatabase(Collections.singletonList(fullSpec))); - subject.init(); - - assertEquals(0, subject.getPendingJobsWithNoDependenciesInCreatedOrder(10).size()); - } - - @Test - public void getPendingJobsWithNoDependenciesInCreatedOrder_noneWhenNextRunTimeIsAfterCurrentTime() { - FullSpec fullSpec = new FullSpec(new JobSpec("1", "f1", "q", 0, 10, 0, 0, -1, null, null, false, false), - Collections.emptyList(), - Collections.emptyList()); - - FastJobStorage subject = new FastJobStorage(fixedDataDatabase(Collections.singletonList(fullSpec))); - subject.init(); - - assertEquals(0, subject.getPendingJobsWithNoDependenciesInCreatedOrder(0).size()); - } - - @Test - public void getPendingJobsWithNoDependenciesInCreatedOrder_noneWhenDependentOnAnotherJob() { - FullSpec fullSpec1 = new FullSpec(new JobSpec("1", "f1", null, 0, 0, 0, 0, -1, null, null, true, false), - Collections.emptyList(), - Collections.emptyList()); - FullSpec fullSpec2 = new FullSpec(new JobSpec("2", "f2", null, 0, 0, 0, 0, -1, null, null, false, false), - Collections.emptyList(), - Collections.singletonList(new DependencySpec("2", "1", false))); - - - FastJobStorage subject = new FastJobStorage(fixedDataDatabase(Arrays.asList(fullSpec1, fullSpec2))); - subject.init(); - - assertEquals(0, subject.getPendingJobsWithNoDependenciesInCreatedOrder(0).size()); - } - - @Test - public void getPendingJobsWithNoDependenciesInCreatedOrder_singleEligibleJob() { - FullSpec fullSpec = new FullSpec(new JobSpec("1", "f1", "q", 0, 0, 0, 0, -1, null, null, false, false), - Collections.emptyList(), - Collections.emptyList()); - - FastJobStorage subject = new FastJobStorage(fixedDataDatabase(Collections.singletonList(fullSpec))); - subject.init(); - - assertEquals(1, subject.getPendingJobsWithNoDependenciesInCreatedOrder(10).size()); - } - - @Test - public void getPendingJobsWithNoDependenciesInCreatedOrder_multipleEligibleJobs() { - FullSpec fullSpec1 = new FullSpec(new JobSpec("1", "f1", null, 0, 0, 0, 0, -1, null, null, false, false), - Collections.emptyList(), - Collections.emptyList()); - FullSpec fullSpec2 = new FullSpec(new JobSpec("2", "f2", null, 0, 0, 0, 0, -1, null, null, false, false), - Collections.emptyList(), - Collections.emptyList()); - - - FastJobStorage subject = new FastJobStorage(fixedDataDatabase(Arrays.asList(fullSpec1, fullSpec2))); - subject.init(); - - assertEquals(2, subject.getPendingJobsWithNoDependenciesInCreatedOrder(10).size()); - } - - @Test - public void getPendingJobsWithNoDependenciesInCreatedOrder_singleEligibleJobInMixedList() { - FullSpec fullSpec1 = new FullSpec(new JobSpec("1", "f1", null, 0, 0, 0, 0, -1, null, null, true, false), - Collections.emptyList(), - Collections.emptyList()); - FullSpec fullSpec2 = new FullSpec(new JobSpec("2", "f2", null, 0, 0, 0, 0, -1, null, null, false, false), - Collections.emptyList(), - Collections.emptyList()); - - - FastJobStorage subject = new FastJobStorage(fixedDataDatabase(Arrays.asList(fullSpec1, fullSpec2))); - subject.init(); - - List jobs = subject.getPendingJobsWithNoDependenciesInCreatedOrder(10); - - assertEquals(1, jobs.size()); - assertEquals("2", jobs.get(0).getId()); - } - - @Test - public void getPendingJobsWithNoDependenciesInCreatedOrder_firstItemInQueue() { - FullSpec fullSpec1 = new FullSpec(new JobSpec("1", "f1", "q", 0, 0, 0, 0, -1, null, null, false, false), - Collections.emptyList(), - Collections.emptyList()); - FullSpec fullSpec2 = new FullSpec(new JobSpec("2", "f2", "q", 0, 0, 0, 0, -1, null, null, false, false), - Collections.emptyList(), - Collections.emptyList()); - - - FastJobStorage subject = new FastJobStorage(fixedDataDatabase(Arrays.asList(fullSpec1, fullSpec2))); - subject.init(); - - List jobs = subject.getPendingJobsWithNoDependenciesInCreatedOrder(10); - - assertEquals(1, jobs.size()); - assertEquals("1", jobs.get(0).getId()); - } - - @Test - public void getPendingJobsWithNoDependenciesInCreatedOrder_migrationJobTakesPrecedence() { - FullSpec plainSpec = new FullSpec(new JobSpec("1", "f1", "q", 0, 0, 0, 0, -1, null, null, false, false), - Collections.emptyList(), - Collections.emptyList()); - FullSpec migrationSpec = new FullSpec(new JobSpec("2", "f2", Job.Parameters.MIGRATION_QUEUE_KEY, 5, 0, 0, 0, -1, null, null, false, false), - Collections.emptyList(), - Collections.emptyList()); - - FastJobStorage subject = new FastJobStorage(fixedDataDatabase(Arrays.asList(plainSpec, migrationSpec))); - subject.init(); - - List jobs = subject.getPendingJobsWithNoDependenciesInCreatedOrder(10); - - assertEquals(1, jobs.size()); - assertEquals("2", jobs.get(0).getId()); - } - - @Test - public void getPendingJobsWithNoDependenciesInCreatedOrder_runningMigrationBlocksNormalJobs() { - FullSpec plainSpec = new FullSpec(new JobSpec("1", "f1", "q", 0, 0, 0, 0, -1, null, null, false, false), - Collections.emptyList(), - Collections.emptyList()); - FullSpec migrationSpec = new FullSpec(new JobSpec("2", "f2", Job.Parameters.MIGRATION_QUEUE_KEY, 5, 0, 0, 0, -1, null, null, true, false), - Collections.emptyList(), - Collections.emptyList()); - - FastJobStorage subject = new FastJobStorage(fixedDataDatabase(Arrays.asList(plainSpec, migrationSpec))); - subject.init(); - - List jobs = subject.getPendingJobsWithNoDependenciesInCreatedOrder(10); - - assertEquals(0, jobs.size()); - } - - @Test - public void getPendingJobsWithNoDependenciesInCreatedOrder_runningMigrationBlocksLaterMigrationJobs() { - FullSpec migrationSpec1 = new FullSpec(new JobSpec("1", "f1", Job.Parameters.MIGRATION_QUEUE_KEY, 0, 0, 0, 0, -1, null, null, true, false), - Collections.emptyList(), - Collections.emptyList()); - FullSpec migrationSpec2 = new FullSpec(new JobSpec("2", "f2", Job.Parameters.MIGRATION_QUEUE_KEY, 5, 0, 0, 0, -1, null, null, false, false), - Collections.emptyList(), - Collections.emptyList()); - - FastJobStorage subject = new FastJobStorage(fixedDataDatabase(Arrays.asList(migrationSpec1, migrationSpec2))); - subject.init(); - - List jobs = subject.getPendingJobsWithNoDependenciesInCreatedOrder(10); - - assertEquals(0, jobs.size()); - } - - @Test - public void getPendingJobsWithNoDependenciesInCreatedOrder_onlyReturnFirstEligibleMigrationJob() { - FullSpec migrationSpec1 = new FullSpec(new JobSpec("1", "f1", Job.Parameters.MIGRATION_QUEUE_KEY, 0, 0, 0, 0, -1, null, null, false, false), - Collections.emptyList(), - Collections.emptyList()); - FullSpec migrationSpec2 = new FullSpec(new JobSpec("2", "f2", Job.Parameters.MIGRATION_QUEUE_KEY, 5, 0, 0, 0, -1, null, null, false, false), - Collections.emptyList(), - Collections.emptyList()); - - FastJobStorage subject = new FastJobStorage(fixedDataDatabase(Arrays.asList(migrationSpec1, migrationSpec2))); - subject.init(); - - List jobs = subject.getPendingJobsWithNoDependenciesInCreatedOrder(10); - - assertEquals(1, jobs.size()); - assertEquals("1", jobs.get(0).getId()); - } - - @Test - public void getPendingJobsWithNoDependenciesInCreatedOrder_onlyMigrationJobWithAppropriateNextRunTime() { - FullSpec migrationSpec1 = new FullSpec(new JobSpec("1", "f1", Job.Parameters.MIGRATION_QUEUE_KEY, 0, 999, 0, 0, -1, null, null, false, false), - Collections.emptyList(), - Collections.emptyList()); - FullSpec migrationSpec2 = new FullSpec(new JobSpec("2", "f2", Job.Parameters.MIGRATION_QUEUE_KEY, 5, 0, 0, 0, -1, null, null, false, false), - Collections.emptyList(), - Collections.emptyList()); - - FastJobStorage subject = new FastJobStorage(fixedDataDatabase(Arrays.asList(migrationSpec1, migrationSpec2))); - subject.init(); - - List jobs = subject.getPendingJobsWithNoDependenciesInCreatedOrder(10); - - assertTrue(jobs.isEmpty()); - } - - @Test - public void deleteJobs_writesToDatabase() { - JobDatabase database = fixedDataDatabase(DataSet1.FULL_SPECS); - FastJobStorage subject = new FastJobStorage(database); - List ids = Arrays.asList("id1", "id2"); - - subject.init(); - subject.deleteJobs(ids); - - verify(database).deleteJobs(ids); - } - - @Test - public void deleteJobs_memoryOnly_doesNotWriteToDatabase() { - JobDatabase database = fixedDataDatabase(DataSetMemory.FULL_SPECS); - FastJobStorage subject = new FastJobStorage(database); - List ids = Collections.singletonList("id1"); - - subject.init(); - subject.deleteJobs(ids); - - verify(database, times(0)).deleteJobs(ids); - } - - @Test - public void deleteJobs_deletesAllRelevantPieces() { - FastJobStorage subject = new FastJobStorage(fixedDataDatabase(DataSet1.FULL_SPECS)); - - subject.init(); - subject.deleteJobs(Collections.singletonList("id1")); - - List jobs = subject.getAllJobSpecs(); - List constraints = subject.getAllConstraintSpecs(); - List dependencies = subject.getAllDependencySpecs(); - - assertEquals(2, jobs.size()); - assertEquals(DataSet1.JOB_2, jobs.get(0)); - assertEquals(DataSet1.JOB_3, jobs.get(1)); - assertEquals(1, constraints.size()); - assertEquals(DataSet1.CONSTRAINT_2, constraints.get(0)); - assertEquals(1, dependencies.size()); - } - - @Test - public void getDependencySpecsThatDependOnJob_startOfChain() { - FastJobStorage subject = new FastJobStorage(fixedDataDatabase(DataSet1.FULL_SPECS)); - - subject.init(); - - List result = subject.getDependencySpecsThatDependOnJob("id1"); - - assertEquals(2, result.size()); - assertEquals(DataSet1.DEPENDENCY_2, result.get(0)); - assertEquals(DataSet1.DEPENDENCY_3, result.get(1)); - } - - @Test - public void getDependencySpecsThatDependOnJob_midChain() { - FastJobStorage subject = new FastJobStorage(fixedDataDatabase(DataSet1.FULL_SPECS)); - - subject.init(); - - List result = subject.getDependencySpecsThatDependOnJob("id2"); - - assertEquals(1, result.size()); - assertEquals(DataSet1.DEPENDENCY_3, result.get(0)); - } - - @Test - public void getDependencySpecsThatDependOnJob_endOfChain() { - FastJobStorage subject = new FastJobStorage(fixedDataDatabase(DataSet1.FULL_SPECS)); - - subject.init(); - - List result = subject.getDependencySpecsThatDependOnJob("id3"); - - assertTrue(result.isEmpty()); - } - - @Test - public void getJobsInQueue_empty() { - FastJobStorage subject = new FastJobStorage(fixedDataDatabase(DataSet1.FULL_SPECS)); - - subject.init(); - - List result = subject.getJobsInQueue("x"); - - assertTrue(result.isEmpty()); - } - - @Test - public void getJobsInQueue_singleJob() { - FastJobStorage subject = new FastJobStorage(fixedDataDatabase(DataSet1.FULL_SPECS)); - - subject.init(); - - List result = subject.getJobsInQueue("q1"); - - assertEquals(1, result.size()); - assertEquals("id1", result.get(0).getId()); - } - - @Test - public void getJobCountForFactory_general() { - FastJobStorage subject = new FastJobStorage(fixedDataDatabase(DataSet1.FULL_SPECS)); - - subject.init(); - - assertEquals(1, subject.getJobCountForFactory("f1")); - assertEquals(0, subject.getJobCountForFactory("does-not-exist")); - } - - @Test - public void getJobCountForFactoryAndQueue_general() { - FastJobStorage subject = new FastJobStorage(fixedDataDatabase(DataSet1.FULL_SPECS)); - - subject.init(); - - assertEquals(1, subject.getJobCountForFactoryAndQueue("f1", "q1")); - assertEquals(0, subject.getJobCountForFactoryAndQueue("f2", "q1")); - assertEquals(0, subject.getJobCountForFactoryAndQueue("f1", "does-not-exist")); - } - - @Test - public void areQueuesEmpty_allNonEmpty() { - FastJobStorage subject = new FastJobStorage(fixedDataDatabase(DataSet1.FULL_SPECS)); - - subject.init(); - - assertFalse(subject.areQueuesEmpty(setOf("q1"))); - assertFalse(subject.areQueuesEmpty(setOf("q1", "q2"))); - } - - @Test - public void areQueuesEmpty_mixedEmpty() { - FastJobStorage subject = new FastJobStorage(fixedDataDatabase(DataSet1.FULL_SPECS)); - - subject.init(); - - assertFalse(subject.areQueuesEmpty(setOf("q1", "q5"))); - } - - @Test - public void areQueuesEmpty_queueDoesNotExist() { - FastJobStorage subject = new FastJobStorage(fixedDataDatabase(DataSet1.FULL_SPECS)); - - subject.init(); - - assertTrue(subject.areQueuesEmpty(setOf("q4"))); - assertTrue(subject.areQueuesEmpty(setOf("q4", "q5"))); - } - - private JobDatabase noopDatabase() { - JobDatabase database = mock(JobDatabase.class); - - when(database.getAllJobSpecs()).thenReturn(Collections.emptyList()); - when(database.getAllConstraintSpecs()).thenReturn(Collections.emptyList()); - when(database.getAllDependencySpecs()).thenReturn(Collections.emptyList()); - - return database; - } - - private JobDatabase fixedDataDatabase(List fullSpecs) { - JobDatabase database = mock(JobDatabase.class); - - when(database.getAllJobSpecs()).thenReturn(Stream.of(fullSpecs).map(FullSpec::getJobSpec).toList()); - when(database.getAllConstraintSpecs()).thenReturn(Stream.of(fullSpecs).map(FullSpec::getConstraintSpecs).flatMap(Stream::of).toList()); - when(database.getAllDependencySpecs()).thenReturn(Stream.of(fullSpecs).map(FullSpec::getDependencySpecs).flatMap(Stream::of).toList()); - - return database; - } - - private static final class DataSet1 { - static final JobSpec JOB_1 = new JobSpec("id1", "f1", "q1", 1, 2, 3, 4, 5, null, null, false, false); - static final JobSpec JOB_2 = new JobSpec("id2", "f2", "q2", 1, 2, 3, 4, 5, null, null, false, false); - static final JobSpec JOB_3 = new JobSpec("id3", "f3", "q3", 1, 2, 3, 4, 5, null, null, false, false); - static final ConstraintSpec CONSTRAINT_1 = new ConstraintSpec("id1", "f1", false); - static final ConstraintSpec CONSTRAINT_2 = new ConstraintSpec("id2", "f2", false); - static final DependencySpec DEPENDENCY_2 = new DependencySpec("id2", "id1", false); - static final DependencySpec DEPENDENCY_3 = new DependencySpec("id3", "id2", false); - static final FullSpec FULL_SPEC_1 = new FullSpec(JOB_1, Collections.singletonList(CONSTRAINT_1), Collections.emptyList()); - static final FullSpec FULL_SPEC_2 = new FullSpec(JOB_2, Collections.singletonList(CONSTRAINT_2), Collections.singletonList(DEPENDENCY_2)); - static final FullSpec FULL_SPEC_3 = new FullSpec(JOB_3, Collections.emptyList(), Collections.singletonList(DEPENDENCY_3)); - static final List FULL_SPECS = Arrays.asList(FULL_SPEC_1, FULL_SPEC_2, FULL_SPEC_3); - - static void assertJobsMatch(@NonNull List jobs) { - assertEquals(jobs.size(), 3); - assertTrue(jobs.contains(JOB_1)); - assertTrue(jobs.contains(JOB_2)); - assertTrue(jobs.contains(JOB_3)); - } - - static void assertConstraintsMatch(@NonNull List constraints) { - assertEquals(constraints.size(), 2); - assertTrue(constraints.contains(CONSTRAINT_1)); - assertTrue(constraints.contains(CONSTRAINT_2)); - } - - static void assertDependenciesMatch(@NonNull List dependencies) { - assertEquals(dependencies.size(), 2); - assertTrue(dependencies.contains(DEPENDENCY_2)); - assertTrue(dependencies.contains(DEPENDENCY_3)); - } - } - - private static final class DataSetMemory { - static final JobSpec JOB_1 = new JobSpec("id1", "f1", "q1", 1, 2, 3, 4, 5, null, null, false, true); - static final ConstraintSpec CONSTRAINT_1 = new ConstraintSpec("id1", "f1", true); - static final FullSpec FULL_SPEC_1 = new FullSpec(JOB_1, Collections.singletonList(CONSTRAINT_1), Collections.emptyList()); - static final List FULL_SPECS = Collections.singletonList(FULL_SPEC_1); - } - - private static final class DataSetCircularDependency { - static final JobSpec JOB_1 = new JobSpec("id1", "f1", "q1", 1, 2, 3, 4, 5, null, null, false, false); - static final JobSpec JOB_2 = new JobSpec("id2", "f2", "q1", 2, 2, 3, 4, 5, null, null, false, false); - static final JobSpec JOB_3 = new JobSpec("id3", "f3", "q3", 3, 2, 3, 4, 5, null, null, false, false); - static final DependencySpec DEPENDENCY_1 = new DependencySpec("id1", "id2", false); - static final DependencySpec DEPENDENCY_3 = new DependencySpec("id3", "id2", false); - static final FullSpec FULL_SPEC_1 = new FullSpec(JOB_1, Collections.emptyList(), Collections.singletonList(DEPENDENCY_1)); - static final FullSpec FULL_SPEC_2 = new FullSpec(JOB_2, Collections.emptyList(), Collections.emptyList()); - static final FullSpec FULL_SPEC_3 = new FullSpec(JOB_3, Collections.emptyList(), Collections.singletonList(DEPENDENCY_3)); - static final List FULL_SPECS = Arrays.asList(FULL_SPEC_1, FULL_SPEC_2, FULL_SPEC_3); - - static void assertJobsMatch(@NonNull List jobs) { - assertEquals(jobs.size(), 3); - assertTrue(jobs.contains(JOB_1)); - assertTrue(jobs.contains(JOB_2)); - assertTrue(jobs.contains(JOB_3)); - } - - static void assertConstraintsMatch(@NonNull List constraints) { - assertEquals(constraints.size(), 0); - } - - static void assertDependenciesMatch(@NonNull List dependencies) { - assertEquals(dependencies.size(), 1); - assertFalse(dependencies.contains(DEPENDENCY_1)); - assertTrue(dependencies.contains(DEPENDENCY_3)); - } - } -} diff --git a/app/src/test/java/org/thoughtcrime/securesms/jobs/FastJobStorageTest.kt b/app/src/test/java/org/thoughtcrime/securesms/jobs/FastJobStorageTest.kt new file mode 100644 index 0000000000..2595e4b9f0 --- /dev/null +++ b/app/src/test/java/org/thoughtcrime/securesms/jobs/FastJobStorageTest.kt @@ -0,0 +1,740 @@ +package org.thoughtcrime.securesms.jobs + +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 +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.testutil.TestHelpers +import java.nio.charset.Charset +import java.util.Arrays + +class FastJobStorageTest { + @Test + fun init_allStoredDataAvailable() { + val subject = FastJobStorage(fixedDataDatabase(DataSet1.FULL_SPECS)) + subject.init() + + DataSet1.assertJobsMatch(subject.allJobSpecs) + DataSet1.assertConstraintsMatch(subject.allConstraintSpecs) + DataSet1.assertDependenciesMatch(subject.allDependencySpecs) + } + + @Test + fun init_removesCircularDependencies() { + val subject = FastJobStorage(fixedDataDatabase(DataSetCircularDependency.FULL_SPECS)) + subject.init() + + DataSetCircularDependency.assertJobsMatch(subject.allJobSpecs) + DataSetCircularDependency.assertConstraintsMatch(subject.allConstraintSpecs) + DataSetCircularDependency.assertDependenciesMatch(subject.allDependencySpecs) + } + + @Test + fun insertJobs_writesToDatabase() { + val database = noopDatabase() + val subject = FastJobStorage(database) + + subject.insertJobs(DataSet1.FULL_SPECS) + + Mockito.verify(database).insertJobs(DataSet1.FULL_SPECS) + } + + @Test + fun insertJobs_memoryOnlyJob_doesNotWriteToDatabase() { + val database = noopDatabase() + val subject = FastJobStorage(database) + + subject.insertJobs(DataSetMemory.FULL_SPECS) + + Mockito.verify(database, Mockito.times(0)).insertJobs(DataSet1.FULL_SPECS) + } + + @Test + fun insertJobs_dataCanBeFound() { + val subject = FastJobStorage(noopDatabase()) + subject.insertJobs(DataSet1.FULL_SPECS) + DataSet1.assertJobsMatch(subject.allJobSpecs) + DataSet1.assertConstraintsMatch(subject.allConstraintSpecs) + DataSet1.assertDependenciesMatch(subject.allDependencySpecs) + } + + @Test + fun insertJobs_individualJobCanBeFound() { + val subject = FastJobStorage(noopDatabase()) + subject.insertJobs(DataSet1.FULL_SPECS) + + subject.getJobSpec(DataSet1.JOB_1.id) assertIs DataSet1.JOB_1 + subject.getJobSpec(DataSet1.JOB_2.id) assertIs DataSet1.JOB_2 + } + + @Test + fun updateAllJobsToBePending_writesToDatabase() { + val database = noopDatabase() + val subject = FastJobStorage(database) + subject.updateAllJobsToBePending() + Mockito.verify(database).updateAllJobsToBePending() + } + + @Test + fun updateAllJobsToBePending_allArePending() { + 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(Arrays.asList(fullSpec1, fullSpec2))) + subject.init() + subject.updateAllJobsToBePending() + + subject.getJobSpec("1")!!.isRunning assertIs false + subject.getJobSpec("2")!!.isRunning assertIs false + } + + @Test + fun updateJobs_writesToDatabase() { + val database = fixedDataDatabase(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) + } + + @Test + fun updateJobs_memoryOnly_doesNotWriteToDatabase() { + val database = fixedDataDatabase(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) + } + + @Test + fun updateJobs_updatesAllFields() { + val fullSpec1 = FullSpec(jobSpec(id = "1", factoryKey = "f1"), emptyList(), emptyList()) + val fullSpec2 = FullSpec(jobSpec(id = "2", factoryKey = "f2"), emptyList(), emptyList()) + val fullSpec3 = FullSpec(jobSpec(id = "3", factoryKey = "f3"), emptyList(), emptyList()) + + val update1 = jobSpec( + id = "1", + factoryKey = "g1", + queueKey = "q1", + createTime = 2, + nextRunAttemptTime = 2, + runAttempt = 2, + maxAttempts = 2, + lifespan = 2, + serializedData = "abc".toByteArray(), + serializedInputData = null, + isRunning = true, + isMemoryOnly = false + ) + val update2 = jobSpec( + id = "2", + factoryKey = "g2", + queueKey = "q2", + createTime = 3, + nextRunAttemptTime = 3, + runAttempt = 3, + maxAttempts = 3, + lifespan = 3, + serializedData = "def".toByteArray(), + serializedInputData = "ghi".toByteArray(), + isRunning = true, + isMemoryOnly = false + ) + + val subject = FastJobStorage(fixedDataDatabase(listOf(fullSpec1, fullSpec2, fullSpec3))) + subject.init() + subject.updateJobs(listOf(update1, update2)) + + subject.getJobSpec("1") assertIs update1 + subject.getJobSpec("2") assertIs update2 + subject.getJobSpec("3") assertIs fullSpec3.jobSpec + } + + @Test + fun updateJobRunningState_writesToDatabase() { + val database = fixedDataDatabase(DataSet1.FULL_SPECS) + + val subject = FastJobStorage(database) + subject.init() + + subject.updateJobRunningState(id = "id1", isRunning = true) + + Mockito.verify(database).updateJobRunningState("id1", true) + } + + @Test + fun updateJobRunningState_stateUpdated() { + val subject = FastJobStorage(fixedDataDatabase(DataSet1.FULL_SPECS)) + subject.init() + + subject.updateJobRunningState(id = DataSet1.JOB_1.id, isRunning = true) + subject.getJobSpec(DataSet1.JOB_1.id)!!.isRunning assertIs true + + subject.updateJobRunningState(id = DataSet1.JOB_1.id, isRunning = false) + subject.getJobSpec(DataSet1.JOB_1.id)!!.isRunning assertIs false + } + + @Test + fun updateJobAfterRetry_writesToDatabase() { + val database = fixedDataDatabase(DataSet1.FULL_SPECS) + + val subject = FastJobStorage(database) + subject.init() + + subject.updateJobAfterRetry( + id = "id1", + isRunning = true, + runAttempt = 1, + nextRunAttemptTime = 10, + serializedData = "a".toByteArray() + ) + + Mockito.verify(database).updateJobAfterRetry("id1", true, 1, 10, "a".toByteArray()) + } + + @Test + fun updateJobAfterRetry_memoryOnly_doesNotWriteToDatabase() { + val database = fixedDataDatabase(DataSetMemory.FULL_SPECS) + + val subject = FastJobStorage(database) + subject.init() + + subject.updateJobAfterRetry( + id = "id1", + isRunning = true, + runAttempt = 1, + nextRunAttemptTime = 10, + serializedData = "a".toByteArray() + ) + + Mockito.verify(database, Mockito.times(0)).updateJobAfterRetry("id1", true, 1, 10, "a".toByteArray()) + } + + @Test + fun updateJobAfterRetry_stateUpdated() { + val fullSpec = FullSpec(jobSpec(id = "1", factoryKey = "f1", isRunning = true), emptyList(), emptyList()) + + val subject = FastJobStorage(fixedDataDatabase(listOf(fullSpec))) + subject.init() + + subject.updateJobAfterRetry("1", false, 1, 10, "a".toByteArray()) + + val job = subject.getJobSpec("1") + check(job != null) + job.isRunning assertIs false + job.runAttempt assertIs 1 + job.nextRunAttemptTime assertIs 10 + job.serializedData!!.toString(Charset.defaultCharset()) assertIs "a" + } + + @Test + fun getPendingJobsWithNoDependenciesInCreatedOrder_noneWhenEarlierItemInQueueInRunning() { + 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))) + subject.init() + + subject.getPendingJobsWithNoDependenciesInCreatedOrder(1).size assertIs 0 + } + + @Test + fun getPendingJobsWithNoDependenciesInCreatedOrder_noneWhenAllJobsAreRunning() { + val fullSpec = FullSpec(jobSpec(id = "1", factoryKey = "f1", queueKey = "q", isRunning = true), emptyList(), emptyList()) + + val subject = FastJobStorage(fixedDataDatabase(listOf(fullSpec))) + subject.init() + + subject.getPendingJobsWithNoDependenciesInCreatedOrder(10).size assertIs 0 + } + + @Test + fun getPendingJobsWithNoDependenciesInCreatedOrder_noneWhenNextRunTimeIsAfterCurrentTime() { + val fullSpec = FullSpec(jobSpec(id = "1", factoryKey = "f1", queueKey = "q", nextRunAttemptTime = 10), emptyList(), emptyList()) + + val subject = FastJobStorage(fixedDataDatabase(listOf(fullSpec))) + subject.init() + + subject.getPendingJobsWithNoDependenciesInCreatedOrder(0).size assertIs 0 + } + + @Test + fun getPendingJobsWithNoDependenciesInCreatedOrder_noneWhenDependentOnAnotherJob() { + 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))) + subject.init() + + subject.getPendingJobsWithNoDependenciesInCreatedOrder(0).size assertIs 0 + } + + @Test + fun getPendingJobsWithNoDependenciesInCreatedOrder_singleEligibleJob() { + val fullSpec = FullSpec(jobSpec(id = "1", factoryKey = "f1", queueKey = "q"), emptyList(), emptyList()) + + val subject = FastJobStorage(fixedDataDatabase(listOf(fullSpec))) + subject.init() + + subject.getPendingJobsWithNoDependenciesInCreatedOrder(10).size assertIs 1 + } + + @Test + fun getPendingJobsWithNoDependenciesInCreatedOrder_multipleEligibleJobs() { + 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))) + subject.init() + + subject.getPendingJobsWithNoDependenciesInCreatedOrder(10).size assertIs 2 + } + + @Test + fun getPendingJobsWithNoDependenciesInCreatedOrder_singleEligibleJobInMixedList() { + 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))) + subject.init() + + val jobs = subject.getPendingJobsWithNoDependenciesInCreatedOrder(10) + jobs.size assertIs 1 + jobs[0].id assertIs "2" + } + + @Test + fun getPendingJobsWithNoDependenciesInCreatedOrder_firstItemInQueue() { + 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))) + subject.init() + + val jobs = subject.getPendingJobsWithNoDependenciesInCreatedOrder(10) + jobs.size assertIs 1 + jobs[0].id assertIs "1" + } + + @Test + fun getPendingJobsWithNoDependenciesInCreatedOrder_migrationJobTakesPrecedence() { + 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))) + subject.init() + + val jobs = subject.getPendingJobsWithNoDependenciesInCreatedOrder(10) + jobs.size assertIs 1 + jobs[0].id assertIs "2" + } + + @Test + fun getPendingJobsWithNoDependenciesInCreatedOrder_runningMigrationBlocksNormalJobs() { + 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))) + subject.init() + + val jobs = subject.getPendingJobsWithNoDependenciesInCreatedOrder(10) + jobs.size assertIs 0 + } + + @Test + fun getPendingJobsWithNoDependenciesInCreatedOrder_runningMigrationBlocksLaterMigrationJobs() { + 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))) + subject.init() + + val jobs = subject.getPendingJobsWithNoDependenciesInCreatedOrder(10) + jobs.size assertIs 0 + } + + @Test + fun getPendingJobsWithNoDependenciesInCreatedOrder_onlyReturnFirstEligibleMigrationJob() { + 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))) + subject.init() + + val jobs = subject.getPendingJobsWithNoDependenciesInCreatedOrder(10) + jobs.size assertIs 1 + jobs[0].id assertIs "1" + } + + @Test + fun getPendingJobsWithNoDependenciesInCreatedOrder_onlyMigrationJobWithAppropriateNextRunTime() { + val migrationSpec1 = FullSpec(jobSpec(id = "1", factoryKey = "f1", queueKey = Job.Parameters.MIGRATION_QUEUE_KEY, createTime = 0, nextRunAttemptTime = 999), emptyList(), emptyList()) + val migrationSpec2 = FullSpec(jobSpec(id = "2", factoryKey = "f2", queueKey = Job.Parameters.MIGRATION_QUEUE_KEY, createTime = 5, nextRunAttemptTime = 0), emptyList(), emptyList()) + + val subject = FastJobStorage(fixedDataDatabase(listOf(migrationSpec1, migrationSpec2))) + subject.init() + + val jobs = subject.getPendingJobsWithNoDependenciesInCreatedOrder(10) + jobs.size assertIs 0 + } + + @Test + fun deleteJobs_writesToDatabase() { + val database = fixedDataDatabase(DataSet1.FULL_SPECS) + val ids: List = listOf("id1", "id2") + + val subject = FastJobStorage(database) + subject.init() + + subject.deleteJobs(ids) + + Mockito.verify(database).deleteJobs(ids) + } + + @Test + fun deleteJobs_memoryOnly_doesNotWriteToDatabase() { + val database = fixedDataDatabase(DataSetMemory.FULL_SPECS) + val ids = listOf("id1") + + val subject = FastJobStorage(database) + subject.init() + + subject.deleteJobs(ids) + + Mockito.verify(database, Mockito.times(0)).deleteJobs(ids) + } + + @Test + fun deleteJobs_deletesAllRelevantPieces() { + val subject = FastJobStorage(fixedDataDatabase(DataSet1.FULL_SPECS)) + subject.init() + + subject.deleteJobs(listOf("id1")) + + val jobs = subject.allJobSpecs + val constraints = subject.allConstraintSpecs + val dependencies = subject.allDependencySpecs + + jobs.size assertIs 2 + jobs[0] assertIs DataSet1.JOB_2 + jobs[1] assertIs DataSet1.JOB_3 + constraints.size assertIs 1 + constraints[0] assertIs DataSet1.CONSTRAINT_2 + dependencies.size assertIs 1 + } + + @Test + fun getDependencySpecsThatDependOnJob_startOfChain() { + val subject = FastJobStorage(fixedDataDatabase(DataSet1.FULL_SPECS)) + subject.init() + + val result = subject.getDependencySpecsThatDependOnJob("id1") + result.size assertIs 2 + result[0] assertIs DataSet1.DEPENDENCY_2 + result[1] assertIs DataSet1.DEPENDENCY_3 + } + + @Test + fun getDependencySpecsThatDependOnJob_midChain() { + val subject = FastJobStorage(fixedDataDatabase(DataSet1.FULL_SPECS)) + subject.init() + + val result = subject.getDependencySpecsThatDependOnJob("id2") + result.size assertIs 1 + result[0] assertIs DataSet1.DEPENDENCY_3 + } + + @Test + fun getDependencySpecsThatDependOnJob_endOfChain() { + val subject = FastJobStorage(fixedDataDatabase(DataSet1.FULL_SPECS)) + subject.init() + + val result = subject.getDependencySpecsThatDependOnJob("id3") + result.size assertIs 0 + } + + @Test + fun getJobsInQueue_empty() { + val subject = FastJobStorage(fixedDataDatabase(DataSet1.FULL_SPECS)) + subject.init() + + val result = subject.getJobsInQueue("x") + result.size assertIs 0 + } + + @Test + fun getJobsInQueue_singleJob() { + val subject = FastJobStorage(fixedDataDatabase(DataSet1.FULL_SPECS)) + subject.init() + + val result = subject.getJobsInQueue("q1") + result.size assertIs 1 + result[0].id assertIs "id1" + } + + @Test + fun getJobCountForFactory_general() { + val subject = FastJobStorage(fixedDataDatabase(DataSet1.FULL_SPECS)) + subject.init() + + subject.getJobCountForFactory("f1") assertIs 1 + subject.getJobCountForFactory("does-not-exist") assertIs 0 + } + + @Test + fun getJobCountForFactoryAndQueue_general() { + val subject = FastJobStorage(fixedDataDatabase(DataSet1.FULL_SPECS)) + subject.init() + + subject.getJobCountForFactoryAndQueue("f1", "q1") assertIs 1 + subject.getJobCountForFactoryAndQueue("f2", "q1") assertIs 0 + subject.getJobCountForFactoryAndQueue("f1", "does-not-exist") assertIs 0 + } + + @Test + fun areQueuesEmpty_allNonEmpty() { + val subject = FastJobStorage(fixedDataDatabase(DataSet1.FULL_SPECS)) + subject.init() + + subject.areQueuesEmpty(TestHelpers.setOf("q1")) assertIs false + subject.areQueuesEmpty(TestHelpers.setOf("q1", "q2")) assertIs false + } + + @Test + fun areQueuesEmpty_mixedEmpty() { + val subject = FastJobStorage(fixedDataDatabase(DataSet1.FULL_SPECS)) + subject.init() + + subject.areQueuesEmpty(TestHelpers.setOf("q1", "q5")) assertIs false + } + + @Test + fun areQueuesEmpty_queueDoesNotExist() { + val subject = FastJobStorage(fixedDataDatabase(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.allJobSpecs).thenReturn(emptyList()) + Mockito.`when`(database.allConstraintSpecs).thenReturn(emptyList()) + Mockito.`when`(database.allDependencySpecs).thenReturn(emptyList()) + return database + } + + private fun fixedDataDatabase(fullSpecs: List): JobDatabase { + val database = Mockito.mock(JobDatabase::class.java) + Mockito.`when`(database.allJobSpecs).thenReturn(fullSpecs.map { it.jobSpec }) + Mockito.`when`(database.allConstraintSpecs).thenReturn(fullSpecs.map { it.constraintSpecs }.flatten()) + Mockito.`when`(database.allDependencySpecs).thenReturn(fullSpecs.map { it.dependencySpecs }.flatten()) + return database + } + + private fun jobSpec( + id: String, + factoryKey: String, + queueKey: String? = null, + createTime: Long = 1, + nextRunAttemptTime: Long = 1, + runAttempt: Int = 1, + maxAttempts: Int = 1, + lifespan: Long = 1, + serializedData: ByteArray? = null, + serializedInputData: ByteArray? = null, + isRunning: Boolean = false, + isMemoryOnly: Boolean = false + ): JobSpec { + return JobSpec( + id = id, + factoryKey = factoryKey, + queueKey = queueKey, + createTime = createTime, + nextRunAttemptTime = nextRunAttemptTime, + runAttempt = runAttempt, + maxAttempts = maxAttempts, + lifespan = lifespan, + serializedData = serializedData, + serializedInputData = serializedInputData, + isRunning = isRunning, + isMemoryOnly = isMemoryOnly + ) + } + + private object DataSet1 { + val JOB_1 = JobSpec( + id = "id1", + factoryKey = "f1", + queueKey = "q1", + createTime = 1, + nextRunAttemptTime = 2, + runAttempt = 3, + maxAttempts = 4, + lifespan = 5, + serializedData = null, + serializedInputData = null, + isRunning = false, + isMemoryOnly = false + ) + val JOB_2 = JobSpec( + id = "id2", + factoryKey = "f2", + queueKey = "q2", + createTime = 1, + nextRunAttemptTime = 2, + runAttempt = 3, + maxAttempts = 4, + lifespan = 5, + serializedData = null, + serializedInputData = null, + isRunning = false, + isMemoryOnly = false + ) + val JOB_3 = JobSpec( + id = "id3", + factoryKey = "f3", + queueKey = "q3", + createTime = 1, + nextRunAttemptTime = 2, + runAttempt = 3, + maxAttempts = 4, + lifespan = 5, + serializedData = null, + serializedInputData = null, + isRunning = false, + isMemoryOnly = false + ) + + val CONSTRAINT_1 = ConstraintSpec(jobSpecId = "id1", factoryKey = "f1", isMemoryOnly = false) + val CONSTRAINT_2 = ConstraintSpec(jobSpecId = "id2", factoryKey = "f2", isMemoryOnly = false) + + val DEPENDENCY_2 = DependencySpec(jobId = "id2", dependsOnJobId = "id1", isMemoryOnly = false) + val DEPENDENCY_3 = DependencySpec(jobId = "id3", dependsOnJobId = "id2", isMemoryOnly = false) + + val FULL_SPEC_1 = FullSpec(JOB_1, listOf(CONSTRAINT_1), emptyList()) + val FULL_SPEC_2 = FullSpec(JOB_2, listOf(CONSTRAINT_2), listOf(DEPENDENCY_2)) + val FULL_SPEC_3 = FullSpec(JOB_3, emptyList(), listOf(DEPENDENCY_3)) + val FULL_SPECS = listOf(FULL_SPEC_1, FULL_SPEC_2, FULL_SPEC_3) + fun assertJobsMatch(jobs: List) { + jobs.size assertIs 3 + jobs.contains(JOB_1) assertIs true + jobs.contains(JOB_2) assertIs true + jobs.contains(JOB_3) assertIs true + } + + fun assertConstraintsMatch(constraints: List) { + constraints.size assertIs 2 + constraints.contains(CONSTRAINT_1) assertIs true + constraints.contains(CONSTRAINT_2) assertIs true + } + + fun assertDependenciesMatch(dependencies: List) { + dependencies.size assertIs 2 + dependencies.contains(DEPENDENCY_2) assertIs true + dependencies.contains(DEPENDENCY_3) assertIs true + } + } + + private object DataSetMemory { + val JOB_1 = JobSpec( + id = "id1", + factoryKey = "f1", + queueKey = "q1", + createTime = 1, + nextRunAttemptTime = 2, + runAttempt = 3, + maxAttempts = 4, + lifespan = 5, + serializedData = null, + serializedInputData = null, + isRunning = false, + isMemoryOnly = true + ) + val CONSTRAINT_1 = ConstraintSpec(jobSpecId = "id1", factoryKey = "f1", isMemoryOnly = true) + val FULL_SPEC_1 = FullSpec(JOB_1, listOf(CONSTRAINT_1), emptyList()) + val FULL_SPECS = listOf(FULL_SPEC_1) + } + + private object DataSetCircularDependency { + val JOB_1 = JobSpec( + id = "id1", + factoryKey = "f1", + queueKey = "q1", + createTime = 1, + nextRunAttemptTime = 2, + runAttempt = 3, + maxAttempts = 4, + lifespan = 5, + serializedData = null, + serializedInputData = null, + isRunning = false, + isMemoryOnly = false + ) + val JOB_2 = JobSpec( + id = "id2", + factoryKey = "f2", + queueKey = "q1", + createTime = 2, + nextRunAttemptTime = 2, + runAttempt = 3, + maxAttempts = 4, + lifespan = 5, + serializedData = null, + serializedInputData = null, + isRunning = false, + isMemoryOnly = false + ) + val JOB_3 = JobSpec( + id = "id3", + factoryKey = "f3", + queueKey = "q3", + createTime = 3, + nextRunAttemptTime = 2, + runAttempt = 3, + maxAttempts = 4, + lifespan = 5, + serializedData = null, + serializedInputData = null, + isRunning = false, + isMemoryOnly = false + ) + + val DEPENDENCY_1 = DependencySpec(jobId = "id1", dependsOnJobId = "id2", isMemoryOnly = false) + val DEPENDENCY_3 = DependencySpec(jobId = "id3", dependsOnJobId = "id2", isMemoryOnly = false) + + val FULL_SPEC_1 = FullSpec(jobSpec = JOB_1, constraintSpecs = emptyList(), dependencySpecs = listOf(DEPENDENCY_1)) + val FULL_SPEC_2 = FullSpec(jobSpec = JOB_2, constraintSpecs = emptyList(), dependencySpecs = emptyList()) + val FULL_SPEC_3 = FullSpec(jobSpec = JOB_3, constraintSpecs = emptyList(), dependencySpecs = listOf(DEPENDENCY_3)) + val FULL_SPECS = listOf(FULL_SPEC_1, FULL_SPEC_2, FULL_SPEC_3) + + fun assertJobsMatch(jobs: List) { + jobs.size assertIs 3 + jobs.contains(JOB_1) assertIs true + jobs.contains(JOB_2) assertIs true + jobs.contains(JOB_3) assertIs true + } + + fun assertConstraintsMatch(constraints: List) { + constraints.size assertIs 0 + } + + fun assertDependenciesMatch(dependencies: List) { + dependencies.size assertIs 1 + dependencies.contains(DEPENDENCY_1) assertIs false + dependencies.contains(DEPENDENCY_3) assertIs true + } + } +}