blob: 3f6c3c0f71f9edf0c364392b27f9801bb7be91aa [file] [log] [blame]
Avi Drissman3e1a26c2022-09-15 20:26:031// Copyright 2016 The Chromium Authors
dschuyler613a1032016-12-15 19:22:352// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
Matt Menke5c626b22025-06-04 20:03:455#include "ui/base/webui/i18n_source_stream.h"
6
7#include <stdint.h>
Arthur Sonzogni24d53e32024-07-26 14:00:548
dschuyler613a1032016-12-15 19:22:359#include <utility>
Matt Menke5c626b22025-06-04 20:03:4510#include <vector>
dschuyler613a1032016-12-15 19:22:3511
Matt Menke5c626b22025-06-04 20:03:4512#include "base/containers/span.h"
Keishi Hattori0e45c022021-11-27 09:25:5213#include "base/memory/raw_ptr.h"
dschuyler613a1032016-12-15 19:22:3514#include "net/base/io_buffer.h"
15#include "net/base/test_completion_callback.h"
16#include "net/filter/mock_source_stream.h"
17#include "testing/gtest/include/gtest/gtest.h"
dschuyler613a1032016-12-15 19:22:3518
Reilly Grantf31ae652017-11-16 20:40:0419namespace ui {
dschuyler613a1032016-12-15 19:22:3520
21namespace {
22
dschuyleread12ef2017-01-10 02:36:1323// This constant is rather arbitrary, though the offsets and other sizes must
dschuyler613a1032016-12-15 19:22:3524// be less than kBufferSize.
25const int kBufferSize = 256;
dschuyler613a1032016-12-15 19:22:3526
dschuyleread12ef2017-01-10 02:36:1327const int kMinimumSize = 1;
28const int kSmallSize = 5; // Arbitrary small value > 1.
29const int kInOneReadSize = INT_MAX;
dschuyler613a1032016-12-15 19:22:3530
dschuyleread12ef2017-01-10 02:36:1331struct I18nTest {
Matt Menke5c626b22025-06-04 20:03:4532 constexpr I18nTest(std::string_view input, std::string_view expected_output)
dschuyleread12ef2017-01-10 02:36:1333 : input(input), expected_output(expected_output) {}
dschuyler613a1032016-12-15 19:22:3534
Matt Menke5c626b22025-06-04 20:03:4535 std::string_view input;
36 std::string_view expected_output;
dschuyleread12ef2017-01-10 02:36:1337};
38
39constexpr I18nTest kTestEmpty = I18nTest("", "");
40
41constexpr I18nTest kTestNoReplacements =
42 I18nTest("This text has no i18n replacements.",
43 "This text has no i18n replacements.");
44
45constexpr I18nTest kTestTagAtEndOfLine =
46 I18nTest("test with tag at end of line $",
47 "test with tag at end of line $");
48
49constexpr I18nTest kTestOneReplacement = I18nTest("$i18n{alpha}", "apple");
50
51constexpr I18nTest kTestOneReplacementPlus =
52 I18nTest("Extra text $i18n{alpha}.", "Extra text apple.");
53
54constexpr I18nTest kTestThreeReplacements =
55 I18nTest("$i18n{alpha}^$i18n{beta}_$i18n{gamma}", "apple^banana_carrot");
56
57constexpr I18nTest kTestExtraBraces =
58 I18nTest("($i18n{alpha})^_^_^_^_$i18n{beta}_beta_$i18n{gamma}}}}}}",
59 "(apple)^_^_^_^_banana_beta_carrot}}}}}");
60
61// These tests with generic names are sequences that might catch an error in the
62// future, depending on how the code changes.
63constexpr I18nTest kTest1 =
64 I18nTest(" } $($i18n{gamma})^_^_^_^_$i18n{alpha}_$i18n{gamma}$",
65 " } $(carrot)^_^_^_^_apple_carrot$");
66
67constexpr I18nTest kTest2 =
68 I18nTest("$i18n{alpha} gamma}{ ^_^_^_^_$abc{beta}:$i18n{gamma}z",
69 "apple gamma}{ ^_^_^_^_$abc{beta}:carrotz");
dschuyler613a1032016-12-15 19:22:3570
71struct I18nTestParam {
dschuyleread12ef2017-01-10 02:36:1372 constexpr I18nTestParam(
73 const I18nTest* test,
74 int buf_size,
75 int read_size,
76 net::MockSourceStream::Mode read_mode = net::MockSourceStream::SYNC)
77 : buffer_size(buf_size),
78 read_size(read_size),
79 mode(read_mode),
80 test(test) {}
dschuyler613a1032016-12-15 19:22:3581
82 const int buffer_size;
dschuyleread12ef2017-01-10 02:36:1383 const int read_size;
dschuyler613a1032016-12-15 19:22:3584 const net::MockSourceStream::Mode mode;
Bartek Nowierskif5eeeba2024-01-25 12:49:3985 raw_ptr<const I18nTest> test;
dschuyler613a1032016-12-15 19:22:3586};
87
88} // namespace
89
90class I18nSourceStreamTest : public ::testing::TestWithParam<I18nTestParam> {
91 protected:
92 I18nSourceStreamTest() : output_buffer_size_(GetParam().buffer_size) {}
93
94 // Helpful function to initialize the test fixture.
95 void Init() {
Tom Sepez12abc682023-11-01 17:30:1996 output_buffer_ =
97 base::MakeRefCounted<net::IOBufferWithSize>(output_buffer_size_);
dschuyler613a1032016-12-15 19:22:3598 std::unique_ptr<net::MockSourceStream> source(new net::MockSourceStream());
99 source_ = source.get();
100
dschuyleread12ef2017-01-10 02:36:13101 replacements_["alpha"] = "apple";
102 replacements_["beta"] = "banana";
103 replacements_["gamma"] = "carrot";
dschuyler613a1032016-12-15 19:22:35104 stream_ = I18nSourceStream::Create(
Tsuyoshi Horo17846bf2025-02-20 23:28:13105 std::move(source), net::SourceStreamType::kNone, &replacements_);
dschuyler613a1032016-12-15 19:22:35106 }
107
108 // If MockSourceStream::Mode is ASYNC, completes 1 read from |mock_stream| and
109 // wait for |callback| to complete. If Mode is not ASYNC, does nothing and
110 // returns |previous_result|.
111 int CompleteReadIfAsync(int previous_result,
112 net::TestCompletionCallback* callback,
113 net::MockSourceStream* mock_stream) {
114 if (GetParam().mode == net::MockSourceStream::ASYNC) {
115 EXPECT_EQ(net::ERR_IO_PENDING, previous_result);
116 mock_stream->CompleteNextRead();
117 return callback->WaitForResult();
118 }
119 return previous_result;
120 }
121
dschuyler613a1032016-12-15 19:22:35122 net::IOBuffer* output_buffer() { return output_buffer_.get(); }
123 char* output_data() { return output_buffer_->data(); }
124 size_t output_buffer_size() { return output_buffer_size_; }
125
126 net::MockSourceStream* source() { return source_; }
127 I18nSourceStream* stream() { return stream_.get(); }
128
Matt Menke5c626b22025-06-04 20:03:45129 void PushReadResults(std::string_view input, size_t chunk_size) {
dschuyleread12ef2017-01-10 02:36:13130 size_t written = 0;
Matt Menke5c626b22025-06-04 20:03:45131 size_t source_size = input.size();
dschuyleread12ef2017-01-10 02:36:13132 while (written != source_size) {
133 size_t write_size = std::min(chunk_size, source_size - written);
Matt Menke5c626b22025-06-04 20:03:45134 source()->AddReadResult(
135 base::as_byte_span(input).subspan(written, write_size), net::OK,
136 GetParam().mode);
dschuyleread12ef2017-01-10 02:36:13137 written += write_size;
138 }
Matt Menke5c626b22025-06-04 20:03:45139 source()->AddReadResult(base::span<uint8_t>(), net::OK, GetParam().mode);
dschuyleread12ef2017-01-10 02:36:13140 }
141
dschuyler613a1032016-12-15 19:22:35142 // Reads from |stream_| until an error occurs or the EOF is reached.
143 // When an error occurs, returns the net error code. When an EOF is reached,
144 // returns the number of bytes read and appends data read to |output|.
145 int ReadStream(std::string* output) {
146 int bytes_read = 0;
147 while (true) {
148 net::TestCompletionCallback callback;
149 int rv = stream_->Read(output_buffer(), output_buffer_size(),
150 callback.callback());
151 if (rv == net::ERR_IO_PENDING)
152 rv = CompleteReadIfAsync(rv, &callback, source());
153 if (rv == net::OK)
154 break;
155 if (rv < net::OK)
156 return rv;
157 EXPECT_GT(rv, net::OK);
158 bytes_read += rv;
159 output->append(output_data(), rv);
160 }
161 return bytes_read;
162 }
163
164 private:
dschuyler613a1032016-12-15 19:22:35165 scoped_refptr<net::IOBuffer> output_buffer_;
166 const int output_buffer_size_;
167
Tom Sepezc42bc802023-02-10 23:56:49168 std::unique_ptr<I18nSourceStream> stream_; // Must outlive `source_`.
Keishi Hattori0e45c022021-11-27 09:25:52169 raw_ptr<net::MockSourceStream> source_;
dschuyler613a1032016-12-15 19:22:35170
Reilly Grantf31ae652017-11-16 20:40:04171 TemplateReplacements replacements_;
dschuyler613a1032016-12-15 19:22:35172};
173
Victor Costanbe7d15f2019-01-27 03:19:40174INSTANTIATE_TEST_SUITE_P(
dschuyler613a1032016-12-15 19:22:35175 I18nSourceStreamTests,
176 I18nSourceStreamTest,
dschuyleread12ef2017-01-10 02:36:13177 ::testing::Values(
178 I18nTestParam(&kTest1, kBufferSize, kInOneReadSize),
179 I18nTestParam(&kTest1, kBufferSize, kSmallSize),
180 I18nTestParam(&kTest1, kMinimumSize, kMinimumSize),
181 I18nTestParam(&kTest1, kMinimumSize, kSmallSize),
dschuyler613a1032016-12-15 19:22:35182
dschuyleread12ef2017-01-10 02:36:13183 I18nTestParam(&kTest2, kBufferSize, kInOneReadSize),
184 I18nTestParam(&kTest2, kBufferSize, kSmallSize),
185 I18nTestParam(&kTest2, kMinimumSize, kMinimumSize),
186 I18nTestParam(&kTest2, kMinimumSize, kSmallSize),
dschuyler613a1032016-12-15 19:22:35187
dschuyleread12ef2017-01-10 02:36:13188 I18nTestParam(&kTestEmpty, kBufferSize, kInOneReadSize),
189 I18nTestParam(&kTestEmpty, kBufferSize, kSmallSize),
190 I18nTestParam(&kTestEmpty, kMinimumSize, kMinimumSize),
191 I18nTestParam(&kTestEmpty, kMinimumSize, kSmallSize),
192
193 I18nTestParam(&kTestExtraBraces, kBufferSize, kInOneReadSize),
194 I18nTestParam(&kTestExtraBraces, kBufferSize, kSmallSize),
195 I18nTestParam(&kTestExtraBraces, kMinimumSize, kMinimumSize),
196 I18nTestParam(&kTestExtraBraces, kMinimumSize, kSmallSize),
197
198 I18nTestParam(&kTestNoReplacements, kBufferSize, kInOneReadSize),
199 I18nTestParam(&kTestNoReplacements, kBufferSize, kSmallSize),
200 I18nTestParam(&kTestNoReplacements, kMinimumSize, kMinimumSize),
201 I18nTestParam(&kTestNoReplacements, kMinimumSize, kSmallSize),
202
203 I18nTestParam(&kTestOneReplacement, kBufferSize, kInOneReadSize),
204 I18nTestParam(&kTestOneReplacement, kBufferSize, kSmallSize),
205 I18nTestParam(&kTestOneReplacement, kMinimumSize, kMinimumSize),
206 I18nTestParam(&kTestOneReplacement, kMinimumSize, kSmallSize),
207
208 I18nTestParam(&kTestOneReplacementPlus, kBufferSize, kInOneReadSize),
209 I18nTestParam(&kTestOneReplacementPlus, kBufferSize, kSmallSize),
210 I18nTestParam(&kTestOneReplacementPlus, kMinimumSize, kMinimumSize),
211 I18nTestParam(&kTestOneReplacementPlus, kMinimumSize, kSmallSize),
212
213 I18nTestParam(&kTestTagAtEndOfLine, kBufferSize, kInOneReadSize),
214 I18nTestParam(&kTestTagAtEndOfLine, kBufferSize, kSmallSize),
215 I18nTestParam(&kTestTagAtEndOfLine, kMinimumSize, kMinimumSize),
216 I18nTestParam(&kTestTagAtEndOfLine, kMinimumSize, kSmallSize),
217
218 I18nTestParam(&kTestThreeReplacements, kBufferSize, kInOneReadSize),
219 I18nTestParam(&kTestThreeReplacements, kBufferSize, kSmallSize),
220 I18nTestParam(&kTestThreeReplacements, kMinimumSize, kMinimumSize),
221 I18nTestParam(&kTestThreeReplacements, kMinimumSize, kSmallSize)));
222
223TEST_P(I18nSourceStreamTest, FilterTests) {
dschuyler613a1032016-12-15 19:22:35224 Init();
dschuyleread12ef2017-01-10 02:36:13225 // Create the chain of read buffers.
226 PushReadResults(GetParam().test->input, GetParam().read_size);
227
228 // Process the buffers.
dschuyler613a1032016-12-15 19:22:35229 std::string actual_output;
230 int rv = ReadStream(&actual_output);
dschuyleread12ef2017-01-10 02:36:13231
232 // Check the results.
233 std::string expected_output(GetParam().test->expected_output);
234 EXPECT_EQ(expected_output.size(), static_cast<size_t>(rv));
235 EXPECT_EQ(expected_output, actual_output);
dschuyler613a1032016-12-15 19:22:35236 EXPECT_EQ("i18n", stream()->Description());
237}
238
dschuyleread12ef2017-01-10 02:36:13239TEST_P(I18nSourceStreamTest, LargeFilterTests) {
dschuyler613a1032016-12-15 19:22:35240 Init();
Matt Menke5c626b22025-06-04 20:03:45241 std::vector<uint8_t> padding;
dschuyleread12ef2017-01-10 02:36:13242 // 251 and 599 are prime and avoid power-of-two repetition.
243 int padding_modulus = 251;
244 int pad_size = 599;
245 padding.resize(pad_size);
246 for (int i = 0; i < pad_size; ++i)
247 padding[i] = i % padding_modulus;
dschuyler613a1032016-12-15 19:22:35248
dschuyleread12ef2017-01-10 02:36:13249 // Create the chain of read buffers.
250 const int kPadCount = 128; // Arbitrary number of pads to add.
251 for (int i = 0; i < kPadCount; ++i) {
Matt Menke5c626b22025-06-04 20:03:45252 source()->AddReadResult(padding, net::OK, GetParam().mode);
dschuyler613a1032016-12-15 19:22:35253 }
dschuyleread12ef2017-01-10 02:36:13254 PushReadResults(GetParam().test->input, GetParam().read_size);
dschuyler613a1032016-12-15 19:22:35255
dschuyleread12ef2017-01-10 02:36:13256 // Process the buffers.
dschuyler3ba7a97b2017-01-03 23:11:42257 std::string actual_output;
258 int rv = ReadStream(&actual_output);
dschuyleread12ef2017-01-10 02:36:13259
260 // Check the results.
261 size_t total_padding = kPadCount * padding.size();
262 std::string expected_output(GetParam().test->expected_output);
263 ASSERT_EQ(expected_output.size() + total_padding, static_cast<size_t>(rv));
264 for (int i = 0; i < kPadCount; ++i) {
Matt Menke5c626b22025-06-04 20:03:45265 EXPECT_EQ(base::as_byte_span(
266 actual_output.substr(i * padding.size(), padding.size())),
dschuyleread12ef2017-01-10 02:36:13267 padding);
268 }
269 EXPECT_EQ(expected_output, &actual_output[total_padding]);
dschuyler3ba7a97b2017-01-03 23:11:42270 EXPECT_EQ("i18n", stream()->Description());
271}
272
Reilly Grantf31ae652017-11-16 20:40:04273} // namespace ui