• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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