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.topics.classifier; 18 19 import static com.android.adservices.service.topics.classifier.CommonClassifierHelper.computeClassifierAssetChecksum; 20 import static com.android.adservices.service.topics.classifier.CommonClassifierHelper.getTopTopics; 21 import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; 22 23 import static com.google.common.truth.Truth.assertThat; 24 25 import static org.junit.Assert.assertThrows; 26 27 import com.android.adservices.MockRandom; 28 import com.android.adservices.common.AdServicesExtendedMockitoTestCase; 29 import com.android.adservices.data.topics.Topic; 30 import com.android.adservices.service.FlagsFactory; 31 import com.android.adservices.service.stats.AdServicesLogger; 32 import com.android.adservices.service.stats.EpochComputationGetTopTopicsStats; 33 import com.android.adservices.shared.testing.SkipLoggingUsageRule; 34 import com.android.modules.utils.testing.ExtendedMockitoRule.SpyStatic; 35 36 import com.google.android.libraries.mobiledatadownload.file.SynchronousFileStorage; 37 import com.google.common.collect.ImmutableList; 38 import com.google.common.collect.ImmutableMap; 39 import com.google.mobiledatadownload.ClientConfigProto.ClientFile; 40 41 import org.junit.Before; 42 import org.junit.Test; 43 import org.mockito.ArgumentCaptor; 44 import org.mockito.Mock; 45 46 import java.util.ArrayList; 47 import java.util.Arrays; 48 import java.util.HashMap; 49 import java.util.List; 50 import java.util.Map; 51 import java.util.Random; 52 import java.util.stream.Collectors; 53 54 /** 55 * Tests for {@link CommonClassifierHelper}. 56 * 57 * <p><b> Note: Some tests in this test class are depend on the ordering of topicIds in 58 * adservices/tests/unittest/service-core/assets/classifier/labels_test_topics.txt, because we will 59 * use Random() or MockRandom() to generate random integer index to get random topicIds. Topics will 60 * be selected from the topics list in order by their index in the topics list. </b> 61 */ 62 @SpyStatic(FlagsFactory.class) 63 // TODO (b/359964245): Remove after bug is resolved. 64 @SkipLoggingUsageRule(reason = "b/359964245") 65 public final class CommonClassifierHelperTest extends AdServicesExtendedMockitoTestCase { 66 private static final String TEST_LABELS_FILE_PATH = "classifier/labels_test_topics.txt"; 67 private static final String TEST_PRECOMPUTED_FILE_PATH = 68 "classifier/precomputed_test_app_list.csv"; 69 private static final String TEST_CLASSIFIER_ASSETS_METADATA_PATH = 70 "classifier/classifier_test_assets_metadata.json"; 71 private static final String TEST_CLASSIFIER_INPUT_CONFIG_PATH = 72 "classifier/classifier_input_config.txt"; 73 private static final String PRODUCTION_LABELS_FILE_PATH = "classifier/labels_topics.txt"; 74 private static final String PRODUCTION_APPS_FILE_PATH = "classifier/precomputed_app_list.csv"; 75 private static final String PRODUCTION_CLASSIFIER_ASSETS_METADATA_PATH = 76 "classifier/classifier_assets_metadata.json"; 77 private static final String PRODUCTION_CLASSIFIER_INPUT_CONFIG_PATH = 78 "classifier/classifier_input_config.txt"; 79 private static final String BUNDLED_MODEL_FILE_PATH = "classifier/model.tflite"; 80 81 private ImmutableList<Integer> testLabels; 82 private ImmutableMap<String, ImmutableMap<String, String>> testClassifierAssetsMetadata; 83 private long mTestTaxonomyVersion; 84 private long mTestModelVersion; 85 86 private ImmutableList<Integer> productionLabels; 87 private ImmutableMap<String, ImmutableMap<String, String>> productionClassifierAssetsMetadata; 88 private long mProductionTaxonomyVersion; 89 private long mProductionModelVersion; 90 91 @Mock private SynchronousFileStorage mMockFileStorage; 92 @Mock private Map<String, ClientFile> mMockDownloadedFiles; 93 @Mock private AdServicesLogger mLogger; 94 95 @Before setUp()96 public void setUp() { 97 mocker.mockGetFlagsForTesting(); 98 99 ModelManager testModelManager = 100 new ModelManager( 101 mContext, 102 TEST_LABELS_FILE_PATH, 103 TEST_PRECOMPUTED_FILE_PATH, 104 TEST_CLASSIFIER_ASSETS_METADATA_PATH, 105 TEST_CLASSIFIER_INPUT_CONFIG_PATH, 106 BUNDLED_MODEL_FILE_PATH, 107 mMockFileStorage, 108 mMockDownloadedFiles); 109 110 ModelManager productionModelManager = 111 new ModelManager( 112 mContext, 113 PRODUCTION_LABELS_FILE_PATH, 114 PRODUCTION_APPS_FILE_PATH, 115 PRODUCTION_CLASSIFIER_ASSETS_METADATA_PATH, 116 PRODUCTION_CLASSIFIER_INPUT_CONFIG_PATH, 117 BUNDLED_MODEL_FILE_PATH, 118 mMockFileStorage, 119 mMockDownloadedFiles); 120 121 // TODO (b/359964245): Delete after bug is resolved and use annotations to verify calls. 122 doNothingOnErrorLogUtilError(); 123 124 testLabels = testModelManager.retrieveLabels(); 125 testClassifierAssetsMetadata = testModelManager.retrieveClassifierAssetsMetadata(); 126 mTestTaxonomyVersion = 127 Long.parseLong( 128 testClassifierAssetsMetadata.get("labels_topics").get("asset_version")); 129 mTestModelVersion = 130 Long.parseLong( 131 testClassifierAssetsMetadata.get("tflite_model").get("asset_version")); 132 133 productionLabels = productionModelManager.retrieveLabels(); 134 productionClassifierAssetsMetadata = 135 productionModelManager.retrieveClassifierAssetsMetadata(); 136 mProductionTaxonomyVersion = 137 Long.parseLong( 138 productionClassifierAssetsMetadata 139 .get("labels_topics") 140 .get("asset_version")); 141 mProductionModelVersion = 142 Long.parseLong( 143 productionClassifierAssetsMetadata 144 .get("tflite_model") 145 .get("asset_version")); 146 } 147 148 @Test testGetTopTopics_legalInput()149 public void testGetTopTopics_legalInput() { 150 ArgumentCaptor<EpochComputationGetTopTopicsStats> argument = 151 ArgumentCaptor.forClass(EpochComputationGetTopTopicsStats.class); 152 // construction the appTopics map so that when sorting by the number of occurrences, 153 // the order of topics are: 154 // topic1, topic2, topic3, topic4, topic5, ..., 155 Map<String, List<Topic>> appTopics = new HashMap<>(); 156 appTopics.put("app1", getTestTopics(Arrays.asList(1, 2, 3, 4, 5))); 157 appTopics.put("app2", getTestTopics(Arrays.asList(1, 2, 3, 4, 5))); 158 appTopics.put("app3", getTestTopics(Arrays.asList(1, 2, 3, 4, 16))); 159 appTopics.put("app4", getTestTopics(Arrays.asList(1, 2, 3, 13, 17))); 160 appTopics.put("app5", getTestTopics(Arrays.asList(1, 2, 11, 14, 18))); 161 appTopics.put("app6", getTestTopics(Arrays.asList(1, 10, 12, 15, 19))); 162 163 // This test case should return top 5 topics from appTopics and 1 random topic 164 List<Topic> testResponse = 165 getTopTopics( 166 appTopics, 167 testLabels, 168 new Random(), 169 /* numberOfTopTopics */ 5, 170 /* numberOfRandomTopics */ 1, 171 mLogger); 172 173 assertThat(testResponse).hasSize(6); 174 expect.that(testResponse.get(0)).isEqualTo(getTestTopic(1)); 175 expect.that(testResponse.get(1)).isEqualTo(getTestTopic(2)); 176 expect.that(testResponse.get(2)).isEqualTo(getTestTopic(3)); 177 expect.that(testResponse.get(3)).isEqualTo(getTestTopic(4)); 178 expect.that(testResponse.get(4)).isEqualTo(getTestTopic(5)); 179 // Check the random topic is not empty 180 // The random topic is at the end 181 expect.that(testResponse.get(5)).isNotNull(); 182 183 verify(mLogger).logEpochComputationGetTopTopicsStats(argument.capture()); 184 expect.that(argument.getValue()) 185 .isEqualTo( 186 EpochComputationGetTopTopicsStats.builder() 187 .setTopTopicCount(5) 188 .setPaddedRandomTopicsCount(0) 189 .setAppsConsideredCount(6) 190 .setSdksConsideredCount(-1) 191 .build()); 192 } 193 194 @Test testGetTopTopics_largeTopTopicsInput()195 public void testGetTopTopics_largeTopTopicsInput() { 196 ArgumentCaptor<EpochComputationGetTopTopicsStats> argument = 197 ArgumentCaptor.forClass(EpochComputationGetTopTopicsStats.class); 198 199 Map<String, List<Topic>> appTopics = new HashMap<>(); 200 appTopics.put("app1", getTestTopics(Arrays.asList(1, 2, 3, 4, 5))); 201 202 // We only have 5 topics but requesting for 15 topics, 203 // so we will pad them with 10 random topics. 204 List<Topic> testResponse = 205 getTopTopics( 206 appTopics, 207 testLabels, 208 new Random(), 209 /* numberOfTopTopics */ 15, 210 /* numberOfRandomTopics */ 1, 211 mLogger); 212 213 // The response body should contain 11 topics. 214 assertThat(testResponse.size()).isEqualTo(16); 215 verify(mLogger).logEpochComputationGetTopTopicsStats(argument.capture()); 216 assertThat(argument.getValue()) 217 .isEqualTo( 218 EpochComputationGetTopTopicsStats.builder() 219 .setTopTopicCount(15) 220 .setPaddedRandomTopicsCount(10) 221 .setAppsConsideredCount(1) 222 .setSdksConsideredCount(-1) 223 .build()); 224 } 225 226 @Test testGetTopTopics_zeroTopTopics()227 public void testGetTopTopics_zeroTopTopics() { 228 Map<String, List<Topic>> appTopics = new HashMap<>(); 229 appTopics.put("app1", getTestTopics(Arrays.asList(1, 2, 3, 4, 5))); 230 231 // This test case should throw an IllegalArgumentException if numberOfTopTopics is 0. 232 assertThrows( 233 IllegalArgumentException.class, 234 () -> 235 getTopTopics( 236 appTopics, 237 testLabels, 238 new Random(), 239 /* numberOfTopTopics */ 0, 240 /* numberOfRandomTopics */ 1, 241 mLogger)); 242 } 243 244 @Test testGetTopTopics_zeroRandomTopics()245 public void testGetTopTopics_zeroRandomTopics() { 246 Map<String, List<Topic>> appTopics = new HashMap<>(); 247 appTopics.put("app1", getTestTopics(Arrays.asList(1, 2, 3, 4, 5))); 248 // This test case should throw an IllegalArgumentException if numberOfRandomTopics is 0. 249 assertThrows( 250 IllegalArgumentException.class, 251 () -> 252 getTopTopics( 253 appTopics, 254 testLabels, 255 new Random(), 256 /* numberOfTopTopics */ 3, 257 /* numberOfRandomTopics */ 0, 258 mLogger)); 259 } 260 261 @Test testGetTopTopics_negativeTopTopics()262 public void testGetTopTopics_negativeTopTopics() { 263 Map<String, List<Topic>> appTopics = new HashMap<>(); 264 appTopics.put("app1", getTestTopics(Arrays.asList(1, 2, 3, 4, 5))); 265 266 // This test case should throw an IllegalArgumentException if numberOfTopTopics is negative. 267 assertThrows( 268 IllegalArgumentException.class, 269 () -> 270 getTopTopics( 271 appTopics, 272 testLabels, 273 new Random(), 274 /* numberOfTopTopics */ -5, 275 /* numberOfRandomTopics */ 1, 276 mLogger)); 277 } 278 279 @Test testGetTopTopics_negativeRandomTopics()280 public void testGetTopTopics_negativeRandomTopics() { 281 Map<String, List<Topic>> appTopics = new HashMap<>(); 282 appTopics.put("app1", getTestTopics(Arrays.asList(1, 2, 3, 4, 5))); 283 284 // This test case should throw an IllegalArgumentException 285 // if numberOfRandomTopics is negative. 286 assertThrows( 287 IllegalArgumentException.class, 288 () -> 289 getTopTopics( 290 appTopics, 291 testLabels, 292 new Random(), 293 /* numberOfTopTopics */ 3, 294 /* numberOfRandomTopics */ -1, 295 mLogger)); 296 } 297 298 @Test testGetTopTopics_emptyAppTopicsMap()299 public void testGetTopTopics_emptyAppTopicsMap() { 300 Map<String, List<Topic>> appTopics = new HashMap<>(); 301 302 // The device does not have an app, an empty top topics list should be returned. 303 List<Topic> testResponse = 304 getTopTopics( 305 appTopics, 306 testLabels, 307 new Random(), 308 /* numberOfTopTopics */ 5, 309 /* numberOfRandomTopics */ 1, 310 mLogger); 311 312 // The response body should be empty. 313 assertThat(testResponse).isEmpty(); 314 } 315 316 @Test testGetTopTopics_emptyTopicInEachApp()317 public void testGetTopTopics_emptyTopicInEachApp() { 318 ArgumentCaptor<EpochComputationGetTopTopicsStats> argument = 319 ArgumentCaptor.forClass(EpochComputationGetTopTopicsStats.class); 320 Map<String, List<Topic>> appTopics = new HashMap<>(); 321 322 // app1 and app2 do not have any classification topics. 323 appTopics.put("app1", new ArrayList<>()); 324 appTopics.put("app2", new ArrayList<>()); 325 326 // The device have some apps but the topic corresponding to the app cannot be obtained. 327 // In this test case, an empty top topics list should be returned. 328 List<Topic> testResponse = 329 getTopTopics( 330 appTopics, 331 testLabels, 332 new Random(), 333 /* numberOfTopTopics */ 5, 334 /* numberOfRandomTopics */ 1, 335 mLogger); 336 337 // The response body should be empty 338 assertThat(testResponse).isEmpty(); 339 verify(mLogger).logEpochComputationGetTopTopicsStats(argument.capture()); 340 assertThat(argument.getValue()) 341 .isEqualTo( 342 EpochComputationGetTopTopicsStats.builder() 343 .setTopTopicCount(0) 344 .setPaddedRandomTopicsCount(0) 345 .setAppsConsideredCount(2) 346 .setSdksConsideredCount(-1) 347 .build()); 348 } 349 350 @Test testGetTopTopics_selectSingleRandomTopic()351 public void testGetTopTopics_selectSingleRandomTopic() { 352 // In this test, in order to make test result to be deterministic so CommonClassifierHelper 353 // has to be mocked to get a random topic. However, real CommonClassifierHelper need to 354 // be tested as well. Therefore, real methods will be called for the other top topics. 355 // 356 // Initialize MockRandom. Append 3 random positive integers (20, 100, 300) to MockRandom 357 // array, 358 // their corresponding topicIds in the topics list will not overlap with 359 // the topicIds of app1 below. 360 MockRandom mockRandom = new MockRandom(new long[] {20, 100, 300}); 361 362 Map<String, List<Topic>> testAppTopics = new HashMap<>(); 363 // We label app1 with the first 5 topics in topics list. 364 testAppTopics.put("app1", getTestTopics(Arrays.asList(253, 146, 277, 59, 127))); 365 366 // Test the random topic with labels file in test assets. 367 List<Topic> testResponse = 368 getTopTopics( 369 testAppTopics, 370 testLabels, 371 mockRandom, 372 /* numberOfTopTopics */ 5, 373 /* numberOfRandomTopics */ 1, 374 mLogger); 375 376 // The response body should contain 5 topics + 1 random topic. 377 assertThat(testResponse.size()).isEqualTo(6); 378 379 // In the following test, we need to verify that the mock random integer index 380 // can match the correct topic in classifier/precomputed_test_app_list_chrome_topics.csv. 381 // "random = n, topicId = m" means this topicId m is from the nth (0-indexed) 382 // topicId in the topics list. 383 // random = 20, topicId = 10021 384 assertThat(testResponse.get(5)).isEqualTo(getTestTopic(10021)); 385 386 Map<String, List<Topic>> productionAppTopics = new HashMap<>(); 387 // We label app1 with the same topic IDs as testAppTopics, but using production metadata. 388 productionAppTopics.put("app1", getProductionTopics(Arrays.asList(253, 146, 277, 59, 127))); 389 390 // Test the random topic with labels file in production assets. 391 List<Topic> productionResponse = 392 getTopTopics( 393 productionAppTopics, 394 productionLabels, 395 new MockRandom(new long[] {50, 100, 300}), 396 /* numberOfTopTopics */ 5, 397 /* numberOfRandomTopics */ 1, 398 mLogger); 399 400 // The response body should contain 5 topics + 1 random topic. 401 assertThat(productionResponse.size()).isEqualTo(6); 402 403 // In the following test, we need to verify that the mock random integer index 404 // can match the correct topic in classifier/precomputed_app_list_chrome_topics.csv. 405 // "random = n, topicId = m" means this topicId m is from the nth (0-indexed) 406 // topicId in the topics list. 407 // random = 50, topicId = 10051 408 assertThat(productionResponse.get(5)).isEqualTo(getProductionTopic(10051)); 409 } 410 411 @Test testGetTopTopics_selectMultipleRandomTopic()412 public void testGetTopTopics_selectMultipleRandomTopic() { 413 // In this test, in order to make test result to be deterministic so CommonClassifierHelper 414 // has to be mocked to get some random topics. However, real CommonClassifierHelper need to 415 // be tested as well. Therefore, real methods will be called for the other top topics. 416 // 417 // Initialize MockRandom. Randomly select 7 indices in MockRandom, their corresponding 418 // topicIds in the topics list 419 // will not overlap with the topicIds of app1 below. 500 in MockRandom exceeds the length 420 // of topics list, so what it represents should be 151st (500 % 349 = 151) topicId 421 // in topics list. 422 MockRandom mockRandom = new MockRandom(new long[] {10, 20, 50, 75, 100, 300, 500}); 423 424 Map<String, List<Topic>> appTopics = new HashMap<>(); 425 // The topicId we use is verticals4 and its index range is from 0 to 1918. 426 // We label app1 with the first 5 topicIds in topics list. 427 appTopics.put("app1", getTestTopics(Arrays.asList(34, 89, 69, 349, 241))); 428 429 List<Topic> testResponse = 430 getTopTopics( 431 appTopics, 432 testLabels, 433 mockRandom, 434 /* numberOfTopTopics */ 5, 435 /* numberOfRandomTopics */ 7, 436 mLogger); 437 438 // The response body should contain 5 topics + 7 random topic. 439 assertThat(testResponse.size()).isEqualTo(12); 440 441 // In the following tests, we need to verify that the mock random integer index 442 // can match the correct topic in classifier/precomputed_test_app_list_chrome_topics.csv. 443 // "random = n, topicId = m" means this topicId m is from the nth (0-indexed) 444 // topicId in the topics list. 445 // random = 10, topicId = 10011 446 assertThat(testResponse.get(5)).isEqualTo(getTestTopic(10011)); 447 448 // random = 20, topicId = 10021 449 assertThat(testResponse.get(6)).isEqualTo(getTestTopic(10021)); 450 451 // random = 50, topicId = 10051 452 assertThat(testResponse.get(7)).isEqualTo(getTestTopic(10051)); 453 454 // random = 75, topicId = 10076 455 assertThat(testResponse.get(8)).isEqualTo(getTestTopic(10076)); 456 457 // random = 100, topicId = 10101 458 assertThat(testResponse.get(9)).isEqualTo(getTestTopic(10101)); 459 460 // random = 300, topicId = 10301 461 assertThat(testResponse.get(10)).isEqualTo(getTestTopic(10301)); 462 463 // random = 500, size of labels list is 446, 464 // index should be 500 % 446 = 54, topicId = 10055 465 assertThat(testResponse.get(11)).isEqualTo(getTestTopic(10055)); 466 } 467 468 @Test testGetTopTopics_selectDuplicateRandomTopic()469 public void testGetTopTopics_selectDuplicateRandomTopic() { 470 ArgumentCaptor<EpochComputationGetTopTopicsStats> argument = 471 ArgumentCaptor.forClass(EpochComputationGetTopTopicsStats.class); 472 // In this test, in order to make test result to be deterministic so CommonClassifierHelper 473 // has to be mocked to get a random topic. However, real CommonClassifierHelper need to 474 // be tested as well. Therefore, real methods will be called for the other top topics. 475 // 476 // Initialize MockRandom. Randomly select 6 indices in MockRandom, their first 5 477 // corresponding topicIds 478 // in the topics list will overlap with the topicIds of app1 below. 479 MockRandom mockRandom = new MockRandom(new long[] {1, 5, 10, 25, 100, 300}); 480 481 Map<String, List<Topic>> appTopics = new HashMap<>(); 482 483 // If the random topic duplicates with the real topic, then pick another random 484 // one until no duplicates. In this test, we will let app1 have five topicIds of 485 // 2, 6, 11, 26, 101. These topicIds are the same as the topicIds in the 486 // classifier/precomputed_test_app_list_chrome_topics.csv corresponding to 487 // the first five indices in the MockRandomArray. 488 appTopics.put("app1", getTestTopics(Arrays.asList(2, 6, 11, 26, 101))); 489 490 List<Topic> testResponse = 491 getTopTopics( 492 appTopics, 493 testLabels, 494 mockRandom, 495 /* numberOfTopTopics */ 5, 496 /* numberOfRandomTopics */ 1, 497 mLogger); 498 499 // The response body should contain 5 topics + 1 random topic 500 assertThat(testResponse.size()).isEqualTo(6); 501 502 // In the following tests, we need to verify that the mock random integer index 503 // can match the correct topic in classifier/precomputed_test_app_list_chrome_topics.csv. 504 // "random = n, topicId = m" means this topicId m is from the nth (0-indexed) 505 // topicId in the topics list. 506 // In this test, if we want to select a random topic that does not repeat, 507 // we should select the one corresponding to the sixth index 508 // in the MockRandom array topicId, i.e. random = 1, topicId = 10002 509 assertThat(testResponse.get(5)).isEqualTo(getTestTopic(10002)); 510 verify(mLogger).logEpochComputationGetTopTopicsStats(argument.capture()); 511 assertThat(argument.getValue()) 512 .isEqualTo( 513 EpochComputationGetTopTopicsStats.builder() 514 .setTopTopicCount(5) 515 .setPaddedRandomTopicsCount(0) 516 .setAppsConsideredCount(1) 517 .setSdksConsideredCount(-1) 518 .build()); 519 } 520 521 @Test testComputeTestAssetChecksum()522 public void testComputeTestAssetChecksum() { 523 // Compute SHA256 checksum of labels topics file in test assets and check the result 524 // can match the checksum saved in the test classifier assets metadata file. 525 String labelsTestTopicsChecksum = 526 computeClassifierAssetChecksum(mContext.getAssets(), TEST_LABELS_FILE_PATH); 527 assertThat(labelsTestTopicsChecksum) 528 .isEqualTo(testClassifierAssetsMetadata.get("labels_topics").get("checksum")); 529 530 // Compute SHA256 checksum of precomputed apps topics file in test assets 531 // and check the result can match the checksum saved in the classifier assets metadata file. 532 String precomputedAppsTestChecksum = 533 computeClassifierAssetChecksum(mContext.getAssets(), TEST_PRECOMPUTED_FILE_PATH); 534 assertThat(precomputedAppsTestChecksum) 535 .isEqualTo( 536 testClassifierAssetsMetadata.get("precomputed_app_list").get("checksum")); 537 } 538 539 @Test testComputeProductionAssetChecksum()540 public void testComputeProductionAssetChecksum() { 541 // Compute SHA256 checksum of labels topics file in production assets and check the result 542 // can match the checksum saved in the production classifier assets metadata file. 543 String labelsProductionTopicsChecksum = 544 computeClassifierAssetChecksum(mContext.getAssets(), PRODUCTION_LABELS_FILE_PATH); 545 assertThat(labelsProductionTopicsChecksum) 546 .isEqualTo(productionClassifierAssetsMetadata.get("labels_topics").get("checksum")); 547 548 // Compute SHA256 checksum of precomputed apps topics file in production assets 549 // and check the result can match the checksum saved in the classifier assets metadata file. 550 String precomputedAppsProductionChecksum = 551 computeClassifierAssetChecksum(mContext.getAssets(), PRODUCTION_APPS_FILE_PATH); 552 assertThat(precomputedAppsProductionChecksum) 553 .isEqualTo( 554 productionClassifierAssetsMetadata 555 .get("precomputed_app_list") 556 .get("checksum")); 557 } 558 559 @Test testGetBundledModelBuildId()560 public void testGetBundledModelBuildId() { 561 // Verify bundled model build_id. This should be changed along with model update. 562 assertThat( 563 CommonClassifierHelper.getBundledModelBuildId( 564 mContext, PRODUCTION_CLASSIFIER_ASSETS_METADATA_PATH)) 565 .isEqualTo(1986); 566 // Verify test model build_id. 567 assertThat( 568 CommonClassifierHelper.getBundledModelBuildId( 569 mContext, TEST_CLASSIFIER_ASSETS_METADATA_PATH)) 570 .isEqualTo(8); 571 } 572 getTestTopic(int topicId)573 private Topic getTestTopic(int topicId) { 574 return Topic.create(topicId, mTestTaxonomyVersion, mTestModelVersion); 575 } 576 getTestTopics(List<Integer> topicIds)577 private List<Topic> getTestTopics(List<Integer> topicIds) { 578 return topicIds.stream().map(this::getTestTopic).collect(Collectors.toList()); 579 } 580 getProductionTopic(int topicId)581 private Topic getProductionTopic(int topicId) { 582 return Topic.create(topicId, mProductionTaxonomyVersion, mProductionModelVersion); 583 } 584 getProductionTopics(List<Integer> topicIds)585 private List<Topic> getProductionTopics(List<Integer> topicIds) { 586 return topicIds.stream().map(this::getProductionTopic).collect(Collectors.toList()); 587 } 588 } 589