diff options
author | Torne (Richard Coles) <torne@google.com> | 2013-08-23 16:39:15 +0100 |
---|---|---|
committer | Torne (Richard Coles) <torne@google.com> | 2013-08-23 16:39:15 +0100 |
commit | 3551c9c881056c480085172ff9840cab31610854 (patch) | |
tree | 23660320f5f4c279966609cf9da7491b96d10ca8 /chrome/test | |
parent | 4e9d9adbbb6cf287125ca44a0823791a570472f5 (diff) | |
download | chromium_org-3551c9c881056c480085172ff9840cab31610854.tar.gz |
Merge from Chromium at DEPS revision r219274
This commit was generated by merge_to_master.py.
Change-Id: Ibb7f41396cadf4071e89153e1913c986d126f65d
Diffstat (limited to 'chrome/test')
79 files changed, 2027 insertions, 1618 deletions
diff --git a/chrome/test/base/chrome_render_view_test.cc b/chrome/test/base/chrome_render_view_test.cc index b642275f89..545a4f5573 100644 --- a/chrome/test/base/chrome_render_view_test.cc +++ b/chrome/test/base/chrome_render_view_test.cc @@ -13,7 +13,9 @@ #include "chrome/renderer/extensions/event_bindings.h" #include "chrome/renderer/extensions/extension_custom_bindings.h" #include "chrome/renderer/spellchecker/spellcheck.h" +#include "components/autofill/content/renderer/autofill_agent.h" #include "components/autofill/content/renderer/password_autofill_agent.h" +#include "components/autofill/content/renderer/test_password_autofill_agent.h" #include "content/public/browser/native_web_keyboard_event.h" #include "content/public/common/renderer_preferences.h" #include "content/public/renderer/render_view.h" @@ -64,7 +66,7 @@ void ChromeRenderViewTest::SetUp() { // RenderView doesn't expose its PasswordAutofillAgent or AutofillAgent // objects, because it has no need to store them directly (they're stored as // RenderViewObserver*). So just create another set. - password_autofill_ = new PasswordAutofillAgent(view_); + password_autofill_ = new autofill::TestPasswordAutofillAgent(view_); autofill_agent_ = new AutofillAgent(view_, password_autofill_); } diff --git a/chrome/test/base/chrome_render_view_test.h b/chrome/test/base/chrome_render_view_test.h index 056f626291..a1002d1536 100644 --- a/chrome/test/base/chrome_render_view_test.h +++ b/chrome/test/base/chrome_render_view_test.h @@ -9,12 +9,11 @@ #include "chrome/renderer/chrome_content_renderer_client.h" #include "chrome/renderer/chrome_mock_render_thread.h" -#include "components/autofill/content/renderer/autofill_agent.h" #include "content/public/test/render_view_test.h" namespace autofill { class AutofillAgent; -class PasswordAutofillAgent; +class TestPasswordAutofillAgent; } namespace extensions { @@ -34,7 +33,7 @@ class ChromeRenderViewTest : public content::RenderViewTest { chrome::ChromeContentRendererClient chrome_content_renderer_client_; extensions::Dispatcher* extension_dispatcher_; - autofill::PasswordAutofillAgent* password_autofill_; + autofill::TestPasswordAutofillAgent* password_autofill_; autofill::AutofillAgent* autofill_agent_; // Naked pointer as ownership is with content::RenderViewTest::render_thread_. diff --git a/chrome/test/base/in_process_browser_test.h b/chrome/test/base/in_process_browser_test.h index 6786342132..8545846371 100644 --- a/chrome/test/base/in_process_browser_test.h +++ b/chrome/test/base/in_process_browser_test.h @@ -14,10 +14,6 @@ #include "content/public/test/browser_test_base.h" #include "testing/gtest/include/gtest/gtest.h" -#if defined(OS_CHROMEOS) -#include "chrome/browser/chromeos/cros/network_library.h" -#endif // defined(OS_CHROMEOS) - namespace base { #if defined(OS_MACOSX) namespace mac { @@ -217,10 +213,6 @@ class InProcessBrowserTest : public content::BrowserTestBase { // not ensure that Browsers are only created on the tested desktop). bool multi_desktop_test_; -#if defined(OS_CHROMEOS) - chromeos::ScopedStubNetworkLibraryEnabler stub_network_library_enabler_; -#endif // defined(OS_CHROMEOS) - #if defined(OS_MACOSX) base::mac::ScopedNSAutoreleasePool* autorelease_pool_; #endif // OS_MACOSX diff --git a/chrome/test/base/scoped_browser_locale.cc b/chrome/test/base/scoped_browser_locale.cc new file mode 100644 index 0000000000..c1538a1dbe --- /dev/null +++ b/chrome/test/base/scoped_browser_locale.cc @@ -0,0 +1,16 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/test/base/scoped_browser_locale.h" + +#include "chrome/browser/browser_process.h" + +ScopedBrowserLocale::ScopedBrowserLocale(const std::string& new_locale) + : old_locale_(g_browser_process->GetApplicationLocale()) { + g_browser_process->SetApplicationLocale(new_locale); +} + +ScopedBrowserLocale::~ScopedBrowserLocale() { + g_browser_process->SetApplicationLocale(old_locale_); +} diff --git a/chrome/test/base/scoped_browser_locale.h b/chrome/test/base/scoped_browser_locale.h new file mode 100644 index 0000000000..0f49b8cc4d --- /dev/null +++ b/chrome/test/base/scoped_browser_locale.h @@ -0,0 +1,24 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_TEST_BASE_SCOPED_BROWSER_LOCALE_H_ +#define CHROME_TEST_BASE_SCOPED_BROWSER_LOCALE_H_ + +#include <string> + +#include "base/basictypes.h" + +// Helper class to temporarily set the locale of the browser process. +class ScopedBrowserLocale { + public: + explicit ScopedBrowserLocale(const std::string& new_locale); + ~ScopedBrowserLocale(); + + private: + const std::string old_locale_; + + DISALLOW_COPY_AND_ASSIGN(ScopedBrowserLocale); +}; + +#endif // CHROME_TEST_BASE_SCOPED_BROWSER_LOCALE_H_ diff --git a/chrome/test/base/test_browser_window.h b/chrome/test/base/test_browser_window.h index 71470582ac..40cff07126 100644 --- a/chrome/test/base/test_browser_window.h +++ b/chrome/test/base/test_browser_window.h @@ -70,8 +70,7 @@ class TestBrowserWindow : public BrowserWindow { virtual LocationBar* GetLocationBar() const OVERRIDE; virtual void SetFocusToLocationBar(bool select_all) OVERRIDE {} virtual void UpdateReloadStopState(bool is_loading, bool force) OVERRIDE {} - virtual void UpdateToolbar(content::WebContents* contents, - bool should_restore_state) OVERRIDE {} + virtual void UpdateToolbar(content::WebContents* contents) OVERRIDE {} virtual void FocusToolbar() OVERRIDE {} virtual void FocusAppMenu() OVERRIDE {} virtual void FocusBookmarksToolbar() OVERRIDE {} @@ -107,7 +106,11 @@ class TestBrowserWindow : public BrowserWindow { #endif virtual bool IsDownloadShelfVisible() const OVERRIDE; virtual DownloadShelf* GetDownloadShelf() OVERRIDE; - virtual void ConfirmBrowserCloseWithPendingDownloads() OVERRIDE {} + virtual void ConfirmBrowserCloseWithPendingDownloads( + int download_count, + Browser::DownloadClosePreventionType dialog_type, + bool app_modal, + const base::Callback<void(bool)>& callback) OVERRIDE {} virtual void UserChangedTheme() OVERRIDE {} virtual int GetExtraRenderViewHeight() const OVERRIDE; virtual void WebContentsFocused(content::WebContents* contents) OVERRIDE {} diff --git a/chrome/test/base/testing_profile.cc b/chrome/test/base/testing_profile.cc index 318c14c349..f53c423f91 100644 --- a/chrome/test/base/testing_profile.cc +++ b/chrome/test/base/testing_profile.cc @@ -167,6 +167,7 @@ TestingProfile::TestingProfile() : start_time_(Time::Now()), testing_prefs_(NULL), incognito_(false), + force_incognito_(false), original_profile_(NULL), last_session_exited_cleanly_(true), browser_context_dependency_manager_( @@ -184,6 +185,7 @@ TestingProfile::TestingProfile(const base::FilePath& path) : start_time_(Time::Now()), testing_prefs_(NULL), incognito_(false), + force_incognito_(false), original_profile_(NULL), last_session_exited_cleanly_(true), profile_path_(path), @@ -200,6 +202,7 @@ TestingProfile::TestingProfile(const base::FilePath& path, : start_time_(Time::Now()), testing_prefs_(NULL), incognito_(false), + force_incognito_(false), original_profile_(NULL), last_session_exited_cleanly_(true), profile_path_(path), @@ -221,11 +224,14 @@ TestingProfile::TestingProfile( const base::FilePath& path, Delegate* delegate, scoped_refptr<ExtensionSpecialStoragePolicy> extension_policy, - scoped_ptr<PrefServiceSyncable> prefs) + scoped_ptr<PrefServiceSyncable> prefs, + bool incognito, + const TestingFactories& factories) : start_time_(Time::Now()), prefs_(prefs.release()), testing_prefs_(NULL), - incognito_(false), + incognito_(incognito), + force_incognito_(false), original_profile_(NULL), last_session_exited_cleanly_(true), extension_special_storage_policy_(extension_policy), @@ -241,6 +247,12 @@ TestingProfile::TestingProfile( profile_path_ = temp_dir_.path(); } + // Set any testing factories prior to initializing the services. + for (TestingFactories::const_iterator it = factories.begin(); + it != factories.end(); ++it) { + it->first->SetTestingFactory(this, it->second); + } + Init(); // If caller supplied a delegate, delay the FinishInit invocation until other // tasks have run. @@ -309,7 +321,11 @@ void TestingProfile::Init() { extensions::ExtensionSystemFactory::GetInstance()->SetTestingFactory( this, extensions::TestExtensionSystem::Build); - browser_context_dependency_manager_->CreateBrowserContextServices(this, true); + // If there is no separate original profile specified for this profile, then + // force preferences to be registered - this allows tests to create a + // standalone incognito profile while still having prefs registered. + browser_context_dependency_manager_->CreateBrowserContextServicesForTest( + this, !original_profile_); #if defined(ENABLE_NOTIFICATIONS) // Install profile keyed service factory hooks for dummy/test services @@ -330,6 +346,9 @@ void TestingProfile::FinishInit() { } TestingProfile::~TestingProfile() { + // Revert to non-incognito mode before shutdown. + force_incognito_ = false; + // Any objects holding live URLFetchers should be deleted before teardown. TemplateURLFetcherFactory::ShutdownForProfile(this); @@ -497,8 +516,6 @@ void TestingProfile::BlockUntilTopSitesLoaded() { content::WindowedNotificationObserver top_sites_loaded_observer( chrome::NOTIFICATION_TOP_SITES_LOADED, content::NotificationService::AllSources()); - if (!HistoryServiceFactory::GetForProfile(this, Profile::EXPLICIT_ACCESS)) - GetTopSites()->HistoryLoaded(); top_sites_loaded_observer.Wait(); } @@ -526,18 +543,22 @@ std::string TestingProfile::GetProfileName() { } bool TestingProfile::IsOffTheRecord() const { - return incognito_; + return force_incognito_ || incognito_; } -void TestingProfile::SetOffTheRecordProfile(Profile* profile) { - incognito_profile_.reset(profile); +void TestingProfile::SetOffTheRecordProfile(scoped_ptr<Profile> profile) { + DCHECK(!IsOffTheRecord()); + incognito_profile_ = profile.Pass(); } void TestingProfile::SetOriginalProfile(Profile* profile) { + DCHECK(IsOffTheRecord()); original_profile_ = profile; } Profile* TestingProfile::GetOffTheRecordProfile() { + if (IsOffTheRecord()) + return this; return incognito_profile_.get(); } @@ -552,8 +573,7 @@ Profile* TestingProfile::GetOriginalProfile() { } bool TestingProfile::IsManaged() { - return GetPrefs()->GetBoolean(prefs::kProfileIsManaged) || - !GetPrefs()->GetString(prefs::kManagedUserId).empty(); + return !GetPrefs()->GetString(prefs::kManagedUserId).empty(); } ExtensionService* TestingProfile::GetExtensionService() { @@ -797,7 +817,8 @@ Profile::ExitType TestingProfile::GetLastSessionExitType() { TestingProfile::Builder::Builder() : build_called_(false), - delegate_(NULL) { + delegate_(NULL), + incognito_(false) { } TestingProfile::Builder::~Builder() { @@ -821,6 +842,16 @@ void TestingProfile::Builder::SetPrefService( pref_service_ = prefs.Pass(); } +void TestingProfile::Builder::SetIncognito() { + incognito_ = true; +} + +void TestingProfile::Builder::AddTestingFactory( + BrowserContextKeyedServiceFactory* service_factory, + BrowserContextKeyedServiceFactory::FactoryFunction callback) { + testing_factories_.push_back(std::make_pair(service_factory, callback)); +} + scoped_ptr<TestingProfile> TestingProfile::Builder::Build() { DCHECK(!build_called_); build_called_ = true; @@ -828,5 +859,7 @@ scoped_ptr<TestingProfile> TestingProfile::Builder::Build() { path_, delegate_, extension_policy_, - pref_service_.Pass())); + pref_service_.Pass(), + incognito_, + testing_factories_)); } diff --git a/chrome/test/base/testing_profile.h b/chrome/test/base/testing_profile.h index 8e63e5a71b..03c95d463a 100644 --- a/chrome/test/base/testing_profile.h +++ b/chrome/test/base/testing_profile.h @@ -11,6 +11,7 @@ #include "base/memory/ref_counted.h" #include "base/memory/scoped_ptr.h" #include "chrome/browser/profiles/profile.h" +#include "components/browser_context_keyed_service/browser_context_keyed_service_factory.h" namespace content { class MockResourceContext; @@ -59,6 +60,10 @@ class TestingProfile : public Profile { // Default constructor that cannot be used with multi-profiles. TestingProfile(); + typedef std::vector<std::pair< + BrowserContextKeyedServiceFactory*, + BrowserContextKeyedServiceFactory::FactoryFunction> > TestingFactories; + // Helper class for building an instance of TestingProfile (allows injecting // mocks for various services prior to profile initialization). // TODO(atwilson): Remove non-default constructors and various setters in @@ -68,13 +73,19 @@ class TestingProfile : public Profile { Builder(); ~Builder(); - // Sets a Delegate to be called back when the Profile is fully initialized. - // This causes the final initialization to be performed via a task so the - // caller must run a MessageLoop. Caller maintains ownership of the Delegate + // Sets a Delegate to be called back during profile init. This causes the + // final initialization to be performed via a task so the caller must run + // a MessageLoop. Caller maintains ownership of the Delegate // and must manage its lifetime so it continues to exist until profile // initialization is complete. void SetDelegate(Delegate* delegate); + // Adds a testing factory to the TestingProfile. These testing factories + // are applied before the ProfileKeyedServices are created. + void AddTestingFactory( + BrowserContextKeyedServiceFactory* service_factory, + BrowserContextKeyedServiceFactory::FactoryFunction callback); + // Sets the ExtensionSpecialStoragePolicy to be returned by // GetExtensionSpecialStoragePolicy(). void SetExtensionSpecialStoragePolicy( @@ -86,6 +97,9 @@ class TestingProfile : public Profile { // Sets the PrefService to be used by this profile. void SetPrefService(scoped_ptr<PrefServiceSyncable> prefs); + // Makes the Profile being built an incognito profile. + void SetIncognito(); + // Creates the TestingProfile using previously-set settings. scoped_ptr<TestingProfile> Build(); @@ -98,6 +112,8 @@ class TestingProfile : public Profile { scoped_refptr<ExtensionSpecialStoragePolicy> extension_policy_; base::FilePath path_; Delegate* delegate_; + bool incognito_; + TestingFactories testing_factories_; DISALLOW_COPY_AND_ASSIGN(Builder); }; @@ -120,7 +136,9 @@ class TestingProfile : public Profile { TestingProfile(const base::FilePath& path, Delegate* delegate, scoped_refptr<ExtensionSpecialStoragePolicy> extension_policy, - scoped_ptr<PrefServiceSyncable> prefs); + scoped_ptr<PrefServiceSyncable> prefs, + bool incognito, + const TestingFactories& factories); virtual ~TestingProfile(); @@ -186,9 +204,26 @@ class TestingProfile : public Profile { virtual TestingProfile* AsTestingProfile() OVERRIDE; virtual std::string GetProfileName() OVERRIDE; - void set_incognito(bool incognito) { incognito_ = incognito; } + + // DEPRECATED, because it's fragile to change a profile from non-incognito + // to incognito after the ProfileKeyedServices have been created (some + // ProfileKeyedServices either should not exist in incognito mode, or will + // crash when they try to get references to other services they depend on, + // but do not exist in incognito mode). + // TODO(atwilson): Remove this API (http://crbug.com/277296). + // + // Changes a profile's to/from incognito mode temporarily - profile will be + // returned to non-incognito before destruction to allow services to + // properly shutdown. This is only supported for legacy tests - new tests + // should create a true incognito profile using Builder::SetIncognito() or + // by using the TestingProfile constructor that allows setting the incognito + // flag. + void ForceIncognito(bool force_incognito) { + force_incognito_ = force_incognito; + } + // Assumes ownership. - virtual void SetOffTheRecordProfile(Profile* profile); + virtual void SetOffTheRecordProfile(scoped_ptr<Profile> profile); virtual void SetOriginalProfile(Profile* profile); virtual Profile* GetOffTheRecordProfile() OVERRIDE; virtual void DestroyOffTheRecordProfile() OVERRIDE {} @@ -306,6 +341,7 @@ class TestingProfile : public Profile { std::wstring id_; bool incognito_; + bool force_incognito_; scoped_ptr<Profile> incognito_profile_; Profile* original_profile_; diff --git a/chrome/test/base/testing_profile_manager.cc b/chrome/test/base/testing_profile_manager.cc index e6f4748a30..644760cc22 100644 --- a/chrome/test/base/testing_profile_manager.cc +++ b/chrome/test/base/testing_profile_manager.cc @@ -16,10 +16,10 @@ namespace testing { -class ProfileManager : public ::ProfileManager { +class ProfileManager : public ::ProfileManagerWithoutInit { public: explicit ProfileManager(const base::FilePath& user_data_dir) - : ::ProfileManager(user_data_dir) {} + : ::ProfileManagerWithoutInit(user_data_dir) {} protected: virtual Profile* CreateProfileHelper( @@ -57,11 +57,10 @@ TestingProfile* TestingProfileManager::CreateTestingProfile( profile_path = profile_path.AppendASCII(profile_name); // Create the profile and register it. - TestingProfile* profile = new TestingProfile( - profile_path, - NULL, - scoped_refptr<ExtensionSpecialStoragePolicy>(), - prefs.Pass()); + TestingProfile::Builder builder; + builder.SetPath(profile_path); + builder.SetPrefService(prefs.Pass()); + TestingProfile* profile = builder.Build().release(); profile_manager_->AddProfile(profile); // Takes ownership. // Update the user metadata. diff --git a/chrome/test/base/view_event_test_base.cc b/chrome/test/base/view_event_test_base.cc index 4654a7e288..c37fe93dd2 100644 --- a/chrome/test/base/view_event_test_base.cc +++ b/chrome/test/base/view_event_test_base.cc @@ -36,6 +36,7 @@ #if defined(OS_CHROMEOS) #include "chromeos/audio/cras_audio_handler.h" +#include "chromeos/network/network_handler.h" #endif namespace { @@ -116,6 +117,7 @@ void ViewEventTestBase::SetUp() { message_center::MessageCenter::Initialize(); #if defined(OS_CHROMEOS) chromeos::CrasAudioHandler::InitializeForTesting(); + chromeos::NetworkHandler::Initialize(); #endif // OS_CHROMEOS ash::test::TestShellDelegate* shell_delegate = new ash::test::TestShellDelegate(); @@ -150,6 +152,7 @@ void ViewEventTestBase::TearDown() { #else ash::Shell::DeleteInstance(); #if defined(OS_CHROMEOS) + chromeos::NetworkHandler::Shutdown(); chromeos::CrasAudioHandler::Shutdown(); #endif // Ash Shell can't just live on its own without a browser process, we need to diff --git a/chrome/test/chromedriver/capabilities.cc b/chrome/test/chromedriver/capabilities.cc index f5f1a6b7ad..b052f0f7f7 100644 --- a/chrome/test/chromedriver/capabilities.cc +++ b/chrome/test/chromedriver/capabilities.cc @@ -182,6 +182,22 @@ Status ParseProxy(const base::Value& option, Capabilities* capabilities) { return Status(kOk); } +Status ParseExcludeSwitches(const base::Value& option, + Capabilities* capabilities) { + const base::ListValue* switches = NULL; + if (!option.GetAsList(&switches)) + return Status(kUnknownError, "'excludeSwitches' must be a list"); + for (size_t i = 0; i < switches->GetSize(); ++i) { + std::string switch_name; + if (!switches->GetString(i, &switch_name)) { + return Status(kUnknownError, + "each switch to be removed must be a string"); + } + capabilities->exclude_switches.insert(switch_name); + } + return Status(kOk); +} + Status ParseDesktopChromeCapabilities( Log* log, const base::Value& capability, @@ -201,6 +217,7 @@ Status ParseDesktopChromeCapabilities( parser_map["prefs"] = base::Bind(&ParsePrefs); parser_map["localState"] = base::Bind(&ParseLocalState); parser_map["extensions"] = base::Bind(&ParseExtensions); + parser_map["excludeSwitches"] = base::Bind(&ParseExcludeSwitches); for (base::DictionaryValue::Iterator it(*chrome_options); !it.IsAtEnd(); it.Advance()) { diff --git a/chrome/test/chromedriver/capabilities.h b/chrome/test/chromedriver/capabilities.h index 2ef9bb0613..c5e243ea76 100644 --- a/chrome/test/chromedriver/capabilities.h +++ b/chrome/test/chromedriver/capabilities.h @@ -5,6 +5,7 @@ #ifndef CHROME_TEST_CHROMEDRIVER_CAPABILITIES_H_ #define CHROME_TEST_CHROMEDRIVER_CAPABILITIES_H_ +#include <set> #include <string> #include <vector> @@ -45,6 +46,10 @@ struct Capabilities { scoped_ptr<base::DictionaryValue> local_state; std::vector<std::string> extensions; scoped_ptr<base::DictionaryValue> logging_prefs; + + // Set of switches which should be removed from default list when launching + // Chrome. + std::set<std::string> exclude_switches; }; #endif // CHROME_TEST_CHROMEDRIVER_CAPABILITIES_H_ diff --git a/chrome/test/chromedriver/capabilities_unittest.cc b/chrome/test/chromedriver/capabilities_unittest.cc index c5294ee518..759646885e 100644 --- a/chrome/test/chromedriver/capabilities_unittest.cc +++ b/chrome/test/chromedriver/capabilities_unittest.cc @@ -317,3 +317,19 @@ TEST(ParseCapabilities, LoggingPrefsNotDict) { Status status = capabilities.Parse(caps, &log); ASSERT_FALSE(status.IsOk()); } + +TEST(ParseCapabilities, ExcludeSwitches) { + Capabilities capabilities; + base::ListValue exclude_switches; + exclude_switches.AppendString("switch1"); + exclude_switches.AppendString("switch2"); + base::DictionaryValue caps; + caps.Set("chromeOptions.excludeSwitches", exclude_switches.DeepCopy()); + Logger log(Log::kError); + Status status = capabilities.Parse(caps, &log); + ASSERT_TRUE(status.IsOk()); + ASSERT_EQ(2u, capabilities.exclude_switches.size()); + const std::set<std::string>& switches = capabilities.exclude_switches; + ASSERT_TRUE(switches.find("switch1") != switches.end()); + ASSERT_TRUE(switches.find("switch2") != switches.end()); +} diff --git a/chrome/test/chromedriver/chrome/adb_impl.cc b/chrome/test/chromedriver/chrome/adb_impl.cc index 363790c700..5432d18f1e 100644 --- a/chrome/test/chromedriver/chrome/adb_impl.cc +++ b/chrome/test/chromedriver/chrome/adb_impl.cc @@ -62,9 +62,10 @@ class ResponseBuffer : public base::RefCountedThreadSafe<ResponseBuffer> { }; void ExecuteCommandOnIOThread( - const std::string& command, scoped_refptr<ResponseBuffer> response_buffer) { + const std::string& command, scoped_refptr<ResponseBuffer> response_buffer, + int port) { CHECK(base::MessageLoop::current()->IsType(base::MessageLoop::TYPE_IO)); - AdbClientSocket::AdbQuery(5037, command, + AdbClientSocket::AdbQuery(port, command, base::Bind(&ResponseBuffer::OnResponse, response_buffer)); } @@ -72,8 +73,8 @@ void ExecuteCommandOnIOThread( AdbImpl::AdbImpl( const scoped_refptr<base::SingleThreadTaskRunner>& io_task_runner, - Log* log) - : io_task_runner_(io_task_runner), log_(log) { + Log* log, int port) + : io_task_runner_(io_task_runner), log_(log), port_(port) { CHECK(io_task_runner_.get()); } @@ -165,8 +166,7 @@ Status AdbImpl::Launch( std::string response; Status status = ExecuteHostShellCommand( device_serial, - "am start -W -n " + package + "/" + activity + - " -d \"data:text/html;charset=utf-8,\"", + "am start -W -n " + package + "/" + activity + " -d about:blank", &response); if (!status.IsOk()) return status; @@ -221,7 +221,7 @@ Status AdbImpl::ExecuteCommand( log_->AddEntry(Log::kDebug, "Sending adb command: " + command); io_task_runner_->PostTask( FROM_HERE, - base::Bind(&ExecuteCommandOnIOThread, command, response_buffer)); + base::Bind(&ExecuteCommandOnIOThread, command, response_buffer, port_)); Status status = response_buffer->GetResponse( response, base::TimeDelta::FromSeconds(30)); if (status.IsOk()) diff --git a/chrome/test/chromedriver/chrome/adb_impl.h b/chrome/test/chromedriver/chrome/adb_impl.h index 155af1cb2f..80ce305ed9 100644 --- a/chrome/test/chromedriver/chrome/adb_impl.h +++ b/chrome/test/chromedriver/chrome/adb_impl.h @@ -23,7 +23,7 @@ class AdbImpl : public Adb { public: explicit AdbImpl( const scoped_refptr<base::SingleThreadTaskRunner>& io_message_loop_proxy, - Log* log); + Log* log, int port); virtual ~AdbImpl(); // Overridden from Adb: @@ -61,6 +61,7 @@ class AdbImpl : public Adb { scoped_refptr<base::SingleThreadTaskRunner> io_task_runner_; Log* log_; + int port_; }; #endif // CHROME_TEST_CHROMEDRIVER_CHROME_ADB_IMPL_H_ diff --git a/chrome/test/chromedriver/chrome/local_state.txt b/chrome/test/chromedriver/chrome/local_state.txt index 75b3067d88..a0d20c1bbb 100644 --- a/chrome/test/chromedriver/chrome/local_state.txt +++ b/chrome/test/chromedriver/chrome/local_state.txt @@ -1,4 +1,7 @@ { + "background_mode": { + "enabled": false + }, "ssl": { "rev_checking": { "enabled": false diff --git a/chrome/test/chromedriver/chrome/preferences.txt b/chrome/test/chromedriver/chrome/preferences.txt index 32f4cb12cd..1ad1f7a15c 100644 --- a/chrome/test/chromedriver/chrome/preferences.txt +++ b/chrome/test/chromedriver/chrome/preferences.txt @@ -2,6 +2,9 @@ "alternate_error_pages": { "enabled": false }, + "autofill": { + "enabled": false + }, "browser": { "check_default_browser": false }, @@ -33,7 +36,8 @@ "notifications": 1, "popups": 1, "ppapi-broker": 1 - } + }, + "password_manager_enabled": false }, "safebrowsing": { "enabled": false diff --git a/chrome/test/chromedriver/chrome_launcher.cc b/chrome/test/chromedriver/chrome_launcher.cc index d151d5d682..6987a2644c 100644 --- a/chrome/test/chromedriver/chrome_launcher.cc +++ b/chrome/test/chromedriver/chrome_launcher.cc @@ -64,6 +64,16 @@ Status UnpackAutomationExtension(const base::FilePath& temp_dir, return Status(kOk); } +void AddSwitches(CommandLine* command, + const char* switches[], + size_t switch_count, + const std::set<std::string>& exclude_switches) { + for (size_t i = 0; i < switch_count; ++i) { + if (exclude_switches.find(switches[i]) == exclude_switches.end()) + command->AppendSwitch(switches[i]); + } +} + Status PrepareCommandLine(int port, const Capabilities& capabilities, CommandLine* prepared_command, @@ -81,13 +91,35 @@ Status PrepareCommandLine(int port, program.value().c_str())); } - command.AppendSwitchASCII("remote-debugging-port", base::IntToString(port)); - command.AppendSwitch("no-first-run"); + const char* excludable_switches[] = { + "disable-hang-monitor", + "disable-prompt-on-repost", + "full-memory-crash-report", + "no-first-run", + "disable-background-networking", + // TODO(chrisgao): Add "disable-sync" when chrome 30- is not supported. + // For chrome 30-, it leads to crash when opening chrome://settings. + "disable-web-resources", + "safebrowsing-disable-auto-update", + "safebrowsing-disable-download-protection", + "disable-client-side-phishing-detection", + "disable-component-update", + "disable-default-apps", + }; + + AddSwitches(&command, excludable_switches, arraysize(excludable_switches), + capabilities.exclude_switches); + AddSwitches(&command, kCommonSwitches, arraysize(kCommonSwitches), + capabilities.exclude_switches); + command.AppendSwitch("enable-logging"); command.AppendSwitchASCII("logging-level", "1"); - command.AppendArg("data:text/html;charset=utf-8,"); + command.AppendSwitchASCII("password-store", "basic"); + command.AppendSwitch("use-mock-keychain"); + command.AppendSwitchASCII("remote-debugging-port", base::IntToString(port)); if (!command.HasSwitch("user-data-dir")) { + command.AppendArg("about:blank"); if (!user_data_dir->CreateUniqueTempDir()) return Status(kUnknownError, "cannot create temp dir for user data dir"); command.AppendSwitchPath("user-data-dir", user_data_dir->path()); @@ -156,8 +188,6 @@ Status LaunchDesktopChrome( if (status.IsError()) return status; - for (size_t i = 0; i < arraysize(kCommonSwitches); i++) - command.AppendSwitch(kCommonSwitches[i]); base::LaunchOptions options; #if !defined(OS_WIN) diff --git a/chrome/test/chromedriver/client/chromedriver.py b/chrome/test/chromedriver/client/chromedriver.py index a0fe44a31c..ac5c478a45 100644 --- a/chrome/test/chromedriver/client/chromedriver.py +++ b/chrome/test/chromedriver/client/chromedriver.py @@ -217,6 +217,15 @@ class ChromeDriver(object): def MouseDoubleClick(self, button=0): self.ExecuteCommand(Command.MOUSE_DOUBLE_CLICK, {'button': button}) + def TouchDown(self, x, y): + self.ExecuteCommand(Command.TOUCH_DOWN, {'x': x, 'y': y}) + + def TouchUp(self, x, y): + self.ExecuteCommand(Command.TOUCH_UP, {'x': x, 'y': y}) + + def TouchMove(self, x, y): + self.ExecuteCommand(Command.TOUCH_MOVE, {'x': x, 'y': y}) + def GetCookies(self): return self.ExecuteCommand(Command.GET_COOKIES) diff --git a/chrome/test/chromedriver/client/webelement.py b/chrome/test/chromedriver/client/webelement.py index a5a706aa35..b3e6a641f5 100644 --- a/chrome/test/chromedriver/client/webelement.py +++ b/chrome/test/chromedriver/client/webelement.py @@ -45,3 +45,6 @@ class WebElement(object): for i in range(len(value)): typing.append(value[i]) self._Execute(Command.SEND_KEYS_TO_ELEMENT, {'value': typing}) + + def GetLocation(self): + return self._Execute(Command.GET_ELEMENT_LOCATION) diff --git a/chrome/test/chromedriver/element_commands.cc b/chrome/test/chromedriver/element_commands.cc index 008c08644e..81e83a36dd 100644 --- a/chrome/test/chromedriver/element_commands.cc +++ b/chrome/test/chromedriver/element_commands.cc @@ -33,6 +33,7 @@ Status SendKeysToElement( const std::string& element_id, const ListValue* key_list) { bool is_displayed = false; + bool is_focused = false; base::Time start_time = base::Time::Now(); while (true) { Status status = IsElementDisplayed( @@ -41,25 +42,35 @@ Status SendKeysToElement( return status; if (is_displayed) break; + status = IsElementFocused(session, web_view, element_id, &is_focused); + if (status.IsError()) + return status; + if (is_focused) + break; if ((base::Time::Now() - start_time).InMilliseconds() >= session->implicit_wait) { return Status(kElementNotVisible); } base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(100)); } + bool is_enabled = false; Status status = IsElementEnabled(session, web_view, element_id, &is_enabled); if (status.IsError()) return status; if (!is_enabled) return Status(kInvalidElementState); - base::ListValue args; - args.Append(CreateElement(element_id)); - scoped_ptr<base::Value> result; - status = web_view->CallFunction( - session->GetCurrentFrameId(), kFocusScript, args, &result); - if (status.IsError()) - return status; + + if (!is_focused) { + base::ListValue args; + args.Append(CreateElement(element_id)); + scoped_ptr<base::Value> result; + status = web_view->CallFunction( + session->GetCurrentFrameId(), kFocusScript, args, &result); + if (status.IsError()) + return status; + } + return SendKeysOnWindow(web_view, key_list, true, &session->sticky_modifiers); } diff --git a/chrome/test/chromedriver/element_util.cc b/chrome/test/chromedriver/element_util.cc index ccf5409db3..6c4bca00c2 100644 --- a/chrome/test/chromedriver/element_util.cc +++ b/chrome/test/chromedriver/element_util.cc @@ -286,6 +286,32 @@ Status FindElement( return Status(kUnknownError); } +Status GetActiveElement( + Session* session, + WebView* web_view, + scoped_ptr<base::Value>* value) { + base::ListValue args; + return web_view->CallFunction( + session->GetCurrentFrameId(), + "function() { return document.activeElement || document.body }", + args, + value); +} + +Status IsElementFocused( + Session* session, + WebView* web_view, + const std::string& element_id, + bool* is_focused) { + scoped_ptr<base::Value> result; + Status status = GetActiveElement(session, web_view, &result); + if (status.IsError()) + return status; + scoped_ptr<base::Value> element_dict(CreateElement(element_id)); + *is_focused = result->Equals(element_dict.get()); + return Status(kOk); +} + Status GetElementAttribute( Session* session, WebView* web_view, diff --git a/chrome/test/chromedriver/element_util.h b/chrome/test/chromedriver/element_util.h index 86bb2d1568..0fbd766179 100644 --- a/chrome/test/chromedriver/element_util.h +++ b/chrome/test/chromedriver/element_util.h @@ -34,6 +34,17 @@ Status FindElement( const base::DictionaryValue& params, scoped_ptr<base::Value>* value); +Status GetActiveElement( + Session* session, + WebView* web_view, + scoped_ptr<base::Value>* value); + +Status IsElementFocused( + Session* session, + WebView* web_view, + const std::string& element_id, + bool* is_focused); + Status GetElementAttribute( Session* session, WebView* web_view, diff --git a/chrome/test/chromedriver/run_buildbot_steps.py b/chrome/test/chromedriver/run_buildbot_steps.py index af4a10b811..2b1765dbb3 100755 --- a/chrome/test/chromedriver/run_buildbot_steps.py +++ b/chrome/test/chromedriver/run_buildbot_steps.py @@ -178,18 +178,19 @@ def WaitForLatestSnapshot(revision): def main(): parser = optparse.OptionParser() parser.add_option( - '', '--android-package', - help='Application package name, if running tests on Android.') + '', '--android-packages', + help='Comma separated list of application package names, ' + 'if running tests on Android.') parser.add_option( '-r', '--revision', type='string', default=None, help='Chromium revision') options, _ = parser.parse_args() - if not options.android_package: + if not options.android_packages: KillChromes() CleanTmpDir() - if options.android_package: + if options.android_packages: Download() else: if not options.revision: @@ -204,12 +205,12 @@ def main(): sys.executable, os.path.join(_THIS_DIR, 'test', 'run_all_tests.py'), ] - if options.android_package: - cmd.append('--android-package=' + options.android_package) + if options.android_packages: + cmd.append('--android-packages=' + options.android_packages) passed = (util.RunCommand(cmd) == 0) - if not options.android_package and passed: + if not options.android_packages and passed: MaybeRelease(options.revision) diff --git a/chrome/test/chromedriver/server/chromedriver_server.cc b/chrome/test/chromedriver/server/chromedriver_server.cc index c036814086..6c402474d8 100644 --- a/chrome/test/chromedriver/server/chromedriver_server.cc +++ b/chrome/test/chromedriver/server/chromedriver_server.cc @@ -139,7 +139,8 @@ void StartServerOnIOThread(int port, lazy_tls_server.Pointer()->Set(temp_server.release()); } -void RunServer(Log::Level log_level, int port, const std::string& url_base) { +void RunServer(Log::Level log_level, int port, const std::string& url_base, + int adb_port) { base::Thread io_thread("ChromeDriver IO"); CHECK(io_thread.StartWithOptions( base::Thread::Options(base::MessageLoop::TYPE_IO, 0))); @@ -150,7 +151,8 @@ void RunServer(Log::Level log_level, int port, const std::string& url_base) { HttpHandler handler(cmd_run_loop.QuitClosure(), io_thread.message_loop_proxy(), &log, - url_base); + url_base, + adb_port); HttpRequestHandlerFunc handle_request_func = base::Bind(&HandleRequestOnCmdThread, &handler); @@ -181,6 +183,7 @@ int main(int argc, char *argv[]) { // Parse command line flags. int port = 9515; + int adb_port = 5037; std::string url_base; base::FilePath log_path; Log::Level log_level = Log::kError; @@ -188,6 +191,7 @@ int main(int argc, char *argv[]) { std::string options; const char* kOptionAndDescriptions[] = { "port=PORT", "port to listen on", + "adb-port=PORT", "adb server port", "log-path=FILE", "write server log to file instead of stderr, " "increases log level to INFO", "verbose", "log verbosely", @@ -208,6 +212,13 @@ int main(int argc, char *argv[]) { return 1; } } + if (cmd_line->HasSwitch("adb-port")) { + if (!base::StringToInt(cmd_line->GetSwitchValueASCII("adb-port"), + &adb_port)) { + printf("Invalid adb-port. Exiting...\n"); + return 1; + } + } if (cmd_line->HasSwitch("url-base")) url_base = cmd_line->GetSwitchValueASCII("url-base"); if (url_base.empty() || url_base[0] != '/') @@ -255,6 +266,6 @@ int main(int argc, char *argv[]) { if (!cmd_line->HasSwitch("verbose")) logging::SetMinLogLevel(logging::LOG_FATAL); - RunServer(log_level, port, url_base); + RunServer(log_level, port, url_base, adb_port); return 0; } diff --git a/chrome/test/chromedriver/server/http_handler.cc b/chrome/test/chromedriver/server/http_handler.cc index e619ce7f68..1211bbcc26 100644 --- a/chrome/test/chromedriver/server/http_handler.cc +++ b/chrome/test/chromedriver/server/http_handler.cc @@ -67,7 +67,8 @@ HttpHandler::HttpHandler( const base::Closure& quit_func, const scoped_refptr<base::SingleThreadTaskRunner> io_task_runner, Log* log, - const std::string& url_base) + const std::string& url_base, + int adb_port) : quit_func_(quit_func), log_(log), url_base_(url_base), @@ -78,7 +79,7 @@ HttpHandler::HttpHandler( #endif context_getter_ = new URLRequestContextGetter(io_task_runner); socket_factory_ = CreateSyncWebSocketFactory(context_getter_.get()); - adb_.reset(new AdbImpl(io_task_runner, log_)); + adb_.reset(new AdbImpl(io_task_runner, log_, adb_port)); device_manager_.reset(new DeviceManager(adb_.get())); CommandMapping commands[] = { @@ -395,13 +396,13 @@ HttpHandler::HttpHandler( WrapToCommand(base::Bind(&ExecuteTouchSingleTap))), CommandMapping(kPost, "session/:sessionId/touch/down", - base::Bind(&UnimplementedCommand)), + WrapToCommand(base::Bind(&ExecuteTouchDown))), CommandMapping(kPost, "session/:sessionId/touch/up", - base::Bind(&UnimplementedCommand)), + WrapToCommand(base::Bind(&ExecuteTouchUp))), CommandMapping(kPost, "session/:sessionId/touch/move", - base::Bind(&UnimplementedCommand)), + WrapToCommand(base::Bind(&ExecuteTouchMove))), CommandMapping(kPost, "session/:sessionId/touch/scroll", base::Bind(&UnimplementedCommand)), diff --git a/chrome/test/chromedriver/server/http_handler.h b/chrome/test/chromedriver/server/http_handler.h index 733255489e..13298311dd 100644 --- a/chrome/test/chromedriver/server/http_handler.h +++ b/chrome/test/chromedriver/server/http_handler.h @@ -64,7 +64,8 @@ class HttpHandler { HttpHandler(const base::Closure& quit_func, const scoped_refptr<base::SingleThreadTaskRunner> io_task_runner, Log* log, - const std::string& url_base); + const std::string& url_base, + int adb_port); ~HttpHandler(); void Handle(const net::HttpServerRequestInfo& request, diff --git a/chrome/test/chromedriver/test/run_all_tests.py b/chrome/test/chromedriver/test/run_all_tests.py index 7236c86b83..d8d6852895 100755 --- a/chrome/test/chromedriver/test/run_all_tests.py +++ b/chrome/test/chromedriver/test/run_all_tests.py @@ -65,7 +65,7 @@ def RunPythonTests(chromedriver, ref_chromedriver, chrome_version_name=None, android_package=None): version_info = '' if chrome_version_name: - version_info = '(v%s)' % chrome_version_name + version_info = '(%s)' % chrome_version_name util.MarkBuildStepStart('python_tests%s' % version_info) code = util.RunCommand( _GenerateTestCommand('run_py_tests.py', @@ -83,7 +83,7 @@ def RunJavaTests(chromedriver, chrome=None, chrome_version=None, chrome_version_name=None, android_package=None): version_info = '' if chrome_version_name: - version_info = '(v%s)' % chrome_version_name + version_info = '(%s)' % chrome_version_name util.MarkBuildStepStart('java_tests%s' % version_info) code = util.RunCommand( _GenerateTestCommand('run_java_tests.py', @@ -113,8 +113,9 @@ def DownloadChrome(version_name, revision, download_site): def main(): parser = optparse.OptionParser() parser.add_option( - '', '--android-package', - help='Application package name, if running tests on Android.') + '', '--android-packages', + help='Comma separated list of application package names, ' + 'if running tests on Android.') # Option 'chrome-version' is for desktop only. parser.add_option( '', '--chrome-version', @@ -130,7 +131,7 @@ def main(): server_name = 'chromedriver2_server' + exe_postfix required_build_outputs = [server_name] - if not options.android_package: + if not options.android_packages: required_build_outputs += [cpp_tests_name] build_dir = chrome_paths.GetBuildDir(required_build_outputs) print 'Using build outputs from', build_dir @@ -153,15 +154,20 @@ def main(): # For Windows bots: add ant, java(jre) and the like to system path. _AddToolsToSystemPathForWindows() - if options.android_package: + if options.android_packages: os.environ['PATH'] += os.pathsep + os.path.join( _THIS_DIR, os.pardir, 'chrome') - code1 = RunPythonTests(chromedriver, - ref_chromedriver, - android_package=options.android_package) - code2 = RunJavaTests(chromedriver, - android_package=options.android_package) - return code1 or code2 + code = 0 + for package in options.android_packages.split(','): + code1 = RunPythonTests(chromedriver, + ref_chromedriver, + chrome_version_name=package, + android_package=package) + code2 = RunJavaTests(chromedriver, + chrome_version_name=package, + android_package=package) + code = code or code1 or code2 + return code else: latest_snapshot_revision = archive.GetLatestRevision(archive.Site.SNAPSHOT) versions = [ @@ -184,10 +190,10 @@ def main(): ref_chromedriver, chrome=chrome_path, chrome_version=version[0], - chrome_version_name=version_name) + chrome_version_name='v%s' % version_name) code2 = RunJavaTests(chromedriver, chrome=chrome_path, chrome_version=version[0], - chrome_version_name=version_name) + chrome_version_name='v%s' % version_name) code = code or code1 or code2 cpp_tests = os.path.join(build_dir, cpp_tests_name) return RunCppTests(cpp_tests) or code diff --git a/chrome/test/chromedriver/test/run_py_tests.py b/chrome/test/chromedriver/test/run_py_tests.py index 2a19c6c655..c347ce2cf9 100755 --- a/chrome/test/chromedriver/test/run_py_tests.py +++ b/chrome/test/chromedriver/test/run_py_tests.py @@ -35,6 +35,15 @@ if util.IsLinux(): from pylib import valgrind_tools +_NEGATIVE_FILTER = {} +_NEGATIVE_FILTER['HEAD'] = [ + # https://code.google.com/p/chromedriver/issues/detail?id=213 + 'ChromeDriverTest.testClickElementInSubFrame', + # This test is flaky since it uses setTimeout. + # Re-enable once crbug.com/177511 is fixed and we can remove setTimeout. + 'ChromeDriverTest.testAlert', +] + _DESKTOP_OS_SPECIFIC_FILTER = [] if util.IsWindows(): _DESKTOP_OS_SPECIFIC_FILTER = [ @@ -60,14 +69,12 @@ elif util.IsMac(): _DESKTOP_NEGATIVE_FILTER = {} _DESKTOP_NEGATIVE_FILTER['HEAD'] = ( + _NEGATIVE_FILTER['HEAD'] + _DESKTOP_OS_SPECIFIC_FILTER + [ - # https://code.google.com/p/chromedriver/issues/detail?id=213 - 'ChromeDriverTest.testClickElementInSubFrame', - # This test is flaky since it uses setTimeout. - # Re-enable once crbug.com/177511 is fixed and we can remove setTimeout. - 'ChromeDriverTest.testAlert', - # Desktop doesn't support TAP. + # Desktop doesn't support touch (without --touch-events). 'ChromeDriverTest.testSingleTapElement', + 'ChromeDriverTest.testTouchDownUpElement', + 'ChromeDriverTest.testTouchMovedElement', ] ) @@ -80,15 +87,14 @@ def _GetDesktopNegativeFilter(version_name): _ANDROID_NEGATIVE_FILTER = {} _ANDROID_NEGATIVE_FILTER['com.google.android.apps.chrome'] = ( - _DESKTOP_NEGATIVE_FILTER['HEAD'] + [ + _NEGATIVE_FILTER['HEAD'] + [ # Android doesn't support switches and extensions. 'ChromeSwitchesCapabilityTest.*', 'ChromeExtensionsCapabilityTest.*', - # https://code.google.com/p/chromedriver/issues/detail?id=262 - 'ChromeDriverTest.testCloseWindow', - 'ChromeDriverTest.testGetWindowHandles', - 'ChromeDriverTest.testSwitchToWindow', + # https://code.google.com/p/chromedriver/issues/detail?id=459 'ChromeDriverTest.testShouldHandleNewWindowLoadingProperly', + # https://crbug.com/274650 + 'ChromeDriverTest.testCloseWindow', # https://code.google.com/p/chromedriver/issues/detail?id=259 'ChromeDriverTest.testSendKeysToElement', # https://code.google.com/p/chromedriver/issues/detail?id=270 @@ -104,8 +110,21 @@ _ANDROID_NEGATIVE_FILTER['com.google.android.apps.chrome'] = ( 'PerfTest.testColdExecuteScript', ] ) +_ANDROID_NEGATIVE_FILTER['com.android.chrome'] = ( + _ANDROID_NEGATIVE_FILTER['com.google.android.apps.chrome'] + [ + # Touch support was added to devtools in Chrome v30. + 'ChromeDriverTest.testTouchDownUpElement', + 'ChromeDriverTest.testTouchMovedElement', + ] +) +_ANDROID_NEGATIVE_FILTER['com.chrome.beta'] = ( + _ANDROID_NEGATIVE_FILTER['com.google.android.apps.chrome']) _ANDROID_NEGATIVE_FILTER['org.chromium.chrome.testshell'] = ( - _ANDROID_NEGATIVE_FILTER['com.google.android.apps.chrome'] + [] + _ANDROID_NEGATIVE_FILTER['com.google.android.apps.chrome'] + [ + # ChromiumTestShell doesn't support multiple tabs. + 'ChromeDriverTest.testGetWindowHandles', + 'ChromeDriverTest.testSwitchToWindow', + ] ) @@ -353,7 +372,6 @@ class ChromeDriverTest(ChromeDriverBaseTest): 'document.body.innerHTML = "<div>old</div>";' 'var div = document.getElementsByTagName("div")[0];' 'div.addEventListener("click", function() {' - ' var div = document.getElementsByTagName("div")[0];' ' div.innerHTML="new<br>";' '});' 'return div;') @@ -364,14 +382,40 @@ class ChromeDriverTest(ChromeDriverBaseTest): div = self._driver.ExecuteScript( 'document.body.innerHTML = "<div>old</div>";' 'var div = document.getElementsByTagName("div")[0];' - 'div.addEventListener("click", function() {' - ' var div = document.getElementsByTagName("div")[0];' + 'div.addEventListener("touchend", function() {' ' div.innerHTML="new<br>";' '});' 'return div;') div.SingleTap() self.assertEquals(1, len(self._driver.FindElements('tag name', 'br'))) + def testTouchDownUpElement(self): + div = self._driver.ExecuteScript( + 'document.body.innerHTML = "<div>old</div>";' + 'var div = document.getElementsByTagName("div")[0];' + 'div.addEventListener("touchend", function() {' + ' div.innerHTML="new<br>";' + '});' + 'return div;') + loc = div.GetLocation() + self._driver.TouchDown(loc['x'], loc['y']) + self._driver.TouchUp(loc['x'], loc['y']) + self.assertEquals(1, len(self._driver.FindElements('tag name', 'br'))) + + def testTouchMovedElement(self): + div = self._driver.ExecuteScript( + 'document.body.innerHTML = "<div>old</div>";' + 'var div = document.getElementsByTagName("div")[0];' + 'div.addEventListener("touchmove", function() {' + ' div.innerHTML="new<br>";' + '});' + 'return div;') + loc = div.GetLocation() + self._driver.TouchDown(loc['x'], loc['y']) + self._driver.TouchMove(loc['x'] + 1, loc['y'] + 1) + self._driver.TouchUp(loc['x'] + 1, loc['y'] + 1) + self.assertEquals(1, len(self._driver.FindElements('tag name', 'br'))) + def testClickElementInSubFrame(self): self._driver.Load(self.GetHttpUrlForFile('/chromedriver/frame_test.html')) frame = self._driver.FindElement('tag name', 'iframe') @@ -404,7 +448,7 @@ class ChromeDriverTest(ChromeDriverBaseTest): self.assertEquals('0123456789+-*/ Hi, there!', value) def testGetCurrentUrl(self): - self.assertTrue('data:' in self._driver.GetCurrentUrl()) + self.assertTrue('about:blank' in self._driver.GetCurrentUrl()) def testGoBackAndGoForward(self): self._driver.Load(self.GetHttpUrlForFile('/chromedriver/empty.html')) diff --git a/chrome/test/chromedriver/test/test_expectations b/chrome/test/chromedriver/test/test_expectations index b63c41e279..80cbf0083c 100644 --- a/chrome/test/chromedriver/test/test_expectations +++ b/chrome/test/chromedriver/test/test_expectations @@ -161,10 +161,11 @@ _OS_NEGATIVE_FILTER['android'] = [ # http://crbug.com/156390 'DragAndDropTest.*', - # Touch events are not yet supported. + # Flick touch events are not yet implemented. 'TouchFlickTest.*', + + # Scrolling touch events are not supported. 'TouchScrollTest.*', - 'TouchSingleTapTest.*', # These tests start multiple sessions, which is not supported on a single # Android device. diff --git a/chrome/test/chromedriver/window_commands.cc b/chrome/test/chromedriver/window_commands.cc index cec7479624..ad62a9ef54 100644 --- a/chrome/test/chromedriver/window_commands.cc +++ b/chrome/test/chromedriver/window_commands.cc @@ -5,6 +5,7 @@ #include "chrome/test/chromedriver/window_commands.h" #include <list> +#include <string> #include "base/callback.h" #include "base/strings/string_number_conversions.h" @@ -121,6 +122,67 @@ Status GetVisibleCookies(WebView* web_view, return Status(kOk); } +Status ScrollCoordinateInToView( + Session* session, WebView* web_view, int x, int y, int* offset_x, + int* offset_y) { + scoped_ptr<base::Value> value; + base::ListValue args; + args.AppendInteger(x); + args.AppendInteger(y); + Status status = web_view->CallFunction( + std::string(), + "function(x, y) {" + " if (x < window.pageXOffset ||" + " x >= window.pageXOffset + window.innerWidth ||" + " y < window.pageYOffset ||" + " y >= window.pageYOffset + window.innerHeight) {" + " window.scrollTo(x - window.innerWidth/2, y - window.innerHeight/2);" + " }" + " return {" + " view_x: Math.floor(window.pageXOffset)," + " view_y: Math.floor(window.pageYOffset)," + " view_width: Math.floor(window.innerWidth)," + " view_height: Math.floor(window.innerHeight)};" + "}", + args, + &value); + if (!status.IsOk()) + return status; + base::DictionaryValue* view_attrib; + value->GetAsDictionary(&view_attrib); + int view_x, view_y, view_width, view_height; + view_attrib->GetInteger("view_x", &view_x); + view_attrib->GetInteger("view_y", &view_y); + view_attrib->GetInteger("view_width", &view_width); + view_attrib->GetInteger("view_height", &view_height); + *offset_x = x - view_x; + *offset_y = y - view_y; + if (*offset_x < 0 || *offset_x >= view_width || *offset_y < 0 || + *offset_y >= view_height) + return Status(kUnknownError, "Failed to scroll coordinate into view"); + return Status(kOk); +} + +Status ExecuteTouchEvent( + Session* session, WebView* web_view, TouchEventType type, + const base::DictionaryValue& params) { + int x, y; + if (!params.GetInteger("x", &x)) + return Status(kUnknownError, "'x' must be an integer"); + if (!params.GetInteger("y", &y)) + return Status(kUnknownError, "'y' must be an integer"); + int relative_x = x; + int relative_y = y; + Status status = ScrollCoordinateInToView( + session, web_view, x, y, &relative_x, &relative_y); + if (!status.IsOk()) + return status; + std::list<TouchEvent> events; + events.push_back( + TouchEvent(type, relative_x, relative_y)); + return web_view->DispatchTouchEvents(events); +} + } // namespace Status ExecuteWindowCommand( @@ -499,17 +561,36 @@ Status ExecuteMouseDoubleClick( return web_view->DispatchMouseEvents(events, session->GetCurrentFrameId()); } +Status ExecuteTouchDown( + Session* session, + WebView* web_view, + const base::DictionaryValue& params, + scoped_ptr<base::Value>* value) { + return ExecuteTouchEvent(session, web_view, kTouchStart, params); +} + +Status ExecuteTouchUp( + Session* session, + WebView* web_view, + const base::DictionaryValue& params, + scoped_ptr<base::Value>* value) { + return ExecuteTouchEvent(session, web_view, kTouchEnd, params); +} + +Status ExecuteTouchMove( + Session* session, + WebView* web_view, + const base::DictionaryValue& params, + scoped_ptr<base::Value>* value) { + return ExecuteTouchEvent(session, web_view, kTouchMove, params); +} + Status ExecuteGetActiveElement( Session* session, WebView* web_view, const base::DictionaryValue& params, scoped_ptr<base::Value>* value) { - base::ListValue args; - return web_view->CallFunction( - session->GetCurrentFrameId(), - "function() { return document.activeElement || document.body }", - args, - value); + return GetActiveElement(session, web_view, value); } Status ExecuteSendKeysToActiveElement( diff --git a/chrome/test/chromedriver/window_commands.h b/chrome/test/chromedriver/window_commands.h index 8d0ee64969..017813628b 100644 --- a/chrome/test/chromedriver/window_commands.h +++ b/chrome/test/chromedriver/window_commands.h @@ -153,6 +153,27 @@ Status ExecuteMouseDoubleClick( const base::DictionaryValue& params, scoped_ptr<base::Value>* value); +// Touch press at a given coordinate. +Status ExecuteTouchDown( + Session* session, + WebView* web_view, + const base::DictionaryValue& params, + scoped_ptr<base::Value>* value); + +// Touch release at a given coordinate. +Status ExecuteTouchUp( + Session* session, + WebView* web_view, + const base::DictionaryValue& params, + scoped_ptr<base::Value>* value); + +// Touch move at a given coordinate. +Status ExecuteTouchMove( + Session* session, + WebView* web_view, + const base::DictionaryValue& params, + scoped_ptr<base::Value>* value); + Status ExecuteGetActiveElement( Session* session, WebView* web_view, diff --git a/chrome/test/functional/PYAUTO_TESTS b/chrome/test/functional/PYAUTO_TESTS index 69167c5401..9cac8a97ec 100644 --- a/chrome/test/functional/PYAUTO_TESTS +++ b/chrome/test/functional/PYAUTO_TESTS @@ -472,50 +472,6 @@ ], }, - # WebRTC MediaStream tests. Requires webcam and audio device to be present on - # the test machine. - 'WEBRTC': { - 'all': [ - 'media_stream_infobar', - 'webrtc_brutality_test', - 'webrtc_call', - - # ================================================== - # Disabled tests that need to be investigated/fixed. - # ================================================== - # crbug.com/248079 - '-webrtc_brutality_test.WebrtcBrutalityTest.testReloadsAfterGetUserMedia', - ], - }, - - # WebRTC / AppRTC Integration tests. - 'WEBRTC_APPRTC': { - 'all': [ - 'webrtc_apprtc_call', - # ================================================== - # Disabled tests that need to be investigated/fixed. - # ================================================== - # crbug.com/254412 - '-webrtc_apprtc_call.WebrtcApprtcCallTest.testApprtcTabToTabCall', - '-webrtc_apprtc_call.WebrtcApprtcCallTest.testApprtcLoopbackCall', - ], - }, - - # WebRTC quality tests. The video quality test requires a webcam, an audio - # device and a special setup where the webcam plays a barcode-encoded video. - # The webrtc_audio_quality test requires additional configuration as described - # in the test's class comments. - 'WEBRTC_QUALITY': { - 'all': [ - 'webrtc_video_quality', - # Disabled until crbug.com/254437 is resolved. - '-webrtc_audio_quality', - ], - 'mac': [ - '-webrtc_audio_quality', # Not implemented. - ], - }, - # Trace event tests. 'TRACING': { 'all': [ diff --git a/chrome/test/functional/ispy/ispy_core/app.yaml b/chrome/test/functional/ispy/ispy_core/app.yaml new file mode 100644 index 0000000000..cbed5f9011 --- /dev/null +++ b/chrome/test/functional/ispy/ispy_core/app.yaml @@ -0,0 +1,17 @@ +application: google.com:ispy +version: 1 +runtime: python27 +api_version: 1 +threadsafe: True + +handlers: +- url: /.* + script: main.application + +libraries: +- name: webapp2 + version: latest +- name: jinja2 + version: latest +- name: PIL + version: latest diff --git a/chrome/test/functional/ispy/ispy_core/handlers/__init__.py b/chrome/test/functional/ispy/ispy_core/handlers/__init__.py new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/chrome/test/functional/ispy/ispy_core/handlers/__init__.py diff --git a/chrome/test/functional/ispy/ispy_core/handlers/command_handler.py b/chrome/test/functional/ispy/ispy_core/handlers/command_handler.py new file mode 100644 index 0000000000..fb625ee80e --- /dev/null +++ b/chrome/test/functional/ispy/ispy_core/handlers/command_handler.py @@ -0,0 +1,357 @@ +# Copyright 2013 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +"""Request handler to allow restful interaction with tests and runs.""" + +import json +import os +import sys +import webapp2 + +sys.path.append(os.path.join(os.path.dirname(__file__), os.pardir)) + +from tests.rendering_test_manager import cloud_bucket +from tests.rendering_test_manager import cloud_bucket_impl +from tools import image_tools +from tools import rendering_test_manager + +import ispy_auth_constants + +class MissingArgError(Exception): + pass + + +def Command(*expected_parameters): + """Convenient decorator for Command functions the CommandHandler. + + Args: + *expected_parameters: the expected parameters for the command. + + Returns: + A decorated method that accepts a request object and returns nothing. + """ + def _Decorate(method): + def _Wrapper(self, request): + # Try accessing all expected_parameters, if any are absent from the + # request raise MissingArgError. + try: + for param in expected_parameters: + if not request.get(param): + raise MissingArgError('Argument: missing %s' % param) + # If a MissingArgError was raised, write an error response. + except MissingArgError, e: + self.response.headers['Content-Type'] = 'application/json' + self.response.out.write(json.dumps({'success': False, 'error': str(e)})) + else: + # If all parameters are present run the decorated method. + results = method(self, request) + self.response.headers['Content-Type'] = 'application/json' + self.response.out.write(json.dumps(results)) + return _Wrapper + return _Decorate + + +class CommandHandler(webapp2.RequestHandler): + """Command handler that allows restful interaction with tests and runs.""" + + def __init__(self, *args, **kwargs): + super(CommandHandler, self).__init__(*args, **kwargs) + self.command_functions = { + 'upload_test': self._UploadTest, + 'test_exists': self._TestExists, + 'failure_exists': self._FailureExists, + 'run_test': self._RunTest, + 'upload_test_pink_out': self._UploadTestPinkOut, + 'remove_test': self._RemoveTest, + 'remove_failure': self._RemoveFailure, + 'add_to_test_mask': self._AddToTestMask} + + def post(self): + """Handles post requests. + + This method accepts a post request that minimally has a 'command' parameter + which indicates which of the handler's commands to run. If the + command is not in the valid_commands dictionary, then an error + will be returned. All responses are json-encoded objects that + have either a Succeeded or Failed parameter indicating success or + failure in addition to other returned parameters specific to the command. + """ + cmd = self.request.get('command') + if not cmd: + self._Error(self.request) + return + if cmd not in self.command_functions: + self._InvalidCommand(self.request) + return + self.bucket = cloud_bucket_impl.CloudBucketImpl( + ispy_auth_constants.KEY, ispy_auth_constants.SECRET, + ispy_auth_constants.BUCKET) + self.manager = rendering_test_manager.RenderingTestManager(self.bucket) + self.command_functions.get(cmd)(self.request) + + @Command() + def _Error(self, request): + """This command returns an error when no command is given. + + This command has no expected parameters. + + Args: + request: A request object. + + Returns: + A dictionary indicating a failure, and an error message indicating + that no command was specified. + """ + return {'success': False, 'error': 'No command was specified.'} + + @Command() + def _InvalidCommand(self, request): + """This command returns an error when an invalid command is given. + + This command has no expected parameters. + + Args: + request: a request object. + + Returns: + A dictionary indicating a failure, and an error message + containing all possible valid commands. + """ + return {'success': False, + 'error': 'Invalid command. Valid commands are: %s.' % + ' '.join(self.command_functions.keys())} + + @Command('batch_name', 'test_name', 'images') + def _UploadTest(self, request): + """Uploads an ispy-test to GCS. + + This function uploads a collection of images as a 'test' to the + ispy server. A mask is then computed from these images, and the + first image in the images list and the mask are stored in the + GCS as an ispy-test. + + This function is called for the command 'upload_test'. + Request Parameters: + test_name: The name of the test to be uploaded. + images: a json encoded list of base64 encoded png images. + Response JSON: + succeeded: True. + + Args: + request: a request object. + + Returns: + A dictionary indicating success or failure. + """ + batch_name = request.get('batch_name') + test_name = request.get('test_name') + raw_images = request.get('images') + images = json.loads(raw_images) + self.manager.UploadTest( + batch_name, test_name, + [image_tools.DeserializeImage(image) for image in images]) + return {'success': True} + + @Command('batch_name', 'test_name', 'images', 'pink_out', 'RGB') + def _UploadTestPinkOut(self, request): + """Uploads an ispy-test to GCS with the pink_out workaround. + + This function is called for the command 'upload_test_pink_out'. + Request Parameters: + test_name: The name of the test to be uploaded. + images: a json encoded list of base64 encoded png images. + pink_out: a base64 encoded png image. + RGB: a json list representing the RGB values of a color to mask out. + Response JSON: + succeeded: True. + + Args: + request: a request object. + + Returns: + A dictionary indicating success or failure. + """ + batch_name = request.get('batch_name') + test_name = request.get('test_name') + rgb = json.loads(request.get('RGB')) + images = [image_tools.DeserializeImage(i) + for i in json.loads(request.get('images'))] + pink_out = image_tools.DeserializeImage(request.get('pink_out')) + # convert the pink_out into a mask + black = (0, 0, 0, 255) + white = (255, 255, 255, 255) + pink_out.putdata( + [black if px == (rgb[0], rgb[1], rgb[2], 255) else white + for px in pink_out.getdata()]) + mask = image_tools.CreateMask(images) + mask = image_tools.InflateMask(image_tools.CreateMask(images), 7) + combined_mask = image_tools.AddMasks([mask, pink_out]) + path = 'tests/%s/%s/' % (batch_name, test_name) + self.manager.UploadImage(path + 'expected.png', + images[0]) + self.manager.UploadImage(path + 'mask.png', + combined_mask) + return {'success': True} + + @Command('mask', 'batch_name', 'test_name') + def _AddToTestMask(self, request): + """Adds a another mask image to the existing mask for a given test. + + Request Parameters: + mask: A RGBA png white/black mask image. + test_name: The name of a test in i-spy to add to. + Response JSON: + succeeded: True + Response Error: + error: if the test was not found in GCS. + + Args: + request: A request object. + + Returns: + A dictionary indicating success or failure. + """ + batch_name = request.get('batch_name') + test_name = request.get('test_name') + mask_to_be_added = image_tools.InflateMask( + image_tools.DeserializeImage(request.get('mask')), 7) + if not self.manager.TestExists(batch_name, test_name): + return {'success': False, 'error': 'Test does not exist.'} + path = 'tests/%s/%s/mask.png' % (batch_name, test_name) + test_mask = self.manager.DownloadImage(path) + combined_mask = image_tools.AddMasks([test_mask, mask_to_be_added]) + self.manager.UploadImage(path, combined_mask) + return {'success': True} + + @Command('image', 'batch_name', 'test_name') + def _RunTest(self, request): + """Runs a test on GCS and stores a failure if it doesn't pass. + + This method compares the submitted image with the expected and mask + images stored in GCS under the submitted test name. If there are + any differences between the submitted image and the expected test + image (with respect to the mask), the submitted image is stored + as a failure in GCS. + + This method is run if the command parameter is set to 'run_test'. + Request Parameters: + 'image': A base64 encoded screenshot that corresponds to a test in GCS. + 'test_name': The name of the test that 'image' corresponds to. + Response JSON: + 'succeeded': True + + Args: + request: a request object + + Returns: + A dictionary indicating success or failure. + """ + raw_image = request.get('image') + batch_name = request.get('batch_name') + test_name = request.get('test_name') + image = image_tools.DeserializeImage(raw_image) + try: + self.manager.RunTest(batch_name, test_name, image) + except cloud_bucket.FileNotFoundError, e: + return {'success': False, 'error': str(e)} + else: + return {'success': True} + + @Command('batch_name', 'test_name') + def _TestExists(self, request): + """Checks to see if a test exists in GCS. + + This method confirms whether or not an expected image, and + mask exist under the given test_name in GCS. Returning true + only if all components of a test exist. + + This method is run if the command parameter is 'test_exists'. + Request Parameters: + 'test_name': The name of the test to look for. + Response JSON: + 'exists': boolean indicating whether the test exists. + 'succeeded': True. + + Args: + request: a request object. + + Returns: + A dictionary indicating success or failure, and if the command was + successful, whether the test exists. + """ + batch_name = request.get('batch_name') + test_name = request.get('test_name') + return {'success': True, 'exists': self.manager.TestExists(batch_name, + test_name)} + + @Command('batch_name', 'test_name') + def _FailureExists(self, request): + """Checks to see if a particular failed run exists in GCS. + + This method is run if the command parameter is 'failure_exists'. + Request Parameters: + 'test_name': the name of the test that failure occurred on. + Response JSON: + 'exists': boolean indicating whether the failure exists. + 'succeeded': True. + + Args: + request: a request object. + + Returns: + A dictionary indicating success or failure, and if the command was + successful, whether the failed run exists. + """ + batch_name = request.get('batch_name') + test_name = request.get('test_name') + return {'success': True, + 'exists': self.manager.FailureExists( + batch_name, test_name)} + + @Command('batch_name', 'test_name') + def _RemoveTest(self, request): + """Removes a test and associated runs from GCS. + + This method will locate all files in ispy's GCS datastore + associated with the given test_name, and remove them from GCS. + This includes the test's mask, expected image, and all failures + on the given test. + + This method is run if the command parameter is 'remove_test'. + Request Parameters: + 'test_name': the name of the test to remove. + Response JSON: + succeeded: True. + + Args: + request: a request object. + + Returns: + A dictionary indicating success or failure. + """ + batch_name = request.get('batch_name') + test_name = request.get('test_name') + self.manager.RemoveTest(batch_name, test_name) + return {'success': True} + + @Command('batch_name', 'test_name') + def _RemoveFailure(self, request): + """Removes a failure from GCS. + + This method is run if the command parameter is 'remove_failure'. + Request Parameters: + 'test_name': the name of the test in which the failure occurred. + Response JSON: + succeeded: True. + + Args: + request: a request object. + + Returns: + A dictionary indicating success or failure. + """ + batch_name = request.get('batch_name') + test_name = request.get('test_name') + self.manager.RemoveFailure(batch_name, test_name) + return {'success': True} diff --git a/chrome/test/functional/ispy/ispy_core/handlers/debug_view_handler.py b/chrome/test/functional/ispy/ispy_core/handlers/debug_view_handler.py new file mode 100644 index 0000000000..6ef4e9cd64 --- /dev/null +++ b/chrome/test/functional/ispy/ispy_core/handlers/debug_view_handler.py @@ -0,0 +1,43 @@ +# Copyright 2013 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +"""Request handler to display the debug view for a Failure.""" + +import jinja2 +import os +import sys +import webapp2 + +sys.path.append(os.path.join(os.path.dirname(__file__), os.pardir)) +import views + +JINJA = jinja2.Environment( + loader=jinja2.FileSystemLoader(os.path.dirname(views.__file__)), + extensions=['jinja2.ext.autoescape']) + + +class DebugViewHandler(webapp2.RequestHandler): + """Request handler to display the debug view for a failure.""" + + def get(self): + """Handles get requests to the /debug_view page. + + GET Parameters: + diff: The path to the diff image on a failure. + expected: The path to the expected image on a failure. + actual: The path to the actual image on a failure. + """ + diff_path = self.request.get('diff') + expected_path = self.request.get('expected') + actual_path = self.request.get('actual') + data = {} + + def _Encode(url): + return '/image?file_path=%s' % url + + data['diff'] = _Encode(diff_path) + data['expected'] = _Encode(expected_path) + data['actual'] = _Encode(actual_path) + template = JINJA.get_template('debug_view.html') + self.response.write(template.render(data)) diff --git a/chrome/test/functional/ispy/ispy_core/handlers/image_handler.py b/chrome/test/functional/ispy/ispy_core/handlers/image_handler.py new file mode 100644 index 0000000000..331519666f --- /dev/null +++ b/chrome/test/functional/ispy/ispy_core/handlers/image_handler.py @@ -0,0 +1,42 @@ +# Copyright 2013 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +"""Request handler to display an image from ispy.googleplex.com.""" + +import json +import os +import sys +import webapp2 + +sys.path.append(os.path.join(os.path.dirname(__file__), os.pardir)) + +from tests.rendering_test_manager import cloud_bucket +from tests.rendering_test_manager import cloud_bucket_impl + +import ispy_auth_constants + + +class ImageHandler(webapp2.RequestHandler): + """A request handler to avoid the Same-Origin problem in the debug view.""" + + def get(self): + """Handles get requests to the ImageHandler. + + GET Parameters: + file_path: A path to an image resource in Google Cloud Storage. + """ + file_path = self.request.get('file_path') + if not file_path: + self.error(404) + return + bucket = cloud_bucket_impl.CloudBucketImpl( + ispy_auth_constants.KEY, ispy_auth_constants.SECRET, + ispy_auth_constants.BUCKET) + try: + image = bucket.DownloadFile(file_path) + except cloud_bucket.FileNotFoundError: + self.error(404) + else: + self.response.headers['Content-Type'] = 'image/png' + self.response.out.write(image) diff --git a/chrome/test/functional/ispy/ispy_core/handlers/main_view_handler.py b/chrome/test/functional/ispy/ispy_core/handlers/main_view_handler.py new file mode 100644 index 0000000000..032cbab17a --- /dev/null +++ b/chrome/test/functional/ispy/ispy_core/handlers/main_view_handler.py @@ -0,0 +1,117 @@ +# Copyright 2013 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +"""Request handler to serve the main_view page.""" + +import jinja2 +import json +import os +import re +import sys +import webapp2 + +sys.path.append(os.path.join(os.path.dirname(__file__), os.pardir)) + +import views + +from tests.rendering_test_manager import cloud_bucket_impl +from tools import rendering_test_manager + +import ispy_auth_constants + +JINJA = jinja2.Environment( + loader=jinja2.FileSystemLoader(os.path.dirname(views.__file__)), + extensions=['jinja2.ext.autoescape']) + + +class MainViewHandler(webapp2.RequestHandler): + """Request handler to serve the main_view page.""" + + def get(self): + """Handles a get request to the main_view page. + + If the batch_name parameter is specified, then a page displaying all of + the failed runs in the batch will be shown. Otherwise a view listing + all of the batches available for viewing will be displayed. + """ + # Get parameters. + batch_name = self.request.get('batch_name') + # Set up rendering test manager. + bucket = cloud_bucket_impl.CloudBucketImpl( + ispy_auth_constants.KEY, ispy_auth_constants.SECRET, + ispy_auth_constants.BUCKET) + manager = rendering_test_manager.RenderingTestManager(bucket) + # Load the view. + if batch_name: + self._GetForBatch(batch_name, manager) + return + self._GetAllBatches(manager) + + def _GetAllBatches(self, manager): + """Renders a list view of all of the batches available in GCS. + + Args: + manager: An instance of rendering_test_manager.RenderingTestManager. + """ + template = JINJA.get_template('list_view.html') + data = {} + batches = set([re.match(r'^tests/([^/]+)/.+$', path).groups()[0] + for path in manager.GetAllPaths('tests/')]) + base_url = '/?batch_name=%s' + data['links'] = [(batch, base_url % batch) for batch in batches] + self.response.write(template.render(data)) + + def _GetForBatch(self, batch_name, manager): + """Renders a sorted list of failure-rows for a given batch_name. + + This method will produce a list of failure-rows that are sorted + in descending order by number of different pixels. + + Args: + batch_name: The name of the batch to render failure rows from. + manager: An instance of rendering_test_manager.RenderingTestManager. + """ + template = JINJA.get_template('main_view.html') + paths = set([path for path in manager.GetAllPaths('failures/' + batch_name) + if path.endswith('actual.png')]) + rows = [self._CreateRow(batch_name, path, manager) + for path in paths] + # Function that sorts by the different_pixels field in the failure-info. + def _Sorter(x, y): + return cmp(y['info']['different_pixels'], + x['info']['different_pixels']) + self.response.write(template.render({'comparisons': sorted(rows, _Sorter)})) + + def _CreateRow(self, batch_name, path, manager): + """Creates one failure-row. + + This method builds a dictionary with the data necessary to display a + failure in the main_view html template. + + Args: + batch_name: The name of the batch the failure is in. + path: A path to the failure's actual.png file. + manager: An instance of rendering_test_manager.RenderingTestManager. + + Returns: + A dictionary with fields necessary to render a failure-row + in the main_view html template. + """ + res = {} + pattern = r'^failures/%s/([^/]+)/.+$' % batch_name + res['test_name'] = re.match( + pattern, path).groups()[0] + res['batch_name'] = batch_name + res['info'] = json.loads(manager.cloud_bucket.DownloadFile( + '/failures/%s/%s/info.txt' % (res['batch_name'], res['test_name']))) + expected = 'tests/%s/%s/expected.png' % (batch_name, res['test_name']) + diff = 'failures/%s/%s/diff.png' % (batch_name, res['test_name']) + res['expected_path'] = expected + res['diff_path'] = diff + res['actual_path'] = path + res['expected'] = manager.cloud_bucket.GetURL(expected) + res['diff'] = manager.cloud_bucket.GetURL(diff) + res['actual'] = manager.cloud_bucket.GetURL(path) + return res + diff --git a/chrome/test/functional/ispy/ispy_core/handlers/update_mask_handler.py b/chrome/test/functional/ispy/ispy_core/handlers/update_mask_handler.py new file mode 100644 index 0000000000..c17d5d2a92 --- /dev/null +++ b/chrome/test/functional/ispy/ispy_core/handlers/update_mask_handler.py @@ -0,0 +1,64 @@ +# Copyright 2013 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +"""Request Handler to allow test mask updates.""" + +import webapp2 +import re +import sys +import os + +sys.path.append(os.path.join(os.path.dirname(__file__), os.pardir)) +from tools import rendering_test_manager +from tools import image_tools +from tests.rendering_test_manager import cloud_bucket_impl + +import ispy_auth_constants + +class UpdateMaskHandler(webapp2.RequestHandler): + """Request handler to allow test mask updates.""" + + def post(self): + """Accepts post requests. + + This method will accept a post request containing device, site and + device_id parameters. This method takes the diff of the run + indicated by it's parameters and adds it to the mask of the run's + test. It will then delete the run it is applied to and redirect + to the device list view. + """ + batch_name = self.request.get('batch_name') + test_name = self.request.get('test_name') + + # Short-circuit if a parameter is missing. + if not (batch_name and test_name): + self.response.header['Content-Type'] = 'json/application' + self.response.write(json.dumps( + {'error': 'batch_name, and test_name, must be ' + 'supplied to update a mask.'})) + return + # Otherwise, set up the rendering_test_manager. + self.bucket = cloud_bucket_impl.CloudBucketImpl( + ispy_auth_constants.KEY, ispy_auth_constants.SECRET, + ispy_auth_constants.BUCKET) + self.manager = rendering_test_manager.RenderingTestManager(self.bucket) + # Short-circuit if the failure does not exist. + if not self.manager.FailureExists(batch_name, test_name): + self.response.header['Content-Type'] = 'json/application' + self.response.write(json.dumps( + {'error': 'Could not update mask because failure does not exist.'})) + return + # Get the failure namedtuple (which also computes the diff). + failure = self.manager.GetFailure(batch_name, test_name) + # Get the mask of the image. + path = 'tests/%s/%s/mask.png' % (batch_name, test_name) + mask = self.manager.DownloadImage(path) + # Merge the masks. + combined_mask = image_tools.AddMasks([mask, failure.diff]) + # Upload the combined mask in place of the original. + self.manager.UploadImage(path, combined_mask) + # Now that there is no div for the two images, remove the failure. + self.manager.RemoveFailure(batch_name, test_name) + # Redirect back to the sites list for the device. + self.redirect('/?batch_name=%s' % batch_name) diff --git a/chrome/test/functional/ispy/ispy_core/ispy_auth_constants.py b/chrome/test/functional/ispy/ispy_core/ispy_auth_constants.py new file mode 100644 index 0000000000..c4263a8715 --- /dev/null +++ b/chrome/test/functional/ispy/ispy_core/ispy_auth_constants.py @@ -0,0 +1,12 @@ +# Copyright 2013 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +"""Module to hold authentication constants used across handlers.""" + +# This is a dummy file, when updating the I-Spy server, replace these +# empty strings with their correct values. + +KEY = 'GOOGxxxxxxxxxxxxxxxxxxx' +SECRET = 'xxxxxxxxxxxxxxxx/xxxxxxxxxx/xxxxxxxxxxx' +BUCKET = 'i-spy' diff --git a/chrome/test/functional/ispy/ispy_core/main.py b/chrome/test/functional/ispy/ispy_core/main.py new file mode 100644 index 0000000000..5f9665e1c2 --- /dev/null +++ b/chrome/test/functional/ispy/ispy_core/main.py @@ -0,0 +1,18 @@ +# Copyright 2013 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +import webapp2 + +from handlers import command_handler +from handlers import debug_view_handler +from handlers import image_handler +from handlers import main_view_handler +from handlers import update_mask_handler + +application = webapp2.WSGIApplication( + [('/run_command?', command_handler.CommandHandler), + ('/update_mask?', update_mask_handler.UpdateMaskHandler), + ('/debug_view?', debug_view_handler.DebugViewHandler), + ('/image?', image_handler.ImageHandler), + ('/', main_view_handler.MainViewHandler)], debug=True) diff --git a/chrome/test/functional/ispy/ispy_core/run_tests.py b/chrome/test/functional/ispy/ispy_core/run_tests.py new file mode 100755 index 0000000000..7b083fbd3e --- /dev/null +++ b/chrome/test/functional/ispy/ispy_core/run_tests.py @@ -0,0 +1,11 @@ +#!/usr/bin/env python +# +# Copyright 2013 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +import unittest + +if __name__ == '__main__': + suite = unittest.TestLoader().discover('.', pattern='*test.py') + unittest.TextTestRunner(verbosity=2).run(suite) diff --git a/chrome/test/functional/ispy/ispy_core/tests/rendering_test_manager/cloud_bucket_impl.py b/chrome/test/functional/ispy/ispy_core/tests/rendering_test_manager/cloud_bucket_impl.py index e28fc7fc99..ebf18f02e0 100644 --- a/chrome/test/functional/ispy/ispy_core/tests/rendering_test_manager/cloud_bucket_impl.py +++ b/chrome/test/functional/ispy/ispy_core/tests/rendering_test_manager/cloud_bucket_impl.py @@ -3,12 +3,15 @@ # found in the LICENSE file. """Implementation of CloudBucket using Google Cloud Storage as the backend.""" - +import os import sys -sys.path.append(sys.path.join('gsutil', 'third_party', 'boto')) -# TODO(chris) check boto into ispy/third_party/ + +sys.path.append(os.path.join(os.path.dirname(__file__), os.pardir, os.pardir, + 'gsutil', 'third_party', 'boto')) import boto + +sys.path.append(os.path.join(os.path.dirname(__file__), os.pardir, os.pardir)) from tests.rendering_test_manager import cloud_bucket @@ -63,7 +66,9 @@ class CloudBucketImpl(cloud_bucket.CloudBucket): key = boto.gs.key.Key(self.bucket) key.key = path if key.exists(): - return key.generate_url(3600) + # Corrects a bug in boto that incorrectly generates a url + # to a resource in Google Cloud Storage. + return key.generate_url(3600).replace('AWSAccessKeyId', 'GoogleAccessId') else: raise cloud_bucket.FileNotFoundError(path) diff --git a/chrome/test/functional/ispy/ispy_core/tests/rendering_test_manager/mock_cloud_bucket.py b/chrome/test/functional/ispy/ispy_core/tests/rendering_test_manager/mock_cloud_bucket.py index 7ae1def9b5..5753a981dc 100644 --- a/chrome/test/functional/ispy/ispy_core/tests/rendering_test_manager/mock_cloud_bucket.py +++ b/chrome/test/functional/ispy/ispy_core/tests/rendering_test_manager/mock_cloud_bucket.py @@ -4,6 +4,10 @@ """Subclass of CloudBucket used for testing.""" +import os +import sys + +sys.path.append(os.path.join(os.path.dirname(__file__), os.pardir, os.pardir)) from tests.rendering_test_manager import cloud_bucket diff --git a/chrome/test/functional/ispy/ispy_core/tools/__init__.py b/chrome/test/functional/ispy/ispy_core/tools/__init__.py index e69de29bb2..d7a0128100 100644 --- a/chrome/test/functional/ispy/ispy_core/tools/__init__.py +++ b/chrome/test/functional/ispy/ispy_core/tools/__init__.py @@ -0,0 +1,24 @@ +# Copyright 2013 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +"""This module provides a set of tools that I-Spy uses to perform tests. + + +I-Spy breaks rendering-validations down into a hierarchy of batches, + tests, and failures. + A batch is a group of tests that is run together. + A test consists of two images, one image of the page that is known + to be rendered correctly, and another that is a white and black mask, + where white regions represent areas that should be exempted from pixel- + by-pixel comparisons. These masks are generated by taking a + succession of images from a view which is known to be rendered correctly, + and masking the regions that change to prevent animated regions of the + page from breaking the tests. Tests can be run by submitting an image to + them. The submitted image will be compared against the regions of the + test's image (which is known to be rendered correctly) that are not + excluded by the test's mask. If the comparison fails, a failure is stored + in Google Cloud Storage. + A failure consists of the image which failed to compare to the test's image + and mask, and a black and white image representing the difference between + the failure's image and the test's.""" diff --git a/chrome/test/functional/ispy/ispy_core/tools/api_command_runner.py b/chrome/test/functional/ispy/ispy_core/tools/api_command_runner.py new file mode 100644 index 0000000000..e6cdb787d4 --- /dev/null +++ b/chrome/test/functional/ispy/ispy_core/tools/api_command_runner.py @@ -0,0 +1,58 @@ +# Copyright 2013 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +"""RunAPICommand function to authenticate with Ispy and run a command.""" + +import re +import requests + + +class ServerSideError(Exception): + pass + + +class UnknownError(Exception): + pass + + +def RunAPICommand(command, parameters, email, app_specific_password): + """Performs the necessary authentication and runs an ispy api command. + + Args: + command: The name of the command to run. + parameters: A List of 2-tuples (parameter-name, parameter-value). + email: A google.com email to connect to ispy with. + app_specific_password: An application specific password registered + to the given email account. + + Returns: + A JSON representation of the result of the command. + """ + app_name = 'google.com:ispy' + base_url = 'http://ispy.googleplex.com/' + # Use Requests to get an Auth key for the specified email/password. + data = {'Email': email, 'Passwd': app_specific_password, 'service': 'ah', + 'source': app_name, 'accountType': 'GOOGLE'} + auth_keys_text = requests.post('https://google.com/accounts/ClientLogin', + data=data).text + auth_keys = dict(line.split('=') + for line in auth_keys_text.split('\n') if line) + # Establish a session by logging into _ah/login + serv_args = {'continue': '', 'auth': auth_keys['Auth']} + r2 = requests.get(base_url + '_ah/login', + params=serv_args, allow_redirects=False) + r3 = requests.post(base_url+'run_command', + data=dict([('command', command)] + parameters.items()), + cookies=r2.cookies) + try: + return r3.json() + except ValueError: + if '<html>' in r3.text: + match = re.match(r'^.+?<pre>(.+?)</pre>.+?$', r3.text, re.DOTALL) + if match: + raise ServerSideError(match.groups()[0]) + else: + raise ServerSideError(r3.text) + else: + raise UnknownError(r3.text) diff --git a/chrome/test/functional/ispy/ispy_core/tools/api_command_runner_functionaltest.py b/chrome/test/functional/ispy/ispy_core/tools/api_command_runner_functionaltest.py new file mode 100644 index 0000000000..c16238343d --- /dev/null +++ b/chrome/test/functional/ispy/ispy_core/tools/api_command_runner_functionaltest.py @@ -0,0 +1,111 @@ +# Copyright 2013 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +from PIL import Image +import json +import os +import sys +import unittest + +sys.path.append(os.path.join(os.path.dirname(__file__), os.pardir)) +from tools import api_command_runner +from tools import image_tools +import auth_constants + +class APICommandRunnerFunctionalTest(unittest.TestCase): + def setUp(self): + self.u = auth_constants.api_username + self.p = auth_constants.api_password + + def Run(self, command, params, expected): + res = api_command_runner.RunAPICommand(command, params, self.u, self.p) + for item in expected.items(): + self.assertTrue(res.has_key(item[0])) + self.assertEquals(res[item[0]], item[1]) + + def testRunner(self): + batch_test = {'batch_name': 'batch', 'test_name': 'test'} + white = image_tools.SerializeImage(Image.new( + 'RGBA', (25, 25), (255, 255, 255, 255))) + black = image_tools.SerializeImage(Image.new( + 'RGBA', (25, 25), (0, 0, 0, 255))) + pink = image_tools.SerializeImage(Image.new( + 'RGBA', (25, 25), (255, 71, 191, 255))) + test_set = json.dumps([white, white]) + # Removing a test. + self.Run('remove_test', batch_test, {'success': True}) + # Uploading a test. + self.Run('upload_test', dict(batch_test.items() + [('images', test_set)]), + {'success': True}) + # Check that the test we uploaded is there. + self.Run('test_exists', batch_test, {'success': True, 'exists': True}) + # Check that we can run the test we uploaded. + self.Run('run_test', dict(batch_test.items() + [('image', black)]), + {'success': True}) + # Check that the test we ran failed. + self.Run('failure_exists', batch_test, {'success': True, 'exists': True}) + # Remove the failure. + self.Run('remove_failure', batch_test, {'success': True}) + # Check that the failure was removed. + self.Run('failure_exists', batch_test, {'success': True, 'exists': False}) + # Remove the test we added. + self.Run('remove_test', batch_test, {'success': True}) + # Check that the test was removed. + self.Run('test_exists', batch_test, {'success': True, 'exists': False}) + # Check that a bad command returns a failure. + self.Run('bad_command', {}, {'success': False}) + # Check that missing parameters returns a failure. + self.Run('run_test', batch_test, {'success': False}) + # Check that running a bad test returns a failure. + self.Run('run_test', {'batch_name': 'batch', 'test_name': 'doesnt_exist', + 'image': white}, + {'success': False}) + # Upload a test with the pinkout workaround (pink). + self.Run('upload_test_pink_out', + dict(batch_test.items() + + [('images', test_set), ('pink_out', pink), + ('RGB', json.dumps([255, 71, 191, 255]))]), + {'success': True}) + # Check that the test is there. + self.Run('test_exists', batch_test, {'success': True}) + # Run the test against a black image (traditionally fails). + self.Run('run_test', dict(batch_test.items() + [('image', black)]), + {'success': True}) + # Check that the failure exists. + self.Run('failure_exists', batch_test, {'success': True, 'exists': True}) + # Remove the test. + self.Run('remove_test', batch_test, {'success': True}) + # Upload a test with the pinkout workaround (not pink). + self.Run('upload_test_pink_out', + dict(batch_test.items() + + [('images', test_set), ('pink_out', black), + ('RGB', json.dumps([255, 71, 191, 255]))]), + {'success': True}) + # Check that the test exists. + self.Run('test_exists', batch_test, {'success': True}) + # Run the test against a black image (traditionally fails). + self.Run('run_test', dict(batch_test.items() + [('image', black)]), + {'success': True}) + # Check that a failure doesn't exist. + self.Run('failure_exists', batch_test, {'success': True, 'exists': False}) + # Remove the test. + self.Run('remove_test', batch_test, {'success': True}) + # Check that hte test doesn't exist. + self.Run('test_exists', batch_test, {'success': True, 'exists': False}) + # Add a test. + self.Run('upload_test', dict(batch_test.items() + [('images', test_set)]), + {'success': True}) + # At this point the mask is black, add a white image to the mask. + self.Run('add_to_test_mask', dict(batch_test.items() + [('mask', white)]), + {'success': True}) + # Run the test against a black image. + self.Run('run_test', dict(batch_test.items() + [('image', black)]), + {'success': True}) + # Check that a failure doesn't exist (because of the added mask). + self.Run('failure_exists', batch_test, {'success': True, 'exists': False}) + # Remove the test. + self.Run('remove_test', batch_test, {'success': True}) + +if __name__ == '__main__': + unittest.main() diff --git a/chrome/test/functional/ispy/ispy_core/tools/image_tools_unittest.py b/chrome/test/functional/ispy/ispy_core/tools/image_tools_unittest.py index 46f3702a8f..52e4825f3d 100644 --- a/chrome/test/functional/ispy/ispy_core/tools/image_tools_unittest.py +++ b/chrome/test/functional/ispy/ispy_core/tools/image_tools_unittest.py @@ -1,10 +1,14 @@ -# Copyright 2013 The Chromium Authors. All rights reserved. +# Copyright (c) 2013 The Chromium Authors. All rights reserved. # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. import unittest +import sys +import os from PIL import Image -import image_tools + +sys.path.append(os.path.join(os.path.dirname(__file__), os.pardir)) +from tools import image_tools def _GenImage(size, color): diff --git a/chrome/test/functional/ispy/ispy_core/tools/rendering_test_manager.py b/chrome/test/functional/ispy/ispy_core/tools/rendering_test_manager.py index 9fd584c3d8..d5f370cb87 100644 --- a/chrome/test/functional/ispy/ispy_core/tools/rendering_test_manager.py +++ b/chrome/test/functional/ispy/ispy_core/tools/rendering_test_manager.py @@ -4,10 +4,15 @@ """Utilities for managing files in Google Cloud Storage.""" -from tools import image_tools import collections -import posixpath import itertools +import json +import os +import posixpath +import sys + +sys.path.append(os.path.join(os.path.dirname(__file__), os.pardir)) +from tools import image_tools class RenderingTestManager(object): @@ -32,7 +37,8 @@ class RenderingTestManager(object): image: a RGB PIL.Image to be uploaded. """ self.cloud_bucket.UploadFile( - full_path, image_tools.SerializeImage(image), 'image/png') + full_path, image_tools.SerializeImage(image).decode('base64'), + 'image/png') def DownloadImage(self, full_path): """Downloads an image from a location in GCS. @@ -47,27 +53,29 @@ class RenderingTestManager(object): cloud_bucket.NotFoundError: if the path to the image is not valid. """ return image_tools.DeserializeImage( - self.cloud_bucket.DownloadFile(full_path)) + self.cloud_bucket.DownloadFile(full_path).encode('base64')) - def UploadTest(self, test_name, images): + def UploadTest(self, batch_name, test_name, images): """Creates and uploads a test to GCS from a set of images and name. This method generates a mask from the uploaded images, then uploads the mask and first of the images to GCS as a test. Args: + batch_name: the name of the batch. test_name: the name of the test. images: a list of RGB encoded PIL.Images """ - path = posixpath.join('tests', test_name) - mask = image_tools.CreateMask(images) + path = posixpath.join('tests', batch_name, test_name) + mask = image_tools.InflateMask(image_tools.CreateMask(images), 7) self.UploadImage(posixpath.join(path, 'expected.png'), images[0]) self.UploadImage(posixpath.join(path, 'mask.png'), mask) - def RunTest(self, test_name, run_name, actual): + def RunTest(self, batch_name, test_name, actual): """Runs an image comparison, and uploads discrepancies to GCS. Args: + batch_name: the name of the batch. test_name: the name of the test to run. run_name: the name of this run of the test. actual: an RGB-encoded PIL.Image that is the actual result of the @@ -76,15 +84,24 @@ class RenderingTestManager(object): Raises: cloud_bucket.NotFoundError: if the given test_name is not found. """ - path = posixpath.join('failures', test_name, run_name) - test = self.GetTest(test_name) - if not image_tools.SameImage(actual, test.expected): + path = posixpath.join('failures', batch_name, test_name) + test = self.GetTest(batch_name, test_name) + if not image_tools.SameImage(actual, test.expected, mask=test.mask): self.UploadImage(posixpath.join(path, 'actual.png'), actual) - - def GetTest(self, test_name): + diff = image_tools.VisualizeImageDifferences( + test.expected, actual, mask=test.mask) + diff_pxls = sum(1 if pxl == (255, 255, 255, 255) else 0 + for pxl in diff.getdata()) + self.UploadImage(posixpath.join(path, 'diff.png'), diff) + self.cloud_bucket.UploadFile( + posixpath.join(path, 'info.txt'), + json.dumps({'different_pixels': diff_pxls}), 'application/json') + + def GetTest(self, batch_name, test_name): """Returns given test from GCS. Args: + batch_name: the name of the batch. test_name: the name of the test to get from GCS. Returns: @@ -93,71 +110,79 @@ class RenderingTestManager(object): Raises: cloud_bucket.NotFoundError: if the test is not found in GCS. """ - path = posixpath.join('tests', test_name) + path = posixpath.join('tests', batch_name, test_name) Test = collections.namedtuple('Test', ['expected', 'mask']) return Test(self.DownloadImage(posixpath.join(path, 'expected.png')), self.DownloadImage(posixpath.join(path, 'mask.png'))) - def TestExists(self, test_name): + def TestExists(self, batch_name, test_name): """Returns whether the given test exists in GCS. Args: + batch_name: the name of the batch. test_name: the name of the test to look for. Returns: A boolean indicating whether the test exists. """ - path = posixpath.join('tests', test_name) + path = posixpath.join('tests', batch_name, test_name) expected_image_exists = self.cloud_bucket.FileExists( posixpath.join(path, 'expected.png')) mask_image_exists = self.cloud_bucket.FileExists( posixpath.join(path, 'mask.png')) return expected_image_exists and mask_image_exists - def FailureExists(self, test_name, run_name): + def FailureExists(self, batch_name, test_name): """Returns whether the given run exists in GCS. Args: + batch_name: the name of the batch. test_name: the name of the test that failed. run_name: the name of the run that the given test failed on. Returns: A boolean indicating whether the failure exists. """ - failure_path = posixpath.join('failures', test_name, run_name) + failure_path = posixpath.join('failures', batch_name, test_name) actual_image_exists = self.cloud_bucket.FileExists( posixpath.join(failure_path, 'actual.png')) - return self.TestExists(test_name) and actual_image_exists + test_exists = self.TestExists(batch_name, test_name) + info_exists = self.cloud_bucket.FileExists( + posixpath.join(failure_path, 'info.txt')) + return test_exists and actual_image_exists and info_exists - def RemoveTest(self, test_name): + def RemoveTest(self, batch_name, test_name): """Removes a Test from GCS, and all associated failures with that test. Args: + batch_name: the name of the batch. test_name: the name of the test to remove. """ - test_path = posixpath.join('tests', test_name) - failure_path = posixpath.join('failures', test_name) + test_path = posixpath.join('tests', batch_name, test_name) + failure_path = posixpath.join('failures', batch_name, test_name) test_paths = self.cloud_bucket.GetAllPaths(test_path) failure_paths = self.cloud_bucket.GetAllPaths(failure_path) for path in itertools.chain(failure_paths, test_paths): self.cloud_bucket.RemoveFile(path) - def RemoveFailure(self, test_name, run_name): + def RemoveFailure(self, batch_name, test_name): """Removes a failure from GCS. Args: + batch_name: the name of the batch. test_name: the test on which the failure to be removed occured. run_name: the name of the run on the given test that failed. """ - failure_path = posixpath.join('failures', test_name, run_name) + failure_path = posixpath.join('failures', batch_name, test_name) failure_paths = self.cloud_bucket.GetAllPaths(failure_path) for path in failure_paths: self.cloud_bucket.RemoveFile(path) - def GetFailure(self, test_name, run_name): + def GetFailure(self, batch_name, test_name): """Returns a given test failure's expected, diff, and actual images. Args: + batch_name: the name of the batch. test_name: the name of the test the result corresponds to. run_name: the name of the result on the given test. @@ -168,15 +193,17 @@ class RenderingTestManager(object): Raises: cloud_bucket.NotFoundError: if the result is not found in GCS. """ - test_path = posixpath.join('tests', test_name) - failure_path = posixpath.join('failures', test_name, run_name) + test_path = posixpath.join('tests', batch_name, test_name) + failure_path = posixpath.join('failures', batch_name, test_name) expected = self.DownloadImage(posixpath.join(test_path, 'expected.png')) - mask = self.DownloadImage(posixpath.join(test_path, 'mask.png')) actual = self.DownloadImage(posixpath.join(failure_path, 'actual.png')) - diff = image_tools.VisualizeImageDifferences( - expected, actual, mask=mask) - Failure = collections.namedtuple('Failure', ['expected', 'diff', 'actual']) - return Failure(expected, diff, actual) + diff = self.DownloadImage(posixpath.join(failure_path, 'diff.png')) + info = json.loads(self.cloud_bucket.DownloadFile( + posixpath.join(failure_path, 'info.txt'))) + Failure = collections.namedtuple( + 'Failure', + ['expected', 'diff', 'actual', 'info']) + return Failure(expected, diff, actual, info) def GetAllPaths(self, prefix): """Gets urls to all files in GCS whose path starts with a given prefix. diff --git a/chrome/test/functional/ispy/ispy_core/tools/rendering_test_manager_unittest.py b/chrome/test/functional/ispy/ispy_core/tools/rendering_test_manager_unittest.py index 843ab38835..a0661f0a71 100644 --- a/chrome/test/functional/ispy/ispy_core/tools/rendering_test_manager_unittest.py +++ b/chrome/test/functional/ispy/ispy_core/tools/rendering_test_manager_unittest.py @@ -2,10 +2,14 @@ # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. +import os from PIL import Image -import unittest import sets +import sys +import unittest + +sys.path.append(os.path.join(os.path.dirname(__file__), os.pardir)) from tests.rendering_test_manager import mock_cloud_bucket from tests.rendering_test_manager import cloud_bucket from tools import rendering_test_manager @@ -18,9 +22,9 @@ class BucketRenderingTestManagerUnitTest(unittest.TestCase): # Set up structures that will be reused throughout testing. self.bucket = mock_cloud_bucket.MockCloudBucket() self.manager = rendering_test_manager.RenderingTestManager(self.bucket) - self.white = Image.new('RGB', (25, 25), (255, 255, 255)) - self.red = Image.new('RGB', (25, 25), (255, 0, 0)) - self.black = Image.new('RGB', (25, 25), (0, 0, 0)) + self.white = Image.new('RGBA', (25, 25), (255, 255, 255, 255)) + self.red = Image.new('RGBA', (25, 25), (255, 0, 0, 255)) + self.black = Image.new('RGBA', (25, 25), (0, 0, 0, 255)) def testUploadImage(self): self.bucket.Reset() @@ -30,11 +34,11 @@ class BucketRenderingTestManagerUnitTest(unittest.TestCase): self.manager.UploadImage('path/to/red.png', self.red) # Confirm that the images actually got uploaded. self.assertEquals(self.bucket.datastore['path/to/white.png'], - image_tools.SerializeImage(self.white)) + image_tools.SerializeImage(self.white).decode('base64')) self.assertEquals(self.bucket.datastore['path/to/black.png'], - image_tools.SerializeImage(self.black)) + image_tools.SerializeImage(self.black).decode('base64')) self.assertEquals(self.bucket.datastore['path/to/red.png'], - image_tools.SerializeImage(self.red)) + image_tools.SerializeImage(self.red).decode('base64')) def testDownloadImage(self): self.bucket.Reset() @@ -64,39 +68,39 @@ class BucketRenderingTestManagerUnitTest(unittest.TestCase): def testUploadTest(self): self.bucket.Reset() # Upload some tests to the datastore. - self.manager.UploadTest('test1', [self.white, self.black]) - self.manager.UploadTest('test2', [self.black, self.black]) + self.manager.UploadTest('batch', 'test1', [self.white, self.black]) + self.manager.UploadTest('batch', 'test2', [self.black, self.black]) # Confirm that the tests were successfully uploaded. - self.assertEquals(self.bucket.datastore['tests/test1/expected.png'], - image_tools.SerializeImage(self.white)) - self.assertEquals(self.bucket.datastore['tests/test1/mask.png'], - image_tools.SerializeImage(self.white)) - self.assertEquals(self.bucket.datastore['tests/test2/expected.png'], - image_tools.SerializeImage(self.black)) - self.assertEquals(self.bucket.datastore['tests/test2/mask.png'], - image_tools.SerializeImage(self.black)) + self.assertEquals(self.bucket.datastore['tests/batch/test1/expected.png'], + image_tools.SerializeImage(self.white).decode('base64')) + self.assertEquals(self.bucket.datastore['tests/batch/test1/mask.png'], + image_tools.SerializeImage(self.white).decode('base64')) + self.assertEquals(self.bucket.datastore['tests/batch/test2/expected.png'], + image_tools.SerializeImage(self.black).decode('base64')) + self.assertEquals(self.bucket.datastore['tests/batch/test2/mask.png'], + image_tools.SerializeImage(self.black).decode('base64')) def testRunTest(self): self.bucket.Reset() - self.manager.UploadTest('test1', [self.red, self.red]) - self.manager.RunTest('test1', 'run1', self.black) + self.manager.UploadTest('batch', 'test1', [self.red, self.red]) + self.manager.RunTest('batch', 'test1', 'run1', self.black) self.assertEquals( - self.bucket.datastore['failures/test1/run1/actual.png'], - image_tools.SerializeImage(self.black)) - self.manager.RunTest('test1', 'run2', self.red) + self.bucket.datastore['failures/batch/test1/run1/actual.png'], + image_tools.SerializeImage(self.black).decode('base64')) + self.manager.RunTest('batch', 'test1', 'run2', self.red) self.assertFalse( - self.bucket.datastore.has_key('failures/test1/run2/actual.png')) + self.bucket.datastore.has_key('failures/batch/test1/run2/actual.png')) self.assertRaises(cloud_bucket.FileNotFoundError, self.manager.RunTest, - 'test2', 'run1', self.black) + 'batch', 'test2', 'run1', self.black) def testGetTest(self): self.bucket.Reset() # Upload some tests to the datastore - self.manager.UploadTest('test1', [self.white, self.black]) - self.manager.UploadTest('test2', [self.red, self.white]) - test1 = self.manager.GetTest('test1') - test2 = self.manager.GetTest('test2') + self.manager.UploadTest('batch', 'test1', [self.white, self.black]) + self.manager.UploadTest('batch', 'test2', [self.red, self.white]) + test1 = self.manager.GetTest('batch', 'test1') + test2 = self.manager.GetTest('batch', 'test2') # Check that GetTest gets the appropriate tests. self.assertEquals(image_tools.SerializeImage(test1.expected), image_tools.SerializeImage(self.white)) @@ -108,55 +112,55 @@ class BucketRenderingTestManagerUnitTest(unittest.TestCase): image_tools.SerializeImage(self.white)) # Check that GetTest throws an error for a nonexistant test. self.assertRaises( - cloud_bucket.FileNotFoundError, self.manager.GetTest, 'test3') + cloud_bucket.FileNotFoundError, self.manager.GetTest, 'batch', 'test3') def testTestExists(self): self.bucket.Reset() - self.manager.UploadTest('test1', [self.white, self.black]) - self.manager.UploadTest('test2', [self.white, self.black]) - self.assertTrue(self.manager.TestExists('test1')) - self.assertTrue(self.manager.TestExists('test2')) - self.assertFalse(self.manager.TestExists('test3')) + self.manager.UploadTest('batch', 'test1', [self.white, self.black]) + self.manager.UploadTest('batch', 'test2', [self.white, self.black]) + self.assertTrue(self.manager.TestExists('batch', 'test1')) + self.assertTrue(self.manager.TestExists('batch', 'test2')) + self.assertFalse(self.manager.TestExists('batch', 'test3')) def testFailureExists(self): self.bucket.Reset() - self.manager.UploadTest('test1', [self.white, self.white]) - self.manager.RunTest('test1', 'run1', self.black) - self.manager.RunTest('test1', 'run2', self.white) - self.assertTrue(self.manager.FailureExists('test1', 'run1')) - self.assertFalse(self.manager.FailureExists('test1', 'run2')) + self.manager.UploadTest('batch', 'test1', [self.white, self.white]) + self.manager.RunTest('batch', 'test1', 'run1', self.black) + self.manager.RunTest('batch', 'test1', 'run2', self.white) + self.assertTrue(self.manager.FailureExists('batch', 'test1', 'run1')) + self.assertFalse(self.manager.FailureExists('batch', 'test1', 'run2')) def testRemoveTest(self): self.bucket.Reset() - self.manager.UploadTest('test1', [self.white, self.white]) - self.manager.UploadTest('test2', [self.white, self.white]) - self.assertTrue(self.manager.TestExists('test1')) - self.assertTrue(self.manager.TestExists('test2')) - self.manager.RemoveTest('test1') - self.assertFalse(self.manager.TestExists('test1')) - self.assertTrue(self.manager.TestExists('test2')) - self.manager.RemoveTest('test2') - self.assertFalse(self.manager.TestExists('test1')) - self.assertFalse(self.manager.TestExists('test2')) + self.manager.UploadTest('batch', 'test1', [self.white, self.white]) + self.manager.UploadTest('batch', 'test2', [self.white, self.white]) + self.assertTrue(self.manager.TestExists('batch', 'test1')) + self.assertTrue(self.manager.TestExists('batch', 'test2')) + self.manager.RemoveTest('batch', 'test1') + self.assertFalse(self.manager.TestExists('batch', 'test1')) + self.assertTrue(self.manager.TestExists('batch', 'test2')) + self.manager.RemoveTest('batch', 'test2') + self.assertFalse(self.manager.TestExists('batch', 'test1')) + self.assertFalse(self.manager.TestExists('batch', 'test2')) def testRemoveFailure(self): self.bucket.Reset() - self.manager.UploadTest('test1', [self.white, self.white]) - self.manager.UploadTest('test2', [self.white, self.white]) - self.manager.RunTest('test1', 'run1', self.black) - self.manager.RunTest('test1', 'run2', self.black) - self.manager.RemoveFailure('test1', 'run1') - self.assertFalse(self.manager.FailureExists('test1', 'run1')) - self.assertTrue(self.manager.TestExists('test1')) - self.assertTrue(self.manager.FailureExists('test1', 'run2')) - self.assertTrue(self.manager.TestExists('test2')) + self.manager.UploadTest('batch', 'test1', [self.white, self.white]) + self.manager.UploadTest('batch', 'test2', [self.white, self.white]) + self.manager.RunTest('batch', 'test1', 'run1', self.black) + self.manager.RunTest('batch', 'test1', 'run2', self.black) + self.manager.RemoveFailure('batch', 'test1', 'run1') + self.assertFalse(self.manager.FailureExists('batch', 'test1', 'run1')) + self.assertTrue(self.manager.TestExists('batch', 'test1')) + self.assertTrue(self.manager.FailureExists('batch', 'test1', 'run2')) + self.assertTrue(self.manager.TestExists('batch', 'test2')) def testGetFailure(self): self.bucket.Reset() # Upload a result - self.manager.UploadTest('test1', [self.red, self.red]) - self.manager.RunTest('test1', 'run1', self.black) - res = self.manager.GetFailure('test1', 'run1') + self.manager.UploadTest('batch', 'test1', [self.red, self.red]) + self.manager.RunTest('batch', 'test1', 'run1', self.black) + res = self.manager.GetFailure('batch', 'test1', 'run1') # Check that the function correctly got the result. self.assertEquals(image_tools.SerializeImage(res.expected), image_tools.SerializeImage(self.red)) @@ -167,27 +171,27 @@ class BucketRenderingTestManagerUnitTest(unittest.TestCase): # Check that the function raises an error when given non-existant results. self.assertRaises(cloud_bucket.FileNotFoundError, self.manager.GetFailure, - 'test1', 'run2') + 'batch', 'test1', 'run2') self.assertRaises(cloud_bucket.FileNotFoundError, self.manager.GetFailure, - 'test2', 'run1') + 'batch', 'test2', 'run1') def testGetAllPaths(self): self.bucket.Reset() # Upload some tests. - self.manager.UploadTest('test1', [self.white, self.black]) - self.manager.UploadTest('test2', [self.red, self.white]) + self.manager.UploadTest('batch', 'test1', [self.white, self.black]) + self.manager.UploadTest('batch', 'test2', [self.red, self.white]) # Check that the function gets all urls matching the prefix. self.assertEquals( - sets.Set(self.manager.GetAllPaths('tests/test')), - sets.Set(['tests/test1/expected.png', - 'tests/test1/mask.png', - 'tests/test2/expected.png', - 'tests/test2/mask.png'])) - self.assertEquals(sets.Set(self.manager.GetAllPaths('tests/test1')), - sets.Set(['tests/test1/expected.png', - 'tests/test1/mask.png'])) - self.assertEquals(sets.Set(self.manager.GetAllPaths('tests/test3')), + sets.Set(self.manager.GetAllPaths('tests/batch/test')), + sets.Set(['tests/batch/test1/expected.png', + 'tests/batch/test1/mask.png', + 'tests/batch/test2/expected.png', + 'tests/batch/test2/mask.png'])) + self.assertEquals(sets.Set(self.manager.GetAllPaths('tests/batch/test1')), + sets.Set(['tests/batch/test1/expected.png', + 'tests/batch/test1/mask.png'])) + self.assertEquals(sets.Set(self.manager.GetAllPaths('tests/batch/test3')), sets.Set()) diff --git a/chrome/test/functional/ispy/ispy_core/tools/reverse_port_forwarder.py b/chrome/test/functional/ispy/ispy_core/tools/reverse_port_forwarder.py index 289c116ddf..0c45152d4a 100644 --- a/chrome/test/functional/ispy/ispy_core/tools/reverse_port_forwarder.py +++ b/chrome/test/functional/ispy/ispy_core/tools/reverse_port_forwarder.py @@ -14,7 +14,6 @@ sys.path.append(os.path.join(os.path.abspath(os.path.dirname(__file__)), os.pardir, os.pardir, 'build', 'android')) from pylib import android_commands from pylib import forwarder -from pylib import valgrind_tools class ReversePortForwarder(object): @@ -54,16 +53,15 @@ class ReversePortForwarder(object): self._adb = android_commands.AndroidCommands(self._device_serial) self._adb.StartAdbServer() # Begin forwarding the device_ports to the host_ports. - self._forwarder = forwarder.Forwarder(self._cmd, 'Release') - self._forwarder.Run([ - (self._device_http, self._host_http), - (self._device_https, self._host_https)], - valgrind_tools.BaseTool()) + forwarder.Forwarder.Map([(self._device_http, self._host_http), + (self._device_https, self._host_https)], + self._adb, build_type='Release', tool=None) def Stop(self): """Cleans up after the start call by closing the forwarder.""" # shut down the forwarder. - self._forwarder.Close() + forwarder.Forwarder.UnmapDevicePort(self._device_http, self._adb) + forwarder.Forwarder.UnmapDevicePort(self._device_https, self._adb) def GetChromeArgs(self): """Makes a list of arguments to enable reverse port forwarding on chrome. @@ -73,5 +71,5 @@ class ReversePortForwarder(object): """ args = ['testing-fixed-http-port=%s' % self._device_http, 'testing-fixed-https-port=%s' % self._device_https, - 'host-resolver-rules=\'MAP * 127.0.0.1,EXCEPT, localhost\''] + 'host-resolver-rules=\"MAP * 127.0.0.1,EXCEPT, localhost\"'] return args diff --git a/chrome/test/functional/ispy/ispy_core/views/__init__.py b/chrome/test/functional/ispy/ispy_core/views/__init__.py new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/chrome/test/functional/ispy/ispy_core/views/__init__.py diff --git a/chrome/test/functional/ispy/ispy_core/views/debug_view.html b/chrome/test/functional/ispy/ispy_core/views/debug_view.html new file mode 100644 index 0000000000..e5eb1cf8fa --- /dev/null +++ b/chrome/test/functional/ispy/ispy_core/views/debug_view.html @@ -0,0 +1,71 @@ +<html> + <head> + <style> + .hidden { + display: none; + } + </style> + <script> + // Set up a getImageData function. + var getImageData = function(id) { + // Set up an image with the src from the img_id. + var image = new Image(); + image.src = document.getElementById(id).src; + // Create a canvas in memeory to hold the image. + var mem_canvas = document.createElement('canvas'); + mem_canvas.setAttribute('width', image.width.toString()); + mem_canvas.setAttribute('height', image.height.toString()); + var mem_context = mem_canvas.getContext('2d'); + // Draw the image into the canvas. + mem_context.drawImage(image, 0, 0); + // Extract the image from the canvas in editable form. + return mem_context.getImageData(0, 0, image.width, image.height); + }; + // Set up a function to generate the image to be displayed. + var computeDiffAlpha = function(expected, diff, actual) { + // Go through all of the pixels in the diff. + for(var i=0; i<diff.data.length; i += 4) { + // If the pixel in the diff isn't white, use the expected pixel. + if ((diff.data[i] == 255 && diff.data[i + 1] == 255 && + diff.data[i + 2] == 255 && diff.data[i + 3] == 255)) { + // If the diff pixel is not white, it should not be seen. + expected.data[i] = actual.data[i]; + expected.data[i + 1] = actual.data[i + 1]; + expected.data[i + 2] = actual.data[i + 2]; + expected.data[i + 3] = actual.data[i + 3]; + } + // Otherwise don't touch the actual_data pixel. + } + // Return the modified pixel data. + return expected; + }; + // Set everything up to run after the document loads. + var loader = function() { + var canvas = document.getElementById('image'); + var context = canvas.getContext('2d'); + var expected = getImageData('expected'); + var diff = getImageData('diff'); + var debug = computeDiffAlpha(getImageData('expected'), diff, getImageData('actual')); + var state = 'expected'; + canvas.width = diff.width; + canvas.height = diff.height; + var swapper = setInterval(function() { + if (state === 'expected') { + state = 'debug'; + context.putImageData(expected, 0, 0); + } + else { + state = 'expected'; + context.putImageData(debug, 0, 0); + } + }, 1000); + }; + </script> + </head> + <body onload="loader();"> + <img class='hidden' id='expected' src="{{ expected }}"/> + <img class='hidden' id='diff' src="{{ diff }}"/> + <img class='hidden' id='actual' src="{{ actual }}"/> + <canvas id='image'></canvas> + </body> +</html> diff --git a/chrome/test/functional/ispy/ispy_core/views/diff_view.html b/chrome/test/functional/ispy/ispy_core/views/diff_view.html new file mode 100644 index 0000000000..6cf10835da --- /dev/null +++ b/chrome/test/functional/ispy/ispy_core/views/diff_view.html @@ -0,0 +1,62 @@ +<!DOCTYPE html> +{% autoescape true %} +<html> + <head> + <style> + #container { + display: table; + background-color:#A8A8A8; + border:2px solid black; + } + #row { + display: table-row; + } + #left, #right, #middle { + display: table-cell; + padding-right: 25px; + padding-left: 25px; + } + #left p, #right p, #middle p { + margin: 1px 1px; + } + #title { + display: block; + text-align: center; + } + #info-box { + background-color:#A8A8A8; + border:2px solid black; + display: block; + width: 400px; + } + .image { + width: 300px; + } + </style> + </head> + <body> + <span id="info-box"> Device: {{ device }}<br> + Site: {{ site }}<br> + Device_ID: {{ device_id }}</span> + <form action="/update_mask?{{ encoded_parameters }}" method="post"> + <input type="submit" value="Ignore Diff"/> + </form> + <div id="container"> + <div id="row"> + <div id="left"> + <span id="title">Expected</span><br> + <img class="image" src="{{ expected }}"> + </div> + <div id="center"> + <span id="title">Diff</span><br> + <img class="image" src="{{ diff }}"> + </div> + <div id="right"> + <span id="title">Actual</span><br> + <img class="image" src="{{ actual }}"> + </div> + </div> + </div> + </body> +</html> +{% endautoescape %} diff --git a/chrome/test/functional/ispy/ispy_core/views/list_view.html b/chrome/test/functional/ispy/ispy_core/views/list_view.html new file mode 100644 index 0000000000..fdbcab0044 --- /dev/null +++ b/chrome/test/functional/ispy/ispy_core/views/list_view.html @@ -0,0 +1,36 @@ +<!DOCTYPE html> +{% autoescape on %} +<html> + <head> + <style> + #container { + display: table; + background-color:#A8A8A8; + border:2px solid black; + width: 600px + } + #row { + display: table-row; + border 1px solid gray; + width: 400px; + height: 25px; + border: 2px solid black; + } + #col { + display: table-cell; + padding-right: 25px; + padding-left: 25px; + } + </style> + </head> + <body> + <div id="container"> + {% for link in links %} + <span id="row"> + <a href="{{ link[1] }}">{{ link[0] }}</a> + </span> + {% endfor %} + </div> + </body> +</html> +{% endautoescape %} diff --git a/chrome/test/functional/ispy/ispy_core/views/main_view.html b/chrome/test/functional/ispy/ispy_core/views/main_view.html new file mode 100644 index 0000000000..cd61d0c019 --- /dev/null +++ b/chrome/test/functional/ispy/ispy_core/views/main_view.html @@ -0,0 +1,57 @@ +<!DOCTYPE html> +<html> + <head> + <style> + .image { + height: 325px; + } + .cell { + padding-right: 25px; + padding-left: 25px; + float: left; + } + .info { + background-color:#A8A8A8; + width: 300px; + } + .row { + padding-top: 10px; + padding-bottom: 10px; + border-bottom: 2px solid black; + height: 350px; + } + </style> + </head> + <body> + {% for comp in comparisons %} + <div class="row"> + <div class="cell"> + Diff<br> + <img class="image" src={{ comp['diff'] }}> + </div> + <div class="cell"> + Expected<br> + <img class="image" src={{ comp['expected'] }}> + </div> + <div class="cell"> + Actual<br> + <img class="image" src={{ comp['actual'] }}> + </div> + <div class="cell"> + <br> + <div class="info"> + Test Name: {{ comp['test_name'] }}<br> + Run Name: {{ comp['run_name'] }} + </div> + <form action="/update_mask" method="post"> + <input type="hidden" name="batch_name" value="{{ comp['batch_name'] }}"/> + <input type="hidden" name="test_name" value="{{ comp['test_name'] }}"/> + <input type="hidden" name="run_name" value="{{ comp['run_name'] }}"/> + <input type="submit" value="Ignore Diff"/> + </form> + <a href='/debug_view?diff={{ comp['diff_path'] }}&actual={{ comp['actual_path'] }}&expected={{ comp['expected_path'] }}'>Debug View</a> + </div> + </div> + {% endfor %} + </body> +</html> diff --git a/chrome/test/functional/media_stream_infobar.py b/chrome/test/functional/media_stream_infobar.py deleted file mode 100755 index 686882af6a..0000000000 --- a/chrome/test/functional/media_stream_infobar.py +++ /dev/null @@ -1,83 +0,0 @@ -#!/usr/bin/env python -# Copyright (c) 2012 The Chromium Authors. All rights reserved. -# Use of this source code is governed by a BSD-style license that can be -# found in the LICENSE file. - -import pyauto_functional -import pyauto -import webrtc_test_base - - -class MediaStreamInfobarTest(webrtc_test_base.WebrtcTestBase): - """Performs basic tests on the media stream infobar. - - This infobar is used to grant or deny access to WebRTC capabilities for a - webpage. If a page calls the getUserMedia function the infobar will ask the - user if it is OK for the webpage to use the webcam or microphone on the user's - machine. These tests ensure that the infobar works as intended. - """ - - def ExtraChromeFlags(self): - """Adds flags to the Chrome command line.""" - extra_flags = ['--enable-media-stream'] - return pyauto.PyUITest.ExtraChromeFlags(self) + extra_flags - - def testAllowingUserMedia(self): - """Test that selecting 'accept' gives us a media stream. - - When the user clicks allow, the javascript should have the success callback - called with a media stream. - """ - self.assertEquals('ok-got-stream', - self._TestGetUserMedia(with_action='accept')) - - def testDenyingUserMedia(self): - """Tests that selecting 'cancel' actually denies access to user media. - - When the user clicks deny in the user media bar, the javascript should have - the error callback called with an error specification instead of the success - callback with a media stream. This is important since the user should be - able to deny the javascript to access the webcam. - """ - # Error 1 = Permission denied - self.assertEquals('failed-with-error-PERMISSION_DENIED', - self._TestGetUserMedia(with_action='cancel')) - - def testDismissingUserMedia(self): - """Dismiss should be treated just like deny, which is described above.""" - # Error 1 = Permission denied - self.assertEquals('failed-with-error-PERMISSION_DENIED', - self._TestGetUserMedia(with_action='dismiss')) - - def testConsecutiveGetUserMediaCalls(self): - """Ensures we deal appropriately with several consecutive requests.""" - self.assertEquals('failed-with-error-PERMISSION_DENIED', - self._TestGetUserMedia(with_action='dismiss')) - self.assertEquals('failed-with-error-PERMISSION_DENIED', - self._TestGetUserMedia(with_action='cancel')) - self.assertEquals('ok-got-stream', - self._TestGetUserMedia(with_action='accept')) - self.assertEquals('failed-with-error-PERMISSION_DENIED', - self._TestGetUserMedia(with_action='cancel')) - self.assertEquals('ok-got-stream', - self._TestGetUserMedia(with_action='accept')) - self.assertEquals('failed-with-error-PERMISSION_DENIED', - self._TestGetUserMedia(with_action='dismiss')) - - def _TestGetUserMedia(self, with_action): - """Runs getUserMedia in the test page and returns the result.""" - url = self.GetFileURLForDataPath('webrtc', 'webrtc_jsep01_test.html') - self.NavigateToURL(url) - - self.assertEquals('ok-requested', self.ExecuteJavascript( - 'getUserMedia("{ audio: true, video: true, }")')) - - self.WaitForInfobarCount(1) - self.PerformActionOnInfobar(with_action, infobar_index=0) - self.WaitForGetUserMediaResult(tab_index=0) - - return self.GetUserMediaResult(tab_index=0) - - -if __name__ == '__main__': - pyauto_functional.Main() diff --git a/chrome/test/functional/webrtc_apprtc_call.py b/chrome/test/functional/webrtc_apprtc_call.py deleted file mode 100755 index 986fa21554..0000000000 --- a/chrome/test/functional/webrtc_apprtc_call.py +++ /dev/null @@ -1,71 +0,0 @@ -#!/usr/bin/env python -# Copyright (c) 2012 The Chromium Authors. All rights reserved. -# Use of this source code is governed by a BSD-style license that can be -# found in the LICENSE file. - -import random -import time - -# Note: pyauto_functional must come before pyauto. -import pyauto_functional -import pyauto -import webrtc_test_base - - -class WebrtcApprtcCallTest(webrtc_test_base.WebrtcTestBase): - """Tests calling apprtc.appspot.com and setting up a call. - - Prerequisites: This test case must run on a machine with a webcam, either - fake or real, and with some kind of audio device. The machine must have access - to the public Internet. - - This should be considered an integration test: test failures could mean - that the AppRTC reference is broken, that WebRTC is broken, or both. - """ - - def tearDown(self): - pyauto.PyUITest.tearDown(self) - self.assertEquals('', self.CheckErrorsAndCrashes(), - 'Chrome crashed or hit a critical error during test.') - - def testApprtcLoopbackCall(self): - self.NavigateToURL('http://apprtc.appspot.com/?debug=loopback') - self.WaitForInfobarCount(1, tab_index=0) - self.PerformActionOnInfobar('accept', infobar_index=0, tab_index=0) - - self._WaitForCallEstablishment(tab_index=0) - - def testApprtcTabToTabCall(self): - # Randomize the call session id. If we would use the same id we would risk - # getting problems with hung calls and lingering state in AppRTC. - random_call_id = 'pyauto%d' % random.randint(0, 65536) - apprtc_url = 'http://apprtc.appspot.com/?r=%s' % random_call_id - - self.NavigateToURL(apprtc_url) - self.AppendTab(pyauto.GURL(apprtc_url)) - - self.WaitForInfobarCount(1, tab_index=0) - self.WaitForInfobarCount(1, tab_index=1) - - self.PerformActionOnInfobar('accept', infobar_index=0, tab_index=0) - # TODO(phoglund): workaround for - # https://code.google.com/p/webrtc/issues/detail?id=1742 - time.sleep(1) - self.PerformActionOnInfobar('accept', infobar_index=0, tab_index=1) - - self._WaitForCallEstablishment(tab_index=0) - self._WaitForCallEstablishment(tab_index=1) - - def _WaitForCallEstablishment(self, tab_index): - # AppRTC will set opacity to 1 for remote video when the call is up. - video_playing = self.WaitUntil( - function=lambda: self.GetDOMValue('remoteVideo.style.opacity', - tab_index=tab_index), - expect_retval='1') - self.assertTrue(video_playing, - msg=('Timed out while waiting for ' - 'remoteVideo.style.opacity to return 1.')) - - -if __name__ == '__main__': - pyauto_functional.Main() diff --git a/chrome/test/functional/webrtc_audio_quality.py b/chrome/test/functional/webrtc_audio_quality.py deleted file mode 100755 index 8e1cff4560..0000000000 --- a/chrome/test/functional/webrtc_audio_quality.py +++ /dev/null @@ -1,184 +0,0 @@ -#!/usr/bin/env python -# Copyright (c) 2013 The Chromium Authors. All rights reserved. -# Use of this source code is governed by a BSD-style license that can be -# found in the LICENSE file. - -import os -import sys -import tempfile -import time - -import media.audio_tools as audio_tools - -# Note: pyauto_functional must come before pyauto. -import pyauto_functional -import pyauto -import pyauto_utils -import webrtc_test_base - -_MEDIA_PATH = os.path.abspath(os.path.join(pyauto.PyUITest.DataDir(), - 'pyauto_private', 'webrtc')) -if 'win32' in sys.platform: - _REFERENCE_FILE = os.path.join(_MEDIA_PATH, 'human-voice-win.wav') -else: - _REFERENCE_FILE = os.path.join(_MEDIA_PATH, 'human-voice-linux.wav') -_JAVASCRIPT_PATH = os.path.abspath(os.path.join(pyauto.PyUITest.DataDir(), - 'webrtc')) - - -class WebrtcAudioQualityTest(webrtc_test_base.WebrtcTestBase): - """Test we can set up a WebRTC call and play audio through it. - - This test will only work on machines that have been configured to record their - own input*. - - * On Linux: - 1. # sudo apt-get install pavucontrol - 2. For the user who will run the test: # pavucontrol - 3. In a separate terminal, # arecord dummy - 4. In pavucontrol, go to the recording tab. - 5. For the ALSA plug-in [aplay]: ALSA Capture from, change from <x> to - <Monitor of x>, where x is whatever your primary sound device is called. - 6. Try launching chrome as the target user on the target machine, try - playing, say, a YouTube video, and record with # arecord -f dat mine.dat. - Verify the recording with aplay (should have recorded what you played - from chrome). - - * On Windows 7: - 1. Control panel > Sound > Manage audio devices. - 2. In the recording tab, right-click in an empty space in the pane with the - devices. Tick 'show disabled devices'. - 3. You should see a 'stero mix' device - this is what your speakers output. - Right click > Properties. - 4. In the Listen tab for the mix device, check the 'listen to this device' - checkbox. Ensure the mix device is the default recording device. - 5. Launch chrome and try playing a video with sound. You should see movement - in the volume meter for the mix device. Configure the mix device to have - 50 / 100 in level. Also go into the playback tab, right-click Speakers, - and set that level to 50 / 100. Otherwise you will get distortion in - the recording. - """ - def setUp(self): - pyauto.PyUITest.setUp(self) - self.StartPeerConnectionServer() - - def tearDown(self): - self.StopPeerConnectionServer() - - pyauto.PyUITest.tearDown(self) - self.assertEquals('', self.CheckErrorsAndCrashes()) - - def testWebrtcAudioCallAndMeasureQuality(self): - """Measures how much WebRTC distorts speech. - - The input file is about 9.3 seconds long and has had silence trimmed on both - sides. We will set up a WebRTC call, load the file with WebAudio in the - javascript, connect the WebAudio buffer node to the peer connection and play - it out on the other side (in a video tag). - - We originally got the input file by playing a file through this test and - using the resulting file. The purpose is to lessen the impact on the score - from known distortions such as comfort noise. You can do such a rebase on - the _REFERENCE_FILE by setting REBASE=1 before running the test. The file - will end up in the system temp folder and will end with _webrtc.wav. - - We then record what Chrome plays out. We give it plenty of time to play - the whole file over the connection, and then we trim silence on both ends. - That is finally fed into PESQ for comparison. - """ - # We'll use a relative path since the javascript will be loading the file - # relative to where the javascript itself is. - self.assertTrue(os.path.exists(_MEDIA_PATH), - msg='Missing pyauto_private in chrome/test/data: you need ' - 'to check out src_internal in your .gclient to run ' - 'this test.') - - input_relative_path = os.path.relpath(_REFERENCE_FILE, _JAVASCRIPT_PATH) - - def CallWithWebAudio(): - self._AudioCallWithWebAudio(duration_seconds=15, - input_relative_path=input_relative_path) - - def MeasureQuality(output_no_silence): - results = audio_tools.RunPESQ(_REFERENCE_FILE, output_no_silence, - sample_rate=16000) - self.assertTrue(results, msg=('Failed to compute PESQ (most likely, we ' - 'recorded only silence)')) - pyauto_utils.PrintPerfResult('audio_pesq', 'raw_mos', results[0], 'score') - pyauto_utils.PrintPerfResult('audio_pesq', 'mos_lqo', results[1], 'score') - - self._RecordAndVerify(record_duration_seconds=20, - sound_producing_function=CallWithWebAudio, - verification_function=MeasureQuality) - - def _AudioCallWithWebAudio(self, duration_seconds, input_relative_path): - self.LoadTestPageInTwoTabs(test_page='webrtc_audio_quality_test.html'); - - self.Connect('user_1', tab_index=0) - self.Connect('user_2', tab_index=1) - - self.CreatePeerConnection(tab_index=0) - self.AddWebAudioFile(tab_index=0, input_relative_path=input_relative_path) - - self.EstablishCall(from_tab_with_index=0, to_tab_with_index=1) - - # Note: the media flow isn't necessarily established on the connection just - # because the ready state is ok on both sides. We sleep a bit between call - # establishment and playing to avoid cutting of the beginning of the audio - # file. - time.sleep(2) - self.PlayWebAudioFile(tab_index=0) - - # Keep the call up while we detect audio. - time.sleep(duration_seconds) - - # The hang-up will automatically propagate to the second tab. - self.HangUp(from_tab_with_index=0) - self.WaitUntilHangUpVerified(tab_index=1) - - self.Disconnect(tab_index=0) - self.Disconnect(tab_index=1) - - # Ensure we didn't miss any errors. - self.AssertNoFailures(tab_index=0) - self.AssertNoFailures(tab_index=1) - - def _RecordAndVerify(self, record_duration_seconds, sound_producing_function, - verification_function): - audio_tools.ForceMicrophoneVolumeTo100Percent() - rebase = 'REBASE' in os.environ - - # The two temp files that will be potentially used in the test. - temp_file = None - file_no_silence = None - try: - temp_file = self._CreateTempFile() - record_thread = audio_tools.AudioRecorderThread(record_duration_seconds, - temp_file, - record_mono=True) - record_thread.start() - sound_producing_function() - record_thread.join() - - if record_thread.error: - self.fail(record_thread.error) - file_no_silence = self._CreateTempFile() - audio_tools.RemoveSilence(temp_file, file_no_silence) - - verification_function(file_no_silence) - finally: - # Delete the temporary files used by the test. - if temp_file: - os.remove(temp_file) - if file_no_silence and not rebase: - os.remove(file_no_silence) - - def _CreateTempFile(self): - """Returns an absolute path to an empty temp file.""" - file_handle, path = tempfile.mkstemp(suffix='_webrtc.wav') - os.close(file_handle) - return path - - -if __name__ == '__main__': - pyauto_functional.Main()
\ No newline at end of file diff --git a/chrome/test/functional/webrtc_brutality_test.py b/chrome/test/functional/webrtc_brutality_test.py deleted file mode 100755 index b5852e2124..0000000000 --- a/chrome/test/functional/webrtc_brutality_test.py +++ /dev/null @@ -1,73 +0,0 @@ -#!/usr/bin/env python -# Copyright (c) 2012 The Chromium Authors. All rights reserved. -# Use of this source code is governed by a BSD-style license that can be -# found in the LICENSE file. - -import pyauto_functional -import webrtc_test_base - - -class WebrtcBrutalityTest(webrtc_test_base.WebrtcTestBase): - """Tests how WebRTC deals with inconvenient reloads, etc.""" - - def testReloadsAfterGetUserMedia(self): - """Tests how we deal with reloads. - - This test will quickly reload the page after running getUserMedia, which - will remove the pending request. This crashed the browser before the fix - for crbug.com/135043. - - The test will make repeated getUserMedia requests with refreshes between - them. Sometimes it will click past the bar and then refresh. - """ - if self.PlatformIsWinXP(): - print 'Skipping this test on Windows XP due to flakiness.' - return - self.LoadTestPageInOneTab() - for i in range(1, 100): - if i % 10 == 0: - self.GetUserMedia(tab_index=0, action='accept') - else: - self._GetUserMediaWithoutTakingAction(tab_index=0) - self.ReloadTab(tab_index=0) - - def testRepeatedGetUserMediaRequests(self): - """Tests how we deal with lots of consecutive getUserMedia requests. - - The test will alternate unanswered requests with requests that get answered. - """ - if self.PlatformIsWinXP(): - print 'Skipping this test on Windows XP due to flakiness.' - return - self.LoadTestPageInOneTab() - for i in range(1, 100): - if i % 10 == 0: - self.GetUserMedia(tab_index=0, action='accept') - else: - self._GetUserMediaWithoutTakingAction(tab_index=0) - - def testSuccessfulGetUserMediaAndThenReload(self): - """Waits for WebRTC to respond, and immediately reloads the tab.""" - self.LoadTestPageInOneTab() - self.GetUserMedia(tab_index=0, action='accept') - self.ReloadTab(tab_index=0) - - def testClosingTabAfterGetUserMedia(self): - """Tests closing the tab right after a getUserMedia call.""" - self.LoadTestPageInOneTab() - self._GetUserMediaWithoutTakingAction(tab_index=0) - self.CloseTab(tab_index=0) - - def testSuccessfulGetUserMediaAndThenClose(self): - """Waits for WebRTC to respond, and closes the tab.""" - self.LoadTestPageInOneTab() - self.GetUserMedia(tab_index=0, action='accept') - self.CloseTab(tab_index=0) - - def _GetUserMediaWithoutTakingAction(self, tab_index): - self.assertEquals('ok-requested', self.ExecuteJavascript( - 'getUserMedia("{ audio: true, video: true, }")', tab_index=0)) - - -if __name__ == '__main__': - pyauto_functional.Main() diff --git a/chrome/test/functional/webrtc_call.py b/chrome/test/functional/webrtc_call.py deleted file mode 100755 index 8c54b114d5..0000000000 --- a/chrome/test/functional/webrtc_call.py +++ /dev/null @@ -1,224 +0,0 @@ -#!/usr/bin/env python -# Copyright (c) 2012 The Chromium Authors. All rights reserved. -# Use of this source code is governed by a BSD-style license that can be -# found in the LICENSE file. - -import time - -# This little construct ensures we can run even if we have a bad version of -# psutil installed. If so, we'll just skip the test that needs it. -_HAS_CORRECT_PSUTIL_VERSION = False -try: - import psutil - if 'version_info' in dir(psutil): - # If psutil has any version info at all, it's recent enough. - _HAS_CORRECT_PSUTIL_VERSION = True -except ImportError, e: - pass - - -# Note: pyauto_functional must come before pyauto. -import pyauto_functional -import pyauto -import pyauto_utils -import webrtc_test_base - - -class WebrtcCallTest(webrtc_test_base.WebrtcTestBase): - """Test we can set up a WebRTC call and disconnect it. - - Prerequisites: This test case must run on a machine with a webcam, either - fake or real, and with some kind of audio device. You must make the - peerconnection_server target before you run. - - The test case will launch a custom binary - (peerconnection_server) which will allow two WebRTC clients to find each - other. For more details, see the source code which is available at the site - http://code.google.com/p/libjingle/source/browse/ (make sure to browse to - trunk/talk/examples/peerconnection/server). - """ - - def setUp(self): - pyauto.PyUITest.setUp(self) - self.StartPeerConnectionServer() - - def tearDown(self): - self.StopPeerConnectionServer() - - pyauto.PyUITest.tearDown(self) - self.assertEquals('', self.CheckErrorsAndCrashes()) - - def _SimpleWebrtcCall(self, request_video, request_audio, duration_seconds=0): - """Tests we can call and hang up with WebRTC. - - This test exercises pretty much the whole happy-case for the WebRTC - JavaScript API. Currently, it exercises a normal call setup using the API - defined at http://dev.w3.org/2011/webrtc/editor/webrtc.html. The API is - still evolving. - - The test will load the supplied HTML file, which in turn will load different - javascript files depending on which version of the signaling protocol - we are running. - The supplied HTML file will be loaded in two tabs and tell the web - pages to start up WebRTC, which will acquire video and audio devices on the - system. This will launch a dialog in Chrome which we click past using the - automation controller. Then, we will order both tabs to connect the server, - which will make the two tabs aware of each other. Once that is done we order - one tab to call the other. - - We make sure that the javascript tells us that the call succeeded, lets it - run for a while and try to hang up the call after that. We verify video is - playing by using the video detector. - - Args: - request_video: Whether to request video. - request_audio: Whether to request audio. - duration_seconds: The number of seconds to keep the call up before - shutting it down. - """ - self._SetupCall(request_video=request_video, request_audio=request_audio) - - if duration_seconds: - print 'Call up: sleeping %d seconds...' % duration_seconds - time.sleep(duration_seconds); - - # The hang-up will automatically propagate to the second tab. - self.HangUp(from_tab_with_index=0) - self.WaitUntilHangUpVerified(tab_index=1) - - self.Disconnect(tab_index=0) - self.Disconnect(tab_index=1) - - # Ensure we didn't miss any errors. - self.AssertNoFailures(tab_index=0) - self.AssertNoFailures(tab_index=1) - - def testWebrtcCall(self): - self.LoadTestPageInTwoTabs() - self._SimpleWebrtcCall(request_video=True, request_audio=True) - - def testWebrtcVideoOnlyCall(self): - self.LoadTestPageInTwoTabs() - self._SimpleWebrtcCall(request_video=True, request_audio=False) - - def testWebrtcAudioOnlyCall(self): - self.LoadTestPageInTwoTabs() - self._SimpleWebrtcCall(request_video=False, request_audio=True) - - def testWebrtcJsep01CallAndMeasureCpu20Seconds(self): - if not _HAS_CORRECT_PSUTIL_VERSION: - print ('WARNING: Can not run cpu/mem measurements with this version of ' - 'psutil. You must have at least psutil 0.4.1 installed for the ' - 'version of python you are running this test with.') - return - - self.LoadTestPageInTwoTabs(test_page='webrtc_jsep01_test.html') - - # Prepare CPU measurements. - renderer_process = self._GetChromeRendererProcess(tab_index=0) - renderer_process.get_cpu_percent() - - self._SimpleWebrtcCall(request_video=True, - request_audio=True, - duration_seconds=20) - - cpu_usage = renderer_process.get_cpu_percent(interval=0) - mem_usage_bytes = renderer_process.get_memory_info()[0] - mem_usage_kb = float(mem_usage_bytes) / 1024 - pyauto_utils.PrintPerfResult('cpu', 'jsep01_call', cpu_usage, '%') - pyauto_utils.PrintPerfResult('memory', 'jsep01_call', mem_usage_kb, 'KiB') - - def testLocalPreview(self): - """Brings up a local preview and ensures video is playing. - - This test will launch a window with a single tab and run a getUserMedia call - which will give us access to the webcam and microphone. Then the javascript - code will hook up the webcam data to the local-view video tag. We will - detect video in that tag using the video detector, and if we see video - moving the test passes. - """ - self.LoadTestPageInOneTab() - self.assertEquals('ok-got-stream', self.GetUserMedia(tab_index=0)) - self._StartDetectingVideo(tab_index=0, video_element='local-view') - - self._WaitForVideo(tab_index=0, expect_playing=True) - - def testHandlesNewGetUserMediaRequestSeparately(self): - """Ensures WebRTC doesn't allow new requests to piggy-back on old ones.""" - self.LoadTestPageInTwoTabs() - - self.GetUserMedia(tab_index=0) - self.GetUserMedia(tab_index=1) - self.Connect("user_1", tab_index=0) - self.Connect("user_2", tab_index=1) - - self.CreatePeerConnection(tab_index=0) - self.AddUserMediaLocalStream(tab_index=0) - self.EstablishCall(from_tab_with_index=0, to_tab_with_index=1) - - self.assertEquals('failed-with-error-PERMISSION_DENIED', - self.GetUserMedia(tab_index=0, action='cancel')) - self.assertEquals('failed-with-error-PERMISSION_DENIED', - self.GetUserMedia(tab_index=0, action='dismiss')) - - def _SetupCall(self, request_video, request_audio): - """Gets user media and establishes a call. - - Assumes that two tabs are already opened with a suitable test page. - - Args: - request_video: Whether to request video. - request_audio: Whether to request audio. - """ - self.assertEquals('ok-got-stream', self.GetUserMedia( - tab_index=0, request_video=request_video, request_audio=request_audio)) - self.assertEquals('ok-got-stream', self.GetUserMedia( - tab_index=1, request_video=request_video, request_audio=request_audio)) - self.Connect('user_1', tab_index=0) - self.Connect('user_2', tab_index=1) - - self.CreatePeerConnection(tab_index=0) - self.AddUserMediaLocalStream(tab_index=0) - self.EstablishCall(from_tab_with_index=0, to_tab_with_index=1) - - if request_video: - self._StartDetectingVideo(tab_index=0, video_element='remote-view') - self._StartDetectingVideo(tab_index=1, video_element='remote-view') - - self._WaitForVideo(tab_index=0, expect_playing=True) - self._WaitForVideo(tab_index=1, expect_playing=True) - - def _StartDetectingVideo(self, tab_index, video_element): - self.assertEquals('ok-started', self.ExecuteJavascript( - 'startDetection("%s", "frame-buffer", 320, 240)' % video_element, - tab_index=tab_index)); - - def _WaitForVideo(self, tab_index, expect_playing): - # TODO(phoglund): Remove this hack if we manage to get a more stable Linux - # bot to run these tests. - if self.IsLinux(): - print "Linux; pretending to wait for video..." - time.sleep(1) - return - - expect_retval='video-playing' if expect_playing else 'video-not-playing' - - video_playing = self.WaitUntil( - function=lambda: self.ExecuteJavascript('isVideoPlaying()', - tab_index=tab_index), - expect_retval=expect_retval) - self.assertTrue(video_playing, - msg= 'Timed out while waiting for isVideoPlaying to ' + - 'return ' + expect_retval + '.') - - def _GetChromeRendererProcess(self, tab_index): - """Returns the Chrome renderer process as a psutil process wrapper.""" - tab_info = self.GetBrowserInfo()['windows'][0]['tabs'][tab_index] - renderer_id = tab_info['renderer_pid'] - if not renderer_id: - self.fail('Can not find the tab renderer process.') - return psutil.Process(renderer_id) - - -if __name__ == '__main__': - pyauto_functional.Main() diff --git a/chrome/test/functional/webrtc_test_base.py b/chrome/test/functional/webrtc_test_base.py deleted file mode 100755 index 973691c5b8..0000000000 --- a/chrome/test/functional/webrtc_test_base.py +++ /dev/null @@ -1,215 +0,0 @@ -#!/usr/bin/env python -# Copyright (c) 2012 The Chromium Authors. All rights reserved. -# Use of this source code is governed by a BSD-style license that can be -# found in the LICENSE file. - -import os -import platform -import re -import subprocess - -import pyauto - - -class MissingRequiredBinaryException(Exception): - pass - - -class WebrtcTestBase(pyauto.PyUITest): - """This base class provides helpers for WebRTC calls.""" - - DEFAULT_TEST_PAGE = 'webrtc_jsep01_test.html' - - def ExtraChromeFlags(self): - """Adds flags to the Chrome command line.""" - extra_flags = ['--enable-data-channels', '--enable-dcheck'] - return pyauto.PyUITest.ExtraChromeFlags(self) + extra_flags - - def LoadTestPageInTwoTabs(self, test_page=DEFAULT_TEST_PAGE): - url = self.GetFileURLForDataPath('webrtc', test_page) - self.NavigateToURL(url) - self.AppendTab(pyauto.GURL(url)) - - def LoadTestPageInOneTab(self, test_page=DEFAULT_TEST_PAGE): - url = self.GetFileURLForDataPath('webrtc', test_page) - self.NavigateToURL(url) - - def GetUserMedia(self, tab_index, action='accept', - request_video=True, request_audio=True): - """Acquires webcam or mic for one tab and returns the result. - - Args: - tab_index: The tab to request user media on. - action: The action to take on the info bar. Can be 'accept', 'cancel' or - 'dismiss'. - request_video: Whether to request video. - request_audio: Whether to request audio. - - Returns: - A string as specified by the getUserMedia javascript function. - """ - constraints = '{ video: %s, audio: %s }' % (str(request_video).lower(), - str(request_audio).lower()) - self.assertEquals('ok-requested', self.ExecuteJavascript( - 'getUserMedia("%s")' % constraints, tab_index=tab_index)) - - self.WaitForInfobarCount(1, tab_index=tab_index) - self.PerformActionOnInfobar(action, infobar_index=0, tab_index=tab_index) - self.WaitForGetUserMediaResult(tab_index=0) - - result = self.GetUserMediaResult(tab_index=0) - self.AssertNoFailures(tab_index) - return result - - def WaitForGetUserMediaResult(self, tab_index): - """Waits until WebRTC has responded to a getUserMedia query. - - Fails an assert if WebRTC doesn't respond within the default timeout. - - Args: - tab_index: the tab to query. - """ - def HasResult(): - return self.GetUserMediaResult(tab_index) != 'not-called-yet' - self.assertTrue(self.WaitUntil(HasResult), - msg='Timed out while waiting for getUserMedia callback.') - - def GetUserMediaResult(self, tab_index): - """Retrieves WebRTC's answer to a user media query. - - Args: - tab_index: the tab to query. - - Returns: - Specified in obtainGetUserMediaResult() in getusermedia.js. - """ - return self.ExecuteJavascript( - 'obtainGetUserMediaResult()', tab_index=tab_index) - - def AssertNoFailures(self, tab_index): - """Ensures the javascript hasn't registered any asynchronous errors. - - Args: - tab_index: The tab to check. - """ - self.assertEquals('ok-no-errors', self.ExecuteJavascript( - 'getAnyTestFailures()', tab_index=tab_index)) - - def Connect(self, user_name, tab_index): - self.assertEquals('ok-connected', self.ExecuteJavascript( - 'connect("http://localhost:8888", "%s")' % user_name, - tab_index=tab_index)) - self.AssertNoFailures(tab_index) - - def CreatePeerConnection(self, tab_index): - self.assertEquals('ok-peerconnection-created', self.ExecuteJavascript( - 'preparePeerConnection()', tab_index=tab_index)) - - def AddUserMediaLocalStream(self, tab_index): - self.assertEquals('ok-added', self.ExecuteJavascript( - 'addLocalStream()', tab_index=tab_index)) - - def AddWebAudioFile(self, tab_index, input_relative_path): - """The path must be relative to where the javascript is. - - This call just loads and adds a file to a peer connection, but it doesn't - start to play it until you call PlayWebAudioFile. - """ - self.assertEquals('ok-added', self.ExecuteJavascript( - 'addAudioFile("%s")' % re.escape(input_relative_path), - tab_index=tab_index)) - - def PlayWebAudioFile(self, tab_index): - """Plays a web audio file which was added earlier.""" - self.assertEquals('ok-playing', self.ExecuteJavascript( - 'playAudioFile()', tab_index=tab_index)) - - def EstablishCall(self, from_tab_with_index, to_tab_with_index): - self.WaitUntilPeerConnects(tab_index=from_tab_with_index) - - self.assertEquals('ok-negotiating', self.ExecuteJavascript( - 'negotiateCall()', tab_index=from_tab_with_index)) - self.AssertNoFailures(from_tab_with_index) - - self.WaitUntilReadyState(ready_state='active', - tab_index=from_tab_with_index) - - # Double-check the call reached the other side. - self.WaitUntilReadyState(ready_state='active', - tab_index=to_tab_with_index) - - def HangUp(self, from_tab_with_index): - self.assertEquals('ok-call-hung-up', self.ExecuteJavascript( - 'hangUp()', tab_index=from_tab_with_index)) - self.WaitUntilHangUpVerified(tab_index=from_tab_with_index) - self.AssertNoFailures(tab_index=from_tab_with_index) - - def WaitUntilPeerConnects(self, tab_index): - peer_connected = self.WaitUntil( - function=lambda: self.ExecuteJavascript('remotePeerIsConnected()', - tab_index=tab_index), - expect_retval='peer-connected') - self.assertTrue(peer_connected, - msg='Timed out while waiting for peer to connect.') - - def WaitUntilReadyState(self, ready_state, tab_index): - got_ready_state = self.WaitUntil( - function=lambda: self.ExecuteJavascript('getPeerConnectionReadyState()', - tab_index=tab_index), - expect_retval=ready_state) - self.assertTrue(got_ready_state, - msg=('Timed out while waiting for peer connection ready ' - 'state to change to %s for tab %d.' % (ready_state, - tab_index))) - - def WaitUntilHangUpVerified(self, tab_index): - self.WaitUntilReadyState('no-peer-connection', tab_index=tab_index) - - def Disconnect(self, tab_index): - self.assertEquals('ok-disconnected', self.ExecuteJavascript( - 'disconnect()', tab_index=tab_index)) - - def BinPathForPlatform(self, path): - """Form a platform specific path to a binary. - - Args: - path(string): The path to the binary without an extension. - Return: - (string): The platform-specific bin path. - """ - if self.IsWin(): - path += '.exe' - return path - - def PlatformIsWinXP(self): - """Check if the executing platform is Windows XP. - - Return: - True if the platform is Windows XP. - """ - return platform.system() == 'Windows' and platform.release() == 'XP' - - def StartPeerConnectionServer(self): - """Starts peerconnection_server. - - Peerconnection_server is a custom binary allowing two WebRTC clients to find - each other. For more details, see the source code which is available at the - site http://code.google.com/p/libjingle/source/browse/ (make sure to browse - to trunk/talk/examples/peerconnection/server). - """ - # Start the peerconnection_server. It should be next to chrome. - binary_path = os.path.join(self.BrowserPath(), 'peerconnection_server') - binary_path = self.BinPathForPlatform(binary_path) - - if not os.path.exists(binary_path): - raise MissingRequiredBinaryException( - 'Could not locate peerconnection_server. Have you built the ' - 'peerconnection_server target? We expect to have a ' - 'peerconnection_server binary next to the chrome binary.') - - self._server_process = subprocess.Popen(binary_path) - - def StopPeerConnectionServer(self): - """Stops the peerconnection_server.""" - assert self._server_process - self._server_process.kill() diff --git a/chrome/test/functional/webrtc_video_quality.py b/chrome/test/functional/webrtc_video_quality.py deleted file mode 100755 index 2b20f42ccd..0000000000 --- a/chrome/test/functional/webrtc_video_quality.py +++ /dev/null @@ -1,401 +0,0 @@ -#!/usr/bin/env python -# Copyright (c) 2012 The Chromium Authors. All rights reserved. -# Use of this source code is governed by a BSD-style license that can be -# found in the LICENSE file. - -import os -import subprocess -import sys - -import pyauto_functional -import pyauto -import pyauto_paths -import pyauto_utils -import webrtc_test_base - -# If you change the port number, don't forget to modify video_extraction.js too. -_PYWEBSOCKET_PORT_NUMBER = '12221' - -_HOME_ENV_NAME = 'HOMEPATH' if pyauto.PyUITest.IsWin() else 'HOME' -_WORKING_DIR = os.path.join(os.environ[_HOME_ENV_NAME], 'webrtc_video_quality') - -# This is the reference file that is being played by the virtual web camera. -_REFERENCE_YUV_FILE = os.path.join(_WORKING_DIR, 'reference_video.yuv') - -# The YUV file is the file produced by rgba_to_i420_converter. -_OUTPUT_YUV_FILE = os.path.join(_WORKING_DIR, 'captured_video.yuv') - - -class MissingRequiredToolException(Exception): - pass - - -class FailedToRunToolException(Exception): - pass - - -class WebrtcVideoQualityTest(webrtc_test_base.WebrtcTestBase): - """Test the video quality of the WebRTC output. - - Prerequisites: This test case must run on a machine with a virtual webcam that - plays video from the reference file located in the location defined by - _REFERENCE_YUV_FILE. You must also compile the chromium_builder_webrtc target - before you run this test to get all the tools built. - The external compare_videos.py script also depends on two external executables - which must be located in the PATH when running this test. - * zxing (see the CPP version at https://code.google.com/p/zxing) - * ffmpeg 0.11.1 or compatible version (see http://www.ffmpeg.org) - - The test case will launch a custom binary (peerconnection_server) which will - allow two WebRTC clients to find each other. - - The test also runs several other custom binaries - rgba_to_i420 converter and - frame_analyzer. Both tools can be found under third_party/webrtc/tools. The - test also runs a stand alone Python implementation of a WebSocket server - (pywebsocket) and a barcode_decoder script. - """ - - def setUp(self): - pyauto.PyUITest.setUp(self) - if not os.path.exists(_WORKING_DIR): - self.fail('Cannot find the working directory for the reference video and ' - 'the temporary files: %s' % _WORKING_DIR) - if not os.path.exists(_REFERENCE_YUV_FILE): - self.fail('Cannot find the reference file to be used for video quality ' - 'comparison: %s' % _REFERENCE_YUV_FILE) - self.StartPeerConnectionServer() - - def tearDown(self): - self._StopPywebsocketServer() - self.StopPeerConnectionServer() - - pyauto.PyUITest.tearDown(self) - self.assertEquals('', self.CheckErrorsAndCrashes()) - - def _WebRtcCallWithHelperPage(self, test_page, helper_page): - - """Tests we can call, let run for some time and hang up with WebRTC. - - This test exercises pretty much the whole happy-case for the WebRTC - JavaScript API. Currently, it exercises a normal call setup using the API - defined at http://dev.w3.org/2011/webrtc/editor/webrtc.html. The API is - still evolving. - - The test will load the supplied HTML file, which in turn will load different - javascript files depending on which version of the signaling protocol - we are running. - The supplied HTML files will be loaded in two tabs and tell the web - pages to start up WebRTC, which will acquire video and audio devices on the - system. This will launch a dialog in Chrome which we click past using the - automation controller. Then, we will order both tabs to connect the server, - which will make the two tabs aware of each other. Once that is done we order - one tab to call the other. - - We make sure that the javascript tells us that the call succeeded, lets it - run for some time and try to hang up the call after that. While the call is - running, we capture frames with the help of the functions in the - video_extraction.js file. - - Args: - test_page(string): The name of the test HTML page. It is looked for in the - webrtc directory under chrome/test/data. - helper_page(string): The name of the helper HTML page. It is looked for in - the same directory as the test_page. - """ - assert helper_page - url = self.GetFileURLForDataPath('webrtc', test_page) - helper_url = self.GetFileURLForDataPath('webrtc', helper_page) - - # Start the helper page in the first tab - self.NavigateToURL(helper_url) - - # Start the test page in the second page. - self.AppendTab(pyauto.GURL(url)) - - self.assertEquals('ok-got-stream', self.GetUserMedia(tab_index=0)) - self.assertEquals('ok-got-stream', self.GetUserMedia(tab_index=1)) - self.Connect('user_1', tab_index=0) - self.Connect('user_2', tab_index=1) - - self.CreatePeerConnection(tab_index=0) - self.AddUserMediaLocalStream(tab_index=0) - self.EstablishCall(from_tab_with_index=0, to_tab_with_index=1) - - # Wait for JavaScript to capture all the frames. In the HTML file we specify - # how many seconds to capture frames. - done_capturing = self.WaitUntil( - function=lambda: self.ExecuteJavascript('doneFrameCapturing()', - tab_index=1), - expect_retval='done-capturing', retry_sleep=1.0, - # TODO(phoglund): Temporary fix; remove after 2013-04-01 - timeout=90) - - self.assertTrue(done_capturing, - msg='Timed out while waiting frames to be captured.') - - # The hang-up will automatically propagate to the second tab. - self.HangUp(from_tab_with_index=0) - self.WaitUntilHangUpVerified(tab_index=1) - - self.Disconnect(tab_index=0) - self.Disconnect(tab_index=1) - - # Ensure we didn't miss any errors. - self.AssertNoFailures(tab_index=0) - self.AssertNoFailures(tab_index=1) - - def testVgaVideoQuality(self): - """Tests the WebRTC video output for a VGA video input. - - On the bots we will be running fake webcam driver and we will feed a video - with overlaid barcodes. In order to run the analysis on the output, we need - to use the original input video as a reference video. - """ - helper_page = webrtc_test_base.WebrtcTestBase.DEFAULT_TEST_PAGE - self._StartVideoQualityTest(test_page='webrtc_video_quality_test.html', - helper_page=helper_page, - reference_yuv=_REFERENCE_YUV_FILE, width=640, - height=480) - - def _StartVideoQualityTest(self, reference_yuv, - test_page='webrtc_video_quality_test.html', - helper_page='webrtc_jsep01_test.html', - width=640, height=480): - """Captures video output into a canvas and sends it to a server. - - This test captures the output frames of a WebRTC connection to a canvas and - later sends them over WebSocket to a WebSocket server implemented in Python. - At the server side we can store the frames for subsequent quality analysis. - - After the frames are sent to the pywebsocket server, we run the RGBA to I420 - converter, the barcode decoder and finally the frame analyzer. We also print - everything to the Perf Graph for visualization - - Args: - reference_yuv(string): The name of the reference YUV video that will be - used in the analysis. - test_page(string): The name of the test HTML page. To be looked for in the - webrtc directory under chrome/test/data. - helper_page(string): The name of the HTML helper page. To be looked for in - the same directory as the test_page. - width(int): The width of the test video frames. - height(int): The height of the test video frames. - """ - self._StartPywebsocketServer() - - self._WebRtcCallWithHelperPage(test_page, helper_page) - - # Wait for JavaScript to send all the frames to the server. The test will - # have quite a lot of frames to send, so it will take at least several - # seconds. - no_more_frames = self.WaitUntil( - function=lambda: self.ExecuteJavascript('haveMoreFramesToSend()', - tab_index=1), - expect_retval='no-more-frames', retry_sleep=1, timeout=150) - self.assertTrue(no_more_frames, - msg='Timed out while waiting for frames to send.') - - self.assertTrue(self._RunRGBAToI420Converter(width, height)) - - stats_file = os.path.join(_WORKING_DIR, 'pyauto_stats.txt') - analysis_result = self._CompareVideos(width, height, _OUTPUT_YUV_FILE, - reference_yuv, stats_file) - self._ProcessPsnrAndSsimOutput(analysis_result) - self._ProcessFramesCountOutput(analysis_result) - - def _StartPywebsocketServer(self): - """Starts the pywebsocket server.""" - print 'Starting pywebsocket server.' - - # Pywebsocket source directory. - path_pyws_dir = os.path.join(pyauto_paths.GetThirdPartyDir(), 'pywebsocket', - 'src') - - # Pywebsocket standalone server. - path_to_pywebsocket= os.path.join(path_pyws_dir, 'mod_pywebsocket', - 'standalone.py') - - # Path to the data handler to handle data received by the server. - path_to_handler = os.path.join(pyauto_paths.GetSourceDir(), 'chrome', - 'test', 'functional') - - # The python interpreter binary. - python_interp = sys.executable - - # The pywebsocket start command - we could add --log-level=debug for debug. - # -p stands for port, -d stands for root_directory (where the data handlers - # are). - start_cmd = [python_interp, path_to_pywebsocket, - '-p', _PYWEBSOCKET_PORT_NUMBER, - '-d', path_to_handler,] - env = os.environ - # Set PYTHONPATH to include the pywebsocket base directory. - env['PYTHONPATH'] = (path_pyws_dir + os.path.pathsep + - env.get('PYTHONPATH', '')) - - # Start the pywebsocket server. The server will not start instantly, so the - # code opening websockets to it should take this into account. - self._pywebsocket_server = subprocess.Popen(start_cmd, env=env) - - def _StopPywebsocketServer(self): - """Stops the running instance of pywebsocket server.""" - print 'Stopping pywebsocket server.' - if self._pywebsocket_server: - self._pywebsocket_server.kill() - - def _RunRGBAToI420Converter(self, width, height): - """Runs the RGBA to I420 converter. - - The rgba_to_i420_converter is part of the webrtc_test_tools target which - should be build prior to running this test. The resulting binary should live - next to Chrome. - - Args: - width(int): The width of the frames to be converted and stitched together. - height(int): The height of the frames to be converted and stitched. - - Returns: - (bool): True if the conversion is successful, false otherwise. - """ - path_to_rgba_converter = os.path.join(self.BrowserPath(), - 'rgba_to_i420_converter') - path_to_rgba_converter = os.path.abspath(path_to_rgba_converter) - path_to_rgba_converter = self.BinPathForPlatform(path_to_rgba_converter) - - if not os.path.exists(path_to_rgba_converter): - raise webrtc_test_base.MissingRequiredBinaryException( - 'Could not locate rgba_to_i420_converter! Did you build the ' - 'webrtc_test_tools target?') - - # We produce an output file that will later be used as an input to the - # barcode decoder and frame analyzer tools. - start_cmd = [path_to_rgba_converter, '--frames_dir=%s' % _WORKING_DIR, - '--output_file=%s' % _OUTPUT_YUV_FILE, '--width=%d' % width, - '--height=%d' % height, '--delete_frames'] - print 'Start command: ', ' '.join(start_cmd) - rgba_converter = subprocess.Popen(start_cmd, stdout=sys.stdout, - stderr=sys.stderr) - rgba_converter.wait() - return rgba_converter.returncode == 0 - - def _CompareVideos(self, width, height, captured_video_filename, - reference_video_filename, stats_filename): - """Compares the captured video with the reference video. - - The barcode decoder decodes the captured video containing barcodes overlaid - into every frame of the video (produced by rgba_to_i420_converter). It - produces a set of PNG images and a stats file that describes the relation - between the filenames and the (decoded) frame number of each frame. - - Args: - width(int): The frames width of the video to be decoded. - height(int): The frames height of the video to be decoded. - captured_video_filename(string): The captured video file we want to - extract frame images and decode frame numbers from. - reference_video_filename(string): The reference video file we want to - compare the captured video quality with. - stats_filename(string): Filename for the output file containing - data that shows the relation between each frame filename and the - reference file's frame numbers. - - Returns: - (string): The output of the script. - - Raises: - FailedToRunToolException: If the script fails to run. - """ - path_to_analyzer = os.path.join(self.BrowserPath(), 'frame_analyzer') - path_to_analyzer = os.path.abspath(path_to_analyzer) - path_to_analyzer = self.BinPathForPlatform(path_to_analyzer) - - path_to_compare_script = os.path.join(pyauto_paths.GetThirdPartyDir(), - 'webrtc', 'tools', - 'compare_videos.py') - if not os.path.exists(path_to_compare_script): - raise MissingRequiredToolException('Cannot find the script at %s' % - path_to_compare_script) - python_interp = sys.executable - cmd = [ - python_interp, - path_to_compare_script, - '--ref_video=%s' % reference_video_filename, - '--test_video=%s' % captured_video_filename, - '--frame_analyzer=%s' % path_to_analyzer, - '--yuv_frame_width=%d' % width, - '--yuv_frame_height=%d' % height, - '--stats_file=%s' % stats_filename, - ] - print 'Start command: ', ' '.join(cmd) - - compare_videos = subprocess.Popen(cmd, stdout=subprocess.PIPE, - stderr=subprocess.PIPE) - output, error = compare_videos.communicate() - if compare_videos.returncode != 0: - raise FailedToRunToolException('Failed to run compare videos script!') - - return output - - def _ProcessFramesCountOutput(self, output): - """Processes the analyzer output for the different frame counts. - - The frame analyzer outputs additional information about the number of unique - frames captured, The max number of repeated frames in a sequence and the - max number of skipped frames. These values are then written to the Perf - Graph. (Note: Some of the repeated or skipped frames will probably be due to - the imperfection of JavaScript timers.) - - Args: - output(string): The output from the frame analyzer to be processed. - """ - # The output from frame analyzer will be in the format: - # <PSNR and SSIM stats> - # Unique_frames_count:<value> - # Max_repeated:<value> - # Max_skipped:<value> - unique_fr_pos = output.rfind('Unique_frames_count') - result_str = output[unique_fr_pos:] - - result_list = result_str.split() - - for result in result_list: - colon_pos = result.find(':') - key = result[:colon_pos] - value = result[colon_pos+1:] - pyauto_utils.PrintPerfResult(key, 'VGA', value, '') - - def _ProcessPsnrAndSsimOutput(self, output): - """Processes the analyzer output to extract the PSNR and SSIM values. - - The frame analyzer produces PSNR and SSIM results for every unique frame - that has been captured. This method forms a list of all the psnr and ssim - values and passes it to PrintPerfResult() for printing on the Perf Graph. - - Args: - output(string): The output from the frame analyzer to be processed. - """ - # The output is in the format: - # BSTATS - # psnr ssim; psnr ssim; ... psnr ssim; - # ESTATS - stats_beginning = output.find('BSTATS') # Get the beginning of the stats - stats_ending = output.find('ESTATS') # Get the end of the stats - stats_str = output[(stats_beginning + len('BSTATS')):stats_ending] - - stats_list = stats_str.split(';') - - psnr = [] - ssim = [] - - for item in stats_list: - item = item.strip() - if item != '': - entry = item.split(' ') - psnr.append(float(entry[0])) - ssim.append(float(entry[1])) - - pyauto_utils.PrintPerfResult('PSNR', 'VGA', psnr, '') - pyauto_utils.PrintPerfResult('SSIM', 'VGA', ssim, '') - - -if __name__ == '__main__': - pyauto_functional.Main() diff --git a/chrome/test/mini_installer/config/chrome_installed.prop b/chrome/test/mini_installer/config/chrome_installed.prop index 9db2a192c9..1b95385a76 100644 --- a/chrome/test/mini_installer/config/chrome_installed.prop +++ b/chrome/test/mini_installer/config/chrome_installed.prop @@ -1,4 +1,13 @@ { + "Files": { + "$LOCAL_APPDATA\\Google\\Chrome\\Application\\chrome.exe": {"exists": true}, + "$LOCAL_APPDATA\\Google\\Chrome\\Application\\$MINI_INSTALLER_FILE_VERSION\\chrome.dll": + {"exists": true}, + "$LOCAL_APPDATA\\Google\\Chrome\\Application\\$MINI_INSTALLER_FILE_VERSION\\Installer\\chrome.7z": + {"exists": true}, + "$LOCAL_APPDATA\\Google\\Chrome\\Application\\$MINI_INSTALLER_FILE_VERSION\\Installer\\setup.exe": + {"exists": true} + }, "RegistryEntries": { "HKEY_CURRENT_USER\\Software\\Google\\Update\\Clients\\{8A69D345-D564-463c-AFF1-A69D9E530F96}": {"exists": true} diff --git a/chrome/test/mini_installer/config/chrome_not_installed.prop b/chrome/test/mini_installer/config/chrome_not_installed.prop index a9f7685aff..dfa4ffd84b 100644 --- a/chrome/test/mini_installer/config/chrome_not_installed.prop +++ b/chrome/test/mini_installer/config/chrome_not_installed.prop @@ -1,4 +1,7 @@ { + "Files": { + "$LOCAL_APPDATA\\Google\\Chrome\\Application": {"exists": false} + }, "RegistryEntries": { "HKEY_CURRENT_USER\\Software\\Google\\Update\\Clients\\{8A69D345-D564-463c-AFF1-A69D9E530F96}": {"exists": false} diff --git a/chrome/test/mini_installer/config/config.config b/chrome/test/mini_installer/config/config.config index ab7093801c..94725f6d59 100644 --- a/chrome/test/mini_installer/config/config.config +++ b/chrome/test/mini_installer/config/config.config @@ -4,7 +4,8 @@ ["chrome_installed", ["chrome_installed.prop"]] ], "actions": [ - ["install chrome", "mini_installer.exe --chrome --multi-install"], + ["install chrome", + "\"$MINI_INSTALLER\" --chrome --multi-install --do-not-launch-chrome"], ["uninstall chrome", "python uninstall_chrome.py"] ], "tests": [ diff --git a/chrome/test/mini_installer/file_verifier.py b/chrome/test/mini_installer/file_verifier.py new file mode 100644 index 0000000000..045cc4c96c --- /dev/null +++ b/chrome/test/mini_installer/file_verifier.py @@ -0,0 +1,26 @@ +# Copyright 2013 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +import os + +import path_resolver + + +def VerifyFiles(files): + """Verifies that the current files match the expectation dictionaries. + + This method will throw an AssertionError if file state doesn't match the + provided expectation. + + Args: + files: A dictionary whose keys are file paths and values are expectation + dictionaries. An expectation dictionary is a dictionary with the + following key and value: + 'exists' a boolean indicating whether the file should exist. + """ + for file_path, expectation in files.iteritems(): + file_exists = os.path.exists(path_resolver.ResolvePath(file_path)) + assert expectation['exists'] == file_exists, \ + ('File %s exists' % file_path) if file_exists else \ + ('File %s is missing' % file_path) diff --git a/chrome/test/mini_installer/path_resolver.py b/chrome/test/mini_installer/path_resolver.py new file mode 100644 index 0000000000..570a584c11 --- /dev/null +++ b/chrome/test/mini_installer/path_resolver.py @@ -0,0 +1,45 @@ +# Copyright 2013 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +import os +import string +import win32api +import win32com.client +from win32com.shell import shell, shellcon + + +def ResolvePath(path): + """Resolve variables in a file path, and return the resolved path. + + This method resolves only variables defined below. It does not resolve + environment variables. Any dollar signs that are not part of variables must be + escaped with $$, otherwise a KeyError or a ValueError will be raised. + + Args: + path: An absolute or relative path. + + Returns: + A new path created by replacing + * $PROGRAM_FILES with the path to the Program Files folder, + * $LOCAL_APPDATA with the path to the Local Application Data folder, + * $MINI_INSTALLER with the path to the mini_installer, and + * $MINI_INSTALLER_FILE_VERSION with the file version of the + mini_installer. + """ + program_files_path = shell.SHGetFolderPath(0, shellcon.CSIDL_PROGRAM_FILES, + None, 0) + local_appdata_path = shell.SHGetFolderPath(0, shellcon.CSIDL_LOCAL_APPDATA, + None, 0) + #TODO(sukolsak): Copy the mini_installer.exe from the build output into here. + mini_installer_path = os.path.abspath('mini_installer.exe') + mini_installer_file_version = win32com.client.Dispatch( + 'Scripting.FileSystemObject').GetFileVersion(mini_installer_path) + + variable_mapping = { + 'PROGRAM_FILES': program_files_path, + 'LOCAL_APPDATA': local_appdata_path, + 'MINI_INSTALLER': mini_installer_path, + 'MINI_INSTALLER_FILE_VERSION': mini_installer_file_version, + } + return string.Template(path).substitute(variable_mapping) diff --git a/chrome/test/mini_installer/test_installer.py b/chrome/test/mini_installer/test_installer.py index a98347872f..8f5d9af182 100644 --- a/chrome/test/mini_installer/test_installer.py +++ b/chrome/test/mini_installer/test_installer.py @@ -15,6 +15,7 @@ import os import subprocess import unittest +import path_resolver import verifier @@ -102,7 +103,11 @@ class InstallerTest(unittest.TestCase): raise AssertionError("In state '%s', %s" % (state, e)) def _RunCommand(self, command): - subprocess.call(command, shell=True) + exit_status = subprocess.call(path_resolver.ResolvePath(command), + shell=True) + if exit_status != 0: + self.fail('Command %s returned non-zero exit status %s' % (command, + exit_status)) def MergePropertyDictionaries(current_property, new_property): diff --git a/chrome/test/mini_installer/uninstall_chrome.py b/chrome/test/mini_installer/uninstall_chrome.py index fe2e8708eb..3680940870 100644 --- a/chrome/test/mini_installer/uninstall_chrome.py +++ b/chrome/test/mini_installer/uninstall_chrome.py @@ -2,9 +2,42 @@ # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. +"""Uninstall Chrome. + +This script reads the uninstall command from registry, calls it, and verifies +the output status code. +""" + +import _winreg +import argparse import subprocess +import sys + + +def main(): + parser = argparse.ArgumentParser(description='Uninstall Chrome.') + parser.add_argument('--system-level', dest='system_level', + action='store_const', const=True, default=False, + help='Uninstall Chrome at system level.') + args = parser.parse_args() + + # TODO(sukolsak): Add support for uninstalling MSI-based Chrome installs when + # we support testing MSIs. + if args.system_level: + root_key = _winreg.HKEY_LOCAL_MACHINE + else: + root_key = _winreg.HKEY_CURRENT_USER + sub_key = ('SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\' + 'Google Chrome') + key = _winreg.OpenKey(root_key, sub_key, 0, _winreg.KEY_QUERY_VALUE) + uninstall_string, _ = _winreg.QueryValueEx(key, 'UninstallString') + exit_status = subprocess.call(uninstall_string, shell=True) + # The exit status for successful uninstallation of Chrome is 19 (see + # chrome/installer/util/util_constants.h). + if exit_status != 19: + raise Exception('Could not uninstall Chrome. The installer exited with ' + 'status %d.' % exit_status) + return 0 -# TODO(sukolsak): This should read the uninstall command from the registry and -# run that instead. -subprocess.call('mini_installer.exe --uninstall --multi-install --chrome', - shell=True) +if __name__ == '__main__': + sys.exit(main()) diff --git a/chrome/test/mini_installer/verifier.py b/chrome/test/mini_installer/verifier.py index 8a735ba19c..1436040e36 100644 --- a/chrome/test/mini_installer/verifier.py +++ b/chrome/test/mini_installer/verifier.py @@ -2,6 +2,7 @@ # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. +import file_verifier import registry_verifier @@ -16,7 +17,9 @@ def Verify(property): property: A property dictionary. """ for verifier_name, value in property.iteritems(): - if verifier_name == 'RegistryEntries': + if verifier_name == 'Files': + file_verifier.VerifyFiles(value) + elif verifier_name == 'RegistryEntries': registry_verifier.VerifyRegistryEntries(value) else: # TODO(sukolsak): Implement other verifiers diff --git a/chrome/test/nacl/nacl_browsertest.cc b/chrome/test/nacl/nacl_browsertest.cc index e16aae4fa9..2cad78ab96 100644 --- a/chrome/test/nacl/nacl_browsertest.cc +++ b/chrome/test/nacl/nacl_browsertest.cc @@ -15,40 +15,13 @@ namespace { -// These tests fail on Linux ASAN bots: <http://crbug.com/161709>. -#if defined(OS_LINUX) && defined(ADDRESS_SANITIZER) -#define MAYBE_SimpleLoad DISABLED_SimpleLoad -#define MAYBE_ExitStatus0 DISABLED_ExitStatus0 -#define MAYBE_ExitStatus254 DISABLED_ExitStatus254 -#define MAYBE_ExitStatusNeg2 DISABLED_ExitStatusNeg2 -#define MAYBE_PPAPICore DISABLED_PPAPICore -#define MAYBE_ProgressEvents DISABLED_ProgressEvents -#define MAYBE_PnaclMimeType DISABLED_PnaclMimeType -#define MAYBE_CrossOriginCORS DISABLED_CrossOriginCORS -#define MAYBE_CrossOriginFail DISABLED_CrossOriginFail -#define MAYBE_SameOriginCookie DISABLED_SameOriginCookie -#define MAYBE_CORSNoCookie DISABLED_CORSNoCookie -#define MAYBE_SysconfNprocessorsOnln DISABLED_SysconfNprocessorsOnln -#else -#define MAYBE_SimpleLoad SimpleLoad -#define MAYBE_ExitStatus0 ExitStatus0 -#define MAYBE_ExitStatus254 ExitStatus254 -#define MAYBE_ExitStatusNeg2 ExitStatusNeg2 -#define MAYBE_PPAPICore PPAPICore -#define MAYBE_ProgressEvents ProgressEvents -#define MAYBE_PnaclMimeType PnaclMimeType -#define MAYBE_CrossOriginCORS CrossOriginCORS -#define MAYBE_CrossOriginFail CrossOriginFail -#define MAYBE_SameOriginCookie SameOriginCookie -#define MAYBE_CORSNoCookie CORSNoCookie -# if defined(OS_WIN) +#if defined(OS_WIN) # define MAYBE_SysconfNprocessorsOnln DISABLED_SysconfNprocessorsOnln -# else +#else # define MAYBE_SysconfNprocessorsOnln SysconfNprocessorsOnln -# endif #endif -NACL_BROWSER_TEST_F(NaClBrowserTest, MAYBE_SimpleLoad, { +NACL_BROWSER_TEST_F(NaClBrowserTest, SimpleLoad, { RunLoadTest(FILE_PATH_LITERAL("nacl_load_test.html")); }) @@ -62,33 +35,29 @@ IN_PROC_BROWSER_TEST_F(NaClBrowserTestPnaclWithOldCache, RunNaClIntegrationTest(FILE_PATH_LITERAL("pnacl_error_handling.html")); } -NACL_BROWSER_TEST_F(NaClBrowserTest, MAYBE_ExitStatus0, { +NACL_BROWSER_TEST_F(NaClBrowserTest, ExitStatus0, { RunNaClIntegrationTest(FILE_PATH_LITERAL( "pm_exit_status_test.html?trigger=exit0&expected_exit=0")); }) -NACL_BROWSER_TEST_F(NaClBrowserTest, MAYBE_ExitStatus254, { +NACL_BROWSER_TEST_F(NaClBrowserTest, ExitStatus254, { RunNaClIntegrationTest(FILE_PATH_LITERAL( "pm_exit_status_test.html?trigger=exit254&expected_exit=254")); }) -NACL_BROWSER_TEST_F(NaClBrowserTest, MAYBE_ExitStatusNeg2, { +NACL_BROWSER_TEST_F(NaClBrowserTest, ExitStatusNeg2, { RunNaClIntegrationTest(FILE_PATH_LITERAL( "pm_exit_status_test.html?trigger=exitneg2&expected_exit=254")); }) -NACL_BROWSER_TEST_F(NaClBrowserTest, MAYBE_PPAPICore, { +NACL_BROWSER_TEST_F(NaClBrowserTest, PPAPICore, { RunNaClIntegrationTest(FILE_PATH_LITERAL("ppapi_ppb_core.html")); }) -NACL_BROWSER_TEST_F(NaClBrowserTest, MAYBE_ProgressEvents, { +NACL_BROWSER_TEST_F(NaClBrowserTest, ProgressEvents, { RunNaClIntegrationTest(FILE_PATH_LITERAL("ppapi_progress_events.html")); }) -NACL_BROWSER_TEST_F(NaClBrowserTest, MAYBE_PnaclMimeType, { - RunLoadTest(FILE_PATH_LITERAL("pnacl_mime_type.html")); -}) - // Some versions of Visual Studio does not like preprocessor // conditionals inside the argument of a macro, so we put the // conditionals on a helper function. We are already in an anonymous @@ -144,19 +113,19 @@ NACL_BROWSER_TEST_F(NaClBrowserTest, MAYBE_SysconfNprocessorsOnln, { RunNaClIntegrationTest(path); }) -IN_PROC_BROWSER_TEST_F(NaClBrowserTestStatic, MAYBE_CrossOriginCORS) { +IN_PROC_BROWSER_TEST_F(NaClBrowserTestStatic, CrossOriginCORS) { RunLoadTest(FILE_PATH_LITERAL("cross_origin/cors.html")); } -IN_PROC_BROWSER_TEST_F(NaClBrowserTestStatic, MAYBE_CrossOriginFail) { +IN_PROC_BROWSER_TEST_F(NaClBrowserTestStatic, CrossOriginFail) { RunLoadTest(FILE_PATH_LITERAL("cross_origin/fail.html")); } -IN_PROC_BROWSER_TEST_F(NaClBrowserTestStatic, MAYBE_SameOriginCookie) { +IN_PROC_BROWSER_TEST_F(NaClBrowserTestStatic, SameOriginCookie) { RunLoadTest(FILE_PATH_LITERAL("cross_origin/same_origin_cookie.html")); } -IN_PROC_BROWSER_TEST_F(NaClBrowserTestStatic, MAYBE_CORSNoCookie) { +IN_PROC_BROWSER_TEST_F(NaClBrowserTestStatic, CORSNoCookie) { RunLoadTest(FILE_PATH_LITERAL("cross_origin/cors_no_cookie.html")); } @@ -192,4 +161,12 @@ IN_PROC_BROWSER_TEST_F(NaClBrowserTestPnacl, "pnacl_exception_handling_disabled.html")); } +IN_PROC_BROWSER_TEST_F(NaClBrowserTestPnacl, PnaclMimeType) { + RunLoadTest(FILE_PATH_LITERAL("pnacl_mime_type.html")); +} + +IN_PROC_BROWSER_TEST_F(NaClBrowserTestPnaclDisabled, PnaclMimeType) { + RunLoadTest(FILE_PATH_LITERAL("pnacl_mime_type.html")); +} + } // namespace diff --git a/chrome/test/nacl/nacl_browsertest_uma.cc b/chrome/test/nacl/nacl_browsertest_uma.cc index eaabd8a150..a76776cc98 100644 --- a/chrome/test/nacl/nacl_browsertest_uma.cc +++ b/chrome/test/nacl/nacl_browsertest_uma.cc @@ -34,7 +34,7 @@ NACL_BROWSER_TEST_F(NaClBrowserTest, MAYBE_SuccessfulLoadUMA, { LOAD_OK, 1); // Make sure we have other important histograms. - if (!IsPnacl()) { + if (!IsAPnaclTest()) { histograms.ExpectTotalCount("NaCl.Perf.StartupTime.LoadModule", 1); histograms.ExpectTotalCount("NaCl.Perf.StartupTime.Total", 1); histograms.ExpectTotalCount("NaCl.Perf.Size.Manifest", 1); diff --git a/chrome/test/nacl/nacl_browsertest_util.cc b/chrome/test/nacl/nacl_browsertest_util.cc index 1c171a75e4..ace7305d6a 100644 --- a/chrome/test/nacl/nacl_browsertest_util.cc +++ b/chrome/test/nacl/nacl_browsertest_util.cc @@ -187,6 +187,15 @@ static void AddPnaclParm(const base::FilePath::StringType& url, } } +static void AddPnaclDisabledParm(const base::FilePath::StringType& url, + base::FilePath::StringType* url_with_parm) { + if (url.find(FILE_PATH_LITERAL("?")) == base::FilePath::StringType::npos) { + *url_with_parm = url + FILE_PATH_LITERAL("?pnacl_disabled=1"); + } else { + *url_with_parm = url + FILE_PATH_LITERAL("&pnacl_disabled=1"); + } +} + NaClBrowserTestBase::NaClBrowserTestBase() { } @@ -210,7 +219,11 @@ bool NaClBrowserTestBase::GetDocumentRoot(base::FilePath* document_root) { return GetNaClVariantRoot(Variant(), document_root); } -bool NaClBrowserTestBase::IsPnacl() { +bool NaClBrowserTestBase::IsAPnaclTest() { + return false; +} + +bool NaClBrowserTestBase::IsPnaclDisabled() { return false; } @@ -233,11 +246,15 @@ bool NaClBrowserTestBase::RunJavascriptTest(const GURL& url, void NaClBrowserTestBase::RunLoadTest( const base::FilePath::StringType& test_file) { LoadTestMessageHandler handler; - base::FilePath::StringType test_file_with_parm = test_file; - if (IsPnacl()) { - AddPnaclParm(test_file, &test_file_with_parm); + base::FilePath::StringType test_file_with_pnacl = test_file; + if (IsAPnaclTest()) { + AddPnaclParm(test_file, &test_file_with_pnacl); + } + base::FilePath::StringType test_file_with_both = test_file_with_pnacl; + if (IsPnaclDisabled()) { + AddPnaclDisabledParm(test_file_with_pnacl, &test_file_with_both); } - bool ok = RunJavascriptTest(TestURL(test_file_with_parm), &handler); + bool ok = RunJavascriptTest(TestURL(test_file_with_both), &handler); ASSERT_TRUE(ok) << handler.error_message(); ASSERT_TRUE(handler.test_passed()) << "Test failed."; } @@ -245,11 +262,15 @@ void NaClBrowserTestBase::RunLoadTest( void NaClBrowserTestBase::RunNaClIntegrationTest( const base::FilePath::StringType& url_fragment) { NaClIntegrationMessageHandler handler; - base::FilePath::StringType url_fragment_with_parm = url_fragment; - if (IsPnacl()) { - AddPnaclParm(url_fragment, &url_fragment_with_parm); + base::FilePath::StringType url_fragment_with_pnacl = url_fragment; + if (IsAPnaclTest()) { + AddPnaclParm(url_fragment, &url_fragment_with_pnacl); } - bool ok = RunJavascriptTest(TestURL(url_fragment_with_parm), &handler); + base::FilePath::StringType url_fragment_with_both = url_fragment_with_pnacl; + if (IsPnaclDisabled()) { + AddPnaclDisabledParm(url_fragment_with_pnacl, &url_fragment_with_both); + } + bool ok = RunJavascriptTest(TestURL(url_fragment_with_both), &handler); ASSERT_TRUE(ok) << handler.error_message(); ASSERT_TRUE(handler.test_passed()) << "Test failed."; } @@ -278,13 +299,24 @@ base::FilePath::StringType NaClBrowserTestPnacl::Variant() { return FILE_PATH_LITERAL("pnacl"); } -bool NaClBrowserTestPnacl::IsPnacl() { +bool NaClBrowserTestPnacl::IsAPnaclTest() { return true; } -void NaClBrowserTestPnacl::SetUpCommandLine(CommandLine* command_line) { +base::FilePath::StringType NaClBrowserTestPnaclDisabled::Variant() { + return FILE_PATH_LITERAL("pnacl"); +} + +bool NaClBrowserTestPnaclDisabled::IsAPnaclTest() { + return true; +} + +bool NaClBrowserTestPnaclDisabled::IsPnaclDisabled() { + return true; +} +void NaClBrowserTestPnaclDisabled::SetUpCommandLine(CommandLine* command_line) { NaClBrowserTestBase::SetUpCommandLine(command_line); - command_line->AppendSwitch(switches::kEnablePnacl); + command_line->AppendSwitch(switches::kDisablePnacl); } NaClBrowserTestPnaclWithOldCache::NaClBrowserTestPnaclWithOldCache() { diff --git a/chrome/test/nacl/nacl_browsertest_util.h b/chrome/test/nacl/nacl_browsertest_util.h index e79b5657c3..27e638b02e 100644 --- a/chrome/test/nacl/nacl_browsertest_util.h +++ b/chrome/test/nacl/nacl_browsertest_util.h @@ -74,7 +74,9 @@ class NaClBrowserTestBase : public InProcessBrowserTest { // Where are the files for this class of test located on disk? virtual bool GetDocumentRoot(base::FilePath* document_root); - virtual bool IsPnacl(); + virtual bool IsAPnaclTest(); + + virtual bool IsPnaclDisabled(); // Map a file relative to the variant directory to a URL served by the test // web server. @@ -116,11 +118,22 @@ class NaClBrowserTestGLibc : public NaClBrowserTestBase { class NaClBrowserTestPnacl : public NaClBrowserTestBase { public: + virtual base::FilePath::StringType Variant() OVERRIDE; + + virtual bool IsAPnaclTest() OVERRIDE; +}; + +// Class used to test that when --disable-pnacl is specified the PNaCl mime +// type is not available. +class NaClBrowserTestPnaclDisabled : public NaClBrowserTestBase { + public: virtual void SetUpCommandLine(CommandLine* command_line) OVERRIDE; virtual base::FilePath::StringType Variant() OVERRIDE; - virtual bool IsPnacl() OVERRIDE; + virtual bool IsAPnaclTest() OVERRIDE; + + virtual bool IsPnaclDisabled() OVERRIDE; }; // Temporary class for running tests with the old cache enabled. Once all the @@ -137,12 +150,10 @@ class NaClBrowserTestStatic : public NaClBrowserTestBase { virtual bool GetDocumentRoot(base::FilePath* document_root) OVERRIDE; }; -// PNaCl's cache and PPB_FileIO currently trip up under ASAN: -// https://code.google.com/p/chromium/issues/detail?id=171810 // PNaCl tests take a long time on windows debug builds // and sometimes time out. Disable until it is made faster: // https://code.google.com/p/chromium/issues/detail?id=177555 -#if defined(ADDRESS_SANITIZER) || (defined(OS_WIN) && !defined(NDEBUG)) +#if (defined(OS_WIN) && !defined(NDEBUG)) #define MAYBE_PNACL(test_name) DISABLED_##test_name #else #define MAYBE_PNACL(test_name) test_name @@ -157,7 +168,7 @@ body #else -// Otherwise, we have Glibc, Newlib and PNaCl tests +// Otherwise, we have Glibc, Newlib and Pnacl tests #define NACL_BROWSER_TEST_F(suite, name, body) \ IN_PROC_BROWSER_TEST_F(suite##Newlib, name) \ body \ diff --git a/chrome/test/perf/rendering/throughput_tests.cc b/chrome/test/perf/rendering/throughput_tests.cc index 4aab2e766a..a21c6857ad 100644 --- a/chrome/test/perf/rendering/throughput_tests.cc +++ b/chrome/test/perf/rendering/throughput_tests.cc @@ -50,7 +50,8 @@ enum RunTestFlags { kNone = 0, kInternal = 1 << 0, // Test uses internal test data. kAllowExternalDNS = 1 << 1, // Test needs external DNS lookup. - kIsGpuCanvasTest = 1 << 2 // Test uses GPU accelerated canvas features. + kIsGpuCanvasTest = 1 << 2, // Test uses GPU accelerated canvas features. + kIsFlaky = 1 << 3 }; enum ThroughputTestFlags { @@ -369,7 +370,9 @@ class ThroughputTest : public BrowserPerfTest { frames = &events_sw; EXPECT_EQ(0u, events_gpu.size()); } - ASSERT_GT(frames->size(), 20u); + if (!(flags & kIsFlaky)) { + ASSERT_GT(frames->size(), 20u); + } // Cull a few leading and trailing events as they might be unreliable. TraceEventVector rate_events(frames->begin() + kIgnoreSomeFrames, frames->end() - kIgnoreSomeFrames); @@ -379,7 +382,7 @@ class ThroughputTest : public BrowserPerfTest { // Print perf results. double mean_ms = stats.mean_us / 1000.0; - double std_dev_ms = stats.standard_deviation_us / 1000.0 / 1000.0; + double std_dev_ms = stats.standard_deviation_us / 1000.0; std::string trace_name = use_compositor_thread_? "gpu_thread" : ran_on_gpu ? "gpu" : "software"; std::string mean_and_error = base::StringPrintf("%f,%f", mean_ms, @@ -536,11 +539,13 @@ IN_PROC_BROWSER_TEST_F(ThroughputTestSW, DrawImageShadowSW) { } IN_PROC_BROWSER_TEST_F(ThroughputTestGPU, DrawImageShadowGPU) { - RunTest("canvas2d_balls_with_shadow", kNone | kIsGpuCanvasTest); + // TODO(junov): Fix test flakiness crbug.com/272383 + RunTest("canvas2d_balls_with_shadow", kNone | kIsGpuCanvasTest | kIsFlaky); } IN_PROC_BROWSER_TEST_F(ThroughputTestThread, DrawImageShadowGPU) { - RunTest("canvas2d_balls_with_shadow", kNone | kIsGpuCanvasTest); + // TODO(junov): Fix test flakiness crbug.com/272383 + RunTest("canvas2d_balls_with_shadow", kNone | kIsGpuCanvasTest | kIsFlaky); } IN_PROC_BROWSER_TEST_F(ThroughputTestSW, CanvasToCanvasDrawSW) { diff --git a/chrome/test/ppapi/ppapi_browsertest.cc b/chrome/test/ppapi/ppapi_browsertest.cc index 0aef3d5aff..5952b08e8f 100644 --- a/chrome/test/ppapi/ppapi_browsertest.cc +++ b/chrome/test/ppapi/ppapi_browsertest.cc @@ -134,11 +134,6 @@ using content::RenderViewHost; // Interface tests. // -// Disable tests under ASAN. http://crbug.com/104832. -// This is a bit heavy handed, but the majority of these tests fail under ASAN. -// See bug for history. -#if !defined(ADDRESS_SANITIZER) - TEST_PPAPI_IN_PROCESS(Broker) // Flaky, http://crbug.com/111355 TEST_PPAPI_OUT_OF_PROCESS(DISABLED_Broker) @@ -477,7 +472,13 @@ IN_PROC_BROWSER_TEST_F(PPAPITest, URLLoader) { LIST_TEST(URLLoader_PrefetchBufferThreshold) ); } -IN_PROC_BROWSER_TEST_F(OutOfProcessPPAPITest, URLLoader) { +// Timing out on Windows dbg. http://crbug.com/95005 +#if defined(OS_WIN) && !defined(NDEBUG) +#define MAYBE_URLLoader DISABLED_URLLoader +#else +#define MAYBE_URLLoader URLLoader +#endif +IN_PROC_BROWSER_TEST_F(OutOfProcessPPAPITest, MAYBE_URLLoader) { RunTestViaHTTP( LIST_TEST(URLLoader_BasicGET) LIST_TEST(URLLoader_BasicPOST) @@ -1312,8 +1313,13 @@ IN_PROC_BROWSER_TEST_F(PPAPINaClPNaClTest, AudioConfig) { LIST_TEST(AudioConfig_InvalidConfigs)); } - -IN_PROC_BROWSER_TEST_F(PPAPITest, Audio) { +// Flaky on ChromeOS dbg, http://crbug.com/277564. +#if defined(OS_CHROMEOS) && !defined(NDEBUG) +#define MAYBE_Audio DISABLED_Audio +#else +#define MAYBE_Audio Audio +#endif +IN_PROC_BROWSER_TEST_F(PPAPITest, MAYBE_Audio) { RunTest(LIST_TEST(Audio_Creation) LIST_TEST(Audio_DestroyNoStop) LIST_TEST(Audio_Failures) @@ -1521,5 +1527,3 @@ IN_PROC_BROWSER_TEST_F(OutOfProcessPPAPITest, FlashDRM) { TEST_PPAPI_IN_PROCESS(TalkPrivate) TEST_PPAPI_OUT_OF_PROCESS(TalkPrivate) - -#endif // ADDRESS_SANITIZER diff --git a/chrome/test/ppapi/ppapi_test.cc b/chrome/test/ppapi/ppapi_test.cc index 28f3eff211..0456e1ecf0 100644 --- a/chrome/test/ppapi/ppapi_test.cc +++ b/chrome/test/ppapi/ppapi_test.cc @@ -328,9 +328,8 @@ void PPAPINaClTest::SetUpCommandLine(CommandLine* command_line) { EXPECT_TRUE(PathService::Get(chrome::FILE_NACL_PLUGIN, &plugin_lib)); EXPECT_TRUE(base::PathExists(plugin_lib)); - // Enable running NaCl outside of the store. + // Enable running (non-portable) NaCl outside of the Chrome web store. command_line->AppendSwitch(switches::kEnableNaCl); - command_line->AppendSwitch(switches::kEnablePnacl); command_line->AppendSwitchASCII(switches::kAllowNaClSocketAPI, "127.0.0.1"); command_line->AppendSwitch(switches::kUseFakeDeviceForMediaStream); command_line->AppendSwitch(switches::kUseFakeUIForMediaStream); @@ -365,9 +364,8 @@ void PPAPINaClTestDisallowedSockets::SetUpCommandLine( EXPECT_TRUE(PathService::Get(chrome::FILE_NACL_PLUGIN, &plugin_lib)); EXPECT_TRUE(base::PathExists(plugin_lib)); - // Enable running NaCl outside of the store. + // Enable running (non-portable) NaCl outside of the Chrome web store. command_line->AppendSwitch(switches::kEnableNaCl); - command_line->AppendSwitch(switches::kEnablePnacl); } // Append the correct mode and testcase string |