Skip to content

Commit a00a540

Browse files
fix: update to latest build tools. slight refactor of datafile service. (#335)
* retool the datafile service. * fix tests * update travis build tools * change api as well as build tools * cleanup gradle files * remove unused import * refactor to not allow more than one datafile download within a minute * update to add another test for double download * added a test for when two downloads come in longer than minimum. changed min via reflection. * add a unit test for threading in datafile download * remove unused imports
1 parent 85c360d commit a00a540

File tree

12 files changed

+279
-44
lines changed

12 files changed

+279
-44
lines changed

.travis.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@ dist: trusty
44
env:
55
global:
66
# These parameters should match the parameters for build tools and sdk versions in the gradle file
7-
- ANDROID_BUILD_TOOLS=28.0.3 # should match gradle
7+
- ANDROID_BUILD_TOOLS=29.0.3 # should match gradle
88
- ADB_INSTALL_TIMEOUT=5 # minutes
9-
- ANDROID_API=28 # api is same as gradle file
9+
- ANDROID_API=29 # api is same as gradle file
1010
matrix:
1111
- EMULATOR_API=21
1212
- EMULATOR_API=22

android-sdk/src/androidTest/java/com/optimizely/ab/android/sdk/OptimizelyManagerTest.java

Lines changed: 80 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -509,7 +509,7 @@ public void injectOptimizelyDoesNotDuplicateCallback() {
509509
@Test
510510
public void initializeSyncWithUpdateOnNewDatafileDisabled() {
511511
boolean downloadToCache = true;
512-
boolean updateConfigOnNewDatafiel = false;
512+
boolean updateConfigOnNewDatafile = false;
513513
int pollingInterval = 0; // disable polling
514514

515515
DefaultDatafileHandler datafileHandler = spy(new DefaultDatafileHandler());
@@ -528,7 +528,7 @@ public Object answer(InvocationOnMock invocation) {
528528
}
529529
}).when(manager.getDatafileHandler()).downloadDatafile(any(Context.class), any(DatafileConfig.class), any(DatafileLoadedListener.class));
530530

531-
OptimizelyClient client = manager.initialize(context, defaultDatafile, downloadToCache, updateConfigOnNewDatafiel);
531+
OptimizelyClient client = manager.initialize(context, defaultDatafile, downloadToCache, updateConfigOnNewDatafile);
532532

533533
try {
534534
executor.awaitTermination(1, TimeUnit.SECONDS);
@@ -542,7 +542,7 @@ public Object answer(InvocationOnMock invocation) {
542542
@Test
543543
public void initializeSyncWithUpdateOnNewDatafileEnabled() {
544544
boolean downloadToCache = true;
545-
boolean updateConfigOnNewDatafiel = true;
545+
boolean updateConfigOnNewDatafile = true;
546546
int pollingInterval = 0; // disable polling
547547

548548
DefaultDatafileHandler datafileHandler = spy(new DefaultDatafileHandler());
@@ -561,7 +561,7 @@ public Object answer(InvocationOnMock invocation) {
561561
}
562562
}).when(manager.getDatafileHandler()).downloadDatafile(any(Context.class), any(DatafileConfig.class), any(DatafileLoadedListener.class));
563563

564-
OptimizelyClient client = manager.initialize(context, defaultDatafile, downloadToCache, updateConfigOnNewDatafiel);
564+
OptimizelyClient client = manager.initialize(context, defaultDatafile, downloadToCache, updateConfigOnNewDatafile);
565565

566566
try {
567567
executor.awaitTermination(1, TimeUnit.SECONDS);
@@ -575,7 +575,7 @@ public Object answer(InvocationOnMock invocation) {
575575
@Test
576576
public void initializeSyncWithDownloadToCacheDisabled() {
577577
boolean downloadToCache = false;
578-
boolean updateConfigOnNewDatafiel = true;
578+
boolean updateConfigOnNewDatafile = true;
579579
int pollingInterval = 0; // disable polling
580580

581581
DefaultDatafileHandler datafileHandler = spy(new DefaultDatafileHandler());
@@ -594,7 +594,7 @@ public Object answer(InvocationOnMock invocation) {
594594
}
595595
}).when(manager.getDatafileHandler()).downloadDatafile(any(Context.class), any(DatafileConfig.class), any(DatafileLoadedListener.class));
596596

597-
OptimizelyClient client = manager.initialize(context, defaultDatafile, downloadToCache, updateConfigOnNewDatafiel);
597+
OptimizelyClient client = manager.initialize(context, defaultDatafile, downloadToCache, updateConfigOnNewDatafile);
598598

599599
try {
600600
executor.awaitTermination(1, TimeUnit.SECONDS);
@@ -608,7 +608,39 @@ public Object answer(InvocationOnMock invocation) {
608608
@Test
609609
public void initializeSyncWithUpdateOnNewDatafileDisabledWithPeriodicPollingEnabled() {
610610
boolean downloadToCache = true;
611-
boolean updateConfigOnNewDatafiel = false;
611+
boolean updateConfigOnNewDatafile = false;
612+
int pollingInterval = 30; // enable polling
613+
614+
DefaultDatafileHandler datafileHandler = spy(new DefaultDatafileHandler());
615+
Logger logger = mock(Logger.class);
616+
Context context = InstrumentationRegistry.getTargetContext();
617+
618+
OptimizelyManager manager = new OptimizelyManager(testProjectId, testSdkKey, null, logger, pollingInterval, datafileHandler, null, 0,
619+
null, null, null, null);
620+
621+
doAnswer(
622+
(Answer<Object>) invocation -> {
623+
String newDatafile = manager.getDatafile(context, R.raw.datafile_api);
624+
datafileHandler.saveDatafile(context, manager.getDatafileConfig(), newDatafile);
625+
return null;
626+
}).when(manager.getDatafileHandler()).downloadDatafile(any(Context.class), any(DatafileConfig.class), any(DatafileLoadedListener.class));
627+
628+
OptimizelyClient client = manager.initialize(context, defaultDatafile, downloadToCache, updateConfigOnNewDatafile);
629+
630+
try {
631+
executor.awaitTermination(1, TimeUnit.SECONDS);
632+
} catch (InterruptedException e) {
633+
//
634+
}
635+
636+
// when periodic polling enabled, project config always updated on cache datafile update (regardless of "updateConfigOnNewDatafile" setting)
637+
assertEquals(client.getOptimizelyConfig().getRevision(), "241"); // wait for first download.
638+
}
639+
640+
@Test
641+
public void initializeSyncWithUpdateOnNewDatafileEnabledWithPeriodicPollingEnabled() {
642+
boolean downloadToCache = true;
643+
boolean updateConfigOnNewDatafile = true;
612644
int pollingInterval = 30; // enable polling
613645

614646
DefaultDatafileHandler datafileHandler = spy(new DefaultDatafileHandler());
@@ -627,23 +659,22 @@ public Object answer(InvocationOnMock invocation) {
627659
}
628660
}).when(manager.getDatafileHandler()).downloadDatafile(any(Context.class), any(DatafileConfig.class), any(DatafileLoadedListener.class));
629661

630-
OptimizelyClient client = manager.initialize(context, defaultDatafile, downloadToCache, updateConfigOnNewDatafiel);
662+
OptimizelyClient client = manager.initialize(context, defaultDatafile, downloadToCache, updateConfigOnNewDatafile);
631663

632664
try {
633665
executor.awaitTermination(1, TimeUnit.SECONDS);
634666
} catch (InterruptedException e) {
635667
//
636668
}
637669

638-
// when periodic polling enabled, project config always updated on cache datafile update (regardless of "updateConfigOnNewDatafile" setting)
639670
assertEquals(client.getOptimizelyConfig().getRevision(), "241");
640671
}
641672

642673
@Test
643-
public void initializeSyncWithUpdateOnNewDatafileEnabledWithPeriodicPollingEnabled() {
674+
public void initializeSyncWithUpdateOnNewDatafileDisabledWithPeriodicPollingDisabled() {
644675
boolean downloadToCache = true;
645-
boolean updateConfigOnNewDatafiel = true;
646-
int pollingInterval = 30; // enable polling
676+
boolean updateConfigOnNewDatafile = false;
677+
int pollingInterval = 0; // disable polling
647678

648679
DefaultDatafileHandler datafileHandler = spy(new DefaultDatafileHandler());
649680
Logger logger = mock(Logger.class);
@@ -661,7 +692,7 @@ public Object answer(InvocationOnMock invocation) {
661692
}
662693
}).when(manager.getDatafileHandler()).downloadDatafile(any(Context.class), any(DatafileConfig.class), any(DatafileLoadedListener.class));
663694

664-
OptimizelyClient client = manager.initialize(context, defaultDatafile, downloadToCache, updateConfigOnNewDatafiel);
695+
OptimizelyClient client = manager.initialize(context, defaultDatafile, downloadToCache, updateConfigOnNewDatafile);
665696

666697
try {
667698
executor.awaitTermination(1, TimeUnit.SECONDS);
@@ -670,13 +701,46 @@ public Object answer(InvocationOnMock invocation) {
670701
}
671702

672703
// when periodic polling enabled, project config always updated on cache datafile update (regardless of "updateConfigOnNewDatafile" setting)
704+
assertEquals(client.getOptimizelyConfig().getRevision(), "7"); // wait for first download.
705+
}
706+
707+
@Test
708+
public void initializeSyncWithUpdateOnNewDatafileEnabledWithPeriodicPollingDisabled() {
709+
boolean downloadToCache = true;
710+
boolean updateConfigOnNewDatafile = true;
711+
int pollingInterval = 0; // disable polling
712+
713+
DefaultDatafileHandler datafileHandler = spy(new DefaultDatafileHandler());
714+
Logger logger = mock(Logger.class);
715+
Context context = InstrumentationRegistry.getTargetContext();
716+
717+
OptimizelyManager manager = new OptimizelyManager(testProjectId, testSdkKey, null, logger, pollingInterval, datafileHandler, null, 0,
718+
null, null, null, null);
719+
720+
doAnswer(
721+
new Answer<Object>() {
722+
public Object answer(InvocationOnMock invocation) {
723+
String newDatafile = manager.getDatafile(context, R.raw.datafile_api);
724+
datafileHandler.saveDatafile(context, manager.getDatafileConfig(), newDatafile);
725+
return null;
726+
}
727+
}).when(manager.getDatafileHandler()).downloadDatafile(any(Context.class), any(DatafileConfig.class), any(DatafileLoadedListener.class));
728+
729+
OptimizelyClient client = manager.initialize(context, defaultDatafile, downloadToCache, updateConfigOnNewDatafile);
730+
731+
try {
732+
executor.awaitTermination(1, TimeUnit.SECONDS);
733+
} catch (InterruptedException e) {
734+
//
735+
}
736+
673737
assertEquals(client.getOptimizelyConfig().getRevision(), "241");
674738
}
675739

676740
@Test
677741
public void initializeSyncWithResourceDatafileNoCache() {
678742
boolean downloadToCache = true;
679-
boolean updateConfigOnNewDatafiel = true;
743+
boolean updateConfigOnNewDatafile = true;
680744
int pollingInterval = 30; // enable polling
681745

682746
DefaultDatafileHandler datafileHandler = spy(new DefaultDatafileHandler());
@@ -687,15 +751,13 @@ public void initializeSyncWithResourceDatafileNoCache() {
687751
null, null, null, null));
688752

689753
datafileHandler.removeSavedDatafile(context, manager.getDatafileConfig());
690-
OptimizelyClient client = manager.initialize(context, R.raw.datafile, downloadToCache, updateConfigOnNewDatafiel);
754+
OptimizelyClient client = manager.initialize(context, R.raw.datafile, downloadToCache, updateConfigOnNewDatafile);
691755

692-
verify(manager).initialize(eq(context), eq(defaultDatafile), eq(downloadToCache), eq(updateConfigOnNewDatafiel));
756+
verify(manager).initialize(eq(context), eq(defaultDatafile), eq(downloadToCache), eq(updateConfigOnNewDatafile));
693757
}
694758

695759
@Test
696760
public void initializeSyncWithResourceDatafileNoCacheWithDefaultParams() {
697-
boolean downloadToCache = true;
698-
boolean updateConfigOnNewDatafiel = true;
699761
int pollingInterval = 30; // enable polling
700762

701763
DefaultDatafileHandler datafileHandler = spy(new DefaultDatafileHandler());

android-sdk/src/main/java/com/optimizely/ab/android/sdk/OptimizelyManager.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -491,8 +491,12 @@ public DatafileHandler getDatafileHandler() {
491491
return datafileHandler;
492492
}
493493

494+
private boolean datafileDownloadEnabled() {
495+
return datafileDownloadInterval > 0;
496+
}
497+
494498
private void startDatafileHandler(Context context) {
495-
if (datafileDownloadInterval <= 0) {
499+
if (!datafileDownloadEnabled()) {
496500
logger.debug("Invalid download interval, ignoring background updates.");
497501
return;
498502
}

build.gradle

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,10 +49,10 @@ allprojects {
4949
}
5050

5151
ext {
52-
compile_sdk_version = 28
53-
build_tools_version = "28.0.3"
52+
compile_sdk_version = 29
53+
build_tools_version = "29.0.3"
5454
min_sdk_version = 14
55-
target_sdk_version = 28
55+
target_sdk_version = 29
5656
java_core_ver = "3.4.3"
5757
android_logger_ver = "1.3.6"
5858
jacksonversion= "2.9.9.1"

datafile-handler/src/androidTest/java/com/optimizely/ab/android/datafile_handler/DatafileLoaderTest.java

Lines changed: 120 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@
3636
import org.slf4j.Logger;
3737

3838
import java.io.IOException;
39+
import java.lang.reflect.Field;
40+
import java.lang.reflect.Modifier;
3941
import java.net.MalformedURLException;
4042
import java.util.concurrent.TimeUnit;
4143

@@ -45,8 +47,10 @@
4547
import static junit.framework.Assert.fail;
4648
import static org.mockito.Matchers.any;
4749
import static org.mockito.Matchers.anyInt;
50+
import static org.mockito.Mockito.atLeast;
4851
import static org.mockito.Mockito.atMost;
4952
import static org.mockito.Mockito.mock;
53+
import static org.mockito.Mockito.never;
5054
import static org.mockito.Mockito.verify;
5155
import static org.mockito.Mockito.when;
5256

@@ -150,7 +154,7 @@ public void noCacheAndLoadFromCDNFails() {
150154
public void warningsAreLogged() throws IOException {
151155
final ListeningExecutorService executor = MoreExecutors.newDirectExecutorService();
152156
Cache cache = mock(Cache.class);
153-
datafileCache = new DatafileCache("1", cache, logger);
157+
datafileCache = new DatafileCache("warningsAreLogged", cache, logger);
154158
DatafileLoader datafileLoader =
155159
new DatafileLoader(datafileService, datafileClient, datafileCache, executor, logger);
156160

@@ -159,7 +163,7 @@ public void warningsAreLogged() throws IOException {
159163
when(cache.delete(datafileCache.getFileName())).thenReturn(false);
160164
when(cache.save(datafileCache.getFileName(), "{}")).thenReturn(false);
161165

162-
datafileLoader.getDatafile("1", datafileLoadedListener);
166+
datafileLoader.getDatafile("warningsAreLogged", datafileLoadedListener);
163167
try {
164168
executor.awaitTermination(5, TimeUnit.SECONDS);
165169
} catch (InterruptedException e) {
@@ -170,4 +174,118 @@ public void warningsAreLogged() throws IOException {
170174
verify(logger).warn("Unable to save new datafile");
171175
verify(datafileLoadedListener, atMost(1)).onDatafileLoaded("{}");
172176
}
177+
178+
@Test
179+
public void debugLogged() throws IOException {
180+
final ListeningExecutorService executor = MoreExecutors.newDirectExecutorService();
181+
Cache cache = mock(Cache.class);
182+
datafileCache = new DatafileCache("debugLogged", cache, logger);
183+
DatafileLoader datafileLoader =
184+
new DatafileLoader(datafileService, datafileClient, datafileCache, executor, logger);
185+
186+
when(client.execute(any(Client.Request.class), anyInt(), anyInt())).thenReturn("{}");
187+
when(cache.exists(datafileCache.getFileName())).thenReturn(true);
188+
when(cache.delete(datafileCache.getFileName())).thenReturn(false);
189+
when(cache.save(datafileCache.getFileName(), "{}")).thenReturn(false);
190+
191+
datafileLoader.getDatafile("debugLogged", datafileLoadedListener);
192+
datafileLoader.getDatafile("debugLogged", datafileLoadedListener);
193+
try {
194+
executor.awaitTermination(5, TimeUnit.SECONDS);
195+
} catch (InterruptedException e) {
196+
fail();
197+
}
198+
199+
verify(logger).debug("Last download happened under 1 minute ago. Throttled to be at least 1 minute apart.");
200+
verify(datafileLoadedListener, atMost(2)).onDatafileLoaded("{}");
201+
verify(datafileLoadedListener, atLeast(1)).onDatafileLoaded("{}");
202+
}
203+
204+
@Test
205+
public void debugLoggedMultiThreaded() throws IOException {
206+
final ListeningExecutorService executor = MoreExecutors.newDirectExecutorService();
207+
Cache cache = mock(Cache.class);
208+
datafileCache = new DatafileCache("debugLoggedMultiThreaded", cache, logger);
209+
DatafileLoader datafileLoader =
210+
new DatafileLoader(datafileService, datafileClient, datafileCache, executor, logger);
211+
212+
when(client.execute(any(Client.Request.class), anyInt(), anyInt())).thenReturn("{}");
213+
when(cache.exists(datafileCache.getFileName())).thenReturn(true);
214+
when(cache.delete(datafileCache.getFileName())).thenReturn(true);
215+
when(cache.load(datafileCache.getFileName())).thenReturn("{}");
216+
when(cache.save(datafileCache.getFileName(), "{}")).thenReturn(true);
217+
218+
Runnable r = () -> datafileLoader.getDatafile("debugLoggedMultiThreaded", datafileLoadedListener);
219+
220+
new Thread(r).start();
221+
new Thread(r).start();
222+
new Thread(r).start();
223+
new Thread(r).start();
224+
225+
datafileLoader.getDatafile("debugLoggedMultiThreaded", datafileLoadedListener);
226+
227+
try {
228+
executor.awaitTermination(5, TimeUnit.SECONDS);
229+
} catch (InterruptedException e) {
230+
fail();
231+
}
232+
233+
verify(logger, atLeast(1)).debug("Last download happened under 1 minute ago. Throttled to be at least 1 minute apart.");
234+
verify(datafileLoadedListener, atMost(4)).onDatafileLoaded("{}");
235+
verify(datafileLoadedListener, atLeast(1)).onDatafileLoaded("{}");
236+
}
237+
238+
239+
private void setTestDownloadFrequency(DatafileLoader datafileLoader, long value) {
240+
try {
241+
Field betweenDownloadsMilli = DatafileLoader.class.getDeclaredField("minTimeBetweenDownloadsMilli");
242+
betweenDownloadsMilli.setAccessible(true);
243+
244+
//Field modifiersField;
245+
//modifiersField = Field.class.getDeclaredField("modifiers");
246+
//modifiersField.setAccessible(true);
247+
//modifiersField.setInt(betweenDownloadsMilli, betweenDownloadsMilli.getModifiers() & ~Modifier.FINAL);
248+
betweenDownloadsMilli.set(null, value);
249+
250+
} catch (NoSuchFieldException e) {
251+
e.printStackTrace();
252+
}
253+
catch (IllegalAccessException e) {
254+
e.printStackTrace();
255+
}
256+
257+
}
258+
259+
@Test
260+
public void allowDoubleDownload() throws IOException {
261+
final ListeningExecutorService executor = MoreExecutors.newDirectExecutorService();
262+
Cache cache = mock(Cache.class);
263+
datafileCache = new DatafileCache("allowDoubleDownload", cache, logger);
264+
DatafileLoader datafileLoader =
265+
new DatafileLoader(datafileService, datafileClient, datafileCache, executor, logger);
266+
267+
// set download time to 1 second
268+
setTestDownloadFrequency(datafileLoader, 1000L);
269+
270+
when(client.execute(any(Client.Request.class), anyInt(), anyInt())).thenReturn("{}");
271+
when(cache.exists(datafileCache.getFileName())).thenReturn(true);
272+
when(cache.delete(datafileCache.getFileName())).thenReturn(false);
273+
when(cache.save(datafileCache.getFileName(), "{}")).thenReturn(false);
274+
275+
datafileLoader.getDatafile("allowDoubleDownload", datafileLoadedListener);
276+
try {
277+
executor.awaitTermination(2, TimeUnit.SECONDS);
278+
} catch (InterruptedException e) {
279+
fail();
280+
}
281+
282+
datafileLoader.getDatafile("allowDoubleDownload", datafileLoadedListener);
283+
284+
// reset back to normal.
285+
setTestDownloadFrequency(datafileLoader, 60 * 1000L);
286+
287+
verify(logger, never()).debug("Last download happened under 1 minute ago. Throttled to be at least 1 minute apart.");
288+
verify(datafileLoadedListener, atMost(2)).onDatafileLoaded("{}");
289+
verify(datafileLoadedListener, atLeast(1)).onDatafileLoaded("{}");
290+
}
173291
}

0 commit comments

Comments
 (0)