// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chromeos/ash/components/language_packs/language_packs_util.h"
#include <optional>
#include <string>
#include "ash/constants/ash_pref_names.h"
#include "base/containers/flat_set.h"
#include "base/containers/span.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "chromeos/ash/components/dbus/dlcservice/dlcservice.pb.h"
#include "chromeos/ash/components/language_packs/language_pack_manager.h"
#include "components/prefs/pref_registry_simple.h"
#include "components/prefs/pref_service.h"
#include "components/prefs/testing_pref_service.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/cros_system_api/dbus/dlcservice/dbus-constants.h"
namespace ash::language_packs {
using ::dlcservice::DlcState;
using ::dlcservice::DlcState_State_INSTALLED;
using ::dlcservice::DlcState_State_INSTALLING;
using ::dlcservice::DlcState_State_NOT_INSTALLED;
using ::testing::IsEmpty;
using ::testing::UnorderedElementsAre;
TEST(LanguagePacksUtil, ConvertDlcState_EmptyInput) {
DlcState input;
PackResult output = ConvertDlcStateToPackResult(input);
// The default value in the input is 'NOT_INSTALLED'.
EXPECT_EQ(output.pack_state, PackResult::StatusCode::kNotInstalled);
EXPECT_EQ(output.operation_error, PackResult::ErrorCode::kNone);
}
TEST(LanguagePacksUtil, ConvertDlcState_NotInstalled) {
DlcState input;
input.set_state(DlcState_State_NOT_INSTALLED);
PackResult output = ConvertDlcStateToPackResult(input);
EXPECT_EQ(output.pack_state, PackResult::StatusCode::kNotInstalled);
EXPECT_EQ(output.operation_error, PackResult::ErrorCode::kNone);
// Even if the path is set (by mistake) in the input, we should not return it.
input.set_root_path("/var/somepath");
output = ConvertDlcStateToPackResult(input);
EXPECT_EQ(output.pack_state, PackResult::StatusCode::kNotInstalled);
EXPECT_TRUE(output.path.empty());
}
TEST(LanguagePacksUtil, ConvertDlcState_Installing) {
DlcState input;
input.set_state(DlcState_State_INSTALLING);
PackResult output = ConvertDlcStateToPackResult(input);
EXPECT_EQ(output.pack_state, PackResult::StatusCode::kInProgress);
EXPECT_EQ(output.operation_error, PackResult::ErrorCode::kNone);
// Even if the path is set (by mistake) in the input, we should not return it.
input.set_root_path("/var/somepath");
output = ConvertDlcStateToPackResult(input);
EXPECT_EQ(output.pack_state, PackResult::StatusCode::kInProgress);
EXPECT_TRUE(output.path.empty());
}
TEST(LanguagePacksUtil, ConvertDlcState_Installed) {
DlcState input;
input.set_state(DlcState_State_INSTALLED);
input.set_root_path("/var/somepath");
PackResult output = ConvertDlcStateToPackResult(input);
EXPECT_EQ(output.pack_state, PackResult::StatusCode::kInstalled);
EXPECT_EQ(output.operation_error, PackResult::ErrorCode::kNone);
EXPECT_EQ(output.path, "/var/somepath");
}
// Tests the behaviour in case the state received from the input in not a valid
// value. This could happen for example if the proto changes without notice.
TEST(LanguagePacksUtil, ConvertDlcState_MalformedProto) {
DlcState input;
// Enum value '3' is beyond currently defined values.
input.set_state(static_cast<dlcservice::DlcState_State>(3));
input.set_root_path("/var/somepath");
PackResult output = ConvertDlcStateToPackResult(input);
EXPECT_EQ(output.pack_state, PackResult::StatusCode::kUnknown);
EXPECT_EQ(output.operation_error, PackResult::ErrorCode::kNone);
EXPECT_TRUE(output.path.empty());
}
TEST(LanguagePacksUtil, ConvertDlcState_ErrorSet) {
DlcState input;
input.set_last_error_code(dlcservice::kErrorNeedReboot);
PackResult output = ConvertDlcStateToPackResult(input);
EXPECT_EQ(output.pack_state, PackResult::StatusCode::kNotInstalled);
EXPECT_EQ(output.operation_error, PackResult::ErrorCode::kNeedReboot);
EXPECT_TRUE(output.path.empty());
}
TEST(LanguagePacksUtil, ConvertDlcInstallResult_Success) {
DlcserviceClient::InstallResult input;
input.error = "";
input.root_path = "/var/somepath";
PackResult output = ConvertDlcInstallResultToPackResult(input);
EXPECT_EQ(output.pack_state, PackResult::StatusCode::kInstalled);
EXPECT_EQ(output.operation_error, PackResult::ErrorCode::kNone);
EXPECT_EQ(output.path, "/var/somepath");
}
TEST(LanguagePacksUtil, ConvertDlcInstallResult_Error) {
DlcserviceClient::InstallResult input;
input.error = dlcservice::kErrorInternal;
PackResult output = ConvertDlcInstallResultToPackResult(input);
EXPECT_EQ(output.pack_state, PackResult::StatusCode::kUnknown);
EXPECT_EQ(output.operation_error, PackResult::ErrorCode::kOther);
EXPECT_TRUE(output.path.empty());
}
// Tests the conversion of all error types returned by DlcserviceClient.
TEST(LanguagePacksUtil, ConvertDlcError_AllErrorsTypes) {
EXPECT_EQ(ConvertDlcErrorToErrorCode(""), PackResult::ErrorCode::kNone);
EXPECT_EQ(ConvertDlcErrorToErrorCode(dlcservice::kErrorNone),
PackResult::ErrorCode::kNone);
EXPECT_EQ(ConvertDlcErrorToErrorCode(dlcservice::kErrorAllocation),
PackResult::ErrorCode::kAllocation);
EXPECT_EQ(ConvertDlcErrorToErrorCode(dlcservice::kErrorInvalidDlc),
PackResult::ErrorCode::kWrongId);
EXPECT_EQ(ConvertDlcErrorToErrorCode(dlcservice::kErrorNeedReboot),
PackResult::ErrorCode::kNeedReboot);
EXPECT_EQ(ConvertDlcErrorToErrorCode(dlcservice::kErrorNoImageFound),
PackResult::ErrorCode::kOther);
EXPECT_EQ(ConvertDlcErrorToErrorCode(dlcservice::kErrorInternal),
PackResult::ErrorCode::kOther);
}
// For Handwriting we only keep the language part, not the country/region.
TEST(LanguagePacksUtil, ResolveLocaleHandwriting) {
EXPECT_EQ(ResolveLocale(kHandwritingFeatureId, "en-US"), "en");
EXPECT_EQ(ResolveLocale(kHandwritingFeatureId, "en-us"), "en");
EXPECT_EQ(ResolveLocale(kHandwritingFeatureId, "fr"), "fr");
EXPECT_EQ(ResolveLocale(kHandwritingFeatureId, "it-IT"), "it");
EXPECT_EQ(ResolveLocale(kHandwritingFeatureId, "zh"), "zh");
EXPECT_EQ(ResolveLocale(kHandwritingFeatureId, "zh-TW"), "zh");
// Chinese HongKong is an exception.
EXPECT_EQ(ResolveLocale(kHandwritingFeatureId, "zh-HK"), "zh-HK");
}
TEST(LanguagePacksUtil, ResolveLocaleTts) {
// For these locales we keep the region.
EXPECT_EQ(ResolveLocale(kTtsFeatureId, "en-AU"), "en-au");
EXPECT_EQ(ResolveLocale(kTtsFeatureId, "en-au"), "en-au");
EXPECT_EQ(ResolveLocale(kTtsFeatureId, "en-GB"), "en-gb");
EXPECT_EQ(ResolveLocale(kTtsFeatureId, "en-gb"), "en-gb");
EXPECT_EQ(ResolveLocale(kTtsFeatureId, "en-US"), "en-us");
EXPECT_EQ(ResolveLocale(kTtsFeatureId, "en-us"), "en-us");
EXPECT_EQ(ResolveLocale(kTtsFeatureId, "es-ES"), "es-es");
EXPECT_EQ(ResolveLocale(kTtsFeatureId, "es-es"), "es-es");
EXPECT_EQ(ResolveLocale(kTtsFeatureId, "es-US"), "es-us");
EXPECT_EQ(ResolveLocale(kTtsFeatureId, "es-us"), "es-us");
EXPECT_EQ(ResolveLocale(kTtsFeatureId, "pt-BR"), "pt-br");
EXPECT_EQ(ResolveLocale(kTtsFeatureId, "pt-br"), "pt-br");
EXPECT_EQ(ResolveLocale(kTtsFeatureId, "pt-PT"), "pt-pt");
EXPECT_EQ(ResolveLocale(kTtsFeatureId, "pt-pt"), "pt-pt");
// For all other locales we only keep the language.
EXPECT_EQ(ResolveLocale(kTtsFeatureId, "bn-bd"), "bn");
EXPECT_EQ(ResolveLocale(kTtsFeatureId, "fil-ph"), "fil");
EXPECT_EQ(ResolveLocale(kTtsFeatureId, "it-it"), "it");
EXPECT_EQ(ResolveLocale(kTtsFeatureId, "ja-jp"), "ja");
}
TEST(LanguagePacksUtil, ResolveLocaleFonts) {
// Language pack resolution is handled by the client.
EXPECT_EQ(ResolveLocale(kFontsFeatureId, "ja"), "ja");
EXPECT_EQ(ResolveLocale(kFontsFeatureId, "ko"), "ko");
}
TEST(LanguagePacksUtil, MapThenFilterStringsNoInput) {
EXPECT_THAT(MapThenFilterStrings(
{}, base::BindRepeating(
[](const std::string&) -> std::optional<std::string> {
return "ignored";
})),
IsEmpty());
}
TEST(LanguagePacksUtil, MapThenFilterStringsAllToNullopt) {
EXPECT_THAT(MapThenFilterStrings(
{{"en", "de"}},
base::BindRepeating(
[](const std::string&) -> std::optional<std::string> {
return std::nullopt;
})),
IsEmpty());
}
TEST(LanguagePacksUtil, MapThenFilterStringsAllToUniqueStrings) {
EXPECT_THAT(
MapThenFilterStrings(
{{"en", "de"}},
base::BindRepeating(
[](const std::string& input) -> std::optional<std::string> {
return input;
})),
UnorderedElementsAre("en", "de"));
}
TEST(LanguagePacksUtil, MapThenFilterStringsRepeatedString) {
EXPECT_THAT(
MapThenFilterStrings(
{{"repeat", "unique", "repeat"}},
base::BindRepeating(
[](const std::string& input) -> std::optional<std::string> {
return input;
})),
UnorderedElementsAre("repeat", "unique"));
}
TEST(LanguagePacksUtil, MapThenFilterStringsSomeNullopt) {
EXPECT_THAT(
MapThenFilterStrings(
{{"pass_1", "fail", "pass_2"}},
base::BindRepeating(
[](const std::string& input) -> std::optional<std::string> {
return (input == "fail") ? std::nullopt
: std::optional<std::string>(input);
})),
UnorderedElementsAre("pass_1", "pass_2"));
}
TEST(LanguagePacksUtil, MapThenFilterStringsDeduplicateOutput) {
EXPECT_THAT(
MapThenFilterStrings(
{{"a", "dedup-1", "dedup-2"}},
base::BindRepeating(
[](const std::string& input) -> std::optional<std::string> {
return (input.length() < 2) ? input : "dedup";
})),
UnorderedElementsAre("a", "dedup"));
}
TEST(LanguagePacksUtil, MapThenFilterStringsDisjointSet) {
EXPECT_THAT(
MapThenFilterStrings(
{{"a", "b", "d"}},
base::BindRepeating(
[](const std::string& input) -> std::optional<std::string> {
return "something else";
})),
UnorderedElementsAre("something else"));
}
TEST(LanguagePacksUtil, ExtractInputMethodsFromPrefsEmpty) {
auto pref = std::make_unique<TestingPrefServiceSimple>();
pref->registry()->RegisterStringPref(prefs::kLanguagePreloadEngines,
std::string());
EXPECT_THAT(ExtractInputMethodsFromPrefs(pref.get()), IsEmpty());
}
TEST(LanguagePacksUtil, ExtractInputMethodsFromPrefsOne) {
auto pref = std::make_unique<TestingPrefServiceSimple>();
pref->registry()->RegisterStringPref(prefs::kLanguagePreloadEngines,
"xkb:it::ita");
EXPECT_THAT(ExtractInputMethodsFromPrefs(pref.get()),
UnorderedElementsAre("xkb:it::ita"));
}
TEST(LanguagePacksUtil, ExtractInputMethodsFromPrefsMultiple) {
auto pref = std::make_unique<TestingPrefServiceSimple>();
pref->registry()->RegisterStringPref(prefs::kLanguagePreloadEngines,
"xkb:it::ita,xkb:fr::fra,xkb:de::ger");
EXPECT_THAT(
ExtractInputMethodsFromPrefs(pref.get()),
UnorderedElementsAre("xkb:it::ita", "xkb:fr::fra", "xkb:de::ger"));
}
} // namespace ash::language_packs