diff --git a/ContainerShip/Dockerfile.android b/ContainerShip/Dockerfile.android
new file mode 100644
index 000000000..9feadf159
--- /dev/null
+++ b/ContainerShip/Dockerfile.android
@@ -0,0 +1,55 @@
+FROM containership/android-base:latest
+
+# set default environment variables
+ENV GRADLE_OPTS="-Dorg.gradle.jvmargs=\"-Xmx512m -XX:+HeapDumpOnOutOfMemoryError\""
+ENV JAVA_TOOL_OPTIONS="-Dfile.encoding=UTF8"
+ENV REACT_NATIVE_MAX_WORKERS=1
+
+# add ReactAndroid directory
+ADD .buckconfig /app/.buckconfig
+ADD .buckjavaargs /app/.buckjavaargs
+ADD ReactAndroid /app/ReactAndroid
+ADD ReactCommon /app/ReactCommon
+ADD keystores /app/keystores
+
+# set workdir
+WORKDIR /app
+
+# run buck fetches
+RUN buck fetch ReactAndroid/src/test/java/com/facebook/react/modules
+RUN buck fetch ReactAndroid/src/main/java/com/facebook/react
+RUN buck fetch ReactAndroid/src/main/java/com/facebook/react/shell
+RUN buck fetch ReactAndroid/src/test/...
+RUN buck fetch ReactAndroid/src/androidTest/...
+
+# build app
+RUN buck build ReactAndroid/src/main/java/com/facebook/react
+RUN buck build ReactAndroid/src/main/java/com/facebook/react/shell
+
+ADD gradle /app/gradle
+ADD gradlew /app/gradlew
+ADD settings.gradle /app/settings.gradle
+ADD build.gradle /app/build.gradle
+ADD react.gradle /app/react.gradle
+
+# run gradle downloads
+RUN ./gradlew :ReactAndroid:downloadBoost :ReactAndroid:downloadDoubleConversion :ReactAndroid:downloadFolly :ReactAndroid:downloadGlog
+
+# compile native libs with Gradle script, we need bridge for unit and integration tests
+RUN ./gradlew :ReactAndroid:packageReactNdkLibsForBuck -Pjobs=1 -Pcom.android.build.threadPoolSize=1
+
+# add all react-native code
+ADD . /app
+WORKDIR /app
+
+# https://github.com/npm/npm/issues/13306
+RUN cd $(npm root -g)/npm && npm install fs-extra && sed -i -e s/graceful-fs/fs-extra/ -e s/fs.rename/fs.move/ ./lib/utils/rename.js
+
+# build node dependencies
+RUN npm install
+RUN npm install github@0.2.4
+
+WORKDIR /app/website
+RUN npm install
+
+WORKDIR /app
diff --git a/ContainerShip/Dockerfile.android-base b/ContainerShip/Dockerfile.android-base
new file mode 100644
index 000000000..47475078b
--- /dev/null
+++ b/ContainerShip/Dockerfile.android-base
@@ -0,0 +1,78 @@
+FROM library/ubuntu:16.04
+
+# set default build arguments
+ARG ANDROID_VERSION=25.2.3
+ARG BUCK_VERSION=2016.11.11.01
+ARG NDK_VERSION=10e
+ARG NODE_VERSION=6.2.0
+ARG WATCHMAN_VERSION=4.7.0
+
+# set default environment variables
+ENV ADB_INSTALL_TIMEOUT=10
+ENV PATH=${PATH}:/opt/buck/bin/
+ENV ANDROID_HOME=/opt/android
+ENV ANDROID_SDK_HOME=${ANDROID_HOME}
+ENV PATH=${PATH}:${ANDROID_HOME}/tools:${ANDROID_HOME}/platform-tools
+ENV ANDROID_NDK=/opt/ndk/android-ndk-r$NDK_VERSION
+ENV PATH=${PATH}:${ANDROID_NDK}
+
+# install system dependencies
+RUN apt-get update && apt-get install ant autoconf automake curl g++ gcc git libqt5widgets5 lib32z1 lib32stdc++6 make maven npm openjdk-8* python-dev python3-dev qml-module-qtquick-controls qtdeclarative5-dev unzip -y
+
+# configure npm
+RUN npm config set spin=false
+RUN npm config set progress=false
+
+# install node
+RUN npm install n -g
+RUN n $NODE_VERSION
+
+# download buck
+RUN git clone https://github.com/facebook/buck.git /opt/buck
+WORKDIR /opt/buck
+RUN git checkout v$BUCK_VERSION
+
+# build buck
+RUN ant
+
+# download watchman
+RUN git clone https://github.com/facebook/watchman.git /opt/watchman
+WORKDIR /opt/watchman
+RUN git checkout v$WATCHMAN_VERSION
+
+# build watchman
+RUN ./autogen.sh
+RUN ./configure
+RUN make
+RUN make install
+
+# download and unpack android
+RUN mkdir /opt/android
+WORKDIR /opt/android
+RUN curl --silent https://dl.google.com/android/repository/tools_r$ANDROID_VERSION-linux.zip > android.zip
+RUN unzip android.zip
+RUN rm android.zip
+
+# download and unpack NDK
+RUN mkdir /opt/ndk
+WORKDIR /opt/ndk
+RUN curl --silent https://dl.google.com/android/repository/android-ndk-r$NDK_VERSION-linux-x86_64.zip > ndk.zip
+RUN unzip ndk.zip
+
+# cleanup NDK
+RUN rm ndk.zip
+
+# add android SDK tools
+# 2 - Android SDK Platform-tools, revision 25.0.3
+# 12 - Android SDK Build-tools, revision 23.0.1
+# 35 - SDK Platform Android 6.0, API 23, revision 3
+# 39 - SDK Platform Android 4.4.2, API 19, revision 4
+# 102 - ARM EABI v7a System Image, Android API 19, revision 5
+# 103 - Intel x86 Atom System Image, Android API 19, revision 5
+# 131 - Google APIs, Android API 23, revision 1
+# 166 - Android Support Repository, revision 41
+RUN echo "y" | android update sdk -u -a -t 2,12,35,39,102,103,131,166
+RUN ln -s /opt/android/platform-tools/adb /usr/bin/adb
+
+# clean up unnecessary directories
+RUN rm -rf /opt/android/system-images/android-19/default/x86
diff --git a/ContainerShip/Dockerfile.javascript b/ContainerShip/Dockerfile.javascript
new file mode 100644
index 000000000..d9fc1f57c
--- /dev/null
+++ b/ContainerShip/Dockerfile.javascript
@@ -0,0 +1,19 @@
+FROM library/node:6.9.2
+
+ENV YARN_VERSION=0.19.1
+
+# install dependencies
+RUN apt-get update && apt-get install ocaml libelf-dev -y
+RUN npm install yarn@$YARN_VERSION -g
+
+# add code
+RUN mkdir /app
+ADD . /app
+
+WORKDIR /app
+RUN yarn install --ignore-engines
+
+WORKDIR website
+RUN yarn install --ignore-engines --ignore-platform
+
+WORKDIR /app
diff --git a/ContainerShip/scripts/run-android-ci-instrumentation-tests.js b/ContainerShip/scripts/run-android-ci-instrumentation-tests.js
new file mode 100644
index 000000000..fe29cbac1
--- /dev/null
+++ b/ContainerShip/scripts/run-android-ci-instrumentation-tests.js
@@ -0,0 +1,153 @@
+/**
+ * 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.
+ */
+
+'use strict';
+
+/**
+ * This script runs instrumentation tests one by one with retries
+ * Instrumentation tests tend to be flaky, so rerunning them individually increases
+ * chances for success and reduces total average execution time.
+ *
+ * We assume that all instrumentation tests are flat in one folder
+ * Available arguments:
+ * --path - path to all .java files with tests
+ * --package - com.facebook.react.tests
+ * --retries [num] - how many times to retry possible flaky commands: npm install and running tests, default 1
+ */
+/*eslint-disable no-undef */
+
+const argv = require('yargs').argv;
+const async = require('async');
+const child_process = require('child_process');
+const fs = require('fs');
+const path = require('path');
+
+const colors = {
+ GREEN: '\x1b[32m',
+ RED: '\x1b[31m',
+ RESET: '\x1b[0m'
+};
+
+const test_opts = {
+ FILTER: new RegExp(argv.filter || '.*', 'i'),
+ PACKAGE: argv.package || 'com.facebook.react.tests',
+ PATH: argv.path || './ReactAndroid/src/androidTest/java/com/facebook/react/tests',
+ RETRIES: parseInt(argv.retries || 2, 10),
+
+ TEST_TIMEOUT: parseInt(argv['test-timeout'] || 1000 * 60 * 10),
+
+ OFFSET: argv.offset,
+ COUNT: argv.count
+}
+
+let max_test_class_length = Number.NEGATIVE_INFINITY;
+
+let testClasses = fs.readdirSync(path.resolve(process.cwd(), test_opts.PATH))
+ .filter((file) => {
+ return file.endsWith('.java');
+ }).map((clazz) => {
+ return path.basename(clazz, '.java');
+ }).map((clazz) => {
+ return test_opts.PACKAGE + '.' + clazz;
+ }).filter((clazz) => {
+ return test_opts.FILTER.test(clazz);
+ });
+
+// only process subset of the tests at corresponding offset and count if args provided
+if (test_opts.COUNT != null && test_opts.OFFSET != null) {
+ const testCount = testClasses.length;
+ const start = test_opts.COUNT * test_opts.OFFSET;
+ const end = start + test_opts.COUNT;
+
+ if (start >= testClasses.length) {
+ testClasses = [];
+ } else if (end >= testClasses.length) {
+ testClasses = testClasses.slice(start);
+ } else {
+ testClasses = testClasses.slice(start, end);
+ }
+}
+
+return async.mapSeries(testClasses, (clazz, callback) => {
+ if(clazz.length > max_test_class_length) {
+ max_test_class_length = clazz.length;
+ }
+
+ return async.retry(test_opts.RETRIES, (retryCb) => {
+ const test_process = child_process.spawn('./ContainerShip/scripts/run-instrumentation-tests-via-adb-shell.sh', [test_opts.PACKAGE, clazz], {
+ stdio: 'inherit'
+ })
+
+ const timeout = setTimeout(() => {
+ test_process.kill();
+ }, test_opts.TEST_TIMEOUT);
+
+ test_process.on('error', (err) => {
+ clearTimeout(timeout);
+ retryCb(err);
+ });
+
+ test_process.on('exit', (code) => {
+ clearTimeout(timeout);
+
+ if(code !== 0) {
+ return retryCb(new Error(`Process exited with code: ${code}`));
+ }
+
+ return retryCb();
+ });
+ }, (err) => {
+ return callback(null, {
+ name: clazz,
+ status: err ? 'failure' : 'success'
+ });
+ });
+}, (err, results) => {
+ print_test_suite_results(results);
+
+ const failures = results.filter((test) => {
+ test.status === 'failure';
+ });
+
+ return failures.length === 0 ? process.exit(0) : process.exit(1);
+});
+
+function print_test_suite_results(results) {
+ console.log('\n\nTest Suite Results:\n');
+
+ let color;
+ let failing_suites = 0;
+ let passing_suites = 0;
+
+ function pad_output(num_chars) {
+ let i = 0;
+
+ while(i < num_chars) {
+ process.stdout.write(' ');
+ i++;
+ }
+ }
+ results.forEach((test) => {
+ if(test.status === 'success') {
+ color = colors.GREEN;
+ passing_suites++;
+ } else if(test.status === 'failure') {
+ color = colors.RED;
+ failing_suites++;
+ }
+
+ process.stdout.write(color);
+ process.stdout.write(test.name);
+ pad_output((max_test_class_length - test.name.length) + 8);
+ process.stdout.write(test.status);
+ process.stdout.write(`${colors.RESET}\n`);
+ });
+
+ console.log(`\n${passing_suites} passing, ${failing_suites} failing!`);
+}
diff --git a/ContainerShip/scripts/run-android-docker-instrumentation-tests.sh b/ContainerShip/scripts/run-android-docker-instrumentation-tests.sh
new file mode 100644
index 000000000..9d3f768f4
--- /dev/null
+++ b/ContainerShip/scripts/run-android-docker-instrumentation-tests.sh
@@ -0,0 +1,34 @@
+#!/bin/bash
+
+# for buck gen
+mount -o remount,exec /dev/shm
+
+AVD_UUID=$(cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 8 | head -n 1)
+
+# create virtual device
+echo no | android create avd -n $AVD_UUID -f -t android-19 --abi default/armeabi-v7a
+
+# emulator setup
+emulator64-arm -avd $AVD_UUID -no-skin -no-audio -no-window -no-boot-anim &
+bootanim=""
+until [[ "$bootanim" =~ "stopped" ]]; do
+ sleep 5
+ bootanim=$(adb -e shell getprop init.svc.bootanim 2>&1)
+ echo "boot animation status=$bootanim"
+done
+
+set -x
+
+# solve issue with max user watches limit
+echo 65536 | tee -a /proc/sys/fs/inotify/max_user_watches
+watchman shutdown-server
+
+# integration tests
+# build JS bundle for instrumentation tests
+node local-cli/cli.js bundle --platform android --dev true --entry-file ReactAndroid/src/androidTest/js/TestBundle.js --bundle-output ReactAndroid/src/androidTest/assets/AndroidTestBundle.js
+
+# build test APK
+buck install ReactAndroid/src/androidTest/buck-runner:instrumentation-tests --config build.threads=1
+
+# run installed apk with tests
+node ./ContainerShip/scripts/run-android-ci-instrumentation-tests.js $*
diff --git a/ContainerShip/scripts/run-android-docker-unit-tests.sh b/ContainerShip/scripts/run-android-docker-unit-tests.sh
new file mode 100644
index 000000000..5a58bb352
--- /dev/null
+++ b/ContainerShip/scripts/run-android-docker-unit-tests.sh
@@ -0,0 +1,12 @@
+#!/bin/bash
+
+# set default environment variables
+UNIT_TESTS_BUILD_THREADS="${UNIT_TESTS_BUILD_THREADS:-1}"
+
+# for buck gen
+mount -o remount,exec /dev/shm
+
+set -x
+
+# run unit tests
+buck test ReactAndroid/src/test/... --config build.threads=$UNIT_TESTS_BUILD_THREADS
diff --git a/ContainerShip/scripts/run-ci-e2e-tests.sh b/ContainerShip/scripts/run-ci-e2e-tests.sh
new file mode 100755
index 000000000..e6739dfe9
--- /dev/null
+++ b/ContainerShip/scripts/run-ci-e2e-tests.sh
@@ -0,0 +1,250 @@
+#!/bin/bash
+
+set -ex
+
+# set default environment variables
+ROOT=$(pwd)
+SCRIPTS=$(pwd)/scripts
+
+RUN_ANDROID=0
+RUN_CLI_INSTALL=1
+RUN_IOS=0
+RUN_JS=0
+
+RETRY_COUNT=${RETRY_COUNT:-3}
+AVD_UUID=$(cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 8 | head -n 1)
+
+ANDROID_NPM_DEPS="appium@1.5.1 mocha@2.4.5 wd@0.3.11 colors@1.0.3 pretty-data2@0.40.1"
+CLI_PACKAGE=$ROOT/react-native-cli/react-native-cli-*.tgz
+PACKAGE=$ROOT/react-native-*.tgz
+REACT_NATIVE_MAX_WORKERS=1
+
+# retries command on failure
+# $1 -- max attempts
+# $2 -- command to run
+function retry() {
+ local -r -i max_attempts="$1"; shift
+ local -r cmd="$@"
+ local -i attempt_num=1
+
+ until $cmd; do
+ if (( attempt_num == max_attempts )); then
+ echo "Execution of '$cmd' failed; no more attempts left"
+ return 1
+ else
+ (( attempt_num++ ))
+ echo "Execution of '$cmd' failed; retrying for attempt number $attempt_num..."
+ fi
+ done
+}
+
+# parse command line args & flags
+while :; do
+ case "$1" in
+ --android)
+ RUN_ANDROID=1
+ shift
+ ;;
+
+ --ios)
+ RUN_IOS=1
+ shift
+ ;;
+
+ --js)
+ RUN_JS=1
+ shift
+ ;;
+
+ --skip-cli-install)
+ RUN_CLI_INSTALL=0
+ shift
+ ;;
+
+ --tvos)
+ RUN_IOS=1
+ shift
+ ;;
+
+ *)
+ break
+ esac
+done
+
+function e2e_suite() {
+ cd $ROOT
+
+ if [ $RUN_ANDROID -eq 0 ] && [ $RUN_IOS -eq 0 ] && [ $RUN_JS -eq 0 ]; then
+ echo "No e2e tests specified!"
+ return 0
+ fi
+
+ # create temp dir
+ TEMP_DIR=$(mktemp -d /tmp/react-native-XXXXXXXX)
+
+ # To make sure we actually installed the local version
+ # of react-native, we will create a temp file inside the template
+ # and check that it exists after `react-native init
+ IOS_MARKER=$(mktemp $ROOT/local-cli/templates/HelloWorld/ios/HelloWorld/XXXXXXXX)
+ ANDROID_MARKER=$(mktemp ${ROOT}/local-cli/templates/HelloWorld/android/XXXXXXXX)
+
+ # install CLI
+ cd react-native-cli
+ npm pack
+ cd ..
+
+ # can skip cli install for non sudo mode
+ if [ $RUN_CLI_INSTALL -ne 0 ]; then
+ npm install -g $CLI_PACKAGE
+ if [ $? -ne 0 ]; then
+ echo "Could not install react-native-cli globally, please run in su mode"
+ echo "Or with --skip-cli-install to skip this step"
+ return 1
+ fi
+ fi
+
+ if [ $RUN_ANDROID -ne 0 ]; then
+ set +ex
+
+ # create virtual device
+ if ! android list avd | grep "$AVD_UUID" > /dev/null; then
+ echo no | android create avd -n $AVD_UUID -f -t android-19 --abi default/armeabi-v7a
+ fi
+
+ # newline at end of adb devices call and first line is headers
+ DEVICE_COUNT=$(adb devices | wc -l)
+ ((DEVICE_COUNT -= 2))
+
+ # will always kill an existing emulator if one exists for fresh setup
+ if [[ $DEVICE_COUNT -ge 1 ]]; then
+ adb emu kill
+ fi
+
+ # emulator setup
+ emulator64-arm -avd $AVD_UUID -no-skin -no-audio -no-window -no-boot-anim &
+
+ bootanim=""
+ until [[ "$bootanim" =~ "stopped" ]]; do
+ sleep 5
+ bootanim=$(adb -e shell getprop init.svc.bootanim 2>&1)
+ echo "boot animation status=$bootanim"
+ done
+
+ set -ex
+
+ ./gradlew :ReactAndroid:installArchives -Pjobs=1 -Dorg.gradle.jvmargs="-Xmx512m -XX:+HeapDumpOnOutOfMemoryError"
+ if [ $? -ne 0 ]; then
+ echo "Failed to compile Android binaries"
+ return 1
+ fi
+ fi
+
+ npm pack
+ if [ $? -ne 0 ]; then
+ echo "Failed to pack react-native"
+ return 1
+ fi
+
+ cd $TEMP_DIR
+
+ retry $RETRY_COUNT react-native init EndToEndTest --version $PACKAGE --npm
+ if [ $? -ne 0 ]; then
+ echo "Failed to execute react-native init"
+ echo "Most common reason is npm registry connectivity, try again"
+ return 1
+ fi
+
+ cd EndToEndTest
+
+ # android tests
+ if [ $RUN_ANDROID -ne 0 ]; then
+ echo "Running an Android e2e test"
+ echo "Installing e2e framework"
+
+ retry $RETRY_COUNT npm install --save-dev $ANDROID_NPM_DEPS --silent >> /dev/null
+ if [ $? -ne 0 ]; then
+ echo "Failed to install appium"
+ echo "Most common reason is npm registry connectivity, try again"
+ return 1
+ fi
+
+ cp $SCRIPTS/android-e2e-test.js android-e2e-test.js
+
+ cd android
+ echo "Downloading Maven deps"
+ ./gradlew :app:copyDownloadableDepsToLibs
+
+ cd ..
+ keytool -genkey -v -keystore android/keystores/debug.keystore -storepass android -alias androiddebugkey -keypass android -dname "CN=Android Debug,O=Android,C=US"
+
+ echo "Starting packager server"
+ node ./node_modules/.bin/appium >> /dev/null &
+ APPIUM_PID=$!
+ echo "Starting appium server $APPIUM_PID"
+
+ echo "Building app"
+ buck build android/app
+
+ # hack to get node unhung (kill buckd)
+ kill -9 $(pgrep java)
+
+ if [ $? -ne 0 ]; then
+ echo "could not execute Buck build, is it installed and in PATH?"
+ return 1
+ fi
+
+ npm start >> /dev/null &
+ SERVER_PID=$!
+ sleep 15
+
+ echo "Executing android e2e test"
+ retry $RETRY_COUNT node node_modules/.bin/_mocha android-e2e-test.js
+ if [ $? -ne 0 ]; then
+ echo "Failed to run Android e2e tests"
+ echo "Most likely the code is broken"
+ return 1
+ fi
+
+ # kill packager process
+ if kill -0 $SERVER_PID; then
+ echo "Killing packager $SERVER_PID"
+ kill -9 $SERVER_PID
+ fi
+
+ # kill appium process
+ if kill -0 $APPIUM_PID; then
+ echo "Killing appium $APPIUM_PID"
+ kill -9 $APPIUM_PID
+ fi
+
+ fi
+
+ # ios tests
+ if [ $RUN_IOS -ne 0 ]; then
+ echo "Running ios e2e tests not yet implemented for docker!"
+ fi
+
+ # js tests
+ if [ $RUN_JS -ne 0 ]; then
+ # Check the packager produces a bundle (doesn't throw an error)
+ REACT_NATIVE_MAX_WORKERS=1 react-native bundle --platform android --dev true --entry-file index.android.js --bundle-output android-bundle.js
+ if [ $? -ne 0 ]; then
+ echo "Could not build android bundle"
+ return 1
+ fi
+
+ REACT_NATIVE_MAX_WORKERS=1 react-native bundle --platform ios --dev true --entry-file index.ios.js --bundle-output ios-bundle.js
+ if [ $? -ne 0 ]; then
+ echo "Could not build iOS bundle"
+ return 1
+ fi
+ fi
+
+ # directory cleanup
+ rm $IOS_MARKER
+ rm $ANDROID_MARKER
+
+ return 0
+}
+
+retry $RETRY_COUNT e2e_suite
diff --git a/ContainerShip/scripts/run-instrumentation-tests-via-adb-shell.sh b/ContainerShip/scripts/run-instrumentation-tests-via-adb-shell.sh
new file mode 100755
index 000000000..ea5255052
--- /dev/null
+++ b/ContainerShip/scripts/run-instrumentation-tests-via-adb-shell.sh
@@ -0,0 +1,65 @@
+#!/bin/bash
+
+# Python script to run instrumentation tests, copied from https://github.com/circleci/circle-dummy-android
+# Example: ./scripts/run-android-instrumentation-tests.sh com.facebook.react.tests com.facebook.react.tests.ReactPickerTestCase
+#
+export PATH="$ANDROID_HOME/platform-tools:$ANDROID_HOME/tools:$PATH"
+
+# clear the logs
+adb logcat -c
+
+# run tests and check output
+python - $1 $2 << END
+
+import re
+import subprocess as sp
+import sys
+import threading
+import time
+
+done = False
+
+test_app = sys.argv[1]
+test_class = None
+
+if len(sys.argv) > 2:
+ test_class = sys.argv[2]
+
+def update():
+ # prevent CircleCI from killing the process for inactivity
+ while not done:
+ time.sleep(5)
+ print "Running in background. Waiting for 'adb' command reponse..."
+
+t = threading.Thread(target=update)
+t.dameon = True
+t.start()
+
+def run():
+ sp.Popen(['adb', 'wait-for-device']).communicate()
+ if (test_class != None):
+ p = sp.Popen('adb shell am instrument -w -e class %s %s/android.support.test.runner.AndroidJUnitRunner'
+ % (test_class, test_app), shell=True, stdout=sp.PIPE, stderr=sp.PIPE, stdin=sp.PIPE)
+ else :
+ p = sp.Popen('adb shell am instrument -w %s/android.support.test.runner.AndroidJUnitRunner'
+ % (test_app), shell=True, stdout=sp.PIPE, stderr=sp.PIPE, stdin=sp.PIPE)
+ return p.communicate()
+
+success = re.compile(r'OK \(\d+ test(s)?\)')
+stdout, stderr = run()
+
+done = True
+print stderr
+print stdout
+
+if success.search(stderr + stdout):
+ sys.exit(0)
+else:
+ # dump the logs
+ sp.Popen(['adb', 'logcat', '-d']).communicate()
+ sys.exit(1) # make sure we fail if the test failed
+END
+
+RETVAL=$?
+
+exit $RETVAL
diff --git a/DockerTests.md b/DockerTests.md
new file mode 100644
index 000000000..790d19527
--- /dev/null
+++ b/DockerTests.md
@@ -0,0 +1,96 @@
+# Dockerfile Tests
+
+This is a high level overview of the test configuration using docker. It explains how to run the tests locally
+and how they integrate with the Jenkins Pipeline script to run the automated tests on ContainerShip .
+
+## Docker Installation
+
+It is required to have Docker running on your machine in order to build and run the tests in the Dockerfiles.
+See for more information on how to install.
+
+## Convenience NPM Run Scripts
+
+We have added a number of default run scripts to the `package.json` file to simplify building and running your tests.
+
+`npm run test-android-setup` - Pulls down the base android docker image used for running the tests
+
+`npm run test-android-build` - Builds the docker image used to run the tests
+
+`npm run test-android-run-unit` - Runs all the unit tests that have been built in the latest react/android docker image (note: you need to run test-android-build before executing this, if the image does not exist it will fail)
+
+`npm run test-android-run-instrumentation` - Runs all the instrumentation tests that have been built in the latest react/android docker image (note: you need to run test-android-build before executing this, if the image does not exist it will fail). You can also pass additional flags to filter which tests instrumentation tests are run. Ex: `npm run test-android-run-instrumentation -- --filter=TestIdTestCase` to only run the TestIdTestCase instrumentation test. See below for more information
+on the instrumentation test flags.
+
+`npm run test-android-run-e2e` - Runs all the end to end tests that have been built in the latest react/android docker image (note: you need to run test-android-build before executing this, if the image does not exist it will fail)
+
+`npm run test-android-unit` - Builds and runs the android unit tests.
+
+`npm run test-android-instrumentation` - Builds and runs the android instrumentation tests.
+
+`npm run test-android-e2e` - Builds and runs the android end to end tests.
+
+## Detailed Android Setup
+
+There are two Dockerfiles for use with the Android codebase.
+
+The `Dockerfile.android-base` contains all the necessary prerequisites required to run the React Android tests. It is
+separated out into a separate Dockerfile because these are dependencies that rarely change and also because it is quite
+a beastly image since it contains all the Android depedencies for running android and the emulators (~9GB).
+
+The good news is you should rarely have to build or pull down the base image! All iterative code updates happen as
+part of the `Dockerfile.android` image build.
+
+So step one...
+
+`docker pull containership/android-base:latest`
+
+This will take quite some time depending on your connection and you need to ensure you have ~10GB of free disk space.
+
+Once this is done, you can run tests locally by executing two simple commands:
+
+1. `docker build -t react/android -f ./ContainerShip/Dockerfile.android .`
+2. `docker run --cap-add=SYS_ADMIN -it react/android bash ContainerShip/scripts/run-android-docker-unit-tests.sh`
+
+> Note: `--cap-add=SYS_ADMIN` flag is required for the `ContainerShip/scripts/run-android-docker-unit-tests.sh` and
+`ContainerShip/scripts/run-android-docker-instrumentation-tests.sh` in order to allow the remounting of `/dev/shm` as writeable
+so the `buck` build system may write temporary output to that location
+
+Every time you make any modifications to the codebase, you should re-run the `docker build ...` command in order for your
+updates to be included in your local docker image.
+
+The following shell scripts have been provided for android testing:
+
+`ContainerShip/scripts/run-android-docker-unit-tests.sh` - Runs the standard android unit tests
+
+`ContainerShip/scripts/run-android-docker-instrumentation-tests.sh` - Runs the android instrumentation tests on the emulator. *Note* that these
+tests take quite some time to run so there are various flags you can pass in order to filter which tests are run (see below)
+
+`ContainerShip/scripts/run-ci-e2e-tests.sh` - Runs the android end to end tests
+
+#### ContainerShip/scripts/run-android-docker-instrumentation-tests.sh
+
+The instrumentation test script accepts the following flags in order to customize the execution of the tests:
+
+`--filter` - A regex that filters which instrumentation tests will be run. (Defaults to .*)
+
+`--package` - Name of the java package containing the instrumentation tests (Defaults to com.facebook.react.tests)
+
+`--path` - Path to the directory containing the instrumentation tests. (Defaults to ./ReactAndroid/src/androidTest/java/com/facebook/react/tests)
+
+`--retries` - Number of times to retry a failed test before declaring a failure (Defaults to 2)
+
+For example, if locally you only wanted to run the InitialPropsTestCase, you could do the following:
+
+`docker run --cap-add=SYS_ADMIN -it react/android bash ContainerShip/scripts/run-android-docker-instrumentation-tests.sh --filter="InitialPropsTestCase"`
+
+# Javascript Setup
+
+There is a single Dockerfile for use with the javascript codebase.
+
+The `Dockerfile.javascript` base requires all the necessary dependencies for running Javascript tests.
+
+Any time you make an update to the codebase, you can build and run the javascript tests with the following three commands:
+
+1. `docker build -t react/js -f ./ContainerShip/Dockerfile.javascript .`
+2. `docker run -it react/js yarn test --maxWorkers=4`
+3. `docker run -it react/js yarn run flow -- check`
diff --git a/Jenkinsfile b/Jenkinsfile
new file mode 100644
index 000000000..3563e6bbb
--- /dev/null
+++ b/Jenkinsfile
@@ -0,0 +1,189 @@
+import groovy.json.JsonSlurperClassic
+
+def runPipeline() {
+ try {
+ ansiColor('xterm') {
+ runStages();
+ }
+ } catch(err) {
+ echo "Error: ${err}"
+ currentBuild.result = "FAILED"
+ }
+}
+
+def pullDockerImage(imageName) {
+ def result = sh(script: "docker pull ${imageName}", returnStatus: true)
+
+ if (result != 0) {
+ throw new Exception("Failed to pull image[${imageName}]")
+ }
+}
+
+def buildDockerfile(dockerfilePath = "Dockerfile", imageName) {
+ def buildCmd = "docker build -f ${dockerfilePath} -t ${imageName} ."
+ echo "${buildCmd}"
+
+ def result = sh(script: buildCmd, returnStatus: true)
+
+ if (result != 0) {
+ throw new Exception("Failed to build image[${imageName}] from '${dockerfilePath}'")
+ }
+}
+
+def runCmdOnDockerImage(imageName, cmd, run_opts = '') {
+ def result = sh(script: "docker run ${run_opts} -i ${imageName} sh -c '${cmd}'", returnStatus: true)
+
+ if(result != 0) {
+ throw new Exception("Failed to run cmd[${cmd}] on image[${imageName}]")
+ }
+}
+
+def calculateGithubInfo() {
+ return [
+ branch: env.BRANCH_NAME,
+ sha: sh(returnStdout: true, script: 'git rev-parse HEAD').trim(),
+ tag: null,
+ isPR: "${env.CHANGE_URL}".contains('/pull/')
+ ]
+}
+
+def getParallelInstrumentationTests(testDir, parallelCount, imageName) {
+ def integrationTests = [:]
+ def testCount = sh(script: "ls ${testDir} | wc -l", returnStdout: true).trim().toInteger()
+ def testPerParallel = testCount.intdiv(parallelCount) + 1
+
+ for (def x = 0; (x*testPerParallel) < testCount; x++) {
+ def offset = x
+ integrationTests["android integration tests: ${offset}"] = {
+ run: {
+ runCmdOnDockerImage(imageName, "bash /app/ContainerShip/scripts/run-android-docker-instrumentation-tests.sh --offset=${offset} --count=${testPerParallel}", '--privileged --rm')
+ }
+ }
+ }
+
+ return integrationTests
+}
+
+def runStages() {
+ def buildInfo = [
+ image: [
+ name: "facebook/react-native",
+ tag: null
+ ],
+ scm: [
+ branch: null,
+ sha: null,
+ tag: null,
+ isPR: false
+ ]
+ ]
+
+ node {
+ def jsDockerBuild, androidDockerBuild
+ def jsTag, androidTag, jsImageName, androidImageName, parallelInstrumentationTests
+
+ try {
+ stage('Setup') {
+ parallel(
+ 'pull images': {
+ pullDockerImage('containership/android-base:latest')
+ }
+ )
+ }
+
+ stage('Build') {
+ checkout scm
+
+ def githubInfo = calculateGithubInfo()
+ buildInfo.scm.branch = githubInfo.branch
+ buildInfo.scm.sha = githubInfo.sha
+ buildInfo.scm.tag = githubInfo.tag
+ buildInfo.scm.isPR = githubInfo.isPR
+ buildInfo.image.tag = "${buildInfo.scm.sha}-${env.BUILD_TAG.replace("/", "-").replace("%2F", "-")}"
+
+ jsTag = "${buildInfo.image.tag}"
+ androidTag = "${buildInfo.image.tag}"
+ jsImageName = "${buildInfo.image.name}-js:${jsTag}"
+ androidImageName = "${buildInfo.image.name}-android:${androidTag}"
+
+ parallelInstrumentationTests = getParallelInstrumentationTests('./ReactAndroid/src/androidTest/java/com/facebook/react/tests', 3, androidImageName)
+
+ parallel(
+ 'javascript build': {
+ jsDockerBuild = docker.build("${jsImageName}", "-f ContainerShip/Dockerfile.javascript .")
+ },
+ 'android build': {
+ androidDockerBuild = docker.build("${androidImageName}", "-f ContainerShip/Dockerfile.android .")
+ }
+ )
+
+ }
+
+ stage('Tests JS') {
+ parallel(
+ 'javascript flow': {
+ runCmdOnDockerImage(jsImageName, 'yarn run flow -- check', '--rm')
+ },
+ 'javascript tests': {
+ runCmdOnDockerImage(jsImageName, 'yarn test --maxWorkers=4', '--rm')
+ },
+ 'documentation tests': {
+ runCmdOnDockerImage(jsImageName, 'cd website && yarn test', '--rm')
+ },
+ 'documentation generation': {
+ runCmdOnDockerImage(jsImageName, 'cd website && node ./server/generate.js', '--rm')
+ }
+ )
+ }
+
+ stage('Tests Android') {
+ parallel(
+ 'android unit tests': {
+ runCmdOnDockerImage(androidImageName, 'bash /app/ContainerShip/scripts/run-android-docker-unit-tests.sh', '--privileged --rm')
+ },
+ 'android e2e tests': {
+ runCmdOnDockerImage(androidImageName, 'bash /app/ContainerShip/scripts/run-ci-e2e-tests.sh --android --js', '--rm')
+ }
+ )
+ }
+
+ stage('Tests Android Instrumentation') {
+ // run all tests in parallel
+ parallel(parallelInstrumentationTests)
+ }
+
+ stage('Cleanup') {
+ cleanupImage(jsDockerBuild)
+ cleanupImage(androidDockerBuild)
+ }
+ } catch(err) {
+ cleanupImage(jsDockerBuild)
+ cleanupImage(androidDockerBuild)
+
+ throw err
+ }
+ }
+
+}
+
+def isMasterBranch() {
+ return env.GIT_BRANCH == 'master'
+}
+
+def gitCommit() {
+ return sh(returnStdout: true, script: 'git rev-parse HEAD').trim()
+}
+
+def cleanupImage(image) {
+ if (image) {
+ try {
+ sh "docker ps -a | awk '{ print \$1,\$2 }' | grep ${image.id} | awk '{print \$1 }' | xargs -I {} docker rm {}"
+ sh "docker rmi -f ${image.id}"
+ } catch(e) {
+ echo "Error cleaning up ${image.id}"
+ echo "${e}"
+ }
+ }
+}
+
+runPipeline()
diff --git a/package.json b/package.json
index 40861a648..bf92e9ae6 100644
--- a/package.json
+++ b/package.json
@@ -112,7 +112,16 @@
"test": "jest",
"flow": "flow",
"lint": "eslint Examples/ Libraries/",
- "start": "/usr/bin/env bash -c './packager/packager.sh \"$@\" || true' --"
+ "start": "/usr/bin/env bash -c './packager/packager.sh \"$@\" || true' --",
+ "test-android-setup": "docker pull containership/android-base:latest",
+ "test-android-build": "docker build -t react/android -f ContainerShip/Dockerfile.android .",
+ "test-android-run-instrumentation": "docker run --cap-add=SYS_ADMIN -it react/android bash ContainerShip/scripts/run-android-docker-instrumentation-tests.sh",
+ "test-android-run-unit": "docker run --cap-add=SYS_ADMIN -it react/android bash ContainerShip/scripts/run-android-docker-unit-tests.sh",
+ "test-android-run-e2e": "docker run -it react/android bash ContainerShip/scripts/run-ci-e2e-tests.sh --android --js",
+ "test-android-all": "npm run test-android-build && npm run test-android-run-unit && npm run test-android-run-instrumentation && npm run test-android-run-e2e",
+ "test-android-instrumentation": "npm run test-android-build && npm run test-android-run-instrumentation",
+ "test-android-unit": "npm run test-android-build && npm run test-android-run-unit",
+ "test-android-e2e": "npm run test-android-build && npm run test-android-run-e2e"
},
"bin": {
"react-native": "local-cli/wrong-react-native.js"