1 /* 2 * Copyright 2023 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 android.adservices.ondevicepersonalization; 18 19 import static com.google.common.truth.Truth.assertThat; 20 21 import static org.junit.Assert.assertArrayEquals; 22 import static org.junit.Assert.assertNull; 23 import static org.junit.Assert.assertTrue; 24 25 import android.adservices.ondevicepersonalization.aidl.IDataAccessService; 26 import android.adservices.ondevicepersonalization.aidl.IDataAccessServiceCallback; 27 import android.os.Bundle; 28 import android.os.RemoteException; 29 30 import androidx.test.ext.junit.runners.AndroidJUnit4; 31 import androidx.test.filters.SmallTest; 32 33 import com.android.ondevicepersonalization.internal.util.ByteArrayParceledSlice; 34 35 import org.junit.Before; 36 import org.junit.Test; 37 import org.junit.runner.RunWith; 38 39 import java.util.HashMap; 40 import java.util.HashSet; 41 import java.util.Objects; 42 import java.util.Set; 43 import java.util.concurrent.CountDownLatch; 44 import java.util.concurrent.TimeUnit; 45 46 /** 47 * Unit Tests of LocalDataImpl API. 48 */ 49 @SmallTest 50 @RunWith(AndroidJUnit4.class) 51 public class LocalDataTest { 52 MutableKeyValueStore mLocalData; 53 LocalDataService mLocalDataService; 54 private static final long TIMEOUT_MILLI = 5000; 55 56 @Before setup()57 public void setup() { 58 mLocalDataService = new LocalDataService(); 59 mLocalData = new LocalDataImpl(IDataAccessService.Stub.asInterface(mLocalDataService)); 60 } 61 62 @Test testLookupSuccess()63 public void testLookupSuccess() throws Exception { 64 assertArrayEquals(new byte[] {1, 2, 3}, mLocalData.get("a")); 65 assertArrayEquals(new byte[] {7, 8, 9}, mLocalData.get("c")); 66 assertNull(mLocalData.get("e")); 67 68 assertTrue( 69 "Failed to await countdown latch!", 70 mLocalDataService.mLatch.await(TIMEOUT_MILLI, TimeUnit.MILLISECONDS)); 71 assertThat(mLocalDataService.mResponseCode).isEqualTo(Constants.STATUS_SUCCESS); 72 } 73 74 @Test testLookupError()75 public void testLookupError() throws InterruptedException { 76 // Triggers an expected error in the mock service. 77 assertNull(mLocalData.get("z")); 78 79 assertTrue( 80 "Failed to await countdown latch!", 81 mLocalDataService.mLatch.await(TIMEOUT_MILLI, TimeUnit.MILLISECONDS)); 82 assertThat(mLocalDataService.mResponseCode).isEqualTo(Constants.STATUS_SERVICE_FAILED); 83 } 84 85 @Test testLookupEmptyResult()86 public void testLookupEmptyResult() throws Exception { 87 // Triggers an expected error in the mock service. 88 assertNull(mLocalData.get("empty")); 89 90 assertTrue( 91 "Failed to await countdown latch!", 92 mLocalDataService.mLatch.await(TIMEOUT_MILLI, TimeUnit.MILLISECONDS)); 93 assertThat(mLocalDataService.mResponseCode) 94 .isEqualTo(Constants.STATUS_SUCCESS_EMPTY_RESULT); 95 } 96 97 @Test testKeysetSuccess()98 public void testKeysetSuccess() throws InterruptedException { 99 Set<String> expectedResult = new HashSet<>(); 100 expectedResult.add("a"); 101 expectedResult.add("b"); 102 expectedResult.add("c"); 103 104 assertThat(expectedResult).isEqualTo(mLocalData.keySet()); 105 assertTrue( 106 "Failed to await countdown latch!", 107 mLocalDataService.mLatch.await(TIMEOUT_MILLI, TimeUnit.MILLISECONDS)); 108 assertThat(mLocalDataService.mResponseCode).isEqualTo(Constants.STATUS_SUCCESS); 109 } 110 111 @Test testPutSuccess()112 public void testPutSuccess() throws Exception { 113 assertArrayEquals(new byte[] {1, 2, 3}, mLocalData.put("a", new byte[10])); 114 assertNull(mLocalData.put("e", new byte[] {1, 2, 3})); 115 assertArrayEquals(new byte[] {1, 2, 3}, mLocalData.get("e")); 116 117 assertTrue( 118 "Failed to await countdown latch!", 119 mLocalDataService.mLatch.await(TIMEOUT_MILLI, TimeUnit.MILLISECONDS)); 120 assertThat(mLocalDataService.mResponseCode).isEqualTo(Constants.STATUS_SUCCESS); 121 } 122 123 @Test testPutError()124 public void testPutError() throws InterruptedException { 125 // Triggers an expected error in the mock service. 126 assertNull(mLocalData.put("z", new byte[10])); 127 128 assertTrue( 129 "Failed to await countdown latch!", 130 mLocalDataService.mLatch.await(TIMEOUT_MILLI, TimeUnit.MILLISECONDS)); 131 assertThat(mLocalDataService.mResponseCode).isEqualTo(Constants.STATUS_SERVICE_FAILED); 132 } 133 134 @Test testRemoveSuccess()135 public void testRemoveSuccess() throws Exception { 136 assertArrayEquals(new byte[] {1, 2, 3}, mLocalData.remove("a")); 137 assertNull(mLocalData.remove("e")); 138 assertNull(mLocalData.get("a")); 139 140 assertTrue( 141 "Failed to await countdown latch!", 142 mLocalDataService.mLatch.await(TIMEOUT_MILLI, TimeUnit.MILLISECONDS)); 143 assertThat(mLocalDataService.mResponseCode).isEqualTo(Constants.STATUS_SUCCESS); 144 } 145 146 @Test testRemoveError()147 public void testRemoveError() throws InterruptedException { 148 // Triggers an expected error in the mock service. 149 assertNull(mLocalData.remove("z")); 150 151 assertTrue( 152 "Failed to await countdown latch!", 153 mLocalDataService.mLatch.await(TIMEOUT_MILLI, TimeUnit.MILLISECONDS)); 154 assertThat(mLocalDataService.mResponseCode).isEqualTo(Constants.STATUS_SERVICE_FAILED); 155 } 156 157 public static class LocalDataService extends IDataAccessService.Stub { 158 HashMap<String, byte[]> mContents = new HashMap<String, byte[]>(); 159 int mResponseCode; 160 CountDownLatch mLatch = new CountDownLatch(1); 161 LocalDataService()162 public LocalDataService() { 163 mContents.put("a", new byte[] {1, 2, 3}); 164 mContents.put("b", new byte[] {4, 5, 6}); 165 mContents.put("c", new byte[] {7, 8, 9}); 166 } 167 168 @Override onRequest( int operation, Bundle params, IDataAccessServiceCallback callback)169 public void onRequest( 170 int operation, 171 Bundle params, 172 IDataAccessServiceCallback callback) { 173 174 if (operation == Constants.DATA_ACCESS_OP_LOCAL_DATA_KEYSET) { 175 Bundle result = new Bundle(); 176 result.putSerializable(Constants.EXTRA_RESULT, 177 new HashSet<>(mContents.keySet())); 178 try { 179 callback.onSuccess(result); 180 } catch (RemoteException e) { 181 // Ignored. 182 } 183 return; 184 } 185 186 String key = params.getString(Constants.EXTRA_LOOKUP_KEYS); 187 Objects.requireNonNull(key); 188 if (key.equals("empty")) { 189 // Raise expected error. 190 try { 191 callback.onSuccess(null); 192 } catch (RemoteException e) { 193 // Ignored. 194 } 195 return; 196 } 197 if (key.equals("z")) { 198 // Raise expected error. 199 try { 200 callback.onError(Constants.STATUS_SERVICE_FAILED); 201 } catch (RemoteException e) { 202 // Ignored. 203 } 204 return; 205 } 206 ByteArrayParceledSlice parceledByteArray = params.getParcelable( 207 Constants.EXTRA_VALUE, ByteArrayParceledSlice.class); 208 byte[] value = null; 209 if (parceledByteArray != null) { 210 value = parceledByteArray.getByteArray(); 211 } 212 213 if (operation == Constants.DATA_ACCESS_OP_LOCAL_DATA_LOOKUP 214 || operation == Constants.DATA_ACCESS_OP_LOCAL_DATA_PUT 215 || operation == Constants.DATA_ACCESS_OP_LOCAL_DATA_REMOVE) { 216 byte[] existingValue = null; 217 if (mContents.containsKey(key)) { 218 existingValue = mContents.get(key); 219 } 220 if (operation == Constants.DATA_ACCESS_OP_LOCAL_DATA_REMOVE) { 221 mContents.remove(key); 222 } 223 if (operation == Constants.DATA_ACCESS_OP_LOCAL_DATA_PUT) { 224 mContents.put(key, value); 225 } 226 Bundle result = new Bundle(); 227 result.putParcelable( 228 Constants.EXTRA_RESULT, new ByteArrayParceledSlice(existingValue)); 229 try { 230 callback.onSuccess(result); 231 } catch (RemoteException e) { 232 // Ignored. 233 } 234 } 235 } 236 237 @Override logApiCallStats(int apiName, long latencyMillis, int responseCode)238 public void logApiCallStats(int apiName, long latencyMillis, int responseCode) { 239 mLatch.countDown(); 240 mResponseCode = responseCode; 241 } 242 } 243 } 244