Docker Testing Environment for Android & JS
Summary: Created a containerized environment to run unit and integration tests for both javascript and android as well as a Jenkinsfile using the new 2.0 Pipeline syntax for integration into a Jenkins CI cluster. Here is a quick summary of the changes: * The android image is built from two separate dockerfiles. There is a base image that handles the heavy lifting of dependencies that are infrequently changed while the secondary image extends the base and allows for much quicker incremental builds on code updates. * The javascript image is simple and is relatively quick to build, therefore there is no base image for any react specific javascript dependencies and it is all packaged in a single docker image. * A new `scripts/docker` has been created including some javascript files and shell scripts to aid in the running of the tests * The instrumentation test runner script can be passed various flags to control which tests run since the entire suite takes a significant amount of time to run synchronously * Jen Closes https://github.com/facebook/react-native/pull/11902 Differential Revision: D4609238 Pulled By: ericvicenti fbshipit-source-id: a317f3ac3be898180b009254a9604ca7d579a8b9
This commit is contained in:
parent
387ec8ce37
commit
fe2ff122dc
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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!`);
|
||||
}
|
|
@ -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 $*
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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 <https://www.containership.io/>.
|
||||
|
||||
## Docker Installation
|
||||
|
||||
It is required to have Docker running on your machine in order to build and run the tests in the Dockerfiles.
|
||||
See <https://docs.docker.com/engine/installation/> 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`
|
|
@ -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()
|
11
package.json
11
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"
|
||||
|
|
Loading…
Reference in New Issue