blob: 724afff95679d7987542eb9f5e69af56d05bad11 [file] [log] [blame]
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/translate/core/browser/translate_manager.h"
#include "base/json/json_reader.h"
#include "base/run_loop.h"
#include "base/test/histogram_tester.h"
#include "base/test/scoped_feature_list.h"
#include "build/build_config.h"
#include "components/infobars/core/infobar.h"
#include "components/pref_registry/pref_registry_syncable.h"
#include "components/pref_registry/testing_pref_service_syncable.h"
#include "components/translate/core/browser/mock_translate_driver.h"
#include "components/translate/core/browser/translate_browser_metrics.h"
#include "components/translate/core/browser/translate_client.h"
#include "components/translate/core/browser/translate_download_manager.h"
#include "components/translate/core/browser/translate_prefs.h"
#include "components/translate/core/common/translate_pref_names.h"
#include "components/variations/variations_associated_data.h"
#include "net/base/network_change_notifier.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
using testing::_;
using testing::Return;
using testing::SetArgPointee;
namespace translate {
namespace {
const char kTrialName[] = "MyTrial";
#if defined(OS_CHROMEOS)
const char kLanguagePreferredLanguages[] =
"settings.language.preferred_languages";
#else
const char* kLanguagePreferredLanguages = nullptr;
#endif
const char kAcceptLanguages[] = "intl.accept_languages";
// Overrides NetworkChangeNotifier, simulatng connection type changes for tests.
// TODO(groby): Combine with similar code in ResourceRequestAllowedNotifierTest.
class TestNetworkChangeNotifier : public net::NetworkChangeNotifier {
public:
TestNetworkChangeNotifier()
: net::NetworkChangeNotifier(),
connection_type_to_return_(
net::NetworkChangeNotifier::CONNECTION_UNKNOWN) {}
// Simulates a change of the connection type to |type|. This will notify any
// objects that are NetworkChangeNotifiers.
void SimulateNetworkConnectionChange(
net::NetworkChangeNotifier::ConnectionType type) {
connection_type_to_return_ = type;
net::NetworkChangeNotifier::NotifyObserversOfConnectionTypeChangeForTests(
connection_type_to_return_);
base::RunLoop().RunUntilIdle();
}
void SimulateOffline() {
connection_type_to_return_ =net::NetworkChangeNotifier::CONNECTION_NONE;
}
void SimulateOnline() {
connection_type_to_return_ = net::NetworkChangeNotifier::CONNECTION_UNKNOWN;
}
private:
ConnectionType GetCurrentConnectionType() const override {
return connection_type_to_return_;
}
// The currently simulated network connection type. If this is set to
// CONNECTION_NONE, then NetworkChangeNotifier::IsOffline will return true.
net::NetworkChangeNotifier::ConnectionType connection_type_to_return_;
DISALLOW_COPY_AND_ASSIGN(TestNetworkChangeNotifier);
};
// TODO(groby): Combine with MockTranslateClient in TranslateUiDelegateTest.
class MockTranslateClient : public TranslateClient {
public:
MockTranslateClient(TranslateDriver* driver, PrefService* prefs)
: driver_(driver), prefs_(prefs) {}
// TODO(groby): Does TranslateClient need a virtual dtor?
virtual ~MockTranslateClient() {}
TranslateDriver* GetTranslateDriver() { return driver_; }
PrefService* GetPrefs() { return prefs_; }
std::unique_ptr<TranslatePrefs> GetTranslatePrefs() {
return base::MakeUnique<TranslatePrefs>(prefs_, kAcceptLanguages,
kLanguagePreferredLanguages);
}
MOCK_METHOD0(GetTranslateAcceptLanguages, TranslateAcceptLanguages*());
MOCK_CONST_METHOD0(GetInfobarIconID, int());
#if !defined(USE_AURA)
MOCK_CONST_METHOD1(CreateInfoBarMock,
infobars::InfoBar*(TranslateInfoBarDelegate*));
std::unique_ptr<infobars::InfoBar> CreateInfoBar(
std::unique_ptr<TranslateInfoBarDelegate> delegate) const {
return base::WrapUnique(CreateInfoBarMock(std::move(delegate).get()));
}
#endif
MOCK_METHOD5(ShowTranslateUI,
void(translate::TranslateStep,
const std::string&,
const std::string&,
TranslateErrors::Type,
bool));
MOCK_METHOD1(IsTranslatableURL, bool(const GURL&));
MOCK_METHOD1(ShowReportLanguageDetectionErrorUI,
void(const GURL& report_url));
private:
TranslateDriver* driver_;
PrefService* prefs_;
};
} // namespace
namespace testing {
class TranslateManagerTest : public ::testing::Test {
protected:
TranslateManagerTest()
: translate_prefs_(&prefs_,
kAcceptLanguages,
kLanguagePreferredLanguages),
manager_(TranslateDownloadManager::GetInstance()),
mock_translate_client_(&driver_, &prefs_),
field_trial_list_(new base::FieldTrialList(nullptr)) {}
void SetUp() override {
// Ensure we're not requesting a server-side translate language list.
TranslateLanguageList::DisableUpdate();
prefs_.registry()->RegisterStringPref(kAcceptLanguages, std::string());
#if defined(OS_CHROMEOS)
prefs_.registry()->RegisterStringPref(kLanguagePreferredLanguages,
std::string());
#endif
TranslatePrefs::RegisterProfilePrefs(prefs_.registry());
// TODO(groby): Figure out RegisterProfilePrefs() should register this.
prefs_.registry()->RegisterBooleanPref(
prefs::kEnableTranslate, true,
user_prefs::PrefRegistrySyncable::SYNCABLE_PREF);
manager_->ResetForTesting();
}
void TearDown() override {
manager_->ResetForTesting();
variations::testing::ClearAllVariationParams();
}
// Utility function to prepare translate_manager_ for testing.
void PrepareTranslateManager() {
TranslateManager::SetIgnoreMissingKeyForTesting(true);
translate_manager_.reset(new translate::TranslateManager(
&mock_translate_client_, kAcceptLanguages));
}
// Prepare the test for ULP related tests.
// Put the ulp json into profile.
void PrepareULPTest(const char* ulp_json, bool turn_on_feature) {
PrepareTranslateManager();
std::unique_ptr<base::Value> profile(CreateProfileFromJSON(ulp_json));
prefs_.SetUserPref(TranslatePrefs::kPrefLanguageProfile, profile.release());
if (turn_on_feature)
TurnOnTranslateByULP();
}
std::unique_ptr<base::Value> CreateProfileFromJSON(const char* json) {
int error_code = 0;
std::string error_msg;
int error_line = 0;
int error_column = 0;
std::unique_ptr<base::Value> profile(base::JSONReader::ReadAndReturnError(
json, 0, &error_code, &error_msg, &error_line, &error_column));
EXPECT_EQ(0, error_code) << error_msg << " at " << error_line << ":"
<< error_column << std::endl
<< json;
return profile;
}
void TurnOnTranslateByULP() {
scoped_refptr<base::FieldTrial> trial(
CreateFieldTrial(kTrialName, 100, "Enabled", NULL));
std::unique_ptr<base::FeatureList> feature_list(new base::FeatureList);
feature_list->RegisterFieldTrialOverride(
translate::kTranslateLanguageByULP.name,
base::FeatureList::OVERRIDE_ENABLE_FEATURE, trial.get());
scoped_feature_list_.InitWithFeatureList(std::move(feature_list));
}
scoped_refptr<base::FieldTrial> CreateFieldTrial(
const std::string& trial_name,
int total_probability,
const std::string& default_group_name,
int* default_group_number) {
return base::FieldTrialList::FactoryGetFieldTrial(
trial_name, total_probability, default_group_name,
base::FieldTrialList::kNoExpirationYear, 1, 1,
base::FieldTrial::SESSION_RANDOMIZED, default_group_number);
}
// Functions to help TEST_F in subclass to access private functions in
// TranslteManager so we can unit test them.
std::string CallGetTargetLanguageFromULP() {
return TranslateManager::GetTargetLanguageFromULP(&translate_prefs_);
}
bool CallLanguageInULP(const std::string& language) {
return translate_manager_->LanguageInULP(language);
}
user_prefs::TestingPrefServiceSyncable prefs_;
// TODO(groby): request TranslatePrefs from |mock_translate_client_| instead.
TranslatePrefs translate_prefs_;
TranslateDownloadManager* manager_;
TestNetworkChangeNotifier network_notifier_;
translate::testing::MockTranslateDriver driver_;
::testing::NiceMock<MockTranslateClient> mock_translate_client_;
std::unique_ptr<TranslateManager> translate_manager_;
std::unique_ptr<base::FieldTrialList> field_trial_list_;
base::test::ScopedFeatureList scoped_feature_list_;
};
// Target language comes from application locale if the locale's language
// is supported.
TEST_F(TranslateManagerTest, GetTargetLanguageDefaultsToAppLocale) {
// Ensure the locale is set to a supported language.
ASSERT_TRUE(TranslateDownloadManager::IsSupportedLanguage("en"));
manager_->set_application_locale("en");
EXPECT_EQ("en", TranslateManager::GetTargetLanguage(&translate_prefs_));
// Try a second supported language.
ASSERT_TRUE(TranslateDownloadManager::IsSupportedLanguage("de"));
manager_->set_application_locale("de");
EXPECT_EQ("de", TranslateManager::GetTargetLanguage(&translate_prefs_));
}
// If the application locale's language is not supported, the target language
// falls back to the first supported language in |accept_languages_list|. If
// none of the languages in |accept_language_list| is supported, the target
// language is empty.
TEST_F(TranslateManagerTest, GetTargetLanguageAcceptLangFallback) {
std::vector<std::string> accept_language_list;
// Ensure locale is set to a not-supported language.
ASSERT_FALSE(TranslateDownloadManager::IsSupportedLanguage("xy"));
manager_->set_application_locale("xy");
// Default return is empty string.
EXPECT_EQ("", TranslateManager::GetTargetLanguage(&translate_prefs_));
// Unsupported languages still result in the empty string.
ASSERT_FALSE(TranslateDownloadManager::IsSupportedLanguage("zy"));
accept_language_list.push_back("zy");
translate_prefs_.UpdateLanguageList(accept_language_list);
EXPECT_EQ("", TranslateManager::GetTargetLanguage(&translate_prefs_));
// First supported language is the fallback language.
ASSERT_TRUE(TranslateDownloadManager::IsSupportedLanguage("en"));
accept_language_list.push_back("en");
translate_prefs_.UpdateLanguageList(accept_language_list);
EXPECT_EQ("en", TranslateManager::GetTargetLanguage(&translate_prefs_));
}
TEST_F(TranslateManagerTest, DontTranslateOffline) {
TranslateManager::SetIgnoreMissingKeyForTesting(true);
translate_manager_.reset(new translate::TranslateManager(
&mock_translate_client_, kAcceptLanguages));
// The test measures that the "Translate was disabled" exit can only be
// reached after the early-out tests including IsOffline() passed.
const char kMetricName[] = "Translate.InitiationStatus.v2";
base::HistogramTester histogram_tester;
prefs_.SetBoolean(prefs::kEnableTranslate, false);
translate_manager_->GetLanguageState().LanguageDetermined("de", true);
// In the offline case, Initiate will early-out before even hitting the API
// key test.
network_notifier_.SimulateOffline();
translate_manager_->InitiateTranslation("de");
histogram_tester.ExpectTotalCount(kMetricName, 0);
// In the online case, InitiateTranslation will proceed past early out tests.
network_notifier_.SimulateOnline();
translate_manager_->InitiateTranslation("de");
histogram_tester.ExpectUniqueSample(
kMetricName,
translate::TranslateBrowserMetrics::INITIATION_STATUS_DISABLED_BY_PREFS,
1);
}
// Utility function to set the threshold params
void ChangeThresholdInParams(
const char* initiate_translation_confidence_threshold,
const char* initiate_translation_probability_threshold,
const char* target_language_confidence_threshold,
const char* target_language_probability_threshold) {
ASSERT_TRUE(variations::AssociateVariationParams(
kTrialName, "Enabled", {{"initiate_translation_ulp_confidence_threshold",
initiate_translation_confidence_threshold},
{"initiate_translation_ulp_probability_threshold",
initiate_translation_probability_threshold},
{"target_language_ulp_confidence_threshold",
target_language_confidence_threshold},
{"target_language_ulp_probability_threshold",
target_language_probability_threshold}}));
}
// Normal ULP in Json
const char ulp_1[] =
"{\n"
" \"reading\": {\n"
" \"confidence\": 0.8,\n"
" \"preference\": [\n"
" {\n"
" \"language\": \"fr\",\n"
" \"probability\": 0.6\n"
" }, {\n"
" \"language\": \"pt-PT\",\n"
" \"probability\": 0.4\n"
" }\n"
" ]\n"
" }\n"
"}";
// ULP in Json with smaller probability of several es-* language codes
// sum up to 0.7.
const char ulp_2[] =
"{\n"
" \"reading\": {\n"
" \"confidence\": 0.9,\n"
" \"preference\": [\n"
" {\n"
" \"language\": \"fr\",\n"
" \"probability\": 0.3\n"
" }, {\n"
" \"language\": \"es-419\",\n"
" \"probability\": 0.2\n"
" }, {\n"
" \"language\": \"es-MX\",\n"
" \"probability\": 0.2\n"
" }, {\n"
" \"language\": \"es-US\",\n"
" \"probability\": 0.2\n"
" }, {\n"
" \"language\": \"es-CL\",\n"
" \"probability\": 0.1\n"
" }\n"
" ]\n"
" }\n"
"}";
TEST_F(TranslateManagerTest, TestGetTargetLanguageFromULPFeatureOff) {
PrepareULPTest(ulp_1, false);
EXPECT_STREQ("", CallGetTargetLanguageFromULP().c_str());
}
TEST_F(TranslateManagerTest, TestGetTargetLanguageFromULPHighConfidence) {
PrepareULPTest(ulp_1, true);
// The default hardcoded threshold are confidence: 0.7, probability: 0.55
EXPECT_STREQ("fr", CallGetTargetLanguageFromULP().c_str());
}
TEST_F(TranslateManagerTest,
TestGetTargetLanguageFromULPHighConfidenceThresholdFromConfig) {
PrepareULPTest(ulp_1, true);
ChangeThresholdInParams("", "", "0.81", "0.5");
// Should get empty string as result since the confidence threshold is above
// the ULP (0.8 in the ulp_1).
EXPECT_STREQ("", CallGetTargetLanguageFromULP().c_str());
}
TEST_F(TranslateManagerTest,
TestGetTargetLanguageFromULPHighProbabilityThresholdFromConfig) {
PrepareULPTest(ulp_1, true);
ChangeThresholdInParams("", "", "0.4", "0.61");
// Should get empty string as result since the confidence threshold is above
// the ULP (0.6 for fr in the ulp_1).
EXPECT_STREQ("", CallGetTargetLanguageFromULP().c_str());
}
TEST_F(TranslateManagerTest, TestGetTargetLanguageFromULPProbabilitySumUp) {
PrepareULPTest(ulp_2, true);
ChangeThresholdInParams("", "", "0.4", "0.61");
// Should get "es" since the sum of the "es-*" probability is 0.7.
EXPECT_STREQ("es", CallGetTargetLanguageFromULP().c_str());
}
TEST_F(TranslateManagerTest, TestLanguageInULPFeatureOff) {
PrepareULPTest(ulp_1, false);
EXPECT_FALSE(CallLanguageInULP("fr"));
EXPECT_FALSE(CallLanguageInULP("pt"));
EXPECT_FALSE(CallLanguageInULP("zh-TW"));
}
TEST_F(TranslateManagerTest, TestLanguageInULPDefaultThreshold) {
PrepareULPTest(ulp_1, true);
// The default hardcoded threshold are confidence: 0.75, probability: 0.5
EXPECT_TRUE(CallLanguageInULP("fr"));
EXPECT_FALSE(CallLanguageInULP("pt"));
EXPECT_FALSE(CallLanguageInULP("zh-TW"));
}
TEST_F(TranslateManagerTest,
TestLanguageInULPHighConfidenceThresholdFromConfig) {
PrepareULPTest(ulp_1, true);
ChangeThresholdInParams("0.9", "0.5", "", "");
// "fr" and "pt" should return false because the confidence threshold is set
// to 0.9.
EXPECT_FALSE(CallLanguageInULP("fr"));
EXPECT_FALSE(CallLanguageInULP("pt"));
EXPECT_FALSE(CallLanguageInULP("zh-TW"));
}
TEST_F(TranslateManagerTest,
TestLanguageInULPLowConfidenceThresholdFromConfig) {
PrepareULPTest(ulp_1, true);
ChangeThresholdInParams("0.79", "0.39", "", "");
// Both "fr" and "pt" should reutrn true because the confidence threshold is
// 0.79 and lower than 0.8 and the probability threshold is lower than both
// the one with "fr" (0.6) and "pt-PT" (0.4).
EXPECT_TRUE(CallLanguageInULP("fr"));
EXPECT_TRUE(CallLanguageInULP("pt"));
EXPECT_FALSE(CallLanguageInULP("zh-TW"));
}
} // namespace testing
} // namespace translate