1 /*
2  * Copyright (C) 2023 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.adservices.service.common;
18 
19 import static com.google.common.truth.Truth.assertThat;
20 
21 import com.android.adservices.concurrency.AdServicesExecutors;
22 
23 import com.google.common.util.concurrent.FluentFuture;
24 import com.google.common.util.concurrent.ListeningExecutorService;
25 
26 import org.junit.Test;
27 
28 import java.util.concurrent.CountDownLatch;
29 import java.util.concurrent.atomic.AtomicInteger;
30 import java.util.function.Supplier;
31 
32 public class SingletonRunnerTest {
33     private final ListeningExecutorService mRunnerExecutor =
34             AdServicesExecutors.getBlockingExecutor();
35 
36     @Test
testShouldRunTask()37     public void testShouldRunTask() throws Exception {
38         final AtomicInteger invocationCount = new AtomicInteger(0);
39         SingletonRunner<Integer> singletonRunner =
40                 new SingletonRunner<>(
41                         "Test task",
42                         shouldStop ->
43                                 FluentFuture.from(
44                                         mRunnerExecutor.submit(invocationCount::incrementAndGet)));
45         singletonRunner.runSingleInstance().get();
46         assertThat(invocationCount.get()).isEqualTo(1);
47     }
48 
49     @Test
testShouldReturnSameFutureIfTaskIsNotCompleted()50     public void testShouldReturnSameFutureIfTaskIsNotCompleted() throws Exception {
51         TestTaskRunner testTaskRunner = new TestTaskRunner();
52 
53         SingletonRunner<Integer> singletonRunner = new SingletonRunner<>("Test task", testTaskRunner);
54         assertThat(singletonRunner.runSingleInstance())
55                 .isSameInstanceAs(singletonRunner.runSingleInstance());
56     }
57 
58     @Test
testShouldReturnDifferentFuturesIfTaskIsCompleted()59     public void testShouldReturnDifferentFuturesIfTaskIsCompleted() throws Exception {
60         TestTaskRunner testTaskRunner = new TestTaskRunner();
61 
62         SingletonRunner<Integer> singletonRunner = new SingletonRunner<>("Test task", testTaskRunner);
63         FluentFuture<Integer> firstCall = singletonRunner.runSingleInstance();
64         testTaskRunner.mTestHasDecidedToStop.countDown();
65         // Waiting for first run to complete
66         firstCall.get();
67 
68         assertThat(firstCall).isNotSameInstanceAs(singletonRunner.runSingleInstance());
69     }
70 
71     @Test
testShouldRunOnce()72     public void testShouldRunOnce() throws Exception {
73         TestTaskRunner testTaskRunner = new TestTaskRunner();
74 
75         SingletonRunner<Integer> singletonRunner = new SingletonRunner<>("Test task", testTaskRunner);
76         FluentFuture<Integer> firstRunResult = singletonRunner.runSingleInstance();
77         singletonRunner.runSingleInstance();
78         testTaskRunner.mTestHasDecidedToStop.countDown();
79 
80         assertThat(firstRunResult.get()).isEqualTo(1);
81         assertThat(testTaskRunner.mInvocationCount.get()).isEqualTo(1);
82     }
83 
84     @Test
testShouldRunOnceMultipleStartStops()85     public void testShouldRunOnceMultipleStartStops() throws Exception {
86         TestTaskRunner testTaskRunner = new TestTaskRunner();
87         SingletonRunner<Integer> singletonRunner = new SingletonRunner<>("Test task", testTaskRunner);
88 
89         FluentFuture<Integer> firstRunResult = singletonRunner.runSingleInstance();
90         FluentFuture<Integer> secondRunResult = singletonRunner.runSingleInstance();
91         singletonRunner.stopWork();
92         FluentFuture<Integer> thirdRunResult = singletonRunner.runSingleInstance();
93 
94         // The first task will stop only after this latch count has reached zero.
95         testTaskRunner.mTestHasDecidedToStop.countDown();
96 
97         assertThat(firstRunResult).isSameInstanceAs(secondRunResult);
98         assertThat(secondRunResult).isSameInstanceAs(thirdRunResult);
99         assertThat(firstRunResult.get()).isEqualTo(1);
100         assertThat(testTaskRunner.mInvocationCount.get()).isEqualTo(1);
101     }
102 
103     @Test
testShouldRunAgain()104     public void testShouldRunAgain() throws Exception {
105         TestTaskRunner testTaskRunner = new TestTaskRunner();
106         SingletonRunner<Integer> singletonRunner = new SingletonRunner<>("Test task", testTaskRunner);
107 
108         FluentFuture<Integer> firstRunResult = singletonRunner.runSingleInstance();
109         testTaskRunner.mTestHasDecidedToStop.countDown();
110 
111         assertThat(firstRunResult.get()).isEqualTo(1);
112 
113         // once the first runner completed the second can start
114         FluentFuture<Integer> secondRunResult = singletonRunner.runSingleInstance();
115 
116         // Same runnable invoked twice maintaining state
117         assertThat(secondRunResult.get()).isEqualTo(2);
118     }
119 
120     @Test
testShouldStopTask()121     public void testShouldStopTask() throws Exception {
122         final CountDownLatch testHasDecidedToStop = new CountDownLatch(1);
123         final AtomicInteger counter = new AtomicInteger(0);
124         SingletonRunner<Integer> singletonRunner =
125                 new SingletonRunner<>(
126                         "",
127                         shouldStop ->
128                                 FluentFuture.from(
129                                         mRunnerExecutor.submit(
130                                                 () -> {
131                                                     counter.incrementAndGet();
132 
133                                                     try {
134                                                         testHasDecidedToStop.await();
135                                                     } catch (InterruptedException e) {
136                                                         // Intentionally ignored
137                                                     }
138 
139                                                     if (!shouldStop.get()) {
140                                                         counter.incrementAndGet();
141                                                     }
142 
143                                                     return counter.get();
144                                                 })));
145         FluentFuture<Integer> taskResult = singletonRunner.runSingleInstance();
146 
147         singletonRunner.stopWork();
148         testHasDecidedToStop.countDown();
149 
150         assertThat(taskResult.get()).isEqualTo(1);
151     }
152 
153     class TestTaskRunner implements SingletonRunner.InterruptableTaskRunner<Integer> {
154         final AtomicInteger mInvocationCount = new AtomicInteger(0);
155         final CountDownLatch mTestHasDecidedToStop = new CountDownLatch(1);
156 
157         @Override
run(Supplier<Boolean> stopFlagChecker)158         public FluentFuture<Integer> run(Supplier<Boolean> stopFlagChecker) {
159             return FluentFuture.from(
160                     mRunnerExecutor.submit(
161                             () -> {
162                                 mInvocationCount.incrementAndGet();
163                                 try {
164                                     mTestHasDecidedToStop.await();
165                                 } catch (InterruptedException e) {
166                                     // Ignored
167                                 }
168                                 return mInvocationCount.get();
169                             }));
170         }
171     }
172 }
173