2017-05-01 03:26:56 +03:00
#!/usr/bin/env python3
2020-11-09 07:23:58 +03:00
# SPDX-License-Identifier: LGPL-2.1-or-later
2022-09-02 21:14:53 +03:00
# pylint: disable=line-too-long,too-many-lines,too-many-branches,too-many-statements,too-many-arguments
# pylint: disable=too-many-public-methods,too-many-boolean-expressions,invalid-name,no-self-use
# pylint: disable=missing-function-docstring,missing-class-docstring,missing-module-docstring
2017-02-15 14:40:52 +03:00
#
2018-06-12 20:00:24 +03:00
# Copyright © 2017 Michal Sekletar <msekleta@redhat.com>
2017-02-15 14:40:52 +03:00
# ATTENTION: This uses the *installed* systemd, not the one from the built
# source tree.
import os
import subprocess
2020-02-03 15:07:45 +03:00
import sys
2022-09-02 21:14:53 +03:00
import time
import unittest
2022-09-13 21:14:48 +03:00
import uuid
2017-02-15 14:40:52 +03:00
from enum import Enum
2022-08-26 10:30:01 +03:00
class InstallChange ( Enum ) :
2017-02-15 14:40:52 +03:00
NO_CHANGE = 0
LINES_SWAPPED = 1
COMMAND_ADDED_BEFORE = 2
COMMAND_ADDED_AFTER = 3
COMMAND_INTERLEAVED = 4
REMOVAL = 5
class ExecutionResumeTest ( unittest . TestCase ) :
def setUp ( self ) :
self . unit = ' test-issue-518.service '
2022-09-02 21:14:53 +03:00
self . unitfile_path = f ' /run/systemd/system/ { self . unit } '
2022-09-13 21:14:48 +03:00
self . output_file = f " /tmp/test-issue-518- { uuid . uuid4 ( ) } "
2017-02-15 14:40:52 +03:00
self . unit_files = { }
2022-09-02 21:14:53 +03:00
unit_file_content = f '''
2017-02-15 14:40:52 +03:00
[ Service ]
Type = oneshot
2020-02-03 15:07:45 +03:00
ExecStart = / bin / sleep 3
2022-09-02 21:14:53 +03:00
ExecStart = / bin / bash - c " echo foo >> {self.output_file} "
'''
2022-08-26 10:30:01 +03:00
self . unit_files [ InstallChange . NO_CHANGE ] = unit_file_content
2017-02-15 14:40:52 +03:00
2022-09-02 21:14:53 +03:00
unit_file_content = f '''
2017-02-15 14:40:52 +03:00
[ Service ]
Type = oneshot
2022-09-02 21:14:53 +03:00
ExecStart = / bin / bash - c " echo foo >> {self.output_file} "
2020-02-03 15:07:45 +03:00
ExecStart = / bin / sleep 3
2022-09-02 21:14:53 +03:00
'''
2022-08-26 10:30:01 +03:00
self . unit_files [ InstallChange . LINES_SWAPPED ] = unit_file_content
2017-02-15 14:40:52 +03:00
2022-09-02 21:14:53 +03:00
unit_file_content = f '''
2017-02-15 14:40:52 +03:00
[ Service ]
Type = oneshot
2022-09-02 21:14:53 +03:00
ExecStart = / bin / bash - c " echo bar >> {self.output_file} "
2020-02-03 15:07:45 +03:00
ExecStart = / bin / sleep 3
2022-09-02 21:14:53 +03:00
ExecStart = / bin / bash - c " echo foo >> {self.output_file} "
'''
2022-08-26 10:30:01 +03:00
self . unit_files [ InstallChange . COMMAND_ADDED_BEFORE ] = unit_file_content
2017-02-15 14:40:52 +03:00
2022-09-02 21:14:53 +03:00
unit_file_content = f '''
2017-02-15 14:40:52 +03:00
[ Service ]
Type = oneshot
2020-02-03 15:07:45 +03:00
ExecStart = / bin / sleep 3
2022-09-02 21:14:53 +03:00
ExecStart = / bin / bash - c " echo foo >> {self.output_file} "
ExecStart = / bin / bash - c " echo bar >> {self.output_file} "
'''
2022-08-26 10:30:01 +03:00
self . unit_files [ InstallChange . COMMAND_ADDED_AFTER ] = unit_file_content
2017-02-15 14:40:52 +03:00
2022-09-02 21:14:53 +03:00
unit_file_content = f '''
2017-02-15 14:40:52 +03:00
[ Service ]
Type = oneshot
2022-09-02 21:14:53 +03:00
ExecStart = / bin / bash - c " echo baz >> {self.output_file} "
2020-02-03 15:07:45 +03:00
ExecStart = / bin / sleep 3
2022-09-02 21:14:53 +03:00
ExecStart = / bin / bash - c " echo foo >> {self.output_file} "
ExecStart = / bin / bash - c " echo bar >> {self.output_file} "
'''
2022-08-26 10:30:01 +03:00
self . unit_files [ InstallChange . COMMAND_INTERLEAVED ] = unit_file_content
2017-02-15 14:40:52 +03:00
2022-09-02 21:14:53 +03:00
unit_file_content = f '''
2017-02-15 14:40:52 +03:00
[ Service ]
Type = oneshot
2022-09-02 21:14:53 +03:00
ExecStart = / bin / bash - c " echo bar >> {self.output_file} "
ExecStart = / bin / bash - c " echo baz >> {self.output_file} "
2022-09-13 21:07:22 +03:00
'''
2022-08-26 10:30:01 +03:00
self . unit_files [ InstallChange . REMOVAL ] = unit_file_content
2017-02-15 14:40:52 +03:00
def reload ( self ) :
subprocess . check_call ( [ ' systemctl ' , ' daemon-reload ' ] )
def write_unit_file ( self , unit_file_change ) :
2022-08-26 10:30:01 +03:00
if not isinstance ( unit_file_change , InstallChange ) :
2017-02-15 14:40:52 +03:00
raise ValueError ( ' Unknown unit file change ' )
content = self . unit_files [ unit_file_change ]
2022-09-02 21:14:53 +03:00
with open ( self . unitfile_path , ' w ' , encoding = ' utf-8 ' ) as f :
2017-02-15 14:40:52 +03:00
f . write ( content )
self . reload ( )
def check_output ( self , expected_output ) :
2022-09-02 21:06:12 +03:00
for _ in range ( 15 ) :
2022-09-30 10:31:47 +03:00
# Wait until the unit finishes so we don't check an incomplete log
if subprocess . call ( [ ' systemctl ' , ' -q ' , ' is-active ' , self . unit ] ) == 0 :
continue
2022-10-17 19:11:21 +03:00
os . sync ( )
2022-09-02 21:06:12 +03:00
try :
2022-09-02 21:14:53 +03:00
with open ( self . output_file , ' r ' , encoding = ' utf-8 ' ) as log :
2022-09-02 21:06:12 +03:00
output = log . read ( )
self . assertEqual ( output , expected_output )
return
except IOError :
pass
time . sleep ( 1 )
2017-02-15 14:40:52 +03:00
2022-09-02 21:14:53 +03:00
self . fail ( f ' Timed out while waiting for the output file { self . output_file } to appear ' )
2017-02-15 14:40:52 +03:00
def setup_unit ( self ) :
2022-08-26 10:30:01 +03:00
self . write_unit_file ( InstallChange . NO_CHANGE )
2017-02-15 14:40:52 +03:00
subprocess . check_call ( [ ' systemctl ' , ' --job-mode=replace ' , ' --no-block ' , ' start ' , self . unit ] )
2020-02-03 15:07:45 +03:00
time . sleep ( 1 )
2017-02-15 14:40:52 +03:00
def test_no_change ( self ) :
expected_output = ' foo \n '
self . setup_unit ( )
self . reload ( )
self . check_output ( expected_output )
def test_swapped ( self ) :
self . setup_unit ( )
2022-08-26 10:30:01 +03:00
self . write_unit_file ( InstallChange . LINES_SWAPPED )
2017-02-15 14:40:52 +03:00
self . reload ( )
self . assertTrue ( not os . path . exists ( self . output_file ) )
def test_added_before ( self ) :
expected_output = ' foo \n '
self . setup_unit ( )
2022-08-26 10:30:01 +03:00
self . write_unit_file ( InstallChange . COMMAND_ADDED_BEFORE )
2017-02-15 14:40:52 +03:00
self . reload ( )
self . check_output ( expected_output )
def test_added_after ( self ) :
expected_output = ' foo \n bar \n '
self . setup_unit ( )
2022-08-26 10:30:01 +03:00
self . write_unit_file ( InstallChange . COMMAND_ADDED_AFTER )
2017-02-15 14:40:52 +03:00
self . reload ( )
self . check_output ( expected_output )
def test_interleaved ( self ) :
expected_output = ' foo \n bar \n '
self . setup_unit ( )
2022-08-26 10:30:01 +03:00
self . write_unit_file ( InstallChange . COMMAND_INTERLEAVED )
2017-02-15 14:40:52 +03:00
self . reload ( )
self . check_output ( expected_output )
def test_removal ( self ) :
self . setup_unit ( )
2022-08-26 10:30:01 +03:00
self . write_unit_file ( InstallChange . REMOVAL )
2017-02-15 14:40:52 +03:00
self . reload ( )
self . assertTrue ( not os . path . exists ( self . output_file ) )
2017-08-25 16:36:10 +03:00
def test_issue_6533 ( self ) :
unit = " test-issue-6533.service "
2022-09-02 21:14:53 +03:00
unitfile_path = f " /run/systemd/system/ { unit } "
2017-08-25 16:36:10 +03:00
content = '''
[ Service ]
ExecStart = / bin / sleep 5
'''
2022-09-02 21:14:53 +03:00
with open ( unitfile_path , ' w ' , encoding = ' utf-8 ' ) as f :
2017-08-25 16:36:10 +03:00
f . write ( content )
self . reload ( )
subprocess . check_call ( [ ' systemctl ' , ' --job-mode=replace ' , ' --no-block ' , ' start ' , unit ] )
time . sleep ( 2 )
content = '''
[ Service ]
ExecStart = / bin / sleep 5
ExecStart = / bin / true
'''
2022-09-02 21:14:53 +03:00
with open ( unitfile_path , ' w ' , encoding = ' utf-8 ' ) as f :
2017-08-25 16:36:10 +03:00
f . write ( content )
self . reload ( )
time . sleep ( 5 )
2022-10-13 11:31:37 +03:00
self . assertNotEqual ( subprocess . call ( " journalctl -b _PID=1 | grep -q ' Freezing execution ' " , shell = True ) , 0 )
2017-08-25 16:36:10 +03:00
2017-02-15 14:40:52 +03:00
def tearDown ( self ) :
for f in [ self . output_file , self . unitfile_path ] :
try :
os . remove ( f )
except OSError :
# ignore error if log file doesn't exist
pass
self . reload ( )
if __name__ == ' __main__ ' :
2020-02-03 15:07:45 +03:00
unittest . main ( testRunner = unittest . TextTestRunner ( stream = sys . stdout , verbosity = 3 ) )