Merge pull request #1 from peterbourgon/zombietest

Zombie test
This commit is contained in:
Peter Bourgon 2015-09-28 23:24:04 +02:00
commit a02bfa75fa
10 changed files with 172 additions and 17 deletions

3
.gitignore vendored
View File

@ -1,6 +1,9 @@
runsvinit
runsvinit-*-*
examples/runsvinit-linux-amd64*
zombietest/zombie
zombietest/runsvinit
*.uptodate
# Compiled Object files, Static and Dynamic libs (Shared Objects)
*.o

8
circle.yml Normal file
View File

@ -0,0 +1,8 @@
machine:
services:
- docker
test:
override:
- go test -v
- make CIRCLECI=true SUDO=sudo RM= -C zombietest

45
main.go
View File

@ -1,6 +1,7 @@
package main
import (
"flag"
"log"
"os"
"os/exec"
@ -13,6 +14,9 @@ import (
const etcService = "/etc/service"
func main() {
reap := flag.Bool("reap", true, "reap orphan children")
flag.Parse()
log.SetFlags(0)
runsvdir, err := exec.LookPath("runsvdir")
@ -35,7 +39,12 @@ func main() {
log.Printf("warning: I'm not PID 1, I'm PID %d", pid)
}
go reapAll()
if *reap {
log.Print("reaping zombies")
go reapLoop()
} else {
log.Print("NOT reaping zombies")
}
supervisor := cmd(runsvdir, etcService)
if err := supervisor.Start(); err != nil {
@ -53,31 +62,33 @@ func main() {
}
}
func reapAll() {
// From https://github.com/ramr/go-reaper/blob/master/reaper.go
func reapLoop() {
c := make(chan os.Signal)
signal.Notify(c, syscall.SIGCHLD)
for range c {
go reapOne()
reapChildren()
}
}
// From https://github.com/ramr/go-reaper/blob/master/reaper.go
func reapOne() {
var (
ws syscall.WaitStatus
pid int
err error
)
func reapChildren() {
for {
pid, err = syscall.Wait4(-1, &ws, 0, nil)
if err != syscall.EINTR {
break
var (
ws syscall.WaitStatus
pid int
err error
)
for {
pid, err = syscall.Wait4(-1, &ws, 0, nil)
if err != syscall.EINTR {
break
}
}
if err == syscall.ECHILD {
return // done
}
log.Printf("reaped child process %d (%+v)", pid, ws)
}
if err == syscall.ECHILD {
return
}
log.Printf("reaped child process %d (%+v)", pid, ws)
}
type signaler interface {

11
zombietest/Dockerfile Normal file
View File

@ -0,0 +1,11 @@
FROM alpine:latest
RUN echo "http://dl-4.alpinelinux.org/alpine/edge/testing" >>/etc/apk/repositories && \
apk add --update runit && \
rm -rf /var/cache/apk/*
COPY zombie /
RUN mkdir -p /etc/service/zombie
COPY run-zombie /etc/service/zombie/run
COPY /runsvinit /

27
zombietest/Makefile Normal file
View File

@ -0,0 +1,27 @@
GO?=go
SUDO?=
RM?=--rm
.PHONY: test clean
test: .test.uptodate
./test.bash
.test.uptodate: runsvinit zombie run-zombie Dockerfile
$(SUDO) docker build -t zombietest .
touch $@
runsvinit: ../*.go
env GOOS=linux GOARCH=amd64 $(GO) build -o $@ github.com/peterbourgon/runsvinit
zombie: .build.uptodate
$(SUDO) docker run $(RM) -v $(shell pwd):/mount zombietest-build cc -Wall -Werror -o /mount/zombie /zombie.c
.build.uptodate: build/zombie.c build/Dockerfile
$(SUDO) docker build -t zombietest-build build/
touch $@
clean:
rm -rf .test.uptodate .build.uptodate runsvinit zombie
$(SUDO) docker stop zombietest zombietest-build >/dev/null 2>&1 || true
$(SUDO) docker rm zombietest zombietest-build >/dev/null 2>&1 || true

24
zombietest/README.md Normal file
View File

@ -0,0 +1,24 @@
# zombietest
This directory contains an integration test to prove runsvinit is actually
reaping zombies. `make` builds and executes the test as follows:
1. We produce a linux/amd64 runsvinit binary by setting GOOS/GOARCH and
invoking the Go compiler. Requires Go 1.5, or Go 1.4 built with the
appropriate cross-compile options.
2. The build/zombie.c program spawns five zombies and exits. We compile it for
linux/amd64 via a zombietest-build container. We do this so `make` works
from a Mac. This requires a working Docker installation.
3. Once we have linux/amd64 runsvinit and zombie binaries, we produce a
zombietest container via the Dockerfile. That container contains a single
runit service, /etc/service/zombie, which supervises the zombie binary. We
provide no default ENTRYPOINT, so we can supply it at runtime.
4. Once the zombietest container is built, we invoke the test.bash script.
That launches a version of the container with runsvinit set to NOT reap
zombies, and after 1 second, verifies that zombies exist. Then, it launches
a version of the container with runsvinit set to reap zombies, and after 1
second, verifies that no zombies exist.

View File

@ -0,0 +1,3 @@
FROM alpine:latest
RUN apk add --update gcc musl-dev && rm -rf /var/cache/apk/*
COPY zombie.c /

18
zombietest/build/zombie.c Normal file
View File

@ -0,0 +1,18 @@
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
int main() {
pid_t pid;
int i;
for (i = 0; i<5; i++) {
pid = fork();
if (pid > 0) {
printf("Zombie #%d born\n", i + 1);
} else {
printf("Brains...\n");
exit(0);
}
}
return 0;
}

3
zombietest/run-zombie Executable file
View File

@ -0,0 +1,3 @@
#!/bin/sh
exec /zombie

47
zombietest/test.bash Executable file
View File

@ -0,0 +1,47 @@
#!/bin/bash
function zombies() {
if [ -z "$CIRCLECI" ]
then
docker exec $C ps -o pid,stat | grep Z | wc -l
else
# https://circleci.com/docs/docker#docker-exec
sudo lxc-attach -n "$(docker inspect --format '{{.Id}}' $C)" -- sh -c "ps -o pid,stat | grep Z | wc -l"
fi
}
function stop_rm() {
docker stop $1
docker rm $1
}
SLEEP=1
RC=0
C=$(docker run -d zombietest /runsvinit -reap=false)
sleep $SLEEP
NOREAP=$(zombies)
echo -n without reaping, we have $NOREAP zombies...
if [ "$NOREAP" -le "0" ]
then
echo " FAIL"
RC=1
else
echo " good"
fi
stop_rm $C
C=$(docker run -d zombietest /runsvinit)
sleep $SLEEP
YESREAP=$(zombies)
echo -n with reaping, we have $YESREAP zombies...
if [ "$YESREAP" -gt "0" ]
then
echo " FAIL"
RC=1
else
echo " good"
fi
stop_rm $C
exit $RC