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.

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