OLD | NEW |
1 // Copyright (c) 2009 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2009 The Chromium Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #include "base/file_path.h" | 5 #include "chrome/test/ui/ui_layout_test.h" |
| 6 |
6 #include "base/file_util.h" | 7 #include "base/file_util.h" |
7 #include "base/path_service.h" | |
8 #include "base/platform_thread.h" | |
9 #include "base/string_util.h" | |
10 #include "chrome/browser/worker_host/worker_service.h" | |
11 #include "chrome/common/chrome_paths.h" | 8 #include "chrome/common/chrome_paths.h" |
12 #include "chrome/test/automation/browser_proxy.h" | 9 #include "chrome/common/chrome_switches.h" |
13 #include "chrome/test/automation/tab_proxy.h" | 10 #include "chrome/test/automation/tab_proxy.h" |
14 #include "chrome/test/ui/ui_test.h" | |
15 #include "net/base/escape.h" | 11 #include "net/base/escape.h" |
16 #include "net/base/net_util.h" | 12 #include "net/base/net_util.h" |
17 | 13 |
18 #if defined(OS_WIN) | 14 #if defined(OS_WIN) |
19 const char kPlatformName[] = "chromium-win"; | 15 static const char kPlatformName[] = "chromium-win"; |
20 #elif defined(OS_MACOSX) | 16 #elif defined(OS_MACOSX) |
21 const char kPlatformName[] = "chromium-mac"; | 17 static const char kPlatformName[] = "chromium-mac"; |
22 #elif defined(OS_LINUX) | 18 #elif defined(OS_LINUX) |
23 const char kPlatformName[] = "chromium-linux"; | 19 static const char kPlatformName[] = "chromium-linux"; |
24 #else | 20 #else |
25 #error No known OS defined | 21 #error No known OS defined |
26 #endif | 22 #endif |
27 | 23 |
28 const char kTestCompleteCookie[] = "status"; | 24 static const char kTestCompleteCookie[] = "status"; |
29 const char kTestCompleteSuccess[] = "OK"; | |
30 const int kTestIntervalMs = 250; | |
31 const int kTestWaitTimeoutMs = 60 * 1000; | |
32 | 25 |
33 class WorkerTest : public UITest { | 26 UILayoutTest::UILayoutTest() |
34 protected: | |
35 WorkerTest(); | |
36 virtual ~WorkerTest(); | |
37 | |
38 void RunTest(const std::wstring& test_case); | |
39 | |
40 void InitializeForLayoutTest(const FilePath& test_parent_dir, | |
41 const FilePath& test_case_dir, | |
42 bool is_http_test); | |
43 void RunLayoutTest(const std::string& test_case_file_name, bool is_http_test); | |
44 | |
45 protected: | |
46 bool ReadExpectedResult(const FilePath& result_dir_path, | |
47 const std::string test_case_file_name, | |
48 std::string* expected_result_value); | |
49 | |
50 bool initialized_for_layout_test_; | |
51 int test_count_; | |
52 FilePath temp_test_dir_; | |
53 FilePath layout_test_dir_; | |
54 FilePath test_case_dir_; | |
55 FilePath new_http_root_dir_; | |
56 FilePath new_layout_test_dir_; | |
57 FilePath rebase_result_dir_; | |
58 FilePath rebase_result_win_dir_; | |
59 std::string layout_test_controller_; | |
60 }; | |
61 | |
62 WorkerTest::WorkerTest() | |
63 : UITest(), initialized_for_layout_test_(false), test_count_(0) { | 27 : UITest(), initialized_for_layout_test_(false), test_count_(0) { |
64 } | 28 } |
65 | 29 |
66 WorkerTest::~WorkerTest() { | 30 UILayoutTest::~UILayoutTest() { |
67 // The deletion might fail because HTTP server process might not been | 31 // The deletion might fail because HTTP server process might not been |
68 // completely shut down yet and is still holding certain handle to it. | 32 // completely shut down yet and is still holding certain handle to it. |
69 // To work around this problem, we try to repeat the deletion several | 33 // To work around this problem, we try to repeat the deletion several |
70 // times. | 34 // times. |
71 if (!temp_test_dir_.empty()) { | 35 if (!temp_test_dir_.empty()) { |
72 static const int kRetryNum = 10; | 36 static const int kRetryNum = 10; |
73 static const int kRetryDelayTimeMs = 500; | 37 static const int kRetryDelayTimeMs = 500; |
74 | 38 |
75 int retry_time = 0; | 39 int retry_time = 0; |
76 for (int i = 0; i < kRetryNum; ++i) { | 40 for (int i = 0; i < kRetryNum; ++i) { |
77 file_util::Delete(temp_test_dir_, true); | 41 file_util::Delete(temp_test_dir_, true); |
78 if (!file_util::DirectoryExists(temp_test_dir_)) | 42 if (!file_util::DirectoryExists(temp_test_dir_)) |
79 break; | 43 break; |
80 | 44 |
81 PlatformThread::Sleep(kRetryDelayTimeMs); | 45 PlatformThread::Sleep(kRetryDelayTimeMs); |
82 retry_time += kRetryDelayTimeMs; | 46 retry_time += kRetryDelayTimeMs; |
83 } | 47 } |
84 | 48 |
85 if (retry_time) | 49 if (retry_time) { |
86 printf("Retrying %d ms to delete temp worker directory.\n", retry_time); | 50 printf("Retrying %d ms to delete temp layout test directory.\n", |
| 51 retry_time); |
| 52 } |
87 } | 53 } |
88 } | 54 } |
89 | 55 |
90 void WorkerTest::RunTest(const std::wstring& test_case) { | 56 void UILayoutTest::InitializeForLayoutTest(const FilePath& test_parent_dir, |
91 scoped_refptr<TabProxy> tab(GetActiveTab()); | 57 const FilePath& test_case_dir, |
92 ASSERT_TRUE(tab.get()); | 58 bool is_http_test) { |
93 | |
94 GURL url = GetTestUrl(L"workers", test_case); | |
95 ASSERT_TRUE(tab->NavigateToURL(url)); | |
96 | |
97 std::string value = WaitUntilCookieNonEmpty(tab.get(), url, | |
98 kTestCompleteCookie, kTestIntervalMs, kTestWaitTimeoutMs); | |
99 ASSERT_STREQ(kTestCompleteSuccess, value.c_str()); | |
100 } | |
101 | |
102 void WorkerTest::InitializeForLayoutTest(const FilePath& test_parent_dir, | |
103 const FilePath& test_case_dir, | |
104 bool is_http_test) { | |
105 FilePath src_dir; | 59 FilePath src_dir; |
106 PathService::Get(base::DIR_SOURCE_ROOT, &src_dir); | 60 PathService::Get(base::DIR_SOURCE_ROOT, &src_dir); |
107 | 61 |
108 // Gets the file path to WebKit layout tests for workers, that is, | 62 // Gets the file path to WebKit ui layout tests, that is, |
109 // chrome/test/data/workers/LayoutTests/.../workers | 63 // chrome/test/data/ui_tests/LayoutTests/... |
110 // Note that we have to use our copy of WebKit layout tests for workers. | 64 // Note that we have to use our own copy of WebKit layout tests because our |
111 // This is because our build machines do not have WebKit layout tests added. | 65 // build machines do not have WebKit layout tests added. |
112 layout_test_dir_ = src_dir.AppendASCII("chrome"); | 66 layout_test_dir_ = src_dir.AppendASCII("chrome"); |
113 layout_test_dir_ = layout_test_dir_.AppendASCII("test"); | 67 layout_test_dir_ = layout_test_dir_.AppendASCII("test"); |
114 layout_test_dir_ = layout_test_dir_.AppendASCII("data"); | 68 layout_test_dir_ = layout_test_dir_.AppendASCII("data"); |
115 layout_test_dir_ = layout_test_dir_.AppendASCII("workers"); | 69 layout_test_dir_ = layout_test_dir_.AppendASCII("layout_tests"); |
116 layout_test_dir_ = layout_test_dir_.Append(test_parent_dir); | 70 layout_test_dir_ = layout_test_dir_.Append(test_parent_dir); |
117 layout_test_dir_ = layout_test_dir_.Append(test_case_dir); | 71 layout_test_dir_ = layout_test_dir_.Append(test_case_dir); |
| 72 ASSERT_TRUE(file_util::DirectoryExists(layout_test_dir_)); |
118 | 73 |
119 // If not found, try to use the original copy of WebKit layout tests for | 74 // Gets the file path to rebased expected result directory for the current |
120 // workers. For testing only in local machine only. | 75 // platform. |
121 // third_party/LayoutTests/.../workers | 76 // webkit/data/layout_tests/platform/chromium_***/LayoutTests/... |
122 if (!file_util::DirectoryExists(layout_test_dir_)) { | |
123 layout_test_dir_ = src_dir.AppendASCII("third_party"); | |
124 layout_test_dir_ = layout_test_dir_.Append(test_parent_dir); | |
125 layout_test_dir_ = layout_test_dir_.Append(test_case_dir); | |
126 ASSERT_TRUE(file_util::DirectoryExists(layout_test_dir_)); | |
127 } | |
128 | |
129 // Gets the file path to rebased expected result directory for workers for | |
130 // current platform. | |
131 // webkit/data/layout_tests/platform/chromium_***/LayoutTests/.../workers | |
132 rebase_result_dir_ = src_dir.AppendASCII("webkit"); | 77 rebase_result_dir_ = src_dir.AppendASCII("webkit"); |
133 rebase_result_dir_ = rebase_result_dir_.AppendASCII("data"); | 78 rebase_result_dir_ = rebase_result_dir_.AppendASCII("data"); |
134 rebase_result_dir_ = rebase_result_dir_.AppendASCII("layout_tests"); | 79 rebase_result_dir_ = rebase_result_dir_.AppendASCII("layout_tests"); |
135 rebase_result_dir_ = rebase_result_dir_.AppendASCII("platform"); | 80 rebase_result_dir_ = rebase_result_dir_.AppendASCII("platform"); |
136 rebase_result_dir_ = rebase_result_dir_.AppendASCII(kPlatformName); | 81 rebase_result_dir_ = rebase_result_dir_.AppendASCII(kPlatformName); |
137 rebase_result_dir_ = rebase_result_dir_.Append(test_parent_dir); | 82 rebase_result_dir_ = rebase_result_dir_.Append(test_parent_dir); |
138 rebase_result_dir_ = rebase_result_dir_.Append(test_case_dir); | 83 rebase_result_dir_ = rebase_result_dir_.Append(test_case_dir); |
139 | 84 |
140 // Gets the file path to rebased expected result directory for workers under | 85 // Gets the file path to rebased expected result directory under the |
141 // win32 platform. This is used by other non-win32 platform to use the same | 86 // win32 platform. This is used by other non-win32 platform to use the same |
142 // rebased expected results. | 87 // rebased expected results. |
143 #if !defined(OS_WIN) | 88 #if !defined(OS_WIN) |
144 rebase_result_win_dir_ = src_dir.AppendASCII("webkit"); | 89 rebase_result_win_dir_ = src_dir.AppendASCII("webkit"); |
145 rebase_result_win_dir_ = rebase_result_win_dir_.AppendASCII("data"); | 90 rebase_result_win_dir_ = rebase_result_win_dir_.AppendASCII("data"); |
146 rebase_result_win_dir_ = rebase_result_win_dir_.AppendASCII("layout_tests"); | 91 rebase_result_win_dir_ = rebase_result_win_dir_.AppendASCII("layout_tests"); |
147 rebase_result_win_dir_ = rebase_result_win_dir_.AppendASCII("platform"); | 92 rebase_result_win_dir_ = rebase_result_win_dir_.AppendASCII("platform"); |
148 rebase_result_win_dir_ = rebase_result_win_dir_.AppendASCII("chromium-win"); | 93 rebase_result_win_dir_ = rebase_result_win_dir_.AppendASCII("chromium-win"); |
149 rebase_result_win_dir_ = rebase_result_win_dir_.Append(test_parent_dir); | 94 rebase_result_win_dir_ = rebase_result_win_dir_.Append(test_parent_dir); |
150 rebase_result_win_dir_ = rebase_result_win_dir_.Append(test_case_dir); | 95 rebase_result_win_dir_ = rebase_result_win_dir_.Append(test_case_dir); |
151 #endif | 96 #endif |
152 | 97 |
153 // Creates the temporary directory. | 98 // Creates the temporary directory. |
154 ASSERT_TRUE(file_util::CreateNewTempDirectory( | 99 ASSERT_TRUE(file_util::CreateNewTempDirectory( |
155 FILE_PATH_LITERAL("chrome_worker_test_"), &temp_test_dir_)); | 100 FILE_PATH_LITERAL("chrome_ui_layout_tests_"), &temp_test_dir_)); |
156 | 101 |
157 // Creates the new layout test subdirectory under the temp directory. | 102 // Creates the new layout test subdirectory under the temp directory. |
158 // Note that we have to mimic the same layout test directory structure, | 103 // Note that we have to mimic the same layout test directory structure, |
159 // like .../LayoutTests/fast/workers/.... Otherwise those layout tests | 104 // like .../LayoutTests/fast/workers/.... Otherwise those layout tests |
160 // dealing with location property, like worker-location.html, could fail. | 105 // dealing with location property, like worker-location.html, could fail. |
161 new_layout_test_dir_ = temp_test_dir_; | 106 new_layout_test_dir_ = temp_test_dir_; |
162 new_layout_test_dir_ = new_layout_test_dir_.Append(test_parent_dir); | 107 new_layout_test_dir_ = new_layout_test_dir_.Append(test_parent_dir); |
163 if (is_http_test) { | 108 if (is_http_test) { |
164 new_http_root_dir_ = new_layout_test_dir_; | 109 new_http_root_dir_ = new_layout_test_dir_; |
165 test_case_dir_ = test_case_dir; | 110 test_case_dir_ = test_case_dir; |
(...skipping 19 matching lines...) Expand all Loading... |
185 FilePath new_parent_resource_path(new_layout_test_dir_.DirName()); | 130 FilePath new_parent_resource_path(new_layout_test_dir_.DirName()); |
186 new_parent_resource_path = | 131 new_parent_resource_path = |
187 new_parent_resource_path.AppendASCII("resources"); | 132 new_parent_resource_path.AppendASCII("resources"); |
188 ASSERT_TRUE(file_util::CopyDirectory( | 133 ASSERT_TRUE(file_util::CopyDirectory( |
189 parent_resource_path, new_parent_resource_path, true)); | 134 parent_resource_path, new_parent_resource_path, true)); |
190 } | 135 } |
191 | 136 |
192 // Reads the layout test controller simulation script. | 137 // Reads the layout test controller simulation script. |
193 FilePath path; | 138 FilePath path; |
194 PathService::Get(chrome::DIR_TEST_DATA, &path); | 139 PathService::Get(chrome::DIR_TEST_DATA, &path); |
195 path = path.AppendASCII("workers"); | 140 path = path.AppendASCII("layout_tests"); |
196 path = path.AppendASCII("layout_test_controller.html"); | 141 path = path.AppendASCII("layout_test_controller.html"); |
197 ASSERT_TRUE(file_util::ReadFileToString(path, &layout_test_controller_)); | 142 ASSERT_TRUE(file_util::ReadFileToString(path, &layout_test_controller_)); |
198 } | 143 } |
199 | 144 |
200 void WorkerTest::RunLayoutTest(const std::string& test_case_file_name, | 145 void UILayoutTest::RunLayoutTest(const std::string& test_case_file_name, |
201 bool is_http_test) { | 146 bool is_http_test) { |
202 SCOPED_TRACE(test_case_file_name.c_str()); | 147 SCOPED_TRACE(test_case_file_name.c_str()); |
203 | 148 |
204 ASSERT_TRUE(!layout_test_controller_.empty()); | 149 ASSERT_TRUE(!layout_test_controller_.empty()); |
205 | 150 |
206 // Creates a new cookie name. We will have to use a new cookie because | 151 // Creates a new cookie name. We will have to use a new cookie because |
207 // this function could be called multiple times. | 152 // this function could be called multiple times. |
208 std::string status_cookie(kTestCompleteCookie); | 153 std::string status_cookie(kTestCompleteCookie); |
209 status_cookie += IntToString(test_count_); | 154 status_cookie += IntToString(test_count_); |
210 test_count_++; | 155 test_count_++; |
211 | 156 |
(...skipping 56 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
268 } | 213 } |
269 ASSERT_TRUE(!expected_result_value.empty()); | 214 ASSERT_TRUE(!expected_result_value.empty()); |
270 | 215 |
271 // Normalizes the expected result. | 216 // Normalizes the expected result. |
272 ReplaceSubstringsAfterOffset(&expected_result_value, 0, "\r", ""); | 217 ReplaceSubstringsAfterOffset(&expected_result_value, 0, "\r", ""); |
273 | 218 |
274 // Compares the results. | 219 // Compares the results. |
275 EXPECT_STREQ(expected_result_value.c_str(), value.c_str()); | 220 EXPECT_STREQ(expected_result_value.c_str(), value.c_str()); |
276 } | 221 } |
277 | 222 |
278 bool WorkerTest::ReadExpectedResult(const FilePath& result_dir_path, | 223 bool UILayoutTest::ReadExpectedResult(const FilePath& result_dir_path, |
279 const std::string test_case_file_name, | 224 const std::string test_case_file_name, |
280 std::string* expected_result_value) { | 225 std::string* expected_result_value) { |
281 FilePath expected_result_path(result_dir_path); | 226 FilePath expected_result_path(result_dir_path); |
282 expected_result_path = expected_result_path.AppendASCII(test_case_file_name); | 227 expected_result_path = expected_result_path.AppendASCII(test_case_file_name); |
283 expected_result_path = expected_result_path.InsertBeforeExtension( | 228 expected_result_path = expected_result_path.InsertBeforeExtension( |
284 FILE_PATH_LITERAL("-expected")); | 229 FILE_PATH_LITERAL("-expected")); |
285 expected_result_path = | 230 expected_result_path = |
286 expected_result_path.ReplaceExtension(FILE_PATH_LITERAL("txt")); | 231 expected_result_path.ReplaceExtension(FILE_PATH_LITERAL("txt")); |
287 return file_util::ReadFileToString(expected_result_path, | 232 return file_util::ReadFileToString(expected_result_path, |
288 expected_result_value); | 233 expected_result_value); |
289 } | 234 } |
290 | |
291 TEST_F(WorkerTest, SingleWorker) { | |
292 RunTest(L"single_worker.html"); | |
293 } | |
294 | |
295 TEST_F(WorkerTest, MultipleWorkers) { | |
296 RunTest(L"multi_worker.html"); | |
297 } | |
298 | |
299 TEST_F(WorkerTest, WorkerFastLayoutTests) { | |
300 static const char* kLayoutTestFiles[] = { | |
301 "stress-js-execution.html", | |
302 "use-machine-stack.html", | |
303 "worker-call.html", | |
304 "worker-close.html", | |
305 "worker-constructor.html", | |
306 "worker-context-gc.html", | |
307 "worker-event-listener.html", | |
308 "worker-gc.html", | |
309 "worker-location.html", | |
310 "worker-navigator.html", | |
311 "worker-replace-global-constructor.html", | |
312 "worker-replace-self.html", | |
313 "worker-script-error.html", | |
314 "worker-terminate.html", | |
315 "worker-timeout.html" | |
316 }; | |
317 | |
318 FilePath fast_test_dir; | |
319 fast_test_dir = fast_test_dir.AppendASCII("LayoutTests"); | |
320 fast_test_dir = fast_test_dir.AppendASCII("fast"); | |
321 | |
322 FilePath worker_test_dir; | |
323 worker_test_dir = worker_test_dir.AppendASCII("workers"); | |
324 InitializeForLayoutTest(fast_test_dir, worker_test_dir, false); | |
325 | |
326 for (size_t i = 0; i < arraysize(kLayoutTestFiles); ++i) | |
327 RunLayoutTest(kLayoutTestFiles[i], false); | |
328 } | |
329 | |
330 TEST_F(WorkerTest, WorkerHttpLayoutTests) { | |
331 static const char* kLayoutTestFiles[] = { | |
332 // flakey? BUG 16934 "text-encoding.html", | |
333 "worker-importScripts.html", | |
334 "worker-redirect.html", | |
335 }; | |
336 | |
337 FilePath http_test_dir; | |
338 http_test_dir = http_test_dir.AppendASCII("LayoutTests"); | |
339 http_test_dir = http_test_dir.AppendASCII("http"); | |
340 http_test_dir = http_test_dir.AppendASCII("tests"); | |
341 | |
342 FilePath worker_test_dir; | |
343 worker_test_dir = worker_test_dir.AppendASCII("workers"); | |
344 InitializeForLayoutTest(http_test_dir, worker_test_dir, true); | |
345 | |
346 StartHttpServer(new_http_root_dir_); | |
347 for (size_t i = 0; i < arraysize(kLayoutTestFiles); ++i) | |
348 RunLayoutTest(kLayoutTestFiles[i], true); | |
349 StopHttpServer(); | |
350 } | |
351 | |
352 TEST_F(WorkerTest, WorkerXhrHttpLayoutTests) { | |
353 static const char* kLayoutTestFiles[] = { | |
354 "abort-exception-assert.html", | |
355 "close.html", | |
356 //"methods-async.html", | |
357 //"methods.html", | |
358 "xmlhttprequest-file-not-found.html" | |
359 }; | |
360 | |
361 FilePath http_test_dir; | |
362 http_test_dir = http_test_dir.AppendASCII("LayoutTests"); | |
363 http_test_dir = http_test_dir.AppendASCII("http"); | |
364 http_test_dir = http_test_dir.AppendASCII("tests"); | |
365 | |
366 FilePath worker_test_dir; | |
367 worker_test_dir = worker_test_dir.AppendASCII("xmlhttprequest"); | |
368 worker_test_dir = worker_test_dir.AppendASCII("workers"); | |
369 InitializeForLayoutTest(http_test_dir, worker_test_dir, true); | |
370 | |
371 StartHttpServer(new_http_root_dir_); | |
372 for (size_t i = 0; i < arraysize(kLayoutTestFiles); ++i) | |
373 RunLayoutTest(kLayoutTestFiles[i], true); | |
374 StopHttpServer(); | |
375 } | |
376 | |
377 TEST_F(WorkerTest, LimitPerPage) { | |
378 int max_workers_per_tab = WorkerService::kMaxWorkersPerTabWhenSeparate; | |
379 GURL url = GetTestUrl(L"workers", L"many_workers.html"); | |
380 url = GURL(url.spec() + StringPrintf("?count=%d", max_workers_per_tab + 1)); | |
381 | |
382 scoped_refptr<TabProxy> tab(GetActiveTab()); | |
383 ASSERT_TRUE(tab.get()); | |
384 ASSERT_TRUE(tab->NavigateToURL(url)); | |
385 | |
386 EXPECT_EQ(max_workers_per_tab + 1 + (UITest::in_process_renderer() ? 0 : 1), | |
387 UITest::GetBrowserProcessCount()); | |
388 } | |
389 | |
390 TEST_F(WorkerTest, LimitTotal) { | |
391 int max_workers_per_tab = WorkerService::kMaxWorkersPerTabWhenSeparate; | |
392 int total_workers = WorkerService::kMaxWorkersWhenSeparate; | |
393 | |
394 int tab_count = (total_workers / max_workers_per_tab) + 1; | |
395 GURL url = GetTestUrl(L"workers", L"many_workers.html"); | |
396 url = GURL(url.spec() + StringPrintf("?count=%d", max_workers_per_tab)); | |
397 | |
398 scoped_refptr<TabProxy> tab(GetActiveTab()); | |
399 ASSERT_TRUE(tab.get()); | |
400 ASSERT_TRUE(tab->NavigateToURL(url)); | |
401 scoped_refptr<BrowserProxy> window(automation()->GetBrowserWindow(0)); | |
402 for (int i = 1; i < tab_count; ++i) | |
403 window->AppendTab(url); | |
404 | |
405 // Check that we didn't create more than the max number of workers. | |
406 EXPECT_EQ(total_workers + 1 + (UITest::in_process_renderer() ? 0 : tab_count), | |
407 UITest::GetBrowserProcessCount()); | |
408 | |
409 // Now close the first tab and check that the queued workers were started. | |
410 tab->Close(true); | |
411 tab->NavigateToURL(GetTestUrl(L"google", L"google.html")); | |
412 | |
413 EXPECT_EQ(total_workers + 1 + (UITest::in_process_renderer() ? 0 : tab_count), | |
414 UITest::GetBrowserProcessCount()); | |
415 } | |
OLD | NEW |