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.

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