Skip to content

Instantly share code, notes, and snippets.

What would you like to do?
A script to test the difference between Requires, PartOf, and BindsTo of a systemd.unit
#! /usr/bin/env python
# -*- coding: utf-8 -*-
# vim:fenc=utf-8
# Copyright © 2021 Pi-Yueh Chuang <>
# Distributed under terms of the BSD 3-Clause license.
"""Test the difference between Requires=, PartOf=, and BindsTo= of a systemd.unit.
import os
import time
import atexit
import subprocess
import pandas
def kernel(command):
"""Execute a command and return the status of the two services.
command : str;
The target systemd command.
A string of either V/V, V/X, X/V. or X/X. The first character indicates the status of a.service, and the last
character indicated the status of b.service. "V" means the service is running, while "X" means otherwise.
_ ="systemctl --user daemon-reload", shell=True, check=True)
_ =, shell=True, check=True)
respond_a ="systemctl --user is-active a.service", shell=True, capture_output=True, check=False)
respond_b ="systemctl --user is-active b.service", shell=True, capture_output=True, check=False)
respond_a = respond_a.stdout.decode("utf-8").strip()
respond_b = respond_b.stdout.decode("utf-8").strip()
_ ="systemctl --user reset-failed a.service", shell=True, capture_output=True, check=False)
_ ="systemctl --user reset-failed b.service", shell=True, capture_output=True, check=False)
_ ="systemctl --user stop a.service b.service", shell=True, check=True)
convert = {"active": "V", "inactive": "X", "failed": "X"}
return "{}/{}".format(convert[respond_a], convert[respond_b])
def main(html=False):
"""Main function.
html : bool
Whether to output html or just string.
If html is True, return a html representation of the result table. Otherwise, return a string representation
of the result table that can be used in `print`.
# path to a.service
a_service_path = os.path.join(os.path.expanduser("~"), ".config", "systemd", "user", "a.service")
# path to b.service
b_service_path = os.path.join(os.path.expanduser("~"), ".config", "systemd", "user", "b.service")
# let the program automatically remove service files when the program exits, no matter how
atexit.register(os.remove, a_service_path)
atexit.register(os.remove, b_service_path)
# commands to be tested
test_cmds = [
"systemctl --user start a.service",
"systemctl --user start b.service",
"systemctl --user start a.service b.service;" + "systemctl --user stop a.service",
"systemctl --user start a.service b.service;" + "systemctl --user stop b.service",
"systemctl --user start a.service b.service;" +
"kill -9 $(systemctl --user show a.service | grep -oP '(?<=ExecMainPID=)\\d*')",
"systemctl --user start a.service b.service;" +
"kill -9 $(systemctl --user show b.service | grep -oP '(?<=ExecMainPID=)\\d*')",
"systemctl --user start a.service b.service;" + "systemctl --user restart a.service",
"systemctl --user start a.service b.service;" + "systemctl --user restart b.service",
# result holder
result = pandas.DataFrame(
index=["Test {}".format(i+1) for i in range(len(test_cmds))],
columns=["Requires", "PartOf", "BindsTo"],
# loop through different experiment groups
for tag in ["Requires", "PartOf", "BindsTo"]:
# write to a.service
with open(a_service_path, "w") as fobj:
"Description=Test target A\n",
"ExecStart=sh -c 'while true; do echo A is alive; sleep 3; done'\n",
# write to a.service
with open(b_service_path, "w") as fobj:
"Description=Test service B.\n",
"ExecStart=sh -c 'while true; do echo B is alive; sleep 3; done'\n",
for i, cmd in enumerate(test_cmds):
print("Running experiment group of {} test {}".format(tag, i+1))
result.loc["Test {}".format(i+1), tag] = kernel(cmd)
return result.to_html() if html else result.to_string()
if __name__ == "__main__":
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment