From 1a863f836655878d9b9bce3a7642a541286d0d68 Mon Sep 17 00:00:00 2001 From: Thomas Orozco Date: Sun, 28 Jun 2015 17:00:04 +0200 Subject: [PATCH] Run some functional tests in ci/run_build.sh Using the child subreaper mechanism, we can actually run tests inside the CI environment without depending on Docker. While this does not replace the existing tests, it allows at least some functional coverage within CI. --- .travis.yml | 4 +++ Dockerfile | 4 ++- ci/run_build.sh | 38 +++++++++++++++++++++- dtest.sh | 2 +- test/0001-Add-PR_SET_CHILD_SUBREAPER.patch | 35 ++++++++++++++++++++ test/reaping/stage_1.py | 27 ++++++++++----- test/reaping/stage_2.py | 2 +- test/run_inner_tests.py | 33 +++++++++++++++++++ test/{test.py => run_outer_tests.py} | 0 test/signals/test.py | 2 +- test/subreaper-proxy.py | 19 +++++++++++ tpl/travis.yml.tpl | 4 +++ 12 files changed, 156 insertions(+), 14 deletions(-) create mode 100644 test/0001-Add-PR_SET_CHILD_SUBREAPER.patch create mode 100755 test/run_inner_tests.py rename test/{test.py => run_outer_tests.py} (100%) mode change 100644 => 100755 create mode 100755 test/subreaper-proxy.py diff --git a/.travis.yml b/.travis.yml index 7bc25f9..425b695 100644 --- a/.travis.yml +++ b/.travis.yml @@ -18,6 +18,10 @@ addons: - git - gdb - valgrind + - python-dev + - libcap-dev + - python-pip + - python-virtualenv script: ./ci/run_build.sh diff --git a/Dockerfile b/Dockerfile index cf3eed1..8c688c9 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,7 @@ FROM ubuntu:precise RUN apt-get update \ - && apt-get install --no-install-recommends --yes build-essential git gdb valgrind cmake rpm python3 \ + && apt-get install --no-install-recommends --yes build-essential git gdb valgrind cmake rpm python-dev libcap-dev python-pip python-virtualenv \ && rm -rf /var/lib/apt/lists/* + +RUN pip install psutil diff --git a/ci/run_build.sh b/ci/run_build.sh index ffc4b20..a21011a 100755 --- a/ci/run_build.sh +++ b/ci/run_build.sh @@ -3,13 +3,23 @@ set -o errexit set -o nounset +# Paths : ${SOURCE_DIR:="."} : ${DIST_DIR:="${SOURCE_DIR}/dist"} : ${BUILD_DIR:="/tmp/build"} +# Make those paths absolute, and export them for the Python tests to consume. +export SOURCE_DIR="$(readlink -f "${SOURCE_DIR}")" +export DIST_DIR="$(readlink -f "${DIST_DIR}")" +export BUILD_DIR="$(readlink -f "${BUILD_DIR}")" + + +# Ensure Python output is not buffered (to make tests output clearer) +export PYTHONUNBUFFERED=1 + # Set path to prioritize our utils export REAL_PATH="${PATH}" -export PATH="$(readlink -f "${SOURCE_DIR}")/ci/util:${PATH}" +export PATH="${SOURCE_DIR}/ci/util:${PATH}" # Build cmake -B"${BUILD_DIR}" -H"${SOURCE_DIR}" @@ -46,3 +56,29 @@ for tini in "${BUILD_DIR}/tini" "${BUILD_DIR}/tini-static"; do dpkg --contents "${DIST_DIR}/tini"*deb fi done + +# Create virtual environment to run tests +VENV="${BUILD_DIR}/venv" +virtualenv "${VENV}" + +# Don't use activate because it does not play nice with nounset +export PATH="${VENV}/bin:${PATH}" + +# Install test dependencies + +# We need a patched version because Travis only gives us Ubuntu Precise +# (whose Linux headers don't include PR_SET_CHILD_SUBREAPER), but actually +# runs a newer Linux Kernel (because we're actually in Docker) that has the +# PR_SET_CHILD_SUBREAPER prctl call. +pushd /tmp +pip install python-prctl==1.6.1 --download="." +tar -xvf /tmp/python-prctl-1.6.1.tar.gz +cd python-prctl-1.6.1 +patch -p1 < "${SOURCE_DIR}/test/0001-Add-PR_SET_CHILD_SUBREAPER.patch" +python setup.py install +popd + +pip install psutil + +# Run tests +python "${SOURCE_DIR}/test/run_inner_tests.py" diff --git a/dtest.sh b/dtest.sh index 07fcfbf..00e0894 100755 --- a/dtest.sh +++ b/dtest.sh @@ -6,4 +6,4 @@ IMG="tini" docker build -t "${IMG}" . -python test/test.py "${IMG}" +python test/run_outer_tests.py "${IMG}" diff --git a/test/0001-Add-PR_SET_CHILD_SUBREAPER.patch b/test/0001-Add-PR_SET_CHILD_SUBREAPER.patch new file mode 100644 index 0000000..f2f339b --- /dev/null +++ b/test/0001-Add-PR_SET_CHILD_SUBREAPER.patch @@ -0,0 +1,35 @@ +From b8c6ccd4575837e3901bbdee7b219ef951dc2065 Mon Sep 17 00:00:00 2001 +From: Thomas Orozco +Date: Sun, 28 Jun 2015 15:25:37 +0200 +Subject: [PATCH] Add PR_SET_CHILD_SUBREAPER + +--- + _prctlmodule.c | 12 ++++++++++++ + 1 file changed, 12 insertions(+) + +diff --git a/_prctlmodule.c b/_prctlmodule.c +index 14121c3..19ad141 100644 +--- a/_prctlmodule.c ++++ b/_prctlmodule.c +@@ -15,6 +15,18 @@ + #include + #include + ++/* Our builds run in a Docker environment that has those, but they are ++ * not in the kernel headers. Add them. ++ */ ++ ++#ifndef PR_SET_CHILD_SUBREAPER ++#define PR_SET_CHILD_SUBREAPER 36 ++#endif ++ ++#ifndef PR_GET_CHILD_SUBREAPER ++#define PR_GET_CHILD_SUBREAPER 37 ++#endif ++ + /* New in 2.6.32, but named and implemented inconsistently. The linux + * implementation has two ways of setting the policy to the default, and thus + * needs an extra argument. We ignore the first argument and always call +-- +2.4.3 + diff --git a/test/reaping/stage_1.py b/test/reaping/stage_1.py index d8dba2f..de39b66 100755 --- a/test/reaping/stage_1.py +++ b/test/reaping/stage_1.py @@ -1,25 +1,34 @@ -#!/usr/bin/env python3 +#!/usr/bin/env python +from __future__ import print_function import os import subprocess import time +import psutil -if __name__ == "__main__": + +def main(): p = subprocess.Popen([os.path.join(os.path.dirname(__file__), "stage_2.py")]) p.wait() - # These are the only PIDs that should remain if the system is well-behaved: - # - This process - # - Init - expected_pids = [1, os.getpid()] + # In tests, we assume this process is the direct child of init + this_process = psutil.Process(os.getpid()) + init_process = this_process.parent() + + print("Reaping test: stage_1 is pid{0}, init is pid{1}".format(this_process.pid, init_process.pid)) + + # The only child PID that should persist is this one. + expected_pids = [this_process.pid] print("Expecting pids to remain: {0}".format(", ".join(str(pid) for pid in expected_pids))) while 1: - pids = [pid for pid in os.listdir('/proc') if pid.isdigit()] - print("Has pids: {0}".format(", ".join(pids))) - if set(int(pid) for pid in pids) == set(expected_pids): + pids = [p.pid for p in init_process.children(recursive=True)] + print("Has pids: {0}".format(", ".join(str(pid) for pid in pids))) + if set(pids) == set(expected_pids): break time.sleep(1) +if __name__ == "__main__": + main() diff --git a/test/reaping/stage_2.py b/test/reaping/stage_2.py index f4cc17d..1156afc 100755 --- a/test/reaping/stage_2.py +++ b/test/reaping/stage_2.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python3 +#!/usr/bin/env python from __future__ import print_function import subprocess import os diff --git a/test/run_inner_tests.py b/test/run_inner_tests.py new file mode 100755 index 0000000..5e25a6b --- /dev/null +++ b/test/run_inner_tests.py @@ -0,0 +1,33 @@ +#!/usr/bin/env python +#coding:utf-8 +import os +import sys +import signal +import subprocess + + +def main(): + src = os.environ["SOURCE_DIR"] + build = os.environ["BUILD_DIR"] + + proxy = os.path.join(src, "test", "subreaper-proxy.py") + tini = os.path.join(build, "tini") + + # Run the reaping test + print "Running reaping test" + p = subprocess.Popen([proxy, tini, "--", os.path.join(src, "test", "reaping", "stage_1.py")]) + ret = p.wait() + assert ret == 0, "Reaping test failed!" + + # Run the signals test + for signame in "SIGINT", "SIGTERM": + print "running signal test for: {0}".format(signame) + p = subprocess.Popen([proxy, tini, "--", os.path.join(src, "test", "signals", "test.py")]) + sig = getattr(signal, signame) + p.send_signal(sig) + ret = p.wait() + assert ret == - sig, "Signals test failed!" + + +if __name__ == "__main__": + main() diff --git a/test/test.py b/test/run_outer_tests.py old mode 100644 new mode 100755 similarity index 100% rename from test/test.py rename to test/run_outer_tests.py diff --git a/test/signals/test.py b/test/signals/test.py index ad320ef..bf225f1 100755 --- a/test/signals/test.py +++ b/test/signals/test.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python3 +#!/usr/bin/env python import time if __name__ == "__main__": diff --git a/test/subreaper-proxy.py b/test/subreaper-proxy.py new file mode 100755 index 0000000..a125a44 --- /dev/null +++ b/test/subreaper-proxy.py @@ -0,0 +1,19 @@ +#!/usr/bin/env python +#coding:utf-8 +import os +import sys + +import prctl + + +def main(): + args = sys.argv[1:] + + print "subreaper-proxy: running '%s'" % (" ".join(args)) + + prctl.set_child_subreaper(1) + os.execv(args[0], args) + + +if __name__ == '__main__': + main() diff --git a/tpl/travis.yml.tpl b/tpl/travis.yml.tpl index 2af079c..57df6e5 100644 --- a/tpl/travis.yml.tpl +++ b/tpl/travis.yml.tpl @@ -18,6 +18,10 @@ addons: - git - gdb - valgrind + - python-dev + - libcap-dev + - python-pip + - python-virtualenv script: ./ci/run_build.sh