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.

227 lines
9.2 KiB

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