Skip to content

Commit

Permalink
add implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
themiswang committed Jul 24, 2023
1 parent d91342e commit 0c03205
Show file tree
Hide file tree
Showing 6 changed files with 339 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,19 @@

package com.google.firebase.crashlytics.internal.metadata;

import com.google.common.truth.Truth;
import com.google.firebase.crashlytics.internal.CrashlyticsTestCase;
import com.google.firebase.crashlytics.internal.common.CrashlyticsBackgroundWorker;
import com.google.firebase.crashlytics.internal.persistence.FileStore;
import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.junit.Test;

@SuppressWarnings("ResultOfMethodCallIgnored") // Convenient use of files.
public class MetaDataStoreTest extends CrashlyticsTestCase {
Expand All @@ -43,6 +47,8 @@ public class MetaDataStoreTest extends CrashlyticsTestCase {

private static final String ESCAPED = "\ttest\nvalue";

private static final List<RolloutAssignment> ROLLOUTS_STATE = new ArrayList<>();

private FileStore fileStore;
private final CrashlyticsBackgroundWorker worker = new CrashlyticsBackgroundWorker(Runnable::run);

Expand All @@ -53,6 +59,10 @@ public void setUp() throws Exception {
super.setUp();
fileStore = new FileStore(getContext());
storeUnderTest = new MetaDataStore(fileStore);

RolloutAssignment assignment =
new RolloutAssignment("rollout_1", "my_feature", "false", "control", 1);
ROLLOUTS_STATE.add(assignment);
}

private UserMetadata metadataWithUserId(String sessionId) {
Expand Down Expand Up @@ -282,6 +292,14 @@ public void testReadKeys_noStoredData() {
assertEquals(0, readKeys.size());
}

@Test
public void testWriteReadRolloutState() throws Exception {
storeUnderTest.writeRolloutState(SESSION_ID_1, ROLLOUTS_STATE);
List<RolloutAssignment> readRolloutsState = storeUnderTest.readRolloutsState(SESSION_ID_1);

Truth.assertThat(readRolloutsState).isEqualTo(ROLLOUTS_STATE);
}

public static void assertEqualMaps(Map<String, String> expected, Map<String, String> actual) {
assertEquals(expected.size(), actual.size());
for (String key : expected.keySet()) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
package com.google.firebase.crashlytics.internal.metadata;

import com.google.common.truth.Truth;
import com.google.firebase.crashlytics.internal.CrashlyticsTestCase;
import com.google.firebase.encoders.DataEncoder;
import com.google.firebase.encoders.json.JsonDataEncoderBuilder;
import java.util.ArrayList;
import java.util.List;
import org.json.JSONException;
import org.junit.Test;

public class RolloutAssignmentListTest extends CrashlyticsTestCase {
private static final String ROLLOUTS_STATE_JSON_1 =
"[{"
+ "\"rolloutId\":\"rollout_1\","
+ "\"variantId\":\"control\","
+ "\"parameterKey\":\"my_feature\","
+ "\"parameterValue\":\"false\","
+ "\"templateVersion\":1"
+ "}]";

private static final String ROLLOUTS_STATE_JSON_2 =
"["
+ "{"
+ "\"rolloutId\":\"rollout_1\","
+ "\"variantId\":\"control\","
+ "\"parameterKey\":\"my_feature\","
+ "\"parameterValue\":\"false\","
+ "\"templateVersion\":1"
+ "},"
+ "{"
+ "\"rollout_id\":\"rollout_2\""
+ "\"variantId\":\"control\","
+ "\"parameterKey\":\"color_feature\","
+ "\"parameterValue\":\"blue\","
+ "\"templateVersion\":2"
+ "}]";

private static List<RolloutAssignment> ROLLOUTS_STATE_1;

private static List<RolloutAssignment> ROLLOUTS_STATE_2;

private static final String ROLLOUT_ASSIGNMENT_JSON =
"{"
+ "\"rolloutId\":\"rollout_1\","
+ "\"variantId\":\"control\","
+ "\"parameterKey\":\"my_feature\","
+ "\"parameterValue\":\"false\","
+ "\"templateVersion\":1"
+ "}";

private static final int MAX_ENTRIES = 64;

private final RolloutAssignmentList rolloutAssignmentList =
new RolloutAssignmentList(MAX_ENTRIES);

@Override
public void setUp() throws Exception {
super.setUp();

ROLLOUTS_STATE_1 = new ArrayList<>();
ROLLOUTS_STATE_1.add(new RolloutAssignment("rollout_1", "my_feature", "false", "control", 1));

ROLLOUTS_STATE_2 = new ArrayList<>();
ROLLOUTS_STATE_2.add(new RolloutAssignment("rollout_1", "my_feature", "false", "control", 1));
ROLLOUTS_STATE_2.add(
new RolloutAssignment("rollout_2", "color_feature", "false", "control", 2));
}

@Test
public void testRollAssignmentInit_json() throws JSONException {
RolloutAssignment expected =
new RolloutAssignment("rollout_1", "my_feature", "false", "control", 1);
RolloutAssignment rolloutAssignmentInitFromJson =
new RolloutAssignment(ROLLOUT_ASSIGNMENT_JSON);

Truth.assertThat(rolloutAssignmentInitFromJson).isEqualTo(expected);
}

@Test
public void testRolloutAssignment_encodeJSON() {
RolloutAssignment rolloutAssignment =
new RolloutAssignment("rollout_1", "my_feature", "false", "control", 1);
DataEncoder encoder =
new JsonDataEncoderBuilder().configureWith(AutoRolloutAssignmentEncoder.CONFIG).build();
String data = encoder.encode(rolloutAssignment);

Truth.assertThat(data).isNotEmpty();
}

@Test
public void testUpdateRolloutAssignmentList() throws Exception {
final List<String> changed = new ArrayList<String>();

rolloutAssignmentList.updateMapList(ROLLOUTS_STATE_1);

new Thread(
new Runnable() {
@Override
public void run() {
changed.add("changed");
rolloutAssignmentList.updateMapList(ROLLOUTS_STATE_2);
}
})
.start();

new Thread(
new Runnable() {
@Override
public void run() {
List<RolloutAssignment> list = rolloutAssignmentList.getKeysMapList();
if (changed.isEmpty()) {
Truth.assertThat(rolloutAssignmentList.getKeysMapList().size()).isEqualTo(1);
} else {
Truth.assertThat(rolloutAssignmentList.getKeysMapList().size()).isEqualTo(2);
}
}
})
.start();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
import com.google.firebase.crashlytics.internal.Logger;
import com.google.firebase.crashlytics.internal.common.CommonUtils;
import com.google.firebase.crashlytics.internal.persistence.FileStore;
import com.google.firebase.encoders.DataEncoder;
import com.google.firebase.encoders.json.JsonDataEncoderBuilder;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
Expand All @@ -27,10 +29,13 @@
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

Expand Down Expand Up @@ -135,6 +140,45 @@ Map<String, String> readKeyData(String sessionId, boolean isInternal) {
return Collections.emptyMap();
}

public List<RolloutAssignment> readRolloutsState(String sessionId) {
final File f = getRolloutsStateForSession(sessionId);
if (!f.exists() || f.length() == 0) {
safeDeleteCorruptFile(f);
return Collections.emptyList();
}

InputStream is = null;
try {
is = new FileInputStream(f);
List<RolloutAssignment> rolloutsState = jsonToRolloutsState(CommonUtils.streamToString(is));
Logger.getLogger().d("Loaded rollouts state:\n" + rolloutsState + "\nfor session " + sessionId);
return rolloutsState;
} catch (Exception e) {
Logger.getLogger().w("Error deserializing rollouts state.", e);
safeDeleteCorruptFile(f);
} finally {
CommonUtils.closeOrLog(is, "Failed to close rollouts state file.");
}
return Collections.emptyList();
}

public void writeRolloutState(String sessionId, List<RolloutAssignment> rolloutsState) {
final File f = getRolloutsStateForSession(sessionId);

Writer writer = null;
try {
final String keyDataString = rolloutsStateToJson(rolloutsState);
writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(f), UTF_8));
writer.write(keyDataString);
writer.flush();
} catch (Exception e) {
Logger.getLogger().w("Error serializing rollouts state.", e);
safeDeleteCorruptFile(f);
} finally {
CommonUtils.closeOrLog(writer, "Failed to close rollouts state file.");
}
}

@NonNull
public File getUserDataFileForSession(String sessionId) {
return fileStore.getSessionFile(sessionId, UserMetadata.USERDATA_FILENAME);
Expand All @@ -150,6 +194,11 @@ public File getInternalKeysFileForSession(String sessionId) {
return fileStore.getSessionFile(sessionId, UserMetadata.INTERNAL_KEYDATA_FILENAME);
}

@NonNull
public File getRolloutsStateForSession(String sessionId) {
return fileStore.getSessionFile(sessionId, UserMetadata.ROLLOUTS_STATE_FILENAME);
}

@Nullable
private String jsonToUserId(String json) throws JSONException {
final JSONObject dataObj = new JSONObject(json);
Expand Down Expand Up @@ -179,6 +228,35 @@ private static String keysDataToJson(final Map<String, String> keyData) {
return new JSONObject(keyData).toString();
}

private static List<RolloutAssignment> jsonToRolloutsState(String json) throws JSONException {
final List<RolloutAssignment> rolloutsState = new ArrayList<RolloutAssignment>();
final JSONArray dataArray = new JSONArray(json);

for (int i = 0; i < dataArray.length(); i++) {
String dataObjectString = dataArray.getString(i);

try {
final RolloutAssignment rolloutAssignment = new RolloutAssignment(dataObjectString);
rolloutsState.add(rolloutAssignment);
} catch (Exception e) {
Logger.getLogger().w("Failed de-serializing rollouts state. " + dataObjectString, e);
}
}
return rolloutsState;
}

private static String rolloutsStateToJson(List<RolloutAssignment> rolloutsState) {
DataEncoder encoder =
new JsonDataEncoderBuilder().configureWith(AutoRolloutAssignmentEncoder.CONFIG).build();

List<String> rolloutsStateJson = new ArrayList<>();
for (int i = 0; i < rolloutsState.size(); i++) {
String rolloutAssignmentJson = encoder.encode(rolloutsState.get(i));
rolloutsStateJson.add(rolloutAssignmentJson);
}
return new JSONArray(rolloutsStateJson).toString();
}

private static String valueOrNull(JSONObject json, String key) {
return !json.isNull(key) ? json.optString(key, null) : null;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package com.google.firebase.crashlytics.internal.metadata;

import com.google.firebase.encoders.annotations.Encodable;
import java.util.Objects;
import org.json.JSONException;
import org.json.JSONObject;

/**
* model used in user metadata context which make rollout assignment serialize, deserialize easier
*/
@Encodable
public class RolloutAssignment {

private final String rolloutId;
private final String parameterKey;
private final String parameterValue;
private final String variantId;
private final int templateVersion;

public String getVariantId() {
return variantId;
}

public String getParameterKey() {
return parameterKey;
}

public String getParameterValue() {
return parameterValue;
}

public String getRolloutId() {
return rolloutId;
}

public int getTemplateVersion() {
return templateVersion;
}

public RolloutAssignment(
String rolloutId,
String parameterKey,
String parameterValue,
String variantId,
int templateVersion) {
this.rolloutId = rolloutId;
this.parameterKey = parameterKey;
this.parameterValue = parameterValue;
this.variantId = variantId;
this.templateVersion = templateVersion;
}

public RolloutAssignment(String json) throws JSONException {
final JSONObject dataObj = new JSONObject(json);

this.rolloutId = dataObj.getString("rolloutId");
this.parameterKey = dataObj.getString("parameterKey");
this.parameterValue = dataObj.getString("parameterValue");
this.variantId = dataObj.getString("variantId");
this.templateVersion = dataObj.getInt("templateVersion");
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
RolloutAssignment that = (RolloutAssignment) o;
return templateVersion == that.templateVersion
&& Objects.equals(rolloutId, that.rolloutId)
&& Objects.equals(parameterKey, that.parameterKey)
&& Objects.equals(parameterValue, that.parameterValue)
&& Objects.equals(variantId, that.variantId);
}

@Override
public int hashCode() {
return Objects.hash(rolloutId, parameterKey, parameterValue, variantId, templateVersion);
}
}
Loading

0 comments on commit 0c03205

Please sign in to comment.