-
Kien Le authored
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
test_plotting_data.py 25.87 KiB
"""Tests for functions defined in sohstationviewer.controller.plottingData"""
from unittest import TestCase
from obspy import UTCDateTime
from sohstationviewer.controller.plotting_data import (
get_masspos_value_colors,
format_time,
get_title,
get_gaps,
get_time_ticks,
get_day_ticks,
get_unit_bitweight,
)
class TestGetGaps(TestCase):
"""Test suite for get_gaps."""
def test_mixed_gap_sizes(self):
"""
Test get_gaps - 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(get_gaps(gaps, min_gap), [(180, 360)])
def test_empty_gap_list(self):
"""
Test get_gaps - the given list of gaps is empty.
"""
gaps = []
min_gap = 3
self.assertListEqual(get_gaps(gaps, min_gap), [])
def test_all_gaps_are_too_short(self):
"""
Test get_gaps - the given list of gaps only contain gaps that are too
short.
"""
gaps = [(0, 60), (60, 180)]
min_gap = 3
self.assertListEqual(get_gaps(gaps, min_gap), [])
def test_all_gaps_are_long_enough(self):
"""
Test get_gaps - the given list of gaps only contain gaps that are long
enough.
"""
gaps = [(0, 180), (180, 360)]
min_gap = 3
self.assertListEqual(get_gaps(gaps, min_gap), [(0, 180), (180, 360)])
class TestGetDayTicks(TestCase):
"""Test suite for get_day_ticks."""
def test_get_day_ticks(self):
"""Test get_day_ticks."""
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(get_day_ticks(), expected)
class TestGetMassposValue(TestCase):
"""Test suite for getMasspossValue"""
def test_string_output(self):
"""
Test basic functionality of get_masspos_value_colors - 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(
get_masspos_value_colors(input_val[0], '',
input_val[1], []),
expected_input_output_pairs[input_val]
)
idx += 1
def test_list_output(self):
"""
Test basic functionality of get_masspos_value_colors - 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(
get_masspos_value_colors(
input_val[0], '', input_val[1], [], ret_type=''),
expected_input_output_pairs[input_val]
)
def test_range_option_not_supported(self):
"""
Test basic functionality of get_masspos_value_colors - the range option
is not supported.
"""
errors = []
empty_color_option = ''
self.assertIsNone(
get_masspos_value_colors(empty_color_option, '', 'B', errors))
self.assertGreater(len(errors), 0)
errors = []
bad_color_option = 'unsupported'
self.assertIsNone(
get_masspos_value_colors(bad_color_option, '', 'B', errors))
self.assertGreater(len(errors), 0)
def test_color_mode_not_supported(self):
"""
Test basic functionality of get_masspos_value_colors - color mode is
not supported.
"""
errors = []
empty_color_mode = ''
with self.assertRaises(KeyError):
get_masspos_value_colors('regular', '', empty_color_mode, errors)
errors = []
bad_color_mode = 'unsupported'
with self.assertRaises(KeyError):
get_masspos_value_colors('regular', '', bad_color_mode, errors)
class TestGetTimeTicks(TestCase):
"""Test suite for get_time_ticks."""
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 get_time_ticks - 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(
get_time_ticks(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(
get_time_ticks(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(
get_time_ticks(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(
get_time_ticks(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(
get_time_ticks(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(
get_time_ticks(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(
get_time_ticks(earliest, latest, self.date_fmt,
self.label_cnt),
expected
)
def test_boundary_time_range(self):
"""
Test basic functionality of get_time_ticks - the given time range is
exactly 1 second, 1 minute, or 1 hour. Test the behavior where these
time ranges make get_time_ticks 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(
get_time_ticks(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(
get_time_ticks(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(
get_time_ticks(earliest, latest,
self.date_fmt, self.label_cnt),
expected
)
def test_earliest_time_later_than_latest_time(self):
"""
Test basic functionality of get_time_ticks - the given latest time is
earlier than the earliest time.
"""
self.assertTupleEqual(
get_time_ticks(100, 0, self.date_fmt, self.label_cnt),
([], [], [])
)
def test_time_range_is_zero(self):
"""
Test basic functionality of get_time_ticks - the given time range is 0.
"""
self.assertTupleEqual(
get_time_ticks(0, 0, self.date_fmt, self.label_cnt),
([], [], [])
)
def test_get_time_ticks_no_label_displayed(self):
"""
Test basic functionality of get_time_ticks - no time label is requested
to be displayed.
"""
zero_label_cnt = 0
with self.assertRaises(ZeroDivisionError):
get_time_ticks(0, 1000, self.date_fmt, zero_label_cnt)
class TestFormatTimeAndGetTitle(TestCase):
"""Test suite for format_time and get_title"""
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 format_time - 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(
format_time(self.positive_epoch_time, date_mode),
self.positive_formatted_dates[date_mode])
self.assertEqual(
format_time(self.negative_epoch_time, date_mode),
self.negative_formatted_dates[date_mode])
self.assertEqual(
format_time(0, date_mode),
zero_epoch_formatted[test_name])
def test_format_time_epoch_time_date_and_time(self):
"""
Test basic functionality of format_time - 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(
format_time(self.positive_epoch_time, date_mode,
'HH:MM:SS'),
positive_time_expected
)
self.assertEqual(
format_time(self.negative_epoch_time, date_mode,
'HH:MM:SS'),
negative_time_expected
)
self.assertEqual(format_time(0, date_mode, 'HH:MM:SS'),
zero_epoch_formatted[test_name])
def test_format_time_UTCDateTime_date_only(self):
"""
Test basic functionality of format_time - 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(format_time(utc_date_time, date_mode),
expected_dates[date_mode])
def test_format_time_UTCDateTime_date_and_time(self):
"""
Test basic functionality of format_time - 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(
format_time(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 format_time - 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(format_time(test_time, empty_format),
expected)
self.assertEqual(format_time(test_time, bad_format),
expected)
with self.subTest('test_with_time_format'):
expected = f' {self.positive_formatted_time}'
self.assertEqual(format_time(test_time, empty_format, 'HH:MM:SS'),
expected)
self.assertEqual(format_time(test_time, bad_format, 'HH:MM:SS'),
expected)
def test_format_time_unsupported_time_format(self):
"""
Test basic functionality of format_time - 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(format_time(test_time, date_format, empty_format),
expected)
self.assertEqual(format_time(test_time, date_format, bad_format),
expected)
def test_format_time_unsupported_date_and_time_format(self):
"""
Test basic functionality of format_time - 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(
format_time(test_time, date_format, time_format),
expected
)
def test_get_title(self):
"""Test basic functionality of get_title."""
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(get_title(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(get_title(key, min_time, max_time, date_mode),
expected)
def test_get_title_max_time_earlier_than_min_time(self):
"""
Test basic functionality of get_title - 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(get_title(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(get_title(key, min_time, max_time, date_mode),
expected)
class TestGetUnitBitweight(TestCase):
"""Test suite for get_unit_bitweight."""
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 bitweight_opt. 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 get_unit_bitweight - the given plot type is
linesDots, linesSRate, or linesMassposs.
"""
self.chan_info['plotType'] = 'linesDots'
with self.subTest('test_no_fix_point'):
self.assertEqual(
get_unit_bitweight(self.chan_info, self.default_bitweight_opt),
'{}test_unit'
)
with self.subTest('test_have_fix_point'):
self.chan_info['fixPoint'] = 1
self.assertEqual(
get_unit_bitweight(self.chan_info, self.default_bitweight_opt),
'{:.1f}test_unit'
)
def test_soh_channel_other_plot_type(self):
"""
Test basic functionality of get_unit_bitweight - the given plot type is
not linesDots, linesSRate, or linesMassposs and the channel is not a
seismic data channel.
"""
self.assertEqual(
get_unit_bitweight(self.chan_info, self.default_bitweight_opt),
''
)
def test_seismic_channel_have_fix_point(self):
"""
Test basic functionality of get_unit_bitweight - 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(
get_unit_bitweight(self.chan_info, self.default_bitweight_opt),
'{:.1f}test_unit'
)
def test_seismic_channel_no_fix_point(self):
"""
Test basic functionality of get_unit_bitweight - 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(get_unit_bitweight(self.chan_info, 'high'), '{}V')
with self.subTest('test_have_bitweight_option'):
self.assertEqual(get_unit_bitweight(self.chan_info, ''),
'{}test_unit')
def test_no_fix_point(self):
"""
Test basic functionality of get_unit_bitweight - 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 chan_db_info 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
# chan_db_info and catching the resulting KeyError. So, in order to
# test that get_unit_bitweight handles this case correctly, we assert
# that no exception was raised.
try:
get_unit_bitweight(self.chan_info, '')
except KeyError:
self.fail()