"""Tests for functions defined in sohstationviewer.controller.plottingData""" from unittest import TestCase from obspy import UTCDateTime from sohstationviewer.controller.plottingData import ( getMassposValueColors, formatTime, getTitle, getGaps, getTimeTicks, getDayTicks, getUnitBitweight ) class TestGetGaps(TestCase): """Test suite for getGaps.""" def test_mixed_gap_sizes(self): """ Test getGaps - the given list of gaps contain both gaps that are too short and gaps that are long enough. """ gaps = [(0, 60), (60, 180), (180, 360)] min_gap = 3 self.assertListEqual(getGaps(gaps, min_gap), [(180, 360)]) def test_empty_gap_list(self): """ Test getGaps - the given list of gaps is empty. """ gaps = [] min_gap = 3 self.assertListEqual(getGaps(gaps, min_gap), []) def test_all_gaps_are_too_short(self): """ Test getGaps - the given list of gaps only contain gaps that are too short. """ gaps = [(0, 60), (60, 180)] min_gap = 3 self.assertListEqual(getGaps(gaps, min_gap), []) def test_all_gaps_are_long_enough(self): """ Test getGaps - the given list of gaps only contain gaps that are long enough. """ gaps = [(0, 180), (180, 360)] min_gap = 3 self.assertListEqual(getGaps(gaps, min_gap), [(0, 180), (180, 360)]) class TestGetDayTicks(TestCase): """Test suite for getDayTicks.""" def test_get_day_ticks(self): """Test getDayTicks.""" expected = ( [12, 24, 36, 48, 60, 72, 84, 96, 108, 120, 132, 144, 156, 168, 180, 192, 204, 216, 228, 240, 252, 264, 276], [48, 96, 144, 192, 240], ['04', '08', '12', '16', '20'] ) self.assertTupleEqual(getDayTicks(), expected) class TestGetMassposValue(TestCase): """Test suite for getMasspossValue""" def test_string_output(self): """ Test basic functionality of getMassposValueColors - the range option and color mode are correct, and the output is a string. """ expected_input_output_pairs = { ('regular', 'B'): '0.5:C|2.0:G|4.0:Y|7.0:R|7.0:+M', ('regular', 'W'): '0.5:B|2.0:B|4.0:B|7.0:B|7.0:+B', ('trillium', 'B'): '0.5:C|1.8:G|2.4:Y|3.5:R|3.5:+M', ('trillium', 'W'): '0.5:B|1.8:B|2.4:B|3.5:B|3.5:+B', } test_names = ( 'test_regular_B', 'test_regular_W', 'test_trillium_B', 'test_trillium_W', ) idx = 0 for input_val in expected_input_output_pairs: with self.subTest(test_names[idx]): self.assertEqual( getMassposValueColors(input_val[0], '', input_val[1], []), expected_input_output_pairs[input_val] ) idx += 1 def test_list_output(self): """ Test basic functionality of getMassposValueColors - the range option and color mode are correct, and the output is a list. """ expected_input_output_pairs = { ('regular', 'B'): [(0.5, 'C'), (2.0, 'G'), (4.0, 'Y'), (7.0, 'R'), (7.0, 'M')], ('regular', 'W'): [(0.5, 'B'), (2.0, 'B'), (4.0, 'B'), (7.0, 'B'), (7.0, 'B')], ('trillium', 'B'): [(0.5, 'C'), (1.8, 'G'), (2.4, 'Y'), (3.5, 'R'), (3.5, 'M')], ('trillium', 'W'): [(0.5, 'B'), (1.8, 'B'), (2.4, 'B'), (3.5, 'B'), (3.5, 'B')], } test_names = ( 'test_regular_B', 'test_regular_W', 'test_trillium_B', 'test_trillium_W', ) for i, input_val in enumerate(expected_input_output_pairs): with self.subTest(test_names[i]): self.assertListEqual( getMassposValueColors( input_val[0], '', input_val[1], [], retType=''), expected_input_output_pairs[input_val] ) def test_range_option_not_supported(self): """ Test basic functionality of getMassposValueColors - the range option is not supported. """ errors = [] empty_color_option = '' self.assertIsNone( getMassposValueColors(empty_color_option, '', 'B', errors)) self.assertGreater(len(errors), 0) errors = [] bad_color_option = 'unsupported' self.assertIsNone( getMassposValueColors(bad_color_option, '', 'B', errors)) self.assertGreater(len(errors), 0) def test_color_mode_not_supported(self): """ Test basic functionality of getMassposValueColors - the color mode is not supported. """ errors = [] empty_color_mode = '' with self.assertRaises(KeyError): getMassposValueColors('regular', '', empty_color_mode, errors) errors = [] bad_color_mode = 'unsupported' with self.assertRaises(KeyError): getMassposValueColors('regular', '', bad_color_mode, errors) class TestGetTimeTicks(TestCase): """Test suite for getTimeTicks.""" def setUp(self) -> None: """Set up text fixtures.""" self.label_cnt = 5 self.date_fmt = 'YYYYMMDD' def test_expected_time_range(self): """ Test basic functionality of getTimeTicks - the given time range is expected in the data. """ with self.subTest('test_less_than_a_minute'): earliest = UTCDateTime(1970, 1, 1, 0, 0, 0).timestamp latest = UTCDateTime(1970, 1, 1, 0, 0, 5).timestamp expected = ( [1.0, 2.0, 3.0, 4.0], [1.0, 2.0, 3.0, 4.0], ['19700101 00:00:01', '19700101 00:00:02', '19700101 00:00:03', '19700101 00:00:04'] ) self.assertTupleEqual( getTimeTicks(earliest, latest, self.date_fmt, self.label_cnt), expected ) with self.subTest('test_at_least_a_minute'): earliest = UTCDateTime(1970, 1, 1, 0, 0, 0).timestamp latest = UTCDateTime(1970, 1, 1, 0, 5, 53).timestamp expected = ( [60.0, 120.0, 180.0, 240.0, 300.0], [60.0, 120.0, 180.0, 240.0, 300.0], ['19700101 00:01:00', '19700101 00:02:00', '19700101 00:03:00', '19700101 00:04:00', '19700101 00:05:00'] ) self.assertTupleEqual( getTimeTicks(earliest, latest, self.date_fmt, self.label_cnt), expected ) with self.subTest('test_at_least_an_hour'): earliest = UTCDateTime(1970, 1, 1, 0, 0, 0).timestamp latest = UTCDateTime(1970, 1, 1, 7, 45, 51).timestamp expected = ( [3600.0, 7200.0, 10800.0, 14400.0, 18000.0, 21600.0, 25200.0], [3600.0, 10800.0, 18000.0, 25200.0], ['19700101 01:00', '19700101 03:00', '19700101 05:00', '19700101 07:00'] ) self.assertTupleEqual( getTimeTicks(earliest, latest, self.date_fmt, self.label_cnt), expected ) with self.subTest('test_at_least_ten_days'): # Exactly 10 days earliest = UTCDateTime(1970, 1, 1, 0, 0, 0).timestamp latest = UTCDateTime(1970, 1, 11, 0, 0, 0).timestamp expected = ( [86400.0, 172800.0, 259200.0, 345600.0, 432000.0, 518400.0, 604800.0, 691200.0, 777600.0], [86400.0, 259200.0, 432000.0, 604800.0, 777600.0], ['19700102', '19700104', '19700106', '19700108', '19700110'] ) self.assertTupleEqual( getTimeTicks(earliest, latest, self.date_fmt, self.label_cnt), expected ) # More than 10 days but fewer than 30 days earliest = UTCDateTime(1970, 1, 1, 0, 0, 0).timestamp latest = UTCDateTime(1970, 1, 14, 3, 5, 21).timestamp expected = ( [86400.0, 172800.0, 259200.0, 345600.0, 432000.0, 518400.0, 604800.0, 691200.0, 777600.0, 864000.0, 950400.0, 1036800.0, 1123200.0], [86400.0, 345600.0, 604800.0, 864000.0, 1123200.0], ['19700102', '19700105', '19700108', '19700111', '19700114'] ) self.assertTupleEqual( getTimeTicks(earliest, latest, self.date_fmt, self.label_cnt), expected ) with self.subTest('test_at_least_thirty_days'): # Exactly 30 days earliest = UTCDateTime(1970, 1, 1, 0, 0, 0).timestamp latest = UTCDateTime(1970, 1, 31, 0, 0, 0).timestamp expected = ([864000.0, 1728000.0], [864000.0, 1728000.0], ['19700111', '19700121']) self.assertTupleEqual( getTimeTicks(earliest, latest, self.date_fmt, self.label_cnt), expected ) # More than 30 days earliest = UTCDateTime(1970, 1, 1, 0, 0, 0).timestamp latest = UTCDateTime(1970, 3, 21, 21, 43, 53).timestamp expected = ( [864000.0, 1728000.0, 2592000.0, 3456000.0, 4320000.0, 5184000.0, 6048000.0], [864000.0, 2592000.0, 4320000.0, 6048000.0], ['19700111', '19700131', '19700220', '19700312'] ) self.assertTupleEqual( getTimeTicks(earliest, latest, self.date_fmt, self.label_cnt), expected ) def test_boundary_time_range(self): """ Test basic functionality of getTimeTicks - the given time range is exactly 1 second, 1 minute, or 1 hour. Test the behavior where these time ranges make getTimeTicks returns a tuple that contains empty lists. """ expected = ([], [], []) with self.subTest('test_exactly_one_second'): earliest = UTCDateTime(1970, 1, 1, 0, 0, 0).timestamp latest = UTCDateTime(1970, 1, 1, 0, 0, 1).timestamp self.assertTupleEqual( getTimeTicks(earliest, latest, self.date_fmt, self.label_cnt), expected ) with self.subTest('test_exactly_one_minute'): earliest = UTCDateTime(1970, 1, 1, 0, 0, 0).timestamp latest = UTCDateTime(1970, 1, 1, 0, 1, 0).timestamp self.assertTupleEqual( getTimeTicks(earliest, latest, self.date_fmt, self.label_cnt), expected ) with self.subTest('test_exactly_one_hour'): earliest = UTCDateTime(1970, 1, 1, 0, 0, 0).timestamp latest = UTCDateTime(1970, 1, 1, 1, 0, 0).timestamp self.assertTupleEqual( getTimeTicks(earliest, latest, self.date_fmt, self.label_cnt), expected ) def test_earliest_time_later_than_latest_time(self): """ Test basic functionality of getTimeTicks - the given latest time is earlier than the earliest time. """ self.assertTupleEqual( getTimeTicks(100, 0, self.date_fmt, self.label_cnt), ([], [], []) ) def test_time_range_is_zero(self): """ Test basic functionality of getTimeTicks - the given time range is 0. """ self.assertTupleEqual( getTimeTicks(0, 0, self.date_fmt, self.label_cnt), ([], [], []) ) def test_get_time_ticks_no_label_displayed(self): """ Test basic functionality of getTimeTicks - no time label is requested to be displayed. """ zero_label_cnt = 0 with self.assertRaises(ZeroDivisionError): getTimeTicks(0, 1000, self.date_fmt, zero_label_cnt) class TestFormatTimeAndGetTitle(TestCase): """Test suite for formatTime and getTitle""" def setUp(self) -> None: """Set up text fixtures.""" self.positive_epoch_time = 67567567 self.positive_formatted_dates = { 'YYYY-MM-DD': '1972-02-22', 'YYYYMMDD': '19720222', 'YYYY:DOY': '1972:053', } self.positive_formatted_time = '00:46:07' self.negative_epoch_time = -67567567 self.negative_formatted_dates = { 'YYYY-MM-DD': '1967-11-10', 'YYYYMMDD': '19671110', 'YYYY:DOY': '1967:314', } self.negative_formatted_time = '23:13:53' def test_format_time_epoch_time_date_only(self): """ Test basic functionality of formatTime - given time is epoch time and uses only a date format. Tests three cases for each date format: epoch time is positive, negative, and zero. """ # formatter:off test_name_to_date_mode_map = { 'test_year_month_day_format': 'YYYY-MM-DD', 'test_year_month_day_format_no_dash': 'YYYYMMDD', 'test_day_of_year_format': 'YYYY:DOY' } zero_epoch_formatted = { 'test_year_month_day_format': '1970-01-01', 'test_year_month_day_format_no_dash': '19700101', 'test_day_of_year_format': '1970:001', } # formatter:on for test_name, date_mode in test_name_to_date_mode_map.items(): with self.subTest(test_name): self.assertEqual( formatTime(self.positive_epoch_time, date_mode), self.positive_formatted_dates[date_mode]) self.assertEqual( formatTime(self.negative_epoch_time, date_mode), self.negative_formatted_dates[date_mode]) self.assertEqual( formatTime(0, date_mode), zero_epoch_formatted[test_name]) def test_format_time_epoch_time_date_and_time(self): """ Test basic functionality of formatTime - given time is epoch time and both a time and a date format are used. Tests three cases for each date format: epoch time is positive, negative, and zero. """ # formatter:off test_name_to_date_mode_map = { 'test_year_month_day_format': 'YYYY-MM-DD', 'test_year_month_day_format_no_dash': 'YYYYMMDD', 'test_day_of_year_format': 'YYYY:DOY', } zero_epoch_formatted = { 'test_year_month_day_format': '1970-01-01 00:00:00', 'test_year_month_day_format_no_dash': '19700101 00:00:00', 'test_day_of_year_format': '1970:001 00:00:00', } # formatter:on for test_name, date_mode in test_name_to_date_mode_map.items(): with self.subTest(test_name): positive_time_expected = ( f'{self.positive_formatted_dates[date_mode]} ' f'{self.positive_formatted_time}' ) negative_time_expected = ( f'{self.negative_formatted_dates[date_mode]} ' f'{self.negative_formatted_time}' ) self.assertEqual( formatTime(self.positive_epoch_time, date_mode, 'HH:MM:SS'), positive_time_expected ) self.assertEqual( formatTime(self.negative_epoch_time, date_mode, 'HH:MM:SS'), negative_time_expected ) self.assertEqual(formatTime(0, date_mode, 'HH:MM:SS'), zero_epoch_formatted[test_name]) def test_format_time_UTCDateTime_date_only(self): """ Test basic functionality of formatTime - given time is an UTCDateTime instance and uses only a date format. """ test_name_to_date_mode_map = { 'test_year_month_day_format': 'YYYY-MM-DD', 'test_year_month_day_format_no_dash': 'YYYYMMDD', 'test_day_of_year_format': 'YYYY:DOY', } utc_date_time = UTCDateTime(self.positive_epoch_time) expected_dates = self.positive_formatted_dates for test_name, date_mode in test_name_to_date_mode_map.items(): with self.subTest(test_name): self.assertEqual(formatTime(utc_date_time, date_mode), expected_dates[date_mode]) def test_format_time_UTCDateTime_date_and_time(self): """ Test basic functionality of formatTime - given time is an UTCDateTime instance and both a time and a date format are used. """ test_name_to_date_mode_map = { 'test_year_month_day_format': 'YYYY-MM-DD', 'test_year_month_day_format_no_dash': 'YYYYMMDD', 'test_day_of_year_format': 'YYYY:DOY', } test_time = UTCDateTime(self.positive_epoch_time) expected_dates = self.positive_formatted_dates expected_time = self.positive_formatted_time for test_name, date_mode in test_name_to_date_mode_map.items(): with self.subTest('test_year_month_day_format'): self.assertEqual( formatTime(test_time, date_mode, 'HH:MM:SS'), f'{expected_dates[date_mode]} {expected_time}' ) def test_format_time_unsupported_date_format(self): """ Test basic functionality of formatTime - given date format is not supported. """ test_time = self.positive_epoch_time empty_format = '' bad_format = 'bad_format' with self.subTest('test_without_time_format'): expected = '' self.assertEqual(formatTime(test_time, empty_format), expected) self.assertEqual(formatTime(test_time, bad_format), expected) with self.subTest('test_with_time_format'): expected = f' {self.positive_formatted_time}' self.assertEqual(formatTime(test_time, empty_format, 'HH:MM:SS'), expected) self.assertEqual(formatTime(test_time, bad_format, 'HH:MM:SS'), expected) def test_format_time_unsupported_time_format(self): """ Test basic functionality of formatTime - given time format is not supported. """ test_time = self.positive_epoch_time date_format = 'YYYYMMDD' empty_format = '' bad_format = 'bad_format' expected = self.positive_formatted_dates[date_format] self.assertEqual(formatTime(test_time, date_format, empty_format), expected) self.assertEqual(formatTime(test_time, date_format, bad_format), expected) def test_format_time_unsupported_date_and_time_format(self): """ Test basic functionality of formatTime - both given date and time format are unsupported. """ test_time = self.positive_epoch_time expected = '' bad_date_formats = ['', 'bad_date_format'] bad_time_format = ['', 'bad_time_format'] for date_format in bad_date_formats: for time_format in bad_time_format: self.assertEqual( formatTime(test_time, date_format, time_format), expected ) def test_get_title(self): """Test basic functionality of getTitle.""" date_mode = 'YYYYMMDD' min_time = 0 max_time = self.positive_epoch_time formatted_max_time = (f'{self.positive_formatted_dates[date_mode]}' f' {self.positive_formatted_time}') with self.subTest('test_mseed'): key = '3734' expected = (f'3734 19700101 00:00:00 to ' f'{formatted_max_time} (18768.77)') self.assertEqual(getTitle(key, min_time, max_time, date_mode), expected) with self.subTest('test_rt130'): key = ('92EB', 25) expected = (f"('92EB', 25) 19700101 00:00:00 to " f"{formatted_max_time} (18768.77)") self.assertEqual(getTitle(key, min_time, max_time, date_mode), expected) def test_get_title_max_time_earlier_than_min_time(self): """ Test basic functionality of getTitle - the given maximum time is chronologically earlier than the given minimum time. """ date_mode = 'YYYYMMDD' min_time = self.positive_epoch_time max_time = 0 formatted_max_time = (f'{self.positive_formatted_dates[date_mode]}' f' {self.positive_formatted_time}') with self.subTest('test_mseed'): key = '3734' expected = (f'3734 {formatted_max_time} to ' f'19700101 00:00:00 (-18768.77)') self.assertEqual(getTitle(key, min_time, max_time, date_mode), expected) with self.subTest('test_rt130'): key = ('92EB', 25) expected = (f"('92EB', 25) {formatted_max_time} to " f"19700101 00:00:00 (-18768.77)") self.assertEqual(getTitle(key, min_time, max_time, date_mode), expected) class TestGetUnitBitweight(TestCase): """Test suite for getUnitBitweight.""" def setUp(self) -> None: """Set up test fixtures.""" self.chan_info = { 'channel': 'Test Channel Name', 'fixPoint': 0, 'plotType': 'test_plot_type', 'unit': 'test_unit' } # In most cases, we do not care about the value of bitweightOpt. So, we # give it a default value unless needed. self.default_bitweight_opt = 'low' def test_soh_channel_linesDots_linesSRate_linesMasspos_plot_type(self): """ Test basic functionality of getUnitBitweight - the given plot type is linesDots, linesSRate, or linesMassposs. """ self.chan_info['plotType'] = 'linesDots' with self.subTest('test_no_fix_point'): self.assertEqual( getUnitBitweight(self.chan_info, self.default_bitweight_opt), '{}test_unit' ) with self.subTest('test_have_fix_point'): self.chan_info['fixPoint'] = 1 self.assertEqual( getUnitBitweight(self.chan_info, self.default_bitweight_opt), '{:.1f}test_unit' ) def test_soh_channel_other_plot_type(self): """ Test basic functionality of getUnitBitweight - the given plot type is not linesDots, linesSRate, or linesMassposs and the channel is not a seismic data channel. """ self.assertEqual( getUnitBitweight(self.chan_info, self.default_bitweight_opt), '' ) def test_seismic_channel_have_fix_point(self): """ Test basic functionality of getUnitBitweight - the given plot type is not linesDots, linesSRate, or linesMassposs, the channel is a seismic data channel, and there is a fix point. """ self.chan_info['channel'] = 'SEISMIC' self.chan_info['fixPoint'] = 1 self.assertEqual( getUnitBitweight(self.chan_info, self.default_bitweight_opt), '{:.1f}test_unit' ) def test_seismic_channel_no_fix_point(self): """ Test basic functionality of getUnitBitweight - the given plot type is not linesDots, linesSRate, or linesMassposs, the channel is a seismic data channel, and there is no fix point. """ self.chan_info['channel'] = 'SEISMIC' with self.subTest('test_no_bitweight_option'): self.assertEqual(getUnitBitweight(self.chan_info, 'high'), '{}V') with self.subTest('test_have_bitweight_option'): self.assertEqual(getUnitBitweight(self.chan_info, ''), '{}test_unit') def test_no_fix_point(self): """ Test basic functionality of getUnitBitweight - the given channel info does not contain a value for fixPoint. """ del self.chan_info['fixPoint'] # Asserts that an error is not raised. TestCase does not have a method # to check that an exception was not raised so we have to use this # workaround. For more discussion on this topic, see these # stackoverflow questions and the answers # https://stackoverflow.com/questions/6181555/pass-a-python-unittest-if-an-exception-isnt-raised?noredirect=1&lq=1 # noqa # https://stackoverflow.com/questions/4319825/python-unittest-opposite-of-assertraises/4319870#4319870 # noqa:E501 # Some context for this code: in getUnitBitWeight, if chanDB does not # have 'fix_point' as a key, then the value of fix_point defaults to 0. # This is implemented by getting the value of 'fix_point' in chanDB and # catching the resulting KeyError. So, in order to test that # getUnitBitweight handles this case correctly, we assert that no # exception was raised. try: getUnitBitweight(self.chan_info, '') except KeyError: self.fail()