70913c8d9abc4d6af17fde9ae55168cfdb859136
[platform/kernel/u-boot.git] / test / py / tests / test_env.py
1 # SPDX-License-Identifier: GPL-2.0
2 # Copyright (c) 2015 Stephen Warren
3 # Copyright (c) 2015-2016, NVIDIA CORPORATION. All rights reserved.
4
5 # Test operation of shell commands relating to environment variables.
6
7 import os
8 import os.path
9 from subprocess import call, check_call, CalledProcessError
10
11 import pytest
12 import u_boot_utils
13
14 # FIXME: This might be useful for other tests;
15 # perhaps refactor it into ConsoleBase or some other state object?
16 class StateTestEnv(object):
17     """Container that represents the state of all U-Boot environment variables.
18     This enables quick determination of existant/non-existant variable
19     names.
20     """
21
22     def __init__(self, u_boot_console):
23         """Initialize a new StateTestEnv object.
24
25         Args:
26             u_boot_console: A U-Boot console.
27
28         Returns:
29             Nothing.
30         """
31
32         self.u_boot_console = u_boot_console
33         self.get_env()
34         self.set_var = self.get_non_existent_var()
35
36     def get_env(self):
37         """Read all current environment variables from U-Boot.
38
39         Args:
40             None.
41
42         Returns:
43             Nothing.
44         """
45
46         if self.u_boot_console.config.buildconfig.get(
47                 'config_version_variable', 'n') == 'y':
48             with self.u_boot_console.disable_check('main_signon'):
49                 response = self.u_boot_console.run_command('printenv')
50         else:
51             response = self.u_boot_console.run_command('printenv')
52         self.env = {}
53         for l in response.splitlines():
54             if not '=' in l:
55                 continue
56             (var, value) = l.split('=', 1)
57             self.env[var] = value
58
59     def get_existent_var(self):
60         """Return the name of an environment variable that exists.
61
62         Args:
63             None.
64
65         Returns:
66             The name of an environment variable.
67         """
68
69         for var in self.env:
70             return var
71
72     def get_non_existent_var(self):
73         """Return the name of an environment variable that does not exist.
74
75         Args:
76             None.
77
78         Returns:
79             The name of an environment variable.
80         """
81
82         n = 0
83         while True:
84             var = 'test_env_' + str(n)
85             if var not in self.env:
86                 return var
87             n += 1
88
89 ste = None
90 @pytest.fixture(scope='function')
91 def state_test_env(u_boot_console):
92     """pytest fixture to provide a StateTestEnv object to tests."""
93
94     global ste
95     if not ste:
96         ste = StateTestEnv(u_boot_console)
97     return ste
98
99 def unset_var(state_test_env, var):
100     """Unset an environment variable.
101
102     This both executes a U-Boot shell command and updates a StateTestEnv
103     object.
104
105     Args:
106         state_test_env: The StateTestEnv object to update.
107         var: The variable name to unset.
108
109     Returns:
110         Nothing.
111     """
112
113     state_test_env.u_boot_console.run_command('setenv %s' % var)
114     if var in state_test_env.env:
115         del state_test_env.env[var]
116
117 def set_var(state_test_env, var, value):
118     """Set an environment variable.
119
120     This both executes a U-Boot shell command and updates a StateTestEnv
121     object.
122
123     Args:
124         state_test_env: The StateTestEnv object to update.
125         var: The variable name to set.
126         value: The value to set the variable to.
127
128     Returns:
129         Nothing.
130     """
131
132     bc = state_test_env.u_boot_console.config.buildconfig
133     if bc.get('config_hush_parser', None):
134         quote = '"'
135     else:
136         quote = ''
137         if ' ' in value:
138             pytest.skip('Space in variable value on non-Hush shell')
139
140     state_test_env.u_boot_console.run_command(
141         'setenv %s %s%s%s' % (var, quote, value, quote))
142     state_test_env.env[var] = value
143
144 def validate_empty(state_test_env, var):
145     """Validate that a variable is not set, using U-Boot shell commands.
146
147     Args:
148         var: The variable name to test.
149
150     Returns:
151         Nothing.
152     """
153
154     response = state_test_env.u_boot_console.run_command('echo $%s' % var)
155     assert response == ''
156
157 def validate_set(state_test_env, var, value):
158     """Validate that a variable is set, using U-Boot shell commands.
159
160     Args:
161         var: The variable name to test.
162         value: The value the variable is expected to have.
163
164     Returns:
165         Nothing.
166     """
167
168     # echo does not preserve leading, internal, or trailing whitespace in the
169     # value. printenv does, and hence allows more complete testing.
170     response = state_test_env.u_boot_console.run_command('printenv %s' % var)
171     assert response == ('%s=%s' % (var, value))
172
173 def test_env_echo_exists(state_test_env):
174     """Test echoing a variable that exists."""
175
176     var = state_test_env.get_existent_var()
177     value = state_test_env.env[var]
178     validate_set(state_test_env, var, value)
179
180 @pytest.mark.buildconfigspec('cmd_echo')
181 def test_env_echo_non_existent(state_test_env):
182     """Test echoing a variable that doesn't exist."""
183
184     var = state_test_env.set_var
185     validate_empty(state_test_env, var)
186
187 def test_env_printenv_non_existent(state_test_env):
188     """Test printenv error message for non-existant variables."""
189
190     var = state_test_env.set_var
191     c = state_test_env.u_boot_console
192     with c.disable_check('error_notification'):
193         response = c.run_command('printenv %s' % var)
194     assert(response == '## Error: "%s" not defined' % var)
195
196 @pytest.mark.buildconfigspec('cmd_echo')
197 def test_env_unset_non_existent(state_test_env):
198     """Test unsetting a nonexistent variable."""
199
200     var = state_test_env.get_non_existent_var()
201     unset_var(state_test_env, var)
202     validate_empty(state_test_env, var)
203
204 def test_env_set_non_existent(state_test_env):
205     """Test set a non-existant variable."""
206
207     var = state_test_env.set_var
208     value = 'foo'
209     set_var(state_test_env, var, value)
210     validate_set(state_test_env, var, value)
211
212 def test_env_set_existing(state_test_env):
213     """Test setting an existant variable."""
214
215     var = state_test_env.set_var
216     value = 'bar'
217     set_var(state_test_env, var, value)
218     validate_set(state_test_env, var, value)
219
220 @pytest.mark.buildconfigspec('cmd_echo')
221 def test_env_unset_existing(state_test_env):
222     """Test unsetting a variable."""
223
224     var = state_test_env.set_var
225     unset_var(state_test_env, var)
226     validate_empty(state_test_env, var)
227
228 def test_env_expansion_spaces(state_test_env):
229     """Test expanding a variable that contains a space in its value."""
230
231     var_space = None
232     var_test = None
233     try:
234         var_space = state_test_env.get_non_existent_var()
235         set_var(state_test_env, var_space, ' ')
236
237         var_test = state_test_env.get_non_existent_var()
238         value = ' 1${%(var_space)s}${%(var_space)s} 2 ' % locals()
239         set_var(state_test_env, var_test, value)
240         value = ' 1   2 '
241         validate_set(state_test_env, var_test, value)
242     finally:
243         if var_space:
244             unset_var(state_test_env, var_space)
245         if var_test:
246             unset_var(state_test_env, var_test)
247
248 @pytest.mark.buildconfigspec('cmd_importenv')
249 def test_env_import_checksum_no_size(state_test_env):
250     """Test that omitted ('-') size parameter with checksum validation fails the
251        env import function.
252     """
253     c = state_test_env.u_boot_console
254     ram_base = u_boot_utils.find_ram_base(state_test_env.u_boot_console)
255     addr = '%08x' % ram_base
256
257     with c.disable_check('error_notification'):
258         response = c.run_command('env import -c %s -' % addr)
259     assert(response == '## Error: external checksum format must pass size')
260
261 @pytest.mark.buildconfigspec('cmd_importenv')
262 def test_env_import_whitelist_checksum_no_size(state_test_env):
263     """Test that omitted ('-') size parameter with checksum validation fails the
264        env import function when variables are passed as parameters.
265     """
266     c = state_test_env.u_boot_console
267     ram_base = u_boot_utils.find_ram_base(state_test_env.u_boot_console)
268     addr = '%08x' % ram_base
269
270     with c.disable_check('error_notification'):
271         response = c.run_command('env import -c %s - foo1 foo2 foo4' % addr)
272     assert(response == '## Error: external checksum format must pass size')
273
274 @pytest.mark.buildconfigspec('cmd_exportenv')
275 @pytest.mark.buildconfigspec('cmd_importenv')
276 def test_env_import_whitelist(state_test_env):
277     """Test importing only a handful of env variables from an environment."""
278     c = state_test_env.u_boot_console
279     ram_base = u_boot_utils.find_ram_base(state_test_env.u_boot_console)
280     addr = '%08x' % ram_base
281
282     set_var(state_test_env, 'foo1', 'bar1')
283     set_var(state_test_env, 'foo2', 'bar2')
284     set_var(state_test_env, 'foo3', 'bar3')
285
286     c.run_command('env export %s' % addr)
287
288     unset_var(state_test_env, 'foo1')
289     set_var(state_test_env, 'foo2', 'test2')
290     set_var(state_test_env, 'foo4', 'bar4')
291
292     # no foo1 in current env, foo2 overridden, foo3 should be of the value
293     # before exporting and foo4 should be of the value before importing.
294     c.run_command('env import %s - foo1 foo2 foo4' % addr)
295
296     validate_set(state_test_env, 'foo1', 'bar1')
297     validate_set(state_test_env, 'foo2', 'bar2')
298     validate_set(state_test_env, 'foo3', 'bar3')
299     validate_set(state_test_env, 'foo4', 'bar4')
300
301     # Cleanup test environment
302     unset_var(state_test_env, 'foo1')
303     unset_var(state_test_env, 'foo2')
304     unset_var(state_test_env, 'foo3')
305     unset_var(state_test_env, 'foo4')
306
307 @pytest.mark.buildconfigspec('cmd_exportenv')
308 @pytest.mark.buildconfigspec('cmd_importenv')
309 def test_env_import_whitelist_delete(state_test_env):
310
311     """Test importing only a handful of env variables from an environment, with.
312        deletion if a var A that is passed to env import is not in the
313        environment to be imported.
314     """
315     c = state_test_env.u_boot_console
316     ram_base = u_boot_utils.find_ram_base(state_test_env.u_boot_console)
317     addr = '%08x' % ram_base
318
319     set_var(state_test_env, 'foo1', 'bar1')
320     set_var(state_test_env, 'foo2', 'bar2')
321     set_var(state_test_env, 'foo3', 'bar3')
322
323     c.run_command('env export %s' % addr)
324
325     unset_var(state_test_env, 'foo1')
326     set_var(state_test_env, 'foo2', 'test2')
327     set_var(state_test_env, 'foo4', 'bar4')
328
329     # no foo1 in current env, foo2 overridden, foo3 should be of the value
330     # before exporting and foo4 should be empty.
331     c.run_command('env import -d %s - foo1 foo2 foo4' % addr)
332
333     validate_set(state_test_env, 'foo1', 'bar1')
334     validate_set(state_test_env, 'foo2', 'bar2')
335     validate_set(state_test_env, 'foo3', 'bar3')
336     validate_empty(state_test_env, 'foo4')
337
338     # Cleanup test environment
339     unset_var(state_test_env, 'foo1')
340     unset_var(state_test_env, 'foo2')
341     unset_var(state_test_env, 'foo3')
342     unset_var(state_test_env, 'foo4')
343
344 @pytest.mark.buildconfigspec('cmd_nvedit_info')
345 def test_env_info(state_test_env):
346
347     """Test 'env info' command with all possible options.
348     """
349     c = state_test_env.u_boot_console
350
351     response = c.run_command('env info')
352     nb_line = 0
353     for l in response.split('\n'):
354         if 'env_valid = ' in l:
355             assert '= invalid' in l or '= valid' in l or '= redundant' in l
356             nb_line += 1
357         elif 'env_ready =' in l or 'env_use_default =' in l:
358             assert '= true' in l or '= false' in l
359             nb_line += 1
360         else:
361             assert true
362     assert nb_line == 3
363
364     response = c.run_command('env info -p -d')
365     assert 'Default environment is used' in response or "Environment was loaded from persistent storage" in response
366     assert 'Environment can be persisted' in response or "Environment cannot be persisted" in response
367
368     response = c.run_command('env info -p -d -q')
369     assert response == ""
370
371     response = c.run_command('env info -p -q')
372     assert response == ""
373
374     response = c.run_command('env info -d -q')
375     assert response == ""
376
377 @pytest.mark.boardspec('sandbox')
378 @pytest.mark.buildconfigspec('cmd_nvedit_info')
379 @pytest.mark.buildconfigspec('cmd_echo')
380 def test_env_info_sandbox(state_test_env):
381     """Test 'env info' command result with several options on sandbox
382        with a known ENV configuration: ready & default & persistent
383     """
384     c = state_test_env.u_boot_console
385
386     response = c.run_command('env info')
387     assert 'env_ready = true' in response
388     assert 'env_use_default = true' in response
389
390     response = c.run_command('env info -p -d')
391     assert 'Default environment is used' in response
392     assert 'Environment cannot be persisted' in response
393
394     response = c.run_command('env info -d -q')
395     response = c.run_command('echo $?')
396     assert response == "0"
397
398     response = c.run_command('env info -p -q')
399     response = c.run_command('echo $?')
400     assert response == "1"
401
402     response = c.run_command('env info -d -p -q')
403     response = c.run_command('echo $?')
404     assert response == "1"
405
406 def mk_env_ext4(state_test_env):
407
408     """Create a empty ext4 file system volume."""
409     c = state_test_env.u_boot_console
410     filename = 'env.ext4.img'
411     persistent = c.config.persistent_data_dir + '/' + filename
412     fs_img = c.config.result_dir  + '/' + filename
413
414     if os.path.exists(persistent):
415         c.log.action('Disk image file ' + persistent + ' already exists')
416     else:
417         try:
418             u_boot_utils.run_and_log(c, 'dd if=/dev/zero of=%s bs=1M count=16' % persistent)
419             u_boot_utils.run_and_log(c, 'mkfs.ext4 -O ^metadata_csum %s' % persistent)
420         except CalledProcessError:
421             call('rm -f %s' % persistent, shell=True)
422             raise
423
424     u_boot_utils.run_and_log(c, ['cp',  '-f', persistent, fs_img])
425     return fs_img
426
427 @pytest.mark.boardspec('sandbox')
428 @pytest.mark.buildconfigspec('cmd_echo')
429 @pytest.mark.buildconfigspec('cmd_nvedit_info')
430 @pytest.mark.buildconfigspec('cmd_nvedit_load')
431 @pytest.mark.buildconfigspec('cmd_nvedit_select')
432 @pytest.mark.buildconfigspec('env_is_in_ext4')
433 def test_env_ext4(state_test_env):
434
435     """Test ENV in EXT4 on sandbox."""
436     c = state_test_env.u_boot_console
437     fs_img = ''
438     try:
439         fs_img = mk_env_ext4(state_test_env)
440
441         c.run_command('host bind 0  %s' % fs_img)
442
443         response = c.run_command('ext4ls host 0:0')
444         assert 'uboot.env' not in response
445
446         # force env location: EXT4 (prio 1 in sandbox)
447         response = c.run_command('env select EXT4')
448         assert 'Select Environment on EXT4: OK' in response
449
450         response = c.run_command('env save')
451         assert 'Saving Environment to EXT4' in response
452
453         response = c.run_command('env load')
454         assert 'Loading Environment from EXT4... OK' in response
455
456         response = c.run_command('ext4ls host 0:0')
457         assert '8192 uboot.env' in response
458
459         response = c.run_command('env info')
460         assert 'env_valid = valid' in response
461         assert 'env_ready = true' in response
462         assert 'env_use_default = false' in response
463
464         response = c.run_command('env info -p -d')
465         assert 'Environment was loaded from persistent storage' in response
466         assert 'Environment can be persisted' in response
467
468         response = c.run_command('env info -d -q')
469         assert response == ""
470         response = c.run_command('echo $?')
471         assert response == "1"
472
473         response = c.run_command('env info -p -q')
474         assert response == ""
475         response = c.run_command('echo $?')
476         assert response == "0"
477
478         # restore env location: NOWHERE (prio 0 in sandbox)
479         response = c.run_command('env select nowhere')
480         assert 'Select Environment on nowhere: OK' in response
481
482         response = c.run_command('env load')
483         assert 'Loading Environment from nowhere... OK' in response
484
485         response = c.run_command('env info')
486         assert 'env_valid = invalid' in response
487         assert 'env_ready = true' in response
488         assert 'env_use_default = true' in response
489
490         response = c.run_command('env info -p -d')
491         assert 'Default environment is used' in response
492         assert 'Environment cannot be persisted' in response
493
494     finally:
495         if fs_img:
496             call('rm -f %s' % fs_img, shell=True)