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