1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. 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/command_line.h" 6 #include "base/file_path.h" 7 #include "base/file_util.h" 8 #include "base/string_util.h" 9 #include "base/test/test_file_util.h" 10 #include "base/threading/simple_thread.h" 11 #include "base/utf_string_conversions.h" 12 #include "chrome/test/automation/tab_proxy.h" 13 #include "chrome/test/ui/ui_test.h" 14 #include "net/test/test_server.h" 15 #include "printing/image.h" 16 #include "printing/printing_test.h" 17 18 namespace { 19 20 using printing::Image; 21 22 const char kGenerateSwitch[] = "print-layout-generate"; 23 const FilePath::CharType kDocRoot[] = FILE_PATH_LITERAL("chrome/test/data"); 24 25 class PrintingLayoutTest : public PrintingTest<UITest> { 26 public: 27 PrintingLayoutTest() { 28 emf_path_ = browser_directory_.AppendASCII("metafile_dumps"); 29 launch_arguments_.AppendSwitchPath("debug-print", emf_path_); 30 show_window_ = true; 31 } 32 33 virtual void SetUp() { 34 // Make sure there is no left overs. 35 CleanupDumpDirectory(); 36 UITest::SetUp(); 37 } 38 39 virtual void TearDown() { 40 UITest::TearDown(); 41 file_util::Delete(emf_path_, true); 42 } 43 44 protected: 45 void PrintNowTab() { 46 scoped_refptr<TabProxy> tab_proxy(GetActiveTab()); 47 ASSERT_TRUE(tab_proxy.get()); 48 ASSERT_TRUE(tab_proxy->PrintNow()); 49 } 50 51 // Finds the dump for the last print job and compares it to the data named 52 // |verification_name|. Compares the saved printed job pixels with the test 53 // data pixels and returns the percentage of different pixels; 0 for success, 54 // [0, 100] for failure. 55 double CompareWithResult(const std::wstring& verification_name) { 56 FilePath test_result(ScanFiles(verification_name)); 57 if (test_result.value().empty()) { 58 // 100% different, the print job buffer is not there. 59 return 100.; 60 } 61 62 std::wstring verification_file(test_data_directory_.value()); 63 file_util::AppendToPath(&verification_file, L"printing"); 64 file_util::AppendToPath(&verification_file, verification_name); 65 FilePath emf(verification_file + L".emf"); 66 FilePath png(verification_file + L".png"); 67 68 // Looks for Cleartype override. 69 if (file_util::PathExists( 70 FilePath::FromWStringHack(verification_file + L"_cleartype.png")) && 71 IsClearTypeEnabled()) { 72 png = FilePath(verification_file + L"_cleartype.png"); 73 } 74 75 if (GenerateFiles()) { 76 // Copy the .emf and generate an .png. 77 file_util::CopyFile(test_result, emf); 78 Image emf_content(emf); 79 emf_content.SaveToPng(png); 80 // Saving is always fine. 81 return 0; 82 } else { 83 // File compare between test and result. 84 Image emf_content(emf); 85 Image test_content(test_result); 86 Image png_content(png); 87 double diff_emf = emf_content.PercentageDifferent(test_content); 88 89 EXPECT_EQ(0., diff_emf) << verification_name << 90 L" original size:" << emf_content.size() << 91 L" result size:" << test_content.size(); 92 if (diff_emf) { 93 // Backup the result emf file. 94 file_util::CopyFile(test_result, FilePath( 95 verification_file + L"_failed.emf")); 96 } 97 98 // This verification is only to know that the EMF rendering stays 99 // immutable. 100 double diff_png = emf_content.PercentageDifferent(png_content); 101 EXPECT_EQ(0., diff_png) << verification_name << 102 L" original size:" << emf_content.size() << 103 L" result size:" << test_content.size(); 104 if (diff_png) { 105 // Backup the rendered emf file to detect the rendering difference. 106 emf_content.SaveToPng(FilePath(verification_file + L"_rendering.png")); 107 } 108 return std::max(diff_png, diff_emf); 109 } 110 } 111 112 // Makes sure the directory exists and is empty. 113 void CleanupDumpDirectory() { 114 EXPECT_TRUE(file_util::DieFileDie(emf_path(), true)); 115 EXPECT_TRUE(file_util::CreateDirectory(emf_path())); 116 } 117 118 // Returns if Clear Type is currently enabled. 119 static bool IsClearTypeEnabled() { 120 BOOL ct_enabled = 0; 121 if (SystemParametersInfo(SPI_GETCLEARTYPE, 0, &ct_enabled, 0) && ct_enabled) 122 return true; 123 UINT smoothing = 0; 124 if (SystemParametersInfo(SPI_GETFONTSMOOTHINGTYPE, 0, &smoothing, 0) && 125 smoothing == FE_FONTSMOOTHINGCLEARTYPE) 126 return true; 127 return false; 128 } 129 130 private: 131 // Verifies that there is one .emf and one .prn file in the dump directory. 132 // Returns the path of the .emf file and deletes the .prn file. 133 std::wstring ScanFiles(const std::wstring& verification_name) { 134 // Try to 10 seconds. 135 std::wstring emf_file; 136 std::wstring prn_file; 137 bool found_emf = false; 138 bool found_prn = false; 139 for (int i = 0; i < 100; ++i) { 140 file_util::FileEnumerator enumerator(emf_path(), false, 141 file_util::FileEnumerator::FILES); 142 emf_file.clear(); 143 prn_file.clear(); 144 found_emf = false; 145 found_prn = false; 146 FilePath file; 147 while (!(file = enumerator.Next()).empty()) { 148 std::wstring ext = file.Extension(); 149 if (base::strcasecmp(WideToUTF8(ext).c_str(), ".emf") == 0) { 150 EXPECT_FALSE(found_emf) << "Found a leftover .EMF file: \"" << 151 emf_file << "\" and \"" << file.value() << 152 "\" when looking for \"" << verification_name << "\""; 153 found_emf = true; 154 emf_file = file.value(); 155 continue; 156 } 157 if (base::strcasecmp(WideToUTF8(ext).c_str(), ".prn") == 0) { 158 EXPECT_FALSE(found_prn) << "Found a leftover .PRN file: \"" << 159 prn_file << "\" and \"" << file.value() << 160 "\" when looking for \"" << verification_name << "\""; 161 prn_file = file.value(); 162 found_prn = true; 163 file_util::Delete(file, false); 164 continue; 165 } 166 EXPECT_TRUE(false); 167 } 168 if (found_emf && found_prn) 169 break; 170 base::PlatformThread::Sleep(100); 171 } 172 EXPECT_TRUE(found_emf) << ".PRN file is: " << prn_file; 173 EXPECT_TRUE(found_prn) << ".EMF file is: " << emf_file; 174 return emf_file; 175 } 176 177 static bool GenerateFiles() { 178 return CommandLine::ForCurrentProcess()->HasSwitch(kGenerateSwitch); 179 } 180 181 const FilePath& emf_path() const { return emf_path_; } 182 183 FilePath emf_path_; 184 185 DISALLOW_COPY_AND_ASSIGN(PrintingLayoutTest); 186 }; 187 188 // Tests that don't need UI access. 189 class PrintingLayoutTestHidden : public PrintingLayoutTest { 190 public: 191 PrintingLayoutTestHidden() { 192 show_window_ = false; 193 } 194 }; 195 196 class PrintingLayoutTextTest : public PrintingLayoutTest { 197 typedef PrintingLayoutTest Parent; 198 public: 199 // Returns if the test is disabled. 200 // http://crbug.com/64869 Until the issue is fixed, disable the test if 201 // ClearType is enabled. 202 static bool IsTestCaseDisabled() { 203 return Parent::IsTestCaseDisabled() || IsClearTypeEnabled(); 204 } 205 }; 206 207 // Finds the first dialog window owned by owner_process. 208 HWND FindDialogWindow(DWORD owner_process) { 209 HWND dialog_window(NULL); 210 for (;;) { 211 dialog_window = FindWindowEx(NULL, 212 dialog_window, 213 MAKEINTATOM(32770), 214 NULL); 215 if (!dialog_window) 216 break; 217 218 // The dialog must be owned by our target process. 219 DWORD process_id = 0; 220 GetWindowThreadProcessId(dialog_window, &process_id); 221 if (process_id == owner_process) 222 break; 223 } 224 return dialog_window; 225 } 226 227 // Tries to close a dialog window. 228 bool CloseDialogWindow(HWND dialog_window) { 229 LRESULT res = SendMessage(dialog_window, DM_GETDEFID, 0, 0); 230 if (!res) 231 return false; 232 EXPECT_EQ(DC_HASDEFID, HIWORD(res)); 233 WORD print_button_id = LOWORD(res); 234 res = SendMessage( 235 dialog_window, 236 WM_COMMAND, 237 print_button_id, 238 reinterpret_cast<LPARAM>(GetDlgItem(dialog_window, print_button_id))); 239 return res == 0; 240 } 241 242 // Dismiss the first dialog box owned by owner_process by "executing" the 243 // default button. 244 class DismissTheWindow : public base::DelegateSimpleThread::Delegate { 245 public: 246 explicit DismissTheWindow(DWORD owner_process) 247 : owner_process_(owner_process) { 248 } 249 250 virtual void Run() { 251 HWND dialog_window; 252 for (;;) { 253 // First enumerate the windows. 254 dialog_window = FindDialogWindow(owner_process_); 255 256 // Try to close it. 257 if (dialog_window) { 258 if (CloseDialogWindow(dialog_window)) { 259 break; 260 } 261 } 262 base::PlatformThread::Sleep(10); 263 } 264 265 // Now verify that it indeed closed itself. 266 while (IsWindow(dialog_window)) { 267 CloseDialogWindow(dialog_window); 268 base::PlatformThread::Sleep(10); 269 } 270 } 271 272 DWORD owner_process() { return owner_process_; } 273 274 private: 275 DWORD owner_process_; 276 }; 277 278 } // namespace 279 280 // Fails, see http://crbug.com/7721. 281 TEST_F(PrintingLayoutTextTest, DISABLED_Complex) { 282 if (IsTestCaseDisabled()) 283 return; 284 285 DismissTheWindow dismisser(base::GetProcId(process())); 286 base::DelegateSimpleThread close_printdlg_thread(&dismisser, 287 "close_printdlg_thread"); 288 289 // Print a document, check its output. 290 net::TestServer test_server(net::TestServer::TYPE_HTTP, FilePath(kDocRoot)); 291 ASSERT_TRUE(test_server.Start()); 292 293 NavigateToURL(test_server.GetURL("files/printing/test1.html")); 294 close_printdlg_thread.Start(); 295 PrintNowTab(); 296 close_printdlg_thread.Join(); 297 EXPECT_EQ(0., CompareWithResult(L"test1")); 298 } 299 300 struct TestPool { 301 const char* source; 302 const wchar_t* result; 303 }; 304 305 const TestPool kTestPool[] = { 306 // ImagesB&W 307 "files/printing/test2.html", L"test2", 308 // ImagesTransparent 309 "files/printing/test3.html", L"test3", 310 // ImageColor 311 "files/printing/test4.html", L"test4", 312 }; 313 314 // http://crbug.com/7721 315 TEST_F(PrintingLayoutTestHidden, DISABLED_ManyTimes) { 316 if (IsTestCaseDisabled()) 317 return; 318 319 net::TestServer test_server(net::TestServer::TYPE_HTTP, FilePath(kDocRoot)); 320 ASSERT_TRUE(test_server.Start()); 321 322 DismissTheWindow dismisser(base::GetProcId(process())); 323 324 ASSERT_GT(arraysize(kTestPool), 0u); 325 for (int i = 0; i < arraysize(kTestPool); ++i) { 326 if (i) 327 CleanupDumpDirectory(); 328 const TestPool& test = kTestPool[i % arraysize(kTestPool)]; 329 NavigateToURL(test_server.GetURL(test.source)); 330 base::DelegateSimpleThread close_printdlg_thread1(&dismisser, 331 "close_printdlg_thread"); 332 EXPECT_EQ(NULL, FindDialogWindow(dismisser.owner_process())); 333 close_printdlg_thread1.Start(); 334 PrintNowTab(); 335 close_printdlg_thread1.Join(); 336 EXPECT_EQ(0., CompareWithResult(test.result)) << test.result; 337 CleanupDumpDirectory(); 338 base::DelegateSimpleThread close_printdlg_thread2(&dismisser, 339 "close_printdlg_thread"); 340 EXPECT_EQ(NULL, FindDialogWindow(dismisser.owner_process())); 341 close_printdlg_thread2.Start(); 342 PrintNowTab(); 343 close_printdlg_thread2.Join(); 344 EXPECT_EQ(0., CompareWithResult(test.result)) << test.result; 345 CleanupDumpDirectory(); 346 base::DelegateSimpleThread close_printdlg_thread3(&dismisser, 347 "close_printdlg_thread"); 348 EXPECT_EQ(NULL, FindDialogWindow(dismisser.owner_process())); 349 close_printdlg_thread3.Start(); 350 PrintNowTab(); 351 close_printdlg_thread3.Join(); 352 EXPECT_EQ(0., CompareWithResult(test.result)) << test.result; 353 CleanupDumpDirectory(); 354 base::DelegateSimpleThread close_printdlg_thread4(&dismisser, 355 "close_printdlg_thread"); 356 EXPECT_EQ(NULL, FindDialogWindow(dismisser.owner_process())); 357 close_printdlg_thread4.Start(); 358 PrintNowTab(); 359 close_printdlg_thread4.Join(); 360 EXPECT_EQ(0., CompareWithResult(test.result)) << test.result; 361 } 362 } 363 364 // Prints a popup and immediately closes it. Disabled because it crashes. 365 TEST_F(PrintingLayoutTest, DISABLED_Delayed) { 366 if (IsTestCaseDisabled()) 367 return; 368 369 net::TestServer test_server(net::TestServer::TYPE_HTTP, FilePath(kDocRoot)); 370 ASSERT_TRUE(test_server.Start()); 371 372 { 373 scoped_refptr<TabProxy> tab_proxy(GetActiveTab()); 374 ASSERT_TRUE(tab_proxy.get()); 375 bool is_timeout = true; 376 GURL url = test_server.GetURL("files/printing/popup_delayed_print.htm"); 377 EXPECT_EQ(AUTOMATION_MSG_NAVIGATION_SUCCESS, 378 tab_proxy->NavigateToURL(url)); 379 380 DismissTheWindow dismisser(base::GetProcId(process())); 381 base::DelegateSimpleThread close_printdlg_thread(&dismisser, 382 "close_printdlg_thread"); 383 close_printdlg_thread.Start(); 384 close_printdlg_thread.Join(); 385 386 // Force a navigation elsewhere to verify that it's fine with it. 387 url = test_server.GetURL("files/printing/test1.html"); 388 EXPECT_EQ(AUTOMATION_MSG_NAVIGATION_SUCCESS, 389 tab_proxy->NavigateToURL(url)); 390 } 391 CloseBrowserAndServer(); 392 393 EXPECT_EQ(0., CompareWithResult(L"popup_delayed_print")) 394 << L"popup_delayed_print"; 395 } 396 397 // Prints a popup and immediately closes it. http://crbug.com/7721 398 TEST_F(PrintingLayoutTest, DISABLED_IFrame) { 399 if (IsTestCaseDisabled()) 400 return; 401 402 net::TestServer test_server(net::TestServer::TYPE_HTTP, FilePath(kDocRoot)); 403 ASSERT_TRUE(test_server.Start()); 404 405 { 406 scoped_refptr<TabProxy> tab_proxy(GetActiveTab()); 407 ASSERT_TRUE(tab_proxy.get()); 408 GURL url = test_server.GetURL("files/printing/iframe.htm"); 409 EXPECT_EQ(AUTOMATION_MSG_NAVIGATION_SUCCESS, 410 tab_proxy->NavigateToURL(url)); 411 412 DismissTheWindow dismisser(base::GetProcId(process())); 413 base::DelegateSimpleThread close_printdlg_thread(&dismisser, 414 "close_printdlg_thread"); 415 close_printdlg_thread.Start(); 416 close_printdlg_thread.Join(); 417 418 // Force a navigation elsewhere to verify that it's fine with it. 419 url = test_server.GetURL("files/printing/test1.html"); 420 EXPECT_EQ(AUTOMATION_MSG_NAVIGATION_SUCCESS, 421 tab_proxy->NavigateToURL(url)); 422 } 423 CloseBrowserAndServer(); 424 425 EXPECT_EQ(0., CompareWithResult(L"iframe")) << L"iframe"; 426 } 427