1 // Copyright 2010-2018, Google Inc.
2 // All rights reserved.
3 //
4 // Redistribution and use in source and binary forms, with or without
5 // modification, are permitted provided that the following conditions are
6 // met:
7 //
8 //     * Redistributions of source code must retain the above copyright
9 // notice, this list of conditions and the following disclaimer.
10 //     * Redistributions in binary form must reproduce the above
11 // copyright notice, this list of conditions and the following disclaimer
12 // in the documentation and/or other materials provided with the
13 // distribution.
14 //     * Neither the name of Google Inc. nor the names of its
15 // contributors may be used to endorse or promote products derived from
16 // this software without specific prior written permission.
17 //
18 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21 // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22 // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 
30 #include <cstddef>
31 #include <ios>
32 #include <istream>
33 #include <memory>
34 
35 #include "base/config_file_stream.h"
36 #include "base/file_util.h"
37 #include "base/system_util.h"
38 #include "testing/base/public/googletest.h"
39 #include "testing/base/public/gunit.h"
40 
41 namespace mozc {
42 
43 namespace {
44 // Returns all data of the filename.
GetFileData(const string & filename)45 string GetFileData(const string &filename) {
46   InputFileStream ifs(filename.c_str(), std::ios::binary);
47   char c = '\0';
48   string data;
49   while (!ifs.get(c).fail()) {
50     data.append(1, c);
51   }
52   return data;
53 }
54 
55 // Returns true if |input_stream| is at the end of stream. This function
56 // peeks one more character in case the current position is actually at
57 // the end of the stream but |input_stream| instance has not observed it
58 // yet. In other words, this method may change the internal state of
59 // |input_stream| as a side effect.
IsEof(std::istream * input_stream)60 bool IsEof(std::istream *input_stream) {
61   return (input_stream->peek() == std::istream::traits_type::eof() &&
62           // On some enviroment (e.g. Mac OS 10.8 w/ Xcode 4.5),
63           // peek() does not flip eofbit.  So calling get() is also
64           // required.
65           input_stream->get() == std::istream::traits_type::eof() &&
66           input_stream->eof());
67 }
68 
69 }  // namespace
70 
71 class ConfigFileStreamTest : public testing::Test {
72  protected:
SetUp()73   virtual void SetUp() {
74     default_profile_directory_ = SystemUtil::GetUserProfileDirectory();
75     SystemUtil::SetUserProfileDirectory(FLAGS_test_tmpdir);
76   }
77 
TearDown()78   virtual void TearDown() {
79     SystemUtil::SetUserProfileDirectory(default_profile_directory_);
80   }
81 
82  private:
83   string default_profile_directory_;
84 };
85 
TEST_F(ConfigFileStreamTest,OnMemoryFiles)86 TEST_F(ConfigFileStreamTest, OnMemoryFiles) {
87   const string kData = "data";
88   const string kPath = "memory://test";
89   EXPECT_TRUE(ConfigFileStream::GetFileName(kPath).empty());
90   ConfigFileStream::AtomicUpdate(kPath, kData);
91 
92   {
93     std::unique_ptr<std::istream> ifs(ConfigFileStream::LegacyOpen(kPath));
94     ASSERT_NE(nullptr, ifs.get());
95     std::unique_ptr<char[]> buf(new char[kData.size() + 1]);
96     ifs->read(buf.get(), kData.size());
97     buf.get()[kData.size()] = '\0';
98     EXPECT_EQ(kData, buf.get());
99     EXPECT_TRUE(IsEof(ifs.get()));
100   }
101 
102   ConfigFileStream::ClearOnMemoryFiles();
103 
104   {
105     std::unique_ptr<std::istream> ifs(ConfigFileStream::LegacyOpen(kPath));
106     ASSERT_NE(nullptr, ifs.get());
107     EXPECT_TRUE(IsEof(ifs.get()));
108   }
109 }
110 
TEST_F(ConfigFileStreamTest,AtomicUpdate)111 TEST_F(ConfigFileStreamTest, AtomicUpdate) {
112   const string prefixed_filename = "user://atomic_update_test";
113   const string filename = ConfigFileStream::GetFileName(prefixed_filename);
114   const string tmp_filename = filename + ".tmp";
115 
116   EXPECT_FALSE(FileUtil::FileExists(filename));
117   EXPECT_FALSE(FileUtil::FileExists(tmp_filename));
118 
119   const string contents = "123\n2\n3";
120   ConfigFileStream::AtomicUpdate(prefixed_filename, contents);
121   EXPECT_TRUE(FileUtil::FileExists(filename));
122   EXPECT_FALSE(FileUtil::FileExists(tmp_filename));
123   EXPECT_EQ(contents, GetFileData(filename));
124 
125   const string new_contents = "246\n4\n6";
126   ConfigFileStream::AtomicUpdate(prefixed_filename, new_contents);
127   EXPECT_TRUE(FileUtil::FileExists(filename));
128   EXPECT_FALSE(FileUtil::FileExists(tmp_filename));
129   EXPECT_EQ(new_contents, GetFileData(filename));
130 
131   if (FileUtil::FileExists(filename)) {
132     FileUtil::Unlink(filename);
133   }
134 }
135 
TEST_F(ConfigFileStreamTest,OpenReadBinary)136 TEST_F(ConfigFileStreamTest, OpenReadBinary) {
137   // At first, generate a binary data file in (temporary) user directory
138   // so that we can load it as "user://my_binary_file.dat"
139   const char kTestFileName[] = "my_binary_file.dat";
140   const string &test_file_path = FileUtil::JoinPath(
141       SystemUtil::GetUserProfileDirectory(), kTestFileName);
142 
143   const char kBinaryData[] = {
144     ' ', ' ', '\r', ' ', '\n', ' ', '\r', '\n', ' ', '\0', ' ',
145   };
146   const size_t kBinaryDataSize = sizeof(kBinaryData);
147   {
148     OutputFileStream ofs(test_file_path.c_str(),
149                          std::ios::out | std::ios::binary);
150     ofs.write(kBinaryData, kBinaryDataSize);
151   }
152 
153   ASSERT_TRUE(FileUtil::FileExists(test_file_path));
154 
155   {
156     std::unique_ptr<std::istream> ifs(
157         ConfigFileStream::OpenReadBinary("user://" + string(kTestFileName)));
158     ASSERT_NE(nullptr, ifs.get());
159     std::unique_ptr<char[]> buf(new char[kBinaryDataSize]);
160     ifs->read(buf.get(), kBinaryDataSize);
161     // Check if all the data are loaded as binary mode.
162     for (size_t i = 0; i < kBinaryDataSize; ++i) {
163       EXPECT_EQ(static_cast<int>(kBinaryData[i]), static_cast<int>(buf[i]));
164     }
165     EXPECT_TRUE(IsEof(ifs.get()));
166   }
167 
168   // Remove test file just in case.
169   EXPECT_TRUE(FileUtil::Unlink(test_file_path));
170   EXPECT_FALSE(FileUtil::FileExists(test_file_path));
171 }
172 
TEST_F(ConfigFileStreamTest,OpenReadText)173 TEST_F(ConfigFileStreamTest, OpenReadText) {
174   // At first, generate a binary data file in (temporary) user directory
175   // so that we can load it as "user://my_binary_file.dat"
176   const char kTestFileName[] = "my_text_file.dat";
177   const string &test_file_path = FileUtil::JoinPath(
178       SystemUtil::GetUserProfileDirectory(), kTestFileName);
179 
180   const char kSourceTextData[] = {
181       'a', 'b', '\r', 'c', '\n', 'd', '\r', '\n', 'e',
182   };
183   {
184     // Use |ios::binary| to preserve the line-end character.
185     OutputFileStream ofs(test_file_path.c_str(),
186                          std::ios::out | std::ios::binary);
187     ofs.write(kSourceTextData, sizeof(kSourceTextData));
188   }
189 
190   ASSERT_TRUE(FileUtil::FileExists(test_file_path));
191 
192 #ifdef OS_WIN
193 #define TRAILING_CARRIAGE_RETURN ""
194 #else
195 #define TRAILING_CARRIAGE_RETURN "\r"
196 #endif
197   const char *kExpectedLines[] = {
198     "ab\rc", "d" TRAILING_CARRIAGE_RETURN, "e",
199   };
200 #undef TRAILING_CARRIAGE_RETURN
201 
202   {
203     std::unique_ptr<std::istream> ifs(
204         ConfigFileStream::OpenReadText("user://" + string(kTestFileName)));
205     ASSERT_NE(nullptr, ifs.get());
206     string line;
207     int line_number = 0;  // note that this is 1-origin.
208     while (!getline(*ifs.get(), line).fail()) {
209       ++line_number;
210       ASSERT_LE(line_number, arraysize(kExpectedLines));
211       EXPECT_EQ(line, kExpectedLines[line_number - 1])
212           << "failed at line: " << line_number;
213     }
214   }
215 
216   // Remove test file just in case.
217   EXPECT_TRUE(FileUtil::Unlink(test_file_path));
218   EXPECT_FALSE(FileUtil::FileExists(test_file_path));
219 }
220 
221 }  // namespace mozc
222