diff --git a/ReactAndroid/src/main/java/com/facebook/react/bridge/ReactMarkerConstants.java b/ReactAndroid/src/main/java/com/facebook/react/bridge/ReactMarkerConstants.java index f1fbae600..8aab2c73c 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/bridge/ReactMarkerConstants.java +++ b/ReactAndroid/src/main/java/com/facebook/react/bridge/ReactMarkerConstants.java @@ -35,7 +35,4 @@ public class ReactMarkerConstants { "CREATE_UI_MANAGER_MODULE_CONSTANTS_END"; public static final String CREATE_MODULE_START = "CREATE_MODULE_START"; public static final String CREATE_MODULE_END = "CREATE_MODULE_END"; - public static final String UNPACKER_CHECK_START = "UNPACKER_CHECK_START"; - public static final String UNPACKER_CHECK_END = "UNPACKER_CHECK_END"; - public static final String UNPACKER_BUNDLE_EXTRACTED = "UNPACKER_BUNDLE_EXTRACTED"; } diff --git a/ReactAndroid/src/main/java/com/facebook/react/cxxbridge/OptimizedJSBundleLoader.java b/ReactAndroid/src/main/java/com/facebook/react/cxxbridge/OptimizedJSBundleLoader.java new file mode 100644 index 000000000..6efcb3a3f --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/cxxbridge/OptimizedJSBundleLoader.java @@ -0,0 +1,35 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +package com.facebook.react.cxxbridge; + +/** + * Bundle loader using optimized bundle API + */ +public class OptimizedJSBundleLoader extends JSBundleLoader { + private String mPath; + private String mSourceURL; + private int mLoadFlags; + + public OptimizedJSBundleLoader(String path, String sourceURL, int loadFlags) { + mLoadFlags = loadFlags; + mSourceURL = sourceURL; + mPath = path; + } + + @Override + public void loadScript(CatalystInstanceImpl instance) { + instance.loadScriptFromOptimizedBundle(mPath, mSourceURL, mLoadFlags); + } + + @Override + public String getSourceUrl() { + return mSourceURL; + } +} diff --git a/ReactAndroid/src/main/java/com/facebook/react/cxxbridge/UnpackingJSBundleLoader.java b/ReactAndroid/src/main/java/com/facebook/react/cxxbridge/UnpackingJSBundleLoader.java deleted file mode 100644 index dd593d010..000000000 --- a/ReactAndroid/src/main/java/com/facebook/react/cxxbridge/UnpackingJSBundleLoader.java +++ /dev/null @@ -1,484 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - */ - -package com.facebook.react.cxxbridge; - -import android.content.Context; -import android.content.res.AssetManager; - -import com.facebook.infer.annotation.Assertions; -import com.facebook.react.bridge.ReactMarker; -import com.facebook.react.bridge.ReactMarkerConstants; -import com.facebook.soloader.FileLocker; -import com.facebook.soloader.SysUtil; -import com.facebook.systrace.Systrace; - -import java.io.ByteArrayOutputStream; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.io.RandomAccessFile; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.concurrent.Semaphore; - -import javax.annotation.Nullable; - -import static com.facebook.systrace.Systrace.TRACE_TAG_REACT_JAVA_BRIDGE; - -/** - * JSBundleLoader capable of unpacking specified files necessary for executing - * JS bundle stored in optimized format. - */ -public class UnpackingJSBundleLoader extends JSBundleLoader { - - /** - * Name of the lock files. Multiple processes can be spawned off the same app - * and we need to guarantee that at most one unpacks files at any time. To - * make that work any process is required to hold file system lock on - * LOCK_FILE when checking whether files should be unpacked and unpacking - * them. - */ - static final String LOCK_FILE = "unpacking-bundle-loader.lock"; - - /** - * Existence of this file indicates that the last unpacking operation finished - * before the app was killed or crashed. File with this name is created in the - * destination directory as the last one. If it is present it means that - * all the files that needed to be fsynced were fsynced and their content is - * what it should be. - */ - static final String DOT_UNPACKED_FILE = ".unpacked"; - - private static final int IO_BUFFER_SIZE = 16 * 1024; - - /** - * Where all the files should go to. - */ - private final File mDirectoryPath; - - private final String mSourceURL; - private final Context mContext; - private final int mLoadFlags; - private final boolean mFinishOnBackgroundThread; - private final @Nullable Runnable mOnUnpackedCallback; - - /** - * True if prepare was called. - */ - private boolean mPrepared; - - /** - * Synchronizes unpacking within this process. - */ - private static final Semaphore sProcessLock = new Semaphore(1); - - /** - * Synchronizes unpacking across multiple processes. - */ - private @Nullable FileLocker mFileLocker; - - /** - * Description of what needs to be unpacked. - */ - private final Unpacker[] mUnpackers; - - /* package */ UnpackingJSBundleLoader(Builder builder) { - mContext = Assertions.assertNotNull(builder.context); - mDirectoryPath = Assertions.assertNotNull(builder.destinationPath); - mSourceURL = Assertions.assertNotNull(builder.sourceURL); - mUnpackers = builder.unpackers.toArray(new Unpacker[builder.unpackers.size()]); - mLoadFlags = builder.loadFlags; - mFinishOnBackgroundThread = builder.finishOnBackgroundThread; - mOnUnpackedCallback = builder.callback; - mFileLocker = null; - mPrepared = false; - } - - /** - * Checks if any file needs to be extracted again, and if so, clears the destination - * directory and unpacks everything again. - * - * This method does not do anything if called for the second time - */ - public synchronized void prepare() { - if (mPrepared) { - return; - } - - ReactMarker.logMarker(ReactMarkerConstants.UNPACKER_CHECK_START); - - boolean unpacked = false; - try { - lock(); - - Systrace.beginSection(TRACE_TAG_REACT_JAVA_BRIDGE, "UnpackingJSBundleLoader.prepare"); - try { - unpacked = prepareLocked(); - } finally { - Systrace.endSection(TRACE_TAG_REACT_JAVA_BRIDGE); - if (!mFinishOnBackgroundThread || !unpacked) { - unlock(); - } - } - } catch (IOException | InterruptedException e) { - throw new RuntimeException(e); - } - - if (unpacked) { - ReactMarker.logMarker(ReactMarkerConstants.UNPACKER_BUNDLE_EXTRACTED); - } - - if (unpacked && mOnUnpackedCallback != null) { - mOnUnpackedCallback.run(); - } - - ReactMarker.logMarker(ReactMarkerConstants.UNPACKER_CHECK_END); - - mPrepared = true; - } - - private boolean prepareLocked() throws IOException { - final File dotFinishedFilePath = new File(mDirectoryPath, DOT_UNPACKED_FILE); - boolean shouldReconstruct = !mDirectoryPath.exists() || !dotFinishedFilePath.exists(); - - final byte[] buffer = new byte[IO_BUFFER_SIZE]; - for (int i = 0; i < mUnpackers.length && !shouldReconstruct; ++i) { - shouldReconstruct = mUnpackers[i].shouldReconstructDir(mContext, buffer); - } - - if (!shouldReconstruct) { - return false; - } - - boolean succeeded = false; - try { - SysUtil.dumbDeleteRecursive(mDirectoryPath); - if (!mDirectoryPath.mkdirs()) { - throw new IOException("Coult not create the destination directory"); - } - - for (Unpacker unpacker : mUnpackers) { - unpacker.unpack(mContext, buffer); - } - - if (mFinishOnBackgroundThread) { - finishUnpackingOnBackgroundThread(); - } else { - finishUnpacking(); - } - - succeeded = true; - } finally { - // In case of failure do yourself a favor and remove partially initialized state. - if (!succeeded) { - SysUtil.dumbDeleteRecursive(mDirectoryPath); - } - } - - return true; - } - - private void finishUnpacking() throws IOException { - for (Unpacker unpacker : mUnpackers) { - unpacker.finishUnpacking(mContext); - } - - final File dotFinishedFilePath = new File(mDirectoryPath, DOT_UNPACKED_FILE); - if (!dotFinishedFilePath.createNewFile()) { - throw new IOException("Could not create .unpacked file"); - } - - // It would be nice to fsync a few directories and files here. The thing is, if we crash and - // lose some data then it should be noticed on the next prepare invocation and the directory - // will be reconstructed. It is only crucial to fsync those files whose content is not - // verified on each start. Everything else is a tradeoff between perf with no crashes - // situation and perf when user experiences crashes. Fortunately Unpackers corresponding - // to files whose content is not checked handle fsyncs themselves. - } - - /** - * Finishes unpacking and unlocks the unpacker on a background thread. - */ - private void finishUnpackingOnBackgroundThread() { - new Thread(new Runnable() { - @Override - public void run() { - Systrace.beginSection( - TRACE_TAG_REACT_JAVA_BRIDGE, - "UnpackingJSBundleLoader.finishUnpackingOnBackgroundThread()"); - try { - finishUnpacking(); - unlock(); - } catch (IOException e) { - throw new RuntimeException(e); - } finally { - Systrace.endSection(TRACE_TAG_REACT_JAVA_BRIDGE); - } - } - }).start(); - } - - @Override - public void loadScript(CatalystInstanceImpl instance) { - prepare(); - instance.loadScriptFromOptimizedBundle( - mDirectoryPath.getPath(), - mSourceURL, - mLoadFlags); - } - - private void lock() throws IOException, InterruptedException { - Systrace.beginSection(TRACE_TAG_REACT_JAVA_BRIDGE, "UnpackingJSBundleLoader.lock"); - try { - sProcessLock.acquire(); - boolean success = false; - - try { - Assertions.assertCondition(mFileLocker == null); - mFileLocker = FileLocker.lock(new File(mContext.getFilesDir(), LOCK_FILE)); - success = true; - } finally { - if (!success) { - sProcessLock.release(); - } - } - } finally { - Systrace.endSection(TRACE_TAG_REACT_JAVA_BRIDGE); - } - } - - private void unlock() throws IOException { - Assertions.assertNotNull(mFileLocker).close(); - mFileLocker = null; - sProcessLock.release(); - } - - @Override - public String getSourceUrl() { - return mSourceURL; - } - - static void fsync(File path) throws IOException { - Systrace.beginSection(TRACE_TAG_REACT_JAVA_BRIDGE, "UnpackingJSBundleLoader.fsync"); - try (RandomAccessFile file = new RandomAccessFile(path, "r")) { - file.getFD().sync(); - } finally { - Systrace.endSection(TRACE_TAG_REACT_JAVA_BRIDGE); - } - } - - /** - * Reads all the bytes (but no more that maxSize) from given input stream through ioBuffer - * and returns byte array containing all the read bytes. - */ - static byte[] readBytes(InputStream is, byte[] ioBuffer, int maxSize) throws IOException { - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - copyBytes(baos, is, ioBuffer, maxSize); - return baos.toByteArray(); - } - - /** - * Pumps all the bytes (but no more that maxSize) from given input stream through ioBuffer - * to given output stream and returns number of moved bytes. - */ - static int copyBytes( - OutputStream os, - InputStream is, - byte[] ioBuffer, - int maxSize) throws IOException { - int totalSize = 0; - while (totalSize < maxSize) { - int rc = is.read(ioBuffer, 0, Math.min(maxSize - totalSize, ioBuffer.length)); - if (rc == -1) { - break; - } - os.write(ioBuffer, 0, rc); - totalSize += rc; - } - return totalSize; - } - - public static Builder newBuilder() { - return new Builder(); - } - - public static class Builder { - private @Nullable Context context; - private @Nullable File destinationPath; - private @Nullable String sourceURL; - private final ArrayList unpackers; - private int loadFlags; - private boolean finishOnBackgroundThread; - private @Nullable Runnable callback; - - public Builder() { - this.unpackers = new ArrayList(); - context = null; - destinationPath = null; - sourceURL = null; - loadFlags = 0; - finishOnBackgroundThread = true; - callback = null; - } - - public Builder setContext(Context context) { - this.context = context; - return this; - } - - public Builder setDestinationPath(File destinationPath) { - this.destinationPath = destinationPath; - return this; - } - - public Builder setSourceURL(String sourceURL) { - this.sourceURL = sourceURL; - return this; - } - - public Builder setLoadFlags(int loadFlags) { - this.loadFlags = loadFlags; - return this; - } - - public Builder setFinishOnBackgroundThread(boolean finishOnBackgroundThread) { - this.finishOnBackgroundThread = finishOnBackgroundThread; - return this; - } - - /** - * Adds a file for unpacking. Content of extracted file is not checked on each - * start against content of the file bundled in apk. - */ - public Builder unpackFile(String nameInApk, String destFileName) { - unpackers.add(new ExistenceCheckingUnpacker(nameInApk, destFileName)); - return this; - } - - /** - * Adds a file for unpacking. Content of extracted file is compared on each - * start with content of the same file bundled in apk. It is usefull for - * detecting bundle/app changes. - */ - public Builder checkAndUnpackFile(String nameInApk, String destFileName) { - unpackers.add(new ContentCheckingUnpacker(nameInApk, destFileName)); - return this; - } - - /** - * Adds arbitrary unpacker. Usefull for injecting mocks. - */ - Builder addUnpacker(Unpacker u) { - unpackers.add(u); - return this; - } - - public Builder setOnUnpackedCallback(Runnable callback) { - this.callback = callback; - return this; - } - - public UnpackingJSBundleLoader build() { - Assertions.assertNotNull(destinationPath); - for (int i = 0; i < unpackers.size(); ++i) { - unpackers.get(i).setDestinationDirectory(destinationPath); - } - return new UnpackingJSBundleLoader(this); - } - } - - /** - * Abstraction for dealing with unpacking single file from apk. - */ - static abstract class Unpacker { - protected final String mNameInApk; - private final String mFileName; - protected @Nullable File mDestinationFilePath; - - public Unpacker(String nameInApk, String fileName) { - mNameInApk = nameInApk; - mFileName = fileName; - } - - public void setDestinationDirectory(File destinationDirectoryPath) { - mDestinationFilePath = new File(destinationDirectoryPath, mFileName); - } - - public abstract boolean shouldReconstructDir(Context context, byte[] ioBuffer) - throws IOException; - - public void unpack(Context context, byte[] ioBuffer) throws IOException { - AssetManager am = context.getAssets(); - try (InputStream is = am.open(mNameInApk, AssetManager.ACCESS_STREAMING)) { - try (FileOutputStream fileOutputStream = new FileOutputStream( - Assertions.assertNotNull(mDestinationFilePath))) { - copyBytes(fileOutputStream, is, ioBuffer, Integer.MAX_VALUE); - } - } - } - - public void finishUnpacking(Context context) throws IOException { - } - } - - /** - * Deals with unpacking files whose content is not checked on each start and - * need to be fsynced after unpacking. - */ - static class ExistenceCheckingUnpacker extends Unpacker { - public ExistenceCheckingUnpacker(String nameInApk, String fileName) { - super(nameInApk, fileName); - } - - @Override - public boolean shouldReconstructDir(Context context, byte[] ioBuffer) { - return !Assertions.assertNotNull(mDestinationFilePath).exists(); - } - - @Override - public void finishUnpacking(Context context) throws IOException { - fsync(Assertions.assertNotNull(mDestinationFilePath)); - } - } - - /** - * Deals with unpacking files whose content is checked on each start and thus - * do not require fsync. - */ - static class ContentCheckingUnpacker extends Unpacker { - public ContentCheckingUnpacker(String nameInApk, String fileName) { - super(nameInApk, fileName); - } - - @Override - public boolean shouldReconstructDir(Context context, byte[] ioBuffer) throws IOException { - if (!Assertions.assertNotNull(mDestinationFilePath).exists()) { - return true; - } - - AssetManager am = context.getAssets(); - final byte[] assetContent; - try (InputStream assetStream = am.open(mNameInApk, AssetManager.ACCESS_STREAMING)) { - assetContent = readBytes(assetStream, ioBuffer, Integer.MAX_VALUE); - } - - final byte[] fileContent; - try (InputStream fileStream = new FileInputStream( - Assertions.assertNotNull(mDestinationFilePath))) { - fileContent = readBytes(fileStream, ioBuffer, assetContent.length + 1); - } - - return !Arrays.equals(assetContent, fileContent); - } - } -} diff --git a/ReactAndroid/src/test/java/com/facebook/react/cxxbridge/BUCK b/ReactAndroid/src/test/java/com/facebook/react/cxxbridge/BUCK deleted file mode 100644 index da5b9ff3d..000000000 --- a/ReactAndroid/src/test/java/com/facebook/react/cxxbridge/BUCK +++ /dev/null @@ -1,41 +0,0 @@ -include_defs('//ReactAndroid/DEFS') - -STANDARD_TEST_SRCS = [ - '*Test.java', -] - -android_library( - name = 'testhelpers', - srcs = glob(['*.java'], excludes = STANDARD_TEST_SRCS), - deps = [ - react_native_dep('third-party/java/junit:junit'), - react_native_dep('third-party/java/mockito:mockito'), - ], - visibility = [ - 'PUBLIC' - ], -) - -robolectric3_test( - name = 'bridge', - # Please change the contact to the oncall of your team - contacts = ['oncall+fbandroid_sheriff@xmail.facebook.com'], - srcs = glob(STANDARD_TEST_SRCS), - deps = [ - ':testhelpers', - react_native_dep('libraries/fbcore/src/test/java/com/facebook/powermock:powermock'), - react_native_dep('libraries/soloader/java/com/facebook/soloader:soloader'), - react_native_dep('third-party/java/junit:junit'), - react_native_dep('third-party/java/mockito:mockito'), - react_native_dep('third-party/java/robolectric3/robolectric:robolectric'), - react_native_target('java/com/facebook/react/bridge:bridge'), - react_native_target('java/com/facebook/react/cxxbridge:bridge'), - ], - visibility = [ - 'PUBLIC' - ], -) - -project_config( - test_target = ':bridge', -) diff --git a/ReactAndroid/src/test/java/com/facebook/react/cxxbridge/ContentCheckingUnpackerTest.java b/ReactAndroid/src/test/java/com/facebook/react/cxxbridge/ContentCheckingUnpackerTest.java deleted file mode 100644 index 23ba3ca67..000000000 --- a/ReactAndroid/src/test/java/com/facebook/react/cxxbridge/ContentCheckingUnpackerTest.java +++ /dev/null @@ -1,85 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - */ - -package com.facebook.react.cxxbridge; - -import java.io.FileDescriptor; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.InputStream; -import java.io.IOException; - -import org.junit.Before; -import org.junit.Rule; -import org.junit.runner.RunWith; -import org.junit.Test; -import org.powermock.core.classloader.annotations.PowerMockIgnore; -import org.powermock.core.classloader.annotations.PrepareForTest; -import org.powermock.modules.junit4.rule.PowerMockRule; -import org.robolectric.RobolectricTestRunner; - -import static org.junit.Assert.assertArrayEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; -import static org.mockito.Matchers.any; -import static org.mockito.Matchers.eq; -import static org.mockito.Mockito.times; -import static org.powermock.api.mockito.PowerMockito.mockStatic; -import static org.powermock.api.mockito.PowerMockito.verifyStatic; - -@PrepareForTest({UnpackingJSBundleLoader.class}) -@PowerMockIgnore({ "org.mockito.*", "org.robolectric.*", "android.*" }) -@RunWith(RobolectricTestRunner.class) -public class ContentCheckingUnpackerTest extends UnpackerTestBase { - @Rule - public PowerMockRule rule = new PowerMockRule(); - - private UnpackingJSBundleLoader.ContentCheckingUnpacker mUnpacker; - - @Before - public void setUp() throws IOException { - super.setUp(); - mUnpacker = new UnpackingJSBundleLoader.ContentCheckingUnpacker( - NAME_IN_APK, - DESTINATION_NAME); - mUnpacker.setDestinationDirectory(folder.getRoot()); - } - - @Test - public void testReconstructsIfFileDoesNotExist() throws IOException { - assertTrue(mUnpacker.shouldReconstructDir(mContext, mIOBuffer)); - } - - @Test - public void testReconstructsIfContentDoesNotMatch() throws IOException { - try (FileOutputStream fos = new FileOutputStream(mDestinationPath)) { - fos.write(ASSET_DATA, 0, ASSET_DATA.length - 1); - fos.write((byte) (ASSET_DATA[ASSET_DATA.length - 1] + 1)); - } - assertTrue(mUnpacker.shouldReconstructDir(mContext, mIOBuffer)); - } - - @Test - public void testDoesNotReconstructIfContentMatches() throws IOException { - try (FileOutputStream fos = new FileOutputStream(mDestinationPath)) { - fos.write(ASSET_DATA); - } - assertFalse(mUnpacker.shouldReconstructDir(mContext, mIOBuffer)); - } - - @Test - public void testUnpacksFile() throws IOException { - mUnpacker.unpack(mContext, mIOBuffer); - assertTrue(mDestinationPath.exists()); - try (InputStream is = new FileInputStream(mDestinationPath)) { - byte[] storedData = UnpackingJSBundleLoader.readBytes(is, mIOBuffer, Integer.MAX_VALUE); - assertArrayEquals(ASSET_DATA, storedData); - } - } -} diff --git a/ReactAndroid/src/test/java/com/facebook/react/cxxbridge/ExistenceCheckingUnpackerTest.java b/ReactAndroid/src/test/java/com/facebook/react/cxxbridge/ExistenceCheckingUnpackerTest.java deleted file mode 100644 index 8950d79e1..000000000 --- a/ReactAndroid/src/test/java/com/facebook/react/cxxbridge/ExistenceCheckingUnpackerTest.java +++ /dev/null @@ -1,78 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - */ - -package com.facebook.react.cxxbridge; - -import java.io.FileInputStream; -import java.io.InputStream; -import java.io.IOException; - -import org.junit.Before; -import org.junit.Rule; -import org.junit.runner.RunWith; -import org.junit.Test; -import org.powermock.core.classloader.annotations.PowerMockIgnore; -import org.powermock.core.classloader.annotations.PrepareForTest; -import org.powermock.modules.junit4.rule.PowerMockRule; -import org.robolectric.RobolectricTestRunner; - -import static org.junit.Assert.assertArrayEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; -import static org.mockito.Mockito.times; -import static org.powermock.api.mockito.PowerMockito.mockStatic; -import static org.powermock.api.mockito.PowerMockito.verifyStatic; - -@PrepareForTest({UnpackingJSBundleLoader.class}) -@PowerMockIgnore({ "org.mockito.*", "org.robolectric.*", "android.*" }) -@RunWith(RobolectricTestRunner.class) -public class ExistenceCheckingUnpackerTest extends UnpackerTestBase { - @Rule - public PowerMockRule rule = new PowerMockRule(); - - private UnpackingJSBundleLoader.ExistenceCheckingUnpacker mUnpacker; - - @Before - public void setUp() throws IOException { - super.setUp(); - mUnpacker = new UnpackingJSBundleLoader.ExistenceCheckingUnpacker( - NAME_IN_APK, - DESTINATION_NAME); - mUnpacker.setDestinationDirectory(folder.getRoot()); - } - - @Test - public void testReconstructsIfFileDoesNotExist() { - assertTrue(mUnpacker.shouldReconstructDir(mContext, mIOBuffer)); - } - - @Test - public void testDoesNotReconstructIfFileExists() throws IOException { - mDestinationPath.createNewFile(); - assertFalse(mUnpacker.shouldReconstructDir(mContext, mIOBuffer)); - } - - @Test - public void testUnpacksFile() throws IOException { - mUnpacker.unpack(mContext, mIOBuffer); - assertTrue(mDestinationPath.exists()); - try (InputStream is = new FileInputStream(mDestinationPath)) { - byte[] storedData = UnpackingJSBundleLoader.readBytes(is, mIOBuffer, Integer.MAX_VALUE); - assertArrayEquals(ASSET_DATA, storedData); - } - } - - @Test - public void testFsyncsAfterUnpacking() throws IOException { - mockStatic(UnpackingJSBundleLoader.class); - mUnpacker.finishUnpacking(mContext); - verifyStatic(times(1)); - UnpackingJSBundleLoader.fsync(mDestinationPath); - } -} diff --git a/ReactAndroid/src/test/java/com/facebook/react/cxxbridge/UnpackerTestBase.java b/ReactAndroid/src/test/java/com/facebook/react/cxxbridge/UnpackerTestBase.java deleted file mode 100644 index a2865a612..000000000 --- a/ReactAndroid/src/test/java/com/facebook/react/cxxbridge/UnpackerTestBase.java +++ /dev/null @@ -1,95 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - */ - -package com.facebook.react.cxxbridge; - -import android.content.Context; -import android.content.res.AssetManager; - -import java.io.ByteArrayInputStream; -import java.io.File; -import java.io.FileDescriptor; -import java.io.FileInputStream; -import java.io.InputStream; -import java.io.IOException; - -import org.junit.Rule; -import org.junit.rules.TemporaryFolder; -import org.mockito.invocation.InvocationOnMock; -import org.mockito.stubbing.Answer; - -import static org.mockito.Matchers.any; -import static org.mockito.Matchers.anyInt; -import static org.mockito.Matchers.eq; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -public class UnpackerTestBase { - static final String NAME_IN_APK = "nameInApk"; - static final String DESTINATION_NAME = "destination"; - static final byte[] ASSET_DATA = new byte[]{(byte) 1, (byte) 101, (byte) 50}; - - @Rule - public TemporaryFolder folder = new TemporaryFolder(); - - File mDestinationPath; - byte[] mIOBuffer; - - Context mContext; - AssetManager mAssetManager; - - public void setUp() throws IOException { - mDestinationPath = new File(folder.getRoot(), DESTINATION_NAME); - mIOBuffer = new byte[16 * 1024]; - - mContext = mock(Context.class); - mAssetManager = mock(AssetManager.class); - - when(mContext.getAssets()).thenReturn(mAssetManager); - when(mAssetManager.open(eq(NAME_IN_APK), anyInt())) - .then(new Answer() { - @Override - public FileInputStream answer(InvocationOnMock invocation) throws Throwable { - final ByteArrayInputStream bais = new ByteArrayInputStream(ASSET_DATA); - final FileInputStream fis = mock(FileInputStream.class); - when(fis.read()) - .then(new Answer() { - @Override - public Integer answer(InvocationOnMock invocation) throws Throwable { - return bais.read(); - } - }); - when(fis.read(any(byte[].class))) - .then(new Answer() { - @Override - public Integer answer(InvocationOnMock invocation) throws Throwable { - return bais.read((byte[]) invocation.getArguments()[0]); - } - }); - when(fis.read(any(byte[].class), any(int.class), any(int.class))) - .then(new Answer() { - @Override - public Integer answer(InvocationOnMock invocation) throws Throwable { - return bais.read( - (byte[]) invocation.getArguments()[0], - (int) invocation.getArguments()[1], - (int) invocation.getArguments()[2]); - } - }); - when(fis.available()).then(new Answer() { - @Override - public Integer answer(InvocationOnMock invocation) throws Throwable { - return bais.available(); - } - }); - return fis; - } - }); - } -} diff --git a/ReactAndroid/src/test/java/com/facebook/react/cxxbridge/UnpackingJSBundleLoaderTest.java b/ReactAndroid/src/test/java/com/facebook/react/cxxbridge/UnpackingJSBundleLoaderTest.java deleted file mode 100644 index b44fa65cc..000000000 --- a/ReactAndroid/src/test/java/com/facebook/react/cxxbridge/UnpackingJSBundleLoaderTest.java +++ /dev/null @@ -1,215 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - */ - -package com.facebook.react.cxxbridge; - -import android.content.Context; - -import com.facebook.soloader.SoLoader; - -import java.io.File; -import java.io.IOException; - -import org.junit.Before; -import org.junit.Rule; -import org.junit.rules.TemporaryFolder; -import org.junit.runner.RunWith; -import org.junit.Test; -import org.robolectric.RobolectricTestRunner; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; -import static org.mockito.Matchers.any; -import static org.mockito.Matchers.eq; -import static org.mockito.Matchers.same; -import static org.mockito.Mockito.doThrow; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoMoreInteractions; - -@RunWith(RobolectricTestRunner.class) -public class UnpackingJSBundleLoaderTest { - static { - SoLoader.setInTestMode(); - } - - private static final String URL = "http://this.is.an.url"; - private static final int MOCK_UNPACKERS_NUM = 2; - private static final int UNPACKER_TEST_FLAGS = 129; - - @Rule - public TemporaryFolder folder = new TemporaryFolder(); - - private File mDestinationPath; - private File mFilesPath; - - private UnpackingJSBundleLoader.Builder mBuilder; - private Context mContext; - private CatalystInstanceImpl mCatalystInstanceImpl; - private UnpackingJSBundleLoader.Unpacker[] mMockUnpackers; - - private Runnable mCallback; - - @Before - public void setUp() throws IOException { - mDestinationPath = folder.newFolder("destination"); - mFilesPath = folder.newFolder("files"); - - mContext = mock(Context.class); - when(mContext.getFilesDir()).thenReturn(mFilesPath); - - mCatalystInstanceImpl = mock(CatalystInstanceImpl.class); - - mBuilder = UnpackingJSBundleLoader.newBuilder() - .setDestinationPath(mDestinationPath) - .setSourceURL(URL) - .setContext(mContext) - .setFinishOnBackgroundThread(false); - - mMockUnpackers = new UnpackingJSBundleLoader.Unpacker[MOCK_UNPACKERS_NUM]; - for (int i = 0; i < mMockUnpackers.length; ++i) { - mMockUnpackers[i] = mock(UnpackingJSBundleLoader.Unpacker.class); - } - - mCallback = mock(Runnable.class); - } - - private void addUnpackers() { - for (UnpackingJSBundleLoader.Unpacker unpacker : mMockUnpackers) { - mBuilder.addUnpacker(unpacker); - } - } - - @Test - public void testGetSourceUrl() { - assertEquals(URL, mBuilder.build().getSourceUrl()); - } - - @Test - public void testCreatesDotUnpackedFile() throws IOException { - mBuilder.build().prepare(); - assertTrue(new File(mDestinationPath, UnpackingJSBundleLoader.DOT_UNPACKED_FILE).exists()); - } - - @Test - public void testCreatesLockFile() throws IOException { - mBuilder.build().prepare(); - assertTrue(new File(mFilesPath, UnpackingJSBundleLoader.LOCK_FILE).exists()); - } - - @Test - public void testCallsAppropriateInstanceMethod() throws IOException { - mBuilder.build().loadScript(mCatalystInstanceImpl); - verify(mCatalystInstanceImpl).loadScriptFromOptimizedBundle( - eq(mDestinationPath.getPath()), - eq(URL), - eq(0)); - verifyNoMoreInteractions(mCatalystInstanceImpl); - } - - @Test - public void testSetLoadFlags() throws IOException { - mBuilder.setLoadFlags(UNPACKER_TEST_FLAGS) - .build() - .loadScript(mCatalystInstanceImpl); - verify(mCatalystInstanceImpl).loadScriptFromOptimizedBundle( - eq(mDestinationPath.getPath()), - eq(URL), - eq(UNPACKER_TEST_FLAGS)); - } - - @Test - public void testLoadScriptUnpacks() { - mBuilder.build().loadScript(mCatalystInstanceImpl); - assertTrue(new File(mDestinationPath, UnpackingJSBundleLoader.DOT_UNPACKED_FILE).exists()); - } - - @Test - public void testPrepareCallDoesNotRecreateDirIfNotNecessary() throws IOException { - mBuilder.build().prepare(); - - addUnpackers(); - mBuilder.build().prepare(); - for (UnpackingJSBundleLoader.Unpacker unpacker : mMockUnpackers) { - verify(unpacker).setDestinationDirectory(mDestinationPath); - verify(unpacker).shouldReconstructDir( - same(mContext), - any(byte[].class)); - verifyNoMoreInteractions(unpacker); - } - } - - @Test - public void testShouldReconstructDirForcesRecreation() throws IOException { - mBuilder.build().prepare(); - - addUnpackers(); - when(mMockUnpackers[0].shouldReconstructDir( - same(mContext), - any(byte[].class))) - .thenReturn(true); - mBuilder.build().prepare(); - - verify(mMockUnpackers[0]).shouldReconstructDir( - same(mContext), - any(byte[].class)); - for (UnpackingJSBundleLoader.Unpacker unpacker : mMockUnpackers) { - verify(unpacker).setDestinationDirectory(mDestinationPath); - verify(unpacker).unpack( - same(mContext), - any(byte[].class)); - verify(unpacker).finishUnpacking(same(mContext)); - verifyNoMoreInteractions(unpacker); - } - } - - @Test - public void testDirectoryReconstructionRemovesDir() throws IOException { - mBuilder.build().prepare(); - final File aFile = new File(mDestinationPath, "a_file"); - aFile.createNewFile(); - - when(mMockUnpackers[0].shouldReconstructDir( - same(mContext), - any(byte[].class))) - .thenReturn(true); - addUnpackers(); - mBuilder.build().prepare(); - - assertFalse(aFile.exists()); - } - - @Test(expected = RuntimeException.class) - public void testDropsDirectoryOnException() throws IOException { - doThrow(new IOException("An expected IOException")) - .when(mMockUnpackers[0]).unpack( - same(mContext), - any(byte[].class)); - try { - mBuilder.addUnpacker(mMockUnpackers[0]).build().prepare(); - } finally { - assertFalse(mDestinationPath.exists()); - } - } - - @Test - public void testCallbackIsCalledAfterUnpack() { - mBuilder.setOnUnpackedCallback(mCallback).build().prepare(); - verify(mCallback).run(); - } - - @Test - public void testCallbackIsNotCalledIfNothingIsUnpacked() { - mBuilder.build().prepare(); - mBuilder.setOnUnpackedCallback(mCallback).build().prepare(); - verifyNoMoreInteractions(mCallback); - } -}