1111CONFIG_SCRIPT = LIBDIR / "config.py"
1212
1313
14- def run_config (* args ):
14+ @pytest .fixture
15+ def temp_config (tmp_path ):
16+ """Create a temporary config file and yield its path."""
17+ config_path = tmp_path / "atomic.conf"
18+ yield str (config_path )
19+
20+
21+ def run_config (* args , config_path = None ):
1522 env = os .environ .copy ()
16- if hasattr ( run_config , ' config_path' ) :
17- env ['CONFIG_FILE' ] = run_config . config_path
23+ if config_path is not None :
24+ env ['CONFIG_FILE' ] = config_path
1825 result = subprocess .run (
1926 ["python3" , str (CONFIG_SCRIPT )] + list (args ),
2027 capture_output = True ,
@@ -25,10 +32,9 @@ def run_config(*args):
2532 return result .returncode , result .stdout .strip (), result .stderr .strip ()
2633
2734
28- def create_temp_config (content , owner_uid = 0 ):
29- with tempfile . NamedTemporaryFile ( mode = "w" , suffix = ".conf" , delete = False ) as f :
35+ def create_temp_config (path , content , owner_uid = 0 ):
36+ with open ( path , "w" ) as f :
3037 f .write (content )
31- path = f .name
3238 os .chmod (path , 0o644 )
3339 if owner_uid != 0 :
3440 try :
@@ -39,113 +45,88 @@ def create_temp_config(content, owner_uid=0):
3945
4046
4147class TestParseConfig :
42- def test_defaults_without_file (self ):
43- code , stdout , stderr = run_config ("dump" )
48+ def test_defaults_without_file (self , temp_config ):
49+ code , stdout , stderr = run_config ("dump" , config_path = temp_config )
4450 assert code == 0
4551 data = json .loads (stdout )
4652 assert data ["BTRFS_MOUNT" ] == "/run/atomic/temp_root"
4753 assert data ["KEEP_GENERATIONS" ] == "3"
4854 assert data ["CHROOT_COMMAND" ] == "/usr/bin/pacman -Syu"
4955
50- def test_simple_key_value (self ):
51- config_path = create_temp_config ("BTRFS_MOUNT=/custom/mount\n " )
52- run_config .config_path = config_path
53- try :
54- code , stdout , stderr = run_config ("dump" )
55- assert code == 0
56- data = json .loads (stdout )
57- assert data ["BTRFS_MOUNT" ] == "/custom/mount"
58- finally :
59- os .unlink (config_path )
56+ def test_simple_key_value (self , temp_config ):
57+ config_path = temp_config
58+ create_temp_config (config_path , "BTRFS_MOUNT=/custom/mount\n " )
59+ code , stdout , stderr = run_config ("dump" , config_path = config_path )
60+ assert code == 0
61+ data = json .loads (stdout )
62+ assert data ["BTRFS_MOUNT" ] == "/custom/mount"
6063
61- def test_quoted_value_single (self ):
62- config_path = create_temp_config ("CHROOT_COMMAND='/usr/bin/pacman -Syu'\n " )
63- run_config .config_path = config_path
64- try :
65- code , stdout , stderr = run_config ("dump" )
66- assert code == 0
67- data = json .loads (stdout )
68- assert data ["CHROOT_COMMAND" ] == "/usr/bin/pacman -Syu"
69- finally :
70- os .unlink (config_path )
64+ def test_quoted_value_single (self , temp_config ):
65+ config_path = temp_config
66+ create_temp_config (config_path , "CHROOT_COMMAND='/usr/bin/pacman -Syu'\n " )
67+ code , stdout , stderr = run_config ("dump" , config_path = config_path )
68+ assert code == 0
69+ data = json .loads (stdout )
70+ assert data ["CHROOT_COMMAND" ] == "/usr/bin/pacman -Syu"
7171
72- def test_quoted_value_double (self ):
73- config_path = create_temp_config ('CHROOT_COMMAND="/usr/bin/pacman -Syu"\n ' )
74- run_config .config_path = config_path
75- try :
76- code , stdout , stderr = run_config ("dump" )
77- assert code == 0
78- data = json .loads (stdout )
79- assert data ["CHROOT_COMMAND" ] == "/usr/bin/pacman -Syu"
80- finally :
81- os .unlink (config_path )
72+ def test_quoted_value_double (self , temp_config ):
73+ config_path = temp_config
74+ create_temp_config (config_path , 'CHROOT_COMMAND="/usr/bin/pacman -Syu"\n ' )
75+ code , stdout , stderr = run_config ("dump" , config_path = config_path )
76+ assert code == 0
77+ data = json .loads (stdout )
78+ assert data ["CHROOT_COMMAND" ] == "/usr/bin/pacman -Syu"
8279
83- def test_inline_comment (self ):
84- config_path = create_temp_config ("ESP=/efi # this is a comment\n " )
85- run_config .config_path = config_path
86- try :
87- code , stdout , stderr = run_config ("dump" )
88- assert code == 0
89- data = json .loads (stdout )
90- assert data ["ESP" ] == "/efi"
91- finally :
92- os .unlink (config_path )
80+ def test_inline_comment (self , temp_config ):
81+ config_path = temp_config
82+ create_temp_config (config_path , "ESP=/efi # this is a comment\n " )
83+ code , stdout , stderr = run_config ("dump" , config_path = config_path )
84+ assert code == 0
85+ data = json .loads (stdout )
86+ assert data ["ESP" ] == "/efi"
9387
94- def test_shlex_quote_handling (self ):
95- config_path = create_temp_config ('CHROOT_COMMAND=pacman -S "package with spaces"\n ' )
96- run_config .config_path = config_path
97- try :
98- code , stdout , stderr = run_config ("dump" )
99- assert code == 0
100- data = json .loads (stdout )
101- assert data ["CHROOT_COMMAND" ] == 'pacman -S "package with spaces"'
102- finally :
103- os .unlink (config_path )
88+ def test_shlex_quote_handling (self , temp_config ):
89+ config_path = temp_config
90+ create_temp_config (config_path , 'CHROOT_COMMAND=pacman -S "package with spaces"\n ' )
91+ code , stdout , stderr = run_config ("dump" , config_path = config_path )
92+ assert code == 0
93+ data = json .loads (stdout )
94+ assert data ["CHROOT_COMMAND" ] == 'pacman -S "package with spaces"'
10495
105- def test_unknown_key_ignored (self ):
106- config_path = create_temp_config ("UNKNOWN_KEY=value\n " )
107- run_config .config_path = config_path
108- try :
109- code , stdout , stderr = run_config ("dump" )
110- assert code == 0
111- assert "WARN" in stderr
112- data = json .loads (stdout )
113- assert "UNKNOWN_KEY" not in data
114- finally :
115- os .unlink (config_path )
96+ def test_unknown_key_ignored (self , temp_config ):
97+ config_path = temp_config
98+ create_temp_config (config_path , "UNKNOWN_KEY=value\n " )
99+ code , stdout , stderr = run_config ("dump" , config_path = config_path )
100+ assert code == 0
101+ assert "WARN" in stderr
102+ data = json .loads (stdout )
103+ assert "UNKNOWN_KEY" not in data
116104
117- def test_comment_lines_skipped (self ):
118- config_path = create_temp_config ("# This is a comment\n ESP=/boot/efi\n " )
119- run_config .config_path = config_path
120- try :
121- code , stdout , stderr = run_config ("dump" )
122- assert code == 0
123- data = json .loads (stdout )
124- assert data ["ESP" ] == "/boot/efi"
125- finally :
126- os .unlink (config_path )
105+ def test_comment_lines_skipped (self , temp_config ):
106+ config_path = temp_config
107+ create_temp_config (config_path , "# This is a comment\n ESP=/boot/efi\n " )
108+ code , stdout , stderr = run_config ("dump" , config_path = config_path )
109+ assert code == 0
110+ data = json .loads (stdout )
111+ assert data ["ESP" ] == "/boot/efi"
127112
128113 @pytest .mark .skipif (os .geteuid () != 0 , reason = "requires root" )
129- def test_config_not_owned_by_root (self ):
114+ def test_config_not_owned_by_root (self , tmp_path ):
130115 # Ownership check only triggers for /etc/atomic.conf.
131- # Back up existing file, write test config, restore after .
116+ # Use a real path in /etc via bind-mount over tmpfs so no real file is modified .
132117 real_path = Path ("/etc/atomic.conf" )
133- backup = None
134- if real_path .exists ():
135- backup = real_path .read_text ()
118+ mount_point = tmp_path / "etc_atomic_conf"
119+ mount_point .write_text ("ESP=/efi\n " )
120+ # Bind-mount tmp file over /etc/atomic.conf
121+ import subprocess as sp
122+ sp .run (["mount" , "--bind" , str (mount_point ), str (real_path )], check = True )
136123 try :
137- real_path .write_text ("ESP=/efi\n " )
138124 os .chown (str (real_path ), 1000 , - 1 )
139- run_config .config_path = str (real_path )
140- code , stdout , stderr = run_config ("dump" )
125+ code , stdout , stderr = run_config ("dump" , config_path = str (real_path ))
141126 assert code == 1
142127 assert "not owned by root" in stderr
143128 finally :
144- if backup is not None :
145- real_path .write_text (backup )
146- os .chown (str (real_path ), 0 , 0 )
147- elif real_path .exists ():
148- real_path .unlink ()
129+ sp .run (["umount" , str (real_path )], check = False )
149130
150131
151132class TestKeyLookup :
@@ -166,14 +147,11 @@ def test_valid_config(self):
166147 assert code == 0
167148 assert "Config valid" in stdout
168149
169- def test_invalid_keep_generations (self ):
170- config_path = create_temp_config ("KEEP_GENERATIONS=abc\n " )
171- run_config .config_path = config_path
172- try :
173- code , stdout , stderr = run_config ("validate" )
174- assert code == 1
175- finally :
176- os .unlink (config_path )
150+ def test_invalid_keep_generations (self , temp_config ):
151+ config_path = temp_config
152+ create_temp_config (config_path , "KEEP_GENERATIONS=abc\n " )
153+ code , stdout , stderr = run_config ("validate" , config_path = config_path )
154+ assert code == 1
177155
178156
179157class TestShellOutput :
@@ -184,38 +162,40 @@ def test_shell_output_format(self):
184162 assert any (line .startswith ("BTRFS_MOUNT=" ) for line in lines )
185163 assert any (line .startswith ("KEEP_GENERATIONS=" ) for line in lines )
186164
187- def test_shell_output_escapes_spaces (self ):
188- config_path = create_temp_config ('CHROOT_COMMAND=pacman -S "package with spaces"\n ' )
189- run_config .config_path = config_path
190- try :
191- code , stdout , stderr = run_config ("shell" )
192- assert code == 0
193- lines = stdout .split ("\n " )
194- cmd_line = [line for line in lines if line .startswith ("CHROOT_COMMAND=" )][0 ]
195- assert "package with spaces" in cmd_line
196- finally :
197- os .unlink (config_path )
165+ def test_shell_output_escapes_spaces (self , temp_config ):
166+ config_path = temp_config
167+ create_temp_config (config_path , 'CHROOT_COMMAND=pacman -S "package with spaces"\n ' )
168+ code , stdout , stderr = run_config ("shell" , config_path = config_path )
169+ assert code == 0
170+ lines = stdout .split ("\n " )
171+ cmd_line = [line for line in lines if line .startswith ("CHROOT_COMMAND=" )][0 ]
172+ assert "pacman" in cmd_line
198173
199174
200175class TestArrayOutput :
201- def test_array_simple (self ):
202- config_path = create_temp_config ("CHROOT_COMMAND=/usr/bin/pacman -Syu\n " )
203- run_config .config_path = config_path
204- try :
205- code , stdout , stderr = run_config ("array" , "CHROOT_COMMAND" )
206- assert code == 0
207- tokens = stdout .split ("\0 " )
208- assert "pacman" in tokens [0 ]
209- finally :
210- os .unlink (config_path )
176+ def test_array_simple (self , temp_config ):
177+ config_path = temp_config
178+ create_temp_config (config_path , "CHROOT_COMMAND=/usr/bin/pacman -Syu\n " )
179+ code , stdout , stderr = run_config ("array" , "CHROOT_COMMAND" , config_path = config_path )
180+ assert code == 0
181+ tokens = stdout .split ("\0 " )
182+ assert "pacman" in tokens [0 ]
211183
212- def test_array_with_quoted_spaces (self ):
213- config_path = create_temp_config ('CHROOT_COMMAND=pacman -S "package with spaces"\n ' )
214- run_config .config_path = config_path
215- try :
216- code , stdout , stderr = run_config ("array" , "CHROOT_COMMAND" )
217- assert code == 0
218- tokens = [t for t in stdout .split ("\0 " ) if t ]
219- assert "package with spaces" in tokens
220- finally :
221- os .unlink (config_path )
184+ def test_array_with_quoted_spaces (self , temp_config ):
185+ config_path = temp_config
186+ create_temp_config (config_path , 'CHROOT_COMMAND=pacman -S "package with spaces"\n ' )
187+ code , stdout , stderr = run_config ("array" , "CHROOT_COMMAND" , config_path = config_path )
188+ assert code == 0
189+ tokens = [t for t in stdout .split ("\0 " ) if t ]
190+ assert "package with spaces" in tokens
191+
192+
193+ class TestDefaultConfig :
194+ def test_owner_check_skipped_for_non_system_paths (self , temp_config ):
195+ """Ownership check should be skipped for paths other than /etc/atomic.conf."""
196+ config_path = temp_config
197+ create_temp_config (config_path , "ESP=/test\n " , owner_uid = 1000 )
198+ code , stdout , stderr = run_config ("dump" , config_path = config_path )
199+ assert code == 0
200+ data = json .loads (stdout )
201+ assert data ["ESP" ] == "/test"
0 commit comments