blob: 71661452d80072b52385265854c7d3be49c9ef69 [file] [log] [blame]
Peter Visontay859206c2018-01-03 15:43:19 +00001/*
2 * Copyright (C) 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 */
Svet Ganov8455ba22019-01-02 13:05:56 -080016package com.android.server.appop;
Peter Visontay859206c2018-01-03 15:43:19 +000017
Amith Yamasani23d4cd72019-04-10 17:57:00 -070018import static android.app.ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE;
19import static android.app.ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE_LOCATION;
Peter Visontay859206c2018-01-03 15:43:19 +000020import static android.app.AppOpsManager.MODE_ALLOWED;
21import static android.app.AppOpsManager.MODE_ERRORED;
Amith Yamasani23d4cd72019-04-10 17:57:00 -070022import static android.app.AppOpsManager.MODE_FOREGROUND;
Peter Visontay76712362018-04-19 22:04:14 +010023import static android.app.AppOpsManager.OP_COARSE_LOCATION;
Peter Visontay859206c2018-01-03 15:43:19 +000024import static android.app.AppOpsManager.OP_READ_SMS;
Peter Visontay76712362018-04-19 22:04:14 +010025import static android.app.AppOpsManager.OP_WIFI_SCAN;
Peter Visontay859206c2018-01-03 15:43:19 +000026import static android.app.AppOpsManager.OP_WRITE_SMS;
27
28import static com.google.common.truth.Truth.assertThat;
29import static com.google.common.truth.Truth.assertWithMessage;
30
Amith Yamasani23d4cd72019-04-10 17:57:00 -070031import android.app.ActivityManager;
Winson4e3b4352019-05-07 16:29:59 -070032import android.app.AppOpsManager;
Peter Visontay859206c2018-01-03 15:43:19 +000033import android.app.AppOpsManager.OpEntry;
34import android.app.AppOpsManager.PackageOps;
35import android.content.Context;
36import android.os.Handler;
37import android.os.HandlerThread;
38import android.os.Process;
Winson4e3b4352019-05-07 16:29:59 -070039import android.os.RemoteCallback;
Svet Ganovdc0f4432019-06-04 14:16:51 -070040import android.provider.Settings;
Brett Chabota26eda92018-07-23 13:08:30 -070041
42import androidx.test.InstrumentationRegistry;
43import androidx.test.filters.SmallTest;
44import androidx.test.runner.AndroidJUnit4;
Peter Visontay859206c2018-01-03 15:43:19 +000045
Svet Ganovdc0f4432019-06-04 14:16:51 -070046import org.junit.AfterClass;
Peter Visontay859206c2018-01-03 15:43:19 +000047import org.junit.Before;
Svet Ganovdc0f4432019-06-04 14:16:51 -070048import org.junit.BeforeClass;
Philip P. Moltmannf816bb52019-06-27 13:11:19 -070049import org.junit.Ignore;
Peter Visontay859206c2018-01-03 15:43:19 +000050import org.junit.Test;
51import org.junit.runner.RunWith;
52
53import java.io.File;
54import java.util.List;
Winson4e3b4352019-05-07 16:29:59 -070055import java.util.concurrent.CountDownLatch;
56import java.util.concurrent.TimeUnit;
57import java.util.concurrent.atomic.AtomicReference;
Peter Visontay859206c2018-01-03 15:43:19 +000058
59/**
60 * Unit tests for AppOpsService. Covers functionality that is difficult to test using CTS tests
61 * or for which we can write more detailed unit tests than CTS tests (because the internal APIs are
62 * more finegrained data than the public ones).
63 */
64@SmallTest
65@RunWith(AndroidJUnit4.class)
66public class AppOpsServiceTest {
67
68 private static final String TAG = AppOpsServiceTest.class.getSimpleName();
69 // State will be persisted into this XML file.
70 private static final String APP_OPS_FILENAME = "appops-service-test.xml";
71
72 private File mAppOpsFile;
73 private Context mContext;
74 private Handler mHandler;
Svet Ganovdc0f4432019-06-04 14:16:51 -070075 private AppOpsManager mAppOpsManager;
Peter Visontay859206c2018-01-03 15:43:19 +000076 private AppOpsService mAppOpsService;
77 private String mMyPackageName;
78 private int mMyUid;
79 private long mTestStartMillis;
80
Svet Ganovdc0f4432019-06-04 14:16:51 -070081 private static String sDefaultAppopHistoryParameters;
82
Peter Visontay859206c2018-01-03 15:43:19 +000083 @Before
84 public void setUp() {
85 mContext = InstrumentationRegistry.getTargetContext();
86 mAppOpsFile = new File(mContext.getFilesDir(), APP_OPS_FILENAME);
87 if (mAppOpsFile.exists()) {
88 // Start with a clean state (persisted into XML).
89 mAppOpsFile.delete();
90 }
91
92 HandlerThread handlerThread = new HandlerThread(TAG);
93 handlerThread.start();
94 mHandler = new Handler(handlerThread.getLooper());
95 mMyPackageName = mContext.getOpPackageName();
96 mMyUid = Process.myUid();
97
Svet Ganovdc0f4432019-06-04 14:16:51 -070098 mAppOpsManager = mContext.getSystemService(AppOpsManager.class);
Peter Visontay859206c2018-01-03 15:43:19 +000099 mAppOpsService = new AppOpsService(mAppOpsFile, mHandler);
Svet Ganovdc0f4432019-06-04 14:16:51 -0700100 mAppOpsService.mHistoricalRegistry.systemReady(mContext.getContentResolver());
Peter Visontay859206c2018-01-03 15:43:19 +0000101 mAppOpsService.mContext = mContext;
102 mTestStartMillis = System.currentTimeMillis();
103 }
104
Svet Ganovdc0f4432019-06-04 14:16:51 -0700105 @BeforeClass
106 public static void configureDesiredAppopHistoryParameters() {
107 final Context context = InstrumentationRegistry.getTargetContext();
108 sDefaultAppopHistoryParameters = Settings.Global.getString(context.getContentResolver(),
109 Settings.Global.APPOP_HISTORY_PARAMETERS);
110 Settings.Global.putString(InstrumentationRegistry.getTargetContext().getContentResolver(),
111 Settings.Global.APPOP_HISTORY_PARAMETERS, null);
112 }
113
114 @AfterClass
115 public static void restoreDefaultAppopHistoryParameters() {
116 Settings.Global.putString(InstrumentationRegistry.getTargetContext().getContentResolver(),
117 Settings.Global.APPOP_HISTORY_PARAMETERS,
118 sDefaultAppopHistoryParameters);
119 }
120
Peter Visontay859206c2018-01-03 15:43:19 +0000121 @Test
122 public void testGetOpsForPackage_noOpsLogged() {
123 assertThat(getLoggedOps()).isNull();
124 }
125
126 @Test
127 public void testNoteOperationAndGetOpsForPackage() {
128 mAppOpsService.setMode(OP_READ_SMS, mMyUid, mMyPackageName, MODE_ALLOWED);
129 mAppOpsService.setMode(OP_WRITE_SMS, mMyUid, mMyPackageName, MODE_ERRORED);
130
131 // Note an op that's allowed.
132 mAppOpsService.noteOperation(OP_READ_SMS, mMyUid, mMyPackageName);
133 List<PackageOps> loggedOps = getLoggedOps();
134 assertContainsOp(loggedOps, OP_READ_SMS, mTestStartMillis, -1, MODE_ALLOWED);
135
136 // Note another op that's not allowed.
137 mAppOpsService.noteOperation(OP_WRITE_SMS, mMyUid, mMyPackageName);
138 loggedOps = getLoggedOps();
139 assertContainsOp(loggedOps, OP_READ_SMS, mTestStartMillis, -1, MODE_ALLOWED);
140 assertContainsOp(loggedOps, OP_WRITE_SMS, -1, mTestStartMillis, MODE_ERRORED);
141 }
142
Peter Visontay76712362018-04-19 22:04:14 +0100143 /**
144 * Tests the scenario where an operation's permission is controlled by another operation.
145 * For example the results of a WIFI_SCAN can be used to infer the location of a user, so the
146 * ACCESS_COARSE_LOCATION op is used to check whether WIFI_SCAN is allowed.
147 */
148 @Test
149 public void testNoteOperationAndGetOpsForPackage_controlledByDifferentOp() {
150 // This op controls WIFI_SCAN
151 mAppOpsService.setMode(OP_COARSE_LOCATION, mMyUid, mMyPackageName, MODE_ALLOWED);
152
153 assertThat(mAppOpsService.noteOperation(OP_WIFI_SCAN, mMyUid, mMyPackageName))
154 .isEqualTo(MODE_ALLOWED);
155
156 assertContainsOp(getLoggedOps(), OP_WIFI_SCAN, mTestStartMillis, -1,
157 MODE_ALLOWED /* default for WIFI_SCAN; this is not changed or used in this test */);
158
159 // Now set COARSE_LOCATION to ERRORED -> this will make WIFI_SCAN disabled as well.
160 mAppOpsService.setMode(OP_COARSE_LOCATION, mMyUid, mMyPackageName, MODE_ERRORED);
161 assertThat(mAppOpsService.noteOperation(OP_WIFI_SCAN, mMyUid, mMyPackageName))
162 .isEqualTo(MODE_ERRORED);
163
164 assertContainsOp(getLoggedOps(), OP_WIFI_SCAN, mTestStartMillis, mTestStartMillis,
165 MODE_ALLOWED /* default for WIFI_SCAN; this is not changed or used in this test */);
166 }
167
Peter Visontay859206c2018-01-03 15:43:19 +0000168 // Tests the dumping and restoring of the in-memory state to/from XML.
169 @Test
170 public void testStatePersistence() {
171 mAppOpsService.setMode(OP_READ_SMS, mMyUid, mMyPackageName, MODE_ALLOWED);
172 mAppOpsService.setMode(OP_WRITE_SMS, mMyUid, mMyPackageName, MODE_ERRORED);
173 mAppOpsService.noteOperation(OP_READ_SMS, mMyUid, mMyPackageName);
174 mAppOpsService.noteOperation(OP_WRITE_SMS, mMyUid, mMyPackageName);
175 mAppOpsService.writeState();
176
177 // Create a new app ops service, and initialize its state from XML.
178 mAppOpsService = new AppOpsService(mAppOpsFile, mHandler);
179 mAppOpsService.mContext = mContext;
180 mAppOpsService.readState();
181
182 // Query the state of the 2nd service.
183 List<PackageOps> loggedOps = getLoggedOps();
184 assertContainsOp(loggedOps, OP_READ_SMS, mTestStartMillis, -1, MODE_ALLOWED);
185 assertContainsOp(loggedOps, OP_WRITE_SMS, -1, mTestStartMillis, MODE_ERRORED);
186 }
187
188 // Tests that ops are persisted during shutdown.
189 @Test
190 public void testShutdown() {
191 mAppOpsService.setMode(OP_READ_SMS, mMyUid, mMyPackageName, MODE_ALLOWED);
192 mAppOpsService.noteOperation(OP_READ_SMS, mMyUid, mMyPackageName);
193 mAppOpsService.shutdown();
194
195 // Create a new app ops service, and initialize its state from XML.
196 mAppOpsService = new AppOpsService(mAppOpsFile, mHandler);
197 mAppOpsService.mContext = mContext;
198 mAppOpsService.readState();
199
200 // Query the state of the 2nd service.
201 List<PackageOps> loggedOps = getLoggedOps();
202 assertContainsOp(loggedOps, OP_READ_SMS, mTestStartMillis, -1, MODE_ALLOWED);
203 }
204
205 @Test
206 public void testGetOpsForPackage() {
207 mAppOpsService.setMode(OP_READ_SMS, mMyUid, mMyPackageName, MODE_ALLOWED);
208 mAppOpsService.noteOperation(OP_READ_SMS, mMyUid, mMyPackageName);
209
210 // Query all ops
211 List<PackageOps> loggedOps = mAppOpsService.getOpsForPackage(
212 mMyUid, mMyPackageName, null /* all ops */);
213 assertContainsOp(loggedOps, OP_READ_SMS, mTestStartMillis, -1, MODE_ALLOWED);
214
215 // Query specific ops
216 loggedOps = mAppOpsService.getOpsForPackage(
217 mMyUid, mMyPackageName, new int[]{OP_READ_SMS, OP_WRITE_SMS});
218 assertContainsOp(loggedOps, OP_READ_SMS, mTestStartMillis, -1, MODE_ALLOWED);
219
220 // Query unknown UID
221 loggedOps = mAppOpsService.getOpsForPackage(mMyUid + 1, mMyPackageName, null /* all ops */);
222 assertThat(loggedOps).isNull();
223
224 // Query unknown package name
225 loggedOps = mAppOpsService.getOpsForPackage(mMyUid, "fake.package", null /* all ops */);
226 assertThat(loggedOps).isNull();
227
228 // Query op code that's not been logged
229 loggedOps = mAppOpsService.getOpsForPackage(mMyUid, mMyPackageName,
230 new int[]{OP_WRITE_SMS});
231 assertThat(loggedOps).isNull();
232 }
233
234 @Test
235 public void testPackageRemoved() {
236 mAppOpsService.setMode(OP_READ_SMS, mMyUid, mMyPackageName, MODE_ALLOWED);
237 mAppOpsService.noteOperation(OP_READ_SMS, mMyUid, mMyPackageName);
238
239 List<PackageOps> loggedOps = getLoggedOps();
240 assertContainsOp(loggedOps, OP_READ_SMS, mTestStartMillis, -1, MODE_ALLOWED);
241
242 mAppOpsService.packageRemoved(mMyUid, mMyPackageName);
243 assertThat(getLoggedOps()).isNull();
244 }
245
Philip P. Moltmannf816bb52019-06-27 13:11:19 -0700246 @Ignore("Historical appops are disabled in Android Q")
Peter Visontay859206c2018-01-03 15:43:19 +0000247 @Test
Winson4e3b4352019-05-07 16:29:59 -0700248 public void testPackageRemovedHistoricalOps() throws InterruptedException {
249 mAppOpsService.setMode(OP_READ_SMS, mMyUid, mMyPackageName, MODE_ALLOWED);
250 mAppOpsService.noteOperation(OP_READ_SMS, mMyUid, mMyPackageName);
251
252 AppOpsManager.HistoricalOps historicalOps = new AppOpsManager.HistoricalOps(0, 15000);
253 historicalOps.increaseAccessCount(OP_READ_SMS, mMyUid, mMyPackageName,
254 AppOpsManager.UID_STATE_PERSISTENT, 0, 1);
255
256 mAppOpsService.addHistoricalOps(historicalOps);
257
258 AtomicReference<AppOpsManager.HistoricalOps> resultOpsRef = new AtomicReference<>();
259 AtomicReference<CountDownLatch> latchRef = new AtomicReference<>(new CountDownLatch(1));
260 RemoteCallback callback = new RemoteCallback(result -> {
261 resultOpsRef.set(result.getParcelable(AppOpsManager.KEY_HISTORICAL_OPS));
262 latchRef.get().countDown();
263 });
264
265 // First, do a fetch to ensure it's written
266 mAppOpsService.getHistoricalOps(mMyUid, mMyPackageName, null, 0, Long.MAX_VALUE, 0,
267 callback);
268
269 latchRef.get().await(5, TimeUnit.SECONDS);
270 assertThat(latchRef.get().getCount()).isEqualTo(0);
271 assertThat(resultOpsRef.get().isEmpty()).isFalse();
272
273 // Then, check it's deleted on removal
274 mAppOpsService.packageRemoved(mMyUid, mMyPackageName);
275
276 latchRef.set(new CountDownLatch(1));
277
278 mAppOpsService.getHistoricalOps(mMyUid, mMyPackageName, null, 0, Long.MAX_VALUE, 0,
279 callback);
280
281 latchRef.get().await(5, TimeUnit.SECONDS);
282 assertThat(latchRef.get().getCount()).isEqualTo(0);
283 assertThat(resultOpsRef.get().isEmpty()).isTrue();
284 }
285
286 @Test
Peter Visontay859206c2018-01-03 15:43:19 +0000287 public void testUidRemoved() {
288 mAppOpsService.setMode(OP_READ_SMS, mMyUid, mMyPackageName, MODE_ALLOWED);
289 mAppOpsService.noteOperation(OP_READ_SMS, mMyUid, mMyPackageName);
290
291 List<PackageOps> loggedOps = getLoggedOps();
292 assertContainsOp(loggedOps, OP_READ_SMS, mTestStartMillis, -1, MODE_ALLOWED);
293
294 mAppOpsService.uidRemoved(mMyUid);
295 assertThat(getLoggedOps()).isNull();
296 }
297
Amith Yamasani23d4cd72019-04-10 17:57:00 -0700298 private void setupProcStateTests() {
299 // For the location proc state tests
300 mAppOpsService.setMode(OP_COARSE_LOCATION, mMyUid, mMyPackageName, MODE_FOREGROUND);
301 mAppOpsService.mConstants.FG_SERVICE_STATE_SETTLE_TIME = 0;
302 mAppOpsService.mConstants.TOP_STATE_SETTLE_TIME = 0;
303 mAppOpsService.mConstants.BG_STATE_SETTLE_TIME = 0;
304 }
305
306 @Test
307 public void testUidProcStateChange_cachedToTopToCached() throws Exception {
308 setupProcStateTests();
309
310 mAppOpsService.updateUidProcState(mMyUid, ActivityManager.PROCESS_STATE_CACHED_EMPTY);
311 assertThat(mAppOpsService.noteOperation(OP_COARSE_LOCATION, mMyUid, mMyPackageName))
312 .isNotEqualTo(MODE_ALLOWED);
313
314 mAppOpsService.updateUidProcState(mMyUid, ActivityManager.PROCESS_STATE_TOP);
315 assertThat(mAppOpsService.noteOperation(OP_COARSE_LOCATION, mMyUid, mMyPackageName))
316 .isEqualTo(MODE_ALLOWED);
317
318 mAppOpsService.updateUidProcState(mMyUid, ActivityManager.PROCESS_STATE_CACHED_EMPTY);
319 // Second time to make sure that settle time is overcome
320 Thread.sleep(50);
321 mAppOpsService.updateUidProcState(mMyUid, ActivityManager.PROCESS_STATE_CACHED_EMPTY);
322 assertThat(mAppOpsService.noteOperation(OP_COARSE_LOCATION, mMyUid, mMyPackageName))
323 .isNotEqualTo(MODE_ALLOWED);
324 }
325
326 @Test
327 public void testUidProcStateChange_cachedToFgs() throws Exception {
328 setupProcStateTests();
329
330 mAppOpsService.updateUidProcState(mMyUid, ActivityManager.PROCESS_STATE_CACHED_EMPTY);
331 assertThat(mAppOpsService.noteOperation(OP_COARSE_LOCATION, mMyUid, mMyPackageName))
332 .isNotEqualTo(MODE_ALLOWED);
333
334 mAppOpsService.updateUidProcState(mMyUid, PROCESS_STATE_FOREGROUND_SERVICE);
335 assertThat(mAppOpsService.noteOperation(OP_COARSE_LOCATION, mMyUid, mMyPackageName))
336 .isNotEqualTo(MODE_ALLOWED);
337 }
338
339 @Test
340 public void testUidProcStateChange_cachedToFgsLocation() throws Exception {
341 setupProcStateTests();
342
343 mAppOpsService.updateUidProcState(mMyUid, ActivityManager.PROCESS_STATE_CACHED_EMPTY);
344 assertThat(mAppOpsService.noteOperation(OP_COARSE_LOCATION, mMyUid, mMyPackageName))
345 .isNotEqualTo(MODE_ALLOWED);
346
347 mAppOpsService.updateUidProcState(mMyUid,
348 PROCESS_STATE_FOREGROUND_SERVICE_LOCATION);
349 assertThat(mAppOpsService.noteOperation(OP_COARSE_LOCATION, mMyUid, mMyPackageName))
350 .isEqualTo(MODE_ALLOWED);
351 }
352
353 @Test
354 public void testUidProcStateChange_topToFgs() throws Exception {
355 setupProcStateTests();
356
357 mAppOpsService.updateUidProcState(mMyUid, ActivityManager.PROCESS_STATE_CACHED_EMPTY);
358 assertThat(mAppOpsService.noteOperation(OP_COARSE_LOCATION, mMyUid, mMyPackageName))
359 .isNotEqualTo(MODE_ALLOWED);
360
361 mAppOpsService.updateUidProcState(mMyUid, ActivityManager.PROCESS_STATE_TOP);
362 assertThat(mAppOpsService.noteOperation(OP_COARSE_LOCATION, mMyUid, mMyPackageName))
363 .isEqualTo(MODE_ALLOWED);
364
365 mAppOpsService.updateUidProcState(mMyUid, PROCESS_STATE_FOREGROUND_SERVICE);
366 // Second time to make sure that settle time is overcome
367 Thread.sleep(50);
368 mAppOpsService.updateUidProcState(mMyUid, PROCESS_STATE_FOREGROUND_SERVICE);
369 assertThat(mAppOpsService.noteOperation(OP_COARSE_LOCATION, mMyUid, mMyPackageName))
370 .isNotEqualTo(MODE_ALLOWED);
371 }
372
373 @Test
374 public void testUidProcStateChange_topToFgsLocationToFgs() throws Exception {
375 setupProcStateTests();
376
377 mAppOpsService.updateUidProcState(mMyUid, ActivityManager.PROCESS_STATE_CACHED_EMPTY);
378 assertThat(mAppOpsService.noteOperation(OP_COARSE_LOCATION, mMyUid, mMyPackageName))
379 .isNotEqualTo(MODE_ALLOWED);
380
381 mAppOpsService.updateUidProcState(mMyUid, ActivityManager.PROCESS_STATE_TOP);
382 assertThat(mAppOpsService.noteOperation(OP_COARSE_LOCATION, mMyUid, mMyPackageName))
383 .isEqualTo(MODE_ALLOWED);
384
385 mAppOpsService.updateUidProcState(mMyUid, PROCESS_STATE_FOREGROUND_SERVICE_LOCATION);
386 // Second time to make sure that settle time is overcome
387 Thread.sleep(50);
388 mAppOpsService.updateUidProcState(mMyUid, PROCESS_STATE_FOREGROUND_SERVICE_LOCATION);
389 assertThat(mAppOpsService.noteOperation(OP_COARSE_LOCATION, mMyUid, mMyPackageName))
390 .isEqualTo(MODE_ALLOWED);
391
392 mAppOpsService.updateUidProcState(mMyUid, PROCESS_STATE_FOREGROUND_SERVICE);
393 // Second time to make sure that settle time is overcome
394 Thread.sleep(50);
395 mAppOpsService.updateUidProcState(mMyUid, PROCESS_STATE_FOREGROUND_SERVICE);
396 assertThat(mAppOpsService.noteOperation(OP_COARSE_LOCATION, mMyUid, mMyPackageName))
397 .isNotEqualTo(MODE_ALLOWED);
398 }
399
Peter Visontay859206c2018-01-03 15:43:19 +0000400 private List<PackageOps> getLoggedOps() {
401 return mAppOpsService.getOpsForPackage(mMyUid, mMyPackageName, null /* all ops */);
402 }
403
404 private void assertContainsOp(List<PackageOps> loggedOps, int opCode, long minMillis,
405 long minRejectMillis, int mode) {
406
407 boolean opLogged = false;
408 for (PackageOps pkgOps : loggedOps) {
409 assertWithMessage("Unexpected UID").that(mMyUid).isEqualTo(pkgOps.getUid());
410 assertWithMessage("Unexpected package name").that(mMyPackageName).isEqualTo(
411 pkgOps.getPackageName());
412
413 for (OpEntry opEntry : pkgOps.getOps()) {
414 if (opCode != opEntry.getOp()) {
415 continue;
416 }
417 opLogged = true;
418
419 assertWithMessage("Unexpected mode").that(mode).isEqualTo(opEntry.getMode());
420 if (minMillis > 0) {
421 assertWithMessage("Unexpected timestamp")
422 .that(opEntry.getTime()).isAtLeast(minMillis);
423 }
424 if (minRejectMillis > 0) {
425 assertWithMessage("Unexpected rejection timestamp")
426 .that(opEntry.getRejectTime()).isAtLeast(minRejectMillis);
427 }
428 }
429 }
430 assertWithMessage("Op was not logged").that(opLogged).isTrue();
431 }
432}