You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
461 lines
16 KiB
461 lines
16 KiB
# Copyright 2020 The Pigweed Authors
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
|
# use this file except in compliance with the License. You may obtain a copy of
|
|
# the License at
|
|
#
|
|
# https://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
# License for the specific language governing permissions and limitations under
|
|
# the License.
|
|
"""Tests for env_setup.environment.
|
|
|
|
This tests the error-checking, context manager, and written environment scripts
|
|
of the Environment class.
|
|
|
|
Tests that end in "_ctx" modify the environment and validate it in-process.
|
|
|
|
Tests that end in "_written" write the environment to a file intended to be
|
|
evaluated by the shell, then launches the shell and then saves the environment.
|
|
This environment is then validated in the test process.
|
|
"""
|
|
|
|
import logging
|
|
import os
|
|
import subprocess
|
|
import tempfile
|
|
import unittest
|
|
|
|
import six
|
|
|
|
from pw_env_setup import environment
|
|
|
|
# pylint: disable=super-with-arguments
|
|
|
|
|
|
class WrittenEnvFailure(Exception):
|
|
pass
|
|
|
|
|
|
def _evaluate_env_in_shell(env):
|
|
"""Write env to a file then evaluate and save the resulting environment.
|
|
|
|
Write env to a file, then launch a shell command that sources that file
|
|
and dumps the environment to stdout. Parse that output into a dict and
|
|
return it.
|
|
|
|
Args:
|
|
env(environment.Environment): environment to write out
|
|
|
|
Returns dictionary of resulting environment.
|
|
"""
|
|
|
|
# Write env sourcing script to file.
|
|
with tempfile.NamedTemporaryFile(
|
|
prefix='pw-test-written-env-',
|
|
suffix='.bat' if os.name == 'nt' else '.sh',
|
|
delete=False,
|
|
mode='w+') as temp:
|
|
env.write(temp)
|
|
temp_name = temp.name
|
|
|
|
# Evaluate env sourcing script and capture output of 'env'.
|
|
if os.name == 'nt':
|
|
# On Windows you just run batch files and they modify your
|
|
# environment, no need to call 'source' or '.'.
|
|
cmd = '{} && set'.format(temp_name)
|
|
else:
|
|
# Using '.' instead of 'source' because 'source' is not POSIX.
|
|
cmd = '. {} && env'.format(temp_name)
|
|
|
|
res = subprocess.run(cmd, capture_output=True, shell=True)
|
|
if res.returncode:
|
|
raise WrittenEnvFailure(res.stderr)
|
|
|
|
# Parse environment from stdout of subprocess.
|
|
env_ret = {}
|
|
for line in res.stdout.splitlines():
|
|
line = line.decode()
|
|
|
|
# Some people inexplicably have newlines in some of their
|
|
# environment variables. This module does not allow that so we can
|
|
# ignore any such extra lines.
|
|
if '=' not in line:
|
|
continue
|
|
|
|
var, value = line.split('=', 1)
|
|
env_ret[var] = value
|
|
|
|
return env_ret
|
|
|
|
|
|
# pylint: disable=too-many-public-methods
|
|
class EnvironmentTest(unittest.TestCase):
|
|
"""Tests for env_setup.environment."""
|
|
def setUp(self):
|
|
self.env = environment.Environment()
|
|
|
|
# Name of a variable that is already set when the test starts.
|
|
self.var_already_set = self.env.normalize_key('var_already_set')
|
|
os.environ[self.var_already_set] = 'orig value'
|
|
self.assertIn(self.var_already_set, os.environ)
|
|
|
|
# Name of a variable that is not set when the test starts.
|
|
self.var_not_set = self.env.normalize_key('var_not_set')
|
|
if self.var_not_set in os.environ:
|
|
del os.environ[self.var_not_set]
|
|
self.assertNotIn(self.var_not_set, os.environ)
|
|
|
|
self.orig_env = os.environ.copy()
|
|
|
|
def tearDown(self):
|
|
self.assertEqual(os.environ, self.orig_env)
|
|
|
|
def test_set_notpresent_ctx(self):
|
|
self.env.set(self.var_not_set, '1')
|
|
with self.env(export=False) as env:
|
|
self.assertIn(self.var_not_set, env)
|
|
self.assertEqual(env[self.var_not_set], '1')
|
|
|
|
def test_set_notpresent_written(self):
|
|
self.env.set(self.var_not_set, '1')
|
|
env = _evaluate_env_in_shell(self.env)
|
|
self.assertIn(self.var_not_set, env)
|
|
self.assertEqual(env[self.var_not_set], '1')
|
|
|
|
def test_set_present_ctx(self):
|
|
self.env.set(self.var_already_set, '1')
|
|
with self.env(export=False) as env:
|
|
self.assertIn(self.var_already_set, env)
|
|
self.assertEqual(env[self.var_already_set], '1')
|
|
|
|
def test_set_present_written(self):
|
|
self.env.set(self.var_already_set, '1')
|
|
env = _evaluate_env_in_shell(self.env)
|
|
self.assertIn(self.var_already_set, env)
|
|
self.assertEqual(env[self.var_already_set], '1')
|
|
|
|
def test_clear_notpresent_ctx(self):
|
|
self.env.clear(self.var_not_set)
|
|
with self.env(export=False) as env:
|
|
self.assertNotIn(self.var_not_set, env)
|
|
|
|
def test_clear_notpresent_written(self):
|
|
self.env.clear(self.var_not_set)
|
|
env = _evaluate_env_in_shell(self.env)
|
|
self.assertNotIn(self.var_not_set, env)
|
|
|
|
def test_clear_present_ctx(self):
|
|
self.env.clear(self.var_already_set)
|
|
with self.env(export=False) as env:
|
|
self.assertNotIn(self.var_already_set, env)
|
|
|
|
def test_clear_present_written(self):
|
|
self.env.clear(self.var_already_set)
|
|
env = _evaluate_env_in_shell(self.env)
|
|
self.assertNotIn(self.var_already_set, env)
|
|
|
|
def test_value_replacement(self):
|
|
self.env.set(self.var_not_set, '/foo/bar/baz')
|
|
self.env.add_replacement('FOOBAR', '/foo/bar')
|
|
buf = six.StringIO()
|
|
self.env.write(buf)
|
|
assert '/foo/bar' not in buf.getvalue()
|
|
|
|
def test_variable_replacement(self):
|
|
self.env.set('FOOBAR', '/foo/bar')
|
|
self.env.set(self.var_not_set, '/foo/bar/baz')
|
|
self.env.add_replacement('FOOBAR')
|
|
buf = six.StringIO()
|
|
self.env.write(buf)
|
|
print(buf.getvalue())
|
|
assert '/foo/bar/baz' not in buf.getvalue()
|
|
|
|
def test_nonglobal(self):
|
|
self.env.set(self.var_not_set, '1')
|
|
with self.env(export=False) as env:
|
|
self.assertIn(self.var_not_set, env)
|
|
self.assertNotIn(self.var_not_set, os.environ)
|
|
|
|
def test_global(self):
|
|
self.env.set(self.var_not_set, '1')
|
|
with self.env(export=True) as env:
|
|
self.assertIn(self.var_not_set, env)
|
|
self.assertIn(self.var_not_set, os.environ)
|
|
|
|
def test_set_badnametype(self):
|
|
with self.assertRaises(environment.BadNameType):
|
|
self.env.set(123, '123')
|
|
|
|
def test_set_badvaluetype(self):
|
|
with self.assertRaises(environment.BadValueType):
|
|
self.env.set('var', 123)
|
|
|
|
def test_prepend_badnametype(self):
|
|
with self.assertRaises(environment.BadNameType):
|
|
self.env.prepend(123, '123')
|
|
|
|
def test_prepend_badvaluetype(self):
|
|
with self.assertRaises(environment.BadValueType):
|
|
self.env.prepend('var', 123)
|
|
|
|
def test_append_badnametype(self):
|
|
with self.assertRaises(environment.BadNameType):
|
|
self.env.append(123, '123')
|
|
|
|
def test_append_badvaluetype(self):
|
|
with self.assertRaises(environment.BadValueType):
|
|
self.env.append('var', 123)
|
|
|
|
def test_set_badname_empty(self):
|
|
with self.assertRaises(environment.BadVariableName):
|
|
self.env.set('', '123')
|
|
|
|
def test_set_badname_digitstart(self):
|
|
with self.assertRaises(environment.BadVariableName):
|
|
self.env.set('123', '123')
|
|
|
|
def test_set_badname_equals(self):
|
|
with self.assertRaises(environment.BadVariableName):
|
|
self.env.set('foo=bar', '123')
|
|
|
|
def test_set_badname_period(self):
|
|
with self.assertRaises(environment.BadVariableName):
|
|
self.env.set('abc.def', '123')
|
|
|
|
def test_set_badname_hyphen(self):
|
|
with self.assertRaises(environment.BadVariableName):
|
|
self.env.set('abc-def', '123')
|
|
|
|
def test_set_empty_value(self):
|
|
with self.assertRaises(environment.EmptyValue):
|
|
self.env.set('var', '')
|
|
|
|
def test_set_newline_in_value(self):
|
|
with self.assertRaises(environment.NewlineInValue):
|
|
self.env.set('var', '123\n456')
|
|
|
|
def test_equal_sign_in_value(self):
|
|
with self.assertRaises(environment.BadVariableValue):
|
|
self.env.append(self.var_already_set, 'pa=th')
|
|
|
|
|
|
class _PrependAppendEnvironmentTest(unittest.TestCase):
|
|
"""Tests for env_setup.environment."""
|
|
def __init__(self, *args, **kwargs):
|
|
windows = kwargs.pop('windows', False)
|
|
pathsep = kwargs.pop('pathsep', os.pathsep)
|
|
allcaps = kwargs.pop('allcaps', False)
|
|
super(_PrependAppendEnvironmentTest, self).__init__(*args, **kwargs)
|
|
self.windows = windows
|
|
self.pathsep = pathsep
|
|
self.allcaps = allcaps
|
|
|
|
# If we're testing Windows behavior and actually running on Windows,
|
|
# actually launch a subprocess to evaluate the shell init script.
|
|
# Likewise if we're testing POSIX behavior and actually on a POSIX
|
|
# system. Tests can check self.run_shell_tests and exit without
|
|
# doing anything.
|
|
real_windows = (os.name == 'nt')
|
|
self.run_shell_tests = (self.windows == real_windows)
|
|
|
|
def setUp(self):
|
|
self.env = environment.Environment(windows=self.windows,
|
|
pathsep=self.pathsep,
|
|
allcaps=self.allcaps)
|
|
|
|
self.var_already_set = self.env.normalize_key('VAR_ALREADY_SET')
|
|
os.environ[self.var_already_set] = self.pathsep.join(
|
|
'one two three'.split())
|
|
self.assertIn(self.var_already_set, os.environ)
|
|
|
|
self.var_not_set = self.env.normalize_key('VAR_NOT_SET')
|
|
if self.var_not_set in os.environ:
|
|
del os.environ[self.var_not_set]
|
|
self.assertNotIn(self.var_not_set, os.environ)
|
|
|
|
self.orig_env = os.environ.copy()
|
|
|
|
def split(self, val):
|
|
return val.split(self.pathsep)
|
|
|
|
def tearDown(self):
|
|
self.assertEqual(os.environ, self.orig_env)
|
|
|
|
|
|
# TODO(mohrr) remove disable=useless-object-inheritance once in Python 3.
|
|
# pylint: disable=useless-object-inheritance
|
|
class _AppendPrependTestMixin(object):
|
|
def test_prepend_present_ctx(self):
|
|
orig = os.environ[self.var_already_set]
|
|
self.env.prepend(self.var_already_set, 'path')
|
|
with self.env(export=False) as env:
|
|
self.assertEqual(env[self.var_already_set],
|
|
self.pathsep.join(('path', orig)))
|
|
|
|
def test_prepend_present_written(self):
|
|
if not self.run_shell_tests:
|
|
return
|
|
|
|
orig = os.environ[self.var_already_set]
|
|
self.env.prepend(self.var_already_set, 'path')
|
|
env = _evaluate_env_in_shell(self.env)
|
|
self.assertEqual(env[self.var_already_set],
|
|
self.pathsep.join(('path', orig)))
|
|
|
|
def test_prepend_notpresent_ctx(self):
|
|
self.env.prepend(self.var_not_set, 'path')
|
|
with self.env(export=False) as env:
|
|
self.assertEqual(env[self.var_not_set], 'path')
|
|
|
|
def test_prepend_notpresent_written(self):
|
|
if not self.run_shell_tests:
|
|
return
|
|
|
|
self.env.prepend(self.var_not_set, 'path')
|
|
env = _evaluate_env_in_shell(self.env)
|
|
self.assertEqual(env[self.var_not_set], 'path')
|
|
|
|
def test_append_present_ctx(self):
|
|
orig = os.environ[self.var_already_set]
|
|
self.env.append(self.var_already_set, 'path')
|
|
with self.env(export=False) as env:
|
|
self.assertEqual(env[self.var_already_set],
|
|
self.pathsep.join((orig, 'path')))
|
|
|
|
def test_append_present_written(self):
|
|
if not self.run_shell_tests:
|
|
return
|
|
|
|
orig = os.environ[self.var_already_set]
|
|
self.env.append(self.var_already_set, 'path')
|
|
env = _evaluate_env_in_shell(self.env)
|
|
self.assertEqual(env[self.var_already_set],
|
|
self.pathsep.join((orig, 'path')))
|
|
|
|
def test_append_notpresent_ctx(self):
|
|
self.env.append(self.var_not_set, 'path')
|
|
with self.env(export=False) as env:
|
|
self.assertEqual(env[self.var_not_set], 'path')
|
|
|
|
def test_append_notpresent_written(self):
|
|
if not self.run_shell_tests:
|
|
return
|
|
|
|
self.env.append(self.var_not_set, 'path')
|
|
env = _evaluate_env_in_shell(self.env)
|
|
self.assertEqual(env[self.var_not_set], 'path')
|
|
|
|
def test_remove_ctx(self):
|
|
self.env.set(self.var_not_set,
|
|
self.pathsep.join(('path', 'one', 'path', 'two', 'path')))
|
|
|
|
self.env.append(self.var_not_set, 'path')
|
|
with self.env(export=False) as env:
|
|
self.assertEqual(env[self.var_not_set],
|
|
self.pathsep.join(('one', 'two', 'path')))
|
|
|
|
def test_remove_written(self):
|
|
if not self.run_shell_tests:
|
|
return
|
|
|
|
if self.windows: # TODO(pwbug/231) Re-enable for Windows.
|
|
return
|
|
|
|
self.env.set(self.var_not_set,
|
|
self.pathsep.join(('path', 'one', 'path', 'two', 'path')))
|
|
|
|
self.env.append(self.var_not_set, 'path')
|
|
env = _evaluate_env_in_shell(self.env)
|
|
self.assertEqual(env[self.var_not_set],
|
|
self.pathsep.join(('one', 'two', 'path')))
|
|
|
|
def test_remove_ctx_space(self):
|
|
self.env.set(self.var_not_set,
|
|
self.pathsep.join(('pa th', 'one', 'pa th', 'two')))
|
|
|
|
self.env.append(self.var_not_set, 'pa th')
|
|
with self.env(export=False) as env:
|
|
self.assertEqual(env[self.var_not_set],
|
|
self.pathsep.join(('one', 'two', 'pa th')))
|
|
|
|
def test_remove_written_space(self):
|
|
if not self.run_shell_tests:
|
|
return
|
|
|
|
if self.windows: # TODO(pwbug/231) Re-enable for Windows.
|
|
return
|
|
|
|
self.env.set(self.var_not_set,
|
|
self.pathsep.join(('pa th', 'one', 'pa th', 'two')))
|
|
|
|
self.env.append(self.var_not_set, 'pa th')
|
|
env = _evaluate_env_in_shell(self.env)
|
|
self.assertEqual(env[self.var_not_set],
|
|
self.pathsep.join(('one', 'two', 'pa th')))
|
|
|
|
def test_remove_ctx_empty(self):
|
|
self.env.remove(self.var_not_set, 'path')
|
|
with self.env(export=False) as env:
|
|
self.assertNotIn(self.var_not_set, env)
|
|
|
|
def test_remove_written_empty(self):
|
|
if not self.run_shell_tests:
|
|
return
|
|
|
|
self.env.remove(self.var_not_set, 'path')
|
|
env = _evaluate_env_in_shell(self.env)
|
|
self.assertNotIn(self.var_not_set, env)
|
|
|
|
|
|
class WindowsEnvironmentTest(_PrependAppendEnvironmentTest,
|
|
_AppendPrependTestMixin):
|
|
def __init__(self, *args, **kwargs):
|
|
kwargs['pathsep'] = ';'
|
|
kwargs['windows'] = True
|
|
kwargs['allcaps'] = True
|
|
super(WindowsEnvironmentTest, self).__init__(*args, **kwargs)
|
|
|
|
|
|
class PosixEnvironmentTest(_PrependAppendEnvironmentTest,
|
|
_AppendPrependTestMixin):
|
|
def __init__(self, *args, **kwargs):
|
|
kwargs['pathsep'] = ':'
|
|
kwargs['windows'] = False
|
|
kwargs['allcaps'] = False
|
|
super(PosixEnvironmentTest, self).__init__(*args, **kwargs)
|
|
self.real_windows = (os.name == 'nt')
|
|
|
|
|
|
class WindowsCaseInsensitiveTest(unittest.TestCase):
|
|
def test_lower_handling(self):
|
|
# This is only for testing case-handling on Windows. It doesn't make
|
|
# sense to run it on other systems.
|
|
if os.name != 'nt':
|
|
return
|
|
|
|
lower_var = 'lower_var'
|
|
upper_var = lower_var.upper()
|
|
|
|
if upper_var in os.environ:
|
|
del os.environ[upper_var]
|
|
|
|
self.assertNotIn(lower_var, os.environ)
|
|
|
|
env = environment.Environment()
|
|
env.append(lower_var, 'foo')
|
|
env.append(upper_var, 'bar')
|
|
with env(export=False) as env_:
|
|
self.assertNotIn(lower_var, env_)
|
|
self.assertIn(upper_var, env_)
|
|
self.assertEqual(env_[upper_var], 'foo;bar')
|
|
|
|
|
|
if __name__ == '__main__':
|
|
import sys
|
|
logging.basicConfig(stream=sys.stderr, level=logging.DEBUG)
|
|
unittest.main()
|