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 7import os 8import os.path 9from subprocess import call, check_call, CalledProcessError 10 11import pytest 12import u_boot_utils 13 14# FIXME: This might be useful for other tests; 15# perhaps refactor it into ConsoleBase or some other state object? 16class 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 89ste = None 90@pytest.fixture(scope='function') 91def 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 99def 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 117def 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 144def 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 157def 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 173def 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') 181def 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 187def 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') 197def 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 204def 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 212def 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') 221def 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 228def 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') 249def 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') 262def 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') 276def 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') 309def 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') 345def 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') 380def 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 406def 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 # Some distributions do not add /sbin to the default PATH, where mkfs.ext4 lives 418 os.environ["PATH"] += os.pathsep + '/sbin' 419 try: 420 u_boot_utils.run_and_log(c, 'dd if=/dev/zero of=%s bs=1M count=16' % persistent) 421 u_boot_utils.run_and_log(c, 'mkfs.ext4 %s' % persistent) 422 sb_content = u_boot_utils.run_and_log(c, 'tune2fs -l %s' % persistent) 423 if 'metadata_csum' in sb_content: 424 u_boot_utils.run_and_log(c, 'tune2fs -O ^metadata_csum %s' % persistent) 425 except CalledProcessError: 426 call('rm -f %s' % persistent, shell=True) 427 raise 428 429 u_boot_utils.run_and_log(c, ['cp', '-f', persistent, fs_img]) 430 return fs_img 431 432@pytest.mark.boardspec('sandbox') 433@pytest.mark.buildconfigspec('cmd_echo') 434@pytest.mark.buildconfigspec('cmd_nvedit_info') 435@pytest.mark.buildconfigspec('cmd_nvedit_load') 436@pytest.mark.buildconfigspec('cmd_nvedit_select') 437@pytest.mark.buildconfigspec('env_is_in_ext4') 438def test_env_ext4(state_test_env): 439 440 """Test ENV in EXT4 on sandbox.""" 441 c = state_test_env.u_boot_console 442 fs_img = '' 443 try: 444 fs_img = mk_env_ext4(state_test_env) 445 446 c.run_command('host bind 0 %s' % fs_img) 447 448 response = c.run_command('ext4ls host 0:0') 449 assert 'uboot.env' not in response 450 451 # force env location: EXT4 (prio 1 in sandbox) 452 response = c.run_command('env select EXT4') 453 assert 'Select Environment on EXT4: OK' in response 454 455 response = c.run_command('env save') 456 assert 'Saving Environment to EXT4' in response 457 458 response = c.run_command('env load') 459 assert 'Loading Environment from EXT4... OK' in response 460 461 response = c.run_command('ext4ls host 0:0') 462 assert '8192 uboot.env' in response 463 464 response = c.run_command('env info') 465 assert 'env_valid = valid' in response 466 assert 'env_ready = true' in response 467 assert 'env_use_default = false' in response 468 469 response = c.run_command('env info -p -d') 470 assert 'Environment was loaded from persistent storage' in response 471 assert 'Environment can be persisted' in response 472 473 response = c.run_command('env info -d -q') 474 assert response == "" 475 response = c.run_command('echo $?') 476 assert response == "1" 477 478 response = c.run_command('env info -p -q') 479 assert response == "" 480 response = c.run_command('echo $?') 481 assert response == "0" 482 483 response = c.run_command('env erase') 484 assert 'OK' in response 485 486 response = c.run_command('env load') 487 assert 'Loading Environment from EXT4... ' in response 488 assert 'bad CRC, using default environment' in response 489 490 response = c.run_command('env info') 491 assert 'env_valid = invalid' in response 492 assert 'env_ready = true' in response 493 assert 'env_use_default = true' in response 494 495 response = c.run_command('env info -p -d') 496 assert 'Default environment is used' in response 497 assert 'Environment can be persisted' in response 498 499 # restore env location: NOWHERE (prio 0 in sandbox) 500 response = c.run_command('env select nowhere') 501 assert 'Select Environment on nowhere: OK' in response 502 503 response = c.run_command('env load') 504 assert 'Loading Environment from nowhere... OK' in response 505 506 response = c.run_command('env info') 507 assert 'env_valid = invalid' in response 508 assert 'env_ready = true' in response 509 assert 'env_use_default = true' in response 510 511 response = c.run_command('env info -p -d') 512 assert 'Default environment is used' in response 513 assert 'Environment cannot be persisted' in response 514 515 finally: 516 if fs_img: 517 call('rm -f %s' % fs_img, shell=True) 518