Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
271 changes: 122 additions & 149 deletions Development.md
Original file line number Diff line number Diff line change
@@ -1,212 +1,185 @@
# Testing

All of these tests have been automated in [sanity.rs](./pid1/tests/sanity.rs).
All of these tests have been automated in [`pid1/tests/sanity.rs`](./pid1/tests/sanity.rs).

The `examples` directory contains several programs used for testing this library.
The [`pid1/examples` directory](./pid1/examples) contains several programs used for testing this library.

- `simple.rs`: A program that demonstrates the usage of the `pid1-rs` library.
- `zombie.rs`: Creates a zombie process.
- `sigterm_handler.rs`: A program that has a `SIGTERM` handler and exits upon receiving the signal.
- `sigterm_loop.rs`: A buggy program that does not exit on `SIGTERM`.
- `dumb_shell.rs`: An alternative shell for testing, since `bash`
automatically reaps child processes.
- `dumb_shell.rs`: An alternative shell for testing, since `bash` automatically reaps child processes.

## Environment setup

- Run the test program via the just recipe: `build-image`
- You can get shell access to it via the recipe: `exec-shell`

``` shellsession
just exec-shell
docker exec -it pid1rs sh
/ # ps aux
PID USER TIME COMMAND
1 root 0:05 /simple
7 root 0:00 /simple
8 root 0:00 sh
14 root 0:00 [date]
15 root 0:00 ps aux
```
```console
$ just exec-shell

# docker exec -it pid1rs sh
$ ps aux
PID USER TIME COMMAND
1 root 0:05 /simple
7 root 0:00 /simple
8 root 0:00 sh
15 root 0:00 ps aux
```

# Tests

## Basic functionality

``` shellsession
just test
...
docker run --name pid1rs -t pid1rstest
```console
$ just test

# docker run --rm --name pid1rs pid1rstest
pid1-rs: Process running as PID 1
pid1-rs: Process not running as Pid 1: PID 7
In the simple process, going to sleep. Process ID is 7
Args: ["/simple"]
Wed Sep 27 08:29:35 UTC 2023
Wed Sep 27 08:29:37 UTC 2023
Wed Sep 27 08:29:39 UTC 2023
pid1-rs: Reaped PID 7
```

Ensure that the process exits with a status code of 0. The test above
confirms the following:
Ensure that the process exits with a status code of `0`. The test above confirms the following:

- The `simple` program was executed as PID 1.
- It relaunched itself as PID 7, which then executed various child processes.
- It relaunched itself as PID 7, before exiting and was then reaped.

## Zombie process

``` shellsession
❯ just run-image
...
docker rm pid1rs || exit 0
pid1rs
docker run --name pid1rs -t pid1rstest /simple --sleep
pid1-rs: Process running as PID 1
pid1-rs: Process not running as Pid 1: PID 7
In the simple process, going to sleep. Process ID is 7
Args: ["/simple", "--sleep"]
Wed Sep 27 08:34:57 UTC 2023
Wed Sep 27 08:34:59 UTC 2023
Wed Sep 27 08:35:01 UTC 2023
Going to sleep 500 seconds
Wed Sep 27 08:35:03 UTC 2023
```
1. Run the `run-image` recipe:

While it's executing, open a new shell and run the recipe to create a
zombie process:
```console
$ just run-image

``` shellsession
❯ just run-zombie
docker exec -t pid1rs zombie
Process ID is 9
Parent process: going to sleep and exit
```
# docker run --rm --name pid1rs pid1rstest /simple --sleep 20
pid1-rs: Process running as PID 1
In the simple process, going to sleep. Process ID is 7
Args: ["/simple", "--sleep", "20"]
Going to sleep 20 seconds
```

You should see the following logs from the `simple` process, indicating
that a process has been reaped:
2. While it's executing, open a new shell and run the recipe to create a zombie process:

``` shellsession
...
Wed Sep 27 09:40:56 UTC 2023
Reaped pid: 15
```
```console
$ just run-zombie

# docker exec pid1rs zombie
Process ID is 9
Parent process: going to sleep and exit
```

3. You should see the following logs from the `simple` process, indicating that a process has been reaped:

```console
Reaped PID: 15
```

## Child Exit code status propagation

- Run the `run-image` recipe:
1. Run the `run-orphans` recipe:

``` shellsession
❯ just run-image
...
docker rm pid1rs || exit 0
pid1rs
docker run --name pid1rs -t pid1rstest /simple --sleep
pid1-rs: Process running as PID 1
pid1-rs: Process not running as Pid 1: PID 7
In the simple process, going to sleep. Process ID is 7
Args: ["/simple", "--sleep"]
Wed Sep 27 08:34:57 UTC 2023
Wed Sep 27 08:34:59 UTC 2023
Wed Sep 27 08:35:01 UTC 2023
Going to sleep 500 seconds
Wed Sep 27 08:35:03 UTC 2023
```
```console
$ just run-image

- Run the `exec-shell` recipe and perform the test:

``` shellsession
❯ just exec-shell
docker exec -it pid1rs sh
/ # ps -aux
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 1 0.0 0.0 996 4 pts/0 Ss+ 12:34 0:00 /simple --sleep
root 7 0.0 0.0 976 4 pts/0 S+ 12:34 0:00 /simple --sleep
root 8 0.0 0.0 0 0 pts/0 Z+ 12:34 0:00 [date] <defunct>
root 9 0.0 0.0 1672 1048 pts/1 Ss 12:34 0:00 sh
root 14 0.0 0.0 0 0 pts/0 Z+ 12:34 0:00 [date] <defunct>
root 15 0.0 0.0 0 0 pts/0 Z+ 12:34 0:00 [date] <defunct>
root 16 0.0 0.0 0 0 pts/0 Z+ 12:34 0:00 [date] <defunct>
root 17 0.0 0.0 2460 1608 pts/1 R+ 12:34 0:00 ps -aux
/ # kill -12 7
```
# docker run --rm --name pid1rs pid1rstest /simple --sleep 20 --create-grandchildren
pid1-rs: Process running as PID 1
In the simple process, going to sleep. Process ID is 7
Args: ["/simple", "--sleep", "20", "--create-grandchildren"]
Spawning 3 grandchildren processes that will be orphaned.
Going to sleep 20 seconds
```

- Check the logs of the `run-image` recipe to find the exit status
code:
2. Run the `exec-shell` recipe and perform the test:

``` shellsession
...
error: Recipe `run-image` failed on line 21 with exit code 140
```
```console
$ just exec-shell

# docker exec -it pid1rs sh
$ ps -aux
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 1 0.8 0.0 1048 756 ? Ss 10:11 0:00 /simple --sleep 20 --create-grandchildren
root 7 0.0 0.0 1040 860 ? S 10:11 0:00 /simple --sleep 20 --create-grandchildren
root 8 0.0 0.0 0 0 ? Z 10:11 0:00 [sleep] <defunct>
root 9 0.0 0.0 0 0 ? Z 10:11 0:00 [sleep] <defunct>
root 10 0.0 0.0 0 0 ? Z 10:11 0:00 [sleep] <defunct>
root 11 0.3 0.1 2900 1888 pts/0 Ss 10:11 0:00 sh
root 17 0.0 0.1 7072 3068 pts/0 R+ 10:11 0:00 ps -aux

$ kill -12 7
```

3. Check the logs of the `run-orphans` recipe to find the exit status code:

The exit code 140 is correct (128 + 12).
```console
error: Recipe `run-orphans` failed on line 21 with exit code 140
```

## SIGINT/SIGTERM handling
The exit code 140 is correct (128 + 12).

The goal of this test is to verify that the child process's `SIGTERM`
handler is called if it is defined.
## SIGINT / SIGTERM handling

The goal of this test is to verify that the child process's `SIGTERM` handler is called if it is defined.

These are the signal codes:

- SIGINT: 2
- SIGTERM: 15
- `SIGINT`: 2
- `SIGTERM`: 15

Execute the recipe `sigterm-test`:
1. Execute the recipe `sigterm-test`:

``` shellsession
❯ just sigterm-test
docker rm pid1rs || exit 0
pid1rs
docker run --name pid1rs -t pid1rstest sigterm_handler
pid1-rs: Process running as PID 1
pid1-rs: Process not running as Pid 1: PID 7
This APP can be killed by SIGTERM (15)
```
```console
$ just sigterm-test

Now, send `SIGTERM` to the PID 1 process:
# docker run --rm --name pid1rs pid1rstest sigterm_handler
pid1-rs: Process running as PID 1
This APP can be killed by SIGTERM (15)
```

``` shellsession
❯ just send-sigterm
```
2. Now, send `SIGTERM` to the PID 1 process:

Confirm from the logs that the `SIGTERM` handler was called and that
the process exited with a status of 0:
```shell
just send-sigterm
```

``` shellsession
App got SIGTERM 15, going to exit
```
3. Confirm from the logs that the `SIGTERM` handler was called and that the process exited with a status of `0`:

```console
App got SIGTERM 15, going to exit
```

Now, perform the same test with `SIGINT`. You can confirm that nothing
is printed, as this signal is not handled by the application.
Now, perform the same test with `SIGINT`. You can confirm that nothing is printed,
as this signal is not handled by the application.

## SIGTERM ignore

This test is for an application that ignores `SIGTERM` and continues
running. This library should be able to forcibly kill such processes.
This test is for an application that ignores `SIGTERM` and continues running.
This library should be able to forcibly kill such processes.

Execute the recipe `sigloop-test`:
1. Execute the recipe `sigloop-test`:

``` shellsession
❯ just sigloop-test
docker rm pid1rs || exit 0
pid1rs
docker run --name pid1rs -t pid1rstest sigterm_loop
pid1-rs: Process running as PID 1
pid1-rs: Process not running as Pid 1: PID 7
This APP ignores SIGTERM (15)
```
```console
$ just sigloop-test

Now, send `SIGTERM` to the PID 1 process:
# docker run --rm --name pid1rs pid1rstest sigterm_loop
pid1-rs: Process running as PID 1
This APP ignores SIGTERM (15)
```

``` shellsession
❯ just send-sigterm
```
2. Now, send `SIGTERM` to the PID 1 process:

You will see that it would have exited:
```shell
just send-sigterm
```

``` shellsession
App got SIGTERM 15, but will not exit
App got SIGTERM 15, but will not exit
error: Recipe `sigloop-test` failed on line 44 with exit code 137
```
3. You will see that it would have exited:

```console
App got SIGTERM 15, but will not exit
App got SIGTERM 15, but will not exit
error: Recipe `sigloop-test` failed on line 44 with exit code 137
```

You can confirm from the status code (137) that the child process was
killed by `SIGKILL` (signal 9, exit code 128 + 9), because the
application ignored `SIGTERM`.
You can confirm from the status code (137) that the child process was killed by
`SIGKILL` (signal 9, exit code 128 + 9), because the application ignored `SIGTERM`.
19 changes: 11 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -109,22 +109,25 @@ CMD [ "your-application", "--arg1" ]

The `pid1` binary supports various command-line options:

``` shellsession
```shellsession
❯ pid1 --help
Usage:
A Unix PID1 process wrapper for signal handling and zombie reaping

Usage: pid1 [OPTIONS] <COMMAND> [ARGS]...

Arguments:
<COMMAND> Process to run
[ARGS]... Arguments to the process
[ARGS]... Arguments to that process

Options:
-w, --workdir <DIR> Specify working direcory
-t, --timeout <TIMEOUT> Timeout (in seconds) to wait for child proess to exit [default: 2]
-w, --workdir <DIR> Specify working directory
-t, --timeout <SECONDS> Grace period for stopping before escalating to SIGKILL [default: 2]
-v, --verbose Turn on verbose output
-e, --env <ENV> Override environment variables. Can specify multiple times
-u, --user-id <USER_ID> Run command with user ID
-g, --group-id <GROUP_ID> Run command with group ID
-e, --env <KEY=VALUE> Override environment variables. Can specify multiple times
-u, --user-id <USER ID> Run command with user ID
-g, --group-id <GROUP ID> Run command with group ID
-h, --help Print help
-V, --version Print version
```

---
Expand Down
Loading