209 lines
7.5 KiB
Python
209 lines
7.5 KiB
Python
|
import os
|
||
|
import tempfile
|
||
|
from textwrap import dedent
|
||
|
import unittest
|
||
|
from unittest import mock
|
||
|
from numba.tests.support import (TestCase, temp_directory, override_env_config,
|
||
|
run_in_subprocess)
|
||
|
from numba.core import config
|
||
|
|
||
|
try:
|
||
|
import yaml
|
||
|
_HAVE_YAML = True
|
||
|
except ImportError:
|
||
|
_HAVE_YAML = False
|
||
|
|
||
|
_skip_msg = "pyyaml needed for configuration file tests"
|
||
|
needs_yaml = unittest.skipIf(not _HAVE_YAML, _skip_msg)
|
||
|
|
||
|
|
||
|
@needs_yaml
|
||
|
class TestConfig(TestCase):
|
||
|
|
||
|
# Disable parallel testing due to envvars modification
|
||
|
_numba_parallel_test_ = False
|
||
|
|
||
|
def setUp(self):
|
||
|
# use support.temp_directory, it can do the clean up
|
||
|
self.tmppath = temp_directory('config_tmp')
|
||
|
self.maxDiff = 2500
|
||
|
super(TestConfig, self).setUp()
|
||
|
|
||
|
def mock_cfg_location(self):
|
||
|
"""
|
||
|
Creates a mock launch location.
|
||
|
Returns the location path.
|
||
|
"""
|
||
|
return tempfile.mkdtemp(dir=self.tmppath)
|
||
|
|
||
|
def inject_mock_cfg(self, location, cfg):
|
||
|
"""
|
||
|
Injects a mock configuration at 'location'
|
||
|
"""
|
||
|
tmpcfg = os.path.join(location, config._config_fname)
|
||
|
with open(tmpcfg, 'wt') as f:
|
||
|
yaml.dump(cfg, f, default_flow_style=False)
|
||
|
|
||
|
def get_settings(self):
|
||
|
"""
|
||
|
Gets the current numba config settings
|
||
|
"""
|
||
|
store = dict()
|
||
|
for x in dir(config):
|
||
|
if x.isupper():
|
||
|
store[x] = getattr(config, x)
|
||
|
return store
|
||
|
|
||
|
def create_config_effect(self, cfg):
|
||
|
"""
|
||
|
Returns a config "original" from a location with no config file
|
||
|
and then the impact of applying the supplied cfg dictionary as
|
||
|
a config file at a location in the returned "current".
|
||
|
"""
|
||
|
|
||
|
# store original cwd
|
||
|
original_cwd = os.getcwd()
|
||
|
|
||
|
# create mock launch location
|
||
|
launch_dir = self.mock_cfg_location()
|
||
|
|
||
|
# switch cwd to the mock launch location, get and store settings
|
||
|
os.chdir(launch_dir)
|
||
|
# use override to ensure that the config is zero'd out with respect
|
||
|
# to any existing settings
|
||
|
with override_env_config('_', '_'):
|
||
|
original = self.get_settings()
|
||
|
|
||
|
# inject new config into a file in the mock launch location
|
||
|
self.inject_mock_cfg(launch_dir, cfg)
|
||
|
|
||
|
try:
|
||
|
# override something but don't change the value, this is to refresh
|
||
|
# the config and make sure the injected config file is read
|
||
|
with override_env_config('_', '_'):
|
||
|
current = self.get_settings()
|
||
|
finally:
|
||
|
# switch back to original dir with no new config
|
||
|
os.chdir(original_cwd)
|
||
|
return original, current
|
||
|
|
||
|
def test_config(self):
|
||
|
# ensure a non empty settings file does impact config and that the
|
||
|
# case of the key makes no difference
|
||
|
key = 'COLOR_SCHEME'
|
||
|
for case in [str.upper, str.lower]:
|
||
|
orig, curr = self.create_config_effect({case(key): 'light_bg'})
|
||
|
self.assertTrue(orig != curr)
|
||
|
self.assertTrue(orig[key] != curr[key])
|
||
|
self.assertEqual(curr[key], 'light_bg')
|
||
|
# check that just the color scheme is the cause of difference
|
||
|
orig.pop(key)
|
||
|
curr.pop(key)
|
||
|
self.assertEqual(orig, curr)
|
||
|
|
||
|
def test_empty_config(self):
|
||
|
# ensure an empty settings file does not impact config
|
||
|
orig, curr = self.create_config_effect({})
|
||
|
self.assertEqual(orig, curr)
|
||
|
|
||
|
def test_illegal_error_style_handling(self):
|
||
|
# ensure that illegal error styles are ignored
|
||
|
new_env = os.environ.copy()
|
||
|
new_env['NUMBA_CAPTURED_ERRORS'] = 'not_a_known_style'
|
||
|
source_compiled = "the source compiled"
|
||
|
code = ("from numba import njit\n@njit\ndef foo():\n\t"
|
||
|
f"print('{source_compiled}')\nfoo()")
|
||
|
out, err = run_in_subprocess(dedent(code), env=new_env)
|
||
|
expected = ("Environment variable \'NUMBA_CAPTURED_ERRORS\' is defined "
|
||
|
"but its associated value \'not_a_known_style\' could not "
|
||
|
"be parsed.")
|
||
|
err_msg = err.decode('utf-8')
|
||
|
self.assertIn(expected, err_msg)
|
||
|
ex_expected = ("Invalid style in NUMBA_CAPTURED_ERRORS: "
|
||
|
"not_a_known_style")
|
||
|
self.assertIn(ex_expected, err_msg)
|
||
|
self.assertIn(source_compiled, out.decode('utf-8'))
|
||
|
|
||
|
|
||
|
class TestNumbaOptLevel(TestCase):
|
||
|
# Tests that the setting of NUMBA_OPT influences the "cheap" module pass.
|
||
|
# Spot checks NUMBA_OPT={'max', '3', '0'}
|
||
|
|
||
|
def check(self, expected, opt_value, raw_value):
|
||
|
# local imports for state-safety
|
||
|
from numba import config, njit
|
||
|
|
||
|
# check opt value and its raw_value
|
||
|
self.assertEqual(config.OPT, opt_value)
|
||
|
self.assertEqual(config.OPT._raw_value, raw_value)
|
||
|
|
||
|
# Patch the CPUCodegen to make capture calls to the
|
||
|
# `_module_pass_manager` through a `side_effect` function that asserts
|
||
|
# that the kwargs being passed are as expected per the "NUMBA_OPT"
|
||
|
# level. The `side_effect` function immediately raises with a knwon
|
||
|
# message to abort further stages compilation once the check is
|
||
|
# complete.
|
||
|
from numba.core.codegen import CPUCodegen
|
||
|
side_effect_message = "expected side effect"
|
||
|
|
||
|
def side_effect(*args, **kwargs):
|
||
|
self.assertEqual(kwargs, expected)
|
||
|
raise RuntimeError(side_effect_message)
|
||
|
|
||
|
with mock.patch.object(CPUCodegen, '_module_pass_manager',
|
||
|
side_effect=side_effect):
|
||
|
with self.assertRaises(RuntimeError) as raises:
|
||
|
njit(lambda : ...)()
|
||
|
|
||
|
self.assertIn(side_effect_message, str(raises.exception))
|
||
|
|
||
|
@TestCase.run_test_in_subprocess(envvars={'NUMBA_OPT': 'max'})
|
||
|
def test_opt_max(self):
|
||
|
# NUMBA_OPT='max' should set opt to 3 and enable loop_vectorize
|
||
|
expected = {'loop_vectorize': True,
|
||
|
'slp_vectorize': False,
|
||
|
'opt': 3,
|
||
|
'cost': 'cheap'}
|
||
|
self.check(expected, 3, 'max')
|
||
|
|
||
|
@TestCase.run_test_in_subprocess(envvars={'NUMBA_OPT': '3'})
|
||
|
def test_opt_3(self):
|
||
|
# NUMBA_OPT='3' should not impact opt or loop_vectorize
|
||
|
expected = {'loop_vectorize': False,
|
||
|
'slp_vectorize': False,
|
||
|
'opt': 0,
|
||
|
'cost': 'cheap'}
|
||
|
self.check(expected, 3, 3)
|
||
|
|
||
|
@TestCase.run_test_in_subprocess(envvars={'NUMBA_OPT': '0'})
|
||
|
def test_opt_0(self):
|
||
|
# NUMBA_OPT='0' should not impact opt or loop_vectorize
|
||
|
expected = {'loop_vectorize': False,
|
||
|
'slp_vectorize': False,
|
||
|
'opt': 0,
|
||
|
'cost': 'cheap'}
|
||
|
self.check(expected, 0, 0)
|
||
|
|
||
|
@TestCase.run_test_in_subprocess()
|
||
|
def test_opt_default(self):
|
||
|
# NUMBA_OPT is not set, the default should not impact opt or
|
||
|
# loop_vectorize
|
||
|
expected = {'loop_vectorize': False,
|
||
|
'slp_vectorize': False,
|
||
|
'opt': 0,
|
||
|
'cost': 'cheap'}
|
||
|
self.check(expected, 3, 3)
|
||
|
|
||
|
@TestCase.run_test_in_subprocess(envvars={'NUMBA_OPT': 'invalid'})
|
||
|
def test_opt_invalid(self):
|
||
|
# NUMBA_OPT='invalid' should just proceed as default case
|
||
|
expected = {'loop_vectorize': False,
|
||
|
'slp_vectorize': False,
|
||
|
'opt': 0,
|
||
|
'cost': 'cheap'}
|
||
|
self.check(expected, 3, 3)
|
||
|
|
||
|
|
||
|
if __name__ == '__main__':
|
||
|
unittest.main()
|