blob: a6389eeafcf208e7cdf7592f85bb30a6c366267a [file] [log] [blame]
// Copyright 2018 The Feed Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.android.libraries.feed.common.concurrent;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.MockitoAnnotations.initMocks;
import com.google.android.libraries.feed.common.concurrent.TaskQueue.TaskType;
import com.google.android.libraries.feed.common.concurrent.testing.ClockBackedFakeMainThreadRunner;
import com.google.android.libraries.feed.common.time.testing.FakeClock;
import com.google.android.libraries.feed.host.logging.InternalFeedError;
import com.google.android.libraries.feed.testing.host.logging.FakeBasicLoggingApi;
import com.google.common.util.concurrent.MoreExecutors;
import java.util.ArrayList;
import java.util.List;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
/** Tests of the {@link TaskQueue} class. */
@RunWith(RobolectricTestRunner.class)
public class TaskQueueTest {
private final FakeBasicLoggingApi fakeBasicLoggingApi = new FakeBasicLoggingApi();
private final FakeClock fakeClock = new FakeClock();
private final List<Integer> callOrder = new ArrayList<>();
private final ClockBackedFakeMainThreadRunner fakeMainThreadRunner =
ClockBackedFakeMainThreadRunner.create(fakeClock);
private final TaskQueue taskQueue =
new TaskQueue(
fakeBasicLoggingApi,
MoreExecutors.directExecutor(),
fakeMainThreadRunner,
fakeClock,
/* checkStarvation= */ true);
private boolean delayedTaskHasRun = false;
@Before
public void setUp() {
initMocks(this);
callOrder.clear();
}
@Test
public void testInitialization() {
assertThat(taskQueue.isDelayed()).isTrue();
taskQueue.initialize(this::noOp);
assertThat(taskQueue.isDelayed()).isFalse();
}
@Test
public void testInitializationCallOrder() {
Runnable background = () -> runnable(1);
Runnable userFacing = () -> runnable(2);
Runnable postInit = () -> runnable(3);
Runnable init = () -> runnable(4);
taskQueue.execute("background", TaskType.BACKGROUND, background);
taskQueue.execute("userFacing", TaskType.USER_FACING, userFacing);
taskQueue.execute("postInit", TaskType.IMMEDIATE, postInit);
assertThat(taskQueue.hasBacklog()).isTrue();
taskQueue.initialize(init);
assertThat(taskQueue.hasBacklog()).isFalse();
assertOrder(4, 3, 2, 1);
}
@Test
public void testPostInit() {
taskQueue.initialize(this::noOp);
Runnable invalidate = () -> runnable(1);
Runnable postInit = () -> runnable(2);
Runnable userFacing = () -> runnable(3);
Runnable reset = () -> runnable(4);
taskQueue.execute("headInvalidate", TaskType.HEAD_INVALIDATE, invalidate);
taskQueue.execute("postInit", TaskType.IMMEDIATE, postInit);
taskQueue.execute("userFacing", TaskType.USER_FACING, userFacing);
taskQueue.execute("reset", TaskType.HEAD_RESET, reset);
assertThat(taskQueue.hasBacklog()).isFalse();
assertOrder(1, 2, 4, 3);
}
@Test
public void testHeadInvalidateReset() {
taskQueue.initialize(this::noOp);
assertThat(taskQueue.isDelayed()).isFalse();
taskQueue.execute("headInvalidate", TaskType.HEAD_INVALIDATE, this::noOp);
taskQueue.execute("delayedTask", TaskType.BACKGROUND, this::delayedTask);
assertThat(taskQueue.isDelayed()).isTrue();
assertThat(delayedTaskHasRun).isFalse();
taskQueue.execute("headReset", TaskType.HEAD_RESET, this::noOp);
assertThat(taskQueue.isDelayed()).isFalse();
assertThat(delayedTaskHasRun).isTrue();
}
@Test
public void testHeadInvalidateDropMultiple() {
Runnable invalidate = () -> runnable(1);
Runnable invalidateNotRun = () -> runnable(2);
Runnable init = () -> runnable(3);
taskQueue.execute("headInvalidate", TaskType.HEAD_INVALIDATE, invalidate);
taskQueue.execute("headInvalidateDropped", TaskType.HEAD_INVALIDATE, invalidateNotRun);
assertThat(taskQueue.hasBacklog()).isTrue();
taskQueue.initialize(init);
assertThat(taskQueue.hasBacklog()).isFalse();
assertOrder(3, 1);
}
@Test
public void testHeadInvalidateCallOrder() {
taskQueue.initialize(this::noOp);
assertThat(taskQueue.isDelayed()).isFalse();
Runnable headInvalidate = () -> runnable(1);
Runnable background = () -> runnable(2);
Runnable userFacing = () -> runnable(3);
Runnable postInit = () -> runnable(4);
Runnable headReset = () -> runnable(5);
taskQueue.execute("headInvalidate", TaskType.HEAD_INVALIDATE, headInvalidate);
taskQueue.execute("background", TaskType.BACKGROUND, background);
taskQueue.execute("userFacing", TaskType.USER_FACING, userFacing);
taskQueue.execute("postInit", TaskType.IMMEDIATE, postInit); // run immediately
assertThat(taskQueue.hasBacklog()).isTrue();
taskQueue.execute("headReset", TaskType.HEAD_RESET, headReset);
assertThat(taskQueue.hasBacklog()).isFalse();
assertOrder(1, 4, 5, 3, 2);
}
@Test
public void testResetTaskQueue_immediate() {
taskQueue.initialize(this::noOp);
assertThat(taskQueue.isDelayed()).isFalse();
taskQueue.reset();
assertThat(taskQueue.isDelayed()).isTrue();
taskQueue.initialize(this::noOp);
assertThat(taskQueue.isDelayed()).isFalse();
}
@Test
public void testResetQueue_withDelay() {
taskQueue.initialize(this::noOp);
assertThat(taskQueue.isDelayed()).isFalse();
Runnable headInvalidate = () -> runnable(1);
Runnable background = () -> runnable(2);
Runnable userFacing = () -> runnable(3);
Runnable postInit = () -> runnable(4);
taskQueue.execute("headInvalidate", TaskType.HEAD_INVALIDATE, headInvalidate);
taskQueue.execute("background", TaskType.BACKGROUND, background);
taskQueue.execute("userFacing", TaskType.USER_FACING, userFacing);
taskQueue.execute("postInit", TaskType.IMMEDIATE, postInit);
assertThat(taskQueue.hasBacklog()).isTrue();
taskQueue.reset();
assertThat(taskQueue.isDelayed()).isTrue();
assertThat(taskQueue.hasBacklog()).isFalse();
taskQueue.initialize(this::noOp);
assertThat(taskQueue.isDelayed()).isFalse();
}
@Test
public void testStarvation_initialization() {
taskQueue.initialize(this::noOp);
taskQueue.execute("delayedTask", TaskType.BACKGROUND, this::delayedTask);
assertThat(delayedTaskHasRun).isTrue();
assertThat(taskQueue.isDelayed()).isFalse();
}
@Test
public void testStarvation_headInvalidate() {
taskQueue.initialize(this::noOp);
taskQueue.execute("headInvalidate", TaskType.HEAD_INVALIDATE, this::noOp);
taskQueue.execute("delayedTask", TaskType.BACKGROUND, this::delayedTask);
assertThat(fakeMainThreadRunner.getCompletedTaskCount()).isEqualTo(0);
assertThat(delayedTaskHasRun).isFalse();
assertThat(taskQueue.isDelayed()).isTrue();
runAndAssertStarvationChecks();
assertThat(delayedTaskHasRun).isTrue();
assertThat(taskQueue.isDelayed()).isFalse();
assertThat(fakeBasicLoggingApi.lastInternalError)
.isEqualTo(InternalFeedError.TASK_QUEUE_STARVATION);
}
@Test
public void testStarvation_reset() {
taskQueue.initialize(this::noOp);
taskQueue.reset();
taskQueue.execute("delayedTask", TaskType.BACKGROUND, this::delayedTask);
assertThat(delayedTaskHasRun).isFalse();
assertThat(taskQueue.isDelayed()).isTrue();
runAndAssertStarvationChecks();
assertThat(delayedTaskHasRun).isTrue();
assertThat(taskQueue.isDelayed()).isFalse();
}
private void runAndAssertStarvationChecks() {
int starvationTaskCount = 0;
long startTimeMillis = fakeClock.currentTimeMillis();
while (fakeClock.currentTimeMillis() - startTimeMillis < TaskQueue.STARVATION_TIMEOUT_MS) {
fakeClock.advance(TaskQueue.STARVATION_CHECK_MS);
starvationTaskCount++;
assertThat(fakeMainThreadRunner.getCompletedTaskCount()).isEqualTo(starvationTaskCount);
}
}
private void assertOrder(Integer... args) {
assertThat(callOrder.size()).isEqualTo(args.length);
for (int i = 0; i < args.length; i++) {
assertThat(callOrder.get(i)).isEqualTo(args[i]);
}
}
private void runnable(int taskId) {
callOrder.add(taskId);
}
private void noOp() {}
private void delayedTask() {
delayedTaskHasRun = true;
}
}