| # Licensed to the Apache Software Foundation (ASF) under one |
| # or more contributor license agreements. See the NOTICE file |
| # distributed with this work for additional information |
| # regarding copyright ownership. The ASF licenses this file |
| # to you 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 |
| # |
| # http://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. |
| |
| from mock import patch |
| import unittest |
| |
| import ocw_config_runner.configuration_parsing as parser |
| import ocw.metrics as metrics |
| |
| import yaml |
| |
| |
| class TestIsConfigValid(unittest.TestCase): |
| @classmethod |
| def setUpClass(self): |
| not_minimal_config = """ |
| datasets: |
| """ |
| self.not_minimal = yaml.load(not_minimal_config) |
| |
| not_well_formed_config = """ |
| datasets: |
| reference: |
| data_source: local |
| file_count: 1 |
| path: /a/fake/path/file.py |
| variable: pr |
| |
| targets: |
| - data_source: local |
| file_count: 5 |
| file_glob_pattern: something for globbing files here |
| variable: pr |
| optional_args: |
| name: Target1 |
| |
| - data_source: esgf |
| dataset_id: fake dataset id |
| variable: pr |
| esgf_username: my esgf username |
| esgf_password: my esgf password |
| |
| metrics: |
| - Bias |
| - TemporalStdDev |
| """ |
| self.not_well_formed = yaml.load(not_well_formed_config) |
| |
| @patch('ocw_config_runner.configuration_parsing.logger') |
| def test_not_minimal_config(self, mock_logger): |
| ret = parser.is_config_valid(self.not_minimal) |
| self.assertFalse(ret) |
| |
| mock_logger.error.assert_called_with( |
| 'Insufficient configuration file data for an evaluation' |
| ) |
| |
| @patch('ocw_config_runner.configuration_parsing.logger') |
| def test_not_valid_config(self, mock_logger): |
| ret = parser.is_config_valid(self.not_well_formed) |
| self.assertFalse(ret) |
| |
| mock_logger.error.assert_called_with( |
| 'Configuration data is not well formed' |
| ) |
| |
| |
| class TestValidMinimalConfig(unittest.TestCase): |
| @classmethod |
| def setUpClass(self): |
| no_datasets_config = """ |
| metrics: |
| - Bias |
| """ |
| self.no_datasets = yaml.load(no_datasets_config) |
| |
| no_metrics_config = """ |
| datasets: |
| reference: |
| data_source: dap |
| url: afakeurl.com |
| variable: pr |
| """ |
| self.no_metrics = yaml.load(no_metrics_config) |
| |
| unary_with_reference_config = """ |
| datasets: |
| reference: |
| data_source: dap |
| url: afakeurl.com |
| variable: pr |
| |
| metrics: |
| - TemporalStdDev |
| """ |
| self.unary_with_reference = yaml.load(unary_with_reference_config) |
| |
| unary_with_target_config = """ |
| datasets: |
| targets: |
| - data_source: dap |
| url: afakeurl.com |
| variable: pr |
| |
| metrics: |
| - TemporalStdDev |
| """ |
| self.unary_with_target = yaml.load(unary_with_target_config) |
| |
| unary_no_reference_or_target = """ |
| datasets: |
| not_ref_or_target: |
| - data_source: dap |
| url: afakeurl.com |
| variable: pr |
| |
| metrics: |
| - TemporalStdDev |
| """ |
| self.unary_no_ref_or_target = yaml.load(unary_no_reference_or_target) |
| |
| binary_valid_config = """ |
| datasets: |
| reference: |
| data_source: dap |
| url: afakeurl.com |
| variable: pr |
| |
| targets: |
| - data_source: dap |
| url: afakeurl.com |
| variable: pr |
| metrics: |
| - Bias |
| """ |
| self.binary_valid = yaml.load(binary_valid_config) |
| |
| binary_no_reference_config = """ |
| datasets: |
| targets: |
| - data_source: dap |
| url: afakeurl.com |
| variable: pr |
| metrics: |
| - Bias |
| """ |
| self.binary_no_reference = yaml.load(binary_no_reference_config) |
| |
| binary_no_target_config = """ |
| datasets: |
| reference: |
| data_source: dap |
| url: afakeurl.com |
| variable: pr |
| |
| metrics: |
| - Bias |
| """ |
| self.binary_no_target = yaml.load(binary_no_target_config) |
| |
| @patch('ocw_config_runner.configuration_parsing.logger') |
| def test_no_datasets(self, mock_logger): |
| ret = parser._valid_minimal_config(self.no_datasets) |
| self.assertFalse(ret) |
| |
| mock_logger.error.assert_called_with( |
| 'No datasets specified in configuration data.' |
| ) |
| |
| @patch('ocw_config_runner.configuration_parsing.logger') |
| def test_no_metrics(self, mock_logger): |
| ret = parser._valid_minimal_config(self.no_metrics) |
| self.assertFalse(ret) |
| |
| mock_logger.error.assert_called_with( |
| 'No metrics specified in configuration data.' |
| ) |
| |
| def test_unary_with_reference(self): |
| ret = parser._valid_minimal_config(self.unary_with_reference) |
| self.assertTrue(ret) |
| |
| def test_unary_with_target(self): |
| ret = parser._valid_minimal_config(self.unary_with_target) |
| self.assertTrue(ret) |
| |
| @patch('ocw_config_runner.configuration_parsing.logger') |
| def test_unary_no_datasets(self, mock_logger): |
| ret = parser._valid_minimal_config(self.unary_no_ref_or_target) |
| self.assertFalse(ret) |
| |
| mock_logger.error.assert_called_with( |
| 'Unary metric in configuration data requires either a reference ' |
| 'or target dataset to be present for evaluation. Please ensure ' |
| 'that your config is well formed.' |
| ) |
| |
| def test_valid_binary(self): |
| ret = parser._valid_minimal_config(self.binary_valid) |
| self.assertTrue(ret) |
| |
| @patch('ocw_config_runner.configuration_parsing.logger') |
| def test_binary_no_reference(self, mock_logger): |
| ret = parser._valid_minimal_config(self.binary_no_reference) |
| self.assertFalse(ret) |
| |
| mock_logger.error.assert_called_with( |
| 'Binary metric in configuration requires both a reference ' |
| 'and target dataset to be present for evaluation. Please ensure ' |
| 'that your config is well formed.' |
| ) |
| |
| @patch('ocw_config_runner.configuration_parsing.logger') |
| def test_binary_no_target(self, mock_logger): |
| ret = parser._valid_minimal_config(self.binary_no_target) |
| self.assertFalse(ret) |
| |
| mock_logger.error.assert_called_with( |
| 'Binary metric in configuration requires both a reference ' |
| 'and target dataset to be present for evaluation. Please ensure ' |
| 'that your config is well formed.' |
| ) |
| |
| |
| class TestConfigIsWellFormed(unittest.TestCase): |
| @classmethod |
| def setUpClass(self): |
| malformed_reference_config = """ |
| datasets: |
| reference: |
| data_source: notavalidlocation |
| |
| metrics: |
| - Bias |
| """ |
| self.malformed_reference_conf = yaml.load(malformed_reference_config) |
| |
| malformed_target_list_config = """ |
| datasets: |
| targets: |
| notalist: |
| a_key: a_value |
| |
| alsonotalist: |
| a_key: a_value |
| |
| metrics: |
| - Bias |
| """ |
| self.malformed_target_list = yaml.load(malformed_target_list_config) |
| |
| missing_metric_name_config = """ |
| datasets: |
| reference: |
| data_source: dap |
| url: afakeurl.com |
| variable: pr |
| |
| metrics: |
| - NotABuiltInMetric |
| """ |
| self.missing_metric_name = yaml.load(missing_metric_name_config) |
| |
| bad_plot_config = """ |
| datasets: |
| reference: |
| data_source: dap |
| url: afakeurl.com |
| variable: pr |
| |
| metrics: |
| - Bias |
| |
| plots: |
| - type: NotARealPlotName |
| """ |
| bad_plot = yaml.load(bad_plot_config) |
| |
| bad_subregion_config_type = """ |
| datasets: |
| reference: |
| data_source: dap |
| url: afakeurl.com |
| variable: pr |
| |
| metrics: |
| - Bias |
| |
| subregions: |
| - this is a string instead of a list |
| """ |
| self.bad_subregion_type = yaml.load(bad_subregion_config_type) |
| |
| bad_subregion_config_length = """ |
| datasets: |
| reference: |
| data_source: dap |
| url: afakeurl.com |
| variable: pr |
| |
| metrics: |
| - Bias |
| |
| subregions: |
| - [1, 2, 3, 4, 5] |
| """ |
| self.bad_subregion_length = yaml.load(bad_subregion_config_length) |
| |
| def test_malformed_reference_config(self): |
| ret = parser._config_is_well_formed(self.malformed_reference_conf) |
| self.assertFalse(ret) |
| |
| @patch('ocw_config_runner.configuration_parsing.logger') |
| def test_malformed_target_dataset_list(self, mock_logger): |
| ret = parser._config_is_well_formed(self.malformed_target_list) |
| self.assertFalse(ret) |
| |
| mock_logger.error.assert_called_with( |
| "Expected to find list of target datasets but instead found " |
| "object of type <type 'dict'>" |
| ) |
| |
| def test_not_builtin_metric(self): |
| ret = parser._config_is_well_formed(self.missing_metric_name) |
| self.assertFalse(ret) |
| |
| @patch('ocw_config_runner.configuration_parsing.logger') |
| def test_warns_regarding_not_builtin_metric(self, mock_logger): |
| ret = parser._config_is_well_formed(self.missing_metric_name) |
| mock_logger.warn.assert_called_with( |
| 'Unable to locate metric name NotABuiltInMetric in built-in ' |
| 'metrics. If this is not a user defined metric then please check ' |
| 'for potential misspellings.' |
| ) |
| |
| def test_bad_plot_config(self): |
| ret = parser._config_is_well_formed(self.missing_metric_name) |
| self.assertFalse(ret) |
| |
| def test_bad_subregion_type(self): |
| ret = parser._config_is_well_formed(self.bad_subregion_type) |
| self.assertFalse(ret) |
| |
| def test_bad_subregion_length(self): |
| ret = parser._config_is_well_formed(self.bad_subregion_length) |
| self.assertFalse(ret) |
| |
| |
| class MetricFetchTest(unittest.TestCase): |
| @classmethod |
| def setUpClass(self): |
| binary_config = """ |
| metrics: |
| - Bias |
| - StdDevRatio |
| """ |
| unary_config = """ |
| metrics: |
| - TemporalStdDev |
| """ |
| self.unary_conf = yaml.load(unary_config) |
| self.binary_conf = yaml.load(binary_config) |
| |
| def test_contains_binary_metric(self): |
| ret = parser._contains_binary_metrics(self.binary_conf['metrics']) |
| self.assertTrue(ret) |
| |
| def test_does_not_contain_binary_metric(self): |
| ret = parser._contains_binary_metrics(self.unary_conf['metrics']) |
| self.assertFalse(ret) |
| |
| def test_contains_unary_metric(self): |
| ret = parser._contains_unary_metrics(self.unary_conf['metrics']) |
| self.assertTrue(ret) |
| |
| def test_does_not_contain_unary_metric(self): |
| ret = parser._contains_unary_metrics(self.binary_conf['metrics']) |
| self.assertFalse(ret) |
| |
| |
| class InvalidDatasetConfig(unittest.TestCase): |
| @classmethod |
| def setUpClass(self): |
| example_config_yaml = """ |
| - file_count: 1 |
| path: /a/fake/path |
| variable: pr |
| |
| - data_source: invalid_location_identifier |
| """ |
| conf = yaml.load(example_config_yaml) |
| self.missing_data_source = conf[0] |
| self.invalid_data_source = conf[1] |
| |
| @patch('ocw_config_runner.configuration_parsing.logger') |
| def test_missing_data_source_config(self, mock_logger): |
| parser._valid_dataset_config_data(self.missing_data_source) |
| mock_logger.error.assert_called_with( |
| 'Dataset does not contain a data_source attribute.' |
| ) |
| |
| @patch('ocw_config_runner.configuration_parsing.logger') |
| def test_invalid_data_source(self, mock_logger): |
| parser._valid_dataset_config_data(self.invalid_data_source) |
| mock_logger.error.assert_called_with( |
| 'Dataset does not contain a valid data_source location.' |
| ) |
| |
| |
| class TestLocalDatasetConfig(unittest.TestCase): |
| @classmethod |
| def setUpClass(self): |
| self.required_local_keys = set(['data_source', 'file_count', 'path', 'variable']) |
| example_config_yaml = """ |
| - data_source: local |
| file_count: 1 |
| path: /a/fake/path |
| variable: pr |
| optional_args: |
| name: Target1 |
| |
| - data_source: local |
| |
| - data_source: local |
| file_count: 5 |
| file_glob_pattern: something for globbing files here |
| variable: pr |
| path: /a/fake/path |
| optional_args: |
| name: Target1 |
| |
| - data_source: local |
| file_count: 5 |
| variable: pr |
| path: /a/fake/path |
| """ |
| |
| conf = yaml.load(example_config_yaml) |
| self.valid_local_single = conf[0] |
| self.invalid_local_single = conf[1] |
| self.valid_local_multi = conf[2] |
| self.invalid_local_multi = conf[1] |
| self.invalid_local_multi_file_glob = conf[3] |
| |
| def test_valid_local_config_single_file(self): |
| ret = parser._valid_dataset_config_data(self.valid_local_single) |
| self.assertTrue(ret) |
| |
| def test_valid_local_config_multi_file(self): |
| ret = parser._valid_dataset_config_data(self.valid_local_multi) |
| self.assertTrue(ret) |
| |
| @patch('ocw_config_runner.configuration_parsing.logger') |
| def test_invalid_local_config(self, mock_logger): |
| parser._valid_dataset_config_data(self.invalid_local_single) |
| |
| present_keys = set(self.invalid_local_single.keys()) |
| missing_keys = self.required_local_keys - present_keys |
| missing = sorted(list(missing_keys)) |
| |
| error = ( |
| 'Dataset does not contain required keys. ' |
| 'The following keys are missing: {}'.format(', '.join(missing)) |
| ) |
| mock_logger.error.assert_called_with(error) |
| |
| @patch('ocw_config_runner.configuration_parsing.logger') |
| def test_invalid_local_config_multi_file(self, mock_logger): |
| # mutlifile config is handled slightly differently. We should see the |
| # same missing keys in this situation as we would on the single file |
| # local config. We will test for a missing file_glob_pattern in a |
| # different test. |
| parser._valid_dataset_config_data(self.invalid_local_multi) |
| |
| present_keys = set(self.invalid_local_multi.keys()) |
| missing_keys = self.required_local_keys - present_keys |
| missing = sorted(list(missing_keys)) |
| |
| error = ( |
| 'Dataset does not contain required keys. ' |
| 'The following keys are missing: {}'.format(', '.join(missing)) |
| ) |
| mock_logger.error.assert_called_with(error) |
| |
| @patch('ocw_config_runner.configuration_parsing.logger') |
| def test_invalid_local_config_multi_file_missing_file_glob(self, mock_logger): |
| # We can't check for the file_glob_pattern pattern until after we have |
| # verified that the single local file config has been met. |
| parser._valid_dataset_config_data(self.invalid_local_multi_file_glob) |
| |
| mock_logger.error.assert_called_with( |
| 'Multi-file local dataset is missing key: file_glob_pattern' |
| ) |
| |
| |
| class TestRCMEDDatasetConfig(unittest.TestCase): |
| @classmethod |
| def setUpClass(self): |
| self.required_rcmed_keys = set([ |
| 'dataset_id', |
| 'parameter_id', |
| 'min_lat', |
| 'max_lat', |
| 'min_lon', |
| 'max_lon', |
| 'start_time', |
| 'end_time' |
| ]) |
| example_config_yaml = """ |
| - data_source: rcmed |
| dataset_id: 4 |
| parameter_id: 4 |
| min_lat: -40 |
| max_lat: 40 |
| min_lon: -50 |
| max_lon: 50 |
| start_time: YYYY-MM-DDThh:mm:ss |
| end_time: YYYY-MM-DDThh:mm:ss |
| |
| - data_source: rcmed |
| """ |
| conf = yaml.load(example_config_yaml) |
| self.valid_rcmed = conf[0] |
| self.invalid_rcmed = conf[1] |
| |
| def test_valid_rcmed_config(self): |
| ret = parser._valid_dataset_config_data(self.valid_rcmed) |
| self.assertTrue(ret) |
| |
| @patch('ocw_config_runner.configuration_parsing.logger') |
| def test_invalid_rcmed_config(self, mock_logger): |
| parser._valid_dataset_config_data(self.invalid_rcmed) |
| |
| present_keys = set(self.invalid_rcmed.keys()) |
| missing_keys = self.required_rcmed_keys - present_keys |
| missing = sorted(list(missing_keys)) |
| |
| error = ( |
| 'Dataset does not contain required keys. ' |
| 'The following keys are missing: {}'.format(', '.join(missing)) |
| ) |
| mock_logger.error.assert_called_with(error) |
| |
| |
| class TestESGFDatasetConfig(unittest.TestCase): |
| @classmethod |
| def setUpClass(self): |
| self.required_esgf_keys = set([ |
| 'data_source', |
| 'dataset_id', |
| 'variable', |
| 'esgf_username', |
| 'esgf_password' |
| ]) |
| example_config_yaml = """ |
| - data_source: esgf |
| dataset_id: fake dataset id |
| variable: pr |
| esgf_username: my esgf username |
| esgf_password: my esgf password |
| |
| - data_source: esgf |
| """ |
| conf = yaml.load(example_config_yaml) |
| self.valid_esgf = conf[0] |
| self.invalid_esgf = conf[1] |
| |
| def test_valid_esgf_conf(self): |
| ret = parser._valid_dataset_config_data(self.valid_esgf) |
| self.assertTrue(ret) |
| |
| @patch('ocw_config_runner.configuration_parsing.logger') |
| def test_invalid_esgf_conf(self, mock_logger): |
| parser._valid_dataset_config_data(self.invalid_esgf) |
| |
| present_keys = set(self.invalid_esgf.keys()) |
| missing_keys = self.required_esgf_keys - present_keys |
| missing = sorted(list(missing_keys)) |
| |
| error = ( |
| 'Dataset does not contain required keys. ' |
| 'The following keys are missing: {}'.format(', '.join(missing)) |
| ) |
| mock_logger.error.assert_called_with(error) |
| |
| |
| class TestDAPDatasetConfig(unittest.TestCase): |
| @classmethod |
| def setUpClass(self): |
| self.required_dap_keys = set(['url', 'variable']) |
| example_config_yaml = """ |
| - data_source: dap |
| url: afakeurl.com |
| variable: pr |
| |
| - data_source: dap |
| """ |
| conf = yaml.load(example_config_yaml) |
| self.valid_dap = conf[0] |
| self.invalid_dap = conf[1] |
| |
| def test_valid_dap_config(self): |
| ret = parser._valid_dataset_config_data(self.valid_dap) |
| self.assertTrue(ret) |
| |
| @patch('ocw_config_runner.configuration_parsing.logger') |
| def test_invalid_dap_config(self, mock_logger): |
| parser._valid_dataset_config_data(self.invalid_dap) |
| |
| present_keys = set(self.invalid_dap.keys()) |
| missing_keys = self.required_dap_keys - present_keys |
| missing = sorted(list(missing_keys)) |
| |
| error = ( |
| 'Dataset does not contain required keys. ' |
| 'The following keys are missing: {}'.format(', '.join(missing)) |
| ) |
| mock_logger.error.assert_called_with(error) |
| |
| |
| class ContourMapConfig(unittest.TestCase): |
| @classmethod |
| def setUpClass(self): |
| valid_contour_config = """ |
| type: contour |
| results_indices: |
| - !!python/tuple [0, 0] |
| lats: |
| range_min: -20 |
| range_max: 20 |
| range_step: 1 |
| lons: |
| range_min: -20 |
| range_max: 20 |
| range_step: 1 |
| output_name: wrf_bias_compared_to_knmi |
| """ |
| self.valid_contour = yaml.load(valid_contour_config) |
| |
| missing_keys_contour_config = """ |
| type: contour |
| """ |
| self.missing_keys_contour = yaml.load(missing_keys_contour_config) |
| |
| self.required_contour_keys = set([ |
| 'results_indices', |
| 'lats', |
| 'lons', |
| 'output_name' |
| ]) |
| |
| def test_valid_contour(self): |
| ret = parser._valid_plot_config_data(self.valid_contour) |
| self.assertTrue(ret) |
| |
| @patch('ocw_config_runner.configuration_parsing.logger') |
| def test_missing_keys_contour(self, mock_logger): |
| ret = parser._valid_plot_config_data(self.missing_keys_contour) |
| |
| present_keys = set(self.missing_keys_contour.keys()) |
| missing_keys = self.required_contour_keys - present_keys |
| missing = sorted(list(missing_keys)) |
| |
| err = ( |
| 'Plot config does not contain required keys. ' |
| 'The following keys are missing: {}' |
| ).format(', '.join(missing)) |
| mock_logger.error.assert_called_with(err) |
| |
| |
| class TestSubregionPlotConfig(unittest.TestCase): |
| @classmethod |
| def setUpClass(self): |
| valid_subregion_config = """ |
| type: subregion |
| lats: |
| range_min: -20 |
| range_max: 20 |
| range_step: 1 |
| lons: |
| range_min: -20 |
| range_max: 20 |
| range_step: 1 |
| output_name: fake_plot_name |
| """ |
| self.valid_subregion = yaml.load(valid_subregion_config) |
| |
| missing_keys_subregion_config = """ |
| type: subregion |
| """ |
| self.missing_keys_subregion = yaml.load(missing_keys_subregion_config) |
| |
| self.required_subregion_keys = set([ |
| 'lats', |
| 'lons', |
| 'output_name' |
| ]) |
| |
| def test_valid_subregion(self): |
| ret = parser._valid_plot_config_data(self.valid_subregion) |
| self.assertTrue(ret) |
| |
| @patch('ocw_config_runner.configuration_parsing.logger') |
| def test_missing_keys_subregion(self, mock_logger): |
| ret = parser._valid_plot_config_data(self.missing_keys_subregion) |
| |
| present_keys = set(self.missing_keys_subregion.keys()) |
| missing_keys = self.required_subregion_keys - present_keys |
| missing = sorted(list(missing_keys)) |
| |
| err = ( |
| 'Plot config does not contain required keys. ' |
| 'The following keys are missing: {}' |
| ).format(', '.join(missing)) |
| mock_logger.error.assert_called_with(err) |
| |
| |
| class TestInvalidPlotConfig(unittest.TestCase): |
| @classmethod |
| def setUpClass(self): |
| bad_plot_type_config = """ |
| type: NotAPlotType |
| """ |
| self.bad_plot_type = yaml.load(bad_plot_type_config) |
| |
| missing_plot_type_config = """ |
| results_indices: |
| - !!python/tuple [0, 0] |
| lats: |
| range_min: -20 |
| range_max: 20 |
| range_step: 1 |
| lons: |
| range_min: -20 |
| range_max: 20 |
| range_step: 1 |
| output_name: wrf_bias_compared_to_knmi |
| """ |
| self.missing_plot_type = yaml.load(missing_plot_type_config) |
| |
| missing_subregions_for_plot_type = """ |
| datasets: |
| - blah |
| |
| metrics: |
| - blah |
| |
| plots: |
| - type: subregion |
| results_indices: |
| - !!python/tuple [0, 0] |
| lats: |
| range_min: -20 |
| range_max: 20 |
| range_step: 1 |
| lons: |
| range_min: -20 |
| range_max: 20 |
| range_step: 1 |
| output_name: wrf_bias_compared_to_knmi |
| """ |
| self.missing_subregions = yaml.load(missing_subregions_for_plot_type) |
| |
| @patch('ocw_config_runner.configuration_parsing.logger') |
| def test_invalid_plot_type(self, mock_logger): |
| ret = parser._valid_plot_config_data(self.bad_plot_type) |
| self.assertFalse(ret) |
| |
| mock_logger.error.assert_called_with( |
| 'Invalid plot type specified.' |
| ) |
| |
| @patch('ocw_config_runner.configuration_parsing.logger') |
| def test_missing_plot_type(self, mock_logger): |
| ret = parser._valid_plot_config_data(self.missing_plot_type) |
| self.assertFalse(ret) |
| |
| mock_logger.error.assert_called_with( |
| 'Plot config does not include a type attribute.' |
| ) |
| |
| @patch('ocw_config_runner.configuration_parsing.logger') |
| def test_missing_subregion(self, mock_logger): |
| ret = parser._config_is_well_formed(self.missing_subregions) |
| self.assertFalse(ret) |
| |
| mock_logger.error.assert_called_with( |
| 'Plot config that requires subregion information is present ' |
| 'in a config file without adequate subregion information ' |
| 'provided. Please ensure that you have properly supplied 1 or ' |
| 'more subregion config values.' |
| ) |