1 /*
2  * Copyright 2020 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 androidx.camera.integration.core;
18 
19 import static com.google.common.truth.Truth.assertThat;
20 import static com.google.common.truth.Truth.assertWithMessage;
21 
22 import com.google.common.truth.StandardSubjectBuilder;
23 
24 import org.jspecify.annotations.NonNull;
25 import org.junit.Test;
26 import org.junit.runner.RunWith;
27 import org.junit.runners.JUnit4;
28 
29 import java.util.Arrays;
30 
31 @RunWith(JUnit4.class)
32 public class FpsRecorderTest {
33 
34     private static final int TEST_FPS = 30;
35     private static final long TEST_FPS_DURATION_NANOS = 1_000_000_000L / TEST_FPS;
36     private static final long TIMESTAMP_BASE_NANOS = 1234567654321L;
37     private static final double FPS_ALLOWED_ERROR = 1e-4;
38 
39     @Test
fpsIsCalculated_withSingleSampleBuffer()40     public void fpsIsCalculated_withSingleSampleBuffer() {
41         FpsRecorder fpsRecorder = new FpsRecorder(1);
42         fpsRecorder.recordTimestamp(TIMESTAMP_BASE_NANOS);
43         double fps = fpsRecorder.recordTimestamp(TIMESTAMP_BASE_NANOS + TEST_FPS_DURATION_NANOS);
44         assertThat(fps).isWithin(FPS_ALLOWED_ERROR).of(TEST_FPS);
45     }
46 
47     @Test
fpsIsNaN_untilBufferSizePlusOneSamples()48     public void fpsIsNaN_untilBufferSizePlusOneSamples() {
49         double[] expected = {Double.NaN, Double.NaN, Double.NaN, Double.NaN, TEST_FPS};
50         double[] actual = new double[expected.length];
51 
52         FpsRecorder fpsRecorder = new FpsRecorder(expected.length - 1);
53         long timestamp = TIMESTAMP_BASE_NANOS;
54         for (int i = 0; i < expected.length; ++i) {
55             actual[i] = fpsRecorder.recordTimestamp(timestamp);
56             timestamp += TEST_FPS_DURATION_NANOS;
57         }
58 
59         // Cannot compare with withTolerance() since NaN is not considered equal to itself in
60         // that case
61         for (int i = 0; i < actual.length; ++i) {
62             if (Double.isFinite(actual[i])) {
63                 assertThat(actual[i]).isWithin(FPS_ALLOWED_ERROR).of(expected[i]);
64             } else {
65                 assertThat(actual[i]).isEqualTo(expected[i]);
66             }
67         }
68 
69         assertElementsWithinTolerance_ignoringNaN(actual, expected);
70     }
71 
72     @Test
fpsIsNaN_afterReset()73     public void fpsIsNaN_afterReset() {
74         double[] expected =
75                 {Double.NaN, Double.NaN, Double.NaN, TEST_FPS, Double.NaN, Double.NaN, Double.NaN,
76                         TEST_FPS};
77         double[] actual = new double[expected.length];
78 
79         FpsRecorder fpsRecorder = new FpsRecorder(expected.length / 2 - 1);
80         long timestamp = TIMESTAMP_BASE_NANOS;
81         for (int i = 0; i < expected.length; ++i) {
82             if (i == expected.length / 2) {
83                 fpsRecorder.reset();
84             }
85 
86             actual[i] = fpsRecorder.recordTimestamp(timestamp);
87             timestamp += TEST_FPS_DURATION_NANOS;
88         }
89 
90         assertElementsWithinTolerance_ignoringNaN(actual, expected);
91     }
92 
93     @Test
fpsIsCalculated_withLargeBuffer()94     public void fpsIsCalculated_withLargeBuffer() {
95         int bufferSize = 100;
96         FpsRecorder fpsRecorder = new FpsRecorder(bufferSize);
97         long timeStamp = TIMESTAMP_BASE_NANOS;
98         double fpsOut = Double.NaN;
99         for (int i = 0; i < bufferSize + 1; ++i) {
100             fpsOut = fpsRecorder.recordTimestamp(timeStamp);
101             timeStamp += TEST_FPS_DURATION_NANOS;
102         }
103 
104         assertThat(fpsOut).isWithin(FPS_ALLOWED_ERROR).of(TEST_FPS);
105     }
106 
107     @Test
skippedFrame_dropsFps()108     public void skippedFrame_dropsFps() {
109         int bufferSize = 4;
110         FpsRecorder fpsRecorder = new FpsRecorder(bufferSize);
111         long timeStamp = TIMESTAMP_BASE_NANOS;
112         double fpsOut = Double.NaN;
113         for (int i = 0; i < bufferSize + 1; ++i) {
114             fpsOut = fpsRecorder.recordTimestamp(timeStamp);
115             timeStamp += TEST_FPS_DURATION_NANOS;
116             if (i == 1) {
117                 // Simulate dropped frame. Add additional duration to timestamp.
118                 timeStamp += TEST_FPS_DURATION_NANOS;
119             }
120         }
121 
122         assertThat(fpsOut).isNotWithin(FPS_ALLOWED_ERROR).of(TEST_FPS);
123         assertThat(fpsOut).isLessThan(TEST_FPS);
124     }
125 
assertElementsWithinTolerance_ignoringNaN(double @NonNull [] actual, double @NonNull [] expected)126     private void assertElementsWithinTolerance_ignoringNaN(double @NonNull [] actual,
127             double @NonNull [] expected) {
128         // Cannot compare with withTolerance() since NaN is not considered equal to itself in
129         // that case
130         for (int i = 0; i < actual.length; ++i) {
131             StandardSubjectBuilder assertWithMessage = assertWithMessage(
132                     "Assumption violation while "
133                             + "comparing actual: [%s] to expected: [%s] at position %s",
134                     Arrays.toString(actual),
135                     Arrays.toString(expected),
136                     Integer.toString(i));
137             if (Double.isFinite(actual[i])) {
138                 assertWithMessage.that(actual[i]).isWithin(FPS_ALLOWED_ERROR).of(expected[i]);
139             } else {
140                 assertWithMessage.that(actual[i]).isEqualTo(expected[i]);
141             }
142         }
143     }
144 }
145