You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

247 lines
9.4 KiB

  1. import abc
  2. from django.core.exceptions import ValidationError
  3. from django.db import IntegrityError
  4. from django.test import TestCase
  5. from model_mommy import mommy
  6. from api.models import SEQUENCE_LABELING
  7. from label_types.models import SpanType
  8. from labels.models import Span
  9. from api.tests.api.utils import prepare_project
  10. class TestSpanLabeling(abc.ABC, TestCase):
  11. overlapping = False
  12. collaborative = False
  13. @classmethod
  14. def setUpTestData(cls):
  15. cls.project = prepare_project(
  16. SEQUENCE_LABELING,
  17. allow_overlapping=cls.overlapping,
  18. collaborative_annotation=cls.collaborative
  19. )
  20. cls.example = mommy.make('Example', project=cls.project.item)
  21. cls.label_type = mommy.make('SpanType', project=cls.project.item)
  22. users = cls.project.users
  23. cls.user = users[0]
  24. cls.another_user = users[1]
  25. cls.span = Span(
  26. example=cls.example,
  27. label=cls.label_type,
  28. user=cls.user,
  29. start_offset=0,
  30. end_offset=5
  31. )
  32. def test_can_annotate_span_to_unannotated_data(self):
  33. can_annotate = Span.objects.can_annotate(self.span, self.project.item)
  34. self.assertTrue(can_annotate)
  35. class NonCollaborativeMixin:
  36. def test_allow_another_user_to_annotate_same_span(self):
  37. mommy.make(
  38. 'Span',
  39. example=self.example,
  40. label=self.label_type,
  41. user=self.another_user,
  42. start_offset=self.span.start_offset,
  43. end_offset=self.span.end_offset
  44. )
  45. can_annotate = Span.objects.can_annotate(self.span, self.project.item)
  46. self.assertTrue(can_annotate)
  47. class TestNonOverlappingSpanLabeling(TestSpanLabeling, NonCollaborativeMixin):
  48. overlapping = False
  49. collaborative = False
  50. def test_cannot_annotate_same_span_to_annotated_data(self):
  51. mommy.make(
  52. 'Span',
  53. example=self.example,
  54. label=self.label_type,
  55. user=self.user,
  56. start_offset=self.span.start_offset,
  57. end_offset=self.span.end_offset,
  58. )
  59. can_annotate = Span.objects.can_annotate(self.span, self.project.item)
  60. self.assertFalse(can_annotate)
  61. def test_cannot_annotate_different_span_type_to_annotated_data(self):
  62. mommy.make(
  63. 'Span',
  64. example=self.example,
  65. user=self.user,
  66. start_offset=self.span.start_offset,
  67. end_offset=self.span.end_offset
  68. )
  69. can_annotate = Span.objects.can_annotate(self.span, self.project.item)
  70. self.assertFalse(can_annotate)
  71. class TestOverlappingSpanLabeling(TestSpanLabeling, NonCollaborativeMixin):
  72. overlapping = True
  73. collaborative = False
  74. def test_can_annotate_same_span_to_annotated_data(self):
  75. mommy.make(
  76. 'Span',
  77. example=self.example,
  78. label=self.label_type,
  79. user=self.user,
  80. start_offset=self.span.start_offset,
  81. end_offset=self.span.end_offset,
  82. )
  83. can_annotate = Span.objects.can_annotate(self.span, self.project.item)
  84. self.assertTrue(can_annotate)
  85. class TestCollaborativeNonOverlappingSpanLabeling(TestSpanLabeling):
  86. overlapping = False
  87. collaborative = True
  88. def test_deny_another_user_to_annotate_same_span_type(self):
  89. mommy.make(
  90. 'Span',
  91. example=self.example,
  92. label=self.label_type,
  93. user=self.another_user,
  94. start_offset=self.span.start_offset,
  95. end_offset=self.span.end_offset
  96. )
  97. can_annotate = Span.objects.can_annotate(self.span, self.project.item)
  98. self.assertFalse(can_annotate)
  99. def test_deny_another_user_to_annotate_different_span_type(self):
  100. mommy.make(
  101. 'Span',
  102. example=self.example,
  103. user=self.another_user,
  104. start_offset=self.span.start_offset,
  105. end_offset=self.span.end_offset
  106. )
  107. can_annotate = Span.objects.can_annotate(self.span, self.project.item)
  108. self.assertFalse(can_annotate)
  109. class TestCollaborativeOverlappingSpanLabeling(TestSpanLabeling):
  110. overlapping = True
  111. collaborative = True
  112. def test_allow_another_user_to_annotate_same_span(self):
  113. mommy.make(
  114. 'Span',
  115. example=self.example,
  116. label=self.label_type,
  117. user=self.another_user,
  118. start_offset=self.span.start_offset,
  119. end_offset=self.span.end_offset
  120. )
  121. can_annotate = Span.objects.can_annotate(self.span, self.project.item)
  122. self.assertTrue(can_annotate)
  123. class TestSpan(TestCase):
  124. def setUp(self):
  125. self.project = prepare_project(SEQUENCE_LABELING, allow_overlapping=False)
  126. self.example = mommy.make('Example', project=self.project.item)
  127. self.user = self.project.users[0]
  128. def test_start_offset_is_not_negative(self):
  129. with self.assertRaises(IntegrityError):
  130. mommy.make('Span', start_offset=-1, end_offset=0)
  131. def test_end_offset_is_not_negative(self):
  132. with self.assertRaises(IntegrityError):
  133. mommy.make('Span', start_offset=-2, end_offset=-1)
  134. def test_start_offset_is_less_than_end_offset(self):
  135. with self.assertRaises(IntegrityError):
  136. mommy.make('Span', start_offset=0, end_offset=0)
  137. def test_unique_constraint(self):
  138. mommy.make('Span', example=self.example, start_offset=5, end_offset=10, user=self.user)
  139. mommy.make('Span', example=self.example, start_offset=0, end_offset=5, user=self.user)
  140. mommy.make('Span', example=self.example, start_offset=10, end_offset=15, user=self.user)
  141. def test_unique_constraint_violated(self):
  142. mommy.make('Span', example=self.example, start_offset=5, end_offset=10, user=self.user)
  143. spans = [(5, 10), (5, 11), (4, 10), (6, 9), (9, 15), (0, 6)]
  144. for start_offset, end_offset in spans:
  145. with self.assertRaises(ValidationError):
  146. mommy.make(
  147. 'Span',
  148. example=self.example,
  149. start_offset=start_offset,
  150. end_offset=end_offset,
  151. user=self.user
  152. )
  153. def test_unique_constraint_if_overlapping_is_allowed(self):
  154. project = prepare_project(SEQUENCE_LABELING, allow_overlapping=True)
  155. example = mommy.make('Example', project=project.item)
  156. user = project.users[0]
  157. mommy.make('Span', example=example, start_offset=5, end_offset=10, user=user)
  158. spans = [(5, 10), (5, 11), (4, 10), (6, 9), (9, 15), (0, 6)]
  159. for start_offset, end_offset in spans:
  160. mommy.make('Span', example=example, start_offset=start_offset, end_offset=end_offset, user=user)
  161. def test_update(self):
  162. span = mommy.make('Span', example=self.example, start_offset=0, end_offset=5)
  163. span.end_offset = 6
  164. span.save()
  165. class TestSpanWithoutCollaborativeMode(TestCase):
  166. def setUp(self):
  167. self.project = prepare_project(SEQUENCE_LABELING, False, allow_overlapping=False)
  168. self.example = mommy.make('Example', project=self.project.item)
  169. def test_allow_users_to_create_same_spans(self):
  170. mommy.make('Span', example=self.example, start_offset=5, end_offset=10, user=self.project.users[0])
  171. mommy.make('Span', example=self.example, start_offset=5, end_offset=10, user=self.project.users[1])
  172. class TestSpanWithCollaborativeMode(TestCase):
  173. def test_deny_users_to_create_same_spans(self):
  174. project = prepare_project(SEQUENCE_LABELING, True, allow_overlapping=False)
  175. example = mommy.make('Example', project=project.item)
  176. mommy.make('Span', example=example, start_offset=5, end_offset=10, user=project.users[0])
  177. with self.assertRaises(ValidationError):
  178. mommy.make('Span', example=example, start_offset=5, end_offset=10, user=project.users[1])
  179. def test_allow_users_to_create_same_spans_if_overlapping_is_allowed(self):
  180. project = prepare_project(SEQUENCE_LABELING, True, allow_overlapping=True)
  181. example = mommy.make('Example', project=project.item)
  182. mommy.make('Span', example=example, start_offset=5, end_offset=10, user=project.users[0])
  183. mommy.make('Span', example=example, start_offset=5, end_offset=10, user=project.users[1])
  184. class TestLabelDistribution(TestCase):
  185. def setUp(self):
  186. self.project = prepare_project(SEQUENCE_LABELING, allow_overlapping=False)
  187. self.example = mommy.make('Example', project=self.project.item)
  188. self.user = self.project.users[0]
  189. def test_calc_label_distribution(self):
  190. label_a = mommy.make('SpanType', text='labelA', project=self.project.item)
  191. label_b = mommy.make('SpanType', text='labelB', project=self.project.item)
  192. mommy.make('Span', example=self.example, start_offset=5, end_offset=10, user=self.user, label=label_a)
  193. mommy.make('Span', example=self.example, start_offset=10, end_offset=15, user=self.user, label=label_b)
  194. distribution = Span.objects.calc_label_distribution(
  195. examples=self.project.item.examples.all(),
  196. members=self.project.users,
  197. labels=SpanType.objects.all()
  198. )
  199. expected = {user.username: {label.text: 0 for label in SpanType.objects.all()} for user in self.project.users}
  200. expected[self.user.username][label_a.text] = 1
  201. expected[self.user.username][label_b.text] = 1
  202. self.assertEqual(distribution, expected)