1 # SPDX-License-Identifier: GPL-2.0
2 # Copyright (c) 2015 Stephen Warren
3 # Copyright (c) 2015-2016, NVIDIA CORPORATION. All rights reserved.
5 # Test operation of shell commands relating to environment variables.
9 from subprocess import call, check_call, CalledProcessError
15 # FIXME: This might be useful for other tests;
16 # perhaps refactor it into ConsoleBase or some other state object?
17 class StateTestEnv(object):
18 """Container that represents the state of all U-Boot environment variables.
19 This enables quick determination of existant/non-existant variable
23 def __init__(self, u_boot_console):
24 """Initialize a new StateTestEnv object.
27 u_boot_console: A U-Boot console.
33 self.u_boot_console = u_boot_console
35 self.set_var = self.get_non_existent_var()
38 """Read all current environment variables from U-Boot.
47 if self.u_boot_console.config.buildconfig.get(
48 'config_version_variable', 'n') == 'y':
49 with self.u_boot_console.disable_check('main_signon'):
50 response = self.u_boot_console.run_command('printenv')
52 response = self.u_boot_console.run_command('printenv')
54 for l in response.splitlines():
57 (var, value) = l.split('=', 1)
60 def get_existent_var(self):
61 """Return the name of an environment variable that exists.
67 The name of an environment variable.
73 def get_non_existent_var(self):
74 """Return the name of an environment variable that does not exist.
80 The name of an environment variable.
85 var = 'test_env_' + str(n)
86 if var not in self.env:
91 @pytest.fixture(scope='function')
92 def state_test_env(u_boot_console):
93 """pytest fixture to provide a StateTestEnv object to tests."""
97 ste = StateTestEnv(u_boot_console)
100 def unset_var(state_test_env, var):
101 """Unset an environment variable.
103 This both executes a U-Boot shell command and updates a StateTestEnv
107 state_test_env: The StateTestEnv object to update.
108 var: The variable name to unset.
114 state_test_env.u_boot_console.run_command('setenv %s' % var)
115 if var in state_test_env.env:
116 del state_test_env.env[var]
118 def set_var(state_test_env, var, value):
119 """Set an environment variable.
121 This both executes a U-Boot shell command and updates a StateTestEnv
125 state_test_env: The StateTestEnv object to update.
126 var: The variable name to set.
127 value: The value to set the variable to.
133 bc = state_test_env.u_boot_console.config.buildconfig
134 if bc.get('config_hush_parser', None):
139 pytest.skip('Space in variable value on non-Hush shell')
141 state_test_env.u_boot_console.run_command(
142 'setenv %s %s%s%s' % (var, quote, value, quote))
143 state_test_env.env[var] = value
145 def validate_empty(state_test_env, var):
146 """Validate that a variable is not set, using U-Boot shell commands.
149 var: The variable name to test.
155 response = state_test_env.u_boot_console.run_command('echo ${%s}' % var)
156 assert response == ''
158 def validate_set(state_test_env, var, value):
159 """Validate that a variable is set, using U-Boot shell commands.
162 var: The variable name to test.
163 value: The value the variable is expected to have.
169 # echo does not preserve leading, internal, or trailing whitespace in the
170 # value. printenv does, and hence allows more complete testing.
171 response = state_test_env.u_boot_console.run_command('printenv %s' % var)
172 assert response == ('%s=%s' % (var, value))
174 def test_env_echo_exists(state_test_env):
175 """Test echoing a variable that exists."""
177 var = state_test_env.get_existent_var()
178 value = state_test_env.env[var]
179 validate_set(state_test_env, var, value)
181 @pytest.mark.buildconfigspec('cmd_echo')
182 def test_env_echo_non_existent(state_test_env):
183 """Test echoing a variable that doesn't exist."""
185 var = state_test_env.set_var
186 validate_empty(state_test_env, var)
188 def test_env_printenv_non_existent(state_test_env):
189 """Test printenv error message for non-existant variables."""
191 var = state_test_env.set_var
192 c = state_test_env.u_boot_console
193 with c.disable_check('error_notification'):
194 response = c.run_command('printenv %s' % var)
195 assert(response == '## Error: "%s" not defined' % var)
197 @pytest.mark.buildconfigspec('cmd_echo')
198 def test_env_unset_non_existent(state_test_env):
199 """Test unsetting a nonexistent variable."""
201 var = state_test_env.get_non_existent_var()
202 unset_var(state_test_env, var)
203 validate_empty(state_test_env, var)
205 def test_env_set_non_existent(state_test_env):
206 """Test set a non-existant variable."""
208 var = state_test_env.set_var
210 set_var(state_test_env, var, value)
211 validate_set(state_test_env, var, value)
213 def test_env_set_existing(state_test_env):
214 """Test setting an existant variable."""
216 var = state_test_env.set_var
218 set_var(state_test_env, var, value)
219 validate_set(state_test_env, var, value)
221 @pytest.mark.buildconfigspec('cmd_echo')
222 def test_env_unset_existing(state_test_env):
223 """Test unsetting a variable."""
225 var = state_test_env.set_var
226 unset_var(state_test_env, var)
227 validate_empty(state_test_env, var)
229 def test_env_expansion_spaces(state_test_env):
230 """Test expanding a variable that contains a space in its value."""
235 var_space = state_test_env.get_non_existent_var()
236 set_var(state_test_env, var_space, ' ')
238 var_test = state_test_env.get_non_existent_var()
239 value = ' 1${%(var_space)s}${%(var_space)s} 2 ' % locals()
240 set_var(state_test_env, var_test, value)
242 validate_set(state_test_env, var_test, value)
245 unset_var(state_test_env, var_space)
247 unset_var(state_test_env, var_test)
249 @pytest.mark.buildconfigspec('cmd_importenv')
250 def test_env_import_checksum_no_size(state_test_env):
251 """Test that omitted ('-') size parameter with checksum validation fails the
254 c = state_test_env.u_boot_console
255 ram_base = u_boot_utils.find_ram_base(state_test_env.u_boot_console)
256 addr = '%08x' % ram_base
258 with c.disable_check('error_notification'):
259 response = c.run_command('env import -c %s -' % addr)
260 assert(response == '## Error: external checksum format must pass size')
262 @pytest.mark.buildconfigspec('cmd_importenv')
263 def test_env_import_whitelist_checksum_no_size(state_test_env):
264 """Test that omitted ('-') size parameter with checksum validation fails the
265 env import function when variables are passed as parameters.
267 c = state_test_env.u_boot_console
268 ram_base = u_boot_utils.find_ram_base(state_test_env.u_boot_console)
269 addr = '%08x' % ram_base
271 with c.disable_check('error_notification'):
272 response = c.run_command('env import -c %s - foo1 foo2 foo4' % addr)
273 assert(response == '## Error: external checksum format must pass size')
275 @pytest.mark.buildconfigspec('cmd_exportenv')
276 @pytest.mark.buildconfigspec('cmd_importenv')
277 def test_env_import_whitelist(state_test_env):
278 """Test importing only a handful of env variables from an environment."""
279 c = state_test_env.u_boot_console
280 ram_base = u_boot_utils.find_ram_base(state_test_env.u_boot_console)
281 addr = '%08x' % ram_base
283 set_var(state_test_env, 'foo1', 'bar1')
284 set_var(state_test_env, 'foo2', 'bar2')
285 set_var(state_test_env, 'foo3', 'bar3')
287 c.run_command('env export %s' % addr)
289 unset_var(state_test_env, 'foo1')
290 set_var(state_test_env, 'foo2', 'test2')
291 set_var(state_test_env, 'foo4', 'bar4')
293 # no foo1 in current env, foo2 overridden, foo3 should be of the value
294 # before exporting and foo4 should be of the value before importing.
295 c.run_command('env import %s - foo1 foo2 foo4' % addr)
297 validate_set(state_test_env, 'foo1', 'bar1')
298 validate_set(state_test_env, 'foo2', 'bar2')
299 validate_set(state_test_env, 'foo3', 'bar3')
300 validate_set(state_test_env, 'foo4', 'bar4')
302 # Cleanup test environment
303 unset_var(state_test_env, 'foo1')
304 unset_var(state_test_env, 'foo2')
305 unset_var(state_test_env, 'foo3')
306 unset_var(state_test_env, 'foo4')
308 @pytest.mark.buildconfigspec('cmd_exportenv')
309 @pytest.mark.buildconfigspec('cmd_importenv')
310 def test_env_import_whitelist_delete(state_test_env):
312 """Test importing only a handful of env variables from an environment, with.
313 deletion if a var A that is passed to env import is not in the
314 environment to be imported.
316 c = state_test_env.u_boot_console
317 ram_base = u_boot_utils.find_ram_base(state_test_env.u_boot_console)
318 addr = '%08x' % ram_base
320 set_var(state_test_env, 'foo1', 'bar1')
321 set_var(state_test_env, 'foo2', 'bar2')
322 set_var(state_test_env, 'foo3', 'bar3')
324 c.run_command('env export %s' % addr)
326 unset_var(state_test_env, 'foo1')
327 set_var(state_test_env, 'foo2', 'test2')
328 set_var(state_test_env, 'foo4', 'bar4')
330 # no foo1 in current env, foo2 overridden, foo3 should be of the value
331 # before exporting and foo4 should be empty.
332 c.run_command('env import -d %s - foo1 foo2 foo4' % addr)
334 validate_set(state_test_env, 'foo1', 'bar1')
335 validate_set(state_test_env, 'foo2', 'bar2')
336 validate_set(state_test_env, 'foo3', 'bar3')
337 validate_empty(state_test_env, 'foo4')
339 # Cleanup test environment
340 unset_var(state_test_env, 'foo1')
341 unset_var(state_test_env, 'foo2')
342 unset_var(state_test_env, 'foo3')
343 unset_var(state_test_env, 'foo4')
345 @pytest.mark.buildconfigspec('cmd_nvedit_info')
346 def test_env_info(state_test_env):
348 """Test 'env info' command with all possible options.
350 c = state_test_env.u_boot_console
352 response = c.run_command('env info')
354 for l in response.split('\n'):
355 if 'env_valid = ' in l:
356 assert '= invalid' in l or '= valid' in l or '= redundant' in l
358 elif 'env_ready =' in l or 'env_use_default =' in l:
359 assert '= true' in l or '= false' in l
365 response = c.run_command('env info -p -d')
366 assert 'Default environment is used' in response or "Environment was loaded from persistent storage" in response
367 assert 'Environment can be persisted' in response or "Environment cannot be persisted" in response
369 response = c.run_command('env info -p -d -q')
370 assert response == ""
372 response = c.run_command('env info -p -q')
373 assert response == ""
375 response = c.run_command('env info -d -q')
376 assert response == ""
378 @pytest.mark.boardspec('sandbox')
379 @pytest.mark.buildconfigspec('cmd_nvedit_info')
380 @pytest.mark.buildconfigspec('cmd_echo')
381 def test_env_info_sandbox(state_test_env):
382 """Test 'env info' command result with several options on sandbox
383 with a known ENV configuration: ready & default & persistent
385 c = state_test_env.u_boot_console
387 response = c.run_command('env info')
388 assert 'env_ready = true' in response
389 assert 'env_use_default = true' in response
391 response = c.run_command('env info -p -d')
392 assert 'Default environment is used' in response
393 assert 'Environment cannot be persisted' in response
395 response = c.run_command('env info -d -q')
396 response = c.run_command('echo $?')
397 assert response == "0"
399 response = c.run_command('env info -p -q')
400 response = c.run_command('echo $?')
401 assert response == "1"
403 response = c.run_command('env info -d -p -q')
404 response = c.run_command('echo $?')
405 assert response == "1"
407 def mk_env_ext4(state_test_env):
409 """Create a empty ext4 file system volume."""
410 c = state_test_env.u_boot_console
411 filename = 'env.ext4.img'
412 persistent = c.config.persistent_data_dir + '/' + filename
413 fs_img = c.config.result_dir + '/' + filename
415 if os.path.exists(persistent):
416 c.log.action('Disk image file ' + persistent + ' already exists')
418 # Some distributions do not add /sbin to the default PATH, where mkfs.ext4 lives
419 os.environ["PATH"] += os.pathsep + '/sbin'
421 u_boot_utils.run_and_log(c, 'dd if=/dev/zero of=%s bs=1M count=16' % persistent)
422 u_boot_utils.run_and_log(c, 'mkfs.ext4 %s' % persistent)
423 sb_content = u_boot_utils.run_and_log(c, 'tune2fs -l %s' % persistent)
424 if 'metadata_csum' in sb_content:
425 u_boot_utils.run_and_log(c, 'tune2fs -O ^metadata_csum %s' % persistent)
426 except CalledProcessError:
427 call('rm -f %s' % persistent, shell=True)
430 u_boot_utils.run_and_log(c, ['cp', '-f', persistent, fs_img])
433 @pytest.mark.boardspec('sandbox')
434 @pytest.mark.buildconfigspec('cmd_echo')
435 @pytest.mark.buildconfigspec('cmd_nvedit_info')
436 @pytest.mark.buildconfigspec('cmd_nvedit_load')
437 @pytest.mark.buildconfigspec('cmd_nvedit_select')
438 @pytest.mark.buildconfigspec('env_is_in_ext4')
439 def test_env_ext4(state_test_env):
441 """Test ENV in EXT4 on sandbox."""
442 c = state_test_env.u_boot_console
445 fs_img = mk_env_ext4(state_test_env)
447 c.run_command('host bind 0 %s' % fs_img)
449 response = c.run_command('ext4ls host 0:0')
450 assert 'uboot.env' not in response
452 # force env location: EXT4 (prio 1 in sandbox)
453 response = c.run_command('env select EXT4')
454 assert 'Select Environment on EXT4: OK' in response
456 response = c.run_command('env save')
457 assert 'Saving Environment to EXT4' in response
459 response = c.run_command('env load')
460 assert 'Loading Environment from EXT4... OK' in response
462 response = c.run_command('ext4ls host 0:0')
463 assert '8192 uboot.env' in response
465 response = c.run_command('env info')
466 assert 'env_valid = valid' in response
467 assert 'env_ready = true' in response
468 assert 'env_use_default = false' in response
470 response = c.run_command('env info -p -d')
471 assert 'Environment was loaded from persistent storage' in response
472 assert 'Environment can be persisted' in response
474 response = c.run_command('env info -d -q')
475 assert response == ""
476 response = c.run_command('echo $?')
477 assert response == "1"
479 response = c.run_command('env info -p -q')
480 assert response == ""
481 response = c.run_command('echo $?')
482 assert response == "0"
484 response = c.run_command('env erase')
485 assert 'OK' in response
487 response = c.run_command('env load')
488 assert 'Loading Environment from EXT4... ' in response
489 assert 'bad CRC, using default environment' in response
491 response = c.run_command('env info')
492 assert 'env_valid = invalid' in response
493 assert 'env_ready = true' in response
494 assert 'env_use_default = true' in response
496 response = c.run_command('env info -p -d')
497 assert 'Default environment is used' in response
498 assert 'Environment can be persisted' in response
500 # restore env location: NOWHERE (prio 0 in sandbox)
501 response = c.run_command('env select nowhere')
502 assert 'Select Environment on nowhere: OK' in response
504 response = c.run_command('env load')
505 assert 'Loading Environment from nowhere... OK' in response
507 response = c.run_command('env info')
508 assert 'env_valid = invalid' in response
509 assert 'env_ready = true' in response
510 assert 'env_use_default = true' in response
512 response = c.run_command('env info -p -d')
513 assert 'Default environment is used' in response
514 assert 'Environment cannot be persisted' in response
518 call('rm -f %s' % fs_img, shell=True)
520 def test_env_text(u_boot_console):
521 """Test the script that converts the environment to a text file"""
523 def check_script(intext, expect_val):
527 intext: Text to pass to the script
528 expect_val: Expected value of the CONFIG_EXTRA_ENV_TEXT string, or
529 None if we expect it not to be defined
531 with tempfile.TemporaryDirectory() as path:
532 fname = os.path.join(path, 'infile')
533 with open(fname, 'w') as inf:
534 print(intext, file=inf)
535 result = u_boot_utils.run_and_log(cons, ['awk', '-f', script, fname])
536 if expect_val is not None:
537 expect = '#define CONFIG_EXTRA_ENV_TEXT "%s"\n' % expect_val
538 assert result == expect
542 cons = u_boot_console
543 script = os.path.join(cons.config.source_dir, 'scripts', 'env2string.awk')
545 # simple script with a single var
546 check_script('fred=123', 'fred=123\\0')
549 check_script('', None)
552 check_script('''fred=123
553 ernie=456''', 'fred=123\\0ernie=456\\0')
556 check_script('''fred=123
561 ''', 'fred=123\\0ernie=456\\0')
564 check_script('''fred=123
566 fred+= 456''', 'fred=123 456\\0ernie=456\\0')
569 check_script('''fred=
571 fred+= 456''', 'fred= 456\\0ernie=456\\0')
573 # variable with + in it
574 check_script('fred+ernie=123', 'fred+ernie=123\\0')
576 # ignores variables that are empty
577 check_script('''fred=
579 ernie=456''', 'ernie=456\\0')
581 # single-character env name
582 check_script('''f=123
584 f+= 456''', 'e=456\\0f=123 456\\0')
587 check_script('''fred="my var"
588 ernie=another"''', 'fred=\\"my var\\"\\0ernie=another\\"\\0')
590 # variable name ending in +
591 check_script('''fred\\+=my var
592 fred++= again''', 'fred+=my var again\\0')
594 # variable name containing +
595 check_script('''fred+jane=both
597 ernie=456''', 'fred+jane=bothagain\\0ernie=456\\0')
599 # multi-line vars - new vars always start at column 1
600 check_script('''fred=first
606 ernie=another"''', 'fred=first second third with tab after blank confusing=oops\\0ernie=another\\"\\0')
609 check_script('''ubifs_boot=
610 env exists bootubipart ||
611 env set bootubipart UBI;
612 env exists bootubivol ||
613 env set bootubivol boot;
614 if ubi part ${bootubipart} &&
615 ubifsmount ubi${devnum}:${bootubivol};
618 run scan_dev_for_boot;
621 'ubifs_boot=env exists bootubipart || env set bootubipart UBI; '
622 'env exists bootubivol || env set bootubivol boot; '
623 'if ubi part ${bootubipart} && ubifsmount ubi${devnum}:${bootubivol}; '
624 'then devtype=ubi; run scan_dev_for_boot; fi\\0')