• 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
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 }