blob: 06484103085d8a339bf8b24819427729a24b8c56 [file] [log] [blame]
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.adservices.service.common;
import static com.android.adservices.service.common.Throttler.ApiKey.ADID_API_APP_PACKAGE_NAME;
import static com.android.adservices.service.common.Throttler.ApiKey.APPSETID_API_APP_PACKAGE_NAME;
import static com.android.adservices.service.common.Throttler.ApiKey.MEASUREMENT_API_REGISTER_SOURCE;
import static com.android.adservices.service.common.Throttler.ApiKey.MEASUREMENT_API_REGISTER_TRIGGER;
import static com.android.adservices.service.common.Throttler.ApiKey.MEASUREMENT_API_REGISTER_WEB_SOURCE;
import static com.android.adservices.service.common.Throttler.ApiKey.UNKNOWN;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertThrows;
import androidx.test.filters.SmallTest;
import com.android.adservices.service.Flags;
import org.junit.Test;
/** Unit tests for {@link Throttler}. */
@SmallTest
public class ThrottlerTest {
@Test
public void testTryAcquire_skdName() throws InterruptedException {
// Create a throttler with 1 permit per second.
Throttler throttler = createThrottler(1);
// Sdk1 sends 2 requests almost concurrently. The first request is OK, the second fails
// since the time between 2 requests is too small (less than the
// sdkRequestPermitsPerSecond = 1)
assertThat(throttler.tryAcquire(Throttler.ApiKey.TOPICS_API_SDK_NAME, "sdk1")).isTrue();
assertThat(throttler.tryAcquire(Throttler.ApiKey.TOPICS_API_SDK_NAME, "sdk1")).isFalse();
// Sdk2 is OK since there was no previous sdk2 request.
assertThat(throttler.tryAcquire(Throttler.ApiKey.TOPICS_API_SDK_NAME, "sdk2")).isTrue();
// Now sleep for 1 second so that the permits is refilled.
Thread.sleep(1000L);
// Now the request is OK again.
assertThat(throttler.tryAcquire(Throttler.ApiKey.TOPICS_API_SDK_NAME, "sdk1")).isTrue();
assertThat(throttler.tryAcquire(Throttler.ApiKey.TOPICS_API_SDK_NAME, "sdk2")).isTrue();
}
@Test
public void testTryAcquire_appPackageName() throws InterruptedException {
// Create a throttler with 1 permit per second.
Throttler throttler = createThrottler(1);
// App1 sends 2 requests almost concurrently. The first request is OK, the second fails
// since the time between 2 requests is too small (less than the
// sdkRequestPermitsPerSecond = 1)
assertThat(throttler.tryAcquire(Throttler.ApiKey.TOPICS_API_APP_PACKAGE_NAME, "app1"))
.isTrue();
assertThat(throttler.tryAcquire(Throttler.ApiKey.TOPICS_API_APP_PACKAGE_NAME, "app1"))
.isFalse();
// Sdk1 calling Topics API is ok since App and Sdk are throttled separately.
assertThat(throttler.tryAcquire(Throttler.ApiKey.TOPICS_API_SDK_NAME, "sdk1")).isTrue();
// App2 is OK since there was no previous App2 request.
assertThat(throttler.tryAcquire(Throttler.ApiKey.TOPICS_API_APP_PACKAGE_NAME, "app2"))
.isTrue();
// Now sleep for 1 second so that the permits is refilled.
Thread.sleep(1000L);
// Now the request is OK again.
assertThat(throttler.tryAcquire(Throttler.ApiKey.TOPICS_API_APP_PACKAGE_NAME, "app1"))
.isTrue();
assertThat(throttler.tryAcquire(Throttler.ApiKey.TOPICS_API_APP_PACKAGE_NAME, "app2"))
.isTrue();
}
@Test
public void testTryAcquire_skippingRateLimiting() {
// Setting the permits per second to -1 so that we will skip the rate limiting.
Throttler throttler = createThrottler(-1);
assertThat(throttler.tryAcquire(Throttler.ApiKey.TOPICS_API_SDK_NAME, "sdk1")).isTrue();
// Without skipping rate limiting, this second request would have failed.
assertThat(throttler.tryAcquire(Throttler.ApiKey.TOPICS_API_SDK_NAME, "sdk1")).isTrue();
// Another SDK.
assertThat(throttler.tryAcquire(Throttler.ApiKey.TOPICS_API_SDK_NAME, "sdk2")).isTrue();
assertThat(throttler.tryAcquire(Throttler.ApiKey.TOPICS_API_SDK_NAME, "sdk2")).isTrue();
}
@Test
public void testTryAcquire_withThreePermitsPerSecond() {
// Create a throttler with 3 permits per second.
Throttler throttler = createThrottler(3);
assertAcquireSeveralTimes(throttler, MEASUREMENT_API_REGISTER_SOURCE, 3);
// Calling a different API.
assertAcquireSeveralTimes(throttler, MEASUREMENT_API_REGISTER_TRIGGER, 3);
}
@Test
public void testTryAcquire_withSeveralFlagsDifferentPermitsPerSecond() {
// Create a throttler with several flags
final Flags flags = mock(Flags.class);
doReturn(1F).when(flags).getSdkRequestPermitsPerSecond();
doReturn(2F).when(flags).getAdIdRequestPermitsPerSecond();
doReturn(3F).when(flags).getAppSetIdRequestPermitsPerSecond();
doReturn(4F).when(flags).getMeasurementRegisterSourceRequestPermitsPerSecond();
doReturn(5F).when(flags).getMeasurementRegisterWebSourceRequestPermitsPerSecond();
final Throttler throttler = new Throttler(flags);
// Default ApiKey configured with 1 request per second
assertAcquireSeveralTimes(throttler, UNKNOWN, 1);
// Ad id ApiKey configured with 2 request per second
assertAcquireSeveralTimes(throttler, ADID_API_APP_PACKAGE_NAME, 2);
// App set id ApiKey configured with 3 request per second
assertAcquireSeveralTimes(throttler, APPSETID_API_APP_PACKAGE_NAME, 3);
// Register Source ApiKey configured with 4 request per second
assertAcquireSeveralTimes(throttler, MEASUREMENT_API_REGISTER_SOURCE, 4);
// Register Web Source ApiKey configured with 5 request per second
assertAcquireSeveralTimes(throttler, MEASUREMENT_API_REGISTER_WEB_SOURCE, 5);
}
@Test
public void testGetInstance_withOnePermitPerSecond() {
// Reset throttler state. There is no guarantee another class has initialized the Throttler
// using getInstance, therefore destroying the throttler before testing. This is the only
// method using getInstance while the others are using the constructor, therefore there is
// no need to add this to the setup and tear down test phase.
Throttler.destroyExistingThrottler();
// Create a throttler with 1 permit per second from getInstance.
final Flags flags = createFlags(1F);
Throttler throttler = Throttler.getInstance(flags);
// tryAcquire should return false after 1 permit
assertAcquireSeveralTimes(throttler, MEASUREMENT_API_REGISTER_SOURCE, 1);
// Calling a different API. tryAcquire should return false after 1 permit
assertAcquireSeveralTimes(throttler, MEASUREMENT_API_REGISTER_TRIGGER, 1);
// Reset throttler state.
// If another class calls getInstance, it can be initialized to its original state
Throttler.destroyExistingThrottler();
}
@Test
public void testGetInstance_onEmptyFlags_throwNullPointerException() {
assertThrows(NullPointerException.class, () -> Throttler.getInstance(null));
}
private void assertAcquireSeveralTimes(
Throttler throttler, Throttler.ApiKey apiKey, int validTimes) {
final String defaultRequester = "requester";
for (int i = 0; i < validTimes; i++) {
assertThat(throttler.tryAcquire(apiKey, defaultRequester)).isTrue();
}
assertThat(throttler.tryAcquire(apiKey, defaultRequester)).isFalse();
}
private Throttler createThrottler(float permitsPerSecond) {
final Flags flags = createFlags(permitsPerSecond);
return new Throttler(flags);
}
private Flags createFlags(float permitsPerSecond) {
final Flags flags = mock(Flags.class);
doReturn(permitsPerSecond).when(flags).getSdkRequestPermitsPerSecond();
doReturn(permitsPerSecond)
.when(flags)
.getMeasurementRegisterSourceRequestPermitsPerSecond();
doReturn(permitsPerSecond)
.when(flags)
.getMeasurementRegisterWebSourceRequestPermitsPerSecond();
return flags;
}
}