Skip to content

Commit

Permalink
Enhanced error handling and design of parse_data
Browse files Browse the repository at this point in the history
FileNotFoundError when config file path results in 0 read files
Data influx now denoted as required and optional in comments
  required data will stop execution with error
  required data was updated with error handling
  optional data does not stop execution
  optional data reatained its original behaviors:
    booleans are optional, but become None not False when missing
    benchmark data sets until fail then passes in a "don't care" fashion
KeyError catch for required data now extends original key error
  includes config_filepath
  includes key value that failed
Updated and added approprate unit tests
  needed return values with length for config.read call
  added error handling tests
  • Loading branch information
asgibson committed Sep 5, 2023
1 parent 7552b33 commit 0873b5d
Show file tree
Hide file tree
Showing 2 changed files with 90 additions and 20 deletions.
43 changes: 26 additions & 17 deletions onair/src/run_scripts/execution_engine.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,9 @@ def __init__(self, config_file='', run_name='', save_flag=False):
# Init Paths
self.dataFilePath = ''
self.metadataFilePath = ''
self.benchmarkFilePath = ''
self.metaFiles = ''
self.telemetryFiles = ''
self.benchmarkFilePath = ''
self.benchmarkFiles = ''
self.benchmarkIndices = ''

Expand All @@ -63,28 +63,37 @@ def parse_configs(self, config_filepath):
# print("Using config file: {}".format(config_filepath))

config = configparser.ConfigParser()
config.read(config_filepath)
## Sort Data: Telementry Data & Configuration
self.dataFilePath = config['DEFAULT']['TelemetryDataFilePath']
self.metadataFilePath = config['DEFAULT']['TelemetryMetadataFilePath']
self.metaFiles = config['DEFAULT']['MetaFiles'] # Config for vehicle telemetry
self.telemetryFiles = config['DEFAULT']['TelemetryFiles'] # Vehicle telemetry data
if len(config.read(config_filepath)) == 0:
raise FileNotFoundError(f"Config file at '{config_filepath}' could not be read.")

try:
## Sort Required Data: Telementry Data & Configuration
self.dataFilePath = config['DEFAULT']['TelemetryDataFilePath']
self.metadataFilePath = config['DEFAULT']['TelemetryMetadataFilePath']
self.metaFiles = config['DEFAULT']['MetaFiles'] # Config for vehicle telemetry
self.telemetryFiles = config['DEFAULT']['TelemetryFiles'] # Vehicle telemetry data

## Sort Required Data: Names
self.parser_file_name = config['DEFAULT']['ParserFileName']
self.parser_name = config['DEFAULT']['ParserName']
self.sim_name = config['DEFAULT']['SimName']
except KeyError as e:
new_message = f"Config file: '{config_filepath}', missing key: {e.args[0]}"
raise KeyError(new_message) from e

## Sort Optional Data: Flags
self.IO_Flag = config['RUN_FLAGS'].getboolean('IO_Flag')
self.Dev_Flag = config['RUN_FLAGS'].getboolean('Dev_Flag')
self.SBN_Flag = config['RUN_FLAGS'].getboolean('SBN_Flag')
self.Viz_Flag = config['RUN_FLAGS'].getboolean('Viz_Flag')

## Sort Optional Data: Benchmarks
try:
self.benchmarkFilePath = config['DEFAULT']['BenchmarkFilePath']
self.benchmarkFiles = config['DEFAULT']['BenchmarkFiles'] # Vehicle telemetry data
self.benchmarkIndices = config['DEFAULT']['BenchmarkIndices']
except:
pass
## Sort Data: Names
self.parser_file_name = config['DEFAULT']['ParserFileName']
self.parser_name = config['DEFAULT']['ParserName']
self.sim_name = config['DEFAULT']['SimName']

## Sort Data: Flags
self.IO_Flag = config['RUN_FLAGS'].getboolean('IO_Flag')
self.Dev_Flag = config['RUN_FLAGS'].getboolean('Dev_Flag')
self.SBN_Flag = config['RUN_FLAGS'].getboolean('SBN_Flag')
self.Viz_Flag = config['RUN_FLAGS'].getboolean('Viz_Flag')

def parse_data(self, parser_name, parser_file_name, dataFilePath, metadataFilePath, subsystems_breakdown=False):
parser = importlib.import_module('onair.data_handling.parsers.' + parser_file_name)
Expand Down
67 changes: 64 additions & 3 deletions test/src/run_scripts/test_execution_engine.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,26 @@ def test_ExecutionEngine__init__accepts_no_arguments_using_defaults_instead_with
assert cut.init_save_paths.call_count == 0

# parse_configs tests
def test_ExecutionEngine_parse_configs_raises_FileNotFoundError_when_config_cannot_be_read(mocker):
# Arrange
arg_bad_config_filepath = MagicMock()

fake_config = MagicMock()
fake_config_read_result = MagicMock()
fake_config_read_result.__len__.return_value = 0

cut = ExecutionEngine.__new__(ExecutionEngine)

mocker.patch(execution_engine.__name__ + '.configparser.ConfigParser', return_value=fake_config)
mocker.patch.object(fake_config, 'read', return_value=fake_config_read_result)

# Act
with pytest.raises(FileNotFoundError) as e_info:
cut.parse_configs(arg_bad_config_filepath)

# Assert
assert e_info.match(f"Config file at '{arg_bad_config_filepath}' could not be read.")

def test_ExecutionEngine_parse_configs_sets_all_items_without_error(mocker):
# Arrange
arg_config_filepath = MagicMock()
Expand All @@ -118,6 +138,8 @@ def test_ExecutionEngine_parse_configs_sets_all_items_without_error(mocker):
fake_dict_for_Config = {'DEFAULT':fake_default, 'RUN_FLAGS':fake_run_flags}
fake_config = MagicMock()
fake_config.__getitem__.side_effect = fake_dict_for_Config.__getitem__
fake_config_read_result = MagicMock()
fake_config_read_result.__len__.return_value = 1
fake_IO_flags = MagicMock()
fake_Dev_flags = MagicMock()
fake_SBN_flags = MagicMock()
Expand All @@ -126,7 +148,7 @@ def test_ExecutionEngine_parse_configs_sets_all_items_without_error(mocker):
cut = ExecutionEngine.__new__(ExecutionEngine)

mocker.patch(execution_engine.__name__ + '.configparser.ConfigParser', return_value=fake_config)
mocker.patch.object(fake_config, 'read')
mocker.patch.object(fake_config, 'read', return_value=fake_config_read_result)
mocker.patch.object(fake_run_flags, 'getboolean', side_effect=[fake_IO_flags, fake_Dev_flags, fake_SBN_flags, fake_Viz_flags])

# Act
Expand Down Expand Up @@ -155,7 +177,7 @@ def test_ExecutionEngine_parse_configs_sets_all_items_without_error(mocker):
assert fake_run_flags.getboolean.call_args_list[3].args == ('Viz_Flag', )
assert cut.Viz_Flag == fake_Viz_flags

def test_ExecutionEngine_parse_configs_bypasses_benmarks_when_access_raises_error(mocker):
def test_ExecutionEngine_parse_configs_bypasses_benchmarks_when_access_raises_error(mocker):
# Arrange
arg_config_filepath = MagicMock()

Expand All @@ -172,6 +194,8 @@ def test_ExecutionEngine_parse_configs_bypasses_benmarks_when_access_raises_erro
fake_dict_for_Config = {'DEFAULT':fake_default, 'RUN_FLAGS':fake_run_flags}
fake_config = MagicMock()
fake_config.__getitem__.side_effect = fake_dict_for_Config.__getitem__
fake_config_read_result = MagicMock()
fake_config_read_result.__len__.return_value = 1
fake_IO_flags = MagicMock()
fake_Dev_flags = MagicMock()
fake_SBN_flags = MagicMock()
Expand All @@ -180,7 +204,7 @@ def test_ExecutionEngine_parse_configs_bypasses_benmarks_when_access_raises_erro
cut = ExecutionEngine.__new__(ExecutionEngine)

mocker.patch(execution_engine.__name__ + '.configparser.ConfigParser', return_value=fake_config)
mocker.patch.object(fake_config, 'read')
mocker.patch.object(fake_config, 'read', return_value=fake_config_read_result)
mocker.patch.object(fake_run_flags, 'getboolean', side_effect=[fake_IO_flags, fake_Dev_flags, fake_SBN_flags, fake_Viz_flags])

# Act
Expand All @@ -191,6 +215,43 @@ def test_ExecutionEngine_parse_configs_bypasses_benmarks_when_access_raises_erro
assert hasattr(cut, 'benchmarkFiles') == False
assert hasattr(cut, 'benchmarkIndices') == False

def test_ExecutionEngine_parse_configs_raises_KeyError_with_config_file_info_when_a_required_key_is_not_in_config(mocker):
# Arrange
arg_config_filepath = MagicMock()

fake_default = {'TelemetryDataFilePath':MagicMock(),
'TelemetryMetadataFilePath':MagicMock(),
'MetaFiles':MagicMock(),
'TelemetryFiles':MagicMock(),
'BenchmarkFilePath':MagicMock(),
'BenchmarkFiles':MagicMock(),
'BenchmarkIndices':MagicMock(),
'ParserFileName':MagicMock(),
'ParserName':MagicMock(),
'SimName':MagicMock(),
}
required_keys = [item for item in list(fake_default.keys()) if 'Benchmark' not in item]
missing_key = pytest.gen.choice(required_keys)
del fake_default[missing_key]
fake_run_flags = MagicMock()
fake_dict_for_Config = {'DEFAULT':fake_default, 'RUN_FLAGS':fake_run_flags}
fake_config = MagicMock()
fake_config.__getitem__.side_effect = fake_dict_for_Config.__getitem__
fake_config_read_result = MagicMock()
fake_config_read_result.__len__.return_value = 1

cut = ExecutionEngine.__new__(ExecutionEngine)

mocker.patch(execution_engine.__name__ + '.configparser.ConfigParser', return_value=fake_config)
mocker.patch.object(fake_config, 'read', return_value=fake_config_read_result)

# Act
with pytest.raises(KeyError) as e_info:
cut.parse_configs(arg_config_filepath)

# Assert
assert e_info.match(f"Config file: '{arg_config_filepath}', missing key: {missing_key}")

# parse_data tests
def test_ExecutionEngine_parse_data_sets_the_processedSimData_to_the_TimeSynchronizer_which_was_given_the_sim_data_received_from_parsed_data(mocker):
# Arrange
Expand Down

0 comments on commit 0873b5d

Please sign in to comment.