1 /* 2 * Copyright 2018 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package androidx.recyclerview.widget; 18 19 import static org.hamcrest.CoreMatchers.is; 20 import static org.hamcrest.CoreMatchers.not; 21 import static org.hamcrest.CoreMatchers.notNullValue; 22 import static org.hamcrest.CoreMatchers.sameInstance; 23 import static org.hamcrest.MatcherAssert.assertThat; 24 25 import android.os.Looper; 26 import android.support.test.filters.MediumTest; 27 import android.support.test.rule.ActivityTestRule; 28 import android.support.test.runner.AndroidJUnit4; 29 30 import org.junit.Before; 31 import org.junit.Rule; 32 import org.junit.Test; 33 import org.junit.runner.RunWith; 34 35 import java.util.HashMap; 36 import java.util.Map; 37 import java.util.concurrent.Semaphore; 38 import java.util.concurrent.TimeUnit; 39 40 @RunWith(AndroidJUnit4.class) 41 @MediumTest 42 public class ThreadUtilTest { 43 @Rule 44 public ActivityTestRule<TestActivity> mActivityRule = 45 new ActivityTestRule<>(TestActivity.class); 46 47 Map<String, LockedObject> results = new HashMap<>(); 48 49 ThreadUtil.MainThreadCallback<Integer> mMainThreadProxy; 50 ThreadUtil.BackgroundCallback<Integer> mBackgroundProxy; 51 52 @Before setup()53 public void setup() throws Throwable { 54 mActivityRule.runOnUiThread(new Runnable() { 55 @Override 56 public void run() { 57 ThreadUtil<Integer> threadUtil = new MessageThreadUtil<>(); 58 59 mMainThreadProxy = threadUtil.getMainThreadProxy( 60 new ThreadUtil.MainThreadCallback<Integer>() { 61 @Override 62 public void updateItemCount(int generation, int itemCount) { 63 assertMainThread(); 64 setResultData("updateItemCount", generation, itemCount); 65 } 66 67 @Override 68 public void addTile(int generation, TileList.Tile<Integer> data) { 69 assertMainThread(); 70 setResultData("addTile", generation, data); 71 } 72 73 @Override 74 public void removeTile(int generation, int position) { 75 assertMainThread(); 76 setResultData("removeTile", generation, position); 77 } 78 }); 79 80 mBackgroundProxy = threadUtil.getBackgroundProxy( 81 new ThreadUtil.BackgroundCallback<Integer>() { 82 @Override 83 public void refresh(int generation) { 84 assertBackgroundThread(); 85 setResultData("refresh", generation); 86 } 87 88 @Override 89 public void updateRange(int rangeStart, int rangeEnd, int extRangeStart, 90 int extRangeEnd, int scrollHint) { 91 assertBackgroundThread(); 92 setResultData("updateRange", rangeStart, rangeEnd, 93 extRangeStart, extRangeEnd, scrollHint); 94 } 95 96 @Override 97 public void loadTile(int position, int scrollHint) { 98 assertBackgroundThread(); 99 setResultData("loadTile", position, scrollHint); 100 } 101 102 @Override 103 public void recycleTile(TileList.Tile<Integer> data) { 104 assertBackgroundThread(); 105 setResultData("recycleTile", data); 106 } 107 }); 108 } 109 }); 110 } 111 112 @Test updateItemCount()113 public void updateItemCount() throws InterruptedException { 114 initWait("updateItemCount"); 115 // In this test and below the calls to mMainThreadProxy are not really made from the UI 116 // thread. That's OK since the message queue inside mMainThreadProxy is synchronized. 117 mMainThreadProxy.updateItemCount(7, 9); 118 Object[] data = waitFor("updateItemCount"); 119 assertThat(data, is(new Object[]{7, 9})); 120 } 121 122 @Test addTile()123 public void addTile() throws InterruptedException { 124 initWait("addTile"); 125 TileList.Tile<Integer> tile = new TileList.Tile<Integer>(Integer.class, 10); 126 mMainThreadProxy.addTile(3, tile); 127 Object[] data = waitFor("addTile"); 128 assertThat(data, is(new Object[]{3, tile})); 129 } 130 131 @Test removeTile()132 public void removeTile() throws InterruptedException { 133 initWait("removeTile"); 134 mMainThreadProxy.removeTile(1, 2); 135 Object[] data = waitFor("removeTile"); 136 assertThat(data, is(new Object[]{1, 2})); 137 } 138 139 @Test refresh()140 public void refresh() throws InterruptedException { 141 initWait("refresh"); 142 // In this test and below the calls to mBackgroundProxy are not really made from the worker 143 // thread. That's OK since the message queue inside mBackgroundProxy is synchronized. 144 mBackgroundProxy.refresh(2); 145 Object[] data = waitFor("refresh"); 146 assertThat(data, is(new Object[]{2})); 147 } 148 149 @Test rangeUpdate()150 public void rangeUpdate() throws InterruptedException { 151 initWait("updateRange"); 152 mBackgroundProxy.updateRange(10, 20, 5, 25, 1); 153 Object[] data = waitFor("updateRange"); 154 assertThat(data, is(new Object[] {10, 20, 5, 25, 1})); 155 } 156 157 @Test loadTile()158 public void loadTile() throws InterruptedException { 159 initWait("loadTile"); 160 mBackgroundProxy.loadTile(2, 1); 161 Object[] data = waitFor("loadTile"); 162 assertThat(data, is(new Object[]{2, 1})); 163 } 164 165 @Test recycleTile()166 public void recycleTile() throws InterruptedException { 167 initWait("recycleTile"); 168 TileList.Tile<Integer> tile = new TileList.Tile<Integer>(Integer.class, 10); 169 mBackgroundProxy.recycleTile(tile); 170 Object[] data = waitFor("recycleTile"); 171 assertThat(data, is(new Object[]{tile})); 172 } 173 assertMainThread()174 private void assertMainThread() { 175 assertThat(Looper.myLooper(), notNullValue()); 176 assertThat(Looper.myLooper(), sameInstance(Looper.getMainLooper())); 177 } 178 assertBackgroundThread()179 private void assertBackgroundThread() { 180 assertThat(Looper.myLooper(), not(Looper.getMainLooper())); 181 } 182 initWait(String key)183 private void initWait(String key) throws InterruptedException { 184 results.put(key, new LockedObject()); 185 } 186 waitFor(String key)187 private Object[] waitFor(String key) throws InterruptedException { 188 return results.get(key).waitFor(); 189 } 190 setResultData(String key, Object... args)191 private void setResultData(String key, Object... args) { 192 if (results.containsKey(key)) { 193 results.get(key).set(args); 194 } 195 } 196 197 private class LockedObject { 198 private Semaphore mLock = new Semaphore(1); 199 private volatile Object[] mArgs; 200 LockedObject()201 public LockedObject() { 202 mLock.drainPermits(); 203 } 204 set(Object... args)205 public void set(Object... args) { 206 mArgs = args; 207 mLock.release(1); 208 } 209 waitFor()210 public Object[] waitFor() throws InterruptedException { 211 mLock.tryAcquire(1, 2, TimeUnit.SECONDS); 212 return mArgs; 213 } 214 } 215 } 216