Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 23 additions & 7 deletions bin/deepstate/core/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"]

Expand All @@ -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

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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

Expand All @@ -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)
40 changes: 40 additions & 0 deletions tests/test_config_types.py
Original file line number Diff line number Diff line change
@@ -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")