Skip to content

Commit a05e9f8

Browse files
motiz88facebook-github-bot
authored andcommitted
Android: Sort modules by ID when serializing delta bundle
Summary: Fixes redbox/yellowbox symbolication when the Java delta client is enabled. Previously the modules would get concatenated in a nondeterministic order (owing to Metro's parallelism) which differed from their order in the source map, where they're explicitly sorted by module ID. This diff changes the data structure holding modules in memory from a `LinkedHashMap` (which iterates in insertion order) to a `TreeMap` (which iterates in key order). NOTE: Similar to this change in the Chrome debugger's delta client: react-native-community/cli#279 Reviewed By: dcaspi Differential Revision: D15301927 fbshipit-source-id: 27bdecfb3d6963aa358e4d542c8b7663fd9eb437
1 parent 4105450 commit a05e9f8

File tree

2 files changed

+145
-4
lines changed

2 files changed

+145
-4
lines changed

ReactAndroid/src/main/java/com/facebook/react/devsupport/BundleDeltaClient.java

+4-4
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
import java.io.FileOutputStream;
1212
import java.io.IOException;
1313
import java.io.InputStreamReader;
14-
import java.util.LinkedHashMap;
14+
import java.util.TreeMap;
1515
import javax.annotation.Nullable;
1616

1717
import android.util.JsonReader;
@@ -75,7 +75,7 @@ private static class BundleDeltaJavaClient extends BundleDeltaClient {
7575

7676
byte[] mPreCode;
7777
byte[] mPostCode;
78-
final LinkedHashMap<Number, byte[]> mModules = new LinkedHashMap<Number, byte[]>();
78+
final TreeMap<Number, byte[]> mModules = new TreeMap<Number, byte[]>();
7979

8080
@Override
8181
public boolean canHandle(ClientType type) {
@@ -146,7 +146,7 @@ public synchronized Pair<Boolean, NativeDeltaClient> processDelta(
146146
return Pair.create(Boolean.TRUE, null);
147147
}
148148

149-
private static int setModules(JsonReader jsonReader, LinkedHashMap<Number, byte[]> map)
149+
private static int setModules(JsonReader jsonReader, TreeMap<Number, byte[]> map)
150150
throws IOException {
151151
jsonReader.beginArray();
152152

@@ -167,7 +167,7 @@ private static int setModules(JsonReader jsonReader, LinkedHashMap<Number, byte[
167167
return numModules;
168168
}
169169

170-
private static int removeModules(JsonReader jsonReader, LinkedHashMap<Number, byte[]> map)
170+
private static int removeModules(JsonReader jsonReader, TreeMap<Number, byte[]> map)
171171
throws IOException {
172172
jsonReader.beginArray();
173173

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
/**
2+
* Copyright (c) Facebook, Inc. and its affiliates.
3+
*
4+
* <p>This source code is licensed under the MIT license found in the LICENSE file in the root
5+
* directory of this source tree.
6+
*/
7+
package com.facebook.react.devsupport;
8+
9+
import static org.fest.assertions.api.Assertions.assertThat;
10+
11+
import com.facebook.react.common.StandardCharsets;
12+
import com.facebook.react.devsupport.BundleDeltaClient;
13+
import org.junit.Test;
14+
import org.junit.Before;
15+
import org.junit.runner.RunWith;
16+
import org.robolectric.RobolectricTestRunner;
17+
import okio.BufferedSource;
18+
import org.junit.Rule;
19+
import org.junit.rules.TemporaryFolder;
20+
import okio.Okio;
21+
import java.io.ByteArrayInputStream;
22+
import java.nio.file.Files;
23+
import java.io.File;
24+
import java.io.IOException;
25+
26+
@RunWith(RobolectricTestRunner.class)
27+
public class BundleDeltaClientTest {
28+
private BundleDeltaClient mClient;
29+
30+
@Rule public TemporaryFolder mFolder = new TemporaryFolder();
31+
32+
@Before
33+
public void setUp() {
34+
mClient = BundleDeltaClient.create(BundleDeltaClient.ClientType.DEV_SUPPORT);
35+
}
36+
37+
@Test
38+
public void testAcceptsSimpleInitialBundle() throws IOException {
39+
File file = mFolder.newFile();
40+
mClient.processDelta(
41+
bufferedSource(
42+
"{"
43+
+ "\"pre\": \"console.log('Hello World!');\","
44+
+ "\"post\": \"console.log('That is all folks!');\","
45+
+ "\"modules\": [[0, \"console.log('Best module.');\"]]"
46+
+ "}"),
47+
file);
48+
assertThat(contentOf(file))
49+
.isEqualTo(
50+
"console.log('Hello World!');\n"
51+
+ "console.log('Best module.');\n"
52+
+ "console.log('That is all folks!');\n");
53+
}
54+
55+
@Test
56+
public void testPatchesInitialBundleWithDeltaBundle() throws IOException {
57+
File file = mFolder.newFile();
58+
mClient.processDelta(
59+
bufferedSource(
60+
"{"
61+
+ "\"pre\": \"pre\","
62+
+ "\"post\": \"post\","
63+
+ "\"modules\": [[0, \"0\"], [1, \"1\"]]"
64+
+ "}"),
65+
file);
66+
file = mFolder.newFile();
67+
mClient.processDelta(
68+
bufferedSource(
69+
"{"
70+
+ "\"added\": [[2, \"2\"]],"
71+
+ "\"modified\": [[0, \"0.1\"]],"
72+
+ "\"deleted\": [1]"
73+
+ "}"),
74+
file);
75+
assertThat(contentOf(file))
76+
.isEqualTo(
77+
"pre\n"
78+
+ "0.1\n"
79+
+ "2\n"
80+
+ "post\n");
81+
}
82+
83+
@Test
84+
public void testSortsModulesByIdInInitialBundle() throws IOException {
85+
File file = mFolder.newFile();
86+
mClient.processDelta(
87+
bufferedSource(
88+
"{"
89+
+ "\"pre\": \"console.log('Hello World!');\","
90+
+ "\"post\": \"console.log('That is all folks!');\","
91+
+ "\"modules\": [[3, \"3\"], [0, \"0\"], [2, \"2\"], [1, \"1\"]]"
92+
+ "}"),
93+
file);
94+
assertThat(contentOf(file))
95+
.isEqualTo(
96+
"console.log('Hello World!');\n"
97+
+ "0\n"
98+
+ "1\n"
99+
+ "2\n"
100+
+ "3\n"
101+
+ "console.log('That is all folks!');\n");
102+
}
103+
104+
@Test
105+
public void testSortsModulesByIdInPatchedBundle() throws IOException {
106+
File file = mFolder.newFile();
107+
mClient.processDelta(
108+
bufferedSource(
109+
"{"
110+
+ "\"pre\": \"console.log('Hello World!');\","
111+
+ "\"post\": \"console.log('That is all folks!');\","
112+
+ "\"modules\": [[3, \"3\"], [0, \"0\"], [1, \"1\"]]"
113+
+ "}"),
114+
file);
115+
file = mFolder.newFile();
116+
mClient.processDelta(
117+
bufferedSource(
118+
"{"
119+
+ "\"added\": [[2, \"2\"]],"
120+
+ "\"modified\": [[0, \"0.1\"]],"
121+
+ "\"deleted\": [1]"
122+
+ "}"),
123+
file);
124+
assertThat(contentOf(file))
125+
.isEqualTo(
126+
"console.log('Hello World!');\n"
127+
+ "0.1\n"
128+
+ "2\n"
129+
+ "3\n"
130+
+ "console.log('That is all folks!');\n");
131+
}
132+
133+
private static BufferedSource bufferedSource(String string) {
134+
return Okio.buffer(
135+
Okio.source(new ByteArrayInputStream(string.getBytes(StandardCharsets.UTF_8))));
136+
}
137+
138+
private static String contentOf(File file) throws IOException {
139+
return new String(Files.readAllBytes(file.toPath()), StandardCharsets.UTF_8);
140+
}
141+
}

0 commit comments

Comments
 (0)