/** * Copyright 2012 Facebook * * 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.facebook.widget; import android.test.suitebuilder.annotation.LargeTest; import android.test.suitebuilder.annotation.MediumTest; import android.test.suitebuilder.annotation.SmallTest; import com.facebook.FacebookTestCase; import com.facebook.widget.WorkQueue; import java.util.ArrayList; import java.util.concurrent.Executor; import java.security.SecureRandom; public class WorkQueueTests extends FacebookTestCase { @SmallTest @MediumTest @LargeTest public void testEmptyValidate() { WorkQueue manager = new WorkQueue(); manager.validate(); } @SmallTest @MediumTest @LargeTest public void testRunSomething() { CountingRunnable run = new CountingRunnable(); assertEquals(0, run.getRunCount()); ScriptableExecutor executor = new ScriptableExecutor(); assertEquals(0, executor.getPendingCount()); WorkQueue manager = new WorkQueue(1, executor); addActiveWorkItem(manager, run); assertEquals(1, executor.getPendingCount()); assertEquals(0, run.getRunCount()); executeNext(manager, executor); assertEquals(0, executor.getPendingCount()); assertEquals(1, run.getRunCount()); } @SmallTest @MediumTest @LargeTest public void testRunSequence() { final int workTotal = 100; CountingRunnable run = new CountingRunnable(); ScriptableExecutor executor = new ScriptableExecutor(); WorkQueue manager = new WorkQueue(1, executor); for (int i = 0; i < workTotal; i++) { addActiveWorkItem(manager, run); assertEquals(1, executor.getPendingCount()); } for (int i = 0; i < workTotal; i++) { assertEquals(1, executor.getPendingCount()); assertEquals(i, run.getRunCount()); executeNext(manager, executor); } assertEquals(0, executor.getPendingCount()); assertEquals(workTotal, run.getRunCount()); } @SmallTest @MediumTest @LargeTest public void testRunParallel() { final int workTotal = 100; CountingRunnable run = new CountingRunnable(); ScriptableExecutor executor = new ScriptableExecutor(); WorkQueue manager = new WorkQueue(workTotal, executor); for (int i = 0; i < workTotal; i++) { assertEquals(i, executor.getPendingCount()); addActiveWorkItem(manager, run); } for (int i = 0; i < workTotal; i++) { assertEquals(workTotal - i, executor.getPendingCount()); assertEquals(i, run.getRunCount()); executeNext(manager, executor); } assertEquals(0, executor.getPendingCount()); assertEquals(workTotal, run.getRunCount()); } @SmallTest @MediumTest @LargeTest public void testSimpleCancel() { CountingRunnable run = new CountingRunnable(); ScriptableExecutor executor = new ScriptableExecutor(); WorkQueue manager = new WorkQueue(1, executor); addActiveWorkItem(manager, run); WorkQueue.WorkItem work1 = addActiveWorkItem(manager, run); cancelWork(manager, work1); assertEquals(1, executor.getPendingCount()); executeNext(manager, executor); assertEquals(0, executor.getPendingCount()); } @SmallTest @MediumTest @LargeTest public void testMoveToFront() { final int firstCount = 8; final int highCount = 17; ArrayList<WorkQueue.WorkItem> highWorkItems = new ArrayList<WorkQueue.WorkItem>(); CountingRunnable highRun = new CountingRunnable(); CountingRunnable firstRun = new CountingRunnable(); CountingRunnable lowRun = new CountingRunnable(); ScriptableExecutor executor = new ScriptableExecutor(); WorkQueue manager = new WorkQueue(firstCount, executor); for (int i = 0; i < firstCount; i++) { addActiveWorkItem(manager, firstRun); } int lowCount = 0; for (int h = 0; h < highCount; h++) { highWorkItems.add(addActiveWorkItem(manager, highRun)); for (int l = 0; l < h; l++) { addActiveWorkItem(manager, lowRun); lowCount++; } } assertEquals(firstCount, executor.getPendingCount()); for (WorkQueue.WorkItem highItem : highWorkItems) { prioritizeWork(manager, highItem); } for (int i = 0; i < firstCount; i++) { assertEquals(i, firstRun.getRunCount()); executeNext(manager, executor); } for (int i = 0; i < highCount; i++) { assertEquals(i, highRun.getRunCount()); executeNext(manager, executor); } for (int i = 0; i < lowCount; i++) { assertEquals(i, lowRun.getRunCount()); executeNext(manager, executor); } assertEquals(firstCount, firstRun.getRunCount()); assertEquals(highCount, highRun.getRunCount()); assertEquals(lowCount, lowRun.getRunCount()); } // Test cancelling running work item, completed work item @LargeTest public void testThreadStress() { WorkQueue manager = new WorkQueue(); ArrayList<StressRunnable> runnables = new ArrayList<StressRunnable>(); final int threadCount = 20; for (int i = 0; i < threadCount; i++) { runnables.add(new StressRunnable(manager, 20)); } for (int i = 0; i < threadCount; i++) { manager.addActiveWorkItem(runnables.get(i)); } for (int i = 0; i < threadCount; i++) { runnables.get(i).waitForDone(); } } private WorkQueue.WorkItem addActiveWorkItem(WorkQueue manager, Runnable runnable) { manager.validate(); WorkQueue.WorkItem workItem = manager.addActiveWorkItem(runnable); manager.validate(); return workItem; } private void executeNext(WorkQueue manager, ScriptableExecutor executor) { manager.validate(); executor.runNext(); manager.validate(); } private void cancelWork(WorkQueue manager, WorkQueue.WorkItem workItem) { manager.validate(); workItem.cancel(); manager.validate(); } private void prioritizeWork(WorkQueue manager, WorkQueue.WorkItem workItem) { manager.validate(); workItem.moveToFront(); manager.validate(); } static class StressRunnable implements Runnable { static ArrayList<WorkQueue.WorkItem> tracked = new ArrayList<WorkQueue.WorkItem>(); final WorkQueue manager; final SecureRandom random = new SecureRandom(); final int iterationCount; int iterationIndex = 0; boolean isDone = false; StressRunnable(WorkQueue manager, int iterationCount) { this.manager = manager; this.iterationCount = iterationCount; } @Override public void run() { // Each iteration runs a random action against the WorkQueue. if (iterationIndex++ < iterationCount) { final int sleepWeight = 80; final int trackThisWeight = 10; final int prioritizeTrackedWeight = 6; final int validateWeight = 2; int weight = 0; final int n = random.nextInt(sleepWeight + trackThisWeight + prioritizeTrackedWeight + validateWeight); WorkQueue.WorkItem workItem = manager.addActiveWorkItem(this); if (n < (weight += sleepWeight)) { // Sleep try { Thread.sleep(n/4); } catch (InterruptedException e) { } } else if (n < (weight += trackThisWeight)) { // Track this work item to activate later synchronized (tracked) { tracked.add(workItem); } } else if (n < (weight += prioritizeTrackedWeight)) { // Background all pending items, prioritize tracked items, and clear tracked list ArrayList<WorkQueue.WorkItem> items = new ArrayList<WorkQueue.WorkItem>(); synchronized (tracked) { items.addAll(tracked); tracked.clear(); } for (WorkQueue.WorkItem item : items) { item.moveToFront(); } } else { // Validate manager.validate(); } } else { // Also have all threads validate once they are done. manager.validate(); synchronized (this) { isDone = true; this.notifyAll(); } } } void waitForDone() { synchronized (this) { while (!isDone) { try { this.wait(); } catch (InterruptedException e) { } } } } } class ScriptableExecutor implements Executor { private final ArrayList<Runnable> runnables = new ArrayList<Runnable>(); int getPendingCount() { return runnables.size(); } void runNext() { assertTrue(runnables.size() > 0); runnables.get(0).run(); runnables.remove(0); } void runLast() { assertTrue(runnables.size() > 0); int index = runnables.size() - 1; runnables.get(index).run(); runnables.remove(index); } @Override public void execute(Runnable runnable) { synchronized (this) { runnables.add(runnable); } } } class CountingRunnable implements Runnable { volatile int runCount = 0; int getRunCount() { return runCount; } @Override public void run() { synchronized (this) { runCount++; } try { Thread.sleep(1); } catch (InterruptedException e) { } } } }