• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2022 Google LLC
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 package com.google.android.libraries.mobiledatadownload.internal.logging;
17 
18 import static com.google.android.libraries.mobiledatadownload.internal.logging.SharedPreferencesLoggingState.SALT_KEY;
19 import static com.google.common.base.Preconditions.checkState;
20 import static com.google.common.truth.Truth.assertThat;
21 import static org.junit.Assume.assumeTrue;
22 
23 import android.content.Context;
24 import android.content.SharedPreferences;
25 import androidx.test.core.app.ApplicationProvider;
26 import com.google.android.libraries.mobiledatadownload.Flags;
27 import com.google.android.libraries.mobiledatadownload.file.common.testing.TemporaryUri;
28 import com.google.android.libraries.mobiledatadownload.testing.FakeTimeSource;
29 import com.google.common.base.Optional;
30 import com.google.common.util.concurrent.ListeningExecutorService;
31 import com.google.common.util.concurrent.MoreExecutors;
32 import com.google.mobiledatadownload.LogProto.StableSamplingInfo;
33 import java.util.Arrays;
34 import java.util.List;
35 import java.util.Random;
36 import java.util.concurrent.Executors;
37 import org.junit.Before;
38 import org.junit.Rule;
39 import org.junit.Test;
40 import org.junit.runner.RunWith;
41 import org.robolectric.ParameterizedRobolectricTestRunner;
42 import org.robolectric.ParameterizedRobolectricTestRunner.Parameter;
43 import org.robolectric.ParameterizedRobolectricTestRunner.Parameters;
44 
45 @RunWith(ParameterizedRobolectricTestRunner.class)
46 public final class LogSamplerTest {
47   @Parameter(value = 0)
48   public boolean stableLoggingEnabled;
49 
50   @Parameters(name = "stableLoggingEnabled = {0}")
parameters()51   public static List<Boolean> parameters() {
52     return Arrays.asList(true, false);
53   }
54 
55   private LoggingStateStore loggingStateStore;
56   private SharedPreferences loggingStateSharedPrefs;
57   private static final ListeningExecutorService executorService =
58       MoreExecutors.listeningDecorator(Executors.newCachedThreadPool());
59   private LogSampler logSampler;
60 
61   @Rule public final TemporaryUri tmpUri = new TemporaryUri();
62 
63   private static final FakeTimeSource timeSource = new FakeTimeSource();
64   private Context context;
65 
66   // Seed for first long
67   private static final int LOGS_AT_1_PERCENT_SEED = 750; // -5772485602628857500
68 
69   private static final int ONE_PERCENT_SAMPLE_INTERVAL = 100;
70   private static final int TEN_PERCENT_SAMPLE_INTERVAL = 10;
71   private static final int ONE_HUNDRED_PERCENT_SAMPLE_INTERVAL = 1;
72   private static final int NEVER_SAMPLE_INTERVAL = 0;
73 
74   @Before
setUp()75   public void setUp() throws Exception {
76     context = ApplicationProvider.getApplicationContext();
77 
78     loggingStateSharedPrefs = context.getSharedPreferences("loggingStateSharedPrefs", 0);
79 
80     loggingStateStore =
81         SharedPreferencesLoggingState.create(
82             () -> loggingStateSharedPrefs, timeSource, executorService, new Random());
83 
84     logSampler = constructLogSampler(0);
85   }
86 
87   @Test
shouldLog_withInvalidSamplingRate_returnsAbsent()88   public void shouldLog_withInvalidSamplingRate_returnsAbsent() throws Exception {
89     int invalidSamplingRate = -1;
90     Optional<StableSamplingInfo> samplingInfo =
91         logSampler.shouldLog(invalidSamplingRate, Optional.of(loggingStateStore)).get();
92 
93     assertThat(samplingInfo).isAbsent();
94   }
95 
96   @Test
shouldLog_with0SamplingRate_returnsAbsent()97   public void shouldLog_with0SamplingRate_returnsAbsent() throws Exception {
98     Optional<StableSamplingInfo> samplingInfo =
99         logSampler.shouldLog(NEVER_SAMPLE_INTERVAL, Optional.of(loggingStateStore)).get();
100 
101     assertThat(samplingInfo).isAbsent();
102   }
103 
104   @Test
shouldLog_stable_with1PercentGroup_logsAt1Percent()105   public void shouldLog_stable_with1PercentGroup_logsAt1Percent() throws Exception {
106     assumeTrue(stableLoggingEnabled);
107     setStableSamplingRandomNumber(100); // 100 % 100 = 0
108 
109     Optional<StableSamplingInfo> samplingInfo =
110         logSampler.shouldLog(ONE_PERCENT_SAMPLE_INTERVAL, Optional.of(loggingStateStore)).get();
111 
112     assertThat(samplingInfo).isPresent();
113     assertThat(samplingInfo.get().getStableSamplingUsed()).isTrue();
114     assertThat(samplingInfo.get().getPartOfAlwaysLoggingGroup()).isTrue();
115     assertThat(samplingInfo.get().getInvalidSamplingRateUsed()).isFalse();
116   }
117 
118   @Test
shouldLog_stable_with1PercentGroup_logsAt10Percent()119   public void shouldLog_stable_with1PercentGroup_logsAt10Percent() throws Exception {
120     assumeTrue(stableLoggingEnabled);
121     setStableSamplingRandomNumber(100); // 100 % 100 = 0
122 
123     Optional<StableSamplingInfo> samplingInfo =
124         logSampler.shouldLog(TEN_PERCENT_SAMPLE_INTERVAL, Optional.of(loggingStateStore)).get();
125 
126     assertThat(samplingInfo).isPresent();
127     assertThat(samplingInfo.get().getStableSamplingUsed()).isTrue();
128     assertThat(samplingInfo.get().getPartOfAlwaysLoggingGroup()).isTrue();
129     assertThat(samplingInfo.get().getInvalidSamplingRateUsed()).isFalse();
130   }
131 
132   @Test
shouldLog_stable_with10PercentGroup_doesntLogAt1Percent()133   public void shouldLog_stable_with10PercentGroup_doesntLogAt1Percent() throws Exception {
134     assumeTrue(stableLoggingEnabled);
135     setStableSamplingRandomNumber(10); // 10 % 100 = 10
136 
137     Optional<StableSamplingInfo> samplingInfo =
138         logSampler.shouldLog(ONE_PERCENT_SAMPLE_INTERVAL, Optional.of(loggingStateStore)).get();
139 
140     assertThat(samplingInfo).isAbsent();
141   }
142 
143   @Test
shouldLog_stable_with10PercentGroup_logsAt10Percent()144   public void shouldLog_stable_with10PercentGroup_logsAt10Percent() throws Exception {
145     assumeTrue(stableLoggingEnabled);
146     setStableSamplingRandomNumber(10); // 10 % 100 = 10
147 
148     Optional<StableSamplingInfo> samplingInfo =
149         logSampler.shouldLog(TEN_PERCENT_SAMPLE_INTERVAL, Optional.of(loggingStateStore)).get();
150 
151     assertThat(samplingInfo).isPresent();
152     assertThat(samplingInfo.get().getStableSamplingUsed()).isTrue();
153     assertThat(samplingInfo.get().getPartOfAlwaysLoggingGroup()).isFalse();
154     assertThat(samplingInfo.get().getInvalidSamplingRateUsed()).isFalse();
155   }
156 
157   @Test
shouldLog_stable_withIncompatibleSamplingRate_isMarkedAsIncompatible()158   public void shouldLog_stable_withIncompatibleSamplingRate_isMarkedAsIncompatible()
159       throws Exception {
160     assumeTrue(stableLoggingEnabled);
161     setStableSamplingRandomNumber(77);
162 
163     Optional<StableSamplingInfo> samplingInfo =
164         logSampler.shouldLog(77, Optional.of(loggingStateStore)).get();
165 
166     assertThat(samplingInfo).isPresent();
167     assertThat(samplingInfo.get().getStableSamplingUsed()).isTrue();
168     assertThat(samplingInfo.get().getInvalidSamplingRateUsed()).isTrue();
169     assertThat(samplingInfo.get().getPartOfAlwaysLoggingGroup()).isFalse();
170   }
171 
172   @Test
shouldLog_with100Percent_logsAt100Percent()173   public void shouldLog_with100Percent_logsAt100Percent() throws Exception {
174     Optional<StableSamplingInfo> samplingInfo1 =
175         logSampler
176             .shouldLog(ONE_HUNDRED_PERCENT_SAMPLE_INTERVAL, Optional.of(loggingStateStore))
177             .get();
178     Optional<StableSamplingInfo> samplingInfo2 =
179         logSampler
180             .shouldLog(ONE_HUNDRED_PERCENT_SAMPLE_INTERVAL, Optional.of(loggingStateStore))
181             .get();
182 
183     assertThat(samplingInfo1).isPresent();
184     assertThat(samplingInfo2).isPresent();
185     assertThat(samplingInfo1.get().getStableSamplingUsed()).isEqualTo(stableLoggingEnabled);
186     assertThat(samplingInfo2.get().getStableSamplingUsed()).isEqualTo(stableLoggingEnabled);
187   }
188 
189   @Test
shouldLog_event_changesPerEvent()190   public void shouldLog_event_changesPerEvent() throws Exception {
191     assumeTrue(!stableLoggingEnabled);
192 
193     LogSampler logSampler = constructLogSampler(LOGS_AT_1_PERCENT_SEED);
194     checkState(
195         logSampler
196             .shouldLog(ONE_PERCENT_SAMPLE_INTERVAL, Optional.of(loggingStateStore))
197             .get()
198             .isPresent());
199 
200     assertThat(
201             logSampler.shouldLog(ONE_PERCENT_SAMPLE_INTERVAL, Optional.of(loggingStateStore)).get())
202         .isAbsent();
203   }
204 
205   @Test
shouldLog_stable_withoutLoggingStateStore_usesPerEvent()206   public void shouldLog_stable_withoutLoggingStateStore_usesPerEvent() throws Exception {
207     assumeTrue(stableLoggingEnabled);
208 
209     Optional<StableSamplingInfo> stableSamplingInfo =
210         logSampler.shouldLog(ONE_HUNDRED_PERCENT_SAMPLE_INTERVAL, Optional.absent()).get();
211 
212     assertThat(stableSamplingInfo).isPresent();
213     assertThat(stableSamplingInfo.get().getStableSamplingUsed()).isFalse();
214   }
215 
constructLogSampler(int seed)216   private LogSampler constructLogSampler(int seed) {
217     return new LogSampler(
218         new Flags() {
219           @Override
220           public boolean enableRngBasedDeviceStableSampling() {
221             return stableLoggingEnabled;
222           }
223         },
224         new Random(seed));
225   }
226 
227   private void setStableSamplingRandomNumber(int randomNumber) throws Exception {
228     SharedPreferences.Editor editor = loggingStateSharedPrefs.edit();
229     editor.putLong(SALT_KEY, randomNumber);
230     assumeTrue(editor.commit());
231   }
232 }
233