content_annotations_cluster_processor_unittest.cc 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243
  1. // Copyright 2021 The Chromium Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style license that can be
  3. // found in the LICENSE file.
  4. #include "components/history_clusters/core/content_annotations_cluster_processor.h"
  5. #include "base/run_loop.h"
  6. #include "base/test/task_environment.h"
  7. #include "components/history_clusters/core/clustering_test_utils.h"
  8. #include "components/history_clusters/core/config.h"
  9. #include "components/history_clusters/core/on_device_clustering_features.h"
  10. #include "testing/gmock/include/gmock/gmock.h"
  11. #include "testing/gtest/include/gtest/gtest.h"
  12. namespace history_clusters {
  13. namespace {
  14. using ::testing::ElementsAre;
  15. using ::testing::UnorderedElementsAre;
  16. class ContentAnnotationsClusterProcessorTest : public ::testing::Test {
  17. public:
  18. ContentAnnotationsClusterProcessorTest() {
  19. config_.content_clustering_enabled = true;
  20. config_.content_cluster_on_intersection_similarity = false;
  21. config_.content_clustering_similarity_threshold = 0.5;
  22. SetConfigForTesting(config_);
  23. }
  24. void SetUp() override {
  25. cluster_processor_ = std::make_unique<ContentAnnotationsClusterProcessor>();
  26. }
  27. void TearDown() override { cluster_processor_.reset(); }
  28. std::vector<history::Cluster> ProcessClusters(
  29. const std::vector<history::Cluster>& clusters) {
  30. return cluster_processor_->ProcessClusters(clusters);
  31. }
  32. private:
  33. Config config_;
  34. std::unique_ptr<ContentAnnotationsClusterProcessor> cluster_processor_;
  35. };
  36. TEST_F(ContentAnnotationsClusterProcessorTest, AboveThreshold) {
  37. std::vector<history::Cluster> clusters;
  38. history::AnnotatedVisit visit =
  39. testing::CreateDefaultAnnotatedVisit(1, GURL("https://github.com/"));
  40. visit.content_annotations.model_annotations.entities = {{"github", 1}};
  41. visit.content_annotations.model_annotations.categories = {{"category", 1}};
  42. history::AnnotatedVisit visit2 =
  43. testing::CreateDefaultAnnotatedVisit(2, GURL("https://google.com/"));
  44. visit2.content_annotations.model_annotations.entities = {{"github", 1}};
  45. history::AnnotatedVisit visit4 =
  46. testing::CreateDefaultAnnotatedVisit(4, GURL("https://github.com/"));
  47. visit4.content_annotations.model_annotations.entities = {{"github", 1}};
  48. history::Cluster cluster1;
  49. cluster1.visits = {testing::CreateClusterVisit(visit),
  50. testing::CreateClusterVisit(visit2),
  51. testing::CreateClusterVisit(visit4)};
  52. clusters.push_back(cluster1);
  53. // After the context clustering, visit5 will not be in the same cluster as
  54. // visit, visit2, and visit4 but all of the visits have the same entity
  55. // so they will be clustered in the content pass.
  56. history::AnnotatedVisit visit5 = testing::CreateDefaultAnnotatedVisit(
  57. 10, GURL("https://nonexistentreferrer.com/"));
  58. visit5.content_annotations.model_annotations.entities = {{"github", 1}};
  59. visit5.content_annotations.model_annotations.categories = {{"category", 1}};
  60. history::Cluster cluster2;
  61. cluster2.visits = {testing::CreateClusterVisit(visit5)};
  62. clusters.push_back(cluster2);
  63. std::vector<history::Cluster> result_clusters = ProcessClusters(clusters);
  64. EXPECT_THAT(
  65. testing::ToVisitResults(result_clusters),
  66. ElementsAre(ElementsAre(
  67. testing::VisitResult(1, 1.0), testing::VisitResult(2, 1.0),
  68. testing::VisitResult(4, 1.0), testing::VisitResult(10, 1.0))));
  69. ASSERT_EQ(result_clusters.size(), 1u);
  70. }
  71. TEST_F(ContentAnnotationsClusterProcessorTest, BelowThreshold) {
  72. std::vector<history::Cluster> clusters;
  73. history::AnnotatedVisit visit =
  74. testing::CreateDefaultAnnotatedVisit(1, GURL("https://github.com/"));
  75. visit.content_annotations.model_annotations.entities = {{"github", 1}};
  76. visit.content_annotations.model_annotations.categories = {{"category", 1}};
  77. history::AnnotatedVisit visit2 =
  78. testing::CreateDefaultAnnotatedVisit(2, GURL("https://google.com/"));
  79. visit2.visit_row.visit_duration = base::Seconds(20);
  80. history::Cluster cluster1;
  81. cluster1.visits = {testing::CreateClusterVisit(visit),
  82. testing::CreateClusterVisit(visit2)};
  83. clusters.push_back(cluster1);
  84. // After the context clustering, visit4 will not be in the same cluster as
  85. // visit and visit2 but should be clustered together since they have the same
  86. // entities.
  87. history::AnnotatedVisit visit4 =
  88. testing::CreateDefaultAnnotatedVisit(4, GURL("https://github.com/"));
  89. visit4.content_annotations.model_annotations.categories = {{"category", 1}};
  90. visit4.content_annotations.model_annotations.entities = {{"github", 1}};
  91. history::Cluster cluster2;
  92. cluster2.visits = {testing::CreateClusterVisit(visit4)};
  93. clusters.push_back(cluster2);
  94. // This visit has the same entities but no categories and shouldn't be
  95. // grouped with the others.
  96. history::AnnotatedVisit visit5 = testing::CreateDefaultAnnotatedVisit(
  97. 10, GURL("https://nonexistentreferrer.com/"));
  98. visit5.content_annotations.model_annotations.entities = {{"github", 1}};
  99. history::Cluster cluster3;
  100. cluster3.visits = {testing::CreateClusterVisit(visit5)};
  101. clusters.push_back(cluster3);
  102. // This visit has the same categories but no entities and shouldn't be
  103. // grouped with the others.
  104. history::AnnotatedVisit visit6 =
  105. testing::CreateDefaultAnnotatedVisit(11, GURL("https://othervisit.com/"));
  106. visit6.content_annotations.model_annotations.categories = {{"category", 1}};
  107. history::Cluster cluster4;
  108. cluster4.visits = {testing::CreateClusterVisit(visit6)};
  109. clusters.push_back(cluster4);
  110. // This visit has no content annotations and shouldn't be grouped with the
  111. // others.
  112. history::AnnotatedVisit visit7 = testing::CreateDefaultAnnotatedVisit(
  113. 12, GURL("https://nocontentannotations.com/"));
  114. history::Cluster cluster5;
  115. cluster5.visits = {testing::CreateClusterVisit(visit7)};
  116. clusters.push_back(cluster5);
  117. std::vector<history::Cluster> result_clusters = ProcessClusters(clusters);
  118. EXPECT_THAT(testing::ToVisitResults(result_clusters),
  119. ElementsAre(ElementsAre(testing::VisitResult(1, 1.0),
  120. testing::VisitResult(2, 1.0),
  121. testing::VisitResult(4, 1.0)),
  122. ElementsAre(testing::VisitResult(10, 1.0)),
  123. ElementsAre(testing::VisitResult(11, 1.0)),
  124. ElementsAre(testing::VisitResult(12, 1.0))));
  125. EXPECT_THAT(result_clusters.size(), 4u);
  126. }
  127. class ContentAnnotationsIntersectionMetricTest
  128. : public ContentAnnotationsClusterProcessorTest {
  129. public:
  130. ContentAnnotationsIntersectionMetricTest() {
  131. config_.content_clustering_enabled = true;
  132. config_.content_cluster_on_intersection_similarity = true;
  133. SetConfigForTesting(config_);
  134. }
  135. private:
  136. Config config_;
  137. };
  138. TEST_F(ContentAnnotationsIntersectionMetricTest, AboveThreshold) {
  139. std::vector<history::Cluster> clusters;
  140. history::AnnotatedVisit visit =
  141. testing::CreateDefaultAnnotatedVisit(1, GURL("https://github.com/"));
  142. visit.content_annotations.model_annotations.entities = {{"github", 1}};
  143. visit.content_annotations.model_annotations.categories = {{"category", 1}};
  144. history::AnnotatedVisit visit2 =
  145. testing::CreateDefaultAnnotatedVisit(2, GURL("https://google.com/"));
  146. visit2.content_annotations.model_annotations.entities = {{"github", 1}};
  147. visit2.content_annotations.model_annotations.categories = {{"category2", 2}};
  148. history::AnnotatedVisit visit4 =
  149. testing::CreateDefaultAnnotatedVisit(4, GURL("https://github.com/"));
  150. visit4.content_annotations.model_annotations.entities = {{"github", 1}};
  151. history::Cluster cluster1;
  152. cluster1.visits = {testing::CreateClusterVisit(visit),
  153. testing::CreateClusterVisit(visit2),
  154. testing::CreateClusterVisit(visit4)};
  155. clusters.push_back(cluster1);
  156. // After the context clustering, visit5 will not be in the same cluster as
  157. // visit, visit2, and visit4 but has the same two categories, which is
  158. // above the default intersection threshold.
  159. history::AnnotatedVisit visit5 = testing::CreateDefaultAnnotatedVisit(
  160. 10, GURL("https://nonexistentreferrer.com/"));
  161. visit5.content_annotations.model_annotations.entities = {{"github", 1}};
  162. visit5.content_annotations.model_annotations.categories = {{"category", 1}};
  163. history::AnnotatedVisit visit6 = testing::CreateDefaultAnnotatedVisit(
  164. 11, GURL("https://nonexistentreferrer.com/"));
  165. visit6.content_annotations.model_annotations.entities = {{"github", 1}};
  166. visit6.content_annotations.model_annotations.categories = {{"category2", 2}};
  167. history::Cluster cluster2;
  168. cluster2.visits = {testing::CreateClusterVisit(visit5),
  169. testing::CreateClusterVisit(visit6)};
  170. clusters.push_back(cluster2);
  171. std::vector<history::Cluster> result_clusters = ProcessClusters(clusters);
  172. EXPECT_THAT(testing::ToVisitResults(result_clusters),
  173. ElementsAre(ElementsAre(
  174. testing::VisitResult(1, 1.0), testing::VisitResult(2, 1.0),
  175. testing::VisitResult(4, 1.0), testing::VisitResult(10, 1.0),
  176. testing::VisitResult(11, 1.0))));
  177. ASSERT_EQ(result_clusters.size(), 1u);
  178. }
  179. TEST_F(ContentAnnotationsIntersectionMetricTest, BelowThreshold) {
  180. std::vector<history::Cluster> clusters;
  181. history::AnnotatedVisit visit =
  182. testing::CreateDefaultAnnotatedVisit(1, GURL("https://github.com/"));
  183. visit.content_annotations.model_annotations.entities = {{"github", 1}};
  184. visit.content_annotations.model_annotations.categories = {{"category", 1}};
  185. history::AnnotatedVisit visit2 =
  186. testing::CreateDefaultAnnotatedVisit(2, GURL("https://google.com/"));
  187. visit2.content_annotations.model_annotations.entities = {{"github", 1}};
  188. visit2.content_annotations.model_annotations.categories = {{"category2", 2}};
  189. history::AnnotatedVisit visit4 =
  190. testing::CreateDefaultAnnotatedVisit(4, GURL("https://github.com/"));
  191. visit4.content_annotations.model_annotations.entities = {{"github", 1}};
  192. history::Cluster cluster1;
  193. cluster1.visits = {testing::CreateClusterVisit(visit),
  194. testing::CreateClusterVisit(visit2),
  195. testing::CreateClusterVisit(visit4)};
  196. clusters.push_back(cluster1);
  197. // After the context clustering, visit5 will not be in the same cluster as
  198. // visit, visit2, and visit4 and it only intersects on one categories, which
  199. // is below the default intersection threshold.
  200. history::AnnotatedVisit visit5 = testing::CreateDefaultAnnotatedVisit(
  201. 10, GURL("https://nonexistentreferrer.com/"));
  202. visit5.content_annotations.model_annotations.entities = {{"github", 1}};
  203. visit5.content_annotations.model_annotations.categories = {{"category", 1}};
  204. history::Cluster cluster2;
  205. cluster2.visits = {testing::CreateClusterVisit(visit5)};
  206. clusters.push_back(cluster2);
  207. std::vector<history::Cluster> result_clusters = ProcessClusters(clusters);
  208. ASSERT_EQ(result_clusters.size(), 2u);
  209. }
  210. } // namespace
  211. } // namespace history_clusters