summaryrefslogtreecommitdiffstats
path: root/test/units/TEST-07-PID1.exec-deserialization.sh
blob: 04f17c88e9dbcfc3552aaec1d4694fdfdc621592 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
#!/usr/bin/env bash
# SPDX-License-Identifier: LGPL-2.1-or-later
# shellcheck disable=SC2016
set -eux
set -o pipefail

# shellcheck source=test/units/test-control.sh
. "$(dirname "$0")"/test-control.sh
# shellcheck source=test/units/util.sh
. "$(dirname "$0")"/util.sh

setup_base_unit() {
    local unit_path="${1:?}"
    local log_file="${2:?}"
    local unit_name="${unit_path##*/}"

    cat >"$unit_path" <<EOF
[Service]
Type=oneshot
ExecStart=sleep 3
ExecStart=bash -c "echo foo >>$log_file"
EOF
    systemctl daemon-reload

    systemctl --job-mode=replace --no-block start "$unit_name"
    # Wait until the unit leaves the "inactive" state
    timeout 5s bash -xec "while [[ \"\$(systemctl show -P ActiveState $unit_name)\" == inactive ]]; do sleep .1; done"
    # Sleep for 1 second from the unit start to get well "into" the first (or second) ExecStart= directive
    sleep 1
}

check_output() {
    local unit_name="${1:?}"
    local log_file="${2:?}"
    local expected="${3?}"
    local unit_name="${unit_path##*/}"

    # Wait until the unit becomes inactive before checking the log
    timeout 10s bash -xec "while [[ \"\$(systemctl show -P ActiveState $unit_name)\" != inactive ]]; do sleep .5; done"

    diff "$log_file" <(echo -ne "$expected")
}

testcase_no_change() {
    local unit_path log_file

    unit_path="$(mktemp /run/systemd/system/test-deserialization-no-change-XXX.service)"
    log_file="$(mktemp)"

    setup_base_unit "$unit_path" "$log_file"

    # Simple sanity test without any reordering shenanignans, to check if the base unit works as expected.
    check_output "$unit_path" "$log_file" "foo\n"

    rm -f "$unit_path" "$log_file"
}

testcase_swapped() {
    local unit_path log_file

    unit_path="$(mktemp /run/systemd/system/test-deserialization-swapped-XXX.service)"
    log_file="$(mktemp)"

    setup_base_unit "$unit_path" "$log_file"

    # Swap the two ExecStart= lines.
    #
    # Since we should be in the first "sleep" of the base unit, after replacing the unit with the following
    # one we should continue running from the respective "ExecStart=sleep 3" line, which is now the last
    # one, resulting no output in the final log file.
    cat >"$unit_path" <<EOF
[Service]
Type=oneshot
ExecStart=bash -c "echo foo >>$log_file"
ExecStart=sleep 3
EOF
    systemctl daemon-reload

    check_output "$unit_path" "$log_file" ""

    rm -f "$unit_path" "$log_file"
}

testcase_added_before() {
    local unit_path log_file

    unit_path="$(mktemp /run/systemd/system/test-deserialization-added-before-XXX.service)"
    log_file="$(mktemp)"

    setup_base_unit "$unit_path" "$log_file"

    # Add one new ExecStart= before the existing ones.
    #
    # Since, after reload, we should continue running from the "sleep 3" statement, the newly added "echo
    # bar" one will have no effect and we should end up with the same output as in the previous case.
    cat >"$unit_path" <<EOF
[Service]
Type=oneshot
ExecStart=bash -c "echo bar >>$log_file"
ExecStart=sleep 3
ExecStart=bash -c "echo foo >>$log_file"
EOF
    systemctl daemon-reload

    check_output "$unit_path" "$log_file" "foo\n"

    rm -f "$unit_path" "$log_file"
}

testcase_added_after() {
    local unit_path log_file

    unit_path="$(mktemp /run/systemd/system/test-deserialization-added-after-XXX.service)"
    log_file="$(mktemp)"

    setup_base_unit "$unit_path" "$log_file"

    # Add an ExecStart= line after the existing ones.
    #
    # Same case as above, except the newly added ExecStart= should get executed, as it was added after the
    # "sleep 3" statement.
    cat >"$unit_path" <<EOF
[Service]
Type=oneshot
ExecStart=sleep 3
ExecStart=bash -c "echo foo >>$log_file"
ExecStart=bash -c "echo bar >>$log_file"
EOF
    systemctl daemon-reload

    check_output "$unit_path" "$log_file" "foo\nbar\n"

    rm -f "$unit_path" "$log_file"
}

testcase_interleaved() {
    local unit_path log_file

    unit_path="$(mktemp /run/systemd/system/test-deserialization-interleaved-XXX.service)"
    log_file="$(mktemp)"

    setup_base_unit "$unit_path" "$log_file"

    # Combination of the two previous cases.
    cat >"$unit_path" <<EOF
[Service]
Type=oneshot
ExecStart=bash -c "echo baz >>$log_file"
ExecStart=sleep 3
ExecStart=bash -c "echo foo >>$log_file"
ExecStart=bash -c "echo bar >>$log_file"
EOF
    systemctl daemon-reload

    check_output "$unit_path" "$log_file" "foo\nbar\n"

    rm -f "$unit_path" "$log_file"
}

testcase_removal() {
    local unit_path log_file

    unit_path="$(mktemp /run/systemd/system/test-deserialization-removal-XXX.service)"
    log_file="$(mktemp)"

    setup_base_unit "$unit_path" "$log_file"

    # Remove the currently executed ExecStart= line.
    #
    # In this case we completely drop the currently executed "sleep 3" statement, so after reload systemd
    # should complain that the currently executed command vanished and simply finish executing the unit,
    # resulting in an empty log.
    cat >"$unit_path" <<EOF
[Service]
Type=oneshot
ExecStart=bash -c "echo bar >>$log_file"
ExecStart=bash -c "echo baz >>$log_file"
EOF
    systemctl daemon-reload

    check_output "$unit_path" "$log_file" ""

    rm -f "$unit_path" "$log_file"
}

testcase_issue_6533() {
    local unit_path unit_name log_file

    unit_path="$(mktemp /run/systemd/system/test-deserialization-issue-6533-XXX.service)"
    unit_name="${unit_path##*/}"
    log_file="$(mktemp)"

    cat >"$unit_path" <<EOF
[Service]
Type=simple
ExecStart=/bin/sleep 5
EOF
    systemctl daemon-reload

    systemctl --job-mode=replace --no-block start "$unit_name"
    sleep 2

    # Make sure we try to execute the next command only for oneshot services, as for other types we allow
    # only one ExecStart= directive.
    #
    # See: https://github.com/systemd/systemd/issues/6533
    cat >"$unit_path" <<EOF
[Service]
Type=simple
ExecStart=/bin/sleep 5
ExecStart=bash -c "echo foo >>$log_file"
EOF
    systemctl daemon-reload

    check_output "$unit_path" "$log_file" ""
    (! journalctl -b --grep "Freezing execution" _PID=1)
}

mkdir -p /run/systemd/system/
run_testcases
systemctl daemon-reload