Compare commits
1 commit
Author | SHA1 | Date | |
---|---|---|---|
|
b2203fd5b1 |
31 changed files with 247 additions and 377 deletions
60
.github/workflows/ci.yaml
vendored
60
.github/workflows/ci.yaml
vendored
|
@ -1,60 +0,0 @@
|
||||||
name: CI
|
|
||||||
on: push
|
|
||||||
jobs:
|
|
||||||
build-and-test:
|
|
||||||
runs-on: ubuntu-20.04
|
|
||||||
strategy:
|
|
||||||
fail-fast: false
|
|
||||||
matrix:
|
|
||||||
include:
|
|
||||||
- arch: amd64
|
|
||||||
manylinux_arch: x86_64
|
|
||||||
docker_image: debian:buster
|
|
||||||
|
|
||||||
- arch: arm64
|
|
||||||
manylinux_arch: aarch64
|
|
||||||
docker_image: arm64v8/debian:buster
|
|
||||||
|
|
||||||
- arch: ppc64le
|
|
||||||
manylinux_arch: ppc64le
|
|
||||||
docker_image: ppc64le/debian:buster
|
|
||||||
|
|
||||||
- arch: s390x
|
|
||||||
manylinux_arch: s390x
|
|
||||||
docker_image: s390x/debian:buster
|
|
||||||
|
|
||||||
env:
|
|
||||||
BASE_IMAGE: ${{ matrix.docker_image }}
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v2
|
|
||||||
|
|
||||||
- name: Set up QEMU
|
|
||||||
id: qemu
|
|
||||||
uses: docker/setup-qemu-action@v1
|
|
||||||
if: ${{ matrix.arch != 'amd64' }}
|
|
||||||
with:
|
|
||||||
image: tonistiigi/binfmt:latest
|
|
||||||
|
|
||||||
- name: Build Docker image
|
|
||||||
run: make docker-image
|
|
||||||
|
|
||||||
- name: Run python tests
|
|
||||||
run: docker run --rm -v $(pwd):/mnt:rw dumb-init-build /mnt/ci/docker-python-test
|
|
||||||
|
|
||||||
- name: Build Debian package
|
|
||||||
run: docker run --init --rm -v $(pwd):/mnt:rw dumb-init-build make -C /mnt builddeb
|
|
||||||
|
|
||||||
- name: Test built Debian package
|
|
||||||
# XXX: This uses the clean base image (not the build one) to make
|
|
||||||
# sure it installs in a clean image without any hidden dependencies.
|
|
||||||
run: docker run --rm -v $(pwd):/mnt:rw ${{ matrix.docker_image }} /mnt/ci/docker-deb-test
|
|
||||||
|
|
||||||
- name: Build wheels
|
|
||||||
run: sudo make python-dists-${{ matrix.manylinux_arch }}
|
|
||||||
|
|
||||||
- name: Upload build artifacts
|
|
||||||
uses: actions/upload-artifact@v2
|
|
||||||
with:
|
|
||||||
name: ${{ matrix.arch }}
|
|
||||||
path: dist
|
|
|
@ -1,7 +1,8 @@
|
||||||
repos:
|
repos:
|
||||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||||
rev: v4.4.0
|
rev: v1.2.3
|
||||||
hooks:
|
hooks:
|
||||||
|
- id: autopep8-wrapper
|
||||||
- id: check-added-large-files
|
- id: check-added-large-files
|
||||||
- id: check-docstring-first
|
- id: check-docstring-first
|
||||||
- id: check-executables-have-shebangs
|
- id: check-executables-have-shebangs
|
||||||
|
@ -11,33 +12,15 @@ repos:
|
||||||
- id: detect-private-key
|
- id: detect-private-key
|
||||||
- id: double-quote-string-fixer
|
- id: double-quote-string-fixer
|
||||||
- id: end-of-file-fixer
|
- id: end-of-file-fixer
|
||||||
|
- id: flake8
|
||||||
- id: name-tests-test
|
- id: name-tests-test
|
||||||
- id: requirements-txt-fixer
|
- id: requirements-txt-fixer
|
||||||
- id: trailing-whitespace
|
- id: trailing-whitespace
|
||||||
- repo: https://github.com/pre-commit/mirrors-autopep8
|
|
||||||
rev: v2.0.0
|
|
||||||
hooks:
|
|
||||||
- id: autopep8
|
|
||||||
- repo: https://github.com/pycqa/flake8
|
|
||||||
rev: 6.0.0
|
|
||||||
hooks:
|
|
||||||
- id: flake8
|
|
||||||
- repo: https://github.com/asottile/reorder_python_imports
|
- repo: https://github.com/asottile/reorder_python_imports
|
||||||
rev: v3.9.0
|
rev: v1.0.1
|
||||||
hooks:
|
hooks:
|
||||||
- id: reorder-python-imports
|
- id: reorder-python-imports
|
||||||
args: ['--py3-plus']
|
|
||||||
- repo: https://github.com/Lucas-C/pre-commit-hooks
|
- repo: https://github.com/Lucas-C/pre-commit-hooks
|
||||||
rev: v1.3.1
|
rev: v1.1.5
|
||||||
hooks:
|
hooks:
|
||||||
- id: remove-tabs
|
- id: remove-tabs
|
||||||
- repo: https://github.com/asottile/pyupgrade
|
|
||||||
rev: v3.3.0
|
|
||||||
hooks:
|
|
||||||
- id: pyupgrade
|
|
||||||
args: ['--py3-plus']
|
|
||||||
- repo: https://github.com/asottile/add-trailing-comma
|
|
||||||
rev: v2.3.0
|
|
||||||
hooks:
|
|
||||||
- id: add-trailing-comma
|
|
||||||
args: ['--py36-plus']
|
|
||||||
|
|
19
.travis.yml
Normal file
19
.travis.yml
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
language: c
|
||||||
|
services:
|
||||||
|
- docker
|
||||||
|
|
||||||
|
matrix:
|
||||||
|
include:
|
||||||
|
- env: ITEST_TARGET=itest_trusty
|
||||||
|
- env: ITEST_TARGET=itest_xenial
|
||||||
|
- env: ITEST_TARGET=itest_bionic
|
||||||
|
- env: ITEST_TARGET=itest_stretch
|
||||||
|
- env: ITEST_TARGET=itest_tox
|
||||||
|
- os: linux-ppc64le
|
||||||
|
env: ITEST_TARGET=itest_stretch
|
||||||
|
|
||||||
|
script:
|
||||||
|
- make "$ITEST_TARGET"
|
||||||
|
|
||||||
|
after_script:
|
||||||
|
- ci/artifact-upload
|
|
@ -29,11 +29,11 @@ The process to release a new version is:
|
||||||
4. Commit the changes and tag the commit like `v1.0.0`.
|
4. Commit the changes and tag the commit like `v1.0.0`.
|
||||||
5. `git push --tags origin master`
|
5. `git push --tags origin master`
|
||||||
6. Wait for Travis to run, then find and download the binary and Debian
|
6. Wait for Travis to run, then find and download the binary and Debian
|
||||||
packages for all architectures; there will be links printed at the
|
packages for both amd64 and ppc64el; there will be links printed at the end
|
||||||
end of the Travis output. Put these into your `dist` directory.
|
of the Travis output. Put these into your `dist` directory.
|
||||||
7. Run `make release`
|
7. Run `make release`
|
||||||
8. Run `twine upload --skip-existing dist/*.tar.gz dist/*.whl` to upload the
|
8. Run `twine upload --skip-existing dist/*.tar.gz dist/*.whl` to upload the
|
||||||
new version to PyPI
|
new version to PyPI
|
||||||
9. Upload the resulting Debian packages, binaries, and sha256sums file (all
|
9. Upload the resulting Debian package, binary (inside the `dist` directory),
|
||||||
inside the `dist` directory) to a new [GitHub
|
and sha256sums file to a new [GitHub
|
||||||
release](https://github.com/Yelp/dumb-init/releases)
|
release](https://github.com/Yelp/dumb-init/releases)
|
||||||
|
|
30
Dockerfile
30
Dockerfile
|
@ -1,28 +1,24 @@
|
||||||
ARG BASE_IMAGE=debian:buster
|
FROM debian:stretch
|
||||||
FROM $BASE_IMAGE
|
|
||||||
|
|
||||||
LABEL maintainer="Chris Kuehl <ckuehl@yelp.com>"
|
LABEL maintainer="Chris Kuehl <ckuehl@yelp.com>"
|
||||||
|
|
||||||
|
# The default mirrors are too flaky to run reliably in CI.
|
||||||
|
RUN sed -E \
|
||||||
|
'/security\.debian/! s@http://[^/]+/@http://mirrors.kernel.org/@' \
|
||||||
|
-i /etc/apt/sources.list
|
||||||
|
|
||||||
# Install the bare minimum dependencies necessary for working with Debian
|
# Install the bare minimum dependencies necessary for working with Debian
|
||||||
# packages. Build dependencies should be added under "Build-Depends" inside
|
# packages. Build dependencies should be added under "Build-Depends" inside
|
||||||
# debian/control instead.
|
# debian/control instead.
|
||||||
RUN : \
|
RUN DEBIAN_FRONTEND=noninteractive apt-get update && \
|
||||||
&& apt-get update \
|
apt-get install -y --no-install-recommends \
|
||||||
&& DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
|
|
||||||
build-essential \
|
build-essential \
|
||||||
devscripts \
|
devscripts \
|
||||||
equivs \
|
equivs \
|
||||||
lintian \
|
lintian \
|
||||||
python3-distutils \
|
&& rm -rf /var/lib/apt/lists/* && apt-get clean
|
||||||
python3-setuptools \
|
WORKDIR /mnt
|
||||||
python3-pip \
|
|
||||||
&& apt-get clean \
|
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
|
||||||
WORKDIR /tmp/mnt
|
|
||||||
|
|
||||||
COPY debian/control /control
|
ENTRYPOINT apt-get update && \
|
||||||
RUN : \
|
mk-build-deps -i --tool 'apt-get --no-install-recommends -y' && \
|
||||||
&& apt-get update \
|
make builddeb
|
||||||
&& mk-build-deps --install --tool 'apt-get -y --no-install-recommends' /control \
|
|
||||||
&& apt-get clean \
|
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
|
||||||
|
|
41
Makefile
41
Makefile
|
@ -26,21 +26,18 @@ clean-tox:
|
||||||
.PHONY: release
|
.PHONY: release
|
||||||
release: python-dists
|
release: python-dists
|
||||||
cd dist && \
|
cd dist && \
|
||||||
sha256sum --binary dumb-init_$(VERSION)_amd64.deb dumb-init_$(VERSION)_x86_64 dumb-init_$(VERSION)_ppc64el.deb dumb-init_$(VERSION)_ppc64le dumb-init_$(VERSION)_s390x.deb dumb-init_$(VERSION)_s390x dumb-init_$(VERSION)_arm64.deb dumb-init_$(VERSION)_aarch64 \
|
sha256sum --binary dumb-init_$(VERSION)_amd64.deb dumb-init_$(VERSION)_amd64 dumb-init_$(VERSION)_ppc64el.deb dumb-init_$(VERSION)_ppc64el \
|
||||||
> sha256sums
|
> sha256sums
|
||||||
|
|
||||||
.PHONY: python-dists
|
.PHONY: python-dists
|
||||||
python-dists: python-dists-x86_64 python-dists-aarch64 python-dists-ppc64le python-dists-s390x
|
python-dists: VERSION.h
|
||||||
|
|
||||||
.PHONY: python-dists-%
|
|
||||||
python-dists-%: VERSION.h
|
|
||||||
python setup.py sdist
|
python setup.py sdist
|
||||||
docker run \
|
docker run \
|
||||||
--user $$(id -u):$$(id -g) \
|
--user $$(id -u):$$(id -g) \
|
||||||
-v `pwd`/dist:/dist:rw \
|
-v $(PWD)/dist:/dist:rw \
|
||||||
quay.io/pypa/manylinux2014_$*:latest \
|
quay.io/pypa/manylinux1_x86_64:latest \
|
||||||
bash -exc ' \
|
bash -exc ' \
|
||||||
/opt/python/cp38-cp38/bin/pip wheel --wheel-dir /tmp /dist/*.tar.gz && \
|
/opt/python/cp35-cp35m/bin/pip wheel --wheel-dir /tmp /dist/*.tar.gz && \
|
||||||
auditwheel repair --wheel-dir /dist /tmp/*.whl --wheel-dir /dist \
|
auditwheel repair --wheel-dir /dist /tmp/*.whl --wheel-dir /dist \
|
||||||
'
|
'
|
||||||
|
|
||||||
|
@ -52,16 +49,16 @@ builddeb:
|
||||||
# Extract the built binary from the Debian package
|
# Extract the built binary from the Debian package
|
||||||
dpkg-deb --fsys-tarfile dist/dumb-init_$(VERSION)_$(shell dpkg --print-architecture).deb | \
|
dpkg-deb --fsys-tarfile dist/dumb-init_$(VERSION)_$(shell dpkg --print-architecture).deb | \
|
||||||
tar -C dist --strip=3 -xvf - ./usr/bin/dumb-init
|
tar -C dist --strip=3 -xvf - ./usr/bin/dumb-init
|
||||||
mv dist/dumb-init dist/dumb-init_$(VERSION)_$(shell uname -m)
|
mv dist/dumb-init dist/dumb-init_$(VERSION)_$(shell dpkg --print-architecture)
|
||||||
|
|
||||||
.PHONY: builddeb-docker
|
.PHONY: builddeb-docker
|
||||||
builddeb-docker: docker-image
|
builddeb-docker: docker-image
|
||||||
mkdir -p dist
|
mkdir -p dist
|
||||||
docker run --init --user $$(id -u):$$(id -g) -v $(PWD):/tmp/mnt dumb-init-build make builddeb
|
docker run -v $(PWD):/mnt dumb-init-build
|
||||||
|
|
||||||
.PHONY: docker-image
|
.PHONY: docker-image
|
||||||
docker-image:
|
docker-image:
|
||||||
docker build $(if $(BASE_IMAGE),--build-arg BASE_IMAGE=$(BASE_IMAGE)) -t dumb-init-build .
|
docker build -t dumb-init-build .
|
||||||
|
|
||||||
.PHONY: test
|
.PHONY: test
|
||||||
test:
|
test:
|
||||||
|
@ -71,3 +68,25 @@ test:
|
||||||
.PHONY: install-hooks
|
.PHONY: install-hooks
|
||||||
install-hooks:
|
install-hooks:
|
||||||
tox -e pre-commit -- install -f --install-hooks
|
tox -e pre-commit -- install -f --install-hooks
|
||||||
|
|
||||||
|
ITEST_TARGETS = itest_trusty itest_xenial itest_bionic itest_stretch
|
||||||
|
|
||||||
|
.PHONY: itest $(ITEST_TARGETS)
|
||||||
|
itest: $(ITEST_TARGETS)
|
||||||
|
|
||||||
|
itest_trusty: _itest-ubuntu-trusty
|
||||||
|
itest_xenial: _itest-ubuntu-xenial
|
||||||
|
itest_bionic: _itest-ubuntu-bionic
|
||||||
|
itest_stretch: _itest-debian-stretch
|
||||||
|
|
||||||
|
itest_tox:
|
||||||
|
$(DOCKER_RUN_TEST) ubuntu:bionic /mnt/ci/docker-tox-test
|
||||||
|
|
||||||
|
_itest-%: _itest_deb-% _itest_python-%
|
||||||
|
@true
|
||||||
|
|
||||||
|
_itest_python-%:
|
||||||
|
$(DOCKER_RUN_TEST) $(shell sed 's/-/:/' <<< "$*") /mnt/ci/docker-python-test
|
||||||
|
|
||||||
|
_itest_deb-%: builddeb-docker
|
||||||
|
$(DOCKER_RUN_TEST) $(shell sed 's/-/:/' <<< "$*") /mnt/ci/docker-deb-test
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
dumb-init
|
dumb-init
|
||||||
========
|
========
|
||||||
|
|
||||||
|
[![Travis CI](https://travis-ci.org/Yelp/dumb-init.svg?branch=master)](https://travis-ci.org/Yelp/dumb-init/)
|
||||||
[![PyPI version](https://badge.fury.io/py/dumb-init.svg)](https://pypi.python.org/pypi/dumb-init)
|
[![PyPI version](https://badge.fury.io/py/dumb-init.svg)](https://pypi.python.org/pypi/dumb-init)
|
||||||
|
|
||||||
|
|
||||||
|
@ -173,7 +174,7 @@ If you don't have an internal apt server, you can use `dpkg -i` to install the
|
||||||
One possibility is with the following commands in your Dockerfile:
|
One possibility is with the following commands in your Dockerfile:
|
||||||
|
|
||||||
```Dockerfile
|
```Dockerfile
|
||||||
RUN wget https://github.com/Yelp/dumb-init/releases/download/v1.2.5/dumb-init_1.2.5_amd64.deb
|
RUN wget https://github.com/Yelp/dumb-init/releases/download/v1.2.1/dumb-init_1.2.1_amd64.deb
|
||||||
RUN dpkg -i dumb-init_*.deb
|
RUN dpkg -i dumb-init_*.deb
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -184,7 +185,7 @@ Since dumb-init is released as a statically-linked binary, you can usually just
|
||||||
plop it into your images. Here's an example of doing that in a Dockerfile:
|
plop it into your images. Here's an example of doing that in a Dockerfile:
|
||||||
|
|
||||||
```Dockerfile
|
```Dockerfile
|
||||||
RUN wget -O /usr/local/bin/dumb-init https://github.com/Yelp/dumb-init/releases/download/v1.2.5/dumb-init_1.2.5_x86_64
|
RUN wget -O /usr/local/bin/dumb-init https://github.com/Yelp/dumb-init/releases/download/v1.2.1/dumb-init_1.2.1_amd64
|
||||||
RUN chmod +x /usr/local/bin/dumb-init
|
RUN chmod +x /usr/local/bin/dumb-init
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
2
VERSION
2
VERSION
|
@ -1 +1 @@
|
||||||
1.2.5
|
1.2.1
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
// THIS FILE IS AUTOMATICALLY GENERATED
|
// THIS FILE IS AUTOMATICALLY GENERATED
|
||||||
// Run `make VERSION.h` to update it after modifying VERSION.
|
// Run `make VERSION.h` to update it after modifying VERSION.
|
||||||
unsigned char VERSION[] = {
|
unsigned char VERSION[] = {
|
||||||
0x31, 0x2e, 0x32, 0x2e, 0x35, 0x0a
|
0x31, 0x2e, 0x32, 0x2e, 0x31, 0x0a
|
||||||
};
|
};
|
||||||
unsigned int VERSION_len = 6;
|
unsigned int VERSION_len = 6;
|
||||||
|
|
18
ci/docker
Normal file
18
ci/docker
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
# The default mirrors are too flaky to run reliably in CI.
|
||||||
|
sed -E \
|
||||||
|
'/security\.debian/! s@http://[^/]+/@http://mirrors.kernel.org/@' \
|
||||||
|
-i /etc/apt/sources.list
|
||||||
|
|
||||||
|
apt-get update
|
||||||
|
apt-get install -y --no-install-recommends \
|
||||||
|
build-essential \
|
||||||
|
procps \
|
||||||
|
python \
|
||||||
|
python-dev \
|
||||||
|
python-pip \
|
||||||
|
python-setuptools
|
||||||
|
|
||||||
|
cp -r /mnt/ /test
|
||||||
|
cd /test
|
||||||
|
|
||||||
|
# vim: ft=sh
|
|
@ -1,12 +1,10 @@
|
||||||
#!/bin/bash -eux
|
#!/bin/bash -eux
|
||||||
set -o pipefail
|
set -o pipefail
|
||||||
|
|
||||||
apt-get update
|
. /mnt/ci/docker
|
||||||
apt-get -y --no-install-recommends install python3-pip procps
|
|
||||||
|
|
||||||
cd /mnt
|
|
||||||
dpkg -i dist/*.deb
|
dpkg -i dist/*.deb
|
||||||
pip3 install -r requirements-dev.txt
|
pip install -r requirements-dev.txt
|
||||||
pytest tests/
|
py.test tests/
|
||||||
|
|
||||||
exec dumb-init /mnt/tests/test-zombies
|
exec dumb-init /mnt/tests/test-zombies
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
#!/bin/bash -eux
|
#!/bin/bash -eux
|
||||||
set -euo pipefail
|
set -o pipefail
|
||||||
|
|
||||||
cd /mnt
|
. /mnt/ci/docker
|
||||||
|
|
||||||
python3 setup.py clean
|
python setup.py clean
|
||||||
python3 setup.py sdist
|
python setup.py sdist
|
||||||
pip3 install -vv dist/*.tar.gz
|
pip install -vv dist/*.tar.gz
|
||||||
pip3 install -r requirements-dev.txt
|
pip install -r requirements-dev.txt
|
||||||
pytest-3 -vv tests/
|
py.test tests/
|
||||||
|
|
||||||
exec dumb-init /mnt/tests/test-zombies
|
exec dumb-init /mnt/tests/test-zombies \
|
||||||
|
|
13
ci/docker-tox-test
Executable file
13
ci/docker-tox-test
Executable file
|
@ -0,0 +1,13 @@
|
||||||
|
#!/bin/bash -eux
|
||||||
|
set -o pipefail
|
||||||
|
|
||||||
|
. /mnt/ci/docker
|
||||||
|
|
||||||
|
apt-get update
|
||||||
|
apt-get install -y --no-install-recommends \
|
||||||
|
git \
|
||||||
|
python2.7-dev \
|
||||||
|
python3.6-dev \
|
||||||
|
tox
|
||||||
|
|
||||||
|
tox
|
43
debian/changelog
vendored
43
debian/changelog
vendored
|
@ -1,46 +1,3 @@
|
||||||
dumb-init (1.2.5) unstable; urgency=medium
|
|
||||||
|
|
||||||
* Change the working directory in the parent process to "/" after forking.
|
|
||||||
|
|
||||||
https://github.com/Yelp/dumb-init/pull/210
|
|
||||||
|
|
||||||
Thanks to @Villemoes for the patch!
|
|
||||||
|
|
||||||
-- Chris Kuehl <ckuehl@yelp.com> Thu, 10 Dec 2020 10:54:47 -0800
|
|
||||||
|
|
||||||
dumb-init (1.2.4) unstable; urgency=medium
|
|
||||||
|
|
||||||
* Actually fix the bug that can cause `--help` or `--version` to crash in
|
|
||||||
some scenarios.
|
|
||||||
|
|
||||||
https://github.com/Yelp/dumb-init/pull/215
|
|
||||||
|
|
||||||
Thanks to @suve for the patch!
|
|
||||||
|
|
||||||
-- Chris Kuehl <ckuehl@yelp.com> Mon, 07 Dec 2020 11:58:06 -0800
|
|
||||||
|
|
||||||
dumb-init (1.2.3) unstable; urgency=medium
|
|
||||||
|
|
||||||
* Fix a bug that can cause `--help` or `--version` to crash in some
|
|
||||||
scenarios.
|
|
||||||
|
|
||||||
https://github.com/Yelp/dumb-init/pull/213
|
|
||||||
|
|
||||||
Thanks to @suve for the patch!
|
|
||||||
|
|
||||||
-- Chris Kuehl <ckuehl@yelp.com> Wed, 02 Dec 2020 10:43:02 -0800
|
|
||||||
|
|
||||||
dumb-init (1.2.2) unstable; urgency=medium
|
|
||||||
|
|
||||||
* Fix a race condition which can cause the child to receive SIGHUP and
|
|
||||||
SIGCONT very shortly after start (#174).
|
|
||||||
|
|
||||||
In general this was very rare, but some environments (especially some
|
|
||||||
container and virtualization environments) appear to encounter it at a
|
|
||||||
much higher rate, possibly due to scheduler quirks.
|
|
||||||
|
|
||||||
-- Chris Kuehl <ckuehl@yelp.com> Wed, 01 Aug 2018 16:36:22 -0700
|
|
||||||
|
|
||||||
dumb-init (1.2.1) unstable; urgency=medium
|
dumb-init (1.2.1) unstable; urgency=medium
|
||||||
|
|
||||||
* Fix verbose debug logging for ignored signals.
|
* Fix verbose debug logging for ignored signals.
|
||||||
|
|
6
debian/control
vendored
6
debian/control
vendored
|
@ -2,14 +2,16 @@ Source: dumb-init
|
||||||
Section: utils
|
Section: utils
|
||||||
Priority: extra
|
Priority: extra
|
||||||
Maintainer: Chris Kuehl <ckuehl@yelp.com>
|
Maintainer: Chris Kuehl <ckuehl@yelp.com>
|
||||||
|
Uploaders: Kent Wills <rkwills@yelp.com>
|
||||||
Build-Depends:
|
Build-Depends:
|
||||||
debhelper (>= 9),
|
debhelper (>= 9),
|
||||||
help2man,
|
help2man,
|
||||||
musl-tools,
|
musl-tools,
|
||||||
## Tests:
|
## Tests:
|
||||||
procps,
|
procps,
|
||||||
python3,
|
python,
|
||||||
python3-pytest,
|
python-mock,
|
||||||
|
python-pytest,
|
||||||
Standards-Version: 3.9.7
|
Standards-Version: 3.9.7
|
||||||
Homepage: https://github.com/Yelp/dumb-init
|
Homepage: https://github.com/Yelp/dumb-init
|
||||||
Vcs-Browser: https://github.com/Yelp/dumb-init
|
Vcs-Browser: https://github.com/Yelp/dumb-init
|
||||||
|
|
2
debian/rules
vendored
2
debian/rules
vendored
|
@ -31,4 +31,4 @@ override_dh_builddeb:
|
||||||
override_dh_auto_test:
|
override_dh_auto_test:
|
||||||
find . -name '*.pyc' -delete
|
find . -name '*.pyc' -delete
|
||||||
find . -name '__pycache__' -delete
|
find . -name '__pycache__' -delete
|
||||||
PATH=.:$$PATH timeout --signal=KILL 60 pytest-3 -vv tests/
|
PATH=.:$$PATH timeout --signal=KILL 60 py.test -vv tests/
|
||||||
|
|
19
dumb-init.c
19
dumb-init.c
|
@ -94,7 +94,7 @@ void handle_signal(int signum) {
|
||||||
DEBUG("Received signal %d.\n", signum);
|
DEBUG("Received signal %d.\n", signum);
|
||||||
|
|
||||||
if (signal_temporary_ignores[signum] == 1) {
|
if (signal_temporary_ignores[signum] == 1) {
|
||||||
DEBUG("Ignoring tty hand-off signal %d.\n", signum);
|
DEBUG("Ignoring signal %d during its first receive.\n", signum);
|
||||||
signal_temporary_ignores[signum] = 0;
|
signal_temporary_ignores[signum] = 0;
|
||||||
} else if (signum == SIGCHLD) {
|
} else if (signum == SIGCHLD) {
|
||||||
int status, exit_status;
|
int status, exit_status;
|
||||||
|
@ -126,7 +126,7 @@ void handle_signal(int signum) {
|
||||||
|
|
||||||
void print_help(char *argv[]) {
|
void print_help(char *argv[]) {
|
||||||
fprintf(stderr,
|
fprintf(stderr,
|
||||||
"dumb-init v%.*s"
|
"dumb-init v%s"
|
||||||
"Usage: %s [option] command [[arg] ...]\n"
|
"Usage: %s [option] command [[arg] ...]\n"
|
||||||
"\n"
|
"\n"
|
||||||
"dumb-init is a simple process supervisor that forwards signals to children.\n"
|
"dumb-init is a simple process supervisor that forwards signals to children.\n"
|
||||||
|
@ -144,7 +144,7 @@ void print_help(char *argv[]) {
|
||||||
" -V, --version Print the current version and exit.\n"
|
" -V, --version Print the current version and exit.\n"
|
||||||
"\n"
|
"\n"
|
||||||
"Full help is available online at https://github.com/Yelp/dumb-init\n",
|
"Full help is available online at https://github.com/Yelp/dumb-init\n",
|
||||||
VERSION_len, VERSION,
|
VERSION,
|
||||||
argv[0]
|
argv[0]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -175,9 +175,8 @@ void parse_rewrite_signum(char *arg) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void set_rewrite_to_sigstop_if_not_defined(int signum) {
|
void set_rewrite_to_sigstop_if_not_defined(int signum) {
|
||||||
if (signal_rewrite[signum] == -1) {
|
if (signal_rewrite[signum] == -1)
|
||||||
signal_rewrite[signum] = SIGSTOP;
|
signal_rewrite[signum] = SIGSTOP;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
char **parse_command(int argc, char *argv[]) {
|
char **parse_command(int argc, char *argv[]) {
|
||||||
|
@ -199,7 +198,7 @@ char **parse_command(int argc, char *argv[]) {
|
||||||
debug = 1;
|
debug = 1;
|
||||||
break;
|
break;
|
||||||
case 'V':
|
case 'V':
|
||||||
fprintf(stderr, "dumb-init v%.*s", VERSION_len, VERSION);
|
fprintf(stderr, "dumb-init v%s", VERSION);
|
||||||
exit(0);
|
exit(0);
|
||||||
case 'c':
|
case 'c':
|
||||||
use_setsid = 0;
|
use_setsid = 0;
|
||||||
|
@ -256,9 +255,8 @@ int main(int argc, char *argv[]) {
|
||||||
sigprocmask(SIG_BLOCK, &all_signals, NULL);
|
sigprocmask(SIG_BLOCK, &all_signals, NULL);
|
||||||
|
|
||||||
int i = 0;
|
int i = 0;
|
||||||
for (i = 1; i <= MAXSIG; i++) {
|
for (i = 1; i <= MAXSIG; i++)
|
||||||
signal(i, dummy);
|
signal(i, dummy);
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Detach dumb-init from controlling tty, so that the child's session can
|
* Detach dumb-init from controlling tty, so that the child's session can
|
||||||
|
@ -326,11 +324,6 @@ int main(int argc, char *argv[]) {
|
||||||
} else {
|
} else {
|
||||||
/* parent */
|
/* parent */
|
||||||
DEBUG("Child spawned with PID %d.\n", child_pid);
|
DEBUG("Child spawned with PID %d.\n", child_pid);
|
||||||
if (chdir("/") == -1) {
|
|
||||||
DEBUG("Unable to chdir(\"/\") (errno=%d %s)\n",
|
|
||||||
errno,
|
|
||||||
strerror(errno));
|
|
||||||
}
|
|
||||||
for (;;) {
|
for (;;) {
|
||||||
int signum;
|
int signum;
|
||||||
sigwait(&all_signals, &signum);
|
sigwait(&all_signals, &signum);
|
||||||
|
|
|
@ -1,2 +1,2 @@
|
||||||
[pytest]
|
[pytest]
|
||||||
timeout = 20
|
timeout = 5
|
||||||
|
|
|
@ -1,6 +1,4 @@
|
||||||
|
mock
|
||||||
pre-commit>=0.5.0
|
pre-commit>=0.5.0
|
||||||
pytest
|
pytest
|
||||||
# TODO: This pin is to work around an issue where the system pytest is too old.
|
pytest-timeout
|
||||||
# We should fix this by not depending on the system pytest/python packages at
|
|
||||||
# some point.
|
|
||||||
pytest-timeout<2.0.0
|
|
||||||
|
|
8
setup.py
8
setup.py
|
@ -1,9 +1,11 @@
|
||||||
|
from __future__ import print_function
|
||||||
|
|
||||||
import os.path
|
import os.path
|
||||||
import subprocess
|
import subprocess
|
||||||
import tempfile
|
import tempfile
|
||||||
|
|
||||||
from distutils.command.build import build as orig_build
|
from distutils.command.build import build as orig_build
|
||||||
from distutils.core import Command
|
from distutils.core import Command
|
||||||
|
|
||||||
from setuptools import Distribution
|
from setuptools import Distribution
|
||||||
from setuptools import Extension
|
from setuptools import Extension
|
||||||
from setuptools import setup
|
from setuptools import setup
|
||||||
|
@ -56,8 +58,7 @@ class install_cexe(Command):
|
||||||
# this initializes attributes based on other commands' attributes
|
# this initializes attributes based on other commands' attributes
|
||||||
self.set_undefined_options('build', ('build_scripts', 'build_dir'))
|
self.set_undefined_options('build', ('build_scripts', 'build_dir'))
|
||||||
self.set_undefined_options(
|
self.set_undefined_options(
|
||||||
'install', ('install_scripts', 'install_dir'),
|
'install', ('install_scripts', 'install_dir'))
|
||||||
)
|
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
|
|
||||||
|
@ -123,7 +124,6 @@ setup(
|
||||||
author='Yelp',
|
author='Yelp',
|
||||||
url='https://github.com/Yelp/dumb-init/',
|
url='https://github.com/Yelp/dumb-init/',
|
||||||
platforms='linux',
|
platforms='linux',
|
||||||
packages=[],
|
|
||||||
c_executables=[Extension('dumb-init', ['dumb-init.c'])],
|
c_executables=[Extension('dumb-init', ['dumb-init.c'])],
|
||||||
cmdclass={
|
cmdclass={
|
||||||
'bdist_wheel': bdist_wheel,
|
'bdist_wheel': bdist_wheel,
|
||||||
|
|
|
@ -8,6 +8,9 @@ from contextlib import contextmanager
|
||||||
from subprocess import PIPE
|
from subprocess import PIPE
|
||||||
from subprocess import Popen
|
from subprocess import Popen
|
||||||
|
|
||||||
|
from py._path.local import LocalPath
|
||||||
|
|
||||||
|
|
||||||
# these signals cause dumb-init to suspend itself
|
# these signals cause dumb-init to suspend itself
|
||||||
SUSPEND_SIGNALS = frozenset([
|
SUSPEND_SIGNALS = frozenset([
|
||||||
signal.SIGTSTP,
|
signal.SIGTSTP,
|
||||||
|
@ -17,8 +20,8 @@ SUSPEND_SIGNALS = frozenset([
|
||||||
|
|
||||||
NORMAL_SIGNALS = frozenset(
|
NORMAL_SIGNALS = frozenset(
|
||||||
set(range(1, 32)) -
|
set(range(1, 32)) -
|
||||||
{signal.SIGKILL, signal.SIGSTOP, signal.SIGCHLD} -
|
set([signal.SIGKILL, signal.SIGSTOP, signal.SIGCHLD]) -
|
||||||
SUSPEND_SIGNALS,
|
SUSPEND_SIGNALS
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -34,7 +37,7 @@ def print_signals(args=()):
|
||||||
stdout=PIPE,
|
stdout=PIPE,
|
||||||
)
|
)
|
||||||
line = proc.stdout.readline()
|
line = proc.stdout.readline()
|
||||||
m = re.match(b'^ready \\(pid: ([0-9]+)\\)\n$', line)
|
m = re.match(b'^ready \(pid: ([0-9]+)\)\n$', line)
|
||||||
assert m, line
|
assert m, line
|
||||||
|
|
||||||
yield proc, m.group(1).decode('ascii')
|
yield proc, m.group(1).decode('ascii')
|
||||||
|
@ -46,25 +49,15 @@ def print_signals(args=()):
|
||||||
def child_pids(pid):
|
def child_pids(pid):
|
||||||
"""Return a list of direct child PIDs for the given PID."""
|
"""Return a list of direct child PIDs for the given PID."""
|
||||||
children = set()
|
children = set()
|
||||||
for p in os.listdir('/proc'):
|
for p in LocalPath('/proc').listdir():
|
||||||
try:
|
try:
|
||||||
with open(os.path.join('/proc', p, 'stat')) as f:
|
stat = open(p.join('stat').strpath).read()
|
||||||
stat = f.read()
|
m = re.match('^\d+ \(.+?\) [a-zA-Z] (\d+) ', stat)
|
||||||
m = re.match(
|
|
||||||
r'^\d+ \(.+?\) '
|
|
||||||
# This field, state, is normally a single letter, but can be
|
|
||||||
# "0" if there are some unusual security settings that prevent
|
|
||||||
# reading the process state (happens under GitHub Actions with
|
|
||||||
# QEMU for some reason).
|
|
||||||
'[0a-zA-Z] '
|
|
||||||
r'(\d+) ',
|
|
||||||
stat,
|
|
||||||
)
|
|
||||||
assert m, stat
|
assert m, stat
|
||||||
ppid = int(m.group(1))
|
ppid = int(m.group(1))
|
||||||
if ppid == pid:
|
if ppid == pid:
|
||||||
children.add(int(p))
|
children.add(int(p.basename))
|
||||||
except OSError:
|
except IOError:
|
||||||
# Happens when the process exits after listing it, or between
|
# Happens when the process exits after listing it, or between
|
||||||
# opening stat and reading it.
|
# opening stat and reading it.
|
||||||
pass
|
pass
|
||||||
|
@ -74,23 +67,22 @@ def child_pids(pid):
|
||||||
def pid_tree(pid):
|
def pid_tree(pid):
|
||||||
"""Return a list of all descendant PIDs for the given PID."""
|
"""Return a list of all descendant PIDs for the given PID."""
|
||||||
children = child_pids(pid)
|
children = child_pids(pid)
|
||||||
return {
|
return set(
|
||||||
pid
|
pid
|
||||||
for child in children
|
for child in children
|
||||||
for pid in pid_tree(child)
|
for pid in pid_tree(child)
|
||||||
} | children
|
) | children
|
||||||
|
|
||||||
|
|
||||||
def is_alive(pid):
|
def is_alive(pid):
|
||||||
"""Return whether a process is running with the given PID."""
|
"""Return whether a process is running with the given PID."""
|
||||||
return os.path.isdir(os.path.join('/proc', str(pid)))
|
return LocalPath('/proc').join(str(pid)).isdir()
|
||||||
|
|
||||||
|
|
||||||
def process_state(pid):
|
def process_state(pid):
|
||||||
"""Return a process' state, such as "stopped" or "running"."""
|
"""Return a process' state, such as "stopped" or "running"."""
|
||||||
with open(os.path.join('/proc', str(pid), 'status')) as f:
|
status = LocalPath('/proc').join(str(pid), 'status').read()
|
||||||
status = f.read()
|
m = re.search('^State:\s+[A-Z] \(([a-z]+)\)$', status, re.MULTILINE)
|
||||||
m = re.search(r'^State:\s+[A-Z] \(([a-z]+)\)$', status, re.MULTILINE)
|
|
||||||
return m.group(1)
|
return m.group(1)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -4,6 +4,8 @@
|
||||||
Since all signals are printed and otherwise ignored, you'll need to send
|
Since all signals are printed and otherwise ignored, you'll need to send
|
||||||
SIGKILL (kill -9) to this process to actually end it.
|
SIGKILL (kill -9) to this process to actually end it.
|
||||||
"""
|
"""
|
||||||
|
from __future__ import print_function
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import signal
|
import signal
|
||||||
import sys
|
import sys
|
||||||
|
@ -11,7 +13,7 @@ import time
|
||||||
|
|
||||||
|
|
||||||
CATCHABLE_SIGNALS = frozenset(
|
CATCHABLE_SIGNALS = frozenset(
|
||||||
set(range(1, 32)) - {signal.SIGKILL, signal.SIGSTOP, signal.SIGCHLD},
|
set(range(1, 32)) - set([signal.SIGKILL, signal.SIGSTOP, signal.SIGCHLD])
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -20,7 +22,7 @@ last_signal = None
|
||||||
|
|
||||||
|
|
||||||
def unbuffered_print(line):
|
def unbuffered_print(line):
|
||||||
sys.stdout.write('{}\n'.format(line))
|
sys.stdout.write('{0}\n'.format(line))
|
||||||
sys.stdout.flush()
|
sys.stdout.flush()
|
||||||
|
|
||||||
|
|
||||||
|
@ -32,7 +34,7 @@ if __name__ == '__main__':
|
||||||
for signum in CATCHABLE_SIGNALS:
|
for signum in CATCHABLE_SIGNALS:
|
||||||
signal.signal(signum, print_signal)
|
signal.signal(signum, print_signal)
|
||||||
|
|
||||||
unbuffered_print('ready (pid: {})'.format(os.getpid()))
|
unbuffered_print('ready (pid: {0})'.format(os.getpid()))
|
||||||
|
|
||||||
# loop forever just printing signals
|
# loop forever just printing signals
|
||||||
while True:
|
while True:
|
||||||
|
|
|
@ -17,7 +17,7 @@ def spawn_and_kill_pipeline():
|
||||||
proc = Popen((
|
proc = Popen((
|
||||||
'dumb-init',
|
'dumb-init',
|
||||||
'sh', '-c',
|
'sh', '-c',
|
||||||
"yes 'oh, hi' | tail & yes error | tail >&2",
|
"yes 'oh, hi' | tail & yes error | tail >&2"
|
||||||
))
|
))
|
||||||
|
|
||||||
def assert_living_pids():
|
def assert_living_pids():
|
||||||
|
@ -32,7 +32,7 @@ def spawn_and_kill_pipeline():
|
||||||
|
|
||||||
|
|
||||||
def living_pids(pids):
|
def living_pids(pids):
|
||||||
return {pid for pid in pids if is_alive(pid)}
|
return set(pid for pid in pids if is_alive(pid))
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.usefixtures('both_debug_modes', 'setsid_enabled')
|
@pytest.mark.usefixtures('both_debug_modes', 'setsid_enabled')
|
||||||
|
@ -80,7 +80,7 @@ def spawn_process_which_dies_with_children():
|
||||||
# we need to sleep before the shell exits, or dumb-init might send
|
# we need to sleep before the shell exits, or dumb-init might send
|
||||||
# TERM to print_signals before it has had time to register custom
|
# TERM to print_signals before it has had time to register custom
|
||||||
# signal handlers
|
# signal handlers
|
||||||
'{python} -m testing.print_signals & sleep 1'.format(
|
'{python} -m testing.print_signals & sleep 0.1'.format(
|
||||||
python=sys.executable,
|
python=sys.executable,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -91,7 +91,7 @@ def spawn_process_which_dies_with_children():
|
||||||
|
|
||||||
# read a line from print_signals, figure out its pid
|
# read a line from print_signals, figure out its pid
|
||||||
line = proc.stdout.readline()
|
line = proc.stdout.readline()
|
||||||
match = re.match(b'ready \\(pid: ([0-9]+)\\)\n', line)
|
match = re.match(b'ready \(pid: ([0-9]+)\)\n', line)
|
||||||
assert match, line
|
assert match, line
|
||||||
child_pid = int(match.group(1))
|
child_pid = int(match.group(1))
|
||||||
|
|
||||||
|
@ -129,14 +129,12 @@ def test_processes_dont_receive_term_on_exit_if_no_setsid():
|
||||||
os.kill(child_pid, signal.SIGKILL)
|
os.kill(child_pid, signal.SIGKILL)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize('args', [
|
||||||
'args', [
|
('/doesnotexist',),
|
||||||
('/doesnotexist',),
|
('--', '/doesnotexist'),
|
||||||
('--', '/doesnotexist'),
|
('-c', '/doesnotexist'),
|
||||||
('-c', '/doesnotexist'),
|
('--single-child', '--', '/doesnotexist'),
|
||||||
('--single-child', '--', '/doesnotexist'),
|
])
|
||||||
],
|
|
||||||
)
|
|
||||||
@pytest.mark.usefixtures('both_debug_modes', 'both_setsid_modes')
|
@pytest.mark.usefixtures('both_debug_modes', 'both_setsid_modes')
|
||||||
def test_fails_nonzero_with_bad_exec(args):
|
def test_fails_nonzero_with_bad_exec(args):
|
||||||
"""If dumb-init can't exec as requested, it should exit nonzero."""
|
"""If dumb-init can't exec as requested, it should exit nonzero."""
|
||||||
|
|
|
@ -12,34 +12,12 @@ def current_version():
|
||||||
return open('VERSION').read().strip()
|
return open('VERSION').read().strip()
|
||||||
|
|
||||||
|
|
||||||
def normalize_stderr(stderr):
|
|
||||||
# dumb-init prints out argv[0] in its usage message. This should always be
|
|
||||||
# just "dumb-init" under regular test scenarios here since that is how we
|
|
||||||
# call it, but in CI the use of QEMU causes the argv[0] to be replaced with
|
|
||||||
# the full path.
|
|
||||||
#
|
|
||||||
# This is supposed to be avoidable by:
|
|
||||||
# 1) Setting the "P" flag in the binfmt register:
|
|
||||||
# https://en.wikipedia.org/wiki/Binfmt_misc#Registration
|
|
||||||
# This can be done by setting the QEMU_PRESERVE_PARENT env var when
|
|
||||||
# calling binfmt.
|
|
||||||
#
|
|
||||||
# 2) Setting the "QEMU_ARGV0" env var to empty string for *all*
|
|
||||||
# processes:
|
|
||||||
# https://bugs.launchpad.net/qemu/+bug/1835839
|
|
||||||
#
|
|
||||||
# I can get it working properly in CI outside of Docker, but for some
|
|
||||||
# reason not during Docker builds. This doesn't affect the built executable
|
|
||||||
# so I decided to just punt on it.
|
|
||||||
return re.sub(rb'(^|(?<=\s))[a-z/.]+/dumb-init', b'dumb-init', stderr)
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.usefixtures('both_debug_modes', 'both_setsid_modes')
|
@pytest.mark.usefixtures('both_debug_modes', 'both_setsid_modes')
|
||||||
def test_no_arguments_prints_usage():
|
def test_no_arguments_prints_usage():
|
||||||
proc = Popen(('dumb-init'), stderr=PIPE)
|
proc = Popen(('dumb-init'), stderr=PIPE)
|
||||||
_, stderr = proc.communicate()
|
_, stderr = proc.communicate()
|
||||||
assert proc.returncode != 0
|
assert proc.returncode != 0
|
||||||
assert normalize_stderr(stderr) == (
|
assert stderr == (
|
||||||
b'Usage: dumb-init [option] program [args]\n'
|
b'Usage: dumb-init [option] program [args]\n'
|
||||||
b'Try dumb-init --help for full usage.\n'
|
b'Try dumb-init --help for full usage.\n'
|
||||||
)
|
)
|
||||||
|
@ -50,7 +28,7 @@ def test_exits_invalid_with_invalid_args():
|
||||||
proc = Popen(('dumb-init', '--yolo', '/bin/true'), stderr=PIPE)
|
proc = Popen(('dumb-init', '--yolo', '/bin/true'), stderr=PIPE)
|
||||||
_, stderr = proc.communicate()
|
_, stderr = proc.communicate()
|
||||||
assert proc.returncode == 1
|
assert proc.returncode == 1
|
||||||
assert normalize_stderr(stderr) in (
|
assert stderr in (
|
||||||
b"dumb-init: unrecognized option '--yolo'\n", # glibc
|
b"dumb-init: unrecognized option '--yolo'\n", # glibc
|
||||||
b'dumb-init: unrecognized option: yolo\n', # musl
|
b'dumb-init: unrecognized option: yolo\n', # musl
|
||||||
)
|
)
|
||||||
|
@ -65,7 +43,7 @@ def test_help_message(flag, current_version):
|
||||||
proc = Popen(('dumb-init', flag), stderr=PIPE)
|
proc = Popen(('dumb-init', flag), stderr=PIPE)
|
||||||
_, stderr = proc.communicate()
|
_, stderr = proc.communicate()
|
||||||
assert proc.returncode == 0
|
assert proc.returncode == 0
|
||||||
assert normalize_stderr(stderr) == (
|
assert stderr == (
|
||||||
b'dumb-init v' + current_version.encode('ascii') + b'\n'
|
b'dumb-init v' + current_version.encode('ascii') + b'\n'
|
||||||
b'Usage: dumb-init [option] command [[arg] ...]\n'
|
b'Usage: dumb-init [option] command [[arg] ...]\n'
|
||||||
b'\n'
|
b'\n'
|
||||||
|
@ -107,15 +85,15 @@ def test_verbose(flag):
|
||||||
assert stdout == b'oh, hi\n'
|
assert stdout == b'oh, hi\n'
|
||||||
|
|
||||||
# child/parent race to print output after the fork(), can't guarantee exact order
|
# child/parent race to print output after the fork(), can't guarantee exact order
|
||||||
assert re.search(b'(^|\n)\\[dumb-init\\] setsid complete\\.\n', stderr), stderr # child
|
assert re.search(b'(^|\n)\[dumb-init\] setsid complete\.\n', stderr), stderr # child
|
||||||
assert re.search( # parent
|
assert re.search( # parent
|
||||||
(
|
(
|
||||||
'(^|\n)\\[dumb-init\\] Child spawned with PID [0-9]+\\.\n'
|
'(^|\n)\[dumb-init\] Child spawned with PID [0-9]+\.\n'
|
||||||
'.*' # child might print here
|
'.*' # child might print here
|
||||||
'\\[dumb-init\\] Received signal {signal.SIGCHLD}\\.\n'
|
'\[dumb-init\] Received signal {signal.SIGCHLD}\.\n'
|
||||||
'\\[dumb-init\\] A child with PID [0-9]+ exited with exit status 0.\n'
|
'\[dumb-init\] A child with PID [0-9]+ exited with exit status 0.\n'
|
||||||
'\\[dumb-init\\] Forwarded signal 15 to children\\.\n'
|
'\[dumb-init\] Forwarded signal 15 to children\.\n'
|
||||||
'\\[dumb-init\\] Child exited with status 0\\. Goodbye\\.\n$'
|
'\[dumb-init\] Child exited with status 0\. Goodbye\.\n$'
|
||||||
).format(signal=signal).encode('utf8'),
|
).format(signal=signal).encode('utf8'),
|
||||||
stderr,
|
stderr,
|
||||||
re.DOTALL,
|
re.DOTALL,
|
||||||
|
@ -132,30 +110,28 @@ def test_verbose_and_single_child(flag1, flag2):
|
||||||
assert stdout == b'oh, hi\n'
|
assert stdout == b'oh, hi\n'
|
||||||
assert re.match(
|
assert re.match(
|
||||||
(
|
(
|
||||||
'^\\[dumb-init\\] Child spawned with PID [0-9]+\\.\n'
|
'^\[dumb-init\] Child spawned with PID [0-9]+\.\n'
|
||||||
'\\[dumb-init\\] Received signal {signal.SIGCHLD}\\.\n'
|
'\[dumb-init\] Received signal {signal.SIGCHLD}\.\n'
|
||||||
'\\[dumb-init\\] A child with PID [0-9]+ exited with exit status 0.\n'
|
'\[dumb-init\] A child with PID [0-9]+ exited with exit status 0.\n'
|
||||||
'\\[dumb-init\\] Forwarded signal 15 to children\\.\n'
|
'\[dumb-init\] Forwarded signal 15 to children\.\n'
|
||||||
'\\[dumb-init\\] Child exited with status 0\\. Goodbye\\.\n$'
|
'\[dumb-init\] Child exited with status 0\. Goodbye\.\n$'
|
||||||
).format(signal=signal).encode('utf8'),
|
).format(signal=signal).encode('utf8'),
|
||||||
stderr,
|
stderr,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize('extra_args', [
|
||||||
'extra_args', [
|
('-r',),
|
||||||
('-r',),
|
('-r', ''),
|
||||||
('-r', ''),
|
('-r', 'herp'),
|
||||||
('-r', 'herp'),
|
('-r', 'herp:derp'),
|
||||||
('-r', 'herp:derp'),
|
('-r', '15'),
|
||||||
('-r', '15'),
|
('-r', '15::12'),
|
||||||
('-r', '15::12'),
|
('-r', '15:derp'),
|
||||||
('-r', '15:derp'),
|
('-r', '15:12', '-r'),
|
||||||
('-r', '15:12', '-r'),
|
('-r', '15:12', '-r', '0'),
|
||||||
('-r', '15:12', '-r', '0'),
|
('-r', '15:12', '-r', '1:32'),
|
||||||
('-r', '15:12', '-r', '1:32'),
|
])
|
||||||
],
|
|
||||||
)
|
|
||||||
@pytest.mark.usefixtures('both_debug_modes', 'both_setsid_modes')
|
@pytest.mark.usefixtures('both_debug_modes', 'both_setsid_modes')
|
||||||
def test_rewrite_errors(extra_args):
|
def test_rewrite_errors(extra_args):
|
||||||
proc = Popen(
|
proc = Popen(
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import os
|
import os
|
||||||
from unittest import mock
|
|
||||||
|
|
||||||
|
import mock
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,28 +0,0 @@
|
||||||
import os
|
|
||||||
import shutil
|
|
||||||
from subprocess import PIPE
|
|
||||||
from subprocess import run
|
|
||||||
|
|
||||||
import pytest
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.usefixtures('both_debug_modes', 'both_setsid_modes')
|
|
||||||
def test_working_directories():
|
|
||||||
"""The child process must start in the working directory in which
|
|
||||||
dumb-init was invoked, but dumb-init itself should not keep a
|
|
||||||
reference to that."""
|
|
||||||
|
|
||||||
# We need absolute path to dumb-init since we pass cwd=/tmp to get
|
|
||||||
# predictable output - so we can't rely on dumb-init being found
|
|
||||||
# in the "." directory.
|
|
||||||
dumb_init = os.path.realpath(shutil.which('dumb-init'))
|
|
||||||
proc = run(
|
|
||||||
(
|
|
||||||
dumb_init,
|
|
||||||
'sh', '-c', 'readlink /proc/$PPID/cwd && readlink /proc/$$/cwd',
|
|
||||||
),
|
|
||||||
cwd='/tmp', stdout=PIPE, stderr=PIPE,
|
|
||||||
)
|
|
||||||
|
|
||||||
assert proc.returncode == 0
|
|
||||||
assert proc.stdout == b'/\n/tmp\n'
|
|
|
@ -11,19 +11,17 @@ def test_exit_status_regular_exit(exit_status):
|
||||||
"""dumb-init should exit with the same exit status as the process that it
|
"""dumb-init should exit with the same exit status as the process that it
|
||||||
supervises when that process exits normally.
|
supervises when that process exits normally.
|
||||||
"""
|
"""
|
||||||
proc = Popen(('dumb-init', 'sh', '-c', 'exit {}'.format(exit_status)))
|
proc = Popen(('dumb-init', 'sh', '-c', 'exit {0}'.format(exit_status)))
|
||||||
proc.wait()
|
proc.wait()
|
||||||
assert proc.returncode == exit_status
|
assert proc.returncode == exit_status
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize('signal', [
|
||||||
'signal', [
|
signal.SIGTERM,
|
||||||
signal.SIGTERM,
|
signal.SIGHUP,
|
||||||
signal.SIGHUP,
|
signal.SIGQUIT,
|
||||||
signal.SIGQUIT,
|
signal.SIGKILL,
|
||||||
signal.SIGKILL,
|
])
|
||||||
],
|
|
||||||
)
|
|
||||||
@pytest.mark.usefixtures('both_debug_modes', 'both_setsid_modes')
|
@pytest.mark.usefixtures('both_debug_modes', 'both_setsid_modes')
|
||||||
def test_exit_status_terminated_by_signal(signal):
|
def test_exit_status_terminated_by_signal(signal):
|
||||||
"""dumb-init should exit with status 128 + signal when the child process is
|
"""dumb-init should exit with status 128 + signal when the child process is
|
||||||
|
@ -31,10 +29,8 @@ def test_exit_status_terminated_by_signal(signal):
|
||||||
"""
|
"""
|
||||||
# We use Python because sh is "dash" on Debian and "bash" on others.
|
# We use Python because sh is "dash" on Debian and "bash" on others.
|
||||||
# https://github.com/Yelp/dumb-init/issues/115
|
# https://github.com/Yelp/dumb-init/issues/115
|
||||||
proc = Popen((
|
proc = Popen(('dumb-init', sys.executable, '-c', 'import os; os.kill(os.getpid(), {0})'.format(
|
||||||
'dumb-init', sys.executable, '-c', 'import os; os.kill(os.getpid(), {})'.format(
|
signal,
|
||||||
signal,
|
)))
|
||||||
),
|
|
||||||
))
|
|
||||||
proc.wait()
|
proc.wait()
|
||||||
assert proc.returncode == 128 + signal
|
assert proc.returncode == 128 + signal
|
||||||
|
|
|
@ -15,54 +15,52 @@ def test_proxies_signals():
|
||||||
with print_signals() as (proc, _):
|
with print_signals() as (proc, _):
|
||||||
for signum in NORMAL_SIGNALS:
|
for signum in NORMAL_SIGNALS:
|
||||||
proc.send_signal(signum)
|
proc.send_signal(signum)
|
||||||
assert proc.stdout.readline() == '{}\n'.format(signum).encode('ascii')
|
assert proc.stdout.readline() == '{0}\n'.format(signum).encode('ascii')
|
||||||
|
|
||||||
|
|
||||||
def _rewrite_map_to_args(rewrite_map):
|
def _rewrite_map_to_args(rewrite_map):
|
||||||
return chain.from_iterable(
|
return chain.from_iterable(
|
||||||
('-r', '{}:{}'.format(src, dst)) for src, dst in rewrite_map.items()
|
('-r', '{0}:{1}'.format(src, dst)) for src, dst in rewrite_map.items()
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize('rewrite_map,sequence,expected', [
|
||||||
'rewrite_map,sequence,expected', [
|
(
|
||||||
(
|
{},
|
||||||
{},
|
[signal.SIGTERM, signal.SIGQUIT, signal.SIGCONT, signal.SIGINT],
|
||||||
[signal.SIGTERM, signal.SIGQUIT, signal.SIGCONT, signal.SIGINT],
|
[signal.SIGTERM, signal.SIGQUIT, signal.SIGCONT, signal.SIGINT],
|
||||||
[signal.SIGTERM, signal.SIGQUIT, signal.SIGCONT, signal.SIGINT],
|
),
|
||||||
),
|
|
||||||
|
|
||||||
(
|
(
|
||||||
{signal.SIGTERM: signal.SIGINT},
|
{signal.SIGTERM: signal.SIGINT},
|
||||||
[signal.SIGTERM, signal.SIGQUIT, signal.SIGCONT, signal.SIGINT],
|
[signal.SIGTERM, signal.SIGQUIT, signal.SIGCONT, signal.SIGINT],
|
||||||
[signal.SIGINT, signal.SIGQUIT, signal.SIGCONT, signal.SIGINT],
|
[signal.SIGINT, signal.SIGQUIT, signal.SIGCONT, signal.SIGINT],
|
||||||
),
|
),
|
||||||
|
|
||||||
(
|
(
|
||||||
{
|
{
|
||||||
signal.SIGTERM: signal.SIGINT,
|
signal.SIGTERM: signal.SIGINT,
|
||||||
signal.SIGINT: signal.SIGTERM,
|
signal.SIGINT: signal.SIGTERM,
|
||||||
signal.SIGQUIT: signal.SIGQUIT,
|
signal.SIGQUIT: signal.SIGQUIT,
|
||||||
},
|
},
|
||||||
[signal.SIGTERM, signal.SIGQUIT, signal.SIGCONT, signal.SIGINT],
|
[signal.SIGTERM, signal.SIGQUIT, signal.SIGCONT, signal.SIGINT],
|
||||||
[signal.SIGINT, signal.SIGQUIT, signal.SIGCONT, signal.SIGTERM],
|
[signal.SIGINT, signal.SIGQUIT, signal.SIGCONT, signal.SIGTERM],
|
||||||
),
|
),
|
||||||
|
|
||||||
# Lowest possible and highest possible signals.
|
# Lowest possible and highest possible signals.
|
||||||
(
|
(
|
||||||
{1: 31, 31: 1},
|
{1: 31, 31: 1},
|
||||||
[1, 31],
|
[1, 31],
|
||||||
[31, 1],
|
[31, 1],
|
||||||
),
|
),
|
||||||
],
|
])
|
||||||
)
|
|
||||||
@pytest.mark.usefixtures('both_debug_modes', 'both_setsid_modes')
|
@pytest.mark.usefixtures('both_debug_modes', 'both_setsid_modes')
|
||||||
def test_proxies_signals_with_rewrite(rewrite_map, sequence, expected):
|
def test_proxies_signals_with_rewrite(rewrite_map, sequence, expected):
|
||||||
"""Ensure dumb-init can rewrite signals."""
|
"""Ensure dumb-init can rewrite signals."""
|
||||||
with print_signals(_rewrite_map_to_args(rewrite_map)) as (proc, _):
|
with print_signals(_rewrite_map_to_args(rewrite_map)) as (proc, _):
|
||||||
for send, expect_receive in zip(sequence, expected):
|
for send, expect_receive in zip(sequence, expected):
|
||||||
proc.send_signal(send)
|
proc.send_signal(send)
|
||||||
assert proc.stdout.readline() == '{}\n'.format(expect_receive).encode('ascii')
|
assert proc.stdout.readline() == '{0}\n'.format(expect_receive).encode('ascii')
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.usefixtures('both_debug_modes', 'setsid_enabled')
|
@pytest.mark.usefixtures('both_debug_modes', 'setsid_enabled')
|
||||||
|
@ -80,12 +78,12 @@ def test_default_rewrites_can_be_overriden_with_setsid_enabled():
|
||||||
assert process_state(proc.pid) in ['running', 'sleeping']
|
assert process_state(proc.pid) in ['running', 'sleeping']
|
||||||
proc.send_signal(send)
|
proc.send_signal(send)
|
||||||
|
|
||||||
assert proc.stdout.readline() == '{}\n'.format(expect_receive).encode('ascii')
|
assert proc.stdout.readline() == '{0}\n'.format(expect_receive).encode('ascii')
|
||||||
os.waitpid(proc.pid, os.WUNTRACED)
|
os.waitpid(proc.pid, os.WUNTRACED)
|
||||||
assert process_state(proc.pid) == 'stopped'
|
assert process_state(proc.pid) == 'stopped'
|
||||||
|
|
||||||
proc.send_signal(signal.SIGCONT)
|
proc.send_signal(signal.SIGCONT)
|
||||||
assert proc.stdout.readline() == '{}\n'.format(signal.SIGCONT).encode('ascii')
|
assert proc.stdout.readline() == '{0}\n'.format(signal.SIGCONT).encode('ascii')
|
||||||
assert process_state(proc.pid) in ['running', 'sleeping']
|
assert process_state(proc.pid) in ['running', 'sleeping']
|
||||||
|
|
||||||
|
|
||||||
|
@ -100,8 +98,8 @@ def test_ignored_signals_are_not_proxied():
|
||||||
with print_signals(_rewrite_map_to_args(rewrite_map)) as (proc, _):
|
with print_signals(_rewrite_map_to_args(rewrite_map)) as (proc, _):
|
||||||
proc.send_signal(signal.SIGTERM)
|
proc.send_signal(signal.SIGTERM)
|
||||||
proc.send_signal(signal.SIGINT)
|
proc.send_signal(signal.SIGINT)
|
||||||
assert proc.stdout.readline() == '{}\n'.format(signal.SIGQUIT).encode('ascii')
|
assert proc.stdout.readline() == '{0}\n'.format(signal.SIGQUIT).encode('ascii')
|
||||||
|
|
||||||
proc.send_signal(signal.SIGWINCH)
|
proc.send_signal(signal.SIGWINCH)
|
||||||
proc.send_signal(signal.SIGHUP)
|
proc.send_signal(signal.SIGHUP)
|
||||||
assert proc.stdout.readline() == '{}\n'.format(signal.SIGHUP).encode('ascii')
|
assert proc.stdout.readline() == '{0}\n'.format(signal.SIGHUP).encode('ascii')
|
||||||
|
|
|
@ -31,7 +31,7 @@ def test_shell_background_support_setsid():
|
||||||
# and then both wake up again
|
# and then both wake up again
|
||||||
proc.send_signal(SIGCONT)
|
proc.send_signal(SIGCONT)
|
||||||
assert (
|
assert (
|
||||||
proc.stdout.readline() == '{}\n'.format(SIGCONT).encode('ascii')
|
proc.stdout.readline() == '{0}\n'.format(SIGCONT).encode('ascii')
|
||||||
)
|
)
|
||||||
assert process_state(pid) in ['running', 'sleeping']
|
assert process_state(pid) in ['running', 'sleeping']
|
||||||
assert process_state(proc.pid) in ['running', 'sleeping']
|
assert process_state(proc.pid) in ['running', 'sleeping']
|
||||||
|
@ -46,12 +46,12 @@ def test_shell_background_support_without_setsid():
|
||||||
for signum in SUSPEND_SIGNALS:
|
for signum in SUSPEND_SIGNALS:
|
||||||
assert process_state(proc.pid) in ['running', 'sleeping']
|
assert process_state(proc.pid) in ['running', 'sleeping']
|
||||||
proc.send_signal(signum)
|
proc.send_signal(signum)
|
||||||
assert proc.stdout.readline() == '{}\n'.format(signum).encode('ascii')
|
assert proc.stdout.readline() == '{0}\n'.format(signum).encode('ascii')
|
||||||
os.waitpid(proc.pid, os.WUNTRACED)
|
os.waitpid(proc.pid, os.WUNTRACED)
|
||||||
assert process_state(proc.pid) == 'stopped'
|
assert process_state(proc.pid) == 'stopped'
|
||||||
|
|
||||||
proc.send_signal(SIGCONT)
|
proc.send_signal(SIGCONT)
|
||||||
assert (
|
assert (
|
||||||
proc.stdout.readline() == '{}\n'.format(SIGCONT).encode('ascii')
|
proc.stdout.readline() == '{0}\n'.format(SIGCONT).encode('ascii')
|
||||||
)
|
)
|
||||||
assert process_state(proc.pid) in ['running', 'sleeping']
|
assert process_state(proc.pid) in ['running', 'sleeping']
|
||||||
|
|
|
@ -77,7 +77,7 @@ def test_child_gets_controlling_tty_if_we_had_one():
|
||||||
output = readall(sfd)
|
output = readall(sfd)
|
||||||
assert os.waitpid(pid, 0) == (pid, 0), output
|
assert os.waitpid(pid, 0) == (pid, 0), output
|
||||||
|
|
||||||
m = re.search(b'flags are: \\[\\[([a-zA-Z]+)\\]\\]\n', output)
|
m = re.search(b'flags are: \[\[([a-zA-Z]+)\]\]\n', output)
|
||||||
assert m, output
|
assert m, output
|
||||||
|
|
||||||
# "m" is job control
|
# "m" is job control
|
||||||
|
@ -111,8 +111,8 @@ def test_sighup_sigcont_ignored_if_was_session_leader():
|
||||||
|
|
||||||
output = readall(fd).decode('UTF-8')
|
output = readall(fd).decode('UTF-8')
|
||||||
|
|
||||||
assert 'Ignoring tty hand-off signal {}.'.format(signal.SIGHUP) in output
|
assert 'Ignoring signal {} during its first receive.'.format(signal.SIGHUP) in output
|
||||||
assert 'Ignoring tty hand-off signal {}.'.format(signal.SIGCONT) in output
|
assert 'Ignoring signal {} during its first receive.'.format(signal.SIGCONT) in output
|
||||||
|
|
||||||
assert '[dumb-init] Forwarded signal {} to children.'.format(signal.SIGHUP) in output
|
assert '[dumb-init] Forwarded signal {} to children.'.format(signal.SIGHUP) in output
|
||||||
assert '[dumb-init] Forwarded signal {} to children.'.format(signal.SIGCONT) not in output
|
assert '[dumb-init] Forwarded signal {} to children.'.format(signal.SIGCONT) not in output
|
||||||
|
|
7
tox.ini
7
tox.ini
|
@ -1,21 +1,20 @@
|
||||||
[tox]
|
[tox]
|
||||||
envlist = py38,gcov
|
envlist = py27,py36,gcov
|
||||||
|
|
||||||
[testenv]
|
[testenv]
|
||||||
deps = -r{toxinidir}/requirements-dev.txt
|
deps = -r{toxinidir}/requirements-dev.txt
|
||||||
commands =
|
commands =
|
||||||
pytest
|
python -m pytest
|
||||||
|
|
||||||
[testenv:gcov]
|
[testenv:gcov]
|
||||||
skip_install = True
|
skip_install = True
|
||||||
basepython = /usr/bin/python3.8
|
|
||||||
commands =
|
commands =
|
||||||
{toxinidir}/ci/gcov-build {envbindir}
|
{toxinidir}/ci/gcov-build {envbindir}
|
||||||
{[testenv]commands}
|
{[testenv]commands}
|
||||||
{toxinidir}/ci/gcov-report
|
{toxinidir}/ci/gcov-report
|
||||||
|
|
||||||
[testenv:pre-commit]
|
[testenv:pre-commit]
|
||||||
basepython = /usr/bin/python3.8
|
basepython = /usr/bin/python3.6
|
||||||
commands = pre-commit {posargs:run --all-files}
|
commands = pre-commit {posargs:run --all-files}
|
||||||
|
|
||||||
[flake8]
|
[flake8]
|
||||||
|
|
Loading…
Reference in a new issue