diff --git a/bin/deepstate/core/base.py b/bin/deepstate/core/base.py index 4c3aa5e8..dde2c85d 100644 --- a/bin/deepstate/core/base.py +++ b/bin/deepstate/core/base.py @@ -179,13 +179,20 @@ def parse_args(cls) -> Optional[argparse.Namespace]: target_args_parsed.append((key, val)) _args['target_args'] = target_args_parsed - - # if configuration is specified, parse and replace argument instantiations if args.config: _args.update(cls.build_from_config(args.config)) # type: ignore + # Re-apply argparse types to values read from config, since configparser + # returns everything as strings (e.g. timeout="36000" instead of 36000). + for action in parser._actions: + if action.dest in _args and action.type is not None: + try: + _args[action.dest] = action.type(_args[action.dest]) + except (ValueError, TypeError): + pass + # Cleanup: force --no_exit_compile to be on, meaning if user specifies a `[test]` section, - # execution will continue. Delete config as well + # execution will continue. Delete config as well. _args["no_exit_compile"] = True # type: ignore del _args["config"] @@ -200,7 +207,7 @@ def parse_args(cls) -> Optional[argparse.Namespace]: logger.setLevel(LOG_LEVEL_INT_TO_STR[_args["min_log_level"]]) else: L.debug("Using log level from $DEEPSTATE_LOG.") - + cls._ARGS = args return cls._ARGS @@ -236,7 +243,8 @@ def build_from_config(config: str, allowed_keys: Optional[List[str]] = None, inc "test" # configurations for harness execution under analysis tool ] - parser = configparser.SafeConfigParser() + # ConfigParser replaces the deprecated SafeConfigParser removed in Python 3.12 + parser = configparser.ConfigParser() parser.read(config) for section, kv in parser._sections.items(): # type: ignore @@ -264,7 +272,15 @@ def build_from_config(config: str, allowed_keys: Optional[List[str]] = None, inc if isinstance(val, list): _context[key].append(val) else: - _context[key] = val + # configparser returns all values as strings. Try casting to int + # then float, falling back to string for non-numeric values. + try: + _context[key] = int(val) + except (ValueError, TypeError): + try: + _context[key] = float(val) + except (ValueError, TypeError): + _context[key] = val return context # type: ignore @@ -278,4 +294,4 @@ def init_from_dict(self, _args: Optional[Dict[str, str]] = None) -> None: """ args: Dict[str, str] = vars(self._ARGS) if _args is None else _args for key, value in args.items(): - setattr(self, key, value) + setattr(self, key, value) \ No newline at end of file diff --git a/tests/test_config_types.py b/tests/test_config_types.py new file mode 100644 index 00000000..c2b4e091 --- /dev/null +++ b/tests/test_config_types.py @@ -0,0 +1,40 @@ +from __future__ import print_function +import configparser +from unittest.mock import patch + +from deepstate.core.base import AnalysisBackend +import deepstate_base + + +INI_DATA = """ +[test] +timeout = 36000 +mem_limit = 100 +min_log_level = 2 +float_value = 3.14 +output_test_dir = /tmp/deepstate_out +""" + + +def fake_read(self, *args, **kwargs): + self.read_string(INI_DATA) + return [] + +class ConfigParseTest(deepstate_base.DeepStateTestCase): + def run_deepstate(self, deepstate): + # Not actually invoking deepstate, just reusing the test harness structure + + with patch("deepstate.core.base.configparser.ConfigParser.read", fake_read): + result = AnalysisBackend.build_from_config("dummy_path") + + self.assertIsInstance(result["timeout"], int) + self.assertIsInstance(result["mem_limit"], int) + self.assertIsInstance(result["min_log_level"], int) + self.assertIsInstance(result["float_value"], float) + self.assertIsInstance(result["output_test_dir"], str) + + self.assertEqual(result["timeout"], 36000) + self.assertEqual(result["mem_limit"], 100) + self.assertEqual(result["min_log_level"], 2) + self.assertEqual(result["float_value"], 3.14) + self.assertEqual(result["output_test_dir"], "/tmp/deepstate_out")