summaryrefslogtreecommitdiff
path: root/test
diff options
context:
space:
mode:
Diffstat (limited to 'test')
-rw-r--r--test/README.md2
-rw-r--r--test/apiv2/30-volumes.at2
-rw-r--r--test/e2e/common_test.go2
-rw-r--r--test/e2e/container_create_volume_test.go127
-rw-r--r--test/e2e/pod_create_test.go16
-rw-r--r--test/e2e/run_exit_test.go6
-rw-r--r--test/e2e/volume_ls_test.go16
-rw-r--r--test/system/090-events.bats1
-rw-r--r--test/system/125-import.bats45
-rw-r--r--test/system/250-systemd.bats39
-rw-r--r--test/system/255-auto-update.bats33
-rw-r--r--test/system/270-socket-activation.bats17
-rw-r--r--test/system/500-networking.bats2
-rw-r--r--test/system/helpers.bash2
-rw-r--r--test/system/helpers.systemd.bash30
15 files changed, 258 insertions, 82 deletions
diff --git a/test/README.md b/test/README.md
index d7710cc95..769bdbfd7 100644
--- a/test/README.md
+++ b/test/README.md
@@ -84,7 +84,7 @@ file itself. Consider the following actual test:
It("podman inspect bogus pod", func() {
session := podmanTest.Podman([]string{"pod", "inspect", "foobar"})
session.WaitWithDefaultTimeout()
- Expect(session.ExitCode()).To(Not(Equal(0)))
+ Expect(session).To(ExitWithError())
})
```
diff --git a/test/apiv2/30-volumes.at b/test/apiv2/30-volumes.at
index b639e05f9..fd1542293 100644
--- a/test/apiv2/30-volumes.at
+++ b/test/apiv2/30-volumes.at
@@ -174,6 +174,8 @@ t POST libpod/volumes/create \
# with date way back in the past, volume should not be deleted (compat api)
t POST volumes/prune?filters='{"until":["500000"]}' 200
t GET libpod/volumes/json?filters='{"label":["testuntilcompat"]}' 200 length=1
+t GET libpod/volumes/json?filters='{"until":["500000"]}' 200 length=0
+t GET libpod/volumes/json?filters='{"until":["5000000000"]}' 200 length=1
# with date far in the future, volume should be deleted (compat api)
t POST volumes/prune?filters='{"until":["5000000000"]}' 200
diff --git a/test/e2e/common_test.go b/test/e2e/common_test.go
index 5a6cf7ffb..2e48e1763 100644
--- a/test/e2e/common_test.go
+++ b/test/e2e/common_test.go
@@ -811,7 +811,7 @@ func generateNetworkConfig(p *PodmanTestIntegration) (string, string) {
func (p *PodmanTestIntegration) removeCNINetwork(name string) {
session := p.Podman([]string{"network", "rm", "-f", name})
session.WaitWithDefaultTimeout()
- Expect(session.ExitCode()).To(BeNumerically("<=", 1))
+ Expect(session.ExitCode()).To(BeNumerically("<=", 1), "Exit code must be 0 or 1")
}
func (p *PodmanSessionIntegration) jq(jqCommand string) (string, error) {
diff --git a/test/e2e/container_create_volume_test.go b/test/e2e/container_create_volume_test.go
new file mode 100644
index 000000000..001698239
--- /dev/null
+++ b/test/e2e/container_create_volume_test.go
@@ -0,0 +1,127 @@
+package integration
+
+import (
+ "fmt"
+ "io/ioutil"
+ "os"
+ "path/filepath"
+
+ . "github.com/containers/podman/v3/test/utils"
+ . "github.com/onsi/ginkgo"
+ . "github.com/onsi/gomega"
+ . "github.com/onsi/gomega/gexec"
+)
+
+func buildDataVolumeImage(pTest *PodmanTestIntegration, image, data, dest string) {
+ // Create a dummy file for data volume
+ dummyFile := filepath.Join(pTest.TempDir, data)
+ err := ioutil.WriteFile(dummyFile, []byte(data), 0644)
+ Expect(err).To(BeNil())
+
+ // Create a data volume container image but no CMD binary in it
+ containerFile := fmt.Sprintf(`FROM scratch
+CMD doesnotexist.sh
+ADD %s %s/
+VOLUME %s/`, data, dest, dest)
+ pTest.BuildImage(containerFile, image, "false")
+}
+
+func createContainersConfFile(pTest *PodmanTestIntegration) {
+ configPath := filepath.Join(pTest.TempDir, "containers.conf")
+ containersConf := []byte(fmt.Sprintf("[containers]\nprepare_volume_on_create = true\n"))
+ err := ioutil.WriteFile(configPath, containersConf, os.ModePerm)
+ Expect(err).To(BeNil())
+
+ // Set custom containers.conf file
+ os.Setenv("CONTAINERS_CONF", configPath)
+ if IsRemote() {
+ pTest.RestartRemoteService()
+ }
+}
+
+func checkDataVolumeContainer(pTest *PodmanTestIntegration, image, cont, dest, data string) {
+ create := pTest.Podman([]string{"create", "--name", cont, image})
+ create.WaitWithDefaultTimeout()
+ Expect(create).Should(Exit(0))
+
+ inspect := pTest.InspectContainer(cont)
+ Expect(len(inspect)).To(Equal(1))
+ Expect(len(inspect[0].Mounts)).To(Equal(1))
+ Expect(inspect[0].Mounts[0].Destination).To(Equal(dest))
+
+ mntName, mntSource := inspect[0].Mounts[0].Name, inspect[0].Mounts[0].Source
+
+ volList := pTest.Podman([]string{"volume", "list", "--quiet"})
+ volList.WaitWithDefaultTimeout()
+ Expect(volList).Should(Exit(0))
+ Expect(len(volList.OutputToStringArray())).To(Equal(1))
+ Expect(volList.OutputToStringArray()[0]).To(Equal(mntName))
+
+ // Check the mount source directory
+ files, err := ioutil.ReadDir(mntSource)
+ Expect(err).To(BeNil())
+
+ if data == "" {
+ Expect(len(files)).To(Equal(0))
+ } else {
+ Expect(len(files)).To(Equal(1))
+ Expect(files[0].Name()).To(Equal(data))
+ }
+}
+
+var _ = Describe("Podman create data volume", func() {
+ var (
+ tempdir string
+ err error
+ podmanTest *PodmanTestIntegration
+ )
+
+ BeforeEach(func() {
+ tempdir, err = CreateTempDirInTempDir()
+ if err != nil {
+ os.Exit(1)
+ }
+ podmanTest = PodmanTestCreate(tempdir)
+ podmanTest.Setup()
+ podmanTest.SeedImages()
+ })
+
+ AfterEach(func() {
+ podmanTest.Cleanup()
+ f := CurrentGinkgoTestDescription()
+ processTestResult(f)
+ os.Unsetenv("CONTAINERS_CONF")
+ })
+
+ It("podman create with volume data copy turned off", func() {
+ imgName, volData, volDest := "dataimg", "dummy", "/test"
+
+ buildDataVolumeImage(podmanTest, imgName, volData, volDest)
+
+ // Create a container with the default containers.conf and
+ // check that the volume is not copied from the image.
+ checkDataVolumeContainer(podmanTest, imgName, "ctr-nocopy", volDest, "")
+ })
+
+ It("podman create with volume data copy turned on", func() {
+ imgName, volData, volDest := "dataimg", "dummy", "/test"
+
+ buildDataVolumeImage(podmanTest, imgName, volData, volDest)
+
+ // Create a container with the custom containers.conf and
+ // check that the volume is copied from the image.
+ createContainersConfFile(podmanTest)
+
+ checkDataVolumeContainer(podmanTest, imgName, "ctr-copy", volDest, volData)
+ })
+
+ It("podman run with volume data copy turned on", func() {
+ // Create a container with the custom containers.conf and
+ // check that the container is run successfully
+ createContainersConfFile(podmanTest)
+
+ session := podmanTest.Podman([]string{"run", "--rm", ALPINE, "echo"})
+ session.WaitWithDefaultTimeout()
+ Expect(session).Should(Exit(0))
+ })
+})
diff --git a/test/e2e/pod_create_test.go b/test/e2e/pod_create_test.go
index 63f55fb88..4c6788b9d 100644
--- a/test/e2e/pod_create_test.go
+++ b/test/e2e/pod_create_test.go
@@ -566,11 +566,11 @@ ENTRYPOINT ["sleep","99999"]
ns := "ns:/proc/self/ns/"
podCreate := podmanTest.Podman([]string{"pod", "create", "--pid", ns, "--name", podName, "--share", "pid"})
podCreate.WaitWithDefaultTimeout()
- Expect(podCreate.ExitCode()).To(Equal(0))
+ Expect(podCreate).Should(Exit(0))
podInspect := podmanTest.Podman([]string{"pod", "inspect", podName})
podInspect.WaitWithDefaultTimeout()
- Expect(podInspect.ExitCode()).To(Equal(0))
+ Expect(podInspect).Should(Exit(0))
podJSON := podInspect.InspectPodToJSON()
Expect(podJSON.InfraConfig.PidNS).To(Equal("path"))
@@ -579,11 +579,11 @@ ENTRYPOINT ["sleep","99999"]
podCreate = podmanTest.Podman([]string{"pod", "create", "--pid", ns, "--name", podName, "--share", "pid"})
podCreate.WaitWithDefaultTimeout()
- Expect(podCreate.ExitCode()).To(Equal(0))
+ Expect(podCreate).Should(Exit(0))
podInspect = podmanTest.Podman([]string{"pod", "inspect", podName})
podInspect.WaitWithDefaultTimeout()
- Expect(podInspect.ExitCode()).To(Equal(0))
+ Expect(podInspect).Should(Exit(0))
podJSON = podInspect.InspectPodToJSON()
Expect(podJSON.InfraConfig.PidNS).To(Equal("pod"))
@@ -592,11 +592,11 @@ ENTRYPOINT ["sleep","99999"]
podCreate = podmanTest.Podman([]string{"pod", "create", "--pid", ns, "--name", podName, "--share", "pid"})
podCreate.WaitWithDefaultTimeout()
- Expect(podCreate.ExitCode()).To(Equal(0))
+ Expect(podCreate).Should(Exit(0))
podInspect = podmanTest.Podman([]string{"pod", "inspect", podName})
podInspect.WaitWithDefaultTimeout()
- Expect(podInspect.ExitCode()).To(Equal(0))
+ Expect(podInspect).Should(Exit(0))
podJSON = podInspect.InspectPodToJSON()
Expect(podJSON.InfraConfig.PidNS).To(Equal("host"))
@@ -605,11 +605,11 @@ ENTRYPOINT ["sleep","99999"]
podCreate = podmanTest.Podman([]string{"pod", "create", "--pid", ns, "--name", podName, "--share", "pid"})
podCreate.WaitWithDefaultTimeout()
- Expect(podCreate.ExitCode()).To(Equal(0))
+ Expect(podCreate).Should(Exit(0))
podInspect = podmanTest.Podman([]string{"pod", "inspect", podName})
podInspect.WaitWithDefaultTimeout()
- Expect(podInspect.ExitCode()).To(Equal(0))
+ Expect(podInspect).Should(Exit(0))
podJSON = podInspect.InspectPodToJSON()
Expect(podJSON.InfraConfig.PidNS).To(Equal("private"))
diff --git a/test/e2e/run_exit_test.go b/test/e2e/run_exit_test.go
index 21f1a8650..e86718577 100644
--- a/test/e2e/run_exit_test.go
+++ b/test/e2e/run_exit_test.go
@@ -49,11 +49,7 @@ var _ = Describe("Podman run exit", func() {
It("podman run exit ExecErrorCodeNotFound", func() {
result := podmanTest.Podman([]string{"run", ALPINE, "foobar"})
result.WaitWithDefaultTimeout()
- Expect(result.ExitCode()).To(Not(Equal(define.ExecErrorCodeGeneric)))
- // TODO This is failing we believe because of a race condition
- // Between conmon and podman closing the socket early.
- // Test with the following, once the race condition is solved
- // Expect(result).Should(Exit(define.ExecErrorCodeNotFound))
+ Expect(result).Should(Exit(define.ExecErrorCodeNotFound))
})
It("podman run exit 0", func() {
diff --git a/test/e2e/volume_ls_test.go b/test/e2e/volume_ls_test.go
index ff3551ad9..0dd1a2b7c 100644
--- a/test/e2e/volume_ls_test.go
+++ b/test/e2e/volume_ls_test.go
@@ -101,6 +101,22 @@ var _ = Describe("Podman volume ls", func() {
Expect(len(session.OutputToStringArray())).To(Equal(0))
})
+ It("podman ls volume with --filter until flag", func() {
+ session := podmanTest.Podman([]string{"volume", "create"})
+ session.WaitWithDefaultTimeout()
+ Expect(session).Should(Exit(0))
+
+ session = podmanTest.Podman([]string{"volume", "ls", "--filter", "until=5000000000"})
+ session.WaitWithDefaultTimeout()
+ Expect(session).Should(Exit(0))
+ Expect(len(session.OutputToStringArray())).To(Equal(2))
+
+ session = podmanTest.Podman([]string{"volume", "ls", "--filter", "until=50000"})
+ session.WaitWithDefaultTimeout()
+ Expect(session).Should(Exit(0))
+ Expect(len(session.OutputToStringArray())).To(Equal(0))
+ })
+
It("podman volume ls with --filter dangling", func() {
volName1 := "volume1"
session := podmanTest.Podman([]string{"volume", "create", volName1})
diff --git a/test/system/090-events.bats b/test/system/090-events.bats
index d889bd7f9..22edaeee9 100644
--- a/test/system/090-events.bats
+++ b/test/system/090-events.bats
@@ -81,6 +81,7 @@ function _events_disjunctive_filters() {
@test "events with disjunctive filters - journald" {
skip_if_remote "remote does not support --events-backend"
+ skip_if_journald_unavailable "system does not support journald events"
_events_disjunctive_filters --events-backend=journald
}
diff --git a/test/system/125-import.bats b/test/system/125-import.bats
new file mode 100644
index 000000000..c53711618
--- /dev/null
+++ b/test/system/125-import.bats
@@ -0,0 +1,45 @@
+#!/usr/bin/env bats -*- bats -*-
+#
+# tests for podman import
+#
+
+load helpers
+
+@test "podman import" {
+ local archive=$PODMAN_TMPDIR/archive.tar
+ local random_content=$(random_string 12)
+ # Generate a random name and tag (must be lower-case)
+ local random_name=x0$(random_string 12 | tr A-Z a-z)
+ local random_tag=t0$(random_string 7 | tr A-Z a-z)
+ local fqin=localhost/$random_name:$random_tag
+
+ run_podman run --name import $IMAGE sh -c "echo ${random_content} > /random.txt"
+ run_podman export import -o $archive
+ run_podman rm -f import
+
+ # Simple import
+ run_podman import -q $archive
+ iid="$output"
+ run_podman run -t --rm $iid cat /random.txt
+ is "$output" "$random_content" "simple import"
+ run_podman rmi -f $iid
+
+ # Simple import via stdin
+ run_podman import -q - < <(cat $archive)
+ iid="$output"
+ run_podman run -t --rm $iid cat /random.txt
+ is "$output" "$random_content" "simple import via stdin"
+ run_podman rmi -f $iid
+
+ # Tagged import
+ run_podman import -q $archive $fqin
+ run_podman run -t --rm $fqin cat /random.txt
+ is "$output" "$random_content" "tagged import"
+ run_podman rmi -f $fqin
+
+ # Tagged import via stdin
+ run_podman import -q - $fqin < <(cat $archive)
+ run_podman run -t --rm $fqin cat /random.txt
+ is "$output" "$random_content" "tagged import via stdin"
+ run_podman rmi -f $fqin
+}
diff --git a/test/system/250-systemd.bats b/test/system/250-systemd.bats
index aafe385c8..ee951ff21 100644
--- a/test/system/250-systemd.bats
+++ b/test/system/250-systemd.bats
@@ -4,17 +4,10 @@
#
load helpers
+load helpers.systemd
SERVICE_NAME="podman_test_$(random_string)"
-SYSTEMCTL="systemctl"
-UNIT_DIR="/usr/lib/systemd/system"
-if is_rootless; then
- UNIT_DIR="$HOME/.config/systemd/user"
- mkdir -p $UNIT_DIR
-
- SYSTEMCTL="$SYSTEMCTL --user"
-fi
UNIT_FILE="$UNIT_DIR/$SERVICE_NAME.service"
function setup() {
@@ -24,38 +17,28 @@ function setup() {
}
function teardown() {
- run '?' $SYSTEMCTL stop "$SERVICE_NAME"
+ run '?' systemctl stop "$SERVICE_NAME"
rm -f "$UNIT_FILE"
- $SYSTEMCTL daemon-reload
+ systemctl daemon-reload
run_podman rmi -a
basic_teardown
}
-# Helper to setup xdg runtime for rootless
-function xdg_rootless() {
- # podman initializes this if unset, but systemctl doesn't
- if is_rootless; then
- if [ -z "$XDG_RUNTIME_DIR" ]; then
- export XDG_RUNTIME_DIR=/run/user/$(id -u)
- fi
- fi
-}
-
# Helper to start a systemd service running a container
function service_setup() {
run_podman generate systemd --new $cname
echo "$output" > "$UNIT_FILE"
run_podman rm $cname
- $SYSTEMCTL daemon-reload
+ systemctl daemon-reload
- run $SYSTEMCTL start "$SERVICE_NAME"
+ run systemctl start "$SERVICE_NAME"
if [ $status -ne 0 ]; then
die "Error starting systemd unit $SERVICE_NAME, output: $output"
fi
- run $SYSTEMCTL status "$SERVICE_NAME"
+ run systemctl status "$SERVICE_NAME"
if [ $status -ne 0 ]; then
die "Non-zero status of systemd unit $SERVICE_NAME, output: $output"
fi
@@ -63,20 +46,18 @@ function service_setup() {
# Helper to stop a systemd service running a container
function service_cleanup() {
- run $SYSTEMCTL stop "$SERVICE_NAME"
+ run systemctl stop "$SERVICE_NAME"
if [ $status -ne 0 ]; then
die "Error stopping systemd unit $SERVICE_NAME, output: $output"
fi
rm -f "$UNIT_FILE"
- $SYSTEMCTL daemon-reload
+ systemctl daemon-reload
}
# These tests can fail in dev. environment because of SELinux.
# quick fix: chcon -t container_runtime_exec_t ./bin/podman
@test "podman generate - systemd - basic" {
- xdg_rootless
-
cname=$(random_string)
# See #7407 for --pull=always.
run_podman create --pull=always --name $cname --label "io.containers.autoupdate=registry" $IMAGE top
@@ -100,8 +81,6 @@ function service_cleanup() {
}
@test "podman autoupdate local" {
- xdg_rootless
-
cname=$(random_string)
run_podman create --name $cname --label "io.containers.autoupdate=local" $IMAGE top
@@ -128,8 +107,6 @@ function service_cleanup() {
# These tests can fail in dev. environment because of SELinux.
# quick fix: chcon -t container_runtime_exec_t ./bin/podman
@test "podman generate systemd - envar" {
- xdg_rootless
-
cname=$(random_string)
FOO=value BAR=%s run_podman create --name $cname --env FOO -e BAR --env MYVAR=myval \
$IMAGE sh -c 'printenv && sleep 100'
diff --git a/test/system/255-auto-update.bats b/test/system/255-auto-update.bats
index 4d7a8e783..25eaba45b 100644
--- a/test/system/255-auto-update.bats
+++ b/test/system/255-auto-update.bats
@@ -4,21 +4,12 @@
#
load helpers
+load helpers.systemd
-DASHUSER=""
-UNIT_DIR="/run/systemd/system"
SNAME_FILE=$BATS_TMPDIR/services
function setup() {
skip_if_remote "systemd tests are meaningless over remote"
-
- if is_rootless; then
- test -n "${XDG_RUNTIME_DIR}" || skip "\$XDG_RUNTIME_DIR is unset"
- UNIT_DIR="${XDG_RUNTIME_DIR}/systemd/user"
- mkdir -p $UNIT_DIR
- # Why isn't systemd smart enough to figure this out on its own?
- DASHUSER="--user"
- fi
basic_setup
}
@@ -26,10 +17,10 @@ function teardown() {
while read line; do
if [[ "$line" =~ "podman-auto-update" ]]; then
echo "Stop timer: $line.timer"
- systemctl $DASHUSER stop $line.timer
- systemctl $DASHUSER disable $line.timer
+ systemctl stop $line.timer
+ systemctl disable $line.timer
else
- systemctl $DASHUSER stop $line
+ systemctl stop $line
fi
rm -f $UNIT_DIR/$line.{service,timer}
done < $SNAME_FILE
@@ -69,9 +60,9 @@ function generate_service() {
echo "container-$cname" >> $SNAME_FILE
run_podman rm -f $cname
- systemctl $DASHUSER daemon-reload
- systemctl $DASHUSER start container-$cname
- systemctl $DASHUSER status container-$cname
+ systemctl daemon-reload
+ systemctl start container-$cname
+ systemctl status container-$cname
# Original image ID.
# IMPORTANT: variable 'ori_image' is passed (out of scope) up to caller!
@@ -84,7 +75,7 @@ function _wait_service_ready() {
local timeout=6
while [[ $timeout -gt 1 ]]; do
- if systemctl $DASHUSER -q is-active $sname; then
+ if systemctl -q is-active $sname; then
return
fi
sleep 1
@@ -92,7 +83,7 @@ function _wait_service_ready() {
done
# Print serivce status as debug information before failed the case
- systemctl $DASHUSER status $sname
+ systemctl status $sname
die "Timed out waiting for $sname to start"
}
@@ -267,14 +258,14 @@ WantedBy=multi-user.target default.target
EOF
echo "podman-auto-update-$cname" >> $SNAME_FILE
- systemctl $DASHUSER enable --now podman-auto-update-$cname.timer
- systemctl $DASHUSER list-timers --all
+ systemctl enable --now podman-auto-update-$cname.timer
+ systemctl list-timers --all
local expect='Finished Podman auto-update testing service'
local failed_start=failed
local count=0
while [ $count -lt 120 ]; do
- run journalctl $DASHUSER -n 15 -u podman-auto-update-$cname.service
+ run journalctl -n 15 -u podman-auto-update-$cname.service
if [[ "$output" =~ $expect ]]; then
failed_start=
break
diff --git a/test/system/270-socket-activation.bats b/test/system/270-socket-activation.bats
index 25206c6a7..031ba161b 100644
--- a/test/system/270-socket-activation.bats
+++ b/test/system/270-socket-activation.bats
@@ -4,21 +4,12 @@
#
load helpers
+load helpers.systemd
SERVICE_NAME="podman_test_$(random_string)"
-SYSTEMCTL="systemctl"
-UNIT_DIR="/usr/lib/systemd/system"
SERVICE_SOCK_ADDR="/run/podman/podman.sock"
-
if is_rootless; then
- UNIT_DIR="$HOME/.config/systemd/user"
- mkdir -p $UNIT_DIR
-
- SYSTEMCTL="$SYSTEMCTL --user"
- if [ -z "$XDG_RUNTIME_DIR" ]; then
- export XDG_RUNTIME_DIR=/run/user/$(id -u)
- fi
SERVICE_SOCK_ADDR="$XDG_RUNTIME_DIR/podman/podman.sock"
fi
@@ -66,13 +57,13 @@ EOF
rm -f $pause_pid
fi
fi
- $SYSTEMCTL start "$SERVICE_NAME.socket"
+ systemctl start "$SERVICE_NAME.socket"
}
function teardown() {
- $SYSTEMCTL stop "$SERVICE_NAME.socket"
+ systemctl stop "$SERVICE_NAME.socket"
rm -f "$SERVICE_FILE" "$SOCKET_FILE"
- $SYSTEMCTL daemon-reload
+ systemctl daemon-reload
basic_teardown
}
diff --git a/test/system/500-networking.bats b/test/system/500-networking.bats
index 419d325b0..495c7948b 100644
--- a/test/system/500-networking.bats
+++ b/test/system/500-networking.bats
@@ -139,7 +139,7 @@ load helpers
$IMAGE nc -l -n -v -p $myport
cid="$output"
- wait_for_port 127.0.0.1 $myport
+ wait_for_output "listening on .*:$myport .*" $cid
# emit random string, and check it
teststring=$(random_string 30)
diff --git a/test/system/helpers.bash b/test/system/helpers.bash
index 02fd7252c..bd9471ace 100644
--- a/test/system/helpers.bash
+++ b/test/system/helpers.bash
@@ -288,7 +288,7 @@ function wait_for_port() {
# Wait
while [ $_timeout -gt 0 ]; do
- { exec 3<> /dev/tcp/$host/$port; } &>/dev/null && return
+ { exec 5<> /dev/tcp/$host/$port; } &>/dev/null && return
sleep 1
_timeout=$(( $_timeout - 1 ))
done
diff --git a/test/system/helpers.systemd.bash b/test/system/helpers.systemd.bash
new file mode 100644
index 000000000..4bde912a4
--- /dev/null
+++ b/test/system/helpers.systemd.bash
@@ -0,0 +1,30 @@
+# -*- bash -*-
+#
+# BATS helpers for systemd-related functionality
+#
+
+# podman initializes this if unset, but systemctl doesn't
+if [ -z "$XDG_RUNTIME_DIR" ]; then
+ if is_rootless; then
+ export XDG_RUNTIME_DIR=/run/user/$(id -u)
+ fi
+fi
+
+# For tests which write systemd unit files
+UNIT_DIR="/run/systemd/system"
+_DASHUSER=
+if is_rootless; then
+ UNIT_DIR="${XDG_RUNTIME_DIR}/systemd/user"
+ # Why isn't systemd smart enough to figure this out on its own?
+ _DASHUSER="--user"
+fi
+
+mkdir -p $UNIT_DIR
+
+systemctl() {
+ command systemctl $_DASHUSER "$@"
+}
+
+journalctl() {
+ command journalctl $_DASHUSER "$@"
+}