1 // Copyright 2023 The Chromium Authors 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 package org.chromium.net.httpflags; 6 7 import static com.google.common.truth.Truth.assertThat; 8 9 import static org.junit.Assert.assertThrows; 10 11 import androidx.test.ext.junit.runners.AndroidJUnit4; 12 import androidx.test.filters.SmallTest; 13 14 import com.google.common.truth.Correspondence; 15 import com.google.protobuf.ByteString; 16 17 import org.junit.Test; 18 import org.junit.runner.RunWith; 19 20 import org.chromium.base.test.util.Batch; 21 22 import java.util.Map; 23 24 /** Tests {@link ResolvedFlags} */ 25 @Batch(Batch.UNIT_TESTS) 26 @RunWith(AndroidJUnit4.class) 27 public final class ResolvedFlagsTest { 28 private static final Correspondence<ResolvedFlags.Value, String> FLAG_STRING_VALUE_EQUALS = 29 Correspondence.transforming( 30 ResolvedFlags.Value::getStringValue, "has a string value of"); 31 stringConstrainedValue(String value)32 private static FlagValue.ConstrainedValue.Builder stringConstrainedValue(String value) { 33 return FlagValue.ConstrainedValue.newBuilder().setStringValue(value); 34 } 35 singleFlag(String flagName, FlagValue.Builder flagValue)36 private static Flags singleFlag(String flagName, FlagValue.Builder flagValue) { 37 return Flags.newBuilder().putFlags(flagName, flagValue.build()).build(); 38 } 39 40 @Test 41 @SmallTest testResolve_emptyOnEmptyProto()42 public void testResolve_emptyOnEmptyProto() { 43 assertThat( 44 ResolvedFlags.resolve(Flags.newBuilder().build(), "test_app_id", "1.2.3.4") 45 .flags()) 46 .isEmpty(); 47 } 48 49 @Test 50 @SmallTest testResolve_returnsAllFlags()51 public void testResolve_returnsAllFlags() { 52 assertThat( 53 ResolvedFlags.resolve( 54 Flags.newBuilder() 55 .putFlags( 56 "test_flag_1", 57 FlagValue.newBuilder() 58 .addConstrainedValues( 59 stringConstrainedValue( 60 "test_flag_1_value")) 61 .build()) 62 .putFlags( 63 "test_flag_2", 64 FlagValue.newBuilder() 65 .addConstrainedValues( 66 stringConstrainedValue( 67 "test_flag_2_value")) 68 .build()) 69 .build(), 70 "test_app_id", 71 "1.2.3.4") 72 .flags()) 73 .comparingValuesUsing(FLAG_STRING_VALUE_EQUALS) 74 .containsExactly( 75 "test_flag_1", "test_flag_1_value", "test_flag_2", "test_flag_2_value"); 76 } 77 78 @Test 79 @SmallTest testResolve_doesNotReturnFlagWithEmptyConstrainedValue()80 public void testResolve_doesNotReturnFlagWithEmptyConstrainedValue() { 81 assertThat( 82 ResolvedFlags.resolve( 83 singleFlag( 84 "test_flag", 85 FlagValue.newBuilder() 86 .addConstrainedValues( 87 FlagValue.ConstrainedValue 88 .newBuilder())), 89 "test_app_id", 90 "1.2.3.4") 91 .flags()) 92 .isEmpty(); 93 } 94 95 @Test 96 @SmallTest testResolve_doesNotReturnFlagWithNoConstrainedValues()97 public void testResolve_doesNotReturnFlagWithNoConstrainedValues() { 98 assertThat( 99 ResolvedFlags.resolve( 100 singleFlag("test_flag", FlagValue.newBuilder()), 101 "test_app_id", 102 "1.2.3.4") 103 .flags()) 104 .isEmpty(); 105 } 106 107 @Test 108 @SmallTest testResolve_returnsFlagThatMatchesAppId()109 public void testResolve_returnsFlagThatMatchesAppId() { 110 assertThat( 111 ResolvedFlags.resolve( 112 singleFlag( 113 "test_flag", 114 FlagValue.newBuilder() 115 .addConstrainedValues( 116 stringConstrainedValue( 117 "test_flag_value") 118 .setAppId("test_app_id"))), 119 "test_app_id", 120 "1.2.3.4") 121 .flags()) 122 .comparingValuesUsing(FLAG_STRING_VALUE_EQUALS) 123 .containsExactly("test_flag", "test_flag_value"); 124 } 125 126 @Test 127 @SmallTest testResolve_doesNotReturnFlagThatDoesNotMatchAppId()128 public void testResolve_doesNotReturnFlagThatDoesNotMatchAppId() { 129 assertThat( 130 ResolvedFlags.resolve( 131 singleFlag( 132 "test_flag", 133 FlagValue.newBuilder() 134 .addConstrainedValues( 135 stringConstrainedValue( 136 "test_flag_value") 137 .setAppId( 138 "nonmatching_app_id"))), 139 "test_app_id", 140 "1.2.3.4") 141 .flags()) 142 .isEmpty(); 143 } 144 145 @Test 146 @SmallTest testResolve_throwsOnEmptyCronetVersion()147 public void testResolve_throwsOnEmptyCronetVersion() { 148 assertThrows( 149 IllegalArgumentException.class, 150 () -> { 151 ResolvedFlags.resolve(Flags.newBuilder().build(), "test_app_id", ""); 152 }); 153 } 154 155 @Test 156 @SmallTest testResolve_throwsOnInvalidCronetVersion()157 public void testResolve_throwsOnInvalidCronetVersion() { 158 assertThrows( 159 IllegalArgumentException.class, 160 () -> { 161 ResolvedFlags.resolve(Flags.newBuilder().build(), "test_app_id", "1.2.a.4"); 162 }); 163 } 164 165 @Test 166 @SmallTest testResolve_throwsOnEmptyMinVersion()167 public void testResolve_throwsOnEmptyMinVersion() { 168 Flags flags = 169 singleFlag( 170 "test_flag", 171 FlagValue.newBuilder() 172 .addConstrainedValues( 173 stringConstrainedValue("test_flag_value") 174 .setMinVersion(""))); 175 assertThrows( 176 IllegalArgumentException.class, 177 () -> { 178 ResolvedFlags.resolve(flags, "test_app_id", "1.2.3.4"); 179 }); 180 } 181 182 @Test 183 @SmallTest testResolve_throwsOnInvalidMinVersion()184 public void testResolve_throwsOnInvalidMinVersion() { 185 Flags flags = 186 singleFlag( 187 "test_flag", 188 FlagValue.newBuilder() 189 .addConstrainedValues( 190 stringConstrainedValue("test_flag_value") 191 .setMinVersion("1.2.a.4"))); 192 assertThrows( 193 IllegalArgumentException.class, 194 () -> { 195 ResolvedFlags.resolve(flags, "test_app_id", "1.2.3.4"); 196 }); 197 } 198 checkMinVersion(String cronetVersion, String minVersion, boolean expectMatch)199 private void checkMinVersion(String cronetVersion, String minVersion, boolean expectMatch) { 200 Map<String, ResolvedFlags.Value> flags = 201 ResolvedFlags.resolve( 202 singleFlag( 203 "test_flag", 204 FlagValue.newBuilder() 205 .addConstrainedValues( 206 stringConstrainedValue("test_flag_value") 207 .setMinVersion(minVersion))), 208 "test_app_id", 209 cronetVersion) 210 .flags(); 211 if (expectMatch) { 212 assertThat(flags) 213 .comparingValuesUsing(FLAG_STRING_VALUE_EQUALS) 214 .containsExactly("test_flag", "test_flag_value"); 215 } else { 216 assertThat(flags).isEmpty(); 217 } 218 } 219 220 @Test 221 @SmallTest testResolve_returnsFlagIfCronetVersionIsSameAsMinVersion()222 public void testResolve_returnsFlagIfCronetVersionIsSameAsMinVersion() { 223 checkMinVersion("5.6.7.8", "5.6.7.8", true); 224 } 225 226 @Test 227 @SmallTest testResolve_returnsFlagIfCronetPatchVersionIsHigherThanMinPatchVersion()228 public void testResolve_returnsFlagIfCronetPatchVersionIsHigherThanMinPatchVersion() { 229 checkMinVersion("5.6.7.9", "5.6.7.8", true); 230 } 231 232 @Test 233 @SmallTest testResolve_doesNotReturnFlagIfCronetPatchVersionIsLowerThanMinPatchVersion()234 public void testResolve_doesNotReturnFlagIfCronetPatchVersionIsLowerThanMinPatchVersion() { 235 checkMinVersion("5.6.7.7", "5.6.7.8", false); 236 } 237 238 @Test 239 @SmallTest testResolve_returnsFlagIfCronetMajorVersionIsHigherThanMinMajorVersion()240 public void testResolve_returnsFlagIfCronetMajorVersionIsHigherThanMinMajorVersion() { 241 checkMinVersion("6.6.7.8", "5.6.7.8", true); 242 } 243 244 @Test 245 @SmallTest testResolve_doesNotReturnFlagIfCronetPatchVersionIsLowerThanMinMajorVersion()246 public void testResolve_doesNotReturnFlagIfCronetPatchVersionIsLowerThanMinMajorVersion() { 247 checkMinVersion("4.6.7.7", "5.6.7.8", false); 248 } 249 250 @Test 251 @SmallTest testResolve_returnsFlagOnCronetMajorVersionMatch()252 public void testResolve_returnsFlagOnCronetMajorVersionMatch() { 253 checkMinVersion("5.0.0.0", "5", true); 254 } 255 256 @Test 257 @SmallTest testResolve_doesNotReturnFlagOnLowerCronetMajorVersion()258 public void testResolve_doesNotReturnFlagOnLowerCronetMajorVersion() { 259 checkMinVersion("4.9.9.9", "5", false); 260 } 261 262 @Test 263 @SmallTest testResolve_returnsOnlyMatchingConstrainedValue()264 public void testResolve_returnsOnlyMatchingConstrainedValue() { 265 FlagValue.ConstrainedValue matching_value = 266 stringConstrainedValue("matching_test_flag_value").setAppId("test_app_id").build(); 267 FlagValue.ConstrainedValue nonmatching_value = 268 stringConstrainedValue("nonmatching_test_flag_value") 269 .setAppId("nonmatching_app_id") 270 .build(); 271 272 assertThat( 273 ResolvedFlags.resolve( 274 singleFlag( 275 "test_flag", 276 FlagValue.newBuilder() 277 .addConstrainedValues(matching_value) 278 .addConstrainedValues(nonmatching_value)), 279 "test_app_id", 280 "1.2.3.4") 281 .flags()) 282 .comparingValuesUsing(FLAG_STRING_VALUE_EQUALS) 283 .containsExactly("test_flag", "matching_test_flag_value"); 284 assertThat( 285 ResolvedFlags.resolve( 286 singleFlag( 287 "test_flag", 288 FlagValue.newBuilder() 289 .addConstrainedValues(nonmatching_value) 290 .addConstrainedValues(matching_value)), 291 "test_app_id", 292 "1.2.3.4") 293 .flags()) 294 .comparingValuesUsing(FLAG_STRING_VALUE_EQUALS) 295 .containsExactly("test_flag", "matching_test_flag_value"); 296 } 297 298 @Test 299 @SmallTest testResolve_returnsFirstMatchingConstrainedValue()300 public void testResolve_returnsFirstMatchingConstrainedValue() { 301 assertThat( 302 ResolvedFlags.resolve( 303 singleFlag( 304 "test_flag", 305 FlagValue.newBuilder() 306 .addConstrainedValues( 307 stringConstrainedValue( 308 "test_flag_value_1")) 309 .addConstrainedValues( 310 stringConstrainedValue( 311 "test_flag_value_2"))), 312 "test_app_id", 313 "1.2.3.4") 314 .flags()) 315 .comparingValuesUsing(FLAG_STRING_VALUE_EQUALS) 316 .containsExactly("test_flag", "test_flag_value_1"); 317 } 318 319 @Test 320 @SmallTest testResolve_doesNotReturnFlagIfMatchingValueIsEmpty()321 public void testResolve_doesNotReturnFlagIfMatchingValueIsEmpty() { 322 assertThat( 323 ResolvedFlags.resolve( 324 singleFlag( 325 "test_flag", 326 FlagValue.newBuilder() 327 .addConstrainedValues( 328 FlagValue.ConstrainedValue 329 .newBuilder()) 330 .addConstrainedValues( 331 stringConstrainedValue( 332 "test_flag_value_should_be_skipped"))), 333 "test_app_id", 334 "1.2.3.4") 335 .flags()) 336 .isEmpty(); 337 } 338 339 @Test 340 @SmallTest testResolve_returnsFalseValue()341 public void testResolve_returnsFalseValue() { 342 ResolvedFlags.Value value = 343 ResolvedFlags.resolve( 344 singleFlag( 345 "test_flag", 346 FlagValue.newBuilder() 347 .addConstrainedValues( 348 FlagValue.ConstrainedValue.newBuilder() 349 .setBoolValue(false))), 350 "test_app_id", 351 "1.2.3.4") 352 .flags() 353 .get("test_flag"); 354 assertThat(value).isNotNull(); 355 assertThat(value.getType()).isEqualTo(ResolvedFlags.Value.Type.BOOL); 356 assertThat(value.getBoolValue()).isFalse(); 357 } 358 359 @Test 360 @SmallTest testResolve_returnsTrueValue()361 public void testResolve_returnsTrueValue() { 362 ResolvedFlags.Value value = 363 ResolvedFlags.resolve( 364 singleFlag( 365 "test_flag", 366 FlagValue.newBuilder() 367 .addConstrainedValues( 368 FlagValue.ConstrainedValue.newBuilder() 369 .setBoolValue(true))), 370 "test_app_id", 371 "1.2.3.4") 372 .flags() 373 .get("test_flag"); 374 assertThat(value).isNotNull(); 375 assertThat(value.getType()).isEqualTo(ResolvedFlags.Value.Type.BOOL); 376 assertThat(value.getBoolValue()).isTrue(); 377 } 378 379 @Test 380 @SmallTest testResolve_returnsZeroIntValue()381 public void testResolve_returnsZeroIntValue() { 382 ResolvedFlags.Value value = 383 ResolvedFlags.resolve( 384 singleFlag( 385 "test_flag", 386 FlagValue.newBuilder() 387 .addConstrainedValues( 388 FlagValue.ConstrainedValue.newBuilder() 389 .setIntValue(0))), 390 "test_app_id", 391 "1.2.3.4") 392 .flags() 393 .get("test_flag"); 394 assertThat(value).isNotNull(); 395 assertThat(value.getType()).isEqualTo(ResolvedFlags.Value.Type.INT); 396 assertThat(value.getIntValue()).isEqualTo(0); 397 } 398 399 @Test 400 @SmallTest testResolve_returnsNonZeroIntValue()401 public void testResolve_returnsNonZeroIntValue() { 402 ResolvedFlags.Value value = 403 ResolvedFlags.resolve( 404 singleFlag( 405 "test_flag", 406 FlagValue.newBuilder() 407 .addConstrainedValues( 408 FlagValue.ConstrainedValue.newBuilder() 409 .setIntValue(42))), 410 "test_app_id", 411 "1.2.3.4") 412 .flags() 413 .get("test_flag"); 414 assertThat(value).isNotNull(); 415 assertThat(value.getType()).isEqualTo(ResolvedFlags.Value.Type.INT); 416 assertThat(value.getIntValue()).isEqualTo(42); 417 } 418 419 @Test 420 @SmallTest testResolve_returnsZeroFloatValue()421 public void testResolve_returnsZeroFloatValue() { 422 ResolvedFlags.Value value = 423 ResolvedFlags.resolve( 424 singleFlag( 425 "test_flag", 426 FlagValue.newBuilder() 427 .addConstrainedValues( 428 FlagValue.ConstrainedValue.newBuilder() 429 .setFloatValue(0))), 430 "test_app_id", 431 "1.2.3.4") 432 .flags() 433 .get("test_flag"); 434 assertThat(value).isNotNull(); 435 assertThat(value.getType()).isEqualTo(ResolvedFlags.Value.Type.FLOAT); 436 assertThat(value.getFloatValue()).isEqualTo(0f); 437 } 438 439 @Test 440 @SmallTest testResolve_returnsNonZeroFloatValue()441 public void testResolve_returnsNonZeroFloatValue() { 442 ResolvedFlags.Value value = 443 ResolvedFlags.resolve( 444 singleFlag( 445 "test_flag", 446 FlagValue.newBuilder() 447 .addConstrainedValues( 448 FlagValue.ConstrainedValue.newBuilder() 449 .setFloatValue(42))), 450 "test_app_id", 451 "1.2.3.4") 452 .flags() 453 .get("test_flag"); 454 assertThat(value).isNotNull(); 455 assertThat(value.getType()).isEqualTo(ResolvedFlags.Value.Type.FLOAT); 456 assertThat(value.getFloatValue()).isEqualTo(42f); 457 } 458 459 @Test 460 @SmallTest testResolve_returnsEmptyStringValue()461 public void testResolve_returnsEmptyStringValue() { 462 ResolvedFlags.Value value = 463 ResolvedFlags.resolve( 464 singleFlag( 465 "test_flag", 466 FlagValue.newBuilder() 467 .addConstrainedValues( 468 FlagValue.ConstrainedValue.newBuilder() 469 .setStringValue(""))), 470 "test_app_id", 471 "1.2.3.4") 472 .flags() 473 .get("test_flag"); 474 assertThat(value).isNotNull(); 475 assertThat(value.getType()).isEqualTo(ResolvedFlags.Value.Type.STRING); 476 assertThat(value.getStringValue()).isEqualTo(""); 477 } 478 479 @Test 480 @SmallTest testResolve_returnsNonEmptyStringValue()481 public void testResolve_returnsNonEmptyStringValue() { 482 ResolvedFlags.Value value = 483 ResolvedFlags.resolve( 484 singleFlag( 485 "test_flag", 486 FlagValue.newBuilder() 487 .addConstrainedValues( 488 FlagValue.ConstrainedValue.newBuilder() 489 .setStringValue( 490 "test_string_value"))), 491 "test_app_id", 492 "1.2.3.4") 493 .flags() 494 .get("test_flag"); 495 assertThat(value).isNotNull(); 496 assertThat(value.getType()).isEqualTo(ResolvedFlags.Value.Type.STRING); 497 assertThat(value.getStringValue()).isEqualTo("test_string_value"); 498 } 499 500 @Test 501 @SmallTest testResolve_returnsEmptyBytesValue()502 public void testResolve_returnsEmptyBytesValue() { 503 ResolvedFlags.Value value = 504 ResolvedFlags.resolve( 505 singleFlag( 506 "test_flag", 507 FlagValue.newBuilder() 508 .addConstrainedValues( 509 FlagValue.ConstrainedValue.newBuilder() 510 .setBytesValue(ByteString.EMPTY))), 511 "test_app_id", 512 "1.2.3.4") 513 .flags() 514 .get("test_flag"); 515 assertThat(value).isNotNull(); 516 assertThat(value.getType()).isEqualTo(ResolvedFlags.Value.Type.BYTES); 517 assertThat(value.getBytesValue()).isEqualTo(ByteString.EMPTY); 518 } 519 520 @Test 521 @SmallTest testResolve_returnsNonEmptyBytesValue()522 public void testResolve_returnsNonEmptyBytesValue() { 523 ByteString byteString = ByteString.copyFrom(new byte[] {0, 1, 2, 42, -128, 127}); 524 ResolvedFlags.Value value = 525 ResolvedFlags.resolve( 526 singleFlag( 527 "test_flag", 528 FlagValue.newBuilder() 529 .addConstrainedValues( 530 FlagValue.ConstrainedValue.newBuilder() 531 .setBytesValue(byteString))), 532 "test_app_id", 533 "1.2.3.4") 534 .flags() 535 .get("test_flag"); 536 assertThat(value).isNotNull(); 537 assertThat(value.getType()).isEqualTo(ResolvedFlags.Value.Type.BYTES); 538 assertThat(value.getBytesValue()).isEqualTo(byteString); 539 } 540 541 @Test 542 @SmallTest testResolve_throwsOnWrongTypeAccess()543 public void testResolve_throwsOnWrongTypeAccess() { 544 ResolvedFlags.Value value = 545 ResolvedFlags.resolve( 546 singleFlag( 547 "test_flag", 548 FlagValue.newBuilder() 549 .addConstrainedValues( 550 FlagValue.ConstrainedValue.newBuilder() 551 .setStringValue("test_string"))), 552 "test_app_id", 553 "1.2.3.4") 554 .flags() 555 .get("test_flag"); 556 assertThat(value.getType()).isEqualTo(ResolvedFlags.Value.Type.STRING); 557 assertThrows( 558 IllegalStateException.class, 559 () -> { 560 value.getIntValue(); 561 }); 562 } 563 } 564