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