Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright 2016 The Chromium Authors. All rights reserved. | 1 // Copyright 2016 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include "components/ntp_snippets/remote/ntp_snippets_fetcher.h" | 5 #include "components/ntp_snippets/remote/ntp_snippets_fetcher.h" |
| 6 | 6 |
| 7 #include <deque> | |
| 7 #include <map> | 8 #include <map> |
| 8 #include <utility> | 9 #include <utility> |
| 9 | 10 |
| 10 #include "base/json/json_reader.h" | 11 #include "base/json/json_reader.h" |
| 11 #include "base/memory/ptr_util.h" | 12 #include "base/memory/ptr_util.h" |
| 12 #include "base/strings/stringprintf.h" | 13 #include "base/strings/stringprintf.h" |
| 13 #include "base/test/histogram_tester.h" | 14 #include "base/test/histogram_tester.h" |
| 14 #include "base/test/test_mock_time_task_runner.h" | 15 #include "base/test/test_mock_time_task_runner.h" |
| 15 #include "base/threading/thread_task_runner_handle.h" | 16 #include "base/threading/thread_task_runner_handle.h" |
| 16 #include "base/time/time.h" | 17 #include "base/time/time.h" |
| 17 #include "base/values.h" | 18 #include "base/values.h" |
| 18 #include "components/ntp_snippets/category_factory.h" | 19 #include "components/ntp_snippets/category_factory.h" |
| 20 #include "components/ntp_snippets/features.h" | |
| 19 #include "components/ntp_snippets/ntp_snippets_constants.h" | 21 #include "components/ntp_snippets/ntp_snippets_constants.h" |
| 20 #include "components/ntp_snippets/remote/ntp_snippet.h" | 22 #include "components/ntp_snippets/remote/ntp_snippet.h" |
| 21 #include "components/ntp_snippets/user_classifier.h" | 23 #include "components/ntp_snippets/user_classifier.h" |
| 22 #include "components/prefs/testing_pref_service.h" | 24 #include "components/prefs/testing_pref_service.h" |
| 23 #include "components/signin/core/browser/account_tracker_service.h" | 25 #include "components/signin/core/browser/account_tracker_service.h" |
| 24 #include "components/signin/core/browser/fake_profile_oauth2_token_service.h" | 26 #include "components/signin/core/browser/fake_profile_oauth2_token_service.h" |
| 25 #include "components/signin/core/browser/fake_signin_manager.h" | 27 #include "components/signin/core/browser/fake_signin_manager.h" |
| 26 #include "components/signin/core/browser/test_signin_client.h" | 28 #include "components/signin/core/browser/test_signin_client.h" |
| 27 #include "components/variations/entropy_provider.h" | 29 #include "components/variations/entropy_provider.h" |
| 28 #include "components/variations/variations_params_manager.h" | 30 #include "components/variations/variations_params_manager.h" |
| (...skipping 124 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 153 NTPSnippetsFetcher::OptionalFetchedCategories fetched_categories) { | 155 NTPSnippetsFetcher::OptionalFetchedCategories fetched_categories) { |
| 154 Run(fetch_result, &fetched_categories); | 156 Run(fetch_result, &fetched_categories); |
| 155 } | 157 } |
| 156 | 158 |
| 157 MOCK_METHOD2( | 159 MOCK_METHOD2( |
| 158 Run, | 160 Run, |
| 159 void(NTPSnippetsFetcher::FetchResult fetch_result, | 161 void(NTPSnippetsFetcher::FetchResult fetch_result, |
| 160 NTPSnippetsFetcher::OptionalFetchedCategories* fetched_categories)); | 162 NTPSnippetsFetcher::OptionalFetchedCategories* fetched_categories)); |
| 161 }; | 163 }; |
| 162 | 164 |
| 165 // DelegateCallingTestURLFetcherFactory can be used to temporarily inject | |
| 166 // TestURLFetcher instances into a scope. | |
| 167 // Client code can access the last created fetcher to verify expected | |
| 168 // properties. When the factory gets destroyed, all available delegates of still | |
| 169 // valid fetchers will be called. | |
| 170 // This ensures once-bound callbacks (like SnippetsAvailableCallback) will be | |
| 171 // called at some point and are not leaked. | |
| 172 class DelegateCallingTestURLFetcherFactory | |
| 173 : public net::TestURLFetcherFactory, | |
| 174 public net::TestURLFetcherDelegateForTests { | |
| 175 public: | |
| 176 DelegateCallingTestURLFetcherFactory() { | |
| 177 SetDelegateForTests(this); | |
| 178 set_remove_fetcher_on_delete(true); | |
| 179 } | |
| 180 | |
| 181 ~DelegateCallingTestURLFetcherFactory() override { | |
| 182 while (!fetchers_.empty()) { | |
| 183 DropAndCallDelegate(fetchers_.front()); | |
| 184 } | |
| 185 } | |
| 186 | |
| 187 std::unique_ptr<net::URLFetcher> CreateURLFetcher( | |
| 188 int id, | |
| 189 const GURL& url, | |
| 190 net::URLFetcher::RequestType request_type, | |
| 191 net::URLFetcherDelegate* d) override { | |
| 192 if (GetFetcherByID(id)) { | |
| 193 LOG(WARNING) << "The ID " << id << " was already assigned to a fetcher." | |
| 194 << "Its delegate will thereforde be called right now."; | |
| 195 DropAndCallDelegate(id); | |
| 196 } | |
| 197 fetchers_.push_back(id); | |
| 198 return TestURLFetcherFactory::CreateURLFetcher(id, url, request_type, d); | |
| 199 } | |
| 200 | |
| 201 // Returns the raw pointer of the last created URL fetcher. | |
| 202 // If it was destroyed or no fetcher was created, it will return a nulltpr. | |
| 203 net::TestURLFetcher* GetLastCreatedFetcher() { | |
| 204 if (fetchers_.empty()) { | |
| 205 return nullptr; | |
| 206 } | |
| 207 return GetFetcherByID(fetchers_.front()); | |
| 208 } | |
| 209 | |
| 210 private: | |
| 211 // The fetcher can either be destroyed because the delegate was called during | |
| 212 // execution or because we called it on destruction. | |
| 213 void DropAndCallDelegate(int fetcher_id) { | |
| 214 auto found_id_iter = | |
| 215 std::find(fetchers_.begin(), fetchers_.end(), fetcher_id); | |
| 216 if (found_id_iter == fetchers_.end()) { | |
| 217 return; | |
| 218 } | |
| 219 fetchers_.erase(found_id_iter); | |
| 220 net::TestURLFetcher* fetcher = GetFetcherByID(fetcher_id); | |
| 221 if (!fetcher || !fetcher->delegate()) { | |
| 222 return; | |
|
Marc Treib
2016/12/12 10:17:16
When would |fetcher| be null here? Add a comment t
fhorschig
2016/12/12 11:05:10
Removed. It cannot be 0. If it is, crashing is an
| |
| 223 } | |
| 224 fetcher->delegate()->OnURLFetchComplete(fetcher); | |
| 225 } | |
| 226 | |
| 227 // net::TestURLFetcherDelegateForTests overrides: | |
| 228 void OnRequestStart(int fetcher_id) override {} | |
| 229 void OnChunkUpload(int fetcher_id) override {} | |
| 230 void OnRequestEnd(int fetcher_id) override { | |
| 231 DropAndCallDelegate(fetcher_id); | |
| 232 } | |
| 233 | |
| 234 std::deque<int> fetchers_; // std::queue doesn't support std::find. | |
| 235 }; | |
| 236 | |
| 163 // Factory for FakeURLFetcher objects that always generate errors. | 237 // Factory for FakeURLFetcher objects that always generate errors. |
| 164 class FailingFakeURLFetcherFactory : public net::URLFetcherFactory { | 238 class FailingFakeURLFetcherFactory : public net::URLFetcherFactory { |
| 165 public: | 239 public: |
| 166 std::unique_ptr<net::URLFetcher> CreateURLFetcher( | 240 std::unique_ptr<net::URLFetcher> CreateURLFetcher( |
| 167 int id, const GURL& url, net::URLFetcher::RequestType request_type, | 241 int id, const GURL& url, net::URLFetcher::RequestType request_type, |
| 168 net::URLFetcherDelegate* d) override { | 242 net::URLFetcherDelegate* d) override { |
| 169 return base::MakeUnique<net::FakeURLFetcher>( | 243 return base::MakeUnique<net::FakeURLFetcher>( |
| 170 url, d, /*response_data=*/std::string(), net::HTTP_NOT_FOUND, | 244 url, d, /*response_data=*/std::string(), net::HTTP_NOT_FOUND, |
| 171 net::URLRequestStatus::FAILED); | 245 net::URLRequestStatus::FAILED); |
| 172 } | 246 } |
| (...skipping 31 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 204 class RequestBuilderWithMockedFlagsForTesting | 278 class RequestBuilderWithMockedFlagsForTesting |
| 205 : public NTPSnippetsFetcher::RequestBuilder { | 279 : public NTPSnippetsFetcher::RequestBuilder { |
| 206 public: | 280 public: |
| 207 RequestBuilderWithMockedFlagsForTesting() : RequestBuilder() {} | 281 RequestBuilderWithMockedFlagsForTesting() : RequestBuilder() {} |
| 208 | 282 |
| 209 private: | 283 private: |
| 210 bool IsSendingUserClassEnabled() const override { return true; } | 284 bool IsSendingUserClassEnabled() const override { return true; } |
| 211 bool IsSendingTopLanguagesEnabled() const override { return true; } | 285 bool IsSendingTopLanguagesEnabled() const override { return true; } |
| 212 }; | 286 }; |
| 213 | 287 |
| 288 struct ExpectationForVariationParam { | |
|
Marc Treib
2016/12/12 10:17:16
nit: Move this into the test that uses it.
fhorschig
2016/12/12 11:05:10
Done.
| |
| 289 std::string param_value; | |
| 290 int expected_value; | |
| 291 }; | |
| 292 | |
| 214 NTPSnippetsFetcherTest() | 293 NTPSnippetsFetcherTest() |
| 215 : NTPSnippetsFetcherTest(GURL(kTestChromeReaderUrl), | 294 : NTPSnippetsFetcherTest(GURL(kTestChromeReaderUrl), |
| 216 std::map<std::string, std::string>()) {} | 295 std::map<std::string, std::string>()) {} |
| 217 | 296 |
| 218 NTPSnippetsFetcherTest(const GURL& gurl, | 297 NTPSnippetsFetcherTest(const GURL& gurl, |
| 219 const std::map<std::string, std::string>& params) | 298 const std::map<std::string, std::string>& params) |
| 220 : params_manager_( | 299 : params_manager_( |
| 221 base::MakeUnique<variations::testing::VariationParamsManager>( | 300 base::MakeUnique<variations::testing::VariationParamsManager>( |
| 222 ntp_snippets::kStudyName, | 301 ntp_snippets::kStudyName, |
| 223 params)), | 302 params)), |
| (...skipping 61 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 285 void SetFetchingPersonalizationVariation( | 364 void SetFetchingPersonalizationVariation( |
| 286 const std::string& personalization_string) { | 365 const std::string& personalization_string) { |
| 287 params_manager_.reset(); | 366 params_manager_.reset(); |
| 288 std::map<std::string, std::string> params = { | 367 std::map<std::string, std::string> params = { |
| 289 {"fetching_personalization", personalization_string}}; | 368 {"fetching_personalization", personalization_string}}; |
| 290 params_manager_ = | 369 params_manager_ = |
| 291 base::MakeUnique<variations::testing::VariationParamsManager>( | 370 base::MakeUnique<variations::testing::VariationParamsManager>( |
| 292 ntp_snippets::kStudyName, params); | 371 ntp_snippets::kStudyName, params); |
| 293 } | 372 } |
| 294 | 373 |
| 374 void SetVariationParametersForFeatures( | |
| 375 const std::map<std::string, std::string>& params, | |
| 376 const std::set<std::string>& features) { | |
| 377 params_manager_.reset(); | |
| 378 params_manager_ = | |
| 379 base::MakeUnique<variations::testing::VariationParamsManager>( | |
| 380 ntp_snippets::kStudyName, params, features); | |
| 381 } | |
| 382 | |
| 295 void SetFakeResponse(const std::string& response_data, | 383 void SetFakeResponse(const std::string& response_data, |
| 296 net::HttpStatusCode response_code, | 384 net::HttpStatusCode response_code, |
| 297 net::URLRequestStatus::Status status) { | 385 net::URLRequestStatus::Status status) { |
| 298 InitFakeURLFetcherFactory(); | 386 InitFakeURLFetcherFactory(); |
| 299 fake_url_fetcher_factory_->SetFakeResponse(test_url_, response_data, | 387 fake_url_fetcher_factory_->SetFakeResponse(test_url_, response_data, |
| 300 response_code, status); | 388 response_code, status); |
| 301 } | 389 } |
| 302 | 390 |
| 303 std::unique_ptr<translate::LanguageModel> MakeLanguageModel( | 391 std::unique_ptr<translate::LanguageModel> MakeLanguageModel( |
| 304 const std::vector<std::string>& codes) { | 392 const std::vector<std::string>& codes) { |
| (...skipping 600 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 905 EXPECT_THAT(snippets_fetcher().last_json(), Eq(kJsonStr)); | 993 EXPECT_THAT(snippets_fetcher().last_json(), Eq(kJsonStr)); |
| 906 EXPECT_THAT( | 994 EXPECT_THAT( |
| 907 histogram_tester().GetAllSamples("NewTabPage.Snippets.FetchResult"), | 995 histogram_tester().GetAllSamples("NewTabPage.Snippets.FetchResult"), |
| 908 ElementsAre(base::Bucket(/*min=*/0, /*count=*/1))); | 996 ElementsAre(base::Bucket(/*min=*/0, /*count=*/1))); |
| 909 EXPECT_THAT(histogram_tester().GetAllSamples( | 997 EXPECT_THAT(histogram_tester().GetAllSamples( |
| 910 "NewTabPage.Snippets.FetchHttpResponseOrErrorCode"), | 998 "NewTabPage.Snippets.FetchHttpResponseOrErrorCode"), |
| 911 ElementsAre(base::Bucket(/*min=*/200, /*count=*/1))); | 999 ElementsAre(base::Bucket(/*min=*/200, /*count=*/1))); |
| 912 } | 1000 } |
| 913 | 1001 |
| 914 TEST_F(NTPSnippetsFetcherTest, ShouldRestrictToHosts) { | 1002 TEST_F(NTPSnippetsFetcherTest, ShouldRestrictToHosts) { |
| 915 net::TestURLFetcherFactory test_url_fetcher_factory; | 1003 DelegateCallingTestURLFetcherFactory fetcher_factory; |
| 916 NTPSnippetsFetcher::Params params = test_params(); | 1004 NTPSnippetsFetcher::Params params = test_params(); |
| 917 params.hosts = {"www.somehost1.com", "www.somehost2.com"}; | 1005 params.hosts = {"www.somehost1.com", "www.somehost2.com"}; |
| 918 params.count_to_fetch = 17; | 1006 params.count_to_fetch = 17; |
| 1007 | |
| 919 snippets_fetcher().FetchSnippets( | 1008 snippets_fetcher().FetchSnippets( |
| 920 params, ToSnippetsAvailableCallback(&mock_callback())); | 1009 params, ToSnippetsAvailableCallback(&mock_callback())); |
| 921 net::TestURLFetcher* fetcher = test_url_fetcher_factory.GetFetcherByID(0); | 1010 |
| 1011 net::TestURLFetcher* fetcher = fetcher_factory.GetLastCreatedFetcher(); | |
| 922 ASSERT_THAT(fetcher, NotNull()); | 1012 ASSERT_THAT(fetcher, NotNull()); |
| 923 std::unique_ptr<base::Value> value = | 1013 std::unique_ptr<base::Value> value = |
| 924 base::JSONReader::Read(fetcher->upload_data()); | 1014 base::JSONReader::Read(fetcher->upload_data()); |
| 925 ASSERT_TRUE(value) << " failed to parse JSON: " | 1015 ASSERT_TRUE(value) << " failed to parse JSON: " |
| 926 << PrintToString(fetcher->upload_data()); | 1016 << PrintToString(fetcher->upload_data()); |
| 927 const base::DictionaryValue* dict = nullptr; | 1017 const base::DictionaryValue* dict = nullptr; |
| 928 ASSERT_TRUE(value->GetAsDictionary(&dict)); | 1018 ASSERT_TRUE(value->GetAsDictionary(&dict)); |
| 929 const base::DictionaryValue* local_scoring_params = nullptr; | 1019 const base::DictionaryValue* local_scoring_params = nullptr; |
| 930 ASSERT_TRUE(dict->GetDictionary("advanced_options.local_scoring_params", | 1020 ASSERT_TRUE(dict->GetDictionary("advanced_options.local_scoring_params", |
| 931 &local_scoring_params)); | 1021 &local_scoring_params)); |
| 932 const base::ListValue* content_selectors = nullptr; | 1022 const base::ListValue* content_selectors = nullptr; |
| 933 ASSERT_TRUE( | 1023 ASSERT_TRUE( |
| 934 local_scoring_params->GetList("content_selectors", &content_selectors)); | 1024 local_scoring_params->GetList("content_selectors", &content_selectors)); |
| 935 ASSERT_THAT(content_selectors->GetSize(), Eq(static_cast<size_t>(2))); | 1025 ASSERT_THAT(content_selectors->GetSize(), Eq(static_cast<size_t>(2))); |
| 936 const base::DictionaryValue* content_selector = nullptr; | 1026 const base::DictionaryValue* content_selector = nullptr; |
| 937 ASSERT_TRUE(content_selectors->GetDictionary(0, &content_selector)); | 1027 ASSERT_TRUE(content_selectors->GetDictionary(0, &content_selector)); |
| 938 std::string content_selector_value; | 1028 std::string content_selector_value; |
| 939 EXPECT_TRUE(content_selector->GetString("value", &content_selector_value)); | 1029 EXPECT_TRUE(content_selector->GetString("value", &content_selector_value)); |
| 940 EXPECT_THAT(content_selector_value, Eq("www.somehost1.com")); | 1030 EXPECT_THAT(content_selector_value, Eq("www.somehost1.com")); |
| 941 ASSERT_TRUE(content_selectors->GetDictionary(1, &content_selector)); | 1031 ASSERT_TRUE(content_selectors->GetDictionary(1, &content_selector)); |
| 942 EXPECT_TRUE(content_selector->GetString("value", &content_selector_value)); | 1032 EXPECT_TRUE(content_selector->GetString("value", &content_selector_value)); |
| 943 EXPECT_THAT(content_selector_value, Eq("www.somehost2.com")); | 1033 EXPECT_THAT(content_selector_value, Eq("www.somehost2.com")); |
| 944 // Call the delegate callback manually as the TestURLFetcher deletes any | 1034 } |
| 945 // call to the delegate that usually happens on |Start|. | 1035 |
| 946 // Without the call to the delegate, it leaks the request that owns itself. | 1036 TEST_F(NTPSnippetsFetcherTest, RetryOnInteractiveRequests) { |
| 947 ASSERT_THAT(fetcher->delegate(), NotNull()); | 1037 DelegateCallingTestURLFetcherFactory fetcher_factory; |
| 948 EXPECT_CALL(mock_callback(), | 1038 NTPSnippetsFetcher::Params params = test_params(); |
| 949 Run(NTPSnippetsFetcher::FetchResult::URL_REQUEST_STATUS_ERROR, | 1039 params.interactive_request = true; |
| 950 /*snippets=*/Not(HasValue()))) | 1040 |
| 951 .Times(1); | 1041 snippets_fetcher().FetchSnippets( |
| 952 // An 4XX response needs the least configuration to successfully invoke the | 1042 params, ToSnippetsAvailableCallback(&mock_callback())); |
| 953 // callback properly as the results are not important in this test. | 1043 |
| 954 fetcher->set_response_code(net::HTTP_NOT_FOUND); | 1044 net::TestURLFetcher* fetcher = fetcher_factory.GetLastCreatedFetcher(); |
| 955 fetcher->set_status(net::URLRequestStatus(net::URLRequestStatus::FAILED, -2)); | 1045 ASSERT_THAT(fetcher, NotNull()); |
| 956 fetcher->delegate()->OnURLFetchComplete(fetcher); | 1046 EXPECT_THAT(fetcher->GetMaxRetriesOn5xx(), Eq(2)); |
| 1047 } | |
| 1048 | |
| 1049 TEST_F(NTPSnippetsFetcherTest, RetriesConfigurableOnNonInteractiveRequests) { | |
| 1050 NTPSnippetsFetcher::Params params = test_params(); | |
| 1051 params.interactive_request = false; | |
| 1052 const std::vector<ExpectationForVariationParam> retry_config_expectation = { | |
| 1053 {"", 0}, // Keep 2 retries if no variation is set. | |
|
Marc Treib
2016/12/12 10:17:16
Comment is out of date.
One alternative is adding
fhorschig
2016/12/12 11:05:10
Good idea.
tschumann
2016/12/12 11:55:48
This is fine by me, but we're getting close into t
fhorschig
2016/12/12 12:16:45
Looks like a valid concern even though this very e
| |
| 1054 {"0", 0}, | |
| 1055 {"-1", 0}, // Allow no negative retry count. | |
| 1056 {"3", 3}}; | |
| 1057 | |
| 1058 for (const auto& retry_config : retry_config_expectation) { | |
| 1059 DelegateCallingTestURLFetcherFactory fetcher_factory; | |
| 1060 SetVariationParametersForFeatures( | |
| 1061 {{"background_5xx_retries_count", retry_config.param_value}}, | |
| 1062 {ntp_snippets::kArticleSuggestionsFeature.name}); | |
| 1063 | |
| 1064 snippets_fetcher().FetchSnippets( | |
| 1065 params, ToSnippetsAvailableCallback(&mock_callback())); | |
| 1066 | |
| 1067 net::TestURLFetcher* fetcher = fetcher_factory.GetLastCreatedFetcher(); | |
| 1068 ASSERT_THAT(fetcher, NotNull()); | |
| 1069 EXPECT_THAT(fetcher->GetMaxRetriesOn5xx(), Eq(retry_config.expected_value)); | |
| 1070 } | |
| 957 } | 1071 } |
| 958 | 1072 |
| 959 TEST_F(NTPSnippetsFetcherTest, ShouldReportUrlStatusError) { | 1073 TEST_F(NTPSnippetsFetcherTest, ShouldReportUrlStatusError) { |
| 960 SetFakeResponse(/*response_data=*/std::string(), net::HTTP_NOT_FOUND, | 1074 SetFakeResponse(/*response_data=*/std::string(), net::HTTP_NOT_FOUND, |
| 961 net::URLRequestStatus::FAILED); | 1075 net::URLRequestStatus::FAILED); |
| 962 EXPECT_CALL(mock_callback(), | 1076 EXPECT_CALL(mock_callback(), |
| 963 Run(NTPSnippetsFetcher::FetchResult::URL_REQUEST_STATUS_ERROR, | 1077 Run(NTPSnippetsFetcher::FetchResult::URL_REQUEST_STATUS_ERROR, |
| 964 /*snippets=*/Not(HasValue()))) | 1078 /*snippets=*/Not(HasValue()))) |
| 965 .Times(1); | 1079 .Times(1); |
| 966 snippets_fetcher().FetchSnippets( | 1080 snippets_fetcher().FetchSnippets( |
| (...skipping 149 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 1116 const NTPSnippetsFetcher::OptionalFetchedCategories& fetched_categories) { | 1230 const NTPSnippetsFetcher::OptionalFetchedCategories& fetched_categories) { |
| 1117 if (fetched_categories) { | 1231 if (fetched_categories) { |
| 1118 // Matchers above aren't any more precise than this, so this is sufficient | 1232 // Matchers above aren't any more precise than this, so this is sufficient |
| 1119 // for test-failure diagnostics. | 1233 // for test-failure diagnostics. |
| 1120 return os << "list with " << fetched_categories->size() << " elements"; | 1234 return os << "list with " << fetched_categories->size() << " elements"; |
| 1121 } | 1235 } |
| 1122 return os << "null"; | 1236 return os << "null"; |
| 1123 } | 1237 } |
| 1124 | 1238 |
| 1125 } // namespace ntp_snippets | 1239 } // namespace ntp_snippets |
| OLD | NEW |