1 /* 2 * Copyright (C) 2022 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 com.android.adservices.service.measurement; 18 19 import static android.view.MotionEvent.ACTION_BUTTON_PRESS; 20 import static android.view.MotionEvent.obtain; 21 22 import static com.android.adservices.service.Flags.MEASUREMENT_MAX_REPORTING_REGISTER_SOURCE_EXPIRATION_IN_SECONDS; 23 import static com.android.adservices.service.Flags.MEASUREMENT_MIN_REPORTING_REGISTER_SOURCE_EXPIRATION_IN_SECONDS; 24 import static com.android.adservices.service.measurement.reporting.AggregateReportSender.AGGREGATE_ATTRIBUTION_REPORT_URI_PATH; 25 import static com.android.adservices.service.measurement.reporting.AggregateReportSender.DEBUG_AGGREGATE_ATTRIBUTION_REPORT_URI_PATH; 26 import static com.android.adservices.service.measurement.reporting.DebugReportSender.DEBUG_REPORT_URI_PATH; 27 import static com.android.adservices.service.measurement.reporting.EventReportSender.DEBUG_EVENT_ATTRIBUTION_REPORT_URI_PATH; 28 import static com.android.adservices.service.measurement.reporting.EventReportSender.EVENT_ATTRIBUTION_REPORT_URI_PATH; 29 30 import android.content.res.AssetManager; 31 import android.database.Cursor; 32 import android.database.DatabaseUtils; 33 import android.database.sqlite.SQLiteDatabase; 34 import android.provider.DeviceConfig; 35 import android.util.Log; 36 import android.view.InputDevice; 37 import android.view.InputEvent; 38 import android.view.MotionEvent.PointerCoords; 39 import android.view.MotionEvent.PointerProperties; 40 41 import androidx.annotation.Nullable; 42 43 import com.android.adservices.common.AdServicesUnitTestCase; 44 import com.android.adservices.data.DbTestUtil; 45 import com.android.adservices.service.FlagsConstants; 46 import com.android.adservices.service.measurement.actions.Action; 47 import com.android.adservices.service.measurement.actions.AggregateReportingJob; 48 import com.android.adservices.service.measurement.actions.EventReportingJob; 49 import com.android.adservices.service.measurement.actions.InstallApp; 50 import com.android.adservices.service.measurement.actions.RegisterListSources; 51 import com.android.adservices.service.measurement.actions.RegisterSource; 52 import com.android.adservices.service.measurement.actions.RegisterTrigger; 53 import com.android.adservices.service.measurement.actions.RegisterWebSource; 54 import com.android.adservices.service.measurement.actions.RegisterWebTrigger; 55 import com.android.adservices.service.measurement.actions.ReportObjects; 56 import com.android.adservices.service.measurement.actions.UninstallApp; 57 import com.android.adservices.service.measurement.actions.UriConfig; 58 59 import com.google.common.collect.ImmutableList; 60 61 import org.json.JSONArray; 62 import org.json.JSONException; 63 import org.json.JSONObject; 64 import org.junit.Assert; 65 import org.junit.Test; 66 67 import java.io.IOException; 68 import java.io.InputStream; 69 import java.nio.charset.StandardCharsets; 70 import java.util.ArrayList; 71 import java.util.Arrays; 72 import java.util.Collection; 73 import java.util.Collections; 74 import java.util.Comparator; 75 import java.util.HashMap; 76 import java.util.HashSet; 77 import java.util.Iterator; 78 import java.util.List; 79 import java.util.Map; 80 import java.util.Set; 81 import java.util.concurrent.TimeUnit; 82 import java.util.function.Function; 83 84 /** 85 * End-to-end test from source and trigger registration to attribution reporting. Extensions of this 86 * class can implement different ways to prepare the registrations, either with an external server 87 * or mocking HTTP responses, for example; similarly for examining the attribution reports. 88 * 89 * <p>Consider @RunWith(Parameterized.class) 90 */ 91 public abstract class E2ETest extends AdServicesUnitTestCase { 92 // Used to fuzzy-match expected report (not delivery) time 93 private static final String LOG_TAG = "ADSERVICES_MSMT_E2E_TEST"; 94 95 private final String mName; 96 private final Collection<Action> mActionsList; 97 final ReportObjects mExpectedOutput; 98 private final Map<String, String> mPhFlagsMap; 99 // Extenders of the class populate in their own ways this container for actual output. 100 final ReportObjects mActualOutput; 101 102 public static final long AGGREGATE_REPORT_DELAY = TimeUnit.HOURS.toMillis(1); 103 104 enum ReportType { 105 EVENT, 106 AGGREGATE, 107 EVENT_DEBUG, 108 AGGREGATE_DEBUG, 109 VERBOSE_DEBUG 110 } 111 112 private enum OutputType { 113 EXPECTED, 114 ACTUAL 115 } 116 117 private interface EventReportPayloadKeys { 118 // Keys used to compare actual with expected output 119 List<String> STRINGS = 120 ImmutableList.of( 121 "scheduled_report_time", 122 "source_event_id", 123 "trigger_data", 124 "source_type", 125 "source_debug_key", 126 "trigger_debug_key", 127 "trigger_summary_bucket"); 128 String DOUBLE = "randomized_trigger_rate"; 129 String STRING_OR_ARRAY = "attribution_destination"; 130 String ARRAY = "trigger_debug_keys"; 131 } 132 133 interface AggregateReportPayloadKeys { 134 String SHARED_INFO = "shared_info"; 135 String AGGREGATION_COORDINATOR_ORIGIN = "aggregation_coordinator_origin"; 136 String HISTOGRAMS = "histograms"; 137 String SOURCE_DEBUG_KEY = "source_debug_key"; 138 String TRIGGER_DEBUG_KEY = "trigger_debug_key"; 139 String TRIGGER_CONTEXT_ID = "trigger_context_id"; 140 } 141 142 static List<String> sAggregateReportSharedInfoKeys = 143 ImmutableList.of( 144 "api", 145 "attribution_destination", 146 "debug_mode", 147 "reporting_origin", 148 "scheduled_report_time", 149 "version", 150 "source_registration_time"); 151 152 interface DebugReportPayloadKeys { 153 String TYPE = "type"; 154 String BODY = "body"; 155 List<String> BODY_KEYS = 156 ImmutableList.of( 157 "attribution_destination", 158 "limit", 159 "randomized_trigger_rate", 160 "scheduled_report_time", 161 "source_debug_key", 162 "source_event_id", 163 "source_site", 164 "source_type", 165 "trigger_debug_key"); 166 } 167 168 interface AggregateHistogramKeys { 169 String BUCKET = "key"; 170 String VALUE = "value"; 171 } 172 173 public interface TestFormatJsonMapping { 174 String DEFAULT_CONFIG_FILENAME = "default_config.json"; 175 String API_CONFIG_KEY = "api_config"; 176 String PH_FLAGS_OVERRIDE_KEY = "phflags_override"; 177 String TEST_INPUT_KEY = "input"; 178 String TEST_OUTPUT_KEY = "output"; 179 String REGISTRATIONS_KEY = "registrations"; 180 String SOURCE_REGISTRATIONS_KEY = "sources"; 181 String WEB_SOURCES_KEY = "web_sources"; 182 String LIST_SOURCES_KEY = "list_sources"; 183 String SOURCE_PARAMS_REGISTRATIONS_KEY = "source_params"; 184 String TRIGGERS_KEY = "triggers"; 185 String WEB_TRIGGERS_KEY = "web_triggers"; 186 String TRIGGER_PARAMS_REGISTRATIONS_KEY = "trigger_params"; 187 String URI_TO_RESPONSE_HEADERS_KEY = "responses"; 188 String URI_TO_RESPONSE_HEADERS_URL_KEY = "url"; 189 String URI_TO_RESPONSE_HEADERS_RESPONSE_KEY = "response"; 190 String REGISTRATION_REQUEST_KEY = "registration_request"; 191 String ATTRIBUTION_SOURCE_KEY = "registrant"; 192 String ATTRIBUTION_SOURCE_DEFAULT = "com.interop.app"; 193 String CONTEXT_ORIGIN_URI_KEY = "context_origin"; 194 String SOURCE_TOP_ORIGIN_URI_KEY = "source_origin"; 195 String TRIGGER_TOP_ORIGIN_URI_KEY = "destination_origin"; 196 String SOURCE_APP_DESTINATION_URI_KEY = "app_destination"; 197 String SOURCE_WEB_DESTINATION_URI_KEY = "web_destination"; 198 String SOURCE_VERIFIED_DESTINATION_URI_KEY = "verified_destination"; 199 String REGISTRATION_URI_KEY = "attribution_src_url"; 200 String REGISTRATION_URIS_KEY = "attribution_src_urls"; 201 String HAS_AD_ID_PERMISSION = "has_ad_id_permission"; 202 String DEBUG_KEY = "debug_key"; 203 String DEBUG_PERMISSION_KEY = "debug_permission"; 204 String DEBUG_REPORTING_KEY = "debug_reporting"; 205 String INPUT_EVENT_KEY = "source_type"; 206 String SOURCE_VIEW_TYPE = "event"; 207 String INTEROP_INPUT_EVENT_KEY = "Attribution-Reporting-Eligible"; 208 String INTEROP_SOURCE_VIEW_TYPE = "event-source"; 209 String SOURCE_REGISTRATION_HEADER = "Attribution-Reporting-Register-Source"; 210 String TRIGGER_REGISTRATION_HEADER = "Attribution-Reporting-Register-Trigger"; 211 String TIMESTAMP_KEY = "timestamp"; 212 String REPORTS_OBJECTS_KEY = "reports"; 213 String EVENT_REPORT_OBJECTS_KEY = "event_level_results"; 214 String AGGREGATE_REPORT_OBJECTS_KEY = "aggregatable_results"; 215 String DEBUG_EVENT_REPORT_OBJECTS_KEY = "debug_event_level_results"; 216 String DEBUG_AGGREGATE_REPORT_OBJECTS_KEY = "debug_aggregatable_results"; 217 String VERBOSE_DEBUG_OBJECTS_KEY = "verbose_debug_reports"; 218 String INSTALLS_KEY = "installs"; 219 String UNINSTALLS_KEY = "uninstalls"; 220 String INSTALLS_URI_KEY = "uri"; 221 String INSTALLS_TIMESTAMP_KEY = "timestamp"; 222 String REPORT_TIME_KEY = "report_time"; 223 String REPORT_TO_KEY = "report_url"; 224 String PAYLOAD_KEY = "payload"; 225 String ENROLL = "enroll"; 226 String PLATFORM_AD_ID = "platform_ad_id"; 227 String SOURCE_REGISTRATION_TIME = "source_registration_time"; 228 String SCHEDULED_REPORT_TIME = "scheduled_report_time"; 229 } 230 231 private interface ApiConfigKeys { 232 // Privacy params 233 String NAVIGATION_SOURCE_TRIGGER_DATA_CARDINALITY = 234 "navigation_source_trigger_data_cardinality"; 235 } 236 237 public static class ParamsProvider { 238 // Privacy params 239 private Integer mNavigationTriggerDataCardinality; ParamsProvider(JSONObject json)240 public ParamsProvider(JSONObject json) throws JSONException { 241 // Privacy params 242 if (!json.isNull(ApiConfigKeys.NAVIGATION_SOURCE_TRIGGER_DATA_CARDINALITY)) { 243 mNavigationTriggerDataCardinality = json.getInt( 244 ApiConfigKeys.NAVIGATION_SOURCE_TRIGGER_DATA_CARDINALITY); 245 } else { 246 mNavigationTriggerDataCardinality = 247 PrivacyParams.getNavigationTriggerDataCardinality(); 248 } 249 } 250 251 // Privacy params getNavigationTriggerDataCardinality()252 public Integer getNavigationTriggerDataCardinality() { 253 return mNavigationTriggerDataCardinality; 254 } 255 } 256 data(String testDirName, Function<String, String> preprocessor)257 static Collection<Object[]> data(String testDirName, Function<String, String> preprocessor) 258 throws IOException, JSONException { 259 return data(testDirName, preprocessor, new HashMap<>()); 260 } 261 data(String testDirName, Function<String, String> preprocessor, Map<String, String> apiConfigPhFlags)262 static Collection<Object[]> data(String testDirName, Function<String, String> preprocessor, 263 Map<String, String> apiConfigPhFlags) throws IOException, JSONException { 264 AssetManager assetManager = sContext.getAssets(); 265 List<InputStream> inputStreams = new ArrayList<>(); 266 List<String> dirPathList = new ArrayList<>(Collections.singletonList(testDirName)); 267 List<String> testFileList = new ArrayList<>(); 268 while (dirPathList.size() > 0) { 269 testDirName = dirPathList.remove(0); 270 String[] testAssets = assetManager.list(testDirName); 271 for (String testAsset : testAssets) { 272 if (isDirectory(testDirName + "/" + testAsset)) { 273 dirPathList.add(testDirName + "/" + testAsset); 274 } else { 275 inputStreams.add(assetManager.open(testDirName + "/" + testAsset)); 276 testFileList.add(testAsset); 277 } 278 } 279 } 280 return getTestCasesFrom( 281 inputStreams, 282 testFileList.stream().toArray(String[]::new), 283 preprocessor, 284 apiConfigPhFlags); 285 } 286 isDirectory(String testAssetName)287 private static boolean isDirectory(String testAssetName) throws IOException { 288 String[] assetList = sContext.getAssets().list(testAssetName); 289 if (assetList.length > 0) { 290 return true; 291 } 292 return false; 293 } 294 295 /** Returns the first URL in the list of registration responses. */ getFirstUrl(JSONObject registrationObj)296 public static String getFirstUrl(JSONObject registrationObj) 297 throws JSONException { 298 return registrationObj 299 .getJSONArray(TestFormatJsonMapping.URI_TO_RESPONSE_HEADERS_KEY) 300 .getJSONObject(0) 301 .getString(TestFormatJsonMapping.URI_TO_RESPONSE_HEADERS_URL_KEY); 302 } 303 hasArDebugPermission(JSONObject obj)304 public static boolean hasArDebugPermission(JSONObject obj) throws JSONException { 305 JSONObject urlToResponse = 306 obj.getJSONArray(TestFormatJsonMapping.URI_TO_RESPONSE_HEADERS_KEY) 307 .getJSONObject(0); 308 return urlToResponse.optBoolean(TestFormatJsonMapping.DEBUG_PERMISSION_KEY, false); 309 } 310 hasAdIdPermission(JSONObject obj)311 public static boolean hasAdIdPermission(JSONObject obj) throws JSONException { 312 JSONObject urlToResponse = 313 obj.getJSONArray(TestFormatJsonMapping.URI_TO_RESPONSE_HEADERS_KEY) 314 .getJSONObject(0); 315 return urlToResponse.optBoolean(TestFormatJsonMapping.HAS_AD_ID_PERMISSION, false); 316 } 317 hasSourceDebugReportingPermission(JSONObject obj)318 public static boolean hasSourceDebugReportingPermission(JSONObject obj) throws JSONException { 319 JSONObject headersMapJson = 320 obj.getJSONArray(TestFormatJsonMapping.URI_TO_RESPONSE_HEADERS_KEY) 321 .getJSONObject(0) 322 .getJSONObject(TestFormatJsonMapping.URI_TO_RESPONSE_HEADERS_RESPONSE_KEY); 323 if (headersMapJson.isNull(TestFormatJsonMapping.SOURCE_REGISTRATION_HEADER)) { 324 return false; 325 } 326 Object registerSourceObj = 327 headersMapJson.get(TestFormatJsonMapping.SOURCE_REGISTRATION_HEADER); 328 if (!(registerSourceObj instanceof JSONObject)) { 329 return false; 330 } 331 return ((JSONObject) registerSourceObj).optBoolean( 332 TestFormatJsonMapping.DEBUG_REPORTING_KEY, false); 333 } 334 hasTriggerDebugReportingPermission(JSONObject obj)335 public static boolean hasTriggerDebugReportingPermission(JSONObject obj) throws JSONException { 336 JSONObject headersMapJson = 337 obj.getJSONArray(TestFormatJsonMapping.URI_TO_RESPONSE_HEADERS_KEY) 338 .getJSONObject(0) 339 .getJSONObject(TestFormatJsonMapping.URI_TO_RESPONSE_HEADERS_RESPONSE_KEY); 340 if (headersMapJson.isNull(TestFormatJsonMapping.TRIGGER_REGISTRATION_HEADER)) { 341 return false; 342 } 343 Object registerTriggerObj = 344 headersMapJson.get(TestFormatJsonMapping.TRIGGER_REGISTRATION_HEADER); 345 if (!(registerTriggerObj instanceof JSONObject)) { 346 return false; 347 } 348 return ((JSONObject) registerTriggerObj).optBoolean( 349 TestFormatJsonMapping.DEBUG_REPORTING_KEY, false); 350 } 351 getUriToResponseHeadersMap( JSONObject obj)352 public static Map<String, List<Map<String, List<String>>>> getUriToResponseHeadersMap( 353 JSONObject obj) throws JSONException { 354 JSONArray uriToResArray = obj.getJSONArray( 355 TestFormatJsonMapping.URI_TO_RESPONSE_HEADERS_KEY); 356 Map<String, List<Map<String, List<String>>>> uriToResponseHeadersMap = new HashMap<>(); 357 358 for (int i = 0; i < uriToResArray.length(); i++) { 359 JSONObject urlToResponse = uriToResArray.getJSONObject(i); 360 String uri = urlToResponse.getString( 361 TestFormatJsonMapping.URI_TO_RESPONSE_HEADERS_URL_KEY); 362 JSONObject headersMapJson = urlToResponse.getJSONObject( 363 TestFormatJsonMapping.URI_TO_RESPONSE_HEADERS_RESPONSE_KEY); 364 365 Iterator<String> headers = headersMapJson.keys(); 366 Map<String, List<String>> headersMap = new HashMap<>(); 367 368 while (headers.hasNext()) { 369 String header = headers.next(); 370 if (!headersMapJson.isNull(header)) { 371 String data = headersMapJson.getString(header); 372 if (header.equals("Attribution-Reporting-Redirect")) { 373 JSONArray redirects = new JSONArray(data); 374 for (int j = 0; j < redirects.length(); j++) { 375 String redirectUri = redirects.getString(j); 376 headersMap.computeIfAbsent( 377 header, k -> new ArrayList<>()).add(redirectUri); 378 } 379 } else { 380 headersMap.put(header, Collections.singletonList(data)); 381 } 382 } else { 383 headersMap.put(header, null); 384 } 385 } 386 387 uriToResponseHeadersMap.computeIfAbsent(uri, k -> new ArrayList<>()).add(headersMap); 388 } 389 390 return uriToResponseHeadersMap; 391 } 392 getUriConfigMap(JSONObject obj)393 public static Map<String, UriConfig> getUriConfigMap(JSONObject obj) throws JSONException { 394 JSONArray uriToResArray = 395 obj.getJSONArray(TestFormatJsonMapping.URI_TO_RESPONSE_HEADERS_KEY); 396 Map<String, UriConfig> uriConfigMap = new HashMap<>(); 397 398 for (int i = 0; i < uriToResArray.length(); i++) { 399 JSONObject urlToResponse = uriToResArray.getJSONObject(i); 400 String uri = 401 urlToResponse.getString(TestFormatJsonMapping.URI_TO_RESPONSE_HEADERS_URL_KEY); 402 uriConfigMap.put(uri, new UriConfig(urlToResponse)); 403 } 404 405 return uriConfigMap; 406 } 407 getInputEvent()408 public static InputEvent getInputEvent() { 409 return obtain( 410 0 /*long downTime*/, 411 0 /*long eventTime*/, 412 ACTION_BUTTON_PRESS, 413 1 /*int pointerCount*/, 414 new PointerProperties[] { new PointerProperties() }, 415 new PointerCoords[] { new PointerCoords() }, 416 0 /*int metaState*/, 417 0 /*int buttonState*/, 418 1.0f /*float xPrecision*/, 419 1.0f /*float yPrecision*/, 420 0 /*int deviceId*/, 421 0 /*int edgeFlags*/, 422 InputDevice.SOURCE_TOUCH_NAVIGATION, 423 0 /*int flags*/); 424 } 425 getReportUrl(ReportType reportType, String origin)426 static String getReportUrl(ReportType reportType, String origin) { 427 String reportUrl = null; 428 if (reportType == ReportType.EVENT) { 429 reportUrl = EVENT_ATTRIBUTION_REPORT_URI_PATH; 430 } else if (reportType == ReportType.AGGREGATE) { 431 reportUrl = AGGREGATE_ATTRIBUTION_REPORT_URI_PATH; 432 } else if (reportType == ReportType.EVENT_DEBUG) { 433 reportUrl = DEBUG_EVENT_ATTRIBUTION_REPORT_URI_PATH; 434 } else if (reportType == ReportType.AGGREGATE_DEBUG) { 435 reportUrl = DEBUG_AGGREGATE_ATTRIBUTION_REPORT_URI_PATH; 436 } else if (reportType == ReportType.VERBOSE_DEBUG) { 437 reportUrl = DEBUG_REPORT_URI_PATH; 438 } 439 return origin + "/" + reportUrl; 440 } 441 clearDatabase()442 static void clearDatabase() { 443 SQLiteDatabase db = DbTestUtil.getMeasurementDbHelperForTest().getWritableDatabase(); 444 emptyTables(db); 445 446 DbTestUtil.getSharedDbHelperForTest() 447 .getWritableDatabase() 448 .delete("enrollment_data", null, null); 449 } 450 451 // The 'name' parameter is needed for the JUnit parameterized test, although it's ostensibly 452 // unused by this constructor. E2ETest( Collection<Action> actions, ReportObjects expectedOutput, String name, Map<String, String> phFlagsMap)453 E2ETest( 454 Collection<Action> actions, 455 ReportObjects expectedOutput, 456 String name, 457 Map<String, String> phFlagsMap) { 458 mActionsList = actions; 459 mExpectedOutput = expectedOutput; 460 mActualOutput = new ReportObjects(); 461 mName = name; 462 mPhFlagsMap = phFlagsMap; 463 } 464 465 @Test runTest()466 public void runTest() throws IOException, JSONException, DeviceConfig.BadConfigException { 467 clearDatabase(); 468 setupDeviceConfigForPhFlags(); 469 for (Action action : mActionsList) { 470 if (action instanceof RegisterSource) { 471 processAction((RegisterSource) action); 472 } else if (action instanceof RegisterTrigger) { 473 processAction((RegisterTrigger) action); 474 } else if (action instanceof RegisterWebSource) { 475 processAction((RegisterWebSource) action); 476 } else if (action instanceof RegisterWebTrigger) { 477 processAction((RegisterWebTrigger) action); 478 } else if (action instanceof RegisterListSources) { 479 processAction((RegisterListSources) action); 480 } else if (action instanceof EventReportingJob) { 481 processAction((EventReportingJob) action); 482 } else if (action instanceof AggregateReportingJob) { 483 processAction((AggregateReportingJob) action); 484 } else if (action instanceof InstallApp) { 485 processAction((InstallApp) action); 486 } else if (action instanceof UninstallApp) { 487 processAction((UninstallApp) action); 488 } 489 } 490 evaluateResults(); 491 clearDatabase(); 492 } 493 log(String message)494 public void log(String message) { 495 Log.i(LOG_TAG, String.format("%s: %s", mName, message)); 496 } 497 498 /** 499 * The reporting job may be handled differently depending on whether network requests are mocked 500 * or a test server is used. 501 */ processAction(EventReportingJob reportingJob)502 abstract void processAction(EventReportingJob reportingJob) throws IOException, JSONException; 503 504 /** 505 * The reporting job may be handled differently depending on whether network requests are mocked 506 * or a test server is used. 507 */ processAction(AggregateReportingJob reportingJob)508 abstract void processAction(AggregateReportingJob reportingJob) 509 throws IOException, JSONException; 510 511 /** 512 * Override with HTTP response mocks, for example. 513 */ prepareRegistrationServer(RegisterSource sourceRegistration)514 abstract void prepareRegistrationServer(RegisterSource sourceRegistration) 515 throws IOException; 516 517 /** Override with HTTP response mocks, for example. */ prepareRegistrationServer(RegisterListSources sourceRegistration)518 abstract void prepareRegistrationServer(RegisterListSources sourceRegistration) 519 throws IOException; 520 521 /** 522 * Override with HTTP response mocks, for example. 523 */ prepareRegistrationServer(RegisterTrigger triggerRegistration)524 abstract void prepareRegistrationServer(RegisterTrigger triggerRegistration) 525 throws IOException; 526 527 /** Override with HTTP response mocks, for example. */ prepareRegistrationServer(RegisterWebSource sourceRegistration)528 abstract void prepareRegistrationServer(RegisterWebSource sourceRegistration) 529 throws IOException; 530 531 /** Override with HTTP response mocks, for example. */ prepareRegistrationServer(RegisterWebTrigger triggerRegistration)532 abstract void prepareRegistrationServer(RegisterWebTrigger triggerRegistration) 533 throws IOException; 534 hashForEventReportObject(OutputType outputType, JSONObject obj)535 private static int hashForEventReportObject(OutputType outputType, JSONObject obj) { 536 int n = EventReportPayloadKeys.STRINGS.size(); 537 int numValuesExcludingN = 5; 538 Object[] objArray = new Object[n + numValuesExcludingN]; 539 objArray[0] = obj.optLong(TestFormatJsonMapping.REPORT_TIME_KEY, 0L); 540 String url = obj.optString(TestFormatJsonMapping.REPORT_TO_KEY, ""); 541 objArray[1] = 542 outputType == OutputType.EXPECTED ? url : getReportUrl(ReportType.EVENT, url); 543 JSONObject payload = obj.optJSONObject(TestFormatJsonMapping.PAYLOAD_KEY); 544 objArray[2] = payload.optDouble(EventReportPayloadKeys.DOUBLE, 0); 545 // Try string then JSONArray in order so as to override the string if the array parsing is 546 // successful. 547 objArray[3] = null; 548 String maybeString = payload.optString(EventReportPayloadKeys.STRING_OR_ARRAY); 549 if (maybeString != null) { 550 objArray[3] = maybeString; 551 } 552 JSONArray maybeArray1 = payload.optJSONArray(EventReportPayloadKeys.STRING_OR_ARRAY); 553 if (maybeArray1 != null) { 554 objArray[3] = maybeArray1; 555 } 556 JSONArray maybeArray2 = payload.optJSONArray(EventReportPayloadKeys.ARRAY); 557 objArray[4] = maybeArray2; 558 for (int i = 0; i < n; i++) { 559 objArray[i + numValuesExcludingN] = 560 payload.optString(EventReportPayloadKeys.STRINGS.get(i), ""); 561 } 562 return Arrays.hashCode(objArray); 563 } 564 565 // 'obj1' is the expected result, 'obj2' is the actual result. matchReportTimeAndReportTo(ReportType reportType, JSONObject obj1, JSONObject obj2)566 private boolean matchReportTimeAndReportTo(ReportType reportType, JSONObject obj1, 567 JSONObject obj2) throws JSONException { 568 if (obj1.getLong(TestFormatJsonMapping.REPORT_TIME_KEY) 569 != obj2.getLong(TestFormatJsonMapping.REPORT_TIME_KEY)) { 570 log("Report-time mismatch. Report type: " + reportType.name()); 571 return false; 572 } 573 String reportTo1 = obj1.getString(TestFormatJsonMapping.REPORT_TO_KEY); 574 String reportTo2 = getReportUrl(reportType, 575 obj2.getString(TestFormatJsonMapping.REPORT_TO_KEY)); 576 if (!reportTo1.equals(reportTo2)) { 577 log(String.format( 578 "Report-to mismatch. Report type: %s Report-to-1: %s Report-to-2: %s", 579 reportType.name(), reportTo1, reportTo2)); 580 return false; 581 } 582 return true; 583 } 584 areEqualStringOrJSONArray(Object expected, Object actual)585 private static boolean areEqualStringOrJSONArray(Object expected, Object actual) 586 throws JSONException { 587 if (expected instanceof String) { 588 return (actual instanceof String) && (expected.equals(actual)); 589 } else { 590 JSONArray jsonArr1 = (JSONArray) expected; 591 JSONArray jsonArr2 = (JSONArray) actual; 592 if (jsonArr1.length() != jsonArr2.length()) { 593 return false; 594 } 595 for (int i = 0; i < jsonArr1.length(); i++) { 596 if (!jsonArr1.getString(i).equals(jsonArr2.getString(i))) { 597 return false; 598 } 599 } 600 } 601 return true; 602 } 603 areNullOrEqualJSONArray(JSONArray expected, JSONArray actual)604 private static boolean areNullOrEqualJSONArray(JSONArray expected, JSONArray actual) 605 throws JSONException { 606 if (expected == null) { 607 return actual == null; 608 } else if (actual == null) { 609 return false; 610 } 611 if (expected.length() != actual.length()) { 612 return false; 613 } 614 for (int i = 0; i < expected.length(); i++) { 615 if (!expected.getString(i).equals(actual.getString(i))) { 616 return false; 617 } 618 } 619 return true; 620 } 621 areEqualEventReportJsons( ReportType reportType, JSONObject expected, JSONObject actual)622 private boolean areEqualEventReportJsons( 623 ReportType reportType, JSONObject expected, JSONObject actual) throws JSONException { 624 JSONObject expectedPayload = expected.getJSONObject(TestFormatJsonMapping.PAYLOAD_KEY); 625 JSONObject actualPayload = actual.getJSONObject(TestFormatJsonMapping.PAYLOAD_KEY); 626 if (expectedPayload.getDouble(EventReportPayloadKeys.DOUBLE) 627 != actualPayload.getDouble(EventReportPayloadKeys.DOUBLE)) { 628 log("Event payload double mismatch. Report type: " + reportType.name()); 629 return false; 630 } 631 if (!areEqualStringOrJSONArray( 632 expectedPayload.get(EventReportPayloadKeys.STRING_OR_ARRAY), 633 actualPayload.get(EventReportPayloadKeys.STRING_OR_ARRAY))) { 634 log("Event payload string-or-array mismatch. Report type: " + reportType.name()); 635 return false; 636 } 637 if (!areNullOrEqualJSONArray( 638 expectedPayload.optJSONArray(EventReportPayloadKeys.ARRAY), 639 actualPayload.optJSONArray(EventReportPayloadKeys.ARRAY))) { 640 log("Event payload array mismatch. Report type: " + reportType.name()); 641 return false; 642 } 643 for (String key : EventReportPayloadKeys.STRINGS) { 644 if (!expectedPayload.optString(key, "").equals(actualPayload.optString(key, ""))) { 645 log("Event payload string mismatch: " + key + ". Report type: " 646 + reportType.name()); 647 return false; 648 } 649 } 650 return matchReportTimeAndReportTo(reportType, expected, actual); 651 } 652 areEqualSharedInfoJsons(JSONObject obj1, JSONObject obj2)653 private boolean areEqualSharedInfoJsons(JSONObject obj1, JSONObject obj2) throws JSONException { 654 for (String key : sAggregateReportSharedInfoKeys) { 655 if (!obj1.optString(key, "").equals(obj2.optString(key, ""))) { 656 log("Aggregate shared_info mismatch for key " + key); 657 return false; 658 } 659 } 660 return true; 661 } 662 areEqualAggregateReportJsons( ReportType reportType, JSONObject expected, JSONObject actual)663 private boolean areEqualAggregateReportJsons( 664 ReportType reportType, JSONObject expected, JSONObject actual) throws JSONException { 665 JSONObject payload1 = expected.getJSONObject(TestFormatJsonMapping.PAYLOAD_KEY); 666 JSONObject payload2 = actual.getJSONObject(TestFormatJsonMapping.PAYLOAD_KEY); 667 if (!areEqualSharedInfoJsons(payload1.getJSONObject(AggregateReportPayloadKeys.SHARED_INFO), 668 payload2.getJSONObject(AggregateReportPayloadKeys.SHARED_INFO))) { 669 log("Aggregate report shared_info mismatch"); 670 return false; 671 } 672 if (!payload1.optString( 673 AggregateReportPayloadKeys.AGGREGATION_COORDINATOR_ORIGIN, "").equals( 674 payload2.optString( 675 AggregateReportPayloadKeys.AGGREGATION_COORDINATOR_ORIGIN, ""))) { 676 log("Aggregate aggregation coordinator origin mismatch"); 677 return false; 678 } 679 if (!payload1.optString(AggregateReportPayloadKeys.SOURCE_DEBUG_KEY, "") 680 .equals(payload2.optString(AggregateReportPayloadKeys.SOURCE_DEBUG_KEY, ""))) { 681 log("Source debug key mismatch"); 682 return false; 683 } 684 if (!payload1.optString(AggregateReportPayloadKeys.TRIGGER_DEBUG_KEY, "") 685 .equals(payload2.optString(AggregateReportPayloadKeys.TRIGGER_DEBUG_KEY, ""))) { 686 log("Trigger debug key mismatch"); 687 return false; 688 } 689 JSONArray histograms1 = payload1.optJSONArray(AggregateReportPayloadKeys.HISTOGRAMS); 690 JSONArray histograms2 = payload2.optJSONArray(AggregateReportPayloadKeys.HISTOGRAMS); 691 if (!getComparableHistograms(histograms1).equals(getComparableHistograms(histograms2))) { 692 log("Aggregate histogram mismatch"); 693 return false; 694 } 695 if (!payload1.optString(AggregateReportPayloadKeys.TRIGGER_CONTEXT_ID, "") 696 .equals(payload2.optString(AggregateReportPayloadKeys.TRIGGER_CONTEXT_ID, ""))) { 697 log("Trigger context id mismatch"); 698 return false; 699 } 700 701 return matchReportTimeAndReportTo(reportType, expected, actual); 702 } 703 areEqualDebugReportJsons( ReportType reportType, JSONObject expected, JSONObject actual)704 private boolean areEqualDebugReportJsons( 705 ReportType reportType, JSONObject expected, JSONObject actual) throws JSONException { 706 JSONArray payloads1 = expected.getJSONArray(TestFormatJsonMapping.PAYLOAD_KEY); 707 JSONArray payloads2 = actual.getJSONArray(TestFormatJsonMapping.PAYLOAD_KEY); 708 if (payloads1.length() != payloads2.length()) { 709 log("Debug report size mismatch"); 710 return false; 711 } 712 for (int i = 0; i < payloads1.length(); i++) { 713 JSONObject payload1 = payloads1.getJSONObject(i); 714 String type = payload1.optString(DebugReportPayloadKeys.TYPE, ""); 715 boolean hasSameType = false; 716 for (int j = 0; j < payloads2.length(); j++) { 717 JSONObject payload2 = payloads2.getJSONObject(j); 718 if (type.equals(payload2.optString(DebugReportPayloadKeys.TYPE, ""))) { 719 hasSameType = true; 720 JSONObject body1 = payload1.getJSONObject(DebugReportPayloadKeys.BODY); 721 JSONObject body2 = payload2.getJSONObject(DebugReportPayloadKeys.BODY); 722 if (body1.length() != body2.length()) { 723 log( 724 "Verbose debug report payload body key-value pair not equal for" 725 + " type: " 726 + type); 727 return false; 728 } 729 for (String key : DebugReportPayloadKeys.BODY_KEYS) { 730 if (!body1.optString(key, "").equals(body2.optString(key, ""))) { 731 log( 732 "Verbose debug report payload body mismatch for type: " 733 + type 734 + ", body key: " 735 + key); 736 return false; 737 } 738 } 739 break; 740 } 741 } 742 if (!hasSameType) { 743 log("Debug report type mismatch."); 744 return false; 745 } 746 } 747 return matchReportTimeAndReportTo(reportType, expected, actual); 748 } 749 getComparableHistograms(@ullable JSONArray arr)750 private static String getComparableHistograms(@Nullable JSONArray arr) { 751 if (arr == null) { 752 return ""; 753 } 754 try { 755 List<String> tempList = new ArrayList<>(); 756 for (int i = 0; i < arr.length(); i++) { 757 JSONObject pair = arr.getJSONObject(i); 758 tempList.add(pair.getString(AggregateHistogramKeys.BUCKET) + "," 759 + pair.getString(AggregateHistogramKeys.VALUE)); 760 } 761 Collections.sort(tempList); 762 return String.join(";", tempList); 763 } catch (JSONException ignored) { 764 return ""; 765 } 766 } 767 reportTimeFrom(JSONObject obj)768 private static long reportTimeFrom(JSONObject obj) { 769 return obj.optLong(TestFormatJsonMapping.REPORT_TIME_KEY, 0L); 770 } 771 aggregateReportToFrom(OutputType outputType, JSONObject obj)772 private static String aggregateReportToFrom(OutputType outputType, JSONObject obj) { 773 String url = obj.optString(TestFormatJsonMapping.REPORT_TO_KEY, ""); 774 return outputType == OutputType.EXPECTED 775 ? url 776 : getReportUrl(ReportType.AGGREGATE, url); 777 } 778 sourceRegistrationTimeFrom(JSONObject obj)779 private static String sourceRegistrationTimeFrom(JSONObject obj) { 780 JSONObject payload = obj.optJSONObject(TestFormatJsonMapping.PAYLOAD_KEY); 781 return payload.optJSONObject(AggregateReportPayloadKeys.SHARED_INFO) 782 .optString("source_registration_time", ""); 783 } 784 sortEventReportObjects(OutputType outputType, List<JSONObject> eventReportObjects)785 private static void sortEventReportObjects(OutputType outputType, 786 List<JSONObject> eventReportObjects) { 787 eventReportObjects.sort( 788 // Report time can vary across implementations so cannot be included in the hash; 789 // they should be similarly ordered, however, so we can use them to sort. 790 Comparator.comparing(E2ETest::reportTimeFrom) 791 .thenComparing(obj -> hashForEventReportObject(outputType, obj))); 792 } 793 sortAggregateReportObjects(OutputType outputType, List<JSONObject> aggregateReportObjects)794 private static void sortAggregateReportObjects(OutputType outputType, 795 List<JSONObject> aggregateReportObjects) { 796 aggregateReportObjects.sort( 797 Comparator.comparing(E2ETest::reportTimeFrom) 798 .thenComparing(E2ETest::sourceRegistrationTimeFrom) 799 .thenComparing(obj -> aggregateReportToFrom(outputType, obj))); 800 } 801 areEqual(ReportObjects expected, ReportObjects actual)802 private boolean areEqual(ReportObjects expected, ReportObjects actual) throws JSONException { 803 if (expected.mEventReportObjects.size() != actual.mEventReportObjects.size() 804 || expected.mAggregateReportObjects.size() != actual.mAggregateReportObjects.size() 805 || expected.mDebugAggregateReportObjects.size() 806 != actual.mDebugAggregateReportObjects.size() 807 || expected.mDebugEventReportObjects.size() 808 != actual.mDebugEventReportObjects.size() 809 || expected.mDebugReportObjects.size() != actual.mDebugReportObjects.size()) { 810 log("Report list size mismatch"); 811 return false; 812 } 813 for (int i = 0; i < expected.mEventReportObjects.size(); i++) { 814 if (!areEqualEventReportJsons( 815 ReportType.EVENT, 816 expected.mEventReportObjects.get(i), 817 actual.mEventReportObjects.get(i))) { 818 log("Event report object mismatch"); 819 return false; 820 } 821 } 822 for (int i = 0; i < expected.mAggregateReportObjects.size(); i++) { 823 if (!areEqualAggregateReportJsons( 824 ReportType.AGGREGATE, 825 expected.mAggregateReportObjects.get(i), 826 actual.mAggregateReportObjects.get(i))) { 827 log("Aggregate report object mismatch"); 828 return false; 829 } 830 } 831 for (int i = 0; i < expected.mDebugEventReportObjects.size(); i++) { 832 if (!areEqualEventReportJsons( 833 ReportType.EVENT_DEBUG, 834 expected.mDebugEventReportObjects.get(i), 835 actual.mDebugEventReportObjects.get(i))) { 836 log("Debug event report object mismatch"); 837 return false; 838 } 839 } 840 for (int i = 0; i < expected.mDebugAggregateReportObjects.size(); i++) { 841 if (!areEqualAggregateReportJsons( 842 ReportType.AGGREGATE_DEBUG, 843 expected.mDebugAggregateReportObjects.get(i), 844 actual.mDebugAggregateReportObjects.get(i))) { 845 log("Debug aggregate report object mismatch"); 846 return false; 847 } 848 } 849 for (int i = 0; i < expected.mDebugReportObjects.size(); i++) { 850 if (!areEqualDebugReportJsons( 851 ReportType.VERBOSE_DEBUG, 852 expected.mDebugReportObjects.get(i), 853 actual.mDebugReportObjects.get(i))) { 854 log("Debug report object mismatch"); 855 return false; 856 } 857 } 858 859 return true; 860 } 861 getTestFailureMessage(ReportObjects expectedOutput, ReportObjects actualOutput)862 private static String getTestFailureMessage(ReportObjects expectedOutput, 863 ReportObjects actualOutput) { 864 return String.format( 865 "Actual output does not match expected.\n\n" 866 + "(Note that displayed randomized_trigger_rate and report_url are not" 867 + " normalised.\n" 868 + "Note that report IDs are ignored in comparisons since they are not" 869 + " known in advance.)\n\n" 870 + "Event report objects:\n" 871 + "%s\n\n" 872 + "Debug Event report objects:\n" 873 + "%s\n\n" 874 + "Expected aggregate report objects: %s\n\n" 875 + "Actual aggregate report objects: %s\n\n" 876 + "Expected debug aggregate report objects: %s\n\n" 877 + "Actual debug aggregate report objects: %s\n\n" 878 + "Expected debug report objects: %s\n\n" 879 + "Actual debug report objects: %s\n", 880 prettify( 881 expectedOutput.mEventReportObjects, 882 actualOutput.mEventReportObjects), 883 prettify( 884 expectedOutput.mDebugEventReportObjects, 885 actualOutput.mDebugEventReportObjects), 886 expectedOutput.mAggregateReportObjects, 887 actualOutput.mAggregateReportObjects, 888 expectedOutput.mDebugAggregateReportObjects, 889 actualOutput.mDebugAggregateReportObjects, 890 expectedOutput.mDebugReportObjects, 891 actualOutput.mDebugReportObjects) 892 + getDatastoreState(); 893 } 894 prettify(List<JSONObject> expected, List<JSONObject> actual)895 private static String prettify(List<JSONObject> expected, List<JSONObject> actual) { 896 StringBuilder result = new StringBuilder("(Expected ::: Actual)" 897 + "\n------------------------\n"); 898 for (int i = 0; i < Math.max(expected.size(), actual.size()); i++) { 899 if (i < expected.size() && i < actual.size()) { 900 result.append(prettifyObjs(expected.get(i), actual.get(i))); 901 } else { 902 if (i < expected.size()) { 903 result.append(prettifyObj("", expected.get(i))); 904 } 905 if (i < actual.size()) { 906 result.append(prettifyObj(" ::: ", actual.get(i))); 907 } 908 } 909 result.append("\n------------------------\n"); 910 } 911 return result.toString(); 912 } 913 prettifyObjs(JSONObject obj1, JSONObject obj2)914 private static String prettifyObjs(JSONObject obj1, JSONObject obj2) { 915 StringBuilder result = new StringBuilder(); 916 result.append(TestFormatJsonMapping.REPORT_TIME_KEY + ": ") 917 .append(obj1.optString(TestFormatJsonMapping.REPORT_TIME_KEY)) 918 .append(" ::: ") 919 .append(obj2.optString(TestFormatJsonMapping.REPORT_TIME_KEY)) 920 .append("\n"); 921 result.append(TestFormatJsonMapping.REPORT_TO_KEY + ": ") 922 .append(obj1.optString(TestFormatJsonMapping.REPORT_TO_KEY)) 923 .append(" ::: ") 924 .append(obj2.optString(TestFormatJsonMapping.REPORT_TO_KEY)) 925 .append("\n"); 926 JSONObject payload1 = obj1.optJSONObject(TestFormatJsonMapping.PAYLOAD_KEY); 927 JSONObject payload2 = obj2.optJSONObject(TestFormatJsonMapping.PAYLOAD_KEY); 928 try { 929 result.append(EventReportPayloadKeys.STRING_OR_ARRAY + ": ") 930 .append(payload1.get(EventReportPayloadKeys.STRING_OR_ARRAY).toString()) 931 .append(" ::: ") 932 .append(payload2.get(EventReportPayloadKeys.STRING_OR_ARRAY).toString() + "\n"); 933 } catch (JSONException e) { 934 result.append("JSONObject::get failed for EventReportPayloadKeys.STRING_OR_ARRAY " 935 + e + "\n"); 936 } 937 result.append(EventReportPayloadKeys.ARRAY + ": ") 938 .append(payload1.optJSONArray(EventReportPayloadKeys.ARRAY)) 939 .append(" ::: ") 940 .append(payload2.optJSONArray(EventReportPayloadKeys.ARRAY)) 941 .append("\n"); 942 for (String key : EventReportPayloadKeys.STRINGS) { 943 result.append(key) 944 .append(": ") 945 .append(payload1.optString(key)) 946 .append(" ::: ") 947 .append(payload2.optString(key)) 948 .append("\n"); 949 } 950 result.append(EventReportPayloadKeys.DOUBLE + ": ") 951 .append(payload1.optDouble(EventReportPayloadKeys.DOUBLE)) 952 .append(" ::: ") 953 .append(payload2.optDouble(EventReportPayloadKeys.DOUBLE)); 954 return result.toString(); 955 } 956 prettifyObj(String pad, JSONObject obj)957 private static String prettifyObj(String pad, JSONObject obj) { 958 StringBuilder result = new StringBuilder(); 959 result.append(TestFormatJsonMapping.REPORT_TIME_KEY + ": ") 960 .append(pad) 961 .append(obj.optString(TestFormatJsonMapping.REPORT_TIME_KEY)) 962 .append("\n"); 963 JSONObject payload = obj.optJSONObject(TestFormatJsonMapping.PAYLOAD_KEY); 964 try { 965 result.append(EventReportPayloadKeys.STRING_OR_ARRAY + ": ") 966 .append(pad) 967 .append(payload.get(EventReportPayloadKeys.STRING_OR_ARRAY).toString() + "\n"); 968 } catch (JSONException e) { 969 result.append("JSONObject::get failed for EventReportPayloadKeys.STRING_OR_ARRAY " 970 + e + "\n"); 971 } 972 result.append(EventReportPayloadKeys.ARRAY + ": ") 973 .append(pad) 974 .append(payload.optJSONArray(EventReportPayloadKeys.ARRAY)) 975 .append("\n"); 976 for (String key : EventReportPayloadKeys.STRINGS) { 977 result.append(key).append(": ").append(pad).append(payload.optString(key)).append("\n"); 978 } 979 result.append(EventReportPayloadKeys.DOUBLE + ": ") 980 .append(pad) 981 .append(payload.optDouble(EventReportPayloadKeys.DOUBLE)); 982 return result.toString(); 983 } 984 getDatastoreState()985 protected static String getDatastoreState() { 986 StringBuilder result = new StringBuilder(); 987 SQLiteDatabase db = DbTestUtil.getMeasurementDbHelperForTest().getWritableDatabase(); 988 List<String> tableNames = 989 ImmutableList.of( 990 "msmt_source", 991 "msmt_source_destination", 992 "msmt_trigger", 993 "msmt_attribution", 994 "msmt_event_report", 995 "msmt_aggregate_report", 996 "msmt_async_registration_contract"); 997 for (String tableName : tableNames) { 998 result.append("\n" + tableName + ":\n"); 999 result.append(getTableState(db, tableName)); 1000 } 1001 SQLiteDatabase enrollmentDb = DbTestUtil.getSharedDbHelperForTest().getWritableDatabase(); 1002 List<String> enrollmentTables = ImmutableList.of("enrollment_data"); 1003 for (String tableName : enrollmentTables) { 1004 result.append("\n" + tableName + ":\n"); 1005 result.append(getTableState(enrollmentDb, tableName)); 1006 } 1007 return result.toString(); 1008 } 1009 getTableState(SQLiteDatabase db, String tableName)1010 private static String getTableState(SQLiteDatabase db, String tableName) { 1011 Cursor cursor = getAllRows(db, tableName); 1012 StringBuilder result = new StringBuilder(); 1013 while (cursor.moveToNext()) { 1014 result.append("\n" + DatabaseUtils.dumpCurrentRowToString(cursor)); 1015 } 1016 return result.toString(); 1017 } 1018 getAllRows(SQLiteDatabase db, String tableName)1019 private static Cursor getAllRows(SQLiteDatabase db, String tableName) { 1020 return db.query( 1021 /* boolean distinct */ false, 1022 tableName, 1023 /* String[] columns */ null, 1024 /* String selection */ null, 1025 /* String[] selectionArgs */ null, 1026 /* String groupBy */ null, 1027 /* String having */ null, 1028 /* String orderBy */ null, 1029 /* String limit */ null); 1030 } 1031 getExpiryTimesFrom( Collection<List<Map<String, List<String>>>> responseHeadersCollection)1032 private static Set<Long> getExpiryTimesFrom( 1033 Collection<List<Map<String, List<String>>>> responseHeadersCollection) 1034 throws JSONException { 1035 Set<Long> expiryTimes = new HashSet<>(); 1036 1037 for (List<Map<String, List<String>>> responseHeaders : responseHeadersCollection) { 1038 for (Map<String, List<String>> headersMap : responseHeaders) { 1039 if (!headersMap.containsKey(TestFormatJsonMapping.SOURCE_REGISTRATION_HEADER)) { 1040 continue; 1041 } 1042 if (headersMap.get(TestFormatJsonMapping.SOURCE_REGISTRATION_HEADER) == null) { 1043 continue; 1044 } 1045 try { 1046 String sourceStr = headersMap.get( 1047 TestFormatJsonMapping.SOURCE_REGISTRATION_HEADER).get(0); 1048 JSONObject sourceJson = new JSONObject(sourceStr); 1049 if (sourceJson.has("expiry")) { 1050 expiryTimes.add(sourceJson.getLong("expiry")); 1051 } else { 1052 expiryTimes.add( 1053 MEASUREMENT_MAX_REPORTING_REGISTER_SOURCE_EXPIRATION_IN_SECONDS); 1054 } 1055 if (sourceJson.has("event_report_windows")) { 1056 expiryTimes.addAll( 1057 getFlexEndTimes(sourceJson.getJSONObject("event_report_windows"))); 1058 } 1059 } catch (JSONException e) { 1060 Log.i(LOG_TAG, String.format("%s is not a valid JSON object.", 1061 TestFormatJsonMapping.SOURCE_REGISTRATION_HEADER)); 1062 } 1063 } 1064 } 1065 1066 return expiryTimes; 1067 } 1068 getFlexEndTimes(JSONObject eventReportWindows)1069 private static Set<Long> getFlexEndTimes(JSONObject eventReportWindows) throws JSONException { 1070 Set<Long> endTimes = new HashSet<>(); 1071 JSONArray endTimesArray = eventReportWindows.getJSONArray("end_times"); 1072 for (int i = 0; i < endTimesArray.length(); i++) { 1073 endTimes.add(endTimesArray.getLong(i)); 1074 } 1075 return endTimes; 1076 } 1077 roundSecondsToWholeDays(long seconds)1078 private static long roundSecondsToWholeDays(long seconds) { 1079 long remainder = seconds % TimeUnit.DAYS.toSeconds(1); 1080 boolean roundUp = remainder >= TimeUnit.DAYS.toSeconds(1) / 2L; 1081 return seconds - remainder + (roundUp ? TimeUnit.DAYS.toSeconds(1) : 0); 1082 } 1083 maybeAddEventReportingJobTimes(boolean isEventType, long sourceTime, Collection<List<Map<String, List<String>>>> responseHeaders)1084 private static Set<Action> maybeAddEventReportingJobTimes(boolean isEventType, 1085 long sourceTime, Collection<List<Map<String, List<String>>>> responseHeaders) 1086 throws JSONException { 1087 Set<Action> reportingJobsActions = new HashSet<>(); 1088 Set<Long> expiryTimes = getExpiryTimesFrom(responseHeaders); 1089 for (Long expiry : expiryTimes) { 1090 long validExpiry = expiry; 1091 if (expiry > MEASUREMENT_MAX_REPORTING_REGISTER_SOURCE_EXPIRATION_IN_SECONDS) { 1092 validExpiry = MEASUREMENT_MAX_REPORTING_REGISTER_SOURCE_EXPIRATION_IN_SECONDS; 1093 } else if (expiry < MEASUREMENT_MIN_REPORTING_REGISTER_SOURCE_EXPIRATION_IN_SECONDS) { 1094 validExpiry = MEASUREMENT_MIN_REPORTING_REGISTER_SOURCE_EXPIRATION_IN_SECONDS; 1095 } 1096 if (isEventType) { 1097 validExpiry = roundSecondsToWholeDays(validExpiry); 1098 } 1099 1100 long jobTime = sourceTime + 1000 * validExpiry + 3600000L; 1101 1102 reportingJobsActions.add(new EventReportingJob(jobTime)); 1103 // Add a job two days earlier for interop tests 1104 reportingJobsActions.add(new EventReportingJob(jobTime - TimeUnit.DAYS.toMillis(2))); 1105 } 1106 1107 return reportingJobsActions; 1108 } 1109 preprocessTestJson(String json)1110 static String preprocessTestJson(String json) { 1111 return json.replaceAll("\\.test(?=[\"\\/,])", ".com"); 1112 } 1113 1114 /** 1115 * Builds and returns test cases from a JSON InputStream to be used by JUnit parameterized 1116 * tests. 1117 * 1118 * @return A collection of Object arrays, each with 1119 * {@code [Collection<Object> actions, ReportObjects expectedOutput, 1120 * ParamsProvider paramsProvider, String name]} 1121 */ getTestCasesFrom( List<InputStream> inputStreams, String[] filenames, Function<String, String> preprocessor, Map<String, String> apiConfigPhFlags)1122 private static Collection<Object[]> getTestCasesFrom( 1123 List<InputStream> inputStreams, 1124 String[] filenames, 1125 Function<String, String> preprocessor, 1126 Map<String, String> apiConfigPhFlags) throws IOException, JSONException { 1127 List<Object[]> testCases = new ArrayList<>(); 1128 1129 for (int i = 0; i < inputStreams.size(); i++) { 1130 String name = filenames[i]; 1131 if (name.equals(TestFormatJsonMapping.DEFAULT_CONFIG_FILENAME)) { 1132 continue; 1133 } 1134 int size = inputStreams.get(i).available(); 1135 byte[] buffer = new byte[size]; 1136 inputStreams.get(i).read(buffer); 1137 inputStreams.get(i).close(); 1138 String json = new String(buffer, StandardCharsets.UTF_8); 1139 1140 JSONObject testObj = new JSONObject(preprocessor.apply(json)); 1141 JSONObject input = testObj.getJSONObject(TestFormatJsonMapping.TEST_INPUT_KEY); 1142 JSONObject output = testObj.getJSONObject(TestFormatJsonMapping.TEST_OUTPUT_KEY); 1143 1144 // "Actions" are source or trigger registrations, or a reporting job. 1145 List<Action> actions = new ArrayList<>(); 1146 1147 actions.addAll(createSourceBasedActions(input)); 1148 actions.addAll(createTriggerBasedActions(input)); 1149 actions.addAll(createInstallActions(input)); 1150 actions.addAll(createUninstallActions(input)); 1151 1152 actions.sort(Comparator.comparing(Action::getComparable)); 1153 1154 ReportObjects expectedOutput = getExpectedOutput(output); 1155 1156 JSONObject apiConfigObj = testObj.isNull(TestFormatJsonMapping.API_CONFIG_KEY) 1157 ? new JSONObject() 1158 : testObj.getJSONObject(TestFormatJsonMapping.API_CONFIG_KEY); 1159 1160 ParamsProvider paramsProvider = new ParamsProvider(apiConfigObj); 1161 1162 testCases.add( 1163 new Object[] { 1164 actions, 1165 expectedOutput, 1166 paramsProvider, 1167 name, 1168 extractPhFlags(testObj, apiConfigObj, apiConfigPhFlags) 1169 }); 1170 } 1171 1172 return testCases; 1173 } 1174 extractPhFlags(JSONObject testObj, JSONObject apiConfigObj, Map<String, String> apiConfigPhFlags)1175 private static Map<String, String> extractPhFlags(JSONObject testObj, JSONObject apiConfigObj, 1176 // Interop tests may have some configurations in the "api_config" field that correspond 1177 // with Ph Flags. 1178 Map<String, String> apiConfigPhFlags) { 1179 Map<String, String> phFlagsMap = new HashMap<>(); 1180 apiConfigPhFlags.keySet().forEach((key) -> { 1181 if (!apiConfigObj.isNull(key)) { 1182 // Interop test configuration uses a single key for both event and aggregate 1183 // level attribution limits. 1184 if (key.equals("rate_limit_max_attributions")) { 1185 phFlagsMap.put( 1186 FlagsConstants.KEY_MEASUREMENT_MAX_EVENT_ATTRIBUTION_PER_RATE_LIMIT_WINDOW, 1187 apiConfigObj.optString(key)); 1188 phFlagsMap.put( 1189 FlagsConstants.KEY_MEASUREMENT_MAX_AGGREGATE_ATTRIBUTION_PER_RATE_LIMIT_WINDOW, 1190 apiConfigObj.optString(key)); 1191 } else { 1192 phFlagsMap.put(apiConfigPhFlags.get(key), 1193 apiConfigObj.optString(key)); 1194 } 1195 } 1196 }); 1197 if (testObj.isNull(TestFormatJsonMapping.PH_FLAGS_OVERRIDE_KEY)) { 1198 return phFlagsMap; 1199 } 1200 1201 JSONObject phFlagsObject = 1202 testObj.optJSONObject(TestFormatJsonMapping.PH_FLAGS_OVERRIDE_KEY); 1203 phFlagsObject.keySet().forEach((key) -> phFlagsMap.put(key, phFlagsObject.optString(key))); 1204 return phFlagsMap; 1205 } 1206 isSourceRegistration(JSONObject obj)1207 private static boolean isSourceRegistration(JSONObject obj) throws JSONException { 1208 JSONObject request = obj.getJSONObject(TestFormatJsonMapping.REGISTRATION_REQUEST_KEY); 1209 return !request.isNull(TestFormatJsonMapping.INPUT_EVENT_KEY) 1210 || !request.isNull(TestFormatJsonMapping.INTEROP_INPUT_EVENT_KEY); 1211 } 1212 addSourceRegistration(JSONObject sourceObj, List<Action> actions, Set<Action> eventReportingJobActions)1213 private static void addSourceRegistration(JSONObject sourceObj, List<Action> actions, 1214 Set<Action> eventReportingJobActions) throws JSONException { 1215 RegisterSource sourceRegistration = new RegisterSource(sourceObj); 1216 actions.add(sourceRegistration); 1217 // Add corresponding reporting job time actions 1218 eventReportingJobActions.addAll( 1219 maybeAddEventReportingJobTimes( 1220 sourceRegistration.mRegistrationRequest.getInputEvent() == null, 1221 sourceRegistration.mTimestamp, 1222 sourceRegistration.mUriToResponseHeadersMap.values())); 1223 } 1224 createSourceBasedActions(JSONObject input)1225 private static List<Action> createSourceBasedActions(JSONObject input) throws JSONException { 1226 List<Action> actions = new ArrayList<>(); 1227 // Set avoids duplicate reporting times across sources to do attribution upon. 1228 Set<Action> eventReportingJobActions = new HashSet<>(); 1229 1230 // Interop tests have all registration types in one list 1231 if (!input.isNull(TestFormatJsonMapping.REGISTRATIONS_KEY)) { 1232 JSONArray registrationArray = input.getJSONArray( 1233 TestFormatJsonMapping.REGISTRATIONS_KEY); 1234 for (int i = 0; i < registrationArray.length(); i++) { 1235 if (registrationArray.isNull(i)) { 1236 continue; 1237 } 1238 JSONObject obj = registrationArray.getJSONObject(i); 1239 if (isSourceRegistration(obj)) { 1240 addSourceRegistration(obj, actions, eventReportingJobActions); 1241 } 1242 } 1243 } 1244 1245 if (!input.isNull(TestFormatJsonMapping.SOURCE_REGISTRATIONS_KEY)) { 1246 JSONArray sourceRegistrationArray = input.getJSONArray( 1247 TestFormatJsonMapping.SOURCE_REGISTRATIONS_KEY); 1248 for (int j = 0; j < sourceRegistrationArray.length(); j++) { 1249 if (sourceRegistrationArray.isNull(j)) { 1250 continue; 1251 } 1252 addSourceRegistration(sourceRegistrationArray.getJSONObject(j), 1253 actions, eventReportingJobActions); 1254 } 1255 } 1256 1257 if (!input.isNull(TestFormatJsonMapping.WEB_SOURCES_KEY)) { 1258 JSONArray webSourceRegistrationArray = 1259 input.getJSONArray(TestFormatJsonMapping.WEB_SOURCES_KEY); 1260 for (int j = 0; j < webSourceRegistrationArray.length(); j++) { 1261 RegisterWebSource webSource = 1262 new RegisterWebSource(webSourceRegistrationArray.getJSONObject(j)); 1263 actions.add(webSource); 1264 // Add corresponding reporting job time actions 1265 eventReportingJobActions.addAll( 1266 maybeAddEventReportingJobTimes( 1267 webSource.mRegistrationRequest.getSourceRegistrationRequest() 1268 .getInputEvent() == null, 1269 webSource.mTimestamp, 1270 webSource.mUriToResponseHeadersMap.values())); 1271 } 1272 } 1273 1274 if (!input.isNull(TestFormatJsonMapping.LIST_SOURCES_KEY)) { 1275 JSONArray listSourceRegistrationArray = 1276 input.getJSONArray(TestFormatJsonMapping.LIST_SOURCES_KEY); 1277 for (int j = 0; j < listSourceRegistrationArray.length(); j++) { 1278 RegisterListSources listSources = 1279 new RegisterListSources(listSourceRegistrationArray.getJSONObject(j)); 1280 actions.add(listSources); 1281 // Add corresponding reporting job time actions 1282 eventReportingJobActions.addAll( 1283 maybeAddEventReportingJobTimes( 1284 listSources 1285 .mRegistrationRequest 1286 .getSourceRegistrationRequest() 1287 .getInputEvent() 1288 == null, 1289 listSources.mTimestamp, 1290 listSources.mUriToResponseHeadersMap.values())); 1291 } 1292 } 1293 1294 actions.addAll(eventReportingJobActions); 1295 return actions; 1296 } 1297 createTriggerBasedActions(JSONObject input)1298 private static List<Action> createTriggerBasedActions(JSONObject input) throws JSONException { 1299 List<Action> actions = new ArrayList<>(); 1300 List<Action> aggregateReportingJobActions = new ArrayList<>(); 1301 1302 // Interop tests have all registration types in one list 1303 if (!input.isNull(TestFormatJsonMapping.REGISTRATIONS_KEY)) { 1304 JSONArray registrationArray = input.getJSONArray( 1305 TestFormatJsonMapping.REGISTRATIONS_KEY); 1306 for (int i = 0; i < registrationArray.length(); i++) { 1307 if (registrationArray.isNull(i)) { 1308 continue; 1309 } 1310 JSONObject obj = registrationArray.getJSONObject(i); 1311 if (!isSourceRegistration(obj)) { 1312 RegisterTrigger triggerRegistration = new RegisterTrigger(obj); 1313 actions.add(triggerRegistration); 1314 aggregateReportingJobActions.add(new AggregateReportingJob( 1315 triggerRegistration.mTimestamp + AGGREGATE_REPORT_DELAY)); 1316 } 1317 } 1318 } 1319 1320 if (!input.isNull(TestFormatJsonMapping.TRIGGERS_KEY)) { 1321 JSONArray triggerRegistrationArray = 1322 input.getJSONArray(TestFormatJsonMapping.TRIGGERS_KEY); 1323 for (int j = 0; j < triggerRegistrationArray.length(); j++) { 1324 RegisterTrigger triggerRegistration = 1325 new RegisterTrigger(triggerRegistrationArray.getJSONObject(j)); 1326 actions.add(triggerRegistration); 1327 aggregateReportingJobActions.add(new AggregateReportingJob( 1328 triggerRegistration.mTimestamp + AGGREGATE_REPORT_DELAY)); 1329 } 1330 } 1331 1332 if (!input.isNull(TestFormatJsonMapping.WEB_TRIGGERS_KEY)) { 1333 JSONArray webTriggerRegistrationArray = 1334 input.getJSONArray(TestFormatJsonMapping.WEB_TRIGGERS_KEY); 1335 for (int j = 0; j < webTriggerRegistrationArray.length(); j++) { 1336 RegisterWebTrigger webTrigger = 1337 new RegisterWebTrigger(webTriggerRegistrationArray.getJSONObject(j)); 1338 actions.add(webTrigger); 1339 aggregateReportingJobActions.add(new AggregateReportingJob( 1340 webTrigger.mTimestamp + AGGREGATE_REPORT_DELAY)); 1341 } 1342 } 1343 1344 actions.addAll(aggregateReportingJobActions); 1345 return actions; 1346 } 1347 createInstallActions(JSONObject input)1348 private static List<Action> createInstallActions(JSONObject input) throws JSONException { 1349 List<Action> actions = new ArrayList<>(); 1350 if (!input.isNull(TestFormatJsonMapping.INSTALLS_KEY)) { 1351 JSONArray installsArray = input.getJSONArray(TestFormatJsonMapping.INSTALLS_KEY); 1352 for (int j = 0; j < installsArray.length(); j++) { 1353 InstallApp installApp = new InstallApp(installsArray.getJSONObject(j)); 1354 actions.add(installApp); 1355 } 1356 } 1357 1358 return actions; 1359 } 1360 createUninstallActions(JSONObject input)1361 private static List<Action> createUninstallActions(JSONObject input) throws JSONException { 1362 List<Action> actions = new ArrayList<>(); 1363 if (!input.isNull(TestFormatJsonMapping.UNINSTALLS_KEY)) { 1364 JSONArray uninstallsArray = input.getJSONArray(TestFormatJsonMapping.UNINSTALLS_KEY); 1365 for (int j = 0; j < uninstallsArray.length(); j++) { 1366 UninstallApp uninstallApp = new UninstallApp(uninstallsArray.getJSONObject(j)); 1367 actions.add(uninstallApp); 1368 } 1369 } 1370 1371 return actions; 1372 } 1373 getExpectedOutput(JSONObject output)1374 private static ReportObjects getExpectedOutput(JSONObject output) throws JSONException { 1375 List<JSONObject> eventReportObjects = new ArrayList<>(); 1376 List<JSONObject> aggregateReportObjects = new ArrayList<>(); 1377 List<JSONObject> debugEventReportObjects = new ArrayList<>(); 1378 List<JSONObject> debugAggregateReportObjects = new ArrayList<>(); 1379 List<JSONObject> debugReportObjects = new ArrayList<>(); 1380 1381 // Interop tests have all report types in one list 1382 if (!output.isNull(TestFormatJsonMapping.REPORTS_OBJECTS_KEY)) { 1383 // We compare the suffixes of the different reporting URLs with the report URL to 1384 // determine the report type. This assumes the longest suffix match corresponds with the 1385 // report type. 1386 String[] eventUrlTokens = EVENT_ATTRIBUTION_REPORT_URI_PATH.split("/"); 1387 String[] debugEventUrlTokens = DEBUG_EVENT_ATTRIBUTION_REPORT_URI_PATH.split("/"); 1388 String[] aggregateUrlTokens = AGGREGATE_ATTRIBUTION_REPORT_URI_PATH.split("/"); 1389 String[] debugAggregateUrlTokens = 1390 DEBUG_AGGREGATE_ATTRIBUTION_REPORT_URI_PATH.split("/"); 1391 String[] debugUrlTokens = DEBUG_REPORT_URI_PATH.split("/"); 1392 1393 JSONArray reportsObjectsArray = output.getJSONArray( 1394 TestFormatJsonMapping.REPORTS_OBJECTS_KEY); 1395 1396 for (int i = 0; i < reportsObjectsArray.length(); i++) { 1397 JSONObject obj = reportsObjectsArray.getJSONObject(i); 1398 String[] urlTokens = obj.getString(TestFormatJsonMapping.REPORT_TO_KEY).split("/"); 1399 // Collect reports to different lists (event, aggregate, debug, etc.) based on the 1400 // reporting URL. 1401 if (urlTokens[urlTokens.length - 1].equals( 1402 debugUrlTokens[debugUrlTokens.length - 1])) { 1403 debugReportObjects.add(obj); 1404 } else if (urlTokens[urlTokens.length - 1].equals( 1405 eventUrlTokens[eventUrlTokens.length - 1])) { 1406 if (urlTokens[urlTokens.length - 2].equals( 1407 debugEventUrlTokens[debugEventUrlTokens.length - 2])) { 1408 debugEventReportObjects.add(obj); 1409 } else { 1410 eventReportObjects.add(obj); 1411 } 1412 } else if (urlTokens[urlTokens.length - 1].equals( 1413 aggregateUrlTokens[aggregateUrlTokens.length - 1])) { 1414 if (urlTokens[urlTokens.length - 2].equals( 1415 debugAggregateUrlTokens[debugAggregateUrlTokens.length - 2])) { 1416 debugAggregateReportObjects.add(obj); 1417 } else { 1418 aggregateReportObjects.add(obj); 1419 } 1420 } 1421 } 1422 } 1423 1424 if (!output.isNull(TestFormatJsonMapping.EVENT_REPORT_OBJECTS_KEY)) { 1425 JSONArray eventReportObjectsArray = output.getJSONArray( 1426 TestFormatJsonMapping.EVENT_REPORT_OBJECTS_KEY); 1427 for (int i = 0; i < eventReportObjectsArray.length(); i++) { 1428 eventReportObjects.add(eventReportObjectsArray.getJSONObject(i)); 1429 } 1430 } 1431 1432 if (!output.isNull(TestFormatJsonMapping.AGGREGATE_REPORT_OBJECTS_KEY)) { 1433 JSONArray aggregateReportObjectsArray = 1434 output.getJSONArray(TestFormatJsonMapping.AGGREGATE_REPORT_OBJECTS_KEY); 1435 for (int i = 0; i < aggregateReportObjectsArray.length(); i++) { 1436 aggregateReportObjects.add(aggregateReportObjectsArray.getJSONObject(i)); 1437 } 1438 } 1439 1440 if (!output.isNull(TestFormatJsonMapping.DEBUG_EVENT_REPORT_OBJECTS_KEY)) { 1441 JSONArray debugEventReportObjectsArray = 1442 output.getJSONArray(TestFormatJsonMapping.DEBUG_EVENT_REPORT_OBJECTS_KEY); 1443 for (int i = 0; i < debugEventReportObjectsArray.length(); i++) { 1444 debugEventReportObjects.add(debugEventReportObjectsArray.getJSONObject(i)); 1445 } 1446 } 1447 1448 if (!output.isNull(TestFormatJsonMapping.DEBUG_AGGREGATE_REPORT_OBJECTS_KEY)) { 1449 JSONArray debugAggregateReportObjectsArray = 1450 output.getJSONArray(TestFormatJsonMapping.DEBUG_AGGREGATE_REPORT_OBJECTS_KEY); 1451 for (int i = 0; i < debugAggregateReportObjectsArray.length(); i++) { 1452 debugAggregateReportObjects.add(debugAggregateReportObjectsArray.getJSONObject(i)); 1453 } 1454 } 1455 1456 if (!output.isNull(TestFormatJsonMapping.VERBOSE_DEBUG_OBJECTS_KEY)) { 1457 JSONArray debugReportObjectsArray = 1458 output.getJSONArray(TestFormatJsonMapping.VERBOSE_DEBUG_OBJECTS_KEY); 1459 for (int i = 0; i < debugReportObjectsArray.length(); i++) { 1460 debugReportObjects.add(debugReportObjectsArray.getJSONObject(i)); 1461 } 1462 } 1463 1464 return new ReportObjects( 1465 eventReportObjects, 1466 aggregateReportObjects, 1467 debugEventReportObjects, 1468 debugAggregateReportObjects, 1469 debugReportObjects); 1470 } 1471 1472 /** 1473 * Empties measurement database tables, used for test cleanup. 1474 */ emptyTables(SQLiteDatabase db)1475 private static void emptyTables(SQLiteDatabase db) { 1476 db.delete("msmt_source", null, null); 1477 db.delete("msmt_trigger", null, null); 1478 db.delete("msmt_event_report", null, null); 1479 db.delete("msmt_attribution", null, null); 1480 db.delete("msmt_aggregate_report", null, null); 1481 db.delete("msmt_async_registration_contract", null, null); 1482 } 1483 processAction(RegisterSource sourceRegistration)1484 abstract void processAction(RegisterSource sourceRegistration) 1485 throws IOException, JSONException; 1486 processAction(RegisterWebSource sourceRegistration)1487 abstract void processAction(RegisterWebSource sourceRegistration) 1488 throws IOException, JSONException; 1489 processAction(RegisterListSources sourceRegistration)1490 abstract void processAction(RegisterListSources sourceRegistration) 1491 throws IOException, JSONException; 1492 processAction(RegisterTrigger triggerRegistration)1493 abstract void processAction(RegisterTrigger triggerRegistration) 1494 throws IOException, JSONException; 1495 processAction(RegisterWebTrigger triggerRegistration)1496 abstract void processAction(RegisterWebTrigger triggerRegistration) 1497 throws IOException, JSONException; 1498 processAction(InstallApp installApp)1499 abstract void processAction(InstallApp installApp); 1500 processAction(UninstallApp uninstallApp)1501 abstract void processAction(UninstallApp uninstallApp); 1502 evaluateResults()1503 void evaluateResults() throws JSONException { 1504 sortEventReportObjects(OutputType.EXPECTED, mExpectedOutput.mEventReportObjects); 1505 sortEventReportObjects(OutputType.ACTUAL, mActualOutput.mEventReportObjects); 1506 sortAggregateReportObjects(OutputType.EXPECTED, mExpectedOutput.mAggregateReportObjects); 1507 sortAggregateReportObjects(OutputType.ACTUAL, mActualOutput.mAggregateReportObjects); 1508 sortEventReportObjects(OutputType.EXPECTED, mExpectedOutput.mDebugEventReportObjects); 1509 sortEventReportObjects(OutputType.ACTUAL, mActualOutput.mDebugEventReportObjects); 1510 sortAggregateReportObjects( 1511 OutputType.EXPECTED, mExpectedOutput.mDebugAggregateReportObjects); 1512 sortAggregateReportObjects(OutputType.ACTUAL, mActualOutput.mDebugAggregateReportObjects); 1513 Assert.assertTrue(getTestFailureMessage(mExpectedOutput, mActualOutput), 1514 areEqual(mExpectedOutput, mActualOutput)); 1515 } 1516 setupDeviceConfigForPhFlags()1517 private void setupDeviceConfigForPhFlags() { 1518 mPhFlagsMap 1519 .keySet() 1520 .forEach( 1521 key -> { 1522 log(String.format( 1523 "Setting PhFlag %s to %s", key, mPhFlagsMap.get(key))); 1524 DeviceConfig.setProperty( 1525 DeviceConfig.NAMESPACE_ADSERVICES, 1526 key, 1527 mPhFlagsMap.get(key), 1528 false); 1529 }); 1530 } 1531 } 1532