1 // Copyright 2019 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "base/i18n/icu_util.h"
6
7 #include "base/files/file_util.h"
8 #include "base/logging.h"
9 #include "base/strings/string_util.h"
10 #include "base/test/icu_test_util.h"
11 #include "build/build_config.h"
12 #include "testing/gtest/include/gtest/gtest.h"
13 #include "third_party/icu/source/common/unicode/uclean.h"
14 #include "third_party/icu/source/i18n/unicode/timezone.h"
15
16 namespace base {
17 namespace i18n {
18
19 namespace {
20
21 // Directory path to the tzdata configuration files.
22 const char kTzDataDirPath[] = "/pkg/base/test/data/tzdata/icu/44/le";
23
24 // File path to the text file containing the expected ICU library revision, for
25 // example "2019c".
26 const char kRevisionFilePath[] = "/config/data/tzdata/revision.txt";
27
28 } // namespace
29
30 class TimeZoneDataTest : public testing::Test {
31 protected:
TearDown()32 void TearDown() override {
33 ResetIcu();
34
35 // ICU must be set back up in case e.g. a log statement that formats times
36 // uses it.
37 test::InitializeICUForTesting();
38 }
39
40 // Needed to enable loading of ICU config files that are different from what
41 // is available in Chromium. Both icu_util and ICU library keep internal
42 // state so clear both.
ResetIcu()43 void ResetIcu() {
44 // Clears the state in the reverse order of construction.
45 u_cleanup();
46 ResetGlobalsForTesting();
47 }
48
GetActualRevision(std::string * icu_version)49 void GetActualRevision(std::string* icu_version) {
50 UErrorCode err = U_ZERO_ERROR;
51 *icu_version = icu::TimeZone::getTZDataVersion(err);
52 ASSERT_EQ(U_ZERO_ERROR, err) << u_errorName(err);
53 }
54 };
55
56 // Loads a file revision.txt from the actual underlying filesystem, which
57 // contains the tzdata version we expect to be able to load. It then attempts
58 // to load this configuration from the default path and compares the version it
59 // obtained from the load with the expected version, failing on version
60 // mismatch.
61 //
62 // In Fuchsia build bot setup, we ensure that the file revision.txt exists, so
63 // that this test is not skipped. In Chromium build bot setup, this file may
64 // not be present, in which case we skip running this test.
TEST_F(TimeZoneDataTest,CompareSystemRevisionWithExpected)65 TEST_F(TimeZoneDataTest, CompareSystemRevisionWithExpected) {
66 if (!base::PathExists(base::FilePath(kRevisionFilePath))) {
67 GTEST_SKIP() << "Skipped test because tzdata config is not present";
68 }
69
70 // ResetIcu() ensures that time zone data is loaded from the default location.
71 // This is done after the GTEST_SKIP() call above, since that may output a
72 // timestamp that requires ICU to be set up.
73 ResetIcu();
74
75 ASSERT_TRUE(InitializeICU());
76 std::string expected;
77 EXPECT_TRUE(
78 base::ReadFileToString(base::FilePath(kRevisionFilePath), &expected));
79 std::string actual;
80 GetActualRevision(&actual);
81 EXPECT_EQ(expected, actual);
82 }
83
84 // Verifies that the current version of the ICU library in use can load ICU
85 // data in a specific version format (in this case 44). Designed to fail if
86 // the ICU library version used in Chromium drifts from version 44 so much that
87 // the library is no longer able to load the old tzdata. If the test fails,
88 // this could be a sign that all platforms Chromium runs on need to upgrade the
89 // ICU library versions.
TEST_F(TimeZoneDataTest,TestLoadingTimeZoneDataFromKnownConfigs)90 TEST_F(TimeZoneDataTest, TestLoadingTimeZoneDataFromKnownConfigs) {
91 ASSERT_TRUE(base::DirectoryExists(base::FilePath(kTzDataDirPath)));
92 ResetIcu();
93 SetIcuTimeZoneDataDirForTesting(kTzDataDirPath);
94
95 ASSERT_TRUE(InitializeICU());
96 std::string actual;
97 GetActualRevision(&actual);
98 EXPECT_EQ("2019a", actual) << "If ICU no longer supports this tzdata "
99 "version, tzdata version needs to be upgraded";
100 }
101
TEST_F(TimeZoneDataTest,DoesNotCrashWithInvalidPath)102 TEST_F(TimeZoneDataTest, DoesNotCrashWithInvalidPath) {
103 ResetIcu();
104 SetIcuTimeZoneDataDirForTesting("/some/nonexistent/path");
105
106 ASSERT_TRUE(InitializeICU());
107 std::string actual;
108 GetActualRevision(&actual);
109 EXPECT_TRUE(
110 base::StartsWith(actual, "20", base::CompareCase::INSENSITIVE_ASCII))
111 << "Got version: " << actual;
112 }
113
114 } // namespace i18n
115 } // namespace base
116