1 /* <lambda>null2 * Copyright (C) 2021 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.server.wm.flicker.traces 18 19 import androidx.annotation.VisibleForTesting 20 import com.android.server.wm.flicker.assertions.FlickerSubject 21 import com.android.server.wm.traces.common.Rect 22 import com.android.server.wm.traces.common.RectF 23 import com.android.server.wm.traces.common.Region 24 import com.android.server.wm.traces.parser.toAndroidRect 25 import com.android.server.wm.traces.parser.toAndroidRegion 26 import com.google.common.truth.Fact 27 import com.google.common.truth.FailureMetadata 28 import com.google.common.truth.StandardSubjectBuilder 29 30 /** 31 * Truth subject for [Rect] objects, used to make assertions over behaviors that occur on a 32 * rectangle. 33 */ 34 class RegionSubject( 35 fm: FailureMetadata, 36 private val subjects: List<FlickerSubject>, 37 val region: android.graphics.Region 38 ) : FlickerSubject(fm, region) { 39 private val topPositionSubject 40 get() = check(MSG_ERROR_TOP_POSITION).that(region.bounds.top) 41 private val bottomPositionSubject 42 get() = check(MSG_ERROR_BOTTOM_POSITION).that(region.bounds.bottom) 43 private val leftPositionSubject 44 get() = check(MSG_ERROR_LEFT_POSITION).that(region.bounds.left) 45 private val rightPositionSubject 46 get() = check(MSG_ERROR_RIGHT_POSITION).that(region.bounds.right) 47 private val areaSubject 48 get() = check(MSG_ERROR_AREA).that(region.bounds.area) 49 50 private val android.graphics.Rect.area get() = this.width() * this.height() 51 private val Rect.area get() = this.width * this.height 52 53 override val defaultFacts: String = buildString { 54 subjects.forEach { subject -> appendln(subject.defaultFacts) } 55 } 56 57 /** 58 * {@inheritDoc} 59 */ 60 override fun clone(): FlickerSubject { 61 return RegionSubject(fm, subjects, region) 62 } 63 64 /** 65 * {@inheritDoc} 66 */ 67 override fun fail(reason: List<Fact>): FlickerSubject { 68 val newReason = reason.toMutableList() 69 return super.fail(newReason) 70 } 71 72 private fun assertLeftRightAndAreaEquals(other: android.graphics.Region) { 73 leftPositionSubject.isEqualTo(other.bounds.left) 74 rightPositionSubject.isEqualTo(other.bounds.right) 75 areaSubject.isEqualTo(other.bounds.area) 76 } 77 78 /** 79 * Asserts that the top and bottom coordinates of [RegionSubject.region] are smaller 80 * or equal to those of [region]. 81 * 82 * Also checks that the left and right positions, as well as area, don't change 83 */ 84 fun isHigherOrEqual(subject: RegionSubject): RegionSubject = apply { 85 isHigherOrEqual(subject.region) 86 } 87 88 /** 89 * Asserts that the top and bottom coordinates of [other] are smaller or equal to 90 * those of [region]. 91 * 92 * Also checks that the left and right positions, as well as area, don't change 93 */ 94 fun isHigherOrEqual(other: Rect): RegionSubject = apply { 95 isHigherOrEqual(other.toAndroidRect()) 96 } 97 98 /** 99 * Asserts that the top and bottom coordinates of [other] are smaller or equal to 100 * those of [region]. 101 * 102 * Also checks that the left and right positions, as well as area, don't change 103 */ 104 fun isHigherOrEqual(other: Region): RegionSubject = apply { 105 isHigherOrEqual(other.toAndroidRegion()) 106 } 107 108 /** 109 * Asserts that the top and bottom coordinates of [other] are smaller or equal to 110 * those of [region]. 111 * 112 * Also checks that the left and right positions, as well as area, don't change 113 */ 114 fun isHigherOrEqual(other: android.graphics.Rect): RegionSubject = apply { 115 isHigherOrEqual(android.graphics.Region(other)) 116 } 117 118 /** 119 * Asserts that the top and bottom coordinates of [other] are smaller or equal to 120 * those of [region]. 121 * 122 * Also checks that the left and right positions, as well as area, don't change 123 */ 124 fun isHigherOrEqual(other: android.graphics.Region): RegionSubject = apply { 125 assertLeftRightAndAreaEquals(other) 126 topPositionSubject.isAtMost(other.bounds.top) 127 bottomPositionSubject.isAtMost(other.bounds.bottom) 128 } 129 130 /** 131 * Asserts that the top and bottom coordinates of [RegionSubject.region] are greater 132 * or equal to those of [region]. 133 * 134 * Also checks that the left and right positions, as well as area, don't change 135 */ 136 fun isLowerOrEqual(subject: RegionSubject): RegionSubject = apply { 137 isLowerOrEqual(subject.region) 138 } 139 140 /** 141 * Asserts that the top and bottom coordinates of [other] are greater or equal to 142 * those of [region]. 143 * 144 * Also checks that the left and right positions, as well as area, don't change 145 */ 146 fun isLowerOrEqual(other: Rect): RegionSubject = apply { 147 isLowerOrEqual(other.toAndroidRect()) 148 } 149 150 /** 151 * Asserts that the top and bottom coordinates of [other] are greater or equal to 152 * those of [region]. 153 * 154 * Also checks that the left and right positions, as well as area, don't change 155 */ 156 fun isLowerOrEqual(other: android.graphics.Rect): RegionSubject = apply { 157 isLowerOrEqual(android.graphics.Region(other)) 158 } 159 160 /** 161 * Asserts that the top and bottom coordinates of [other] are greater or equal to 162 * those of [region]. 163 * 164 * Also checks that the left and right positions, as well as area, don't change 165 */ 166 fun isLowerOrEqual(other: android.graphics.Region): RegionSubject = apply { 167 assertLeftRightAndAreaEquals(other) 168 topPositionSubject.isAtLeast(other.bounds.top) 169 bottomPositionSubject.isAtLeast(other.bounds.bottom) 170 } 171 172 /** 173 * Asserts that the top and bottom coordinates of [RegionSubject.region] are smaller than 174 * those of [region]. 175 * 176 * Also checks that the left and right positions, as well as area, don't change 177 */ 178 fun isHigher(subject: RegionSubject): RegionSubject = apply { 179 isHigher(subject.region) 180 } 181 182 /** 183 * Asserts that the top and bottom coordinates of [other] are smaller than those of [region]. 184 * 185 * Also checks that the left and right positions, as well as area, don't change 186 */ 187 fun isHigher(other: Rect): RegionSubject = apply { 188 isHigher(other.toAndroidRect()) 189 } 190 191 /** 192 * Asserts that the top and bottom coordinates of [other] are smaller than those of [region]. 193 * 194 * Also checks that the left and right positions, as well as area, don't change 195 */ 196 fun isHigher(other: android.graphics.Rect): RegionSubject = apply { 197 isHigher(android.graphics.Region(other)) 198 } 199 200 /** 201 * Asserts that the top and bottom coordinates of [other] are smaller than those of [region]. 202 * 203 * Also checks that the left and right positions, as well as area, don't change 204 */ 205 fun isHigher(other: android.graphics.Region): RegionSubject = apply { 206 assertLeftRightAndAreaEquals(other) 207 topPositionSubject.isLessThan(other.bounds.top) 208 bottomPositionSubject.isLessThan(other.bounds.bottom) 209 } 210 211 /** 212 * Asserts that the top and bottom coordinates of [RegionSubject.region] are greater than 213 * those of [region]. 214 * 215 * Also checks that the left and right positions, as well as area, don't change 216 */ 217 fun isLower(subject: RegionSubject): RegionSubject = apply { 218 isLower(subject.region) 219 } 220 221 /** 222 * Asserts that the top and bottom coordinates of [other] are greater than those of [region]. 223 * 224 * Also checks that the left and right positions, as well as area, don't change 225 */ 226 fun isLower(other: Rect): RegionSubject = apply { 227 isLower(other.toAndroidRect()) 228 } 229 230 /** 231 * Asserts that the top and bottom coordinates of [other] are greater than those of [region]. 232 * 233 * Also checks that the left and right positions, as well as area, don't change 234 */ 235 fun isLower(other: android.graphics.Rect): RegionSubject = apply { 236 isLower(android.graphics.Region(other)) 237 } 238 239 /** 240 * Asserts that the top and bottom coordinates of [other] are greater than those of [region]. 241 * 242 * Also checks that the left and right positions, as well as area, don't change 243 */ 244 fun isLower(other: android.graphics.Region): RegionSubject = apply { 245 assertLeftRightAndAreaEquals(other) 246 topPositionSubject.isGreaterThan(other.bounds.top) 247 bottomPositionSubject.isGreaterThan(other.bounds.bottom) 248 } 249 250 /** 251 * Asserts that [region] covers at most [testRegion], that is, its area doesn't cover any 252 * point outside of [testRegion]. 253 * 254 * @param testRegion Expected covered area 255 */ 256 fun coversAtMost(testRegion: android.graphics.Region): RegionSubject = apply { 257 val testRect = testRegion.bounds 258 val intersection = android.graphics.Region(region) 259 val covers = intersection.op(testRect, android.graphics.Region.Op.INTERSECT) && 260 !intersection.op(region, android.graphics.Region.Op.XOR) 261 262 if (!covers) { 263 fail(Fact.fact("Region to test", testRegion), 264 Fact.fact("Covered region", region), 265 Fact.fact("Out-of-bounds region", intersection)) 266 } 267 } 268 269 /** 270 * Asserts that [region] covers at most [testRegion], that is, its area doesn't cover any 271 * point outside of [testRegion]. 272 * 273 * @param testRegion Expected covered area 274 */ 275 fun coversAtMost(testRegion: Region): RegionSubject = apply { 276 coversAtMost(testRegion.toAndroidRegion()) 277 } 278 279 /** 280 * Asserts that [region] covers at most [testRect], that is, its area doesn't cover any 281 * point outside of [testRect]. 282 * 283 * @param testRect Expected covered area 284 */ 285 fun coversAtMost(testRect: Rect): RegionSubject = apply { 286 coversAtMost(Region(testRect)) 287 } 288 289 /** 290 * Asserts that [region] covers at most [testRect], that is, its area doesn't cover any 291 * point outside of [testRect]. 292 * 293 * @param testRect Expected covered area 294 */ 295 fun coversAtMost(testRect: android.graphics.Rect): RegionSubject = apply { 296 coversAtMost(android.graphics.Region(testRect)) 297 } 298 299 /** 300 * Asserts that [region] covers at least [testRegion], that is, its area covers each point 301 * in the region 302 * 303 * @param testRegion Expected covered area 304 */ 305 fun coversAtLeast(testRegion: android.graphics.Region): RegionSubject = apply { 306 val intersection = android.graphics.Region(region) 307 val covers = intersection.op(testRegion, android.graphics.Region.Op.INTERSECT) && 308 !intersection.op(testRegion, android.graphics.Region.Op.XOR) 309 310 if (!covers) { 311 fail(Fact.fact("Region to test", testRegion), 312 Fact.fact("Covered region", region), 313 Fact.fact("Uncovered region", intersection)) 314 } 315 } 316 317 /** 318 * Asserts that [region] covers at least [testRegion], that is, its area covers each point 319 * in the region 320 * 321 * @param testRegion Expected covered area 322 */ 323 fun coversAtLeast(testRegion: Region): RegionSubject = apply { 324 coversAtLeast(testRegion.toAndroidRegion()) 325 } 326 327 /** 328 * Asserts that [region] covers at least [testRect], that is, its area covers each point 329 * in the region 330 * 331 * @param testRect Expected covered area 332 */ 333 fun coversAtLeast(testRect: Rect): RegionSubject = apply { 334 coversAtLeast(Region(testRect)) 335 } 336 337 /** 338 * Asserts that [region] covers at least [testRect], that is, its area covers each point 339 * in the region 340 * 341 * @param testRect Expected covered area 342 */ 343 fun coversAtLeast(testRect: android.graphics.Rect): RegionSubject = apply { 344 coversAtLeast(android.graphics.Region(testRect)) 345 } 346 347 /** 348 * Asserts that [region] covers at exactly [testRegion] 349 * 350 * @param testRegion Expected covered area 351 */ 352 fun coversExactly(testRegion: android.graphics.Region): RegionSubject = apply { 353 val intersection = android.graphics.Region(region) 354 val isNotEmpty = intersection.op(testRegion, android.graphics.Region.Op.XOR) 355 356 if (isNotEmpty) { 357 fail(Fact.fact("Region to test", testRegion), 358 Fact.fact("Covered region", region), 359 Fact.fact("Uncovered region", intersection)) 360 } 361 } 362 363 /** 364 * Asserts that [region] covers at exactly [testRegion] 365 * 366 * @param testRegion Expected covered area 367 */ 368 fun coversExactly(testRegion: Region): RegionSubject = apply { 369 coversExactly(testRegion.toAndroidRegion()) 370 } 371 372 /** 373 * Asserts that [region] covers at exactly [testRect] 374 * 375 * @param testRect Expected covered area 376 */ 377 fun coversExactly(testRect: Rect): RegionSubject = apply { 378 coversExactly(testRect.toAndroidRect()) 379 } 380 381 /** 382 * Asserts that [region] covers at exactly [testRect] 383 * 384 * @param testRect Expected covered area 385 */ 386 fun coversExactly(testRect: android.graphics.Rect): RegionSubject = apply { 387 coversExactly(android.graphics.Region(testRect)) 388 } 389 390 /** 391 * Asserts that [region] and [testRegion] overlap 392 * 393 * @param testRegion Other area 394 */ 395 fun overlaps(testRegion: android.graphics.Region): RegionSubject = apply { 396 val intersection = android.graphics.Region(region) 397 val isEmpty = !intersection.op(testRegion, android.graphics.Region.Op.INTERSECT) 398 399 if (isEmpty) { 400 fail(Fact.fact("Region to test", testRegion), 401 Fact.fact("Covered region", region), 402 Fact.fact("Overlap region", intersection)) 403 } 404 } 405 406 /** 407 * Asserts that [region] and [testRegion] overlap 408 * 409 * @param testRegion Other area 410 */ 411 fun overlaps(testRegion: Region): RegionSubject = apply { 412 overlaps(testRegion.toAndroidRegion()) 413 } 414 415 /** 416 * Asserts that [region] and [testRect] overlap 417 * 418 * @param testRect Other area 419 */ 420 fun overlaps(testRect: android.graphics.Rect): RegionSubject = apply { 421 overlaps(android.graphics.Region(testRect)) 422 } 423 424 /** 425 * Asserts that [region] and [testRect] overlap 426 * 427 * @param testRect Other area 428 */ 429 fun overlaps(testRect: Rect): RegionSubject = apply { 430 overlaps(testRect.toAndroidRect()) 431 } 432 433 /** 434 * Asserts that [region] and [testRegion] don't overlap 435 * 436 * @param testRegion Other area 437 */ 438 fun notOverlaps(testRegion: android.graphics.Region): RegionSubject = apply { 439 val intersection = android.graphics.Region(region) 440 val isEmpty = !intersection.op(testRegion, android.graphics.Region.Op.INTERSECT) 441 442 if (!isEmpty) { 443 fail(Fact.fact("Region to test", testRegion), 444 Fact.fact("Covered region", region), 445 Fact.fact("Overlap region", intersection)) 446 } 447 } 448 449 /** 450 * Asserts that [region] and [testRegion] don't overlap 451 * 452 * @param testRegion Other area 453 */ 454 fun notOverlaps(testRegion: Region): RegionSubject = apply { 455 notOverlaps(testRegion.toAndroidRegion()) 456 } 457 458 /** 459 * Asserts that [region] and [testRect] don't overlap 460 * 461 * @param testRect Other area 462 */ 463 fun notOverlaps(testRect: android.graphics.Rect): RegionSubject = apply { 464 notOverlaps(android.graphics.Region(testRect)) 465 } 466 467 /** 468 * Asserts that [region] and [testRect] don't overlap 469 * 470 * @param testRect Other area 471 */ 472 fun notOverlaps(testRect: Rect): RegionSubject = apply { 473 notOverlaps(testRect.toAndroidRect()) 474 } 475 476 companion object { 477 @VisibleForTesting 478 const val MSG_ERROR_TOP_POSITION = "Incorrect top position" 479 480 @VisibleForTesting 481 const val MSG_ERROR_BOTTOM_POSITION = "Incorrect top position" 482 483 @VisibleForTesting 484 const val MSG_ERROR_LEFT_POSITION = "Incorrect left position" 485 486 @VisibleForTesting 487 const val MSG_ERROR_RIGHT_POSITION = "Incorrect right position" 488 489 @VisibleForTesting 490 const val MSG_ERROR_AREA = "Incorrect rect area" 491 492 private fun mergeRegions(regions: Array<Region>): android.graphics.Region { 493 val result = android.graphics.Region() 494 regions.forEach { region -> 495 region.rects.forEach { rect -> 496 result.op(rect.toAndroidRect(), android.graphics.Region.Op.UNION) 497 } 498 } 499 return result 500 } 501 502 /** 503 * Boiler-plate Subject.Factory for RectSubject 504 */ 505 @JvmStatic 506 @JvmOverloads 507 fun getFactory( 508 flickerSubjects: List<FlickerSubject> = emptyList() 509 ) = Factory { fm: FailureMetadata, region: android.graphics.Region? -> 510 val subjectRegion = region ?: android.graphics.Region() 511 RegionSubject(fm, flickerSubjects, subjectRegion) 512 } 513 514 /** 515 * User-defined entry point for existing android regions 516 */ 517 @JvmStatic 518 @JvmOverloads 519 fun assertThat( 520 region: android.graphics.Region?, 521 flickerSubjects: List<FlickerSubject> = emptyList() 522 ): RegionSubject { 523 val strategy = FlickerFailureStrategy() 524 val subject = StandardSubjectBuilder.forCustomFailureStrategy(strategy) 525 .about(getFactory(flickerSubjects)) 526 .that(region ?: android.graphics.Region()) as RegionSubject 527 strategy.init(subject) 528 return subject 529 } 530 531 /** 532 * User-defined entry point for existing rects 533 */ 534 @JvmStatic 535 @JvmOverloads 536 fun assertThat( 537 rect: Array<Rect>, 538 flickerSubjects: List<FlickerSubject> = emptyList() 539 ): RegionSubject = assertThat(Region(rect), flickerSubjects) 540 541 /** 542 * User-defined entry point for existing rects 543 */ 544 @JvmStatic 545 @JvmOverloads 546 fun assertThat( 547 rect: Rect?, 548 flickerSubjects: List<FlickerSubject> = emptyList() 549 ): RegionSubject = assertThat(Region(rect), flickerSubjects) 550 551 /** 552 * User-defined entry point for existing rects 553 */ 554 @JvmStatic 555 fun assertThat( 556 rect: RectF?, 557 flickerSubjects: List<FlickerSubject> = emptyList() 558 ): RegionSubject = assertThat(rect?.toRect(), flickerSubjects) 559 560 /** 561 * User-defined entry point for existing rects 562 */ 563 @JvmStatic 564 fun assertThat( 565 rect: Array<RectF>, 566 flickerSubjects: List<FlickerSubject> = emptyList() 567 ): RegionSubject = assertThat( 568 mergeRegions(rect.map { Region(it.toRect()) }.toTypedArray()), 569 flickerSubjects) 570 571 /** 572 * User-defined entry point for existing regions 573 */ 574 @JvmStatic 575 @JvmOverloads 576 fun assertThat( 577 regions: Array<Region>, 578 flickerSubjects: List<FlickerSubject> = emptyList() 579 ): RegionSubject = assertThat(mergeRegions(regions), flickerSubjects) 580 581 /** 582 * User-defined entry point for existing regions 583 */ 584 @JvmStatic 585 @JvmOverloads 586 fun assertThat( 587 region: Region?, 588 flickerSubjects: List<FlickerSubject> = emptyList() 589 ): RegionSubject = assertThat(region?.toAndroidRegion(), flickerSubjects) 590 } 591 }