Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Get CI/CD Working #33

Closed
wants to merge 31 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
23c7971
use external mock RAC server
aarsilv Oct 25, 2023
79fd4c1
pass dns as emulator option
aarsilv Oct 25, 2023
c74ac22
upload report
aarsilv Oct 25, 2023
606008e
allow writing to storage in manifest
aarsilv Oct 25, 2023
f811529
upload emulator logs too
aarsilv Oct 25, 2023
53e0025
save logs
aarsilv Oct 25, 2023
6c1f6f3
fix dumb copy paste mistake
aarsilv Oct 25, 2023
cd8ec0d
try a different way to pipe logs:
aarsilv Oct 25, 2023
1163a2d
another log attempt
aarsilv Oct 25, 2023
3ecce64
give test step an id
aarsilv Oct 25, 2023
5d72c0c
make the directory
aarsilv Oct 26, 2023
aeec925
always upload artifacts
aarsilv Oct 26, 2023
d9ecd81
gradle cache
aarsilv Oct 26, 2023
f2d0007
correct path
aarsilv Oct 26, 2023
3b9df94
try to copy logging from other PR
aarsilv Oct 26, 2023
7c864c4
specify API level
aarsilv Oct 26, 2023
5baef28
correct log file name
aarsilv Oct 26, 2023
f4f53d5
log all the logs
aarsilv Oct 26, 2023
48df560
use same log tag everywhere
aarsilv Oct 26, 2023
b6173db
change sleep and add additional logs
aarsilv Oct 26, 2023
e95b857
some more logs
aarsilv Oct 26, 2023
89ba261
include DNS in cache
aarsilv Oct 26, 2023
b27b2e2
skip caching for now
aarsilv Oct 26, 2023
9e92243
remove another gradle caching thing
aarsilv Oct 26, 2023
1660271
adjust log tag
aarsilv Oct 26, 2023
570d3bf
try grep to still filter logs
aarsilv Oct 26, 2023
0b37214
safe truncating of log tag
aarsilv Oct 26, 2023
432611b
rename task names
aarsilv Oct 26, 2023
0c2c12e
minSdk version back to 21
aarsilv Oct 26, 2023
9fc5dd2
git rid of extraneous env variable
aarsilv Oct 26, 2023
44f33b0
remove deprecated android manfest write permission
aarsilv Oct 26, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 72 additions & 0 deletions .github/workflows/test.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
name: Test

on:
push:
branches:
- main
pull_request:
paths:
- '**/*'

jobs:
test-android-sdk:
runs-on: macos-latest
steps:
- name: Check out Java SDK
uses: actions/checkout@v3
with:
repository: 'Eppo-exp/android-sdk'

- name: Set up JDK 11
uses: actions/setup-java@v3
with:
java-version: '11'
distribution: 'adopt'

- name: 'Set up GCP SDK'
uses: 'google-github-actions/setup-gcloud@v0'

- name: Restore gradle.properties
env:
MAVEN_USERNAME: ${{ secrets.MAVEN_USERNAME }}
MAVEN_PASSWORD: ${{ secrets.MAVEN_PASSWORD }}
Comment on lines +31 to +32
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's ok these aren't populated in this repo, they just need to be defined (even if empty string)

shell: bash
run: |
mkdir -p ~/.gradle/
echo "GRADLE_USER_HOME=${HOME}/.gradle" >> $GITHUB_ENV
echo "MAVEN_USERNAME=${MAVEN_USERNAME}" > ~/.gradle/gradle.properties
echo "MAVEN_PASSWORD=${MAVEN_PASSWORD}" >> ~/.gradle/gradle.properties

- name: Set up test data
run: make test-data

- name: Spin up emulator and run tests
id: testing
uses: ReactiveCircus/android-emulator-runner@v2
with:
api-level: 33
target: google_apis
arch: x86_64
emulator-options: -no-window -gpu swiftshader_indirect -no-snapshot -noaudio -no-boot-anim -dns-server 8.8.8.8
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

8.8.8.8 is Google's DNS server.

A bug in Android resulted in newer images not having valid DNS, and thus no internet, so we need to specify this.

script: |
echo "Emulator started"
adb logcat -c # clear logs
mkdir -p app/ # create directory
touch app/emulator.log # create log file
chmod 777 app/emulator.log # allow writing to log file
adb logcat | grep EppoSDK >> app/emulator.log & # pipe all logcat messages into log file as a background process
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We use grep so we get all the logs from our SDK, regardless of the specific class logging.

./gradlew connectedCheck # run tests

- name: Upload Emulator Logs
if: always()
uses: actions/upload-artifact@v2
with:
name: emulator logs
path: app/emulator.log

- name: Upload Test Report
if: always()
uses: actions/upload-artifact@v2
with:
name: report
path: /Users/runner/work/android-sdk/android-sdk/eppo/build/reports/androidTests/connected/index.html
14 changes: 3 additions & 11 deletions eppo/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,7 @@ android {

defaultConfig {
namespace "cloud.eppo.android"

gradle.startParameter.taskNames.each {
if (it.contains("AndroidTest") || it.contains('connectedCheck')) {
minSdk 33 // required to use wiremock in tests
} else {
minSdk 21
}
}
minSdk 21
targetSdk 33

testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
Expand Down Expand Up @@ -63,17 +56,16 @@ ext.versions = [
"androidx_runner": "1.5.2",
"gson": "2.9.1",
"okhttp": "4.10.0",
"wiremock": "2.34.0"
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🪓

"commonsio": "2.14.0"
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We were using some classes, in apache commons, that came with Wiremock outside of our mock server.

]


dependencies {
testImplementation "junit:junit:${versions.junit}"
androidTestImplementation "com.github.tomakehurst:wiremock-jre8:${versions.wiremock}"
androidTestImplementation "androidx.test.ext:junit:${versions.androidx_junit}"
androidTestImplementation "androidx.test:core:${versions.androidx_core}"
androidTestImplementation "androidx.test:runner:${versions.androidx_runner}"

androidTestImplementation "commons-io:commons-io:${versions.commonsio}"
implementation("com.google.code.gson:gson:${versions.gson}")
implementation("com.squareup.okhttp3:okhttp:${versions.okhttp}")
}
Expand Down
46 changes: 13 additions & 33 deletions eppo/src/androidTest/java/cloud/eppo/android/EppoClientTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,19 +9,17 @@
import android.content.res.AssetManager;
import androidx.test.core.app.ApplicationProvider;

import com.github.tomakehurst.wiremock.WireMockServer;
import com.github.tomakehurst.wiremock.client.WireMock;
import org.apache.commons.io.Charsets;
import org.apache.commons.io.IOUtils;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonParseException;

import org.apache.commons.io.Charsets;
import org.apache.commons.io.IOUtils;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;

import java.io.File;
Expand All @@ -38,11 +36,8 @@
import cloud.eppo.android.dto.adapters.EppoValueAdapter;

public class EppoClientTest {
private static final String TAG = EppoClientTest.class.getSimpleName();
private static final int TEST_PORT = 4001;
private static final String HOST = "http://localhost:" + TEST_PORT;
private static final String INVALID_HOST = "http://localhost:" + (TEST_PORT + 1);
private WireMockServer mockServer;
private static final String TEST_HOST = "http://us-central1-eppo-prod-312905.cloudfunctions.net/serveGithubRacTestFile";
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ideally this is some nice DNS thing but not needed

private static final String INVALID_HOST = "http://thisisabaddomainforthistest.com";
private Gson gson = new GsonBuilder()
.registerTypeAdapter(EppoValue.class, new EppoValueAdapter())
.registerTypeAdapter(AssignmentValueType.class, new AssignmentValueTypeAdapter(AssignmentValueType.STRING))
Expand Down Expand Up @@ -154,34 +149,18 @@ public void onError(String errorMessage) {
lock.await(2000, TimeUnit.MILLISECONDS);
}

@Before
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Setting one up once before all tests just caused issues.

public void init() {
setupMockRacServer();

try {
initClient(HOST, true, true);
} catch (InterruptedException e) {
e.printStackTrace();
fail();
}
}

@After
public void teardown() {
this.mockServer.stop();
deleteCacheFiles();
}

private void setupMockRacServer() {
this.mockServer = new WireMockServer(TEST_PORT);
this.mockServer.start();
String racResponseJson = getMockRandomizedAssignmentResponse();
this.mockServer.stubFor(WireMock.get(WireMock.urlMatching(".*randomized_assignment.*"))
.willReturn(WireMock.okJson(racResponseJson)));
}

@Test
public void testAssignments() {
try {
initClient(TEST_HOST, true, true);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
runTestCases();
}

Expand All @@ -202,12 +181,13 @@ private void runTestCases() {
@Test
public void testCachedAssignments() {
try {
initClient(HOST, false, true); // ensure cache is populated
initClient(INVALID_HOST, false, false); // invalid port to force to use cache
initClient(TEST_HOST, false, true); // ensure cache is populated

// wait for a bit since file is loaded asynchronously
System.out.println("Sleeping for a bit to wait for cache population to complete");
Thread.sleep(1000);

initClient(INVALID_HOST, false, false); // invalid port to force to use cache
Comment on lines -206 to +190
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

HUGE

} catch (Exception e) {
fail();
}
Expand Down
1 change: 0 additions & 1 deletion eppo/src/main/java/cloud/eppo/android/ConfigCacheFile.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
import java.io.OutputStreamWriter;

public class ConfigCacheFile {
private static final String TAG = ConfigCacheFile.class.getSimpleName();
static final String CACHE_FILE_NAME = "eppo-sdk-config-v2.json";
private final File filesDir;
private final File cacheFile;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package cloud.eppo.android;

import static cloud.eppo.android.util.Utils.logTag;

import android.util.Log;

import com.google.gson.JsonIOException;
Expand All @@ -10,7 +12,7 @@
import cloud.eppo.android.dto.FlagConfig;

public class ConfigurationRequestor {
private static final String TAG = ConfigurationRequestor.class.getCanonicalName();
private static final String TAG = logTag(ConfigurationRequestor.class);

private EppoHttpClient client;
private ConfigurationStore configurationStore;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package cloud.eppo.android;

import static cloud.eppo.android.util.Utils.logTag;

import android.app.Application;
import android.content.SharedPreferences;
import android.os.AsyncTask;
Expand All @@ -23,7 +25,7 @@

public class ConfigurationStore {

private static final String TAG = ConfigurationStore.class.getSimpleName();
private static final String TAG = logTag(ConfigurationStore.class);

private final Gson gson = new GsonBuilder()
.registerTypeAdapter(EppoValue.class, new EppoValueAdapter())
Expand All @@ -42,17 +44,20 @@ public ConfigurationStore(Application application) {

public boolean loadFromCache(InitializationCallback callback) {
if (flags != null || !cacheFile.exists()) {
Log.d(TAG, "Not loading from cache ("+(flags == null ? "null flags" : "non-null flags")+")");
return false;
}

AsyncTask.execute(() -> {
Log.d(TAG, "Loading from cache");
try {
synchronized (cacheFile) {
InputStreamReader reader = cacheFile.getInputReader();
RandomizationConfigResponse configResponse = gson.fromJson(reader, RandomizationConfigResponse.class);
reader.close();
flags = configResponse.getFlags();
}
Log.d(TAG, "Cache loaded successfully");
} catch (Exception e) {
Log.e(TAG, "Error loading from local cache", e);
cacheFile.delete();
Expand Down
3 changes: 2 additions & 1 deletion eppo/src/main/java/cloud/eppo/android/EppoClient.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package cloud.eppo.android;

import static cloud.eppo.android.util.Utils.logTag;
import static cloud.eppo.android.util.Utils.validateNotEmptyOrNull;

import android.app.ActivityManager;
Expand All @@ -26,7 +27,7 @@
import cloud.eppo.android.util.Utils;

public class EppoClient {
private static final String TAG = EppoClient.class.getCanonicalName();
private static final String TAG = logTag(EppoClient.class);
private static final String DEFAULT_HOST = "https://fscdn.eppo.cloud";

private final ConfigurationRequestor requestor;
Expand Down
11 changes: 8 additions & 3 deletions eppo/src/main/java/cloud/eppo/android/EppoHttpClient.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package cloud.eppo.android;

import static cloud.eppo.android.util.Utils.logTag;

import android.util.Log;

import java.io.IOException;
Expand All @@ -15,6 +17,8 @@
import okhttp3.Response;

public class EppoHttpClient {
private static final String TAG = logTag(EppoHttpClient.class);

private final OkHttpClient client = new OkHttpClient().newBuilder()
.connectTimeout(10, TimeUnit.SECONDS)
.readTimeout(10, TimeUnit.SECONDS)
Expand All @@ -41,6 +45,7 @@ public void get(String path, RequestCallback callback) {
@Override
public void onResponse(Call call, Response response) {
if (response.isSuccessful()) {
Log.d(TAG, "Fetch successfull");
callback.onSuccess(response.body().charStream());
} else {
switch (response.code()) {
Expand All @@ -49,9 +54,9 @@ public void onResponse(Call call, Response response) {
break;
default:
if (BuildConfig.DEBUG) {
Log.e(EppoHttpClient.class.getCanonicalName(), "Fetch failed with status code: " + response.code());
Log.e(TAG, "Fetch failed with status code: " + response.code());
}
callback.onFailure("Unable to fetch from URL");
callback.onFailure("Bad response from URL "+httpUrl);
}
}
response.close();
Expand All @@ -62,7 +67,7 @@ public void onFailure(Call call, IOException e) {
if (BuildConfig.DEBUG) {
e.printStackTrace();
}
callback.onFailure("Unable to fetch from URL");
callback.onFailure("Unable to fetch from URL "+httpUrl);
}
});
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package cloud.eppo.android.dto.adapters;

import static cloud.eppo.android.util.Utils.logTag;

import android.util.Log;

import com.google.gson.JsonArray;
Expand All @@ -13,12 +15,13 @@

import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import cloud.eppo.android.dto.EppoValue;

public class EppoValueAdapter implements JsonDeserializer<EppoValue>, JsonSerializer<EppoValue> {
public static final String TAG = logTag(EppoValueAdapter.class);

@Override
public EppoValue deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
throws JsonParseException {
Expand All @@ -28,7 +31,7 @@ public EppoValue deserialize(JsonElement json, Type typeOfT, JsonDeserialization
try {
array.add(element.getAsString());
} catch (Exception e) {
Log.e(EppoValueAdapter.class.getCanonicalName(), "only Strings are supported");
Log.e(TAG, "only Strings are supported");
}
}
return EppoValue.valueOf(array);
Expand Down
14 changes: 12 additions & 2 deletions eppo/src/main/java/cloud/eppo/android/util/Utils.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,12 @@

import android.content.Context;
import android.content.SharedPreferences;
import android.os.Build;

import java.math.BigInteger;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Base64;
import java.util.Date;
import java.util.TimeZone;

Expand Down Expand Up @@ -65,4 +63,16 @@ public static SharedPreferences getSharedPrefs(Context context) {
public static String generateExperimentKey(String featureFlagKey, String allocationKey) {
return featureFlagKey + '-' + allocationKey;
}

public static String logTag(Class loggingClass) {
// Common prefix can make filtering logs easier
String logTag = ("EppoSDK:"+loggingClass.getSimpleName());

// Android prefers keeping log tags 23 characters or less
if (logTag.length() > 23) {
logTag = logTag.substring(0, 23);
}

return logTag;
}
}
Loading
Loading