/* * Copyright (C) 2012 Samsung Electronics Ltd. All Rights Reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "config.h" #include "WebViewTest.h" #include #include // Name of the test server application creating the webView object. static const char* gTestServerAppName = "InspectorTestServer"; // Max seconds to wait for the test server before inspecting it. static const int gMaxWaitForChild = 5; // The PID for the test server running, so we can kill it if needed. static GPid gChildProcessPid = 0; // Whether the child has replied and it's ready. static bool gChildIsReady = false; static void stopTestServer() { // Do nothing if there's no server running. if (!gChildProcessPid) return; g_spawn_close_pid(gChildProcessPid); kill(gChildProcessPid, SIGTERM); gChildProcessPid = 0; } static void sigAbortHandler(int sigNum) { // Just stop the test server if SIGABRT was received. stopTestServer(); } static gpointer testServerMonitorThreadFunc(gpointer) { // Wait for the specified timeout to happen. g_usleep(gMaxWaitForChild * G_USEC_PER_SEC); // Kill the child process if not ready yet. if (!gChildIsReady) stopTestServer(); g_thread_exit(0); return 0; } static void startTestServerMonitor() { gChildIsReady = false; g_thread_new("TestServerMonitor", testServerMonitorThreadFunc, 0); } static void startTestServer() { // Prepare argv[] for spawning the server process. GUniquePtr testServerPath(g_build_filename(WEBKIT_EXEC_PATH, "TestWebKitAPI", "WebKit2Gtk", gTestServerAppName, NULL)); // We install a handler to ensure that we kill the child process // if the parent dies because of whatever the reason is. signal(SIGABRT, sigAbortHandler); char* testServerArgv[2]; testServerArgv[0] = testServerPath.get(); testServerArgv[1] = 0; // Spawn the server, getting its stdout file descriptor to set a // communication channel, so we know when it's ready. int childStdout = 0; g_assert(g_spawn_async_with_pipes(0, testServerArgv, 0, static_cast(0), 0, 0, &gChildProcessPid, 0, &childStdout, 0, 0)); // Start monitoring the test server (in a separate thread) to // ensure we don't block on the child process more than a timeout. startTestServerMonitor(); char msg[2]; GIOChannel* ioChannel = g_io_channel_unix_new(childStdout); if (g_io_channel_read_chars(ioChannel, msg, 2, 0, 0) == G_IO_STATUS_NORMAL) { // Check whether the server sent a message saying it's ready // and store the result globally, so the monitor can see it. gChildIsReady = msg[0] == 'O' && msg[1] == 'K'; } g_io_channel_unref(ioChannel); close(childStdout); // The timeout was reached and the server is not ready yet, so // stop it inmediately, and let the unit tests fail. if (!gChildIsReady) stopTestServer(); } class InspectorServerTest: public WebViewTest { public: MAKE_GLIB_TEST_FIXTURE(InspectorServerTest); InspectorServerTest() : WebViewTest() { } bool getPageList() { loadHtml("\n", "http://127.0.0.1:2999/"); waitUntilTitleChanged(); if (!strcmp(webkit_web_view_get_title(m_webView), "OK")) return true; return false; } ~InspectorServerTest() { } }; // Test to get inspector server page list from the test server. // Should contain only one entry pointing to http://127.0.0.1:2999/webinspector/Main.html?page=1 static void testInspectorServerPageList(InspectorServerTest* test, gconstpointer) { GUniqueOutPtr error; test->showInWindowAndWaitUntilMapped(GTK_WINDOW_TOPLEVEL); g_assert(test->getPageList()); WebKitJavascriptResult* javascriptResult = test->runJavaScriptAndWaitUntilFinished("pages.length;", &error.outPtr()); g_assert(javascriptResult); g_assert(!error.get()); g_assert_cmpint(WebViewTest::javascriptResultToNumber(javascriptResult), ==, 1); javascriptResult = test->runJavaScriptAndWaitUntilFinished("pages[0].id;", &error.outPtr()); g_assert(javascriptResult); g_assert(!error.get()); int pageId = WebViewTest::javascriptResultToNumber(javascriptResult); GUniquePtr valueString; javascriptResult = test->runJavaScriptAndWaitUntilFinished("pages[0].url;", &error.outPtr()); g_assert(javascriptResult); g_assert(!error.get()); valueString.reset(WebViewTest::javascriptResultToCString(javascriptResult)); g_assert_cmpstr(valueString.get(), ==, "http://127.0.0.1:2999/"); javascriptResult = test->runJavaScriptAndWaitUntilFinished("pages[0].inspectorUrl;", &error.outPtr()); g_assert(javascriptResult); g_assert(!error.get()); valueString.reset(WebViewTest::javascriptResultToCString(javascriptResult)); String validInspectorURL = String("/Main.html?page=") + String::number(pageId); ASSERT_CMP_CSTRING(valueString.get(), ==, validInspectorURL.utf8()); } // Test sending a raw remote debugging message through our web socket server. // For this specific message see: http://code.google.com/chrome/devtools/docs/protocol/tot/runtime.html#command-evaluate static void testRemoteDebuggingMessage(InspectorServerTest* test, gconstpointer) { test->showInWindowAndWaitUntilMapped(GTK_WINDOW_TOPLEVEL); test->loadHtml("", "http://127.0.0.1:2999/"); test->waitUntilTitleChanged(); g_assert_cmpstr(webkit_web_view_get_title(test->m_webView), ==, "4"); } static void openRemoteDebuggingSession(InspectorServerTest* test, gconstpointer) { // To test the whole pipeline this exploits a behavior of the inspector front-end which won't provide the page address as title unless the // debugging session was established correctly through web socket. // In our case page URL should be http://127.0.0.1:2999/ // So this test case will fail if: // - The page list didn't return a valid inspector URL // - Or the front-end couldn't be loaded through the inspector HTTP server // - Or the web socket connection couldn't be established between the front-end and the page through the inspector server // Let's see if this test isn't raising too many false positives, in which case we should use a better predicate if available. test->showInWindowAndWaitUntilMapped(GTK_WINDOW_TOPLEVEL); g_assert(test->getPageList()); GUniqueOutPtr error; WebKitJavascriptResult* javascriptResult = test->runJavaScriptAndWaitUntilFinished("pages[0].inspectorUrl;", &error.outPtr()); g_assert(javascriptResult); g_assert(!error.get()); String resolvedURL = String("http://127.0.0.1:2999") + String::fromUTF8(WebViewTest::javascriptResultToCString(javascriptResult)); test->loadURI(resolvedURL.utf8().data()); test->waitUntilLoadFinished(); const char* title = webkit_web_view_get_title(test->m_webView); if (!title || !*title) test->waitUntilTitleChanged(); g_assert_cmpstr(webkit_web_view_get_title(test->m_webView), ==, "127.0.0.1"); } static void sendIncompleteRequest(InspectorServerTest* test, gconstpointer) { GUniqueOutPtr error; // Connect to the inspector server. GRefPtr client = adoptGRef(g_socket_client_new()); GRefPtr connection = adoptGRef(g_socket_client_connect_to_host(client.get(), "127.0.0.1", 2999, 0, &error.outPtr())); g_assert_no_error(error.get()); // Send incomplete request (missing blank line after headers) and check if inspector server // replies. The server should not reply to an incomplete request and the test should timeout // on read. GOutputStream* ostream = g_io_stream_get_output_stream(G_IO_STREAM(connection.get())); // Request missing blank line after headers. const gchar* incompleteRequest = "GET /devtools/page/1 HTTP/1.1\r\nHost: Localhost\r\n"; g_output_stream_write(ostream, incompleteRequest, strlen(incompleteRequest), 0, &error.outPtr()); g_assert_no_error(error.get()); GInputStream* istream = g_io_stream_get_input_stream(G_IO_STREAM(connection.get())); char response[16]; memset(response, 0, sizeof(response)); GRefPtr cancel = adoptGRef(g_cancellable_new()); g_input_stream_read_async(istream, response, sizeof(response) - 1, G_PRIORITY_DEFAULT, cancel.get(), 0, 0); // Give a chance for the server to reply. test->wait(1); g_cancellable_cancel(cancel.get()); // If we got any answer it means the server replied to an incomplete request, lets fail. g_assert(response[0] == '\0'); } void beforeAll() { // Overwrite WEBKIT_INSPECTOR_SERVER variable with default IP address but different port to avoid conflict with the test inspector server page. g_setenv("WEBKIT_INSPECTOR_SERVER", "127.0.0.1:2998", TRUE); startTestServer(); InspectorServerTest::add("WebKitWebInspectorServer", "test-page-list", testInspectorServerPageList); InspectorServerTest::add("WebKitWebInspectorServer", "test-remote-debugging-message", testRemoteDebuggingMessage); InspectorServerTest::add("WebKitWebInspectorServer", "test-open-debugging-session", openRemoteDebuggingSession); InspectorServerTest::add("WebKitWebInspectorServer", "test-incomplete-request", sendIncompleteRequest); } void afterAll() { stopTestServer(); }