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""" 6Test operation of shell commands relating to environment variables. 7""" 8 9import os 10import os.path 11import re 12from subprocess import call, CalledProcessError 13import tempfile 14 15import pytest 16import u_boot_utils 17 18# FIXME: This might be useful for other tests; 19# perhaps refactor it into ConsoleBase or some other state object? 20class StateTestEnv(object): 21 """Container that represents the state of all U-Boot environment variables. 22 This enables quick determination of existant/non-existant variable 23 names. 24 """ 25 26 def __init__(self, u_boot_console): 27 """Initialize a new StateTestEnv object. 28 29 Args: 30 u_boot_console: A U-Boot console. 31 32 Returns: 33 Nothing. 34 """ 35 36 self.u_boot_console = u_boot_console 37 self.get_env() 38 self.set_var = self.get_non_existent_var() 39 40 def get_env(self): 41 """Read all current environment variables from U-Boot. 42 43 Args: 44 None. 45 46 Returns: 47 Nothing. 48 """ 49 50 if self.u_boot_console.config.buildconfig.get( 51 'config_version_variable', 'n') == 'y': 52 with self.u_boot_console.disable_check('main_signon'): 53 response = self.u_boot_console.run_command('printenv') 54 else: 55 response = self.u_boot_console.run_command('printenv') 56 self.env = {} 57 for l in response.splitlines(): 58 if not '=' in l: 59 continue 60 (var, value) = l.split('=', 1) 61 self.env[var] = value 62 63 def get_existent_var(self): 64 """Return the name of an environment variable that exists. 65 66 Args: 67 None. 68 69 Returns: 70 The name of an environment variable. 71 """ 72 73 for var in self.env: 74 return var 75 76 def get_non_existent_var(self): 77 """Return the name of an environment variable that does not exist. 78 79 Args: 80 None. 81 82 Returns: 83 The name of an environment variable. 84 """ 85 86 n = 0 87 while True: 88 var = 'test_env_' + str(n) 89 if var not in self.env: 90 return var 91 n += 1 92 93ste = None 94@pytest.fixture(scope='function') 95def state_test_env(u_boot_console): 96 """pytest fixture to provide a StateTestEnv object to tests.""" 97 98 global ste 99 if not ste: 100 ste = StateTestEnv(u_boot_console) 101 return ste 102 103def unset_var(state_test_env, var): 104 """Unset an environment variable. 105 106 This both executes a U-Boot shell command and updates a StateTestEnv 107 object. 108 109 Args: 110 state_test_env: The StateTestEnv object to update. 111 var: The variable name to unset. 112 113 Returns: 114 Nothing. 115 """ 116 117 state_test_env.u_boot_console.run_command('setenv %s' % var) 118 if var in state_test_env.env: 119 del state_test_env.env[var] 120 121def set_var(state_test_env, var, value): 122 """Set an environment variable. 123 124 This both executes a U-Boot shell command and updates a StateTestEnv 125 object. 126 127 Args: 128 state_test_env: The StateTestEnv object to update. 129 var: The variable name to set. 130 value: The value to set the variable to. 131 132 Returns: 133 Nothing. 134 """ 135 136 bc = state_test_env.u_boot_console.config.buildconfig 137 if bc.get('config_hush_parser', None): 138 quote = '"' 139 else: 140 quote = '' 141 if ' ' in value: 142 pytest.skip('Space in variable value on non-Hush shell') 143 144 state_test_env.u_boot_console.run_command( 145 'setenv %s %s%s%s' % (var, quote, value, quote)) 146 state_test_env.env[var] = value 147 148def validate_empty(state_test_env, var): 149 """Validate that a variable is not set, using U-Boot shell commands. 150 151 Args: 152 var: The variable name to test. 153 154 Returns: 155 Nothing. 156 """ 157 158 response = state_test_env.u_boot_console.run_command('echo ${%s}' % var) 159 assert response == '' 160 161def validate_set(state_test_env, var, value): 162 """Validate that a variable is set, using U-Boot shell commands. 163 164 Args: 165 var: The variable name to test. 166 value: The value the variable is expected to have. 167 168 Returns: 169 Nothing. 170 """ 171 172 # echo does not preserve leading, internal, or trailing whitespace in the 173 # value. printenv does, and hence allows more complete testing. 174 response = state_test_env.u_boot_console.run_command('printenv %s' % var) 175 assert response == ('%s=%s' % (var, value)) 176 177@pytest.mark.boardspec('sandbox') 178def test_env_initial_env_file(u_boot_console): 179 """Test that the u-boot-initial-env make target works""" 180 cons = u_boot_console 181 builddir = 'O=' + cons.config.build_dir 182 envfile = cons.config.build_dir + '/u-boot-initial-env' 183 184 # remove if already exists from an older run 185 try: 186 os.remove(envfile) 187 except: 188 pass 189 190 u_boot_utils.run_and_log(cons, ['make', builddir, 'u-boot-initial-env']) 191 192 assert os.path.exists(envfile) 193 194 # assume that every environment has a board variable, e.g. board=sandbox 195 with open(envfile, 'r') as file: 196 env = file.read() 197 regex = re.compile('board=.+\\n') 198 assert re.search(regex, env) 199 200def test_env_echo_exists(state_test_env): 201 """Test echoing a variable that exists.""" 202 203 var = state_test_env.get_existent_var() 204 value = state_test_env.env[var] 205 validate_set(state_test_env, var, value) 206 207@pytest.mark.buildconfigspec('cmd_echo') 208def test_env_echo_non_existent(state_test_env): 209 """Test echoing a variable that doesn't exist.""" 210 211 var = state_test_env.set_var 212 validate_empty(state_test_env, var) 213 214def test_env_printenv_non_existent(state_test_env): 215 """Test printenv error message for non-existant variables.""" 216 217 var = state_test_env.set_var 218 c = state_test_env.u_boot_console 219 with c.disable_check('error_notification'): 220 response = c.run_command('printenv %s' % var) 221 assert response == '## Error: "%s" not defined' % var 222 223@pytest.mark.buildconfigspec('cmd_echo') 224def test_env_unset_non_existent(state_test_env): 225 """Test unsetting a nonexistent variable.""" 226 227 var = state_test_env.get_non_existent_var() 228 unset_var(state_test_env, var) 229 validate_empty(state_test_env, var) 230 231def test_env_set_non_existent(state_test_env): 232 """Test set a non-existant variable.""" 233 234 var = state_test_env.set_var 235 value = 'foo' 236 set_var(state_test_env, var, value) 237 validate_set(state_test_env, var, value) 238 239def test_env_set_existing(state_test_env): 240 """Test setting an existant variable.""" 241 242 var = state_test_env.set_var 243 value = 'bar' 244 set_var(state_test_env, var, value) 245 validate_set(state_test_env, var, value) 246 247@pytest.mark.buildconfigspec('cmd_echo') 248def test_env_unset_existing(state_test_env): 249 """Test unsetting a variable.""" 250 251 var = state_test_env.set_var 252 unset_var(state_test_env, var) 253 validate_empty(state_test_env, var) 254 255def test_env_expansion_spaces(state_test_env): 256 """Test expanding a variable that contains a space in its value.""" 257 258 var_space = None 259 var_test = None 260 try: 261 var_space = state_test_env.get_non_existent_var() 262 set_var(state_test_env, var_space, ' ') 263 264 var_test = state_test_env.get_non_existent_var() 265 value = ' 1${%(var_space)s}${%(var_space)s} 2 ' % locals() 266 set_var(state_test_env, var_test, value) 267 value = ' 1 2 ' 268 validate_set(state_test_env, var_test, value) 269 finally: 270 if var_space: 271 unset_var(state_test_env, var_space) 272 if var_test: 273 unset_var(state_test_env, var_test) 274 275@pytest.mark.buildconfigspec('cmd_importenv') 276def test_env_import_checksum_no_size(state_test_env): 277 """Test that omitted ('-') size parameter with checksum validation fails the 278 env import function. 279 """ 280 c = state_test_env.u_boot_console 281 ram_base = u_boot_utils.find_ram_base(state_test_env.u_boot_console) 282 addr = '%08x' % ram_base 283 284 with c.disable_check('error_notification'): 285 response = c.run_command('env import -c %s -' % addr) 286 assert response == '## Error: external checksum format must pass size' 287 288@pytest.mark.buildconfigspec('cmd_importenv') 289def test_env_import_whitelist_checksum_no_size(state_test_env): 290 """Test that omitted ('-') size parameter with checksum validation fails the 291 env import function when variables are passed as parameters. 292 """ 293 c = state_test_env.u_boot_console 294 ram_base = u_boot_utils.find_ram_base(state_test_env.u_boot_console) 295 addr = '%08x' % ram_base 296 297 with c.disable_check('error_notification'): 298 response = c.run_command('env import -c %s - foo1 foo2 foo4' % addr) 299 assert response == '## Error: external checksum format must pass size' 300 301@pytest.mark.buildconfigspec('cmd_exportenv') 302@pytest.mark.buildconfigspec('cmd_importenv') 303def test_env_import_whitelist(state_test_env): 304 """Test importing only a handful of env variables from an environment.""" 305 c = state_test_env.u_boot_console 306 ram_base = u_boot_utils.find_ram_base(state_test_env.u_boot_console) 307 addr = '%08x' % ram_base 308 309 set_var(state_test_env, 'foo1', 'bar1') 310 set_var(state_test_env, 'foo2', 'bar2') 311 set_var(state_test_env, 'foo3', 'bar3') 312 313 c.run_command('env export %s' % addr) 314 315 unset_var(state_test_env, 'foo1') 316 set_var(state_test_env, 'foo2', 'test2') 317 set_var(state_test_env, 'foo4', 'bar4') 318 319 # no foo1 in current env, foo2 overridden, foo3 should be of the value 320 # before exporting and foo4 should be of the value before importing. 321 c.run_command('env import %s - foo1 foo2 foo4' % addr) 322 323 validate_set(state_test_env, 'foo1', 'bar1') 324 validate_set(state_test_env, 'foo2', 'bar2') 325 validate_set(state_test_env, 'foo3', 'bar3') 326 validate_set(state_test_env, 'foo4', 'bar4') 327 328 # Cleanup test environment 329 unset_var(state_test_env, 'foo1') 330 unset_var(state_test_env, 'foo2') 331 unset_var(state_test_env, 'foo3') 332 unset_var(state_test_env, 'foo4') 333 334@pytest.mark.buildconfigspec('cmd_exportenv') 335@pytest.mark.buildconfigspec('cmd_importenv') 336def test_env_import_whitelist_delete(state_test_env): 337 338 """Test importing only a handful of env variables from an environment, with. 339 deletion if a var A that is passed to env import is not in the 340 environment to be imported. 341 """ 342 c = state_test_env.u_boot_console 343 ram_base = u_boot_utils.find_ram_base(state_test_env.u_boot_console) 344 addr = '%08x' % ram_base 345 346 set_var(state_test_env, 'foo1', 'bar1') 347 set_var(state_test_env, 'foo2', 'bar2') 348 set_var(state_test_env, 'foo3', 'bar3') 349 350 c.run_command('env export %s' % addr) 351 352 unset_var(state_test_env, 'foo1') 353 set_var(state_test_env, 'foo2', 'test2') 354 set_var(state_test_env, 'foo4', 'bar4') 355 356 # no foo1 in current env, foo2 overridden, foo3 should be of the value 357 # before exporting and foo4 should be empty. 358 c.run_command('env import -d %s - foo1 foo2 foo4' % addr) 359 360 validate_set(state_test_env, 'foo1', 'bar1') 361 validate_set(state_test_env, 'foo2', 'bar2') 362 validate_set(state_test_env, 'foo3', 'bar3') 363 validate_empty(state_test_env, 'foo4') 364 365 # Cleanup test environment 366 unset_var(state_test_env, 'foo1') 367 unset_var(state_test_env, 'foo2') 368 unset_var(state_test_env, 'foo3') 369 unset_var(state_test_env, 'foo4') 370 371@pytest.mark.buildconfigspec('cmd_nvedit_info') 372def test_env_info(state_test_env): 373 374 """Test 'env info' command with all possible options. 375 """ 376 c = state_test_env.u_boot_console 377 378 response = c.run_command('env info') 379 nb_line = 0 380 for l in response.split('\n'): 381 if 'env_valid = ' in l: 382 assert '= invalid' in l or '= valid' in l or '= redundant' in l 383 nb_line += 1 384 elif 'env_ready =' in l or 'env_use_default =' in l: 385 assert '= true' in l or '= false' in l 386 nb_line += 1 387 else: 388 assert True 389 assert nb_line == 3 390 391 response = c.run_command('env info -p -d') 392 assert 'Default environment is used' in response or \ 393 "Environment was loaded from persistent storage" in response 394 assert 'Environment can be persisted' in response or \ 395 "Environment cannot be persisted" in response 396 397 response = c.run_command('env info -p -d -q') 398 assert response == "" 399 400 response = c.run_command('env info -p -q') 401 assert response == "" 402 403 response = c.run_command('env info -d -q') 404 assert response == "" 405 406@pytest.mark.boardspec('sandbox') 407@pytest.mark.buildconfigspec('cmd_nvedit_info') 408@pytest.mark.buildconfigspec('cmd_echo') 409def test_env_info_sandbox(state_test_env): 410 """Test 'env info' command result with several options on sandbox 411 with a known ENV configuration: ready & default & persistent 412 """ 413 c = state_test_env.u_boot_console 414 415 response = c.run_command('env info') 416 assert 'env_ready = true' in response 417 assert 'env_use_default = true' in response 418 419 response = c.run_command('env info -p -d') 420 assert 'Default environment is used' in response 421 assert 'Environment cannot be persisted' in response 422 423 response = c.run_command('env info -d -q') 424 response = c.run_command('echo $?') 425 assert response == "0" 426 427 response = c.run_command('env info -p -q') 428 response = c.run_command('echo $?') 429 assert response == "1" 430 431 response = c.run_command('env info -d -p -q') 432 response = c.run_command('echo $?') 433 assert response == "1" 434 435def mk_env_ext4(state_test_env): 436 437 """Create a empty ext4 file system volume.""" 438 c = state_test_env.u_boot_console 439 filename = 'env.ext4.img' 440 persistent = c.config.persistent_data_dir + '/' + filename 441 fs_img = c.config.result_dir + '/' + filename 442 443 if os.path.exists(persistent): 444 c.log.action('Disk image file ' + persistent + ' already exists') 445 else: 446 # Some distributions do not add /sbin to the default PATH, where mkfs.ext4 lives 447 os.environ["PATH"] += os.pathsep + '/sbin' 448 try: 449 u_boot_utils.run_and_log(c, 'dd if=/dev/zero of=%s bs=1M count=16' % persistent) 450 u_boot_utils.run_and_log(c, 'mkfs.ext4 %s' % persistent) 451 sb_content = u_boot_utils.run_and_log(c, 'tune2fs -l %s' % persistent) 452 if 'metadata_csum' in sb_content: 453 u_boot_utils.run_and_log(c, 'tune2fs -O ^metadata_csum %s' % persistent) 454 except CalledProcessError: 455 call('rm -f %s' % persistent, shell=True) 456 raise 457 458 u_boot_utils.run_and_log(c, ['cp', '-f', persistent, fs_img]) 459 return fs_img 460 461@pytest.mark.boardspec('sandbox') 462@pytest.mark.buildconfigspec('cmd_echo') 463@pytest.mark.buildconfigspec('cmd_nvedit_info') 464@pytest.mark.buildconfigspec('cmd_nvedit_load') 465@pytest.mark.buildconfigspec('cmd_nvedit_select') 466@pytest.mark.buildconfigspec('env_is_in_ext4') 467def test_env_ext4(state_test_env): 468 469 """Test ENV in EXT4 on sandbox.""" 470 c = state_test_env.u_boot_console 471 fs_img = '' 472 try: 473 fs_img = mk_env_ext4(state_test_env) 474 475 c.run_command('host bind 0 %s' % fs_img) 476 477 response = c.run_command('ext4ls host 0:0') 478 assert 'uboot.env' not in response 479 480 # force env location: EXT4 (prio 1 in sandbox) 481 response = c.run_command('env select EXT4') 482 assert 'Select Environment on EXT4: OK' in response 483 484 response = c.run_command('env save') 485 assert 'Saving Environment to EXT4' in response 486 487 response = c.run_command('env load') 488 assert 'Loading Environment from EXT4... OK' in response 489 490 response = c.run_command('ext4ls host 0:0') 491 assert '8192 uboot.env' in response 492 493 response = c.run_command('env info') 494 assert 'env_valid = valid' in response 495 assert 'env_ready = true' in response 496 assert 'env_use_default = false' in response 497 498 response = c.run_command('env info -p -d') 499 assert 'Environment was loaded from persistent storage' in response 500 assert 'Environment can be persisted' in response 501 502 response = c.run_command('env info -d -q') 503 assert response == "" 504 response = c.run_command('echo $?') 505 assert response == "1" 506 507 response = c.run_command('env info -p -q') 508 assert response == "" 509 response = c.run_command('echo $?') 510 assert response == "0" 511 512 response = c.run_command('env erase') 513 assert 'OK' in response 514 515 response = c.run_command('env load') 516 assert 'Loading Environment from EXT4... ' in response 517 assert 'bad CRC, using default environment' in response 518 519 response = c.run_command('env info') 520 assert 'env_valid = invalid' in response 521 assert 'env_ready = true' in response 522 assert 'env_use_default = true' in response 523 524 response = c.run_command('env info -p -d') 525 assert 'Default environment is used' in response 526 assert 'Environment can be persisted' in response 527 528 # restore env location: NOWHERE (prio 0 in sandbox) 529 response = c.run_command('env select nowhere') 530 assert 'Select Environment on nowhere: OK' in response 531 532 response = c.run_command('env load') 533 assert 'Loading Environment from nowhere... OK' in response 534 535 response = c.run_command('env info') 536 assert 'env_valid = invalid' in response 537 assert 'env_ready = true' in response 538 assert 'env_use_default = true' in response 539 540 response = c.run_command('env info -p -d') 541 assert 'Default environment is used' in response 542 assert 'Environment cannot be persisted' in response 543 544 finally: 545 if fs_img: 546 call('rm -f %s' % fs_img, shell=True) 547 548def test_env_text(u_boot_console): 549 """Test the script that converts the environment to a text file""" 550 551 def check_script(intext, expect_val): 552 """Check a test case 553 554 Args: 555 intext: Text to pass to the script 556 expect_val: Expected value of the CONFIG_EXTRA_ENV_TEXT string, or 557 None if we expect it not to be defined 558 """ 559 with tempfile.TemporaryDirectory() as path: 560 fname = os.path.join(path, 'infile') 561 with open(fname, 'w') as inf: 562 print(intext, file=inf) 563 result = u_boot_utils.run_and_log(cons, ['awk', '-f', script, fname]) 564 if expect_val is not None: 565 expect = '#define CONFIG_EXTRA_ENV_TEXT "%s"\n' % expect_val 566 assert result == expect 567 else: 568 assert result == '' 569 570 cons = u_boot_console 571 script = os.path.join(cons.config.source_dir, 'scripts', 'env2string.awk') 572 573 # simple script with a single var 574 check_script('fred=123', 'fred=123\\0') 575 576 # no vars 577 check_script('', None) 578 579 # two vars 580 check_script('''fred=123 581mary=456''', 'fred=123\\0mary=456\\0') 582 583 # blank lines 584 check_script('''fred=123 585 586 587mary=456 588 589''', 'fred=123\\0mary=456\\0') 590 591 # append 592 check_script('''fred=123 593mary=456 594fred+= 456''', 'fred=123 456\\0mary=456\\0') 595 596 # append from empty 597 check_script('''fred= 598mary=456 599fred+= 456''', 'fred= 456\\0mary=456\\0') 600 601 # variable with + in it 602 check_script('fred+mary=123', 'fred+mary=123\\0') 603 604 # ignores variables that are empty 605 check_script('''fred= 606fred+= 607mary=456''', 'mary=456\\0') 608 609 # single-character env name 610 check_script('''m=123 611e=456 612m+= 456''', 'e=456\\0m=123 456\\0') 613 614 # contains quotes 615 check_script('''fred="my var" 616mary=another"''', 'fred=\\"my var\\"\\0mary=another\\"\\0') 617 618 # variable name ending in + 619 check_script('''fred\\+=my var 620fred++= again''', 'fred+=my var again\\0') 621 622 # variable name containing + 623 check_script('''fred+jane=both 624fred+jane+=again 625mary=456''', 'fred+jane=bothagain\\0mary=456\\0') 626 627 # multi-line vars - new vars always start at column 1 628 check_script('''fred=first 629 second 630\tthird with tab 631 632 after blank 633 confusing=oops 634mary=another"''', 'fred=first second third with tab after blank confusing=oops\\0mary=another\\"\\0') 635 636 # real-world example 637 check_script('''ubifs_boot= 638 env exists bootubipart || 639 env set bootubipart UBI; 640 env exists bootubivol || 641 env set bootubivol boot; 642 if ubi part ${bootubipart} && 643 ubifsmount ubi${devnum}:${bootubivol}; 644 then 645 devtype=ubi; 646 run scan_dev_for_boot; 647 fi 648''', 649 'ubifs_boot=env exists bootubipart || env set bootubipart UBI; ' 650 'env exists bootubivol || env set bootubivol boot; ' 651 'if ubi part ${bootubipart} && ubifsmount ubi${devnum}:${bootubivol}; ' 652 'then devtype=ubi; run scan_dev_for_boot; fi\\0') 653