Skip to content

Commit 41486e2

Browse files
author
Vignesh Raja
authored
Add new test-app and enforce 60 second minimum datafile refresh rate (#74)
2 parents edacf85 + 5e2c909 commit 41486e2

36 files changed

+1162
-464
lines changed

.travis.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,5 +25,5 @@ before_script:
2525
- adb shell input keyevent 82 &
2626
script:
2727
- ./gradlew cleanAllModules
28-
- ./gradlew testAllModules
28+
- ./gradlew testAllModulesTravis
2929
- if [[ -n $TRAVIS_TAG ]]; then ./gradlew ship; fi

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

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,16 @@ public class OptimizelyManager {
8989
this.logger = logger;
9090
}
9191

92+
@NonNull
93+
public Long getDataFileDownloadInterval() {
94+
return dataFileDownloadInterval;
95+
}
96+
97+
@NonNull
98+
public TimeUnit getDataFileDownloadIntervalTimeUnit() {
99+
return dataFileDownloadIntervalTimeUnit;
100+
}
101+
92102
/**
93103
* Returns the {@link OptimizelyManager} builder
94104
*
@@ -606,6 +616,14 @@ public Builder withDataFileDownloadInterval(long interval, @NonNull TimeUnit tim
606616
public OptimizelyManager build() {
607617
final Logger logger = LoggerFactory.getLogger(OptimizelyManager.class);
608618

619+
// AlarmManager doesn't allow intervals less than 60 seconds
620+
if (dataFileDownloadIntervalTimeUnit.toMillis(dataFileDownloadInterval) < (60 * 1000)) {
621+
dataFileDownloadIntervalTimeUnit = TimeUnit.SECONDS;
622+
dataFileDownloadInterval = 60L;
623+
logger.warn("Minimum datafile polling interval is 60 seconds. " +
624+
"Defaulting to 60 seconds.");
625+
}
626+
609627
return new OptimizelyManager(projectId,
610628
eventHandlerDispatchInterval,
611629
eventHandlerDispatchIntervalTimeUnit,
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
/****************************************************************************
2+
* Copyright 2017, Optimizely, Inc. and contributors *
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.optimizely.ab.android.sdk;
18+
19+
import org.junit.Test;
20+
import org.junit.runner.RunWith;
21+
import org.mockito.runners.MockitoJUnitRunner;
22+
23+
import java.util.concurrent.TimeUnit;
24+
25+
import static junit.framework.Assert.assertEquals;
26+
27+
@RunWith(MockitoJUnitRunner.class)
28+
public class OptimizelyManagerBuilderTest {
29+
30+
/**
31+
* Verify that building the {@link OptimizelyManager} with a polling interval less than 60
32+
* seconds defaults to 60 seconds.
33+
*/
34+
@Test
35+
public void testBuildWithInvalidPollingInterval() {
36+
OptimizelyManager manager = OptimizelyManager.builder("1")
37+
.withDataFileDownloadInterval(5, TimeUnit.SECONDS)
38+
.build();
39+
40+
assertEquals(60L, manager.getDataFileDownloadInterval().longValue());
41+
assertEquals(TimeUnit.SECONDS, manager.getDataFileDownloadIntervalTimeUnit());
42+
}
43+
44+
/**
45+
* Verify that building the {@link OptimizelyManager} with a polling interval greater than 60
46+
* seconds is properly registered.
47+
*/
48+
@Test
49+
public void testBuildWithValidPollingInterval() {
50+
OptimizelyManager manager = OptimizelyManager.builder("1")
51+
.withDataFileDownloadInterval(61, TimeUnit.SECONDS)
52+
.build();
53+
54+
assertEquals(61L, manager.getDataFileDownloadInterval().longValue());
55+
assertEquals(TimeUnit.SECONDS, manager.getDataFileDownloadIntervalTimeUnit());
56+
}
57+
}

build.gradle

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,12 @@ task testAllModules << {
8484
logger.info("Running android tests for all modules")
8585
}
8686

87-
testAllModules.dependsOn(':android-sdk:connectedAndroidTest', ':android-sdk:test',
87+
task testAllModulesTravis << {
88+
logger.info("Running android tests for Travis")
89+
}
90+
91+
testAllModulesTravis.dependsOn(':android-sdk:connectedAndroidTest', ':android-sdk:test',
8892
':event-handler:connectedAndroidTest', ':event-handler:test',
8993
':user-profile:connectedAndroidTest', ':shared:connectedAndroidTest')
94+
95+
testAllModules.dependsOn('testAllModulesTravis', ':test-app:connectedAndroidTest')

test-app/README.md

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
# Optimizely X Android SDK Demo App
2+
3+
This module houses the demo app used to demonstrate how to get started with the Android SDK. The app
4+
is also used to run integration tests on using the Android Espresso framework.
5+
6+
## Experiments Run
7+
8+
The experiments run in this demo app are part of the [email protected] account.
9+
10+
We run the following experiment:
11+
- Background change in second activity, which is loaded after the splash screen.
12+
13+
## How it works
14+
15+
The SDK is implemented in the following way:
16+
- The splash screen initializes the Optimizely manager asynchronously. This starts the datafile
17+
fetch.
18+
- Once the datafile is fetched and the Optimizely manager is started, we use grab the `optimizelyClient`
19+
from the manager and use it to activate the `background_experiment`. This buckets the user and sends
20+
an impression event.
21+
- We then use the bucketed variation to determine which activity to show. `VariationAActivity` for
22+
`variation_a` and `VariationBActivity` for `variation_b`.
23+
- Each of those activities include a `Test Conversion` button.
24+
- Clicking on that button will call `optimizelyClient.track()` and send a conversion event for the
25+
event named `sample_conversion`.
26+
- Then the application will navigate to the conversion page to confirm that a conversion event has
27+
been sent.
28+
29+
## Running the test app
30+
31+
Run `./gradlew test-app:connectedAndroidTest` to run the Espresso tests.
32+

test-app/src/androidTest/java/com/optimizely/ab/android/test_app/MainActivityEspressoTest.java

Lines changed: 26 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,18 @@
1-
/*
2-
* Copyright 2016, Optimizely
3-
* <p/>
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-
* <p/>
8-
* http://www.apache.org/licenses/LICENSE-2.0
9-
* <p/>
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-
*/
1+
/****************************************************************************
2+
* Copyright 2017, Optimizely, Inc. and contributors *
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+
***************************************************************************/
1616
package com.optimizely.ab.android.test_app;
1717

1818
import android.app.AlarmManager;
@@ -47,6 +47,7 @@
4747
import static android.support.test.espresso.Espresso.onView;
4848
import static android.support.test.espresso.action.ViewActions.click;
4949
import static android.support.test.espresso.assertion.ViewAssertions.matches;
50+
import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed;
5051
import static android.support.test.espresso.matcher.ViewMatchers.withId;
5152
import static android.support.test.espresso.matcher.ViewMatchers.withText;
5253
import static junit.framework.Assert.assertFalse;
@@ -62,7 +63,7 @@ public class MainActivityEspressoTest {
6263
private ServiceScheduler serviceScheduler;
6364
private Intent dataFileServiceIntent, eventIntentService;
6465

65-
private ActivityTestRule<MainActivity> activityTestRule = new ActivityTestRule<>(MainActivity.class);
66+
private ActivityTestRule<SplashScreenActivity> activityTestRule = new ActivityTestRule<>(SplashScreenActivity.class);
6667
@Rule public TestRule chain = RuleChain
6768
.outerRule(new ExternalResource() {
6869
@Override
@@ -148,46 +149,32 @@ protected void after() {
148149
public void experimentActivationForWhitelistUser() throws InterruptedException {
149150
// Check that the text was changed.
150151
// These tests are pointed at a real project.
151-
// The user 'test_user` is in the whitelist for variation 1 for experiment 0 and experiment 1.
152-
onView(withId(R.id.button_1))
153-
.check(matches(withText(context.getString(R.string.main_act_button_1_text_var_1))));
152+
// The user 'test_user` is in the whitelist for variation_a for experiment background_experiment
153+
onView(withId(R.id.tv_variation_a_text_1))
154+
.check(matches(isDisplayed()));
154155

155156
// Espresso will wait for Optimizely to start due to the registered idling resources
156-
onView(withId(R.id.text_view_1))
157-
.check(matches(withText(context.getString(R.string.main_act_text_view_1_var_1))));
158-
159157
assertTrue(serviceScheduler.isScheduled(dataFileServiceIntent));
160158

161-
onView(withId(R.id.button_1)) // withId(R.id.my_view) is a ViewMatcher
162-
.perform(click()); // click() is a ViewAction
163-
164-
onView(withId(R.id.text_view_1))
165-
.check(matches(withText(context.getString(R.string.secondary_frag_text_view_1_var_1))));
166-
167-
onView(withId(R.id.button_1)) // withId(R.id.my_view) is a ViewMatcher
168-
.perform(click()); // click() is a ViewAction
159+
onView(withId(R.id.btn_variation_conversion)) // withId(R.id.my_view) is a ViewMatcher
160+
.perform(click()); // click() is a ViewAction
169161

170162
List<Pair<String, String>> events = CountingIdlingResourceManager.getEvents();
171-
assertTrue(events.size() == 6);
163+
assertTrue(events.size() == 2);
172164
Iterator<Pair<String, String>> iterator = events.iterator();
173165
while (iterator.hasNext()) {
174166
Pair<String, String> event = iterator.next();
175167
final String url = event.first;
176168
final String payload = event.second;
177-
if (url.equals("https://logx.optimizely.com/log/decision") && payload.contains("7676481120") && payload.contains("7661891902")
178-
|| url.equals("https://logx.optimizely.com/log/decision") && payload.contains("7651112186") && payload.contains("7674261140")
179-
|| url.equals("https://logx.optimizely.com/log/event") && payload.contains("experiment_0")
180-
|| url.equals("https://logx.optimizely.com/log/event") && payload.contains("experiment_1")
181-
|| url.equals("https://logx.optimizely.com/log/decision") && payload.contains("7680080715") && payload.contains("7685562539")
182-
|| url.equals("https://logx.optimizely.com/log/event") && payload.contains("experiment_2")) {
169+
if (url.equals("https://logx.optimizely.com/log/decision") && payload.contains("8126664113") && payload.contains("8146590584")
170+
|| url.equals("https://logx.optimizely.com/log/event") && payload.contains("sample_conversion")) {
183171
iterator.remove();
184172
}
185173
}
186174
assertTrue(events.isEmpty());
187175
MyApplication myApplication = (MyApplication) activityTestRule.getActivity().getApplication();
188176
UserProfile userProfile = myApplication.getOptimizelyManager().getUserProfile();
189177
// Being in the white list should override user profile
190-
assertNull(userProfile.lookup("test_user", "experiment_0"));
191-
assertNull(userProfile.lookup("test_user", "experiment_1"));
178+
assertNull(userProfile.lookup("test_user", "background_experiment"));
192179
}
193180
}

test-app/src/main/AndroidManifest.xml

Lines changed: 34 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,26 @@
11
<?xml version="1.0" encoding="utf-8"?>
2-
<manifest package="com.optimizely.ab.android.test_app"
3-
xmlns:android="http://schemas.android.com/apk/res/android">
2+
<!--
3+
/****************************************************************************
4+
* Copyright 2017, Optimizely, Inc. and contributors *
5+
* *
6+
* Licensed under the Apache License, Version 2.0 (the "License"); *
7+
* you may not use this file except in compliance with the License. *
8+
* You may obtain a copy of the License at *
9+
* *
10+
* http://www.apache.org/licenses/LICENSE-2.0 *
11+
* *
12+
* Unless required by applicable law or agreed to in writing, software *
13+
* distributed under the License is distributed on an "AS IS" BASIS, *
14+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. *
15+
* See the License for the specific language governing permissions and *
16+
* limitations under the License. *
17+
***************************************************************************/
18+
-->
19+
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
20+
package="com.optimizely.ab.android.test_app">
421

5-
<uses-permission android:name="android.permission.DISABLE_KEYGUARD"/>
6-
<uses-permission android:name="android.permission.WAKE_LOCK"/>
22+
<uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
23+
<uses-permission android:name="android.permission.WAKE_LOCK" />
724

825
<application
926
android:name=".MyApplication"
@@ -12,22 +29,23 @@
1229
android:label="@string/app_name"
1330
android:supportsRtl="true"
1431
android:theme="@style/AppTheme">
15-
<activity android:name=".MainActivity">
32+
<service
33+
android:name=".NotificationService"
34+
android:exported="false" />
35+
36+
<activity
37+
android:name=".SplashScreenActivity"
38+
android:theme="@style/SplashTheme">
1639
<intent-filter>
17-
<action android:name="android.intent.action.MAIN"/>
40+
<action android:name="android.intent.action.MAIN" />
1841

19-
<category android:name="android.intent.category.LAUNCHER"/>
42+
<category android:name="android.intent.category.LAUNCHER" />
2043
</intent-filter>
2144
</activity>
22-
<activity
23-
android:name=".SecondaryActivity"
24-
android:parentActivityName=".MainActivity">
25-
</activity>
26-
27-
<service
28-
android:name=".NotificationService"
29-
android:exported="false">
30-
</service>
45+
<activity android:name=".VariationAActivity" />
46+
<activity android:name=".EventConfirmationActivity" />
47+
<activity android:name=".ActivationErrorActivity" />
48+
<activity android:name=".VariationBActivity"></activity>
3149
</application>
3250

3351
</manifest>
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
/****************************************************************************
2+
* Copyright 2017, Optimizely, Inc. and contributors *
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.optimizely.ab.android.test_app;
17+
18+
import android.support.v7.app.AppCompatActivity;
19+
import android.os.Bundle;
20+
import android.view.View;
21+
import android.widget.Button;
22+
23+
public class ActivationErrorActivity extends AppCompatActivity {
24+
25+
@Override
26+
protected void onCreate(Bundle savedInstanceState) {
27+
super.onCreate(savedInstanceState);
28+
setContentView(R.layout.activity_activation_error);
29+
Button btnConversion = (Button)findViewById(R.id.btn_conversion_error_back);
30+
31+
btnConversion.setOnClickListener(new View.OnClickListener() {
32+
@Override
33+
public void onClick(View view) {
34+
onBackPressed();
35+
}
36+
});
37+
}
38+
}

0 commit comments

Comments
 (0)