"""Test cases for utils""" import unittest import pipeline.utils as utils class MockSource(): """Mock Source object for testing Feature""" def __init__(self, source): self.source = source def __str__(self): return self.source def to_json(self): return {"source": self.source} def __eq__(self, other): return self.source == other.source class MockInterval(): """Mock Interval object for testing Feature""" def __init__(self, start, end): self.start = start self.end = end def to_json(self): return {"start": self.start, "end": self.end} class TestSource(unittest.TestCase): """Source is a container for source, path, and provider of a media file source -- the source of the media file (eg, a URL or a local path) path -- the path to the media file provider -- the provider of the media file (eg, "FileInputJSON") Accessing the object should return the path to the media file. Methods: duration() -- return the duration of the media file (uses ffprobe, result is cached) """ # Happy path tests def setUp(self): self.source = "audio_clips/testclip-5min.aac" self.path = "audio_clips/testclip-5min.aac" self.provider = "FileInputJSON" self._duration = 306.121214 # duration of testclip-5min.aac def test_init(self): source = utils.Source(self.source, self.path, self.provider) self.assertEqual(source.source, self.source) self.assertEqual(source.path, self.path) self.assertEqual(source.provider, self.provider) def test_str(self): """Accessing the object should return the path to the media file""" source = utils.Source(self.source, self.path, self.provider) self.assertEqual(str(source), self.path) def test_repr(self): source = utils.Source(self.source, self.path, self.provider) self.assertEqual(repr(source), f"Source({self.source}, {self.path}, {self.provider})") def test_duration(self): """Use a mock duration of 306.121214 for the test clip""" source = utils.Source(self.source, self.path, self.provider) self.assertEqual(source.duration(), self._duration) def test_to_json(self): source = utils.Source(self.source, self.path, self.provider) self.assertEqual(source.to_json(), {"source": self.source, "path": self.path, "provider": self.provider}) # Sad path tests def test_init_no_source(self): with self.assertRaises(ValueError): utils.Source("", self.path, self.provider) def test_init_no_path(self): with self.assertRaises(ValueError): utils.Source(self.source, "", self.provider) def test_init_no_provider(self): with self.assertRaises(ValueError): utils.Source(self.source, self.path, "") def test_duration_no_file(self): """Test that duration raises FileNotFoundError if the file does not exist""" source = utils.Source(self.source, "fakepath-noexist!😇", self.provider) # if that file actually exists I'll eat my hat with self.assertRaises(FileNotFoundError): source.duration() class TestSourceMedia(unittest.TestCase): """SourceMedia is a container for Source objects""" class FakeSource(): """Fake source object for testing SourceMedia Since SourceMedia doesn't actually access any of the attributes of Source objects, we can use a very empty class for testing. """ def setUp(self): self.source1 = self.FakeSource() self.source2 = self.FakeSource() self.source3 = self.FakeSource() self.sources = [self.source1, self.source2, self.source3] def test_init(self): source_media = utils.SourceMedia(sources=self.sources) self.assertEqual(source_media.sources, self.sources) def test_iter(self): source_media = utils.SourceMedia(sources=self.sources) self.assertEqual(list(source_media), self.sources) class TestInterval(unittest.TestCase): """Interval is a container for start, end and duration attributes""" def setUp(self): self.start = 0 self.end = 10 self.duration = 10 # Happy path tests def test_init_start_end(self): """Create an Interval using start and end attributes""" interval = utils.Interval(start=self.start, end=self.end) self.assertEqual(interval.start, self.start) self.assertEqual(interval.end, self.end) self.assertEqual(interval.duration, self.duration) def test_init_start_duration(self): """Create an Interval using start and duration attributes""" interval = utils.Interval(start=self.start, duration=self.duration) self.assertEqual(interval.start, self.start) self.assertEqual(interval.end, self.end) self.assertEqual(interval.duration, self.duration) def test_init_end_duration(self): """Create an Interval using end and duration attributes""" interval = utils.Interval(end=self.end, duration=self.duration) self.assertEqual(interval.start, self.start) self.assertEqual(interval.end, self.end) self.assertEqual(interval.duration, self.duration) def test_from_start_classmethod(self): """Create an Interval using the from_start classmethod (ie start attribute only- uses default duration)""" interval = utils.Interval.from_start(start=self.start) self.assertEqual(interval.start, self.start) self.assertEqual(interval.end, interval.start + utils.Interval.DEFAULT_DURATION) self.assertEqual(interval.duration, utils.Interval.DEFAULT_DURATION) def test_from_end_classmethod(self): """Create an Interval using the from_end classmethod (ie end attribute only- uses default duration)""" interval = utils.Interval.from_end(end=self.end) self.assertEqual(interval.start, interval.end - utils.Interval.DEFAULT_DURATION) self.assertEqual(interval.end, self.end) self.assertEqual(interval.duration, utils.Interval.DEFAULT_DURATION) def test_repr(self): interval = utils.Interval(start=self.start, end=self.end) self.assertEqual(repr(interval), f"Interval({self.start}, {self.end}, {self.duration})") def test_lt(self): """Test the __lt__ method used for sorting Interval objects based on start time If the start times are equal, the interval with the smaller end time is considered smaller """ interval1 = utils.Interval(start=0, end=10) interval2 = utils.Interval(start=0, end=15) interval3 = utils.Interval(start=5, end=10) interval4 = utils.Interval(start=5, end=15) self.assertTrue(interval1 < interval2) # same start, interval1 has smaller end self.assertTrue(interval1 < interval3) # interval1 start is smaller self.assertTrue(interval3 > interval2) # interval3 start is larger self.assertTrue(interval3 < interval4) # same start, interval3 has smaller end def test_eq(self): """Test the __eq__ method for comparing Interval objects""" interval1 = utils.Interval(start=0, end=10) interval2 = utils.Interval(start=0, end=10) interval3 = utils.Interval(start=0, end=15) self.assertEqual(interval1, interval2) self.assertNotEqual(interval1, interval3) def test_to_json(self): """Test to_json method""" interval = utils.Interval(start=self.start, end=self.end) self.assertEqual(interval.to_json(), {"start": self.start, "end": self.end, "duration": self.duration}) def test_move_start(self): """Test the move_start method - changes start time to time specified, keeps end time constant""" interval = utils.Interval(start=self.start, end=self.end) interval.move_start(5) self.assertEqual(interval.start, 5) self.assertEqual(interval.end, 10) self.assertEqual(interval.duration, 5) def test_move_start_relative(self): """Test the move_start method with relative=True - changes start time by a relative amount""" interval = utils.Interval(start=self.start, end=self.end) interval.move_start(2, relative=True) self.assertEqual(interval.start, 2) self.assertEqual(interval.end, 10) self.assertEqual(interval.duration, 8) def test_move_end(self): """Test the move_end method - changes end time to time specified, keeps start time constant""" with self.subTest("non-relative"): interval = utils.Interval(start=self.start, end=self.end) interval.move_end(15) self.assertEqual(interval.start, 0) self.assertEqual(interval.end, 15) self.assertEqual(interval.duration, 15) with self.subTest("relative"): interval = utils.Interval(start=self.start, end=self.end) interval.move_end(5, relative=True) self.assertEqual(interval.start, 0) self.assertEqual(interval.end, 15) self.assertEqual(interval.duration, 15) def test_update_duration(self): """Test the update_duration method - changes duration to time specified, keeps start time constant""" interval = utils.Interval(start=self.start, end=self.end) interval.update_duration(15) self.assertEqual(interval.start, 0) self.assertEqual(interval.end, 15) self.assertEqual(interval.duration, 15) # test with relative=True interval.update_duration(5, relative=True) self.assertEqual(interval.start, 0) def test_overlaps(self): """Test the overlaps method - returns True if the interval overlaps with another interval""" interval1 = utils.Interval(start=0, end=10) interval2 = utils.Interval(start=5, end=15) # overlaps with interval1 interval3 = utils.Interval(start=15, end=20) # does not overlap with interval1 interval4 = utils.Interval(start=10, end=15) # touch overlap with interval1 # test with overlapping interval self.assertTrue(interval1.overlaps(interval2)) self.assertTrue(interval2.overlaps(interval1)) # test with non-overlapping interval self.assertFalse(interval1.overlaps(interval3)) self.assertFalse(interval3.overlaps(interval1)) # test with touching interval self.assertTrue(interval1.overlaps(interval4)) self.assertTrue(interval4.overlaps(interval1)) # Unhappy path tests def test_init_no_start_end(self): with self.assertRaises(ValueError): utils.Interval() def test_init_start_end_duration(self): with self.assertRaises(ValueError): utils.Interval(start=self.start, end=self.end, duration=self.duration) def test_init_start_after_end(self): with self.assertRaises(ValueError): utils.Interval(start=10, end=0) def test_init_negative_duration(self): with self.assertRaises(ValueError): utils.Interval(start=0, duration=-10) with self.assertRaises(ValueError): utils.Interval(end=0, duration=-10) class TestFeature(unittest.TestCase): """Test the Feature class""" # happy path tests def test_init(self): """Test creation of a Feature object. Needs: interval, source, feature_extractor and score""" source = MockSource("source") interval = MockInterval(0, 10) feature_extractor = "feature_extractor" score = 0.5 feature = utils.Feature(interval, source, feature_extractor, score) # NOTE: LSP complains about assign MockSource to source, but it's fine self.assertEqual(feature.interval, interval) self.assertEqual(feature.source, source) self.assertEqual(feature.feature_extractor, feature_extractor) self.assertEqual(feature.score, score) def test_repr(self): source = MockSource("source") interval = MockInterval(0, 10) feature_extractor = "test" score = 0.5 feature = utils.Feature(interval, source, feature_extractor, score) self.assertEqual(repr(feature), f"Feature({interval}, {source}, {feature_extractor}, {score})") def test_to_json(self): """test Feature.to_json method""" source = MockSource("source") interval = MockInterval(0, 10) feature_extractor = "test" score = 0.5 feature = utils.Feature(interval, source, feature_extractor, score) self.assertEqual(feature.to_json(), {"interval": interval.to_json(), "source": source.to_json(), "feature_extractor": feature_extractor, "score": score}) def test_classmethod_from_start(self): """Test the @classmethod for creating Feature with Interval.from_start""" source = MockSource("source") feature_extractor = "test" score = 0.5 feature = utils.Feature.from_start(start=0, source=source, feature_extractor=feature_extractor, score=score) self.assertEqual(feature.interval.start, 0) self.assertEqual(feature.interval.end, 5) self.assertEqual(feature.interval.duration, 5) self.assertEqual(feature.source, source) self.assertEqual(feature.feature_extractor, feature_extractor) self.assertEqual(feature.score, score) def test_classmethod_from_end(self): """Test the @classmethod for creating Feature with Interval.from_end""" source = MockSource("source") feature_extractor = "test" score = 0.5 feature = utils.Feature.from_end(end=10, source=source, feature_extractor=feature_extractor, score=score) self.assertEqual(feature.interval.start, 5) self.assertEqual(feature.interval.end, 10) self.assertEqual(feature.interval.duration, 5) self.assertEqual(feature.source, source) self.assertEqual(feature.feature_extractor, feature_extractor) self.assertEqual(feature.score, score) def test_lt(self): """test __lt__ method for sorting Feature objects""" # TODO: ensure goood coverage of corresponding __lt__ method of Interval feature1 = utils.Feature(utils.Interval(0, 10), MockSource("source"), "test_1", 0.5) feature2 = utils.Feature(utils.Interval(0, 15), MockSource("source"), "test_1", 0.5) feature3 = utils.Feature(utils.Interval(5, 10), MockSource("source"), "test_1", 0.5) feature4 = utils.Feature(utils.Interval(5, 15), MockSource("source"), "test_1", 0.5) feature5 = utils.Feature(utils.Interval(0, 10), MockSource("source"), "test_1", 0.6) # score feature6 = utils.Feature(utils.Interval(0, 10), MockSource("source"), "test_2", 0.5) # feature_extractor # test sorting based on Interval self.assertTrue(feature1 < feature2) self.assertTrue(feature1 < feature3) self.assertTrue(feature3 < feature4) # test sorting based on feature_extractor self.assertTrue(feature1 < feature6) # test sorting based on score self.assertTrue(feature1 < feature5) def test_eq(self): """test __eq__ method for comparing Feature objects""" feature1 = utils.Feature(utils.Interval(0, 10), MockSource("source"), "test_1", 0.5) feature2 = utils.Feature(utils.Interval(0, 10), MockSource("source"), "test_1", 0.5) feature3 = utils.Feature(utils.Interval(0, 10), MockSource("source"), "test_1", 0.6) self.assertEqual(feature1, feature2) self.assertNotEqual(feature1, feature3) def test_feature_no_score(self): """Test creating a Feature without a score""" source = MockSource("source") interval = MockInterval(0, 10) feature_extractor = "test" feature = utils.Feature(interval, source, feature_extractor) self.assertEqual(feature.score, 0.0) # unhappy path tests def test_init_unhappy(self): """Test init with pats missing""" with self.assertRaises(ValueError): utils.Feature(None, None, None, None) # missing interval with self.assertRaises(ValueError): utils.Feature(None, MockSource("source"), "feature_extractor", 0.5) # missing source with self.assertRaises(ValueError): utils.Feature(MockInterval(0, 10), None, "feature_extractor", 0.5) # missing feature_extractor feature = utils.Feature(MockInterval(0, 10), MockSource("source"), None, 0.5) self.assertEqual(feature.feature_extractor, "unknown") if __name__ == "__main__": unittest.main()